टोकन का लेक्टेटर क्या होना चाहिए जो उसके पार्सर में रिटर्न करता है?


21

जैसा कि शीर्षक में कहा गया है कि किस डेटा प्रकार को एक लेक्सर रिटर्न / पार्सर देना चाहिए? विकिपीडिया के पास लेक्सिकल विश्लेषण लेख को पढ़ते समय यह कहा गया है कि:

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

हालांकि, उपरोक्त कथन के पूर्ण विरोधाभास में, जब मैंने एक अन्य प्रश्न एक अलग साइट पर पूछा ( कोड की समीक्षा करें यदि आप उत्सुक हैं) तो उत्तर दिया गया था, जवाब देने वाले व्यक्ति ने कहा कि:

लेक्सर आमतौर पर स्ट्रिंग को पढ़ता है और इसे एक धारा में परिवर्तित करता है ... लेक्समेस का। Lexemes को केवल संख्याओं की एक धारा होना चाहिए ।

और उसने यह दृश्य दिया:

nl_output => 256
output    => 257
<string>  => 258

बाद में लेख में उन्होंने उल्लेख किया Flex, एक पहले से मौजूद लेक्सर, और कहा कि इसके साथ 'नियम' लिखना हाथ से एक लेक्सर लिखने की तुलना में सरल होगा। वह मुझे यह उदाहरण देने के लिए आगे बढ़ा:

Space              [ \r\n\t]
QuotedString       "[^"]*"
%%
nl_output          {return 256;}
output             {return 257;}
{QuotedString}     {return 258;}
{Space}            {/* Ignore */}
.                  {error("Unmatched character");}
%%

मेरी अंतर्दृष्टि को आगे बढ़ाने और अधिक जानकारी प्राप्त करने के लिए, मैंने फ्लेक्स के बारे में विकिपीडिया लेख पढ़ा । फ्लेक्स लेख से पता चला है कि आप टोकन के साथ सिंटैक्स नियमों के एक सेट को निम्न तरीके से परिभाषित कर सकते हैं:

digit         [0-9]
letter        [a-zA-Z]

%%
"+"                  { return PLUS;       }
"-"                  { return MINUS;      }
"*"                  { return TIMES;      }
"/"                  { return SLASH;      }
"("                  { return LPAREN;     }
")"                  { return RPAREN;     }
";"                  { return SEMICOLON;  }
","                  { return COMMA;      }
"."                  { return PERIOD;     }
":="                 { return BECOMES;    }
"="                  { return EQL;        }
"<>"                 { return NEQ;        }
"<"                  { return LSS;        }
">"                  { return GTR;        }
"<="                 { return LEQ;        }
">="                 { return GEQ;        }
"begin"              { return BEGINSYM;   }
"call"               { return CALLSYM;    }
"const"              { return CONSTSYM;   }
"do"                 { return DOSYM;      }
"end"                { return ENDSYM;     }
"if"                 { return IFSYM;      }
"odd"                { return ODDSYM;     }
"procedure"          { return PROCSYM;    }
"then"               { return THENSYM;    }
"var"                { return VARSYM;     }
"while"              { return WHILESYM;   }

यह मुझे प्रतीत होता है कि फ्लेक्स लेक्सर कीवर्ड्स टोकन के तार लौटा रहा है। लेकिन यह कुछ संख्याओं के बराबर स्थिरांक हो सकता है।

अगर लेक्सर नंबरों को वापस करने जा रहा है, तो यह स्ट्रिंग शाब्दिक कैसे पढ़ेगा? एक नंबर लौटाना एकल कीवर्ड के लिए ठीक है, लेकिन आप एक स्ट्रिंग से कैसे निपटेंगे? क्या लेसर को स्ट्रिंग को बाइनरी नंबर में बदलना नहीं होगा और फिर पार्सर संख्याओं को वापस स्ट्रिंग में बदल देगा। यह बहुत अधिक तार्किक (और आसान) लगता है कि लेक्सर के लिए तार वापस आ गए हैं, और फिर पार्सर को किसी भी संख्या स्ट्रिंग शाब्दिक को वास्तविक संख्या में परिवर्तित करने दें।

