lua学习项目笔记-JSON语法简析

这几天草草的浏览了一下电子版的《lua程序设计》,没有懂的地方就自动忽略了,挑拣了一些可以理解的部分一直在推进。推进至后面的时候已经浑浑噩噩的了,有种想看完这本书的强迫症的感觉。推进CAPI部分的时候发现难度一下子提升了,有种难以理解的感觉,而且这本书没有相对应的练习,只是看书没有进行相应的实践,确实难度比较大。这里先暂缓推进这本书的进程,决定拿一下小的项目来试试写lua代码的感觉,写完这个项目在回去体会lua程序设计这本书,这样可能效果会更好一些。这里我会列出项目描述,而且会记录完成这个项目的过程,当然,项目是一个非常简单,而且已经存在优秀的开源库的东西,但是这里先检查一下自己是否能够完成,完成之后可以参阅一下相关开源库的实现,对比之中应该会有所提高。

项目描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
基于lua 5.2.3 封装实现一个可供其他lua脚本使用的功能模块,具体要求为:
     1、实现一个标准的lua功能模块
     2、封装json格式的数据与lua value间的互相转换功能
     3、遵循json格式定义确保相同的数据源彼此转换后数据仍然一致
     4、只允许使用lua内建基础库,不允许使用任何第三方开发库
     5、有合理的注释和单元测试代码
     6、独立完成此作业,对任何代码抄袭现象零容忍
 
基本要求:
提交lua代码文件名为json.lua,测试代码和文档(如果有)可以打一个包作为第二个附件提交
json.lua中需要实现以下接口:
     function Marshal(json_str)  return  lua_val end
     function Unmarshal(lua_val)  return  "json_str"  end
 
