1 คะแนน โดย GN⁺ 2025-02-09 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • TRRE เป็นส่วนขยายของภาษา regular expression ที่เพิ่มตัวดำเนินการ : เพื่อแสดงการแปลงข้อความโดยตรง และมีให้ใช้งานในรูปเครื่องมือ CLI ชื่อ trre ที่คล้าย grep -E สำหรับทดลองแนวคิดนี้
  • รูปแบบพื้นฐานคือ transductive pair ที่เปลี่ยนแพตเทิร์นอินพุตเป็นแพตเทิร์นเอาต์พุต เช่น a:b; การลบเขียนเป็นการแปลงกับสตริงว่างแบบ x: และการแทรกเขียนเป็น :x
  • สามารถใช้ การเลือกทางเลือก, การทำซ้ำ และการแปลงช่วงอักขระได้เหมือน regular expression ทั่วไป พร้อมตัวอย่างอย่าง cat:dog, [a:A-z:Z] และ Caesar cipher
  • ภายในไม่ได้สร้าง FSA ของ regular expression ทั่วไป แต่สร้าง Finite State Transducer(FST) ที่จัดการคู่ input-output และรองรับการทำ determinization แบบ on-the-fly เชิงทดลองด้วย
  • ขณะนี้ยังไม่มีไบนารีที่ build ไว้ล่วงหน้า ต้อง build เอง และยังมี TODO เช่น การทำให้ DFT เสถียร, รองรับ Unicode เต็มรูปแบบ, ทำฟีเจอร์ ERE ให้ครบ และจัดการช่วงอย่างมีประสิทธิภาพ

ปัญหาที่ TRRE ต้องการแก้

  • regular expression ทั่วไปมีประโยชน์ในการหาแพตเทิร์นในข้อความ แต่ในการแก้ไขข้อความ ตรรกะการจัดการกลุ่มอาจกลายเป็นเหมือน post-processing และซับซ้อนได้
  • TRRE ขยายภาษา regular expression เพื่อใส่ทั้งการจับคู่แพตเทิร์นและการแก้ไขข้อความไว้ใน expression เดียวกัน
  • ไวยากรณ์หลักคือรูปแบบ pattern-to-match:pattern-to-generate และตัวอย่างที่ง่ายที่สุดคือ a:b ซึ่งเปลี่ยน a เป็น b
  • เครื่องมือ CLI trre เป็น implementation ที่แสดงแนวคิดนี้ และทำงานให้ความรู้สึกคล้าย grep -E

ไวยากรณ์การแปลงพื้นฐาน

  • การแทนที่สตริงเขียนได้แบบ cat:dog
    • echo 'cat' | ./trre 'cat:dog' จะพิมพ์ dog
    • สามารถได้ผลเดียวกันด้วยการแปลงทีละอักขระ เช่น (c:d)(a:o)(t:g)
  • ใช้แทนที่ทุก match ในสตริงได้เหมือน sed
    • หากใช้ lamb:cat กับ Mary had a little lamb. จะได้ Mary had a little cat.
  • การลบ แสดงโดยเว้นฝั่งขวาให้ว่างในรูป string_to_delete:
    • (x:)or ลบ x ออกจาก xor เพื่อให้ได้ or
    • a: ใน scan mode พื้นฐานจะเปลี่ยน a ทุกตัวเป็นสัญลักษณ์ว่างและลบทิ้ง
    • สามารถใช้ bracket expression เพื่อลบหลายอักขระได้ เช่น [aie]:
  • การแทรก แสดงโดยเว้นฝั่งซ้ายให้ว่างในรูป :string_to_insert
    • (:x)or แทรก x หน้า or เพื่อให้ได้ xor
    • had a (:little )lamb แทรก little ภายในบริบท