या फिर लेक्सर दोनों को वापस लौटा सकता है? मैं सी ++ में एक सरल लेख लिखने की कोशिश कर रहा हूं, जो आपको अपने कार्यों के लिए केवल एक रिटर्न प्रकार देता है। इस प्रकार मुझे अपना प्रश्न पूछने के लिए प्रेरित किया।

मेरे प्रश्न को एक पैराग्राफ में संक्षेपित करने के लिए: जब एक लेक्सर लिखते हैं, और यह मानते हुए कि यह केवल एक डेटा प्रकार (तार या संख्या) वापस कर सकता है , जो अधिक तार्किक विकल्प होगा?


लेक्सर आपको वह देता है जो आप उसे वापस करने के लिए कहते हैं। यदि आपका डिज़ाइन संख्याओं के लिए कहता है, तो यह संख्याओं को वापस कर देगा। जाहिर है, स्ट्रिंग शाब्दिक का प्रतिनिधित्व करने के लिए उससे थोड़ा अधिक की आवश्यकता होगी। यह भी देखें क्या यह नंबर और स्ट्रिंग्स को पार्स करने के लिए एक लेक्सर की नौकरी है? ध्यान दें कि स्ट्रिंग शाब्दिक रूप से "भाषा तत्व" नहीं माना जाता है।
रॉबर्ट हार्वे

@RobertHarvey तो क्या आप स्ट्रिंग शाब्दिक को द्विआधारी संख्या में बदल देंगे?
क्रिश्चियन डीन

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

तो, आपका क्या कहना है कि लेकर स्ट्रिंग शाब्दिक के बारे में नहीं पढ़ता है या परवाह नहीं करता है। और इसलिए पार्सर को इन स्ट्रिंग शाब्दिकों के लिए देखना चाहिए? यह बहुत भ्रामक है।
क्रिश्चियन डीन

आप इसे पढ़ते हुए कुछ मिनट बिताना चाहते हैं: en.wikipedia.org/wiki/Lexical_analysis
रॉबर्ट हार्वे

जवाबों:


10

आम तौर पर, यदि आप लेक्सिंग और पार्सिंग के दौरान भाषा का प्रसंस्करण कर रहे हैं, तो आपको अपने लेक्सिकल टोकन की परिभाषा मिल गई है, जैसे:

NUMBER ::= [0-9]+
ID     ::= [a-Z]+, except for keywords
IF     ::= 'if'
LPAREN ::= '('
RPAREN ::= ')'
COMMA  ::= ','
LBRACE ::= '{'
RBRACE ::= '}'
SEMICOLON ::= ';'
...

और आपके पास पार्सर के लिए एक व्याकरण है:

STATEMENT ::= IF LPAREN EXPR RPAREN STATEMENT
            | LBRACE STATEMENT BRACE
            | EXPR SEMICOLON
EXPR      ::= ID
            | NUMBER
            | ID LPAREN EXPRS RPAREN
...

आपका लेक्सर इनपुट स्ट्रीम लेता है और टोकन की एक धारा का उत्पादन करता है। तोते की धारा का उपयोग पार्सर पेड़ का उत्पादन करने के लिए करता है। कुछ मामलों में, केवल टोकन के प्रकार को जानना ही पर्याप्त है (जैसे, LPAREN, RBRACE, FOR), लेकिन कुछ मामलों में, आपको उस वास्तविक मान की आवश्यकता होगी जो टोकन से जुड़ा हुआ है। उदाहरण के लिए, जब आप एक आईडी टोकन का सामना करते हैं, तो आप वास्तविक पात्रों को पहचानना चाहेंगे जो बाद में आईडी बनाते हैं जब आप यह पता लगाने की कोशिश कर रहे हैं कि आप किस पहचानकर्ता को संदर्भ देने की कोशिश कर रहे हैं।

तो, आपके पास आम तौर पर कम या ज्यादा कुछ इस तरह है:

enum TokenType {
  NUMBER, ID, IF, LPAREN, RPAREN, ...;
}

class Token {
  TokenType type;
  String value;
}

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

if (2 > 0) {
  print("2 > 0");
}
if (0 > 2) {
  print("0 > 2");
}

