一篇学会好玩的Lua

开发 后端
周末看了一下lua引擎的一些实现,也体验了一下lua语言的一些东西,本文简单介绍一下,后续有时间的话再写文章分析引擎的实现。

[[414539]]

本文转载自微信公众号「编程杂技」,作者 theanarkh 。转载本文请联系编程杂技公众号。

最近体验了一下Openresty,了解到Openresty里使用lua语言来增强了Nginx的能力,所以又去了解了一下lua,lua语言小而精悍,lua引擎也值得学习。周末看了一下lua引擎的一些实现,也体验了一下lua语言的一些东西,本文简单介绍一下,后续有时间的话再写文章分析引擎的实现。

1 在c语言中嵌入lua引擎

lua引擎本身是一个库,类似V8一样,我们可以把它嵌入到其他项目中,我们首先安装相关文档安装lua(我安装的是5.1.5)。然后写个demo体验一下。

  1. #include <lua.h> 
  2. #include <lualib.h> 
  3. #include <lauxlib.h> 
  4. #include<stdio.h> 
  5.  
  6. int echo(lua_State *L) { 
  7.     printf("world"); 
  8.  
  9. int main(int argc, char *argv[]) { 
  10.     int s = 0; 
  11.     lua_State *L = lua_open(); 
  12.     // 注册个自定义的函数 
  13.     lua_register(L,"echo", echo); 
  14.     luaL_openlibs(L); 
  15.     // 执行lua脚本 
  16.     luaL_dofile(L, "hello.lua"); 
  17.     lua_close(L); 
  18.     return 1; 

编译上面的代码

  1. gcc hello.c -llua -lm -ldl 

然后写个hello.lua脚本。

  1. print("hello"); 
  2. echo(); 

执行./a.out,我们看到输出了hello world。这个是个简单的体验demo,和直接使用lua提供的命令行工具类似,只不过我们这里还拓展了一个自定义的echo函数给lua脚本调用。如果我们想动态地执行一段脚本,而不是执行一个lua文件,也是可以的。

  1. #include <lua.h> 
  2. #include <lualib.h> 
  3. #include <lauxlib.h> 
  4. const char * script = "print('hi');"
  5. int main(int argc, char *argv[]) { 
  6.     lua_State *L = lua_open(); 
  7.     luaL_openlibs(L); 
  8.     luaL_dostring(L, script); 
  9.     lua_close(L); 
  10.     return 1; 

编译执行以上代码我们会看到输出hi。以上这些似乎没什么大的作用,因为我们执行简单地使用lua语言提供的能力。而lua的能力绝不止于此,lua称为胶水语言,除了可以嵌入其他语言中,还支持拓展。下面我们看如果拓展lua的能力。

2 基于lua的demo运行时

虽然这里只是简单地拓展lua,但是这里称之为运行时是因为类似Node.js基于V8一样,我们也可以通过拓展lua来实现一个基于lua的运行时。下面我们看看怎么拓展(也就是怎么调用其他语言的代码,这里是c)。新建一个test.c文件。

  1. #include <lua.h> 
  2. #include <lualib.h 
  3. >#include <lauxlib.h> 
  4. static int test(lua_State* L){ 
  5.     //取栈第一个参数 
  6.     const char *a = luaL_checkstring(L, 1);  
  7.     //返回值入栈 
  8.     lua_pushstring(L, (const char *)"hi"); 
  9.     return 1; 
  10.  
  11. static const struct luaL_Reg reg_test[] = { 
  12.     {"test", test}, 
  13.     {NULLNULL
  14. }; 
  15.  
  16. int luaopen_test(lua_State *L) { 
  17.     const char* libName = "test"
  18.     luaL_register(L, libName, reg_test); 
  19.     return 1; 

lua和c是通过一个栈进行通信的,lua调用c函数的时候,c函数可以从栈中获取lua的参数,也可也从栈中返回执行结果给lua。我们把以上代码编译成一个动态库。

  1. gcc test.c -fPIC -shared -o test.so 

然后写个测试lua demo。

  1. local test = require "test" 
  2. a = test.test("hello world!"
  3. print(a) 

我们可以看到在lua中成功调用了test模块的test函数,并输出hi。当我们require"test"的时候,lua会去当前目录找test.o,并且执行其中的luaopen_test函数。luaopen_前缀是约定,test则是模块名称。当前去哪里找需要加载的模块这个我们可以设置。我们分析一下c文件的代码,看看拓展lua时的一些内容。首先看luaL_register。

  1. LUALIB_API void (luaL_register) (lua_State *L, const char *libname, const luaL_Reg *l) { 
  2.   luaI_openlib(L, libname, l, 0); 

我们主要关注luaL_register的第二第三个参数libname和luaL_Reg。因为知道这个参数的格式,我们才知道怎么写c代码。其中name是库名称,也就是我们require时传的字符串。luaL_Reg的定义如下

  1. typedef int (*lua_CFunction) (lua_State *L); 
  2.  
  3. typedef struct luaL_Reg { 
  4.   const char *name
  5.   lua_CFunction func; 
  6. } luaL_Reg; 

luaL_Reg是封装了kv的一个结构体,。name是导出的函数名称,即在lua中可以调用的函数。func则是对应的函数,当在lua执行name函数时就会执行func的代码。

3 lua变量存储的设计

lua是动态类型的语言,意味着一个变量的值的类型是可以改变的,下面看一下lua中是如何设计底层的存储的。lua所有变量都使用TValue结构体来表示。

  1. #define TValuefields    Value value; int tt 
  2.  
  3. typedef struct lua_TValue { 
  4.   TValuefields; 
  5. } TValue; 

里面只有两个字段。tt是表示变量类型。lua的类型比较简单。如下

  1. #define LUA_TNIL        0 
  2. #define LUA_TBOOLEAN        1 
  3. #define LUA_TLIGHTUSERDATA  2 
  4. #define LUA_TNUMBER     3 
  5. #define LUA_TSTRING     4 
  6. // 数组和对象都使用一种类型 
  7. #define LUA_TTABLE      5 
  8. #define LUA_TFUNCTION       6 
  9. #define LUA_TUSERDATA       7 
  10. #define LUA_TTHREAD     8 

接下来我们看看Value的定义。

  1. typedef union { 
  2.   GCObject *gc; 
  3.   void *p; 
  4.   lua_Number n; 
  5.   int b; 
  6. } Value; 

Value里分为两种类型,一种是不需要gc的,比如数字,一种是需要gc的,比如数组,lua是带gc的语言。我们继续看GCObject。

  1. union GCObject { 
  2.   GCheader gch; 
  3.   union TString ts; 
  4.   union Udata u; 
  5.   union Closure cl; 
  6.   struct Table h; 
  7.   struct Proto p; 
  8.   struct UpVal uv; 
  9.   struct lua_State th;  /* thread */ 
  10. }; 

我们看到GCObject是一个联合体,可以存储不同类型的变量。我们再看看TString的定义。

  1. typedef union TString { 
  2.   L_Umaxalign dummy;  /* 内存对齐,性能优化 */ 
  3.   struct { 
  4.     CommonHeader; 
  5.     lu_byte reserved; 
  6.     unsigned int hash; 
  7.     size_t len; 
  8.   } tsv; 
  9. } TString; 

字符串结构体里面主要的字段时len和hash,len就是字符串的长度,hash类似一个索引,lua中的字符串不是存储在结构体本身的,而是统一管理起来,主要是为了复用,比如有两个变量的值都是同一个字符串,那么lua中,只会存储一个字符串值,而这两个变量都会通过hash指向这个字符串的值。我们可以看一下一段代码大概了解一下。

  1. // 新建字符串,如果存在则直接复用 
  2. TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { 
  3.   GCObject *o; 
  4.   unsigned int h = cast(unsigned int, l);  /* seed */ 
  5.   size_t step = (l>>5)+1;   
  6.   size_t l1; 
  7.   // 计算字符串的哈希值 
  8.   for (l1=l; l1>=step; l1-=step)  /* compute hash */ 
  9.     h = h ^ ((h<<5)+(h>>2)+cast(unsigned char, str[l1-1])); 
  10.   // 判断是否有一样的字符串存在了,是则共享,直接返回,否则新建 
  11.   for (o = G(L)->strt.hash[lmod(h, G(L)->strt.size)]; 
  12.        o != NULL
  13.        o = o->gch.next) { 
  14.     TString *ts = rawgco2ts(o); 
  15.     if (ts->tsv.len == l && (memcmp(str, getstr(ts), l) == 0)) { 
  16.       if (isdead(G(L), o)) changewhite(o); 
  17.       return ts; 
  18.     } 
  19.   } 
  20.   // 找不到则新建 
  21.   return newlstr(L, str, l, h);  /* not found */ 

我们看到lua的变量存储设计中是一种树状结构,通过上层的变量类型,再进行不同的存取操作。从而我们也可以了解到动态语言在变量存储中的一些设计思想。

后记:这是周末学习lua的一些内容,后续有时间会继续更新,lua是一个非常有意思的项目。

 

责任编辑:武晓燕 来源: 编程杂技
相关推荐

2022-02-07 11:01:23

ZooKeeper

2021-10-26 10:40:26

代理模式虚拟

2021-12-04 22:05:02

Linux

2022-05-17 08:02:55

GoTryLock模式

2022-04-12 08:30:52

回调函数代码调试

2021-10-27 09:59:35

存储

2021-07-02 08:51:29

源码参数Thread

2023-03-13 21:38:08

TCP数据IP地址

2021-10-29 07:35:32

Linux 命令系统

2022-10-20 07:39:26

2021-09-28 08:59:30

复原IP地址

2021-07-16 22:43:10

Go并发Golang

2021-10-14 10:22:19

逃逸JVM性能

2023-11-01 09:07:01

Spring装配源码

2022-03-11 10:21:30

IO系统日志

2021-04-29 10:18:18

循环依赖数组

2022-11-14 08:17:56

2022-01-02 08:43:46

Python

2021-12-01 11:33:21

函数Min

2021-12-08 14:02:20

符串排列搜索
点赞
收藏

51CTO技术栈公众号