การแปลงบน regular expression

  • TRRE รองรับ การเลือกทางเลือก ด้วย | เหมือน regular expression ทั่วไป
    • (c:b)at|(d:h)og เปลี่ยน cat dog เป็น bat hog
  • ตัวดำเนินการทำซ้ำก็ใช้กับการแปลงได้
    • (cat:dog)* เปลี่ยน catcatcat เป็น dogdogdog
    • ใน scan mode พื้นฐาน แค่ cat:dog ก็จะถูกใช้ซ้ำและให้ผลเดียวกันได้
  • เมื่อใช้การทำซ้ำในแพตเทิร์นฝั่งซ้าย จะ consume อินพุตหลายรายการแล้วเปลี่ยนเป็นเอาต์พุตเดียวได้
    • (cat)*:dog เปลี่ยน catcatcat เป็น dog
  • หากใช้ * หรือ + ในแพตเทิร์นฝั่งขวา อาจเกิด ลูปไม่สิ้นสุด ได้
    • ควรหลีกเลี่ยง expression อย่าง :a*
    • หากต้องการทำซ้ำแบบจำกัด ให้ระบุจำนวนครั้ง เช่น :(repeat-10-times){10}

การแปลงช่วงและ generator

  • การแปลงช่วงอักขระเขียนได้แบบ [a:A-z:Z]
    • สามารถเปลี่ยน regular expressions เป็น REGULAR EXPRESSIONS ได้
  • มีตัวอย่าง Caesar cipher รวมอยู่ด้วย
    • [a:b-y:zz:a] เปลี่ยน caesar cipher เป็น dbftbs djqifs
    • [a:zb:a-z:y] เปลี่ยนกลับเป็น caesar cipher
  • ยังสามารถสร้างเอาต์พุตหลายแบบจากอินพุตเดียวได้เหมือน generator
    • ค่าเริ่มต้นจะใช้ match แรกที่เป็นไปได้
    • หากใช้ตัวเลือก -a จะสร้างเอาต์พุตทั้งหมดที่เป็นไปได้
  • ตัวอย่างเช่น เมื่อนำ :(0|1){3} ไปใช้กับอินพุตว่าง จะสร้างลำดับไบนารี 3 บิตตั้งแต่ 000 ถึง 111 ได้
  • เมื่อใช้ :(0|1){,3}? ร่วมกับ -ma จะสร้างเอาต์พุตในรูปแบบ subset ที่มีความยาวไม่เกิน 3

สเปกภาษาและลำดับความสำคัญของตัวดำเนินการ

  • อย่างไม่เป็นทางการ TRRE นิยามเป็นคู่ pattern-to-match:pattern-to-generate
  • ฝั่งซ้าย pattern-to-match อาจเป็นสตริงหรือ regular expression ก็ได้
  • ฝั่งขวา pattern-to-generate โดยทั่วไปเป็นสตริง แต่ก็เป็น regular expression ได้เช่นกัน
  • ปัจจุบันตัวดำเนินการ : ถูกปฏิบัติเป็นแบบ ไม่จับกลุ่มทั้งซ้ายและขวา และรูปแบบ TRRE:TRRE ไม่ได้รับอนุญาตตามไวยากรณ์
    • รูปแบบนี้มีความหมายโดยธรรมชาติว่าเป็นการประกอบความสัมพันธ์ที่ TRRE นิยามไว้ แต่ยังถูกตัดออกเพราะความซับซ้อนอาจสูงขึ้น
  • ลำดับความสำคัญของตัวดำเนินการจากสูงไปต่ำมีดังนี้
    • อักขระ escape \
    • bracket expression []
    • การจัดกลุ่ม ()
    • การทำซ้ำ * + ? {m,n}
    • การต่อกัน
    • Transduction :
    • การเลือกทางเลือก |

โหมดและความ greedy

  • trre รองรับสองโหมด
    • Scan Mode: โหมดเริ่มต้น ใช้การแปลงตามลำดับ
    • Match Mode: ใช้แฟล็ก -m และตรวจสอบว่าทั้งสตริงตรงกับ expression หรือไม่
  • ตัวเลือก -a สร้างเอาต์พุตทั้งหมดที่เป็นไปได้
  • modifier ? ทำให้ตัวดำเนินการ *, +, {,} เป็นแบบ non-greedy
    • <(.:)*> พิมพ์ <> จาก <cat><dog>
    • <(.:)*?> พิมพ์ <><> จากอินพุตเดียวกัน
  • ยังมีตัวอย่างการเปลี่ยนเนื้อหาภายในแท็กหรือวงเล็บด้วย
    • <(.*?:cat)> เปลี่ยน <dog> <mouse> เป็น <cat> <cat>

