2013-05-12 6 views
9

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

level0child0 
level0child1 
    level1child0 
    level1child1 
    level2child0 
    level1child2 

जिसके परिणामस्वरूप पेड़ तो ऐसा दिखाई देगा::

[ 
    { 
    :identifier => "level0child0", 
    :children => [] 
    }, 
    { 
    :identifier => "level0child1", 
    :children => [ 
     { 
     :identifier => "level1child0", 
     :children => [] 
     }, 
     { 
     :identifier => "level1child1", 
     :children => [ 
      { 
      :identifier => "level2child0", 
      :children => [] 
      } 
     ] 
     }, 
     { 
     :identifier => "level1child2", 
     :children => [] 
     }, 
    ] 
    } 
] 

पार्सर मैं अब है कि नेस्टिंग स्तर पार्स कर सकते हैं

निम्न सिंटैक्स मैं पार्स करने के लिए प्रयास कर रहा हूँ का एक उदाहरण है 0 और 1 नोड्स, लेकिन अतीत को पार्स नहीं कर सकते हैं:

require 'parslet' 

class IndentationSensitiveParser < Parslet::Parser 

    rule(:indent) { str(' ') } 
    rule(:newline) { str("\n") } 
    rule(:identifier) { match['A-Za-z0-9'].repeat.as(:identifier) } 

    rule(:node) { identifier >> newline >> (indent >> identifier >> newline.maybe).repeat.as(:children) } 

    rule(:document) { node.repeat } 

    root :document 

end 

require 'ap' 
require 'pp' 

begin 
    input = DATA.read 

    puts '', '----- input ----------------------------------------------------------------------', '' 
    ap input 

    tree = IndentationSensitiveParser.new.parse(input) 

    puts '', '----- tree -----------------------------------------------------------------------', '' 
    ap tree 

rescue IndentationSensitiveParser::ParseFailed => failure 
    puts '', '----- error ----------------------------------------------------------------------', '' 
    puts failure.cause.ascii_tree 
end 

__END__ 
user 
    name 
    age 
recipe 
    name 
foo 
bar 

यह स्पष्ट है कि मुझे डायनेमी की आवश्यकता है सी काउंटर जो 3 इंडेंटेशन नोड्स को घोंसले स्तर पर पहचानकर्ता से मेल खाने की अपेक्षा करता है 3.

मैं इस तरह से अजमोद का उपयोग कर इंडेंटेशन संवेदनशील वाक्यविन्यास पार्सर को कैसे कार्यान्वित कर सकता हूं? क्या यह संभव है?

+0

सुनिश्चित नहीं हैं कि अगर यह बेहतर पार्स के रूप में किया/अलग-अलग चरणों का निर्माण कर रहा है। इंडेंटेशन स्तरों का बहुत अधिक संयोजन वैध और पार्स होगा, इसलिए मेरे लिए यह एक बहुत ही सरल रेखा-आधारित पार्सर को इंगित करता है जो केवल इंडेंटेशन स्तर को कैप्चर करता है, फिर कुछ ऐसा जो पार्सर आउटपुट लेता है और आपकी घोंसला वाली संरचना बनाता है। –

उत्तर

13

कुछ दृष्टिकोण हैं।

  1. इंडेंट का एक संग्रह है और एक पहचानकर्ता के रूप में प्रत्येक पंक्ति को पहचानकर दस्तावेज़ पार्स, तो एक परिवर्तन बाद में इंडेंट की संख्या के आधार पदानुक्रम को फिर से संगठित करने के लिए आवेदन।

  2. उपयोग कि मांगपत्र शामिल करने के लिए वर्तमान मांगपत्र की दुकान और अगले नोड की उम्मीद कब्जा प्लस अधिक (मैं इस दृष्टिकोण बहुत गहन जानकारी नहीं थी अगले एक घटित हुआ)

  3. एक बच्चे के रूप मैच के लिए

    नियम केवल विधियां हैं। तो आप एक विधि के रूप में 'नोड' को परिभाषित कर सकते हैं, जिसका अर्थ है कि आप पैरामीटर पास कर सकते हैं! (इस प्रकार)

