Форматування Lisp-подібного синтаксису


23

Фон

(За матеріалами правдивої, щиросердної історії)

У свій час я часто грав з Ліспом і подібними мовами. Я писав з ними, керував ними, інтерпретував їх, проектував і змушував машини писати разом із ними для мене ... І якщо є одна річ, яка мене турбує, це бачить Лісп, який не відповідає моєму конкретному стилю форматування.

На жаль, деякі текстові редактори ( кашель XCode кашлю ), як правило, знімають мої прекрасні вкладки та пробіли, коли код копіюється та вставляється ... Візьміть цей прекрасно розташований синтаксис, схожий на Lisp:

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

(Де ABCDEє довільні функції)

ДЕЯКІ текстові редактори, які розмалюють цей чудовий код до наступного кінця:

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

Який безлад! Це не читабельно!

Допоможіть мені, тут?

Змагання

Ваша мета в цьому завданні - взяти ряд функцій, розділених новими рядками у форматі, описаному нижче, та повернути більш красиву композицію, яка підкреслює читабельність та елегантність.

Вхідні дані

Ми визначаємо функцію аргументів Farity Nяк конструкцію, аналогічну наступному:

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

де G1, G2, ..., GNвсі функції самі по собі. Арность 0функція Aпросто (A), в той час як арность 2функція Bмає вигляд(B (...) (...))

Ваш код повинен приймати введення як ряд функцій з одним новим рядком перед провідними дужками кожної функції (крім першої функції). Наведений вище приклад є коректним вводом.

Ви можете припустити:

  • Дужки врівноважені.
  • Функцію ніколи не доведеться відступати більше 250 разів.
  • КОЖНА функція оточена дужками: ()
  • Ім'я функції міститиме лише символи, що друкуються ASCII.
  • Ім'я функції ніколи не буде містити дужки чи пробіли.
  • На вході є додатковий новий рядок.

Вихід

Ваш код повинен виводити той самий набір функцій, де єдиними змінами є додавання пробілів чи вкладок перед провідними дужками функцій. Результат повинен відповідати наступним правилам:

  • Перша функція (і пізніші функції верхнього рівня) не повинні мати попередніх пробілів
  • Аргументом горизонтального розташування функції є саме одна вкладка праворуч від горизонтального розташування цієї функції.
  • Вкладка визначена реалізацією, але повинна містити принаймні 3 пробіли.
  • Ви можете необов'язково друкувати максимум два пробіли після кожного рядка.

Правила

Приклади

Вхід:

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

Вихід:

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

Вхід:

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

Вихід:

(!@#$%^&*
    (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)))))

Вихід:

(-: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)))))

Вітаю вас із створенням списку питань гарячої мережі! : D
Олексій А.

@AlexA. Ура! Мої мрії здійснилися. : D
BrainSteel

Що робити, якщо немає назви функції, наприклад ()?
coredump

Чи має бути відступ> = 3 пробіли, або прийнятна вкладка?
isaacg

@isaacg Ви можете припустити, що всі функції названі в цьому випадку. І все, що ваша ОС / мова визначає як горизонтальну вкладку, це добре. Якщо ви використовуєте пробіли, їх повинно бути не менше 3. Я уточню, що коли я можу потрапити до комп’ютера. Спасибі!
BrainSteel

Відповіді:


9

Pyth, 24 20 19 18 байт

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

Збільшує лічильник для кожного рядка, підраховує загальну кількість закритих дужок, що зустрічалися до цього часу, і віднімає його з лічильника. Потім відступаємо counterвкладками.


@Downvoter Care пояснити?
orlp

Я не виступав проти цього, але це твердий *4і непотрібний перевага. FN.z+*ZC9N~Z-1/N\)дозволяє використовувати ширину відступу редактора і зберігає один байт.
Cees Timmerman

Я згоден, вкладка буде на один символ коротшою. \<tab>або C9.
isaacg

9

Ліпп звичайний - 486 414 байт (версія Руба Гольдберга)

(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)))))

Підхід

Замість того, щоб робити так, як усі, і рахувати круглі дужки вручну, давайте звернемось до читача Lisp і зробимо це правильно :-)

  • Прочитайте з вхідного потоку і запишіть у тимчасовий вихідний потік.
  • Роблячи це, агрегатні символи , відмінні від (, )або пробільних як рядків.
  • Проміжний вихід використовується для побудови рядка, який містить синтаксично добре сформовані форми Common-Lisp: вкладені списки рядків.
  • Використовуючи цей рядок як вхідний потік, викличте стандартну readфункцію для складання фактичних списків.
  • Зателефонуйте pдо кожного із цих списків, які рекурсивно записують їх на стандартний вихід із запитуваним форматом. Зокрема, рядки друкуються без котирування.

Як наслідок такого підходу:

  1. Існує менше обмежень щодо формату введення: ви можете читати довільно відформатовані входи, а не лише "одну функцію на рядок" (ugh).
  2. Крім того, якщо вхід недостатньо сформований, буде подана помилка.
  3. Нарешті, функція симпатичного друку добре відокремлена від розбору: ви можете легко перейти на інший спосіб симпатичного друку S-виразів (і вам слід, якщо ви цінуєте свій вертикальний простір).

Приклад

