Pretty-Printing est une compilation

DDD
Libérer: 2024-11-01 04:21:02
original
531 Les gens l'ont consulté

Une plus jolie imprimante de Wadler est une perle fonctionnelle classique. Cependant, que ce soit à cause de la paresse de Haskell ou de la mienne, j'ai eu du mal à réimplémenter ses idées dans d'autres langues (ou même en Haskell, cinq minutes après avoir lu l'article). Heureusement, Lindig a reconnu ce problème et a enflammé les masses dans Strictly Pretty. Pourtant, même cela n’était pas assez stupide pour moi.

Mais après avoir passé un peu plus de temps à bricoler les idées des deux articles, je pense que j'ai enfin compris l'idée.

Aperçu

Comme le titre l'indique, nous considérerons le joli-printing comme un processus de compilation (et d'exécution) de programmes écrits dans un « langage de document » abstrait. Comme la plupart des langages de programmation, ce langage de document, que nous appellerons
Doc : contient des expressions qui peuvent être composées ensemble ; cela permet aux humains de raisonner facilement. Nous compilerons des expressions dans Doc en instructions dans une sorte de langage assembleur (ASM). Les instructions en ASM sont beaucoup plus faciles à transformer en chaînes.

Voici une vue schématique du processus :

Pretty-Printing is Compilation

À titre d'exemple concret, supposons que nous souhaitions imprimer joliment la liste imbriquée :

['onions', ['carrots', 'celery'], 'turnips']
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Voici un programme Doc pour ce faire :

group(
    '['
    + nest(
        4,
        br()
        + "'onions'"
        + ','
        + br(' ')
        + group(
            '['
            + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'")
            + br()
            + ']'
        )
        + ','
        + br(' ')
        + "'turnips'"
    )
    + br()
    + ']'
)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous rencontrerons le groupe, le nid, etc. sous peu. Pour l'instant, il suffit d'avoir une idée générale du langage du document.

Ce programme est ensuite compilé (avec certains « paramètres architecturaux » définis) dans les instructions ASM :

TEXT '['
LINE 4
TEXT "'onions'"
TEXT ','
LINE 4
TEXT '['
TEXT ''
TEXT "'carrots'"
TEXT ','
TEXT ' '
TEXT "'celery'"
TEXT ''
TEXT ']'
TEXT ','
LINE 4
TEXT "'turnips'"
LINE 0
TEXT ']'
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

qui sont ensuite interprétés comme une chaîne :

[
    'onions',
    ['carrots', 'celery'],
    'turnips'
]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Une caractéristique importante évoquée ci-dessus est que le compilateur dispose d'un "paramètre architectural" configurable, qui est une largeur de ligne maximale cible. En fonction de la valeur de ce paramètre, le compilateur émettra différentes instructions ASM pour un même programme Doc. Dans l'exemple ci-dessus, nous avons utilisé une largeur cible de 30. Si nous avions utilisé 20 à la place, les instructions d'assemblage émises seraient différentes, tout comme la chaîne résultante :

[
    'onions',
    [
        'carrots',
        'celery'
    ],
    'turnips'
]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et si on en avait utilisé 60, ce serait :

['onions', ['carrots', 'celery'], 'turnips']
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Langage d'assemblage d'imprimante

Le langage assembleur est simple, nous allons donc l'aborder en premier. Nous considérerons les instructions ASM comme contrôlant un périphérique d'impression très simple qui n'est capable de faire que deux choses :

  1. Émettre une chaîne de texte.
  2. Avancer à la ligne suivante et indenter d'un certain montant.

ASM se compose donc de seulement deux instructions :

  1. TEXT , qui émet une chaîne de texte.
  2. LINE , qui fait avancer l'imprimante à la ligne suivante, puis indente par espaces d'indentation.

Les programmes ASM sont interprétés comme des chaînes en exécutant leurs instructions, les unes après les autres. A titre d'exemple, retraçons l'exécution du programme :

['onions', ['carrots', 'celery'], 'turnips']
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous utiliserons un > pour indiquer l'instruction en cours d'exécution et afficher la sortie actuelle ci-dessous. Le caractère ^ indiquera l'emplacement actuel de la « tête d'impression ». Nous utiliserons également des caractères _ pour indiquer les espaces, car ceux-ci sont autrement difficiles à suivre.

La première instruction TEXT provoque l'émission de la chaîne 'hello' :

