C ++ को LR (1) पार्सर से पार्स क्यों नहीं किया जा सकता है?


153

मैं पार्सर और पार्सर जनरेटर के बारे में पढ़ रहा था और विकिपीडिया के LR पार्सिंग-पेज में इस कथन को पाया:

एलआर पार्सर की कुछ भिन्नता का उपयोग करके कई प्रोग्रामिंग भाषाओं को पार्स किया जा सकता है। एक उल्लेखनीय अपवाद C ++ है।

ऐसा क्यों है? C ++ की किस विशेष संपत्ति के कारण LR पार्सर्स के साथ पार्स करना असंभव है?

Google का उपयोग करते हुए, मैंने केवल पाया कि C को LR (1) के साथ पूरी तरह से पार्स किया जा सकता है लेकिन C ++ को LR (∞) की आवश्यकता होती है।


7
जैसे: आपको पुनरावृत्ति जानने के लिए पुनरावृत्ति को समझने की आवश्यकता है ;-)।
तून Krijthe

5
"इस वाक्यांश को पार्स करने के बाद आप पार्सर्स को समझेंगे।"
एलिया एन।

जवाबों:


92

लैम्ब्डा अल्टीमेट का एक दिलचस्प सूत्र है जो C ++ के लिए LALR व्याकरण पर चर्चा करता है

इसमें पीएचडी थीसिस के लिए एक लिंक शामिल है जिसमें सी ++ पार्सिंग की चर्चा शामिल है, जिसमें कहा गया है:

"C ++ व्याकरण अस्पष्ट है, संदर्भ-निर्भर और संभावित रूप से कुछ अस्पष्टताओं को हल करने के लिए अनंत लुकहेड की आवश्यकता है"।

यह कई उदाहरण देता है (पीडीएफ के पृष्ठ 147 देखें)।

उदाहरण है:

int(x), y, *const z;

अर्थ

int x;
int y;
int *const z;

से तुलना:

int(x), y, new int;

अर्थ

(int(x)), (y), (new int));

(अल्पविराम से अलग की गई अभिव्यक्ति)।

दो टोकन अनुक्रमों में एक ही प्रारंभिक अनुवर्ती लेकिन अलग-अलग पार्स पेड़ हैं, जो अंतिम तत्व पर निर्भर करते हैं। मनमाने ढंग से एक से पहले कई टोकन हो सकते हैं।


29
इस पृष्ठ पर पृष्ठ १४ be के बारे में कुछ सारांश रखना अच्छा होगा। मैं हालांकि उस पृष्ठ को पढ़ने जा रहा हूं। (+1)
चीयर्स

11
उदाहरण है: int (x), y, * const z; // अर्थ: इंट एक्स; इंट वाई; int * const z; (घोषणाओं का एक क्रम) int (x), y, नया int; // अर्थ: (इंट (एक्स)), (वाई), (नया इंट); (एक अल्पविराम से अलग अभिव्यक्ति) दो टोकन अनुक्रमों में एक ही प्रारंभिक अनुवर्ती लेकिन अलग-अलग पार्स पेड़ होते हैं, जो अंतिम तत्व पर निर्भर करते हैं। मनमाने ढंग से एक से पहले कई टोकन हो सकते हैं।
ब्लेज़ोरब्लेड

6
खैर, उस संदर्भ में "का अर्थ है" मनमाने ढंग से कई "क्योंकि लुकहेड हमेशा इनपुट लंबाई से घिरा होगा।
मगनानारा

1
मैं पीएचडी थीसिस से निकाले गए प्रशस्ति पत्र से काफी हैरान हूं। यदि कोई अस्पष्टता है, तो, परिभाषा के अनुसार, NO लुकहेड कभी भी अस्पष्टता को "हल" कर सकता है (यानी यह तय करें कि कौन सा पार्स सही ओन है, क्योंकि कम से कम 2 पर्स व्याकरण द्वारा सही माने जाते हैं)। इसके अलावा, प्रशस्ति पत्र में C की अस्पष्टता का उल्लेख है, लेकिन स्पष्टीकरण में अस्पष्टता नहीं है, लेकिन केवल एक अस्पष्ट उदाहरण है जहां पार्सिंग निर्णय केवल एक मनमाने ढंग से लंबे समय से आगे देखने के बाद लिया जा सकता है।
डोडेसेप्लेक्स

231

LR पार्सर अस्पष्ट व्याकरण के नियमों को संभाल नहीं सकते हैं, डिजाइन द्वारा। (1970 के दशक में जब विचारों पर काम किया जा रहा था तो सिद्धांत को आसान बना दिया गया था)।

