Construction d'arborescences d'expression personnalisées à l'aide d'opérateurs en C #

.net c# expression-trees operators

Question

Cette question concerne la construction d'arborescences d'expression personnalisées dans .NET à l'aide des opérateurs trouvés dans C # (ou tout autre langage). Je fournis la question avec quelques informations de base.


Pour mon assembleur géré en deux phases 64 bits, j'ai besoin de la prise en charge des expressions. Par exemple, on peut vouloir assembler:

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

L'expression 64-$+mystring ne doit pas être une chaîne, mais une expression valide avec les avantages de la vérification de la syntaxe et du type et d'IntelliSense dans VS, entre autres:

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

Cette expression n'est pas évaluée lors de sa construction. Au lieu de cela, il est évalué plus tard dans le contexte de mon assembleur (lorsqu'il détermine les décalages de symboles, etc.). Le framework .NET (depuis .NET 3.5) prend en charge les arbres d'expressions et il me semble qu'il est idéal pour ce type d'expressions qui sont évaluées ultérieurement ou ailleurs.

Mais je ne sais pas comment m'assurer que je peux utiliser la syntaxe C # (en utilisant +, <<,%, etc.) pour construire l'arborescence des expressions. Je veux prévenir des choses comme:

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

Comment vous y prendriez-vous?


Remarque: J'ai besoin d'un arbre d'expression pour pouvoir convertir l'expression en une représentation de chaîne personnalisée acceptable, tout en pouvant l'évaluer à un moment dans le temps autre que sa définition.


Une explication de mon exemple: 64-$+mystring . Le $ est le décalage actuel, il s’agit donc d’un nombre spécifique inconnu à l’avance (mais connu au moment de l’évaluation). Le mystring est un symbole qui peut ou non être connu au moment de l'évaluation (par exemple, quand il n'a pas encore été défini). Soustraire une constante C d'un symbole S est identique à S + -C . En soustrayant deux symboles S0 et S1 ( S1 - S0 ), on obtient la différence entière entre les valeurs des deux symboles.

Cependant, cette question ne concerne pas vraiment comment évaluer les expressions d'assembleur, mais plutôt comment évaluer toute expression contenant des classes personnalisées (pour des éléments tels que les symboles et $ dans l'exemple) et comment s'assurer que cela peut être joli -imprimé en utilisant un visiteur (gardant ainsi l'arbre). Et puisque le framework .NET a ses arbres d'expression et ses visiteurs, il serait bien de les utiliser, si possible.

Réponse acceptée

Je ne sais pas exactement ce que vous souhaitez, mais voici une approche sommaire qui, à mon avis, fonctionnerait.

Note je

  1. ne montre que les expressions de référence indexées (en ignorant pour le moment l'adressage indirect via les registres; vous pouvez ajouter un RegisterInderectReference analogue à la classe SymbolicReference). Cela vaut également pour vous suggéré la fonctionnalité $ (décalage actuel). Ce serait sûrement un registre (?)
  2. ne pas montrer le unaire explicitement / binaire operator- au travail non plus . Cependant, les mécanismes sont en grande partie les mêmes. J'ai arrêté de l'ajouter car je ne pouvais pas comprendre la sémantique des exemples d'expressions dans votre question.
    (Je pense que soustraire l'adresse d'une chaîne connue n'est pas utile, par exemple)
  3. l'approche ne place pas de limites (sémantiques): vous pouvez décaler toute IRFerence dérivée de ReferenceBase. En pratique, vous souhaiterez peut-être n'autoriser qu'un seul niveau d'indexation et il serait plus approprié de définir l' operator+ directement sur SymbolicReference.
  4. A sacrifié le style de codage à des fins de démonstration (en général, vous ne voulez à plusieurs reprises Compile() vos arbres d'expression, et l' évaluation directe avec .Compile()() semble laid et confus. Il est laissé à l'OP de l' intégrer dans une mode plus lisible

  5. La démonstration de l'opérateur de conversion explicite est vraiment hors sujet. Je me suis emporté légèrement (?)

  6. Vous pouvez observer le code en direct sur 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));
        }
    }
}

Réponse populaire

C # prend en charge l'affectation d'une expression lambda à une Expression<TDelegate> , ce qui Expression<TDelegate> le compilateur à émettre du code pour créer un arbre d'expression représentant l'expression lambda, que vous pourrez ensuite manipuler. Par exemple:

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

Vous pouvez ensuite potentiellement convertir l’arbre d’expression généré en arborescence de syntaxe de votre assembleur, mais cela ne semble pas être tout à fait ce que vous recherchez et je ne pense pas que vous serez en mesure de tirer parti de la C # compilateur pour faire cela pour une entrée arbitraire.

Vous allez probablement devoir créer votre propre analyseur syntaxique pour votre langage d'assemblage, car je ne pense pas que le compilateur C # fasse ce que vous voulez dans ce cas.




Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi