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 <root id="rootId"><br> 10 <a> <br> 11 <b> <br> 12 <c name="testC">12345</c> <br> 13 <d id="1">d1</d> <br> 14 <d id="2">d2</d> <br> 15 <d id="3">d3</d> <br> 16 <d id="4">d4</d> <br> 17 </b> <br> 18 </a> <br> 19 </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>