ข้าม​ไป​ยัง​เนื้อหา

pattern เดียวกัน ข้อความ​เดียวกัน แปด engine ตอบ​ตรง​กัน​แค่​ทรง​เดียว​จาก​สิบ​ห้า​ทรง

คุณ​เขียน​ด่าน​ตัว​หนึ่ง​ที่​หา \bแมว\b ใน​ข้อความ​ไทย

มัน​ไม่​เคย​แดง​เลย​สัก​ครั้ง คุณ​จึง​คิด​ว่า​ไม่มี​อะไร​ผิด

หน้า​นี้​ยิง pattern 15 ทรง เข้า engine 8 ตัว รวม 120 ช่อง แล้ว​นับ​ว่า​มี​กี่​ทรง​ที่​ทุก engine ตอบ​ตรง​กัน

คำ​ตอบ​คือ 1 ทรง และ​มัน​คือ​ทรง​ที่​ไม่มี​ใคร match อะไร​เลย

ธี​สิ​ส​ของ​หน้า​นี้​อยู่​ตรง​นี้ — regex ไม่ใช่​ภาษา​เดียว มัน​คือ​ชื่อ​เรียก​รวม​ของ​หลาย​ภาษา ที่​หน้าตา​เหมือน​กัน และ​ภาษา​ที่​คุณ​คิด​ใน​หัว มัก​ไม่ใช่​ภาษา​ที่ engine ตรง​หน้า​คุณ​พูด

ถ้า​ปล่อย​ให้​แต่ละ​ฝั่ง​พิมพ์ pattern เอง การ​เทียบ​จะ​ไม่​ได้​เทียบ​อะไร​เลย

scripts/regex-engines/shapes.json เก็บ pattern กับ​ข้อความ​ทั้ง 15 ทรง​ไว้​ใบ​เดียว · runner ของ​ทั้ง​สี่​ภาษา​อ่าน​จาก​ใบ​นั้น และ​ไม่มี runner ตัว​ไหน​มี pattern ของ​ตัว​เอง

engine ที่​วัด​มี 8 ตัว ซึ่ง​มา​จาก​สี่ runtime · บาง​ตัว​นับ​สอง​เพราะ​มัน​มี​สวิตช์ ที่​เปลี่ยน​คำ​ตอบ

engineคือ
go · go-posixRE2 สอง​โหมด · โหมด​หลัง​เลือก​ทาง​ที่​ยาว​ที่สุด​ตาม​แบบ POSIX
java · java-unicodeต่าง​กัน​ที่​ธง UNICODE_CHARACTER_CLASS
python · python-asciiต่าง​กัน​ที่​ธง re.ASCII
javascript · javascript-unicodeต่าง​กัน​ที่​ธง u
วัด​จาก​อะไร

ทุก​ค่า​ใน​หน้า​นี้​มา​จาก scripts/regex-engines/spike.py ที่​รัน engine ตัว​จริง​ทั้ง​สี่ runtime · go version go1.27.0 darwin/arm64 · openjdk version "25.0.4.1" 2026-08-18 · Python 3.14.6 · Node v24.19.0

หน่วย​ของ​หลัก​ฐาน​ทั้ง​หน้า​คือ​ค่า boolean ว่า match ไหม สตริง​ที่ match ได้ และ​คำ​ว่า​ปฏิเสธ​ตอน compile ไม่มี​หน่วย​เวลา​สัก​ช่อง เครื่อง​ช้า​จึง​เปลี่ยน​คำ​ตอบ​ไม่​ได้

probe ไม่​แตะ​เครือ​ข่าย​เลย · runner ของ Go ตั้ง GOPROXY=off และ​ทุก runner ใช้ standard library ล้วน

ด่าน​ที่​เขียน​แบบ​นั้น ไม่​ได้​จับ​พลาด​บาง​ครั้ง มัน​จับ​ไม่​ได้​เลย​ตลอด​กาล

engine\bแมว\b ใน ฉันมีแมวสองตัว\bแมว\b ใน I have แมว here
Goไม่ matchไม่ match
Javaไม่ matchไม่ match
Java เปิด UNICODE_CHARACTER_CLASSไม่ matchแมว
Pythonไม่ matchแมว
JavaScriptไม่ matchไม่ match

