mame/plugins/console/init.lua
Vas Crabb ec80428647 Fairly significant overhaul of Lua engine and some cleanup.
The things that were previously called device iterators are not
iterators in the C++ sense of the word.  This is confusing for
newcomers.  These have been renamed to be device enumerators.

Several Lua methods and properties that previously returned tables now
return lightweight wrappers for the underlying objects.  This means
creating them is a lot faster, but you can't modify them, and the
performance characteristics of different operations varies.

The render manager's target list uses 1-based indexing to be more like
idiomatic Lua.

It's now possible to create a device enumerator on any device, and then
get subdevices (or sibling devices) using a relative tag.

Much more render/layout functionality has been exposed to Lua.  Layout
scripts now have access to the layout file and can directly set the
state of an item with no bindings, or register callbacks to obtain
state.  Some things that were previously methods are now read-only
properties.

Layout files are no longer required to supply a "name".  This was
problematic because the same layout file could be loaded for multiple
instances of the same device, and each instance of the layout file
should use the correct inputs (and in the future outputs) for the device
instance it's associated with.

This should also fix video output with MSVC builds by avoiding delegates
that return things that don't fit in a register.
2020-11-25 19:18:26 +11:00

277 lines
7.7 KiB
Lua

-- 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 = "The BSD 3-Clause License"
exports.author = { name = "Carl" }
local console = exports
function console.startplugin()
local conth = emu.thread()
local started = false
local ln = require("linenoise")
local preload = false
local matches = {}
local lastindex = 0
local consolebuf
_G.history = function (index)
local history = ln.historyget()
if index then
ln.preload(history[index])
return
end
for num, line in ipairs(history) do
print(num, line)
end
end
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, pos)
status = str .. "\x01" .. tostring(pos)
yield()
ln.addcompletion(c, status:match("([^\x01]*)\x01(.*)"))
end)
return ln.linenoise('$PROMPT')
]]
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)
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 = loadstring("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)
-- 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, " STRING ")
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, expr:match("([%.:%w%(%)%[%]_]-)([:%.%[%(])" .. word .. "$")
end
local function get_completions(line, endpos)
matches = {}
local endstr = line:sub(endpos + 1, -1)
line = line:sub(1, endpos)
endstr = endstr or ""
local start, word = line:match("^(.*[ \t\n\"\\'><=;:%+%-%*/%%^~#{}%(%)%[%].,])(.-)$")
if not start then
start = ""
word = word or line
else
word = word or ""
end
local str, expr, sep = simplify_expression(line, word)
contextual_list(expr, sep, str, word)
if #matches > 1 then
print("\n")
for k, v in pairs(matches) do
print(v)
end
return "\x01" .. "-1"
elseif #matches == 1 then
return start .. matches[1] .. endstr .. "\x01" .. (#start + #matches[1])
end
return "\x01" .. "-1"
end
emu.register_start(function()
if not consolebuf and manager:machine():debugger() then
consolebuf = manager:machine():debugger().consolelog
lastindex = 0
end
end)
emu.register_stop(function() consolebuf = nil end)
emu.register_periodic(function()
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()
end
if conth.yield then
conth:continue(get_completions(conth.result:match("([^\x01]*)\x01(.*)")))
return
elseif conth.busy then
return
elseif started then
local cmd = conth.result
if cmd == "" then
if cmdbuf ~= "" then
print("Incomplete command")
cmdbuf = ""
end
else
cmdbuf = cmdbuf .. "\n" .. 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
local status
status, err = pcall(func)
if not status then
print("error: ", err)
end
cmdbuf = ""
end
ln.historyadd(cmd)
end
end
conth:start(scr:gsub("$PROMPT", prompt))
started = true
end)
end
return exports