2016-02-03 5 views
5

के साथ int या float पार्सिंग मैं FParsec का उपयोग करके फ़ाइल को पार्स करने का प्रयास कर रहा हूं, जिसमें या तो फ्लोट या int मान शामिल हैं। मुझे दो समस्याओं का सामना करना पड़ रहा है जिनके लिए मुझे अच्छा समाधान नहीं मिल रहा है। जब स्ट्रिंग "3.0" और pfloat3.0 वापस आ जाएगी जब एक ही स्ट्रिंग को पार्स पार्स करनेFParsec

दोनों pint32 और pfloat सफलतापूर्वक एक ही स्ट्रिंग पार्स होगा, लेकिन अलग-अलग जवाब देने, उदा pint323 वापस आ जाएगी। क्या pint32 का उपयोग कर फ़्लोटिंग पॉइंट वैल्यू को पार्स करना संभव है और यदि स्ट्रिंग "3.0" है तो यह असफल हो सकता है?

दूसरे शब्दों में, वहाँ निम्नलिखित कोड काम करने के लिए एक तरीका है:

let parseFloatOrInt lines = 
    let rec loop intvalues floatvalues lines = 
     match lines with 
     | [] -> floatvalues, intvalues 
     | line::rest -> 
      match run floatWs line with 
      | Success (r, _, _) -> loop intvalues (r::floatvalues) rest 
      | Failure _ -> 
       match run intWs line with 
       | Success (r, _, _) -> loop (r::intvalues) floatvalues rest 
       | Failure _ -> loop intvalues floatvalues rest 

    loop [] [] lines 

कोड सही तरीके से floatvalues सूची में सभी चल बिन्दु मान रखने जाएगा का यह टुकड़ा है, लेकिन क्योंकि pfloat रिटर्न "3.0" जब पार्स करने स्ट्रिंग "3", सभी पूर्णांक मान भी floatvalues सूची में रखे जाएंगे।

ऊपर कोड उदाहरण थोड़ा मेरे लिए अनाड़ी लगता है, इसलिए मैं वहाँ अनुमान लगा रहा हूँ यह करने के लिए एक बेहतर तरीका होना चाहिए। मैंने choice का उपयोग करके उन्हें संयोजित करने पर विचार किया, हालांकि दोनों पार्सर्स को काम करने के लिए उसी प्रकार को वापस करना होगा। मुझे लगता है कि मैं फ्लोट के लिए एक विकल्प के साथ एक भेदभावपूर्ण संघ बना सकता हूं और एक int के लिए एक और और pfloat से उत्पादन को |>> ऑपरेटर का उपयोग करके परिवर्तित कर सकता हूं। हालांकि, मैं सोच रहा हूं कि कोई बेहतर समाधान है या नहीं?

उत्तर

3

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

यहां बताया गया है कि मैं इसे लिखते थे:

/// The resulting type, or DSL 
type MyData = 
    | IntValue of int 
    | FloatValue of float 
    | Error // special case for all parse failures 

// Then, let's define individual parsers: 
let pMyInt = 
    pint32 
    |>> IntValue 

// this is an alternative version of float parser. 
// it ensures that the value has non-zero fractional part. 
// caveat: the naive approach would treat values like 42.0 as integer 
let pMyFloat = 
    pfloat 
    >>= (fun x -> if x % 1 = 0 then fail "Not a float" else preturn (FloatValue x)) 
let pError = 
    // this parser must consume some input, 
    // otherwise combined with `many` it would hang in a dead loop 
    skipAnyChar 
    >>. preturn Error 

// Now, the combined parser: 
let pCombined = 
    [ pMyFloat; pMyInt; pError ] // note, future parsers will be added here; 
            // mind the order as float supersedes the int, 
            // and Error must be the last 
    |> List.map (fun p -> p .>> ws) // I'm too lazy to add whitespase skipping 
            // into each individual parser 
    |> List.map attempt    // each parser is optional 
    |> choice      // on each iteration, one of the parsers must succeed 
    |> many       // a loop 

ध्यान दें, कोड के ऊपर किसी भी स्रोतों के साथ सक्षम काम कर रहे है: तार, धाराओं, या जो कुछ भी। आपके वास्तविक ऐप को फ़ाइलों के साथ काम करने की आवश्यकता हो सकती है, लेकिन यूनिट परीक्षण को केवल string list का उपयोग कर सरलीकृत किया जा सकता है।

// Now, applying the parser somewhere in the code: 
let maybeParseResult = 
    match run pCombined myStringData with 
    | Success(result, _, _) -> Some result 
    | Failure(_, _, _)  -> None // or anything that indicates general parse failure 

यूपीडी। मैंने टिप्पणियों के अनुसार कोड संपादित किया है। pMyFloat यह सुनिश्चित करने के लिए अद्यतन किया गया था कि पार्स किए गए मान में शून्य-शून्य अंश भाग है।

+0

वैसे @bytebuster, मुझे टेस्ट स्ट्रिंग पर pCombined चलाने में कुछ परेशानी हो रही है, यह त्रुटि संदेश देता है '' संयोजक 'कई को एक पार्सर पर लागू किया गया था जो इनपुट लेने के बिना सफल होता है और पार्सर को बदले बिना किसी अन्य तरीके से राज्य। ''। क्या ईओएफ को संभालने की ज़रूरत है? – Chepe

+0

ग्रेट, धन्यवाद @bytebuster! एक और बात, मैंने 'कई' संयोजक को हटा दिए जाने के बाद स्ट्रिंग '2" पर पार्सर चलाने की कोशिश की, और फिर मुझे वही समस्या मिली जो मैंने ऊपर वर्णित की थी, अर्थात् यह एक फ्लोट के रूप में लौटाया गया था क्योंकि फ्लोट पार्सर int पार्सर से पहले आता है। इसका समाधान करने के तरीके पर कोई विचार? – Chepe

+0

@chepe, कृपया अद्यतन संस्करण की जांच करें। उम्मीद है कि यह दोनों मुद्दों को संबोधित करता है। – bytebuster

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