码迷,mamicode.com
首页 > 编程语言 > 详细

C/C++ Lua Parsing Engine

时间:2015-11-17 20:43:25      阅读:290      评论:0      收藏:0      [点我收藏+]

标签:

catalog

1. Lua语言简介
2. 使用 Lua 编写可嵌入式脚本
3. 嵌入和扩展: C/C++中执行Lua脚本
4. 将C/C++函数导出到Lua引擎中: 在Lua脚本中执行C/C++函数

 

1. Lua语言简介

0x1: 运行

Lua是类C的,所以,他是大小写字符敏感的,同时,Lua脚本的语句的分号是可选的(和GO语言类似)
可以像python一样,在命令行上运行lua命令后进入lua的shell中执行语句

技术分享

也可以把脚本存成一个文件,用如下命令行来运行

技术分享

0x2: 语法

1. 注释

-- 两个减号是行注释
 
--[[
 这是块注释
 这是块注释
 --]]

2. 变量

Lua的数字只有double型,64bits,可以以如下的方式表示数字

num = 1024
num = 3.0
num = 3.1416
num = 314.16e-2
num = 0.31416E1
num = 0xff
num = 0x56

布尔类型只有nil和false是 false,数字0啊,‘‘空字符串(‘\0‘)都是true
另外,需要注意的是:lua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里。变量前加local关键字的是局部变量

theGlobalVar = 50
local theLocalVar = "local variable"

3. 字符串

字符串你可以用单引号,也可以用双引号,还支持C类型的转义,比如

1. \a: 响铃
2. \b: 退格
3. \f: 表单
4. \n: 换行
5. \r: 回车
6. \t: 横向制表
7. \v: 纵向制表
8. \\: 反斜杠
9. \": 双引号
10. \‘: 单引号

下面的四种方式定义了完全相同的字符串(其中的两个中括号可以用于定义有换行的字符串)

