zoukankan      html  css  js  c++  java
  • 为脚本语言平反JavaScript篇(2)

    http://blog.csdn.net/aimingoo/archive/2009/09/08/4532394.aspx
    (书接上回,开讲!)

     

    一、QoBean的元编程框架

    ===========

    现在开讲“DSL in JavaScript,with QoBean’s meta programming framework”。先说说QoBean的元编程框架。这是一个仅仅100来行的小小框架,里面有几个关键函数是我们在讲DSL时要用到的:

    1. Weave(where, code):表示对于一个函数f,在指定where的位置,替换成代码code。如果where是正则表达式,则code中可以使用获取匹配;如果where是字符串,则表明将第一个查找到的该字符串替换成code。
    2. Block(func, tag):对函数func(或直接是它的代码文本)进行结构分析。tag标志为['body', 'param', 'name']之一时,返回该函数源代码相应位置的一个字符串;tag标志为’scope’时,返回一个字符串str,用eval(str)可以在当前位置生成一个新的、具有该代码上下文位置上的闭包的函数;tag标志为’anonymous’时,直接返回一个函数,但该函数是全局函数。对于tag是['scope','anonymous']之一的情况,所得到的新函数是传入参数func的一个完全相同的副本,只是新函数所在的闭包位置不同。
    3. Unique(obj):生成obj对象的一个唯一化的实例。新实例obj2具有原obj对象的全部属性,但修改新对象obj2的成员时,不会影响到原obj对象。这个过程,与Block(func, tag)调用时,tag为['scope','anonymous']之一的情况有类似之处:都是生成一个新的复制,不同的是Block针对函数,Unique针对对象。
    4. Scope(obj, func):使函数func(或直接是它的代码文本)执行在obj的对象闭包中。当func执行在obj的闭包中之后,func所访问到的变量名,即是obj的属性;func访问到的函数名,即是obj的方法。
    5. Owner(obj, func):使函数func(或直接是它的代码文本)执行在以obj为this对象的环境中。在JS中,当一个函数执行时,this关键字要么指向全局的window对象(func是普通函数);要么指向方法所属的对象实例(func是对象方法)。Owner()函数用于改变这种关系,使得函数func在执行时,this关键字总是指向obj。——一般来说,这个功能在其它的JS框架中被实现为Function.prototype.bind()。

    QoBean的元编程系统中还有一些其它的函数,但对于我们讲DSL不关键,所以先不讨论。除了上述讨论的函数之外,我们在DSL部分不会用到其它的任何函数,也不会用到某种JavaScript引擎的独特功能——甚至不会用到arguments.caller。所以,我们在这里实现的DSL,可以跑在ECMS Script标准中的任何一个JavaScript引擎之中。

    最后综述一下上面的五个函数。他们其实对于Meta Programming有着特殊的含义。

    1. Block(),其实是一个简单的Parser。他能够快速分析一个代码文本块,以使重新组织它。相当于我们在书写代码时的重构、改写。
    2. Unique()与Scope()是功能近似的函数,只是一个作用于函数,一个作用于对象。作用是得到一个复制,相当于我们书写代码时的ctrl + C操作。
    3. Scope()与Owner()也是功能近似的函数,前者用于改变代码的上下文,相当于书写代码时移动一段代码(例如把局部函数变成全局公共的),有点类似于ctrl+V操作;后者用于改变代码(作为对象方法时)的属主,相当于*类中的方法,或重构,或范型等等。

    我们注意到,上面几个函数,事实上模拟了我们书写代码时的很多行为。在前面讲到过的文章中,我说到过“那么QoBean如何定义‘元语言’呢?QoBean对此有两项解释”:
      – 元语言定义程序(program)的*础元素:算法与数据结构
      – 元语言说明编程(programming)的*本方法:代码的组织形式

    这里的五个函数,就是第二项解释中的“编程(书写程序)的*本方法”的抽象、实现。

     

    二、DSL的*本设计

    ===========

    现在我们来考虑一个“通用DSL”应该是什么样子,也就是如何设计它的问题。首先,它是一种语言——这很废话对吧?哈哈。其实不是,这是一个语言,表明它应该有语法、语义、语用的问题。语法就意味着需要一个解析器(parser);语义就意味着对于语言中的关键字要有功能实现,即要有执行器(evaluator);语用,就意味着说相同的话——相同的代码文本,在不同的环境下效果未必一致,所以也就意味着要有环境(environment)设定,亦即是“上下文相关文法”或“上下文无关文法”的问题。

    这三个方面的问题有点令人挠头,但用个类比,就挺简单的。例如说“吃饭了吗”这句话,首先就包括汉语语法的问题,例如省略主语、疑问句和主谓结构等等。所以,我们可以改变一种新语法来陈述它,例如“饭,吃了吗”,或“吃了吗,饭”。这些,只是语法上的变化。说话的、写程序的人,先约定一种规则,然后按这个规则来理解它,就行了。

    那么语用呢?或者说所谓的“上下文相关/无关”是什么意思呢?同样的,上面这句话,如果是早晨我跟你碰面在公司楼下,我问这句话的意义,跟说“Hello”,或者“今天天气不错”其实差不多,只是个问候语。但如果是***同学在汶川问某个老乡,那可就真是问“有饭吃没”。话是一样的话,语法是一样的语法,放在不同的环境中,语义上是有差别的。这个,就是语用学讨论的问题。我们用的计算机很笨,没有人那么复杂的思维能力,所以一般来说,要求我们设计的语言是“上下文无关的”,以便于将来开发出来一个机器人,你问“吃饭没”,他真的能回答你“电能充足”,而不是做泪流满面状。

    所以,回到DSL的设计上来。所谓一个语言,也就是“通过某种规则来解析(parser)一段文本,将它执行(evaluator)在某个上下文环境(environment)”中。这个体系中,有一个东西是不变的,也就parser/evaluator/environment的关系。所以,一个新的dsl语言的产生过程,可以描述成这样的一个模式:

    而这个语言执行——或说是讲述、表达、运行、生效——起来,则可以描述成下面这样一个模式:

    至于这个语言的规则部分,是parser负责的;表述效果部分,是evaluator负责的。而要让JavaScript DSL对这个新的dsl起来“维护”作用的,应该是对语境的(语用的)设定——简单的说,我们要帮助新的语言管理上下文环境,其它的则由“创建语言”的人来做。

     

    三、DSL的*本实现

    ===========

    由于在

    中,dsl最终是需要有调用能力的(表达成”dls(…)”的形式。所以

    1. DSL函数应该返回一个函数,并且它被执行在environment环境中;
    2. 考虑到dsl应该与JavaScript的环境无关,它的this对象应该指向environment而非window。

    而上述两件事情,在QoBean meta programming中用两个函数来实现,即:

    OK. 这个结构*本就完成了。但是还有个问题,就是上面Scope()调用中的“…”,他表明我们要在environment中执行代码的内容和方法。如何执行呢?简单的说,就是“先分析输入的代码,然后调用执行器执行它”。要实现这两个步骤,我们可以:

    1. 把它连在一起,
    2. 放在environment中去run一下。

    就好了。这个“连在一起”的事情,就是Weave()函数能做的。所以完整的DSL()函数的代码是下面这样:

    其中的Weave()调用表明:

    1. 将parser()函数的body部分,放到evaluator()函数的开始部分之前执行;
    2. 将上述的结果(代码文本),放在environment的scope闭包中执行。

    OK。我们的“通用DSL语言生成器”就做完了。它只有两行代码。

     

    四、示例

    ===========

    这样的一个示例其实很简单。比如说我们想要有一种语言,它具有如下的性质:

    1. 可以调用一些函数;
    2. 可以访问一些预定义的值;
    3. (为了方便),我们假定它跟JavaScript的*本语法是一样的。

    我们简单的用DSL来实现一下它,以便对这个QoBean’s DSL framework有个概要认识。它还相当不完善,下一段落里,我们再来补全这个DSL系统。

    实现上面的这个小小的语言的方案是:

    OK。结果出来了,显示97、194两个值。对于dsl()后面执行的代码来说,环境environment为他们准备了min/max/show/calc这四个标识符;myeval()提供了执行能力;myparser()则用于将dsl()调用传入的函数中的代码块取出来——之所以传入一个函数,是这样一来,就可以省了一个“语法分析器”(能当函数传入,当然是能通过JavaScript的语法分析过程的)。

    先到这里,吃饭去也。下午来继续扩充这个过程,然后我们就知道一个完整的……相当完整的DSL()实现……其实也并不复杂了。

    下一篇:

    http://blog.csdn.net/aimingoo/archive/2009/09/08/4532567.aspx

  • 相关阅读:
    Java Web 网络留言板2 JDBC数据源 (连接池技术)
    Java Web 网络留言板3 CommonsDbUtils
    Java Web ConnectionPool (连接池技术)
    Java Web 网络留言板
    Java Web JDBC数据源
    Java Web CommonsUtils (数据库连接方法)
    Servlet 起源
    Hibernate EntityManager
    Hibernate Annotation (Hibernate 注解)
    wpf控件设计时支持(1)
  • 原文地址:https://www.cnblogs.com/encounter/p/2188595.html
Copyright © 2011-2022 走看看