एक व्याकरण की स्थापना कैसे करें जो अस्पष्टता को संभाल सकता है


9

मैं अपने द्वारा तैयार किए गए कुछ एक्सेल जैसे सूत्रों को पार्स करने के लिए एक व्याकरण बनाने की कोशिश कर रहा हूं, जहां एक स्ट्रिंग की शुरुआत में एक विशेष चरित्र एक अलग स्रोत का संकेत देता है। उदाहरण के लिए, $एक स्ट्रिंग को सूचित कर सकता है, इसलिए " $This is text" को प्रोग्राम में एक स्ट्रिंग इनपुट के रूप में माना जाएगा और &एक फ़ंक्शन को सूचित &foo()कर सकता है , इसलिए इसे आंतरिक फ़ंक्शन के लिए कॉल के रूप में माना जा सकता है foo

समस्या यह है कि मैं व्याकरण का निर्माण ठीक से कैसे करूं। उदाहरण के लिए, यह MWE के रूप में एक सरलीकृत संस्करण है:

grammar = r'''start: instruction

?instruction: simple
            | func

STARTSYMBOL: "!"|"#"|"$"|"&"|"~"
SINGLESTR: (LETTER+|DIGIT+|"_"|" ")*
simple: STARTSYMBOL [SINGLESTR] (WORDSEP SINGLESTR)*
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: STARTSYMBOL SINGLESTR "(" [simple|func] (ARGSEP simple|func)* ")"

%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''
parser = lark.Lark(grammar, parser='earley')

तो, यह व्याकरण के साथ, जैसी चीजों: $This is a string, &foo(), &foo(#arg1), &foo($arg1,,#arg2)और &foo(!w1,w2,w3,,!w4,w5,w6)सब की उम्मीद के रूप में पार्स कर रहे हैं। लेकिन अगर मैं अपने simpleटर्मिनल में अधिक लचीलापन जोड़ना चाहता हूं , तो मुझे SINGLESTRटोकन परिभाषा के साथ चक्कर लगाना शुरू करना होगा जो सुविधाजनक नहीं है।

मैंने क्या कोशिश की है

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

  • यदि मैं कोष्ठकों को जोड़ता हूं SINGLESTR, तो मैं प्राप्त करता हूं Expected STARTSYMBOL, क्योंकि यह funcपरिभाषा के साथ मिश्रित हो रहा है और यह सोचता है कि एक फ़ंक्शन तर्क पारित किया जाना चाहिए, जो समझ में आता है।
  • यदि मैं केवल कार्यों के लिए एम्परसेंड प्रतीक को आरक्षित करने के लिए व्याकरण को पुनर्परिभाषित करता हूं और कोष्ठक को जोड़ता SINGLESTRहूं, तो मैं कोष्ठक के साथ एक स्ट्रिंग पार्स कर सकता हूं, लेकिन हर फ़ंक्शन मैं पार्स करने की कोशिश कर रहा हूं Expected LPAR

मेरा इरादा यह है कि किसी भी चीज की शुरुआत $एक SINGLESTRटोकन के रूप में की जाएगी और फिर मैं चीजों को पार्स कर सकता हूं &foo($first arg (has) parentheses,,$second arg)

मेरा समाधान, अभी के लिए, मैं अपने तार में LEFTPAR और RIGHTPAR जैसे 'बच' शब्दों का उपयोग कर रहा हूं और जब मैं पेड़ को संसाधित करता हूं तो उन को कोष्ठक में बदलने के लिए सहायक कार्य लिखा होता है। इसलिए, $This is a LEFTPARtestRIGHTPARसही पेड़ का उत्पादन करता है और जब मैं इसे संसाधित करता हूं, तो इसका अनुवाद किया जाता है This is a (test)

एक सामान्य प्रश्न तैयार करने के लिए: क्या मैं अपने व्याकरण को इस तरह से परिभाषित कर सकता हूं कि कुछ वर्ण जो व्याकरण के लिए विशेष हैं, उन्हें कुछ स्थितियों में सामान्य पात्रों के रूप में और किसी अन्य मामले में विशेष माना जाता है?


EDIT 1

एक टिप्पणी के आधार पर से jbndlrमैं अलग-अलग प्रारंभ प्रतीक के आधार पर मोड बनाने के लिए मेरी व्याकरण संशोधित:

grammar = r'''start: instruction