a = alo\n123"
a = "alo\n123\""
a = \97lo\10\04923"
a = [[alo
123"]]

C语言中的NULL在Lua中是nil,比如你访问一个没有声明过的变量,就是nil,比如下面的v的值就是nil

v = UndefinedVariable

4. 控制语句

值得注意的是,Lua没有++或是+=这样的操作

1. while循环
sum = 0
num = 1
while num <= 100 do
    sum = sum + num
    num = num + 1
end
print("sum =",sum)

2. if-else分支
if age == 40 and sex == "Male" then
    print("男人四十一枝花")
elseif age > 60 and sex ~= "Female" then
    print("old man without country!")
elseif age < 20 then
    io.write("too young, too naive!\n")
else
    local age = io.read()
    print("Your age is "..age)
end
/*
上面的语句不但展示了if-else语句,也展示了
1) ~="是不等于,而不是!=
2) io库的分别从stdin和stdout读写的read和write函数
3) 字符串的拼接操作符".."
*/
 
3. for循环
从1加到100 
sum = 0
for i = 1, 100 do
    sum = sum + i
end
 
从1到100的奇数和 
sum = 0
for i = 1, 100, 2 do
    sum = sum + i
end 

从100到1的偶数和 
sum = 0
for i = 100, 1, -2 do
    sum = sum + i
end

4. until循环 
sum = 2
repeat
   sum = sum ^ 2 --幂操作
   print(sum)
until sum >1000

5. 函数

1. 递归 
function fib(n)
  if n < 2 then return 1 end
  return fib(n - 2) + fib(n - 1)
end

2. 闭包
示例一 
function newCounter()
    local i = 0
    return function()     -- anonymous function
        i = i + 1
        return i
    end
end
 
c1 = newCounter()
print(c1())  --> 1
print(c1())  --> 2

示例二 
function myPower(x)
    return function(y) return y^x end
end
 
power2 = myPower(2)
power3 = myPower(3)
 
print(power2(4)) --4的2次方
print(power3(5)) --5的3次方


3. 函数的返回值
和Go语言一样,可以一条语句上赋多个值,如
name, age, bGay = "haoel", 37, false, "haoel@hotmail.com"
//上面的代码中,因为只有3个变量,所以第四个值被丢弃

函数也可以返回多个值
function getUserInfo(id)
    print(id)
    return "haoel", 37, "haoel@hotmail.com", "http://coolshell.cn"
end
 
name, age, email, website, bGay = getUserInfo()
//上面的示例中,因为没有传id,所以函数中的id输出为nil,因为没有返回bGay,所以bGay也是nil

6. 局部函数

下面的两个函数是一样的

function foo(x) return x^2 end
foo = function(x) return x^2 end

7. Table

所谓Table其实就是一个Key Value的数据结构,它很像Javascript中的Object,或是PHP中的数组,在别的语言里叫Dict或Map

haoel = {name="ChenHao", age=37, handsome=True}

下面是table的CRUD操作

haoel.website="http://coolshell.cn/"
local age = haoel.age
haoel.handsome = false
haoel.name=nil

看上去像C/C++中的结构体,但是name,age, handsome, website都是key。我们还可以像下面这样写义Table

t = {[20]=100, [name]="ChenHao", [3.14]="PI"}
//我们可以这样访问: t[20],t["name"], t[3.14] 

数组的定义更加灵活

arr = {10,20,30,40,50}
其等价于
arr = {[1]=10, [2]=20, [3]=30, [4]=40, [5]=50}

也可以定义成不同的类型的数组,比如

arr = {"string", 100, "haoel", function() print("coolshell.cn") end}
//其中的函数可以这样调用: arr[4]() 

Lua的下标不是从0开始的,是从1开始的

for i=1, #arr do
    print(arr[i])
end
//上面的程序中:#arr的意思就是arr的长度

我们知道,Lua中的变量,如果没有local关键字,全都是全局变量,Lua也是用Table来管理全局变量的,Lua把这些全局变量放在了一个叫"_G"的Table里
我们可以用如下的方式来访问一个全局变量(假设我们这个全局变量名叫globalVar)

_G.globalVar
_G["globalVar"]

也可以通过下面的方式来遍历一个Table

for k, v in pairs(t) do
    print(k, v)
end

8. MetaTable 和 MetaMethod

MetaTable和MetaMethod是Lua中的重要的语法,MetaTable主要是用来做一些类似于C++重载操作符式的功能
比如,我们有两个分数

fraction_a = {numerator=2, denominator=3}
fraction_b = {numerator=4, denominator=7}

想实现分数间的相加:2/3 + 4/7,我们如果要执行: fraction_a + fraction_b,会报错的
所以,我们可以动用MetaTable,如下所示

fraction_op={}
function fraction_op.__add(f1, f2)
    ret = {}
    ret.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator
    ret.denominator = f1.denominator * f2.denominator
    return ret
end

为之前定义的两个table设置MetaTable: (其中的setmetatble是库函数)

setmetatable(fraction_a, fraction_op)
setmetatable(fraction_b, fraction_op)

接下来就可以正常进行分数运算了

fraction_s = fraction_a + fraction_b
//调用的是fraction_op.__add()函数

0x3: 模块

我们可以直接使用require(“model_name”)来载入别的lua文件,文件的后缀是.lua。载入的时候就直接执行那个文件了(和PHP的逻辑是一样的)

1. require函数,载入同样的lua文件时,只有第一次的时候会去执行,后面的相同的都不执行了,相当于PHP中的require_once
2. 如果要让每一次文件都会执行的话,你可以使用dofile("hello")函数,相当于PHP中的require
3. 如果要 载入后不执行,等需要的时候执行时,你可以使用 loadfile()函数,如下所示
/*
local hello = loadfile("hello")
... ...
... ...
hello()

loadfile("hello")后,文件并不执行,我们把文件赋给一个变量hello,当hello()时,才真的执行 
*/

mymod.lua

local HaosModel = {}
 
local function getname()
    return "Hao Chen"
end
 
function HaosModel.Greeting()
    print("Hello, My name is "..getname())
end
 
return HaosModel

file.lua

local hao_model = require("mymod")
hao_model.Greeting()

技术分享

Relevant Link:

http://coolshell.cn/articles/10739.html

 

2. 使用 Lua 编写可嵌入式脚本

0x1: Lua新特性

与其他脚本语言一样,Lua也有自己的一些特性

1. Lua类型: 在 Lua 中,值可以有类型,但是变量的类型都是动态决定的。nil、布尔型、数字 和 字符串 类型的工作方式与我们期望的一样
    1) Nil: 是值为 nil 的一种特殊类型,用来表示没有值 
    2) 布尔型的值可以是 truefalse 常量(Nil 也可以表示 false,任何非 nil 的值都表示 true)
    3) Lua 中所有的数字都是双精度的 
    4) 字符串是定长字符数组(因此,要在一个字符串后面附加上字符,必须对其进行拷贝)
    5) 表、函数 和线程类型都是引用。每个都可以赋值给一个变量,作为参数传递,或作为返回值从函数中返回。例如,下面是一个存储函数的例子 
