Использование PetiteParser Flutter для создания FHIRPath

Я хотел бы попросить совета по использованию petitparser (я обновляю этот вопрос). Существует основанная на json грамматика под названием FHIRPath, которую я пытаюсь воссоздать в dart. Я новичок в подобных грамматиках, поэтому мне потребовалось некоторое время, чтобы понять, что я хочу, чтобы он делал (или что, по моему мнению, я хочу, чтобы он делал). Мне удалось заставить его анализировать пути json и общие функции, это выглядит примерно так:

class FhirPathGrammar extends GrammarDefinition {
  Parser start() => ref0(value).end();

  Parser value() => (ref0(parens) | ref0(dotString) | ref0(path)).star();

  Parser parens() =>
      (char('(') & ref0(value) & char(')')).map((value) => value);

  Parser dotString() =>
      (anyOf('-_') | letter() | digit() | range(0x80, 0x10FFF))
          .plus()
          .flatten();
          
  Parser path() => (char('.') & ref0(dotString)).map((value) => value);
}

Если я запускаю эту функцию:

void main() {
  var pathString = 'Patient.name.exists()';
  var definition = FhirPathGrammar();
  final parser = definition.build();
  print(parser.parse(pathString));
}

Вот результат:

[Patient, [., name], [., exists], [(, [], )]]

Все идет нормально. Но теперь, если я изменю свой класс грамматики и добавлю аналогичный синтаксический анализатор:

class FhirPathGrammar extends GrammarDefinition {
  Parser start() => ref0(value).end();

  Parser value() =>
      (ref0(parens) | ref0(dotString) | ref0(path) | ref0(equal)).star();

  Parser equal() =>
      (ref0(value) & string(' = ') & ref0(value)).map((value) => value);

  Parser parens() =>
      (char('(') & ref0(value) & char(')')).map((value) => value);

  Parser dotString() =>
      (anyOf('-_') | letter() | digit() | range(0x80, 0x10FFF))
          .plus()
          .flatten();

  Parser path() => (char('.') & ref0(dotString)).map((value) => value);
}

Я получаю сообщение об ошибке:

Unhandled exception:
Stack Overflow
#0      ChoiceParser.parseOn  package:petitparser/…/combinator/choice.dart:71
#1      PossessiveRepeatingParser.parseOn  package:petitparser/…/repeater/possessive.dart:59
#2      FlattenParser.parseOn  package:petitparser/…/action/flatten.dart:31
// Then these 4 lines repeat
#3      ChoiceParser.parseOn  package:petitparser/…/combinator/choice.dart:69
#4      PossessiveRepeatingParser.parseOn  package:petitparser/…/repeater/possessive.dart:67
#5      SequenceParser.parseOn  package:petitparser/…/combinator/sequence.dart:39
#6      MapParser.parseOn  package:petitparser/…/action/map.dart:38
// until it gets here
#9491   ChoiceParser.parseOn  package:petitparser/…/combinator/choice.dart:69
#9492   PossessiveRepeatingParser.parseOn  package:petitparser/…/repeater/possessive.dart:67
#9493   SequenceParser.parseOn  package:petitparser/…/combinator/sequence.dart:39
#9494   PickParser.parseOn  package:petitparser/…/action/pick.dart:26
#9495   CastParser.parseOn  package:petitparser/…/action/cast.dart:17
#9496   Parser.parse  package:petitparser/…/core/parser.dart:51
#9497   main  fhir_path/also_main.dart:7
#9498   _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:283:19)
#9499   _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

Как отметил @lukas-renggli, кажется, что это входит в бесконечный цикл. Так что, по крайней мере, я думаю, что это то, что происходит. Но я не думаю, что понимаю, как это соответствие вызывает бесконечный цикл.


person Grey    schedule 02.05.2021    source источник


Ответы (1)


Трудно сказать, что происходит, без минимального воспроизводимого примера. Однако я подозреваю, что один из парсеров при выборе в операторе-звезде всегда преуспевает, ничего не потребляя (ref0(empty) выглядит подозрительно). Это вызовет бесконечный цикл.

Например, следующий парсер показывает такое поведение:

epsilon().star().parse('');   // loops forever

Ответ на обновленный вопрос: ваша грамматика леворекурсивная в производство equals. Аналогичный, немного более простой пример:

// Loops forever: expression is recursively called without consuming anything.
Parser expression() => (ref0(expression) & char('+') & ref0(expression)) 
    | digit();`

// Fixes the infinite loop by forcing the grammar to consume something 
// at each step.
Parser expression() => digit().separatedBy(char('+'));

Существуют различные способы исправить ваш пример, в зависимости от того, какое именно поведение вы хотите. Самый простой вариант — переписать грамматику, чтобы в качестве разделителя между другими параметрами использовалось равенство:

Parser value() => (ref0(parens) | ref0(dotString) | ref0(path))
    .separatedBy(string(" = "));

Я рекомендую вам ознакомиться с построителем выражений. Это может упростить создание синтаксических анализаторов выражений и избежать распространенных ошибок.

person Lukas Renggli    schedule 02.05.2021
comment
Спасибо! Это определенно похоже на то, что он делает. Я обновил основной вопрос, чтобы показать свой полный код. Я, очевидно, новичок в грамматике, поэтому я не понимаю, что именно соответствует тому, что вызывает бесконечный цикл. - person Grey; 04.05.2021
comment
Я добавил к своему исходному ответу, чтобы он соответствовал вашему обновленному вопросу. - person Lukas Renggli; 04.05.2021