समीकरण (अभिव्यक्ति) पूर्वता के साथ पार्सर?


104

मैंने एक साधारण स्टैक एल्गोरिथ्म का उपयोग करके एक समीकरण पार्सर विकसित किया है जो बाइनरी (+, -,;,;, *, /, आदि) संचालकों, यूनिरी (!) ऑपरेटरों, और कोष्ठक को संभाल लेगा।

इस पद्धति का उपयोग करना, हालांकि, मुझे एक ही पूर्वता वाले सब कुछ के साथ छोड़ देता है - यह ऑपरेटर की परवाह किए बिना बाएं से दाएं मूल्यांकन किया जाता है, हालांकि कोष्ठक का उपयोग करके पूर्वता को लागू किया जा सकता है।

तो अभी "1 + 11 * 5" रिटर्न 60, 56 नहीं जैसा कि कोई उम्मीद कर सकता है।

हालांकि यह वर्तमान परियोजना के लिए उपयुक्त है, मैं एक सामान्य उद्देश्य दिनचर्या चाहता हूं जिसे मैं बाद की परियोजनाओं के लिए उपयोग कर सकता हूं।

स्पष्टता के लिए संपादित:

पूर्वता के साथ समीकरणों को पार्स करने के लिए एक अच्छा एल्गोरिथ्म क्या है?

मुझे लागू करने और समझने के लिए कुछ सरल में दिलचस्पी है कि मैं उपलब्ध कोड के साथ लाइसेंसिंग मुद्दों से बचने के लिए खुद को कोड कर सकता हूं।

व्याकरण:

मैं व्याकरण के प्रश्न को नहीं समझता - मैंने इसे हाथ से लिखा है। यह काफी सरल है कि मुझे YACC या बाइसन की आवश्यकता नहीं दिख रही है। मुझे केवल "2 + 3 * (42/13)" जैसे समीकरणों के साथ तार की गणना करने की आवश्यकता है।

भाषा: हिन्दी:

मैं सी में यह कर रहा हूं, लेकिन मैं एक एल्गोरिथ्म में दिलचस्पी रखता हूं, न कि भाषा विशिष्ट समाधान। C निम्न स्तर पर पर्याप्त है कि इसे दूसरी भाषा में परिवर्तित करना आसान होगा, जिसकी आवश्यकता उत्पन्न होनी चाहिए।

कोड उदाहरण

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

संबंधित प्रश्न

एक गणित पार्सर की स्मार्ट डिजाइन?

-Adam


मैंने अपने ब्लॉग पर C # में एक अभिव्यक्ति पार्सर लिखा है । यह शंटिंग यार्ड एल्गोरिथ्म में स्टैक के बिना पोस्टफिक्स करने के लिए इन्फिक्स करता है। यह केवल एक सरणी का उपयोग करता है।
5'08

जैसा कि मैं समझता हूं कि आपको केवल अंकगणितीय अभिव्यक्ति के लिए पार्स चाहिए। रिवर्स पोलिश नोटेशन का
mishadoff

जवाबों:


69

मुश्किल रास्ता

आप एक पुनरावर्ती वंश पार्सर चाहते हैं ।

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

1+11*5

इसे मैन्युअल रूप से करने के लिए, आपको पढ़ना होगा 1, फिर प्लस को देखना होगा और एक नया पुनरावर्ती पार्स "सत्र" शुरू करना होगा11 ... और 11 * 5अपने स्वयं के कारक को पार्स ट्री के साथ पैरेडिंग करना सुनिश्चित करें 1 + (11 * 5)

यह सब समझाने की कोशिश करने पर भी इतना दर्दनाक लगता है, विशेष रूप से सी। की शक्तिहीनता के साथ, देखें। 11 को पार्स करने के बाद, यदि * वास्तव में + इसके बजाय, आपको एक शब्द बनाने के प्रयास को छोड़ना होगा और इसके बजाय पार्स करें 11खुद एक कारक के रूप में। मेरा सिर पहले से ही फट रहा है। यह पुनरावर्ती सभ्य रणनीति के साथ संभव है, लेकिन एक बेहतर तरीका है ...

आसान (दाएं) तरीका

यदि आप Bison जैसे GPL टूल का उपयोग करते हैं, तो आपको लाइसेंसिंग के मुद्दों के बारे में चिंता करने की आवश्यकता नहीं है क्योंकि bison द्वारा उत्पन्न C कोड GPL (IANAL) द्वारा कवर नहीं किया गया है, लेकिन मुझे पूरा यकीन है कि GPL उपकरण GPL को बाध्य नहीं करते हैं उत्पन्न कोड / बायनेरिज़, उदाहरण के लिए ऐप्पल कोड कहते हैं, जीसीसी के साथ एपर्चर और वे इसे जीपीएल कोड के बिना बेचते हैं)।

बाइसन डाउनलोड करें (या कुछ समकक्ष, ANTLR, आदि)।

आमतौर पर कुछ सैंपल कोड होते हैं, जिन पर आप केवल बायसन चला सकते हैं और अपना वांछित C कोड प्राप्त कर सकते हैं जो इस चार फ़ंक्शन कैलकुलेटर को प्रदर्शित करता है:

http://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html

उत्पन्न कोड को देखें, और देखें कि यह जितना आसान लगता है उतना आसान नहीं है। इसके अलावा, बाइसन जैसे उपकरण का उपयोग करने के फायदे 1 हैं) आप कुछ सीखते हैं (खासकर यदि आप ड्रैगन पुस्तक पढ़ते हैं और व्याकरण के बारे में सीखते हैं), 2) आप पहिया को सुदृढ़ करने की कोशिश कर रहे एनआईएच से बचते हैं । असली पार्सर-जनरेटर उपकरण के साथ, आपको वास्तव में बाद में स्केलिंग करने की उम्मीद है, अन्य लोगों को दिखाते हुए कि आप जानते हैं कि पार्सर्स पार्सिंग टूल का डोमेन है।


अपडेट करें:

यहां के लोगों ने बहुत अच्छी सलाह दी है। पार्सिंग टूल्स को छोड़ देने या केवल शंटिंग यार्ड एल्गोरिथ्म का उपयोग करने या एक हाथ से पुनरावर्ती सभ्य पार्सर का उपयोग करने के खिलाफ मेरी चेतावनी है कि छोटी खिलौना भाषाएँ 1 किसी दिन बड़े वास्तविक भाषाओं में फ़ंक्शन (पाप, कॉस, लॉग) और चर, शर्तों और साथ के लिए बदल सकती हैं छोरों।

फ्लेक्स / बाइसन एक छोटे, सरल दुभाषिया के लिए बहुत अच्छी तरह से ओवरकिल हो सकता है, लेकिन एक बंद पार्सर + मूल्यांकनकर्ता को नीचे की ओर परेशानी पैदा हो सकती है जब परिवर्तन करने की आवश्यकता होती है या सुविधाओं को जोड़ने की आवश्यकता होती है। आपकी स्थिति अलग-अलग होगी और आपको अपने निर्णय का उपयोग करने की आवश्यकता होगी; बस अपने पापों के लिए अन्य लोगों को दंडितकरें [2] और पर्याप्त उपकरण से कम का निर्माण करें।

पार्सिंग के लिए मेरा पसंदीदा उपकरण