/*
-- example of an anonymous function
-- returned as a value
-- see http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf
function add(x)
  return function (y) return (x + y) end
end
f = add(2)
print(type(f), f(10))
function  12
*/

2. Lua线程: 线程是通过调用内嵌函数 coroutine.create(f) 创建的一个协同例程 (co-routine),其中 f 是一个 Lua 函数。线程不会在创建时启动;相反,它是在创建之后使用 coroutine.resume(t) 启动的,其中 t 就是一个线程。每个协同例程都必须使用 coroutine.yield() 偶尔获得其他协同例程的处理器 

3. 赋值语句: Lua允许使用多种赋值语句,可以先对表达式进行求值,然后再进行赋值。例如,下面的语句 
/*
i = 3
a = {1, 3, 5, 7, 9}
i, a[i], a[i+1], b = i+1, a[i+1], a[i]
print (i, a[3], a[4], b, I)

会生成 4 7 5 nil nil。如果变量列表的个数大于值列表的个数,那么多出的变量都被赋值为 nil;因此,b 就是 nil。如果值的个数多于变量的个数,那么多出的值部分就会简单地丢弃。在 Lua 中,变量名是大小写敏感的,这可以解释为什么 I 的值是 nil
*/

4. 块(Chunk): 块可以是任何 Lua 语句序列。块可以保存到文件中,或者保存到 Lua 程序中的字符串中。每个块都是作为一个匿名函数体来执行的。因此,块可以定义局部变量和返回值。

5. 更酷的东西: Lua 具有一个标记-清理垃圾收集器。在 Lua 5.1 中,垃圾收集器是以增量方式工作的。Lua 具有完整的词法闭包。Lua 具有可靠的尾部调用语义 
在所有的工程任务中,要在编译性语言和解释性语言之间作出选择,就意味着要在这种环境中对每种语言的优缺点、权重和折中进行评测,并接受所带来的风险 

0x2: Lua 提供了高级抽象,却又没失去与硬件的关联

对高性能代码和高级编程的需要进行平衡是 Lua(一种可嵌入式脚本语言)要解决的问题。在需要时我们可以使用编译后的代码来实现底层的功能,然后调用 Lua 脚本来操作复杂的数据。由于 Lua 脚本是与编译代码独立的,因此我们可以单独修改这些脚本。使用 Lua,开发周期就非常类似于

编码 -> 编译 -> 运行 -> 编写脚本 -> 编写脚本 -> 编写脚本 

即Lua对编程开发人员来说是脚本语言,同时它对底层解析引擎中会被进行编译、链接实现高性能和硬件绑定

Relevant Link:

http://www.ibm.com/developerworks/cn/linux/l-lua.html

 

3. 嵌入和扩展: C/C++中执行Lua脚本

