refactor lua header includes
[ardour.git] / libs / ardour / luascripting.cc
1 /*
2  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 #include <cstring>
20
21 #include "pbd/error.h"
22 #include "pbd/file_utils.h"
23 #include "pbd/compose.h"
24
25 #include "ardour/luascripting.h"
26 #include "ardour/lua_script_params.h"
27 #include "ardour/search_paths.h"
28
29 #include "lua/luastate.h"
30 #include "LuaBridge/LuaBridge.h"
31
32 #include "i18n.h"
33
34 using namespace ARDOUR;
35 using namespace PBD;
36 using namespace std;
37
38 LuaScripting* LuaScripting::_instance = 0;
39
40 LuaScripting&
41 LuaScripting::instance ()
42 {
43         if (!_instance) {
44                 _instance = new LuaScripting;
45         }
46         return *_instance;
47 }
48
49 LuaScripting::LuaScripting ()
50         : _sl_dsp (0)
51         , _sl_session (0)
52         , _sl_hook (0)
53         , _sl_action (0)
54 {
55         ;
56 }
57
58 LuaScripting::~LuaScripting ()
59 {
60         if (getenv ("ARDOUR_RUNNING_UNDER_VALGRIND")) {
61                 // don't bother, just exit quickly.
62                 delete _sl_dsp;
63                 delete _sl_session;
64                 delete _sl_hook;
65                 delete _sl_action;
66         }
67 }
68
69 void
70 LuaScripting::check_scan ()
71 {
72         if (!_sl_dsp || !_sl_session || !_sl_hook || !_sl_action) {
73                 scan ();
74         }
75 }
76
77 void
78 LuaScripting::refresh ()
79 {
80         Glib::Threads::Mutex::Lock lm (_lock, Glib::Threads::TRY_LOCK);
81
82         if (!lm.locked()) {
83                 return;
84         }
85
86         delete _sl_dsp;
87         delete _sl_session;
88         delete _sl_hook;
89         delete _sl_action;
90
91         _sl_dsp = 0;
92         _sl_session = 0;
93         _sl_hook = 0;
94         _sl_action = 0;
95 }
96
97 struct ScriptSorter {
98         bool operator () (LuaScriptInfoPtr a, LuaScriptInfoPtr b) {
99                 return a->name < b->name;
100         }
101 };
102
103 void
104 LuaScripting::scan ()
105 {
106         Glib::Threads::Mutex::Lock lm (_lock, Glib::Threads::TRY_LOCK);
107
108         if (!lm.locked()) {
109                 return;
110         }
111
112 #define CLEAR_OR_NEW(LIST) \
113         if (LIST) { LIST->clear (); } else { LIST = new LuaScriptList (); }
114
115         CLEAR_OR_NEW (_sl_dsp)
116         CLEAR_OR_NEW (_sl_session)
117         CLEAR_OR_NEW (_sl_hook)
118         CLEAR_OR_NEW (_sl_action)
119
120 #undef CLEAR_OR_NEW
121
122         vector<string> luascripts;
123         find_files_matching_pattern (luascripts, lua_search_path (), "*.lua");
124
125         for (vector<string>::iterator i = luascripts.begin(); i != luascripts.end (); ++i) {
126                 LuaScriptInfoPtr lsi = scan_script (*i);
127                 if (!lsi) {
128                         PBD::info << string_compose (_("Script '%1' has no valid descriptor."), *i) << endmsg;
129                         continue;
130                 }
131                 switch (lsi->type) {
132                         case LuaScriptInfo::DSP:
133                                 _sl_dsp->push_back(lsi);
134                                 break;
135                         case LuaScriptInfo::Session:
136                                 _sl_session->push_back(lsi);
137                                 break;
138                         case LuaScriptInfo::EditorHook:
139                                 _sl_hook->push_back(lsi);
140                                 break;
141                         case LuaScriptInfo::EditorAction:
142                                 _sl_action->push_back(lsi);
143                                 break;
144                         default:
145                                 break;
146                 }
147         }
148
149         std::sort (_sl_dsp->begin(), _sl_dsp->end(), ScriptSorter());
150         std::sort (_sl_session->begin(), _sl_session->end(), ScriptSorter());
151         std::sort (_sl_hook->begin(), _sl_hook->end(), ScriptSorter());
152         std::sort (_sl_action->begin(), _sl_action->end(), ScriptSorter());
153
154 }
155
156 void
157 LuaScripting::lua_print (std::string s) {
158         PBD::info << "Lua: " << s << "\n";
159 }
160
161 LuaScriptInfoPtr
162 LuaScripting::scan_script (const std::string &fn, const std::string &sc)
163 {
164         LuaState lua;
165         if (!(fn.empty() ^ sc.empty())){
166                 // give either file OR script
167                 assert (0);
168                 return LuaScriptInfoPtr();
169         }
170
171         lua_State* L = lua.getState();
172         lua.Print.connect (&LuaScripting::lua_print);
173
174         lua.do_command ("io = nil;");
175
176         lua.do_command (
177                         "ardourluainfo = {}"
178                         "function ardour (entry)"
179                         "  ardourluainfo['type'] = assert(entry['type'])"
180                         "  ardourluainfo['name'] = assert(entry['name'])"
181                         "  ardourluainfo['author'] = entry['author'] or 'Unknown'"
182                         "  ardourluainfo['license'] = entry['license'] or ''"
183                         "  ardourluainfo['description'] = entry['description'] or ''"
184                         " end"
185                         );
186
187         try {
188                 int err;
189                 if (fn.empty()) {
190                         err = lua.do_command (sc);
191                 } else {
192                         err = lua.do_file (fn);
193                 }
194                 if (err) {
195                         return LuaScriptInfoPtr();
196                 }
197         } catch (...) { // luabridge::LuaException
198                 return LuaScriptInfoPtr();
199         }
200         luabridge::LuaRef nfo = luabridge::getGlobal (L, "ardourluainfo");
201         if (nfo.type() != LUA_TTABLE) {
202                 return LuaScriptInfoPtr();
203         }
204
205         if (nfo["name"].type() != LUA_TSTRING || nfo["type"].type() != LUA_TSTRING) {
206                 return LuaScriptInfoPtr();
207         }
208
209         std::string name = nfo["name"].cast<std::string>();
210         LuaScriptInfo::ScriptType type = LuaScriptInfo::str2type (nfo["type"].cast<std::string>());
211
212         if (name.empty() || type == LuaScriptInfo::Invalid) {
213                 return LuaScriptInfoPtr();
214         }
215
216         LuaScriptInfoPtr lsi (new LuaScriptInfo (type, name, fn));
217
218         for (luabridge::Iterator i(nfo); !i.isNil (); ++i) {
219                 if (!i.key().isString() || !i.value().isString()) {
220                         return LuaScriptInfoPtr();
221                 }
222                 std::string key = i.key().tostring();
223                 std::string val = i.value().tostring();
224
225                 if (key == "author") { lsi->author = val; }
226                 if (key == "license") { lsi->license = val; }
227                 if (key == "description") { lsi->description = val; }
228         }
229
230         return lsi;
231 }
232
233 LuaScriptList &
234 LuaScripting::scripts (LuaScriptInfo::ScriptType type) {
235         check_scan();
236
237         switch (type) {
238                 case LuaScriptInfo::DSP:
239                         return *_sl_dsp;
240                         break;
241                 case LuaScriptInfo::Session:
242                         return *_sl_session;
243                         break;
244                 case LuaScriptInfo::EditorHook:
245                         return *_sl_hook;
246                         break;
247                 case LuaScriptInfo::EditorAction:
248                         return *_sl_action;
249                         break;
250                 default:
251                         return _empty_script_info;
252                         break;
253         }
254 }
255
256
257 std::string
258 LuaScriptInfo::type2str (const ScriptType t) {
259         switch (t) {
260                 case LuaScriptInfo::DSP: return "DSP";
261                 case LuaScriptInfo::Session: return "Session";
262                 case LuaScriptInfo::EditorHook: return "EditorHook";
263                 case LuaScriptInfo::EditorAction: return "EditorAction";
264                 default: return "Invalid";
265         }
266 }
267
268 LuaScriptInfo::ScriptType
269 LuaScriptInfo::str2type (const std::string& str) {
270         const char* type = str.c_str();
271         if (!strcasecmp (type, "DSP")) {return LuaScriptInfo::DSP;}
272         if (!strcasecmp (type, "Session")) {return LuaScriptInfo::Session;}
273         if (!strcasecmp (type, "EditorHook")) {return LuaScriptInfo::EditorHook;}
274         if (!strcasecmp (type, "EditorAction")) {return LuaScriptInfo::EditorAction;}
275         return LuaScriptInfo::Invalid;
276 }
277
278 LuaScriptParamList
279 LuaScriptParams::script_params (LuaScriptInfoPtr lsi, const std::string &pname)
280 {
281         assert (lsi);
282         return LuaScriptParams::script_params (lsi->path, pname);
283 }
284
285 LuaScriptParamList
286 LuaScriptParams::script_params (const std::string& s, const std::string &pname, bool file)
287 {
288         LuaScriptParamList rv;
289
290         LuaState lua;
291         lua_State* L = lua.getState();
292         lua.do_command ("io = nil;");
293         lua.do_command ("function ardour () end");
294
295         try {
296                 if (file) {
297                         lua.do_file (s);
298                 } else {
299                         lua.do_command (s);
300                 }
301         } catch (luabridge::LuaException const& e) {
302                 return rv;
303         }
304
305         luabridge::LuaRef lua_params = luabridge::getGlobal (L, pname.c_str());
306         if (lua_params.isFunction ()) {
307                 luabridge::LuaRef params = lua_params ();
308                 if (params.isTable ()) {
309                         for (luabridge::Iterator i (params); !i.isNil (); ++i) {
310                                 if (!i.key ().isString ())            { continue; }
311                                 if (!i.value ().isTable ())           { continue; }
312                                 if (!i.value ()["title"].isString ()) { continue; }
313
314                                 std::string name = i.key ().cast<std::string> ();
315                                 std::string title = i.value ()["title"].cast<std::string> ();
316                                 std::string dflt;
317                                 bool optional = false;
318
319                                 if (i.value ()["default"].isString ()) {
320                                         dflt = i.value ()["default"].cast<std::string> ();
321                                 }
322                                 if (i.value ()["optional"].isBoolean ()) {
323                                         optional = i.value ()["optional"].cast<bool> ();
324                                 }
325                                 LuaScriptParamPtr lsspp (new LuaScriptParam(name, title, dflt, optional));
326                                 rv.push_back (lsspp);
327                         }
328                 }
329         }
330         return rv;
331 }
332
333 void
334 LuaScriptParams::params_to_ref (luabridge::LuaRef *tbl_args, const LuaScriptParamList& args)
335 {
336         assert (tbl_args &&  (*tbl_args).isTable ());
337         for (LuaScriptParamList::const_iterator i = args.begin(); i != args.end(); ++i) {
338                 if ((*i)->optional && !(*i)->is_set) { continue; }
339                 (*tbl_args)[(*i)->name] = (*i)->value;
340         }
341 }
342
343 void
344 LuaScriptParams::ref_to_params (LuaScriptParamList& args, luabridge::LuaRef *tbl_ref)
345 {
346         assert (tbl_ref &&  (*tbl_ref).isTable ());
347         for (luabridge::Iterator i (*tbl_ref); !i.isNil (); ++i) {
348                 if (!i.key ().isString ()) { assert(0); continue; }
349                 std::string name = i.key ().cast<std::string> ();
350                 std::string value = i.value ().cast<std::string> ();
351                 for (LuaScriptParamList::const_iterator ii = args.begin(); ii != args.end(); ++ii) {
352                         if ((*ii)->name == name) {
353                                 (*ii)->value = value;
354                                 break;
355                         }
356                 }
357         }
358 }
359
360 bool
361 LuaScripting::try_compile (const std::string& script, const LuaScriptParamList& args)
362 {
363         const std::string& bytecode = get_factory_bytecode (script);
364         if (bytecode.empty()) {
365                 return false;
366         }
367         LuaState l;
368         lua_State* L = l.getState();
369
370         l.do_command (""
371                         " function checkfactory (b, a)"
372                         "  assert(type(b) == 'string', 'ByteCode must be string')"
373                         "  load(b)()"
374                         "  assert(type(f) == 'string', 'Assigned ByteCode must be string')"
375                         "  local env = _ENV;  env.f = nil env.debug = nil os.exit = nil"
376                         "  load (string.dump(f, true), nil, nil, env)(a)"
377                         " end"
378                         );
379
380         try {
381                 luabridge::LuaRef lua_test = luabridge::getGlobal (L, "checkfactory");
382                 l.do_command ("checkfactory = nil"); // hide it.
383                 l.do_command ("collectgarbage()");
384                 lua_test (bytecode);
385                 return true; // OK
386         } catch (luabridge::LuaException const& e) { }
387
388         return false;
389 }
390
391 std::string
392 LuaScripting::get_factory_bytecode (const std::string& script)
393 {
394         LuaState l;
395         l.Print.connect (&LuaScripting::lua_print);
396         lua_State* L = l.getState();
397
398         l.do_command (
399                         " function ardour () end"
400                         ""
401                         " function dump_function (f)"
402                         "  assert(type(f) == 'function', 'Factory is a not a function')"
403                         "  return string.format(\"f = %q\", string.dump(f, true))"
404                         " end"
405                         );
406
407         try {
408                 luabridge::LuaRef lua_dump = luabridge::getGlobal (L, "dump_function");
409                 l.do_command ("dump_function = nil"); // hide it
410                 l.do_command (script); // register "factory"
411                 luabridge::LuaRef lua_factory = luabridge::getGlobal (L, "factory");
412
413                 if (lua_factory.isFunction()) {
414                         return (lua_dump(lua_factory)).cast<std::string> ();
415                 }
416         } catch (luabridge::LuaException const& e) { }
417         return "";
418 }