ये टोकन प्रकारों के समान क्रम का उत्पादन करते हैं : IF, LPAREN, NUMBER, GREATER_THAN, NUMBER, RPAREN, LBRACE, ID, LPAREN, STRING, RPAREN, SEMICOLON, RBRACE। इसका मतलब है कि वे पार्स एक ही है, भी। लेकिन जब आप वास्तव में तोते के पेड़ के साथ कुछ कर रहे हैं, तो आप ध्यान देंगे कि पहले नंबर का मूल्य '2' (या '0') है और दूसरे नंबर का मूल्य '0' (या '2) है '), और स्ट्रिंग का मान' 2> 0 '(या' 0> 2 ') है।


मुझे सबसे ज्यादा वही मिलता है जो आपका कहना है, लेकिन यह कैसा हैString value भरा करने के लिए जा रहे हैं? क्या यह एक स्ट्रिंग या संख्या से भरा होगा? और यह भी, मैं Stringप्रकार को कैसे परिभाषित करूंगा ?
ईसाई डीन

1
@ Mr.Python सरलतम मामले में, यह सिर्फ उन पात्रों का तार है जो शाब्दिक उत्पादन से मेल खाते हैं। इसलिए, यदि आप फू (23, "बार") देखते हैं, तो आपको टोकन [आईडी, "फू"], [LPAREN, "("], [NUMBER, "23"], [COMMA, "," मिलेगा ], [STRING, "" 23 ""], [RPAREN, ")"] । उस जानकारी को संरक्षित करना महत्वपूर्ण हो सकता है। या आप एक और दृष्टिकोण ले सकते हैं और मूल्य में एक संघ प्रकार होता है जो एक स्ट्रिंग, या एक संख्या, आदि हो सकता है, और आपके पास किस प्रकार के टोकन प्रकार के आधार पर सही मान प्रकार चुनें (जैसे, जब टोकन प्रकार NUMBER है , value.num का उपयोग करें, और जब यह STRING हो, तो value.str का उपयोग करें)।
जोशुआ टेलर

@MrPththon "और यह भी, मैं स्ट्रिंग प्रकार को कैसे परिभाषित करूंगा?" मैं जावा-ईश मानसिकता से लिख रहा था। यदि आप C ++ में काम कर रहे हैं, तो आप C ++ के स्ट्रिंग प्रकार का उपयोग कर सकते हैं, या यदि आप C में काम कर रहे हैं, तो आप एक char * का उपयोग कर सकते हैं। मुद्दा यह है कि एक टोकन के साथ जुड़ा हुआ है, आपके पास संबंधित मूल्य है, या वह पाठ जिसे आप मूल्य का उत्पादन करने के लिए व्याख्या कर सकते हैं।
जोशुआ टेलर

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

2
@ ollydbg23 एक मामला एक साधारण छद्म मंत्री होगा। यह करना काफी आसान है parse(inputStream).forEach(token -> print(token.string); print(' '))(यानी, अंतरिक्ष द्वारा अलग किए गए टोकन के स्ट्रिंग मानों को प्रिंट करें)। यह बहुत जल्दी है। और यहां तक ​​कि अगर LPAREN केवल "(") से ही आ सकता है, तो यह मेमोरी में एक निरंतर स्ट्रिंग हो सकता है, इसलिए टोकन के संदर्भ में यह संभव नहीं है कि यह शून्य संदर्भ सहित अधिक महंगा हो। सामान्य तौर पर, मैं बल्कि लिखूंगा। कोड जो मुझे कोई विशेष केस नहीं बनाता है।
यहोशू टेलर

6

जैसा कि शीर्षक में कहा गया है कि किस डेटा प्रकार को एक लेक्सर रिटर्न / पार्सर देना चाहिए?

"टोकन", जाहिर है। एक लेसर टोकन की एक धारा का उत्पादन करता है, इसलिए इसे टोकन की एक धारा वापस करना चाहिए ।

उन्होंने फ्लेक्स का उल्लेख किया है, जो पहले से ही मौजूद है, और कहा कि इसके साथ 'नियम' लिखना आसान होगा, जो हाथ से लेक्सर लिखने से ज्यादा आसान होगा।