Lua除了语法简单并且具有功能强大的表结构(Table)之外,Lua 的强大功能使其可以与宿主语言混合使用。由于 Lua 与宿主语言的关系非常密切,因此 Lua 脚本可以对宿主语言的功能进行扩充。但是这种融合是双赢的:宿主语言同时也可以对 Lua 进行扩充。举例来说,C 函数可以调用 Lua 函数,反之亦然
Lua 与宿主语言之间的这种共生关系的核心是宿主语言是一个虚拟堆栈。虚拟堆栈与实际堆栈类似,是一种后进先出(LIFO)的数据结构,可以用来临时存储函数参数和函数结果。要从 Lua 中调用宿主语言的函数(反之亦然),调用者会将一些值压入堆栈中,并调用目标函数;被调用的函数会弹出这些参数(当然要对类型和每个参数的值进行验证),对数据进行处理,然后将结果放入堆栈中。当控制返回给调用程序时,调用程序就可以从堆栈中提取出返回值
实际上在 Lua 中使用的所有的 C 应用程序编程接口(API)都是通过堆栈来进行操作的。堆栈可以保存 Lua 的值,不过值的类型必须是调用程序和被调用者都知道的,特别是向堆栈中压入的值和从堆栈中弹出的值更是如此

0x1: 一个简单的 Lua 解释器

#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <string.h>

int main (void) 
{
    char buff[256];
    int error;
    lua_State *L = lua_open();   /* opens Lua */
    luaL_openlibs(L);
    luaopen_base(L);             /* opens the basic library */
    luaopen_table(L);            /* opens the table library */
    luaopen_io(L);               /* opens the I/O library */
    luaopen_string(L);           /* opens the string lib. */
    luaopen_math(L);             /* opens the math lib. */
    
    while (fgets(buff, sizeof(buff), stdin) != NULL) 
    { 
        error = luaL_loadbuffer(L, buff, strlen(buff), "line")  
        || lua_pcall(L, 0, 0, 0);
        if (error) 
        { 
            fprintf(stderr, "%s", lua_tostring(L, -1));
            lua_pop(L, 1);  /* pop error message from the stack */
        }
    }
    
    lua_close(L);
}

//gcc parseLuaInC.c -o parseLuaInC -llua

运行的时候会出现PANIC: unprotected error in call to Lua API (no calling environment),原因是在Lua5.1中不能直接调用luaopen_*函数,解决办法是调用luaL_openlibs()

#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <string.h>

int main (void) 
{
    char buff[256];
    int error;
    lua_State *L = lua_open();   /* opens Lua */ 
    luaL_openlibs(L);
    //luaopen_base(L);             /* opens the basic library */
    //luaopen_table(L);            /* opens the table library */
    //luaopen_io(L);               /* opens the I/O library */
    //luaopen_string(L);           /* opens the string lib. */
    //luaopen_math(L);             /* opens the math lib. */
    
    while (fgets(buff, sizeof(buff), stdin) != NULL) 
    { 
        error = luaL_loadbuffer(L, buff, strlen(buff), "line")  
        || lua_pcall(L, 0, 0, 0);
        if (error) 
        { 
            fprintf(stderr, "%s", lua_tostring(L, -1));
            lua_pop(L, 1);  /* pop error message from the stack */
        }
    }
    
    lua_close(L);
}

//gcc parseLuaInC.c -o parseLuaInC -llua

技术分享

传输是通过堆栈进行的。从 C 中调用任何 Lua 函数与这段代码类似:使用 lua_getglobal() 来获得函数,将参数压入堆栈,调用 lua_pcall(),然后处理结果。如果 Lua 函数返回 n 个值,那么第一个值的位置在堆栈的 -n 处,最后一个值在堆栈中的位置是 -1。
反之,在 Lua 中调用 C 函数也与之类似。如果您的操作系统支持动态加载,那么 Lua 可以根据需要来动态加载并调用函数。(在必须使用静态加载的操作系统中,可以对 Lua 引擎进行扩充,此时调用 C 函数时需要重新编译 Lua)

0x2: 使用 Lua脚本引擎

Lua 脚本引擎本身是由 C 语言写成的,在 C 或 C++ 中使用 Lua 脚本也相当简单

基本的初始化步骤如下 
1. 使用 lua_newstate() 创建一个新的 Lua 状态机 
2. 若有必要,调用 luaL_openlibs() 函数加载 Lua 的标准库