?instruction: simple
            | func

SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"("|")")*
FUNCNAME: (LETTER+) (LETTER+|DIGIT+|"_")* // no parentheses allowed in the func name
DB: "!" SINGLESTR (WORDSEP SINGLESTR)*
TEXT: "$" SINGLESTR
MD: "#" SINGLESTR
simple: TEXT|DB|MD
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: "&" FUNCNAME "(" [simple|func] (ARGSEP simple|func)* ")"

%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''

यह मेरे दूसरे परीक्षण के मामले में आता है (कुछ हद तक)। मैं सभी simpleप्रकार के तार (पाठ, एमडी या डीबी टोकन जिसमें कोष्ठक हो सकते हैं) और रिक्त होने वाले कार्यों को पार्स कर सकता हूं ; उदाहरण के लिए, &foo()या &foo(&bar())सही ढंग से पार्स। जिस क्षण मैंने एक फ़ंक्शन के भीतर एक तर्क रखा (कोई फर्क नहीं पड़ता कि किस प्रकार), मुझे ए UnexpectedEOF Error: Expected ampersand, RPAR or ARGSEP। अवधारणा के प्रमाण के रूप में, यदि मैं ऊपर दिए गए नए व्याकरण में SINGLESTR की परिभाषा से कोष्ठक हटाता हूं, तो सब कुछ उसी तरह काम करता है, जैसा कि मुझे करना चाहिए, लेकिन मैं एक वर्ग में वापस आ गया हूं।


आपके पास ऐसे चरित्र हैं जो पहचानते हैं कि उनके बाद क्या आ रहा है (आपका STARTSYMBOL) और आप विभाजक और कोष्ठक जोड़ते हैं जहाँ स्पष्ट होना आवश्यक है; मैं यहाँ कोई अस्पष्टता नहीं देखता। आपको अभी भी अपनी STARTSYMBOLसूची को अलग-अलग मदों में विभाजित करना होगा।
jbndlr

मैं जल्द ही एक उत्तर देने वाला हूं, जो कई दिनों से इस पर काम कर रहा है।
०३:१६ पर

मैंने एक उत्तर दिया। हालाँकि यह केवल 2 घंटे है जब तक कि बाउंटी की समय सीमा समाप्त नहीं हो जाती है, तब भी आप 24 घंटे के बाद के ग्रेस पीरियड में मैन्युअल रूप से इनाम दे सकते हैं। अगर मेरा जवाब अच्छा नहीं है, तो कृपया मुझे जल्द बताएं और मैं इसे ठीक कर दूंगा।
अपरिचित

जवाबों:


3
import lark
grammar = r'''start: instruction

?instruction: simple
            | func

MIDTEXTRPAR: /\)+(?!(\)|,,|$))/
SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"("|MIDTEXTRPAR)*
FUNCNAME: (LETTER+) (LETTER+|DIGIT+|"_")* // no parentheses allowed in the func name
DB: "!" SINGLESTR (WORDSEP SINGLESTR)*
TEXT: "$" SINGLESTR
MD: "#" SINGLESTR
simple: TEXT|DB|MD
ARGSEP: ",," // argument separator
WORDSEP: "," // word separator
CONDSEP: ";;" // condition separator
STAR: "*"
func: "&" FUNCNAME "(" [simple|func] (ARGSEP simple|func)* ")"

%import common.LETTER
%import common.WORD
%import common.DIGIT
%ignore ARGSEP
%ignore WORDSEP
'''

parser = lark.Lark(grammar, parser='earley')
parser.parse("&foo($first arg (has) parentheses,,$second arg)")

आउटपुट:

Tree(start, [Tree(func, [Token(FUNCNAME, 'foo'), Tree(simple, [Token(TEXT, '$first arg (has) parentheses')]), Token(ARGSEP, ',,'), Tree(simple, [Token(TEXT, '$second arg')])])])

