UP | HOME

Programming in Lua

Table of Contents

lua note.

<!– more –>

Programming in Lua

Lua Reference Manual

Basic Concpts

Type and Values

bool

lua 在进行条件测试中,nil 和 false 为 false,任何其他值都为 true。

function PrintBoolCheck(var)
    if var then
        print(tostring(var) .. " is true")
    else
        print(tostring(var) .. " is false")
    end
end

PrintBoolCheck(nil)
PrintBoolCheck(false)
PrintBoolCheck({})
--output
-- nil is false
-- false is false
-- table: 0xed7f00 is true

Metatable and Metamethod

metatable 是 lua 中修改 table 默认行为的一种机制。通过给一个 table 指定 metatable,并给该 metatable 添加一些特定的方法,就可以改变 table 的行为。
例如:
给 metatable 添加__index 元方法,可以改变索引 table 不存在字段时的行为。
给 metatable 添加__newindex 元方法,可以改变为 table 不存在字段赋值时的行为。

metatable-metamethod 实现支持继承的 class
实现 class 关键要实现以下几点:
  • 类支持构造对象,构造出的对象共用类的方法
    可以 new 对象,new 对象时调用类的构造函数
  • 子类对象可以访问父类的方法
  • 构造对象时,先调用父类构造函数,后调用子类构造函数
    通常父类的构造函数调用不封装在 class 的实现中,而是放在定义类的时候,这样可以让上层控制,父类构造函数的调用顺序,以及是否调用。
  • 子类对象拥有父类对象的属性
Tips:

在元方法__index 中取属性时,需要使用 rawget,否则会自己调用自己陷入死循环

具体实现

在该实现中,我将父类的构造函数调用也封装到 class 实现中了。

  function class(clsName, super)
      if clsName==nil then
          print("Error: className is nil")
      end
      if _G[clsName] then
          print("Error: class eixst. className = " .. clsName)
      end

      function invoke_ctor(class, obj, ...)
          if class~=nil then
              invoke_ctor(rawget(class,"super"), obj, ...)
              if type(rawget(class,"ctor"))=="function" then
                  class.ctor(obj, ...)
              end
          end
      end
      function get_func(class, key)
          if class~=nil then
              if type(rawget(class,key))=="function" then
                  return class[key]
              else
                  return get_func(rawget(class,"super"), key)
              end
          end
          print("Error: not exist member func -> " .. key)
          return nil
      end

      local cls = {}
      cls.super = super
      cls.__index = function (tbl, key)
          return get_func(cls, key)
      end
      setmetatable(cls,cls)
      cls.new = function (...)
          local obj = {}
          setmetatable(obj, cls)
          invoke_ctor(cls, obj, ...)
          return obj
      end
      return cls
  end
Tips:
  • pairs 不会列出元表中__index 的字段
  • 判断 tabel 是否含有某个字段
  -- 错误的写法:
  -- 当 svrData.IsLeader == false 而 role.data.IsLeader == true 时,下面的代码不会更新 role.data.IsLeader 的值为 false
  if rawget(svrData,"IsLeader") then
      role.data.IsLeader = svrData.IsLeader
  end
  -- 正确的写法:
  if rawget(svrData,"IsLeader")~=nil then
      role.data.IsLeader = svrData.IsLeader
  end
  -- 最好的写法:
  -- 这样即使 svrData[k] == nil 也可以对 role.data[k]进行赋值
  for k,v in pairs(svrData) do
      role.data[k] = v
  end

The Language

The Application Program Interface

The Auxiliary Library

The Standard Libraries

String Manipulation

format
-- 前/后填充
string.format(%10s, str)  --不够 10 个字符前面填充相应空格
string.format(%-10s, str) --不够 10 个字符后面填充相应空格

Lua Standalone

Incompatibilities with the Previous Version

The Complete Syntax of Lua

Usage

开发环境

lua

# windows
pacman -S mingw-w64-x86_64-lua

luarocks 包管理

# 安装 luarocks
pacman -S mingw-w64-x86_64-lua-luarocks

# 在D:\Applications\msys64\mingw64\bin\ 目录下创建 luarocks.bat 文件,将下面内容填充进去:
@echo off
echo input parameters are: %*
lua D:\Applications\msys64\mingw64\bin\luarocks %*
pause