नौकरी के लिए दुनिया में सबसे अच्छा उपकरण पारसेक पुस्तकालय (पुनरावर्ती सभ्य पार्सर के लिए) है जो प्रोग्रामिंग भाषा हास्केल के साथ आता है। यह बीएनएफ की तरह बहुत कुछ दिखता है , या पार्सिंग (नमूना कोड [3]) के लिए कुछ विशेष उपकरण या डोमेन विशिष्ट भाषा की तरह है, लेकिन यह वास्तव में हास्केल में सिर्फ एक नियमित पुस्तकालय है, जिसका अर्थ है कि यह बिलकुल बाकी के समान निर्माण चरण में संकलित करता है। अपने हास्केल कोड, और आप मनमाने ढंग से हास्केल कोड लिख सकते हैं और अपने पार्सर के भीतर कॉल कर सकते हैं, और आप सभी पुस्तकालयों को एक ही कोड में मिला सकते हैं और मेल कर सकते हैं । (हास्केल के अलावा अन्य भाषा में पार्सिंग भाषा को एंबेड करना से सिंटैक्टिक क्रॉफ्ट के भार के परिणामस्वरूप होता है। मैंने सी # में ऐसा किया है और यह काफी अच्छी तरह से काम करता है लेकिन यह इतना सुंदर और संक्षिप्त नहीं है।)

टिप्पणियाँ:

1 रिचर्ड स्टेलमैन कहते हैं, में आपको Tcl का उपयोग क्यों नहीं करना चाहिए

Emacs का मुख्य सबक यह है कि एक्सटेंशन के लिए एक भाषा केवल "एक्सटेंशन भाषा" नहीं होनी चाहिए। यह एक वास्तविक प्रोग्रामिंग भाषा होनी चाहिए, जो पर्याप्त कार्यक्रमों को लिखने और बनाए रखने के लिए डिज़ाइन की गई हो। क्योंकि लोग ऐसा ही करना चाहेंगे!

[२] हां, मैं उस "भाषा" के उपयोग से हमेशा के लिए डर गया।

यह भी ध्यान दें कि जब मैंने इस प्रविष्टि को प्रस्तुत किया था, तो पूर्वावलोकन सही था, लेकिन SO के पर्याप्त पार्सर से कम ने पहले पैराग्राफ पर मेरे करीबी एंकर टैग को खा लिया , यह साबित करते हुए कि पार्सर कुछ के साथ trifled होने के लिए नहीं हैं क्योंकि यदि आप regexes का उपयोग करते हैं और एक आपको हैक करता है शायद कुछ सूक्ष्म और छोटा गलत मिलेगा

[३] पार्सेक का उपयोग कर एक हास्केल पार्सर का स्निपेट: एक चार फंक्शन कैलकुलेटर एक्सपेक्टर्स, कोष्ठक, गुणन के लिए व्हाट्सएप, और स्थिरांक (जैसे पी और ई) के साथ विस्तारित।

aexpr   =   expr `chainl1` toOp
expr    =   optChainl1 term addop (toScalar 0)
term    =   factor `chainl1` mulop
factor  =   sexpr  `chainr1` powop
sexpr   =   parens aexpr
        <|> scalar
        <|> ident

powop   =   sym "^" >>= return . (B Pow)
        <|> sym "^-" >>= return . (\x y -> B Pow x (B Sub (toScalar 0) y))

toOp    =   sym "->" >>= return . (B To)

mulop   =   sym "*" >>= return . (B Mul)
        <|> sym "/" >>= return . (B Div)
        <|> sym "%" >>= return . (B Mod)
        <|>             return . (B Mul)

addop   =   sym "+" >>= return . (B Add) 
        <|> sym "-" >>= return . (B Sub)

scalar = number >>= return . toScalar

ident  = literal >>= return . Lit

parens p = do
             lparen
             result <- p
             rparen
             return result

9
मेरी बात पर जोर देने के लिए, ध्यान दें कि मेरी पोस्ट में मार्कअप सही तरीके से पार्स नहीं किया जा रहा है (और यह मार्कअप के बीच वैधानिक रूप से भिन्न होता है और जो डब्लूएमडी पूर्वावलोकन में प्रस्तुत किया गया है)। इसे ठीक करने के लिए कई प्रयास हुए हैं लेकिन मुझे लगता है कि PARSER गलत है। हर किसी को एक एहसान करो और सही पार्सिंग प्राप्त करें!
जेरेड अपडेटेड

155

Shunting यार्ड एल्गोरिथ्म इस के लिए सही उपकरण है। विकिपीडिया वास्तव में इस बारे में भ्रमित है, लेकिन मूल रूप से एल्गोरिथ्म इस तरह काम करता है:

कहते हैं, आप 1 + 2 * 3 + 4. का मूल्यांकन करना चाहते हैं। सहज रूप से, आपको "पता है" आपको पहले 2 * 3 करना होगा, लेकिन आपको यह परिणाम कैसे मिलेगा? कुंजी को एहसास है कि जब आप स्ट्रिंग स्कैनिंग कर रहे बाएं से दाएं, आप जब ऑपरेटर कि एक ऑपरेटर का मूल्यांकन करेंगे है इस प्रकार यह एक कम है (या के बराबर) पूर्वता। उदाहरण के संदर्भ में, यहां आप क्या करना चाहते हैं:

  1. देखो: 1 + 2, कुछ मत करो।
  2. अब 1 + 2 * 3 को देखें, फिर भी कुछ न करें।
  3. अब 1 + 2 * 3 + 4 को देखें, अब आप जानते हैं कि 2 * 3 का मूल्यांकन किया जाना है क्योंकि अगले ऑपरेटर में कम पूर्वाग्रह है।

आप इसे कैसे लागू करेंगे?

आप दो स्टैक रखना चाहते हैं, एक नंबर के लिए और दूसरा ऑपरेटर के लिए। आप हर समय स्टैक पर संख्याओं को बढ़ाते हैं। आप स्टैक के शीर्ष पर एक के साथ प्रत्येक नए ऑपरेटर की तुलना करते हैं, यदि स्टैक के शीर्ष पर उच्च प्राथमिकता है, तो आप इसे ऑपरेटर स्टैक से पॉप करते हैं, ऑपर को नंबर स्टैक से पॉप करते हैं, ऑपरेटर को लागू करते हैं और परिणाम को धक्का देते हैं संख्या ढेर पर। अब आप स्टैक ऑपरेटर के शीर्ष के साथ तुलना को दोहराते हैं।

उदाहरण के लिए वापस आते हुए, यह इस तरह काम करता है:

एन = [] ऑप्स = []

  • पढ़ें 1. एन = [1], ऑप्स = []
  • + पढ़ें। एन = [1], ऑप्स = [+]
  • पढ़ें 2. एन = [1 2], ऑप्स = [+]
  • पढ़ें * । एन = [१ २], ऑप्स = [+ *]
  • पढ़ें 3. एन = [1 2 3], ऑप्स = [+ *]
  • + पढ़ें। एन = [१ २ ३], ऑप्स = [+ *]
    • पॉप 3, 2 और 2 निष्पादित करें* 3 , और N. N = [1 6], Ops = [+] पर परिणाम धक्का दें
    • +साहचर्य छोड़ दिया जाता है, इसलिए आप 1, 6 को भी पॉप करना चाहते हैं और + निष्पादित करते हैं। एन = [7], ऑप्स = []।
    • अंत में ऑपरेटर स्टैक पर [+] को धक्का दें। एन = [7], ऑप्स = [+]।
  • पढ़ें 4. एन = [7 4]। ऑप्स = [+]।
  • आप इनपुट से बाहर हैं, इसलिए अब आप स्टैक खाली करना चाहते हैं। जिस पर आपको 11 परिणाम मिलेंगे।

वहाँ, यह इतना मुश्किल नहीं है, है ना? और यह किसी भी व्याकरण या पार्सर जनरेटर के लिए कोई आह्वान नहीं करता है।


6
आपको वास्तव में दो स्टैक की आवश्यकता नहीं है, जब तक आप शीर्ष पर पॉपिंग के बिना स्टैक पर दूसरी चीज देख सकते हैं। आप इसके बजाय एक एकल स्टैक का उपयोग कर सकते हैं जो संख्या और ऑपरेटरों को वैकल्पिक करता है। यह वास्तव में एक LR पार्सर जनरेटर (जैसे बायसन) क्या करता है।
क्रिस डोड

2
एल्गोरिथ्म का वास्तव में अच्छा स्पष्टीकरण मैंने अभी लागू किया है। इसके अलावा आप इसे पोस्टफिक्स में परिवर्तित नहीं कर रहे हैं जो कि अच्छा भी है। कोष्ठक के लिए समर्थन जोड़ना बहुत आसान है।
गियोर्गी

4
शंटिंग-यार्ड एल्गोरिथ्म के लिए एक सरलीकृत संस्करण यहाँ पाया जा सकता है: andreinc.net/2010/10/05/… (जावा और अजगर में कार्यान्वयन के साथ)
आंद्रेई

1
इसके लिए धन्यवाद, वास्तव में मैं इसके बाद क्या हूँ!
जो ग्रीन

बाईं ओर के बारे में बहुत कुछ धन्यवाद - साहचर्य। मैं टर्नरी ऑपरेटर के साथ फंस गया: नेस्टेड के साथ जटिल अभिव्यक्तियों को पार्स करने के लिए ":?"। मुझे एहसास हुआ कि दोनों '?' और ':' के लिए समान प्राथमिकता है। और अगर हम व्याख्या करते हैं? ' दायें - साहचर्य और ':' के रूप में छोड़ दिया - साहचर्य इस एल्गोरिथ्म उनके साथ बहुत अच्छी तरह से काम करता है। इसके अलावा, हम 2 ऑपरेटरों को तब ही ध्वस्त कर सकते हैं जब दोनों को छोड़ दिया जाए - साहचर्य।
व्लादिस्लाव

25

http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm

विभिन्न दृष्टिकोणों की बहुत अच्छी व्याख्या:

  • पुनरावर्ती-वंश मान्यता
  • शंटिंग यार्ड एल्गोरिथ्म
  • क्लासिक समाधान
  • पूर्व चढ़ाई

सरल भाषा और छद्म कोड में लिखा गया है।

मुझे 'पूर्ववर्ती चढ़ाई' पसंद है।


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

18

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


16

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

लगभग 2 साल पहले, मैंने इसके बारे में एक पोस्ट किया था, जो पर्ल स्रोत कोड के साथ पूरा हुआ, http://www.perlmonks.org/?node_id=554516 पर । अन्य भाषाओं में पोर्ट करना आसान है: मैंने जो पहला कार्यान्वयन किया वह Z80 असेंबलर में था।

यह संख्याओं के साथ प्रत्यक्ष गणना के लिए आदर्श है, लेकिन आप इसका उपयोग पार्स ट्री का उत्पादन करने के लिए कर सकते हैं यदि आपको करना चाहिए।

अपडेट करें क्योंकि अधिक लोग जावास्क्रिप्ट को पढ़ (या चला सकते हैं), मैंने अपने पार्सर को जावास्क्रिप्ट में फिर से लागू किया है, कोड के पुनर्गठन के बाद। संपूर्ण पार्सर त्रुटि कोड और टिप्पणियों सहित जावास्क्रिप्ट कोड के 5k (पार्सर के लिए लगभग 100 लाइनें, एक आवरण समारोह के लिए 15 लाइनें) के तहत है।

आप http://users.telenet.be/bartl/expressionParser/expressionParser.html पर एक लाइव डेमो पा सकते हैं ।

// operator table
var ops = {
   '+'  : {op: '+', precedence: 10, assoc: 'L', exec: function(l,r) { return l+r; } },
   '-'  : {op: '-', precedence: 10, assoc: 'L', exec: function(l,r) { return l-r; } },
   '*'  : {op: '*', precedence: 20, assoc: 'L', exec: function(l,r) { return l*r; } },
   '/'  : {op: '/', precedence: 20, assoc: 'L', exec: function(l,r) { return l/r; } },
   '**' : {op: '**', precedence: 30, assoc: 'R', exec: function(l,r) { return Math.pow(l,r); } }
};

// constants or variables
var vars = { e: Math.exp(1), pi: Math.atan2(1,1)*4 };

// input for parsing
// var r = { string: '123.45+33*8', offset: 0 };
// r is passed by reference: any change in r.offset is returned to the caller
// functions return the parsed/calculated value
function parseVal(r) {
    var startOffset = r.offset;
    var value;
    var m;
    // floating point number
    // example of parsing ("lexing") without aid of regular expressions
    value = 0;
    while("0123456789".indexOf(r.string.substr(r.offset, 1)) >= 0 && r.offset < r.string.length) r.offset++;
    if(r.string.substr(r.offset, 1) == ".") {
        r.offset++;
        while("0123456789".indexOf(r.string.substr(r.offset, 1)) >= 0 && r.offset < r.string.length) r.offset++;
    }
    if(r.offset > startOffset) {  // did that work?
        // OK, so I'm lazy...
        return parseFloat(r.string.substr(startOffset, r.offset-startOffset));
    } else if(r.string.substr(r.offset, 1) == "+") {  // unary plus
        r.offset++;
        return parseVal(r);
    } else if(r.string.substr(r.offset, 1) == "-") {  // unary minus
        r.offset++;
        return negate(parseVal(r));
    } else if(r.string.substr(r.offset, 1) == "(") {  // expression in parens
        r.offset++;   // eat "("
        value = parseExpr(r);
        if(r.string.substr(r.offset, 1) == ")") {
            r.offset++;
            return value;
        }
        r.error = "Parsing error: ')' expected";
        throw 'parseError';
    } else if(m = /^[a-z_][a-z0-9_]*/i.exec(r.string.substr(r.offset))) {  // variable/constant name        
        // sorry for the regular expression, but I'm too lazy to manually build a varname lexer
        var name = m[0];  // matched string
        r.offset += name.length;
        if(name in vars) return vars[name];  // I know that thing!
        r.error = "Semantic error: unknown variable '" + name + "'";
        throw 'unknownVar';        
    } else {
        if(r.string.length == r.offset) {
            r.error = 'Parsing error at end of string: value expected';
            throw 'valueMissing';
        } else  {
            r.error = "Parsing error: unrecognized value";
            throw 'valueNotParsed';
        }
    }
}

function negate (value) {
    return -value;
}

function parseOp(r) {
    if(r.string.substr(r.offset,2) == '**') {
        r.offset += 2;
        return ops['**'];
    }
    if("+-*/".indexOf(r.string.substr(r.offset,1)) >= 0)
        return ops[r.string.substr(r.offset++, 1)];
    return null;
}

function parseExpr(r) {
    var stack = [{precedence: 0, assoc: 'L'}];
    var op;
    var value = parseVal(r);  // first value on the left
    for(;;){
        op = parseOp(r) || {precedence: 0, assoc: 'L'}; 
        while(op.precedence < stack[stack.length-1].precedence ||
              (op.precedence == stack[stack.length-1].precedence && op.assoc == 'L')) {  
            // precedence op is too low, calculate with what we've got on the left, first
            var tos = stack.pop();
            if(!tos.exec) return value;  // end  reached
            // do the calculation ("reduce"), producing a new value
            value = tos.exec(tos.value, value);
        }
        // store on stack and continue parsing ("shift")
        stack.push({op: op.op, precedence: op.precedence, assoc: op.assoc, exec: op.exec, value: value});
        value = parseVal(r);  // value on the right
    }
}

function parse (string) {   // wrapper
    var r = {string: string, offset: 0};
    try {
        var value = parseExpr(r);
        if(r.offset < r.string.length){
          r.error = 'Syntax error: junk found at offset ' + r.offset;
            throw 'trailingJunk';
        }
        return value;
    } catch(e) {
        alert(r.error + ' (' + e + '):\n' + r.string.substr(0, r.offset) + '<*>' + r.string.substr(r.offset));
        return;
    }    
}

11

यदि आप उस व्याकरण का वर्णन कर सकते हैं जो आप वर्तमान में पार्स करने के लिए उपयोग कर रहे हैं तो यह मदद करेगा। समस्या की तरह लग सकता है वहाँ झूठ!

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

तथ्य यह है कि आप व्याकरण के प्रश्न को नहीं समझते हैं और 'आपने इसे हाथ से लिखा है' बहुत ही संभावित रूप से बताता है कि आपको फॉर्म '1 + 11 * 5' के भाव के साथ समस्या क्यों हो रही है (यानी, ऑपरेटर पूर्वता के साथ) । उदाहरण के लिए, 'अंकगणितीय अभिव्यक्तियों के लिए व्याकरण' के लिए गुग्लिंग करना चाहिए। इस तरह के एक व्याकरण को जटिल नहीं होना चाहिए:

<Exp> ::= <Exp> + <Term> |
          <Exp> - <Term> |
          <Term>

<Term> ::= <Term> * <Factor> |
           <Term> / <Factor> |
           <Factor>

<Factor> ::= x | y | ... |
             ( <Exp> ) |
             - <Factor> |
             <Number>

उदाहरण के लिए चाल चलेंगे, और कुछ अधिक जटिल अभिव्यक्तियों (उदाहरण के लिए कार्य, या शक्तियाँ, सहित ...) की देखभाल करने के लिए तुच्छ रूप से संवर्धित किया जा सकता है।

मेरा सुझाव है कि आप इस धागे पर एक नज़र डालें , उदाहरण के लिए।

व्याकरण / पार्सिंग के लगभग सभी परिचय अंकगणितीय अभिव्यक्तियों को एक उदाहरण के रूप में मानते हैं।

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

<Exp>  :: <Leaf> | <Exp> <Op> <Leaf>

<Op>   :: + | - | * | /

<Leaf> :: <Number> | (<Exp>)

(या इस तरह के कुछ) यह जाने बिना!


8

क्या आपने बूस्ट स्पिरिट के इस्तेमाल के बारे में सोचा है ? यह आपको C ++ में EBNF जैसे व्याकरण लिखने की अनुमति देता है:

group       = '(' >> expression >> ')';
factor      = integer | group;
term        = factor >> *(('*' >> factor) | ('/' >> factor));
expression  = term >> *(('+' >> term) | ('-' >> term));

1
+1 और अपशॉट है, सब कुछ बूस्ट का हिस्सा है। कैलकुलेटर के लिए व्याकरण यहाँ है: spirit.sourceforge.net/distrib/spirit_1_8_5/libs/spirit/example/… । कैलकुलेटर का कार्यान्वयन यहां है: spirit.sourceforge.net/distrib/spirit_1_8_5/libs/spirit/example/… । और प्रलेखन यहाँ है: spirit.sourceforge.net/distrib/spirit_1_8_5/libs/spirit/doc/… । मैं कभी नहीं समझ पाऊंगा कि लोग अभी भी अपने मिनी पार्सर को क्यों लागू करते हैं।
Stephan

5

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

1)। स्पष्ट उपसर्ग विनिर्देश की आवश्यकता को समाप्त करने के लिए पोस्टफिक्स नोटेशन = का आविष्कार किया गया। नेट पर अधिक पढ़ें, लेकिन यहां इसका सार है: इनफ़िक्स एक्सप्रेशन (1 + 2) * 3 जबकि मनुष्यों को पढ़ना आसान है और मशीन के माध्यम से कंप्यूटिंग के लिए बहुत कुशल नहीं है। क्या है? सरल नियम जो कहते हैं कि "पूर्वगामी में कैशिंग द्वारा अभिव्यक्ति को फिर से लिखना, फिर इसे हमेशा बाएं से दाएं संसाधित करें"। तो infix (1 + 2) * 3 एक उपसर्ग 12 + 3 * हो जाता है। POST क्योंकि ऑपरेटर को हमेशा ऑपरेशंस के बाद रखा जाता है।

