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

23

Задний план

(На основе истинной, душераздирающей истории)

В свое время я часто играл с Лиспом и похожими языками. Я написал с ними, запустил их, интерпретировал их, разработал их, и заставил машины писать для них для меня ... И если есть одна вещь, которая беспокоит меня, это видеть Lisp, который не соответствует моему определенному стилю форматирования.

К сожалению, некоторые текстовые редакторы ( кашель 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)))))
BrainSteel
источник
Поздравляю с составлением списка Горячих Сетевых Вопросов! : D
Алекс А.
@AlexA. Ура! Мои мечты сбылись. : D
BrainSteel
Что делать, если нет имени функции, как ()?
coredump
Отступ должен быть> = 3 пробела, или табуляция допустима?
Исаак
@isaacg Вы можете предположить, что все функции названы в этом случае. И то, что ваша ОС / язык определяет как горизонтальную табуляцию, хорошо. Если вы используете пробелы, их должно быть как минимум 3. Я уточню, когда смогу добраться до компьютера. Благодарность!
BrainSteel

Ответы:

9

Pyth, 24 20 19 18 байт

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

Увеличивает счетчик для каждой строки, подсчитывает общее количество закрывающих скобок, встреченных до сих пор, и вычитает его из счетчика. Затем мы делаем отступ по counterвкладкам.

orlp
источник
@Downvoter Хотите объяснить?
Орлп
Я не понизил голос, но *4это жесткое и избыточное предпочтение. FN.z+*ZC9N~Z-1/N\)позволяет использовать ширину отступа вашего редактора и сохраняет один байт.
Сис Тиммерман
Я согласен, вкладка будет на один символ короче. \<tab>или C9.
Исаак
9

Common Lisp - 486 414 байт (версия 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)))))

Подходить

Вместо того, чтобы поступать, как все, и считать скобки вручную, давайте вызовем считыватель Lisp и сделаем это правильно :-)

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

Как следствие этого подхода:

  1. Существует меньше ограничений на формат ввода: вы можете читать произвольно отформатированные входные данные, а не только «одну функцию на строку» (тьфу).
  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)))))
CoreDump
источник
7

Retina , 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`just активирует однострочный (или «точка-все») модификатор, который гарантирует, что он .также соответствует символу новой строки.

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

Теперь мы превращаем каждый символ после этой вкладки в вкладку. Это дает нам достаточное количество вкладок в конце строки, без изменения исходной строки.

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

Это мясо решения. mИ sактивировать режим многострочных (так что ^соответствует началу строки) и режим однострочного. +Говорит Retina, чтобы повторять эту замену , пока на выходе не перестает изменяться (в данном случае это означает, что до тех пор , шаблон больше не совпадает со строкой).

Сам шаблон соответствует префиксу ввода вплоть до необработанного ((то есть a (, который не имеет никаких вкладок перед ним, но должен). В то же время он определяет глубину префикса с балансировочными группами, так что высота стека2 будет соответствовать текущей глубине и, следовательно, количеству вкладок, которые нам нужно добавить. Вот эта часть:

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

Это либо соответствует (, толкая его на2 стек, либо совпадает с a ), извлекая последний захват из 2стека, либо совпадает с чем-то другим и оставляет стек без изменений. Поскольку круглые скобки гарантированно сбалансированы, нам не нужно беспокоиться о попытке выскочить из пустого стека.

После того, как мы прошли через строку, подобную этой, и нашли необработанную, (чтобы остановиться на ней, упреждающий элемент затем переходит к концу строки и захватывает вкладки в группу 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));}

Ungolfed:

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->.... Обратите внимание, что входные данные должны быть допустимой строкой Julia, поэтому знаки доллара ($ ) должны быть экранированы.

Ungolfed + объяснение:

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

Любые предложения приветствуются!

Алекс А.
источник
4

Хаскелл, 83 81

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

очень бесплатное решение.

гордый хаскеллер
источник
Вы можете просто уронить h=.
Уилл Несс
3

Perl, 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/)/)/'
hmatt1
источник
3

Python 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.
Деннис
источник