- 📖试读(douban.com,dangdang.com)
♥️ 购买(jd.com,dangdang.com,z.cn)- 🐛勘误表
- 🎧加入读者交流群(qq: 311942068)
- 🖥Windows命令行代码
- 相关文章
zxh0 / luago-book Goto Github PK
View Code? Open in Web Editor NEW《自己动手实现Lua》随书源代码
License: MIT License
《自己动手实现Lua》随书源代码
License: MIT License
if idx, ok := key.(int64); ok && idx >= 1 {
arrLen := int64(len(self.arr))
if idx <= arrLen {
self.arr[idx-1] = val
if idx == arrLen && val == nil {
self._shrinkArray()
}
return
}
if idx == arrLen+1 {
delete(self._map, key) //此处的delete
if val != nil {
self.arr = append(self.arr, val)
self._expandArray()
}
return
}
}
一直没想明白这里delete(self._map, key)的用途是什么。感觉不管什么情况下,都不会真正触发delete。
当插入Table的键是“len(数组)+1”后,那么数组会自动重整(expand),map键“len(数组)+1”的值应该一直是空,后续操作应该不会触发delete。
例如:
只有当状态为:数组[1,2,3,4],字典{5:100,6:6,8,8}时。
此时,插入t[5]=5,才会真正触发delete。但我实在想不到,在什么情况下,会出现上述情况,即在字典中存在键“len(数组)+1”。
望解答,谢谢。
vm.PushGlobalTable() +1
vm.GetRK(c) +1
vm.GetTable(-2) +0
vm.Repace(a) -1
vm.Pop(1) -1
如果执行了vm.Pop(1)会把加载进栈的Go函数清除,相当于调用getTabUp后没有起到任何作用
原文法:
exp ::= exp12
exp12 ::= exp11 {or exp11}
exp11 ::= exp10 {and exp10}
exp10 ::= exp9 {(‘<’ | ‘>’ | ‘<=’ | ‘>=’ | ‘~=’ | ‘==’) exp9}
exp9 ::= exp8 {‘|’ exp8}
exp8 ::= exp7 {‘~’ exp7}
exp7 ::= exp6 {‘&’ exp6}
exp6 ::= exp5 {(‘<<’ | ‘>>’) exp5}
exp5 ::= exp4 {‘..’ exp4}
exp4 ::= exp3 {(‘+’ | ‘-’) exp3}
exp3 ::= exp2 {(‘*’ | ‘/’ | ‘//’ | ‘%’) exp2}
exp2 ::= {(‘not’ | ‘#’ | ‘-’ | ‘~’)} exp1
exp1 ::= exp0 {‘^’ exp2}
exp0 ::= nil | false | true | Numeral | LiteralString
| ‘...’ | functiondef | prefixexp | tableconstructor
这里的文法 + - * / or and 这些是没有处理左结合的,..没有处理右结合
虽然实现的代码上处理了
符合编译原理的文法应该是这样的
exp ::= exp12
exp12 ::= (exp12 or exp 11) | exp11
exp11 ::= (exp11 and exp10) | exp10
......
省略
......
exp5 ::= exp4 {'..' exp5}
消除左递归形式:
@表示空产生式(ε)
exp12 ::= exp11 exp12'
exp12' ::= (or exp11 exp12') | @
exp11 ::= exp10 exp11'
exp11' ::= (and exp10 exp11') | @
......
省略
......
exp5 ::= exp4 {'..' exp5}
由于exp12'与exp11'这些尾项产生式都是尾递归
因此可以使用书中的循环进行优化
func parseExp12(lexer *Lexer) Exp {
exp := parseExp11(lexer)
for lexer.LookAhead() == TOKEN_OP_OR {
line, op, _ := lexer.NextToken()
lor := &BinopExp{line, op, exp, parseExp11(lexer)}
exp = optimizeLogicalOr(lor)
}
return exp
}
exp5相当于原地展开递归
func parseExp5(lexer *Lexer) Exp {
exp := parseExp4(lexer)
if lexer.LookAhead() != TOKEN_OP_CONCAT {
return exp
}
line := 0
exps := []Exp{exp}
for lexer.LookAhead() == TOKEN_OP_CONCAT {
line, _, _ = lexer.NextToken()
exps = append(exps, parseExp4(lexer))
}
return &ConcatExp{line, exps}
}
书的EBNF有误导成分
在讨论FORPREP
和FORLOOP
指令时,书上举了下面的例子:
$ luac -l -l -
for i=1,2,100 do f() end
^D
main <stdin:0,0> (8 instructions at 0x7f9980c031f0)
0+ params, 5 slots, 1 upvalue, 4 locals, 4 constants, 0 functions
1 [1] LOADK 0 -1 ; 1
2 [1] LOADK 1 -2 ; 2
3 [1] LOADK 2 -3 ; 100
4 [1] FORPREP 0 2 ; to 7
5 [1] GETTABUP 4 0 -4 ; _ENV "f"
6 [1] CALL 4 1 1
7 [1] FORLOOP 0 -3 ; to 5
8 [1] RETURN 0 1
constants (4) for 0x7f9980c031f0:
1 1
2 2
3 100
4 "f"
locals (4) for 0x7f9980c031f0:
0 (for index) 4 8
1 (for limit) 4 8
2 (for step) 4 8
3 i 5 7
upvalues (1) for 0x7f9980c031f0:
0 _ENV 1 0
但是在P112,画堆栈的时候画的却是:
i: |
(step): 2 |
(limit): 100 |
(index): 1) |
但是如果按照示例中for
循环写法,step
应该是100,而不是2。考虑到后面的讲解,我猜测应该是示例写错了,应该是:
for i=1,100,2 do f() end
P152 处书上是这样讲解CLOSURE
指令的:
CLOSURE
指令(iBx
模式)...
但是并不存在iBx
模式,而应该是“iABx
模式”
`
func (self *luaTable) _shrinkArray() {
for i := len(self.arr) - 1; i >= 0; i-- {
if self.arr[i] == nil {
self.arr = self.arr[0:i]
} else {
break //应该是需要break???
}
}
}
`
有幸拜读了作者的《自己动手实现Lua》一书,收益良多. 要是能把lua的垃圾回收机制也讲解下.那就更好了
啥时候能上市,很期待
书上这样讲 LOADNIL
指令的三个操作数的作用:
寄存器的起始索引由操作数 A 指定,寄存器数量则由操作数 B 指定,操作数 C 并没有用。
但是在书上举的例子中:
$ luac -l -
local a,b,c,d,e
^D
main <stdin:0,0> (2 instructions at 0x7fca03500070)
0+ params, 5 slots, 1 upvalue, 5 locals, 0 constants, 0 functions
1 [1] LOADNIL 0 4
2 [1] RETURN 0 1
里面 B 操作数的值为4,但是其实是给5个值(a,b,c,d,e
)赋值nil
了
所以我觉得"寄存器的数量由操作数 B 指定"这句话稍微有点歧义。
书上描述luaTable
只增加keys
字段, 但是下面的描述nextKey
方法提到了changed
字段, 我看ch12的源码中为next增加
keys map[LuaValue]LuaValue
lastKey LuaValue
changed bool
存在如下Lua字节码:
main hello.lua:0,0 (69 instructions at 005f8670)
0+ params, 14 slots, 1 upvalue, 8 locals, 9 constants, 3 functions
1 [10] CLOSURE 0 0 ; 005f8880
2 [13] CLOSURE 1 1 ; 005f8a98
3 [12] SETTABUP 0 -1 1 ; _ENV "fail"
4 [17] CLOSURE 1 2 ; 005f8af0
5 [19] MOVE 2 0
..................................................................................................................
..................................................................................................................
..................................................................................................................
原来的Set函数忽略了idx == top的情况
对于上面的CLOSURE与MOVE指令运行都会失败
以下是C++版本的修复方法
void Set(int idx, const LuaValue& value)
{
int absIdx = AbsIndex(idx);
if(absIdx == top + 1)
++top;
if(absIdx > 0 && absIdx <= top)
{
slots[absIdx - 1] = value;
}
else
{
panic("invalid index!");
}
}
P109,关于TEST
指令,书上是这样讲的:
判断寄存器 A(索引由操作数 A 指定)中的值转换为布尔值之后是否和操作数 C 表示的布尔值一致,如果一致,则跳过下一条指令。
但是根据TEST
指令的伪码:
if not (R(A) <=> C) then pc++
可以发现其实应该是“如果不一致,则跳过下一条指令”。
感谢作者写了这本书!以下是我在阅读的时候遇到的一些错误:
页数 | 章节 | 位置 | 原文 | 更正 |
---|---|---|---|---|
124 | 7.2 | 倒数一段话第一行 | 由于set()方法…… | 由于put(...)方法…… |
178 | 9.3.2 | 倒数第一段话最后一行 | 录下创建inst_Upvalue.go文件 | 录下创建inst_upvalue.go文件 |
187 | 10.1.2 | 第一段话第一行 | 我们再用“luc -l -l”分析…… | 我们再用“luac -l -l”分析…… |
192 | 10.2.2 | 倒数第二段话倒数第三行 | 读者打开luaStack.go文件…… | 读者打开lua_stack.go文件…… |
227 | 12.2.1 | 第一段话第一行 | 请读者打开luaTable.go文件…… | 请读者打开lua_table.go文件…… |
228 | 12.2.2 | 倒数第一段话第二行 | (和luaTable.go文件…… | (和lua_table.go文件…… |
372 | 19.3 | 倒数第一段话第二行 | 就可以通过面相对象…… | 就可以通过面向对象…… |
373 | 19.3 | 第二句代码的打印结果 | print(s:len()) --> 2 | print(s:len()) --> 3 |
另外,Windows和Mac的命令行略有不同,而这本书上是Mac版命令行,我开了一个库记录本书Windows版命令行内容,不关心命令行差异的用Windows的读者可以从这库里直接拷贝命令行内容。
您好,首先十分感谢作者,这本书对帮助我理解虚拟机底层运行机制帮助很大。目前看到第10章,也参考了这个仓库自己实现了一个Rust版本。
在实现好第10章的内容后对upvalue的一些情况做了测试,但是发现有一种情况不能通过,debug后觉得可能是对于upvalue的引用问题。
用例代码如下:
local step = 1
local start = 0
local function assert(v)
if not v then
fail()
end
end
function newCounter()
local count = start
return function()
count = count + step
return count
end
end
c1 = newCounter()
assert(c1() == 1)
assert(c1() == 2)
c2 = newCounter()
assert(c2() == 1) -- 3
assert(c1() == 3) -- 4
assert(c2() == 2) -- 5
似乎是因为newCounter
获取到start
给count
之后,后续对count
的更改都会同时反馈在最外层的start
上。
我想请教一下,lua对于这种情况闭包获取到的upvalue的生命周期是不是还有其他的处理呢?如果有的话是如何实现的呢,在官方c实现的版本里应该去了解哪部分的代码?
如果您方便的话请您帮忙解答一下,非常感谢!
编译器版本:
Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio
操作系统:
macOS 11.3.1
附编译后的指令:
main <tests/upvalue.lua:0,0> (52 instructions at 0x7f8d3a406590)
0+ params, 5 slots, 1 upvalue, 3 locals, 7 constants, 2 functions
1 [1] LOADK 0 -1 ; 1
2 [2] LOADK 1 -2 ; 0
3 [7] CLOSURE 2 0 ; 0x7f8d3a406700
4 [14] CLOSURE 3 1 ; 0x7f8d3a406b40
5 [8] SETTABUP 0 -3 3 ; _ENV "newCounter"
6 [16] GETTABUP 3 0 -3 ; _ENV "newCounter"
7 [16] CALL 3 1 2
8 [16] SETTABUP 0 -4 3 ; _ENV "c1"
9 [17] MOVE 3 2
10 [17] GETTABUP 4 0 -4 ; _ENV "c1"
11 [17] CALL 4 1 2
12 [17] EQ 1 4 -1 ; - 1
13 [17] JMP 0 1 ; to 15
14 [17] LOADBOOL 4 0 1
15 [17] LOADBOOL 4 1 0
16 [17] CALL 3 2 1
17 [18] MOVE 3 2
18 [18] GETTABUP 4 0 -4 ; _ENV "c1"
19 [18] CALL 4 1 2
20 [18] EQ 1 4 -5 ; - 2
21 [18] JMP 0 1 ; to 23
22 [18] LOADBOOL 4 0 1
23 [18] LOADBOOL 4 1 0
24 [18] CALL 3 2 1
25 [20] GETTABUP 3 0 -3 ; _ENV "newCounter"
26 [20] CALL 3 1 2
27 [20] SETTABUP 0 -6 3 ; _ENV "c2"
28 [21] MOVE 3 2
29 [21] GETTABUP 4 0 -6 ; _ENV "c2"
30 [21] CALL 4 1 2
31 [21] EQ 1 4 -1 ; - 1
32 [21] JMP 0 1 ; to 34
33 [21] LOADBOOL 4 0 1
34 [21] LOADBOOL 4 1 0
35 [21] CALL 3 2 1
36 [22] MOVE 3 2
37 [22] GETTABUP 4 0 -4 ; _ENV "c1"
38 [22] CALL 4 1 2
39 [22] EQ 1 4 -7 ; - 3
40 [22] JMP 0 1 ; to 42
41 [22] LOADBOOL 4 0 1
42 [22] LOADBOOL 4 1 0
43 [22] CALL 3 2 1
44 [23] MOVE 3 2
45 [23] GETTABUP 4 0 -6 ; _ENV "c2"
46 [23] CALL 4 1 2
47 [23] EQ 1 4 -5 ; - 2
48 [23] JMP 0 1 ; to 50
49 [23] LOADBOOL 4 0 1
50 [23] LOADBOOL 4 1 0
51 [23] CALL 3 2 1
52 [23] RETURN 0 1
constants (7) for 0x7f8d3a406590:
1 1
2 0
3 "newCounter"
4 "c1"
5 2
6 "c2"
7 3
locals (3) for 0x7f8d3a406590:
0 step 2 53
1 start 3 53
2 assert 4 53
upvalues (1) for 0x7f8d3a406590:
0 _ENV 1 0
function <tests/upvalue.lua:3,7> (5 instructions at 0x7f8d3a406700)
1 param, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions
1 [4] TEST 0 1
2 [4] JMP 0 2 ; to 5
3 [5] GETTABUP 1 0 -1 ; _ENV "fail"
4 [5] CALL 1 1 1
5 [7] RETURN 0 1
constants (1) for 0x7f8d3a406700:
1 "fail"
locals (1) for 0x7f8d3a406700:
0 v 1 6
upvalues (1) for 0x7f8d3a406700:
0 _ENV 0 0
function <tests/upvalue.lua:8,14> (4 instructions at 0x7f8d3a406b40)
0 params, 2 slots, 2 upvalues, 1 local, 0 constants, 1 function
1 [9] GETUPVAL 0 0 ; start
2 [13] CLOSURE 1 0 ; 0x7f8d3a406c40
3 [13] RETURN 1 2
4 [14] RETURN 0 1
constants (0) for 0x7f8d3a406b40:
locals (1) for 0x7f8d3a406b40:
0 count 2 5
upvalues (2) for 0x7f8d3a406b40:
0 start 1 1
1 step 1 0
function <tests/upvalue.lua:10,13> (7 instructions at 0x7f8d3a406c40)
0 params, 2 slots, 2 upvalues, 0 locals, 0 constants, 0 functions
1 [11] GETUPVAL 0 0 ; count
2 [11] GETUPVAL 1 1 ; step
3 [11] ADD 0 0 1
4 [11] SETUPVAL 0 0 ; count
5 [12] GETUPVAL 0 0 ; count
6 [12] RETURN 0 2
7 [13] RETURN 0 1
constants (0) for 0x7f8d3a406c40:
locals (0) for 0x7f8d3a406c40:
upvalues (2) for 0x7f8d3a406c40:
0 count 1 0
1 step 0 1
https://github.com/zxh0/luago-book/blob/master/code/go/ch08/src/luago/vm/inst_table.go
setList书上也没有很具体的解释是什么意思。麻烦解释下。加QQ群号好像也没人。
另外,书上经常出现 luac -l -,然后就是指令,数字。这个怎么看,也没有解释啊。
func (self *Lexer) NextToken() (line, kind int, token string) {
// skip long segment code
if c == '_' || isLetter(c) {
// ...
return line, kind, token // 这里应该是self.line, go初始化始终line为0
// ...
return line, TOKEN_IDENTIFIER, token // 这里应该是self.line, go初始化始终line为0
}
}
源码ch14是对的,书上是错的
发一个不算 issue 的 issue:
有幸在一个前辈的推荐下拜读了您的这本书,目前正在阅读,感觉不管从行文还是代码的安排上都非常舒适,极大地帮助我提高了对编译原理和高级语言虚拟机的兴趣和相关知识。
谨以此文表达对您的感谢😄
P127处,GetTable
方法中用到了getTable
方法:
为了减少重复,我们把根据键从表里取值的逻辑抽取成
GetTable()
方法,代码如下所示。
但是下面展示的却是getTable()
方法的代码,而且根据上下文可知,应该是”抽取成getTable()
方法。“
同理,P129中介绍SetTable
方法时也出现了类似的错误。
请问这个处理?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.