TSV 텍스트를 구문 분석하기 위해 Raku 문법을 어떻게 정의 할 수 있습니까?


13

TSV 데이터가 있습니다

ID     Name    Email
   1   test    test@email.com
 321   stan    stan@nowhere.net

이것을 해시 목록으로 파싱하고 싶습니다.

@entities[0]<Name> eq "test";
@entities[1]<Email> eq "stan@nowhere.net";

줄 바꿈 메타 문자를 사용하여 헤더 행을 값 행에서 구분하는 데 문제가 있습니다. 내 문법 정의 :

use v6;

grammar Parser {
    token TOP       { <headerRow><valueRow>+ }
    token headerRow { [\s*<header>]+\n }
    token header    { \S+ }
    token valueRow  { [\s*<value>]+\n? }
    token value     { \S+ }
}

my $dat = q:to/EOF/;
ID     Name    Email
   1   test    test@email.com
 321   stan    stan@nowhere.net
EOF
say Parser.parse($dat);

그러나 이것은 돌아오고있다 Nil. raku의 정규 표현식에 대한 근본적인 것을 오해하고 있다고 생각합니다.


1
Nil. 피드백이 진행되는 한 꽤 불합리합니다. 디버깅을 위해 쉼표를 다운로드 하지 않은 경우 다운로드 하거나 문법의 오류보고를 어떻게 개선 할 수 있습니까?를 참조하십시오 . . 당신은 가지고 Nil당신의 패턴을 되돌아 의미를 가정 사촌. 그것에 대한 내 대답을 참조하십시오. 역 추적을 피하는 것이 좋습니다. 이에 대한 @ user0721090601의 답변을 참조하십시오. 실용성과 속도에 대해서는 JJ의 답변을 참조하십시오. 또한, "일반적으로 X를 Raku와 구문 분석하고 싶습니다. 누구든지 도울 수 있습니까?" .
raiph

문법 :: 트레이서; #works for me
p6steve

답변:


12

아마도 그것을 버리는 가장 중요한 것은 \s수평 수직 공간 일치 하는 것입니다 . 가로 공간 만 일치 시키려면을 사용 \h하고 세로 공간 만 일치 시키십시오 \v.

한 가지 작은 권장 사항은 토큰에 줄 바꿈을 포함시키지 않는 것입니다. 또한 교대 연산자를 사용 할 수 있습니다 %또는 %%그들이 이런 종류의 작업을 처리하기 위해 설계된 것 같이 :

grammar Parser {
    token TOP       { 
                      <headerRow>     \n
                      <valueRow>+ %%  \n
                    }
    token headerRow { <.ws>* %% <header> }
    token valueRow  { <.ws>* %% <value>  }
    token header    { \S+ }
    token value     { \S+ }
    token ws        { \h* }
} 

이에 대한 결과 Parser.parse($dat)는 다음과 같습니다.

「ID     Name    Email
   1   test    test@email.com
 321   stan    stan@nowhere.net
」
 headerRow => 「ID     Name    Email」
  header => 「ID」
  header => 「Name」
  header => 「Email」
 valueRow => 「   1   test    test@email.com」
  value => 「1」
  value => 「test」
  value => 「test@email.com」
 valueRow => 「 321   stan    stan@nowhere.net」
  value => 「321」
  value => 「stan」
  value => 「stan@nowhere.net」
 valueRow => 「」

문법이 모든 것을 성공적으로 파싱했음을 보여줍니다. 그러나 질문의 ​​두 번째 부분에 중점을 두어 변수에서 사용할 수 있기를 바랍니다. 그러기 위해서는이 프로젝트에 매우 간단한 액션 클래스를 제공해야합니다. 메소드가 문법의 메소드와 일치하는 클래스를 작성하십시오 (문자열 화 외에 특수 처리가 필요하지 않은 value/ 와 같은 매우 단순한 클래스는 header무시할 수 있음). 귀하의 처리를 처리하는 더 독창적이고 컴팩트 한 방법이 있지만, 나는 그림에 대한 초보적인 접근법을 사용합니다. 수업은 다음과 같습니다.

class ParserActions {
  method headerRow ($/) { ... }
  method valueRow  ($/) { ... }
  method TOP       ($/) { ... }
}

