pattern เดียวกัน ข้อความเดียวกัน แปด engine ตอบตรงกันแค่ทรงเดียวจากสิบห้าทรง
คุณเขียนด่านตัวหนึ่งที่หา \bแมว\b ในข้อความไทย
มันไม่เคยแดงเลยสักครั้ง คุณจึงคิดว่าไม่มีอะไรผิด
หน้านี้ยิง pattern 15 ทรง เข้า engine 8 ตัว รวม 120 ช่อง แล้วนับว่ามีกี่ทรงที่ทุก engine ตอบตรงกัน
คำตอบคือ 1 ทรง และมันคือทรงที่ไม่มีใคร match อะไรเลย
ธีสิสของหน้านี้อยู่ตรงนี้ — regex ไม่ใช่ภาษาเดียว มันคือชื่อเรียกรวมของหลายภาษา
ที่หน้าตาเหมือนกัน และภาษาที่คุณคิดในหัว มักไม่ใช่ภาษาที่ engine ตรงหน้าคุณพูด
รายการเดียวที่ทุก engine อ่าน
หัวข้อที่มีชื่อว่า “รายการเดียวที่ทุก engine อ่าน”ถ้าปล่อยให้แต่ละฝั่งพิมพ์ pattern เอง การเทียบจะไม่ได้เทียบอะไรเลย
scripts/regex-engines/shapes.json เก็บ pattern กับข้อความทั้ง 15 ทรงไว้ใบเดียว
· runner ของทั้งสี่ภาษาอ่านจากใบนั้น และไม่มี runner ตัวไหนมี pattern ของตัวเอง
engine ที่วัดมี 8 ตัว ซึ่งมาจากสี่ runtime · บางตัวนับสองเพราะมันมีสวิตช์ ที่เปลี่ยนคำตอบ
| engine | คือ |
|---|---|
go · go-posix | RE2 สองโหมด · โหมดหลังเลือกทางที่ยาวที่สุดตามแบบ 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 ล้วน
\b รอบคำไทย ไม่มี engine ไหน match เลยสักตัว
หัวข้อที่มีชื่อว่า “\b รอบคำไทย ไม่มี engine ไหน match เลยสักตัว”ด่านที่เขียนแบบนั้น ไม่ได้จับพลาดบางครั้ง มันจับไม่ได้เลยตลอดกาล
| 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 | ผล |
|---|---|
| Go | cat |
| Java | cat |
Java เปิด UNICODE_CHARACTER_CLASS | ไม่ match |
| Python | ไม่ match |
Python เปิด re.ASCII | cat |
| JavaScript | cat |
ที่นี่ engine ที่ \w เป็น ASCII ล้วน match ได้ เพราะอักษรไทยสองข้างนับเป็น
อักขระนอกคำ จึงเกิดรอยต่อพอดี
ส่วน engine ที่ \w ครอบอักษรไทย match ไม่ได้ เพราะ แ กับ c เป็น \w ทั้งคู่
มันจึงเป็นคำเดียวกันในสายตาของ engine
สองหัวข้อนี้กลับทิศกันพอดี ธงที่ทำให้ \b ทำงานกับคำไทยได้ คือธงที่ทำให้มันเลิกทำงาน
กับคำละตินที่อยู่ในข้อความไทย
\d ไม่ได้แปลว่าเลขศูนย์ถึงเก้าเสมอไป
หัวข้อที่มีชื่อว่า “\d ไม่ได้แปลว่าเลขศูนย์ถึงเก้าเสมอไป”อักษร
๕เป็นเลขห้าของไทย · engine กี่ตัวนับว่ามันเป็นเลข
| engine | \d บน ๕ | \d บน 5 |
|---|---|---|
| Go | ไม่ match | 5 |
| Java | ไม่ match | 5 |
Java เปิด UNICODE_CHARACTER_CLASS | ๕ | 5 |
| Python | ๕ | 5 |
| JavaScript | ไม่ match | 5 |
2 จาก 8 ช่องนับว่าเป็นเลข
ผลที่ตกกับ code ที่ตรวจข้อมูลนำเข้า คือ ^\d+$ ยอมรับหรือปฏิเสธเลขไทย
ขึ้นกับว่าใครรันมัน ไม่ได้ขึ้นกับว่ากฎของระบบเขียนไว้ว่าอะไร
ทางเลือกซ้ายสุดที่สั้นกว่า ชนะเกือบทุกที่
หัวข้อที่มีชื่อว่า “ทางเลือกซ้ายสุดที่สั้นกว่า ชนะเกือบทุกที่”
a|abบนab· engine ตัวเดียวที่ตอบว่าab
| engine | ผล |
|---|---|
| Go | a |
| Go โหมด POSIX | ab |
| Java | a |
| Python | a |
| JavaScript | a |
Go มี constructor สองตัวที่ให้คำตอบคนละอย่างบน pattern เดียวกัน · ตัวปกติเลือกทางซ้ายสุดที่ match ได้ ส่วนโหมด POSIX เลือกทางที่ยาวที่สุด
นี่คือ engine เดียวกัน ต่างกันที่ชื่อฟังก์ชันที่คุณเรียก
สิ่งที่ RE2 ไม่มี และมันบอกคุณตั้งแต่ compile
หัวข้อที่มีชื่อว่า “สิ่งที่ RE2 ไม่มี และมันบอกคุณตั้งแต่ compile”backreference · lookahead · lookbehind
| pattern | Go | Java | Python | JavaScript |
|---|---|---|---|---|
(a+)\1 | ปฏิเสธตอน compile | aa | aa | aa |
foo(?=bar) | ปฏิเสธตอน compile | foo | foo | foo |
(?<=foo)bar | ปฏิเสธตอน compile | bar | bar | bar |
Go ปฏิเสธทั้งสามตั้งแต่ compile ไม่ใช่ตอบผิดตอนรัน
นี่คือข้อแลกเปลี่ยนที่ RE2 ประกาศไว้เอง · ตัดสามอย่างนี้ทิ้ง แล้วรับประกันได้ว่าเวลาที่ใช้ โตเป็นเส้นตรงตามความยาวของข้อความ
ข้อความ error ตั้งแต่ compile คือของดี มันเกิดตอน build ไม่ใช่ตอนที่ผู้ใช้ส่ง ข้อความที่ทำให้ pattern ระเบิด
ไวยากรณ์ที่ไม่มีสำนักไหนใช้ได้ทุกที่
หัวข้อที่มีชื่อว่า “ไวยากรณ์ที่ไม่มีสำนักไหนใช้ได้ทุกที่”ชื่อกลุ่มมีสองแบบ และธงแบบฝังในตัว pattern มีที่ที่ใช้ไม่ได้
| pattern | Go | Java | Python | JavaScript |
|---|---|---|---|---|
(?P<n>a) | a | ปฏิเสธตอน compile | a | ปฏิเสธตอน compile |
(?<n>a) | a | a | ปฏิเสธตอน compile | a |
(?i)k บนอักษร Kelvin | K | K | K | ปฏิเสธตอน compile |
แถวสุดท้ายคือแถวที่ทำให้ pattern ที่คัดลอกมาจากคำตอบบนเว็บ พังทันทีใน JavaScript · ธงแบบฝังในตัว pattern เป็นไวยากรณ์ที่ JavaScript ไม่รู้จักเลย มันต้องส่งธงแยกต่างหาก
ตารางรวมทุกช่องที่วัด
หัวข้อที่มีชื่อว่า “ตารางรวมทุกช่องที่วัด”15 ทรง คูณ 8 engine · ทรงที่ทุกตัวตอบตรงกันมี 1 ทรง
| ทรง | Go | Go POSIX | Java | Java +U | Python | Python ASCII | JS | JS +u |
|---|---|---|---|---|---|---|---|---|
alternation | a | ab | a | a | a | a | a | a |
backref | ปฏิเสธตอน compile | ปฏิเสธตอน compile | aa | aa | aa | aa | aa | aa |
lookahead | ปฏิเสธตอน compile | ปฏิเสธตอน compile | foo | foo | foo | foo | foo | foo |
lookbehind | ปฏิเสธตอน compile | ปฏิเสธตอน compile | bar | bar | bar | bar | bar | bar |
dollarBeforeNewline | ไม่ match | abc | abc | abc | abc | abc | ไม่ match | ไม่ match |
caretNoMultiline | ไม่ match | b | ไม่ match | ไม่ match | ไม่ match | ไม่ match | ไม่ match | ไม่ match |
dotNewline | ไม่ match | ไม่ match | ไม่ match | ไม่ match | ไม่ match | ไม่ match | ไม่ match | ไม่ match |
latinWordInThai | cat | ปฏิเสธตอน compile | cat | ไม่ match | ไม่ match | cat | cat | cat |
thaiWordInThai | ไม่ match | ปฏิเสธตอน compile | ไม่ match | ไม่ match | ไม่ match | ไม่ match | ไม่ match | ไม่ match |
thaiWordSpaced | ไม่ match | ปฏิเสธตอน compile | ไม่ match | แมว | แมว | ไม่ match | ไม่ match | ไม่ match |
namedGoStyle | a | ปฏิเสธตอน compile | ปฏิเสธตอน compile | ปฏิเสธตอน compile | a | a | ปฏิเสธตอน compile | ปฏิเสธตอน compile |
namedJavaStyle | a | ปฏิเสธตอน compile | a | a | ปฏิเสธตอน compile | ปฏิเสธตอน compile | a | a |
thaiDigit | ไม่ match | ปฏิเสธตอน compile | ไม่ match | ๕ | ๕ | ไม่ match | ไม่ match | ไม่ match |
asciiDigit | 5 | ปฏิเสธตอน compile | 5 | 5 | 5 | 5 | 5 | 5 |
kelvinFold | K | ปฏิเสธตอน compile | K | K | K | K | ปฏิเสธตอน compile | ปฏิเสธตอน compile |
หนึ่ง \b ไม่ได้แปลว่าขอบของคำ มันแปลว่ารอยต่อระหว่าง \w กับสิ่งที่ไม่ใช่ \w
สอง \bคำไทย\b ในข้อความไทยล้วน ไม่ match ในทุก engine ที่วัด
ด่านที่เขียนแบบนั้นจึงแดงไม่ได้
สาม ธงที่ทำให้ \w ครอบอักษรไทย ช่วยเรื่องคำไทย และทำลายเรื่องคำละตินที่อยู่
ในข้อความไทย · เลือกได้อย่างเดียว
สี่ ก่อนคัดลอก pattern จากที่อื่นมาใช้ ให้ถามก่อนว่ามันเขียนขึ้นสำหรับ engine ตัวไหน
เช็กความเข้าใจ
ข้อ 1 / 3เขียนด่านที่หา \bแคตาล็อก\b ใน file บทเรียนภาษาไทย แล้วมันไม่เคยแดงเลย อธิบายข้อไหนถูก
วิธีวัดซ้ำ
หัวข้อที่มีชื่อว่า “วิธีวัดซ้ำ”ทุกค่าในหน้านี้สร้างใหม่ได้จากคำสั่งเดียว
npm run verify:regex-enginesไม่ต้องติดตั้งอะไรเพิ่ม ถ้าเครื่องมี Go, JDK, Node และ Python อยู่แล้ว · probe ไม่แตะเครือข่ายและไม่ต้องใช้ Docker ทุก runner ใช้ standard library ล้วน
จะเพิ่มทรงใหม่ ให้เติมลงใน scripts/regex-engines/shapes.json ใบเดียว
· runner ทั้งสี่อ่านจากใบนั้น การเติมที่เดียวจึงได้ครบทุก engine พร้อมกัน
รหัสออก 1 แปลว่าข้ออ้างในหน้านี้ผิด ส่วนรหัสออก 2 แปลว่าวัดไม่ได้ สองอย่างนี้ไม่เหมือนกัน