C और C ++ दोनों निम्नलिखित कथन की अनुमति देते हैं:

x * y ;

इसके दो अलग-अलग पर्स हैं:

  1. यह x की तरह सूचक के रूप में y की घोषणा हो सकती है
  2. यह एक्स और वाई की एक बहुतायत हो सकती है, उत्तर को दूर फेंक सकती है।

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

संकलक को उपयुक्त परिस्थितियों में उपयुक्त को स्वीकार करना चाहिए, और किसी भी अन्य जानकारी (जैसे, एक्स के प्रकार का ज्ञान) की अनुपस्थिति में दोनों को इकट्ठा करना होगा ताकि बाद में निर्णय लिया जा सके कि क्या करना है। इस प्रकार एक व्याकरण को इसकी अनुमति देनी चाहिए। और इससे व्याकरण अस्पष्ट हो जाता है।

इस प्रकार शुद्ध एलआर पार्सिंग इसे संभाल नहीं सकता है। न ही कई अन्य व्यापक रूप से उपलब्ध पार्सर जनरेटर, जैसे कि एंट्र, जावासीसी, वाईएसीसी, या पारंपरिक बाइसन, या यहां तक ​​कि पीईजी-शैली के पार्सर्स, का उपयोग "शुद्ध" तरीके से किया जा सकता है।

बहुत अधिक जटिल मामले हैं (पार्सिंग टेम्प्लेट सिंटैक्स को मनमाने ढंग से देखने की आवश्यकता होती है, जबकि एलएएलआर (के) अधिकांश k टोकन पर आगे देख सकते हैं), लेकिन केवल शुद्ध एलआर (या अन्य) पार्सिंग को शूट करने के लिए केवल एक काउंटरएक्सप्लिमेंट लेता है ।

अधिकांश वास्तविक C / C ++ पार्सर्स एक अतिरिक्त हैक के साथ कुछ प्रकार के निर्धारक पार्सर का उपयोग करके इस उदाहरण को संभालते हैं: वे प्रतीक तालिका संग्रह के साथ पार्सिंग को परस्पर जोड़ते हैं ... ताकि जब तक "x" का सामना न हो जाए, तो parser जानता है कि क्या x एक प्रकार है या नहीं, और इस प्रकार दो संभावित पार्स के बीच चयन कर सकते हैं। लेकिन एक पार्सर जो ऐसा नहीं करता है वह संदर्भ मुक्त है, और एलआर पार्सर (शुद्ध वाले, आदि) संदर्भ में (सर्वोत्तम) संदर्भ मुक्त हैं।

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

और यदि आप पर्याप्त धोखा देते हैं, तो आप LR पार्सर को C और C ++ के लिए काम कर सकते हैं। जीसीसी के लोगों ने थोड़ी देर के लिए किया, लेकिन इसे हाथ से कोडित पार्सिंग के लिए छोड़ दिया, मुझे लगता है क्योंकि वे बेहतर त्रुटि निदान चाहते थे।

हालांकि, एक और दृष्टिकोण है, जो अच्छा और साफ है और सी और सी ++ को किसी भी प्रतीक तालिका हैकरी के बिना ठीक करता है: जीएलआर पार्सर्स । ये पूर्ण संदर्भ मुक्त पार्सर्स हैं (प्रभावी रूप से अनंत लुकहेड हैं)। जीएलआर पार्सर केवल एक "ट्री" (वास्तव में एक निर्देशित चक्रीय ग्राफ जो ज्यादातर पेड़ की तरह है) का निर्माण करते हुए दोनों पार्स को स्वीकार करते हैं, जो अस्पष्ट पार्स का प्रतिनिधित्व करता है। पोस्ट-पार्सिंग पास अस्पष्टताओं को हल कर सकता है।

हम इस तकनीक का उपयोग अपने डीएमएस सॉफ्टवेयर रेन्गिनियरिंग टूकिट के लिए C और C ++ फ्रंट एंड में करते हैं (जून 2017 तक ये MS और GNU बोलियों में पूर्ण C ++ 17 को हैंडल करते हैं)। स्रोत कोड के पूर्ण विवरण के साथ एएसटी का निर्माण करने वाले सटीक, सटीक पार्स के साथ, बड़े सी और सी ++ सिस्टम की लाखों लाइनों को संसाधित करने के लिए उनका उपयोग किया गया है। ( C ++ के सबसे डरावने पार्स के लिए एएसटी देखें )


11
जबकि 'x * y' उदाहरण दिलचस्प है, वही C में हो सकता है ('y' एक टाइपडिफ या एक चर हो सकता है)। लेकिन C को LR (1) पार्सर द्वारा पार्स किया जा सकता है, इसलिए C ++ में क्या अंतर है?
मार्टिन कोटे