2)। उपसर्ग अभिव्यक्ति का मूल्यांकन। आसान। उपसर्ग स्ट्रिंग से संख्या पढ़ें। जब तक कोई ऑपरेटर दिखाई नहीं देता तब तक उन्हें ढेर पर रखें। ऑपरेटर के प्रकार की जाँच करें - एकरी? द्विआधारी? तृतीयक? इस ऑपरेटर का मूल्यांकन करने के लिए आवश्यक स्टैक के रूप में कई ऑपरेंड पॉप। मूल्यांकन करना। स्टैक पर परिणाम वापस धक्का! और उर लगभग किया। ऐसा तब तक करते रहें जब तक कि स्टैक में केवल एक ही एंट्री = वैल्यू यूर न हो।

आइए करते हैं (1 + 2) * 3 जो कि उपसर्ग में है "12 + 3 *"। पहली संख्या पढ़ें = 1. इसे स्टैक पर रखें। आगे पढ़िए। संख्या = 2. इसे स्टैक पर पुश करें। आगे पढ़िए। ऑपरेटर। कौनसा? +। किस प्रकार? बाइनरी = को दो ऑपरेंड की आवश्यकता होती है। पॉप स्टैक दो बार = एग्रीगेट 2 है और आर्लिफ्ट 1 है। 1 + 2 है 3. स्टैक पर 3 पुश। पोस्टफ़िक्स स्ट्रिंग से आगे पढ़ें। इसकी एक संख्या है। 3.Push। आगे पढ़िए। ऑपरेटर। कौनसा? *। किस प्रकार? बाइनरी = को दो नंबर चाहिए -> पॉप स्टैक दो बार। प्रथम पॉप इन आर्ग्रेट, दूसरी बार आर्केलफ़्ट में। ऑपरेशन का मूल्यांकन करें - 3 गुना 3 9. 9. 9 ढेर पर है। अगला पोस्टफिक्स चार पढ़ें यह अशक्त है। इनपुट का अंत पॉप स्टैक onec = यह आपका जवाब है।

