ฉันเขียน SLIC (ระบบของภาษาเพื่อการใช้งานคอมไพเลอร์) ในตัวของมันเอง จากนั้นรวบรวมมือเข้าสู่การชุมนุม SLIC มีมากเนื่องจากเป็นคอมไพเลอร์เดี่ยวของห้าภาษาย่อย:
- โปรแกรมแปลภาษา SYNTAX Parser PPL
- GENERATOR LISP 2 ภาษาที่สร้างรหัสการรวบรวมข้อมูลต้นไม้ PSEUDO
- ISO ในลำดับ, รหัส PSEUDO, ภาษาการปรับให้เหมาะสม
- มาโคร PSEUDO เช่นภาษาแอสเซมบลีที่ผลิตรหัส
- MACHOP คำสั่งแอสเซมบลีของเครื่องกำหนดภาษา
SLIC ได้รับแรงบันดาลใจจาก CWIC (คอมไพเลอร์สำหรับการเขียนและการใช้คอมไพเลอร์) ซึ่งแตกต่างจากแพคเกจการพัฒนาคอมไพเลอร์ส่วนใหญ่ SLIC และ CWIC จ่าหน้าถึงการสร้างรหัสด้วยความเชี่ยวชาญภาษาเฉพาะโดเมน SLIC ขยายการสร้างรหัส CWICs โดยเพิ่มภาษาย่อย ISO, PSEUDO และ MACHOP เพื่อแยกเครื่องเป้าหมายออกจากภาษาของเครื่องกำเนิดต้นไม้ที่ตระเวน
LISP 2 ต้นไม้และรายการ
ระบบจัดการหน่วยความจำไดนามิกของภาษา LISP 2 เป็นองค์ประกอบสำคัญ รายการจะแสดงเป็นภาษาที่รวมอยู่ในวงเล็บเหลี่ยมส่วนประกอบของมันคั่นด้วยเครื่องหมายจุลภาคเช่นรายการสามองค์ประกอบ [a, b, c]
ต้นไม้:
ADD
/ \
MPY 3
/ \
5 x
ถูกแทนด้วยรายการซึ่งรายการแรกคือวัตถุโหนด:
[ADD,[MPY,5,x],3]
ต้นไม้จะปรากฏขึ้นโดยทั่วไปพร้อมโหนดที่แยกจากกันก่อนหน้ากิ่งก้าน:
ADD[MPY[5,x],3]
การแยกวิเคราะห์ด้วยฟังก์ชันตัวสร้าง LISP 2
ฟังก์ชั่นเครื่องกำเนิดไฟฟ้าเป็นชุดชื่อของ (unparse) => action> pairs ...
<NAME>(<unparse>)=><action>;
(<unparse>)=><action>;
...
(<unparse>)=><action>;
นิพจน์ที่ไม่ชัดเจนคือการทดสอบที่จับคู่รูปแบบต้นไม้และ / หรือชนิดของวัตถุที่แตกออกเป็นชิ้น ๆ และกำหนดส่วนเหล่านั้นให้กับตัวแปรท้องถิ่นที่จะประมวลผลโดยการดำเนินการตามขั้นตอน ชนิดของฟังก์ชั่นโอเวอร์โหลดที่รับอาร์กิวเมนต์ชนิดต่าง ๆ ยกเว้นการทดสอบ () => ... จะพยายามตามลำดับรหัส ความสำเร็จครั้งแรกในการดำเนินการ unparse ที่เกี่ยวข้อง นิพจน์ที่ unparse เป็นการทดสอบแยกชิ้นส่วน เพิ่ม [x, y] จับคู่กับต้นไม้เพิ่มสองกิ่งที่กำหนดสาขาให้กับตัวแปรท้องถิ่น x และ y การกระทำอาจเป็นการแสดงออกที่ง่ายหรือ. เริ่มต้น ... บล็อกรหัสที่ถูกผูกไว้ ฉันจะใช้บล็อก c style {... } วันนี้ การจับคู่ต้นไม้, [], กฎที่แยกกันอาจเรียกผู้สร้างผ่านผลลัพธ์ที่ส่งคืนไปยังแอ็คชัน:
expr_gen(ADD[expr_gen(x),expr_gen(y)])=> x+y;
expr_gen unparse ด้านบนตรงกับต้นไม้ ADD สองสาขา ภายในรูปแบบการทดสอบตัวสร้างอาร์กิวเมนต์เดี่ยวที่วางไว้ในกิ่งต้นไม้จะถูกเรียกพร้อมกับสาขานั้น รายการอาร์กิวเมนต์ของมันคือตัวแปรท้องถิ่นที่กำหนดวัตถุที่ส่งคืน ด้านบน unparse ระบุว่ามีสองสาขาคือ ADD tree disassembly, การเรียกซ้ำโดยกดแต่ละสาขาเพื่อ expr_gen การส่งคืนสาขาทางซ้ายวางลงในตัวแปรท้องถิ่น x ในทำนองเดียวกันสาขาขวาส่งผ่านไปยัง expr_gen กับ y วัตถุกลับ ด้านบนอาจเป็นส่วนหนึ่งของตัวประเมินนิพจน์ตัวเลข มีคุณสมบัติทางลัดที่เรียกว่าเวกเตอร์อยู่ด้านบนแทนสตริงของโหนดเวกเตอร์ของโหนดสามารถใช้กับเวกเตอร์ของการกระทำที่สอดคล้องกัน:
expr_gen(#node[expr_gen(x),expr_gen(y)])=> #action;
node: ADD, SUB, MPY, DIV;
action: x+y, x-y, x*y, x/y;
(NUMBER(x))=> x;
(SYMBOL(x))=> val:(x);
ตัวประเมินนิพจน์ที่สมบูรณ์ยิ่งขึ้นที่กำหนดการส่งคืนจาก expr_gen ออกจากกิ่งไปยัง x และสาขาที่ถูกต้องถึง y เวกเตอร์การกระทำที่สอดคล้องกันดำเนินการกับ x และ y กลับมา คู่สุดท้าย unparse => action จับคู่ออบเจ็กต์ตัวเลขและสัญลักษณ์
คุณลักษณะสัญลักษณ์และสัญลักษณ์
สัญลักษณ์อาจมีชื่อคุณลักษณะ val: (x) เข้าถึงแอตทริบิวต์ val ของวัตถุสัญลักษณ์ที่มีอยู่ใน x สัญลักษณ์ตารางสแต็กทั่วไปเป็นส่วนหนึ่งของ SLIC ตาราง SYMBOL อาจถูกผลักและผุดให้สัญลักษณ์ท้องถิ่นสำหรับฟังก์ชั่น สัญลักษณ์ที่สร้างขึ้นใหม่จะแสดงรายการในตารางสัญลักษณ์ด้านบน การค้นหาสัญลักษณ์ค้นหาสแต็กตารางสัญลักษณ์จากตารางด้านบนก่อนย้อนหลังสแต็ก
การสร้างรหัสอิสระของเครื่อง
ภาษาเครื่องกำเนิดของ SLIC สร้างวัตถุคำสั่ง PSEUDO ผนวกเข้ากับรายการรหัสส่วน A. FLUSH ทำให้รายการรหัส PSEUDO ถูกเรียกใช้ลบคำสั่ง PSEUDO แต่ละรายการออกจากรายการและเรียกมัน หลังจากดำเนินการหน่วยความจำวัตถุ PSEUDO จะถูกปล่อยออกมา เนื้อหากระบวนงานของการกระทำของ PSEUDO และ GENERATOR นั้นเป็นภาษาเดียวกันยกเว้นการส่งออก PSEUDO มีวัตถุประสงค์เพื่อทำหน้าที่เป็นมาโครประกอบที่ให้การจัดลำดับรหัสอิสระของเครื่อง พวกเขาจัดให้มีการแยกเครื่องเป้าหมายที่เฉพาะเจาะจงออกจากภาษาของเครื่องมือสร้างแผนภูมิการรวบรวมข้อมูล PSEUDO เรียกใช้ฟังก์ชัน MACHOP เพื่อส่งออกรหัสเครื่อง MACHOPs ใช้เพื่อกำหนด ops เทียมประกอบ (เช่น dc, กำหนดค่าคงที่เป็นต้น) และคำสั่งเครื่องหรือตระกูลของคำสั่งที่จัดรูปแบบเหมือนกันโดยใช้รายการแบบเวกเตอร์ พวกเขาเพียงแค่เปลี่ยนพารามิเตอร์ของพวกเขาเป็นลำดับของบิตฟิลด์ประกอบคำสั่ง การเรียก MACHOP นั้นดูเหมือนว่าจะเป็นแอสเซมบลีและจัดรูปแบบการพิมพ์ของฟิลด์สำหรับเมื่อแอสเซมบลีแสดงในรายการคอมไพล์ ในโค้ดตัวอย่างฉันใช้การคอมเม้นต์สไตล์ c ที่สามารถเพิ่มได้อย่างง่ายดาย แต่ไม่ได้อยู่ในภาษาต้นฉบับ MACHOPs กำลังสร้างโค้ดในหน่วยความจำบิตที่กำหนดแอดเดรสได้ SLIC linker จัดการเอาต์พุตของคอมไพเลอร์ MACHOP สำหรับคำแนะนำโหมดผู้ใช้ DEC-10 โดยใช้รายการแบบเวกเตอร์: MACHOPs กำลังสร้างโค้ดในหน่วยความจำบิตที่กำหนดแอดเดรสได้ SLIC linker จัดการเอาต์พุตของคอมไพเลอร์ MACHOP สำหรับคำแนะนำโหมดผู้ใช้ DEC-10 โดยใช้รายการแบบเวกเตอร์: MACHOPs กำลังสร้างโค้ดในหน่วยความจำบิตที่กำหนดแอดเดรสได้ SLIC linker จัดการเอาต์พุตของคอมไพเลอร์ MACHOP สำหรับคำแนะนำโหมดผู้ใช้ DEC-10 โดยใช้รายการแบบเวกเตอร์:
.MACHOP #opnm register,@indirect offset (index): // Instruction's parameters.
.MORG 36, O(18): $/36; // Align to 36 bit boundary print format: 18 bit octal $/36
O(9): #opcd; // Op code 9 bit octal print out
(4): register; // 4 bit register field appended print
(1): indirect; // 1 bit appended print
(4): index; // 4 bit index register appended print
O(18): if (#opcd&&3==1) offset // immediate mode use value else
else offset/36; // memory address divide by 36
// to get word address.
// Vectored entry opcode table:
#opnm := MOVE, MOVEI, MOVEM, MOVES, MOVS, MOVSI, MOVSM, MOVSS,
MOVN, MOVNI, MOVNM, MOVNS, MOVM, MOVMI, MOVMM, MOVMS,
IMUL, IMULI, IMULM, IMULB, MUL, MULI, MULM, MULB,
...
TDO, TSO, TDOE, TSOE, TDOA, TSOA, TDON, TSON;
// corresponding opcode value:
#opcd := 0O200, 0O201, 0O202, 0O203, 0O204, 0O205, 0O206, 0O207,
0O210, 0O211, 0O212, 0O213, 0O214, 0O215, 0O216, 0O217,
0O220, 0O221, 0O222, 0O223, 0O224, 0O225, 0O226, 0O227,
...
0O670, 0O671, 0O672, 0O673, 0O674, 0O675, 0O676, 0O677;
. Morg 36, O (18): $ / 36; จัดตำแหน่งให้อยู่ในขอบเขต 36 บิตการพิมพ์ที่อยู่คำที่ $ / 36 ตำแหน่ง 18 บิตในแปด 9 บิต opcd, 4 บิตลงทะเบียน, บิตทางอ้อมและดัชนี 4 บิตจะรวมกันและพิมพ์ราวกับว่าฟิลด์ 18 บิตเดียว ที่อยู่ 18 บิต / 36 หรือค่าทันทีคือเอาต์พุตและพิมพ์เป็นฐานแปด ตัวอย่าง MOVEI พิมพ์ด้วย r1 = 1 และ r2 = 2:
400020 201082 000005 MOVEI r1,5(r2)
ด้วยตัวเลือกคอมไพเลอร์แอสเซมบลีคุณจะได้รับรหัสแอสเซมบลีที่สร้างขึ้นในรายการคอมไพล์
เชื่อมโยงเข้าด้วยกัน
SLIC linker จัดทำขึ้นเป็นห้องสมุดที่จัดการการเชื่อมโยงและการแก้ปัญหาสัญลักษณ์ การจัดรูปแบบไฟล์โหลดเอาต์พุตเป้าหมายเฉพาะ แต่ต้องเขียนสำหรับเครื่องเป้าหมายและลิงก์กับไลบรารีไลบรารี linker
ภาษาเครื่องกำเนิดมีความสามารถในการเขียนต้นไม้ไปยังไฟล์และอ่านพวกเขาอนุญาตให้คอมไพเลอร์มัลติพาสที่จะดำเนินการ
การสร้างรหัสและต้นกำเนิดสั้น ๆ ในฤดูร้อน
ฉันได้ไปสร้างรหัสก่อนเพื่อประกันเป็นที่เข้าใจว่า SLIC เป็นคอมไพเลอร์จริง SLIC ได้รับแรงบันดาลใจจาก CWIC (คอมไพเลอร์สำหรับการเขียนและการนำคอมไพเลอร์) ไปพัฒนาที่ Systems Development Corporation ในช่วงปลายทศวรรษ 1960 CWIC มีภาษา SYNTAX และ GENERATOR เท่านั้นที่สร้างโค้ดไบต์ตัวเลขจากภาษา GENERATOR รหัสไบต์ถูกวางหรือวาง (คำที่ใช้ในเอกสาร CWICs) ลงในบัฟเฟอร์หน่วยความจำที่เกี่ยวข้องกับส่วนที่มีชื่อและเขียนโดยคำสั่ง. FLUSH เอกสาร ACM บน CWIC นั้นมีอยู่ในคลัง ACM
ประสบความสำเร็จในการใช้ภาษาการเขียนโปรแกรมที่สำคัญ
ในปลายปี 1970 SLIC ถูกใช้เพื่อเขียนคอมไพเลอร์ข้ามภาษาโคบอล เสร็จสมบูรณ์ในเวลาประมาณ 3 เดือนส่วนใหญ่โดยโปรแกรมเมอร์เดียว ฉันทำงานกับโปรแกรมเมอร์เล็กน้อยตามที่ต้องการ โปรแกรมเมอร์อีกคนเขียนไลบรารีรันไทม์และ MACHOP สำหรับเป้าหมาย TI-990 mini-COMPUTER คอมไพเลอร์ COBOL นั้นรวบรวมบรรทัดมากขึ้นต่อวินาทีจากนั้นคอมไพเลอร์ COBOL ดั้งเดิม DEC-10 ที่เขียนในชุดประกอบ
มากกว่าจะเรียบเรียงแล้วมักจะพูดถึง
ส่วนใหญ่ในการเขียนคอมไพเลอร์จากศูนย์คือไลบรารีรันไทม์ คุณต้องมีตารางสัญลักษณ์ คุณต้องการอินพุตและเอาต์พุต การจัดการหน่วยความจำแบบไดนามิคเป็นต้นสามารถเขียนไลบรารีรันไทม์สำหรับคอมไพเลอร์ได้ง่ายขึ้นจากนั้นจึงเขียนคอมไพเลอร์ แต่ด้วย SLIC นั้น runtime library นั้นเป็นเรื่องธรรมดาสำหรับคอมไพเลอร์ทั้งหมดที่พัฒนาใน SLIC โปรดทราบว่ามีสองไลบรารีรันไทม์ หนึ่งตัวสำหรับเครื่องเป้าหมาย (ตัวอย่างเช่น COBOL) อีกอันคือคอมไพเลอร์รันไทม์คอมไพเลอร์ไลบรารี
ฉันคิดว่าฉันได้พิสูจน์แล้วว่าสิ่งเหล่านี้ไม่ใช่ตัวแยกวิเคราะห์ ดังนั้นตอนนี้ด้วยความเข้าใจเพียงเล็กน้อยเกี่ยวกับส่วนท้ายฉันสามารถอธิบายภาษาโปรแกรมแยกวิเคราะห์
ภาษาการเขียนโปรแกรม Parser
parser ถูกเขียนโดยใช้สูตรที่เขียนในรูปแบบของสมการง่าย ๆ
<name> <formula type operator> <expression> ;
องค์ประกอบภาษาที่ระดับต่ำสุดคือตัวละคร โทเค็นเกิดจากชุดย่อยของอักขระของภาษา คลาสอักขระถูกใช้เพื่อตั้งชื่อและกำหนดเซ็ตย่อยอักขระเหล่านั้น คลาสอักขระที่กำหนดโอเปอเรเตอร์คืออักขระโคลอน (:) อักขระที่เป็นสมาชิกของคลาสจะถูกเขียนทางด้านขวาของคำจำกัดความ อักขระที่พิมพ์ได้จะอยู่ในสตริง 'ช่วงเวลาเฉพาะช่วงเวลา อักขระที่ไม่พิมพ์และอักขระพิเศษอาจแสดงตามลำดับตัวเลข สมาชิกของคลาสจะถูกคั่นด้วยทางเลือก | ผู้ประกอบการ สูตรคลาสลงท้ายด้วยเซมิโคลอน คลาสอักขระอาจรวมคลาสที่กำหนดไว้ก่อนหน้านี้:
/* Character Class Formula class_mask */
bin: '0'|'1'; // 0b00000010
oct: bin|'2'|'3'|'4'|'5'|'6'|'7'; // 0b00000110
dgt: oct|'8'|'9'; // 0b00001110
hex: dgt|'A'|'B'|'C'|'D'|'E'|'F'|'a'|'b'|'c'|'d'|'e'|'f'; // 0b00011110
upr: 'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|
'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'; // 0b00100000
lwr: 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|
'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'; // 0b01000000
alpha: upr|lwr; // 0b01100000
alphanum: alpha|dgt; // 0b01101110
skip_class 0b00000001 มีการกำหนดไว้ล่วงหน้า แต่อาจ overroad กำลังกำหนด skip_class
โดยสรุป: คลาสอักขระเป็นรายการของทางเลือกที่สามารถเป็นค่าคงที่อักขระลำดับของอักขระหรือคลาสอักขระที่กำหนดไว้ก่อนหน้านี้เท่านั้น เมื่อฉันนำคลาสอักขระไปใช้: สูตรคลาสได้รับการกำหนดมาส์กระดับบิต (แสดงความคิดเห็นด้านบน) สูตรคลาสใด ๆ ที่มีตัวอักษรหรือตัวอักษรใด ๆ จะทำให้บิตของคลาสได้รับการจัดสรร มาสก์ถูกสร้างขึ้นโดย oring มาสก์คลาสที่รวมไว้พร้อมกับบิตที่จัดสรร (ถ้ามี) ตารางเรียนถูกสร้างจากคลาสตัวละคร รายการที่จัดทำดัชนีตามลำดับของตัวละครประกอบด้วยบิตที่ระบุถึงการเป็นสมาชิกคลาสของตัวละคร การทดสอบในชั้นเรียนจะทำแบบอินไลน์ ตัวอย่างรหัส IA-86 พร้อมลำดับของตัวละครใน eax แสดงให้เห็นถึงการทดสอบในชั้นเรียน:
test byte ptr [eax+_classmap],dgt
ตามมาด้วย:
jne <success>
หรือ
je <failure>
มีการใช้ตัวอย่างรหัสคำสั่ง IA-86 เพราะฉันคิดว่าคำแนะนำของ IA-86 นั้นเป็นที่รู้จักอย่างกว้างขวางมากขึ้นในปัจจุบัน ชื่อคลาสที่ประเมินค่ามาสก์ของคลาสนั้นไม่ถูกทำลายด้วย ANDed กับตารางคลาสที่ทำดัชนีโดยอักขระลำดับ (เป็น eax) ผลลัพธ์ที่ไม่เป็นศูนย์บ่งบอกความเป็นสมาชิกของคลาส (EAX เป็นศูนย์ยกเว้น al (8 บิตต่ำของ EAX) ที่มีอักขระ)
โทเค็นแตกต่างกันเล็กน้อยในคอมไพเลอร์เก่าเหล่านี้ คำสำคัญไม่ได้อธิบายว่าเป็นโทเค็น พวกเขาเพียงแค่ถูกจับคู่โดยค่าคงที่สตริงที่ยกมาในภาษา parser สตริงที่อ้างถึงจะไม่ถูกเก็บไว้ตามปกติ อาจใช้ตัวดัดแปลง A + ทำให้สตริงตรงกัน (เช่น + '-' จับคู่ a - อักขระที่รักษาอักขระเมื่อสำเร็จ) The, การดำเนินการ (เช่น 'E') จะแทรกสตริงลงในโทเค็น พื้นที่สีขาวได้รับการจัดการโดยสูตรโทเค็นที่ข้ามอักขระ SKIP_CLASS ชั้นนำจนกว่าจะมีการจับคู่ครั้งแรก โปรดทราบว่าการจับคู่อักขระ skip_class ชัดเจนจะหยุดการข้ามเพื่อให้โทเค็นเริ่มต้นด้วยอักขระ skip_class สูตรโทเค็นสตริงจะข้ามอักขระ skip_class ชั้นนำที่ตรงกับอักขระอัญประกาศเดี่ยวเดียวหรือสตริงที่ยกมาสองครั้ง สิ่งที่น่าสนใจคือการจับคู่ "อักขระภายในสตริง" ที่อ้างถึง:
string .. (''' .ANY ''' | '"' $(-"""" .ANY | """""","""") '"') MAKSTR[];
ทางเลือกแรกตรงกับตัวละครที่ยกมาอ้างใด ๆ ทางเลือกที่ถูกต้องตรงกับสตริงที่มีเครื่องหมายคำพูดคู่ซึ่งอาจรวมถึงอักขระเครื่องหมายคำพูดคู่โดยใช้ "อักขระร่วมกันเพื่อแสดงถึงอักขระ" ตัวเดียว สูตรนี้กำหนดสตริงที่ใช้ในคำจำกัดความของตัวเอง ทางเลือกด้านขวาภายใน '' '$ (- "" "" ".any |" "" "", "" "" ")" "' ตรงกับสตริงที่ยกมาอ้างคู่ เราสามารถใช้อักขระ 'ที่ยกมาเดี่ยวเพื่อจับคู่เครื่องหมายอัญประกาศคู่ "อักขระภายในสตริงที่ยกมาสองครั้งถ้าเราต้องการใช้อักขระ" เราต้องใช้อักขระสองตัว "เพื่อรับหนึ่ง ตัวอย่างเช่นในตัวเลือกด้านซ้ายภายในจับคู่กับอักขระใด ๆ ยกเว้นเครื่องหมายคำพูด:
-"""" .ANY
มองเชิงลบไปข้างหน้า - "" "" จะใช้เมื่อประสบความสำเร็จ (ไม่ตรงกับ "ตัวอักษร") จากนั้นจะจับคู่อักขระ. yany (ซึ่งไม่สามารถเป็น "ตัวอักษรเพราะ -" "" "กำจัดโอกาสที่จะเกิดขึ้น) ทางเลือกที่เหมาะสมกำลังดำเนินการ - "" "" จับคู่อักขระ "และความล้มเหลวเป็นตัวเลือกที่เหมาะสม:
"""""",""""
พยายามที่จะจับคู่ "ตัวละครแทนที่พวกเขาด้วยสองครั้งเดียว" โดยใช้ "" "" "เพื่อแทรก thw เดียว" ตัวละครทั้งสองทางเลือกภายในล้มเหลวอักขระอัญประกาศปิดสตริงถูกจับคู่และ MAKSTR [] เรียกว่าเพื่อสร้างวัตถุสตริง $ ลำดับลูปในขณะที่ประสบความสำเร็จจะใช้ตัวดำเนินการในการจับคู่ลำดับโทเค็นสูตรข้ามตัวละครคลาส skip ชั้นนำ (ช่องว่างเล็กน้อย) เมื่อการจับคู่ครั้งแรกจะทำให้ skip_class ข้ามถูกปิดใช้งานเราสามารถเรียกฟังก์ชั่นโปรแกรมในภาษาอื่น ๆ [], MAKBIN [], MAKOCT [], MAKHEX [], MAKFLOAT [] และ MAKINT [] เป็นฟังก์ชันของไลบรารีที่แปลงสตริงโทเค็นที่ตรงกันให้เป็นวัตถุที่พิมพ์สูตรตัวเลขด้านล่างแสดงการรับรู้โทเค็นที่ค่อนข้างซับซ้อน:
number .. "0B" bin $bin MAKBIN[] // binary integer
|"0O" oct $oct MAKOCT[] // octal integer
|("0H"|"0X") hex $hex MAKHEX[] // hexadecimal integer
// look for decimal number determining if integer or floating point.
| ('+'|+'-'|--) // only - matters
dgt $dgt // integer part
( +'.' $dgt // fractional part?
((+'E'|'e','E') // exponent part
('+'|+'-'|--) // Only negative matters
dgt(dgt(dgt|--)|--)|--) // 1 2 or 3 digit exponent
MAKFLOAT[] ) // floating point
MAKINT[]; // decimal integer
สูตรโทเค็นตัวเลขด้านบนรับรู้จำนวนเต็มและจำนวนจุดลอยตัว ตัวเลือก - จะประสบความสำเร็จเสมอ อาจใช้วัตถุตัวเลขในการคำนวณได้ โทเค็นออบเจ็กต์ถูกส่งไปยังการแยกวิเคราะห์สแต็คตามความสำเร็จของสูตร เลขชี้กำลังเป็นเลขชี้กำลังใน (+ 'E' | 'e', 'E') นั้นน่าสนใจ เราต้องการที่จะมี E ตัวพิมพ์ใหญ่สำหรับ MAKEFLOAT [] แต่เราอนุญาตให้ตัวพิมพ์เล็ก 'e' แทนที่ใช้ 'E'
คุณอาจสังเกตเห็นความสอดคล้องของคลาสอักขระและสูตรโทเค็น สูตรการแยกวิเคราะห์ดำเนินการต่อที่เพิ่มทางเลือกการย้อนรอยและผู้ดำเนินการก่อสร้างต้นไม้ ตัวดำเนินการสำรองและการย้อนรอยที่ไม่ใช่การย้อนรอยอาจไม่ได้รับการผสมภายในระดับนิพจน์ คุณอาจไม่มี (a | b \ c) ผสมการไม่ย้อนรอย | withe \ backtracking ทางเลือก (a \ b \ c), (a | b | c) และ ((a | b) \ c) ถูกต้อง A \ backtracking ทางเลือกบันทึกสถานะการแยกวิเคราะห์ก่อนที่จะลองทางเลือกซ้ายและความล้มเหลวในการกู้คืนสถานะการแยกวิเคราะห์ก่อนที่จะลองทางเลือกที่เหมาะสม ในลำดับของทางเลือกทางเลือกแรกที่ประสบความสำเร็จเป็นไปตามกลุ่ม ทางเลือกเพิ่มเติมจะไม่พยายาม การแยกตัวประกอบและการจัดกลุ่มให้การแยกวิเคราะห์ที่ก้าวหน้าอย่างต่อเนื่อง ตัวเลือกย้อนกลับสร้างสถานะการแยกวิเคราะห์ก่อนที่จะพยายามทดแทนทางซ้าย ต้องใช้การย้อนรอยเมื่อการแยกวิเคราะห์อาจทำการจับคู่บางส่วนและล้มเหลว:
(a b | c d)\ e
ในกรณีข้างต้นหากมีความผิดพลาดในการส่งคืนจะมีการพยายามใช้ซีดีทางเลือก ถ้าหาก c ส่งคืนความล้มเหลวตัวเลือกย้อนกลับจะถูกพยายาม หาก a ประสบความสำเร็จและ b ล้มเหลว parse wile จะย้อนรอยและพยายาม e ในทำนองเดียวกันความล้มเหลวที่ประสบความสำเร็จและขล้มเหลวในการแยกเป็น backtracked และ e ทางเลือกที่นำมา การย้อนรอยไม่ได้ จำกัด อยู่ภายในสูตร หากมีการแยกวิเคราะห์สูตรทำให้การแข่งขันบางส่วนได้ตลอดเวลาและจากนั้นล้มเหลวในการแยกวิเคราะห์จะถูกรีเซ็ตเป็น backtrack ด้านบนและทางเลือกที่นำมาใช้ ความล้มเหลวในการรวบรวมสามารถเกิดขึ้นได้หากรหัสได้รับการส่งออกความรู้สึก backtrack ถูกสร้างขึ้น มีการตั้งค่าย้อนรอยก่อนเริ่มการคอมไพล์ การส่งคืนความล้มเหลวหรือการย้อนรอยกลับเป็นความล้มเหลวของคอมไพเลอร์ Backtracks ซ้อนกัน เราอาจใช้ค่าลบ - และค่าบวก? peek / มองไปข้างหน้าผู้ประกอบการในการทดสอบโดยไม่ต้องแยกวิเคราะห์ล่วงหน้า การทดสอบสตริงคือการมองไปข้างหน้าเพียงต้องการสถานะอินพุตที่บันทึกและรีเซ็ต การมองไปข้างหน้าจะเป็นนิพจน์การแยกวิเคราะห์ที่ทำให้การจับคู่บางส่วนก่อนที่จะล้มเหลว มองไปข้างหน้าจะดำเนินการโดยใช้การย้อนรอย
ภาษาตัวแยกวิเคราะห์ไม่ใช่ตัวแยกวิเคราะห์ LL หรือ LR แต่ภาษาการเขียนโปรแกรมสำหรับการเขียนโปรแกรมวิเคราะห์คำซ้ำซึ่งคุณเขียนโปรแกรมสร้างทรี:
:<node name> creates a node object and pushes it onto the node stack.
.. Token formula create token objects and push them onto
the parse stack.
!<number> pops the top node object and top <number> of parstack
entries into a list representation of the tree. The
tree then pushed onto the parse stack.
+[ ... ]+ creates a list of the parse stack entries created
between them:
'(' +[argument $(',' argument]+ ')'
could parse an argument list. into a list.
ตัวอย่างการแยกวิเคราะห์ที่ใช้กันทั่วไปคือการแสดงออกทางคณิตศาสตร์:
Exp = Term $(('+':ADD|'-':SUB) Term!2);
Term = Factor $(('*':MPY|'/':DIV) Factor!2);
Factor = ( number
| id ( '(' +[Exp $(',' Exp)]+ ')' :FUN!2
| --)
| '(' Exp ')" )
(^' Factor:XPO!2 |--);
Exp และ Term โดยใช้การวนซ้ำสร้างแผนผังมือซ้าย ปัจจัยที่ใช้การเรียกซ้ำที่ถูกต้องจะสร้างทรีที่ถนัดขวา:
d^(x+5)^3-a+b*c => ADD[SUB[EXP[EXP[d,ADD[x,5]],3],a],MPY[b,c]]
ADD
/ \
SUB MPY
/ \ / \
EXP a b c
/ \
d EXP
/ \
ADD 3
/ \
x 5
นี่คือคอมไพเลอร์ cc เล็กน้อย SLIC เวอร์ชันอัปเดตพร้อมกับข้อคิดเห็นสไตล์ c ประเภทฟังก์ชั่น (ไวยากรณ์, โทเค็น, คลาสตัวอักษร, ตัวสร้าง, PSEUDO หรือ MACHOP ถูกกำหนดโดยไวยากรณ์เริ่มต้นตาม ID ของพวกเขาด้วยตัวแยกวิเคราะห์จากบนลงล่างเหล่านี้คุณเริ่มต้นด้วยสูตรกำหนดโปรแกรม:
program = $((declaration // A program is a sequence of
// declarations terminated by
|.EOF .STOP) // End Of File finish & stop compile
\ // Backtrack: .EOF failed or
// declaration long-failed.
(ERRORX["?Error?"] // report unknown error
// flagging furthest parse point.
$(-';' (.ANY // find a ';'. skiping .ANY
| .STOP)) // character: .ANY fails on end of file
// so .STOP ends the compile.
// (-';') failing breaks loop.
';')); // Match ';' and continue
declaration = "#" directive // Compiler directive.
| comment // skips comment text
| global DECLAR[*1] // Global linkage
|(id // functions starting with an id:
( formula PARSER[*1] // Parsing formula
| sequencer GENERATOR[*1] // Code generator
| optimizer ISO[*1] // Optimizer
| pseudo_op PRODUCTION[*1] // Pseudo instruction
| emitor_op MACHOP[*1] // Machine instruction
) // All the above start with an identifier
\ (ERRORX["Syntax error."]
garbol); // skip over error.
// สังเกตว่า id ถูกแยกออกจากกันอย่างไรและรวมกันในภายหลังเมื่อสร้างต้นไม้
formula = ("==" syntax :BCKTRAK // backtrack grammar formula
|'=' syntax :SYNTAX // grammar formula.
|':' chclass :CLASS // character class define
|".." token :TOKEN // token formula
)';' !2 // Combine node name with id
// parsed in calling declaration
// formula and tree produced
// by the called syntax, token
// or character class formula.
$(-(.NL |"/*") (.ANY|.STOP)); Comment ; to line separator?
chclass = +[ letter $('|' letter) ]+;// a simple list of character codes
// except
letter = char | number | id; // when including another class
syntax = seq ('|' alt1|'\' alt2 |--);
alt1 = seq:ALT!2 ('|' alt1|--); Non-backtrack alternative sequence.
alt2 = seq:BKTK!2 ('\' alt2|--); backtrack alternative sequence
seq = +[oper $oper]+;
oper = test | action | '(' syntax ')' | comment;
test = string | id ('[' (arg_list| ,NILL) ']':GENCALL!2|.EMPTY);
action = ':' id:NODE!1
| '!' number:MAKTREE!1
| "+[" seq "]+" :MAKLST!1;
// C style comments
comment = "//" $(-.NL .ANY)
| "/*" $(-"*/" .ANY) "*/";
สิ่งที่ควรทราบคือภาษา parser จัดการกับความคิดเห็นและการกู้คืนข้อผิดพลาด
ฉันคิดว่าฉันได้ตอบคำถาม ต้องเขียนส่วนใหญ่ของผู้สืบทอด SLIC ภาษาซีซีในตัวเองที่นี่ ไม่มีคอมไพเลอร์สำหรับมันในขณะนี้ แต่ฉันสามารถรวบรวมมันลงในรหัสการชุมนุม, ฟังก์ชั่น asm c หรือ c ++ เปล่า