# 下面命令在 PowerShell中执行
# 查看luarocks 环境, 会显示 Options Commands Variables Configuration 这几个方面的信息
luarocks.bat

# 配置 lua dir (默认配置应该是正确的,这一步可以省略)
luarocks.bat config lua_dir D:\Applications\msys64\mingw64\bin

# 配置 lua include dir
luarocks.bat config variables.LUA_INCDIR D:\Applications\msys64\mingw64\include

# 配置 lua lib dir
luarocks.bat config variables.LUA_LIBDIR D:\Applications\msys64\mingw64\lib

# luarocks 创建工程
cd-test
luarocks.bat init lua_test_proj

# install lpeg (TODO 当前还没有成功, 所以还是直接使用 ZeroBrane 算了)
luarocks.bat install lpeg
ERROR

IDE

ZeroBrane

ZeroBrane 自带 lua 环境,不需要额外配置。

Basic

打印代码和对应的执行结果

function locals(env_pos)
	env_pos = env_pos or 3
    local variables = {}
    local idx = 1
    while true do
        local ln, lv = debug.getlocal(env_pos, idx)
        if ln ~= nil then
            variables[ln] = lv
        else
            break
        end
        idx = 1 + idx
    end
    return variables
end

function eval(equation, variables, env_pos)
    if(type(equation) == "string") then
        local eval = loadstring("return "..equation);
        if(type(eval) == "function") then
			variables = variables or locals(env_pos)
            setfenv(eval, variables)
            return eval();
        end
    end
end

function log_exp(exp, str_fmt_count, env_pos)
    str_fmt_count = str_fmt_count or 20
    env_pos = env_pos or 4
    local result = eval(exp, nil, env_pos)
    local fmt_str = '%-' .. tostring(str_fmt_count) .. 's'
	  print(string.format(fmt_str, exp), " -- ", result)
end

log_exp("hi")

local lpeg = require "lpeg"
local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
log_exp("lpeg.match(P'a', 'abc')")

打印代码执行时间的方法

下面打印出的时间单位为秒

  local codeTimer = os.clock()
  -- ... 此处省略若干上帝代码
  print("delta time = " .. tostring(os.clock()-codeTimer))
  -- delta time = 4.928

Lpeg

lpeg

Introduction

下表列出了创建 patterns 的基础操作:

Operator Description 描述
lpeg.P(string) Matches string literally 匹配字符串
lpeg.P(n) Matches exactly n characters 准确匹配 n 个字符
lpeg.S(string) Matches any character in string (Set) 匹配字符串中的任何字符
lpeg.R("xy") Matches any character between x and y (Range) 匹配 x 和 y 之间的任何字符(范围)
patt^n Matches at least n repetitions of patt 匹配至少 n 个重复的 patt
patt^-n Matches at most n repetitions of patt 匹配至多 n 个重复的 patt
patt1 * patt2 Matches patt1 followed by patt2 匹配 patt1 紧跟着 patt2
patt1 + patt2 Matches patt1 or patt2 (ordered choice) 匹配 patt1 或 patt2
patt1 - patt2 Matches patt1 if patt2 does not match 不匹配 patt2,匹配 patt1
-patt Equivalent to ("" - patt) 等价于 "" - patt, 即:匹配 patt 失败则匹配""
#patt Matches patt but consumes no input 匹配 patt,但不消耗任何输入
lpeg.B(patt) Matches patt behind the current position, consuming no input 匹配当前位置后的 patt,不消耗任何输入
-- 下面例子中,都需要包含这里的 包引入 和 名称定义
local lpeg = require "lpeg"

local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V
Functions

lpeg.match(pattern, subject, [,init])

匹配函数。它试图将给定的模式与目标字符串进行匹配。如果匹配成功,返回匹配后第一个字符在目标字符串中的索引,或者捕获的值(如果模式捕获了任何值)。
init 为可选的整数型参数,用于设定进行匹配的开始位置。像 Lua 库中的惯例一样,负值表示从末尾开始计算。