3)। शंटिंग यार्ड का उपयोग मानव (आसानी से) पढ़ने योग्य infix अभिव्यक्ति को उपसर्ग अभिव्यक्ति (कुछ अभ्यास के बाद मानव आसानी से पठनीय भी) में बदलने के लिए किया जाता है। मैन्युअल रूप से कोड करना आसान है। ऊपर और शुद्ध टिप्पणियाँ देखें।


4

क्या कोई ऐसी भाषा है जिसका आप उपयोग करना चाहते हैं? ANTLR आपको जावा के नजरिए से ऐसा करने देगा। रूबी में एक निष्पादन योग्य व्याकरण लिखने के लिए एड्रियन कुह्न का एक उत्कृष्ट लेखन है ; वास्तव में, उनका उदाहरण लगभग आपके अंकगणितीय अभिव्यक्ति उदाहरण है।


मुझे यह स्वीकार करना चाहिए कि ब्लॉग पोस्ट में दिए गए मेरे उदाहरणों को लेफ्ट-रिकर्सशन गलत हो रहा है, अर्थात (- (-बी) - (ए-बी) - सी के बजाय (ए - (बी-सी) का मूल्यांकन करता है। दरअसल, यह मुझे एक टूडू जोड़ने की याद दिलाता है कि मुझे ब्लॉग पोस्ट को ठीक करना चाहिए।
अनकुहन

4

यह इस बात पर निर्भर करता है कि आप कैसे "सामान्य" होना चाहते हैं।

यदि आप चाहते हैं कि यह वास्तव में सामान्य हो जैसे कि गणितीय कार्यों को पार्स करने में सक्षम होने के साथ-साथ पाप (4 + 5) * कोस (7 ^ 3) तो आपको शायद एक पार्स ट्री की आवश्यकता होगी

जिसमें, मुझे नहीं लगता कि एक पूर्ण कार्यान्वयन को यहां चिपकाया जाना उचित है। मेरा सुझाव है कि आप कुख्यात " ड्रैगन बुक " में से एक की जाँच करें ।

लेकिन अगर आप सिर्फ पूर्ववर्ती समर्थन चाहते हैं , तो आप ऐसा कर सकते हैं कि पहले अभिव्यक्ति को पोस्टफिक्स फॉर्म में परिवर्तित करके, जिसमें एक एल्गोरिथ्म जिसे आप कॉपी-एंड-पेस्ट कर सकते हैं, Google से उपलब्ध होना चाहिए या मुझे लगता है कि आप इसे बाइनरी के साथ खुद को कोड कर सकते हैं पेड़।

जब आपके पास यह उपसर्ग के रूप में होता है, तो यह तब से केक का टुकड़ा है जब से आप पहले से ही समझते हैं कि स्टैक कैसे मदद करता है।


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

1
वाह - यह जानना अच्छा है कि "ड्रैगन बुक" की अभी भी चर्चा है। मुझे याद है कि यह अध्ययन - और इसे सभी के माध्यम से - विश्वविद्यालय में, 30 साल पहले पढ़ा था।
Schroedingers बिल्ली

4

मैं सुझाव देना चाहूंगा कि धोखा देना और शंटिंग यार्ड एल्गोरिथम का उपयोग करना । यह एक साधारण कैलकुलेटर-टाइप पार्सर लिखने का एक आसान साधन है और पूर्वता को ध्यान में रखता है।

यदि आप चीजों को अच्छी तरह से टोकन देना चाहते हैं और चर आदि शामिल हैं, तो मैं आगे बढ़ूंगा और एक पुनरावर्ती वंशीय पार्सर लिखूंगा, जैसा कि यहां दूसरों द्वारा सुझाया गया है, हालांकि यदि आपको बस एक कैलकुलेटर-शैली के पार्सर की आवश्यकता है, तो यह एल्गोरिथ्म पर्याप्त होना चाहिए :)


4

मैं शिंगल यार्ड एल्गोरिथ्म के बारे में PIClist पर यह पाया :

हेरोल्ड लिखते हैं:

मुझे याद है, बहुत समय पहले, एक एल्गोरिथ्म जिसने बीजीय अभिव्यक्तियों को आसान मूल्यांकन के लिए आरपीएन में बदल दिया था। प्रत्येक इन्फिक्स मूल्य या ऑपरेटर या कोष्ठक एक ट्रैक पर एक रेलरोड कार द्वारा दर्शाया गया था। एक प्रकार की कार दूसरे ट्रैक से अलग हो गई और दूसरी सीधे आगे बढ़ती रही। मुझे विवरण याद नहीं है (जाहिर है!), लेकिन हमेशा सोचा कि यह कोड के लिए दिलचस्प होगा। यह तब है जब मैं 6800 (68000 नहीं) असेंबली कोड लिख रहा था।

यह "शंटिंग यार्ड एल्ग्रोथ्म" है और यह वही है जो अधिकांश मशीन पार्सर उपयोग करते हैं। विकिपीडिया में पार्सिंग पर लेख देखें। शंटिंग यार्ड एल्ग्रोथ्म को कोड करने का एक आसान तरीका दो स्टैक का उपयोग करना है। एक "पुश" स्टैक है और दूसरा "कम" या "परिणाम" स्टैक है। उदाहरण:

pstack = () // खाली rstack = () इनपुट: 1 + 2 * 3 पूर्ववर्ती = 10 // सबसे कम घटा = 0 / कम न करें

प्रारंभ: टोकन '1': isnumber, pstack (पुश) टोकन '+' में डालें: आइसोपरेटर सेट पूर्वता = 2 यदि पूर्वता <पिछला_परिवर्तन_प्रक्रिया तब कम करें () // नीचे देखें + + 'pstack (धक्का) टोकन' 2 'में डालें। : isnumber, pstack (पुश) टोकन '*' में डालें: isoperator, पूर्ववर्ती सेट करें = 1, pstack में डालें (पुश) // चेक पूर्ववर्तीता जैसे // टोकन '3' के ऊपर: isnumber, pstack (पुश) अंत में डालें इनपुट, कम करने की आवश्यकता है (लक्ष्य खाली pstack है) कम () // किया

पुश स्टैक से पॉप तत्वों को कम करने और उन्हें परिणाम स्टैक में डालने के लिए, हमेशा शीर्ष 2 वस्तुओं को स्वैप पर स्वैप करें यदि वे फॉर्म 'ऑपरेटर' 'संख्या' के हैं:

pstack: '1' '+' '2' ' ' 3 'rstack: () ... pstack: () rstack:' 3 '' 2 ' ' '1' '+' '

अगर अभिव्यक्ति होती:

1 * 2 + 3

तब कम ट्रिगर ने टोकन '+' का पठन किया होगा जो पहले से धकेल दिए गए '*' की तुलना में कम पूर्वव्यापी है, इसलिए यह किया जाएगा:

pstack: '1' ' ' 2 'rstack: () ... pstack: () rstack:' 1 '' 2 '' '

और फिर '+' और फिर '3' को धकेला और फिर अंत में घटाया:

pstack: '+' 3 'rstack:' 1 '' 2 '' '... pstack: () rstack:' 1 '' 2 '' '' 3 '' + '

तो लघु संस्करण है: पुश नंबर, जब ऑपरेटर धक्का देते हैं तो पिछले ऑपरेटर की पूर्वता की जांच करते हैं। यदि यह ऑपरेटर से अधिक था, जिसे अब धक्का दिया जाना है, तो पहले कम करें, फिर वर्तमान ऑपरेटर को धक्का दें। केवल मुर्गियों को संभालने के लिए 'पिछले' संचालक की पूर्वता को बचाएं, और उस पस्टैक पर एक निशान लगाएं जो कम कर देता है जो पराग जोड़े के अंदर को हल करने के दौरान अल्गोरिथम को कम करना बंद कर देता है। समापन पारेन इनपुट के अंत के रूप में एक कमी को ट्रिगर करता है, और खुले पारे के निशान को भी हटा देता है, और 'पिछले ऑपरेशन' की पूर्ववतता को पुनर्स्थापित करता है, इसलिए पार्सिंग बंद पेरेन के बाद भी जारी रह सकता है जहां इसे छोड़ा था। यह पुनरावृत्ति के साथ या बिना किया जा सकता है (संकेत: एक '(' ...) का सामना करते समय पिछली वरीयता को संग्रहीत करने के लिए एक स्टैक का उपयोग करें। इसका सामान्यीकृत संस्करण एक पार्सर जनरेटर है जो शंटिंग यार्ड एल्ग्रोथ्म, एफ.एक्स पर लागू होता है। yacc या bison या taccle (yacc का tcl analog) का उपयोग करना।

पीटर

-Adam


4

पूर्ववर्ती पार्सिंग के लिए एक अन्य संसाधन विकिपीडिया पर ऑपरेटर-पूर्वता पार्सर प्रविष्टि है। दीजकस्ट्रा के शंटिंग यार्ड एल्गोरिदम, और एक पेड़ वैकल्पिक एल्गोरिथ्म को शामिल किया गया है, लेकिन अधिक उल्लेखनीय रूप से एक सरल मैक्रो प्रतिस्थापन एल्गोरिथ्म को कवर किया गया है जिसे किसी भी पूर्ववर्ती अज्ञानी पार्सर के सामने तुच्छ रूप से लागू किया जा सकता है:

#include <stdio.h>
int main(int argc, char *argv[]){
  printf("((((");
  for(int i=1;i!=argc;i++){
    if(argv[i] && !argv[i][1]){
      switch(argv[i]){
      case '^': printf(")^("); continue;
      case '*': printf("))*(("); continue;
      case '/': printf("))/(("); continue;
      case '+': printf(")))+((("); continue;
      case '-': printf(")))-((("); continue;
      }
    }
    printf("%s", argv[i]);
  }
  printf("))))\n");
  return 0;
}

इसे इस रूप में आमंत्रित करें:

$ cc -o parenthesise parenthesise.c
$ ./parenthesise a \* b + c ^ d / e
((((a))*((b)))+(((c)^(d))/((e))))

जो अपनी सादगी में बहुत बढ़िया है, और बहुत ही समझने योग्य है।


3
यह काफी अच्छा मोती है। लेकिन इसे विस्तारित करना (कहते हैं, फ़ंक्शन एप्लिकेशन के साथ, अंतर्निहित गुणन, उपसर्ग और पोस्टफ़िक्स ऑपरेटर, वैकल्पिक प्रकार एनोटेशन, कुछ भी) पूरी चीज़ को तोड़ देगा। दूसरे शब्दों में, यह एक सुंदर हैक है।
जारेड अपडेटिके नोव

मैं बात नहीं देखता। यह सब एक ऑपरेटर-पूर्ववर्ती पार्सिंग समस्या को एक कोष्ठक-पूर्ववर्ती पार्सिंग समस्या में बदल देता है।
लोर्ने

@ ईजेपी सुनिश्चित करें, लेकिन प्रश्न में पार्सर कोष्ठक को ठीक से संभालता है, इसलिए यह एक उचित समाधान है। यदि आपके पास एक पार्सर है जो हालांकि नहीं है, तो आप सही हैं कि यह समस्या को दूसरे क्षेत्र में ले जाता है।
एडम डेविस

4

मैंने अपनी वेब साइट पर एक अल्ट्रा कॉम्पेक्ट (1 वर्ग, <10 KiB) जावा गणित मूल्यांकनकर्ता के लिए स्रोत पोस्ट किया है । यह उस प्रकार का पुनरावर्ती वंशीय पार्सर है जिसने स्वीकृत उत्तर के पोस्टर के लिए कपाल विस्फोट का कारण बना।

यह पूर्ण वरीयता, कोष्ठक, नामांकित चर और एकल-तर्क कार्यों का समर्थन करता है।




2

मैं वर्तमान में डिज़ाइन पैटर्न और पठनीय प्रोग्रामिंग के लिए एक सीखने के उपकरण के रूप में एक नियमित अभिव्यक्ति पार्सर बनाने वाले लेखों की एक श्रृंखला पर काम कर रहा हूं। आप पठनीयकोड पर एक नज़र डाल सकते हैं । आलेख शंटिंग यार्ड एल्गोरिदम का स्पष्ट उपयोग प्रस्तुत करता है।


2

मैंने एफ # में एक अभिव्यक्ति पार्सर लिखा और इसके बारे में यहां ब्लॉग किया । यह शंटिंग यार्ड एल्गोरिदम का उपयोग करता है, लेकिन इन्फिक्स से आरपीएन में परिवर्तित होने के बजाय, मैंने गणनाओं के परिणामों को संचित करने के लिए एक दूसरा स्टैक जोड़ा। यह ऑपरेटर की पूर्वता को सही ढंग से संभालता है, लेकिन यूनिरी ऑपरेटरों का समर्थन नहीं करता है। मैंने इसे F # सीखने के लिए लिखा है, अभिव्यक्ति अभिव्यक्ति सीखने के लिए नहीं।


2

पाइपरिंग का उपयोग करके एक पायथन समाधान यहां पाया जा सकता है । पूर्वता के साथ विभिन्न ऑपरेटरों के साथ पार्सिंग इन्फिक्स अंकन काफी आम है, और इसलिए पीपरिंग में infixNotation(पूर्व में operatorPrecedence) अभिव्यक्ति बिल्डर भी शामिल है । इसके साथ आप उदाहरण के लिए "AND", "OR", "NOT" का उपयोग करके आसानी से बूलियन अभिव्यक्तियों को परिभाषित कर सकते हैं। या आप अन्य ऑपरेटरों का उपयोग करने के लिए अपने चार-फ़ंक्शन अंकगणित का विस्तार कर सकते हैं, जैसे कि! भाज्य के लिए, या मापांक के लिए '%', या क्रमपरिवर्तन और संयोजन की गणना करने के लिए P और C ऑपरेटरों को जोड़ें। आप मैट्रिक्स नोटेशन के लिए एक infix parser लिख सकते हैं, जिसमें '-1' या 'T' ऑपरेटर्स (उलटा और ट्रांसपोज़ के लिए) को हैंडल करना शामिल है। 4-फ़ंक्शन पार्सर ('के साथ' का ऑपरेटरप्रदर्शन उदाहरण!)


1

मुझे पता है कि यह एक देर से जवाब है, लेकिन मैंने सिर्फ एक छोटा पार्सर लिखा है जो सभी ऑपरेटरों (प्रीफ़िक्स, पोस्टफ़िक्स और इन्फिक्स-लेफ्ट, इनफ़िक्स-राइट और नॉनसोशियेटिव) को मनमाने ढंग से पूर्ववर्ती होने की अनुमति देता है।

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

उदाहरण के लिए कुछ सामान्य प्रकार के ऑपरेटर उदाहरण के लिए अनुपलब्ध हैं, जैसे अनुक्रमणिका का उपयोग अनुक्रमणिका तालिका (इंडेक्स] या फ़ंक्शन फ़ंक्शन (पैरामीटर-एक्सप्रेशन, ...) को कॉल करने के लिए किया जा रहा है, लेकिन मैं उन दोनों को जोड़ने जा रहा हूं, लेकिन पोस्टफ़िक्स के रूप में दोनों के बारे में सोचें परिचालक जहां '' 'और'] '' या '' ('और') '' के बीच आता है, अभिव्यक्ति पार्सर के एक अलग उदाहरण के साथ पार्स किया जाता है। क्षमा करें कि उसे छोड़ दिया गया है, लेकिन पोस्टफिक्स का हिस्सा इसमें है - बाकी को जोड़ने से कोड का आकार लगभग दोगुना हो जाएगा।

