Formatage d'une syntaxe de type Lisp

23

Contexte

(Basé sur une histoire vraie et déchirante)

À mon époque, j'ai souvent joué avec le lisp et des langues similaires. J'ai écrit avec eux, les ai exécutés, les ai interprétés, les ai conçus et j'ai fait écrire des machines avec eux ... Et s'il y a une chose qui me dérange, c'est de voir Lisp qui ne correspond pas à mon style de formatage spécifique.

Malheureusement, certains éditeurs de texte ( cough XCode cough ) ont tendance à dépouiller mes beaux onglets et espaces chaque fois que le code est copié et collé ... Prenez cette syntaxe de type Lisp magnifiquement espacée:

(A
    (B
        (C)
        (D))
    (E))

(Où ABCDEsont les fonctions arbitraires)

CERTAINS éditeurs de texte massacrent ce joli code à la fin suivante:

(A
(B
(C)
(D))
(E))

Quel bordel! Ce n'est pas lisible!

Aidez-moi, ici?

Le défi

Votre objectif dans ce défi est de prendre une série de fonctions séparées par des sauts de ligne dans un format décrit ci-dessous et de retourner un arrangement plus beau qui met en valeur la lisibilité et l'élégance.

L'entrée

Nous définissons une fonction Fd' Narguments d' arité comme une construction similaire à la suivante:

(F (G1 ...) (G2 ...) (G3 ...) ... (GN ...))

G1, G2, ..., GNsont toutes les fonctions en soi. Une 0fonction d' arité Aest simplement (A), tandis qu'une 2fonction d' arité Best de la forme(B (...) (...))

Votre code doit prendre la saisie comme une série de fonctions avec une seule nouvelle ligne avant la parenthèse principale de chaque fonction (sauf pour la première fonction). L'exemple ci-dessus est une entrée valide.

Vous pouvez supposer:

  • Les parenthèses sont équilibrées.
  • Une fonction ne devra jamais être mise en retrait plus de 250 fois.
  • CHAQUE fonction est entourée de parenthèses: ()
  • Le nom d'une fonction ne contiendra que des caractères ASCII imprimables.
  • Le nom d'une fonction ne contiendra jamais de parenthèses ou d'espaces.
  • Il y a un retour à la ligne de fin facultatif en entrée.

Le résultat

Votre code doit générer le même ensemble de fonctions, où les seules modifications apportées sont les ajouts d'espaces ou de tabulations avant les parenthèses de début des fonctions. La sortie doit respecter les règles suivantes:

  • La première fonction (et les fonctions de niveau supérieur ultérieures) fournies ne doit pas avoir d'espaces précédents
  • Un argument à l'emplacement horizontal d'une fonction est exactement un onglet à droite de l'emplacement horizontal de cette fonction.
  • Un onglet est défini par l'implémentation, mais doit comporter au moins 3 espaces.
  • Vous pouvez éventuellement imprimer un maximum de deux espaces après chaque ligne.

Règles

Exemples

Contribution:

(A
(B
(C)
(D))
(E))

Sortie:

(A
    (B
        (C)
        (D))
    (E))

Contribution:

