2011-04-01 11 views
24

जावास्क्रिप्ट में पार्स करने के लिए एक मुश्किल व्याकरण है। फॉरवर्ड-स्लैश का अर्थ कई अलग-अलग चीजों से हो सकता है: डिवीजन ऑपरेटर, नियमित अभिव्यक्ति शाब्दिक, टिप्पणी परिचयकर्ता, या लाइन-टिप्पणी परिचयकर्ता। आखिरी दो अंतर करने में आसान हैं: यदि स्लैश एक स्टार द्वारा पीछा किया जाता है, तो यह एक बहुभाषी टिप्पणी शुरू करता है। यदि स्लैश का पीछा किसी अन्य स्लैश द्वारा किया जाता है, तो यह एक पंक्ति-टिप्पणी है।जावास्क्रिप्ट को पार्स करते समय, स्लैश का अर्थ क्या निर्धारित करता है?

लेकिन डिवींबिगेटिंग डिवीजन और रेगेक्स शाब्दिक के नियम मुझे बच रहे हैं। मैं इसे ECMAScript standard में नहीं ढूंढ सकता। स्लेश का मतलब क्या होगा, इस पर निर्भर करता है कि लेक्सिकल व्याकरण स्पष्ट रूप से दो हिस्सों में विभाजित होता है, इनपुट एलीमेंटडिव और इनपुट एलेमेंट रेगएक्सपी। लेकिन इसका उपयोग करने के लिए कुछ भी नहीं समझाता है।

और निश्चित रूप से डरावनी अर्धविराम सम्मिलन नियम सबकुछ जटिल करते हैं।

क्या किसी के पास जावास्क्रिप्ट को लेक्स करने के लिए स्पष्ट कोड का उदाहरण है जिसका उत्तर है?

+0

... डिवीजन-असाइनमेंट ऑपरेटर '/ = ' –

+0

मुझे लगता है कि, spec पढ़ने से मुझे लगता है कि * पार्सर * को यह जानने की जरूरत है कि किस प्रकार का टोकन लाया जाए। यह एक भयानक व्याकरण सुविधा की तरह लगता है, लेकिन जो भी हो। यह भी बहुत अजीब लगता है, क्योंकि अभिव्यक्ति को पार्स करते समय व्याकरण को उन दोनों में से एक को कोशिश करना होता है, * और * एक और "सामान्य" टोकन के लिए अधिक "सामान्य" अनुरोध। Ick। अगर मुझे इसका सामना करना पड़ा तो मुझे लगता है कि मैं वापस जाऊंगा और व्याकरण को ठीक कर दूंगा :-) – Pointy

+0

@ पॉइंटी मेरी समझ से, पार्सर दोनों टोकन की कोशिश करता है और चूंकि कोई संदर्भ नहीं है जहां दोनों मान्य हैं, यह उस व्यक्ति का उपयोग करता है दिए गए संदर्भ में मान्य है। –

उत्तर

3

देखें धारा 7:

शाब्दिक व्याकरण के लिए दो लक्ष्य प्रतीकों कर रहे हैं। InputElementDiv प्रतीक उन वाक्य रचनात्मक व्याकरण संदर्भों में उपयोग किया जाता है जहां एक प्रमुख विभाजन (/) या विभाजन-असाइनमेंट (/ =) ऑपरेटर की अनुमति है। InputElementRegExp प्रतीक अन्य वाक्य रचनात्मक व्याकरण संदर्भों में प्रयोग किया जाता है।

नोट कोई वाक्य रचनात्मक व्याकरण संदर्भ नहीं हैं जहां एक प्रमुख विभाजन या विभाजन-असाइनमेंट, और एक प्रमुख नियमित एक्सप्लोरेशन लिटरल की अनुमति है। यह अर्धविराम सम्मिलन से प्रभावित नहीं है (7.9 देखें); इस तरह के के रूप में उदाहरण में निम्नलिखित:

a = b 
/hi/g.exec(c).map(d); 

जहां पहले गैर-सफ़ेद, एक LineTerminator के बाद गैर टिप्पणी चरित्र स्लैश (/) और वाक्यात्मक संदर्भ विभाजन या विभाजन-असाइनमेंट की अनुमति देता है, कोई अर्धविराम पर डाला जाता है लाइन टर्मिनेटर। यही कारण है, ऊपर के उदाहरण के रूप में एक ही तरह से में व्याख्या की है है:

