मैं वर्तमान में जावासीसी के साथ JavaScript/ECMAScript 5.1 parser विकसित कर रहा हूं। RegularExpressionLiteral और Automatic Semicolon Insertion दो चीजें हैं जो मुझे ईसीएमएस्क्रिप्ट व्याकरण में पागल बनाती हैं। रेगेक्स प्रश्न के लिए यह प्रश्न और उत्तर अमूल्य थे। इस जवाब में मैं अपने स्वयं के निष्कर्ष एक साथ रखना चाहता हूं।
टीएल; डीआर जावासीसी में, lexical states और switch them from the parser का उपयोग करें।
बहुत महत्वपूर्ण क्या थॉम ब्लेक ने लिखा है:
विभाजन ऑपरेटर एक अभिव्यक्ति का पालन करें, और चाहिए एक नियमित अभिव्यक्ति शाब्दिक अभिव्यक्ति का पालन नहीं कर सकते हैं, अन्य सभी मामलों में तो आप कर सकते हैं सुरक्षित रूप से मान लें कि आप एक नियमित अभिव्यक्ति शाब्दिक देख रहे हैं।
तो आप वास्तव में अगर यह एक अभिव्यक्ति है या नहीं पहले था समझने की जरूरत है। यह पार्सर में छोटा है लेकिन लेक्सर में बहुत मुश्किल है।
थॉम pointed out के रूप में, कई (लेकिन, दुर्भाग्यवश, सभी नहीं) मामलों में आप समझ सकते हैं कि यह अंतिम टोकन पर "दिखने" द्वारा अभिव्यक्ति थी। आपको विराम चिह्नियों के साथ-साथ कीवर्ड पर विचार करना होगा।
चलिए कीवर्ड से शुरू करते हैं।
case
delete
do
else
in
instanceof
new
return
throw
typeof
void
इसके बाद, punctuators: निम्नलिखित कीवर्ड को एक DivPunctuator
(उदाहरण के लिए, आप case /5
नहीं हो सकता है), इसलिए यदि आप इन के बाद एक /
देखते हैं, आप एक RegularExpressionLiteral
है पहले नहीं हो सकती। निम्नलिखित punctuators एक DivPunctuator
(पूर्व { /a...
में प्रतीक /
एक प्रभाग शुरू नहीं कर सकते हैं।) पूर्व में होना नहीं कर सकते: एक DivPunctuator
{ ( [
. ; , < > <=
>= == != === !==
+ - * %
<< >> >>> & | ^
! ~ && || ? :
= += -= *= %= <<=
>>= >>>= &= |= ^=
/=
तो अगर आप इन में से एक है और /...
इस के बाद देखते हैं, तो यह कभी नहीं हो सकता और इसलिए RegularExpressionLiteral
होना चाहिए।
इसके बाद, यदि आपके पास: यह भी एक RegularExpressionLiteral
होना चाहिए
/
और /...
के बाद कि। यदि इन स्लेश के बीच कोई जगह नहीं थी (यानी // ...
), तो इसे SingleLineComment
("अधिकतम मर्च") के रूप में संभाला जाना चाहिए।
]
तो निम्नलिखित /
एक DivPunctuator
शुरू करनी चाहिए:
इसके बाद, निम्न punctuator केवल एक अभिव्यक्ति खत्म हो सकता है।
अब हम निम्नलिखित शेष मामलों जो कर रहे हैं, दुर्भाग्य से, अस्पष्ट है:
}
)
++
--
}
के लिए और )
आप को पता है कि अगर वे एक अभिव्यक्ति है या नहीं, अंत ++
और --
के लिए है - वे एक PostfixExpression
अंत या UnaryExpression
शुरू करें।
और मैं इस निष्कर्ष पर आया हूं कि लेक्सर में पता लगाने के लिए यह बहुत कठिन है (यदि असंभव नहीं है)। आपको कुछ उदाहरण देने के लिए, कुछ उदाहरण।
इस उदाहरण में:
{}/a/g
/a/g
एक RegularExpressionLiteral
है, लेकिन इस एक में:
+{}/a/g
/a/g
एक प्रभाग है।
)
के मामले में आप एक प्रभाग हो सकता है:
('a')/a/g
रूप में अच्छी तरह RegularExpressionLiteral
एक के रूप में:
if ('a')/a/g
तो, दुर्भाग्य से, ऐसा लगता है कि आप इसे lexer साथ समाधान नहीं कर सकते अकेला। या आपको लेक्सर में इतना व्याकरण लाना होगा ताकि अब कोई लेक्सर न हो।
यह एक समस्या है।
अब, मेरे मामले में जावासीसी-आधारित एक संभावित समाधान है।
मुझे यकीन है कि यदि आप अन्य पार्सर जनरेटर में इसी तरह की सुविधाएँ नहीं हूँ, लेकिन JavaCC एक lexical states सुविधा है जो के बीच "हम एक DivPunctuator
उम्मीद" और "हम उम्मीद करते हैं एक RegularExpressionLiteral
" राज्यों स्विच करने के लिए इस्तेमाल किया जा सकता है। उदाहरण के लिए, this grammar में NOREGEXP
राज्य का अर्थ है "हम यहां RegularExpressionLiteral
की अपेक्षा नहीं करते हैं"।
यह समस्या का हिस्सा हल करती है, लेकिन नहीं अस्पष्ट )
, }
, ++
और --
।
इसके लिए, आपको पार्सर से लेक्सिकल राज्यों को स्विच करने में सक्षम होना होगा। यह संभव है, JavaCC FAQ में निम्नलिखित प्रश्न देखें:
Can the parser force a switch to a new lexical state?
हाँ, पर यह ऐसा करके कीड़े पैदा करने के लिए बहुत आसान है।
एक अग्रदर्शी पार्सर पहले से ही टोकन धारा में बहुत दूर चले गए हैं सकता है (अर्थात पहले से ही /
एक DIV
या ठीक इसके विपरीत के रूप में पढ़ें)।
Is there a way to make SwitchTo safer?
विचार एक "बैकअप" टोकन धारा बनाने के लिए और अग्रदर्शी दौरान पढ़ा टोकन पुश करने के लिए है:
सौभाग्य से वहाँ शाब्दिक राज्यों थोड़ा सुरक्षित स्विचन बनाने के लिए एक तरह से हो रहा है फिर से वापस।
मुझे लगता है कि इस बात के लिए काम करना चाहिए }
, )
, ++
, --
के रूप में वे सामान्य रूप से अग्रावलोकन (1) स्थितियों में पाए जाते हैं, लेकिन मैं 100% है कि के बारे में सुनिश्चित नहीं हूँ।सबसे बुरे मामले में लेक्सर ने /
-RegularExpressionLiteral
के रूप में टोकन को प्रारंभ करने की कोशिश की हो सकती है और विफल रही क्योंकि इसे /
द्वारा समाप्त नहीं किया गया था।
किसी भी मामले में, मुझे ऐसा करने का कोई बेहतर तरीका नहीं दिखता है। अगली अच्छी बात शायद मामले को पूरी तरह से छोड़ने के लिए होगी (जैसे JSLint
और कई अन्य ने किया), दस्तावेज़ और इन प्रकार के अभिव्यक्तियों को पार्स न करें। {}/a/g
वैसे भी ज्यादा समझ में नहीं आता है।
... डिवीजन-असाइनमेंट ऑपरेटर '/ = ' –
मुझे लगता है कि, spec पढ़ने से मुझे लगता है कि * पार्सर * को यह जानने की जरूरत है कि किस प्रकार का टोकन लाया जाए। यह एक भयानक व्याकरण सुविधा की तरह लगता है, लेकिन जो भी हो। यह भी बहुत अजीब लगता है, क्योंकि अभिव्यक्ति को पार्स करते समय व्याकरण को उन दोनों में से एक को कोशिश करना होता है, * और * एक और "सामान्य" टोकन के लिए अधिक "सामान्य" अनुरोध। Ick। अगर मुझे इसका सामना करना पड़ा तो मुझे लगता है कि मैं वापस जाऊंगा और व्याकरण को ठीक कर दूंगा :-) – Pointy
@ पॉइंटी मेरी समझ से, पार्सर दोनों टोकन की कोशिश करता है और चूंकि कोई संदर्भ नहीं है जहां दोनों मान्य हैं, यह उस व्यक्ति का उपयोग करता है दिए गए संदर्भ में मान्य है। –