自己动手实现Lua:虚拟机、编译器和标准库
上QQ阅读APP看书,第一时间看更新

2.4 解析二进制chunk

如前所述,具体的二进制chunk解析工作由reader结构体来完成。请读者在binchunk目录下面创建reader.go文件,在里面定义reader结构体,代码如下所示。

        package binchunk

        import "encoding/binary"
        import "math"

        type reader struct {
            data []byte
        }

reader结构体只有一个data字段,存放将要被解析的二进制chunk数据。下面我们来看看由reader结构体解析二进制chunk都有哪些方法。

2.4.1 读取基本数据类型

读取基本数据类型的方法一共有7种,其他方法通过调用这7种方法来从二进制chunk里提取数据。最简单的是“readByte()”方法,即从字节流里读取一个字节,代码如下所示。

        func (self *reader) readByte() byte {
            b := self.data[0]
            self.data = self.data[1:]
            return b
        }

readUint32()方法使用小端方式从字节流里读取一个cint存储类型(占4个字节,映射为Go语言uint32类型)的整数,代码如下所示。

        func (self *reader) readUint32() uint32 {
            i := binary.LittleEndian.Uint32(self.data)
            self.data = self.data[4:]
            return i
        }

readUint64()方法使用小端方式从字节流里读取一个size_t存储类型(占8个字节,映射为Go语言uint64类型)的整数,代码如下所示。

        func (self *reader) readUint64() uint64 {
            i := binary.LittleEndian.Uint64(self.data)
            self.data = self.data[8:]
            return i
        }

readLuaInteger()方法借助readUint64()方法从字节流里读取一个Lua整数(占8个字节,映射为Go语言int64类型),代码如下所示。

        func (self *reader) readLuaInteger() int64 {
            return int64(self.readUint64())
        }

readLuaNumber()方法借助readUint64()方法从字节流里读取一个Lua浮点数(占8个字节,映射为Go语言float64类型),代码如下所示。

        func (self *reader) readLuaNumber() float64 {
            return math.Float64frombits(self.readUint64())
        }

readString()方法从字节流里读取字符串(映射为Go语言string类型),代码如下所示:

        func (self *reader) readString() string {
            size := uint(self.readByte())         // 短字符串?
            if size == 0 { // NULL字符串
                return ""
            }
            if size == 0xFF { // 长字符串
                size = uint(self.readUint64())
            }
            bytes := self.readBytes(size -1)
            return string(bytes)
        }

readBytes()方法从字节流里读取n个字节,代码如下所示。

        func (self *reader) readBytes(n uint) []byte {
            bytes := self.data[:n]
            self.data = self.data[n:]
            return bytes
        }

2.4.2 检查头部

checkHeader()方法从字节流里读取并检查二进制chunk头部的各个字段,如果发现某个字段和期望不符,则调用panic函数终止加载,代码如下所示。

        func (self *reader) checkHeader() {
            if string(self.readBytes(4)) ! = LUA_SIGNATURE {
                panic("not a precompiled chunk! ")
            } else if self.readByte() ! = LUAC_VERSION {
                panic("version mismatch! ")
            } else if self.readByte() ! = LUAC_FORMAT {
                panic("format mismatch! ")
            } else if string(self.readBytes(6)) ! = LUAC_DATA {
                panic("corrupted! ")
            } else if self.readByte() ! = CINT_SIZE {
                panic("int size mismatch! ")
            } else if self.readByte() ! = CSZIET_SIZE {
                panic("size_t size mismatch! ")
            } else if self.readByte() ! = INSTRUCTION_SIZE {
                panic("instruction size mismatch! ")
            } else if self.readByte() ! = LUA_INTEGER_SIZE {
                panic("lua_Integer size mismatch! ")
            } else if self.readByte() ! = LUA_NUMBER_SIZE {
                panic("lua_Number size mismatch! ")
            } else if self.readLuaInteger() ! = LUAC_INT {
                panic("endianness mismatch! ")
            } else if self.readLuaNumber() ! = LUAC_NUM {
                panic("float format mismatch! ")
            }
        }

