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