为脚本语言平反-JavaScript篇(3)

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/496249.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

htop 命令详解

htop 官网:http://htop.sourceforge.net/ Linux top 命令的用法详细详解:https://www.cnblogs.com/zhoug2020/p/6336453.html htop 使用详解:https://www.cnblogs.com/programmer-tlh/p/11726016.html 使用 yum 无法直接安装 htop&#xff…

linux主机服务器日志采集,Linux通过Rsyslog搭建集中日志服务器

(一)Rsyslog简介ryslog 是一个快速处理收集系统日志的程序,提供了高性能、安全功能和模块化设计。rsyslog 是syslog 的升级版,它将多种来源输入输出转换结果到目的地。rsyslog是一个开源工具,被广泛用于Linux系统以通过TCP/UDP协议转发或接收…

IDC预测2022年全球智能家居连接设备市场规模将达10亿台!

来源: IDC官网、智慧生活; 物联网资本论编译摘要:2017年,全球智能家居连接设备市场规模达到43310万台,比上一年增长27.6%。2022年市场达到9.397亿台,IDC预计复合年增长率(CAGR&#…

Spring Data JPA 从入门到精通~方法的查询策略的属性表达式

方法的查询策略的属性表达式(Property Expressions) 属性表达式只能引用托管(泛化)实体的直接属性,如前一个示例所示。在查询创建时,已经确保解析的属性是托管实体的属性,但是,还可…

effective C++ 读书笔记

本篇文章都是摘自 《Effective C》 中文版 第三版 和 第二版。 再好的记性也有忘记的一天,记录下以备随时查看。。。 电子书下载地址:https://download.csdn.net/download/freeking101/10278088 《Effective C》第二版在线教程:http://www.…

抓取新浪的每日星座运势

从新浪上抓取每日的星座运势,然后往求实的Astrology版发. #!/usr/bin/python# encoding: utf-8from sgmllib import SGMLParserfrom datetime import datetime, timedeltaimport reimport urllibstarurlhttp://image2.sina.com.cn/ast/2007index/tmp/star_php/star.…

Spring Data JPA 从入门到精通~思维导图

#原图 System.out.println("https://www.processon.com/view/61c7227c0e3e7474fb9b4b76?fromnew1");

发邮件

System.Net.Mail.MailMessage message new System.Net.Mail.MailMessage("发件邮箱gmail.com","收件邮箱163.com", "测试标题", "测试内容"); System.Net.Mail.SmtpClient smtpClient new System.Net.Mail.SmtpClient("smtp.g…

Linux 编译 没有path,编译linux-2.6.21的问题-‘PATH_MAX’ undeclared

编译linux-2.6.21的问题:cripts/mod/sumversion.c: In function ‘get_src_version’:scripts/mod/sumversion.c:384: error: ‘PATH_MAX’ undeclared (first usein this function)scripts/mod/sumversion.c:384: error: (Each undeclared identifier isreported o…

高通5G版图现身!你的网络生活将迎来巨变?

来源:36Kr 作者:桐由于骁龙845移动平台和骁龙636移动平台的首发,3月的手机市场对于持币代购的消费者而言注定是充满期待的,在三星S9和红米Note5刷屏之时,曾经隐身手机幕后的高通也再一次引发用户热议,高通…

异步复位设计中的亚稳态问题及其解决方案

异步复位设计中的亚稳态问题及其解决方案田志明,杨军,罗岚(东南大学国家专用集成电路系统工程技术研究中心,南京 210096)摘 要:尽管异步复位是一种安全可靠复位电路的方法,但如果处理不当的话&a…

linux添加nginx,linux下安装Nginx1.16.0的教程详解

因为最近在倒腾linux,想安装新版本的nginx,找了一圈教程没有找到对应的教程,在稍微倒腾了一会之后终于成功的安装了最新版。服务器环境为centos,接下来是详细步骤:安装必要依赖插件?创建文件夹并切换过去?下载安装包…

深度|2030年8亿人会失业!图解机器人如何取代你的工作

来源:财看见-腾讯财经(ID:qqckj2017)未来智能实验室是人工智能学家与科学院相关机构联合成立的人工智能,互联网和脑科学交叉研究机构。未来智能实验室的主要工作包括:建立AI智能系统智商评测体系&#xff0…

Java使用Itext5.5.10进行pdf签章

来源:Java使用Itext5.5.10进行pdf签章_liumengya007007的博客-CSDN博客_itext 签章 啰嗦 说到PDF数字签名签章,这个其实也是数字证书信息安全的应用范畴,关于数字证书和数字签名,网上有很多解释说明,但讲解都多不够详…

Python的bool类型

写习惯了C#的代码,在想要将一个字符串False转换为bool型的时候,很自然的写了如下的Python代码: 看到上面的结果了没?是True。突然记起Python中除了、""、0、()、[]、{}、None为False之外,其他的都是True。也…

linux qt程序编译出错,QTcreator编译出错

作为移植性很好的QT图形开发工具,他支持windows,linux,FBS,macos,Symbian等OS,在windows下的QT程序在安装了QTcreator后很快就编译出来了,可是在我的redhat enterprise5 安装后,一个hello,world!却报错了错…

C++ Programmer's Cookbook C++基础和C++高级

From:http://www.cppblog.com/mzty/archive/2007/08/13/29922.html ------------------------------------------------------------------------------------------ C随笔分类列表(基础): VS Studio: VS快捷键:http://www.cppb…

工业机器人发展趋势分析 未来营收规模达到百亿级水平

来源:前瞻产业研究院摘要:工业机器人是打造自动化工厂的重要组成,可有效提高效率生产、降低成本和控制质量。工业机器人是面向工业领域的多关节机械手或多自由度的机器装置,它能自动执行工作,是靠自身动力和控制能力来实现各种功能的一种机器。工业机器人…

【itext学习之路】--5.对pdf进行盖章/签章/数字签名

来源:【itext学习之路】-------(第五篇)对pdf进行盖章/签章/数字签名_tomatocc的博客-CSDN博客_itext 数字签名 在上一篇文章中,我们学习了使用itext对pdf增加图片水印和文本水印,那么这篇文章我们将要学习更高级一点…

jquery radio取值,checkbox取值,select取值,radio选中,checkbox选中,select选中,及其相关...

获取一组radio被选中项的值var item $(input[nameitems][checked]).val();获取select被选中项的文本var item $("select[nameitems] option[selected]").text();select下拉框的第二个元素为当前选中值$(#select_id)[0].selectedIndex 1;radio单选组的第二个元素为…