ตัวแยกวิเคราะห์ปกติตามที่สอนโดยทั่วไปแล้วจะมีขั้นตอนเล็ก ๆ lexer (เช่น "สแกนเนอร์" หรือ "tokenizer") สับอินพุตเป็นโทเค็นขนาดเล็กที่ใส่คำอธิบายประกอบด้วยชนิด สิ่งนี้อนุญาตให้ parser หลักใช้โทเค็นเป็นองค์ประกอบเทอร์มินัลแทนที่จะต้องปฏิบัติต่อตัวละครแต่ละตัวเป็นเทอร์มินัลซึ่งนำไปสู่การเพิ่มประสิทธิภาพที่เห็นได้ชัดเจน โดยเฉพาะอย่างยิ่ง lexer ยังสามารถลบความคิดเห็นและพื้นที่สีขาวทั้งหมด อย่างไรก็ตามขั้นตอน tokenizer ที่แยกต่างหากหมายความว่าไม่สามารถใช้คำหลักเป็นตัวระบุได้ (ยกเว้นภาษาที่รองรับการลูบซึ่งค่อนข้างไม่เป็นที่นิยมหรือนำหน้าตัวระบุทั้งหมดที่มีเครื่องหมายคล้าย$foo)
ทำไม? สมมติว่าเรามีโทเค็นง่าย ๆ ที่เข้าใจโทเค็นต่อไปนี้:
FOR = 'for'
LPAREN = '('
RPAREN = ')'
IN = 'in'
IDENT = /\w+/
COLON = ':'
SEMICOLON = ';'
โทเค็นไนเซอร์จะตรงกับโทเค็นที่ยาวที่สุดเสมอและต้องการคำหลักมากกว่าตัวระบุ ดังนั้นinterestingจะเป็น lexed IDENT:interestingแต่inจะเป็น lexed ไม่เคยเป็นIN IDENT:interestingข้อมูลโค้ดชอบ
for(var in expression)
จะถูกแปลเป็นโทเค็นสตรีม
FOR LPAREN IDENT:var IN IDENT:expression RPAREN
จนถึงตอนนี้ก็ใช้งานได้แล้ว แต่ตัวแปรใด ๆ ที่inจะถูกเรียกเป็นคำหลักINแทนที่จะเป็นตัวแปรซึ่งจะทำลายรหัส lexer ไม่รักษาสถานะใด ๆ ระหว่างโทเค็นและไม่สามารถรู้ได้ว่าinควรเป็นตัวแปรยกเว้นเมื่อเราอยู่ในลูป นอกจากนี้รหัสต่อไปนี้ควรถูกกฎหมาย:
for(in in expression)
ครั้งแรกinจะเป็นตัวระบุที่สองจะเป็นคำหลัก
มีสองปฏิกิริยาต่อปัญหานี้:
คำหลักตามบริบททำให้เกิดความสับสนลองนำคำหลักมาใช้แทน
Java มีคำที่สงวนไว้มากมายซึ่งบางคำไม่มีประโยชน์ยกเว้นการให้ข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์กับโปรแกรมเมอร์ที่เปลี่ยนมาใช้ Java จาก C ++ การเพิ่มรหัสแบ่งคำค้นหาใหม่ การเพิ่มคำหลักตามบริบททำให้ผู้อ่านเข้าใจรหัสเว้นแต่จะมีการเน้นไวยากรณ์ที่ดีและทำให้การใช้เครื่องมือทำได้ยากเพราะจะต้องใช้เทคนิคการแยกวิเคราะห์ขั้นสูง (ดูด้านล่าง)
เมื่อเราต้องการขยายภาษาวิธีการที่มีเหตุผลเดียวคือใช้สัญลักษณ์ที่ก่อนหน้านี้ไม่ถูกกฎหมายในภาษา โดยเฉพาะอย่างยิ่งสิ่งเหล่านี้ไม่สามารถระบุได้ ด้วยไวยากรณ์วนลูป foreach, Java นำ:คำสำคัญที่มีอยู่กลับมาใช้ใหม่ด้วยความหมายใหม่ ด้วย lambdas, Java ได้เพิ่ม->คำหลักที่ไม่สามารถเกิดขึ้นก่อนหน้านี้ในโปรแกรมทางกฎหมายใด ๆ ( -->จะยังคงเป็น lexed ตาม'--' '>'ที่ถูกกฎหมายและ->อาจถูก lexed ก่อนหน้านี้เป็น'-', '>'แต่ลำดับนั้นจะถูกปฏิเสธโดย parser)
คำหลักตามบริบทลดความซับซ้อนของภาษามาใช้พวกเขา
Lexers มีประโยชน์อย่างแน่นอน แต่แทนที่จะเรียกใช้ lexer ก่อน parser เราสามารถเรียกใช้พวกมันควบคู่กับ parser ตัวแยกวิเคราะห์จากด้านล่างจะทราบชุดของประเภทโทเค็นที่ยอมรับได้ในตำแหน่งที่กำหนดเสมอ parser จากนั้นสามารถร้องขอ lexer ให้ตรงกับประเภทใด ๆ เหล่านี้ที่ตำแหน่งปัจจุบัน ในแต่ละลูป parser จะอยู่ในตำแหน่งที่แสดงโดย·ในไวยากรณ์ (ง่าย) หลังจากที่ตัวแปรพบ:
for_loop = for_loop_cstyle | for_each_loop
for_loop_cstyle = 'for' '(' declaration · ';' expression ';' expression ')'
for_each_loop = 'for' '(' declaration · 'in' expression ')'
ที่ตำแหน่งนั้นโทเค็นที่ถูกกฎหมายจะมีSEMICOLONหรือINไม่มีIDENTก็ได้ คำหลักinจะไม่คลุมเครือทั้งหมด
ในตัวอย่างนี้ตัวแยกวิเคราะห์จากบนลงล่างจะไม่มีปัญหาเช่นกันเนื่องจากเราสามารถเขียนไวยากรณ์ข้างต้นเป็น
for_loop = 'for' '(' declaration · for_loop_rest ')'
for_loop_rest = · ';' expression ';' expression
for_loop_rest = · 'in' expression
และโทเค็นทั้งหมดที่จำเป็นสำหรับการตัดสินใจสามารถมองเห็นได้โดยไม่ต้องย้อนรอย
พิจารณาการใช้งาน
Java มีแนวโน้มที่จะเน้นความเรียบง่ายของความหมายและความหมายประโยคเสมอ ตัวอย่างเช่นภาษาไม่รองรับการบรรทุกเกินพิกัดเนื่องจากจะทำให้โค้ดมีความซับซ้อนมากขึ้น ดังนั้นเมื่อตัดสินใจระหว่างinและ:สำหรับไวยากรณ์ลูปสำหรับแต่ละเราต้องพิจารณาว่าผู้ใช้มีความสับสนน้อยและชัดเจนมากขึ้น กรณีที่รุนแรงอาจจะเป็น
for (in in in in())
for (in in : in())
(หมายเหตุ: Java มีเนมสเปซแยกต่างหากสำหรับชื่อประเภทตัวแปรและวิธีการฉันคิดว่านี่เป็นข้อผิดพลาดส่วนใหญ่ไม่ได้หมายความว่าการออกแบบภาษาในภายหลังจะต้องเพิ่มความผิดพลาดมากขึ้น )
ทางเลือกใดให้การแยกภาพที่ชัดเจนยิ่งขึ้นระหว่างตัวแปรการทำซ้ำและคอลเลกชันที่ทำซ้ำ? ตัวเลือกใดที่สามารถจดจำได้เร็วขึ้นเมื่อคุณมองดูรหัส ฉันพบว่าสัญลักษณ์การแยกดีกว่าชุดคำเมื่อเทียบกับเกณฑ์เหล่านี้ ภาษาอื่นมีค่าแตกต่างกัน เช่น Python สะกดคำว่าโอเปอเรเตอร์หลายภาษาในภาษาอังกฤษเพื่อให้พวกเขาสามารถอ่านได้อย่างเป็นธรรมชาติและเข้าใจง่าย แต่คุณสมบัติเดียวกันเหล่านั้นทำให้ยากที่จะเข้าใจชิ้นส่วนของ Python ได้อย่างรวดเร็ว