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

(书接上回,继续!)

五、这个DSL框架有什么问题?

=============

有什么问题吗?有一点,并不严重。比如说,我们在Env中声明了一些属性和方法。对于Env这个对象
Env = { max: 100, min: -3, calc: function(adj) { … } }

我们要在calc()方法中访问max/min属性,应该写成“this.max/this.min”,这一则是不方便,另外,在用户的dsl代码中还不得不考虑“当前this是谁”的问题。这是问题之一。

第二个,我们传入了一个evaluator(),相当于脚本执行器,那么我们能不能在dsl()代码中也使用这个执行器呢?也就是说,我们的dsl不单是“domain-specific language”,也可以是一个“domain-script language”的。

第三个问题,我们是不是需要一个类似在JavaScript中的window对象的东西,以便能引用到执行环境的全局。

这三个问题都应该是在DSL()层面解决的。简单说来,第二、三个问题,实质是在初始化环境environment,使之具有某些在dsl代码中能访问到的性质。所以很容易处理:
function DSL(environment, evaluator, parser) { var dsl = Scope(environment, Weave.call(evaluator, /^/, Block(parser, ‘body’)+’/n/n’)); environment.system = environment; environment[Block(evaluator, 'Name')] = dsl; return Owner(environment, dsl); }

同理的,用户可以在上面这里对environment加入更多性质,这些都是可以在用户的dsl(…)中访问到的。以上面为例,当用户传入的执行器evaluator是一个具名函数的时候,则该函数名会成为dsl(…)环境中的可用的执行函数(类似于exec, execScript或eval等)。例如:
function myeval() { … } dsl = DSL(aEnv, myeval, aParser); dsl(function(){ myeval(…); });

注意在dsl()访问到的myeval()方法,其实不是用户原始的myeval(),而是上述dsl变量的一个引用。这个,从DSL()函数的实现中可以看到。

接下来,就是上面三个问题中的第一个,亦即是在calc()方法从必须使用this.max/this.min的问题。事实上,这是因为声明calc方法的时候,该函数位于Env变量所在的全局闭包里面。这样,它就默认只能访问到全局的变量、标识符。所以,解决这个问题的方法,仍然和前面一样:改变它的闭包位置——使用Scope()函数。如下:
function DSL(environment, evaluator, parser) { var dsl = Scope(environment, Weave.call(evaluator, /^/, Block(parser, ‘body’)+’/n/n’)); for (var n in environment) { if (environment[n] instanceof Function) environment[n] = Scope(environment, environment[n]); } … }

