原始脚本如下:
#lang racket
(for ([i (in-range 3)])
(for ([j (in-range 9)])
(display "X"))
(display "n"))
(for ([i (in-range 6)])
(for ([j (in-range 3)])
(display " "))
(for ([j (in-range 3)])
(display "X"))
(for ([j (in-range 3)])
(display " "))
(display "n"))
(for ([i (in-range 3)])
(for ([j (in-range 9)])
(display "X"))
(display "n"))
输出为:
XXXXXXXXX
XXXXXXXXX
XXXXXXXXX
XXX
XXX
XXX
XXX
XXX
XXX
XXXXXXXXX
XXXXXXXXX
XXXXXXXXX
我想知道我是否可以使用这样的DSL重写它:
(define a
"3 9 X
6 3 b 3 X 3 b
3 9 X")
然后:
(interpret a)
来绘制这张图。
有人知道什么是最好的方法吗?
要解决此类问题,请首先描述一种数据类型,它可以捕获DSL中所需的操作,而不是集中于表面语法。一旦你掌握了数据类型,你应该会更容易地处理这个问题。
乍一看,我们可以用您的语言设计3种基本形式:
- 字符串
- 重复
- 排序
我们可以用原始字符串和结构来表示这个不相交的类。让我们把这个类作为一个整体称为"pexpr",表示"可打印的expr"。代码中:
;; An pexpr is one of the following:
;; * a primitive string,
;; * a seq, or
;; * a repeat
(struct seq (bodies) #:transparent) ;; bodies is a list of pexpr
(struct repeat (n body) #:transparent) ;; n is a number, body is a pexpr
将一些辅助函数作为缩写可能会有所帮助,因为"seq"one_answers"repeat"本身就有点冗长。
;; For convenience, we define some abbreviations s and r for seq and repeat,
;; respectively.
(define (s . bodies)
(seq bodies))
(define (r n . bodies)
(repeat n (seq bodies)))
您的示例"I"字符串可以这样写:
(define an-example
(s
(r 3 (r 9 "X") "n")
(r 6 (r 3 " ") (r 3 "X") "n")
(r 3 (r 9 "X") "n")))
请注意,这种编码具有换行符的显式表示,仅从表面语法来看,换行符是隐式的。然后,解析器的工作就是在表面语法中提取行并将其转换为pexpr,但这应该不会太难。希望如此。:)
无论如何,explore函数变成了一个为如下模板填充细节的问题:
(define (interpret pexpr)
(match pexpr
[(? string?)
...]
[(struct seq (bodies))
...]
[(struct repeat (n body))
...]))
其中的"…"s应该很容易填写。
如何设计程序和程序设计语言:应用和解释中描述了这种解决这类问题的方法。我建议你看看它们:它们是好东西。
当然,这看起来是可行的。这主要是一个解析问题。我会这样把它打碎的。每个输入行指定一个输出行块。在Racket中找到一个很好的表达方式。举一些例子,确保它涵盖了你希望它涵盖的内容。接下来,我可能会编写渲染其中一个块的函数。大多数情况下,我会先做一次,这样我就可以对看到产出感到满意。然后,我将编写一个函数,该函数获取这些块规范的列表,并将它们全部输出。然后,我将编写一个函数来解析一行输入。看起来您可以使用空白分割这些行(例如,使用"regexp split"),并且然后使用ad-hoc解析器处理这些列表。这是我认为我最有可能出错的部分,在编码之前我会写一堆测试用例。最后,您需要一个函数,该函数在每一行输入上调用这个解析器,然后将生成的块规范发送给显示函数。