对基本要求的说明:
     1、lua版本要求5.2.3 
     2、lua的空table统一转化成json的空object,而不是空array
     3、test  case 中的json_str符合ECMA-404 json格式标准
     4、Unmarshal传入的lua_val如果是table的话,不会有循环嵌套
     5、table如果是以array方式使用,转换如下:{[2]=1,[4]=1} == {nil,1,nil,1} <-> [null,1,null,1]
     6、table中有string key时,统一处理成hash table的格式,如:{1,2;a=3} -> { "1" :1, "2" :2 "," a":3}
     7、不会出现类似 {1,2;[ "2" ]= "same key 2 in json" } 造成转换有歧义的table
     8、Unicode转成lua字符串时,按lua的字符串格式 \xXX\xYY...
     9、能成功转换的话,只需要 return 单个值
 
进阶要求:
对test  case 的错误进行检查,返回相应错误
     function Marshal(json_str)  return  nil,  "error_type"  end
 
基本测试方法:
local json = require  'json'
 
local test_result1 = json.Marshal( '{"a":1}' )
local test_result2 = json.Unmarshal{ b= "cd"  }
 
-- validate test_result1 & test_result2

 

项目解决过程:

一.模块

    首先这个问题是实现一个模块,在lua 5.1版开始,lua已经为模块和包定义了一系列的规则,我们需要使用table,函数,元表和环境来实现这些规则。其中lua提供了两个重要的函数实现这些规则,分别是require(使用模块函数)和module(创建模块函数)。

    require可以加载模块,这里加载到的为一个table,table内容包含该模块提供的接口和成员变量;规则还定义一个规范的模块应该可以使require返回该模块的table。require使用方法为:

require "<模块名>"。其实这里require的功能感觉和dofile比较像,但是还是存在区别的,require详细的内容我目前也不是很理解,这里先略去。

    完成了加载模块的解释,接下来就是自己实现模块需要遵守什么样的规则呢?模块的主要功能就是定义一个table,然后定义这个table需要导出的成员接口&成员变量,然后返回这个table即完成了这个模块的实现。module函数完成了很多简化模块编写的功能;module函数的更加详细部分以及模块编写的公共部分演化这里略去。

1
2
3
4
5
6
7
8
local modname  =  ...
local M  =  {}
_G[modname]  =  M
package.loaded[modname]  =  M
 
<setup  for  external access>
 
setfenv( 1 ,M)

    关于子模块和包的部分这里也利用不到,所以这部分知识也略去,关于lua程序设计一书中这部分讲述也稍有复杂,其中印刷题中关于'-'和'_'区分不好,看起来比较吃力,不过关于require和module的部分讲解的非常不错。

 

   所以这部分的代码实现如下:

   

1
2
3
4
5
6
7
8
9
module(...,package.seeall)
 
function Marshal(json_str)
     print(json_str)
end
 
function Unmarshal(lua_content)
     print(lua_content)
end

  

这里把函数的内容简化为只进行参数的输出,后面继续分析并实现函数的功能。  

这部分看起来非常的容易,不过还是要明白其中的道理。

 

二.JSON语法简析

json的语法非常的简单,数据在 名称/值 对中,数据由逗号分隔,花括号保存对象,方括号保存数组;

json的值类型包含:数字(整数或者浮点数),字符串(双引号),逻辑值(true或者false),数组(方括号中),对象(花括号中),null

所以json语言还是比较简单的。

 

谈到这种解析的工作,之前接触过(浏览过)一个自己实现的HTMLParser实现,使用的方法是自动机的策略进行实现的。这种有解析思路的项目均可以利用自动机的思想来实现,自动机的课程很早之前学习的了,此时发现智商好低,学习的内容基本都已经完全还给老师了。不过针对JSON的解析可以使用自动机的思路。这里我搬运一下json官网上的自动机图解。

首先json数据是由对象 or 数组组成的基本结构;

lua学习项目笔记-JSON语法简析_第1张图片

json的对象由大括号包含的key:value组成,key:value之间由逗号隔开,其中key为string的类型,string类型具体定义下面给出。value的具体类型下面给出;

lua学习项目笔记-JSON语法简析_第2张图片

json的数组由中括号包含的value组成,value之间由逗号隔开,其中value的具体类型下面给出;

lua学习项目笔记-JSON语法简析_第3张图片

value的具体类型由string,number,object,array和一些其他json常量组成;

lua学习项目笔记-JSON语法简析_第4张图片

string是由双引号包含的一个字符串,其中包含unicode编码和一些转义字符,其中unicode字符在lua中的存储应该也是一个比较棘手的问题;

json中的转义字符也是一个很bug的问题,lua字符串中也存在转义字符。

而且lua中字符串的表示可以利用单引号,双引号和双中括号,其中单双引号的功能比较类似,存在转义符号。然而双中括号会忽略转义符号,所以这个地方编写程序的时候应该特别注意。

 

所以lua中定义的json字符串的转义符号要转换为lua中的转义字符串,根据上面自动机的标示有8个转义字符和一个unicode转义字符。lua的json字符串中的符号应该为"\\",\\/,\\\\,\\b,\\f,\\n,\\r,\\t,\\u...."这些了,分别应该转为lua中的"",/,\,\b,\f,\n,\r,\t"。

当然lua中内部的转义字符转换也应该逆转换过来;

所以会有两个dict进行转换; 关于unicode码的转换后面进行解释;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
json_to_lua  =  {
[ '\\"] = ' "',
[ '\\/' =  '/' ,
[ '\\\\'] = ' \\'
[ "\\t" =  "\t" ,
[ "\\f" =  "\f" ,
[ "\\r" =  "\r" ,
[ "\\n" =  "\n" ,
[ "\\b" =  "\b"
}
lua_json  =  {
[ '"' =  '\\"' ,
[ '\\'] = ' \\\\',
[ '/' =  '\\/' ,
[ '\b' =  '\\b' ,
[ '\f' =  '\\f' ,
[ '\n' =  '\\n' ,
[ '\r' =  '\\r' ,
[ '\t' =  '\\t'
}

lua学习项目笔记-JSON语法简析_第5张图片

数字的自动机转移图,其中数字包含正负类型,浮点数,整型,科学技术法,所以合法的数字类型的状态机比较复杂,但是lua中有一个可以偷懒的地方在于tonumber函数的使用,可以将string转换为number,如果转换不合法,则返回nil;

三.lua编写状态机的一些知识

 由于上面已经提及了自动机来实现json的解析,然而根据lua程序设计,函数尾调用一节中得知,在lua中”尾调用“的一大应用就是编写”状态机“。

”这种程序通常一个函数就是一个状态,改变状态利用’goto‘或尾调用到另外一个特定的函数。尾调用就是一个函数的return的最后语句是另外一个函数,此时针对此函数的栈信息不进行保存,所以最后一个函数调用相当于goto语句的调用,所以如此可以进行无限的调用也不会产生栈溢出的错误。尾调用详细信息这里略去,只是简单介绍一下。

另外lua中的字符串遍历不是很方便,lua中的string是不变量,也没有提供下标访问的接口,所以只能利用string提供的sub函数去一个字符一个字符的遍历;

四.具体代码的实现

(1)json字符串的解析部分

a. Marsha1解析json字符串的接口,从第一个个字符开始遍历json字符串,position设置为1。

b. skipBlank函数跳过空格,tab,换行等空白字符;

c. 找到第一个有效字符,‘{’或者‘[’,分别继续调用decodeObject和decodeArray函数返回相应的内部lua table即可。如果不是这两个符号,则此json字符串存在格式错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- - two interface encode json  or  decode json
function M.Marshal(json_str)
 
     local nowTime  =  os.time()
 
     - - json must be json array  or  object
     local position  =  1
     position  =  M.skipBlank(json_str,position)
     - - null json string case
     local json_len  =  string. len (json_str)
     if  position > json_len then
         return  nil,[[null json string]]
     end
     - - change to json  object  or  json array
     - - otherwise,invalid
     local c  =  string.sub(json_str,position,position)
     local res
     if  = =  [[{]] then
         res  =  M.decodeObject(json_str,position)
     elseif c  = =  [[[]] then
         res  =  M.decodeArray(json_str,position)
     else
         res  =  nil,[[error that json no an  object  or  array,invalid]]
     end
 
     M.MarshalTime  =  M.MarshalTime  +  os.time()  -  nowTime
 
     return  res
end

  

接下来就是decodeObject,decodeArray,decodeNumber,decodeString,decodeOther等,分别对应于json中的object,array,number,string,true,false,null等变量;和skipBlank函数同样,这些函数具有类似的输入和输出参数;

关于这些函数的输入,因为整个过程是遍历字符串,所以字符串和当前遍历的位置信息比较重要,所以这些函数的传入参数为json字符串和当前字符的位置信息;

这些函数的输出函数分别为解析到的lua内部的数据结构和下一个字符的位置信息;

(这里实现的时候同样可以把json字符串和指针的位置信息设置为两个全局变量,思路相同)

了解完这些函数的思想之后接下来就容易理解这些函数的实现代码。

跳过空白字符

其中在代码debug的时候遇到了

malformed pattern (missing ']'),error的错误,

这里因为string.find函数把第二个参数中的'['符号判断为正则表达式符号了,所以这里就产生错误,所以这里针对'['符号进行一下特殊处理;

其他方面就只检测当前字符是不是自定义的blankcharacter中的一个即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- - skip the blank character
- - blank character include space,\n \r \t
- - params
- - [ in ] json_str : json string
- - [ in ] position : current position of the string
- - [out] the  next  of end position of the blank character
function skipBlank(json_str,position)
     local blankcharacter  =  ' \t\r\n'
     local json_len  =  string. len (json_str)
     while  position < =  json_len do
         local c  =  string.sub(json_str,position,position)
         - - malformed pattern (missing  ']' ),error
         if  = =  "["  then
             return  position
         elseif string.find(blankcharacter,c) then
             position  =  position  +  1
         else
             break
         end
     end
     return  position
end

解析json object

函数看似比较复杂,其实逻辑比较清晰;

json object开始于 '{',结束于 '}',由key:value对组成,由 ',' 分隔;

不断的读取key , ':' , value 然后填入相应的lua table,返回lua table即可;

其中利用计数的方法保证 ',' 的语法正确;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
- - decode  from  json  object
- - begin with  '{'
- - params
- - [ in ] json_str : json string
- - [ in ] position : current position of the string
- - [out] lua table ,  the  next  of end the end position of the string
function decodeObject(json_str,position)
     local lua_object  =  {}
     local key,subobject,subarray, str ,number,othervalue
     local c  =  string.sub(json_str,position,position)
     - - check case
     if  c ~ =  [[{]] then
        base. print  [[error that  object  not  start with { ]]
        return  nil,[[error  in  decodeObject begin]]
     end
     position  =  position  +  1
     position  =  skipBlank(json_str,position)
     =  string.sub(json_str,position,position)
     if  = =  [[}]] then
         position  =  position  +  1
         return  lua_object,position
     end
     - - then json array including {key:value,key:value,key:value,...}
     - - key  - - > string
     - - value including
     - - string
     - - number
     - - object
     - - array
     - - true,false,nil
     - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     local precount  =  - 1
     local curcount  =  0
     local json_len  =  string. len (json_str)
     while  position < =  json_len do
         position  =  skipBlank(json_str,position)
         =  string.sub(json_str,position,position)
         - - key:value
         if  = =  [[}]] then
             - - object  over
             if  precount > =  curcount then
                 - - , is  adjace to ]
                 base. print  "error that , is adjace to }"
                 return  nil, "error that , is adjace to }"
             end
             break
         elseif c  = =  [[,]] then
             - - next  key:value  or  over
             position  =  position  +  1
             precount  =  precount  +  1
             if  0  = =  curcount then
                 - - , is  the first,error
                 base. print  [[error that ,  in  key:value  is  the first]]
                 return  nil,[[error that ,  in  key:value  is  the first]]
             end
             if  precount > =  curcount then
                 - - , is  more than one
                 base. print  [[error that ,  in  key:value  is  more than one]]
                 return  nil,[[error that ,  in  key:value  is  more than one]]
             end
         elseif c  = =  [["]] then
             - - begin key:value
             key,position  =  decodeString(json_str,position)
             - - :
             position  =  skipBlank(json_str,position)
             =  string.sub(json_str,position,position)
             if  c ~ =  [[:]] then
                 base. print  [[error,that  object  not  key:value  format ]]
                 return  nil,[[error  in  decodeObject, format  error]]
             else
                 position  =  position  +  1
             end
             - - begin value
             position  =  skipBlank(json_str,position)
             =  string.sub(json_str,position,position)
             if  = =  '['  then
                 subarray,position  =  decodeArray(json_str,position)
                 lua_object[key]  =  subarray
             elseif c  = =  '{'  then
                 subobject,position  =  decodeObject(json_str,position)
                 lua_object[key]  =  subobject
             elseif c  = =  [["]] then
                 str ,position  =  decodeString(json_str,position)
                 lua_object[key]  =  str
             elseif string.find([[ + - 0123456789.e ]],c) then
                 number,position  =  decodeNumber(json_str,position)
                 lua_object[key]  =  number
             else
                 othervalue,position  =  decodeOther(json_str,position)
                 if  othervalue then
                     lua_object[key]  =  othervalue
                 end
             end
             if  not  lua_object[key] then
                 base. print  [[error  in  json  object  key:value  - - > value,can't get value]]
                 return  nil,[[error  in  decodeObject value]]
             else
                 curcount  =  curcount  +  1
             end
             - - end value
         else
             base. print  [[error json  format ]]
             return  nil,[[error json  format , in  decodeObject end]]
         end
     end
     return  lua_object,position  +  1
end

解析json array

json array的解析开始于 '[' 符号,结束于 ']' 符号,value值之间利用 ',' 隔开;

创建一个lua table,针对value值利用 t[#t+1] = value插入新的值,新的值根据符号调用相应的decode方法即可,

其中为了确保json语法错误被检测出来,利用计数的方法保证 ',' 语法的正确;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
- - decode  from  json array
- - begin with  '['
- - params
- - [ in ] json_str : json string
- - [ in ] position : current position of the string
- - [out] lua table ,  the  next  of end the end position of the string
function decodeArray(json_str,position)
     local lua_array  =  {}
     local c  =  string.sub(json_str,position,position)
     - - check case
     if  c ~ =  [[[]] then

你可能感兴趣的:(脚本,lua)