मुझे आशा है कि यह वही है जो आप खोज रहे थे।

जो कुछ दिनों के लिए पागल हो गए हैं। मैंने लार्क की कोशिश की और असफल रहा। मैंने भी कोशिश की persimoniousऔर pyparsing। इन सभी अलग-अलग पार्सर्स के सभी को 'तर्क' टोकन के साथ एक ही समस्या थी कि वे सही कोष्ठक का सेवन कर रहे थे जो कि समारोह का हिस्सा था, अंततः विफल रहा क्योंकि फ़ंक्शन के कोष्ठक बंद नहीं थे।

चाल यह पता लगाने की थी कि आप एक सही कोष्ठक को कैसे परिभाषित करते हैं जो "विशेष नहीं" है। ऊपर MIDTEXTRPARदिए गए कोड में नियमित अभिव्यक्ति देखें । मैंने इसे एक सही कोष्ठक के रूप में परिभाषित किया, जिसका तर्क विच्छेद या स्ट्रिंग के अंत तक पालन नहीं किया गया है। मैंने नियमित अभिव्यक्ति एक्सटेंशन का उपयोग करके ऐसा किया है (?!...)जो केवल तभी से मेल खाता है जब तक इसका पालन नहीं किया जाता है ...लेकिन पात्रों का उपभोग नहीं करता है। सौभाग्य से यह इस विशेष नियमित अभिव्यक्ति विस्तार के अंदर स्ट्रिंग के मिलान अंत की भी अनुमति देता है।

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

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