각 메소드에는 ($/)정규식 일치 변수 인 서명 이 있습니다. 이제 각 토큰에서 원하는 정보를 물어 보겠습니다. 헤더 행에서 각 헤더 값을 행으로 원합니다. 그래서:

  method headerRow ($/) { 
    my   @headers = $<header>.map: *.Str
    make @headers;
  }

그것에 한정 기호와 모든 토큰은으로 간주됩니다 Positional우리는 또한 각 개별 헤더 경기에 액세스 할 수 있도록, $<header>[0], $<header>[1], 등을하지만 그이 일치하는 객체 우리가 신속하게 캐릭터 라인 화 있도록. 이 make명령을 사용하면 다른 토큰이 우리가 만든이 특수 데이터에 액세스 할 수 있습니다.

$<value>토큰이 우리의 관심사 이기 때문에 우리의 가치 행은 동일하게 보일 것 입니다.

  method valueRow ($/) { 
    my   @values = $<value>.map: *.Str
    make @values;
  }

마지막 방법에 도달하면 해시로 배열을 작성하려고합니다.

  method TOP ($/) {
    my @entries;
    my @headers = $<headerRow>.made;
    my @rows    = $<valueRow>.map: *.made;

    for @rows -> @values {
      my %entry = flat @headers Z @values;
      @entries.push: %entry;
    }

    make @entries;
  }

여기에서 우리는 우리가 처리 된 물건에 액세스하는 방법을 볼 수 있습니다 headerRow()valueRow()당신은 사용 .made방법. 여러 valueRows가 있기 때문에 각 made값 을 얻으려면 맵을 작성해야합니다 (이것은 문법에 간단하게 문법을 작성 <header><data>하고 데이터를 여러 행으로 무시하는 상황입니다). 충분히 간단하지는 않습니다).

이제 우리는 두 개의 배열에 헤더와 행을 가지고 있으므로 단순히 for루프 에서 수행하는 해시 배열로 만드는 것 입니다. 는 flat @x Z @y단지 요소를 intercolates, 해시 할당은 우리가 무엇을 의미하는지에 관해 않지만, 당신이 원하는 해시의 배열을 얻을 수있는 다른 방법이 있습니다.

완료되면, 당신은 make그것을 made파싱하고 파싱 에서 사용할 수 있습니다 :

say Parser.parse($dat, :actions(ParserActions)).made
-> [{Email => test@email.com, ID => 1, Name => test} {Email => stan@nowhere.net, ID => 321, Name => stan} {}]

이것을 다음과 같은 방법으로 감싸는 것이 일반적입니다.

sub parse-tsv($tsv) {
  return Parser.parse($tsv, :actions(ParserActions)).made
}

그렇게하면 그냥 말할 수 있습니다

my @entries = parse-tsv($dat);
say @entries[0]<Name>;    # test
say @entries[1]<Email>;   # stan@nowhere.net

나는 행동 클래스를 다르게 작성할 것이라고 생각합니다. class Actions { has @!header; method headerRow ($/) { @!header = @<header>.map(~*); make @!header.List; }; method valueRow ($/) {make (@!header Z=> @<value>.map: ~*).Map}; method TOP ($/) { make @<valueRow>.map(*.made).List }물론 먼저 인스턴스화해야합니다 :actions(Actions.new).
브래드 길버트

@BradGilbert 네, 인스턴스화를 피하기 위해 액션 클래스를 작성하는 경향이 있지만 인스턴스화하는 경우 class Actions { has @!header; has %!entries … }valueRow가 항목을 직접 추가하여 그냥 입력하면 method TOP ($!) { make %!entries }됩니다. 그러나 이것은 결국 Raku이며 TIMTOWTDI :-)
user0721090601

