diff --git a/automation/include/aegisub/ffi.moon b/automation/include/aegisub/ffi.moon new file mode 100644 index 000000000..b58d1e5e5 --- /dev/null +++ b/automation/include/aegisub/ffi.moon @@ -0,0 +1,48 @@ +-- Copyright (c) 2014, Thomas Goyne +-- +-- Permission to use, copy, modify, and distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- +-- Aegisub Project http://www.aegisub.org/ + +ffi = require 'ffi' +ffi.cdef[[ + void free(void *ptr); +]] + +char_ptr = ffi.typeof 'char *' + +-- Convert a const char * to a lua string, returning nil rather than crashing +-- if it's NULL and freeing the input if it's non-const +string = (cdata) -> + return nil if cdata == nil + str = ffi.string cdata + if type(cdata) == char_ptr + ffi.C.free cdata + str + +err_buff = ffi.new 'char *[1]' + +-- Convert a function which has an error out parameter to one which returns +-- the original return value and the error as a string +err_arg_to_multiple_return = (f) -> (arg) -> + err_buff[0] = nil + result = if arg != nil + f arg, err_buff + else + f err_buff + errmsg = string err_buff[0] + if errmsg + return nil, errmsg + return result + +{:string, :err_arg_to_multiple_return} diff --git a/automation/include/aegisub/lfs.moon b/automation/include/aegisub/lfs.moon new file mode 100644 index 000000000..989467a6d --- /dev/null +++ b/automation/include/aegisub/lfs.moon @@ -0,0 +1,80 @@ +-- Copyright (c) 2014, Thomas Goyne +-- +-- Permission to use, copy, modify, and distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +-- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +-- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +-- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- +-- Aegisub Project http://www.aegisub.org/ + +impl = require 'aegisub.__lfs_impl' +ffi = require 'ffi' +ffi_util = require 'aegisub.ffi' + +for k, v in pairs impl + impl[k] = ffi_util.err_arg_to_multiple_return v + +string_ret = (f) -> (...) -> + res, err = f ... + ffi_util.string(res), err + +number_ret = (f) -> (...) -> + res, err = f ... + tonumber(res), err + +attributes = (path, field) -> + switch field + when 'mode' + res, err = impl.get_mode path + ffi_util.string(res), err + when 'modification' + res, err = impl.get_mtime path + tonumber(res), err + when 'size' + res, err = impl.get_size path + tonumber(res), err + else + mode, err = impl.get_mode path + if err or mode == nil then return nil, err + + mod, err = impl.get_mtime path + if err then return nil, err + + size, err = impl.get_size path + if err then return nil, err + + mode: ffi_util.string(mode), modification: tonumber(mod), size: tonumber(size) + +class dir_iter + new: (iter) => + @iter = ffi.gc iter, -> impl.dir_free iter + close: => + impl.dir_close @iter + next: => + str, err = impl.dir_next @iter + if err then error err, 2 + ffi_util.string str + +dir = (path) -> + obj, err = impl.dir_new path + if err + error 2, err + iter = dir_iter obj + iter.next, iter + +return { + :attributes + chdir: number_ret impl.chdir + currentdir: string_ret impl.currentdir + :dir + mkdir: number_ret impl.mkdir + rmdir: number_ret impl.rmdir + touch: number_ret impl.touch +} diff --git a/automation/include/aegisub/unicode.moon b/automation/include/aegisub/unicode.moon index 0b5053e6a..e66090485 100644 --- a/automation/include/aegisub/unicode.moon +++ b/automation/include/aegisub/unicode.moon @@ -30,25 +30,16 @@ impl = require 'aegisub.__unicode_impl' ffi = require 'ffi' -ffi.cdef[[ - void free(void *ptr); -]] +ffi_util = require 'aegisub.ffi' -transfer_string = (cdata) -> - return nil if cdata == nil - str = ffi.string cdata - ffi.C.free cdata - str - -conv_func = (f) -> - err = ffi.new 'char *[1]' - (str) -> - err[0] = nil - result = f str, err - errmsg = transfer_string err[0] - if errmsg - error errmsg, 2 - transfer_string result +err_buff = ffi.new 'char *[1]' +conv_func = (f) -> (str) -> + err_buff[0] = nil + result = f str, err_buff + errmsg = ffi_util.string err_buff[0] + if errmsg + error errmsg, 2 + ffi_util.string result local unicode unicode = diff --git a/automation/include/lfs.lua b/automation/include/lfs.lua new file mode 100644 index 000000000..4b4e542f3 --- /dev/null +++ b/automation/include/lfs.lua @@ -0,0 +1,2 @@ +lfs = require 'aegisub.lfs' +return lfs diff --git a/automation/tests/modules/lfs.moon b/automation/tests/modules/lfs.moon index e72b314a6..74ca7e073 100644 --- a/automation/tests/modules/lfs.moon +++ b/automation/tests/modules/lfs.moon @@ -12,7 +12,7 @@ -- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -lfs = require 'lfs' +lfs = require 'aegisub.lfs' uuid = require 'uuid' uuid.randomseed os.time() diff --git a/libaegisub/include/libaegisub/type_name.h b/libaegisub/include/libaegisub/type_name.h index e777404bf..666b0074e 100644 --- a/libaegisub/include/libaegisub/type_name.h +++ b/libaegisub/include/libaegisub/type_name.h @@ -21,15 +21,19 @@ namespace agi { // Currently supports primitives, pointers, references, const, and function pointers template struct type_name; -#define AGI_TYPE_NAME_PRIMITIVE(type) \ +#define AGI_DEFINE_TYPE_NAME(type) \ template<> struct type_name { static const char *name() { return #type; }} -AGI_TYPE_NAME_PRIMITIVE(bool); -AGI_TYPE_NAME_PRIMITIVE(char); -AGI_TYPE_NAME_PRIMITIVE(double); -AGI_TYPE_NAME_PRIMITIVE(float); -AGI_TYPE_NAME_PRIMITIVE(int); -AGI_TYPE_NAME_PRIMITIVE(void); +AGI_DEFINE_TYPE_NAME(bool); +AGI_DEFINE_TYPE_NAME(char); +AGI_DEFINE_TYPE_NAME(double); +AGI_DEFINE_TYPE_NAME(float); +AGI_DEFINE_TYPE_NAME(int); +AGI_DEFINE_TYPE_NAME(long long); +AGI_DEFINE_TYPE_NAME(long); +AGI_DEFINE_TYPE_NAME(size_t); +AGI_DEFINE_TYPE_NAME(unsigned long long); +AGI_DEFINE_TYPE_NAME(void); #undef AGI_TYPE_NAME_PRIMITIVE diff --git a/libaegisub/lua/modules.cpp b/libaegisub/lua/modules.cpp index 305f9fb22..4777bb396 100644 --- a/libaegisub/lua/modules.cpp +++ b/libaegisub/lua/modules.cpp @@ -21,7 +21,7 @@ extern "C" int luaopen_luabins(lua_State *L); extern "C" int luaopen_re_impl(lua_State *L); extern "C" int luaopen_unicode_impl(lua_State *L); -extern "C" int luaopen_lfs(lua_State *L); +extern "C" int luaopen_lfs_impl(lua_State *L); extern "C" int luaopen_lpeg(lua_State *L); namespace agi { namespace lua { @@ -35,7 +35,7 @@ void preload_modules(lua_State *L) { set_field(L, "aegisub.__re_impl", luaopen_re_impl); set_field(L, "aegisub.__unicode_impl", luaopen_unicode_impl); - set_field(L, "lfs", luaopen_lfs); + set_field(L, "aegisub.__lfs_impl", luaopen_lfs_impl); set_field(L, "lpeg", luaopen_lpeg); set_field(L, "luabins", luaopen_luabins); diff --git a/libaegisub/lua/modules/lfs.cpp b/libaegisub/lua/modules/lfs.cpp index b36992912..7f81b2afd 100644 --- a/libaegisub/lua/modules/lfs.cpp +++ b/libaegisub/lua/modules/lfs.cpp @@ -14,150 +14,152 @@ // // Aegisub Project http://www.aegisub.org/ -#include "libaegisub/lua/utils.h" +#include "libaegisub/fs.h" +#include "libaegisub/type_name.h" #include #include -#include +#include -namespace { -using namespace agi::lua; using namespace agi::fs; namespace bfs = boost::filesystem; -template -int wrap(lua_State *L) { +namespace agi { +AGI_DEFINE_TYPE_NAME(DirectoryIterator); +} + +namespace { +template +auto wrap(char **err, Func f) -> decltype(f()) { try { - func(L); - return 1; + return f(); } catch (bfs::filesystem_error const& e) { - lua_pushnil(L); - push_value(L, e.what()); - return 2; + *err = strdup(e.what()); + return 0; } catch (agi::Exception const& e) { - lua_pushnil(L); - push_value(L, e.GetMessage()); - return 2; - } - catch (error_tag) { - return lua_error(L); + *err = strdup(e.GetMessage().c_str()); + return 0; } } -void chdir(lua_State *L) { - bfs::current_path(check_string(L, 1)); - push_value(L, true); +template +bool setter(const char *path, char **err, Ret (*f)(bfs::path const&)) { + return wrap(err, [=]{ + f(path); + return true; + }); } -void currentdir(lua_State *L) { - push_value(L, bfs::current_path()); +bool lfs_chdir(const char *dir, char **err) { + return setter(dir, err, &bfs::current_path); } -void mkdir(lua_State *L) { - CreateDirectory(check_string(L, 1)); - push_value(L, true); +char *currentdir(char **err) { + return wrap(err, []{ + return strdup(bfs::current_path().string().c_str()); + }); } -void rmdir(lua_State *L) { - Remove(check_string(L, 1)); - push_value(L, true); +bool mkdir(const char *dir, char **err) { + return setter(dir, err, &CreateDirectory); } -void touch(lua_State *L) { - Touch(check_string(L, 1)); - push_value(L, true); +bool lfs_rmdir(const char *dir, char **err) { + return setter(dir, err, &Remove); } -int dir_next(lua_State *L) { - auto& it = get(L, 1, "aegisub.lfs.dir"); - if (it == end(it)) return 0; - push_value(L, *it); - ++it; - return 1; +bool touch(const char *path, char **err) { + return setter(path, err, &Touch); } -int dir_close(lua_State *L) { - auto& it = get(L, 1, "aegisub.lfs.dir"); - // Convert to end iterator rather than destroying to avoid crashes if this - // is called multiple times +char *dir_next(DirectoryIterator &it, char **err) { + if (it == end(it)) return nullptr; + return wrap(err, [&]{ + auto str = strdup((*it).c_str()); + ++it; + return str; + }); +} + +void dir_close(DirectoryIterator &it) { it = DirectoryIterator(); - return 0; } -int dir(lua_State *L) { - const path p = check_string(L, 1); - push_value(L, dir_next); - make(L, "aegisub.lfs.dir", check_string(L, 1), ""); - return 2; +void dir_free(DirectoryIterator *it) { + delete it; } -void attributes(lua_State *L) { - static std::pair fields[] = { - {"mode", [](lua_State *L, path const& p) { - switch (status(p).type()) { - case bfs::file_not_found: lua_pushnil(L); break; - case bfs::regular_file: push_value(L, "file"); break; - case bfs::directory_file: push_value(L, "directory"); break; - case bfs::symlink_file: push_value(L, "link"); break; - case bfs::block_file: push_value(L, "block device"); break; - case bfs::character_file: push_value(L, "char device"); break; - case bfs::fifo_file: push_value(L, "fifo"); break; - case bfs::socket_file: push_value(L, "socket"); break; - case bfs::reparse_file: push_value(L, "reparse point"); break; - default: push_value(L, "other"); break; - } - }}, - {"modification", [](lua_State *L, path const& p) { push_value(L, ModifiedTime(p)); }}, - {"size", [](lua_State *L, path const& p) { push_value(L, Size(p)); }} - }; +DirectoryIterator *dir_new(const char *path, char **err) { + return wrap(err, [=]{ + return new DirectoryIterator(path, ""); + }); +} - const path p = check_string(L, 1); - - const auto field = get_string(L, 2); - if (!field.empty()) { - for (const auto getter : fields) { - if (field == getter.first) { - getter.second(L, p); - return; - } +const char *get_mode(const char *path, char **err) { + return wrap(err, [=]() -> const char * { + switch (bfs::status(path).type()) { + case bfs::file_not_found: return nullptr; break; + case bfs::regular_file: return "file"; break; + case bfs::directory_file: return "directory"; break; + case bfs::symlink_file: return "link"; break; + case bfs::block_file: return "block device"; break; + case bfs::character_file: return "char device"; break; + case bfs::fifo_file: return "fifo"; break; + case bfs::socket_file: return "socket"; break; + case bfs::reparse_file: return "reparse point"; break; + default: return "other"; break; } - error(L, "Invalid attribute name: %s", field.c_str()); - } + }); +} - lua_createtable(L, 0, boost::size(fields)); - for (const auto getter : fields) { - getter.second(L, p); - lua_setfield(L, -2, getter.first); - } +time_t get_mtime(const char *path, char **err) { + return wrap(err, [=] { return ModifiedTime(path); }); +} + +uintmax_t get_size(const char *path, char **err) { + return wrap(err, [=] { return Size(path); }); +} + +template +void push_ffi_function(lua_State *L, const char *name, T *func) { + lua_pushvalue(L, -2); // push cast function + lua_pushstring(L, agi::type_name::name().c_str()); + // This cast isn't legal, but LuaJIT internally requires that it work + lua_pushlightuserdata(L, (void *)func); + lua_call(L, 2, 1); + lua_setfield(L, -2, name); } } -extern "C" int luaopen_lfs(lua_State *L) { - if (luaL_newmetatable(L, "aegisub.lfs.dir")) { - set_field(L, "__gc"); +extern "C" int luaopen_lfs_impl(lua_State *L) { + lua_getglobal(L, "require"); + lua_pushstring(L, "ffi"); + lua_call(L, 1, 1); - lua_createtable(L, 0, 2); - set_field(L, "next"); - set_field(L, "close"); - lua_setfield(L, -2, "__index"); - lua_pop(L, 1); - } + lua_getfield(L, -1, "cdef"); + lua_pushstring(L, "typedef struct DirectoryIterator DirectoryIterator;"); + lua_call(L, 1, 0); - const struct luaL_Reg lib[] = { - {"attributes", wrap}, - {"chdir", wrap}, - {"currentdir", wrap}, - {"dir", exception_wrapper}, - {"mkdir", wrap}, - {"rmdir", wrap}, - {"touch", wrap}, - {nullptr, nullptr}, - }; - lua_createtable(L, 0, boost::size(lib) - 1); - luaL_register(L, nullptr, lib); - lua_pushvalue(L, -1); - lua_setglobal(L, "lfs"); + lua_getfield(L, -1, "cast"); + lua_remove(L, -2); // ffi table + + lua_createtable(L, 0, 12); + push_ffi_function(L, "chdir", lfs_chdir); + push_ffi_function(L, "currentdir", currentdir); + push_ffi_function(L, "mkdir", mkdir); + push_ffi_function(L, "rmdir", lfs_rmdir); + push_ffi_function(L, "touch", touch); + push_ffi_function(L, "get_mtime", get_mtime); + push_ffi_function(L, "get_mode", get_mode); + push_ffi_function(L, "get_size", get_size); + + push_ffi_function(L, "dir_new", dir_new); + push_ffi_function(L, "dir_free", dir_free); + push_ffi_function(L, "dir_next", dir_next); + push_ffi_function(L, "dir_close", dir_close); + + lua_remove(L, -2); // ffi.cast function return 1; }