2017-07-14 18 views
1

私はSpracheパーサーコンビネータライブラリを使用して小さなパーサーを作成しようとしています。パーサは、\という単一の行で終わる行を無視できる空白として解析できる必要があります。パーサーコンビネータを使用して '行継続'を処理する方法

質問

どのように行継続文字\が含まれていてもよい=記号の後の値を解析できるパーサを作成することができますか?例

a = b\e,\ 
    c,\ 
    d 

については は(KeyValuePair (Key, 'a'), (Value, 'b\e, c, d'))として解析されなければなりません。

このライブラリとパーサーコンビネータを一般的に使用するのは初めてです。したがって、正しい方向のポインタは非常に高く評価されています。私が試してみました何

テスト

public class ConfigurationFileGrammerTest 
{ 
    [Theory] 
    [InlineData("x\\\n y", @"x y")] 
    public void ValueIsAnyStringMayContinuedAccrossLinesWithLineContinuation(
     string input, 
     string expectedKey) 
    { 
     var key = ConfigurationFileGrammer.Value.Parse(input); 
     Assert.Equal(expectedKey, key); 
    } 
} 

生産

試み1
public static readonly Parser<string> Value = 
     from leading in Parse.WhiteSpace.Many() 
     from rest in Parse.AnyChar.Except(Parse.Char('\\')).Many() 
      .Or(Parse.String("\\\n") 
      .Then(chs => Parse.Return(chs))).Or(Parse.AnyChar.Except(Parse.LineEnd).Many()) 
     select new string(rest.ToArray()).TrimEnd(); 
テスト出力
Xunit.Sdk.EqualException: Assert.Equal() Failure 
      ↓ (pos 1) 
Expected: x y 
Actual: x\ 
      ↑ (pos 1) 
試み2 あなたが出力に行継続を含めることはできません
public static readonly Parser<string> SingleLineValue = 
     from leading in Parse.WhiteSpace.Many() 
     from rest in Parse.AnyChar.Many().Where(chs => chs.Count() < 2 || !(string.Join(string.Empty, chs.Reverse().Take(2)).Equals("\\\n"))) 
     select new string(rest.ToArray()).TrimEnd(); 

    public static readonly Parser<string> ContinuedValueLines = 
     from firsts in ContinuedValueLine.AtLeastOnce() 
     from last in SingleLineValue 
     select string.Join(" ", firsts) + " " + last; 

    public static readonly Parser<string> Value = SingleLineValue.Once().XOr(ContinuedValueLines.Once()).Select(s => string.Join(" ", s)); 
テスト出力
Xunit.Sdk.EqualException: Assert.Equal() Failure 
      ↓ (pos 1) 
Expected: x y 
Actual: x\\n y 
      ↑ (pos 1) 
+0

あなたが私の答えを見ていましたか? – amirouche

答えて

1

。これが最後の単体テストの唯一の問題です。継続\\\nを解析するときには、出力結果からそれを削除し、空の文字列を返す必要があります。申し訳ありませんが、C#spracheを使用してそれを行う方法がわかりません。たぶん、そのようなもので:

Parse.String("\\\n").Then(chs => Parse.Return('')) 

私はcombinatorix Pythonライブラリを使用して問題を解決しました。これはパーサーコンビネータライブラリです。 APIは、連鎖したメソッドを使用する代わりに関数を使用しますが、アイデアは同じです。ここで

は、コメントとの完全なコードです:

# `apply` return a parser that doesn't consume the input stream. It 
# applies a function (or lambda) to the output result of a parser. 
# The following parser, will remove whitespace from the beginning 
# and the end of what is parsed. 
strip = apply(lambda x: x.strip()) 

# parse a single equal character 
equal = char('=') 

# parse the key part of a configuration line. Since the API is 
# functional it reads "inside-out". Note, the use of the special 
# `unless(predicate, parser)` parser. It is sometime missing from 
# parser combinator libraries. What it does is use `parser` on the 
# input stream if the `predicate` parser fails. It allows to execute 
# under some conditions. It's similar in spirit to negation in prolog. 
# It does parse *anything until an equal sign*, "joins" the characters 
# into a string and strips any space starting or ending the string. 
key = strip(join(one_or_more(unless(equal, anything)))) 

# parse a single carriage return character 
eol = char('\n') 

# returns a parser that return the empty string, this is a constant 
# parser (aka. it always output the same thing). 
return_empty_space = apply(lambda x: '') 
# This will parse a full continuation (ie. including the space 
# starting the new line. It does parse *the continuation string then 
# zero or more spaces* and return the empty string 
continuation = return_empty_space(sequence(string('\\\n'), zero_or_more(char(' ')))) 

# `value` is the parser for the value part. Unless the current char 
# is a `eol` (aka. \n) it tries to parse a continuation, otherwise it 
# parse anything. It does that at least once, ie. the value can not be 
# empty. Then, it "joins" all the chars into a single string and 
# "strip" from any space that start or end the value. 
value = strip(join(one_or_more(unless(eol, either(continuation, anything))))) 

# this basically, remove the element at index 1 and only keep the 
# elements at 0 and 2 in the result. See below. 
kv_apply = apply(lambda x: (x[0], x[2])) 

# This is the final parser for a given kv pair. A kv pair is: 
# 
# - a key part (see key parser) 
# - an equal part (see equal parser) 
# - a value part (see value parser) 
# 
# Those are used to parse the input stream in sequence (one after the 
# other). It will return three values: key, a '=' char and a value. 
# `kv_apply` will only keep the key and value part. 
kv = kv_apply(sequence(key, equal, value)) 


# This is sugar syntax, which turns the string into a stream of chars 
# and execute `kv` parser on it. 
parser = lambda string: combinatorix(string, kv) 


input = 'a = b\\e,\\\n c,\\\n d' 
assert parser(input) == ('a', 'b\\e,c,d') 
関連する問題