이 정보 ( docs.raku.org/language/regexes#Modified_quantifier:_%,_%% )를 읽음으로써 <valueRow>+ %% \n(줄 바꿈으로 구분 된 캡처 행)을 이해한다고 생각 하지만 그 논리를 따르면 <.ws>* %% <header>"선택적 캡처 공백이 아닌 공백 "으로 구분됩니다. 뭔가 빠졌습니까?
Christopher Bottoms

@ChristopherBottoms 거의. 는 <.ws>(캡처하지 않는 <ws>것). OP는 TSV 형식이 선택적 공백으로 시작할 수 있다고 언급했습니다. 실제로, 이것은 아마도 더 나은 정의 할 것 라인 간격으로 정의 토큰 \h*\n\h*valueRow는 다음과 같이 논리적으로 정의 할 수 있도록 것이다,<header> % <.ws>
user0721090601

@ user0721090601 내가 읽은 기억하지 않습니다 %/ %%전에 "교대"영업 이익이라고. 그러나 올바른 이름입니다. (위해 그것을 사용하는 반면 |, ||사촌 항상 이상한 나를 강타했다.). 전에는이 "뒤로"기술을 생각하지 못했습니다. 그러나 반복되는 패턴과 일치하는 정규 표현식을 패턴 일치 항목 사이뿐만 아니라 양쪽 끝 (을 사용하여 %%) 또는 시작 (끝을 사용하지는 않음)으로 허용하는 좋은 관용구입니다 %. 끝에 대한 대안이지만 rule및의 논리를 시작하지는 않습니다 :s. 좋은. :)
raiph

11

TL; DR : 당신은하지 않습니다. Text::CSV모든 형식을 처리 할 수 있는를 사용하십시오 .

나는 몇 살이 Text::CSV유용한 지 보여줄 것이다 :

use Text::CSV;

my $text = q:to/EOF/;
ID  Name    Email
   1    test    test@email.com
 321    stan    stan@nowhere.net
EOF
my @data = $text.lines.map: *.split(/\t/).list;

say @data.perl;

my $csv = csv( in => @data, key => "ID");

print $csv.perl;

여기서 중요한 부분은 초기 파일을 배열로 변환하는 데이터 녹이는 것입니다 (in @data). 그러나 csv명령이 문자열을 처리 할 수 없기 때문에 필요합니다 . 데이터가 파일에 있으면 계속 진행하십시오.

마지막 줄이 인쇄됩니다 :

${"   1" => ${:Email("test\@email.com"), :ID("   1"), :Name("test")}, " 321" => ${:Email("stan\@nowhere.net"), :ID(" 321"), :Name("stan")}}%

ID 필드는 해시의 핵심이되고 모든 것은 해시 배열이됩니다.


2
실용성 때문에지지. OP가 문법을 배우는 것을 더 목표로하는지 (내 대답의 접근법) 또는 구문 분석 해야하는 경우 (응답의 접근법) 확실하지 않습니다. 어느 경우이든, 그는 잘 가야한다 :-)
user0721090601

2
같은 이유로 투표했습니다. :) OP가 정규식 의미론 (따라서 내 대답)의 관점에서 잘못한 것을 배우는 것을 목표로하고 있다고 생각했습니다. ). 팀워크. :)
raiph

7

TL; DR regex 의 역 추적. token하지 않습니다. 그렇기 때문에 패턴이 일치하지 않습니다. 이 답변은이를 설명하고 문법을 간단하게 수정하는 방법에 중점을 둡니다. 그러나 raku 정규식에 대해 배우기보다는 TSV를 구문 분석하려는 경우 반드시 수행해야 할 것입니다.

근본적인 오해?

raku의 정규 표현식에 대한 근본적인 것을 오해하고 있다고 생각합니다.

( "regexes"라는 용어가 매우 모호한 용어라는 것을 이미 알고 있다면이 섹션을 건너 뛰는 것이 좋습니다.)

당신이 오해 할 수있는 한 가지 근본적인 것은 "정규"라는 단어의 의미입니다. 다음은 사람들이 가정하는 일반적인 의미입니다.

  • 공식적인 정규 표현식.

  • 펄 정규식.

  • Perl 호환 정규 표현식 (PCRE).

  • "regexes"라는 텍스트 패턴 일치 표현식으로, 위와 유사하고 비슷한 것을 수행합니다.

이러한 의미 중 어느 것도 서로 호환되지 않습니다.

Perl 정규식은 의미 상 공식적인 정규 표현식의 상위 집합이지만 여러 가지면에서 훨씬 유용하지만 병리학 적 역 추적에 더 취약합니다 .

