javascript设计模式 - 解释器模式(interpreter)

   1 <!doctype html>
   2 <html lang="en">
   3 <head>
   4     <meta charset="UTF-8">
   5     <title>解释器模式</title>
   6 </head>
   7 <body>
   8 
   9 &lt;root id="rootId"><br>
  10 &lt;a> <br>
  11 &lt;b> <br>
  12 &lt;c name="testC">12345</c> <br>
  13 &lt;d id="1">d1</d> <br>
  14 &lt;d id="2">d2</d> <br>
  15 &lt;d id="3">d3</d> <br>
  16 &lt;d id="4">d4</d> <br>
  17 &lt;/b> <br>
  18 &lt;/a> <br>
  19 &lt;/root> <br>
  20 
  21 <script>
  22 /**
  23  * 解释器模式
  24  *
  25  * 定义:
  26  * 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
  27  *
  28  * 本质: 分离实现,解释执行
  29  *
  30  * 动机
  31  * 如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
  32  *
  33  * 抽象语法树
  34  * 解释器模式并未解释如何创建一个抽象语法树。它不涉及语法分析。抽象语法树可用一个表驱动的语法分析程序来完成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接client提供。
  35  *
  36  * 解析器
  37  * 指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序。
  38  *
  39  * 解释器
  40  * 指的是解释抽象语法树,并执行每个节点对应的功能的程序。
  41  *
  42  * 要使用解释器模式,一个重要的前提就是要定义一套语法规则,也成为文法。不管这套文法的规则是简单还是复杂,必须要有这些规则,因为解释器模式就是按照这些规则来进行解析并执行相应的功能的。
  43  *
  44  * 解释器模式用过一个解释器对象处理一个语法规则的方式,把复杂的功能分离开;然后选择需要被执行的功能,并把这些功能组合成为需要被解释执行的抽象语法树;再按照抽象语法树来解释执行,实现相应的功能。
  45  * 从表面上看,解释器模式关注的是我们平时不太用到的自定义语法的处理;但从实质上看,解释器模式的思想然后是分离,封装,简化,和很多模式是一样的。
  46  * 比如,可以使用解释器模式模拟状态模式的功能。如果把解释器模式要处理的语法简化到只有一个状态标记,把解释器看成是对状态的处理对象,对同一个表示状态的语法,可以有很多不用的解释器,也就是有很多不同的处理状态的对象,然后再创建抽象语法树的时候,简化成根据状态的标记来创建相应的解释器,不用再构建树了。
  47  * 同理,解释器模式可以模拟实现策略模式的功能,装饰器模式的功能等,尤其是模拟装饰器模式的功能,构建抽象语法树的过程,自然就对应成为组合装饰器的过程。
  48  *
  49  * 解释器模式执行速度通常不快(大多数时候非常慢),而且错误调试比较困难(附注:虽然调试比较困难,但事实上它降低了错误的发生可能性),但它的优势是显而易见的,它能有效控制模块之间接口的复杂性,对于那种执行频率不高但代码频率足够高,且多样性很强的功能,解释器是非常适合的模式。此外解释器还有一个不太为人所注意的优势,就是它可以方便地跨语言和跨平台。
  50  *
  51  * 优点:
  52  * 1.易于实现语法
  53  * 在解释器模式中,一条语法规则用一个解释器对象来解释执行。对于解释器的实现来讲,功能就变得比较简单,只需要考虑这一条语法规则的实现就可以了,其他的都不用管。
  54  * 2.易于扩展新的语法
  55  * 正是由于采用一个解释器对象负责一条语法规则的方式,使得扩展新的语法非常容易。扩展了新的语法,只需要创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了。
  56  *
  57  * 缺点:
  58  * 不适合复杂的语法
  59  * 如果语法特别复杂,构建解释器模式需要的抽象语法树的工作是非常艰巨的,再加上有可能会需要构建多个抽象语法树。所以解释器模式不太适合复杂的语法。使用语法分析程序或编译器生成器可能会更好一些。
  60  *
  61  * 何时使用
  62  * 当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式。
  63  * 在使用解释器模式的时候,还有两个特点需要考虑,一个是语法相对应该比较简单,太负责的语法不适合使用解释器模式玲玲一个是效率要求不是很高,对效率要求很高的,不适合使用。
  64  *
  65  *
  66  */
  67 
  68 (function () {
  69     // 示例代码
  70 
  71     // 终结符表达式
  72     var TerminalExpression = function () {};
  73     TerminalExpression.prototype = {
  74         /**
  75          * 解释的操作
  76          * @param  {[type]} context [上下文]
  77          */
  78         interpret: function (context) {
  79             // 实现与语法规则中的终结符相关联的解释操作
  80         }
  81     };
  82 
  83     // 非终结符表达式
  84     var NonterminalExpression = function () {};
  85     NonterminalExpression.prototype = {
  86         interpret: function (context) {
  87             // 实现与语法规则中的非终结符相关联的解释操作
  88         }
  89     };
  90 
  91     // 上下文,包含解释器之外的一些全局信息
  92     var Context = function () {};
  93 
  94     // 使用解释器的客户
  95     // 主要按照语法规则对特定句子构建抽象语法树
  96     // 然后调用解释操作
  97 }());
  98 
  99 (function () {
 100     /**
 101      * 1.为表达式设计简单的文法
 102      *
 103      * 为了通用,用root表示根元素,abc等来代表元素,一个简单的xml如下:
 104      * <?xml version="1.0" encoding="UTF-8">
 105      * <root id="rootId">
 106      *     <a>
 107      *         <b>
 108      *             <c name="testC">12345</c>
 109      *             <d id="1">d1</d>
 110      *             <d id="2">d2</d>
 111      *             <d id="3">d3</d>
 112      *             <d id="4">d4</d>
 113      *         </b>
 114      *     </a>
 115      * </root>
 116      *
 117      * 约定表达式的文法如下:
 118      * 1.获取单个元素的值:从根元素开始,一直到想要获取取值的元素,元素中间用“/”分隔,根元素前不加“/”。比如,表达式“root/a/b/c”就表示获取根元素下,a元素下,b元素下,c元素的值。
 119      * 2.获取单个元素的属性的值:当然是多个,要获取值的属性一定是表达式的最后一个元素的属性,在最后一个元素后面添加“.”然后再加上属性的名称。比如,表达式“root/a/b/c.name”就表示获取根元素下,a元素下,b元素下,c元素的name属性的值。
 120      * 3.获取相同元素名称的值,当然是多个,要获取值的元素一定是表达式的最后一个元素,在最后一个元素后面添加“$”。比如,表达式“root/a/b/d$”就表示获取根元素下,a元素下,b元素下的多个d元素的值的集合。
 121      * 4.获取相同元素名称的属性的值,当然也是多个:要获取属性值的元素一定是表达式的最后一个元素,在最后一个元素后面添加"$"。比如,表达式“root/a/b/d$.id$”就表示获取根元素下,a元素下,b元素下的多个d元素的id属性的值的集合。
 122      */
 123 
 124     /**
 125      * 上下文,用来包含解释器需要的一些全局信息
 126      * @param {String} filePathName [需要读取的xml的路径和名字]
 127      */
 128     function Context(filePathName) {
 129         // 上一个被处理元素
 130         this.preEle = null;
 131         // xml的Document对象
 132         this.document = XmlUtil.getRoot(filePathName);
 133     }
 134 
 135     Context.prototype = {
 136         // 重新初始化上下文
 137         reInit: function () {
 138             this.preEle = null;
 139         },
 140         /**
 141          *  各个Expression公共使用的方法
 142          * 根据父元素和当前元素的名称来获取当前元素
 143          * @param  {Element} pEle    [父元素]
 144          * @param  {String} eleName [当前元素名称]
 145          * @return {Element|null}         [找到的当前元素]
 146          */
 147         getNowEle: function (pEle, eleName) {
 148             var tempNodeList = pEle.childNodes;
 149             var nowEle;
 150 
 151             for (var i = 0, len = tempNodeList.length; i < len; i++) {
 152                 if ((nowEle = tempNodeList[i]).nodeType === 1)
 153                     if (nowEle.nodeName === eleName)
 154                         return nowEle;
 155             }
 156 
 157             return null;
 158         },
 159         getPreEle: function () {
 160             return this.preEle;
 161         },
 162         setPreEle: function (preEle) {
 163             this.preEle = preEle;
 164         },
 165         getDocument: function () {
 166             return this.document;
 167         }
 168     };
 169 
 170     // 工具对象
 171     // 解析xml,获取相应的Document对象
 172     var XmlUtil = {
 173         getRoot: function (filePathName) {
 174             var parser = new DOMParser();
 175             var xmldom = parser.parseFromString('<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a></root>', 'text/xml');
 176 
 177             return xmldom;
 178         }
 179     };
 180 
 181     /**
 182      * 元素作为非终结符对应的解释器,解释并执行中间元素
 183      * @param {String} eleName [元素的名称]
 184      */
 185     function ElementExpression(eleName) {
 186         this.eles = [];
 187         this.eleName = eleName;
 188     }
 189 
 190     ElementExpression.prototype = {
 191         addEle: function (eleName) {
 192             this.eles.push(eleName);
 193             return true;
 194         },
 195         removeEle: function (ele) {
 196             for (var i = 0, len = this.eles.length; i < len; i++) {
 197                 if (ele === this.eles[i])
 198                     this.eles.splice(i--, 1);
 199             }
 200             return true;
 201         },
 202         interpret: function (context) {
 203             // 先取出上下文中的当前元素作为父级元素
 204             // 查找到当前元素名称所对应的xml元素,并设置回到上下文中
 205             var pEle = context.getPreEle();
 206 
 207             if (!pEle) {
 208                 // 说明现在获取的是根元素
 209                 context.setPreEle(context.getDocument().documentElement);
 210             } else {
 211                 // 根据父级元素和要查找的元素的名称来获取当前的元素
 212                 var nowEle = context.getNowEle(pEle, this.eleName);
 213                 // 把当前获取的元素放到上下文中
 214                 context.setPreEle(nowEle);
 215             }
 216 
 217             var ss;
 218             // 循环调用子元素的interpret方法
 219             for (var i = 0, len = this.eles.length; i < len; i++) {
 220                 ss = this.eles[i].interpret(context);
 221             }
 222 
 223             // 返回最后一个解释器的解释结果,一般最后一个解释器就是终结符解释器了
 224             return ss;
 225         }
 226     };
 227 
 228     /**
 229      * 元素作为终结符对应的解释器
 230      * @param {String} name [元素的名称]
 231      */
 232     function ElementTerminalExpression(name) {
 233         this.eleName = name;
 234     }
 235 
 236     ElementTerminalExpression.prototype = {
 237         interpret: function (context) {
 238             var pEle = context.getPreEle();
 239             var ele = null;
 240             if (!pEle) {
 241                 ele = context.getDocument().documentElement;
 242             } else {
 243                 ele = context.getNowEle(pEle, this.eleName);
 244                 context.setPreEle(ele);
 245             }
 246 
 247             // 获取元素的值
 248             return ele.firstChild.nodeValue;
 249         }
 250     };
 251 
 252     /**
 253      * 属性作为终结符对应的解释器
 254      * @param {String} propName [属性的名称]
 255      */
 256     function PropertyTerminalExpression(propName) {
 257         this.propName = propName;
 258     }
 259 
 260     PropertyTerminalExpression.prototype = {
 261         interpret: function (context) {
 262             // 直接获取最后的元素属性的值
 263             return context.getPreEle().getAttribute(this.propName);
 264         }
 265     };
 266 
 267     new function () {
 268         var c = new Context('InterpreterTest.xml');
 269         // 想要获取多个d元素的值,也就是如下表达式的值:“root/a/b/c”
 270         // 首先要构建解释器的抽象语法树
 271         var root = new ElementExpression('root');
 272         var aEle = new ElementExpression('a');
 273         var bEle = new ElementExpression('b');
 274         var cEle = new ElementTerminalExpression('c');
 275 
 276         // 组合
 277         root.addEle(aEle);
 278         aEle.addEle(bEle);
 279         bEle.addEle(cEle);
 280 
 281         console.log('c的值是 = ' + root.interpret(c));
 282 
 283     }();
 284 
 285     new function () {
 286         var c = new Context('InterpreterTest.xml');
 287         // 想要获取d元素的id属性,也就是如下表达式的值:“a/b/c.name”
 288         // 这个时候c不是终结了,需要把c修改成ElementExpression
 289         var root = new ElementExpression('root');
 290         var aEle = new ElementExpression('a');
 291         var bEle = new ElementExpression('b');
 292         var cEle = new ElementExpression('c');
 293         var prop = new PropertyTerminalExpression('name');
 294 
 295         // 组合
 296         root.addEle(aEle);
 297         aEle.addEle(bEle);
 298         bEle.addEle(cEle);
 299         cEle.addEle(prop);
 300 
 301         console.log('c的属性name值是 = ' + root.interpret(c));
 302 
 303         // 如果要使用同一个上下文,连续进行解析,需要重新初始化上下文对象
 304         // 比如,要连续的重新再获取一次属性name的值,当然你可以重新组合元素
 305         // 重新解析,只要是在使用同一个上下文,就需要重新初始化上下文对象
 306         c.reInit();
 307         console.log('重新获取c的属性name值是 = ' + root.interpret(c));
 308     }();
 309 
 310 
 311     // 读取多个元素或属性的值
 312     (function () {
 313         /**
 314          * 上下文,用来包含解释器需要的一些全局信息
 315          * @param {String} filePathName [需要读取的xml的路径和名字]
 316          */
 317         function Context(filePathName) {
 318             // 上一个被处理的多个元素
 319             this.preEles = [];
 320             // xml的Document对象
 321             this.document = XmlUtil.getRoot(filePathName);
 322         }
 323 
 324         Context.prototype = {
 325             // 重新初始化上下文
 326             reInit: function () {
 327                 this.preEles = [];
 328             },
 329             /**
 330              *  各个Expression公共使用的方法
 331              * 根据父元素和当前元素的名称来获取当前元素
 332              * @param  {Element} pEle    [父元素]
 333              * @param  {String} eleName [当前元素名称]
 334              * @return {Element|null}         [找到的当前元素]
 335              */
 336             getNowEles: function (pEle, eleName) {
 337                 var elements = [];
 338                 var tempNodeList = pEle.childNodes;
 339                 var nowEle;
 340 
 341                 for (var i = 0, len = tempNodeList.length; i < len; i++) {
 342                     if ((nowEle = tempNodeList[i]).nodeType === 1) {
 343                         if (nowEle.nodeName === eleName) {
 344                             elements.push(nowEle);
 345                         }
 346                     }
 347                 }
 348 
 349                 return elements;
 350             },
 351             getPreEles: function () {
 352                 return this.preEles;
 353             },
 354             setPreEles: function (nowEles) {
 355                 this.preEles = nowEles;
 356             },
 357             getDocument: function () {
 358                 return this.document;
 359             }
 360         };
 361 
 362         // 工具对象
 363         // 解析xml,获取相应的Document对象
 364         var XmlUtil = {
 365             getRoot: function (filePathName) {
 366                 var parser = new DOMParser();
 367                 var xmldom = parser.parseFromString('<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a></root>', 'text/xml');
 368 
 369                 return xmldom;
 370             }
 371         };
 372 
 373         /**
 374          * 元素作为非终结符对应的解释器,解释并执行中间元素
 375          * @param {String} eleName [元素的名称]
 376          */
 377         function ElementExpression(eleName) {
 378             this.eles = [];
 379             this.eleName = eleName;
 380         }
 381 
 382         ElementExpression.prototype = {
 383             addEle: function (eleName) {
 384                 this.eles.push(eleName);
 385                 return true;
 386             },
 387             removeEle: function (ele) {
 388                 for (var i = 0, len = this.eles.length; i < len; i++) {
 389                     if (ele === this.eles[i]) {
 390                         this.eles.splice(i--, 1);
 391                     }
 392                 }
 393                 return true;
 394             },
 395             interpret: function (context) {
 396                 // 先取出上下文中的当前元素作为父级元素
 397                 // 查找到当前元素名称所对应的xml元素,并设置回到上下文中
 398                 var pEles = context.getPreEles();
 399                 var ele = null;
 400                 var nowEles = [];
 401 
 402                 if (!pEles.length) {
 403                     // 说明现在获取的是根元素
 404                     ele = context.getDocument().documentElement;
 405                     pEles.push(ele);
 406                     context.setPreEles(pEles);
 407                 } else {
 408                     var tempEle;
 409                     for (var i = 0, len = pEles.length; i < len; i++) {
 410                         tempEle = pEles[i];
 411                         nowEles = nowEles.concat(context.getNowEles(tempEle, this.eleName));
 412 
 413                         // 找到一个就停止
 414                         if (nowEles.length) break;
 415                     }
 416 
 417                     context.setPreEles([nowEles[0]]);
 418                 }
 419 
 420                 var ss;
 421                 // 循环调用子元素的interpret方法
 422                 for (var i = 0, len = this.eles.length; i < len; i++) {
 423                     ss = this.eles[i].interpret(context);
 424                 }
 425 
 426                 return ss;
 427             }
 428         };
 429 
 430         /**
 431          * 元素作为终结符对应的解释器
 432          * @param {String} name [元素的名称]
 433          */
 434         function ElementTerminalExpression(name) {
 435             this.eleName = name;
 436         }
 437 
 438         ElementTerminalExpression.prototype = {
 439             interpret: function (context) {
 440                 var pEles = context.getPreEles();
 441                 var ele = null;
 442                 if (!pEles.length) {
 443                     ele = context.getDocument().documentElement;
 444                 } else {
 445                     ele = context.getNowEles(pEles[0], this.eleName)[0];
 446                 }
 447 
 448                 // 获取元素的值
 449                 return ele.firstChild.nodeValue;
 450             }
 451         };
 452 
 453         /**
 454          * 属性作为终结符对应的解释器
 455          * @param {String} propName [属性的名称]
 456          */
 457         function PropertyTerminalExpression(propName) {
 458             this.propName = propName;
 459         }
 460 
 461         PropertyTerminalExpression.prototype = {
 462             interpret: function (context) {
 463                 // 直接获取最后的元素属性的值
 464                 return context.getPreEles()[0].getAttribute(this.propName);
 465             }
 466         };
 467 
 468         /**
 469          * 多个属性作为终结符对应的解释器
 470          * @param {String} propName [属性的名称]
 471          */
 472         function PropertysTerminalExpression(propName) {
 473             this.propName = propName;
 474         }
 475 
 476         PropertysTerminalExpression.prototype = {
 477             interpret: function (context) {
 478                 var eles = context.getPreEles();
 479                 var ss = [];
 480 
 481                 for (var i = 0, len = eles.length; i < len; i++) {
 482                     ss.push(eles[i].getAttribute(this.propName));
 483                 }
 484 
 485                 return ss;
 486             }
 487         };
 488 
 489         /**
 490          * 以多个元素作为终结符的解释处理对象
 491          * @param {[type]} name [description]
 492          */
 493         function ElementsTerminalExpression(name) {
 494             this.eleName = name;
 495         }
 496 
 497         ElementsTerminalExpression.prototype = {
 498             interpret: function (context) {
 499                 var pEles = context.getPreEles();
 500                 var nowEles = [];
 501 
 502                 for (var i = 0, len = pEles.length; i < len; i++) {
 503                     nowEles = nowEles.concat(context.getNowEles(pEles[i], this.eleName));
 504                 }
 505 
 506                 var ss = [];
 507 
 508                 for (i = 0, len = nowEles.length; i < len; i++) {
 509                     ss.push(nowEles[i].firstChild.nodeValue);
 510                 }
 511 
 512                 return ss;
 513             }
 514         };
 515 
 516         /**
 517          * 多个元素作为非终结符的解释处理对象
 518          */
 519         function ElementsExpression(name) {
 520             this.eleName = name;
 521             this.eles = [];
 522         }
 523 
 524         ElementsExpression.prototype = {
 525             interpret: function (context) {
 526                 var pEles = context.getPreEles();
 527                 var nowEles = [];
 528 
 529                 for (var i = 0, len = pEles.length; i < len; i++) {
 530                     nowEles = nowEles.concat(context.getNowEles(pEles[i], this.eleName));
 531                 }
 532                 context.setPreEles(nowEles);
 533 
 534                 var ss;
 535                 for (i = 0, len = this.eles.length; i < len; i++) {
 536                     ss = this.eles[i].interpret(context);
 537                 }
 538 
 539                 return ss;
 540             },
 541             addEle: function (ele) {
 542                 this.eles.push(ele);
 543                 return true;
 544             },
 545             removeEle: function (ele) {
 546                 for (var i = 0, len = this.eles.length; i < len; i++) {
 547                     if (ele === this.eles[i]) {
 548                         this.eles.splice(i--, 1);
 549                     }
 550                 }
 551                 return true;
 552             }
 553         };
 554 
 555         new function () {
 556             // "root/a/b/d$"
 557             var c = new Context('Interpreter.xml');
 558             var root = new ElementExpression('root');
 559             var aEle = new ElementExpression('a');
 560             var bEle = new ElementExpression('b');
 561             var dEle = new ElementsTerminalExpression('d');
 562 
 563             root.addEle(aEle);
 564             aEle.addEle(bEle);
 565             bEle.addEle(dEle);
 566 
 567             var ss = root.interpret(c);
 568 
 569             for (var i = 0, len = ss.length; i < len; i++) {
 570                 console.log('d的值是 = ' + ss[i]);
 571             }
 572         }();
 573 
 574         new function () {
 575             // a/b/d$.id$
 576             var c = new Context('Interpreter.xml');
 577             var root = new ElementExpression('root');
 578             var aEle = new ElementExpression('a');
 579             var bEle = new ElementExpression('b');
 580             var dEle = new ElementsExpression('d');
 581             var prop = new PropertysTerminalExpression('id');
 582 
 583             root.addEle(aEle);
 584             aEle.addEle(bEle);
 585             bEle.addEle(dEle);
 586             dEle.addEle(prop);
 587 
 588             var ss = root.interpret(c);
 589 
 590             for (var i = 0, len = ss.length; i < len; i++) {
 591                 console.log('d的属性id的值是 = ' + ss[i]);
 592             }
 593         }();
 594 
 595         // 解析器
 596 
 597         /**
 598          * 解析器的实现思路
 599          * 1.把客户端传递来的表达式进行分解,分解成为一个一个的元素,并用一个对应的解析模型来封装这个元素的一些信息。
 600          * 2.根据每个元素的信息,转化成相对应的解析器对象。
 601          * 3.按照先后顺序,把这些解析器对象组合起来,就得到抽象语法树了。
 602          *
 603          * 为什么不把1和2合并,直接分解出一个元素就转换成相应的解析器对象?
 604          * 1.功能分离,不要让一个方法的功能过于复杂。
 605          * 2.为了今后的修改和扩展,现在语法简单,所以转换成解析器对象需要考虑的东西少,直接转换也不难,但要是语法复杂了,直接转换就很杂乱了。
 606          */
 607 
 608         /**
 609          * 用来封装每一个解析出来的元素对应的属性
 610          */
 611         function ParserModel() {
 612             // 是否单个值
 613             this.singleValue;
 614             // 是否属性,不是属性就是元素
 615             this.propertyValue;
 616             // 是否终结符
 617             this.end;
 618         }
 619 
 620         ParserModel.prototype = {
 621             isEnd: function () {
 622                 return this.end;
 623             },
 624             setEnd: function (end) {
 625                 this.end = end;
 626             },
 627             isSingleValue: function () {
 628                 return this.singleValue;
 629             },
 630             setSingleValue: function (oneValue) {
 631                 this.singleValue = oneValue;
 632             },
 633             isPropertyValue: function () {
 634                 return this.propertyValue;
 635             },
 636             setPropertyValue: function (propertyValue) {
 637                 this.propertyValue = propertyValue;
 638             }
 639         };
 640 
 641         var Parser = function () {
 642             var BACKLASH = '/';
 643             var DOT = '.';
 644             var DOLLAR = '$';
 645             // 按照分解的先后记录需要解析的元素的名称
 646             var listEle = null;
 647 
 648             // 开始实现第一步-------------------------------------
 649 
 650             /**
 651              * 传入一个字符串表达式,通过解析,组合成为一个抽象语法树
 652              * @param  {String} expr [描述要取值的字符串表达式]
 653              * @return {Object}      [对应的抽象语法树]
 654              */
 655             function parseMapPath(expr) {
 656                 // 先按照“/”分割字符串
 657                 var tokenizer = expr.split(BACKLASH);
 658                 // 用来存放分解出来的值的表
 659                 var mapPath = {};
 660                 var onePath, eleName, propName;
 661                 var dotIndex = -1;
 662 
 663                 for (var i = 0, len = tokenizer.length; i < len; i++) {
 664                     onePath = tokenizer[i];
 665 
 666                     if (tokenizer[i + 1]) {
 667                         // 还有下一个值,说明这不是最后一个元素
 668                         // 按照现在的语法,属性必然在最后,因此也不是属性
 669                         setParsePath(false, onePath, false, mapPath);
 670                     } else {
 671                         // 说明到最后了
 672                         dotIndex = onePath.indexOf(DOT);
 673 
 674                         if (dotIndex >= 0) {
 675                             // 说明是要获取属性的值,那就按照“.”来分割
 676                             // 前面的就是元素名称,后面的是属性的名字
 677                             eleName = onePath.substring(0, dotIndex);
 678                             propName = onePath.substring(dotIndex + 1);
 679 
 680                             // 设置属性前面的那个元素,自然不是最后一个,也不是属性
 681                             setParsePath(false, eleName, false, mapPath);
 682                             // 设置属性,按照现在的语法定义,属性只能是最后一个
 683                             setParsePath(true, propName, true, mapPath);
 684                         } else {
 685                             // 说明是取元素的值,而且是最后一个元素的值
 686                             setParsePath(true, onePath, false, mapPath);
 687                         }
 688 
 689                         break;
 690                     }
 691                 }
 692 
 693                 return mapPath;
 694             }
 695 
 696             /**
 697              * 按照分解出来的位置和名称来设置需要解析的元素名称
 698              * @param {Boolean} end           [是否最后一个]
 699              * @param {String} ele           [元素名称]
 700              * @param {Boolean} propertyValue [是否取属性]
 701              * @param {Object} mapPath       [设置需要解析的元素名称,还有该元素对应的解析模型的表]
 702              */
 703             function setParsePath(end, ele, propertyValue, mapPath) {
 704                 var pm = new ParserModel();
 705                 pm.setEnd(end);
 706                 // 如果带有“$”符号就说明不是一个值
 707                 pm.setSingleValue(!(ele.indexOf(DOLLAR) >= 0));
 708                 pm.setPropertyValue(propertyValue);
 709                 // 去掉"$"
 710                 ele = ele.replace(DOLLAR, '');
 711                 mapPath[ele] = pm;
 712                 listEle.push(ele);
 713             }
 714 
 715             // 开始实现第二步-------------------------------------
 716 
 717             /**
 718              * 把分解出来的元素名称根据对应的解析模型转换成为相应的解释器对象
 719              * @param  {Object} mapPath [分解出来的需解析的元素名称,还有该元素对应的解析模型]
 720              * @return {Array}         [把每个元素转换成为相应的解释器对象后的数组]
 721              */
 722             function mapPath2Interpreter(mapPath) {
 723                 var list = [];
 724                 var pm, key;
 725                 var obj = null;
 726 
 727                 // 一定要按照分解的先后顺序来转换成解释器对象
 728                 for (var i = 0, len = listEle.length; i < len; i++) {
 729                     key = listEle[i];
 730                     pm = mapPath[key];
 731 
 732                     // 不是最后一个
 733                     if (!pm.isEnd()) {
 734 
 735                         if (pm.isSingleValue())
 736                         // 是一个值,转化
 737                             obj = new ElementExpression(key);
 738                         else
 739                         // 是多个值,转化
 740                             obj = new ElementsExpression(key);
 741 
 742                     } else {
 743                         // 是最后一个
 744 
 745                         // 是属性值
 746                         if (pm.isPropertyValue()) {
 747                             if (pm.isSingleValue())
 748                                 obj = new PropertyTerminalExpression(key);
 749                             else
 750                                 obj = new PropertysTerminalExpression(key);
 751 
 752                             // 取元素的值
 753                         } else {
 754                             if (pm.isSingleValue())
 755                                 obj = new ElementTerminalExpression(key);
 756                             else
 757                                 obj = new ElementsTerminalExpression(key);
 758                         }
 759                     }
 760 
 761                     list.push(obj);
 762                 }
 763 
 764                 return list;
 765             }
 766 
 767             // 开始实现第三步-------------------------------------
 768 
 769             /**
 770              * 构建抽象语法树
 771              * @param  {[type]} list [把每个元素转换成为相应的解释器对象后的数组]
 772              * @return {[type]}      [description]
 773              */
 774             function buildTree(list) {
 775                 // 第一个对象,也是返回去的对象,就是抽象语法树的根
 776                 var returnReadXMLExpr = null;
 777                 // 定义上一个对象
 778                 var preReadXmlExpr = null;
 779                 var readXml, ele, eles;
 780 
 781                 for (var i = 0, len = list.length; i < len; i++) {
 782                     readXml = list[i];
 783                     // 说明是第一个元素
 784                     if (preReadXmlExpr === null) {
 785                         preReadXmlExpr = readXml;
 786                         returnReadXMLExpr = readXml;
 787 
 788                         // 把元素添加到上一个对象下面,同时把本对象设置成为oldRe
 789                         // 作为下一个对象的父节点
 790                     } else {
 791                         if (preReadXmlExpr instanceof ElementExpression) {
 792                             ele = preReadXmlExpr;
 793                             ele.addEle(readXml);
 794                             preReadXmlExpr = readXml;
 795                         } else if (preReadXmlExpr instanceof ElementsExpression) {
 796                             eles = preReadXmlExpr;
 797                             eles.addEle(readXml);
 798                             preReadXmlExpr = readXml;
 799                         }
 800                     }
 801                 }
 802 
 803                 return returnReadXMLExpr;
 804             }
 805 
 806             return {
 807                 // 公共方法
 808                 parse: function (expr) {
 809                     listEle = [];
 810 
 811                     var mapPath = parseMapPath(expr);
 812                     var list = mapPath2Interpreter(mapPath);
 813 
 814                     return buildTree(list);
 815                 }
 816             };
 817         }();
 818 
 819         new function () {
 820             // 准备上下文
 821             var c = new Context('Interpreter.xml');
 822             // 通过解析其获取抽象语法树
 823             var readXmlExpr = Parser.parse('root/a/b/d$.id$');
 824             // 请求解析,获取返回值
 825             var ss = readXmlExpr.interpret(c);
 826 
 827             console.log('------------parsing--------------');
 828             for (var i = 0, len = ss.length; i < len; i++) {
 829                 console.log('d的属性id的值是 = ' + ss[i]);
 830             }
 831             console.log('---------------parsed--------------');
 832 
 833             // 如果要使用同一个上下文,连续进行解析,需要重新初始化上下文对象
 834             c.reInit();
 835             var readxmlExpr2 = Parser.parse('root/a/b/d$');
 836             var ss2 = readxmlExpr2.interpret(c);
 837             console.log('------------parsing--------------');
 838             for (i = 0, len = ss2.length; i < len; i++) {
 839                 console.log('d的值是 = ' + ss2[i]);
 840             }
 841             console.log('---------------parsed--------------');
 842 
 843             c.reInit();
 844             var readxmlExpr3 = Parser.parse('root/a/b/c');
 845             var ss3 = readxmlExpr3.interpret(c);
 846             console.log('------------parsing--------------');
 847             console.log('c的name属性值是 = ' + ss3);
 848             console.log('---------------parsed--------------');
 849 
 850             c.reInit();
 851             var readxmlExpr4 = Parser.parse('root/a/b/c.name');
 852             var ss4 = readxmlExpr4.interpret(c);
 853             console.log('------------parseing--------------');
 854             console.log('c的name属性值是 = ' + ss4);
 855             console.log('---------------parsed--------------');
 856         }();
 857 
 858         // 这样就实现了类似XPath的部分功能
 859         // 没错,就类似于jQuery选择器的部分功能
 860     }());
 861 
 862 
 863     /**
 864      * 1.功能
 865      * 解释器模式使用解释器对象来表示和处理相应的语法规则,一般一个解释器处理一条语法规则。理论上来说,只要能用解释器对象把符合语法的表达式表示出来,而且能够构成抽象的语法树,就可以使用解释器模式来处理。
 866      *
 867      * 2.语法规则和解释器
 868      * 语法规则和解释器之间是有对应关系的,一般一个解释器处理一条语法规则,但是反过来并不成立,一条语法规则是可以有多种解释和处理的,也就是一条语法规则可以对应多个解释器。
 869      *
 870      * 3.上下文的公用性
 871      * 上下文在解释器模式中起着非常重要的作用。由于上下文会被传递到所有的解释器中。因此可以在上下文中存储和访问解释器的状态,比如,前面的解释器可以存储一些数据在上下文中,后面的解释器就可以获取这些值。
 872      * 另外还可以通过上下文传递一些在解释器外部,但是解释器需要的数据,也可以是一些全局的,公共的数据。
 873      * 上下文还有一个功能,就是可以提供所有解释器对象的公共功能,类似于对象组合,而不是使用继承来获取公共功能,在每个解释器对象中都可以调用
 874      *
 875      * 4.谁来构建抽象语法树
 876      * 在前面的示例中,是自己在客户端手工构建抽象语法树,是很麻烦的,但是在解释器模式中,并没有涉及这部分功能,只是负责对构建好的抽象语法树进行解释处理。后面会介绍可以提供解析器来实现把表达式转换成为抽象语法树。
 877      * 还有一个问题,就是一条语法规则是可以对应多个解释器对象的,也就是说同一个元素,是可以转换成多个解释器对象的,这也就意味着同样一个表达式,是可以构成不用的抽象语法树的,这也造成构建抽象语法树变得很困难,而且工作量非常大。
 878      *
 879      * 5.谁负责解释操作
 880      * 只要定义好了抽象语法树,肯定是解释器来负责解释执行。虽然有不同的语法规则,但是解释器不负责选择究竟用哪个解释器对象来解释执行语法规则,选择解释器的功能在构建抽象语法树的时候就完成了。
 881      *
 882      * 6.解释器模式的调用顺序
 883      * 1)创建上下文对象
 884      * 2)创建多个解释器对象,组合抽象语法树
 885      * 3)调用解释器对象的解释操作
 886      * 3.1)通过上下文来存储和访问解释器的状态。
 887      * 对于非终结符解释器对象,递归调用它所包含的字解释器对象。
 888      *
 889      * 相关模式
 890      *
 891      * 解释器模式和组合模式
 892      * 这两个模式可以组合使用。
 893      * 通常解释器模式都会使用组合模式来实现,这样能够方便地构建抽象语法树,一般非终结符解释器就相当于组合模式中的组合对象;终结符解释器就相当于叶子对象。
 894      *
 895      * 解释器模式和迭代器模式
 896      * 这两个模式可以组合使用。
 897      * 由于解释器模式通常使用组合模式来实现,因此在遍历整个对象结构的时候,自然可以使用迭代器模式。
 898      *
 899      * 解释器和享元模式
 900      * 这两个模式可以组合使用。
 901      * 在使用解释器模式的时候,可能会造成多个细粒度对象,比如,会有各种各样的终结符解释器,而这些终结符解释器对不同的表达式来说是一样的,是可以共用的,因此可以引入享元模式来共享这些对象。
 902      *
 903      * 解释器模式和访问者模式
 904      * 这两个模式可以组合使用。
 905      * 在解释器模式中,语法规则和解释器对象是有对应关系的。语法规则的变动意味着功能的变化,自然会导致使用不用的解释器对象;而且一个语法规则可以被不同的解释器解释执行。
 906      * 因此在构建抽象语法树的时候,如果每个节点所对应的解释器对象是固定的,这就意味着该节点对应的功能是固定的,那么就不得不根据需要来构建不用抽象语法树,
 907      * 为了让构建的抽象语法树较为通用,那就要求解释器的功能不要那么固定,要能很方便的改变解释器的功能,这个时候问题就变成了如何能够很方便地更改树形结构中节点对象的功能了,访问者模式可以很好的实现这个功能。
 908      */
 909 }());
 910 
 911 (function () {
 912     // http://blog.csdn.net/dead_of_winter/article/details/2158492
 913 
 914     /**
 915      * 解释器模式在js中有两个最典型的应用json和正则表达式,对js程序员来说,这应该是很熟悉的两种东西。json用于序列化对象型数据,这个js的对象文字量形式在包括C++,Java在内的各种语言中都有实现的类库,在一些ajax应用中,java或者C#中的对象被序列化为json格式,通过相应客户端的http请求传递给客户端的js程序,js几乎不需要任何处理,仅仅使用eval就可以把json格式的数据还原成js对象(因为json恰巧是来自js),这在解释器模式的实现中是很少见的。现在,不仅仅使与js相关的应用,即使在其他语言的应用中,json也是一种很受欢迎的数据交换格式。正则表达式是js的内置对象,它可以说是最著名的解释器模式了,几乎所有语言中都有它的实现,现在它已经几乎是字符串匹配的事实标准。它能处理字符串的各种格式,有效地避免了过于复杂的string对象接口或者大段的字符串分析代码,这对开发效率至关重要。js的实现是一个比较强的版本,相比java和C#等语言,js允许函数参数为它提供了更大的灵活性。
 916      *
 917      *
 918      * 词法分析·状态机的实现
 919      *
 920      * 通常解释器模式需要将所定义的"语言"字符流转换成适合的程序数据结构,再对这个结构进行分析。对于比较简单的情况,转换和分析可以在一步完成。为了很好好的完成这项工作,我们需要实现一个状态机。
 921      * 状态机原本不是软件和程序中的术语,在数字逻辑中有限状态机是指输出取决于过去输入部分和当前输入部分的时序逻辑电路。这里甚至无需强调有限状态机,可以简单理解状态机为一个黑箱子,向其中投入指令后即可进行操作和装换状态,它有一个最终状态,当到达最终状态时,即可完成任务。
 922      * 词法分析有限状态机任务很简单,从输入字符流中读入一个一个的字符,当辨认出输入的字符能构成一个独立的语法单元(token)时,便将这个token放入待分析的词句流中。
 923      * 这里给出一个简单的例子:正斜杠转义的实现。通常字符串转义都是以反斜杠/实现的,假如有一个字符串,现在我们要把正斜杠用作转义符以做一些特殊用途,其他字符原样放置。那么正斜杠/和它后面的字符必须被看成一个整体,其它每个字符都是一个整体。
 924      * 这个状态机只有两个状态 第一个状态是读入普通字符状态 第二个状态是读入正斜杠以后的状态
 925      */
 926 
 927         // 在js中 充分利用语言特性 将每个状态实现为一个函数 它接受一个状态改变参数 然后返回下一个状态
 928         // 这是一个标准的状态机处理词法分析的例子,事实上,有些简单的解释器模式,仅仅通过词法分析即可实现,功能可以写在状态改变函数中,而无需对产生的token流进行处理。
 929 
 930     function state_machine() {
 931         this.state = _1;
 932         this.result = [];
 933         function _1(c) {
 934             if (c != '/') {
 935                 this.result.push(c);
 936                 return _1;
 937             } else {
 938                 return _2;
 939             }
 940         }
 941 
 942         function _2(c) {
 943             this.result.push('/' + c);
 944             return _1;
 945         }
 946 
 947         this.change = function (c) {
 948             this.state = this.state(c);
 949         };
 950     }
 951 
 952     var sm = new state_machine();
 953     var queue = ("a//sd/jh/ds").split('');
 954 
 955     for (var i = 0; i < queue.length; i++)
 956         sm.change(queue[i]);
 957 
 958     console.log(sm.result);
 959 
 960 
 961     /**
 962      * 函数式语言特性与状态机
 963      *
 964      * 作为函数式语言,js实现解释器模式有非常有趣的方式:以不定个数的参数形式传入函数进行处理,这样可以方便的扩展功能,同时可以使用户更自由的使用解释器提供的接口。
 965      * 下面一段代码是一个用于日期对象的格式化的类 它是状态机词法分析的一个稍微复杂的例子,同时它以函数参数的方式为用户提供了扩展功能。
 966      */
 967 
 968     /*
 969     DateEx类
 970     说明:以参数形式继承自Date对象 为Date对象扩展方法
 971     方法:
 972         format(formatString,[fun],......)
 973         参数:
 974             formatString:格式字符串 将日期转换成所规定的格式字符串
 975                 格式说明:
 976                     %[x]:
 977                         [x]代表日期的一个部分
 978                     %y:年
 979                     %m:月
 980                     %d:日
 981                     %w:星期
 982                     %h:小时
 983                     %i:分
 984                     %s:秒
 985 
 986                     %[num][x]:
 987                         [num]代表长度 [x]意义同上 如果长度不足则用0补齐 如果长度超出[num]则将高位截断
 988 
 989                     %f[x]:
 990                         以自定义函数处理%[x]得到的值,自定义函数在参数列表[fun]中给出,参数中[fun]的个数应与%f[x]的数目一致
 991 
 992 
 993             fun:可选的,处理函数,当格式字符串中有格式符%f出现时,则在fun中取相应的函数处理
 994     */
 995     function DateEx(date) {
 996         date = date || new Date();
 997         date.format = function (formatString) {
 998             var f;
 999             var j = 0;
1000 
1001             function fbuilder(n) {
1002                 return function (v) {
1003                     var s = v.toString();
1004                     if (s.length >= n) return s.slice(s.length - n, s.length);
1005                     if (s.length < n) return new Array(n - s.length + 1).join(0) + s;
1006                 };
1007             }
1008 
1009             var args = arguments;
1010             var resault = new String();
1011             var _1 = function (c)//状态1 是读入格式字符串的状态
1012             {
1013                 if (c != "%")//对于非%字符按原样输出
1014                 {
1015                     resault += c;
1016                     return _1;
1017                 }
1018                 else//读到%时进入状态2 否则延续状态1
1019                 {
1020                     return _2;
1021                 }
1022             };
1023             var _2 = function (c)//状态2 是读入特殊格式字符串的状态
1024             {
1025                 if (c.match(/d/) != null)//对于数字 构造相应处理函数 返回状态3
1026                 {
1027                     f = fbuilder(Number(c));
1028                     return _3;
1029                 }
1030                 else if (c == "f")//对于格式符f 从参数中获取相应处理函数 返回状态3
1031                 {
1032                     f = args[++j];
1033                     return _3;
1034                 }
1035                 else//没有特殊格式符 直接进入状态3
1036                 {
1037                     f = function (v) {return v;}
1038                     return _3(c);
1039                 }
1040 
1041 
1042             };
1043             var _3 = function (c) {
1044                 if (c == "%")//格式符% 连续2个%将被转义为一个% 返回状态1
1045                 {
1046                     resault += c;
1047                     return _1;
1048                 }
1049                 else if (c == "y")//格式符y 取出年份 返回状态1
1050                 {
1051                     resault += f(date.getFullYear());
1052 
1053                     return _1;
1054                 }
1055                 else if (c == "m")//格式符m 取出月份 返回状态1
1056                 {
1057                     resault += f(date.getMonth() + 1);
1058                     return _1;
1059                 }
1060                 else if (c == "d")//格式符d 取出日期 返回状态1
1061                 {
1062                     resault += f(date.getDate());
1063                     return _1;
1064                 }
1065                 else if (c == "w")//格式符w 取出星期 返回状态1
1066                 {
1067                     resault += f(date.getDay());
1068                     return _1;
1069                 }
1070                 else if (c == "h")//格式符h 取出小时 返回状态1
1071                 {
1072                     resault += f(date.getHours());
1073                     return _1;
1074                 }
1075                 else if (c == "i")//格式符i 取出分 返回状态1
1076                 {
1077                     resault += f(date.getMinutes());
1078                     return _1;
1079                 }
1080                 else if (c == "s")//格式符s 取出秒 返回状态1
1081                 {
1082                     resault += f(date.getSeconds());
1083                     return _1;
1084                 }
1085                 else return _1//没有合法格式符 忽略 返回状态1
1086             };
1087             var status = _1;
1088             for (var i = 0; i < formatString.length; i++) {
1089                 status = status(formatString.charAt(i));
1090             }
1091             return resault;
1092         }
1093         return date;
1094     }
1095 
1096     var weekdays = "日一二三四五六";
1097     console.log(new DateEx().format("%2y-%2m-%2d 星期%fw %2h:%2i:%2s %%", function (v) {return weekdays.charAt(v);}))
1098 
1099 
1100     /**
1101      * 动态语言特性·eval与解释器模式
1102      *
1103      * js的另一个非常有趣特点是它本身是一门解释型语言,它允许用eval和Function等方式调用其本身的解释器引擎,这样给解释器的实现带来了很大的方便,可以将某段自定义语言(如代数运算或者布尔运算不分)作为一个独立的token用eval直接执行,这种形式的解释器是静态语言无法比拟的
1104      */
1105 }());
1106 
1107 (function () {
1108     // http://www.dofactory.com/javascript-interpreter-pattern.aspx
1109 
1110     // 将罗马数字表达式转换为阿拉伯数字
1111 
1112     var Context = function (input) {
1113         this.input = input;
1114         this.output = 0;
1115     };
1116 
1117     Context.prototype = {
1118         startsWith: function (str) {
1119             return this.input.substr(0, str.length) === str;
1120         }
1121     };
1122 
1123     var Expression = function (name, one, four, five, nine, multiplier) {
1124         this.name = name;
1125         this.one = one;
1126         this.four = four;
1127         this.five = five;
1128         this.nine = nine;
1129         this.multiplier = multiplier;
1130     };
1131 
1132     Expression.prototype = {
1133         interpret: function (context) {
1134             if (context.input.length == 0) {
1135                 return;
1136             }
1137             else if (context.startsWith(this.nine)) {
1138                 context.output += (9 * this.multiplier);
1139                 context.input = context.input.substr(2);
1140             }
1141             else if (context.startsWith(this.four)) {
1142                 context.output += (4 * this.multiplier);
1143                 context.input = context.input.substr(2);
1144             }
1145             else if (context.startsWith(this.five)) {
1146                 context.output += (5 * this.multiplier);
1147                 context.input = context.input.substr(1);
1148             }
1149 
1150             while (context.startsWith(this.one)) {
1151                 context.output += (1 * this.multiplier);
1152                 context.input = context.input.substr(1);
1153             }
1154         }
1155     };
1156 
1157 
1158     void function run() {
1159 
1160         var roman = "MCMXXVIII"
1161         var context = new Context(roman);
1162         var tree = [];
1163 
1164         tree.push(new Expression("thousand", "M", " ", " ", " ", 1000));
1165         tree.push(new Expression("hundred", "C", "CD", "D", "CM", 100));
1166         tree.push(new Expression("ten", "X", "XL", "L", "XC", 10));
1167         tree.push(new Expression("one", "I", "IV", "V", "IX", 1));
1168 
1169         for (var i = 0, len = tree.length; i < len; i++) {
1170             tree[i].interpret(context);
1171         }
1172 
1173         console.log(roman + " = " + context.output);
1174     }();
1175 }());
1176 
1177 </script>
1178 </body>
1179 </html>

 

posted @ 2014-01-25 10:34  LukeLin  阅读(1828)  评论(0编辑  收藏  举报