与典型的模式匹配函数不同,match 只在 anchored 模式下工作;也就是说,它试图将模式与给定目标字符串的前缀(从 init 位置开始)相匹配,而不是与目标字符串的任意子串相匹配。因此,如果我们想在一个字符串的任何地方找到一个模式,我们必须在 Lua 中写一个循环,或者写一个可以在任何地方匹配的模式。第二种方法更简单,高效。

lpeg.type(value)
如果给定的 value 是一个 pattern,则返回字符串 "pattern",否则返回 nil

lpeg.version()
返回 LPeg 的版本号字符串

lpeg.setmaxstack(max)
设置 backtrack stack 的尺寸,LPeg 会使用 backtrack stack 更正调用和选择。默认值为 400

Basic Constructions
lpeg.P(value)

用下面的规则将一个给定的值转换成一个合适的 pattern:
如果参数是一个 pattern,则返回参数 pattern。
如果参数是一个 string,则返回匹配这个字符串的 pattern。
如果参数是一个非负整数 n, 则返回一个匹配正好是 n 个字符的字符串的 pattern。
如果参数是一个负整数 -n, 则只有在输入的字符串还剩下不到 n 个字符才会成。 lpeg.P(-n) 等同于 -lpeg.P(n) (see the unary minus operation).
如果参数是一个 boolean, value 为 true 返回总是匹配成功的 pattern,value 为 false 返回总是匹配失败的 pattern,不消耗任何输入。
如果参数是一个 table, 则被解读为一个 grammar (see Grammars)。
如果参数是一个 function, 则返回一个 pattern,等价于一个 match-time capture 用一个空字符串匹配.

local lpeg = require "lpeg"

local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V

-- P匹配字符串 从头开始找,返回第一次找到的位置+1
print(lpeg.match(P'a', 'abc'))    -- 2
print(lpeg.match(P'b', 'abc'))    -- nil
print(lpeg.match(P'ab', 'abc'))   -- 3
print(lpeg.match(P'a', 'aaa'))    -- 2
print(lpeg.match(P'ab', 'abab'))  -- 3
lpeg.B(patt)
lpeg.R({range})
local lpeg = require "lpeg"

local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V

-- R匹配范围,09代表0~9,AZ同理,也是从头开始找
print(lpeg.match(R'az', 'abc'))  -- 2
print(lpeg.match(R'aZ', 'abc'))  -- nil
print(lpeg.match(R'AZ', 'abc'))  -- nil
print(lpeg.match(R'Az', 'abc'))  -- 2
print(lpeg.match(R'az', '0abc')) -- nil
lpeg.S(string)
local lpeg = require "lpeg"

local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V

-- S集合匹配 从头开始 若字符串包含集合内元素,匹配成功(ps:那岂不是匹配成功就一定是2,后面再看)
print(lpeg.match(S'abc', 'abc')) -- 2
print(lpeg.match(S'bc',  'abc')) -- nil
print(lpeg.match(S'1a',  'abc')) -- 2
lpeg.V(v)

该操作为 grammar 创建一个非终结变量。非终结变量指的是在闭包语法中由 v 所索引的规则。
This operation creates a non-terminal (a variable) for a grammar. The created non-terminal refers to the rule indexed by v in the enclosing grammar.

lpeg.locale([table])

返回一个表格,其中包含匹配各类字符的模式,这些模式会考虑当前语言环境。该 table 有如下字段 alnum, alpha, cntrl, digit, graph, lower, print, punct, space, upper, 和 xdigit ,每个字段都包含一个相应的模式。每个模式都匹配属于其类别的任何单个字符。

如果调用时有一个参数 table,那么它就在给定的 table 内创建这些字段,并返回该 table。

patt1+patt2
local lpeg = require "lpeg"

local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V

