读书人

racket reader引见

发布时间: 2012-09-08 10:48:07 作者: rapoo

racket reader介绍

racket reader介绍:Table of Contents1 定义语言的重要性:2 如何定义新的语言3 Module Language4 Reader extensions:5 使用 #lang language 表达式:6 使用 #lang reader 表达式:7 使用 #lang exp 表达式:8 杂项:

DSL(Domain-specific language)是解决软件复杂性一个非常重要的手段。但是不是说所有语言都可以方便简单的定义DSL。

lisp/scheme作为meta programming language天生擅长定义DSL。

分层的软件设计是大家常用的设计方法,下层为上层提供接口功能。

但在lisp/scheme中,每层都可以认为是一个新的language,每层在下一层的基础上定义出新的language。我们可以方便的定义DSL。

    reader layer: reader layer可以认为是编译器的parse阶段,结果得到的是语法树。

    racket提供了定义和扩展reader layer的方法。

    reader layer通过parse源代码,生成#'(module module-name lanuage …)的语法数据。

    expand layer: scheme的macro作用的阶段,它的signature为输入syntax object,输出一个syntax object。简单来理解可以认为是一个source-to-source的过程。 scheme的macro的强大威力主要体现在:
      定义新的控制结构。inline code:想想在strict mode代码中如何定义if表达式。定义新的类型,参数化代码….

    scheme的宏提供了对语言的语法扩展(syntactic extensions)能力,但是宏有如下限制:

      宏不能限制宏所在上下文的语法,譬如:宏不能限制使用某些语法;宏只能用语言的词法,语法规则来扩展语言。譬如:宏不能让我们像写js代码一样写scheme代码。

    宏只能在expander layer对语言进行扩展。