现在有了一个新的、完善的DSL()。使用方法与前面是一致的。比如:
Env = { max: 100, calc: function(adj) { return max + adj }, //可以直接访问max了 show: function(msg) { alert(msg) } }; dsl = DSL(Env, myeval, myparser); dsl(function() { show(calc(30)); //显示130 });

最后,留意一下当调用DSL()的时候,我们标出了”Env”这个全局变量。注意的是,我们直接使用了这个对象。那么它与使用Unique(Env)有什么不同呢?答案是,直接使用Env时,在dsl(…)中的代码可以直接修改到Env中的成员,而如果使用Unique(Env),则dsl(…)中的代码只会修改到Env的一个副本。这样一来,我们就有机会为不同的dsl语言提供各各独立的环境了——这有点象沙箱。

六、变量泄漏?

========

在JavaScript语言中有一个“根深蒂固”的问题,就是“当在函数内访问一个不存在的变量时,引擎会试图在全局变量环境中打找该变量”。这通常是很多很多烂系统的根源。对于我们上面的dsl语言来说,系统其实只给出了五个标识符:max/calc/show/system/myeval。其中的后面两个,是DSL()函数在“语言引擎层面”提供的,其它的则是Env环境变量提供的。“变量泄漏”带来的直接问题是,对于上面的这个例子,dsl(…)中除了能访问这五个标识符之外,还能访问全局的window/String/Number/Math/RegExp/NaN等等预定义对象和属性。而这,可能根本就不是我们的dsl语言需要的。

这怎么办呢?

由于Unique()得到了Env环境对象的一个副本,而且在dsl(…)中无法通过这个副本来修改原始的Env的成员,也不能delete它。所以如果我们在Env的属性中加入这些“受保护的标识符”,那么dsl(…)就只能访问到Env的这些属性,而不会访问到全局里面的了。下面的代码简单地实现这一效果:

Env = { … }; protoected = ['window', 'setTimeout', 'setInterval', //window和Global的成员... 'Array', 'Object', 'Function', // 全局的对象构造器... 'null', 'undefined', //引擎定义的,类似系统关键的... 'Env', 'tinyParser', 'dsl', 'myeval' //用户代码环境中的... ]; protoected.forEach(function(item) { this[item] = undefined}, Env); dsl = DSL(Unique(Env), myeval, myparser); dsl(function() { show(Array); // 显示undefined Array = ‘local defined’; show(Array); // 显示local defined });

七、evaluator/parser是不是太简单了?

=================

当然。我们在evaluator, parser中基本什么也没有做,当然是相当简单的。如果你要做一个完整的DSL,那么你得花一些工夫来做语法解析,并实现在语法树的基础上的代码执行、运行环境的维护等等。我QoBean的DSL()中,主要是提供了一个运行你的代码的基础语言环境,有点象是——嗯——沙箱。

当然,除了沙箱的基本功能之外。DSL()通过environment来维护给用户代码的一组基本标识符(或称为保留字),并保证用户在不同的environment之间不会相互影响。

除了上述的基本描述之外,我们最后再关注一下evaluator和parser的实现。对于下面的代码:
function myeval(source) { return eval(source); } function myparser(source){ source = Block(source); } dsl(function() { show(min+max); show(calc(min+max)); });

实际上的效果是dsl()将红色显示部分的函数作为一个一个参数source,传入myparser()和myeval()。parser通过Block()取出这个函数代码的body部分,然后交给myeval()中的eval()函数执行。也就是说,我们在DSL()中调用Weave()的效果就是,将myparser()和myeval()并在一起,变成了:
function(source) { source = Block(source); return eval(source); }

而dsl()最终执行的就是上面这个匿名函数。更进一步,在environment上也会有一个名为’myeval’的方法,指向这个匿名函数。

但是,首先这里就有一个不小的问题:’source’在这里也是一个标识符。在eval(…)中执行时,代码是可以感知到这个标识符的——而对于dsl(…)中的用户代码,source可能是另外需要的一个标识符,所以这里我们要想办法屏蔽掉对这个变量名的依赖。这其实处理起来很简单:
function myeval(source) { return eval(arguments[0]); } function myparser(source){ arguments[0] = Block(arguments[0]); }

你应该注意到,我们用arguments[0]就可以简单地绕过一个入口参数名的使用了。这个,很简单,也很实用。

接下来,我们总不能要求用户每次执行dsl(…)时都要传入一个函数吧?我们最终声明的用户的DSL可能是相当怪异的、完全不符合JS的语法的,根本就不能写到一个函数中去,又该怎么办呢?这个问题,显然的——首先的——他该是parser的问题。因此我们也就简单地讲一下扩充myparser()的方法。比如说,我们想实现下面的效果:

  1. 当dsl(…)传入一个字符串时,让myeval()直接执行该字符串;
  2. 当dsl(…)传入一个函数,但函数体内是完整的整块注释时,让myeval()执行这个注释块。

例如如下的调用:
=========
// 示例1 dsl(”/ apple.more->hi(form) % / tree.clear+>do(function() .. ). / “); //示例2 dsl(function(){/* apple.more->hi(form) % tree.clear+>do(function() .. ). */});

现在我们需要进一步完善我们的myparser(),提供一个基本的模式来支持这种设计。简单的方法如下:
function tinyParser(){ switch (typeof arguments[0]) { case ‘function’: arguments[0] = Block(arguments[0]); arguments[0] = arguments[0].replace(/^/s*///*([/d/D]*)/*///s*$/, ‘$1′); break; } /* 现在你需要 1、对字符串arguments[0]进行语法分析,形成语法树或符号某种规则的代码块, 2、将结果传回arguments[0]。 */ }

当然,由于代码的语法规则改变了,所以myeval()的设计也应该发生相应的变化了。而这些,就应该是DSL语言设计者的工作,而不是QoBean在DSL()框架上要考虑的事情了。

八、终结:DSL,关键不在用什么语言实现,而在于为什么Domain设计什么样的语言

=============

我们用Javascript,只写了不到了10行代码,就实现了一个DSL()的通用框架,但是,我们却没有做出对任何一个真实的Domain有意义的DSL。对于Ruby、Python、Erlang还是Scala,或者更原始的LISP或更新的F#这些基础语言,对他们的选择更多的只是喜好或者出于某些局部的优异与方便的考虑,与我们“设计一个DSL”是没有多大的关系的。一个DSL的设计,在于对领域的、领域相关业务的分析与抽象。在这些分析、抽象的基础上,进行语法设计、语义定义,最终才表现为“怎样的一个语言”。当我们看到这个“表现”的时候,整个DSL的设计都已经结束了——我们接下来只需要构建基本运行库(runtime library),以及其上的应用逻辑就好了。所以,大多数看到某个DSL的人,只是它的实现者和使用者,而不是它的设计者。多数人只是埋头于使用,或者激情于评说,而忘了看看“一个具体DSL的背景”。

例如,难道DOS批处理不是一个DSL吗?10行的JavaScript难道不就是一个完整的DSL framework吗?如果是,那么我们还有必要讨论“什么是DSL”,以及“怎样的DSL开发环境更好”的问题吗?我们是不是看看“我们在什么Domain”,以及“这个Domain如何描述、如何结构化和如何逻辑驱动之”,这些问题是不是才是更关键的?

上面两个示例中都有一个相同的dsl代码片断——这是一种假想的、完全不符合javascript的规范的新语言。示例1是通过一个字符串传给dsl()的,示例2仍然是通过一个函数,但函数体内是从/*..*/的一个注释块。

转载于:https://www.cnblogs.com/encounter/archive/2009/09/08/2188594.html

为脚本语言平反-JavaScript篇(3)相关推荐

  1. 脚本语言语言脚本语言:Shell , JavaScript、VBScript、Perl、PHP、Python、Ruby、Lua

    今天一直在研究脚本语言语言之类的问题,上午正好有机会和大家共享一下. 脚本语言:Shell , JavaScript.VBScript.Perl.PHP.Python.Ruby.Lua 工作控制语言和 ...

  2. 脚本语言【JavaScript基础】JavaScript函数:声明+调用

    文章目录 在javaScript脚本语言当中:函数使用也是分为两部分:声明+调用 数组 在javaScript脚本语言当中:函数使用也是分为两部分:声明+调用 函数在JS当中声明方式有两种 关键字fu ...

  3. 大数据系列博客之 --- 深入简出 Shell 脚本语言(提升篇)

    首先声明,此系列shell系列博客分为四篇发布,分别是: 基础篇:https://www.cnblogs.com/lsy131479/p/9914747.html 提升篇:https://www.cn ...

  4. 从头来过教你PHP脚本语言(先导篇)

    为新手量身打造的,可以没有编程基础.同时, 这篇文章算是,对这一系列的填坑. 抱歉,写的时候,没有大众化,现在来填坑. 作者的锅,sorry 一.var_dump() var_dump() 函数用于输 ...

  5. javascript脚本语言_10分钟快速掌握Javascript核心特性

    JavaScript的历史版本 其中ES5个人认为是使用时间最长也是最稳定的版本.基于该版本衍生出来的很多经典框架诸如ext,propertype.js,jquery,easyUI都是这个时代的杰作. ...

  6. 为什么说JavaScript是一种客户端脚本语言?

    因为它运行于客户端 而不是运行于服务器端 还有就是JS是解释执行的而且它的功能并不像JAVA .NET 等等语言有那么强大的功能,而且在语法的严谨度上安全性上,性能上,等等...各方面也有较大区别,所 ...

  7. js是运行在服务器端的脚本语言,JavaScript脚本语言

    JavaScript脚本语言 JavaScript是一种脚本语言,其源代码在发往客户端运行之前不需经过编译,而是将文本格式的字符代码发送给浏览器由浏览器解释运行.下文为大家分享的是JavaScript ...

  8. ASP脚本语言之vbscript用法

    常用的脚本语言有 Javascript 和 VBscript . 使用哪种语言需先设定.如上节代码中的<%@ LANGUAGE="JSCRIPT"   ...    %> ...

  9. 脚本语言、编程语言、中间件

    1 静态 动态语言区别 答: 动态语言:服务端和客户端代码不一致(如html) 静态语言:服务端和客户端代码一致(如: asp,php,aspx,jsp) 2 常见的脚本语言有那些 答: asp .p ...

最新文章

  1. 半吊子菜鸟学Web开发 -- PHP学习 4 --异常
  2. Java---- 静态内部类与非静态内部类的区别
  3. JZOJ 5602. 【NOI2018模拟3.26】Cti JZOJ 5057. 【GDSOI2017模拟4.13】炮塔
  4. Vue.js-Day02-PM【组件化开发(全局注册组件、局部注册组件、案例)、组件的配置选项、轮播图实例(左右切换按钮、底部导航栏、定时器、鼠标移入-图片静止)】
  5. kill掉占用端口程序
  6. 信号扫描_科研必备“武器”之扫描电子显微镜
  7. linux 机器格式化_为什么机器人应该为我们格式化代码
  8. 协作机器人 ai算法_如果我们希望人工智能为我们服务而不是不利于我们,我们需要协作设计...
  9. C语言里 指针变量强制类型转换,C语言之强制类型转换与指针--#define DIR *((volatile unsigned int *) 0x0022)...
  10. figure服务器无法显示,求大神帮帮忙,看一下为什么第二个figure出不来,只能显示第一个...
  11. 从青铜到王者,来聊聊 Synchronized 底层实现原理 | 原力计划
  12. 作为多年 PHP 的开发者,在使用了 Go 语言之后......
  13. python-socket模块介绍
  14. mysql 1005 150_錯誤1005 errno:150與mysql
  15. 基于OpenCPU方案的BC26 NB模组开发总结
  16. OpenCV 实现读取摄像头、视频读取保存 (C++)
  17. java valuewidget_Flutter开发之常用Widget
  18. 2、AP上线的那些事儿(1)capwap建立过程、设备初始化以及二层上线
  19. MOJITO 发布一周,爬一波弹幕分析下
  20. 如何减少项目中的冗余代码?

热门文章

  1. LeetCode 2013. 检测正方形(字典)
  2. LeetCode 1981. 最小化目标值与所选元素的差(DP)
  3. 程序员面试金典 - 面试题 05.04. 下一个数(线性扫描)
  4. python3经典实例_Python3十大经典错误及解决办法
  5. c写成php的扩展_用C语言编写PHP扩展
  6. html炫酷弹幕特效,jQuery文字弹幕特效
  7. 12种NumpyPandas高效技巧
  8. 什么?!“路由器”也会做信息抽取了?
  9. 深度解析LSTM神经网络的设计原理
  10. 数据结构与算---重点复习知识