यह समस्या इस तथ्य से संबंधित है कि आप अपने प्रश्न में जो वर्णन करते हैं वह संदर्भ-मुक्त व्याकरण नहीं है ( https://en.wikipedia.org/wiki/Context-free_grammar ) जिसके लिए लार्स जैसे पार्सर मौजूद हैं। इसके बजाय यह एक संदर्भ-संवेदनशील व्याकरण ( https://en.wikipedia.org/wiki/Context-sensitive_grammar ) है।

इसका संदर्भ संवेदी व्याकरण होने का कारण यह है कि आपको पार्सर को यह याद रखने की आवश्यकता है कि यह किसी फ़ंक्शन के अंदर नेस्टेड है, और नेस्टिंग के कितने स्तर हैं, और यह मेमोरी किसी तरह से व्याकरण के सिंटैक्स के अंदर उपलब्ध है।

EDIT2:

इसके अलावा निम्नलिखित पार्सर पर एक नज़र डालें जो संदर्भ-संवेदनशील है, और समस्या को हल करने के लिए लगता है, लेकिन नेस्टेड फ़ंक्शन की संख्या में एक घातीय समय जटिलता है, क्योंकि यह सभी संभावित फ़ंक्शन अवरोधों को पार्स करने का प्रयास करता है जब तक कि यह एक काम नहीं करता। मेरा मानना ​​है कि यह एक घातीय जटिलता है क्योंकि यह संदर्भ-मुक्त नहीं है।


_funcPrefix = '&'
_debug = False

class ParseException(Exception):
    pass

def GetRecursive(c):
    if isinstance(c,ParserBase):
        return c.GetRecursive()
    else:
        return c

class ParserBase:
    def __str__(self):
        return type(self).__name__ + ": [" + ','.join(str(x) for x in self.contents) +"]"
    def GetRecursive(self):
        return (type(self).__name__,[GetRecursive(c) for c in self.contents])

class Simple(ParserBase):
    def __init__(self,s):
        self.contents = [s]

class MD(Simple):
    pass

class DB(ParserBase):
    def __init__(self,s):
        self.contents = s.split(',')

class Func(ParserBase):
    def __init__(self,s):
        if s[-1] != ')':
            raise ParseException("Can't find right parenthesis: '%s'" % s)
        lparInd = s.find('(')
        if lparInd < 0:
            raise ParseException("Can't find left parenthesis: '%s'" % s)
        self.contents = [s[:lparInd]]
        argsStr = s[(lparInd+1):-1]
        args = list(argsStr.split(',,'))
        i = 0
        while i<len(args):
            a = args[i]
            if a[0] != _funcPrefix:
                self.contents.append(Parse(a))
                i += 1
            else:
                j = i+1
                while j<=len(args):
                    nestedFunc = ',,'.join(args[i:j])
                    if _debug:
                        print(nestedFunc)
                    try:
                        self.contents.append(Parse(nestedFunc))
                        break
                    except ParseException as PE:
                        if _debug:
                            print(PE)
                        j += 1
                if j>len(args):
                    raise ParseException("Can't parse nested function: '%s'" % (',,'.join(args[i:])))
                i = j

def Parse(arg):
    if arg[0] not in _starterSymbols:
        raise ParseException("Bad prefix: " + arg[0])
    return _starterSymbols[arg[0]](arg[1:])

_starterSymbols = {_funcPrefix:Func,'$':Simple,'!':DB,'#':MD}

P = Parse("&foo($first arg (has)) parentheses,,&f($asdf,,&nested2($23423))),,&second(!arg,wer))")
print(P)

import pprint
pprint.pprint(P.GetRecursive())

1
धन्यवाद, यह इरादा के अनुसार काम करता है! इनाम को पुरस्कृत किया क्योंकि आपको किसी भी तरह से कोष्ठक से बचने की आवश्यकता नहीं है। आप अतिरिक्त मील गए और यह दिखाता है! एक कोष्ठक के साथ समाप्त होने वाले 'पाठ' तर्क के किनारे का मामला अभी भी है, लेकिन मुझे बस उसी के साथ रहना होगा। आपने अस्पष्टताओं को भी स्पष्ट तरीके से समझाया और मुझे बस यह परखने की आवश्यकता है कि थोड़ा और अधिक, लेकिन मुझे लगता है कि मेरे उद्देश्यों के लिए यह बहुत अच्छा काम करेगा। संदर्भ-संवेदनशील व्याकरण पर अधिक जानकारी प्रदान करने के लिए धन्यवाद। मैं वास्तव में इसकी प्रशंसा करता हूँ!
Dima1982

@ Dima1982 आपको बहुत बहुत धन्यवाद!
iliar

@ Dima1982 संपादन पर एक नज़र डालें, मैंने एक ऐसा पार्सर बनाया है जो संभवतः एक घातीय समय जटिलता की कीमत पर आपकी समस्या को हल कर सकता है। इसके अलावा, मैंने इसके बारे में सोचा और अगर आपकी समस्या व्यावहारिक मूल्य की है, तो कोष्ठक से बचना सबसे सरल उपाय हो सकता है। या फ़ंक्शन कोष्ठक बनाना कुछ और है, &उदाहरण के लिए फ़ंक्शंस तर्क सूची के अंत को परिसीमन करना ।
iliar

1

समस्या तर्क के तर्क को कोष्ठक में संलग्न है जहाँ तर्क में एक कोष्ठक हो सकता है।
संभावित समाधानों में से एक स्ट्रिंग के एक भाग होने से पहले (या) बैकस्पेस \ _ का उपयोग होता है

  SINGLESTR: (LETTER+|DIGIT+|"_"|" ") (LETTER+|DIGIT+|"_"|" "|"\("|"\)")*

सी द्वारा उपयोग किए जाने वाले इसी तरह के समाधान में स्ट्रिंग निरंतर के एक भाग के रूप में दोहरे उद्धरण (") को शामिल करना है जहां स्ट्रिंग निरंतर डबल उद्धरण में संलग्न है।

  example_string1='&f(!g\()'
  example_string2='&f(#g)'
  print(parser.parse(example_string1).pretty())
  print(parser.parse(example_string2).pretty())

आउटपुट है

   start
     func
       f
       simple   !g\(

   start
     func
      f
      simple    #g

मुझे लगता है कि यह LEFTPAR और RIGHTPAR के साथ "(" और ")" रिप्लेस करने के ओपी के अपने समाधान के समान ही बहुत सुंदर है।
iliar
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.