चूंकि पार्सर रैकेट कोड की सिर्फ 100 लाइनें हैं, शायद मुझे बस इसे यहां पेस्ट करना चाहिए, मुझे आशा है कि यह स्टैकओवरफ्लो की अनुमति से अधिक लंबा नहीं है।

मनमाने फैसले पर कुछ विवरण:

यदि एक कम पूर्ववर्ती पोस्टफ़िक्स ऑपरेटर कम प्रीफ़ेंस प्रीफ़िक्स ऑपरेटर के रूप में एक ही इन्फिक्स ब्लॉक के लिए प्रतिस्पर्धा कर रहा है, तो उपसर्ग ऑपरेटर जीतता है। यह अधिकांश भाषाओं में नहीं आता है क्योंकि अधिकांश में पूर्ववर्ती पोस्टफ़िक्स ऑपरेटर कम नहीं होते हैं। - उदाहरण के लिए: ((डेटा ए) (बाएं 1 +) (पूर्व 2 नहीं) (डेटा बी) (पोस्ट 3!) (बाएं 1 +) (डेटा सी)) एक + नहीं बी है! + सी जहां नहीं है उपसर्ग ऑपरेटर और! उपसर्ग ऑपरेटर है और दोनों की तुलना में कम पूर्वता है, इसलिए वे असंगत तरीकों से समूह बनाना चाहते हैं (+ a + b b!) + c या as a (not b! + c) इन मामलों में उपसर्ग ऑपरेटर हमेशा जीतता है, इसलिए दूसरा तरीका यह है कि यह पार्स करता है

