Shenma เป็น "โหมดล่าม" หรือไม่?
มาเปิด "GOF" ก่อนและดูคำจำกัดความ:
ให้ภาษากำหนดการเป็นตัวแทนของไวยากรณ์และกำหนดล่ามที่ใช้การเป็นตัวแทนนี้เพื่อตีความประโยคในภาษา
ก่อนเริ่มต้นฉันยังคงต้องใช้แนวคิดหลายประการ:
ทรีไวยากรณ์นามธรรม:
รูปแบบล่ามไม่ได้อธิบายวิธีการสร้างแผนผังไวยากรณ์นามธรรม มันไม่เกี่ยวข้องกับการวิเคราะห์ไวยากรณ์ แผนผังไวยากรณ์นามธรรมสามารถทำได้โดยโปรแกรมการวิเคราะห์ไวยากรณ์ที่ขับเคลื่อนด้วยตารางหรือสร้างโดยโปรแกรมการวิเคราะห์ไวยากรณ์ที่เขียนด้วยลายมือ
Parser:
มันหมายถึงโปรแกรมที่อธิบายการแสดงออกที่ต้องการโดยการโทรของลูกค้าและสร้างแผนผังไวยากรณ์นามธรรมหลังจากการแยกวิเคราะห์
ล่าม:
หมายถึงโปรแกรมที่อธิบายแผนผังนามธรรมแบบนามธรรมและดำเนินการฟังก์ชั่นที่สอดคล้องกันของแต่ละโหนด
สิ่งสำคัญที่สำคัญในการใช้รูปแบบล่ามคือการกำหนดชุดของกฎไวยากรณ์หรือที่เรียกว่าไวยากรณ์ ไม่ว่ากฎของไวยากรณ์นี้จะง่ายหรือซับซ้อนกฎเหล่านี้จะต้องรวมอยู่ด้วยเนื่องจากโหมดล่ามคือการวิเคราะห์และดำเนินการฟังก์ชั่นที่สอดคล้องกัน
มาดูแผนภาพโครงสร้างและคำอธิบายของโหมดล่าม:
AbstractExpression: กำหนดอินเทอร์เฟซของล่ามและเห็นด้วยกับการดำเนินการตีความของล่าม
Terminalexpression: terminalexpression ใช้ในการดำเนินการที่เกี่ยวข้องกับ Terminator ในกฎไวยากรณ์ไม่มีล่ามอื่น ๆ อีกต่อไป หากรูปแบบการรวมกันถูกใช้เพื่อสร้างทรีไวยากรณ์นามธรรมมันจะเทียบเท่ากับวัตถุใบในรูปแบบการรวมกันและอาจมีล่ามเทอร์มิเนเตอร์หลายตัว
Nonterminalexpression: ล่ามที่ไม่ใช่เทอร์มินัลใช้ในการดำเนินการที่ไม่เกี่ยวข้องกับขั้วในกฎไวยากรณ์ โดยปกติแล้วล่ามจะสอดคล้องกับกฎไวยากรณ์และสามารถมีล่ามอื่น ๆ ได้ หากรูปแบบองค์ประกอบถูกใช้เพื่อสร้างทรีไวยากรณ์นามธรรมมันจะเทียบเท่ากับวัตถุรวมในรูปแบบองค์ประกอบ อาจมีล่ามที่ไม่ใช่เทอร์มินัลหลายตัว
บริบท: บริบทมักจะมีข้อมูลที่ต้องการโดยล่ามหรือฟังก์ชั่นสาธารณะ
ไคลเอนต์: ไคลเอนต์หมายถึงไคลเอนต์ที่ใช้ล่าม โดยปกติแล้วนิพจน์ที่ทำตามไวยากรณ์ของภาษาจะถูกแปลงเป็นแผนผังไวยากรณ์นามธรรมที่อธิบายโดยวัตถุล่ามและจากนั้นจึงเรียกการดำเนินการคำอธิบาย
ที่นี่เราใช้ตัวอย่าง XML เพื่อทำความเข้าใจรูปแบบล่าม:
ก่อนอื่นเราต้องออกแบบไวยากรณ์ง่ายๆสำหรับการแสดงออก เพื่อจุดประสงค์ทั่วไปให้ใช้รูทเพื่อแสดงองค์ประกอบรูท, ABC ฯลฯ เพื่อแสดงองค์ประกอบ XML ง่าย ๆ มีดังนี้:
การคัดลอกรหัสมีดังนี้:
<? xml version = "1.0" encoding = "utf-8">
<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>
ไวยากรณ์ของการแสดงออกของอนุสัญญามีดังนี้:
1. รับค่าขององค์ประกอบเดียว: เริ่มต้นจากองค์ประกอบรูทและไปจนถึงองค์ประกอบที่คุณต้องการรับค่า กลางขององค์ประกอบจะถูกคั่นด้วย "/" และไม่มี "/" ถูกเพิ่มก่อนองค์ประกอบรูท ตัวอย่างเช่นนิพจน์ "root/a/b/c" หมายถึงการได้รับค่าขององค์ประกอบ C ภายใต้องค์ประกอบรูทองค์ประกอบ A, องค์ประกอบ B และองค์ประกอบ C
2. รับค่าของแอตทริบิวต์ขององค์ประกอบเดียว: แน่นอนว่ามีหลายคุณลักษณะ แอตทริบิวต์ที่จะได้รับค่าจะต้องเป็นแอตทริบิวต์ขององค์ประกอบสุดท้ายของนิพจน์ เพิ่ม "." หลังจากองค์ประกอบสุดท้ายจากนั้นเพิ่มชื่อของแอตทริบิวต์ ตัวอย่างเช่นนิพจน์ "root/a/b/c.name" หมายถึงการได้รับค่าของแอตทริบิวต์ชื่อขององค์ประกอบรูทองค์ประกอบ A, องค์ประกอบ B, องค์ประกอบ C
3. รับค่าของชื่อองค์ประกอบเดียวกันแน่นอนมีหลายองค์ประกอบ องค์ประกอบที่จะได้รับค่าจะต้องเป็นองค์ประกอบสุดท้ายของนิพจน์และเพิ่ม "$" หลังจากองค์ประกอบสุดท้าย ตัวอย่างเช่นนิพจน์ "root/a/b/d $" หมายถึงการรวบรวมค่าขององค์ประกอบหลาย D ภายใต้องค์ประกอบรูทภายใต้องค์ประกอบ A และภายใต้องค์ประกอบ B
4. รับค่าของแอตทริบิวต์ที่มีชื่อองค์ประกอบเดียวกันแน่นอนมีหลาย: องค์ประกอบที่จะได้รับค่าแอตทริบิวต์จะต้องเป็นองค์ประกอบสุดท้ายของนิพจน์และเพิ่ม "$" หลังจากองค์ประกอบสุดท้าย ตัวอย่างเช่นนิพจน์ "root/a/b/d $ .id $" หมายถึงการรวบรวมค่าของแอตทริบิวต์ ID องค์ประกอบหลายตัวภายใต้องค์ประกอบรูทภายใต้องค์ประกอบ A และภายใต้องค์ประกอบ B
XML ด้านบนซึ่งสอดคล้องกับแผนผังไวยากรณ์นามธรรมและโครงสร้างที่เป็นไปได้จะแสดงในรูป:
มาดูรหัสเฉพาะด้านล่าง:
1. กำหนดบริบท:
การคัดลอกรหัสมีดังนี้:
-
* บริบทใช้เพื่อมีข้อมูลระดับโลกบางส่วนที่ล่าม
* @param {String} filePathName [เส้นทางและชื่อของ XML ที่ต้องอ่าน]
-
บริบทฟังก์ชัน (filepathname) {
// องค์ประกอบที่ประมวลผลก่อนหน้านี้
this.peele = null;
// วัตถุเอกสาร XML
this.document = xmlutil.getRoot (filePathName);
-
context.prototype = {
// reinitialize บริบท
reinit: function () {
this.peele = null;
-
-
* วิธีการใช้งานสาธารณะในแต่ละนิพจน์
* รับองค์ประกอบปัจจุบันตามชื่อขององค์ประกอบหลักและองค์ประกอบปัจจุบัน
* @param {องค์ประกอบ} pele [องค์ประกอบหลัก]
* @param {String} elename [ชื่อองค์ประกอบปัจจุบัน]
* @return {องค์ประกอบ | null} [องค์ประกอบปัจจุบันพบ]
-
getNowele: function (pele, elename) {
var tempnodelist = pele.childnodes;
var nowele;
สำหรับ (var i = 0, len = tempnodelist.length; i <len; i ++) {
if ((nowele = tempnodelist [i]). nodetype === 1)
if (nowele.nodename === elename)
กลับมา Nowele;
-
คืนค่า null;
-
getPreele: function () {
ส่งคืนสิ่งนี้ preele;
-
setPreele: ฟังก์ชั่น (preele) {
this.peele = preele;
-
getDocument: function () {
ส่งคืนเอกสารนี้;
-
-
ในบริบทฉันใช้วัตถุเครื่องมือ xmlutil เพื่อรับ xmldom ด้านล่างฉันใช้ dompaser ของ DOM3 เบราว์เซอร์บางตัวอาจไม่รองรับ กรุณาใช้เบราว์เซอร์พื้นฐาน:
การคัดลอกรหัสมีดังนี้:
// เครื่องมือเครื่องมือ
// แยกวิเคราะห์ XML เพื่อรับวัตถุเอกสารที่เกี่ยวข้อง
var xmlutil = {
getRoot: function (filePathName) {
var parser = new Domparser ();
var xmldom = parser.parsefromstring ('<root id = "rootid"> <a> <b> <c name = "testc"> 12345 </c> <d id = "1"> d1 </d> <d id = "2"> d2 </d> id = "4"> d4 </d> </b> </a> </root> ',' text/xml ');
กลับ xmldom;
-
-
นี่คือรหัสสำหรับล่าม:
การคัดลอกรหัสมีดังนี้:
-
* องค์ประกอบถูกใช้เป็นล่ามที่สอดคล้องกับที่ไม่ใช่ขั้วเพื่อตีความและดำเนินการองค์ประกอบกลาง
* @param {String} elename [ชื่อขององค์ประกอบ]
-
ฟังก์ชั่น ElementExpression (elename) {
this.eles = [];
this.elename = elename;
-
ElementExpression.prototype = {
Addele: ฟังก์ชั่น (elename) {
this.eles.push (elename);
กลับมาจริง;
-
Removeele: ฟังก์ชั่น (ele) {
สำหรับ (var i = 0, len = this.eles.length; i <len; i ++) {
if (ele === this.eles [i])
this.eles.splice (i--, 1);
-
กลับมาจริง;
-
ตีความ: ฟังก์ชั่น (บริบท) {
// ก่อนอื่นนำองค์ประกอบปัจจุบันในบริบทเป็นองค์ประกอบหลัก
// ค้นหาองค์ประกอบ XML ที่สอดคล้องกับชื่อองค์ประกอบปัจจุบันและตั้งกลับเป็นบริบท
var pele = context.getPreele ();
if (! pele) {
// ระบุว่าตอนนี้ได้รับองค์ประกอบรูท
Context.setPreele (context.getDocument (). DocumentElement);
} อื่น {
// รับองค์ประกอบปัจจุบันตามชื่อขององค์ประกอบหลักและองค์ประกอบที่จะค้นหา
var nowele = context.getNowele (pele, this.elename);
// ใส่องค์ประกอบที่ดึงมาในปัจจุบันลงในบริบท
Context.setPreele (Nowele);
-
var ss;
// ลูปเพื่อเรียกวิธีการตีความขององค์ประกอบลูก
สำหรับ (var i = 0, len = this.eles.length; i <len; i ++) {
ss = this.eles [i] .interpret (บริบท);
-
// ส่งคืนผลลัพธ์คำอธิบายของล่ามสุดท้าย โดยทั่วไปล่ามสุดท้ายคือล่ามเทอร์มิเนเตอร์
กลับ SS;
-
-
-
* องค์ประกอบถูกใช้เป็นล่ามที่สอดคล้องกับ terminator
* @param {String} ชื่อ [ชื่อองค์ประกอบ]
-
ฟังก์ชั่น elementterminalexpression (ชื่อ) {
this.elename = ชื่อ;
-
ElementTerMinalexpression.prototype = {
ตีความ: ฟังก์ชั่น (บริบท) {
var pele = context.getPreele ();
var ele = null;
if (! pele) {
Ele = context.getDocument (). DocumentElement;
} อื่น {
ele = context.getNowele (pele, this.elename);
Context.setPreele (Ele);
-
// รับค่าขององค์ประกอบ
ส่งคืน ele.firstchild.nodevalue;
-
-
-
* แอตทริบิวต์ถูกใช้เป็นล่ามที่สอดคล้องกับเทอร์มิเนเตอร์
* @param {String} propname [ชื่อของแอตทริบิวต์]
-
ฟังก์ชั่น PropertyTerminalexpression (ชื่อ propname) {
this.propname = propname;
-
PropertyTreminalexpression.prototype = {
ตีความ: ฟังก์ชั่น (บริบท) {
// รับค่าของแอตทริบิวต์องค์ประกอบสุดท้ายโดยตรง
return context.getPreele (). getAttribute (this.propname);
-
-
ก่อนอื่นให้ดูวิธีการใช้ล่ามเพื่อรับค่าขององค์ประกอบเดียว:
การคัดลอกรหัสมีดังนี้:
ฟังก์ชั่นโมฆะ () {
var c = บริบทใหม่ ();
// ต้องการรับค่าขององค์ประกอบหลายตัวนั่นคือค่าของนิพจน์ต่อไปนี้: "root/a/b/c"
// ก่อนอื่นคุณต้องสร้างแผนผังไวยากรณ์นามธรรมสำหรับล่าม
var root = new ElementExpression ('root');
var aele = new ElementExpression ('A');
var bele = new ElementExpression ('B');
var cele = new Elementterminalexpression ('C');
// การผสมผสาน
root.addele (aele);
aele.addele (เบล);
BELE.ADDELE (CELE);
console.log ('ค่าของ c คือ =' + root.interpret (c));
-
เอาต์พุต: ค่าของ C คือ = 12345
จากนั้นเราใช้รหัสด้านบนเพื่อรับค่าของแอตทริบิวต์ขององค์ประกอบเดียว:
การคัดลอกรหัสมีดังนี้:
ฟังก์ชั่นโมฆะ () {
var c = บริบทใหม่ ();
// ต้องการรับแอตทริบิวต์ ID ขององค์ประกอบ D นั่นคือค่าของนิพจน์ต่อไปนี้: "A/B/C.Name"
// เวลานี้ C ยังไม่จบและคุณต้องแก้ไข C เป็น ElementExpression
var root = new ElementExpression ('root');
var aele = new ElementExpression ('A');
var bele = new ElementExpression ('B');
var cele = new ElementExpression ('C');
var prop = new PropertyTerminalexpression ('ชื่อ');
// การผสมผสาน
root.addele (aele);
aele.addele (เบล);
BELE.ADDELE (CELE);
cele.addele (เสา);
console.log ('ค่าชื่อคุณสมบัติของ c คือ =' + root.interpret (c));
// หากคุณต้องการใช้บริบทเดียวกันและแยกวิเคราะห์อย่างต่อเนื่องคุณต้องปรับเปลี่ยนวัตถุบริบทใหม่
// ตัวอย่างเช่นคุณต้องรับค่าของชื่อแอตทริบิวต์อีกครั้งอย่างต่อเนื่องแน่นอนคุณสามารถรวมองค์ประกอบอีกครั้ง
// re-parse ตราบใดที่มีการใช้บริบทเดียวกันวัตถุบริบทจะต้องมีการเริ่มต้นใหม่
C.Reinit ();
console.log ('reget c ค่าชื่อคุณสมบัติของ C คือ =' + root.interpret (c));
-
เอาต์พุต: ค่าชื่อแอตทริบิวต์ของ C คือ = TESTC ดึงค่าชื่อแอตทริบิวต์ของ C คือ = TESTC
อธิบาย:
1. ฟังก์ชั่นโหมดล่าม:
รูปแบบล่ามใช้วัตถุล่ามเพื่อแสดงและประมวลผลกฎไวยากรณ์ที่สอดคล้องกัน โดยทั่วไปล่ามจัดการกฎไวยากรณ์ ในทางทฤษฎีตราบใดที่การแสดงออกที่สอดคล้องกับไวยากรณ์สามารถแสดงได้ด้วยวัตถุล่ามและสามารถสร้างแผนผังไวยากรณ์นามธรรมรูปแบบล่ามสามารถใช้เพื่อจัดการได้
2. กฎไวยากรณ์และล่าม
มีการติดต่อระหว่างกฎไวยากรณ์และล่าม โดยทั่วไปล่ามจัดการกฎไวยากรณ์ แต่สิ่งที่ตรงกันข้ามไม่เป็นความจริง กฎไวยากรณ์สามารถมีการตีความและการประมวลผลหลายอย่างนั่นคือกฎไวยากรณ์สามารถสอดคล้องกับล่ามหลายคน
3. บริบทสามัญชน
บริบทมีบทบาทสำคัญมากในโหมดล่าม เพราะบริบทถูกส่งผ่านไปยังล่ามทั้งหมด ดังนั้นสถานะของล่ามสามารถจัดเก็บและเข้าถึงในบริบท ตัวอย่างเช่นล่ามก่อนหน้านี้สามารถจัดเก็บข้อมูลบางอย่างในบริบทและล่ามหลังสามารถรับค่าเหล่านี้ได้
นอกจากนี้ข้อมูลบางอย่างที่อยู่นอกล่ามสามารถผ่านบริบทได้ แต่ล่ามต้องการมันและข้อมูลสาธารณะทั่วโลก
นอกจากนี้ยังมีฟังก์ชั่นในบริบทซึ่งก็คือมันสามารถให้ฟังก์ชั่นทั่วไปของวัตถุล่ามทั้งหมดคล้ายกับการรวมกันของวัตถุแทนที่จะใช้การสืบทอดเพื่อให้ได้ฟังก์ชั่นทั่วไปซึ่งสามารถเรียกได้ในแต่ละวัตถุล่าม
4. ใครจะสร้างแผนผังไวยากรณ์นามธรรม
ในตัวอย่างก่อนหน้านี้มันเป็นเรื่องยากมากที่จะสร้างแผนผังไวยากรณ์นามธรรมด้วยตนเองในฝั่งไคลเอ็นต์ แต่ในโหมดล่ามส่วนนี้ส่วนหนึ่งของฟังก์ชั่นไม่เกี่ยวข้องและเป็นเพียงหน้าที่ในการตีความและประมวลผลแผนผังนามธรรมนามธรรมที่สร้างขึ้น เราจะแนะนำว่าเราสามารถจัดเตรียมตัวแยกวิเคราะห์เพื่อแปลงนิพจน์เป็นต้นไม้ไวยากรณ์นามธรรม
มีปัญหาอีกอย่างหนึ่งนั่นคือกฎไวยากรณ์สามารถสอดคล้องกับวัตถุล่ามหลายชิ้นนั่นคือองค์ประกอบเดียวกันสามารถแปลงเป็นวัตถุล่ามหลายชิ้นซึ่งหมายความว่าการแสดงออกเดียวกันสามารถสร้างแผนผังนามธรรมนามธรรมที่ไม่จำเป็น
5. ใครเป็นผู้รับผิดชอบในการอธิบายการดำเนินการ
ตราบใดที่ทรีไวยากรณ์นามธรรมถูกกำหนดล่ามจะต้องรับผิดชอบในการตีความและดำเนินการ แม้ว่าจะมีกฎไวยากรณ์ที่แตกต่างกัน แต่ล่ามจะไม่รับผิดชอบในการเลือกวัตถุล่ามที่จะใช้เพื่อตีความกฎไวยากรณ์การดำเนินการ ฟังก์ชั่นของการเลือกล่ามจะเสร็จสมบูรณ์เมื่อสร้างทรีไวยากรณ์นามธรรม
6. ลำดับการโทรของโหมดล่าม
1) สร้างวัตถุบริบท
2) สร้างวัตถุล่ามหลายชิ้นและรวมต้นไวยากรณ์นามธรรม
3) เรียกใช้การตีความการตีความของวัตถุล่าม
3.1) จัดเก็บและเข้าถึงสถานะของล่ามผ่านบริบท
สำหรับวัตถุล่ามที่ไม่ใช่เทอร์มิเนเตอร์ให้เรียกวัตถุ subinterpreter ซ้ำอีกครั้ง
สาระสำคัญของรูปแบบล่าม: *การใช้งานแยกต่างหากตีความการดำเนินการ *
โมดูลล่ามใช้วัตถุล่ามเพื่อประมวลผลกฎไวยากรณ์เพื่อแยกฟังก์ชั่นที่ซับซ้อน จากนั้นเลือกฟังก์ชั่นที่จำเป็นต้องดำเนินการและรวมฟังก์ชั่นเหล่านี้ไว้ในแผนผังไวยากรณ์นามธรรมที่ต้องตีความและดำเนินการ จากนั้นตีความการดำเนินการตามทรีไวยากรณ์นามธรรมเพื่อใช้ฟังก์ชั่นที่สอดคล้องกัน
บนพื้นผิวโหมดล่ามจะมุ่งเน้นไปที่การประมวลผลของไวยากรณ์ที่กำหนดเองที่เราไม่ได้ใช้ แต่ในสาระสำคัญความคิดของโหมดล่ามคือการแยกการห่อหุ้มการทำให้เข้าใจง่ายและเป็นโหมดเดียวกัน
ตัวอย่างเช่นโหมดล่ามสามารถใช้เพื่อจำลองฟังก์ชั่นของโหมดสถานะ หากไวยากรณ์ที่จะประมวลผลโดยโหมดล่ามจะง่ายขึ้นถึงเครื่องหมายสถานะเดียวเท่านั้นล่ามจะถือเป็นวัตถุการประมวลผลสำหรับสถานะ สำหรับไวยากรณ์เดียวกันที่เป็นตัวแทนของรัฐอาจมีล่ามที่ไม่ได้ใช้จำนวนมากนั่นคือมีวัตถุจำนวนมากที่มีสถานะการประมวลผลที่แตกต่างกัน เมื่อสร้างแผนผังไวยากรณ์นามธรรมมันจะง่ายขึ้นในการสร้างล่ามที่สอดคล้องกันตามเครื่องหมายสถานะและไม่จำเป็นต้องสร้างต้นไม้
ในทำนองเดียวกันโหมดล่ามสามารถจำลองฟังก์ชั่นของการใช้โหมดนโยบายฟังก์ชั่นของโหมดมัณฑนากร ฯลฯ โดยเฉพาะอย่างยิ่งกระบวนการจำลองการทำงานของโหมดมัณฑนากรและกระบวนการสร้างแผนผังไวยากรณ์นามธรรมจะสอดคล้องกับกระบวนการรวมมัณฑนากร
โหมดล่ามมักจะไม่เร็ว (ส่วนใหญ่ช้ามาก) และข้อผิดพลาดการดีบักเป็นเรื่องยาก (ตอนที่ 1: แม้ว่าการดีบักจะยาก แต่จริง ๆ แล้วมันช่วยลดความเป็นไปได้ของข้อผิดพลาด) แต่ข้อดีของมันชัดเจน มันสามารถควบคุมความซับซ้อนของอินเทอร์เฟซระหว่างโมดูลได้อย่างมีประสิทธิภาพ สำหรับฟังก์ชั่นที่มีความถี่การดำเนินการต่ำ แต่ความถี่รหัสสูงพอและมีความหลากหลายมากล่ามนั้นเหมาะสำหรับโหมด นอกจากนี้ล่ามมีข้อได้เปรียบที่เห็นได้ชัดเจนอีกอย่างหนึ่งซึ่งก็คือสามารถข้ามภาษาและข้ามแพลตฟอร์มได้อย่างสะดวก
ข้อดีและข้อเสียของโหมดล่าม:
ข้อได้เปรียบ:
1. ใช้ไวยากรณ์ง่าย
ในโหมดล่ามกฎไวยากรณ์จะถูกตีความด้วยวัตถุล่ามเพื่อตีความการดำเนินการ สำหรับการใช้งานล่ามฟังก์ชั่นจะค่อนข้างง่าย คุณจะต้องพิจารณาการใช้กฎไวยากรณ์นี้และคุณไม่ต้องกังวลเกี่ยวกับสิ่งอื่นใด 2. ง่ายต่อการขยายไวยากรณ์ใหม่
มันเป็นเพราะวิธีที่วัตถุล่ามรับผิดชอบกฎไวยากรณ์ที่ขยายไวยากรณ์ใหม่นั้นง่ายมาก ไวยากรณ์ใหม่ได้รับการขยายและคุณจะต้องสร้างวัตถุล่ามที่เกี่ยวข้องและใช้วัตถุล่ามใหม่นี้เมื่อสร้างแผนผังไวยากรณ์นามธรรม
ข้อบกพร่อง:
ไม่เหมาะสำหรับไวยากรณ์ที่ซับซ้อน
หากไวยากรณ์มีความซับซ้อนเป็นพิเศษการทำงานของการสร้างทรีไวยากรณ์นามธรรมที่ต้องการโดยรูปแบบล่ามนั้นยากมากและเป็นไปได้ที่จะสร้างต้นไวยากรณ์นามธรรมหลายต้น ดังนั้นรูปแบบล่ามจึงไม่เหมาะสำหรับไวยากรณ์ที่ซับซ้อน มันอาจจะดีกว่าที่จะใช้เครื่องวิเคราะห์ไวยากรณ์หรือเครื่องกำเนิดคอมไพเลอร์
ควรใช้เมื่อใด
เมื่อมีภาษาที่ต้องตีความและดำเนินการและประโยคในภาษานั้นสามารถแสดงเป็นแผนผังไวยากรณ์นามธรรมคุณสามารถพิจารณาใช้รูปแบบล่าม
เมื่อใช้โหมดล่ามมีคุณสมบัติอื่นอีกสองอย่างที่ต้องพิจารณา หนึ่งคือไวยากรณ์ควรจะค่อนข้างง่าย ไวยากรณ์ที่รับผิดชอบเกินไปไม่เหมาะสำหรับการใช้โหมดล่าม อีกอย่างคือข้อกำหนดด้านประสิทธิภาพไม่สูงมากและข้อกำหนดด้านประสิทธิภาพสูงมากและไม่เหมาะสำหรับการใช้งาน
การแนะนำก่อนหน้านี้คือวิธีการรับค่าขององค์ประกอบเดียวและค่าของแอตทริบิวต์องค์ประกอบเดียว ลองมาดูวิธีที่จะได้รับค่าขององค์ประกอบหลายอย่างรวมถึงค่าของชื่อของกันและกันในหลายองค์ประกอบและการทดสอบก่อนหน้านี้ทั้งหมดเป็นต้นไม้ไวยากรณ์นามธรรมรวมกัน นอกจากนี้เรายังใช้ตัวแยกวิเคราะห์อย่างง่ายต่อไปนี้เพื่อแปลงนิพจน์ที่สอดคล้องกับไวยากรณ์ที่กำหนดไว้ข้างต้นเป็นแผนผังไวยากรณ์นามธรรมของล่ามที่นำไปใช้ด้านบน: ฉันโพสต์รหัสโดยตรง:
การคัดลอกรหัสมีดังนี้:
// อ่านค่าขององค์ประกอบหลายองค์ประกอบหรือแอตทริบิวต์
(การทำงาน () {
-
* บริบทใช้เพื่อมีข้อมูลระดับโลกบางส่วนที่ล่าม
* @param {String} filePathName [เส้นทางและชื่อของ XML ที่ต้องอ่าน]
-
บริบทฟังก์ชัน (filepathname) {
// องค์ประกอบหลายอย่างที่ประมวลผลในก่อนหน้านี้
this.preeles = [];
// วัตถุเอกสาร XML
this.document = xmlutil.getRoot (filePathName);
-
context.prototype = {
// reinitialize บริบท
reinit: function () {
this.preeles = [];
-
-
* วิธีการใช้งานสาธารณะในแต่ละนิพจน์
* รับองค์ประกอบปัจจุบันตามชื่อขององค์ประกอบหลักและองค์ประกอบปัจจุบัน
* @param {องค์ประกอบ} pele [องค์ประกอบหลัก]
* @param {String} elename [ชื่อองค์ประกอบปัจจุบัน]
* @return {องค์ประกอบ | null} [องค์ประกอบปัจจุบันพบ]
-
getNoweles: function (pele, elename) {
องค์ประกอบ var = [];
var tempnodelist = pele.childnodes;
var nowele;
สำหรับ (var i = 0, len = tempnodelist.length; i <len; i ++) {
if ((nowele = tempNodelist [i]). nodeType === 1) {
if (nowele.nodename === elename) {
Elements.push (Nowele);
-
-
-
องค์ประกอบกลับ;
-
getPreeles: function () {
ส่งคืนสิ่งนี้
-
setPreeles: function (noweles) {
this.preeles = noweles;
-
getDocument: function () {
ส่งคืนเอกสารนี้;
-
-
// เครื่องมือเครื่องมือ
// แยกวิเคราะห์ XML เพื่อรับวัตถุเอกสารที่เกี่ยวข้อง
var xmlutil = {
getRoot: function (filePathName) {
var parser = new Domparser ();
var xmldom = parser.parsefromstring ('<root id = "rootid"> <a> <b> <c name = "testc"> 12345 </c> <d id = "1"> d1 </d> <d id = "2"> d2 </d> id = "4"> d4 </d> </b> </a> </root> ',' text/xml ');
กลับ xmldom;
-
-
-
* องค์ประกอบถูกใช้เป็นล่ามที่สอดคล้องกับที่ไม่ใช่ขั้วเพื่อตีความและดำเนินการองค์ประกอบกลาง
* @param {String} elename [ชื่อขององค์ประกอบ]
-
ฟังก์ชั่น ElementExpression (elename) {
this.eles = [];
this.elename = elename;
-
ElementExpression.prototype = {
Addele: ฟังก์ชั่น (elename) {
this.eles.push (elename);
กลับมาจริง;
-
Removeele: ฟังก์ชั่น (ele) {
สำหรับ (var i = 0, len = this.eles.length; i <len; i ++) {
ถ้า (ele === this.eles [i]) {
this.eles.splice (i--, 1);
-
-
กลับมาจริง;
-
ตีความ: ฟังก์ชั่น (บริบท) {
// ก่อนอื่นนำองค์ประกอบปัจจุบันในบริบทเป็นองค์ประกอบหลัก
// ค้นหาองค์ประกอบ XML ที่สอดคล้องกับชื่อองค์ประกอบปัจจุบันและตั้งกลับเป็นบริบท
var peles = context.getPreeles ();
var ele = null;
var noweles = [];
if (! peles.length) {
// ระบุว่าตอนนี้ได้รับองค์ประกอบรูท
Ele = context.getDocument (). DocumentElement;
Peles.Push (Ele);
Context.setPreeles (Peles);
} อื่น {
var tempele;
สำหรับ (var i = 0, len = peles.length; i <len; i ++) {
tempele = peles [i];
noweles = noweles.concat (context.getNoweles (Tempele, this.elename));
// หยุดถ้าคุณพบ
if (noweles.length) break;
-
Context.setPreeles ([Noweles [0]]);
-
var ss;
// ลูปเพื่อเรียกวิธีการตีความขององค์ประกอบลูก
สำหรับ (var i = 0, len = this.eles.length; i <len; i ++) {
ss = this.eles [i] .interpret (บริบท);
-
กลับ SS;
-
-
-
* องค์ประกอบถูกใช้เป็นล่ามที่สอดคล้องกับ terminator
* @param {String} ชื่อ [ชื่อองค์ประกอบ]
-
ฟังก์ชั่น elementterminalexpression (ชื่อ) {
this.elename = ชื่อ;
-
ElementTerMinalexpression.prototype = {
ตีความ: ฟังก์ชั่น (บริบท) {
var peles = context.getPreeles ();
var ele = null;
if (! peles.length) {
Ele = context.getDocument (). DocumentElement;
} อื่น {
ele = context.getNoweles (peles [0], this.elename) [0];
-
// รับค่าขององค์ประกอบ
ส่งคืน ele.firstchild.nodevalue;
-
-
-
* แอตทริบิวต์ถูกใช้เป็นล่ามที่สอดคล้องกับเทอร์มิเนเตอร์
* @param {String} propname [ชื่อของแอตทริบิวต์]
-
ฟังก์ชั่น PropertyTerminalexpression (ชื่อ propname) {
this.propname = propname;
-
PropertyTreminalexpression.prototype = {
ตีความ: ฟังก์ชั่น (บริบท) {
// รับค่าของแอตทริบิวต์องค์ประกอบสุดท้ายโดยตรง
return context.getPreeles () [0] .getAttribute (this.propname);
-
-
-
* มีการใช้แอตทริบิวต์หลายตัวเป็นล่ามที่สอดคล้องกับเทอร์มิเนเตอร์
* @param {String} propname [ชื่อของแอตทริบิวต์]
-
ฟังก์ชัน PropertyMinalexpression (ชื่อ propname) {
this.propname = propname;
-
PropertysterMinalexpression.prototype = {
ตีความ: ฟังก์ชั่น (บริบท) {
var eles = context.getPreeles ();
var ss = [];
สำหรับ (var i = 0, len = eles.length; i <len; i ++) {
ss.push (eles [i] .getAttribute (this.propname));
-
กลับ SS;
-
-
-
* การตีความวัตถุประมวลผลที่มีหลายองค์ประกอบเป็นเทอร์มินัล
* @param {[type]} ชื่อ [คำอธิบาย]
-
ฟังก์ชัน ElementSterminalexpression (ชื่อ) {
this.elename = ชื่อ;
-
ElementSterminalexpression.prototype = {
ตีความ: ฟังก์ชั่น (บริบท) {
var peles = context.getPreeles ();
var noweles = [];
สำหรับ (var i = 0, len = peles.length; i <len; i ++) {
noweles = noweles.concat (context.getNoweles (peles [i], this.elename));
-
var ss = [];
สำหรับ (i = 0, len = noweles.length; i <len; i ++) {
ss.push (noweles [i] .firstchild.nodevalue);
-
กลับ SS;
-
-
-
* องค์ประกอบหลายอย่างถูกตีความว่าไม่ใช่เทอร์มินัล
-
ฟังก์ชัน ElementSexpression (ชื่อ) {
this.elename = ชื่อ;
this.eles = [];
-
ElementSexpression.prototype = {
ตีความ: ฟังก์ชั่น (บริบท) {
var peles = context.getPreeles ();
var noweles = [];
สำหรับ (var i = 0, len = peles.length; i <len; i ++) {
noweles = noweles.concat (context.getNoweles (peles [i], this.elename));
-
Context.setPreeles (Noweles);
var ss;
สำหรับ (i = 0, len = this.eles.length; i <len; i ++) {
ss = this.eles [i] .interpret (บริบท);
-
กลับ SS;
-
Addele: ฟังก์ชั่น (ele) {
this.eles.push (ele);
กลับมาจริง;
-
Removeele: ฟังก์ชั่น (ele) {
สำหรับ (var i = 0, len = this.eles.length; i <len; i ++) {
ถ้า (ele === this.eles [i]) {
this.eles.splice (i--, 1);
-
-
กลับมาจริง;
-
-
ฟังก์ชั่นโมฆะ () {
// "root/a/b/d $"
var c = บริบทใหม่ ('interpreter.xml');
var root = new ElementExpression ('root');
var aele = new ElementExpression ('A');
var bele = new ElementExpression ('B');
var dele = elementsTerminalexpression ('d');
root.addele (aele);
aele.addele (เบล);
bele.addele (Dele);
var ss = root.interpret (c);
สำหรับ (var i = 0, len = ss.length; i <len; i ++) {
console.log ('d ค่าคือ =' + ss [i]);
-
-
ฟังก์ชั่นโมฆะ () {
// a/b/d $ .id $
var c = บริบทใหม่ ('interpreter.xml');
var root = new ElementExpression ('root');
var aele = new ElementExpression ('A');
var bele = new ElementExpression ('B');
var dele = new ElementSexpression ('D');
var prop = new PropertyMinalexpression ('id');
root.addele (aele);
aele.addele (เบล);
bele.addele (Dele);
dele.addele (เสา);
var ss = root.interpret (c);
สำหรับ (var i = 0, len = ss.length; i <len; i ++) {
console.log ('d id property id ค่าคือ =' + ss [i]);
-
-
// parser
-
* แนวคิดการใช้งานของ Parser
* 1. การย่อยสลายนิพจน์ที่ส่งผ่านโดยลูกค้าย่อยสลายเป็นองค์ประกอบทีละตัวและใช้แบบจำลองการวิเคราะห์ที่สอดคล้องกันเพื่อห่อหุ้มข้อมูลบางอย่างเกี่ยวกับองค์ประกอบนี้
* 2. แปลงเป็นวัตถุตัวแยกวิเคราะห์ที่สอดคล้องกันตามข้อมูลของแต่ละองค์ประกอบ
* 3. รวมวัตถุตัวแยกวิเคราะห์เหล่านี้เพื่อรับแผนผังนามธรรมแบบนามธรรม
-
* ทำไมคุณไม่รวม 1 และ 2 และสลายองค์ประกอบโดยตรงและแปลงเป็นวัตถุตัวแยกวิเคราะห์ที่สอดคล้องกัน
* 1. การแยกฟังก์ชั่นอย่าทำให้ฟังก์ชั่นของวิธีการซับซ้อนเกินไป
* 2. สำหรับการดัดแปลงและการขยายในอนาคตตอนนี้ไวยากรณ์นั้นง่ายดังนั้นจึงมีการพิจารณาเพียงเล็กน้อยเมื่อแปลงเป็นวัตถุตัวแยกวิเคราะห์และมันก็ไม่ยากที่จะแปลงโดยตรง แต่ถ้าไวยากรณ์มีความซับซ้อนการแปลงโดยตรงจะยุ่งเหยิงมาก
-
-
* ใช้เพื่อห่อหุ้มคุณลักษณะที่สอดคล้องกันของแต่ละองค์ประกอบที่แยกวิเคราะห์
-
ฟังก์ชั่น parsermodel () {
// ไม่ว่าจะเป็นค่าเดียว
this.singlevalue;
// ไม่ว่าจะเป็นแอตทริบิวต์ทั้งแอตทริบิวต์หรือองค์ประกอบ
this.propertyvalue;
// ว่าจะยุติ
this.end;
-
parsermodel.prototype = {
isend: function () {
ส่งคืนสิ่งนี้
-
setend: function (end) {
this.end = สิ้นสุด;
-
issinglevalue: function () {
คืนสิ่งนี้ singlevalue;
-
SetSinglevalue: ฟังก์ชั่น (OneValue) {
this.singlevalue = oneValue;
-
isPropertyValue: function () {
ส่งคืนสิ่งนี้ propertyvalue;
-
SetProperTyValue: ฟังก์ชั่น (PropertyValue) {
this.propertyValue = PropertyValue;
-
-
var parser = function () {
var backlash = '/';
var dot = '.';
var dollar = '$';
// บันทึกชื่อขององค์ประกอบที่ต้องแยกวิเคราะห์ตามลำดับของการสลายตัว
var listele = null;
// เริ่มขั้นตอนแรก ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
* ส่งในนิพจน์สตริงแล้วแยกวิเคราะห์และรวมเข้ากับแผนผังนามธรรมแบบนามธรรม
* @param {string} expr [อธิบายนิพจน์สตริงเพื่อรับค่า]
* @return {Object} [ต้นไม้ไวยากรณ์นามธรรมที่สอดคล้องกัน]
-
ฟังก์ชั่น parsemappath (expr) {
// ก่อนแบ่งสตริงตาม "/"
var tokenizer = expr.split (backlash);
// ตารางของค่าย่อยสลาย
var mappath = {};
var onepath, elename, propname;
var dotindex = -1;
สำหรับ (var i = 0, len = tokenizer.length; i <len; i ++) {
onePath = tokenizer [i];
if (tokenizer [i + 1]) {
// มีค่าอื่นซึ่งหมายความว่านี่ไม่ใช่องค์ประกอบสุดท้าย
// ตามไวยากรณ์ปัจจุบันแอตทริบิวต์จะต้องอยู่ในตอนท้ายดังนั้นจึงไม่ใช่แอตทริบิวต์
setParsePath (เท็จ, onePath, false, mappath);
} อื่น {
// มันเป็นจุดจบ
dotindex = onepath.indexof (dot);
if (dotindex> = 0) {
// หมายความว่าคุณต้องการได้รับคุณค่าของแอตทริบิวต์ดังนั้นหารมันตาม "."
// อันแรกคือชื่อองค์ประกอบและชื่อที่สองคือชื่อแอตทริบิวต์
elename = onepath.substring (0, dotindex);
propname = onepath.substring (dotindex + 1);
// องค์ประกอบที่อยู่ด้านหน้าของสถานที่ให้บริการนั้นไม่ใช่องค์ประกอบสุดท้ายและไม่ใช่คุณสมบัติ
setParsePath (เท็จ, elename, false, mappath);
// ตั้งค่าแอตทริบิวต์ ตามคำจำกัดความของไวยากรณ์ปัจจุบันแอตทริบิวต์สามารถเป็นเพียงตัวอย่างสุดท้ายเท่านั้น
setParsePath (จริง, ชื่อ, จริง, mappath);
} อื่น {
// คำแนะนำถูกนำมาเป็นค่าขององค์ประกอบและค่าขององค์ประกอบสุดท้าย
SetParsePath (จริง, onepath, false, mappath);
-
หยุดพัก;
-
-
กลับมา Mappath;
-
-
* ตั้งชื่อองค์ประกอบที่จะแยกวิเคราะห์ตามตำแหน่งและชื่อที่ย่อยสลาย
* @param {boolean} end [มันเป็นครั้งสุดท้าย]
* @param {String} ele [ชื่อองค์ประกอบ]
* @param {Boolean} PropertyValue [ไม่ว่าจะใช้คุณสมบัติ]
* @param {Object} mappath [ตั้งชื่อขององค์ประกอบที่ต้องแยกวิเคราะห์และตารางของโมเดลการแยกวิเคราะห์ที่สอดคล้องกับองค์ประกอบ]
-
ฟังก์ชั่น setParsePath (สิ้นสุด, ele, propertyValue, mappath) {
var pm = ใหม่ parsermodel ();
PM.Setend (สิ้นสุด);
// ถ้าสัญลักษณ์ "$" ไม่ใช่ค่า
pm.setsinglevalue (! (ele.indexof (ดอลลาร์)> = 0));
PM.SetPropertyValue (PropertyValue);
// ลบ "$"
ele = ele.replace (ดอลลาร์, '');
mappath [ele] = pm;
listele.push (ele);
-
// เริ่มขั้นตอนที่สอง ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
* แปลงชื่อองค์ประกอบที่ย่อยสลายเป็นวัตถุล่ามที่สอดคล้องกันตามแบบจำลองการวิเคราะห์ที่สอดคล้องกัน
* @param {Object} mappath [ชื่อองค์ประกอบที่ย่อยสลายได้ที่จะแยกวิเคราะห์และรูปแบบการแยกวิเคราะห์ที่สอดคล้องกับองค์ประกอบ]
* @return {array} [แปลงแต่ละองค์ประกอบเป็นอาร์เรย์ของวัตถุล่ามที่สอดคล้องกัน]
-
ฟังก์ชั่น mappath2interpreter (mappath) {
var list = [];
var pm, คีย์;
var obj = null;
// จำเป็นต้องแปลงเป็นวัตถุล่ามในลำดับของการสลายตัว
สำหรับ (var i = 0, len = listele.length; i <len; i ++) {
key = listele [i];
pm = mappath [key];
// ไม่ใช่คนสุดท้าย
ถ้า (! pm.isend ()) {
ถ้า (pm.issinglevalue ())
// เป็นค่าการแปลง
OBJ = ใหม่ ElementExpression (คีย์);
อื่น
// คือหลายค่าการแปลง
OBJ = ใหม่ ElementSexpression (คีย์);
} อื่น {
// มันเป็นคนสุดท้าย
// เป็นค่าแอตทริบิวต์
if (pm.ispropertyValue ()) {
ถ้า (pm.issinglevalue ())
OBJ = ใหม่ PropertyMinalexpression (คีย์);
อื่น
OBJ = ใหม่ PropertyMinalexpression (คีย์);
// ใช้ค่าขององค์ประกอบ
} อื่น {
ถ้า (pm.issinglevalue ())
obj = ใหม่ elementterminalexpression (คีย์);
อื่น
OBJ = องค์ประกอบใหม่การแสดงออก (คีย์);
-
-
list.push (obj);
-
รายการคืน;
-
// เริ่มขั้นตอนที่สาม ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
* สร้างทรีไวยากรณ์นามธรรม
* @param {[ประเภท]} รายการ [แปลงแต่ละองค์ประกอบเป็นอาร์เรย์ของวัตถุล่ามที่สอดคล้องกัน]
* @return {[type]} [คำอธิบาย]
-
ฟังก์ชั่น buildtree (รายการ) {
// วัตถุแรกซึ่งเป็นวัตถุที่ส่งคืนเป็นรากของทรีไวยากรณ์นามธรรม
var returnedxmlexpr = null;
// กำหนดวัตถุก่อนหน้า
var prereadxmlexpr = null;
var readxml, ele, eles;
สำหรับ (var i = 0, len = list.length; i <len; i ++) {
readxml = list [i];
// คำอธิบายเป็นองค์ประกอบแรก
if (prereadxmlexpr === null) {
prereadxMlexpr = readxml;
returnreadxmlexpr = readxml;
// เพิ่มองค์ประกอบลงในวัตถุก่อนหน้าและตั้งค่าวัตถุเป็น oldre
// เป็นโหนดหลักของวัตถุถัดไป
} อื่น {
if (prereadxmlexpr อินสแตนซ์ของ ElementExpression) {
ELE = PREREADXMLEXPR;
Ele.addele (readxml);
prereadxMlexpr = readxml;
} อื่นถ้า (prereadxmlexpr อินสแตนซ์ของ elementsexpression) {
ELES = PREREADXMLEXPR;
eles.addele (readxml);
prereadxMlexpr = readxml;
-
-
-
returnreadreadxmlexpr;
-
กลับ {
// วิธีการสาธารณะ
แยกวิเคราะห์: ฟังก์ชั่น (expr) {
listele = [];
var mappath = parsemappath (expr);
var list = mappath2interpreter (mappath);
ส่งคืน buildtree (รายการ);
-
-
-
ฟังก์ชั่นโมฆะ () {
// เตรียมบริบท
var c = บริบทใหม่ ('interpreter.xml');
// รับทรีไวยากรณ์นามธรรมโดยแยกวิเคราะห์มัน
var readxmlexpr = parser.parse ('root/a/b/d $ .id $');
// ร้องขอการแยกวิเคราะห์และรับค่าส่งคืน
var ss = readxmlexpr.interpret (c);
console.log ('------------ การแยกวิเคราะห์ --------------');
สำหรับ (var i = 0, len = ss.length; i <len; i ++) {
console.log ('d id property id ค่าคือ =' + ss [i]);
-
console.log ('--------------- แยกวิเคราะห์ --------------');
// หากคุณต้องการใช้บริบทเดียวกันและแยกวิเคราะห์อย่างต่อเนื่องคุณต้องปรับเปลี่ยนวัตถุบริบทใหม่
C.Reinit ();
var readxmlexpr2 = parser.parse ('root/a/b/d $');
var ss2 = readxmlexpr2.interpret (c);
console.log ('------------ การแยกวิเคราะห์ --------------');
สำหรับ (i = 0, len = ss2.length; i <len; i ++) {
console.log('d的值是= ' + ss2[i]);
-
console.log('---------------parsed--------------');
c.reInit();
var readxmlExpr3 = Parser.parse('root/a/b/c');
var ss3 = readxmlExpr3.interpret(c);
console.log('------------parsing--------------');
console.log('c的name属性值是= ' + ss3);
console.log('---------------parsed--------------');
c.reInit();
var readxmlExpr4 = Parser.parse('root/a/b/c.name');
var ss4 = readxmlExpr4.interpret(c);
console.log('------------parseing--------------');
console.log('c的name属性值是= ' + ss4);
console.log('---------------parsed--------------');
-
// 这样就实现了类似XPath的部分功能
// 没错,就类似于jQuery选择器的部分功能
-
输出: d的值是= d1
d的值是= d2
d的值是= d3
d的值是= d4
d的属性id的值是= 1
d的属性id的值是= 2
d的属性id的值是= 3
d的属性id的值是= 4
------------parsing--------------
d的属性id的值是= 1
d的属性id的值是= 2
d的属性id的值是= 3
d的属性id的值是= 4
---------------parsed--------------
------------parsing--------------
d的值是= d1
d的值是= d2
d的值是= d3
d的值是= d4
---------------parsed--------------
------------parsing--------------
c的name属性值是= 12345
---------------parsed--------------
------------parseing--------------
c的name属性值是= testC
---------------parsed--------------