一旦初始化了 Lua 脚本引擎,你可以通过如下步骤执行一段 Lua 脚本
1. 使用 luaL_loadfile 加载一段 Lua 程序或脚本到 Lua 执行引擎中
2. 调用 lua_pcall 函数执行已加载的脚本

如果想在应用程序中加载Lua脚本并执行其中的函数,你必须执行被加载的Lua程序块(chunk)。刚刚加载的程序块只是编译后存放于Lua的脚本引擎中,并没有被执行。只有在程序块被执行后,Lua中的全局变量和函数才会被创建,在这之前这些任何全局变量和函数对于应用程序来说都不可用。作为Lua引擎的环境由应用程序提供给Lua脚本引擎的任何全局变量和函数也不可用。应用程序必须首先创建变量和函数,并使用函数lua_setglobal()让它们可用 

Relevant Link:

http://lua-users.org/wiki/CallingLuaFromCpp
http://gearx.googlecode.com/svn-history/r8/trunk/vc_lua_error.txt
http://www.ibm.com/developerworks/cn/linux/l-lua.html

 

4. 将C/C++函数导出到Lua引擎中: 在Lua脚本中执行C/C++函数

为了创建一个要在Lua脚本中使用的C或者C++辅助函数,C/C++应用程序就必需提供该函数体(the function body)并使用适当的Lua引擎函数让该新函数变为在Lua引擎中可用。在应用程序里将一个函数提供给在Lua引擎中进行使用所需的函数调用要将若干值压入Lua的虚拟堆栈之中,然后调用lua_setglobal()函数,就可以把应用程序中的函数作为全局函数提供给在Lua脚本引擎中使用

lua_pushcclosure (lua, concatMultiWideStrings, 0);
lua_setglobal (lua, "wcscat");

Lua为函数提供闭包概念。当一个Lua脚本引擎调用应用函数时,一个闭包允许一个应用指定一个或多个提供给应用函数的值。这些值可以被应用函数更新,这个例子使用的一个特性是通过一个与应用函数关联的计数器增量提供一个惟一值
要导出的C++函数的源代码应该具有如下所示的形式

// concatenate multiple wide strings
// const wchar_t *wcscat(wchar_t *wcharSt1, const wchar_t *wcharSt2, const wchar_t *wcharSt3, ...)
static int concatMultiWideStrings (lua_State *lua)
{
    // 使用lua_State *这个参数提供给该函数相关的session环境
    int  nPushCount = 0;
    int  nArgIndex = 1;
    //Lua脚本引擎提供了位于Lua虚拟堆栈之中的参数的个数信息
    int argc = lua_gettop(lua);
    wchar_t  tempBuffer[2048];

    if (argc > 0) 
    {
        wchar_t    *pWideString = &tempBuffer[0];
        size_t     iLen = 1;

        while (nArgIndex <= argc) 
        {
            //使用Lua引擎提供的lua_type()函数判断出参数的数据类型,从而可以跳过那些不是正确类型的参数
            if (lua_type(lua, nArgIndex) == LUA_TSTRING) 
            {
                const wchar_t *msgX = (wchar_t *) lua_tostring (lua, nArgIndex);
                while (*msgX) {*pWideString++ = *msgX++; iLen++; }
            }
            nArgIndex++;
        }
        *pWideString = 0;  // final zero terminator
        lua_pushlstring (lua, (char *)(&tempBuffer), iLen * sizeof(wchar_t));
        nPushCount++;
    }

    return nPushCount;
}

0x1: 方案优缺点

缺点
1. Lua解释器本身十分精简小巧,交互不包含任何的高级操作,如果希望通过Lua扩展宿主程序的能力,实现某些高级操作,就需要在宿主程序中用C/C++实现大量的底层操作,并导出给Lua使用(lua_pushcclosure、lua_setglobal)

Relevant Link:

http://www.oschina.net/translate/extending-a-cplusplus-application-with-lua-5-2 

 

Copyright (c) 2015 LittleHann All rights reserved

 

C/C++ Lua Parsing Engine

标签:

原文地址:http://www.cnblogs.com/LittleHann/p/4972459.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!