मशीन द्वारा तैयार किए गए लेक्सर्स का यह फायदा है कि आप उन्हें जल्दी से उत्पन्न कर सकते हैं, जो विशेष रूप से आसान है यदि आपको लगता है कि आपका लेक्सिकल व्याकरण बहुत कुछ बदलने वाला है। उनका नुकसान यह है कि आपको अक्सर अपने कार्यान्वयन विकल्पों में बहुत अधिक लचीलापन नहीं मिलता है।

उस ने कहा, कौन परवाह करता है अगर यह "सरल" है? आम तौर पर लिखनेवाला कठिन हिस्सा नहीं है!

जब एक लेक्सर लिखते हैं, और यह मानते हुए कि यह केवल एक डेटा प्रकार (तार या संख्या) वापस कर सकता है, जो अधिक तार्किक विकल्प होगा?

न तो। एक लेसर के पास आमतौर पर "अगला" ऑपरेशन होता है जो एक टोकन लौटाता है, इसलिए इसे एक टोकन वापस करना चाहिए । एक टोकन एक स्ट्रिंग या एक संख्या नहीं है। यह एक टोकन है।

मेरे द्वारा लिखा गया अंतिम लेसर एक "पूर्ण निष्ठा" का अर्थ था, जिसका अर्थ था कि यह एक टोकन है जो सभी व्हाट्सएप और टिप्पणियों के स्थान को ट्रैक करता है - जिसे हम कार्यक्रम में "सामान्य ज्ञान" कहते हैं, साथ ही एक टोकन भी। मेरे लेक्सर में एक टोकन को इस प्रकार परिभाषित किया गया था:

  • अग्रणी सामान्य ज्ञान की एक सरणी
  • एक टोकन प्रकार
  • पात्रों में एक टोकन चौड़ाई
  • ट्राइविंग ट्रिविया की एक सरणी

सामान्य ज्ञान के रूप में परिभाषित किया गया था:

  • एक सामान्य ज्ञान - व्हाट्सएप, न्यूलाइन, टिप्पणी, और इसी तरह
  • पात्रों में एक सामान्य चौड़ाई

तो अगर हमारे पास कुछ ऐसा था

    foo + /* comment */
/* another comment */ bar;

टोकन प्रकार के साथ चार टोकन के रूप में लेक्स होता है कि Identifier, Plus, Identifier, Semicolon, और चौड़ाई 3, 1, 3, 1. पहले पहचानकर्ता प्रमुख से मिलकर सामान्य ज्ञान है Whitespace4 की चौड़ाई के साथ और सामान्य ज्ञान अनुगामी Whitespace1. की चौड़ाई के साथ Plusकोई अग्रणी सामान्य ज्ञान है और एक व्हाट्सएप, एक टिप्पणी और एक नई पंक्ति से मिलकर ट्रेलिंग ट्रिविया। अंतिम पहचानकर्ता में एक टिप्पणी और एक स्थान का एक प्रमुख सामान्य ज्ञान है, और इसी तरह।

इस योजना के साथ फाइल में प्रत्येक वर्ण को लेक्सर के आउटपुट के लिए जिम्मेदार माना जाता है, जो कि सिंटिक कलरिंग जैसी चीजों के लिए एक उपयोगी संपत्ति है।

बेशक, यदि आपको सामान्य ज्ञान की आवश्यकता नहीं है, तो आप बस एक टोकन दो चीजें बना सकते हैं: तरह और चौड़ाई।

आप देख सकते हैं कि टोकन और ट्रिविया में केवल उनकी चौड़ाई होती है, न कि स्रोत कोड में उनकी पूर्ण स्थिति। वह जानबूझकर है। ऐसी योजना के फायदे हैं:

  • यह मेमोरी और वायर फॉर्मेट में कॉम्पैक्ट है
  • यह संपादन पर पुनः-सक्षम बनाता है; यह उपयोगी है यदि एक आईडीई के अंदर लेक्सर चल रहा है। यही है, यदि आप एक टोकन में एक संपादन का पता लगाते हैं, तो आप अपने लेक्सर को संपादित करने से पहले कुछ टोकन तक वापस कर देते हैं और फिर से लेक्सिंग शुरू करते हैं जब तक कि आप पिछले टोकन स्ट्रीम के साथ सिंक नहीं हो जाते। जब आप उस वर्ण को बदलने के बाद हर टोकन की स्थिति टाइप करते हैं, लेकिन आमतौर पर चौड़ाई में केवल एक या दो टोकन बदलते हैं, तो आप उस सभी स्थिति का फिर से उपयोग कर सकते हैं।
  • प्रत्येक टोकन के सटीक वर्ण ऑफसेट को टोकन स्ट्रीम पर पुनरावृत्ति और वर्तमान ऑफ़सेट का ट्रैक रखने के द्वारा आसानी से प्राप्त किया जा सकता है। एक बार जब आपके पास सटीक वर्ण ऑफसेट हो जाता है तो आवश्यक होने पर पाठ को निकालना आसान होता है।