Perl 호환 정규식은 원래 1990 년대 후반의 표준 Perl 정규식과 동일 하다는 의미에서 Perl과 호환 가능하지만 Perl 은 PCRE 엔진을 포함한 플러그 가능한 정규식 엔진을 지원한다는 점에서 PCRE 정규식 구문은 표준과 동일하지 않습니다. 2020 년에 Perl이 기본적으로 사용하는 Perl 정규식.

"regexes"라는 텍스트 패턴 일치 표현식은 일반적으로 서로 비슷해 보이고 텍스트와 모두 일치하지만 수십, 수백 가지의 구문 변형 및 심지어 동일한 구문에 대한 의미론이 있습니다.

Raku 텍스트 패턴 일치 표현식은 일반적으로 "rules"또는 "regexes"라고합니다. "regexes"라는 용어는 구문이 정리되었지만 다른 regex와 다소 비슷하다는 사실을 전달합니다. "규칙"이라는 용어는 이들이 구문 분석 (및 그 이상)으로 확장 되는 훨씬 광범위한 기능 및 도구 세트의 일부라는 사실을 전달합니다 .

빠른 수정

위의 "regexes"라는 단어의 근본적인 측면을 벗어나서 이제는 "regex" 동작 의 기본 측면으로 전환 할 수 있습니다 .

token선언자 에 대한 문법의 세 가지 패턴을 선언자로 전환하면 regex문법이 의도 한대로 작동합니다.

grammar Parser {
    regex TOP       { <headerRow><valueRow>+ }
    regex headerRow { [\s*<header>]+\n }
    token header    { \S+ }
    regex valueRow  { [\s*<value>]+\n? }
    token value     { \S+ }
}

사이의 유일한 차이 token와는 regex이다 regex반면 역 추적이 token되지 않습니다. 그러므로:

say 'ab' ~~ regex { [ \s* a  ]+ b } # 「ab」
say 'ab' ~~ token { [ \s* a  ]+ b } # 「ab」
say 'ab' ~~ regex { [ \s* \S ]+ b } # 「ab」
say 'ab' ~~ token { [ \s* \S ]+ b } # Nil

마지막 패턴을 처리하는 동안 (이것은 종종 "regex"라고 할 수 있지만 실제 선언자는 token이 아닙니다 regex) 이전 줄에서 정규식을 처리하는 동안 일시적으로했던 것처럼을 \S삼킬 'b'것입니다. 그러나 패턴이로 선언되어 있기 때문에 token규칙 엔진 (일명 "regex engine") 은 역 추적하지 않으므로 전체 일치가 실패합니다.

이것이 OP에서 진행중인 일입니다.

올바른 수정

일반적으로 더 나은 해결책은 악의적으로 구성된 문자열이나 실수로 불행한 문자 조합이있는 문자열과 일치 할 때 느리고 심지어 매우 느리게 (프로그램 중단으로 인해 예상 할 수없는) 역 추적 동작 을 가정 하는 것입니다.

때때로 regexs가 적합합니다. 예를 들어, 일회용을 작성하고 정규식이 작업을 수행하면 완료됩니다. 괜찮아. 이것이 / ... /raku의 구문이 다음과 같이 역 추적 패턴을 선언하는 이유의 일부입니다 regex. (그런 다음 다시 당신이 쓸 수있는 / :r ... /당신이에 전환 할 경우 래 치트 - "래칫은"그래서, "철수"의 반대 의미 :r로 정규식 스위치를 token의미합니다.)

때때로 역 추적은 여전히 ​​구문 분석 컨텍스트에서 역할을합니다. 예를 들어, raku의 문법은 일반적으로 역 추적을 피하고 대신 수백 개의 rules 및 tokens를 갖지만 그럼에도 불구하고 여전히 3 regexs를 갖습니다 .


@ user0721090601 ++의 답변이 유용하기 때문에 upvoted했습니다. 또한 코드에서 관용적으로 벗어난 것처럼 보였고 중요한 것은 tokens에 붙어있는 몇 가지 사항을 설명합니다 . 그것은 당신이 선호하는 대답 일 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.