12
मेरे उत्तरदाता ने पहले ही यह देख लिया था कि C की भी यही समस्या है, मुझे लगता है कि आप चूक गए। नहीं, इसे उसी कारण से LR (1) द्वारा पार्स नहीं किया जा सकता है। एर, तुम्हारा क्या मतलब है 'y' एक टाइप्डिफ हो सकता है? शायद आपका मतलब 'x' था? यह कुछ भी नहीं बदलता है।
इरा बैक्सटर

6
पार्स 2 आवश्यक रूप से सी ++ में बेवकूफ नहीं है, क्योंकि साइड इफेक्ट्स के लिए ओवरराइड किया जा सकता है।
हाई आर्च

8
मैंने देखा x * yऔर चकित हो गया - यह आश्चर्यजनक है कि कोई भी इस तरह की छोटी अस्पष्टताओं के बारे में कैसे सोचता है।
new123456

51
@altie निश्चित रूप से कोई भी एक बिट-शिफ्ट ऑपरेटर को अधिभारित नहीं करेगा, ताकि यह एक धारा के लिए अधिकांश चर प्रकार लिख सके, है ना?
ट्रॉय डेनियल

16

समस्या कभी इस तरह परिभाषित नहीं होती है, जबकि यह दिलचस्प होनी चाहिए:

C ++ व्याकरण में संशोधनों का सबसे छोटा सेट क्या है जो आवश्यक होगा ताकि इस नए व्याकरण को "गैर-संदर्भ-मुक्त" याक पार्सर द्वारा पूरी तरह से पार्स किया जा सके? (केवल एक 'हैक' का उपयोग करना: टाइपनेम / पहचानकर्ता की छूट, हर टाइपकर्ता / वर्ग / संरचना के लेसर को सूचित करने वाला पार्सर)

मैं कुछ लोगों को देखता हूं:

  1. Type Type;निषिद्ध है। एक टाइकून के रूप में घोषित एक पहचानकर्ता एक गैर-टाइपनेम पहचानकर्ता नहीं बन सकता है (ध्यान दें कि struct Type Typeअस्पष्ट नहीं है और इसे अनुमति नहीं दी जा सकती है)।

    3 प्रकार हैं names tokens:

    • types : बिलिन-टाइप या टाइप-एफ / क्लास / स्ट्रक्चर की वजह से
    • टेम्पलेट कार्यों
    • पहचानकर्ता: कार्य / तरीके और चर / वस्तुएं

    विभिन्न टोकन के रूप में टेम्पलेट-कार्यों को ध्यान में रखते हुए func<अस्पष्टता को हल करता है। यदि funcएक टेम्पलेट-फ़ंक्शन नाम है, तो <एक टेम्पलेट पैरामीटर सूची की शुरुआत होनी चाहिए, अन्यथा funcफ़ंक्शन पॉइंटर है और <तुलना ऑपरेटर है।

  2. Type a(2);एक वस्तु तात्कालिकता है। Type a();और Type a(int)फ़ंक्शन प्रोटोटाइप हैं।

  3. int (k); पूरी तरह से निषिद्ध है, लिखा जाना चाहिए int k;

  4. typedef int func_type(); और typedef int (func_type)();निषिद्ध हैं।

    एक फंक्शन टाइप्डफ एक फंक्शन पॉइंटर टाइपफेड होना चाहिए: typedef int (*func_ptr_type)();

  5. टेम्पलेट पुनरावृत्ति 1024 तक सीमित है, अन्यथा एक बढ़ी हुई अधिकतम को संकलक के विकल्प के रूप में पारित किया जा सकता है।

  6. int a,b,c[9],*d,(*f)(), (*g)()[9], h(char); भी मना किया जा सकता है, द्वारा प्रतिस्थापित int a,b,c[9],*d; int (*f)();

    int (*g)()[9];

    int h(char);

    फंक्शन प्रोटोटाइप या फ़ंक्शन पॉइंटर घोषणा के प्रति एक लाइन।

    एक अत्यधिक पसंदीदा विकल्प भयानक फ़ंक्शन पॉइंटर सिंटैक्स को बदलना होगा,

    int (MyClass::*MethodPtr)(char*);

    के रूप में resyntaxed किया जा रहा है:

    int (MyClass::*)(char*) MethodPtr;

    यह कास्ट ऑपरेटर के साथ सुसंगत है (int (MyClass::*)(char*))

  7. typedef int type, *type_ptr; निषिद्ध भी किया जा सकता है: एक पंक्ति प्रति टंकण। इस प्रकार यह बन जाएगा

    typedef int type;

    typedef int *type_ptr;

  8. sizeof int, sizeof char, sizeof long longऔर सह। प्रत्येक स्रोत फ़ाइल में घोषित किया जा सकता है। इस प्रकार, प्रत्येक स्रोत फ़ाइल प्रकार का उपयोग intकरने के साथ शुरू होना चाहिए

    #type int : signed_integer(4)

    और इस निर्देश के unsigned_integer(4)बाहर निषिद्ध होगा कि #typeयह sizeof intइतने सारे सी ++ छात्रों में मौजूद बेवकूफ अस्पष्टता में एक बड़ा कदम होगा