Читання з файлу за допомогою цієї обгортки:

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

Ось результат:

(!@#$%^&*
    (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)))))

(схоже, що вкладки перетворені на пробіли)

Симпатичний друк (версія для гольфу)

На відміну від більш безпечної оригінальної версії, ми очікуємо, що введення буде дійсним.

(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)))))

7

Сітківка , 89 83 байти

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

Де <tab>означає фактичний символ вкладки (0x09) і <empty>означає порожній рядок. Зробивши ці заміни, ви можете запустити вищевказаний код із -sпрапором. Однак я не вважаю цей прапор, тому що ви також можете просто помістити кожен рядок у свій власний вихідний файл, і в цьому випадку 7 нових рядків будуть замінені 7 штрафними байтами для додаткових вихідних файлів.

Це повна програма, яка бере вклад на STDIN і друкує результат на STDOUT.

Пояснення

Кожна пара рядків визначає підстановку регулярного вираження. Основна ідея полягає у використанні балансуючих груп .NET, щоб підрахувати поточну глибину до заданої (, а потім вставити багато вкладок до цього (.

s`.+
$0<tab>$0

Спочатку готуємо вхід. Ми не можемо реально записати умовну кількість вкладок, якщо не зможемо знайти їх десь у вхідному рядку для їх захоплення. Отже, ми починаємо з дублювання всього вводу, розділеного вкладкою. Зауважте, що s`щойно активує однолінійний (або "крапковий") модифікатор, що забезпечує .також відповідність новим рядкам.

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

Тепер ми перетворюємо кожен символ після цієї вкладки також на вкладку. Це дає нам достатню кількість вкладок в кінці рядка, не змінюючи початковий рядок досі.

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

Це м’ясо розчину. mІ sактивувати режим багаторядкових (так що ^відповідає початку рядка) і режим однострочного. +Каже Retina, щоб повторювати цю заміну , поки на виході не перестає змінюватися (в даному випадку це означає, що до тих пір , шаблон більше не збігається з рядком).

Сам шаблон відповідає префіксу вхідного сигналу до необробленого ((тобто такого (, який не має перед собою вкладок, але повинен). Одночасно він визначає глибину префікса з балансуючими групами, таким чином, що висота стека 2буде відповідати поточній глибині, а отже, і кількості вкладок, які нам потрібно додати. Ось ця частина:

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

Він або відповідає a (, натискаючи на 2стек, або він відповідає a ), вискакуючи останнє захоплення зі 2стека, або він відповідає щось інше і залишає стек недоторканим. Оскільки круглі дужки гарантовано збалансовані, нам не потрібно турбуватися про те, щоб намагатися вивести з порожнього стека.

Після того, як ми пройшли такий рядок і виявили необроблену, (на якій зупинимось, lookahead потім пропускає вперед до кінця рядка і фіксує вкладки в групу 3, вискакуючи зі 2стека, поки не випорожниться:

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

Використовуючи +там, ми гарантуємо, що візерунок відповідає лише будь-якому, якщо хоча б одну вкладку потрібно вставити в збіг - це дозволяє уникнути нескінченного циклу, коли існує кілька функцій на рівні кореня.

<tab>+$
<empty>

Нарешті, ми просто позбавляємося тих помічників в кінці рядка, щоб очистити результат.


Це дуже круто. Молодці! Завжди приємно бачити Retina.
BrainSteel

6

C: 95 94 символи

Це ще не дуже гольф, і з питання, я не впевнений, чи прийнятні вкладки, для чого я тут користуюся.

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

Безголівки:

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

Редагувати: Зробили так, що він закривається на EOF.


Вкладки цілком прийнятні.
BrainSteel

2
Чи можете ви використовувати if(c<11)замість if(c==10)?
Цифрова травма

5

Джулія, 103 99 97 94 88 байт

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

Це визначає неназвану функцію, яка приймає рядок і друкує відрізну версію. Щоб зателефонувати, дайте ім’я, наприклад f=p->.... Зауважте, що вхід повинен бути дійсним рядком Джулії, тому знаки долара ( $) потрібно уникати.

Недоліки + пояснення:

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

Приклад, роблячи вигляд, що кожен набір із чотирьох пробілів є вкладкою:

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

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

Будь-які пропозиції більш ніж вітаються!



3

Перл, 41

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

40символів +1для -p.

Виконати з:

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

3

Пітон 2 - 88 78 байт

Досить просте (і не дуже коротке) рішення:

l=0
for x in raw_input().split():g=x.count;d=l*'\t'+x;l+=g("(")-g(")");print d

Пара порад: 1) Ви можете використовувати '\t'замість ' 'одного байта і зберегти його; 2) не потрібно призначати input.split()змінну, оскільки вона використовується лише один раз (те саме для c, а також - dпросто перемістити printоператор); 3) пріоритет оператора означає, що круглі дужки навколо l*cне потрібні. Крім того, схоже, що fвін ні для чого не використовується - це реліквія попередньої версії?
DLosc

Крім того, якщо це Python 2, вам потрібно буде використовувати raw_inputзамість input(і не забудьте дужки після нього!).
DLosc

2

CJam, 20 байт

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

Спробуйте його в Інтернеті в інтерпретаторі CJam .

Як це працює

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.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.