(!@#$%^&*
(asdfghjklm
(this_string_is_particularly_long
(...))
(123456789)))
(THIS_IS_TOP_LEVEL_AGAIN
(HERE'S_AN_ARGUMENT))

Sortie:

(!@#$%^&*
    (asdfghjklm
        (this_string_is_particularly_long
            (...))
        (123456789)))
(THIS_IS_TOP_LEVEL_AGAIN
    (HERE'S_AN_ARGUMENT))

Contribution:

(-:0
(*:0
(%:0
(Arg:6)
(Write:0
(Read:0
(Arg:30))
(Write:0
(Const:-6)
(Arg:10))))
(%:0
(Const:9)
(/:0
(Const:-13)
(%:0
(Arg:14)
(Arg:0)))))
(WriteArg:22
(-:0
(Const:45)
(?:0
(Arg:3)
(Arg:22)
(Arg:0)))))

Sortie:

(-:0
    (*:0
        (%:0
            (Arg:6)
            (Write:0
                (Read:0
                    (Arg:30))
                (Write:0
                    (Const:-6)
                    (Arg:10))))
        (%:0
            (Const:9)
            (/:0
                (Const:-13)
                (%:0
                    (Arg:14)
                    (Arg:0)))))
    (WriteArg:22
        (-:0
            (Const:45)
            (?:0
                (Arg:3)
                (Arg:22)
                (Arg:0)))))
BrainSteel
la source
Félicitations pour avoir créé la liste Hot Network Questions! : D
Alex A.
@AlexA. Hourra! Mes rêves se sont réalisés. : D
BrainSteel
Et s'il n'y a pas de nom de fonction, comme ()?
coredump
L'indentation doit-elle être> = 3 espaces, ou une tabulation est-elle acceptable?
isaacg
@isaacg Vous pouvez supposer que toutes les fonctions sont nommées dans ce cas. Et quel que soit votre système d'exploitation / langue définir comme un onglet horizontal est très bien. Si vous utilisez des espaces, il doit y en avoir au moins 3. Je préciserai cela lorsque je pourrai accéder à un ordinateur. Merci!
BrainSteel

Réponses:

9

Pyth, 24 20 19 18 octets

FN.z+*ZC9N~Z-1/N\)

Incrémente un compteur pour chaque ligne, compte le nombre total de parenthèses fermantes rencontrées jusqu'à présent et le soustrait du compteur. Ensuite, nous mettons en retrait par countertabulations.

orlp
la source
@Downvoter Care à expliquer?
orlp
Je n'ai pas déçu, mais *4c'est une préférence codée en dur et redondante. FN.z+*ZC9N~Z-1/N\)vous permet d'utiliser la largeur de retrait de votre éditeur et enregistre un octet.
Cees Timmerman
Je suis d'accord, un onglet serait un caractère plus court. \<tab>ou C9.
isaacg
9

Lisp commun - 486 414 octets (version Rube Goldberg)

(labels((p(x d)(or(when(listp x)(#2=princ #\()(p(car x)d)(incf d)(dolist(a(cdr x))(format t"~%~v{   ~}"d'(t))(p a d))(#2# #\)))(#2# x))))(let((i(make-string-input-stream(with-output-to-string(o)(#1=ignore-errors(do(b c)(())(if(member(setq c(read-char))'(#\( #\) #\  #\tab #\newline):test'char=)(progn(when b(prin1(coerce(reverse b)'string)o))(#2# c o)(setq b()))(push c b))))))))(#1#(do()(())(p(read i)0)(terpri)))))

Approche

Au lieu de faire comme tout le monde et de compter les parenthèses à la main, invoquons le lecteur Lisp et faisons- le de la bonne façon :-)

  • Lire à partir du flux d'entrée et écrire dans un flux de sortie temporaire .
  • Ce faisant, les caractères globaux différents de (, )ou un espace sous forme de chaînes.
  • La sortie intermédiaire est utilisée pour construire une chaîne, qui contient des formes Common-Lisp syntaxiquement bien formées: listes imbriquées de chaînes.
  • En utilisant cette chaîne comme flux d'entrée, appelez la readfonction standard pour créer des listes réelles.
  • Appelez pchacune de ces listes, qui les écrivez récursivement sur la sortie standard avec le format demandé. En particulier, les chaînes sont imprimées sans guillemets.

En conséquence de cette approche:

  1. Il y a moins de restrictions sur le format d'entrée: vous pouvez lire des entrées au format arbitraire, pas seulement "une fonction par ligne" (ugh).
  2. De plus, si l'entrée n'est pas bien formée, une erreur sera signalée.
  3. Enfin, la fonction de jolie impression est bien découplée de l'analyse: vous pouvez facilement passer à une autre façon d'exprimer de belles expressions S (et vous devriez, si vous appréciez votre espace vertical).

Exemple

Lecture à partir d'un fichier, en utilisant ce wrapper:

(with-open-file (*standard-input* #P"path/to/example/file")
    ...)

Voici le résultat:

(!@#$%^&*
    (asdfghjklm
        (this_string_is_particularly_long
            (...))
        (123456789)))
(THIS_IS_TOP_LEVEL_AGAIN
    (HERE'S_AN_ARGUMENT))
(-:0
    (*:0
        (%:0
            (Arg:6)
            (Write:0
                (Read:0
                    (Arg:30))
                (Write:0
                    (Const:-6)
                    (Arg:10))))
        (%:0
            (Const:9)
            (/:0
                (Const:-13)
                (%:0
                    (Arg:14)
                    (Arg:0)))))
    (WriteArg:22
        (-:0
            (Const:45)
            (?:0
                (Arg:3)
                (Arg:22)
                (Arg:0)))))

(il semble que les tabulations soient converties en espaces ici)

Joli imprimé (version golf)

Contrairement à la version originale plus sûre, nous nous attendons à ce que l'entrée soit valide.

(labels ((p (x d)
           (or
            (when (listp x)
              (princ #\()
              (p (car x) d)
              (incf d)
              (dolist (a (cdr x)) (format t "~%~v{  ~}" d '(t)) (p a d))
              (princ #\)))
            (princ x))))
  (let ((i
         (make-string-input-stream
          (with-output-to-string (o)
            (ignore-errors
             (do (b
                  c)
                 (nil)
               (if (member (setq c (read-char)) '(#\( #\) #\  #\tab #\newline)
                           :test 'char=)
                   (progn
                    (when b (prin1 (coerce (reverse b) 'string) o))
                    (princ c o)
                    (setq b nil))
                   (push c b))))))))
    (ignore-errors (do () (nil) (p (read i) 0) (terpri)))))
coredump
la source
7

Rétine , 89 83 octets

s`.+
$0<tab>$0
s`(?<=<tab>.*).
<tab>
+ms`^((\()|(?<-2>\))|[^)])+^(?=\(.*^((?<-2><tab>)+))
$0$3
<tab>+$
<empty>

<tab>représente un caractère de tabulation réel (0x09) et <empty>représente une ligne vide. Après avoir effectué ces remplacements, vous pouvez exécuter le code ci-dessus avec l' -sindicateur. Cependant, je ne compte pas cet indicateur, car vous pouvez également mettre chaque ligne dans son propre fichier source, auquel cas les 7 sauts de ligne seraient remplacés par 7 octets de pénalité pour les fichiers source supplémentaires.

Il s'agit d'un programme complet, prenant des données sur STDIN et imprimant le résultat sur STDOUT.

Explication

Chaque paire de lignes définit une substitution d'expression régulière. L'idée de base est d'utiliser les groupes d'équilibrage de .NET pour compter la profondeur actuelle jusqu'à une valeur donnée (, puis insérer autant d'onglets avant cela (.

s`.+
$0<tab>$0

Tout d'abord, nous préparons l'entrée. Nous ne pouvons pas vraiment réécrire un nombre conditionnel d'onglets, si nous ne pouvons pas les trouver quelque part dans la chaîne d'entrée pour les capturer. Nous commençons donc par dupliquer la totalité de l'entrée, séparés par un onglet. Notez que le s`juste active le modificateur d'une seule ligne (ou "tout point"), ce qui garantit que le .correspond également aux sauts de ligne.

s`(?<=<tab>.*).
<tab>

Maintenant, nous transformons chaque caractère après cet onglet en onglet également. Cela nous donne un nombre suffisant d'onglets à la fin de la chaîne, sans modifier la chaîne d'origine jusqu'à présent.

+ms`^((\()|(?<-2>\))|[^)])+^(?=\(.*^((?<-2><tab>)+))
$0$3

C'est la viande de la solution. Le met sactiver le mode multi-ligne ( de sorte que ^correspond à des débuts de lignes) et le mode à ligne unique. Le +dit à Retina de continuer à répéter cette substitution jusqu'à ce que la sortie cesse de changer (dans ce cas, cela signifie que le motif ne correspond plus à la chaîne).

Le modèle lui-même correspond à un préfixe de l'entrée jusqu'à un non traité ((c'est-à-dire un (qui n'a aucun onglet avant, mais devrait). En même temps, il détermine la profondeur du préfixe avec les groupes d'équilibrage, de sorte que la hauteur de la pile 2correspondra à la profondeur actuelle, et donc au nombre d'onglets que nous devons ajouter. C'est cette partie:

((\()|(?<-2>\))|[^)])+

Il correspond soit à un (, le poussant sur la 2pile, soit il correspond à un ), faisant sauter la dernière capture de la 2pile, soit il correspond à autre chose et laisse la pile intacte. Étant donné que les parenthèses sont garanties équilibrées, nous n'avons pas à nous soucier d'essayer de sortir d'une pile vide.

Après avoir parcouru la chaîne comme ceci et trouvé un non (traité pour s'arrêter, la tête de lecture passe ensuite à la fin de la chaîne et capture les onglets en groupe 3tout en sautant de la 2pile jusqu'à ce qu'elle soit vide:

(?=\(.*^((?<-2><tab>)+))

En utilisant un +dedans, nous nous assurons que le motif ne correspond à rien si au moins un onglet doit être inséré dans la correspondance - cela évite une boucle infinie lorsqu'il existe plusieurs fonctions de niveau racine.

<tab>+$
<empty>

Enfin, nous nous débarrassons simplement de ces onglets d'aide à la fin de la chaîne pour nettoyer le résultat.

Martin Ender
la source
C'est très cool. Bien joué! C'est toujours un plaisir de voir Retina.
BrainSteel
6

C: 95 94 caractères

Ce n'est pas encore très golfé, et à la question, je ne sais pas si les onglets sont acceptables, c'est ce que j'utilise ici.

i,j;main(c){for(;putchar(c=getchar()),c+1;i+=c==40,i-=c==41)if(c==10)for(j=i;j--;putchar(9));}

Non golfé:

i,j;
main(c){
  for(
    ;
    putchar(c=getchar()),
    c+1;
    i+=c==40,
    i-=c==41
  )
    if(c==10)
      for(
        j=i;
        j--;
        putchar(9)
      );
}

Edit: fait en sorte qu'il se ferme sur EOF.

Fors
la source
Les onglets sont parfaitement acceptables.
BrainSteel
2
Pourriez-vous utiliser à la if(c<11)place de if(c==10)?
Digital Trauma
5

Julia, 103 99 97 94 88 octets

p->(i=j=0;for l=split(p,"\n") i+=1;println("\t"^abs(i-j-1)*l);j+=count(i->i=='\)',l)end)

Cela définit une fonction sans nom qui accepte une chaîne et affiche la version en retrait. Pour l'appeler, donnez-lui un nom, par exemple f=p->.... Notez que l'entrée doit être une chaîne Julia valide, donc les signes dollar ( $) doivent être échappés.

Non golfé + explication:

function f(p)
    # Set counters for the line number and the number of close parens
    i = j = 0

    # Loop over each line of the program
    for l in split(p, "\n")
        # Increment the line number
        i += 1

        # Print the program line with |i-j-1| tabs
        println("\t"^abs(i-j-1) * l)

        # Count the number of close parens on this line
        j += count(i -> i == '\)', l)
    end
end

Exemple, prétendre que chaque ensemble de quatre espaces est un onglet:

julia> f("(A
(B
(C)
(D))
(E))")

(A
    (B
        (C)
        (D))
    (E))

Toutes les suggestions sont plus que bienvenues!

Alex A.
la source
4

Haskell, 83 81

unlines.(scanl(\n s->drop(sum[1|')'<-s])$n++['\t'|'('<-s])"">>=zipWith(++)).lines

une solution très gratuite de points.

fier haskeller
la source
vous pouvez simplement laisser tomber h=.
Will Ness
3

Perl, 41

$_="\t"x($i-$j).$_;$i+=y/(/(/;$j+=y/)/)/

40caractères +1pour -p.

Courir avec:

cat input.txt | perl -pe'$_="\t"x($i-$j).$_;$i+=y/(/(/;$j+=y/)/)/'
hmatt1
la source
3

Python 2 - 88 78 octets

Solution assez simple (et pas très courte):

l=0
for x in raw_input().split():g=x.count;d=l*'\t'+x;l+=g("(")-g(")");print d
Kade
la source
Quelques conseils: 1) Vous pouvez utiliser '\t'au lieu de ' 'et enregistrer un octet; 2) pas besoin d'assigner input.split()à une variable, car elle n'est utilisée qu'une seule fois (la même chose pour c, ainsi que d--just déplacer l' printinstruction); 3) la priorité de l'opérateur signifie que les parenthèses autour l*cne sont pas nécessaires. En outre, il ne semble fpas être utilisé pour quoi que ce soit - est-ce une relique d'une version précédente?
DLosc
De plus, s'il s'agit de Python 2, vous devrez utiliser à la raw_inputplace de input(et n'oubliez pas les parenthèses après!).
DLosc
2

CJam, 20 octets

r{_')e=NU)@-:U9c*r}h

Essayez-le en ligne dans l' interpréteur CJam .

Comment ça marche

r                    e# Read a whitespace-separated token R from STDIN.
{                 }h e# Do, while R is truthy: 
  _')e=              e#   Push C, the number of right parentheses in R. 
       NU            e#   Push a linefeed and U (initially 0).
         )@-         e#   Compute U + 1 - C.
            :U       e#   Save in U.
              9c*    e#   Push a string of U tabulators.
                 r   e#   Read a whitespace-separated token R from STDIN.
Dennis
la source