Вставить части строки в кавычках и без кавычек в таблицу

Я работал над этой частью системы saycommand, которая должна отделять части строки и помещать их в таблицу, которая отправляется функции, запрашиваемой в начале строки. Это будет выглядеть, например, как !save 1 или !teleport 0 1 или !tell 5 "a private message".


Я хотел бы, чтобы эта строка превратилась в таблицу:

[[1 2 word 2 9 'more words' 1 "and more" "1 2 34"]]

(Каждая часть строки без кавычек получает свой собственный ключ, а части в кавычках группируются в ключ)

1 = 1
2 = 2
3 = word
4 = 2
5 = 9
6 = more words
7 = 1
8 = and more
9 = 1 2 34

Я пытался сделать это с помощью шаблона Lua, но я застрял, пытаясь выяснить, как захватить части строки как в кавычках, так и без кавычек. Я много чего перепробовал, но ничего не помогло.

Мои текущие попытки шаблона выглядят так:

a, d = '1 2 word 2 9 "more words" 1 "and more" "1 2 34"" ', {}

     --    previous attempts
    --[[
         This one captures quotes
     a:gsub('(["\'])(.-)%1', function(a, b) table.insert(d, b) end)

         This one captures some values and butchered quotes,
         which might have to do with spaces in the string
     a:gsub('(["%s])(.-)%1', function(a, b) table.insert(d, b) end)

         This one captures every value, but doesn't take care of quotes
     a:gsub('(%w+)', function(a) table.insert(d, a) end)

         This one tries making %s inside of quotes into underscores to
         ignore them there, but it doesn't work
     a = a:gsub('([%w"\']+)', '%1_')
     a:gsub('(["\'_])(.-)%1', function(a, b) table.insert(d, b) end)
     a:gsub('([%w_]+)', function(a) table.insert(d, a) end)

         This one was a wild attempt at cracking it, but no success
     a:gsub('["\']([^"\']-)["\'%s]', function(a) table.insert(d, a) end)
    --]]

    --    This one adds spaces, which would later be trimmed off, to test
    --    whether it helped with the butchered strings, but it doesn't
a = a:gsub('(%w)(%s)(%w)', '%1%2%2%3')
a:gsub('(["\'%s])(.-)%1', function(a, b) table.insert(d, b) end)
for k, v in pairs(d) do
    print(k..' = '..v)
end

Это не нужно для простых команд, но для более сложных, таких как !tell 1 2 3 4 5 "a private message sent to five people", это необходимо, во-первых, чтобы проверить, отправлено ли оно нескольким людям, а затем, чтобы узнать, что это за сообщение.

В дальнейшем я хочу добавить такие команды, как !give 1 2 3 "component:material_iron:weapontype" "food:calories", которые должны добавлять два элемента трем разным людям, что принесет большую пользу от такой системы.


Если это невозможно в шаблоне Lua, я попробую сделать это с помощью циклов for и тому подобного, но я действительно чувствую, что упускаю что-то очевидное. Я слишком много думаю об этом?


person Alistaire    schedule 16.12.2014    source источник


Ответы (2)


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

function split(s)
    local t={}
    local n=0
    local b,e=0,0
    while true do
        b,e=s:find("%s*",e+1)
        b=e+1
        if b>#s then break end
        n=n+1
        if s:sub(b,b)=="'" then
            b,e=s:find(".-'",b+1)
            t[n]=s:sub(b,e-1)
        elseif s:sub(b,b)=='"' then
            b,e=s:find('.-"',b+1)
            t[n]=s:sub(b,e-1)
        else
            b,e=s:find("%S+",b)
            t[n]=s:sub(b,e)
        end
    end
    return t
end

s=[[1 2 word 2 9 'more words' 1 "and more" "1 2 34"]]
print(s)
t=split(s)
for k,v in ipairs(t) do
    print(k,v)
end
person lhf    schedule 17.12.2014

Шаблоны строк Lua и регулярные выражения в этом отношении обычно не очень подходят, когда вам нужно выполнить синтаксический анализ, требующий различных уровней вложенности или балансировки количества токенов, таких как круглые скобки ( ). Но есть еще один инструмент, доступный для Lua, достаточно мощный, чтобы справиться с этим требованием: LPeg.

Синтаксис LPeg немного архаичен и требует некоторого привыкания, поэтому вместо этого я буду использовать модуль lpeg re, чтобы упростить его усвоение. Имейте в виду, что все, что вы можете сделать в одной форме синтаксиса, вы также можете выразить в другой форме.

Я начну с определения грамматики для разбора описания вашего формата:

local re = require 're'
local cmdgrammar =
  [[
    saycmd  <- '!' cmd extra
    cmd     <- %a%w+
    extra   <- (singlequote / doublequote / unquote / .)*
    unquote <- %w+
    singlequote   <- "'" (unquote / %s)* "'"
    doublequote   <- '"' (unquote / %s)* '"'
  ]]

Затем скомпилируйте грамматику и используйте ее, чтобы сопоставить некоторые из ваших тестовых примеров:

local cmd_parser = re.compile(cmdgrammar)
local saytest = 
{
  [[!save 1 2 word 2 9 'more words' 1 "and more" "1 2 34"]],
  [[!tell 5 "a private message"]],
  [[!teleport 0 1]],
  [[!say 'another private message' 42 "foo bar" baz]],
}

В настоящее время в грамматике нет захватов, поэтому re.match возвращает последнюю позицию символа в строке, с которой он смог сопоставиться, до + 1. Это означает, что успешный синтаксический анализ вернет полное количество символов строки + 1 и, следовательно, является допустимым экземпляром. вашей грамматики.

for _, test in ipairs(saytest) do
  assert(cmd_parser:match(test) == #test + 1)
  end

Теперь самое интересное. После того, как грамматика работает должным образом, вы можете теперь добавлять захваты, которые автоматически извлекают нужные результаты в таблицу lua с относительно небольшими усилиями. Вот окончательная спецификация грамматики + таблицы:

local cmdgrammar =
  [[
    saycmd  <- '!' {| {:cmd: cmd :} {:extra: extra :} |}
    cmd     <- %a%w+
    extra   <- {| (singlequote / doublequote / { unquote } / .)* |}
    unquote <- %w+
    singlequote   <- "'" { (unquote / %s)* } "'"
    doublequote   <- '"' { (unquote / %s)* } '"'
  ]]

Снова запускаем тесты и выводим результаты re.match:

for i, test in ipairs(saytest) do
  print(i .. ':')
  dump(cmd_parser:match(test))
  end

Вы должны получить вывод, похожий на:

lua say.lua

1:
{
  extra = {
    "1",
    "2",
    "word",
    "2",
    "9",
    "more words",
    "1",
    "and more",
    "1 2 34"
  },
  cmd = "save"
}
2:
{
  extra = {
    "5",
    "a private message"
  },
  cmd = "tell"
}
3:
{
  extra = {
    "0",
    "1"
  },
  cmd = "teleport"
}
4:
{
  extra = {
    "another private message",
    "42",
    "foo bar",
    "baz"
  },
  cmd = "say"
}
person greatwolf    schedule 18.12.2014