通过在球拍中设计解释器来重写此脚本

时间:2012-09-10 05:10:21

标签: lisp scheme racket

原始脚本是这样的:

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

绘制此图表。

有谁知道最好的方法是什么?

2 个答案:

答案 0 :(得分:8)

要解决此类问题,请首先描述捕获DSL中所需操作的数据类型,而不是专注于表面语法。一旦掌握了数据类型,就可以更轻松地解决问题。

乍一看,我们可以用您的语言设计3种基本形式:

  1. 字符串
  2. 重复
  3. 测序
  4. 我们可以用原始字符串和结构来表示这个不相交的类。让我们将这个类称为“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”和“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")))
    

    请注意,此编码具有新行的显式表示,仅从表面语法中隐含。然后它成为解析器的工作,在表面语法中取代线并将它们变成pexprs,但这不应该太困难。希望。 :)

    无论如何,解释函数就变成了填写这样一个模板细节的问题:

    (define (interpret pexpr)
      (match pexpr
        [(? string?)
         ...]
        [(struct seq (bodies))
         ...]
        [(struct repeat (n body))
         ...]))
    

    其中'...'应该很容易填写。

    这种问题的解决方法由How to Design ProgramsProgramming Languages: Application and Interpretation描述。我建议看看它们:它们是好东西。

答案 1 :(得分:1)

当然,这看起来很可行。这主要是一个解析问题。我会像这样分手。每个输入行都指定一个输出行块。找出一种在Racket中表示的好方法。举一些例子,确保它涵盖了你想要覆盖的内容。接下来,我可能会编写渲染其中一个块的函数。大多数情况下,我先做那个,这样我才能满足于看到输出。然后,我会编写一个函数,列出这些块规范并将它们全部输出。然后,我会编写一个解析单行输入的函数。看起来您可以使用空格分割这些行(例如,使用" regexp-split"),以及 然后使用ad-hoc解析器处理这些列表。这是我认为我最容易出错的部分,在编写之前我会编写一堆测试用例。最后,您需要一个在每行输入上调用此解析器的函数,然后将生成的块规范发送到显示函数。