ช่อง​ซ้าย​คือ​ข้อความ​ไทย​ล้วน และ​ไม่มี​ช่อง​ไหน match เลย

เหตุผล​คือ \b ไม่​ได้​แปล​ว่า “ขอบ​ของ​คำ” มัน​แปล​ว่า รอย​ต่อ​ระหว่าง​อักขระ​ที่​นับ​เป็น \w กับ​อักขระ​ที่​ไม่​นับ · ใน​ข้อความ​ไทย​ล้วน อักขระ​สอง​ข้าง​เป็น​ชนิด​เดียวกัน​เสมอ ไม่​ว่า​จะ​ชนิด​ไหน จึง​ไม่มี​รอย​ต่อ​ให้ \b เกาะ

ช่อง​ขวา​มี​ช่อง​ว่าง​ขนาบ และ​ตรง​นี้​คำ​ตอบ​แยก​ออก​เป็น​สอง​ฝั่ง · engine ที่ \w ครอบ​อักษร​ไทย เห็น​รอย​ต่อ​ระหว่าง​ช่อง​ว่าง​กับ แ ส่วน engine ที่ \w เป็น ASCII ล้วน เห็น​ว่า​ทั้ง​สอง​ข้าง ไม่ใช่ \w เหมือน​กัน จึง​ยัง​ไม่มี​รอย​ต่อ

รูป​ร่าง​ของ​บั๊ก​นี้ คือ​ด่าน​ที่​เขียว​ตลอด

\bคำไทย\b ไม่​ได้​ผิด​แบบ​ที่​ทำให้​ผล​เพี้ยน มัน​ผิด​แบบ​ที่​ทำให้​ผล​เป็น​ศูนย์​เสมอ

ด่าน​ที่​กวาด​หา​อะไร​สัก​อย่าง​แล้ว​เจอ​ศูนย์​ครั้ง อ่าน​เหมือน​ด่าน​ที่​ผ่าน

คำ​ละติน​ที่​อยู่​กลาง​ข้อความ​ไทย กลับ​ตรง​ข้าม

หัวข้อ​ที่​มีชื่อ​ว่า “คำ​ละติน​ที่​อยู่​กลาง​ข้อความ​ไทย กลับ​ตรง​ข้าม”

\bcat\b ใน แมวcatหมา · ธง​ตัว​เดียว​พลิก​คำ​ตอบ และ​มัน​พลิก​คนละ​ทาง​กับ​ที่​คน​คิด

engineผล
Gocat
Javacat
Java เปิด UNICODE_CHARACTER_CLASSไม่ match
Pythonไม่ match
Python เปิด re.ASCIIcat
JavaScriptcat

ที่​นี่ engine ที่ \w เป็น ASCII ล้วน match ได้ เพราะ​อักษร​ไทย​สอง​ข้าง​นับ​เป็น อักขระ​นอก​คำ จึง​เกิด​รอย​ต่อ​พอดี

ส่วน engine ที่ \w ครอบ​อักษร​ไทย match ไม่​ได้ เพราะ แ กับ c เป็น \w ทั้ง​คู่ มัน​จึง​เป็น​คำ​เดียวกัน​ใน​สายตา​ของ engine

สอง​หัวข้อ​นี้​กลับ​ทิศ​กัน​พอดี ธง​ที่​ทำให้ \b ทำงาน​กับ​คำ​ไทย​ได้ คือ​ธง​ที่​ทำให้​มัน​เลิก​ทำงาน กับ​คำ​ละติน​ที่​อยู่​ใน​ข้อความ​ไทย

อักษร ๕ เป็น​เลข​ห้า​ของ​ไทย · engine กี่​ตัว​นับ​ว่า​มัน​เป็น​เลข

engine\d บน ๕\d บน 5
Goไม่ match5
Javaไม่ match5
Java เปิด UNICODE_CHARACTER_CLASS๕5
Python๕5
JavaScriptไม่ match5

2 จาก 8 ช่อง​นับ​ว่า​เป็น​เลข