结合reader layer和expand layer的扩展能力,我们可以方便的定义出任何新的语言。

    Module Language
    ;; module module-name-blabla> (module module-name-blabla racket     (provide (except-out (all-from-out racket) lambda)     (rename-out [lambda function])));; module test> (module test 'module-name-blabla    (map (function (points) (case points                                  [(0) "love"] [(1) "fifteen"]                                  [(2) "thirty"] [(3) "forty"]))    (list 0 2)))> (require 'score)'("love" "thirty")

    module 语法:module-name-blabla表达定义的module的名字,之后的 racket 表示一个module-path,他提供了这个module最初的imports,我们叫他 initial-import module. 他决定了这个module(module-name-blabla)最初有哪些名字绑定,譬如require, except等。这就好像module-path定义了我们 这个module所使用的语言一样。

    Implicit Form Binding: 我们可以以racket这个module为基础,定义自己的module language:
    > (module just-lambda racket    (provide lambda))> (module identity 'just-lambda     (lambda (x) y))eval:2:0: module: no #%module-begin binding in the module'slanguage in: (module identity (quote just-lambda) (lambda(x) x))

    #%module-begin是一个隐式规则,用来包裹住module的body.譬如上面的例子,展开的样子应该是:

    #'(#%module-begin (lambda (x) y))

    类似 #%module-begin这样的隐式规则的还有 #%top:表示free-variable。 #%app:表示函数调用。

    > (module just-lambda racket    (provide lambda #%module-begin #%app #%datum))> (module bla 'just-lambda    ((lambda (x) y) 10))

    这种隐式规则有什么用呢?我们可以重新定义这些隐式规则,这样我们可以修改他们隐式的作用。

    譬如我们想定义一种只允许一个参数的lambda定义(lambda-calculus):

    > (module lambda-calculus racket     (provide (rename-out [1-arg-lambda lambda]                          [1-arg-app #%app]                          [1-form-module-begin #%module-begin]                          [no-literals #%datum]                          [unbound-as-quoted #%top]))   (define-syntax-rule (1-arg-lambda (x) expr)     (lambda (x) expr))   (define-syntax-rule (1-arg-app e1 e2)     (#%app e1 e2))   (define-syntax-rule (1-form-module-begin e)     (#%module-begin e))   (define-syntax (no-literals stx)     (raise-syntax-error #f "no" stx))   (define-syntax-rule (unbound-as-quoted . id)     'id))> (module ok 'lambda-calculus    ((lambda (x) (x z))     (lambda (y) y)))> (require 'ok)> (module not-ok 'lambda-calculus    (lambda (x y) x))eval:4:0: lambda: use does not match pattern: (lambda (x)expr) in: (lambda (x y) x)
    Using #lang s-exp: 直接使用 #lang language 比较麻烦,因为#lang语法可以让我们以不同的方式定义新的语言。而 s-exp的方式则仅允许我们以 module language的方式进行meta-language。

    #lang s-exp module-name

    form

    这实际上等价于:

    (module name module-name form …)

    reader: Racket使用 #reader 在reader layer进行语言的扩展。 #reader 得到的结果是能被expander layer处理的syntax object。

    For example:

    #lang racket/base  ;;five.rkt(provide read read-syntax)(define (read in) (list (read-string 5 in)))(define (read-syntax src in) (list (read-string 5 in)))

    然后我们的程序可以这样使用上面的module:

    '(1 #reader"five.rkt"234567 8) ;;结果为'(1 ("23456") 7 8)

    readread-syntax 有什么区别:
    字面来理解:read用来parse data,read-syntax用来parse program。准确的说read将被ra cket的 read 函数所调用, read-syntax将被racket的 read-syntax 调用。没有强制要求read和read-syntax行为必 须一样,但最好是一样,以免造成 混淆。

    思考,如果一个模块这样写:

    (module bla racket   #reader"yourreader.rkt" body ...);yourreader.rkt返回#‘(#'body ...) 这样就可以定义这个模块新的语法。
    Readtables:

    如果从头开始写一个parser,相信不会有人愿意。但是如果我们可以基于原来的reader,定制 一些我们希望覆盖的规则呢?譬如在原来的symbol-expresstion的基础上增加一个对$…$的解析呢?

    (+ 1?3+2 10)

    这时我只希望写如何parse $1*3+2$这一小段字符串,其他的还是交给racket去parse。readtable可以 做到这一点!

    Racket reader是一个递归下降的parser,readtable将字符映射到parsing handler。比如说: 默认的readtable将 ( 映射到一个handler,这个handler 会一直parse知道找到对应的 ).

    make-readtable 函数构造出新的readtable作为对已经存在的readtable的扩展。

    (make-readtable (current-readtable)                #\$ 'terminating-macro read-dollar)

    parameter current-readtable 决定了哪个readtable被使用。 make-readtable 第一个参数表示新构造出来的readtable在哪个readtable基础上构造出来的, 之后是一系列字符到handler的参数,上面例子只有一对:#\s read-dollar。

    #lang racket(require syntax/readerr         (prefix-in arith: "arith.rkt"))(provide (rename-out [$-read read]                     [$-read-syntax read-syntax]))(define ($-read in)  (parameterize ([current-readtable (make-$-readtable)])    (read in)))(define ($-read-syntax src in)  (parameterize ([current-readtable (make-$-readtable)])    (read-syntax src in)))(define (make-$-readtable)  (make-readtable (current-readtable)                  #\$ 'terminating-macro read-dollar))(define read-dollar  (case-lambda   [(ch in)    (check-$-after (arith:read in) in (object-name in))]   [(ch in src line col pos)    (check-$-after (arith:read-syntax src in) in src)]))(define (check-$-after val in src)  (regexp-match #px"^\\s*" in) ; skip whitespace  (let ([ch (peek-char in)])    (unless (equal? ch #\$) (bad-ending ch src in))    (read-char in))  val)(define (bad-ending ch src in)  (let-values ([(line col pos) (port-next-location in)])    ((if (eof-object? ch)         raise-read-error         raise-read-eof-error)     "expected a closing `$'"     src line col pos     (if (eof-object? ch) 0 1))))> #reader"dollar.rkt" (let ([a $1*2+3$] [b $5/6$]) $a+b$)35/6
#lang language  ;; equal to#reader language/lang/reader

那么language/lang/reader.rkt需要provide read和read-syntax。

read-syntax需要返回一个(module name module-path…) form的syntax,这个syntax在expander 的时候又会使用module-path作为module-language。

这时最复杂的定义language的方式,因为我们同时需要关心reader和module language。

#lang reader "reader.rkt"

等价于:

#reader "reader.rkt" 

使用这种语法,我们可以不用使用#reader也不用使用#lang这种比较复杂的定义language的方式。

#lang exp moduleA

等价于

(module name moduleA …)

通过#lang exp我们可以方便的定义module language。

racket提供了简化我们在reader layer和expand layer层扩展工作的工具包,他们统一称作: Syntax: Meta-Programming Helpers。但是这个包个人感觉非常复杂,很难理解和使用。

Author: ZhangPing

p.zhang.9.25@gmail.com

读书人网 >编程

热点推荐