2.4.3 读取函数原型

readProto()方法从字节流里读取函数原型,代码如下所示。

        func (self *reader) readProto(parentSource string) *Prototype {
            source := self.readString()
            if source == "" { source = parentSource }
            return &Prototype{
                Source:            source,
                LineDefined:       self.readUint32(),
                LastLineDefined:  self.readUint32(),
                NumParams:        self.readByte(),
                IsVararg:          self.readByte(),
                MaxStackSize:     self.readByte(),
                Code:              self.readCode(),
                Constants:        self.readConstants(),
                Upvalues:          self.readUpvalues(),
                Protos:            self.readProtos(source),
                LineInfo:          self.readLineInfo(),
                LocVars:           self.readLocVars(),
                UpvalueNames:     self.readUpvalueNames(),
            }
        }

读取函数基本信息的部分比较简单,只有Source字段的处理稍微有点麻烦,这是因为Lua编译器只给主函数设置了源文件名以减少冗余数据,所以子函数原型需要从自己的父函数原型那里获取源文件名。下面我们来看一下指令表等列表的读取方法。

readCode()方法从字节流里读取指令表,代码如下所示。

        func (self *reader) readCode() []uint32 {
            code := make([]uint32, self.readUint32())
            for i := range code {
                code[i] = self.readUint32()
            }
            return code
        }

        readConstants()方法从字节流里读取常量表,代码如下所示。

        func (self *reader) readConstants() []interface{} {
            constants := make([]interface{}, self.readUint32())
            for i := range constants {
                constants[i] = self.readConstant()
            }
            return constants
        }

readConstant()方法从字节流里读取一个常量,代码如下所示。

        func (self *reader) readConstant() interface{} {
            switch self.readByte() { // tag
            case TAG_NIL:        return nil
            case TAG_BOOLEAN:   return self.readByte() ! = 0
            case TAG_INTEGER:   return self.readLuaInteger()
            case TAG_NUMBER:     return self.readLuaNumber()
            case TAG_SHORT_STR: return self.readString()
            case TAG_LONG_STR:  return self.readString()
            default:              panic("corrupted! ")
            }
        }

readUpvalues()方法从字节流里读取Upvalue表,代码如下所示。

        func (self *reader) readUpvalues() []Upvalue {
            upvalues := make([]Upvalue, self.readUint32())
            for i := range upvalues {
                upvalues[i] = Upvalue{
                    Instack: self.readByte(),
                    Idx:      self.readByte(),
                }
            }
            return upvalues
        }

由于函数原型本身就是递归数据结构,所以readProto()方法也会递归调用自己去读取子函数原型。

readProtos()方法从字节流里读取子函数原型表,代码如下所示。

        func (self *reader) readProtos(parentSource string) []*Prototype {
            protos := make([]*Prototype, self.readUint32())
            for i := range protos {
                protos[i] = self.readProto(parentSource)
            }
            return protos
        }
        readLineInfo()方法从字节流里读取行号表,代码如下所示。
        func (self *reader) readLineInfo() []uint32 {
            lineInfo := make([]uint32, self.readUint32())
            for i := range lineInfo {
                lineInfo[i] = self.readUint32()
            }
            return lineInfo
        }

readLocVars()方法从字节流里读取局部变量表,代码如下所示。

        func (self *reader) readLocVars() []LocVar {
            locVars := make([]LocVar, self.readUint32())
            for i := range locVars {
                locVars[i] = LocVar{
                    VarName: self.readString(),
                    StartPC: self.readUint32(),
                    EndPC:    self.readUint32(),
                }
            }
            return locVars
        }

readUpvalueNames()方法从字节流里读取Upvalue名列表,代码如下所示。

        func (self *reader) readUpvalueNames() []string {
            names := make([]string, self.readUint32())
            for i := range names {
                names[i] = self.readString()
            }
            return names
        }

到此为止,二进制chunk解析代码也都介绍完毕了。在2.5节,我们会实现一个简化版的Lua反编译器。