ผล​ที่​ตก​กับ code ที่​ตรวจ​ข้อมูล​นำ​เข้า คือ ^\d+$ ยอมรับ​หรือ​ปฏิเสธ​เลข​ไทย ขึ้น​กับ​ว่า​ใคร​รัน​มัน ไม่​ได้​ขึ้น​กับ​ว่า​กฎ​ของ​ระบบ​เขียน​ไว้​ว่า​อะไร

ทาง​เลือก​ซ้าย​สุด​ที่​สั้น​กว่า ชนะ​เกือบ​ทุก​ที่

หัวข้อ​ที่​มีชื่อ​ว่า “ทาง​เลือก​ซ้าย​สุด​ที่​สั้น​กว่า ชนะ​เกือบ​ทุก​ที่”

a|ab บน ab · engine ตัว​เดียว​ที่​ตอบ​ว่า ab

engineผล
Goa
Go โหมด POSIXab
Javaa
Pythona
JavaScripta

Go มี constructor สอง​ตัว​ที่​ให้​คำ​ตอบ​คนละ​อย่าง​บน pattern เดียวกัน · ตัว​ปกติ​เลือก​ทาง​ซ้าย​สุด​ที่ match ได้ ส่วน​โหมด POSIX เลือก​ทาง​ที่​ยาว​ที่สุด

นี่​คือ engine เดียวกัน ต่าง​กัน​ที่​ชื่อ​ฟังก์ชัน​ที่​คุณ​เรียก

backreference · lookahead · lookbehind

patternGoJavaPythonJavaScript
(a+)\1ปฏิเสธ​ตอน compileaaaaaa
foo(?=bar)ปฏิเสธ​ตอน compilefoofoofoo
(?<=foo)barปฏิเสธ​ตอน compilebarbarbar

Go ปฏิเสธ​ทั้ง​สาม​ตั้งแต่ compile ไม่ใช่​ตอบ​ผิด​ตอน​รัน

นี่​คือ​ข้อ​แลก​เปลี่ยน​ที่ RE2 ประกาศ​ไว้​เอง · ตัด​สาม​อย่าง​นี้​ทิ้ง แล้ว​รับ​ประกัน​ได้​ว่า​เวลา​ที่​ใช้ โต​เป็น​เส้น​ตรง​ตาม​ความ​ยาว​ของ​ข้อความ

ข้อความ error ตั้งแต่ compile คือ​ของดี มัน​เกิด​ตอน build ไม่ใช่​ตอน​ที่​ผู้​ใช้​ส่ง ข้อความ​ที่​ทำให้ pattern ระเบิด

ชื่อ​กลุ่ม​มี​สอง​แบบ และ​ธง​แบบ​ฝัง​ใน​ตัว pattern มี​ที่​ที่​ใช้​ไม่​ได้

patternGoJavaPythonJavaScript
(?P<n>a)aปฏิเสธ​ตอน compileaปฏิเสธ​ตอน compile
(?<n>a)aaปฏิเสธ​ตอน compilea
(?i)k บน​อักษร KelvinKKKปฏิเสธ​ตอน compile

แถว​สุดท้าย​คือ​แถว​ที่​ทำให้ pattern ที่​คัด​ลอก​มา​จาก​คำ​ตอบ​บน​เว็บ พัง​ทันที​ใน JavaScript · ธง​แบบ​ฝัง​ใน​ตัว pattern เป็น​ไวยากรณ์​ที่ JavaScript ไม่รู้​จัก​เลย มัน​ต้อง​ส่ง​ธง​แยก​ต่าง​หาก

15 ทรง คูณ 8 engine · ทรง​ที่​ทุก​ตัว​ตอบ​ตรง​กัน​มี 1 ทรง

