在表格中插入带引号和不带引号的字符串部分。

3
我一直在开发一个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循环等方法,但我真的觉得自己错过了一些显而易见的东西。难道我想太多了吗?

2个回答

3
你不能使用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

1
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"
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接