a = b/hi/g.exec(c).map(d); 

मैं मानता हूँ, यह भ्रामक है और नहीं बल्कि दो एक से शीर्ष स्तर के व्याकरण अभिव्यक्ति होना चाहिए।


संपादित करें:

लेकिन वहाँ समझा जब जो उपयोग करने के लिए कुछ भी नहीं है।

शायद सरल उत्तर हमें चेहरे पर देख रहा है: एक कोशिश करें और फिर दूसरे को आजमाएं। चूंकि उन्हें दोनों की अनुमति नहीं है, इसलिए अधिकांश में एक त्रुटि मुक्त मैच मिलेगा।

+1

ओपी के प्रश्न से: * "लेकिन इसका उपयोग करने के लिए कुछ भी नहीं समझाता है।" * - I लगता है कि यह इस सवाल का मुख्य मुद्दा है। क्या आप इसे संबोधित कर सकते हैं? –

+0

हालांकि आपका उद्धरण बताता है कि कोई संदर्भ नहीं है जहां दोनों की अनुमति है ... –

+0

मैंने इस भाग को पढ़ा है। यह कहता है कि कोई ओवरलैप नहीं है, लेकिन यह नहीं कहता कि एक दूसरे को चुनने के लिए कब। –

4

आप केवल सिंटैक्स पार्सर को कार्यान्वित करने के तरीके को समझने के तरीके के बारे में जान सकते हैं। एक वैध पार्स पर जो भी लेक्स पथ आता है यह निर्धारित करता है कि चरित्र की व्याख्या कैसे करें। जाहिर है, यह कुछ ऐसा है जिसे उन्होंने फिक्सिंग माना था, लेकिन नहीं किया। अधिक यहाँ पढ़ने: http://www-archive.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions

+2

वाह आकर्षक पढ़ने, धन्यवाद। – Pointy

+1

स्लैश के अर्थ को निर्धारित करने के लिए पिछले टोकन का उपयोग करके उस पृष्ठ में एक काफी सरल नियम है। लेकिन यह एक जेएस 2.0 नियम है, इसलिए यह वर्तमान कोड पर लागू नहीं होता है? –

5

JSLint एक रेगुलर एक्सप्रेशन की उम्मीद करता है, तो पूर्ववर्ती टोकन