-- +代表or(或)
-- P'a' + P'b'^1 代表开头是a 或 开头至少1个b
local a_1b = P'a' + P'b'^1
print(match(a_1b, 'ab')) --2
print(match(a_1b, 'bb')) --3
print(match(a_1b, 'bc')) --2
patt1-patt2
-- 不匹配patt2,匹配patt1
lpeg.match(S'abcd'-S'ab',  'cd') -- 	2
lpeg.match(S'abcd'-S'ab',  'ab') -- 	nil
-patt
-- 等价于 "" - patt
lpeg.match(-S'ab',  'ab') -- 	nil
lpeg.match(-S'ab',  'cd') -- 	1
#patt
-- 匹配patt,匹配成功不消耗输入
lpeg.match(#P'ab',  'ab') -- 	1
lpeg.match(#P'ab',  'cd') -- 	nil
patt1*patt2
local lpeg = require "lpeg"

local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V

-- *代表 连接
-- P'a'*P'b'^0代表开头是a且后面跟至少0个b
print(match(P'a'*P'b'^0, 'aab')) --2
print(match(P'a'*P'b'^1, 'aab')) --nil
print(match(P'a'*P'b'^1, 'abab'))--3
print(match(P'a'^0*P'b', 'aab')) --4
print(match(P'a'^1*P'b', 'aab')) --4
patt^n
local lpeg = require "lpeg"

local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V

-- ^n代表至少匹配n次 ^-n代表至多匹配n次
print(lpeg.match(P'a'^1,   'aaaa'))	--5
print(lpeg.match(P'a'^4,   'aaaa'))	--5
print(lpeg.match(P'a'^5,   'aaaa'))	--nil
print(lpeg.match(P'aa'^2,  'aaaa'))	--5
print(lpeg.match(P'ab'^2,  'ababc'))	--5
print(lpeg.match(P'ab'^-1, 'ababc'))   --3
Grammars

通过使用 Lua 变量,可以逐步定义模式,每个新模式都使用以前定义的模式。然而,这种技术并不允许定义递归模式。对于递归模式,我们需要真正的语法。

LPeg 用表格表示语法,每个条目都是一个规则。

调用 lpeg.V(v)可以创建一个模式,用来表示语法中索引为 v 的非终结符(或变量)。当这个函数被求解时,语法还不存在,所以返回的是一个对该规则的开放引用(open reference)。

当一个表被转换为一个模式时(通过调用 lpeg.P 或者需要模式的地方使用该表),它就被固定了。然后由 lpeg.V(v)创建的每一个开放引用都被纠正为指向规则,该规则在表中由 v 索引。

当一个表被固定时,其结果是一个符合其初始规则的模式。表中索引为 1 的条目定义了其初始规则。如果该条目是一个字符串,则假定它是初始规则的名称。否则,LPeg 假定条目 1 本身就是初始规则。

例如,下面的语法匹配具有相同数量的 a 和 b 的字符串:

equalcount = lpeg.P{
  "S";   -- initial rule name
  S = "a" * lpeg.V"B" + "b" * lpeg.V"A" + "",
  A = "a" * lpeg.V"S" + "b" * lpeg.V"A" * lpeg.V"A",
  B = "b" * lpeg.V"S" + "a" * lpeg.V"B" * lpeg.V"B",
} * -1
Captures

Cature 是一个 pattern,capture 可以依据其匹配的内容生成值()。

Operation What it Produces
lpeg.C(patt) the match for patt plus all captures made by patt
lpeg.Carg(n) the value of the nth extra argument to lpeg.match (matches the empty string)
lpeg.Cb(name) the values produced by the previous group capture named name (matches the empty string)
lpeg.Cc(values) the given values (matches the empty string)
lpeg.Cf(patt, func) a folding of the captures from patt
lpeg.Cg(patt [, name]) the values produced by patt, optionally tagged with name
lpeg.Cp() the current position (matches the empty string)
lpeg.Cs(patt) the match for patt with the values from nested captures replacing their matches
lpeg.Ct(patt) a table with all captures from patt
patt / string string, with some marks replaced by captures of patt
patt / number the n-th value captured by patt, or no value when number is zero.
patt / table table[c], where c is the (first) capture of patt
patt / function the returns of function applied to the captures of patt
lpeg.Cmt(patt, function) the returns of function applied to the captures of patt; the application is done at match time
lpeg.C (patt)
lpeg.Carg (n)
lpeg.Cb (name)
lpeg.Cc ([value, …])
lpeg.Cf (patt, func)
lpeg.Cg (patt [, name])
lpeg.Cp ()
lpeg.Cs (patt)
lpeg.Ct (patt)
patt / string
patt / number
patt / table
patt / function
lpeg.Cmt(patt, function)
Some Examples
使用 Pattern
local lpeg = require "lpeg"

lpeg.match(R'az'^1 * -1,  'hello ')    --	nil
lpeg.match(R'az'^1 * -1,  'hello')     --	6
lpeg.match(R'az'^1 * -P(1),  'hello')  --	6
匹配换行
--开头是\r\n 或 \r 或 \n
P'\r\n' + S'\r\n'
匹配末尾
lpeg.match(S'helloworld'^1 * -1,  'hello'))  	     -- 6
lpeg.match(S'helloworld'^1 * -1,  'helloX')) 	     -- nil
lpeg.match(S'helloworld'^1 * -P(1),  'Xhello'))    --	nil
lpeg.match(S'helloworld'^1 * -P(1),  'Xhello', 2)) --	7
匹配数字
digit = R'09' -- 一个数字
digits = digit^1 -- 任意整数
匹配浮点数
-- maybe(p)  可能有p
function maybe(p) return p^-1 end