group(
    '['
    + nest(
        4,
        br()
        + "'onions'"
        + ','
        + br(' ')
        + group(
            '['
            + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'")
            + br()
            + ']'
        )
        + ','
        + br(' ')
        + "'turnips'"
    )
    + br()
    + ']'
)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

LIGNE 2 passe ensuite à la ligne suivante et indente la tête de 2 espaces :

TEXT '['
LINE 4
TEXT "'onions'"
TEXT ','
LINE 4
TEXT '['
TEXT ''
TEXT "'carrots'"
TEXT ','
TEXT ' '
TEXT "'celery'"
TEXT ''
TEXT ']'
TEXT ','
LINE 4
TEXT "'turnips'"
LINE 0
TEXT ']'
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ensuite, TEXT 'indented' provoque l'ajout de 'indented' :

[
    'onions',
    ['carrots', 'celery'],
    'turnips'
]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Suivi de 'monde', en raison du TEXTE 'monde' :

[
    'onions',
    [
        'carrots',
        'celery'
    ],
    'turnips'
]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

LINE 0 fait avancer l'imprimante à la ligne suivante (et ne met pas du tout en retrait) :

['onions', ['carrots', 'celery'], 'turnips']
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et enfin, TEXT 'goodbye' émet 'goodbye' :

TEXT 'hello'
LINE 2
TEXT 'indented'
TEXT ' world'
LINE 0
TEXT 'goodbye'
Copier après la connexion
Copier après la connexion

Nous représenterons les instructions ASM comme un « type de somme » :

  • Les instructions TEXTE seront représentées par des chaînes Python.
  • Les instructions LINE seront représentées par des entiers.

C'est-à-dire :

> TEXT 'hello'
  LINE 2
  TEXT 'indented'
  TEXT ' world'
  LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
     ^
Copier après la connexion
Copier après la connexion

Interpréter une liste d'AsmInsts dans la chaîne qu'ils représentent est simplement une question de parcourir les instructions et de "faire ce qu'il faut":

  TEXT 'hello'
> LINE 2
  TEXT 'indented'
  TEXT ' world'
  LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
__
  ^
Copier après la connexion
Copier après la connexion

Pour les instructions TEXTE, l'interprète ajoute le texte au résultat ; pour les instructions LINE, l'interpréteur ajoute une nouvelle ligne (« n ») suivie d'espaces de retrait.

Nous pouvons tester l'interprétation avec les instructions ASM de l'exemple ci-dessus, traduites en Python :

  TEXT 'hello'
  LINE 2
> TEXT 'indented'
  TEXT ' world'
  LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
__indented
          ^
Copier après la connexion
Copier après la connexion

Le langage Doc (Teaser)

Nous aimons l'ASM car il est facile à interpréter. Mais c'est pénible à utiliser. Cela motive le langage Doc plus convivial. Alors que les programmes ASM sont des séquences d'instructions, les programmes Doc sont des compositions de expressions. Ces expressions sont résumées par la grammaire suivante :

  TEXT 'hello'
  LINE 2
  TEXT 'indented'
> TEXT ' world'
  LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
__indented world
                ^
Copier après la connexion
Copier après la connexion

Par exemple :

  TEXT 'hello'
  LINE 2
  TEXT 'indented'
  TEXT ' world'
> LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
__indented world

^
Copier après la connexion
Copier après la connexion

est une expression Doc, telle quelle :

  TEXT 'hello'
  LINE 2
  TEXT 'indented'
  TEXT ' world'
  LINE 0
> TEXT 'goodbye'

== OUTPUT ==

hello
__indented world
goodbye
       ^
Copier après la connexion
Copier après la connexion

Que représentent-ils ?

  • Un littéral str Python se représente.
  • br() est un saut de ligne possible.
  • nest(indent, doc) crée une sous-expression « imbriquée » qui est visuellement décalée par des espaces de retrait.
  • group(doc) délimite une sous-expression dans laquelle tous les br() sont traités ou non comme des sauts de ligne.
  • combine les expressions Doc.
  • nil agit comme une expression "vide".

Donc, par exemple :

AsmInst = str | int
Copier après la connexion
Copier après la connexion

représente la chaîne :

def interpret(insts: list[AsmInst]) -> str:
    """Interpret the ASM instructions as a string."""
    result = ""
    for inst in insts:
        match inst:
            case text if isinstance(text, str):
                result += inst
            case indent if isinstance(indent, int):
                result += f"\n{' ' * indent}"
    return result
