mirror of
https://github.com/holub/mame
synced 2025-05-03 13:06:47 +03:00

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.
277 lines
7.7 KiB
Lua
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
|