การ implementation บน FST และ determinization

  • TRRE สร้าง Finite State Transducer(FST) ภายใน
  • FST คล้ายกับ Finite State Automaton(FSA) ที่ใช้ใน regular expression ทั่วไป แต่จัดการคู่ input-output แทนสตริงเดี่ยว
  • ความแตกต่างหลักของ TRRE มีดังนี้
    • นิยาม ความสัมพันธ์ทวิภาค ระหว่าง regular language สองชุด
    • ใช้ FST แทน FSA ในการอนุมาน
    • รองรับการทำ determinization แบบ on-the-fly เชิงทดลองเพื่อประสิทธิภาพ
  • ในเอนจิน regex ทั่วไป determinization จะเปลี่ยน nondeterministic automaton เป็น deterministic automaton ทำให้อนุมานได้ในเวลาเชิงเส้นตามความยาวสตริงอินพุต
  • ใน TRRE ก็ใช้แนวทางคล้ายกันได้ แต่ไม่สามารถเปลี่ยนตัวแปลงแบบไม่กำหนดทั้งหมด NFT เป็นตัวแปลงแบบกำหนด DFT ได้
    • หากมี cycle “bad” สองชุดที่มี input label เดียวกัน การสร้าง state อาจตกอยู่ในลูปไม่สิ้นสุด
    • มีวิธีตรวจจับลูปประเภทนี้ แต่มีต้นทุนสูง

ประสิทธิภาพและสถานะการติดตั้ง

  • มีตัวอย่างที่ระบุว่าเวอร์ชันแบบไม่กำหนดพื้นฐานช้ากว่า sed เล็กน้อยในการแทนที่ง่าย ๆ
    • ./trre '(vodka):(VODKA)': real 0m0.046s
    • sed 's/vodka/VODKA/': real 0m0.024s
  • ในงานที่ซับซ้อน มีตัวอย่างที่เวอร์ชันแบบกำหนด trre_dft เร็วกว่า sed
    • sed -e 's/\(.*\)/\U\1/': real 0m0.508s
    • ./trre_dft '[a:A-z:Z]': real 0m0.131s
  • ยังไม่มีไบนารีที่ build ไว้ล่วงหน้าให้ใช้
  • การติดตั้งทำโดย clone repository แล้ว build และทดสอบด้วย make && sh test.sh
  • TODO ยังมีรายการต่อไปนี้
    • เวอร์ชัน DFT ที่เสถียร
    • รองรับ Unicode เต็มรูปแบบ
    • ทำฟีเจอร์ ERE ให้ครบ
      • การปฏิเสธด้วย ^ ภายใน []
      • character class
      • สัญลักษณ์ anchor $^
    • การจัดการช่วงอย่างมีประสิทธิภาพ

แนวทางที่อ้างอิง

