มันเป็นหัวข้อใหญ่ แต่แทนที่จะแปรงคุณด้วยความขี้เล่น "ไปอ่านหนังสือเด็ก" แทนฉันยินดีที่จะให้พอยน์เตอร์เพื่อช่วยคุณห่อหัวของคุณรอบ ๆ มัน
คอมไพเลอร์ส่วนใหญ่และ / หรือล่ามทำงานเช่นนี้:
tokenize : สแกนข้อความรหัสและทำลายมันลงในรายการของสัญญาณ
ขั้นตอนนี้อาจเป็นเรื่องยุ่งยากเพราะคุณไม่สามารถแยกสตริงในช่องว่างได้คุณต้องจำไว้ว่าif (bar) foo += "a string";
เป็นรายการของโทเค็น 8 รายการ: WORD, OPEN_PAREN, WORD, CLOSE_PAREN, WORD, ASIGNMENT_ADD, STRING_LITERAL, TERMINATOR อย่างที่คุณเห็นเพียงแค่การแยกซอร์สโค้ดในช่องว่างไม่ทำงานคุณต้องอ่านตัวละครแต่ละตัวเป็นลำดับดังนั้นหากคุณพบตัวอักษรและตัวเลขที่คุณอ่านต่อไปเรื่อย ๆ จนกว่าคุณจะตีตัวอักษรที่ไม่ใช่ตัวอักษรและสตริงนั้น เพิ่งอ่านเป็นคำที่จะจัดประเภทเพิ่มเติมในภายหลัง คุณสามารถตัดสินใจด้วยตัวคุณเองว่า tokenizer ของคุณเป็นอย่างไร: ไม่ว่าจะกลืน"a string"
เป็นโทเค็นเดียวที่เรียกว่า STRING_LITERAL เพื่อแยกวิเคราะห์เพิ่มเติมในภายหลังหรือไม่ว่าจะเห็น"a string"
เช่น OPEN_QUOTE, UNPARSED_TEXT, CLOSE_QUOTE หรืออะไรก็ตามนี่เป็นเพียงหนึ่งในตัวเลือกมากมายที่คุณต้องตัดสินใจด้วยตัวคุณเองในขณะที่คุณกำลังเขียนโค้ด
ไฟแนนเชี่ยล : ตอนนี้คุณมีรายการโทเค็นแล้ว คุณอาจติดแท็กโทเค็นบางส่วนที่มีการจำแนกประเภทที่ไม่ชัดเจนเช่น WORD เพราะในช่วงแรกที่คุณผ่านไปคุณไม่ต้องใช้ความพยายามมากเกินไปในการค้นหาบริบทของอักขระแต่ละตัว ดังนั้นให้อ่านรายการโทเค็นแหล่งที่มาของคุณอีกครั้งและจัดประเภทโทเค็นที่ไม่ชัดเจนแต่ละประเภทด้วยโทเค็นที่เฉพาะเจาะจงมากขึ้นโดยยึดตามคำหลักในภาษาของคุณ ดังนั้นคุณมีคำเช่น "if" และ "if" อยู่ในรายการคำหลักพิเศษที่เรียกว่า symbol IF ดังนั้นคุณจึงเปลี่ยนประเภทสัญลักษณ์ของโทเค็นนั้นจาก WORD เป็น IF และ WORD ใด ๆ ที่ไม่อยู่ในรายการคำหลักพิเศษของคุณ เช่น WORD foo เป็น IDENTIFIER
แยกวิเคราะห์ : ดังนั้นตอนนี้คุณจึงเปิดif (bar) foo += "a string";
รายการโทเค็น lexed ที่มีลักษณะเช่นนี้: ถ้า OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR ขั้นตอนคือการรับรู้ลำดับของโทเค็นเป็นคำสั่ง นี่คือการแยกวิเคราะห์ คุณทำได้โดยใช้ไวยากรณ์เช่น:
ประกาศ: = ASIGN_EXPRESSION | IF_STATEMENT
IF_STATEMENT: = IF, PAREN_EXPRESSION, คำชี้แจง
ASIGN_EXPRESSION: = IDENTIFIER, ASIGN_OP, VALUE
PAREN_EXPRESSSION: = OPEN_PAREN, VALUE, CLOSE_PAREN
ค่า: = IDENTIFIER | STRING_LITERAL | PAREN_EXPRESSION
ASIGN_OP: = EQUAL | ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT
การผลิตที่ใช้ "|" ระหว่างคำศัพท์หมายถึง "จับคู่สิ่งเหล่านี้" ถ้ามีเครื่องหมายจุลภาคระหว่างข้อกำหนดหมายความว่า "จับคู่ลำดับของคำนี้"
คุณใช้สิ่งนี้อย่างไร เริ่มต้นด้วยโทเค็นแรกพยายามจับคู่ลำดับโทเค็นของคุณกับการผลิตเหล่านี้ ดังนั้นก่อนอื่นคุณลองจับคู่รายการโทเค็นของคุณกับ STATEMENT ดังนั้นคุณจึงอ่านกฎสำหรับ STATEMENT และมันบอกว่า "STATEMENT เป็น ASIGN_EXPRESSION หรือ IF_STATEMENT" ดังนั้นคุณจึงลองจับคู่ ASIGN_EXPRESSION ก่อนเพื่อให้คุณค้นหากฎไวยากรณ์สำหรับ ASIGN_EXPRESSION และมันบอกว่า "ASIGN_EXPRESSION เป็น IDENTIFIER ตามด้วย ASIGN_OP แล้วตามด้วย VALUE ดังนั้นคุณจึงค้นหากฎไวยากรณ์สำหรับ IDENTIFIER และคุณเห็นว่าไม่มี Ruke ไวยากรณ์สำหรับ IDENTIFIER ซึ่งหมายความว่า IDENTIFIER" terminal "หมายความว่ามันไม่จำเป็นต้องเพิ่มเติม การแยกวิเคราะห์เพื่อจับคู่เพื่อให้คุณสามารถลองจับคู่โดยตรงกับโทเค็นของคุณ แต่โทเค็นต้นทางแรกของคุณคือ IF และ IF ไม่เหมือนกับ IDENTIFIER ดังนั้นการจับคู่ล้มเหลว เกิดอะไรขึ้น คุณกลับไปที่กฎ STATEMENT และลองจับคู่คำถัดไป: IF_STATEMENT คุณค้นหา IF_STATEMENT มันเริ่มต้นด้วย IF, ค้นหา IF, ถ้าเป็นเทอร์มินัล, เปรียบเทียบเทอร์มินัลกับโทเค็นแรกของคุณ, IF โทเค็นที่ตรงกัน, ไปเรื่อย ๆ ต่อไป, เทอมถัดไปคือ PAREN_EXPRESSION, ค้นหา PAREN_EXPRESSION PAREN_EXPRESSION เริ่มต้นด้วย OPEN_PAREN ค้นหา OPEN_PAREN เป็นเทอร์มินัลจับคู่ OPEN_PAREN กับโทเค็นถัดไปของคุณจับคู่กับ .... และอื่น ๆ
วิธีที่ง่ายที่สุดในการเข้าใกล้ขั้นตอนนี้คือคุณมีฟังก์ชั่นที่เรียกว่า parse () ซึ่งคุณจะผ่านโทเค็นซอร์สโค้ดที่คุณพยายามจับคู่และคำศัพท์ไวยากรณ์ที่คุณพยายามจับคู่กับมัน หากคำศัพท์ไวยากรณ์ไม่ใช่เทอร์มินัลคุณจะได้รับการชดเชย: คุณเรียกใช้การแยกวิเคราะห์ () อีกครั้งผ่านโทเค็นแหล่งเดียวกันและคำแรกของกฎไวยากรณ์นี้ นี่คือสาเหตุที่มันถูกเรียกว่า "recursive descent parser" ฟังก์ชัน parse () ส่งคืน (หรือปรับเปลี่ยน) ตำแหน่งปัจจุบันของคุณในการอ่านโทเค็นต้นทางโดยพื้นฐานแล้วจะส่งโทเค็นสุดท้ายในลำดับที่ตรงกัน แยก () จากที่นั่น
แต่ละครั้งที่แยกวิเคราะห์ () ตรงกับการผลิตเช่น ASIGN_EXPRESSION คุณสร้างโครงสร้างที่เป็นตัวแทนของชิ้นส่วนของรหัสนั้น โครงสร้างนี้มีการอ้างอิงถึงโทเค็นต้นฉบับ คุณเริ่มสร้างรายการโครงสร้างเหล่านี้ เราจะเรียกโครงสร้างทั้งหมดนี้ว่าแผนผังบทคัดย่อของบทคัดย่อ (AST)
รวบรวมและ / หรือดำเนินการ : สำหรับโปรดักชั่นบางอย่างในไวยากรณ์ของคุณคุณได้สร้างฟังก์ชั่นการจัดการที่ถ้าได้รับโครงสร้าง AST มันจะรวบรวมหรือดำเนินการอัน AST
ลองดูที่ชิ้นส่วนของ AST ของคุณที่มีประเภท ASIGN_ADD ดังนั้นในฐานะล่ามคุณจะมีฟังก์ชัน ASIGN_ADD_execute () ฟังก์ชันนี้ถูกส่งเป็นชิ้นส่วนของ AST ที่สอดคล้องกับแผนผังการแยกวิเคราะห์foo += "a string"
ดังนั้นฟังก์ชันนี้จึงดูโครงสร้างนั้นและรู้ว่าเทอมแรกในโครงสร้างจะต้องเป็น IDENTIFIER และเทอมที่สองคือ VALUE ดังนั้น ASIGN_ADD_execute () ส่งผ่านคำว่า VALUE ไปยังฟังก์ชัน VALUE_eval () ซึ่งส่งคืนออบเจกต์ที่แสดงค่าที่ประเมินในหน่วยความจำจากนั้น ASIGN_ADD_execute () จะค้นหา "foo" ในตารางตัวแปรของคุณและเก็บการอ้างอิงถึงสิ่งที่ถูกส่งกลับโดย eval_value () ฟังก์ชัน
นั่นคือล่าม คอมไพเลอร์จะมีฟังก์ชั่นจัดการแทนแปล AST เป็นรหัสไบต์หรือรหัสเครื่องแทนการดำเนินการ
ขั้นตอนที่ 1 ถึง 3 และ 4 สามารถทำได้ง่ายขึ้นโดยใช้เครื่องมือเช่น Flex และ Bison (aka. Lex และ Yacc) แต่การเขียนล่ามตัวเองตั้งแต่เริ่มต้นอาจเป็นแบบฝึกหัดที่ช่วยให้โปรแกรมเมอร์สามารถบรรลุผลได้มากที่สุด ความท้าทายในการเขียนโปรแกรมอื่น ๆ นั้นดูเล็กน้อยหลังจากการประชุมสุดยอดครั้งนี้
คำแนะนำของฉันคือการเริ่มต้นเล็ก ๆ : ภาษาเล็ก ๆ ที่มีไวยากรณ์เล็ก ๆ และลองแยกวิเคราะห์และดำเนินการคำสั่งง่ายๆไม่กี่คำแล้วเติบโตจากที่นั่น
อ่านสิ่งเหล่านี้และขอให้โชคดี!
http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c
http://en.wikipedia.org/wiki/Recursive_descent_parser
lex
, และyacc
bison