Nonassociative infix ऑपरेटर्स वास्तव में वहाँ होते हैं ताकि आपको उस ऑपरेटर का दिखावा न करना पड़े जो विभिन्न प्रकारों को वापस लौटाते हैं क्योंकि वे एक साथ अर्थ लेते हैं, लेकिन प्रत्येक के लिए अलग-अलग अभिव्यक्ति प्रकार होने के बिना यह एक कीचड़ है। इस तरह, इस एल्गोरिथ्म में, गैर-सहयोगी ऑपरेटर्स न केवल खुद के साथ बल्कि किसी भी ऑपरेटर के साथ एक ही पूर्वता के साथ जुड़ने से इनकार करते हैं। यह एक सामान्य मामला है जैसा कि <<= ==> आदि अधिकांश भाषाओं में एक दूसरे के साथ नहीं है।

विभिन्न प्रकार के ऑपरेटरों (बाएं, उपसर्ग आदि) का संबंध पूर्वता पर संबंधों को कैसे तोड़ता है, यह सामने नहीं आना चाहिए, क्योंकि यह वास्तव में विभिन्न प्रकारों के ऑपरेटरों को एक ही वरीयता देने के लिए समझ में नहीं आता है। यह एल्गोरिथ्म उन मामलों में कुछ करता है, लेकिन मैं यह पता लगाने के लिए भी परेशान नहीं हूं कि वास्तव में ऐसा क्या है क्योंकि ऐसा व्याकरण पहली जगह में एक बुरा विचार है।