संकलित C ++ को लागू करने वाला कंपाइलर, अगर C ++ स्रोत को अस्पष्ट सिंटैक्स का उपयोग करते हुए मुठभेड़ करता है, तो source.cppएक ambiguous_syntaxफ़ोल्डर को स्थानांतरित करें , और source.cppइसे संकलित करने से पहले स्वचालित रूप से एक अनूदित अनुवाद बना देगा।

यदि आप कुछ जानते हैं तो कृपया अपने अस्पष्ट C ++ सिंटैक्स जोड़ें!


3
C ++ बहुत अच्छी तरह से उलझा हुआ है। व्यवहार में कोई भी ऐसा नहीं करेगा। वे लोग (हमारे जैसे) जो सामने के छोर का निर्माण करते हैं, बस बुलेट को काटते हैं और पार्सर्स को काम करने के लिए इंजीनियरिंग करते हैं। और, जब तक भाषा में टेम्पलेट मौजूद हैं, आपको शुद्ध संदर्भ-मुक्त पार्सर नहीं मिलने वाला है।
इरा बैक्सटर

9

जैसा कि आप यहाँ मेरे जवाब में देख सकते हैं , C ++ में सिंटैक्स होता है जो निश्चित रूप से संचालन के क्रम को बदलते हुए प्रकार रिज़ॉल्यूशन चरण (आमतौर पर पोस्ट-पार्सिंग) के कारण LL या LR पार्सर द्वारा पार्स नहीं किया जा सकता है , और इसलिए AST का मूल आकार ( आमतौर पर पहले चरण के पार्स द्वारा प्रदान किए जाने की उम्मीद है)।


3
पार्सिंग तकनीक जो अस्पष्टता को संभालती है, दोनों एएसटी वेरिएंट का उत्पादन करती है क्योंकि वे पार्स करते हैं, और केवल टाइप जानकारी के आधार पर गलत को समाप्त करते हैं।
इरा बैक्सटर

@ इरा: हाँ, यह सही है। इसका विशेष लाभ यह है कि आप पहले चरण के पार्स के अलगाव को बनाए रखने की अनुमति देते हैं। जबकि यह सबसे आम तौर पर जीएलआर पार्सर में जाना जाता है, कोई विशेष कारण नहीं है जो मैं देखता हूं कि आप "जीएलएल" के साथ सी ++ नहीं मार सकते हैं? साथ ही पार्सर।
सैम हैरवेल

"GLL"? ठीक है, निश्चित है, लेकिन आपको सिद्धांत का पता लगाना होगा और शेष उपयोग के लिए एक पेपर लिखना होगा। अधिक संभावना है, आप टॉप डाउन हैंड कोडेड पार्सर, या बैकट्रैकिंग एलएएलआर () पार्सर (लेकिन "अस्वीकृत") पार्स का उपयोग कर सकते हैं, या एक अर्ली पार्सर चला सकते हैं। जीएलआर के पास एक बहुत ही अच्छा समाधान होने का फायदा है, यह अच्छी तरह से प्रलेखित है और अब तक अच्छी तरह से साबित हुआ है। GLL को प्रदर्शित करने के लिए GLL तकनीक के कुछ बहुत महत्वपूर्ण फायदे होंगे।
इरा बैक्सटर

रास्कल प्रोजेक्ट (नीदरलैंड) दावा कर रहा है कि वे एक स्कैनर रहित जीएलएल पार्सर का निर्माण कर रहे हैं। कार्य प्रगति पर है, कोई भी ऑनलाइन जानकारी प्राप्त करना कठिन हो सकता है। en.wikipedia.org/wiki/RascalMPL
इरा बैक्सटर

@IraBaxter GLL पर नए घटनाक्रम प्रतीत हो रहे हैं: GLL dotat.at/tmp/gll.pdf के
Sjoerd

6

मुझे लगता है कि आप उत्तर के बहुत करीब हैं।