Copier après la connexion
Copier après la connexion

La deuxième expression, plus complexe :

['onions', ['carrots', 'celery'], 'turnips']
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

peut représenter soit :

group(
    '['
    + nest(
        4,
        br()
        + "'onions'"
        + ','
        + br(' ')
        + group(
            '['
            + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'")
            + br()
            + ']'
        )
        + ','
        + br(' ')
        + "'turnips'"
    )
    + br()
    + ']'
)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

ou :

TEXT '['
LINE 4
TEXT "'onions'"
TEXT ','
LINE 4
TEXT '['
TEXT ''
TEXT "'carrots'"
TEXT ','
TEXT ' '
TEXT "'celery'"
TEXT ''
TEXT ']'
TEXT ','
LINE 4
TEXT "'turnips'"
LINE 0
TEXT ']'
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

en fonction de la valeur du "paramètre architectural" de largeur de ligne maximale cible fournie au compilateur. Ainsi, les expressions br peuvent être traitées comme des sauts de ligne ou du texte normal, auquel cas leur valeur de texte est utilisée (ou '' si aucun argument de texte n'a été fourni).

Nous représenterons les expressions Doc en utilisant des classes strs et Python. En particulier :

[
    'onions',
    ['carrots', 'celery'],
    'turnips'
]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et DocExpr DocExpr ? Nous représenterons ceux qui utilisent une classe Concat supplémentaire :

[
    'onions',
    [
        'carrots',
        'celery'
    ],
    'turnips'
]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous souhaitons prendre en charge l'utilisation pour combiner des expressions, nous devons donc implémenter __add__ et __radd__ sur chacune des classes variantes. L'ajout de deux expressions Doc en utilisant simplement construit un Concat des deux. C'est assez simple de le faire manuellement, par exemple :

['onions', ['carrots', 'celery'], 'turnips']
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Mais on peut s'épargner de la saisie en définissant un décorateur pour le faire à notre place :

TEXT 'hello'
LINE 2
TEXT 'indented'
TEXT ' world'
LINE 0
TEXT 'goodbye'
Copier après la connexion
Copier après la connexion

Les classes variantes ressemblent désormais à :

> TEXT 'hello'
  LINE 2
  TEXT 'indented'
  TEXT ' world'
  LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
     ^
Copier après la connexion
Copier après la connexion

Notre tâche consiste maintenant à écrire un compilateur qui traduit les expressions du langage Doc en instructions ASM équivalentes, compte tenu d'une certaine largeur de ligne maximale cible :

  TEXT 'hello'
> LINE 2
  TEXT 'indented'
  TEXT ' world'
  LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
__
  ^
Copier après la connexion
Copier après la connexion

Cependant, il s'avère plus simple de "réduire" d'abord les expressions Doc en expressions dans un langage de Représentation intermédiaire (IR), puis de compiler les expressions IR dans ASM. Introduit ce "pass" supplémentaire rend chaque étape plus claire.

Une représentation intermédiaire

Notre schéma décrivant le processus de jolie impression était donc un peu trop simpliste. Voici l'image complète :

Pretty-Printing is Compilation

Les expressions IR ressemblent aux expressions Doc à bien des égards :

  TEXT 'hello'
  LINE 2
> TEXT 'indented'
  TEXT ' world'
  LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
__indented
          ^
Copier après la connexion
Copier après la connexion

La principale différence est que nous n'avons plus d'expressions nulles : celles-ci sont converties en listes d'expressions IR dans le processus d'abaissement. En fait, c'est tout que fait la passe de descente :

  TEXT 'hello'
  LINE 2
  TEXT 'indented'
> TEXT ' world'
  LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
__indented world
                ^
Copier après la connexion
Copier après la connexion

Nous devons d’abord définir IrExprs.
Cela devrait vous sembler familier :

  TEXT 'hello'
  LINE 2
  TEXT 'indented'
  TEXT ' world'
> LINE 0
  TEXT 'goodbye'

== OUTPUT ==

hello
__indented world

^
Copier après la connexion
Copier après la connexion

Tout ce que Lower fait est de remplacer les instances Nil() par une liste vide ([]) et les instances Concat(car, cdr) en ajoutant les résultats de la réduction des expressions car et cdr. Le même traitement est appliqué aux sous-expressions dans Nest et Group. Ce n'est rien de plus qu'une opération "d'aplatissement" récursive.

  TEXT 'hello'
  LINE 2
  TEXT 'indented'
  TEXT ' world'
  LINE 0
> TEXT 'goodbye'

== OUTPUT ==

hello
__indented world
goodbye
       ^
Copier après la connexion
Copier après la connexion

Test inférieur avec l'un de nos exemples d'expressions Doc ci-dessus :

AsmInst = str | int
Copier après la connexion
Copier après la connexion

ce qui est exactement ce à quoi nous nous attendons.

Le compilateur (enfin)

Maintenant, passons à la dernière étape : compiler. Cette fonction transforme les expressions IR en instructions ASM, en tenant compte de la largeur de ligne maximale cible :

def interpret(insts: list[AsmInst]) -> str:
    """Interpret the ASM instructions as a string."""
    result = ""
    for inst in insts:
        match inst:
            case text if isinstance(text, str):
                result += inst
            case indent if isinstance(indent, int):
                result += f"\n{' ' * indent}"
    return result
Copier après la connexion
Copier après la connexion

Voici l'idée approximative de l'algorithme :

  • Le compilateur conserve certaines informations « d'état » :
    • La position actuelle (horizontale) de la ligne.
    • Le montant actuel de l'indentation.
    • Si les brs doivent être traités ou non comme des sauts de ligne ou rendus « à plat ».
  • Nous parcourons les expressions, émettons des instructions ASM et mettons à jour la position de la ligne de manière appropriée.
['onions', ['carrots', 'celery'], 'turnips']
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

le processus est l'endroit où la magie opère :

group(
    '['
    + nest(
        4,
        br()
        + "'onions'"
        + ','
        + br(' ')
        + group(
            '['
            + nest(4, br() + "'carrots'" + ',' + br(' ') + "'celery'")
            + br()
            + ']'
        )
        + ','
        + br(' ')
        + "'turnips'"
    )
    + br()
    + ']'
)
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

En bref :

  • Pour les expressions textuelles, nous émettons une instruction TEXT et avançons la position (pos) de la longueur du texte.
  • Les expressions br sont gérées en fonction de la valeur de flat :
    • Si flat est True, traitez-les comme du texte.
    • Sinon, émettez une instruction INDENT avec le niveau d'indentation actuel, et réinitialisez la position à cette valeur.
  • Pour les expressions imbriquées, nous traitons toutes les sous-expressions, mais avec le niveau d'indentation actuel augmenté de la valeur d'indentation de l'expression imbriquée.
  • Enfin, pour les expressions de groupe, nous vérifions d'abord si l'ensemble du groupe peut être rendu à plat sans dépasser l'espace restant. Cela détermine la valeur de flat pour toutes les sous-expressions regroupées, qui à son tour décide si les brs sont rendus sous forme de sauts de ligne (ou sous forme de texte).

Comment fonctionnefits_flat ? Il parcourt simplement les instructions du groupe, traite les brs comme du texte et s'arrête quand :

  • Nous n'avons plus d'espace (largeur < 0), auquel cas les sous-expressions groupées ne peuvent pas être rendues à plat.
  • Nous avons traité toutes les sous-expressions, auquel cas le groupe peut être rendu plat.
TEXT '['
LINE 4
TEXT "'onions'"
TEXT ','
LINE 4
TEXT '['
TEXT ''
TEXT "'carrots'"
TEXT ','
TEXT ' '
TEXT "'celery'"
TEXT ''
TEXT ']'
TEXT ','
LINE 4
TEXT "'turnips'"
LINE 0
TEXT ']'
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Mettre tout cela ensemble

Nous pouvons enfin assembler les pièces :

[
    'onions',
    ['carrots', 'celery'],
    'turnips'
]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Les seuls éléments restants de l'interface de Pretty-Printer sont les constructeurs d'expressions de documents :

[
    'onions',
    [
        'carrots',
        'celery'
    ],
    'turnips'
]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Essayons l'exemple de l'introduction :

['onions', ['carrots', 'celery'], 'turnips']
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Voir la source complète ici.

Comment "programmer" dans Doc

? En construction ?

  • Modèles courants.
  • Comment le frère, le nid et le groupe interagissent.

Cloches et sifflets

? En construction ?

  • Ajout d'un groupe de paramètres de pliage pour forcer le pliage.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!