根据Unix哲学,我有一个项目,其中包含一堆使用bash脚本捆绑在一起的小程序。他们的交换格式最初看起来像这样:
meta1a:meta1b:meta1c AST1
meta2a:meta2b:meta2c AST2
:
- 分隔的字段是元数据,而AST
是脚本按原样传递的s表达式。这很好,因为我可以使用cut -d ' '
从AST中分割元数据,并cut -d ':'
来挖掘元数据。但是,我需要添加一个包含空格的元数据字段,这会打破这种格式。由于没有字段使用制表符,我切换到以下内容:
meta1a:meta1b:meta1c:meta 1 d\tAST1
meta2a:meta2b:meta2c:meta 2 d\tAST2
由于我设想将来会添加更多元数据字段,我认为现在是时候切换到更结构化的格式,而不是玩#34;猜猜标点符号游戏。
而不是分隔符和cut
我可以使用JSON和jq
,或者我可以使用XML和xsltproc
,但是因为我已经在使用s表达式来表示AST ,我想知道是否有一种很好的方式在这里使用它们?
例如,看起来像这样:
(echo '(("foo1" "bar1" "baz1" "quux 1") ast1)'
echo '(("foo2" "bar2" "baz2" "quux 2") ast2)') | sexpr 'caar'
"foo1"
"foo2"
我的要求是:
)
显而易见的选择是使用Lisp / Scheme解释器,但我唯一经验丰富的是Emacs,它太重量级了。也许另一个实现更轻量级,适合这个?
在Haskell中,我玩过shelly,turtle和atto-lisp,但我的大部分代码都花在String / Text / ByteString之间转换,包装/解包Lisp
s,实现我自己的{{} 1}},car
,cdr
等
我已经读过一些关于scsh的内容,但不知道这是否合适。
答案 0 :(得分:1)
您可以尝试使用Common Lisp。
直接使用stdio和最小的样板,因为那样 我的程序读/写数据的地方
(loop for (attributes ast) = (safe-read) do (print ...)
safe-read
应该在读取时禁用代码执行。至少one implementation。除非你完全知道那里有什么,否则不要直接eval
你的AST。可以从shell脚本轻松调用或提供非常引人注目的功能 替代bash的流程调用和流水线
与java -jar ...
一样,您可以启动Common Lisp可执行文件,例如sbcl
,参数中包含脚本:sbcl --load file.lisp
。您甚至可以使用预加载的所有内容(save-lisp-and-die
)转储应用程序的核心或可执行核心。
或,使用cl-launch
自动,可移植地执行上述操作,并生成shell脚本和/或从您的代码生成可执行程序。
尽可能流式传输I / O;即。我宁愿一次使用一个AST 而不是消耗整个输入寻找结束)
如果整个输入流以(
开头,则read
将读取结束)
字符,但实际上很少这样做:Common Lisp中的源代码并未包含在一对括号中的每个文件中,而是作为一系列表单。如果您的流产生的不是一个而是多个s-exps,读者将一次读取一个。
快速轻巧,特别是如果它被调用了几次; 每个AST只有几KB,但它们可以加起来几百MB
速度很快,特别是如果您保存核心。轻量级,众所周知,lisp图像可以占用一些磁盘空间(例如46MB),但这很少成为问题。为什么重要?也许你有另一个关于轻量级含义的定义,因为这与你要解析的AST的大小无关。但是,阅读这些AST应该没有问题。
至少应该在Linux上工作;跨平台会很好
见Wikipedia。例如,Clozure CL(CCL)在Mac OS X,FreeBSD,Linux,Solaris和Windows上运行,32/64位。
答案 1 :(得分:0)
处理稍微不同的任务,我再次发现需要处理一堆s表达式。这次我需要对给定的s表达式执行一些非平凡的处理(提取所使用的符号列表等),而不是选择将它们作为不透明的字符串传递。
我试了一下球拍,感到惊喜;它比我之前使用的其他Lisps(Emacs Lisp和各种特定于应用程序的Scheme脚本)更好,因为它有很好的文档和电池包括标准库。
此类任务的一些相关要点:
(current-input-port)
默认为stdin,(current-output-port)
默认为stdout)。端口使stdio和文件访问与shell一样好用:更详细,但更少的边缘情况。port->string
,file->lines
,read
等,可以轻松地以适当的粒度形式(字符,行,字符串,表达式等)获取数据。 )。read
只返回一个,所以需要迭代/递归才能在流式传输中执行此操作方式。"(\n"
和"\n)"
,然后使用(with-input-from-string my-modified-input read)
获取一个大列表。< / LI>
我发现Racket的启动时间非常慢,所以如果速度是一个问题,我不建议一遍又一遍地调用脚本作为循环的一部分。很容易将我的循环移到Racket中,然后调用一次脚本。