digits = R'09'^1
sign = maybe(S'+-')
dot = '.'
exp = S'eE'
-- 浮点数 = 正负号 + 整数 + 小数 + 科学计数法
float = sign * digits * maybe(dot*digits) * maybe(exp*mpm*digits)

print(match(C(float),'3.1415926'))	--3.1415926
print(match(C(float),'3.14.15'))	--3.14
print(match(C(float),'3.14e-3'))	--3.14e-3

Simple mathematical expression parser and evaluator in Lua with LPEG

local lpeg = require"lpeg"
local C, P, R, S, V = lpeg.C, lpeg.P, lpeg.R, lpeg.S, lpeg.V

local white = S(" \t") ^ 0

local integer = white * C(R("09") ^ 1) * white / tonumber
local power   = white * C(P("^")) * white
local muldiv  = white * C(S("/*%")) * white
local addsub  = white * C(S("+-")) * white
local open    = white * P("(") * white
local close   = white * P(")") * white

local bfunc = {
    ["+"] = function(a,b) return a + b end,
    ["-"] = function(a,b) return a - b end,
    ["*"] = function(a,b) return a * b end,
    ["/"] = function(a,b) return math.floor(a / b) end,
    ["%"] = function(a,b) return a % b end,
    ["^"] = function(a,b) return a ^ b end
}

-- Insert binary node into AST
local function binary(rule)
    local function recurse(left,op,right,...)
        if op then
            return recurse(bfunc[op](left,right),...)
        else
            return left
        end
    end
    return rule / recurse
end

-- Insert unary node into AST
local function unary(rule)
    return rule / function(op,right)
        return bfunc[op](0,right)
    end
end

local grammar = P({
        "input",
        input      = V("expression") * -1,
        expression = binary( V("term") * ( addsub * V("term") )^0 ),
        term       = binary( V("factor") * ( muldiv * V("factor"))^0 ),
        factor     = binary( V("primary") * ( power * V("factor"))^0 ),
        primary    = integer
            + open * V("expression") * close
            + unary( addsub * V("primary") )
})

print(grammar:match("6%4"), 6%4)
print(grammar:match("3*(5-7^8)"), 3*(5-7^8))
print(grammar:match("5 + 2*3 - 1 + 7 * 8"), 5 + 2*3 - 1 + 7 * 8)
print(grammar:match("67 + 2 * 3 - 67 + 2/1 - 7"), 67 + 2 * 3 - 67 + 2/1 - 7)
print(grammar:match("(5*7/5) + (23) - 5 * (98-4)/(6*7-42)"), (5*7/5) + (23) - 5 * (98-4)/(6*7-42))
print(grammar:match("(2) + (17*2-30) * (5)+2 - (8/2)*4"), (2) + (17*2-30) * (5)+2 - (8/2)*4)
print(grammar:match("2*3*4/8 -   5/2*4 +  6 + 0/3"), math.floor(2*3*4/8) - math.floor(5/2)*4 +  6 + 0/3)
print(grammar:match("2*3 - 4*5 + 6/3"), 2*3 - 4*5 + 6/3)

参考资料