#lang racket
;cool the algorithm fits in 100 lines!
(define MIN-PREC -10000)
;format (pre prec name) (left prec name) (right prec name) (nonassoc prec name) (post prec name) (data name) (grouped exp)
;for example "not a*-7+5 < b*b or c >= 4"
;which groups as: not ((((a*(-7))+5) < (b*b)) or (c >= 4))"
;is represented as '((pre 0 not)(data a)(left 4 *)(pre 5 -)(data 7)(left 3 +)(data 5)(nonassoc 2 <)(data b)(left 4 *)(data b)(right 1 or)(data c)(nonassoc 2 >=)(data 4)) 
;higher numbers are higher precedence
;"(a+b)*c" is represented as ((grouped (data a)(left 3 +)(data b))(left 4 *)(data c))

(struct prec-parse ([data-stack #:mutable #:auto]
                    [op-stack #:mutable #:auto])
  #:auto-value '())

(define (pop-data stacks)
  (let [(data (car (prec-parse-data-stack stacks)))]
    (set-prec-parse-data-stack! stacks (cdr (prec-parse-data-stack stacks)))
    data))

(define (pop-op stacks)
  (let [(op (car (prec-parse-op-stack stacks)))]
    (set-prec-parse-op-stack! stacks (cdr (prec-parse-op-stack stacks)))
    op))

(define (push-data! stacks data)
    (set-prec-parse-data-stack! stacks (cons data (prec-parse-data-stack stacks))))

(define (push-op! stacks op)
    (set-prec-parse-op-stack! stacks (cons op (prec-parse-op-stack stacks))))

(define (process-prec min-prec stacks)
  (let [(op-stack (prec-parse-op-stack stacks))]
    (cond ((not (null? op-stack))
           (let [(op (car op-stack))]
             (cond ((>= (cadr op) min-prec) 
                    (apply-op op stacks)
                    (set-prec-parse-op-stack! stacks (cdr op-stack))
                    (process-prec min-prec stacks))))))))

(define (process-nonassoc min-prec stacks)
  (let [(op-stack (prec-parse-op-stack stacks))]
    (cond ((not (null? op-stack))
           (let [(op (car op-stack))]
             (cond ((> (cadr op) min-prec) 
                    (apply-op op stacks)
                    (set-prec-parse-op-stack! stacks (cdr op-stack))
                    (process-nonassoc min-prec stacks))
                   ((= (cadr op) min-prec) (error "multiply applied non-associative operator"))
                   ))))))

(define (apply-op op stacks)
  (let [(op-type (car op))]
    (cond ((eq? op-type 'post)
           (push-data! stacks `(,op ,(pop-data stacks) )))
          (else ;assume infix
           (let [(tos (pop-data stacks))]
             (push-data! stacks `(,op ,(pop-data stacks) ,tos))))))) 

(define (finish input min-prec stacks)
  (process-prec min-prec stacks)
  input
  )

(define (post input min-prec stacks)
  (if (null? input) (finish input min-prec stacks)
      (let* [(cur (car input))
             (input-type (car cur))]
        (cond ((eq? input-type 'post)
               (cond ((< (cadr cur) min-prec)
                      (finish input min-prec stacks))
                     (else 
                      (process-prec (cadr cur)stacks)
                      (push-data! stacks (cons cur (list (pop-data stacks))))
                      (post (cdr input) min-prec stacks))))
              (else (let [(handle-infix (lambda (proc-fn inc)
                                          (cond ((< (cadr cur) min-prec)
                                                 (finish input min-prec stacks))
                                                (else 
                                                 (proc-fn (+ inc (cadr cur)) stacks)
                                                 (push-op! stacks cur)
                                                 (start (cdr input) min-prec stacks)))))]
                      (cond ((eq? input-type 'left) (handle-infix process-prec 0))
                            ((eq? input-type 'right) (handle-infix process-prec 1))
                            ((eq? input-type 'nonassoc) (handle-infix process-nonassoc 0))
                            (else error "post op, infix op or end of expression expected here"))))))))

;alters the stacks and returns the input
(define (start input min-prec stacks)
  (if (null? input) (error "expression expected")
      (let* [(cur (car input))
             (input-type (car cur))]
        (set! input (cdr input))
        ;pre could clearly work with new stacks, but could it reuse the current one?
        (cond ((eq? input-type 'pre)
               (let [(new-stack (prec-parse))]
                 (set! input (start input (cadr cur) new-stack))
                 (push-data! stacks 
                             (cons cur (list (pop-data new-stack))))
                 ;we might want to assert here that the cdr of the new stack is null
                 (post input min-prec stacks)))
              ((eq? input-type 'data)
               (push-data! stacks cur)
               (post input min-prec stacks))
              ((eq? input-type 'grouped)
               (let [(new-stack (prec-parse))]
                 (start (cdr cur) MIN-PREC new-stack)
                 (push-data! stacks (pop-data new-stack)))
               ;we might want to assert here that the cdr of the new stack is null
               (post input min-prec stacks))
              (else (error "bad input"))))))

(define (op-parse input)
  (let [(stacks (prec-parse))]
    (start input MIN-PREC stacks)
    (pop-data stacks)))

(define (main)
  (op-parse (read)))

(main)

1

यहाँ जावा में लिखा गया एक साधारण केस रिकर्सिव सॉल्यूशन है। ध्यान दें कि यह नकारात्मक संख्याओं को संभालता नहीं है, लेकिन यदि आप चाहें तो इसे जोड़ सकते हैं:

public class ExpressionParser {

public double eval(String exp){
    int bracketCounter = 0;
    int operatorIndex = -1;

    for(int i=0; i<exp.length(); i++){
        char c = exp.charAt(i);
        if(c == '(') bracketCounter++;
        else if(c == ')') bracketCounter--;
        else if((c == '+' || c == '-') && bracketCounter == 0){
            operatorIndex = i;
            break;
        }
        else if((c == '*' || c == '/') && bracketCounter == 0 && operatorIndex < 0){
            operatorIndex = i;
        }
    }
    if(operatorIndex < 0){
        exp = exp.trim();
        if(exp.charAt(0) == '(' && exp.charAt(exp.length()-1) == ')')
            return eval(exp.substring(1, exp.length()-1));
        else
            return Double.parseDouble(exp);
    }
    else{
        switch(exp.charAt(operatorIndex)){
            case '+':
                return eval(exp.substring(0, operatorIndex)) + eval(exp.substring(operatorIndex+1));
            case '-':
                return eval(exp.substring(0, operatorIndex)) - eval(exp.substring(operatorIndex+1));
            case '*':
                return eval(exp.substring(0, operatorIndex)) * eval(exp.substring(operatorIndex+1));
            case '/':
                return eval(exp.substring(0, operatorIndex)) / eval(exp.substring(operatorIndex+1));
        }
    }
    return 0;
}

}


1

एल्गोरिदम आसानी से पुनरावर्ती वंश पार्सर के रूप में सी में एन्कोड किया जा सकता है।

#include <stdio.h>
#include <ctype.h>

/*
 *  expression -> sum
 *  sum -> product | product "+" sum
 *  product -> term | term "*" product
 *  term -> number | expression
 *  number -> [0..9]+
 */

typedef struct {
    int value;
    const char* context;
} expression_t;

expression_t expression(int value, const char* context) {
    return (expression_t) { value, context };
}

/* begin: parsers */

expression_t eval_expression(const char* symbols);

expression_t eval_number(const char* symbols) {
    // number -> [0..9]+
    double number = 0;        
    while (isdigit(*symbols)) {
        number = 10 * number + (*symbols - '0');
        symbols++;
    }
    return expression(number, symbols);
}

expression_t eval_term(const char* symbols) {
    // term -> number | expression
    expression_t number = eval_number(symbols);
    return number.context != symbols ? number : eval_expression(symbols);
}

expression_t eval_product(const char* symbols) {
    // product -> term | term "*" product
    expression_t term = eval_term(symbols);
    if (*term.context != '*')
        return term;

    expression_t product = eval_product(term.context + 1);
    return expression(term.value * product.value, product.context);
}

expression_t eval_sum(const char* symbols) {
    // sum -> product | product "+" sum
    expression_t product = eval_product(symbols);
    if (*product.context != '+')
        return product;

    expression_t sum = eval_sum(product.context + 1);
    return expression(product.value + sum.value, sum.context);
}

expression_t eval_expression(const char* symbols) {
    // expression -> sum
    return eval_sum(symbols);
}

/* end: parsers */

int main() {
    const char* expression = "1+11*5";
    printf("eval(\"%s\") == %d\n", expression, eval_expression(expression).value);

    return 0;
}

अगले काम उपयोगी हो सकते हैं: युपना - सख्ती से अंकगणितीय संचालन; tinyexpr - अंकगणितीय संचालन + सी गणित फ़ंक्शन + उपयोगकर्ता द्वारा प्रदान किया गया; एमपीसी - पार्सर कॉम्बिनेटर

व्याख्या

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

number -> [0..9]+

इसके ऑपरेंड के साथ एडिशन ऑपरेटर एक और नियम है। यह numberया तो या किसी भी प्रतीक है जो sum "*" sumअनुक्रम का प्रतिनिधित्व करता है।

sum -> number | sum "+" sum

बदले numberमें कोशिश करें sum "+" sumकि number "+" numberकौन सा बदले में इसका विस्तार किया [0..9]+ "+" [0..9]+जा सकता है, 1+8जो कि सही जोड़ अभिव्यक्ति हो सकती है।

अन्य प्रतिस्थापन भी सही अभिव्यक्ति उत्पन्न करेंगे: sum "+" sum-> number "+" sum-> number "+" sum "+" sum-> number "+" sum "+" number-> number "+" number "+" number-> ->12+3+5

बिट द्वारा बिट हम उत्पादन नियमों के उर्फ व्याकरण के समान हो सकते हैं जो सभी संभव बीजीय अभिव्यक्ति को व्यक्त करते हैं।

expression -> sum
sum -> difference | difference "+" sum
difference -> product | difference "-" product
product -> fraction | fraction "*" product
fraction -> term | fraction "/" term
term -> "(" expression ")" | number
number -> digit+                                                                    

ऑपरेटर की पूर्वता को नियंत्रित करने के लिए दूसरों के खिलाफ इसके उत्पादन नियम की स्थिति में बदलाव करें। ऊपर व्याकरण देखें और ध्यान दें कि इसके लिए उत्पादन नियम *नीचे रखा गया है, इससे पहले मूल्यांकन करने के लिए +मजबूर productकिया जाएगा sum। कार्यान्वयन सिर्फ मूल्यांकन के साथ पैटर्न मान्यता को जोड़ती है और इस प्रकार उत्पादन नियमों को बारीकी से दर्शाता है।

expression_t eval_product(const char* symbols) {
    // product -> term | term "*" product
    expression_t term = eval_term(symbols);
    if (*term.context != '*')
        return term;

    expression_t product = eval_product(term.context + 1);
    return expression(term.value * product.value, product.context);
}

यहाँ हम termसबसे पहले इसे लौटाते हैं और यदि इसके उत्पादन नियम में छोड़े* जाने के बाद कोई पात्र नहीं है, तो इसे वापस लौटा दें - प्रतीकों का मूल्यांकन करने के बाद और वापस लौटना हमारे उत्पादन नियम में सही विकल्प है।term.value * product.value term "*" product

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.