-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathinit.lua
308 lines (292 loc) · 8.54 KB
/
init.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
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
-- license:MIT
-- copyright-holders:Carl, Patrick Rapin, Reuben Thomas
-- completion from https://github.com/rrthomas/lua-rlcompleter
local exports = {}
exports.name = "console"
exports.version = "0.0.1"
exports.description = "Console plugin"
exports.license = "BSD-3-Clause"
exports.author = { name = "Carl" }
local console = exports
local history_file = "console_history"
local history_fullpath = nil
local reset_subscription, stop_subscription
function console.startplugin()
local conth = emu.thread()
local ln_started = false
local started = false
local stopped = false
local ln = require("linenoise")
local preload = false
local matches = {}
local lastindex = 0
local consolebuf
print(" /| /| /| /| /| _______")
print(" / | / | / | / | / | / /")
print(" / |/ | / | / |/ | / ____/ ")
print(" / | / | / | / /_ ")
print(" / |/ | / |/ __/ ")
print(" / /| /| /| |/ /| /| /____ ")
print(" / / | / | / | / | / | / ")
print("/ _/ |/ / / |___/ |/ /_______/ ")
print(" / / ")
print(" / _/ \n")
print(emu.app_name() .. " " .. emu.app_version(), "\nCopyright (C) Nicola Salmoria and the MAME team\n");
print(_VERSION, "\nCopyright (C) Lua.org, PUC-Rio\n");
-- linenoise isn't thread safe but that means history can handled here
-- that also means that bad things will happen if anything outside lua tries to use it
-- especially the completion callback
ln.historysetmaxlen(50)
local scr = [[
local ln = require('linenoise')
ln.setcompletion(
function(c, str)
status = str
yield()
for candidate in status:gmatch('([^\001]+)') do
ln.addcompletion(c, candidate)
end
end)
local ret = ln.linenoise('$PROMPT')
if ret == nil then
return "\n"
end
return ret
]]
local keywords = {
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while'
}
local cmdbuf = ""
-- Main completion function. It evaluates the current sub-expression
-- to determine its type. Currently supports tables fields, global
-- variables and function prototype completion.
local function contextual_list(expr, sep, str, word, strs)
local function add(value)
value = tostring(value)
if value:match("^" .. word) then
matches[#matches + 1] = value
end
end
-- This function is called in a context where a keyword or a global
-- variable can be inserted. Local variables cannot be listed!
local function add_globals()
for _, k in ipairs(keywords) do
add(k)
end
for k in pairs(_G) do
add(k)
end
end
if expr and expr ~= "" then
local v = load("local STRING = {'" .. table.concat(strs,"','") .. "'} return " .. expr)
if v then
err, v = pcall(v)
if (not err) or (not v) then
add_globals()
return
end
local t = type(v)
if sep == '.' or sep == ':' then
if t == 'table' then
for k, v in pairs(v) do
if type(k) == 'string' and (sep ~= ':' or type(v) == "function") then
add(k)
end
end
elseif t == 'userdata' then
for k, v in pairs(getmetatable(v)) do
if type(k) == 'string' and (sep ~= ':' or type(v) == "function") then
add(k)
end
end
end
elseif sep == '[' then
if t == 'table' then
for k in pairs(v) do
if type(k) == 'number' then
add(k .. "]")
end
end
if word ~= "" then add_globals() end
end
end
end
end
if #matches == 0 then
add_globals()
end
end
local function find_unmatch(str, openpar, pair)
local done = false
if not str:match(openpar) then
return str
end
local tmp = str:gsub(pair, "")
if not tmp:match(openpar) then
return str
end
repeat
str = str:gsub(".-" .. openpar .. "(.*)", function (s)
tmp = s:gsub(pair, "")
if not tmp:match(openpar) then
done = true
end
return s
end)
until done or str == ""
return str
end
-- This complex function tries to simplify the input line, by removing
-- literal strings, full table constructors and balanced groups of
-- parentheses. Returns the sub-expression preceding the word, the
-- separator item ( '.', ':', '[', '(' ) and the current string in case
-- of an unfinished string literal.
local function simplify_expression(expr, word)
local strs = {}
-- Replace annoying sequences \' and \" inside literal strings
expr = expr:gsub("\\(['\"])", function (c)
return string.format("\\%03d", string.byte(c))
end)
local curstring
-- Remove (finished and unfinished) literal strings
while true do
local idx1, _, equals = expr:find("%[(=*)%[")
local idx2, _, sign = expr:find("(['\"])")
if idx1 == nil and idx2 == nil then
break
end
local idx, startpat, endpat
if (idx1 or math.huge) < (idx2 or math.huge) then
idx, startpat, endpat = idx1, "%[" .. equals .. "%[", "%]" .. equals .. "%]"
else
idx, startpat, endpat = idx2, sign, sign
end
if expr:sub(idx):find("^" .. startpat .. ".-" .. endpat) then
expr = expr:gsub(startpat .. "(.-)" .. endpat, function (str)
strs[#strs + 1] = str
return " STRING[" .. #strs .. "] "
end)
else
expr = expr:gsub(startpat .. "(.*)", function (str)
curstring = str
return "(CURSTRING "
end)
end
end
-- crop string at unmatched open paran
expr = find_unmatch(expr, "%(", "%b()")
expr = find_unmatch(expr, "%[", "%b[]")
--expr = expr:gsub("%b()"," PAREN ") -- Remove groups of parentheses
expr = expr:gsub("%b{}"," TABLE ") -- Remove table constructors
-- Avoid two consecutive words without operator
expr = expr:gsub("(%w)%s+(%w)","%1|%2")
expr = expr:gsub("%s+", "") -- Remove now useless spaces
-- This main regular expression looks for table indexes and function calls.
return curstring, strs, expr:match("([%.:%w%(%)%[%]_]-)([:%.%[%(])" .. word .. "$")
end
local function get_completions(line)
matches = {}
local start, word = line:match("^(.*[ \t\n\"\\'><=;:%+%-%*/%%^~#{}%(%)%[%].,])(.-)$")
if not start then
start = ""
word = word or line
else
word = word or ""
end
local str, strs, expr, sep = simplify_expression(line, word)
contextual_list(expr, sep, str, word, strs)
if #matches == 0 then
return line
elseif #matches == 1 then
return start .. matches[1]
end
print("")
result = { }
for k, v in pairs(matches) do
print(v)
table.insert(result, start .. v)
end
return table.concat(result, '\001')
end
reset_subscription = emu.add_machine_reset_notifier(function ()
if not consolebuf and manager.machine.debugger then
consolebuf = manager.machine.debugger.consolelog
lastindex = 0
end
end)
stop_subscription = emu.add_machine_stop_notifier(function ()
consolebuf = nil
end)
emu.register_periodic(function ()
if stopped then
return
end
if (not started) then
-- options are not available in startplugin, so we load the history here
local homepath = manager.options.entries.homepath:value():match("([^;]+)")
history_fullpath = homepath .. '/' .. history_file
ln.loadhistory(history_fullpath)
started = true
end
local prompt = "\x1b[1;36m[MAME]\x1b[0m> "
if consolebuf and (#consolebuf > lastindex) then
local last = #consolebuf
print("\n")
while lastindex < last do
lastindex = lastindex + 1
print(consolebuf[lastindex])
end
-- ln.refresh() FIXME: how to replicate this now that the API has been removed?
end
if conth.yield then
conth:continue(get_completions(conth.result))
return
elseif conth.busy then
return
elseif ln_started then
local cmd = conth.result
if cmd == "\n" then
stopped = true
return
elseif cmd == "" then
if cmdbuf ~= "" then
print("Incomplete command")
cmdbuf = ""
end
else
cmdbuf = cmdbuf .. "\n" .. cmd
ln.historyadd(cmd)
local func, err = load(cmdbuf)
if not func then
if err:match("<eof>") then
prompt = "\x1b[1;36m[MAME]\x1b[0m>> "
else
print("error: ", err)
cmdbuf = ""
end
else
cmdbuf = ""
stopped = true
local status
status, err = pcall(func)
if not status then
print("error: ", err)
end
stopped = false
end
end
end
conth:start(scr:gsub("$PROMPT", prompt))
ln_started = true
end)
end
setmetatable(console, {
__gc = function ()
if history_fullpath then
local ln = require("linenoise")
ln.savehistory(history_fullpath)
end
end})
return exports