格式化类似Lisp的语法


23

背景

(根据真实的,令人费解的故事)

在我的时间里,我经常玩Lisp和类似语言。我已经与他们一起书写,运行它们,对其进行了解释,对其进行了设计,并让机器为我为它们编写了……如果有什么困扰着我,那就是Lisp不符合我的特定格式样式。

不幸的是,每当复制和粘贴代码时,一些文本编辑器(cough XCode cough)都会剥离我漂亮的制表符和空格。

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

(哪里ABCDE有任意函数)

一些文本编辑器将此可爱的代码分割到了下面:

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

真是一团糟!这是不可读的!

帮帮我吗?

挑战

您在此挑战中的目标是采用一系列功能,这些功能以换行符分隔,格式如下所述,并返回更加美观的布局,突出可读性和优雅性。

输入

我们将Farity N参数的函数定义为类似于以下内容的构造:

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

G1, G2, ..., GN自身的功能在哪里?一个arity 0函数A很简单(A),而arity 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
亚历克斯A.

@AlexA。万岁!我的梦想已经实现。:D
BrainSteel 2015年

如果没有函数名,例如()
coredump

缩进必须大于等于3个空格,还是制表符可接受?
isaacg 2015年

@isaacg在这种情况下,可以假定所有函数都已命名。而且,无论您的OS /语言定义为水平制表符,都可以。如果使用空格,则必须至少有3个。我将在可以使用计算机时澄清这一点。谢谢!
BrainSteel 2015年

Answers:


9

Pyth,24 20 19 18个字节

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

每行增加一个计数器,计算到目前为止遇到的右括号的总数,然后从计数器中减去它。然后我们按counter制表符缩进。


@Downvoter关心解释吗?
orlp 2015年

我没有投票,但是这*4是一个硬编码和多余的首选项。FN.z+*ZC9N~Z-1/N\)使您可以使用编辑器的缩进宽度并节省一个字节。
Cees Timmerman 2015年

我同意,制表符要短一个字符。\<tab>C9
isaacg 2015年

9

Common Lisp- 486414字节(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. 输入格式的限制较少:您可以读取任意格式的输入,而不仅仅是“每行一个功能”(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`Just会激活单行(或“点全部”)修饰符,以确保.也匹配换行符。

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

现在,我们还将该选项卡之后的每个字符都转换为一个选项卡。这使我们在字符串的末尾有足够数量的制表符,而到目前为止还没有修改原始字符串。

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

这是解决方案的关键。的ms激活的多线模式(使得^行的开始处相匹配)和单行模式。将+告诉视网膜不断重复这种替换,直到输出停止变化(在这种情况下,这意味着,直至图案不再匹配字符串)。

模式本身将输入的前缀与未处理的前缀匹配((即,(它之前没有任何制表符,但应该有任何制表符)。同时,它确定带有平衡组的前缀的深度,从而使堆栈的高度2与当前深度相对应,因此与我们需要附加的制表符数量相对应。这部分是:

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

它要么匹配一个(,将其推入2堆栈,要么匹配一个),从2堆栈中弹出最后一个捕获内容,或者匹配其他内容,使堆栈保持不变。由于括号可以保证平衡,因此我们不必担心试图从空堆栈中弹出。

在我们经过这样的字符串并找到未处理(的终止点之后,先行跳转到字符串的末尾,并3在从2堆栈弹出直到其为空之前将选项卡捕获为组:

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

通过+在其中使用in,我们确保只有在匹配项中至少插入一个制表符时,模式才匹配任何内容-这样可以避免在存在多个根级函数时出现无限循环。

<tab>+$
<empty>

最后,我们只是去除了字符串末尾的那些helper选项卡以清理结果。


这很酷。做得好!看到视网膜始终是我的荣幸。
BrainSteel 2015年

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 2015年

2
您能if(c<11)代替使用if(c==10)吗?
Digital Trauma 2015年

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字符串,因此$必须对美元符号()进行转义。

取消+说明:

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

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--just移动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.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.