यह आपको node(depth+1) के मामले में node(depth) परिभाषित करने देता है। हालांकि, इस दृष्टिकोण के साथ समस्या यह है कि node विधि स्ट्रिंग से मेल नहीं खाती है, यह एक पार्सर उत्पन्न करती है। तो एक रिकर्सिव कॉल कभी खत्म नहीं होगा।

यही कारण है कि dynamic मौजूद है। यह एक पार्सर लौटाता है जिसे तब तक हल नहीं किया जाता जब तक कि वह उस मैच से मेल नहीं खाता, जिससे आप बिना किसी समस्या के पुन: प्रयास कर सकते हैं।

निम्नलिखित कोड देखें:

require 'parslet' 

class IndentationSensitiveParser < Parslet::Parser 

    def indent(depth) 
    str(' '*depth) 
    end 

    rule(:newline) { str("\n") } 

    rule(:identifier) { match['A-Za-z0-9'].repeat(1).as(:identifier) } 

    def node(depth) 
    indent(depth) >> 
    identifier >> 
    newline.maybe >> 
    (dynamic{|s,c| node(depth+1).repeat(0)}).as(:children) 
    end 

    rule(:document) { node(0).repeat } 

    root :document 
end 

यह मेरा पसंदीदा समाधान है।

+1

आप, महोदय, मेरे दिमाग को उड़ा दिया है। मैं अब कुछ समय के लिए दृष्टिकोण संख्या 2 का प्रयास कर रहा हूं। मेरी समस्या यह थी कि मुझे 'गतिशील' की स्पष्ट समझ नहीं थी क्योंकि दस्तावेज़ जितना चाहें उतना विषय में डुबकी नहीं लेते हैं। उत्तर के लिए बहुत बहुत धन्यवाद! यह पार्सर संभवतः मेरे भविष्य में कई अन्य लोगों के लिए आधारभूत कार्य प्रदान करेगा: पी – RyanScottLewis

+0

इसके लिए बहुत कुछ धन्यवाद, मैंने इस समाधान का उपयोग https://github.com/alphagov/smartdown – heathd

0

मुझे पूरे व्याकरण के माध्यम से इंडेंटेशन प्रक्रिया के ज्ञान बुनाई के विचार को पसंद नहीं है। मैं सिर्फ इंडेंट और डेडेंट टोकन का उत्पादन करता हूं कि अन्य नियम समान रूप से "{" और "}" अक्षरों से मेल खाने के लिए उपयोग कर सकते हैं। तो मेरा समाधान है। यह एक वर्ग IndentParser है कि कोई भी पार्सर nl, indent, और decent टोकन जेनरेट करने के लिए बढ़ाया जा सकता है।

require 'parslet' 

# Atoms returned from a dynamic that aren't meant to match anything. 
class AlwaysMatch < Parslet::Atoms::Base 
    def try(source, context, consume_all) 
    succ("") 
    end 
end 
class NeverMatch < Parslet::Atoms::Base 
    attr_accessor :msg 
    def initialize(msg = "ignore") 
    self.msg = msg 
    end 
    def try(source, context, consume_all) 
    context.err(self, source, msg) 
    end 
end 
class ErrorMatch < Parslet::Atoms::Base 
    attr_accessor :msg 
    def initialize(msg) 
    self.msg = msg 
    end 
    def try(source, context, consume_all) 
    context.err(self, source, msg) 
    end 
end 

