-
Notifications
You must be signed in to change notification settings - Fork 342
/
deep_pull.go
153 lines (124 loc) · 3.09 KB
/
deep_pull.go
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package util
import (
"errors"
"fmt"
"github.com/Shopify/go-lua"
)
var (
ErrCannotPull = errors.New("cannot pull go type into lua")
ErrStackExhausted = errors.New("pull table, stack exhausted")
)
func Open(l *lua.State) {
l.Register("array", luaArray)
}
func PullStringTable(l *lua.State, idx int) (map[string]string, error) {
if !l.IsTable(idx) {
return nil, fmt.Errorf("need a table at index %d, got %s: %w", idx, lua.TypeNameOf(l, idx), ErrCannotPull)
}
// Table at idx
l.PushNil() // Add free slot for the value, +1
table := make(map[string]string)
// -1:nil, idx:table
for l.Next(idx) {
// -1:val, -2:key, idx:table
key, ok := l.ToString(-2)
if !ok {
return nil, fmt.Errorf("key should be a string (%v): %w", l.ToValue(-2), ErrCannotPull)
}
val, ok := l.ToString(-1)
if !ok {
return nil, fmt.Errorf("value for key '%s' should be a string (%v): %w", key, l.ToValue(-1), ErrCannotPull)
}
table[key] = val
l.Pop(1) // remove val from top, -1
// -1:key, idx: table
}
return table, nil
}
func PullTable(l *lua.State, idx int) (interface{}, error) {
if !l.IsTable(idx) {
return nil, fmt.Errorf("need a table at index %d, got %s: %w", idx, lua.TypeNameOf(l, idx), ErrCannotPull)
}
return pullTableRec(l, idx)
}
func pullTableRec(l *lua.State, idx int) (interface{}, error) {
if !l.CheckStack(2) {
return nil, ErrStackExhausted
}
idx = l.AbsIndex(idx)
if isArray(l, idx) {
return pullArrayRec(l, idx)
}
table := make(map[string]interface{})
l.PushNil()
for l.Next(idx) {
// -1: value, -2: key, ..., idx: table
key, ok := l.ToString(-2)
if !ok {
err := fmt.Errorf("key should be a string (%s): %w", lua.TypeNameOf(l, -2), ErrCannotPull)
l.Pop(2)
return nil, err
}
value, err := toGoValue(l, -1)
if err != nil {
l.Pop(2)
return nil, err
}
table[key] = value
l.Pop(1)
}
return table, nil
}
const arrayMarkerField = "_is_array"
func luaArray(l *lua.State) int {
l.NewTable()
l.PushBoolean(true)
l.SetField(-2, arrayMarkerField)
l.SetMetaTable(-2)
return 1
}
func isArray(l *lua.State, idx int) bool {
if !l.IsTable(idx) {
return false
}
if !lua.MetaField(l, idx, arrayMarkerField) {
return false
}
defer l.Pop(1)
return l.ToBoolean(-1)
}
func pullArrayRec(l *lua.State, idx int) (interface{}, error) {
table := make([]interface{}, lua.LengthEx(l, idx))
l.PushNil()
for l.Next(idx) {
k, ok := l.ToInteger(-2)
if !ok {
l.Pop(2)
return nil, fmt.Errorf("pull array: expected numeric index, got '%s': %w", l.TypeOf(-2), ErrCannotPull)
}
v, err := toGoValue(l, -1)
if err != nil {
l.Pop(2)
return nil, err
}
table[k-1] = v
l.Pop(1)
}
return table, nil
}
func toGoValue(l *lua.State, idx int) (interface{}, error) {
t := l.TypeOf(idx)
switch t {
case lua.TypeBoolean:
return l.ToBoolean(idx), nil
case lua.TypeString:
return lua.CheckString(l, idx), nil
case lua.TypeNumber:
return lua.CheckNumber(l, idx), nil
case lua.TypeTable:
return pullTableRec(l, idx)
default:
err := fmt.Errorf("pull table, unsupported type %s: %w", lua.TypeNameOf(l, idx), ErrCannotPull)
return nil, err
}
}