यदि आप उन परिदृश्यों में से किसी के बारे में परवाह नहीं करते हैं, तो एक टोकन को एक प्रकार और चौड़ाई के बजाय एक प्रकार और ऑफसेट के रूप में दर्शाया जा सकता है।

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


3

आम तौर पर, आप एक छोटी संरचना लौटाते हैं जिसमें एक संख्या होती है जो टोकन (या उपयोग में आसानी के लिए enum मान) और एक वैकल्पिक मूल्य (स्ट्रिंग, या संभवतः जेनेरिक / अस्थायी मान) होता है। एक अन्य दृष्टिकोण उन तत्वों के लिए एक व्युत्पन्न प्रकार को वापस करना होगा, जिन्हें अतिरिक्त डेटा ले जाने की आवश्यकता है। दोनों हल्के से अरुचिकर हैं, लेकिन एक व्यावहारिक समस्या के लिए पर्याप्त समाधान हैं।


आप हल्के से अरुचिकर का क्या मतलब है ? क्या वे स्ट्रिंग मान प्राप्त करने के अक्षम तरीके हैं?
क्रिश्चियन डीन

@ Mr.Python - वे कोड में उपयोग करने से पहले बहुत सारे चेक का नेतृत्व करेंगे, जो अक्षम है, लेकिन मोरेसो कोड को थोड़ा अधिक जटिल / नाजुक बना देता है।
तेलेस्टिन

सी ++ में एक लेसर डिजाइन करते समय मेरे पास एक समान प्रश्न है, मैं एक Token *या बस एक Token, या TokenPtrजो कि Tokenवर्ग का एक साझा सूचक है , वापस कर सकता हूं । लेकिन मैं कुछ लेक्सर को सिर्फ एक टोकन टाइप भी देखता हूं, और स्ट्रिंग या संख्या मान को अन्य वैश्विक या स्थिर चर में संग्रहीत करता हूं। एक और सवाल यह है कि हम स्थान की जानकारी कैसे संग्रहीत कर सकते हैं, क्या मुझे एक टोकन संरचना की आवश्यकता है, जिसमें टोकन टाइप, स्ट्रिंग और स्थान फ़ील्ड हैं? धन्यवाद।
ollydbg23

@ ollydbg23 - इनमें से कोई भी काम कर सकता है। मैं एक संरचना का उपयोग करूंगा। और गैर-शिक्षण भाषाओं के लिए आप किसी भी तरह से पार्सर जनरेटर का उपयोग कर रहे होंगे।
तेलस्टिन

@Telastyn उत्तर के लिए धन्यवाद। आपका मतलब है कि एक टोकन संरचना कुछ इस तरह की हो सकती है struct Token {TokenType id; std::string lexeme; int line; int column;}, है ना? जैसे कि Lexer के किसी सार्वजनिक फ़ंक्शन के लिए PeekToken(), फ़ंक्शन Token *या वापस लौट सकता है TokenPtr। मुझे लगता है कि यह थोड़ी देर के लिए है, अगर फ़ंक्शन सिर्फ टोकन टाइप वापस करता है, तो पार्सर टोकन के बारे में अन्य जानकारी प्राप्त करने का प्रयास कैसे करता है? तो, इस तरह के फ़ंक्शन से रिटर्न के लिए डेटाटाइप जैसे सूचक को प्राथमिकता दी जाती है। मेरे विचार के बारे में कोई टिप्पणी? धन्यवाद
ollydbg23
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.