class IndentParser < Parslet::Parser 

    ## 
    # Indentation handling: when matching a newline we check the following indentation. If 
    # that indicates an indent token or detent tokens (1+) then we stick these in a class 
    # variable and the high-priority indent/dedent rules will match as long as these 
    # remain. The nl rule consumes the indentation itself. 

    rule(:indent) { dynamic {|s,c| 
    if @indent.nil? 
     NeverMatch.new("Not an indent") 
    else 
     @indent = nil 
     AlwaysMatch.new 
    end 
    }} 
    rule(:dedent) { dynamic {|s,c| 
    if @dedents.nil? or @dedents.length == 0 
     NeverMatch.new("Not a dedent") 
    else 
     @dedents.pop 
     AlwaysMatch.new 
    end 
    }} 

    def checkIndentation(source, ctx) 
    # See if next line starts with indentation. If so, consume it and then process 
    # whether it is an indent or some number of dedents. 
    indent = "" 
    while source.matches?(Regexp.new("[ \t]")) 
     indent += source.consume(1).to_s #returns a Slice 
    end 

    if @indentStack.nil? 
     @indentStack = [""] 
    end 

    currentInd = @indentStack[-1] 
    return AlwaysMatch.new if currentInd == indent #no change, just match nl 

    if indent.start_with?(currentInd) 
     # Getting deeper 
     @indentStack << indent 
     @indent = indent #tells the indent rule to match one 
     return AlwaysMatch.new 
    else 
     # Either some number of de-dents or an error 

     # Find first match starting from back 
     count = 0 
     @indentStack.reverse.each do |level| 
     break if indent == level #found it, 

     if level.start_with?(indent) 
      # New indent is prefix, so we de-dented this level. 
      count += 1 
      next 
     end 

     # Not a match, not a valid prefix. So an error! 
     return ErrorMatch.new("Mismatched indentation level") 
     end 

     @dedents = [] if @dedents.nil? 
     count.times { @dedents << @indentStack.pop } 
     return AlwaysMatch.new 
    end 
    end 
    rule(:nl)   { anynl >> dynamic {|source, ctx| checkIndentation(source,ctx) }} 

    rule(:unixnl)  { str("\n") } 
    rule(:macnl)  { str("\r") } 
    rule(:winnl)  { str("\r\n") } 
    rule(:anynl)  { unixnl | macnl | winnl } 

end 

मुझे यकीन है कि एक बहुत सुधार किया जा सकता है, लेकिन यह है कि क्या मैं अब तक के साथ आ गए हैं।

उदाहरण उपयोग:

class MyParser < IndentParser 
    rule(:colon)  { str(':') >> space? } 

    rule(:space)  { match(' \t').repeat(1) } 
    rule(:space?)  { space.maybe } 

    rule(:number)  { match['0-9'].repeat(1).as(:num) >> space? } 
    rule(:identifier) { match['a-zA-Z'] >> match["a-zA-Z0-9"].repeat(0) } 

    rule(:block)  { colon >> nl >> indent >> stmt.repeat.as(:stmts) >> dedent } 
    rule(:stmt)  { identifier.as(:id) >> nl | number.as(:num) >> nl | testblock } 
    rule(:testblock) { identifier.as(:name) >> block } 

    rule(:prgm)  { testblock >> nl.repeat } 
    root :prgm 
end 
+0

में किया है, मुझे अपने उत्तर में कोई समस्या मिली। चूंकि पीईजी लालच से गैर-निर्धारिती विकल्पों पर विचार किए बिना अगले मैच का उपभोग करते हैं, इसलिए ब्लॉक के अंदर बयान जोड़ते समय कुछ भी समर्पण/इंडेंट नियमों की जांच नहीं करता है। तो यदि एक बच्चे के ब्लॉक के बाद एक समर्पण और अधिक बयानों का पालन किया जाता है तो उन stmts को लालच को बच्चे के ब्लॉक में जोड़ा जाता है जो कि समर्पण को अनदेखा करता है। ठीक करने के लिए, या तो व्याकरण (यक) के माध्यम से समान-दांत नियमों को बुनाई दें या इनपुट स्ट्रीम में टोकन डालने की आवश्यकता होगी जैसे कि लेक्सर कैन। – webjprgm

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