Raku: hiệu ứng của các điểm đánh dấu bị mất "cao hơn"

Aug 15 2020

Tập lệnh Raku sau:

#!/usr/bin/env raku
use v6.d;

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    token value { <strvalue> | <numvalue> }
    token strvalue { '"' <( <-["]>* )> '"' }
    token numvalue { '-'? \d+ [ '.' \d* ]? }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

có đầu ra sau:

「foo = 42」
 keyword => 「foo」
 value => 「42」
  numvalue => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「"Hello, World!"」
  strvalue => 「Hello, World!」

Đối với mục thứ hai, lưu ý rằng strvaluecó chứa các chuỗi giá trị mà không có dấu ngoặc kép, như dự định với thị trường chụp <(... )>. Tuy nhiên, trước sự ngạc nhiên của tôi, các trích dẫn được bao gồm trong value.

Có cách nào để giái quyết vấn đề này không?

Trả lời

6 raiph Aug 15 2020 at 22:30

TL; DR Sử dụng "nhiều công văn". [1,2] Xem câu trả lời của @ user0721090601 để được giải thích cặn kẽ về lý do tại sao mọi thứ lại như vậy. Xem @ p6steve's để biết một thay đổi thực sự thông minh đối với ngữ pháp của bạn nếu bạn muốn cú pháp số của mình khớp với Raku.

Một giải pháp nhiều công văn

Có cách nào để giái quyết vấn đề này không?

Một cách là chuyển sang nhiều công văn rõ ràng.

Bạn hiện có một valuemã thông báo gọi các biến thể giá trị được đặt tên cụ thể:

    token value { <strvalue> | <numvalue> }

Thay thế bằng:

    proto token value {*}

và sau đó đổi tên các mã thông báo được gọi theo quy tắc nhắm mục tiêu nhiều công văn ngữ pháp, do đó ngữ pháp trở thành:

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value {*}
    token value:str { '"' <( <-["]>* )> '"' }
    token value:num { '-'? \d+ [ '.' \d* ]? }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

Điều này hiển thị:

「foo = 42」
 keyword => 「foo」
 value => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「Hello, World!」

Điều này không nắm bắt các thay thế riêng lẻ theo mặc định. Chúng tôi có thể gắn bó với "nhiều công văn" nhưng giới thiệu lại cách đặt tên của các bản chụp phụ:

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value { * }
    token value:str { '"' <( $<strvalue>=(<-["]>*) )> '"' } token value:num { $<numvalue>=('-'? \d+ [ '.' \d* ]?) }
}

say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');

hiển thị:

「foo = 42」
 keyword => 「foo」
 value => 「42」
  numvalue => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「Hello, World!」
  strvalue => 「Hello, World!」

Bất ngờ

trước sự ngạc nhiên của tôi, các trích dẫn được bao gồm trong value.

Tôi cũng ngạc nhiên ban đầu. [3]

Nhưng hành vi hiện tại cũng có ý nghĩa đối với tôi ít nhất là theo các nghĩa sau:

  • Hành vi hiện tại có giá trị trong một số trường hợp;

  • Sẽ không ngạc nhiên nếu tôi mong đợi nó, điều mà tôi nghĩ rằng tôi có thể đã làm trong một số trường hợp khác;

  • Thật không dễ dàng để xem làm thế nào một người sẽ có được hành vi hiện tại nếu nó được mong muốn nhưng thay vào đó nó hoạt động như bạn (và tôi) mong đợi ban đầu;

  • Có một giải pháp, như đã đề cập ở trên.

Chú thích

[1] Sử dụng nhiều công văn [2] một giải pháp, nhưng có vẻ quá phức tạp vì vấn đề ban đầu. Có lẽ có một giải pháp đơn giản hơn. Có lẽ ai đó sẽ cung cấp nó trong một câu trả lời khác cho câu hỏi của bạn. Nếu không, tôi hy vọng rằng một ngày nào đó chúng ta có ít nhất một giải pháp đơn giản hơn nhiều. Tuy nhiên, tôi sẽ không ngạc nhiên nếu chúng tôi không nhận được một trong nhiều năm. Chúng tôi có giải pháp trên và còn nhiều việc khác phải làm.

[2] Mặc dù bạn có thể khai báo, nóimethod value:foo { ... }và viết một phương thức (với điều kiện mỗi phương thức như vậy trả về một đối tượng khớp), tôi không nghĩ Rakudo sử dụng cơ chế điều phối nhiều phương thức thông thường để gửi đến các thay thế quy tắc không phải phương thức mà thay vào đó sử dụng một NFA .

[3] Một số người có thể lập luận rằng nó "nên", "có thể", hoặc "sẽ" "là tốt nhất" nếu Raku làm như chúng tôi mong đợi. Tôi thấy rằng tôi nghĩ tốt nhất của mình nếu tôi thường tránh [sh | c | w] nói về các lỗi / tính năng trừ khi tôi sẵn sàng chấp nhận bất kỳ và tất cả những nhược điểm mà người khác đưa ra để xem xét sẵn sàng giúp đỡ để thực hiện công việc cần thiết Mọi thứ đã xong. Vì vậy, tôi chỉ nói rằng tôi hiện đang thấy nó là 10% lỗi, 90% tính năng, nhưng "có thể" chuyển sang 100% lỗi hoặc 100% tính năng tùy thuộc vào việc tôi có muốn hành vi đó hay không trong một tình huống nhất định , và tùy thuộc vào những gì người khác nghĩ.

6 user0721090601 Aug 15 2020 at 22:46

Các điểm đánh dấu <()>bắt chỉ hoạt động trong một mã thông báo nhất định. Về cơ bản, mỗi mã thông báo trả về một Matchđối tượng cho biết "Tôi đã khớp chuỗi ban đầu từ chỉ mục X ( .from) đến chỉ mục Y ( .to)", được tính đến khi chuỗi Matchđối tượng. Đó là những gì đang xảy ra với mã thông báo strvalue của bạn:

my $text = 'bar = "Hello, World!"'; my $m = MyGrammar.parse: $text; my $start = $m<value><strvalue>.from; # 7 my $end   = $m<value><strvalue>.to; # 20 say $text.substr: $start, $end - $start;  # Hello, World!

Bạn sẽ nhận thấy rằng chỉ có hai số: giá trị bắt đầu và giá trị kết thúc. Điều này cho thấy khi bạn nhìn vào valuemã thông báo bạn có, nó không thể tạo ra một kết quả trùng khớp không rõ ràng. Vì vậy, nó .fromđược đặt thành 6 và .to21.

Có hai cách để giải quyết vấn đề này: bằng cách sử dụng (a) đối tượng hành động hoặc (b) đa ngôn ngữ. Cả hai đều có lợi thế của chúng, và tùy thuộc vào cách bạn muốn sử dụng cái này trong một dự án lớn hơn, bạn có thể muốn chọn cái này hay cái kia.

Mặc dù bạn có thể xác định kỹ thuật các hành động trực tiếp trong ngữ pháp, nhưng việc thực hiện chúng thông qua một lớp riêng sẽ dễ dàng hơn nhiều. Vì vậy, chúng tôi có thể có cho bạn:

class MyActions { 
  method TOP      ($/) { make $<keyword>.made => $<value>.made }
  method keyword  ($/) { make ~$/ }
  method value    ($/) { make ($<numvalue> // $<strvalue>).made } method numvalue ($/) { make +$/ } method strvalue ($/) { make ~$/ }
}

Mỗi cấp độ makeđể chuyển các giá trị cho bất kỳ mã thông báo nào bao gồm nó. Và mã thông báo bao quanh có quyền truy cập vào các giá trị của chúng thông qua .madephương thức. Điều này thực sự hay khi thay vì làm việc với các giá trị chuỗi thuần túy, bạn muốn xử lý chúng trước bằng cách nào đó và tạo một đối tượng hoặc tương tự.

Để phân tích cú pháp, bạn chỉ cần làm:

my $m = MyGrammar.parse: $text, :actions(MyActions); say $m.made; # bar => Hello, World!

Mà thực sự là một Pairđối tượng. Bạn có thể thay đổi kết quả chính xác bằng cách sửa đổi TOPphương pháp.

Cách thứ hai bạn có thể giải quyết mọi thứ là sử dụng a multi token. Việc phát triển ngữ pháp khá phổ biến khi sử dụng thứ gì đó tương tự như

token foo { <option-A> | <option-B> }

Nhưng như bạn có thể thấy từ lớp hành động, nó yêu cầu chúng tôi kiểm tra và xem cái nào thực sự được khớp. Thay vào đó, nếu sự thay thế có thể chấp nhận được bằng cách thực hiện với |, bạn có thể sử dụng đa ngôn ngữ:

proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }

Khi bạn sử dụng <foo>trong ngữ pháp của mình, nó sẽ khớp với một trong hai phiên bản đa dạng như thể nó đã ở trong đường cơ sở <foo>. Thậm chí tốt hơn, nếu bạn đang sử dụng một lớp hành động, bạn có thể tương tự chỉ cần sử dụng $<foo>và biết nó ở đó mà không cần bất kỳ điều kiện hoặc kiểm tra nào khác.

Trong trường hợp của bạn, nó sẽ giống như sau:

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value { * }
    multi token value:sym<str> { '"' <( <-["]>* )> '"' }
    multi token value:sym<num> { '-'? \d+ [ '.' \d* ]? }
}

Giờ đây, chúng tôi có thể truy cập những thứ như bạn mong đợi ban đầu mà không cần sử dụng đối tượng hành động:

my $text = 'bar = "Hello, World!"';
my $m = MyGrammar.parse: $text;

say $m; # 「bar = "Hello, World!"」 # keyword => 「bar」 # value => 「Hello, World!」 say $m<value>; # 「Hello, World!」

Để tham khảo, bạn có thể kết hợp cả hai kỹ thuật. Đây là cách bây giờ tôi viết đối tượng hành động được cung cấp nhiều mã thông báo:

class MyActions { 
  method TOP            ($/) { make $<keyword>.made => $<value>.made } method keyword ($/) { make ~$/ } method value:sym<str> ($/) { make ~$/ } method value:sym<num> ($/) { make +$/ }
}

Đó là một chút khó hiểu hơn ở cái nhìn đầu tiên.

2 p6steve Aug 16 2020 at 03:13

Thay vì cuộn giá trị mã thông báo của riêng bạn: str & giá trị mã thông báo: num, bạn có thể muốn sử dụng kiểm tra Regex Boolean cho khớp Num (+) và Str (~) - như tôi đã giải thích ở đây và được ghi lại ở đây

token number { \S+ <?{ defined +"$/" }> } token string { \S+ <?{ defined ~"$/" }> }