(,=:[!&|?{}; 

राइनो हमेशा lexer से एक DIV टोकन देता है में से एक है प्रकट होता है।

14

यह वास्तव में काफी आसान है, लेकिन इसके लिए आपके लेक्सर को सामान्य से थोड़ा अधिक स्मार्ट बनाने की आवश्यकता है।

विभाजन ऑपरेटर को एक अभिव्यक्ति का पालन करना चाहिए, और एक नियमित अभिव्यक्ति शाब्दिक अभिव्यक्ति का पालन नहीं कर सकता है, इसलिए अन्य सभी मामलों में आप सुरक्षित रूप से मान सकते हैं कि आप एक नियमित अभिव्यक्ति शाब्दिक रूप से देख रहे हैं।

यदि आप इसे सही तरीके से कर रहे हैं, तो आपको पहले से ही विराम चिह्नों को बहु-चरित्र तारों के रूप में पहचानना होगा। तो पिछले टोकन को देखें, और देखें कि क्या वह इनमें से किसी भी है:

. (, { } [ ; , < > <= >= == != === !== + - * % ++ -- 
<< >> >>> & |^! ~ && || ? : = += -= *= %= <<= >>= >>>= 
&= |= ^=//= 

इनमें से अधिकांश के लिए, आप अब पता है कि तुम एक संदर्भ में, जहां आप एक नियमित अभिव्यक्ति शाब्दिक पा सकते हैं कर रहे हैं। अब, ++ -- के मामले में, आपको कुछ अतिरिक्त काम करने की आवश्यकता होगी। यदि ++ या -- एक पूर्व-वृद्धि/कमी है, तो / इसके बाद यह एक नियमित अभिव्यक्ति शाब्दिक प्रारंभ होता है; यदि यह पोस्ट-वृद्धि/कमी है, तो / इसके बाद यह एक DivPunctuator प्रारंभ होता है।

सौभाग्य से, आप यह निर्धारित कर सकते हैं कि यह पिछले टोकन की जांच करके "प्री-ऑपरेटर" है या नहीं। सबसे पहले, वृद्धि-वृद्धि/कमी एक प्रतिबंधित उत्पादन है, इसलिए यदि ++ या -- एक लाइनब्रेक से पहले है, तो आप जानते हैं कि यह "प्री-" है। अन्यथा, यदि पिछला टोकन ऐसी चीजों में से कोई है जो एक नियमित अभिव्यक्ति शाब्दिक (yay recursion!) से पहले हो सकता है, तो आप जानते हैं कि यह "pre-" है। अन्य सभी मामलों में, यह "पोस्ट-" है। उदाहरण के लिए if (something) /regex/.exec(x) -

बेशक

, ) punctuator नहीं हमेशा एक अभिव्यक्ति के अंत द्योतक नहीं है। यह मुश्किल है क्योंकि यह को विघटन करने के लिए कुछ अर्थपूर्ण समझ की आवश्यकता है।

अफसोस की बात है, यह बिल्कुल नहीं है। ऐसे कुछ ऑपरेटर हैं जो Punctuators नहीं हैं, और बूट करने के लिए अन्य उल्लेखनीय कीवर्ड हैं। नियमित अभिव्यक्ति अक्षर भी इन का पालन कर सकते हैं। वे हैं:

new delete void typeof instanceof in do return case throw else 

IdentifierName तुम सिर्फ भस्म इन में से एक है, तो आप एक नियमित रूप से शाब्दिक अभिव्यक्ति देख रहे हों; अन्यथा, यह एक DivPunctuator है।

उपर्युक्त ईसीएमएस्क्रिप्ट 5.1 विनिर्देश (जैसा कि here मिला) पर आधारित है और इसमें भाषा में कोई ब्राउज़र-विशिष्ट एक्सटेंशन शामिल नहीं है। लेकिन अगर आपको उन लोगों का समर्थन करने की ज़रूरत है, तो यह निर्धारित करने के लिए आसान दिशानिर्देश प्रदान करना चाहिए कि आप किस प्रकार के संदर्भ में हैं।

बेशक, उपरोक्त में से अधिकांश नियमित अभिव्यक्ति शाब्दिक सहित बहुत मूर्ख मामलों का प्रतिनिधित्व करते हैं। उदाहरण के लिए, आप वास्तव में एक नियमित अभिव्यक्ति को पूर्व-वृद्धि नहीं कर सकते हैं, भले ही यह वाक्य रचनात्मक रूप से अनुमत हो। तो अधिकांश टूल असली दुनिया अनुप्रयोगों के लिए नियमित अभिव्यक्ति संदर्भ जांच को सरल बनाने के साथ दूर हो सकते हैं। (,=:[!&|?{}; के लिए पिछले वर्ण की जांच करने के जेएसलिंट की विधि शायद पर्याप्त है। लेकिन यदि आप जेएस को लेक्स करने के लिए एक उपकरण होने के बारे में सोचते समय ऐसा शॉर्टकट लेते हैं, तो आपको यह ध्यान रखना चाहिए।

+1

यह दृष्टिकोण सबसे यथार्थवादी कोड के लिए काम करता है, लेकिन इस उदाहरण को सही ढंग से नहीं लेगा: 'अगर (कुछ) /regex/.exec (x); ' – JacquesB

+0

@JacquesB अच्छी पकड़! –

+0

@JacquesB 'exec' का कोई दुष्प्रभाव नहीं है। क्या रेगेक्स एक बयान शुरू करने का एक यथार्थवादी उदाहरण है? –

5

मैं वर्तमान में जावासीसी के साथ 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 वैसे भी ज्यादा समझ में नहीं आता है।

+0

यह एक शानदार जवाब है। अंतिम पैराग्राफ के बारे में, दूसरा विकल्प केवल एक ही समय में लेक्स और पार्स करना है, जो इन दिनों मानक है। –

+0

@ थॉमबलेक धन्यवाद। लेक्स और पार्स एक ही समय में - क्या आपको शायद मेरे लिए संकेत मिले, मैं जावा के लिए क्या उपयोग कर सकता हूं? अभी मैं जावासीसी पर हूं। मैं मैदान में नौसिखिया हूं इसलिए एक सूचक के लिए आभारी होंगे। धन्यवाद। – lexicore

+0

मुझे जावा के बारे में लगभग 0 पता है, और मैंने लिखा है कि अधिकांश पार्स मैंने हाथ से किया है। यदि यह मदद करता है, तो राइनो जावा में है और आप शायद कुछ कोड उधार ले सकते हैं। –

संबंधित मुद्दे