script API improvements
[chaz/yoink] / src / Moof / Script.hh
1
2 /*] Copyright (c) 2009-2010, Charles McGarvey [**************************
3 **] All rights reserved.
4 *
5 * vi:ts=4 sw=4 tw=75
6 *
7 * Distributable under the terms and conditions of the 2-clause BSD license;
8 * see the file COPYING for a complete text of the license.
9 *
10 **************************************************************************/
11
12 #ifndef _MOOF_SCRIPT_HH_
13 #define _MOOF_SCRIPT_HH_
14
15 /**
16 * @file Script.hh
17 * A thin wrapper over Lua. This is not meant as a complicated binding
18 * package between C++ and Lua. It is not meant to obscure the division
19 * between C++ and Lua but rather to clarify it and make it more
20 * manageable. It does not hide the concept of the Lua stack, but rather
21 * provides that mechanism with a certain level of abstraction while also
22 * providing a cleaner, more consistent API.
23 */
24
25 #include <iostream>
26 #include <list>
27 #include <map>
28 #include <string>
29 #include <vector>
30
31 #include <boost/bind.hpp>
32 #include <boost/function.hpp>
33 #include <boost/shared_ptr.hpp>
34 #include <lua.hpp>
35
36
37 namespace Mf {
38
39
40 class Script;
41 typedef boost::shared_ptr<Script> ScriptP;
42
43
44 class Script
45 {
46 public:
47
48 typedef boost::function<int(Script&)> Function;
49
50 enum Type
51 {
52 NONE = LUA_TNONE,
53 NIL = LUA_TNIL,
54 BOOLEAN = LUA_TBOOLEAN,
55 LIGHTUSERDATA = LUA_TLIGHTUSERDATA,
56 NUMBER = LUA_TNUMBER,
57 STRING = LUA_TSTRING,
58 TABLE = LUA_TTABLE,
59 FUNCTION = LUA_TFUNCTION,
60 USERDATA = LUA_TUSERDATA,
61 THREAD = LUA_TTHREAD
62 };
63
64 enum Result
65 {
66 SUCCESS = 0,
67 YIELD = LUA_YIELD,
68 RUNTIME_ERROR = LUA_ERRRUN,
69 SYNTAX_ERROR = LUA_ERRSYNTAX,
70 MEMORY_ERROR = LUA_ERRMEM,
71 HANDLER_ERROR = LUA_ERRERR,
72 FILE_ERROR = LUA_ERRFILE
73 };
74
75 enum PseudoIndex
76 {
77 REGISTRY = LUA_REGISTRYINDEX,
78 ENVIRONMENT = LUA_ENVIRONINDEX,
79 GLOBALS = LUA_GLOBALSINDEX
80 };
81
82 /**
83 * This is the most prominent abstraction on top of the standard Lua
84 * API. A Slot object represents a value on the stack. More
85 * specifically, it represents a position on the stack. The
86 * distinction is only important when objects are moved around on the
87 * stack or if the Slot represents a negative index on the stack (the
88 * value of which will change as things are pushed onto and popped from
89 * the stack).
90 */
91
92 struct Slot
93 {
94 /**
95 * You have direct access to the index of the value on the stack
96 * being represented.
97 */
98
99 int index;
100
101
102 Slot(const Script& s, int i = 0) :
103 index(i),
104 mScript(const_cast<Script&>(s)) {}
105
106 /**
107 * A copied value presently points to the same value, except the
108 * real index is used. That means that if a value that refers to a
109 * frame referenced from the top of the stack will have its
110 * normalized index copied into the new value object.
111 */
112
113 //Slot(const Slot& copy) :
114 //index(copy.positiveIndex()),
115 //mScript(copy.mScript) {}
116
117
118 // check the type of the value
119 bool isBoolean() const
120 { return (bool)lua_isboolean(mScript.mState, index); }
121 bool isImportedFunction() const
122 { return (bool)lua_iscfunction(mScript.mState, index); }
123 bool isFunction() const
124 { return (bool)lua_isfunction(mScript.mState, index); }
125 bool isNil() const
126 { return (bool)lua_isnil(mScript.mState, index); }
127 bool isNone() const
128 { return (bool)lua_isnone(mScript.mState, index); }
129 bool isNoneOrNil() const
130 { return (bool)lua_isnoneornil(mScript.mState, index); }
131 bool isNumber() const
132 { return (bool)lua_isnumber(mScript.mState, index); }
133 bool isString() const
134 { return (bool)lua_isstring(mScript.mState, index); }
135 bool isTable() const
136 { return (bool)lua_istable(mScript.mState, index); }
137 bool isThread() const
138 { return (bool)lua_isthread(mScript.mState, index); }
139 bool isData() const
140 { return (bool)lua_isuserdata(mScript.mState, index); }
141 bool isLightData() const
142 { return (bool)lua_islightuserdata(mScript.mState, index); }
143
144 /**
145 * Check the value and throw an error if its the wrong type.
146 * There's a little caveat: This method never returns because it
147 * does a long jump. Consequently, constructed C++ objects which
148 * exist on the stack between the current frame and some lua
149 * function will not be destructed. That's not a problem for
150 * objects that only exist on the stack, but any objects that
151 * allocate memory on the heap (such as containers or strings) will
152 * leak. Therefore, you should only call this method after
153 * cleaning up such objects. The best thing to do for defining
154 * functions is to simply check all the parameters at the get-go
155 * before any C++ objects are even constructed.
156 */
157
158 void requireType(Type t) const
159 {
160 if (t != type())
161 {
162 luaL_typerror(mScript.mState, index,
163 lua_typename(mScript.mState, t));
164 }
165 }
166
167 void raise(const char* error)
168 {
169 luaL_argerror(mScript.mState, index, error);
170 }
171
172
173 Slot& requireBoolean()
174 {
175 if (!isBoolean())
176 {
177 luaL_typerror(mScript.mState, index, "boolean");
178 }
179 return *this;
180 }
181 Slot& requireNumber()
182 {
183 if (!isNumber())
184 {
185 luaL_typerror(mScript.mState, index, "number");
186 }
187 return *this;
188 }
189 Slot& requireString()
190 {
191 if (!isString())
192 {
193 luaL_typerror(mScript.mState, index, "string");
194 }
195 return *this;
196 }
197 Slot& requireTable()
198 {
199 if (!isTable())
200 {
201 luaL_typerror(mScript.mState, index, "table");
202 }
203 return *this;
204 }
205 Slot& requireFunction()
206 {
207 if (!isFunction())
208 {
209 luaL_typerror(mScript.mState, index, "function");
210 }
211 return *this;
212 }
213 Slot& requireData()
214 {
215 if (!isData())
216 {
217 luaL_typerror(mScript.mState, index, "data");
218 }
219 return *this;
220 }
221 Slot& requireNil()
222 {
223 if (!isNil())
224 {
225 luaL_typerror(mScript.mState, index, "nil");
226 }
227 return *this;
228 }
229 Slot& requireThread()
230 {
231 if (!isThread())
232 {
233 luaL_typerror(mScript.mState, index, "thread");
234 }
235 return *this;
236 }
237
238
239 /**
240 * Get the type of the value.
241 */
242
243 Type type() const
244 {
245 return (Type)lua_type(mScript.mState, index);
246 }
247
248 /**
249 * Get the name of the type of the value as a string.
250 */
251
252 std::string typeName() const
253 {
254 return std::string(luaL_typename(mScript.mState, index));
255 }
256
257
258 /**
259 * Get the length of the value according to the definition given by
260 * Lua.
261 */
262
263 size_t length() const
264 {
265 return lua_objlen(mScript.mState, index);
266 }
267
268 int positiveIndex() const
269 {
270 if (index < 0) return index + lua_gettop(mScript.mState) + 1;
271 else return index;
272 }
273
274
275 /**
276 * Get a pointer value (for userdata, tables, threads, and
277 * functions).
278 */
279
280 const void* id() const
281 {
282 return lua_topointer(mScript.mState, index);
283 }
284
285 bool isIdentical(const Slot& rhs) const
286 {
287 return &mScript == &(rhs.mScript) && index == rhs.index;
288 }
289
290 operator bool () const
291 {
292 return !isNone();
293 }
294
295
296 bool operator == (const Slot& rhs) const
297 {
298 return (bool)lua_equal(mScript.mState, index, rhs.index);
299 }
300 bool operator != (const Slot& rhs) const
301 {
302 return !(*this == rhs);
303 }
304
305 bool operator < (const Slot& rhs) const
306 {
307 return (bool)lua_lessthan(mScript.mState, index, rhs.index);
308 }
309 bool operator <= (const Slot& rhs) const
310 {
311 return *this < rhs || *this == rhs;
312 }
313 bool operator > (const Slot& rhs) const
314 {
315 return !(*this <= rhs);
316 }
317 bool operator >= (const Slot& rhs) const
318 {
319 return !(*this < rhs);
320 }
321
322
323 /**
324 * Convert the underlying value to a C++ type.
325 */
326
327 template <class T>
328 bool get(T& value) const
329 {
330 if (isNumber())
331 {
332 value = (T)lua_tointeger(mScript.mState, index);
333 return true;
334 }
335 return false;
336 }
337
338 bool get(float& value) const
339 {
340 if (isNumber())
341 {
342 value = (float)lua_tonumber(mScript.mState, index);
343 return true;
344 }
345 return false;
346 }
347 bool get(double& value) const
348 {
349 if (isNumber())
350 {
351 value = (double)lua_tonumber(mScript.mState, index);
352 return true;
353 }
354 return false;
355 }
356
357 bool get(bool& value) const
358 {
359 if (isBoolean())
360 {
361 value = (bool)lua_toboolean(mScript.mState, index);
362 return true;
363 }
364 return false;
365 }
366
367 bool get(const char*& value, size_t& size) const
368 {
369 if (isString())
370 {
371 value = lua_tolstring(mScript.mState, index, &size);
372 return true;
373 }
374 return false;
375 }
376
377 bool get(std::string& value) const
378 {
379 const char* str;
380 size_t size;
381 if (get(str, size))
382 {
383 value.assign(str, size);
384 return true;
385 }
386 return false;
387 }
388
389 bool get(void*& value) const
390 {
391 if (isData())
392 {
393 value = lua_touserdata(mScript.mState, index);
394 return true;
395 }
396 return false;
397 }
398
399 template <class T>
400 bool get(std::vector<T>& array) const
401 {
402 if (!isTable()) return false;
403
404 array.clear();
405
406 Slot value = mScript[-1];
407 int realIndex = positiveIndex();
408
409 bool done = false;
410 for (int i = 1; !done; ++i)
411 {
412 lua_rawgeti(mScript.mState, realIndex, i);
413
414 T v;
415 if (value.get(v)) array.push_back(v);
416 else done = true;
417
418 mScript.pop();
419 }
420
421 return true;
422 }
423
424 template <class T>
425 bool get(std::map<std::string,T>& dictionary) const
426 {
427 if (!isTable()) return false;
428
429 dictionary.clear();
430
431 Slot key = mScript[-2];
432 Slot value = mScript[-1];
433 int realIndex = positiveIndex();
434
435 mScript.pushNil();
436 while (lua_next(mScript.mState, realIndex) != 0)
437 {
438 std::string k;
439 if (!key.isNumber() && key.get(k))
440 {
441 T v;
442 if (value.get(v)) dictionary[k] = v;
443 }
444 mScript.pop();
445 }
446 mScript.pop();
447
448 return true;
449 }
450
451 /**
452 * Get the value of a field from the table.
453 */
454
455 template <class T, class V>
456 bool get(T& value, V field) const
457 {
458 bool ret = pushField(field).get(value);
459 mScript.pop();
460 return ret;
461 }
462
463
464 template <class T, class V>
465 void setField(T field, V value)
466 {
467 logWarning << "setting " << field << ", " << value << std::endl;
468 mScript.push(field);
469 mScript.push(value);
470 setField();
471 }
472
473 void setField()
474 {
475 lua_settable(mScript.mState, index);
476 }
477
478
479 template <class T>
480 void setField(const std::string& field, T value)
481 {
482 setField(field.c_str(), value);
483 }
484 template <class T>
485 void setField(const char* field, T value)
486 {
487 logWarning << "setfield " << field << ", " << value << std::endl;
488 mScript.push(value);
489 lua_setfield(mScript.mState, index, field);
490 }
491
492
493 /**
494 * This set method, as opposed to the others, sets the value of the
495 * actual slot. The others set table values.
496 */
497 template <class T>
498 void set(T value)
499 {
500 mScript.push(value);
501 replace();
502 }
503
504 void set()
505 {
506 replace();
507 }
508
509
510 /**
511 * Replace this value with the value at the top of the stack.
512 */
513
514 void replace()
515 {
516 lua_replace(mScript.mState, index);
517 }
518
519 void remove()
520 {
521 lua_remove(mScript.mState, index);
522 }
523
524 void pop()
525 {
526 // removes this slot, taking with it everything above it
527 mScript.pop(mScript.stackSize() - index + 1);
528 }
529
530 /**
531 * Inserts the top-most value on the stack at position index,
532 * shifting other values as needed.
533 */
534
535 void insertTopHere()
536 {
537 lua_insert(mScript.mState, index);
538 }
539
540
541 /**
542 * Copy the value and push the copy to the stack.
543 */
544
545 Slot pushCopy() const
546 {
547 lua_pushvalue(mScript.mState, index);
548 return mScript.top();
549 }
550
551 Slot pushMetaTable() const
552 {
553 lua_getmetatable(mScript.mState, index);
554 return mScript.top();
555 }
556
557 Slot pushEnvironment() const
558 {
559 lua_getfenv(mScript.mState, index);
560 return mScript.top();
561 }
562
563
564 Slot pushField() const
565 {
566 lua_gettable(mScript.mState, index);
567 return mScript.top();
568 }
569
570 template <class T>
571 Slot pushField(T index) const
572 {
573 mScript.push(index);
574 return pushField();
575 }
576
577 Slot pushField(const std::string& name) const
578 {
579 return pushField(name.c_str());
580 }
581 Slot pushField(const char* name) const
582 {
583 lua_getfield(mScript.mState, index, name);
584 return mScript.top();
585 }
586
587
588 Script& script()
589 {
590 return mScript;
591 }
592
593 const Script& script() const
594 {
595 return mScript;
596 }
597
598 private:
599
600 Script& mScript;
601 };
602
603
604 Script() :
605 mState(0)
606 {
607 reset();
608 }
609
610 ~Script()
611 {
612 destroy();
613 }
614
615
616 static ScriptP alloc()
617 {
618 return ScriptP(new Script);
619 }
620
621 void reset()
622 {
623 if (mState) destroy();
624 mState = luaL_newstate();
625 registry().setField("Script_hh_Object", (void*)this);
626 }
627
628
629 void importStandardLibraries()
630 {
631 luaL_openlibs(mState);
632 }
633
634 void importBaseLibrary()
635 {
636 lua_pushcfunction(mState, luaopen_base);
637 push(LUA_COLIBNAME);
638 call(1, 0);
639 }
640
641 void importPackageLibrary()
642 {
643 lua_pushcfunction(mState, luaopen_package);
644 push(LUA_LOADLIBNAME);
645 call(1, 0);
646 }
647
648 void importStringLibrary()
649 {
650 lua_pushcfunction(mState, luaopen_string);
651 push(LUA_STRLIBNAME);
652 call(1, 0);
653 }
654
655 void importTableLibrary()
656 {
657 lua_pushcfunction(mState, luaopen_table);
658 push(LUA_TABLIBNAME);
659 call(1, 0);
660 }
661
662 void importMathLibrary()
663 {
664 lua_pushcfunction(mState, luaopen_math);
665 push(LUA_MATHLIBNAME);
666 call(1, 0);
667 }
668
669 void importIoLibrary()
670 {
671 lua_pushcfunction(mState, luaopen_io);
672 push(LUA_IOLIBNAME);
673 call(1, 0);
674 }
675
676 void importOsLibrary()
677 {
678 lua_pushcfunction(mState, luaopen_os);
679 push(LUA_OSLIBNAME);
680 call(1, 0);
681 }
682
683 void importDebugLibrary()
684 {
685 lua_pushcfunction(mState, luaopen_debug);
686 push(LUA_DBLIBNAME);
687 call(1, 0);
688 }
689
690
691 void importFunction(const std::string& name, const Function& function)
692 {
693 push(function);
694 lua_setglobal(mState, name.c_str());
695 }
696
697 Result doString(const std::string& commands)
698 {
699 return (Result)luaL_dostring(mState, commands.c_str());
700 }
701
702 Result doFile(const std::string& file)
703 {
704 return (Result)luaL_dofile(mState, file.c_str());
705 }
706
707
708 /**
709 * Thread-handling methods.
710 */
711
712 Script pushNewThread()
713 {
714 return Script(mState);
715 }
716
717 void pushThread()
718 {
719 lua_pushthread(mState);
720 }
721
722 Result resume(int nargs)
723 {
724 return (Result)lua_resume(mState, nargs);
725 }
726
727 Result getStatus() const
728 {
729 return (Result)lua_status(mState);
730 }
731
732 int yield(int results)
733 {
734 return lua_yield(mState, results);
735 }
736
737 bool isMainThread() const
738 {
739 return mIsMainThread;
740 }
741
742
743 /**
744 * Throw an error with the value at the top of the stack. This method
745 * never returns because it does a long jump. Consequently,
746 * constructed C++ objects which exist on the stack between the
747 * current frame and some lua function will not be destructed. That's
748 * not a problem for objects that only exist on the stack, but any
749 * objects that allocate memory on the heap (such as containers or
750 * strings) will leak. Therefore, you should only call this method
751 * after cleaning up such objects.
752 */
753
754 void raise()
755 {
756 lua_error(mState);
757 }
758
759
760 /**
761 * Get significant values.
762 */
763
764 Slot globals() const
765 {
766 return Slot(*this, GLOBALS);
767 }
768
769 Slot registry() const
770 {
771 return Slot(*this, REGISTRY);
772 }
773
774 Slot environment() const
775 {
776 return Slot(*this, ENVIRONMENT);
777 }
778
779 Slot top() const
780 {
781 return Slot(*this, stackSize());
782 }
783
784 /**
785 * Get the size of the stack; this is also the index of the top-most
786 * value.
787 */
788
789 int stackSize() const
790 {
791 return lua_gettop(mState);
792 }
793
794 void setStackSize(int size)
795 {
796 lua_settop(mState, size);
797 }
798
799 void clearStack()
800 {
801 setStackSize(0);
802 }
803
804
805 /**
806 * Makes sure there is at least extra more places on the stack.
807 * Returns false if space couldn't be created. Just like with the
808 * regular Lua API, you are responsible to make sure the stack is big
809 * enough to hold whatever you want to push on it. This is usually
810 * only an issue if you're pushing stuff in a loop.
811 */
812
813 bool checkStack(int extra)
814 {
815 return (bool)lua_checkstack(mState, extra);
816 }
817
818
819 /**
820 * Concatenates the top-most n slots on the stack.
821 */
822
823 void concatenate(int n = 2)
824 {
825 lua_concat(mState, n);
826 }
827
828
829 /**
830 * Push some values onto the stack.
831 */
832
833 template <class T>
834 Slot push(T value)
835 {
836 lua_pushinteger(mState, lua_Integer(value));
837 return top();
838 }
839
840 Slot push(bool value)
841 {
842 lua_pushboolean(mState, int(value));
843 return top();
844 }
845
846 Slot push(float value)
847 {
848 lua_pushnumber(mState, (lua_Number)value);
849 return top();
850 }
851 Slot push(double value)
852 {
853 lua_pushnumber(mState, (lua_Number)value);
854 return top();
855 }
856
857 Slot push(const std::string& value)
858 {
859 lua_pushlstring(mState, value.c_str(), value.length());
860 return top();
861 }
862 Slot push(const char* value)
863 {
864 lua_pushstring(mState, value);
865 return top();
866 }
867 Slot push(const char* value, size_t length)
868 {
869 lua_pushlstring(mState, value, length);
870 return top();
871 }
872
873 Slot push(const Function& function)
874 {
875 mFunctions.push_back(function);
876 lua_pushlightuserdata(mState, (void*)&mFunctions.back());
877 lua_pushcclosure(mState, dispatchCall, 1);
878 return top();
879 }
880
881 Slot push(void* data)
882 {
883 lua_pushlightuserdata(mState, data);
884 return top();
885 }
886
887 Slot pushNil()
888 {
889 lua_pushnil(mState);
890 return top();
891 }
892
893 Slot pushFromThread(Script& thread, int n)
894 {
895 lua_xmove(thread.mState, mState, n);
896 return top();
897 }
898
899 Slot pushCode(const std::string& file, Result& result)
900 {
901 result = (Result)luaL_loadfile(mState, file.c_str());
902 return top();
903 }
904
905 Slot pushCode(const std::string& name, const char* buffer,
906 size_t size, Result& result)
907 {
908 result = (Result)luaL_loadbuffer(mState,
909 buffer, size, name.c_str());
910 return top();
911 }
912
913 Slot pushNewData(void*& data, size_t size)
914 {
915 data = lua_newuserdata(mState, size);
916 return top();
917 }
918
919 Slot pushNewTable(int narr = 0, int nrec = 0)
920 {
921 lua_createtable(mState, narr, nrec);
922 return top();
923 }
924
925
926 /**
927 * Call a function on the stack. The correct procedure is to push a
928 * function onto the stack followed by nargs arguments. This method
929 * will pop them off upon return, leaving up to nresults return values
930 * (default is any number of return values, depending on the callee).
931 */
932
933 Result call(int nargs = 0, int nresults = LUA_MULTRET)
934 {
935 return (Result)lua_pcall(mState, nargs, nresults, 0);
936 }
937
938
939 /**
940 * Pops n values from the top of the stack.
941 */
942
943 void pop(int n = 1)
944 {
945 lua_pop(mState, n);
946 }
947
948
949 /**
950 * Index into the stack to get a Slot.
951 */
952
953 Slot operator [] (int index) const
954 {
955 return Slot(*this, index);
956 }
957
958
959 /**
960 * Control over the garbage collection process.
961 */
962
963 void collectAll()
964 {
965 lua_gc(mState, LUA_GCCOLLECT, 0);
966 }
967
968 void stopCollector()
969 {
970 lua_gc(mState, LUA_GCSTOP, 0);
971 }
972
973 void restartCollector()
974 {
975 lua_gc(mState, LUA_GCRESTART, 0);
976 }
977
978 int getUsedMemory() const
979 {
980 // in kilobytes
981 return lua_gc(mState, LUA_GCCOUNT, 0);
982 }
983
984 void collectStep(int step)
985 {
986 lua_gc(mState, LUA_GCSTEP, step);
987 }
988
989 void tuneCollector(int pause, int step)
990 {
991 lua_gc(mState, LUA_GCSETPAUSE, pause);
992 lua_gc(mState, LUA_GCSETSTEPMUL, step);
993 }
994
995
996 private:
997
998 Script(lua_State* state) :
999 mState(lua_newthread(state)),
1000 mIsMainThread(false) {}
1001
1002 static int dispatchCall(lua_State* state)
1003 {
1004 const Function* function = (const Function*)lua_touserdata(state,
1005 lua_upvalueindex(1));
1006
1007 lua_getfield(state, LUA_REGISTRYINDEX, "Script_hh_Object");
1008 Script* script = (Script*)lua_touserdata(state, -1);
1009 lua_pop(state, 1);
1010
1011 return (*function)(*script);
1012 }
1013
1014 void destroy()
1015 {
1016 if (mIsMainThread) lua_close(mState);
1017 }
1018
1019 lua_State* mState;
1020 bool mIsMainThread;
1021 std::list<Function> mFunctions;
1022 };
1023
1024
1025 inline std::ostream& operator << (std::ostream& stream,
1026 const Script::Slot& slot)
1027 {
1028 if (slot.isString())
1029 {
1030 std::string str;
1031 slot.get(str);
1032 stream << str;
1033 }
1034 else if (slot.isBoolean())
1035 {
1036 bool value;
1037 slot.get(value);
1038 if (value) stream << "true";
1039 else stream << "false";
1040 }
1041 else if (slot.isNil())
1042 {
1043 stream << "nil";
1044 }
1045 else
1046 {
1047 stream << slot.typeName() << " (" << slot.id() << ")";
1048 }
1049
1050 return stream;
1051 }
1052
1053
1054 } // namespace Mf
1055
1056 #endif // _MOOF_SCRIPT_HH_
1057
This page took 0.084553 seconds and 4 git commands to generate.