lpeg анализирует логический термин первого порядка

Как следует из названия, я пытаюсь разобрать, например

term(A, b, c(d, "e", 7))

в таблице Lua, например

{term, {A, b, {c, {d, "e", 7}}}}

Это грамматика, которую я построил:

local pattern = re.compile[=[
  term      <- variable / function
  argument  <- variable / lowercase /number / string
  function  <- {|lowercase {|(open argument (separator (argument / function))* close)?|}|}
  variable  <- uppercase
  lowercase <- {[a-z][A-Za-z0-9]*}
  uppercase <- {[A-Z][A-Za-z0-9]*}
  string    <- '"' {~ [^"]* ~} '"'
  number    <- {[0-9]+}
  close     <- blank ")"
  open      <- "(" blank
  separator <- blank "," blank
  blank     <- " "*
]=]

У меня следующие проблемы:

  • Он не может анализировать вложенные термины. Для приведенного выше примера возвращается только {term, {} } (в то время как с term(A, b, c) все в порядке).
  • Чтобы удалить кавычки из строк, я использовал {~ ~}, но из-за этого мне пришлось переместить все захваты из argument и term в строки ниже. Есть ли способ избежать этого?
  • Я хотел бы иметь ключ, связанный с каждым элементом, чтобы указать его тип, например, вместо A что-то вроде {value = "A", type = "variable"}. Я нашел способ сделать это с помощью {:name: :}, но порядок элементов в таблице теряется (потому что он не создает новую таблицу, а просто добавляет ключ, в данном случае variable="A" и порядок этих элементов не фиксируется ). Как я могу пометить товары, поддерживающие порядок?

person キキジキ    schedule 26.07.2013    source источник


Ответы (2)


В вашей грамматике у вас есть:

argument  <- variable / lowercase /number / string
function  <- {|lowercase {|(open argument (separator (argument / function))* close)?|}|}

Имейте в виду, что lpeg пытается сопоставить шаблоны/предикаты в правиле в том порядке, в котором они у вас есть. Как только он находит совпадение, lpeg не будет рассматривать дальнейшие возможные совпадения в этом правиле грамматики, даже если позже может быть «лучшее» совпадение.

Здесь ему не удается сопоставить вложенные вызовы функций, потому что он видит, что c может соответствовать

`argument  <- variable`

Поскольку ваш нетерминал variable указан перед function, lpeg не учитывает последний и поэтому прекращает синтаксический анализ токенов, которые идут после.

В качестве эксперимента я немного изменил вашу грамматику и добавил несколько захватов таблиц и имен для большинства интересующих вас нетерминалов.

local pattern = re.compile
[=[
  term      <- {| {:type: '' -> "term" :} term_t |}
  term_t    <- func / var
  func      <- {| {:type: '' -> "func":} {:name: func_id:} "(" arg(separator arg)* ")" |}
  func_id   <- lower / upper
  arg       <- number / string / term_t
  var       <- {| {:type: '' -> "var" :} {:name: lower / upper:} |}
  string    <- '"' {~ [^"]* ~} '"'
  lower <- {%l%w*}
  upper <- {%u%w*}
  number    <- {%d+}
  separator <- blank "," blank
  blank     <- " "*
]=]

С помощью быстрого теста шаблона:

local test = [[fun(A, b, c(d(42), "e", f, 7))]]
dump( pattern:match(test) )

Что дает следующий вывод на моей машине:

{
  {
    {
      type = "var",
      name = "A"
    },
    {
      type = "var",
      name = "b"
    },
    {
      {
        "42",
        type = "func",
        name = "d"
      },
      "e",
      {
        type = "var",
        name = "f"
      },
      "7",
      type = "func",
      name = "c"
    },
    type = "func",
    name = "fun"
  },
  type = "term"
}

Внимательно изучив приведенное выше, вы заметите, что аргументы функции появляются в индексной части таблицы в том порядке, в котором они были переданы. OTOH type и name могут появляться в любом порядке, поскольку они находятся в ассоциативной части таблицы. . Вы можете обернуть эти «атрибуты» в другую таблицу и поместить эту внутреннюю таблицу атрибутов в индексную часть внешней таблицы.

Редактировать. Здесь изменена грамматика, чтобы сделать синтаксический анализ более однородным. Я удалил захват term, чтобы избавиться от ненужных ветвей.

local pattern2 = re.compile
[=[
  term      <- term_t
  term_t    <- func / var
  func      <- {| {:type: '' -> "func":} {:name: func_id:} "(" args? ")" |}
  func_id   <- lower / upper
  arg       <- number / string / term_t
  args      <- arg (separator args)?
  var       <- {| {:type: '' -> "var" :} {:name: lower / upper:} |}
  string    <- {| {:type: '' -> "string" :}'"' {:value: [^"]* :} '"' |}
  lower     <- {%l%w*}
  upper     <- {%u%w*}
  number    <- {| {:type: '' -> "number":} {:value: %d+:} |}
  separator <- blank "," blank
  blank     <- " "*
]=]

Что дает следующее:

{
  {
    type = "var",
    name = "A"
  },
  {
    type = "var",
    name = "b"
  },
  {
    {
      {
        type = "number",
        value = "42"
      },
      type = "func",
      name = "d"
    },
    {
      type = "string",
      value = "e"
    },
    {
      type = "var",
      name = "f"
    },
    {
      type = "number",
      value = "7"
    },
    type = "func",
    name = "c"
  },
  type = "func",
  name = "fun"
}
person greatwolf    schedule 26.07.2013
comment
Большое спасибо! Пеги меня немного смутили. Я приму вашу грамматику и продолжу оттуда. Есть ли способ получить что-то подобное -› pastebin.com/m3udvahC? - person キキジキ; 27.07.2013
comment
@ キ キ ジ キ вы имеете в виду дать ветке term имя? Вероятно, это возможно, если как-то изменить грамматику. Обратите внимание, что в соответствии с текущим определением грамматики fun анализируется как функция и поэтому является потомком term. У самого term здесь нет имени. Какое имя должен взять term? Должна ли она украсть имя своего первого ребенка? Что, если у этого ребенка нет имени? - person greatwolf; 27.07.2013
comment
@ キ キ ジ キ Другая идея - просто полностью отключить захват term. В этом случае все, что вы оставили бы в AST, — это функции, переменные и другие примитивные терминалы, такие как числа, строки и т. д. Это, вероятно, нормально, поскольку не похоже, что term на самом деле добавляет какую-либо другую информацию в пример. - person greatwolf; 27.07.2013
comment
Я хотел бы иметь больше однородных элементов. В c, e и 7 выглядят так, но d и f являются таблицами. Что я хочу сделать, так это то, что каждый элемент представлен одинаково. Например, поскольку 42 является аргументом d, должна быть таблица d с ее именем и типом, а затем вложенная таблица со списком ее аргументов, например {d, {42}}. С метками типа и имени это должно быть {name=d, type=fun, {{name=42, type=num}}}. Это также нормально, если тип и имя находятся в указанных индексах, например {fun, d{{num, 42}}}. Что касается термина, я случайно назвал его термином, но это может быть и что-то другое. - person キキジキ; 27.07.2013
comment
Это немного отличается от того, что есть в pastebin, потому что я забыл там обернуть аргументы в таблицу. - person キキジキ; 27.07.2013
comment
@キキジキ Я добавил исправление грамматики. Посмотрите, подходит ли это. - person greatwolf; 27.07.2013
comment
Большой! Спасибо, что нашли время ответить на мой вопрос. - person キキジキ; 27.07.2013

Извините, у меня не было опыта работы с LPeg, но обычных паттернов Lua вполне достаточно, чтобы легко решить вашу задачу:

local str = 'term(A, b, c(d, "e", 7))'

local function convert(expr)
    return (expr:gsub('(%w+)(%b())',
        function (name, par_expr)
            return '{'..name..', {'..convert(par_expr:sub(2, -2))..'}}'
        end
    ))
end

print(convert(str))  -- {term, {A, b, {c, {d, "e", 7}}}}

Теперь просто load() преобразованная строка для создания таблицы.

person Egor Skriptunoff    schedule 26.07.2013
comment
Что ж, вы правы, это очень хорошее решение! Если я найду способ заставить работать привязки, я смогу получить больше данных (например, тип элемента) и в конечном итоге расширить их, чтобы добавить операторы, но если это слишком сложно, возможно, я выберу ваше решение. - person キキジキ; 27.07.2013