แพคเกจนี้อนุญาตให้คุณใช้ไวยากรณ์ ANTLR และใช้เอาต์พุตพาร์เซอร์เพื่อสร้างแผนผังไวยากรณ์นามธรรม (AST)
pip install antlr-ast
หมายเหตุ: แพ็คเกจนี้ไม่รองรับ python2
# may need:
# pip install pytest
py.test
การใช้ antlr-ast
มีสี่ขั้นตอน:
parse
เพื่อรับเอาต์พุตรันไทม์ ANTLR ตามไฟล์ไวยากรณ์ที่สร้างขึ้นprocess_tree
กับเอาต์พุตของขั้นตอนก่อนหน้าBaseAstVisitor
(ปรับแต่งได้โดยการจัดหาคลาสย่อย) แปลงเอาต์พุต ANTLR เป็นแผนผังแบบอนุกรมของ BaseNode
ซึ่งสร้างขึ้นแบบไดนามิกตามกฎในไวยากรณ์ ANTLRBaseNodeTransformer
สามารถใช้เพื่อแปลงโหนดแต่ละประเภทได้ส่วนถัดไปจะอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับขั้นตอนเหล่านี้
เพื่อให้เห็นภาพกระบวนการสร้างและแปลงแผนผังการแยกวิเคราะห์เหล่านี้ คุณสามารถใช้ ast-viewer นี้
หมายเหตุ: สำหรับส่วนนี้ของบทช่วยสอนนี้ คุณต้องรู้วิธีแยกวิเคราะห์โค้ด
ดูคู่มือการเริ่มต้นใช้งาน ANTLR หากคุณไม่เคยติดตั้ง ANTLR
ANTLR Mega Tutorial มีตัวอย่าง Python ที่เป็นประโยชน์
หน้านี้อธิบายวิธีการเขียนกฎพาร์เซอร์ ANTLR
คำจำกัดความของกฎด้านล่างเป็นตัวอย่างที่มีชื่อที่สื่อความหมายสำหรับองค์ประกอบไวยากรณ์ตัวแยกวิเคราะห์ ANTLR ที่สำคัญ:
rule_name : rule_element? rule_element_label= ' literal ' #RuleAlternativeLabel
| TOKEN + #RuleAlternativeLabel
;
องค์ประกอบกฎและป้ายกำกับทางเลือกเป็นทางเลือก
+
, *
, ?
, |
และ ()
มีความหมายเหมือนกับใน RegEx
ด้านล่างนี้ เราจะใช้ไวยากรณ์ง่ายๆ เพื่ออธิบายวิธีการทำงานของ antlr-ast
ไวยากรณ์นี้มีอยู่ใน /tests/Expr.g4
grammar Expr;
// parser
expr : left=expr op=( ' + ' | ' - ' ) right=expr #BinaryExpr
| NOT expr #NotExpr
| INT #Integer
| ' ( ' expr ' ) ' #SubExpr
;
// lexer
INT : [0-9]+ ; // match integers
NOT : ' not ' ;
WS : [ t]+ -> skip ; // toss out whitespace
ANTLR สามารถใช้ไวยากรณ์ด้านบนเพื่อสร้าง parser ในหลายภาษา ในการสร้าง Python parser คุณสามารถใช้คำสั่งต่อไปนี้
antlr4 -Dlanguage=Python3 -visitor /tests/Expr.g4
สิ่งนี้จะสร้างไฟล์จำนวนหนึ่งในไดเร็กทอรี /tests/
รวมถึง Lexer ( ExprLexer.py
), parser ( ExprParser.py
) และผู้เยี่ยมชม ( ExprVisitor.py
)
คุณสามารถใช้และนำเข้าสิ่งเหล่านี้ได้โดยตรงใน Python ตัวอย่างเช่น จากรากของ repo นี้:
from tests import ExprVisitor
เพื่อให้ใช้งานไฟล์ที่สร้างขึ้นได้อย่างง่ายดาย ไฟล์เหล่านั้นจะถูกใส่ไว้ในแพ็คเกจ antlr_py
ไฟล์ __init__.py
ส่งออกไฟล์ที่สร้างขึ้นภายใต้นามแฝงที่ไม่มีชื่อของไวยากรณ์
คลาสย่อย BaseNode
มีฟิลด์สำหรับองค์ประกอบกฎทั้งหมด และเลเบลสำหรับเลเบลองค์ประกอบกฎทั้งหมดในกฎไวยากรณ์ที่สอดคล้องกัน มีทั้งฟิลด์และป้ายกำกับเป็นคุณสมบัติบนอินสแตนซ์ BaseNode
ป้ายกำกับจะมีความสำคัญเหนือช่องหากชื่อขัดแย้งกัน
ชื่อของ BaseNode
คือชื่อของกฎไวยากรณ์ ANTLR ที่สอดคล้องกัน แต่เริ่มต้นด้วยอักขระตัวพิมพ์ใหญ่ หากมีการระบุป้ายกำกับทางเลือกของกฎสำหรับกฎ ANTLR ป้ายกำกับเหล่านี้จะถูกใช้แทนชื่อกฎ
โดยทั่วไป จะไม่มีการแมปแบบ 1 ต่อ 1 ระหว่างกฎ ANTLR และแนวคิดของภาษา: ลำดับชั้นของกฎจะซ้อนกันมากกว่า การแปลงสามารถใช้เพื่อทำให้แผนผังเริ่มต้นของ BaseNodes ตามกฎ ANTLR คล้ายกับ AST มากขึ้น
BaseNodeTransformer
จะเดินข้ามต้นไม้จากโหนดรูทไปยังโหนดใบ เมื่อเยี่ยมชมโหนด คุณสามารถแปลงโหนดได้ ต้นไม้ได้รับการอัปเดตด้วยโหนดที่แปลงแล้วก่อนที่จะเดินข้ามต้นไม้ต่อไป
หากต้องการกำหนดการแปลงโหนด ให้เพิ่มวิธีการคงที่ให้กับคลาสย่อย BaseNodeTransformer
ที่ส่งผ่านไปยัง process_tree
visit_<BaseNode>
โดยที่ <BaseNode>
ควรถูกแทนที่ด้วยชื่อของคลาสย่อย BaseNode
ที่จะแปลงนี่เป็นตัวอย่างง่ายๆ:
class Transformer ( BaseNodeTransformer ):
@ staticmethod
def visit_My_antlr_rule ( node ):
return node . name_of_part
โหนดที่กำหนดเองสามารถแสดงส่วนหนึ่งของภาษาที่แยกวิเคราะห์ ซึ่งเป็นประเภทของโหนดที่มีอยู่ใน AST
เพื่อให้ง่ายต่อการส่งคืนโหนดแบบกำหนดเอง คุณสามารถกำหนดคลาสย่อย AliasNode
ได้ โดยปกติแล้ว ฟิลด์ของ AliasNode
จะเหมือนกับลิงก์สัญลักษณ์เพื่อนำทางแผนผังของ BaseNode
อินสแตนซ์ของโหนดแบบกำหนดเองถูกสร้างขึ้นจาก BaseNode
ช่องและป้ายกำกับของ BaseNode
ต้นทางยังมีอยู่ใน AliasNode
หากชื่อฟิลด์ AliasNode
ขัดแย้งกับชื่อเหล่านี้ จะมีความสำคัญเหนือกว่าเมื่อเข้าถึงคุณสมบัตินั้น
นี่คือลักษณะของโหนดที่กำหนดเอง:
class NotExpr ( AliasNode ):
_fields_spec = [ "expr" , "op=NOT" ]
รหัสนี้กำหนดโหนดที่กำหนดเอง NotExpr
พร้อมฟิลด์ expr
และ op
คุณสมบัติคลาส _fields_spec
คือรายการที่กำหนดฟิลด์ที่โหนดแบบกำหนดเองควรมี
นี่คือวิธีการใช้ข้อมูลจำเพาะของฟิลด์ในรายการนี้เมื่อสร้างโหนดแบบกำหนดเองจาก BaseNode
(โหนดต้นทาง):
None
None
AliasNode
และด้านขวาคือเส้นทางที่ควรดำเนินการเริ่มต้นในโหนดต้นทางเพื่อรับโหนดที่ควรเป็นค่าสำหรับฟิลด์บน โหนดที่กำหนดเอง บางส่วนของเส้นทางนี้ถูกแยกโดยใช้ .
หากต้องการใช้โหนดแบบกำหนดเองนี้ ให้เพิ่มวิธีการลงในหม้อแปลง:
class Transformer ( BaseNodeTransformer ):
# ...
# here the BaseNode name is the same as the custom node name
# but that isn't required
@ staticmethod
def visit_NotExpr ( node ):
return NotExpr . from_spec ( node )
แทนที่จะกำหนดวิธีการในคลาส Transformer เพื่อใช้โหนดที่กำหนดเอง คุณสามารถดำเนินการนี้ได้โดยอัตโนมัติ:
Transformer . bind_alias_nodes ( alias_nodes )
เพื่อให้ทำงานได้ คลาส AliasNode
ในรายการควรมีคุณสมบัติคลาส _rules
พร้อมด้วยรายการชื่อ BaseNode
ที่ควรแปลง
นี่คือผลลัพธ์:
class NotExpr ( AliasNode ):
_fields_spec = [ "expr" , "op=NOT" ]
_rules = [ "NotExpr" ]
class Transformer ( BaseNodeTransformer ):
pass
alias_nodes = [ NotExpr ]
Transformer . bind_alias_nodes ( alias_nodes )
รายการใน _rules
ก็สามารถเป็นสิ่งอันดับได้เช่นกัน ในกรณีนั้น รายการแรกในทูเพิลคือชื่อ BaseNode
และรายการที่สองคือชื่อของเมธอดคลาสของโหนดแบบกำหนดเอง
มันไม่มีประโยชน์ในตัวอย่างด้านบน แต่เทียบเท่ากับสิ่งนี้:
class NotExpr ( AliasNode ):
_fields_spec = [ "expr" , "op=NOT" ]
_rules = [( "NotExpr" , "from_not" )]
@ classmethod
def from_not ( cls , node ):
return cls . from_spec ( node )
class Transformer ( BaseNodeTransformer ):
pass
alias_nodes = [ NotExpr ]
Transformer . bind_alias_nodes ( alias_nodes )
เป็นเรื่องง่ายที่จะใช้แผนผังที่มีส่วนผสมของ AliasNode
s และ Dynamic BaseNode
s: ต้นไม้ทั้งหมดเป็นเพียงวัตถุ Python ที่ซ้อนกัน
เมื่อค้นหาโหนดในแผนผัง ลำดับความสำคัญของโหนดสามารถนำมาพิจารณาได้ ตามค่าเริ่มต้น BaseNode
มีลำดับความสำคัญ 3 และ AliasNode
มีลำดับความสำคัญ 2
เมื่อเขียนโค้ดเพื่อทำงานกับแผนผัง อาจได้รับผลกระทบจากการเปลี่ยนแปลงไวยากรณ์ การแปลง และโหนดแบบกำหนดเอง ไวยากรณ์มีแนวโน้มที่จะเปลี่ยนแปลงมากที่สุด
หากต้องการให้การอัปเดตไวยากรณ์ไม่มีผลกระทบต่อโค้ดของคุณ อย่าพึ่งพา BaseNode
คุณยังคงสามารถตรวจสอบได้ว่าโหนดพาเรนต์ AliasNode
ของ BaseNode
มีการตั้งค่าฟิลด์ที่ถูกต้องและค้นหา AliasNode
ที่ซ้อนกันในแผนผังย่อยหรือไม่
หากคุณพึ่งพา BaseNode
โค้ดอาจเสียหายได้ด้วยการเพิ่ม AliasNode
ซึ่งจะแทนที่บางส่วนหากชื่อฟิลด์ขัดแย้งกับชื่อฟิลด์ใน BaseNode
ที่ใช้