1 ความคิดเห็น

 
GN⁺ 2025-02-09
ความคิดเห็นบน Hacker News
  • น่าสนใจว่าโปรเจกต์นี้จะไปทางไหน แต่ ลำดับความสำคัญของตัวดำเนินการ ดูไม่เป็นธรรมชาติ และดูเหมือนคนอื่น ๆ ในเธรดนี้ก็รู้สึกคล้ายกัน
    cat:dog ทำให้คาดโดยธรรมชาติว่ามันจะเท่ากับ (cat):(dog) ไม่ใช่ ca(t:d)og

    • เป็นไอเดียที่น่าสนใจในหลายแง่
      ผมเองก็งงเหมือนกันที่ cat:dog ถูกตีความเป็น ca(t:d)og ไม่ใช่ (cat):(dog) แต่พอนึกได้ว่าทุกคนใช้ regex กันผิดไปนิดหน่อยก็เข้าใจขึ้น regex “เดิมที” ควรมองว่าเป็น ตัวสร้างสตริง ไม่ใช่ matcher ดังนั้น cat|dog ในเชิงรูปแบบจึงมองได้ว่าขยายเป็นเซตอย่าง {catog,cadog}
      ในการ matching ก็แค่เอาเซตของสตริงนี้ไปทำ substring matching กับข้อความที่ใหญ่กว่า ปัญหาคือ regex engine ส่วนใหญ่ในโลกจริงไม่ได้ทำงานแบบนี้ และมีพฤติกรรมแปลก ๆ หลายอย่างเพื่อให้ตรงกับความคาดหวังหรือเพื่อประสิทธิภาพ
      ถ้าลองทดสอบเครื่องมือ regex หลาย ๆ ตัว จะได้รูปแบบแปรผันอย่าง (cat)|(dog) หรือ (cat)|(dog)|(ca[td]og) ดังนั้นจากมุมมองที่เป็นทางการกว่า ผมคิดว่า cat:dog สร้าง ca(t:d)og ไม่ใช่ (cat):(dog) จึงถูกต้องแล้ว แต่เพราะประสบการณ์หลายสิบปีที่เราใช้ regex อย่างผิดวัตถุประสงค์ให้เป็นเครื่องมือ matching ที่ปรับตามความคาดหวังของผู้ใช้ ตอนนี้ทุกคนเลยใส่วงเล็บครอบนิพจน์ที่อยากแทนที่
      ข้อเสนอนี้น่าสนใจและออกแบบมาดี แต่สุดท้ายให้ความรู้สึกเหมือนพยายามพา regex กลับไปสู่ โมเดลตัวสร้าง ดั้งเดิม ปัญหาน่าจะอยู่ที่ฝั่งเครื่องมือมากกว่าไวยากรณ์
      ผมเคยทำงานใกล้เคียงกับสายนี้มาก่อน และถ้าคุณไม่เคยคิดถึง regex ในฐานะตัวสร้างเซตของสตริง ลองเล่นได้ที่นี่: https://onlinestringtools.com/generate-string-from-regex
      อย่างไรก็ตาม พฤติกรรมของเครื่องมือสร้างแบบนี้ก็มีความเฉพาะมาก เครื่องมือที่ผมเคยใช้มีหลายวิธีในการจำกัด generator เช่นกำหนดข้อจำกัดให้ closure เป็นต้น
    • ขอบคุณสำหรับฟีดแบ็ก และเรื่อง ลำดับความสำคัญ ผมเองก็กำลังคิดอยู่ อาจเปลี่ยนได้
      ถ้าเลื่อนไปไว้หลัง concatenation ก็อาจเกิดปัญหาอื่น เช่นกับ : ที่ไม่ associative, cat:dog:mouse อาจควรเป็นสิ่งผิดกฎหมาย แต่ยังไม่แน่ใจว่าจะจัดการอย่างไร
      ในเวอร์ชันปัจจุบัน จะใส่ epsilon หรือสตริงว่างเข้าไป เช่นถ้าต้องการลบโดยข้ามทีละตัว ในทางเทคนิคคุณสามารถรัน ..: ซึ่งคือ .(.:eps) ได้
      ผลลัพธ์ของ echo 'abcde' | ./trre '..:' คือ 'ace'
      จริง ๆ แล้วการผูก : อาจมีความหมายเป็น composition ของ regular relation ได้ด้วย แต่ตอนนี้ผมมองว่ามันซับซ้อนเกินไป
    • การแปลงช่วงก็คล้ายกัน ผมอยากเสนอรูปแบบอย่าง [a-z:A-Z] แทน [a:A-z:Z] และ [a-y:b-z;z:a] แทน [a:b-y:zz:a]
  • ถ้าสนใจ finite-state transducer และเครื่องมือที่เกี่ยวข้อง XFST (Xerox Finite-State Transducer) ก็น่าดู ใช้ในงานภาษาศาสตร์เชิงคำนวณมากว่า 20 ปีแล้ว
    เคยมีนักวิจัยชาวฟินแลนด์จาก PARC มาในคลาสที่ UT แล้วสาธิตวิธีจัดการสัณฐานวิทยาภาษาฟินแลนด์ด้วย FST ซึ่งดูจากภายนอกก็ถือว่าน่าทึ่งมาก

    • ผมก็กำลังจะพูดถึงอันนี้เหมือนกัน ลิงก์บทความของ Kaplan: https://aclanthology.org/J94-3001.pdf
      อธิบายงานที่ทำที่ PARC
    • http://hfst.github.io/ คือ เวอร์ชันโอเพนซอร์ส สมัยใหม่ของ XFST ครอบคลุม foma และ OpenFst และน่าจะทำได้เกือบทุกอย่างที่ trre ทำ รวมถึงมากกว่านั้น
    • Pynini ก็น่าสนใจเช่นกัน เป็น Python wrapper ของ OpenFst และมีฟีเจอร์ช่วยให้ใช้ง่ายเพิ่มมาเยอะ
      OpenFst เป็นไลบรารีสำหรับ transducer ที่ยอดเยี่ยมจริง ๆ บทเรียนตัวอย่างการใช้ Pynini ที่ทำเป็นโจทย์โดย Johns Hopkins และที่อื่น ๆ ก็ใช้ได้ดี
      [1] https://www.openfst.org/twiki/bin/view/GRM/Pynini
      [2] https://www.openfst.org/
  • ถ้ากำลังมองหาทางเลือกแทน regex มาตรฐาน โดยเฉพาะถ้าตรรกะของกลุ่มเข้าใจยาก หรืออยากได้ expression ที่ดูแลรักษาได้ Rosie Pattern Language อาจเหมาะ
    https://gitlab.com/rosie-pattern-language/rosie/-/blob/maste...
    https://rosie-lang.org/about/

  • เจ๋งดี ผมเคยเขียนวิทยานิพนธ์ Diplom ด้านวิทยาการคอมพิวเตอร์ราวปี 1997 ด้วย finite-state transducer และมันไม่ใช่เรื่องเล็กน้อยกว่าที่คิดมาก
    งานคือการ implement composition และ DFA เมื่อทำได้ รวมถึง transducer ที่ประกอบกันแล้วด้วย เป็นเรื่อง “พีชคณิตของ finite-state transducer” และ use case คือสัณฐานวิทยา หัวข้อนี้ถูกประเมินต่ำเกินไปมาก จนผมต้องหยุดไว้ประมาณกลางทาง ดังนั้นขอคารวะ
    เรื่องไวยากรณ์ ผมสงสัยว่าต้องการให้ : จับกลุ่มแน่นกว่า concatenation ab จริง ๆ หรือเปล่า

    • ผมเคยใช้ OpenFST ในชีวสารสนเทศช่วงต้นยุค 2000 มันสนุกดีเวลาเอามาเล่น แต่สุดท้ายก็ไม่เป็นประโยชน์กับงานที่ผมทำ
      ดีใจที่เห็นว่า 20 ปีผ่านไปโปรเจกต์นี้ยังเดินหน้าต่อ: https://www.openfst.org/twiki/bin/view/FST/WebHome
    • การเอาการจบการศึกษาไปผูกกับว่า “จัดการ regex ได้หนักพอหรือไม่” นี่เป็นตัวเลือกที่กล้ามาก
    • ใช่แล้ว transducer เป็นหัวข้อที่เก่าแก่มาก ด้วยเหตุผลบางอย่างมันไม่ได้เชื่อมโยงกับภาษาใดภาษาหนึ่งอย่างแน่นแฟ้นเหมือน regex
      ผมยังไม่แน่ใจว่า : ควรจับกลุ่มแน่นกว่า concatenation หรือไม่ ดูตัวอย่างประมาณ 100 ตัวแล้วผมคิดว่าวิธีปัจจุบัน คือให้ : มีลำดับต่ำกว่า . ดูเป็นธรรมชาติกว่า แต่ในโค้ดเปลี่ยนได้ด้วยการเปลี่ยนตัวเลขแค่ตัวเดียวจริง ๆ เพราะงั้นเลยเอามาโพสต์ที่นี่ และต้องการฟีดแบ็กจริง ๆ
  • ทันทีที่ต้องการทำ การแทนที่เชิงโครงสร้าง แบบใดแบบหนึ่ง วิธีนี้ก็ดูเหมือนจะไม่พอ ตัวอย่างเช่น บางครั้งเราอยากทำอะไรอย่าง s/"([^"]*)"/'$1'/
    ยิ่งไปกว่านั้น ถ้าสามารถเปลี่ยนสิ่งที่แมตช์กับ ['] ภายใน [^"] ให้เป็น \' ได้ ก็น่าจะมีประโยชน์มากขึ้น
    โดยทั่วไปกว่านั้น regex กำหนด parse tree ให้กับผลการแมตช์โดยพฤตินัย ดังนั้นถ้าสามารถทำการแปลงที่ทั่วไปกว่านี้กับ tree นั้นได้ก็น่าจะมีประโยชน์

    • ถ้าผมเข้าใจถูก นิพจน์ ttre ต่อไปนี้ทำสิ่งที่ต้องการได้:
      ":'(':(\\')|[^"'])*":'
    • ถ้าเข้าใจถูก คุณอยากเปลี่ยนเนื้อหาภายในบล็อก "..." และเปลี่ยนเครื่องหมายคำพูดเป็นอัญประกาศเดี่ยว '
      ทำได้ด้วยนิพจน์นี้:
      echo '"hello world" "hello again!"' | ./trre "\":'.+?:-\":'"
      ผลลัพธ์คือ '-' '-'
      กล่าวคือ ด้วยนิพจน์ ".+?:-" จะแทนที่ข้อความภายใน "" ด้วยเครื่องหมาย - พร้อมกับเปลี่ยนเครื่องหมายคำพูดรอบ ๆ ไปด้วย เครื่องหมายคำถามหมายถึง โหมดไม่ละโมบ
  • ดูเหมือนทั้งโปรเจกต์จะตั้งอยู่บนข้ออ้างว่า “regex เป็นเครื่องมือที่ยอดเยี่ยมในการหารูปแบบในข้อความ แต่สำหรับการแก้ไขข้อความแล้ว มันรู้สึกไม่เป็นธรรมชาติเสมอ” แต่กลับไม่มีตัวอย่างเลย
    ผมไม่เข้าใจว่าทำไม regex ถึงไม่เป็นธรรมชาติสำหรับการแก้ไข และก็ไม่รู้ว่าในที่นี้การแก้ไขหมายถึงอะไร รวมถึงไม่เข้าใจว่าทำไมผู้คนถึงลำบากกับกลุ่ม
    โปรเจกต์นี้มีตัวอย่างไวยากรณ์เยอะ แต่ผมไม่เห็นว่ามันดีกว่า regex ปกติอย่างไร ถ้ามีตัวอย่างสักสองสามอันแบบ “เวอร์ชัน regex พื้นฐานเป็นแบบนี้ เวอร์ชันของผมเป็นแบบนี้ ดังนั้นจึงง่ายขึ้น” ก็น่าจะช่วยให้เข้าใจโปรเจกต์ได้

    • ผมมองว่า regex โดยทั่วไปมีลักษณะเป็นสิ่งที่ เขียนครั้งเดียวแล้วไม่ค่อยกลับมาแก้ การทำต้นแบบเพื่อมองไปไกลกว่านั้นเป็นวิธีที่ดีในการสำรวจอนาคตที่ดีกว่าของสาขานี้
    • เป็นประเด็นที่สมเหตุสมผล ตัวอย่างที่ชัดที่สุดคือเวลาที่ต้อง เปลี่ยนเฉพาะในบริบท
      เช่น ถ้าต้องการเปลี่ยนเฉพาะ y ที่อยู่ระหว่าง x กับ z ให้เป็น Y ใน Python ก็จะทำประมาณนี้:
      pattern = r'(x)y(z)'
      replacement = r'\1Y\2'
      result = re.sub(pattern, replacement, text)
      ผมอยากแทนที่สิ่งนี้ด้วยแพตเทิร์น xy:Yz:
      result = re.trre('xy:Yz', text)
      ถ้า x, z เป็นแพตเทิร์นที่ซับซ้อนกว่านี้ หรือเป็น regex เอง แนวทางนี้อาจสะดวกกว่า
    • มองว่า regex เพียงอย่างเดียวไม่ได้ให้ฟังก์ชันแก้ไขข้อความก็ถูกแล้ว มีกลุ่มก็จริง แต่ถ้าจะประกอบกลุ่มเหล่านั้นเข้าด้วยกันก็ต้องใช้ ภาษาอื่น อย่าง sed
    • พูดถึงการแทนที่นั่นแหละ ด้วยไวยากรณ์ของผู้เขียน การแสดงการแทนที่ หรือพูดตรง ๆ คือการพิมพ์มัน ทำได้ง่ายกว่า
      เป็นโปรเจกต์ที่ดี
  • โค้ด C อ่านสนุกจริง ๆ ดีมาก และตอนนี้กำลังอ่านอยู่
    ขอคอมเมนต์เล็กน้อยอย่างเดียว ลิงก์ theory.pdf ใน README เสียอยู่ ตัว PDF อยู่ในไดเรกทอรี docs/ ดังนั้นแค่ใส่ docs/ ใน URL ก็พอ

    • ขอบคุณสำหรับฟีดแบ็กและการชี้คำผิด แก้แล้ว จริง ๆ แล้ว ฝีมือ C ของผมค่อนข้างขึ้นสนิม เลยกังวลอยู่นิดหน่อย
  • มีบอกไว้ว่าควรหลีกเลี่ยงการใช้ * หรือ + ในส่วนด้านขวา เพราะอาจเกิดลูปไม่รู้จบได้ แล้วทำไมไม่ห้ามไปเลยล่ะ?
    เข้าใจว่ามันทำให้สเปกไวยากรณ์ยากขึ้น แต่ดูไม่เห็นมีเหตุผลดี ๆ ที่จะเก็บไว้

    • เป็นประเด็นที่สมเหตุสมผลและเห็นด้วย ตอนนี้น่าจะปิดการใช้งานไปก่อนดีกว่า
      เหตุผลเดิมคือผมตั้งใจจะ implement โอเปอเรชันที่น่าสนใจชื่อ การประกอบตัวแปลง เราสามารถทำโอเปอเรชันง่าย ๆ กับสตริงและประกอบ trre ให้เหมือนฟิลเตอร์ได้ แต่ยังทำไม่เสร็จ ดังนั้นก็เป็นประเด็นที่ถูกต้องจริง ๆ
  • เป็นการสำรวจที่น่าสนใจ แต่ยังขาดตัวอย่างว่าทำไมในทางปฏิบัติมันถึงดีกว่า แน่นอนว่าอาจเป็นเพราะผมคุ้นกับ regex มานานเกินไปก็ได้
    เช่น ผมไม่เห็นว่า (cat):(dog) ของ trre ดีกว่า s/cat/dog อย่างไร หรือ (x:)or ดีกว่า s/xor/or ตรงไหน ตัวอย่างแทบทั้งหมดในหัวผมสามารถเทียบเป็น regex ที่ค่อนข้างง่ายได้
    ถ้ามีข้อดีหลักอยู่จริง ก็น่าจะอยู่ทางด้าน ตรรกะของกลุ่ม ดังนั้นตัวอย่างควรโฟกัสไปทางนั้นจะดี ก่อนอธิบายไวยากรณ์พื้นฐาน น่าจะดีกว่าถ้าอธิบายก่อนว่าทำไมมันถึงเป็นตัวเลือกที่ดีกว่า
    ตัวอย่างรหัสซีซาร์ทำให้รู้สึกว่าจำเป็นต้องมีฟีเจอร์ “นำสิ่งนี้ไปใช้แบบย้อนกลับ” มาก ๆ ซึ่งเป็นคำขอที่พบบ่อยในการแทนที่ข้อความจำนวนมาก และในตัวอย่างนี้ก็ชัดเป็นพิเศษ สมองโปรแกรมเมอร์จะร้องขึ้นมาทันทีว่า “ทำไมต้องแสดงตรรกะเดียวกันสองครั้ง?”
    ยังไม่แน่ใจว่ามีประโยชน์หรือไม่ แต่การสำรวจทางเลือกแทนสถานะเดิมที่อยู่มานานเป็นเรื่องยอดเยี่ยม ปกติความพยายามแบบนั้นมีโอกาสสูงที่จะไม่สำเร็จ แต่ถึงอย่างนั้นการสำรวจก็น่าชื่นชม

  • สเปกดูเหมือนจะยังขาดรายละเอียดไปมาก ตัวอย่างแรกก็ดูแปลกแล้ว:
    $ echo 'cat' | trre 'c:da:ot:g'
    dog
    ไม่รู้ว่าเกิดอะไรขึ้นตรงนี้ ไวยากรณ์เขียนไว้แบบนี้:
    TRRE <- TRRE* TRRE|TRRE TRRE.TRRE
    TRRE <- REGEX REGEX:REGEX
    parse tree ตรงนี้คืออะไร? ทำไม c ถึงไม่ถูกเปลี่ยนเป็น da? หรือทำไม c ถึงไม่ถูกลบ แล้ว da ถูกเปลี่ยนเป็น ot?
    ไอเดียที่ให้ความหมายแบบค้นหา/แทนที่ที่เข้าใจง่ายกว่าตัวดำเนินการจัดกลุ่มนั้นดี สมัย MS-DOS เคยทำอะไรอย่าง ren .log .txt ได้และมันก็ทำงาน แม้จะฟังดูไม่สมเหตุสมผลเลยในวิธีคิดแบบ bash ยุคใหม่ แต่ดูปุ๊บก็รู้ชัดมากว่าต้องการทำอะไร

    • นี่เป็นเรื่องของ ลำดับความสำคัญของตัวดำเนินการ และการตัดเป็นโทเคน ในภาษานี้ โทเคนคืออักขระเดี่ยว และมีตัวดำเนินการที่มองไม่เห็นอยู่ระหว่างอักขระ
      ถ้าเรียกตัวดำเนินการนั้นอย่างชัดเจนว่า ~ ตัวอย่างจะดูเป็นแบบนี้:
      $ echo 'cat' | trre 'c:d~a:o~t:g'
      dog
      ถ้าใส่วงเล็บที่ไม่จำเป็นเข้าไปก็จะเป็นแบบนี้:
      $ echo 'cat' | trre '(c:d)~(a:o)~(t:g)'
      dog
    • ไวยากรณ์ยังมีสเปกไม่เพียงพอ ไวยากรณ์ทั้งหมดซับซ้อนกว่านี้ เวอร์ชันปัจจุบันน่าจะต้องเอาออกจากเอกสาร และตอนนี้มันทำให้สับสนจริง ๆ
      เหตุผลที่ c ไม่ถูกเปลี่ยนเป็น da ล้วนเป็นเพราะลำดับความสำคัญ ดูจากการสนทนานี้แล้ว ผมคงเลือกลำดับความสำคัญผิด และนั่นก่อให้เกิดความสับสน
      ตารางลำดับความสำคัญปัจจุบันเป็นดังนี้:
      | 1 | อักขระ escape | \ |
      | 2 | นิพจน์วงเล็บเหลี่ยม | [] |
      | 3 | การจัดกลุ่ม | () |
      | 4 | การทำซ้ำ ERE ของอักขระเดี่ยว | * + ? {m,n} |
      | 5 | การแปลง | : |
      | 6 | การเชื่อมต่อ | . (โดยนัย) |
      | 8 | การเลือก | | |
      ดังนั้น : จึงจับกลุ่มแน่นกว่า . หรือการเชื่อมต่อโดยนัย
    • ใช่ สเปกยังไม่พอ ตัวอย่างการลบแสดงให้เห็นว่าสตริงว่างก็สามารถเป็น REGEX ได้ ถ้าอย่างนั้นจริง ๆ แล้วก็ถือได้ว่าในตำแหน่งใด ๆ มี regex สตริงว่างจำนวนเท่าใดก็ได้ตามต้องการ ทำให้ มีการ parse ได้ไม่จำกัดจำนวน
      แต่ถ้ากำหนดแทนว่า regex ต้องไม่ว่าง ตัวอย่างการลบก็จะพัง แต่ความกำกวมจะย้ายไปอยู่ที่การเชื่อมต่อแทน กล่าวคือจะกำกวมว่าเป็น (((c:d)(a:o))(t:g)) หรือ ((c:d)((a:o)(d:g))) หากสมมติเรื่อง associativity ความแตกต่างนี้ก็คงไม่สำคัญ
    • จากความรู้สึกของพฤติกรรม มันดูเหมือน c:d, a: คือไม่มีอะไร และ ot:g
      แต่พอกลับมาอ่านอีกครั้งก็สับสนจริง ๆ และในเชิงทฤษฎี สิ่งที่ชี้ให้เห็นนั้นถูกต้อง หลังจากอ่าน repository แล้ว ผมเองก็เริ่มเชื่อว่า c ควรถูกเปลี่ยนเป็น da แต่ก็ยังไม่มั่นใจ