Add native support for MoonScript

This commit is contained in:
Thomas Goyne 2013-04-30 06:16:59 -07:00
parent 99d74e18b3
commit 19854e207a
10 changed files with 5155 additions and 866 deletions

View File

@ -1,31 +0,0 @@
local tr = aegisub.gettext
script_name = tr("Select overlaps")
script_description = tr("Select lines which begin while another non-comment line is active")
script_author = "Thomas Goyne"
script_version = "2"
local select_overlaps
select_overlaps = function(subs)
local dialogue = { }
for i, line in ipairs(subs) do
if line.class == "dialogue" then
line.i = i
table.insert(dialogue, line)
end
end
table.sort(dialogue, function(a, b)
return a.start_time < b.start_time or (a.start_time == b.start_time and a.i < b.i)
end)
local end_time = 0
local overlaps = { }
local _list_0 = dialogue
for _index_0 = 1, #_list_0 do
local line = _list_0[_index_0]
if line.start_time >= end_time then
end_time = line.end_time
else
table.insert(overlaps, line.i)
end
end
return overlaps
end
return aegisub.register_macro(script_name, script_description, select_overlaps)

View File

@ -1,314 +0,0 @@
local regex = aegisub.__init_regex()
local select_first
select_first = function(n, a, ...)
if n == 0 then
return
end
return a, select_first(n - 1, ...)
end
local unpack_args
unpack_args = function(...)
local userdata_start = nil
for i = 1, select('#', ...) do
local v = select(i, ...)
if type(v) == 'userdata' then
userdata_start = i
break
end
end
if not (userdata_start) then
return 0, ...
end
local flags = regex.process_flags(select(userdata_start, ...))
if type(flags) == 'string' then
error(flags, 3)
end
return flags, select_first(userdata_start - 1, ...)
end
local check_arg
check_arg = function(arg, expected_type, argn, func_name, level)
if type(arg) ~= expected_type then
return error("Argument " .. tostring(argn) .. " to " .. tostring(func_name) .. " should be a '" .. tostring(expected_type) .. "', is '" .. tostring(type(arg)) .. "' (" .. tostring(arg) .. ")", level + 1)
end
end
local replace_match
replace_match = function(match, func, str, last, acc)
if last < match.last then
acc[#acc + 1] = str:sub(last, match.first - 1)
end
local repl = func(match.str, match.first, match.last)
if type(repl) == 'string' then
acc[#acc + 1] = repl
else
acc[#acc + 1] = match.str
end
return match.first, match.last + 1
end
local do_single_replace_fun
do_single_replace_fun = function(re, func, str, acc, pos)
local matches = re:match(str, pos)
if not (matches) then
return pos
end
local start
if #matches == 1 then
start = 1
else
start = 2
end
local last = pos
local first
for i = start, #matches do
first, last = replace_match(matches[i], func, str, last, acc)
end
if first == last then
acc[#acc + 1] = str:sub(last, last)
last = last + 1
end
return last, matches[1].first <= str:len()
end
local do_replace_fun
do_replace_fun = function(re, func, str, max)
local acc = { }
local pos = 1
local i
for i = 1, max do
local more
pos, more = do_single_replace_fun(re, func, str, acc, pos)
if not (more) then
max = i
break
end
end
return table.concat(acc, '') .. str:sub(pos)
end
local RegEx
do
local start
local _parent_0 = nil
local _base_0 = {
_check_self = function(self)
if not (self.__class == RegEx) then
return error('re method called with invalid self. You probably used . when : is needed.', 3)
end
end,
gsplit = function(self, str, skip_empty, max_split)
self:_check_self()
check_arg(str, 'string', 2, 'gsplit', self._level)
if not max_split or max_split <= 0 then
max_split = str:len()
end
start = 1
local prev = 1
local do_split
do_split = function()
if not str or str:len() == 0 then
return
end
local first, last
if max_split > 0 then
first, last = regex.search(self._regex, str, start)
end
if not first or first > str:len() then
local ret = str:sub(prev, str:len())
str = nil
return ret
end
local ret = str:sub(prev, first - 1)
prev = last + 1
start = 1 + (function()
if start >= last then
return start
else
return last
end
end)()
if skip_empty and ret:len() == 0 then
return do_split()
else
max_split = max_split - 1
return ret
end
end
return do_split
end,
split = function(self, str, skip_empty, max_split)
self:_check_self()
check_arg(str, 'string', 2, 'split', self._level)
return (function()
local _accum_0 = { }
local _len_0 = 1
for v in self:gsplit(str, skip_empty, max_split) do
_accum_0[_len_0] = v
_len_0 = _len_0 + 1
end
return _accum_0
end)()
end,
gfind = function(self, str)
self:_check_self()
check_arg(str, 'string', 2, 'gfind', self._level)
start = 1
return function()
local first, last = regex.search(self._regex, str, start)
if not (first) then
return
end
if last >= start then
start = last + 1
else
start = start + 1
end
return str:sub(first, last), first, last
end
end,
find = function(self, str)
self:_check_self()
check_arg(str, 'string', 2, 'find', self._level)
local ret = (function()
local _accum_0 = { }
local _len_0 = 1
for s, f, l in self:gfind(str) do
_accum_0[_len_0] = {
str = s,
first = f,
last = l
}
_len_0 = _len_0 + 1
end
return _accum_0
end)()
return next(ret) and ret
end,
sub = function(self, str, repl, max_count)
self:_check_self()
check_arg(str, 'string', 2, 'sub', self._level)
if max_count ~= nil then
check_arg(max_count, 'number', 4, 'sub', self._level)
end
if not max_count or max_count == 0 then
max_count = str:len() + 1
end
if type(repl) == 'function' then
return do_replace_fun(self, repl, str, max_count)
elseif type(repl) == 'string' then
return regex.replace(self._regex, repl, str, max_count)
else
return error("Argument 2 to sub should be a string or function, is '" .. tostring(type(repl)) .. "' (" .. tostring(repl) .. ")", self._level)
end
end,
gmatch = function(self, str, start)
self:_check_self()
check_arg(str, 'string', 2, 'gmatch', self._level)
if start then
start = start - 1
else
start = 0
end
local match = regex.match(self._regex, str, start)
local i = 1
return function()
if not (match) then
return
end
local first, last = regex.get_match(match, i)
if not (first) then
return
end
i = i + 1
return {
str = str:sub(first + start, last + start),
first = first + start,
last = last + start
}
end
end,
match = function(self, str, start)
self:_check_self()
check_arg(str, 'string', 2, 'match', self._level)
local ret = (function()
local _accum_0 = { }
local _len_0 = 1
for v in self:gmatch(str, start) do
_accum_0[_len_0] = v
_len_0 = _len_0 + 1
end
return _accum_0
end)()
if next(ret) == nil then
return nil
end
return ret
end
}
_base_0.__index = _base_0
if _parent_0 then
setmetatable(_base_0, _parent_0.__base)
end
local _class_0 = setmetatable({
__init = function(self, _regex, _level)
self._regex, self._level = _regex, _level
end,
__base = _base_0,
__name = "RegEx",
__parent = _parent_0
}, {
__index = function(cls, name)
local val = rawget(_base_0, name)
if val == nil and _parent_0 then
return _parent_0[name]
else
return val
end
end,
__call = function(cls, ...)
local _self_0 = setmetatable({}, _base_0)
cls.__init(_self_0, ...)
return _self_0
end
})
_base_0.__class = _class_0
local self = _class_0
start = 1
if _parent_0 and _parent_0.__inherited then
_parent_0.__inherited(_parent_0, _class_0)
end
RegEx = _class_0
end
local real_compile
real_compile = function(pattern, level, flags, stored_level)
if pattern == '' then
error('Regular expression must not be empty', level + 1)
end
local re = regex.compile(pattern, flags)
if type(re) == 'string' then
error(regex, level + 1)
end
return RegEx(re, stored_level or level + 1)
end
local invoke
invoke = function(str, pattern, fn, flags, ...)
local compiled_regex = real_compile(pattern, 3, flags)
return compiled_regex[fn](compiled_regex, str, ...)
end
local gen_wrapper
gen_wrapper = function(impl_name)
return function(str, pattern, ...)
check_arg(str, 'string', 1, impl_name, 2)
check_arg(pattern, 'string', 2, impl_name, 2)
return invoke(str, pattern, impl_name, unpack_args(...))
end
end
local re = regex.init_flags(re)
re.compile = function(pattern, ...)
check_arg(pattern, 'string', 1, 'compile', 2)
return real_compile(pattern, 2, regex.process_flags(...), 2)
end
re.split = gen_wrapper('split')
re.gsplit = gen_wrapper('gsplit')
re.find = gen_wrapper('find')
re.gfind = gen_wrapper('gfind')
re.match = gen_wrapper('match')
re.gmatch = gen_wrapper('gmatch')
re.sub = gen_wrapper('sub')
return re

View File

@ -1,58 +0,0 @@
local unicode
unicode = {
charwidth = function(s, i)
local b = s:byte(i or 1)
if not b then
return 1
elseif b < 128 then
return 1
elseif b < 224 then
return 2
elseif b < 240 then
return 3
else
return 4
end
end,
chars = function(s)
local curchar, i = 0, 1
return function()
if i > s:len() then
return
end
local j = i
curchar = curchar + 1
i = i + unicode.charwidth(s, i)
return s:sub(j, i - 1), curchar
end
end,
len = function(s)
local n = 0
for c in unicode.chars(s) do
n = n + 1
end
return n
end,
codepoint = function(s)
local b = s:byte(1)
if b < 128 then
return b
end
local res, w
if b < 224 then
res = b - 192
w = 2
elseif b < 240 then
res = b - 224
w = 3
else
res = b - 240
w = 4
end
for i = 2, w do
res = res * 64 + s:byte(i) - 128
end
return res
end
}
return unicode

View File

@ -1,242 +0,0 @@
local sformat = string.format
local copy
copy = function(tbl)
return (function()
local _tbl_0 = { }
for k, v in pairs(tbl) do
_tbl_0[k] = v
end
return _tbl_0
end)()
end
local deep_copy
deep_copy = function(tbl)
local seen = { }
copy = function(val)
if type(tbl) ~= 'table' then
return val
end
if seen[tbl] then
return seen[val]
end
seen[val] = tbl
return (function()
local _tbl_0 = { }
for k, v in pairs(val) do
_tbl_0[k] = copy(v)
end
return _tbl_0
end)()
end
return copy(tbl)
end
local ass_color
ass_color = function(r, g, b)
return sformat("&H%02X%02X%02X&", b, g, r)
end
local ass_alpha
ass_alpha = function(a)
return sformat("&H%02X&", a)
end
local ass_style_color
ass_style_color = function(r, g, b, a)
return sformat("&H%02X%02X%02X%02X", a, b, g, r)
end
local extract_color
extract_color = function(s)
local a, b, g, r
a, b, g, r = s:match('&H(%x%x)(%x%x)(%x%x)(%x%x)')
if a then
return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), tonumber(a, 16)
end
b, g, r = s:match('&H(%x%x)(%x%x)(%x%x)&')
if b then
return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), 0
end
a = s:match('&H(%x%x)&')
if a then
return 0, 0, 0, tonumber(a, 16)
end
r, g, b, a = s:match('#(%x%x)(%x?%x?)(%x?%x?)(%x?%x?)')
if r then
return tonumber(r, 16), tonumber(g, 16) or 0, tonumber(b, 16) or 0, tonumber(a, 16) or 0
end
end
local alpha_from_style
alpha_from_style = function(scolor)
return ass_alpha(select(4, extract_color(scolor)))
end
local color_from_style
color_from_style = function(scolor)
local r, g, b = extract_color(scolor)
return ass_color(r or 0, g or 0, b or 0)
end
local HSV_to_RGB
HSV_to_RGB = function(H, S, V)
local r, g, b = 0, 0, 0
if S == 0 then
r = self:clamp(V * 255, 0, 255)
g = r
b = r
else
H = math.abs(H) % 360
local Hi = math.floor(H / 60)
local f = H / 60.0 - Hi
local p = V * (1 - S)
local q = V * (1 - f * S)
local t = V * (1 - (1 - f) * S)
if Hi == 0 then
r = V * 255.0
g = t * 255.0
b = p * 255.0
elseif Hi == 1 then
r = q * 255.0
g = V * 255.0
b = p * 255.0
elseif Hi == 2 then
r = p * 255.0
g = V * 255.0
b = t * 255.0
elseif Hi == 3 then
r = p * 255.0
g = q * 255.0
b = V * 255.0
elseif Hi == 4 then
r = t * 255.0
g = p * 255.0
b = V * 255.0
elseif Hi == 5 then
r = V * 255.0
g = p * 255.0
b = q * 255.0
else
error("math.floor(H % 360 / 60) should be [0, 6), is " .. tostring(Hi) .. "?")
end
end
return r, g, b
end
local HSL_to_RGB
HSL_to_RGB = function(H, S, L)
local r, g, b
H = math.abs(H) % 360
S = clamp(S, 0, 1)
L = clamp(L, 0, 1)
if S == 0 then
r = L
g = L
b = L
else
local Q
if L < 0.5 then
Q = L * (1.0 + S)
else
Q = L + S - (L * S)
end
local P = 2.0 * L - Q
local Hk = H / 360
local Tr, Tg, Tb
if Hk < 1 / 3 then
Tr = Hk + 1 / 3
Tg = Hk
Tb = Hk + 2 / 3
elseif Hk > 2 / 3 then
Tr = Hk - 2 / 3
Tg = Hk
Tb = Hk - 1 / 3
else
Tr = Hk + 1 / 3
Tg = Hk
Tb = Hk - 1 / 3
end
local get_component
get_component = function(T)
if T < 1 / 6 then
return P + ((Q - P) * 6.0 * T)
elseif 1 / 6 <= T and T < 1 / 2 then
return Q
elseif 1 / 2 <= T and T < 2 / 3 then
return P + ((Q - P) * (2 / 3 - T) * 6.0)
else
return P
end
end
r = get_component(Tr)
g = get_component(Tg)
b = get_component(Tb)
end
return math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5), math.floor(b * 255 + 0.5)
end
local trim
trim = function(s)
return s:gsub('^%s*(.-)%s*$', '%1')
end
local headtail
headtail = function(s)
local a, b, head, tail = s:find('(.-)%s+(.*)')
if a then
return head, tail
else
return s, ''
end
end
local words
words = function(s)
return function()
if s == '' then
return
end
local head, tail = headtail(s)
s = tail
return head
end
end
local clamp
clamp = function(val, min, max)
if val < min then
return min
elseif val > max then
return max
else
return val
end
end
local interpolate
interpolate = function(pct, min, max)
if pct <= 0 then
return min
elseif pct >= 1 then
return max
else
return pct * (max - min) + min
end
end
local interpolate_color
interpolate_color = function(pct, first, last)
local r1, g1, b1 = extract_color(first)
local r2, g2, b2 = extract_color(last)
local r, g, b = interpolate(pct, r1, r2), interpolate(pct, g1, g2), interpolate(pct, b1, b2)
return ass_color(r, g, b)
end
local interpolate_alpha
interpolate_alpha = function(pct, first, last)
return ass_alpha(interpolate(pct, select(4, extract_color(first)), select(4, extract_color(last))))
end
return {
copy = copy,
deep_copy = deep_copy,
ass_color = ass_color,
ass_alpha = ass_alpha,
ass_style_color = ass_style_color,
extract_color = extract_color,
alpha_from_style = alpha_from_style,
color_from_style = color_from_style,
HSV_to_RGB = HSV_to_RGB,
HSL_to_RGB = HSL_to_RGB,
trim = trim,
headtail = headtail,
words = words,
clamp = clamp,
interpolate = interpolate,
interpolate_color = interpolate_color,
interpolate_alpha = interpolate_alpha
}

File diff suppressed because it is too large Load Diff

View File

@ -51,6 +51,7 @@
#include <libaegisub/fs.h>
#include <libaegisub/path.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/format.hpp>
#include <boost/tokenizer.hpp>
@ -517,8 +518,10 @@ namespace Automation4 {
if (fact->GetEngineName().empty() || fact->GetFilenamePattern().empty())
continue;
fnfilter += str(boost::format("%s scripts (%s)|%s|") % fact->GetEngineName() % fact->GetFilenamePattern() % fact->GetFilenamePattern());
catchall += fact->GetFilenamePattern() + ";";
std::string filter(fact->GetFilenamePattern());
boost::replace_all(filter, ",", ";");
fnfilter += str(boost::format("%s scripts (%s)|%s|") % fact->GetEngineName() % fact->GetFilenamePattern() % filter);
catchall += filter + ";";
}
fnfilter += from_wx(_("All Files")) + " (*.*)|*.*";

View File

@ -191,6 +191,103 @@ namespace {
TableIter::init(L);
return 3;
}
int frame_from_ms(lua_State *L)
{
const agi::Context *c = get_context(L);
int ms = lua_tointeger(L, -1);
lua_pop(L, 1);
if (c && c->videoController->TimecodesLoaded())
push_value(L, c->videoController->FrameAtTime(ms, agi::vfr::START));
else
lua_pushnil(L);
return 1;
}
int ms_from_frame(lua_State *L)
{
const agi::Context *c = get_context(L);
int frame = lua_tointeger(L, -1);
lua_pop(L, 1);
if (c && c->videoController->TimecodesLoaded())
push_value(L, c->videoController->TimeAtFrame(frame, agi::vfr::START));
else
lua_pushnil(L);
return 1;
}
int video_size(lua_State *L)
{
const agi::Context *c = get_context(L);
if (c && c->videoController->IsLoaded()) {
push_value(L, c->videoController->GetWidth());
push_value(L, c->videoController->GetHeight());
push_value(L, c->videoController->GetAspectRatioValue());
push_value(L, (int)c->videoController->GetAspectRatioType());
return 4;
}
else {
lua_pushnil(L);
return 1;
}
}
int get_keyframes(lua_State *L)
{
const agi::Context *c = get_context(L);
if (!c) {
lua_pushnil(L);
return 1;
}
std::vector<int> const& kf = c->videoController->GetKeyFrames();
lua_newtable(L);
for (size_t i = 0; i < kf.size(); ++i) {
push_value(L, kf[i]);
lua_rawseti(L, -2, i);
}
return 1;
}
int decode_path(lua_State *L)
{
std::string path = luaL_checkstring(L, 1);
lua_pop(L, 1);
push_value(L, config::path->Decode(path));
return 1;
}
int cancel_script(lua_State *L)
{
lua_pushnil(L);
return lua_error(L);
}
int lua_text_textents(lua_State *L)
{
luaL_argcheck(L, lua_istable(L, 1), 1, "");
luaL_argcheck(L, lua_isstring(L, 2), 2, "");
lua_pushvalue(L, 1);
agi::scoped_ptr<AssEntry> et(Automation4::LuaAssFile::LuaToAssEntry(L));
AssStyle *st = dynamic_cast<AssStyle*>(et.get());
lua_pop(L, 1);
if (!st)
return luaL_error(L, "Not a style entry");
double width, height, descent, extlead;
if (!Automation4::CalculateTextExtents(st, luaL_checkstring(L, 2), width, height, descent, extlead))
return luaL_error(L, "Some internal error occurred calculating text_extents");
push_value(L, width);
push_value(L, height);
push_value(L, descent);
push_value(L, extlead);
return 4;
}
}
extern "C" int luaopen_lpeg (lua_State *L);
@ -198,19 +295,56 @@ extern "C" int luaopen_lpeg (lua_State *L);
namespace Automation4 {
int regex_init(lua_State *L);
// LuaScript
class LuaScript : public Script {
lua_State *L;
std::string name;
std::string description;
std::string author;
std::string version;
std::vector<cmd::Command*> macros;
std::vector<ExportFilter*> filters;
/// load script and create internal structures etc.
void Create();
/// destroy internal structures, unreg features and delete environment
void Destroy();
static int LuaInclude(lua_State *L);
static int LuaModuleLoader(lua_State *L);
public:
LuaScript(agi::fs::path const& filename);
~LuaScript() { Destroy(); }
void RegisterCommand(LuaCommand *command);
void UnregisterCommand(LuaCommand *command);
void RegisterFilter(LuaExportFilter *filter);
static LuaScript* GetScriptObject(lua_State *L);
// Script implementation
void Reload() { Create(); }
std::string GetName() const { return name; }
std::string GetDescription() const { return description; }
std::string GetAuthor() const { return author; }
std::string GetVersion() const { return version; }
bool GetLoadedState() const { return L != 0; }
std::vector<cmd::Command*> GetMacros() const { return macros; }
std::vector<ExportFilter*> GetFilters() const { return filters; }
std::vector<SubtitleFormat*> GetFormats() const { return std::vector<SubtitleFormat*>(); }
};
LuaScript::LuaScript(agi::fs::path const& filename)
: Script(filename)
, L(0)
, L(nullptr)
{
Create();
}
LuaScript::~LuaScript()
{
Destroy();
}
void LuaScript::Create()
{
Destroy();
@ -283,13 +417,13 @@ namespace Automation4 {
set_field(L, "register_macro", LuaCommand::LuaRegister);
set_field(L, "register_filter", LuaExportFilter::LuaRegister);
set_field(L, "text_extents", LuaTextExtents);
set_field(L, "frame_from_ms", LuaFrameFromMs);
set_field(L, "ms_from_frame", LuaMsFromFrame);
set_field(L, "video_size", LuaVideoSize);
set_field(L, "keyframes", LuaGetKeyframes);
set_field(L, "decode_path", LuaDecodePath);
set_field(L, "cancel", LuaCancel);
set_field(L, "text_extents", lua_text_textents);
set_field(L, "frame_from_ms", frame_from_ms);
set_field(L, "ms_from_frame", ms_from_frame);
set_field(L, "video_size", video_size);
set_field(L, "keyframes", get_keyframes);
set_field(L, "decode_path", decode_path);
set_field(L, "cancel", cancel_script);
set_field(L, "lua_automation_version", 4);
set_field(L, "__init_regex", regex_init);
set_field(L, "__init_clipboard", clipboard_init);
@ -301,8 +435,7 @@ namespace Automation4 {
_stackcheck.check_stack(0);
// load user script
LuaScriptReader script_reader(GetFilename());
if (lua_load(L, script_reader.reader_func, &script_reader, GetPrettyFilename().string().c_str())) {
if (!LoadFile(L, GetFilename())) {
std::string err = str(boost::format("Error loading Lua script \"%s\":\n\n%s") % GetPrettyFilename().string() % get_string_or_default(L, -1));
lua_pop(L, 1);
throw ScriptLoadError(err);
@ -359,12 +492,7 @@ namespace Automation4 {
delete_clear(filters);
lua_close(L);
L = 0;
}
void LuaScript::Reload()
{
Create();
L = nullptr;
}
void LuaScript::RegisterCommand(LuaCommand *command)
@ -397,29 +525,6 @@ namespace Automation4 {
return (LuaScript*)ptr;
}
int LuaScript::LuaTextExtents(lua_State *L)
{
luaL_argcheck(L, lua_istable(L, 1), 1, "");
luaL_argcheck(L, lua_isstring(L, 2), 2, "");
lua_pushvalue(L, 1);
agi::scoped_ptr<AssEntry> et(LuaAssFile::LuaToAssEntry(L));
AssStyle *st = dynamic_cast<AssStyle*>(et.get());
lua_pop(L, 1);
if (!st)
return luaL_error(L, "Not a style entry");
double width, height, descent, extlead;
if (!CalculateTextExtents(st, luaL_checkstring(L, 2), width, height, descent, extlead))
return luaL_error(L, "Some internal error occurred calculating text_extents");
push_value(L, width);
push_value(L, height);
push_value(L, descent);
push_value(L, extlead);
return 4;
}
/// @brief Module loader which uses our include rather than Lua's, for unicode file support
/// @param L The Lua state
/// @return Always 1 per loader_Lua?
@ -431,18 +536,27 @@ namespace Automation4 {
// Get the lua package include path (which the user may have modified)
lua_getglobal(L, "package");
push_value(L, "path");
lua_gettable(L, -2);
lua_getfield(L, -1, "path");
std::string package_paths(luaL_checkstring(L, -1));
lua_pop(L, 2);
boost::char_separator<char> sep(";");
for (auto filename : boost::tokenizer<boost::char_separator<char>>(package_paths, sep)) {
boost::replace_all(filename, "?", module);
// If there's a .moon file at that path, load it instead of the
// .lua file
agi::fs::path path = filename;
if (agi::fs::HasExtension(path, "lua")) {
agi::fs::path moonpath = path;
moonpath.replace_extension("moon");
if (agi::fs::FileExists(moonpath))
path = moonpath;
}
try {
LuaScriptReader script_reader(filename);
if (lua_load(L, script_reader.reader_func, &script_reader, filename.c_str()))
return luaL_error(L, "Error loading Lua module \"%s\":\n\n%s", filename.c_str(), luaL_checkstring(L, -1));
if (!LoadFile(L, path))
return luaL_error(L, "Error loading Lua module \"%s\":\n\n%s", path.string().c_str(), luaL_checkstring(L, -1));
break;
}
catch (agi::fs::FileNotFound const&) {
@ -452,7 +566,7 @@ namespace Automation4 {
// Not an error so swallow and continue on
}
catch (agi::Exception const& e) {
return luaL_error(L, "Error loading Lua module \"%s\":\n\n%s", filename.c_str(), e.GetChainedMessage().c_str());
return luaL_error(L, "Error loading Lua module \"%s\":\n\n%s", path.string().c_str(), e.GetChainedMessage().c_str());
}
}
@ -480,8 +594,7 @@ namespace Automation4 {
if (!agi::fs::FileExists(filepath))
return luaL_error(L, "Lua include not found: %s", filename.c_str());
LuaScriptReader script_reader(filepath);
if (lua_load(L, script_reader.reader_func, &script_reader, filename.c_str()))
if (!LoadFile(L, filepath))
return luaL_error(L, "Error loading Lua include \"%s\":\n\n%s", filename.c_str(), luaL_checkstring(L, -1));
int pretop = lua_gettop(L) - 1; // don't count the function value itself
@ -489,80 +602,6 @@ namespace Automation4 {
return lua_gettop(L) - pretop;
}
int LuaScript::LuaFrameFromMs(lua_State *L)
{
const agi::Context *c = get_context(L);
int ms = lua_tointeger(L, -1);
lua_pop(L, 1);
if (c && c->videoController->TimecodesLoaded())
push_value(L, c->videoController->FrameAtTime(ms, agi::vfr::START));
else
lua_pushnil(L);
return 1;
}
int LuaScript::LuaMsFromFrame(lua_State *L)
{
const agi::Context *c = get_context(L);
int frame = lua_tointeger(L, -1);
lua_pop(L, 1);
if (c && c->videoController->TimecodesLoaded())
push_value(L, c->videoController->TimeAtFrame(frame, agi::vfr::START));
else
lua_pushnil(L);
return 1;
}
int LuaScript::LuaVideoSize(lua_State *L)
{
const agi::Context *c = get_context(L);
if (c && c->videoController->IsLoaded()) {
push_value(L, c->videoController->GetWidth());
push_value(L, c->videoController->GetHeight());
push_value(L, c->videoController->GetAspectRatioValue());
push_value(L, (int)c->videoController->GetAspectRatioType());
return 4;
}
else {
lua_pushnil(L);
return 1;
}
}
int LuaScript::LuaGetKeyframes(lua_State *L)
{
const agi::Context *c = get_context(L);
if (!c) {
lua_pushnil(L);
return 1;
}
std::vector<int> const& kf = c->videoController->GetKeyFrames();
lua_newtable(L);
for (size_t i = 0; i < kf.size(); ++i) {
push_value(L, kf[i]);
lua_rawseti(L, -2, i);
}
return 1;
}
int LuaScript::LuaDecodePath(lua_State *L)
{
std::string path = luaL_checkstring(L, 1);
lua_pop(L, 1);
push_value(L, config::path->Decode(path));
return 1;
}
int LuaScript::LuaCancel(lua_State *L)
{
lua_pushnil(L);
return lua_error(L);
}
void LuaThreadedCall(lua_State *L, int nargs, int nresults, std::string const& title, wxWindow *parent, bool can_open_config)
{
bool failed = false;
@ -934,18 +973,15 @@ namespace Automation4 {
}
LuaScriptFactory::LuaScriptFactory()
: ScriptFactory("Lua", "*.lua")
: ScriptFactory("Lua", "*.lua,*.moon")
{
Register(this);
}
Script* LuaScriptFactory::Produce(agi::fs::path const& filename) const
{
// Just check if file extension is .lua
// Reject anything else
if (agi::fs::HasExtension(filename, "lua"))
if (agi::fs::HasExtension(filename, "lua") || agi::fs::HasExtension(filename, "moon"))
return new LuaScript(filename);
else
return 0;
return nullptr;
}
}

View File

@ -276,54 +276,4 @@ namespace Automation4 {
void ProcessSubs(AssFile *subs, wxWindow *export_dialog);
};
class LuaScript : public Script {
lua_State *L;
std::string name;
std::string description;
std::string author;
std::string version;
std::vector<cmd::Command*> macros;
std::vector<ExportFilter*> filters;
/// load script and create internal structures etc.
void Create();
/// destroy internal structures, unreg features and delete environment
void Destroy();
static int LuaTextExtents(lua_State *L);
static int LuaInclude(lua_State *L);
static int LuaModuleLoader(lua_State *L);
static int LuaFrameFromMs(lua_State *L);
static int LuaMsFromFrame(lua_State *L);
static int LuaVideoSize(lua_State *L);
static int LuaGetKeyframes(lua_State *L);
static int LuaDecodePath(lua_State *L);
static int LuaCancel(lua_State *L);
public:
LuaScript(agi::fs::path const& filename);
~LuaScript();
void RegisterCommand(LuaCommand *command);
void UnregisterCommand(LuaCommand *command);
void RegisterFilter(LuaExportFilter *filter);
static LuaScript* GetScriptObject(lua_State *L);
// Script implementation
void Reload();
std::string GetName() const { return name; }
std::string GetDescription() const { return description; }
std::string GetAuthor() const { return author; }
std::string GetVersion() const { return version; }
bool GetLoadedState() const { return L != 0; }
std::vector<cmd::Command*> GetMacros() const { return macros; }
std::vector<ExportFilter*> GetFilters() const { return filters; }
std::vector<SubtitleFormat*> GetFormats() const { return std::vector<SubtitleFormat*>(); }
};
}

View File

@ -22,33 +22,41 @@
#include "auto4_lua_scriptreader.h"
#include <libaegisub/io.h>
#include <libaegisub/fs.h>
#include <fstream>
#include <lua.hpp>
#include <memory>
namespace Automation4 {
LuaScriptReader::LuaScriptReader(agi::fs::path const& filename)
: file(agi::io::Open(filename))
, first(true)
{
}
bool LoadFile(lua_State *L, agi::fs::path const& filename) {
std::unique_ptr<std::istream> file(agi::io::Open(filename, true));
file->seekg(0, std::ios::end);
size_t size = file->tellg();
file->seekg(0, std::ios::beg);
LuaScriptReader::~LuaScriptReader() { }
std::string buff;
buff.resize(size);
const char *LuaScriptReader::Read(size_t *bytes_read) {
file->read(buf, sizeof(buf));
*bytes_read = file->gcount();
if (first) {
first = false;
// Skip the bom
if (*bytes_read >= 3 && buf[0] == -17 && buf[1] == -69 && buf[2] == -65) {
*bytes_read -= 3;
return buf + 3;
}
// Discard the BOM if present
file->read(&buff[0], 3);
size_t start = file->gcount();
if (start == 3 && buff[0] == -17 && buff[1] == -69 && buff[2] == -65) {
buff.resize(size - 3);
start = 0;
}
return buf;
}
const char* LuaScriptReader::reader_func(lua_State *, void *data, size_t *size) {
return static_cast<LuaScriptReader*>(data)->Read(size);
file->read(&buff[start], size - start);
if (!agi::fs::HasExtension(filename, "moon"))
return luaL_loadbuffer(L, &buff[0], buff.size(), filename.string().c_str()) == 0;
// We have a MoonScript file, so we need to load it with that
// It might be nice to have a dedicated lua state for compiling
// MoonScript to Lua
if (luaL_dostring(L, "return require('moonscript').loadstring"))
return false; // Leaves error message on stack
lua_pushlstring(L, &buff[0], buff.size());
return lua_pcall(L, 1, 1, 0) == 0; // Leaves script or error message on stack
}
}

View File

@ -19,23 +19,8 @@
#include <libaegisub/fs_fwd.h>
#include <iosfwd>
#include <memory>
#include <string>
struct lua_State;
namespace Automation4 {
class LuaScriptReader {
std::unique_ptr<std::istream> file;
bool first;
char buf[512];
const char *Read(size_t *bytes_read);
public:
LuaScriptReader(agi::fs::path const& filename);
~LuaScriptReader();
static const char* reader_func(lua_State *, void *data, size_t *size);
};
bool LoadFile(lua_State *L, agi::fs::path const& filename);
}