LR (1) का अर्थ है कि संदर्भ के लिए आगे-आगे देखने के लिए बाएं से दाएं की ओर केवल एक टोकन की आवश्यकता है, जबकि LR (inf) का अर्थ है अनंत-आगे। यही है, पार्सर को सब कुछ जानना होगा जो यह पता लगाने के लिए आ रहा था कि यह अब कहां है।


4
मैं अपने संकलक वर्ग से याद करता हूं कि LR> (n) के लिए n> 0 गणितीय रूप से LR (1) के लिए पुन: प्रयोज्य है। क्या यह n = अनंत के लिए सही नहीं है?
rmeador

14
नहीं, वहाँ n और अनंत के बीच अंतर का एक अगम्य पर्वत है।
१२

4
जवाब नहीं है: हाँ, समय की एक अनंत राशि दी? :)
स्टीव फॉक्स

7
दरअसल, LR (n) -> LR (1) के मेरे अस्पष्ट स्मरण से, इसमें नए मध्यवर्ती राज्यों का निर्माण होता है, इसलिए रनटाइम 'n' का कुछ गैर-स्थिर कार्य है। LR (inf) -> LR (1) का अनुवाद करने में अनंत समय लगेगा।
आरोन

5
"जवाब नहीं है: हाँ, समय की एक अनंत राशि दी?" - नहीं "वाक्यांश ने अनंत समय दिया है" केवल एक गैर-संवेदी, संक्षिप्त रूप से कहने का तरीका है "किसी भी परिमित समय को पूरा नहीं किया जा सकता है"। जब आप "अनंत" देखते हैं, तो सोचें: "कोई परिमित नहीं"।
क्रिस डब्ल्यूडब्ल्यू

4

C ++ में "टाइपडेफ़" समस्या को LALR (1) पार्सर के साथ पार्स किया जा सकता है जो पार्सिंग करते समय प्रतीक चिह्न बनाता है (शुद्ध LALR पार्सर नहीं)। "टेम्पलेट" समस्या शायद इस पद्धति से हल नहीं की जा सकती। इस तरह के एलएएलआर (1) पार्सर का लाभ यह है कि व्याकरण (नीचे दिखाया गया है) एक एलएएलआर (1) व्याकरण (कोई अस्पष्टता नहीं) है।

/* C Typedef Solution. */

/* Terminal Declarations. */

   <identifier> => lookup();  /* Symbol table lookup. */

/* Rules. */

   Goal        -> [Declaration]... <eof>               +> goal_

   Declaration -> Type... VarList ';'                  +> decl_
               -> typedef Type... TypeVarList ';'      +> typedecl_

   VarList     -> Var /','...     
   TypeVarList -> TypeVar /','...

   Var         -> [Ptr]... Identifier 
   TypeVar     -> [Ptr]... TypeIdentifier                               

   Identifier     -> <identifier>       +> identifier_(1)      
   TypeIdentifier -> <identifier>      =+> typedefidentifier_(1,{typedef})

// The above line will assign {typedef} to the <identifier>,  
// because {typedef} is the second argument of the action typeidentifier_(). 
// This handles the context-sensitive feature of the C++ language.

   Ptr          -> '*'                  +> ptr_

   Type         -> char                 +> type_(1)
                -> int                  +> type_(1)
                -> short                +> type_(1)
                -> unsigned             +> type_(1)
                -> {typedef}            +> type_(1)

/* End Of Grammar. */

एक समस्या के बिना निम्नलिखित इनपुट पार्स किया जा सकता है:

 typedef int x;
 x * y;

 typedef unsigned int uint, *uintptr;
 uint    a, b, c;
 uintptr p, q, r;

LRSTAR पार्सर जेनरेटर ऊपर व्याकरण अंकन पढ़ता है और एक पार्सर उत्पन्न करता है कि हैंडल पार्स पेड़ या एएसटी में अस्पष्टता के बिना "typedef" समस्या। (प्रकटीकरण: मैं वह व्यक्ति हूं जिसने LRSTAR बनाया था।)


यह GCC द्वारा अपने पूर्व LR पार्सर के साथ "x * y;" जैसी चीजों की अस्पष्टता को संभालने के लिए उपयोग की जाने वाली मानक हैक है; काश, अभी भी अन्य निर्माणों को पार्स करने के लिए मनमाने ढंग से बड़े लाह की आवश्यकता है, इसलिए एलआर (के) किसी भी निश्चित कश्मीर का समाधान करने में विफल रहता है। (जीसीसी ने अधिक विज्ञापन पदानुक्रम के साथ पुनरावर्ती वंश पर स्विच किया)।
इरा बैक्सटर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.