โดยทั่วไปแล้ว นิพจน์ทั่วไปจะใช้ในการประมวลผลสตริง และสะดวกมากที่จะใช้นิพจน์เหล่านี้เพื่อจับคู่ แยก และแทนที่สตริง
อย่างไรก็ตาม การเรียนรู้นิพจน์ทั่วไปยังคงค่อนข้างยาก แนวคิดต่างๆ เช่น การจับคู่แบบโลภ การจับคู่แบบไม่โลภ การจับกลุ่มย่อย และกลุ่มย่อยที่ไม่จับภาพ ไม่เพียงแต่ยากสำหรับผู้เริ่มต้นเท่านั้นที่จะเข้าใจ แต่ยังสำหรับคนจำนวนมากที่ทำงานมาหลายปีด้วย
ดังนั้นวิธีที่ดีที่สุดในการเรียนรู้นิพจน์ทั่วไปคืออะไร? จะเชี่ยวชาญนิพจน์ทั่วไปอย่างรวดเร็วได้อย่างไร?
ฉันขอแนะนำวิธีเรียนรู้กฎเกณฑ์ปกติที่ฉันคิดว่าดีมาก: เรียนรู้ผ่าน AST
หลักการจับคู่ของนิพจน์ทั่วไปคือการแยกวิเคราะห์สตริงรูปแบบลงใน AST จากนั้นใช้ AST นี้เพื่อจับคู่สตริงเป้าหมาย
ข้อมูลต่างๆ ในสตริงรูปแบบจะถูกจัดเก็บไว้ใน AST หลังจากแยกวิเคราะห์ AST เป็นแผนผังไวยากรณ์เชิงนามธรรม ตามชื่อ มันคือแผนผังที่จัดเรียงตามโครงสร้างทางไวยากรณ์ จากโครงสร้างของ AST คุณสามารถทราบไวยากรณ์ที่นิพจน์ทั่วไปรองรับได้อย่างง่ายดาย
จะดู AST ของนิพจน์ทั่วไปได้อย่างไร
คุณสามารถดูได้ผ่านทางเว็บไซต์ astexplorer.net:
เมื่อเปลี่ยนภาษาแยกวิเคราะห์เป็น RegExp คุณจะสามารถมองเห็น AST ของนิพจน์ทั่วไปได้
ดังที่ได้กล่าวไปแล้ว AST เป็นแผนผังต้นไม้ที่จัดเรียงตามไวยากรณ์ ดังนั้นจึงสามารถแยกไวยากรณ์ต่างๆ ออกจากโครงสร้างได้อย่างง่ายดาย
จากนั้น มาเรียนรู้ไวยากรณ์ต่างๆ จากมุมมองของ AST:
เริ่มจากรูปแบบง่ายๆ กันก่อน /abc/ ปกติสามารถจับคู่สตริงของ 'abc' ได้ และ AST จะเป็นดังนี้:
3 Char ค่าคือ a, b, c ตามลำดับ ชนิดง่าย การจับคู่ที่ตามมาคือการสำรวจ AST และจับคู่อักขระทั้งสามนี้ตามลำดับ
เราทดสอบโดยใช้ exec API:
องค์ประกอบที่ 0 คือสตริงที่ตรงกัน และดัชนีคือดัชนีเริ่มต้นของสตริงที่ตรงกัน input คือสตริงอินพุต
ลองใช้อักขระพิเศษอีกครั้ง:
/ddd/ หมายถึงการจับคู่ตัวเลขสามตัว d คืออักขระเมตา (meta char) ที่มีความหมายพิเศษที่รองรับโดยนิพจน์ทั่วไป
นอกจากนี้เรายังสามารถเห็นผ่าน AST ว่าถึงแม้พวกมันจะเป็น Char เหมือนกัน แต่ประเภทของพวกมันก็เป็นเมตาดาต้า:
สามารถจับคู่ตัวเลขใดๆ ก็ได้โดยใช้ d เมตาอักขระ:
ตัวไหนเป็นตัวอักษรเมตาและตัวไหนเป็นตัวอักษรธรรมดาสามารถดูได้อย่างรวดเร็วผ่าน AST
รองรับการระบุชุดอักขระผ่าน [] เป็นประจำ ซึ่งหมายความว่าสามารถจับคู่อักขระใดก็ได้
เรายังเห็นได้จาก AST ว่าเลเยอร์ CharacterClass ถูกห่อหุ้มไว้ ซึ่งหมายถึงคลาสอักขระ กล่าวคือ มันสามารถจับคู่อักขระใดๆ ที่มีอยู่ได้
นี่เป็นกรณีที่อยู่ระหว่างการทดสอบ:
นิพจน์ทั่วไปรองรับการระบุจำนวนครั้งที่อักขระบางตัวถูกทำซ้ำ โดยใช้รูปแบบ {from,to}
เช่น /b{1,3}/ หมายถึงอักขระ b ซ้ำ 1 ถึง 3 ครั้ง , /[abc ]{1,3}/ หมายความว่าคลาสอักขระ a/b/c นี้ถูกทำซ้ำ 1 ถึง 3 ครั้ง
ดังที่เห็นได้จาก AST ไวยากรณ์นี้เรียกว่าการทำซ้ำ:
มีแอตทริบิวต์ปริมาณที่แสดงถึงปริมาณ ประเภทที่นี่คือช่วงตั้งแต่ 1 ถึง 3
นิพจน์ทั่วไปยังสนับสนุนคำย่อของตัวระบุปริมาณบางตัว เช่น + หมายถึง 1 ถึงจำนวนนับไม่ถ้วน, * หมายถึง 0 ถึงจำนวนนับไม่ถ้วน และ ? หมายถึง 0 หรือ 1 ครั้ง
เป็นตัวปริมาณประเภทต่างๆ:
นักเรียนบางคนอาจถามว่า คุณลักษณะโลภในที่นี้หมายถึงอะไร?
Greedy หมายถึง โลภ คุณลักษณะนี้บ่งชี้ว่าการทำซ้ำนี้เป็นการจับคู่ที่โลภหรือไม่โลภ
หากคุณเพิ่ม ? หลังตัวระบุปริมาณ คุณจะพบว่า greedy กลายเป็นเท็จ ซึ่งหมายถึงการเปลี่ยนไปใช้การจับคู่ที่ไม่โลภ:
แล้วโลภกับไม่โลภหมายความว่าอย่างไร?
มาดูตัวอย่างกัน
การจับคู่การทำซ้ำเริ่มต้นนั้นต้องใช้ความโลภและจะยังคงจับคู่ต่อไปตราบเท่าที่ตรงตามเงื่อนไข ดังนั้นจึงสามารถจับคู่ acbac ได้ที่นี่
การเพิ่ม ? หลังจากตัวปริมาณเปลี่ยนเป็น non-greedy และเฉพาะอันแรกเท่านั้นที่จะจับคู่:
นี่คือการจับคู่แบบโลภและการจับคู่แบบไม่โลภ เมื่อใช้ AST เราจะรู้ได้อย่างชัดเจน ว่าการจับคู่แบบโลภและแบบโลภคือการจับคู่แบบโลภ
นิพจน์ทั่วไปรองรับการใส่ส่วนหนึ่งของสตริงที่ตรงกันลงในกลุ่มย่อยและส่งคืนผ่าน ()
ลองดู AST:
AST ที่สอดคล้องกันเรียกว่ากลุ่ม
และคุณจะพบว่ามันมีคุณลักษณะการจับภาพซึ่งมีค่าเริ่มต้นเป็นจริง:
สิ่งนี้หมายความว่าอย่างไร?
นี่คือไวยากรณ์สำหรับการจับภาพกลุ่มย่อย
ถ้าไม่อยากจับกลุ่มย่อยก็เขียนแบบนี้ได้นะ (?:aaa)
ดูสิ การจับกลายเป็นเรื่องเท็จ
ความแตกต่างระหว่างการจับภาพและการไม่จับภาพคืออะไร?
มาลองดูกัน:
โอ้ ปรากฎว่าคุณลักษณะการจับภาพของกลุ่มแสดงถึงว่าจะแยกหรือไม่
จาก AST เราจะเห็นได้ ว่าการจับภาพนั้นมีไว้สำหรับกลุ่มย่อย ค่าเริ่มต้นคือการจับภาพ ซึ่งหมายความว่าเนื้อหาของกลุ่มย่อยจะถูกแยกออกมา คุณสามารถสลับไปใช้การไม่จับภาพผ่าน ?: และเนื้อหาของกลุ่มย่อยจะไม่ถูกแยกออก
เราคุ้นเคยกับการใช้ AST เพื่อทำความเข้าใจไวยากรณ์ปกติแล้ว แต่มาดูสิ่งที่ยากขึ้นอีกสักหน่อย:
นิพจน์ทั่วไปรองรับการแสดงออกของการยืนยันแบบมองไปข้างหน้าผ่านไวยากรณ์ของ (?=xxx) ซึ่งใช้ในการตัดสินอักขระบางตัวว่าสตริงนั้นนำหน้าด้วยสตริงบางตัวหรือไม่
เมื่อใช้ AST คุณจะเห็นว่าไวยากรณ์นี้เรียกว่า Assertion และประเภทคือ lookahead ซึ่งหมายถึงการมองไปข้างหน้า ตรงกับความหมายก่อนหน้าเท่านั้น:
สิ่งนี้หมายความว่าอย่างไร? ทำไมคุณถึงเขียนสิ่งนี้? /bbb(ccc)/ และ /bbb(?:ccc)/ แตกต่างกันอย่างไร?
มาลองดูกัน:
สามารถเห็นได้จากผลลัพธ์:
/bbb(ccc)/ จับคู่กลุ่มย่อยของ ccc และแยกกลุ่มย่อยนี้เนื่องจากกลุ่มย่อยเริ่มต้นถูกจับ
/bbb(?:ccc)/ ตรงกับกลุ่มย่อยของ ccc แต่ไม่ถูกแยกออกเนื่องจากเราตั้งค่ากลุ่มย่อยไม่ให้บันทึกผ่าน ?:
/bbb(?=ccc)/ กลุ่มย่อยที่ตรงกัน ccc ไม่ได้ถูกแยกออก บ่งชี้ว่ากลุ่มย่อยนั้นไม่ได้จับภาพด้วย ความแตกต่างระหว่าง it และ ?: คือ ccc นั้นไม่ปรากฏในผลลัพธ์ที่ตรงกัน
นี่คือลักษณะของการยืนยัน lookahead: การยืนยัน lookahead หมายความว่าสตริงบางสตริงนำหน้าด้วยสตริงบางกลุ่ม กลุ่มย่อยที่เกี่ยวข้องไม่ได้จับภาพ และสตริงที่ยืนยันจะไม่ปรากฏในผลลัพธ์ที่ตรงกัน
ถ้าไม่ตามด้วยสตริงนั้น มันจะไม่ตรงกัน:
Change ?= to ! จากนั้นความหมายก็เปลี่ยนไป ดู AST:
แม้ว่าการยืนยัน lookahead จะยังคงถูกยืนยันก่อน แต่ก็มีคุณลักษณะเชิงลบเพิ่มเติมที่เป็น true
ความหมายชัดเจน เดิมทีหมายถึง ส่วนหน้าเป็นสตริงที่แน่นอน ภายหลัง negation หมายความว่า ส่วนหน้าไม่ใช่สตริงที่แน่นอน
ผลลัพธ์ที่ตรงกันจะตรงกันข้าม:
ตอนนี้จะจับคู่เฉพาะเมื่อไม่มีสตริงใดสตริงหนึ่งอยู่ข้างหน้า นี่คือการยืนยัน lookahead เชิงลบ
หากมีการยืนยันก่อนหน้า ก็จะมีการยืนยันต่อท้ายโดยธรรมชาติ กล่าวคือ มันจะจับคู่ก็ต่อเมื่อมีการตามด้วยสตริงบางตัวเท่านั้น
ในทำนองเดียวกันก็สามารถปฏิเสธได้:
AST ที่สอดคล้องกับ (?<=aaa) นั้นง่ายต่อการคิด ซึ่งอยู่ข้างหลังการยืนยัน:
AST ที่สอดคล้องกับ (?<!aaa) คือการเพิ่มแอตทริบิวต์เชิงลบ:
การยืนยันแบบมองไปข้างหน้าและการยืนยันแบบมองข้างหลังเป็นไวยากรณ์นิพจน์ทั่วไปที่ยากที่สุดในการทำความเข้าใจ หากคุณเรียนรู้ผ่าน AST ~
นิพจน์ทั่วไปเป็นเครื่องมือที่สะดวกมากสำหรับการประมวลผลสตริง แต่ก็ยังค่อนข้างง่าย ยากที่จะเรียนรู้ หลายคนสับสนเกี่ยวกับไวยากรณ์ เช่น การจับคู่แบบโลภ การจับคู่แบบไม่โลภ การจับกลุ่มย่อย การไม่จับภาพกลุ่มย่อย การยืนยันแบบมองไปข้างหน้า การยืนยันแบบมองข้างหลัง เป็นต้น
ฉันแนะนำให้เรียนรู้กฎปกติผ่าน AST AST เป็นแผนผังวัตถุที่จัดระเบียบตามโครงสร้างไวยากรณ์ต่างๆ สามารถอธิบายได้อย่างง่ายดายผ่านชื่อและคุณลักษณะของโหนด AST
ตัวอย่างเช่น เราได้ชี้แจงผ่าน AST:
ไวยากรณ์การทำซ้ำ (Repetition) อยู่ในรูปของอักขระ + ปริมาณ ค่าเริ่มต้นคือการจับคู่แบบโลภ (greedy เป็นจริง) ซึ่งหมายถึงการจับคู่จนกระทั่งไม่มีการจับคู่ หลังจากเพิ่ม ? - การจับคู่ที่โลภ ให้หยุดเมื่อจับคู่อักขระตัวใดตัวหนึ่ง
ไวยากรณ์กลุ่มย่อย (กลุ่ม) ใช้เพื่อแยกสตริงบางอย่าง ค่าเริ่มต้นคือการจับภาพ (การจับภาพเป็นจริง) ซึ่งหมายความว่าจำเป็นต้องแยกออก คุณสามารถสลับเป็นการไม่จับภาพผ่าน (?:xxx) ซึ่งตรงกันเท่านั้นแต่ไม่ได้แยกออกมา .
ไวยากรณ์การยืนยัน (Assertion) แสดงถึงสตริงบางอย่างก่อนหรือหลัง แบ่งออกเป็น การยืนยัน lookahead และ การยืนยัน lookbehind ตามลำดับ คุณสามารถส่งผ่าน แทนที่ = ด้วย ! การปฏิเสธ (เชิงลบเป็นจริง) ซึ่งหมายถึงสิ่งที่ตรงกันข้ามทุกประการ
เป็นความเข้าใจเชิงลึกเกี่ยวกับไวยากรณ์ในเอกสารต่าง ๆ หรือเป็นความเข้าใจเชิงลึกเกี่ยวกับไวยากรณ์ในคอมไพเลอร์หรือไม่?
ไม่ต้องถาม ต้องคอมไพเลอร์เท่านั้น!
ถ้าอย่างนั้น การเรียนรู้ไวยากรณ์ผ่านโครงสร้างไวยากรณ์ที่แยกวิเคราะห์ตามไวยากรณ์จะดีกว่าการเรียนรู้จากเอกสารโดยธรรมชาติ
สิ่งนี้ใช้ได้กับนิพจน์ทั่วไป และใช้กับการเรียนรู้ไวยากรณ์อื่นๆ ด้วย หากคุณสามารถเรียนรู้ไวยากรณ์โดยใช้ AST คุณไม่จำเป็นต้องอ่านเอกสารประกอบ