ทรงGoGo POSIXJavaJava +UPythonPython ASCIIJSJS +u
alternationaabaaaaaa
backrefปฏิเสธ​ตอน compileปฏิเสธ​ตอน compileaaaaaaaaaaaa
lookaheadปฏิเสธ​ตอน compileปฏิเสธ​ตอน compilefoofoofoofoofoofoo
lookbehindปฏิเสธ​ตอน compileปฏิเสธ​ตอน compilebarbarbarbarbarbar
dollarBeforeNewlineไม่ matchabcabcabcabcabcไม่ matchไม่ match
caretNoMultilineไม่ matchbไม่ matchไม่ matchไม่ matchไม่ matchไม่ matchไม่ match
dotNewlineไม่ matchไม่ matchไม่ matchไม่ matchไม่ matchไม่ matchไม่ matchไม่ match
latinWordInThaicatปฏิเสธ​ตอน compilecatไม่ matchไม่ matchcatcatcat
thaiWordInThaiไม่ matchปฏิเสธ​ตอน compileไม่ matchไม่ matchไม่ matchไม่ matchไม่ matchไม่ match
thaiWordSpacedไม่ matchปฏิเสธ​ตอน compileไม่ matchแมวแมวไม่ matchไม่ matchไม่ match
namedGoStyleaปฏิเสธ​ตอน compileปฏิเสธ​ตอน compileปฏิเสธ​ตอน compileaaปฏิเสธ​ตอน compileปฏิเสธ​ตอน compile
namedJavaStyleaปฏิเสธ​ตอน compileaaปฏิเสธ​ตอน compileปฏิเสธ​ตอน compileaa
thaiDigitไม่ matchปฏิเสธ​ตอน compileไม่ match๕๕ไม่ matchไม่ matchไม่ match
asciiDigit5ปฏิเสธ​ตอน compile555555
kelvinFoldKปฏิเสธ​ตอน compileKKKKปฏิเสธ​ตอน compileปฏิเสธ​ตอน compile
สิ่ง​ที่​ควร​จำ​จาก​หน้า​นี้

หนึ่ง \b ไม่​ได้​แปล​ว่า​ขอบ​ของ​คำ มัน​แปล​ว่า​รอย​ต่อ​ระหว่าง \w กับ​สิ่ง​ที่​ไม่ใช่ \w

สอง \bคำไทย\b ใน​ข้อความ​ไทย​ล้วน ไม่ match ใน​ทุก engine ที่​วัด ด่าน​ที่​เขียน​แบบ​นั้น​จึง​แดง​ไม่​ได้

สาม ธง​ที่​ทำให้ \w ครอบ​อักษร​ไทย ช่วย​เรื่อง​คำ​ไทย และ​ทำลาย​เรื่อง​คำ​ละติน​ที่​อยู่ ใน​ข้อความ​ไทย · เลือก​ได้​อย่าง​เดียว

สี่ ก่อน​คัด​ลอก pattern จาก​ที่​อื่น​มา​ใช้ ให้​ถาม​ก่อน​ว่า​มัน​เขียน​ขึ้น​สำหรับ engine ตัว​ไหน

เช็กความเข้าใจ

ข้อ 1 / 3

เขียนด่านที่หา \bแคตาล็อก\b ใน file บทเรียนภาษาไทย แล้วมันไม่เคยแดงเลย อธิบายข้อไหนถูก

ทุก​ค่า​ใน​หน้า​นี้​สร้าง​ใหม่​ได้​จาก​คำ​สั่ง​เดียว

Terminal window
npm run verify:regex-engines

ไม่​ต้อง​ติด​ตั้ง​อะไร​เพิ่ม ถ้า​เครื่อง​มี Go, JDK, Node และ Python อยู่​แล้ว · probe ไม่​แตะ​เครือ​ข่าย​และ​ไม่​ต้อง​ใช้ Docker ทุก runner ใช้ standard library ล้วน

จะ​เพิ่ม​ทรง​ใหม่ ให้​เติม​ลง​ใน scripts/regex-engines/shapes.json ใบ​เดียว · runner ทั้ง​สี่​อ่าน​จาก​ใบ​นั้น การ​เติม​ที่​เดียว​จึง​ได้​ครบ​ทุก engine พร้อม​กัน

รหัส​ออก 1 แปล​ว่า​ข้อ​อ้าง​ใน​หน้า​นี้​ผิด ส่วน​รหัส​ออก 2 แปล​ว่า​วัด​ไม่​ได้ สอง​อย่าง​นี้​ไม่​เหมือน​กัน