From dd009ccdfd62f9153dbc72f5f5de5d5f72979690 Mon Sep 17 00:00:00 2001 From: David Walter Seikel Date: Tue, 22 Apr 2014 15:13:38 +1000 Subject: Move all source into the new src directory, and shuffle a few other things around. --- src/LuaSL/LuaSL.edc | 205 ++++ src/LuaSL/LuaSL.h | 82 ++ src/LuaSL/LuaSL_LSL_tree.h | 443 ++++++++ src/LuaSL/LuaSL_compile.c | 2345 ++++++++++++++++++++++++++++++++++++++++ src/LuaSL/LuaSL_lemon_yaccer.y | 275 +++++ src/LuaSL/LuaSL_lexer.l | 164 +++ src/LuaSL/LuaSL_main.c | 708 ++++++++++++ src/LuaSL/LuaSL_test.c | 467 ++++++++ src/LuaSL/LuaSL_threads.c | 496 +++++++++ src/LuaSL/LuaSL_threads.h | 54 + src/LuaSL/LuaSL_utilities.c | 60 + src/LuaSL/build.lua | 23 + src/LuaSL/test.sh | 27 + 13 files changed, 5349 insertions(+) create mode 100644 src/LuaSL/LuaSL.edc create mode 100644 src/LuaSL/LuaSL.h create mode 100644 src/LuaSL/LuaSL_LSL_tree.h create mode 100644 src/LuaSL/LuaSL_compile.c create mode 100644 src/LuaSL/LuaSL_lemon_yaccer.y create mode 100644 src/LuaSL/LuaSL_lexer.l create mode 100644 src/LuaSL/LuaSL_main.c create mode 100644 src/LuaSL/LuaSL_test.c create mode 100644 src/LuaSL/LuaSL_threads.c create mode 100644 src/LuaSL/LuaSL_threads.h create mode 100644 src/LuaSL/LuaSL_utilities.c create mode 100755 src/LuaSL/build.lua create mode 100755 src/LuaSL/test.sh (limited to 'src/LuaSL') diff --git a/src/LuaSL/LuaSL.edc b/src/LuaSL/LuaSL.edc new file mode 100644 index 0000000..844fc8e --- /dev/null +++ b/src/LuaSL/LuaSL.edc @@ -0,0 +1,205 @@ +color_classes { + color_class { name: "test_colour"; color: 255 255 255 255; } +} + +//fonts { +// font: "Vera.ttf" "default"; +//} + +images { + image: "bubble.png" COMP; + image: "test.png" COMP; +} + +collections { + group { + name: "main"; + lua_script_only: 1; + lua_script { + --// stick object private/local vars here + local D; + local text_geom; + + --// init object here + D = {}; --// data is empty table to start + + --// send some random edje message + edje.messagesend(7, "none" ); + edje.messagesend(7, "sig", "signal", "source"); + edje.messagesend(7, "str", "hello world"); + edje.messagesend(7, "int", 987); + edje.messagesend(7, "float", 987.321); + edje.messagesend(7, "strset", {"hello", "there", "world"}); + edje.messagesend(7, "intset", {1, 2, 3}); + edje.messagesend(7, "floatset", {1.1, 2.2, 3.3}); + edje.messagesend(7, "strint", "hello world", 7); + edje.messagesend(7, "strfloat", "hello world", 7.654); + edje.messagesend(7, "strintset","hello world", {1, 2, 3}); + + D.edje = edje.edje(); + D.edje:file("plain/edje/group"); + D.edje:show(); + + D.text = edje.text(); + D.text:geom (50, 5, 150, 50); + D.text:color (255, 0, 0, 255); + D.text:font("Sans:style=Bold", 32); + D.text:text("Lua rocks!"); + text_geom = D.text:geom(); +--// print(D.text:text()); + D.text:show(); + + + --// shutdown func - generally empty or not there. everything garbage collected for you + function shutdown () +--// print("lua::shutdown"); + end + + function show () +--// print("lua::show"); + end + + function hide () +--// print("lua::hide"); + end + + function move (x, y) +--// print("lua::move x=" .. x .. " x=" .. y); + D.edje:move(0, 0); + end + + function resize (w, h) +--// print("lua::resize w=" .. w .. " h=" .. h); + D.text:move((w - text_geom.w) / 2, (h - text_geom.h) / 8); + D.edje:resize(w, h); + end + + function message (id, type, ...) + print("lua::message id=" .. id .. " type=" .. type); + --// handle your message type here. check id + type then use the + --// vararg appropriately. they are the same as the params passed + --// to edje:messagesend() (if any are passed at all). Any array + --// arguments are passed as a single table. + + if ("none" == type) then + print("lua::message no args"); + elseif ("strset" == type) then + strs = ... ; + print_table_start(strs, "", "lua::message strings"); + elseif ("intset" == type) then + ints = ... ; + print_table_start(ints, "", "lua::message ints"); + elseif ("floatset" == type) then + floats = ... ; + print_table_start(floats, "", "lua::message floats"); + elseif ("strintset" == type) then + str, ints = ... ; + print("lua::message " .. str); + print_table_start(ints, "", "lua::message ints"); + elseif ("strfloatset" == type) then + str, floats = ... ; + print("lua::message " .. str); + print_table_start(floats, "", "lua::message floats"); + else + print("lua::message " .. ... ); + end + end + + function signal (sig, src) + print("lua::signal sig= " .. sig .. " src= " .. src); + end + } + } + + // The group name NEEDS a / in it, + // or the part below that tries to swallow it won't work. + // Leaving just the lua part visible. + group { + name: "bubbles/lua"; + lua_script_only: 1; + lua_script { + local bubbles = { }; + local bubbleCols = 8; + local bubbleRows = 6; + + for i = 1, bubbleRows do + row = { }; + for j = 1, bubbleCols do + image = edje.image(); + image:image("bubble.png"); + image:show(); + table.insert(row, image); + end + table.insert(bubbles, row); + end + + function resize (w, h) + for i = 1, bubbleRows do + for j = 1, bubbleCols do + w1 = w / bubbleCols; + h1 = h / bubbleRows; + bubbles[i][j]:geom((j - 1) * w1, (i - 1) * h1, w1, h1); + if ((1 == i) or (1 == j) or (bubbleRows == i) or (bubbleCols == j)) then + bubbles[i][j]:color(0, 255, 0, 200); + else + bubbles[i][j]:color(math.random(200) + 55, 0, math.random(255) + 55, 200); + end + end + end + end + } + } + + group { + name: "plain/edje/group"; + parts { + part { + name: "background"; + type: RECT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 0 0 0 255; + } + } + + // A lua group embedded in an edje group. + part { + name: "bubbles_lua"; + type: GROUP; + source: "bubbles/lua"; + mouse_events: 0; + description { state: "default" 0.0; } + } + + part { + name: "background_image"; + type: IMAGE; + mouse_events: 0; + description { + state: "default" 0.0; + aspect_preference: HORIZONTAL; + color_class: "test_colour"; + image { normal: "test.png"; } + } + } + + part { + name: "some_text"; + type: TEXT; + mouse_events: 0; + description { + state: "default" 0; + text + { + text: "This is test text."; + text_class: "test_text_class"; + } + } + } + + } + } + +} + diff --git a/src/LuaSL/LuaSL.h b/src/LuaSL/LuaSL.h new file mode 100644 index 0000000..d1f448e --- /dev/null +++ b/src/LuaSL/LuaSL.h @@ -0,0 +1,82 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#else +//#define PACKAGE_EXAMPLES_DIR "." +#define __UNUSED__ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +typedef struct _script script; // Define this here, so LuaSL_threads.h can use it. +typedef struct _gameGlobals gameGlobals; // Define this here, so LuaSL_threads.h can use it. + +#include "LuaSL_threads.h" +#include "LumbrJack.h" + + +#define WIDTH (512) +#define HEIGHT (384) + + +#define TABLE_WIDTH 7 +#define TABLE_HEIGHT 42 + + +struct _gameGlobals +{ + Ecore_Evas *ee; // Our window. + Evas *canvas; // The canvas for drawing directly onto. + Evas_Object *bg; // Our background edje, also the game specific stuff. + Evas_Object *edje; // The edje of the background. + Ecore_Con_Server *server; + Eina_Hash *scripts, *names; + int logDom; + const char *address; + int port; + boolean ui; // Wether we actually start up the UI. +}; + +struct _script +{ + Eina_Clist node; + gameGlobals *game; + char SID[PATH_MAX]; + char fileName[PATH_MAX]; + lua_State *L; + struct timeval startTime; + float compileTime, timerTime; + int bugs, warnings; + boolean running; + int status; + int args; + Eina_Clist messages; + Ecore_Con_Client *client; + Ecore_Timer *timer; +}; + +typedef struct +{ + Eina_Clist node; + script *script; + const char message[PATH_MAX]; +} scriptMessage; + + +void scriptSendBack(void * data); +void sendBack(gameGlobals *ourGlobals, Ecore_Con_Client *client, const char *SID, const char *message, ...); +void sendForth(gameGlobals *ourGlobals, const char *SID, const char *message, ...); +float timeDiff(struct timeval *now, struct timeval *then); + +#include "LuaSL_LSL_tree.h" diff --git a/src/LuaSL/LuaSL_LSL_tree.h b/src/LuaSL/LuaSL_LSL_tree.h new file mode 100644 index 0000000..5415228 --- /dev/null +++ b/src/LuaSL/LuaSL_LSL_tree.h @@ -0,0 +1,443 @@ + +#ifndef __LUASL_TREE_H__ +#define __LUASL_TREE_H__ + +#define LUASL_DEBUG 0 +#define LUASL_DIFF_CHECK 0 + + +#include // So we can have NULL defined. +#include +#include +#include + +#include "assert.h" +#include +#include +#include +#include // For PATH_MAX. + +#include "LuaSL_lemon_yaccer.h" + +#define YYERRCODE 256 +#define YYDEBUG 1 + + +// http://w-hat.com/stackdepth is a useful discussion about some aspects of the LL parser. + + +typedef struct _allowedTypes allowedTypes; +typedef struct _LSL_Token LSL_Token; +typedef struct _LSL_Text LSL_Text; +typedef struct _LSL_Leaf LSL_Leaf; +typedef struct _LSL_Numby LSL_Numby; +typedef struct _LSL_Parenthesis LSL_Parenthesis; +typedef struct _LSL_Identifier LSL_Identifier; +typedef struct _LSL_Statement LSL_Statement; +typedef struct _LSL_Block LSL_Block; +typedef struct _LSL_Function LSL_Function; +typedef struct _LSL_FunctionCall LSL_FunctionCall; +typedef struct _LSL_State LSL_State; +typedef struct _LSL_Script LSL_Script; + +extern LSL_Token **tokens; +extern int lowestToken; + +typedef int LSL_Type; + +typedef enum +{ + OM_LSL, + OM_LUA +} outputMode; + +typedef void (*outputToken) (FILE *file, outputMode mode, LSL_Leaf *content); + +//#ifndef FALSE +//typedef enum +//{ +// FALSE = 0, +// TRUE = 1 +//} boolean; +//#endif + +typedef enum +{ + LSL_NONE = 0, + LSL_LEFT2RIGHT = 1, + LSL_RIGHT2LEFT = 2, + LSL_INNER2OUTER = 4, + LSL_UNARY = 8, + LSL_ASSIGNMENT = 16, + LSL_CREATION = 32, + LSL_NOIGNORE = 64, + LSL_TYPE = 128 +} LSL_Flags; + + +// VERY IMPORTANT to keep this in sync with allowedTypes allowed[] from LuaSL_compile.c! +typedef enum +{ + OT_nothing, + + OT_bool, + OT_integer, + OT_float, + OT_key, + OT_list, + OT_rotation, + OT_string, + OT_vector, + OT_other, + + OT_boolBool, + OT_intBool, + OT_intInt, + OT_intFloat, + OT_floatInt, + OT_floatFloat, + OT_keyKey, + OT_keyString, + OT_stringKey, + OT_stringString, + OT_listList, + OT_listBool, + OT_listInt, + OT_listFloat, + OT_listString, + OT_intList, + OT_floatList, + OT_listOther, + OT_vectorVector, + OT_vectorFloat, + OT_vectorRotation, + OT_rotationRotation, + OT_otherOther, + OT_undeclared, + OT_invalid +} opType; + +typedef enum +{ + ST_NONE = 0, + ST_ASSIGNMENT = 1, // -= *= /= + ST_BIT_NOT = 2, // ~ + ST_BOOL_NOT = 4, // ! + ST_BITWISE = 8, // & | ^ << >> + ST_BOOLEAN = 16, // && !! + ST_COMPARISON = 32, // < > <= >= + ST_CONCATENATION = 64, // = += + ST_EQUALITY = 128, // == != + ST_ADD = 512, // + + ST_SUBTRACT = 1024, // - + ST_NEGATE = 2048, // - + ST_MULTIPLY = 4096, // * / + ST_MODULO = 8192 // % %= +} opSubType; + +typedef enum +{ + MF_NONE = 0, + MF_LOCAL = 1, + MF_NOASSIGN = 2, // These two are for completely different purposes. This one is for variable definitions with no assignment. + MF_ASSIGNEXP = 4, // These two are for completely different purposes. This one is for assignments being used as expressions. + MF_WRAPFUNC = 8, + MF_PREDEC = 16, + MF_PREINC = 32, + MF_POSTDEC = 64, + MF_POSTINC = 128, + MF_LSLCONST = 256, + MF_TYPECAST = 512 +} miscFlags; + +struct _allowedTypes +{ + opType result; + const char *name; + int subTypes; +}; + +struct _LSL_Token +{ + LSL_Type type; + opSubType subType; + const char *toKen; + LSL_Flags flags; + outputToken output; +}; + +struct _LSL_Text +{ + const char *text; +#if LUASL_DIFF_CHECK + Eina_Strbuf *ignorable; +#endif +}; + +struct _LSL_Leaf +{ + LSL_Leaf *left; + LSL_Leaf *right; + LSL_Token *toKen; +#if LUASL_DIFF_CHECK + Eina_Strbuf *ignorable; +#endif + int line, column, len; + opType basicType; + miscFlags flags; + union + { + float floatValue; + float vectorValue[3]; + float rotationValue[4]; + int integerValue; + LSL_Numby *numbyValue; + LSL_Leaf *listValue; + const char *stringValue; + opType operationValue; + LSL_Parenthesis *parenthesis; + LSL_Identifier *identifierValue; + LSL_Statement *statementValue; + LSL_Block *blockValue; + LSL_Function *functionValue; + LSL_FunctionCall *functionCallValue; + LSL_State *stateValue; + LSL_Script *scriptValue; + } value; +}; + +struct _LSL_Numby +{ + LSL_Text text; + LSL_Type type; + union + { + float floatValue; + int integerValue; + } value; +}; + +struct _LSL_Parenthesis +{ + LSL_Leaf *contents; +#if LUASL_DIFF_CHECK + Eina_Strbuf *rightIgnorable; +#endif + LSL_Type type; + miscFlags flags; +}; + +struct _LSL_Identifier // For variables and function parameters. +{ + LSL_Text name; + LSL_Statement *definition; + Eina_Strbuf *ignorable; + const char *sub; + LSL_Leaf value; + miscFlags flags; +}; + +struct _LSL_Statement +{ + Eina_Clist statement; // For block statement lists, this is the entry. + LSL_Text identifier; + LSL_Parenthesis *parenthesis; + LSL_Leaf *expressions; // A for statement will have three expressions, and two semicolons, everything else has zero or one. + LSL_Block *block; + LSL_Statement *single; // For single statement "blocks". + LSL_Statement *elseBlock; + LSL_Type type; // Expression type. +#if LUASL_DIFF_CHECK + Eina_Strbuf **ignorable; // Can be up to five of these I think. +#endif + miscFlags flags; +}; + +struct _LSL_Block +{ + LSL_Block *outerBlock; + Eina_Clist statements; // For statement lists, this is the HEAD. + Eina_Hash *variables; // Those variables in this scope. + LSL_Function *function; // A pointer to the function if this block is a function. +#if LUASL_DIFF_CHECK + Eina_Strbuf *openIgnorable; + Eina_Strbuf *closeIgnorable; +#endif +}; + +struct _LSL_Function +{ + LSL_Text name; + LSL_Text type; + const char *state; +#if LUASL_DIFF_CHECK +// LSL_Leaf *params; // So we store the parenthesis, and their ignorables. + // This points to the params leaf, which is a function, pointing to this structure. The actual params are in vars. +#endif + Eina_Inarray vars; // Eina Inarray has not been released yet (Eina 1.2). + LSL_Block *block; + miscFlags flags; +}; + +struct _LSL_FunctionCall +{ + LSL_Function *function; + Eina_Inarray params; // Eina Inarray has not been released yet (Eina 1.2). + Eina_Clist dangler; // Entry for function calls used before the function is defined. + LSL_Leaf *call; // This is to stash the details for dangling ones, to search later. + // The line and column details are needed for bitching, so we need the leaf. + // Also need the stringValue for the search. + // On top of all that, the leaf is still used in expressions, so need to keep it around and update it when resolving danglers. +}; + +struct _LSL_State +{ + LSL_Text name; + LSL_Text state; + LSL_Block *block; + Eina_Hash *handlers; +}; + +struct _LSL_Script +{ + const char *name; + Eina_Hash *functions; + Eina_Hash *states; + Eina_Hash *variables; + int bugCount, warningCount; +}; + +/* Tracking variables. + +There are global variables, block local variables, and function parameters. + +For outputting Lua, which is the ultimate goal - + track order, name, and type. + +For looking them up during the compile - + quick access from name. + +For validating them during compile - + track type. + +For outputting LSL to double check - + track order, name, type, and white space. + +For executing directly from the AST - + track order, name, type, and value. + In this case, order is only important for functions. + +We can assume that names are stringshared. This means we only have to +compare pointers. It also means the same name stored at diffferent +scopes, must be stored in separate structures, coz the pointers are the +same. + +Order is taken care of by the AST anyway, but for somethings we want to +condense the AST down to something more efficient. + +On the other hand, no need to micro optimise it just yet, we should be +able to try out other data structures at a later date, then benchmark +them with typical scripts. + +Right now I see nothing wrong with the current use of hash for script +and block variables. The same for script states and functions, as well +as state functions. Though in the near future, they will have similar +problems to functions I think - the need to track order and white +space. + +Function params got unwieldy. Cleaned that up now. + +*/ + +/* General design. + +NOTE We can remove the white space tracking at compile time, as it's +only a debugging aid. Will be a performance and memory gain for +productidon use. Tracking values on the other hand will still be useful +for constants. + +The compile process starts with turning tokens into AST nodes connected +in a tree. During that process the parser wants to condense nodes down +to more efficient data structures. This is a good idea, as we will +spend a fair amount of time looking up names, no matter which part of +the process is important at the time. + +Once the parser has condensed things down, it only deals with the +condensed nodes. So we can get rid of some of the AST parts at this +time, so long as we keep the relevant information. This is what the +other data structures above are for. Lemon tries to free the no longer +needed AST nodes itself, even if we are still using them internally. +Need to do something about that. + +*/ + +// Define the type for flex and lemon. +#define YYSTYPE LSL_Leaf + +typedef struct +{ + gameGlobals *game; + Ecore_Con_Client *client; + void *scanner; // This should be of type yyscan_t, which is typedef to void * anyway, but that does not get defined until LuaSL_lexer.h, which depends on this struct being defined first. + int argc; + char **argv; + char SID[37]; + char fileName[PATH_MAX]; + FILE *file; + LSL_Leaf *ast; + LSL_Script script; + LSL_State state; +#if LUASL_DIFF_CHECK + Eina_Strbuf *ignorable; +#endif + LSL_Leaf *lval; + LSL_Block *currentBlock; + LSL_Function *currentFunction; + Eina_Clist danglingCalls; // HEAD for function calls used before the function is defined. + int column, line; + int undeclared; + boolean inState; +} LuaSL_compiler; + + +#ifndef excludeLexer + #include "LuaSL_lexer.h" +#endif + + + +boolean compilerSetup(gameGlobals *ourGlobals); +boolean compileLSL(gameGlobals *ourGlobals, Ecore_Con_Client *client, char *SID, char *script, boolean doConstants); +void burnLeaf(void *data); + +LSL_Leaf *addBlock(LuaSL_compiler *compiler, LSL_Leaf *left, LSL_Leaf *lval, LSL_Leaf *right); +LSL_Leaf *addCrement(LuaSL_compiler *compiler, LSL_Leaf *variable, LSL_Leaf *crement, LSL_Type type); +LSL_Leaf *addFor(LuaSL_compiler *compiler, LSL_Leaf *lval, LSL_Leaf *flow, LSL_Leaf *left, LSL_Leaf *expr0, LSL_Leaf *stat0, LSL_Leaf *expr1, LSL_Leaf *stat1, LSL_Leaf *expr2, LSL_Leaf *right, LSL_Leaf *block); +LSL_Leaf *addFunction(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier, LSL_Leaf *open, LSL_Leaf *params, LSL_Leaf *close); +LSL_Leaf *addFunctionBody(LuaSL_compiler *compiler, LSL_Leaf *function, LSL_Leaf *block); +LSL_Leaf *addFunctionCall(LuaSL_compiler *compiler, LSL_Leaf *identifier, LSL_Leaf *open, LSL_Leaf *params, LSL_Leaf *close); +LSL_Leaf *addIfElse(LuaSL_compiler *compiler, LSL_Leaf *ifBlock, LSL_Leaf *elseBlock); +LSL_Leaf *addList(LSL_Leaf *left, LSL_Leaf *list, LSL_Leaf *right); +LSL_Leaf *addNumby(LSL_Leaf *numby); +LSL_Leaf *addOperation(LuaSL_compiler *compiler, LSL_Leaf *left, LSL_Leaf *lval, LSL_Leaf *right); +LSL_Leaf *addParameter(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *newParam); +LSL_Leaf *addParenthesis(LSL_Leaf *lval, LSL_Leaf *expr, LSL_Type type, LSL_Leaf *rval); +LSL_Leaf *addRotVec(LSL_Leaf *left, LSL_Leaf *list, LSL_Leaf *right); +LSL_Leaf *addState(LuaSL_compiler *compiler, LSL_Leaf *state, LSL_Leaf *identifier, LSL_Leaf *block); +LSL_Leaf *addStatement(LuaSL_compiler *compiler, LSL_Leaf *lval, LSL_Leaf *flow, LSL_Leaf *left, LSL_Leaf *expr, LSL_Leaf *right, LSL_Leaf *block, LSL_Leaf *identifier); +LSL_Leaf *addTypecast(LSL_Leaf *lval, LSL_Leaf *type, LSL_Leaf *rval, LSL_Leaf *expr); +LSL_Leaf *addVariable(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier, LSL_Leaf *assignment, LSL_Leaf *expr); + +LSL_Leaf *beginBlock(LuaSL_compiler *compiler, LSL_Leaf *block); +LSL_Leaf *checkVariable(LuaSL_compiler *compiler, LSL_Leaf *identifier, LSL_Leaf *dot, LSL_Leaf *sub); +LSL_Leaf *collectArguments(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *comma, LSL_Leaf *arg); +LSL_Leaf *collectParameters(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *comma, LSL_Leaf *newParam); +LSL_Leaf *collectStatements(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *newStatement); + +void *ParseAlloc(void *(*mallocProc)(size_t)); +void ParseTrace(FILE *TraceFILE, char *zTracePrompt); +void Parse(void *yyp, int yymajor, LSL_Leaf *yyminor, LuaSL_compiler *compiler); +void ParseFree(void *p, void (*freeProc)(void*)); + + +#endif // __LUASL_LSL_TREE_H__ diff --git a/src/LuaSL/LuaSL_compile.c b/src/LuaSL/LuaSL_compile.c new file mode 100644 index 0000000..771888e --- /dev/null +++ b/src/LuaSL/LuaSL_compile.c @@ -0,0 +1,2345 @@ + +#include "LuaSL.h" + +/* TODO - problem de jour +*/ + + +static void outputBitOp(FILE *file, outputMode mode, LSL_Leaf *leaf); +static void outputBlockToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputCrementsToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputFloatToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputFunctionToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputFunctionCallToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputIntegerToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputIdentifierToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputListToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputParameterListToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputParenthesisToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputStateToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputStatementToken(FILE *file, outputMode mode, LSL_Leaf *content); +static void outputStringToken(FILE *file, outputMode mode, LSL_Leaf *content); + +LSL_Token LSL_Tokens[] = +{ + // Various forms of "space". + {LSL_COMMENT, ST_NONE, "/*", LSL_NONE, NULL}, + {LSL_COMMENT_LINE, ST_NONE, "//", LSL_NONE, NULL}, + {LSL_SPACE, ST_NONE, " ", LSL_NONE, NULL}, + + // Operators, in order of precedence, low to high + // Left to right, unless otherwise stated. + // According to http://wiki.secondlife.com/wiki/Category:LSL_Operators, which was obsoleted by http://wiki.secondlife.com/wiki/LSL_Operators but that has less info. + + {LSL_ASSIGNMENT_CONCATENATE,ST_CONCATENATION, "+=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, + {LSL_ASSIGNMENT_ADD, ST_CONCATENATION, "+=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, + {LSL_ASSIGNMENT_SUBTRACT, ST_ASSIGNMENT, "-=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, + {LSL_ASSIGNMENT_MULTIPLY, ST_ASSIGNMENT, "*=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, + {LSL_ASSIGNMENT_MODULO, ST_MODULO, "%=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, + {LSL_ASSIGNMENT_DIVIDE, ST_ASSIGNMENT, "/=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, + {LSL_ASSIGNMENT_PLAIN, ST_CONCATENATION, "=", LSL_RIGHT2LEFT | LSL_ASSIGNMENT, NULL}, + + {LSL_BOOL_AND, ST_BOOLEAN, "&&", LSL_RIGHT2LEFT, NULL}, +// QUIRK - Seems to be some disagreement about BOOL_AND/BOOL_OR precedence. Either they are equal, or OR is higher. +// QUIRK - No boolean short circuiting. +// LUA - Short circiuts boolean operations, and goes left to right. +// LUA - "and" returns its first argument if it is false, otherwise, it returns its second argument. "or" returns its first argument if it is not false, otherwise it returns its second argument. +// Note that the above means that "and/or" can return any type. + {LSL_BOOL_OR, ST_BOOLEAN, "||", LSL_RIGHT2LEFT, NULL}, + {LSL_BIT_OR, ST_BITWISE, "|", LSL_LEFT2RIGHT, outputBitOp}, + {LSL_BIT_XOR, ST_BITWISE, "^", LSL_LEFT2RIGHT, outputBitOp}, + {LSL_BIT_AND, ST_BITWISE, "&", LSL_LEFT2RIGHT, outputBitOp}, +// QUIRK - Booleans and conditionals are executed right to left. Or maybe not, depending on who you believe. + {LSL_NOT_EQUAL, ST_EQUALITY, "!=", LSL_RIGHT2LEFT, NULL}, + {LSL_EQUAL, ST_EQUALITY, "==", LSL_RIGHT2LEFT, NULL}, + {LSL_GREATER_EQUAL, ST_COMPARISON, ">=", LSL_RIGHT2LEFT, NULL}, + {LSL_LESS_EQUAL, ST_COMPARISON, "<=", LSL_RIGHT2LEFT, NULL}, + {LSL_GREATER_THAN, ST_COMPARISON, ">", LSL_RIGHT2LEFT, NULL}, + {LSL_LESS_THAN, ST_COMPARISON, "<", LSL_RIGHT2LEFT, NULL}, +// LUA - comparisons are always false if they are different types. Tables, userdata, and functions are compared by reference. Strings compare in alphabetical order, depending on current locale. +// LUA - really only has three conditionals, as it translates a ~= b to not (a == b), a > b to b < a, and a >= b to b <= a. + {LSL_RIGHT_SHIFT, ST_BITWISE, ">>", LSL_LEFT2RIGHT, outputBitOp}, + {LSL_LEFT_SHIFT, ST_BITWISE, "<<", LSL_LEFT2RIGHT, outputBitOp}, + {LSL_CONCATENATE, ST_ADD, "+", LSL_LEFT2RIGHT, NULL}, + {LSL_ADD, ST_ADD, "+", LSL_LEFT2RIGHT, NULL}, + {LSL_SUBTRACT, ST_SUBTRACT, "-", LSL_LEFT2RIGHT, NULL}, + {LSL_CROSS_PRODUCT, ST_NONE, "%", LSL_LEFT2RIGHT, NULL}, + {LSL_DOT_PRODUCT, ST_NONE, "*", LSL_LEFT2RIGHT, NULL}, + {LSL_MULTIPLY, ST_MULTIPLY, "*", LSL_LEFT2RIGHT, NULL}, + {LSL_MODULO, ST_MODULO, "%", LSL_LEFT2RIGHT, NULL}, + {LSL_DIVIDE, ST_MULTIPLY, "/", LSL_LEFT2RIGHT, NULL}, + {LSL_NEGATION, ST_NEGATE, "-", LSL_RIGHT2LEFT | LSL_UNARY, NULL}, + {LSL_BOOL_NOT, ST_BOOL_NOT, "!", LSL_RIGHT2LEFT | LSL_UNARY, NULL}, + {LSL_BIT_NOT, ST_BIT_NOT, "~", LSL_RIGHT2LEFT | LSL_UNARY, outputBitOp}, + +// LUA precedence - (it has no bit operators, at least not until 5.2, but LuaJIT has them as table functions.) +// or +// and +// < > <= >= ~= == +// .. +// + - +// * / +// not negate +// exponentiation (^) + + {LSL_TYPECAST_CLOSE, ST_NONE, ")", LSL_RIGHT2LEFT | LSL_UNARY, NULL}, + {LSL_TYPECAST_OPEN, ST_NONE, "(", LSL_RIGHT2LEFT | LSL_UNARY, outputParenthesisToken}, + {LSL_ANGLE_CLOSE, ST_NONE, ">", LSL_LEFT2RIGHT | LSL_CREATION, NULL}, + {LSL_ANGLE_OPEN, ST_NONE, "<", LSL_LEFT2RIGHT | LSL_CREATION, NULL}, + {LSL_BRACKET_CLOSE, ST_NONE, "]", LSL_INNER2OUTER | LSL_CREATION, NULL}, + {LSL_BRACKET_OPEN, ST_NONE, "[", LSL_INNER2OUTER | LSL_CREATION, NULL}, + {LSL_PARENTHESIS_CLOSE, ST_NONE, ")", LSL_INNER2OUTER, NULL}, + {LSL_PARENTHESIS_OPEN, ST_NONE, "(", LSL_INNER2OUTER, outputParenthesisToken}, + {LSL_DOT, ST_NONE, ".", LSL_RIGHT2LEFT, NULL}, + {LSL_DECREMENT_POST, ST_NONE, "--", LSL_RIGHT2LEFT | LSL_UNARY, outputCrementsToken}, + {LSL_DECREMENT_PRE, ST_NONE, "--", LSL_RIGHT2LEFT | LSL_UNARY, outputCrementsToken}, + {LSL_INCREMENT_POST, ST_NONE, "++", LSL_RIGHT2LEFT | LSL_UNARY, outputCrementsToken}, + {LSL_INCREMENT_PRE, ST_NONE, "++", LSL_RIGHT2LEFT | LSL_UNARY, outputCrementsToken}, + {LSL_COMMA, ST_NONE, ",", LSL_LEFT2RIGHT, NULL}, + + {LSL_EXPRESSION, ST_NONE, "expression", LSL_NONE , NULL}, + + // Types. + {LSL_FLOAT, ST_NONE, "float", LSL_NONE, outputFloatToken}, + {LSL_INTEGER, ST_NONE, "integer", LSL_NONE, outputIntegerToken}, + {LSL_KEY, ST_NONE, "key", LSL_NONE, outputStringToken}, + {LSL_LIST, ST_NONE, "list", LSL_NONE, outputListToken}, + {LSL_ROTATION, ST_NONE, "rotation", LSL_NONE, outputListToken}, + {LSL_STRING, ST_NONE, "string", LSL_NONE, outputStringToken}, + {LSL_VECTOR, ST_NONE, "vector", LSL_NONE, outputListToken}, + + // Types names. + {LSL_TYPE_FLOAT, ST_NONE, "float", LSL_TYPE, NULL}, + {LSL_TYPE_INTEGER, ST_NONE, "integer", LSL_TYPE, NULL}, + {LSL_TYPE_KEY, ST_NONE, "key", LSL_TYPE, NULL}, + {LSL_TYPE_LIST, ST_NONE, "list", LSL_TYPE, NULL}, + {LSL_TYPE_ROTATION, ST_NONE, "rotation", LSL_TYPE, NULL}, + {LSL_TYPE_STRING, ST_NONE, "string", LSL_TYPE, NULL}, + {LSL_TYPE_VECTOR, ST_NONE, "vector", LSL_TYPE, NULL}, + + // Then the rest of the syntax tokens. + {LSL_FUNCTION_CALL, ST_NONE, "funccall", LSL_NONE, outputFunctionCallToken}, + {LSL_IDENTIFIER, ST_NONE, "identifier", LSL_NONE, outputIdentifierToken}, + {LSL_VARIABLE, ST_NONE, "variable", LSL_NONE, outputIdentifierToken}, + + {LSL_LABEL, ST_NONE, "@", LSL_NONE, NULL}, + + {LSL_DO, ST_NONE, "do", LSL_NONE, NULL}, + {LSL_FOR, ST_NONE, "for", LSL_NONE, NULL}, + {LSL_ELSE, ST_NONE, "else", LSL_NONE, NULL}, + {LSL_ELSEIF, ST_NONE, "elseif", LSL_NONE, NULL}, + {LSL_IF, ST_NONE, "if", LSL_NONE, NULL}, + {LSL_JUMP, ST_NONE, "jump", LSL_NONE, NULL}, + {LSL_RETURN, ST_NONE, "return", LSL_NONE, NULL}, + {LSL_STATE_CHANGE, ST_NONE, "state", LSL_NONE, NULL}, + {LSL_WHILE, ST_NONE, "while", LSL_NONE, NULL}, + {LSL_STATEMENT, ST_NONE, ";", LSL_NOIGNORE, outputStatementToken}, + + {LSL_BLOCK_CLOSE, ST_NONE, "}", LSL_NONE, NULL}, + {LSL_BLOCK_OPEN, ST_NONE, "{", LSL_NONE, outputBlockToken}, + {LSL_PARAMETER, ST_NONE, "parameter", LSL_NONE, outputIdentifierToken}, + {LSL_PARAMETER_LIST, ST_NONE, "plist", LSL_NONE, outputParameterListToken}, + {LSL_FUNCTION, ST_NONE, "function", LSL_NONE, outputFunctionToken}, + {LSL_DEFAULT, ST_NONE, "default", LSL_NONE, outputStateToken}, + {LSL_STATE, ST_NONE, "state", LSL_NONE, outputStateToken}, + {LSL_SCRIPT, ST_NONE, "", LSL_NONE, NULL}, + + {LSL_UNKNOWN, ST_NONE, "unknown", LSL_NONE, NULL}, + + // A sentinal. + {999999, ST_NONE, NULL, LSL_NONE, NULL} +}; + +// VERY IMPORTANT to keep this in sync with enum opType from LuaSL_LSL_tree.h! +allowedTypes allowed[] = +{ + {OT_nothing, "nothing", (ST_NONE)}, // + + {OT_bool, "boolean", (ST_BOOL_NOT)}, // bool ! + {OT_integer, "integer", (ST_BOOL_NOT | ST_BIT_NOT | ST_NEGATE)}, // int ! - ~ + {OT_float, "float", (ST_BOOL_NOT | ST_NEGATE)}, // float ! - + {OT_key, "key", (ST_BOOL_NOT)}, // key ! + {OT_list, "list", (ST_NONE)}, // + {OT_rotation, "rotation", (ST_NONE)}, // + {OT_string, "string", (ST_BOOL_NOT)}, // string ! + {OT_vector, "vector", (ST_NONE)}, // + {OT_other, "other", (ST_NONE)}, // + + {OT_bool, "boolean", (ST_BOOLEAN | ST_EQUALITY)}, // bool bool == != = && || + + {OT_integer, "integer", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT | ST_MODULO | ST_BITWISE)}, // int boolean * / + - % == != < > <= >= = += -= *= /= %= & | ^ << >> + {OT_integer, "integer", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT | ST_MODULO | ST_BITWISE)}, // int int * / + - % == != < > <= >= = += -= *= /= %= & | ^ << >> + {OT_float, "float", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT)}, // int float cast to float float + {OT_float, "float", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT)}, // float int cast to float float + {OT_float, "float", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT)}, // float float * / + - == != < > <= >= = += -= *= /= + + {OT_string, "string", (ST_ADD | ST_EQUALITY | ST_CONCATENATION)}, // key key cast to string string + {OT_string, "string", (ST_ADD | ST_EQUALITY | ST_CONCATENATION)}, // key string cast to string string + {OT_string, "string", (ST_ADD | ST_EQUALITY | ST_CONCATENATION)}, // string key cast to string string + {OT_string, "string", (ST_ADD | ST_EQUALITY | ST_CONCATENATION)}, // string string + == != = += + + {OT_list, "list", (ST_ADD | ST_EQUALITY | ST_CONCATENATION | ST_ASSIGNMENT )}, // list list + == != = += + {OT_list, "list", (ST_ADD | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT )}, // list boolean + < > <= >= = += + {OT_list, "list", (ST_ADD | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT )}, // list integer + < > <= >= = += + {OT_list, "list", (ST_ADD | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT )}, // list float + < > <= >= = += + {OT_list, "list", (ST_ADD | ST_COMPARISON | ST_CONCATENATION | ST_ASSIGNMENT )}, // list string + < > <= >= = += + {OT_integer, "integer", (ST_ADD | ST_COMPARISON)}, // integer list + < > <= >= + {OT_float, "float", (ST_ADD | ST_COMPARISON)}, // float list + < > <= >= + {OT_list, "list", (ST_ADD | ST_CONCATENATION)}, // list other + = += + + {OT_vector, "vector", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_CONCATENATION | ST_ASSIGNMENT | ST_MODULO)}, // vector vector * / + - % == != = += -= *= /= %= + {OT_vector, "vector", (ST_MULTIPLY | ST_ASSIGNMENT)}, // vector float * / *= /= + {OT_vector, "vector", (ST_MULTIPLY)}, // vector rotation * / + + {OT_rotation, "rotation", (ST_MULTIPLY | ST_ADD | ST_SUBTRACT | ST_EQUALITY | ST_CONCATENATION | ST_ASSIGNMENT)}, // rotation rotation * / + - == != = += -= *= /= + + {OT_other, "other", (ST_NONE)}, // + {OT_undeclared, "undeclared", (ST_NONE)}, // + {OT_invalid, "invalid", (ST_NONE)} // +}; + +opType opExpr[][10] = +{ + {OT_nothing, OT_bool, OT_integer, OT_float, OT_key, OT_list, OT_rotation, OT_string, OT_vector, OT_other}, + {OT_bool, OT_boolBool, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid}, + {OT_integer, OT_intBool, OT_intInt, OT_intFloat, OT_invalid, OT_intList, OT_invalid, OT_invalid, OT_invalid, OT_invalid}, + {OT_float, OT_invalid, OT_floatInt, OT_floatFloat, OT_invalid, OT_floatList, OT_invalid, OT_invalid, OT_invalid, OT_invalid}, + {OT_key, OT_invalid, OT_invalid, OT_invalid, OT_keyKey, OT_invalid, OT_invalid, OT_keyString, OT_invalid, OT_invalid}, + {OT_list, OT_listBool, OT_listInt, OT_listFloat, OT_invalid, OT_listList, OT_invalid, OT_listString, OT_invalid, OT_listOther}, + {OT_rotation, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_rotationRotation, OT_invalid, OT_invalid, OT_invalid}, + {OT_string, OT_invalid, OT_invalid, OT_invalid, OT_stringKey, OT_invalid, OT_invalid, OT_stringString, OT_invalid, OT_invalid}, + {OT_vector, OT_invalid, OT_invalid, OT_vectorFloat, OT_invalid, OT_invalid, OT_vectorRotation, OT_invalid, OT_vectorVector, OT_invalid}, + {OT_other, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_invalid, OT_otherOther} +}; + + +LSL_Token **tokens = NULL; +LSL_Script constants; +int lowestToken = 999999; + + +static LSL_Leaf *newLeaf(LSL_Type type, LSL_Leaf *left, LSL_Leaf *right) +{ + LSL_Leaf *leaf = calloc(1, sizeof(LSL_Leaf)); + + if (leaf) + { + leaf->left = left; + leaf->right = right; + leaf->toKen = tokens[type - lowestToken]; + } + + return leaf; +} + +void burnLeaf(void *data) +{ + LSL_Leaf *leaf = data; + + if (leaf) + { +// TODO - the problem here is that lemon wants to free these after a reduce, but we might want to keep them around. Should ref count them or something. +// burnLeaf(leaf->left); +// burnLeaf(leaf->right); + // TODO - Should free up the value to. +//#if LUASL_DIFF_CHECK +// eina_strbuf_free(leaf->ignorable); +//#endif +// free(leaf); + } +} + +static LSL_Leaf *findFunction(LuaSL_compiler *compiler, const char *name) +{ + LSL_Leaf *func = NULL; + + if (name) + { + if (NULL == func) + func = eina_hash_find(constants.functions, name); + if (NULL != func) + { + func->flags |= MF_LSLCONST; + func->value.functionValue->flags |= MF_LSLCONST; + } + else + func = eina_hash_find(compiler->script.functions, name); + + } + + return func; +} + +static LSL_Leaf *findVariable(LuaSL_compiler *compiler, const char *name) +{ + LSL_Leaf *var = NULL; + + if (name) + { + LSL_Block *block = compiler->currentBlock; + + while ((block) && (NULL == var)) + { + if (block->function) + { + LSL_Leaf *param = NULL; + EINA_INARRAY_FOREACH((&(block->function->vars)), param) + { + if ((param) && (LSL_PARAMETER == param->toKen->type)) + { +// if (name == param->value.identifierValue->name.text) // Assuming they are stringshares. + if (0 == strcmp(name, param->value.identifierValue->name.text)) // Not assuming they are stringeshares. They should be. + var = param; + } + } + } + if ((NULL == var) && block->variables) + var = eina_hash_find(block->variables, name); + block = block->outerBlock; + } + + if (NULL == var) + { + var = eina_hash_find(constants.variables, name); + if (var) + { + var->flags |= MF_LSLCONST; + var->value.identifierValue->flags |= MF_LSLCONST; + } + } + if (NULL == var) + var = eina_hash_find(compiler->script.variables, name); + } + + return var; +} + +LSL_Leaf *checkVariable(LuaSL_compiler *compiler, LSL_Leaf *identifier, LSL_Leaf *dot, LSL_Leaf *sub) +{ + gameGlobals *ourGlobals = compiler->game; + const char *search; + + if (dot) + search = identifier->value.identifierValue->name.text; + else + search = identifier->value.stringValue; + + if (identifier) + { + LSL_Leaf *var = findVariable(compiler, search); + + if (var) + { + if (LUASL_DEBUG) + PI("Found %s!", identifier->value.stringValue); + identifier->value.identifierValue = var->value.identifierValue; + identifier->basicType = var->basicType; + if ((dot) && (sub)) + { + LSL_Identifier *id = calloc(1, sizeof(LSL_Identifier)); + + if (id) + { + memcpy(id, var->value.identifierValue, sizeof(LSL_Identifier)); + identifier->value.identifierValue = id; + if (LSL_ROTATION == var->toKen->type) + { + // TODO - check if it's one of x, y, z, or s. + } + if (LSL_VECTOR == var->toKen->type) + { + // TODO - check if it's one of x, y, or z. + } + identifier->value.identifierValue->sub = sub->value.stringValue; + identifier->basicType = OT_float; + } + } + } + else + { + compiler->script.bugCount++; + sendBack(ourGlobals, compiler->client, compiler->SID, "compilerError(%d,%d,NOT found %s)", identifier->line, identifier->column, identifier->value.stringValue); + } + } + + return identifier; +} + +LSL_Leaf *addOperation(LuaSL_compiler *compiler, LSL_Leaf *left, LSL_Leaf *lval, LSL_Leaf *right) +{ + gameGlobals *ourGlobals = compiler->game; + + if (lval) + { + opType lType, rType; + + lval->left = left; + lval->right = right; + + // Convert subtract to negate if needed. + if ((NULL == left) && (LSL_SUBTRACT == lval->toKen->type)) + lval->toKen = tokens[LSL_NEGATION - lowestToken]; + + // Try to figure out what type of operation this is. + if (NULL == left) + lType = OT_nothing; + else + { + if ((left->toKen) && (LSL_IDENTIFIER == left->toKen->type) && (left->value.identifierValue)) + { + LSL_Leaf *var = findVariable(compiler, left->value.identifierValue->name.text); + + if (var) + lType = var->basicType; + if (left->value.identifierValue->sub) + { + // TODO - keep an eye on this, but I think all the sub types are floats. + lType = OT_float; + } + } + else + lType = left->basicType; + if (OT_undeclared == lType) + { + compiler->script.warningCount++; + sendBack(ourGlobals, compiler->client, compiler->SID, "compilerWarning(%d,%d,Undeclared identifier issue, deferring this until the second pass)", lval->line, lval->column); + lval->basicType = OT_undeclared; + return lval; + } + if (OT_vector < lType) + lType = allowed[lType].result; + } + if (NULL == right) + rType = OT_nothing; + else + { + if ((right->toKen) && (LSL_IDENTIFIER == right->toKen->type) && (right->value.identifierValue)) + { + LSL_Leaf *var = findVariable(compiler, right->value.identifierValue->name.text); + + if (var) + rType = var->basicType; + if (right->value.identifierValue->sub) + { + // TODO - keep an eye on this, but I think all the sub types are floats. + rType = OT_float; + } + } + else + rType = right->basicType; + if (OT_undeclared == rType) + { + compiler->script.warningCount++; + sendBack(ourGlobals, compiler->client, compiler->SID, "compilerWarning(%d,%d,Undeclared identifier issue, deferring this until the second pass)", lval->line, lval->column); + lval->basicType = OT_undeclared; + return lval; + } + if (OT_vector < rType) + rType = allowed[rType].result; + } + + // Convert add to concatenate if needed. + if ((LSL_ADD == lval->toKen->type) && (OT_string == lType) && (OT_string == rType)) + lval->toKen = tokens[LSL_CONCATENATE - lowestToken]; + + switch (lval->toKen->subType) + { + case ST_BOOLEAN : + case ST_COMPARISON : + case ST_EQUALITY : + lval->basicType = OT_bool; + break; + default : + // The basic lookup. + lval->basicType = opExpr[lType][rType]; + if (OT_invalid != lval->basicType) + { + // Check if it's an allowed operation. + if (0 == (lval->toKen->subType & allowed[lval->basicType].subTypes)) + lval->basicType = OT_invalid; + else + { + // Double check the corner cases. + switch (lval->toKen->subType) + { + case ST_MULTIPLY : + if (OT_vectorVector == lval->basicType) + { + if (LSL_MULTIPLY == lval->toKen->type) + { + lval->basicType = OT_float; + lval->toKen = tokens[LSL_DOT_PRODUCT - lowestToken]; + } + else + lval->basicType = OT_vector; + } + break; + default : + break; + } + } + } + break; + } + + /* Flag assignments for the "assignments are statements, which can't happen inside expressions" test. + * + * Assignments in the middle of expressions are legal in LSL, but not in Lua. + * The big complication is that they often happen in the conditionals of flow control statements. That's a big bitch. + * + * So things like - + * + * while ((x = doSomething()) == foo) + * { + * buggerAround(); + * } + * + * Turns into - + * + * x = doSomething(); + * while (x == foo) + * { + * buggerAround(); + * x = doSomething(); + * } + * + * http://lua-users.org/wiki/StatementsInExpressions was helpful. Which suggests something like this - + * + * while ( (function() x = doSomething(); return x; end)() == foo) + * { + * buggerAround(); + * } + * + * The remaining problem is when to recognise the need to do that. + * That's what this code and the matching code in addParenthesis() does. + */ + // TODO - Only got one of these in my test scripts, so leave all this debugging shit in until it's been tested more. + if (left) + { + if (left->flags & MF_ASSIGNEXP) + { +//if ((left) && (right)) +// printf("%s %s %s\n", left->toKen->toKen, lval->toKen->toKen, right->toKen->toKen); +//else if (left) +// printf("%s %s NORIGHT\n", left->toKen->toKen, lval->toKen->toKen); +//else if (right) +// printf("NOLEFT %s %s\n", lval->toKen->toKen, right->toKen->toKen); +//else +// printf("NOLEFT %s NORIGHT\n", lval->toKen->toKen); +// printf("############################################################################## left\n"); + left->flags |= MF_WRAPFUNC; + if (LSL_PARENTHESIS_OPEN == left->toKen->type) + left->value.parenthesis->flags |= MF_WRAPFUNC; + } + } + if (lval) + { +// if (lval->flags & MF_ASSIGNEXP) +// printf("############################################################################## lval %s %s\n", left->toKen->toKen, right->toKen->toKen); + if (LSL_ASSIGNMENT & lval->toKen->flags) + { + lval->flags |= MF_ASSIGNEXP; +//// printf("******************* lval %s %s\n", left->toKen->toKen, right->toKen->toKen); + if (LSL_IDENTIFIER == left->toKen->type) // It always should be. + { + left->flags |= MF_ASSIGNEXP; +//// printf("$$$$$$$$$$$$$$$$$ lval\n"); + } + } + } + // TODO - Don't think I have to do this on the right. + if (right) + { + if (right->flags & MF_ASSIGNEXP) + { +if ((left) && (right)) + printf("%s %s %s\n", left->toKen->toKen, lval->toKen->toKen, right->toKen->toKen); +else if (left) + printf("%s %s NORIGHT\n", left->toKen->toKen, lval->toKen->toKen); +else if (right) + printf("NOLEFT %s %s\n", lval->toKen->toKen, right->toKen->toKen); +else + printf("NOLEFT %s NORIGHT\n", lval->toKen->toKen); + printf("############################################################################## right\n"); + right->flags |= MF_WRAPFUNC; + } + } + + if (OT_invalid == lval->basicType) + { + const char *leftType = "", *rightType = "", *leftToken = "", *rightToken = ""; + + if (left) + { + if (left->toKen) + leftToken = left->toKen->toKen; + else + PE("BROKEN LEFT TOKEN!!!!!!!!!!!!!!!!!!"); + leftType = allowed[left->basicType].name; + } + if (right) + { + if (right->toKen) + rightToken = right->toKen->toKen; + else + PE("BROKEN RIGHT TOKEN!!!!!!!!!!!!!!!!!!"); + rightType = allowed[right->basicType].name; + } + + compiler->script.bugCount++; + sendBack(ourGlobals, compiler->client, compiler->SID, "compilerError(%d,%d,Invalid operation [%s(%s) %s %s(%s)])", lval->line, lval->column, leftType, leftToken, lval->toKen->toKen, rightType, rightToken); + } + } + + return lval; +} + +LSL_Leaf *addBlock(LuaSL_compiler *compiler, LSL_Leaf *left, LSL_Leaf *lval, LSL_Leaf *right) +{ + // Damn, look ahead. The } symbol is getting read (and thus endBlock called) before the last statement in the block is reduced (which actually calls the add*() functions). + compiler->currentBlock = compiler->currentBlock->outerBlock; +#if LUASL_DIFF_CHECK + if ((left) && (right)) + { + left->value.blockValue->closeIgnorable = right->ignorable; + right->ignorable = NULL; + } +#endif + return lval; +} + +LSL_Leaf *addCrement(LuaSL_compiler *compiler, LSL_Leaf *variable, LSL_Leaf *crement, LSL_Type type) +{ + if ((variable) && (crement)) + { + crement->value.identifierValue = variable->value.identifierValue; +#if LUASL_DIFF_CHECK + crement->value.identifierValue->ignorable = variable->ignorable; + variable->ignorable = NULL; +#endif + crement->basicType = variable->basicType; + crement->toKen = tokens[type - lowestToken]; + switch (crement->toKen->type) + { + case LSL_DECREMENT_PRE : variable->value.identifierValue->flags |= MF_PREDEC; break; + case LSL_INCREMENT_PRE : variable->value.identifierValue->flags |= MF_PREINC; break; + case LSL_DECREMENT_POST : variable->value.identifierValue->flags |= MF_POSTDEC; break; + case LSL_INCREMENT_POST : variable->value.identifierValue->flags |= MF_POSTINC; break; + } + variable->value.identifierValue->definition->flags = variable->value.identifierValue->flags; + } + + return crement; +} + +LSL_Leaf *addParameter(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier) +{ + LSL_Identifier *result = calloc(1, sizeof(LSL_Identifier)); + + if ( (identifier) && (result)) + { + result->name.text = identifier->value.stringValue; +#if LUASL_DIFF_CHECK + result->name.ignorable = identifier->ignorable; + identifier->ignorable = NULL; +#endif + result->value.toKen = tokens[LSL_UNKNOWN - lowestToken]; + identifier->value.identifierValue = result; + identifier->toKen = tokens[LSL_PARAMETER - lowestToken]; + identifier->left = type; + if (type) + { + identifier->basicType = type->basicType; + result->value.basicType = type->basicType; + result->value.toKen = type->toKen; // This is the LSL_TYPE_* toKen instead of the LSL_* toKen. Not sure if that's a problem. + } + } + return identifier; +} + +LSL_Leaf *collectParameters(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *comma, LSL_Leaf *newParam) +{ + LSL_Function *func = NULL; + + if (NULL == list) + list = newLeaf(LSL_FUNCTION, NULL, NULL); + + if (list) + { + func = list->value.functionValue; + if (NULL == func) + { + func = calloc(1, sizeof(LSL_Function)); + if (func) + { + list->value.functionValue = func; + eina_inarray_step_set(&(func->vars), sizeof(Eina_Inarray), sizeof(LSL_Leaf), 3); + } + } + + if (func) + { + if (newParam) + { + if (LUASL_DIFF_CHECK) + { + // Stash the comma for diff later. + if (comma) + eina_inarray_push(&(func->vars), comma); + } + eina_inarray_push(&(func->vars), newParam); + // At this point, pointers to newParams are not pointing to the one in func->vars, AND newParam is no longer needed. + } + } + } + return list; +} + +LSL_Leaf *addFunction(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier, LSL_Leaf *open, LSL_Leaf *params, LSL_Leaf *close) +{ + LSL_Function *func = NULL; + + if (params) + { + func = params->value.functionValue; + // At this point, params is no longer needed, except if we are doing diff. + // open and close are not needed either if we are not doing diff. + if (func) + { + if (identifier) + { + func->name.text = identifier->value.stringValue; +#if LUASL_DIFF_CHECK + func->name.ignorable = identifier->ignorable; + identifier->ignorable = NULL; +#endif + identifier->toKen = tokens[LSL_FUNCTION - lowestToken]; + identifier->value.functionValue = func; + if (type) + { + func->type.text = type->toKen->toKen; +#if LUASL_DIFF_CHECK + func->type.ignorable = type->ignorable; + type->ignorable = NULL; +#endif + identifier->basicType = type->basicType; + } + else + identifier->basicType = OT_nothing; + if (compiler->inState) + eina_hash_add(compiler->state.handlers, func->name.text, func); + else + eina_hash_add(compiler->script.functions, func->name.text, identifier); +#if LUASL_DIFF_CHECK +// func->params = addParenthesis(open, params, LSL_PARAMETER_LIST, close); +#endif + } + compiler->currentFunction = func; + } + } + + return identifier; +} + +LSL_Leaf *addFunctionBody(LuaSL_compiler *compiler, LSL_Leaf *function, LSL_Leaf *block) +{ + LSL_Leaf *statement = NULL; + + if (function) + { + function->value.functionValue->block = block->value.blockValue; + statement = addStatement(compiler, NULL, function, NULL, function, NULL, NULL, NULL); + } + + return statement; +} + +LSL_Leaf *collectArguments(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *comma, LSL_Leaf *arg) +{ + LSL_FunctionCall *call = NULL; + + if (NULL == list) + list = newLeaf(LSL_FUNCTION_CALL, NULL, NULL); + + if (list) + { + call = list->value.functionCallValue; + if (NULL == call) + { + call = calloc(1, sizeof(LSL_FunctionCall)); + if (call) + { + list->value.functionCallValue = call; + eina_inarray_step_set(&(call->params), sizeof(Eina_Inarray), sizeof(LSL_Leaf), 3); + } + } + + if (call) + { + if (arg) + { + if (LUASL_DIFF_CHECK) + { + // Stash the comma for diff later. + if (comma) + eina_inarray_push(&(call->params), comma); + } + eina_inarray_push(&(call->params), arg); + // At this point, pointers to arg are not pointing to the one in call->params, AND arg is no longer needed. + } + } + } + return list; +} + +LSL_Leaf *addFunctionCall(LuaSL_compiler *compiler, LSL_Leaf *identifier, LSL_Leaf *open, LSL_Leaf *params, LSL_Leaf *close) +{ + LSL_Leaf *func = findFunction(compiler, identifier->value.stringValue); + LSL_FunctionCall *call = NULL; + + if (params) + { + call = params->value.functionCallValue; + } + else + call = calloc(1, sizeof(LSL_FunctionCall)); + + if (func) + { + if (call) + { + call->function = func->value.functionValue; + eina_clist_element_init(&(call->dangler)); + } + identifier->value.functionCallValue = call; + identifier->toKen = tokens[LSL_FUNCTION_CALL - lowestToken]; + identifier->basicType = func->basicType; + } + else + { + // It may be declared later, so store it and check later. + if (call) + { + eina_clist_add_tail(&(compiler->danglingCalls), &(call->dangler)); + call->call = identifier; + } + // Here the identifier stringValue needs to be kept for later searching. + identifier->toKen = tokens[LSL_UNKNOWN - lowestToken]; + identifier->basicType = OT_undeclared; + compiler->undeclared = TRUE; + } + + return identifier; +} + +LSL_Leaf *addList(LSL_Leaf *left, LSL_Leaf *list, LSL_Leaf *right) +{ + left = addParenthesis(left, list, LSL_LIST, right); + left->toKen = tokens[LSL_LIST - lowestToken]; + left->basicType = OT_list; + return left; +} + +LSL_Leaf *addRotVec(LSL_Leaf *left, LSL_Leaf *list, LSL_Leaf *right) +{ + LSL_Type type = LSL_ROTATION; + opType otype = OT_rotation; + + // TODO - count the members of list to see if it's a vector. + left = addParenthesis(left, list, type, right); + left->toKen = tokens[type - lowestToken]; + left->basicType = otype; + return left; +} + +LSL_Leaf *addNumby(LSL_Leaf *numby) +{ + LSL_Numby *num = calloc(1, sizeof(LSL_Numby)); + + if ((numby) && (num)) + { + num->text.text = numby->value.stringValue; +#if LUASL_DIFF_CHECK + num->text.ignorable = numby->ignorable; + numby->ignorable = NULL; +#endif + switch (numby->toKen->type) + { + case LSL_FLOAT : + { + num->value.floatValue = atof(num->text.text); + numby->basicType = OT_float; + break; + } + case LSL_INTEGER : + { + num->value.integerValue = atoi(num->text.text); + numby->basicType = OT_integer; + break; + } + default: + break; + } + numby->value.numbyValue = num; + num->type = numby->basicType; + } + return numby; +} + +LSL_Leaf *addParenthesis(LSL_Leaf *lval, LSL_Leaf *expr, LSL_Type type, LSL_Leaf *rval) +{ + LSL_Parenthesis *parens = calloc(1, sizeof(LSL_Parenthesis)); + + if (parens) + { + parens->contents = expr; + parens->type = type; +#if LUASL_DIFF_CHECK + parens->rightIgnorable = rval->ignorable; + // Actualy, at this point, rval is no longer needed. + rval->ignorable = NULL; +#endif + if (lval) + { + lval->value.parenthesis = parens; + if (expr) + { + lval->basicType = expr->basicType; + // Propagate these flags inwards and outwards. + if (MF_ASSIGNEXP & expr->flags) + lval->flags |= MF_ASSIGNEXP; + if (MF_WRAPFUNC & expr->flags) + parens->flags |= MF_WRAPFUNC; + } + } + } + return lval; +} + +LSL_Leaf *addState(LuaSL_compiler *compiler, LSL_Leaf *state, LSL_Leaf *identifier, LSL_Leaf *block) +{ + LSL_State *result = calloc(1, sizeof(LSL_State)); + + if ((identifier) && (result)) + { + Eina_Iterator *handlers; + LSL_Function *func; + + memcpy(result, &(compiler->state), sizeof(LSL_State)); + compiler->state.block = NULL; + compiler->state.handlers = NULL; + result->name.text = identifier->value.stringValue; +#if LUASL_DIFF_CHECK + result->name.ignorable = identifier->ignorable; + identifier->ignorable = NULL; +#endif + handlers = eina_hash_iterator_data_new(result->handlers); + while(eina_iterator_next(handlers, (void **) &func)) + { + func->state = result->name.text; + } + result->block = block->value.blockValue; + if (state) + { + result->state.text = state->toKen->toKen; +#if LUASL_DIFF_CHECK + result->state.ignorable = state->ignorable; + state->ignorable = NULL; +#endif + } + identifier->value.stateValue = result; + identifier->toKen = tokens[LSL_STATE - lowestToken]; + eina_hash_add(compiler->script.states, result->name.text, identifier); + compiler->inState = FALSE; + } + + return identifier; +} + +LSL_Leaf *addIfElse(LuaSL_compiler *compiler, LSL_Leaf *ifBlock, LSL_Leaf *elseBlock) +{ + if (ifBlock->value.statementValue->elseBlock) + { + LSL_Statement *oldElseIf = ifBlock->value.statementValue->elseBlock; + + while (oldElseIf->elseBlock) + oldElseIf = oldElseIf->elseBlock; + + oldElseIf->elseBlock = elseBlock->value.statementValue; + } + else + ifBlock->value.statementValue->elseBlock = elseBlock->value.statementValue; + return ifBlock; +} + +LSL_Leaf *addFor(LuaSL_compiler *compiler, LSL_Leaf *lval, LSL_Leaf *flow, LSL_Leaf *left, LSL_Leaf *expr0, LSL_Leaf *stat0, LSL_Leaf *expr1, LSL_Leaf *stat1, LSL_Leaf *expr2, LSL_Leaf *right, LSL_Leaf *block) +{ + LSL_Leaf **exprs = calloc(5, sizeof(LSL_Leaf *)); + + if (exprs) + { + lval = addStatement(compiler, lval, flow, left, expr0, right, block, NULL); + exprs[0] = expr0; + exprs[1] = stat0; + exprs[2] = expr1; + exprs[3] = stat1; + exprs[4] = expr2; + lval->value.statementValue->expressions = (LSL_Leaf *) exprs; + } + return lval; +} + +LSL_Leaf *addStatement(LuaSL_compiler *compiler, LSL_Leaf *lval, LSL_Leaf *flow, LSL_Leaf *left, LSL_Leaf *expr, LSL_Leaf *right, LSL_Leaf *block, LSL_Leaf *identifier) +{ + gameGlobals *ourGlobals = compiler->game; + LSL_Statement *stat = calloc(1, sizeof(LSL_Statement)); + boolean justOne = FALSE; + + if (NULL == lval) + lval = newLeaf(LSL_STATEMENT, NULL, NULL); + + if (stat) + { + stat->type = flow->toKen->type; + stat->expressions = expr; + if (block) + { + if (LSL_BLOCK_OPEN == block->toKen->type) + stat->block = block->value.blockValue; + else + stat->single = block->value.statementValue; + } + eina_clist_element_init(&(stat->statement)); + if (identifier) + { + stat->identifier.text = identifier->value.stringValue; +#if LUASL_DIFF_CHECK + stat->identifier.ignorable = identifier->ignorable; + identifier->ignorable = NULL; +#endif + } + if (left) + { + LSL_Leaf *parens = addParenthesis(left, expr, LSL_EXPRESSION, right); + + if (parens) + stat->parenthesis = parens->value.parenthesis; + } + + switch (stat->type) + { + case LSL_EXPRESSION : + { + break; + } + case LSL_FUNCTION : + { + break; + } + case LSL_DO : + { + break; + } + case LSL_FOR : + { + justOne = TRUE; + break; + } + case LSL_IF : + { + justOne = TRUE; + break; + } + case LSL_ELSE : + { + justOne = TRUE; + break; + } + case LSL_ELSEIF : + { + justOne = TRUE; + break; + } + case LSL_JUMP : + { + justOne = TRUE; + break; + } + case LSL_LABEL : + { + justOne = TRUE; + break; + } + case LSL_RETURN : + { + justOne = TRUE; + break; + } + case LSL_STATE_CHANGE : + { + justOne = TRUE; + break; + } + case LSL_STATEMENT : + { + break; + } + case LSL_WHILE : + { + stat->identifier.text = NULL; + justOne = TRUE; + break; + } + case LSL_IDENTIFIER : + { + break; + } + case LSL_VARIABLE : + { + if (identifier) + { + stat->identifier.text = identifier->value.identifierValue->name.text; + identifier->value.identifierValue->definition = stat; + stat->flags = identifier->value.identifierValue->flags; + } + break; + } + default : + { + compiler->script.bugCount++; + PE("Should not be here %d.", stat->type); + break; + } + } + +#if LUASL_DIFF_CHECK + if (justOne && (flow)) + { + stat->ignorable = calloc(2, sizeof(Eina_Strbuf *)); + if (stat->ignorable) + { + stat->ignorable[1] = flow->ignorable; + flow->ignorable = NULL; + } + } +#endif + + if (lval) + { +#if LUASL_DIFF_CHECK + if (NULL == stat->ignorable) + stat->ignorable = calloc(1, sizeof(Eina_Strbuf *)); + if (stat->ignorable) + { + stat->ignorable[0] = lval->ignorable; + lval->ignorable = NULL; + } +#endif + lval->value.statementValue = stat; + } + +#if LUASL_DIFF_CHECK + if (left) + { + if (NULL == stat->ignorable) + stat->ignorable = calloc(3, sizeof(Eina_Strbuf *)); + else + stat->ignorable = realloc(stat->ignorable, 3 * sizeof(Eina_Strbuf *)); + if (stat->ignorable) + { + stat->ignorable[2] = left->ignorable; + left->ignorable = NULL; + } + } +#endif + } + + return lval; +} + +LSL_Leaf *collectStatements(LuaSL_compiler *compiler, LSL_Leaf *list, LSL_Leaf *statement) +{ +// Guess this is not needed after all, and seemed to cause the "states with only one function get dropped" bug. +// boolean wasNull = FALSE; + + if (NULL == list) + { + list = newLeaf(LSL_BLOCK_OPEN, NULL, NULL); +// wasNull = TRUE; + } + + if (list) + { + if (statement) + { +// if (!wasNull) + list->value.blockValue = compiler->currentBlock; // Maybe NULL. + + if ((compiler->inState) && (LSL_FUNCTION == statement->value.statementValue->type)) + { + eina_clist_add_tail(&(compiler->state.block->statements), &(statement->value.statementValue->statement)); + } + else if (list->value.blockValue) + { + eina_clist_add_tail(&(list->value.blockValue->statements), &(statement->value.statementValue->statement)); + } + } + } + + return list; +} + +/* Typecasting + +LSL is statically typed, so stored values are not converted, only the values used in expressions are. +Lua is dynamically typed, so stored values are changed (sometimes I think). + +LSL implicitly typecasts - There is a shitload of QUIRKs about this. Apparently some don't work anyway. + integer -> float (Says in lslwiki that precision is never lost, which is bullshit, since they are both 32 bit. Would be true if the float is 64 bit. Lua suggest to use 64 bit floats to emulate 32 bit integers.) + string -> key + Some functions need help with this or the other way around. + string -> vector (Maybe, should test that.) + vector -> string (Maybe, should test that.) + Also happens when getting stuff from lists. + +Explicit type casting - + string -> integer + Leading spaces are ignored, as are any characters after the run of digits. + All other strings convert to 0. + Which means "" and " " convert to 0. + Strings in hexadecimal format will work, same in Lua (though Lua can't handle "0x", but "0x0" is fine). + keys <-> string + No other typecasting can be done with keys. + float -> string + You get a bunch of trailing 0s. + +QUIRK - I have seen cases where a double explicit typecast was needed in SL, but was considered to be invalid syntax in OS. + +Any binary operation involving a float and an integer implicitly casts the integer to float. + +A boolean operation deals with TRUE (1) and FALSE (0). Any non zero value is a TRUE (generally sigh). +On the other hand, in Lua, only false and nil are false, everything else is true. 0 is true. sigh +Bitwise operations only apply to integers. Right shifts are arithmetic, not logical. + +integer = integer0 % integer1; // Apparently only applies to integers, but works fine on floats in OS. +string = string0 + string1; // Concatenation. +list = list0 + list1; // Concatenation. Also works if either is not a list, it's promoted to a list first. +list = (list=[]) + list + ["new_item"]; // Voodoo needed for old LSL, works in Mono but not needed, does not work in OS. Works for strings to. +bool = list == != int // Only compares the lengths, probably applies to the other conditionals to. +vector = vector0 + vector1; // Add elements together. +vector = vector0 - vector1; // Subtract elements of vector1 from elements of vector0. +float = vector0 * vector1; // A dot product of the vectors. +vector = vector0 % vector1; // A cross product of the vectors. +vector = vector * float; // Scale the vector, works the other way around I think. Works for integer to, but it will end up being cast to float. +vector = vector / float; // Scale the vector, works the other way around I think. Works for integer to, but it will end up being cast to float. +vector = vector * rotation; // Rotate the vector by the rotation. Other way around wont compile. +vector = vector / rotation; // Rotate the vector by the rotation, in the opposite direction. Other way around wont compile. +rotation = llGetRot() * rotation; // Rotate an object around the global axis. +rotation = rotation * llGetLocalRot(); // Rotate an object around the local axis. +rotation = rotation0 * rotation1; // Add two rotations, so the result is as if you applied each rotation one after the other. + // Division rotates in the opposite direction. +rotation = rotation0 + rotation1; // Similar to vector, but it's a meaningless thing as far as rotations go. +rotation = rotation0 - rotation1; // Similar to vector, but it's a meaningless thing as far as rotations go. + +A boolean operator results in a boolean value. (any types) +A comparison operator results in a boolean value. (any types) +A bitwise operator results in an integer value. (intInt or int) +A dot product operator results in a float value. (vector * vector) +A vectorFloat results in a vector value. + +*/ + +LSL_Leaf *addTypecast(LSL_Leaf *lval, LSL_Leaf *type, LSL_Leaf *rval, LSL_Leaf *expr) +{ + addParenthesis(lval, expr, LSL_TYPECAST_OPEN, rval); + if (lval) + { + if (type) + { + lval->basicType = type->basicType; + if ((expr) && (OT_integer == type->basicType)) // TODO - Should be from string, but I guess I'm not propagating basic types up from function calls and parenthesis? + lval->value.parenthesis->flags |= MF_TYPECAST; + } + // Actualy, at this point, type is no longer needed. + lval->toKen = tokens[LSL_TYPECAST_OPEN - lowestToken]; + } +// if (rval) +// rval->toKen = tokens[LSL_TYPECAST_CLOSE - lowestToken]; + + return lval; +} + +LSL_Leaf *addVariable(LuaSL_compiler *compiler, LSL_Leaf *type, LSL_Leaf *identifier, LSL_Leaf *assignment, LSL_Leaf *expr) +{ + LSL_Identifier *result = calloc(1, sizeof(LSL_Identifier)); + + if ( (identifier) && (result)) + { + result->name.text = identifier->value.stringValue; +#if LUASL_DIFF_CHECK + result->name.ignorable = identifier->ignorable; + identifier->ignorable = NULL; +#endif + result->value.toKen = tokens[LSL_UNKNOWN - lowestToken]; + identifier->value.identifierValue = result; + identifier->toKen = tokens[LSL_VARIABLE - lowestToken]; + identifier->left = type; + identifier->right = assignment; + if (assignment) + assignment->right = expr; + else + identifier->flags |= MF_NOASSIGN; + if (type) + { + if (compiler->currentBlock) + { + identifier->flags |= MF_LOCAL; + result->flags |= MF_LOCAL; + type->flags |= MF_LOCAL; + } + identifier->basicType = type->basicType; + result->value.basicType = type->basicType; + result->value.toKen = type->toKen; // This is the LSL_TYPE_* toKen instead of the LSL_* toKen. Not sure if that's a problem. + } + if (compiler->currentBlock) + eina_hash_add(compiler->currentBlock->variables, result->name.text, identifier); + else + eina_hash_add(compiler->script.variables, result->name.text, identifier); + } + + return identifier; +} + +LSL_Leaf *beginBlock(LuaSL_compiler *compiler, LSL_Leaf *block) +{ + LSL_Block *blok = calloc(1, sizeof(LSL_Block)); + + if (blok) + { + eina_clist_init(&(blok->statements)); + blok->variables = eina_hash_stringshared_new(burnLeaf); + block->value.blockValue = blok; + if ((NULL == compiler->currentBlock) && (NULL == compiler->currentFunction)) + { + compiler->inState = TRUE; + compiler->state.block=blok; + compiler->state.handlers = eina_hash_stringshared_new(free); + } + blok->outerBlock = compiler->currentBlock; + compiler->currentBlock = blok; + blok->function = compiler->currentFunction; + compiler->currentFunction = NULL; +#if LUASL_DIFF_CHECK + blok->openIgnorable = block->ignorable; + block->ignorable = NULL; +#endif + } + return block; +} + +static void secondPass(LuaSL_compiler *compiler, LSL_Leaf *leaf) +{ + if (leaf) + { + secondPass(compiler, leaf->left); + if (OT_undeclared == leaf->basicType) + leaf = addOperation(compiler, leaf->left, leaf, leaf->right); + secondPass(compiler, leaf->right); + } +} + +static void outputLeaf(FILE *file, outputMode mode, LSL_Leaf *leaf) +{ + if (leaf) + { + if ((OM_LUA == mode) &&(ST_BITWISE != leaf->toKen->subType)) + outputLeaf(file, mode, leaf->left); +#if LUASL_DIFF_CHECK + if ((!(LSL_NOIGNORE & leaf->toKen->flags)) && (leaf->ignorable)) + fwrite(eina_strbuf_string_get(leaf->ignorable), 1, eina_strbuf_length_get(leaf->ignorable), file); +#endif + if (leaf->toKen->output) + leaf->toKen->output(file, mode, leaf); + else + { + if (OM_LUA == mode) + { + if (MF_WRAPFUNC & leaf->flags) + { +// TODO - Leaving this here in case we trip over one. +if ((leaf->left) && (leaf->right)) + printf("%s %s %s\n", leaf->left->toKen->toKen, leaf->toKen->toKen, leaf->right->toKen->toKen); +else if (leaf->left) + printf("%s %s NORIGHT\n", leaf->left->toKen->toKen, leaf->toKen->toKen); +else if (leaf->right) + printf("NOLEFT %s %s\n", leaf->toKen->toKen, leaf->right->toKen->toKen); +else + printf("NOLEFT %s NORIGHT\n", leaf->toKen->toKen); + } + if ((LSL_ASSIGNMENT & leaf->toKen->flags) && (LSL_ASSIGNMENT_PLAIN != leaf->toKen->type)) + { + if (leaf->left->value.identifierValue->sub) + fprintf(file, " --[[%s]] = %s.%s %.1s ", leaf->toKen->toKen, leaf->left->value.identifierValue->name.text, leaf->left->value.identifierValue->sub, leaf->toKen->toKen); + else + fprintf(file, " --[[%s]] = %s %.1s ", leaf->toKen->toKen, leaf->left->value.identifierValue->name.text, leaf->toKen->toKen); + } + else if (LSL_TYPE & leaf->toKen->flags) + { + if (MF_LOCAL & leaf->flags) + fprintf(file, " local "); + fprintf(file, " --[[%s]] ", leaf->toKen->toKen); + } + else if (LSL_BOOL_AND == leaf->toKen->type) + fprintf(file, " and "); + else if (LSL_BOOL_OR == leaf->toKen->type) + fprintf(file, " or "); + else if (LSL_BOOL_NOT == leaf->toKen->type) + fprintf(file, " not "); + else if (LSL_CONCATENATE == leaf->toKen->type) + fprintf(file, " .. "); + else if (LSL_NOT_EQUAL == leaf->toKen->type) + fprintf(file, " ~= "); + else + fprintf(file, "%s", leaf->toKen->toKen); + } + else + fprintf(file, "%s", leaf->toKen->toKen); + } + if ((OM_LUA == mode) &&(ST_BITWISE != leaf->toKen->subType)) + outputLeaf(file, mode, leaf->right); + } +} + +// Circular references, so declare this one first. +static void outputRawStatement(FILE *file, outputMode mode, LSL_Statement *statement); + +static void outputRawBlock(FILE *file, outputMode mode, LSL_Block *block, boolean doEnd) +{ + if (block) + { + LSL_Statement *stat = NULL; + +#if LUASL_DIFF_CHECK + if (block->openIgnorable) + fwrite(eina_strbuf_string_get(block->openIgnorable), 1, eina_strbuf_length_get(block->openIgnorable), file); + if (OM_LSL == mode) + fprintf(file, "{"); +#else + if (OM_LSL == mode) + fprintf(file, "\n{\n"); + else if (doEnd && (OM_LUA == mode)) + fprintf(file, "\n"); +#endif + EINA_CLIST_FOR_EACH_ENTRY(stat, &(block->statements), LSL_Statement, statement) + { + outputRawStatement(file, mode, stat); + } +#if LUASL_DIFF_CHECK + if (block->closeIgnorable) + fwrite(eina_strbuf_string_get(block->closeIgnorable), 1, eina_strbuf_length_get(block->closeIgnorable), file); +#endif + if (OM_LSL == mode) + fprintf(file, "}"); + else if (doEnd && (OM_LUA == mode)) + fprintf(file, "end "); + } +} + +// TODO - should clean this up by refactoring the bits in the switch outside. +static void outputRawParenthesisToken(FILE *file, outputMode mode, LSL_Parenthesis *parenthesis, const char *typeName) +{ + if ((OM_LUA == mode) && (LSL_TYPECAST_OPEN == parenthesis->type)) + { + if (MF_TYPECAST & parenthesis->flags) + fprintf(file, " _LSL.%sTypecast(", typeName); + else + fprintf(file, " --[[%s]] ", typeName); + outputLeaf(file, mode, parenthesis->contents); + if (MF_TYPECAST & parenthesis->flags) + fprintf(file, ") "); + return; + } + + if ((OM_LUA == mode) && (MF_WRAPFUNC & parenthesis->flags)) + fprintf(file, " (function() "); + else + fprintf(file, "("); + if (LSL_TYPECAST_OPEN == parenthesis->type) + fprintf(file, "%s", typeName); // TODO - We are missing the type ignorable text here. + else + outputLeaf(file, mode, parenthesis->contents); + if ((OM_LUA == mode) && (MF_WRAPFUNC & parenthesis->flags)) + fprintf(file, "; return x; end)() "); + else + { +#if LUASL_DIFF_CHECK + fprintf(file, "%s)", eina_strbuf_string_get(parenthesis->rightIgnorable)); +#else + fprintf(file, ")"); +#endif + } + + if (LSL_TYPECAST_OPEN == parenthesis->type) + outputLeaf(file, mode, parenthesis->contents); +} + +static void outputText(FILE *file, LSL_Text *text, boolean ignore) +{ + if (text->text) + { +#if LUASL_DIFF_CHECK + if (ignore && (text->ignorable)) + fwrite(eina_strbuf_string_get(text->ignorable), 1, eina_strbuf_length_get(text->ignorable), file); +#endif + fprintf(file, "%s", text->text); + } +} + +static void outputRawStatement(FILE *file, outputMode mode, LSL_Statement *statement) +{ + boolean isBlock = FALSE; + + if (statement) + { + switch (statement->type) + { + case LSL_EXPRESSION : + { + break; + } + case LSL_FUNCTION : + { + isBlock = TRUE; + break; + } + case LSL_DO : + { + fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); + break; + } + case LSL_FOR : + { +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[1])) + fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); +#endif + if (OM_LSL == mode) + { + isBlock = TRUE; + fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); + } + else if (OM_LUA == mode) + { + LSL_Leaf **exprs = (LSL_Leaf **) statement->expressions; + + outputLeaf(file, mode, exprs[0]); + fprintf(file, ";\nwhile ("); + outputLeaf(file, mode, exprs[2]); +#if LUASL_DIFF_CHECK + fprintf(file, "%s)\n", eina_strbuf_string_get(statement->parenthesis->rightIgnorable)); +#else + fprintf(file, ") do\n"); +#endif + if (statement->block) + outputRawBlock(file, mode, statement->block, FALSE); + if (statement->single) + outputRawStatement(file, mode, statement->single); + fprintf(file, "\n"); + outputLeaf(file, mode, exprs[4]); + fprintf(file, ";\nend\n"); + return; + } + break; + } + case LSL_IF : + case LSL_ELSE : + case LSL_ELSEIF : + { + isBlock = TRUE; +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[1])) + fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); +#endif + fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); + if (OM_LUA == mode) + { + fprintf(file, " "); + if (LSL_ELSE != statement->type) + { + if (statement->parenthesis) + outputRawParenthesisToken(file, mode, statement->parenthesis, ""); + else + outputLeaf(file, mode, statement->expressions); + fprintf(file, " then\n"); + } + if (statement->block) + outputRawBlock(file, mode, statement->block, FALSE); + if (statement->single) + outputRawStatement(file, mode, statement->single); + if (statement->elseBlock) + outputRawStatement(file, mode, statement->elseBlock); + if (LSL_IF == statement->type) + { +#if 1 + fprintf(file, " end\n"); +#else + fprintf(file, " end --[["); + if (statement->parenthesis) + outputRawParenthesisToken(file, mode, statement->parenthesis, ""); + else + outputLeaf(file, mode, statement->expressions); + fprintf(file, "]]\n"); +#endif + } + return; + } + break; + } + case LSL_JUMP : + { +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[1])) + fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); +#endif + fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); + break; + } + case LSL_LABEL : + { +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[1])) + fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); +#endif + fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); + break; + } + case LSL_RETURN : + { +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[1])) + fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); +#endif + fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); + break; + } + case LSL_STATE_CHANGE : + { +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[1])) + fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); +#endif + if (OM_LSL == mode) + { + fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); + if (statement->identifier.text) + outputText(file, &(statement->identifier), TRUE); + } + else if (OM_LUA == mode) + { + fprintf(file, "return _LSL.stateChange(_"); + if (statement->identifier.text) + outputText(file, &(statement->identifier), TRUE); + fprintf(file, "State)"); + } + break; + } + case LSL_STATEMENT : + { + break; + } + case LSL_WHILE : + { + isBlock = TRUE; +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[1])) + fwrite(eina_strbuf_string_get(statement->ignorable[1]), 1, eina_strbuf_length_get(statement->ignorable[1]), file); +#else + if (OM_LUA == mode) + fprintf(file, "\n"); +#endif + fprintf(file, "%s", tokens[statement->type - lowestToken]->toKen); + if (OM_LUA == mode) + { + if (statement->parenthesis) + outputRawParenthesisToken(file, mode, statement->parenthesis, ""); + fprintf(file, " do "); + if (statement->block) + outputRawBlock(file, mode, statement->block, TRUE); + if (statement->single) + outputRawStatement(file, mode, statement->single); + fprintf(file, "\n"); + return; + } + break; + } + case LSL_IDENTIFIER : + { + break; + } + case LSL_VARIABLE : + { + break; + } + default : + { + fprintf(file, "@@Should not be here %s.@@", tokens[statement->type - lowestToken]->toKen); + break; + } + } + +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[2])) + fwrite(eina_strbuf_string_get(statement->ignorable[2]), 1, eina_strbuf_length_get(statement->ignorable[2]), file); +#else + if (OM_LUA == mode) + fprintf(file, " "); +#endif + if (LSL_FOR == statement->type) + { + LSL_Leaf **exprs = (LSL_Leaf **) statement->expressions; + int i; + + fprintf(file, "("); + for (i = 0; i < 5; i++) + { + outputLeaf(file, mode, exprs[i]); + if (i % 2) + fprintf(file, ";"); + } +#if LUASL_DIFF_CHECK + fprintf(file, "%s)", eina_strbuf_string_get(statement->parenthesis->rightIgnorable)); +#else + fprintf(file, ")"); +#endif + } + else if (statement->parenthesis) + outputRawParenthesisToken(file, mode, statement->parenthesis, ""); + else + outputLeaf(file, mode, statement->expressions); + + if (statement->block) + outputRawBlock(file, mode, statement->block, TRUE); + if (statement->single) + outputRawStatement(file, mode, statement->single); + +#if LUASL_DIFF_CHECK + if ((statement->ignorable) && (statement->ignorable[0])) + fwrite(eina_strbuf_string_get(statement->ignorable[0]), 1, eina_strbuf_length_get(statement->ignorable[0]), file); +#endif + + if (!isBlock) + { + fprintf(file, ";"); + if (!LUASL_DIFF_CHECK) + fprintf(file, "\n"); + } + + if ((LSL_VARIABLE == statement->type) && (OM_LUA == mode) && (MF_LOCAL & statement->flags)) + { + const char *name = statement->identifier.text; + +// if ((MF_PREDEC | MF_PREINC | MF_POSTDEC | MF_POSTINC) & statement->flags) +// fprintf(file, "\n"); + if (MF_PREDEC & statement->flags) fprintf(file, "local function _preDecrement_%s() %s = %s - 1; return %s; end\n", name, name, name, name); + if (MF_PREINC & statement->flags) fprintf(file, "local function _preIncrement_%s() %s = %s + 1; return %s; end\n", name, name, name, name); + if (MF_POSTDEC & statement->flags) fprintf(file, "local function _postDecrement_%s() local _temp = %s; %s = %s - 1; return _temp; end\n", name, name, name, name); + if (MF_POSTINC & statement->flags) fprintf(file, "local function _postDecrement_%s() local _temp = %s; %s = %s + 1; return _temp; end\n", name, name, name, name); + } + + if (statement->elseBlock) + outputRawStatement(file, mode, statement->elseBlock); + } +} + +static void outputBitOp(FILE *file, outputMode mode, LSL_Leaf *leaf) +{ + if (OM_LSL == mode) + outputLeaf(file, mode, leaf); + else if (OM_LUA == mode) + { + switch (leaf->toKen->type) + { + case LSL_BIT_AND : fprintf(file, " _bit.band("); break; + case LSL_BIT_OR : fprintf(file, " _bit.bor("); break; + case LSL_BIT_XOR : fprintf(file, " _bit.xor("); break; + case LSL_BIT_NOT : fprintf(file, " _bit.bnot("); break; + case LSL_LEFT_SHIFT : fprintf(file, " _bit.lshift("); break; + case LSL_RIGHT_SHIFT : fprintf(file, " _bit.arshift("); break; + default : break; + } + outputLeaf(file, mode, leaf->left); + if (LSL_BIT_NOT != leaf->toKen->type) + { + fprintf(file, ", "); + outputLeaf(file, mode, leaf->right); + } + fprintf(file, ") "); + } +} + +static void outputBlockToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + outputRawBlock(file, mode, content->value.blockValue, TRUE); +} + +static void outputCrementsToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + { + if (OM_LSL == mode) + { + switch (content->toKen->type) + { + case LSL_DECREMENT_PRE : + case LSL_INCREMENT_PRE : + { + fprintf(file, "%s", content->toKen->toKen); +#if LUASL_DIFF_CHECK + if (content->value.identifierValue->ignorable) + fwrite(eina_strbuf_string_get(content->value.identifierValue->ignorable), 1, eina_strbuf_length_get(content->value.identifierValue->ignorable), file); +#endif + outputText(file, &(content->value.identifierValue->name), FALSE); + break; + } + case LSL_DECREMENT_POST : + case LSL_INCREMENT_POST : + { +#if LUASL_DIFF_CHECK + if (content->value.identifierValue->ignorable) + fwrite(eina_strbuf_string_get(content->value.identifierValue->ignorable), 1, eina_strbuf_length_get(content->value.identifierValue->ignorable), file); +#endif + outputText(file, &(content->value.identifierValue->name), FALSE); + fprintf(file, "%s", content->toKen->toKen); + break; + } + default : + break; + } + } + else if (OM_LUA == mode) + { + if (MF_LOCAL & content->value.identifierValue->flags) + fprintf(file, " _"); + else + fprintf(file, " _LSL."); + switch (content->toKen->type) + { + case LSL_DECREMENT_PRE : fprintf(file, "preDecrement"); break; + case LSL_INCREMENT_PRE : fprintf(file, "preIncrement"); break; + case LSL_DECREMENT_POST : fprintf(file, "postDecrement"); break; + case LSL_INCREMENT_POST : fprintf(file, "postIncrement"); break; + default : + break; + } + if (MF_LOCAL & content->value.identifierValue->flags) + fprintf(file, "_"); + else + fprintf(file, "(\""); +#if LUASL_DIFF_CHECK + if (content->value.identifierValue->ignorable) + fwrite(eina_strbuf_string_get(content->value.identifierValue->ignorable), 1, eina_strbuf_length_get(content->value.identifierValue->ignorable), file); +#endif + outputText(file, &(content->value.identifierValue->name), FALSE); + if (MF_LOCAL & content->value.identifierValue->flags) + fprintf(file, "()"); + else + fprintf(file, "\")"); + } + } +} + +static void outputFloatToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + outputText(file, &(content->value.numbyValue->text), !(LSL_NOIGNORE & content->toKen->flags)); +} + +static void outputFunctionToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + { + LSL_Function *func = content->value.functionValue; + LSL_Leaf *param = NULL; + int first = TRUE; + + if (OM_LSL == mode) + { + outputText(file, &(func->type), !(LSL_NOIGNORE & content->toKen->flags)); + outputText(file, &(func->name), !(LSL_NOIGNORE & content->toKen->flags)); + // TODO - should print comma and parenthesis ignorables. + fprintf(file, "("); + EINA_INARRAY_FOREACH((&(func->vars)), param) + { + if (!LUASL_DIFF_CHECK) + { + if (!first) + fprintf(file, ", "); + } + outputLeaf(file, mode, param); + first = FALSE; + } + fprintf(file, ")"); + outputRawBlock(file, mode, func->block, TRUE); + if (!LUASL_DIFF_CHECK) + fprintf(file, "\n"); + } + else if (OM_LUA == mode) + { + if (func->state) + fprintf(file, "\n\n_%sState.%s = function(", func->state, func->name.text); + else + { + fprintf(file, "\n\nfunction "); + if (func->type.text) + fprintf(file, " --[[%s]] ", func->type.text); + fprintf(file, "%s(", func->name.text); + } + EINA_INARRAY_FOREACH((&(func->vars)), param) + { + // Commenting out the types is done in outputLeaf() which outputs all the types. + if (!LUASL_DIFF_CHECK) + { + if (!first) + fprintf(file, ", "); + } + outputLeaf(file, mode, param); + first = FALSE; + } + fprintf(file, ")"); + outputRawBlock(file, mode, func->block, TRUE); + } + } +} + +static void outputFunctionCallToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + { + LSL_FunctionCall *call = content->value.functionCallValue; + LSL_Leaf *param = NULL; + boolean first = TRUE; + + // TODO - should output it's own ignorable here. + if ((OM_LUA == mode) && (MF_LSLCONST & call->function->flags)) + fprintf(file, "_LSL."); + outputText(file, &(call->function->name), FALSE); // Don't output the function definitions ignorable. + fprintf(file, "("); + EINA_INARRAY_FOREACH((&(call->params)), param) + { + if ((OM_LUA == mode) && (!first)) + fprintf(file, ", "); + outputLeaf(file, mode, param); + first = FALSE; + } + fprintf(file, ")"); + } +} + +static void outputIntegerToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + outputText(file, &(content->value.numbyValue->text), !(LSL_NOIGNORE & content->toKen->flags)); +} + +static void outputIdentifierToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + { + if (LSL_IDENTIFIER == content->toKen->type) + { + if ((OM_LUA == mode) && (MF_LSLCONST & content->value.identifierValue->flags)) + fprintf(file, "_LSL."); + outputText(file, &(content->value.identifierValue->name), FALSE); + if (content->value.identifierValue->sub) + fprintf(file, ".%s", content->value.identifierValue->sub); + } + else + if ((LSL_VARIABLE == content->toKen->type) && (MF_NOASSIGN & content->flags)) + { + outputText(file, &(content->value.identifierValue->name), !(LSL_NOIGNORE & content->toKen->flags)); + if (OM_LUA == mode) + { + switch (content->basicType) + { + case OT_bool : fprintf(file, " = false"); break; + case OT_integer : fprintf(file, " = 0"); break; + case OT_float : fprintf(file, " = 0.0"); break; + case OT_key : fprintf(file, " = _LSL.NULL_KEY"); break; + case OT_list : fprintf(file, " = {}"); break; + case OT_rotation : fprintf(file, " = _LSL.ZERO_ROTATION"); break; + case OT_string : fprintf(file, " = \"\""); break; + case OT_vector : fprintf(file, " = _LSL.ZERO_VECTOR"); break; + default : fprintf(file, " = nil"); break; + } + } + } + else + outputText(file, &(content->value.identifierValue->name), !(LSL_NOIGNORE & content->toKen->flags)); + } +} + +static void outputListToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + { + LSL_Parenthesis *parens = content->value.parenthesis; + + if (parens->contents) + { + LSL_FunctionCall *call = parens->contents->value.functionCallValue; + LSL_Leaf *param = NULL; + const char *ig = ""; + + // TODO - should output it's own ignorable here. + if (OM_LSL == mode) + { + switch (parens->type) + { + case LSL_LIST : fprintf(file, "["); break; + case LSL_ROTATION : + case LSL_VECTOR : fprintf(file, "<"); + default : break; + } + } + else if (OM_LUA == mode) + { + switch (parens->type) + { + case LSL_LIST : fprintf(file, "{"); break; + case LSL_ROTATION : + case LSL_VECTOR : fprintf(file, "{"); + default : break; + } + } + EINA_INARRAY_FOREACH((&(call->params)), param) + { + outputLeaf(file, mode, param); + if (OM_LUA == mode) + fprintf(file, ", "); + } +#if LUASL_DIFF_CHECK + ig = eina_strbuf_string_get(parens->rightIgnorable); +#endif + if (OM_LSL == mode) + { + switch (parens->type) + { + case LSL_LIST : fprintf(file, "%s]", ig); break; + case LSL_ROTATION : + case LSL_VECTOR : fprintf(file, "%s>", ig); + default : break; + } + } + else if (OM_LUA == mode) + { + switch (parens->type) + { + case LSL_LIST : fprintf(file, "%s}", ig); break; + case LSL_ROTATION : + case LSL_VECTOR : fprintf(file, "%s}", ig); + default : break; + } + } + } + } +} + +static void outputParameterListToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + outputLeaf(file, mode, content->value.listValue); + // TODO - Should go through the list, and output any crements functions we need at the top of the block. +} + +static void outputParenthesisToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + outputRawParenthesisToken(file, mode, content->value.parenthesis, allowed[content->basicType].name); +} + +static void outputStateToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + { + LSL_State *state = content->value.stateValue; + + if (state) + { + if (OM_LSL == mode) + { + outputText(file, &(state->state), !(LSL_NOIGNORE & content->toKen->flags)); + outputText(file, &(state->name), !(LSL_NOIGNORE & content->toKen->flags)); + outputRawBlock(file, mode, state->block, TRUE); + } + else if (OM_LUA == mode) + { + fprintf(file, "\n\n--[[state]] _"); + outputText(file, &(state->name), !(LSL_NOIGNORE & content->toKen->flags)); + fprintf(file, "State = {};"); + outputRawBlock(file, mode, state->block, FALSE); + } + } + } +} + +static void outputStatementToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + outputRawStatement(file, mode, content->value.statementValue); +} + +static void outputStringToken(FILE *file, outputMode mode, LSL_Leaf *content) +{ + if (content) + fprintf(file, "%s", content->value.stringValue); // The quotes are part of the string value already. +} + +boolean compilerSetup(gameGlobals *ourGlobals) +{ + int i; + + // Figure out what numbers lemon gave to our tokens. + for (i = 0; LSL_Tokens[i].toKen != NULL; i++) + { + if (lowestToken > LSL_Tokens[i].type) + lowestToken = LSL_Tokens[i].type; + } + tokens = calloc(i + 1, sizeof(LSL_Token *)); + if (tokens) + { + char buf[PATH_MAX]; + + // Sort the token table. + for (i = 0; LSL_Tokens[i].toKen != NULL; i++) + { + int j = LSL_Tokens[i].type - lowestToken; + + tokens[j] = &(LSL_Tokens[i]); + } + + // Compile the constants. + snprintf(buf, sizeof(buf), "lua -e 'require(\"LSL\").gimmeLSL()' > %s/libraries/constants.lsl", PACKAGE_DATA_DIR); + system(buf); + snprintf(buf, sizeof(buf), "%s/libraries/constants.lsl", PACKAGE_DATA_DIR); + compileLSL(ourGlobals, NULL, "FAKE_SID", buf, TRUE); + + return TRUE; + } + else + PC("No memory for tokens!"); + + return FALSE; +} + +static int luaWriter(lua_State *L, const void* p, size_t sz, void* ud) +{ + FILE *out = ud; + int result = 0; + + if (sz != fwrite(p, 1, sz, out)) + result = -1; + return result; +} + +boolean compileLSL(gameGlobals *ourGlobals, Ecore_Con_Client *client, char *SID, char *script, boolean doConstants) +{ + boolean result = FALSE; + LuaSL_compiler compiler; + void *pParser = ParseAlloc(malloc); + int yv; + +// Parse the LSL script, validating it and reporting errors. +// Just pass all LSL constants and ll*() )function names through to Lua, assume they are globals there. + + memset(&compiler, 0, sizeof(LuaSL_compiler)); + compiler.game = ourGlobals; + compiler.client = client; + compiler.script.functions = eina_hash_stringshared_new(burnLeaf); + compiler.script.states = eina_hash_stringshared_new(burnLeaf); + compiler.script.variables = eina_hash_stringshared_new(burnLeaf); + eina_clist_init(&(compiler.danglingCalls)); +#if LUASL_DIFF_CHECK + compiler.ignorable = eina_strbuf_new(); +#endif + + strncpy(compiler.SID, SID, 36); + compiler.SID[36] = '\0'; + strncpy(compiler.fileName, script, PATH_MAX - 1); + compiler.fileName[PATH_MAX - 1] = '\0'; + compiler.file = fopen(compiler.fileName, "r"); + if (NULL == compiler.file) + { + PE("Error opening file %s.", compiler.fileName); + return FALSE; + } + compiler.ast = NULL; + compiler.lval = newLeaf(LSL_UNKNOWN, NULL, NULL); + // Text editors usually start counting at 1, even programmers editors. mcedit is an exception, but you can deal with that yourself. + compiler.column = 1; + compiler.line = 1; + + if (yylex_init_extra(&compiler, &(compiler.scanner))) + return result; + if (LUASL_DEBUG) + { + yyset_debug(1, compiler.scanner); + ParseTrace(stdout, "LSL_lemon "); + } + yyset_in(compiler.file, compiler.scanner); + // on EOF yylex will return 0 + while((yv = yylex(compiler.lval, compiler.scanner)) != 0) + { + Parse(pParser, yv, compiler.lval, &compiler); + if (LSL_SCRIPT == yv) + break; + compiler.lval = newLeaf(LSL_UNKNOWN, NULL, NULL); + } + + yylex_destroy(compiler.scanner); + Parse (pParser, 0, compiler.lval, &compiler); + ParseFree(pParser, free); + + if (compiler.undeclared) + { +// PW("A second pass is needed to check if functions where used before they where declared. To avoid this second pass, don't do that."); + if (eina_clist_count(&(compiler.danglingCalls))) + { + LSL_FunctionCall *call = NULL; + + EINA_CLIST_FOR_EACH_ENTRY(call, &(compiler.danglingCalls), LSL_FunctionCall, dangler) + { + LSL_Leaf *func = findFunction(&(compiler), call->call->value.stringValue); + + if (func) + { + call->function = func->value.functionValue; + // Coz the leaf still had the stringValue from before. + call->call->value.functionCallValue = call; + call->call->toKen = tokens[LSL_FUNCTION_CALL - lowestToken]; + call->call->basicType = func->basicType; + } + else + sendBack(ourGlobals, compiler.client, compiler.SID, "compilerError(%d,%d,Undeclared function %s called)", call->call->line, call->call->column, call->call->value.stringValue); + } + } + secondPass(&compiler, compiler.ast); +// PD("Second pass completed."); + } + + if (doConstants) + { + memcpy(&constants, &(compiler.script), sizeof(LSL_Script)); + result = TRUE; + } + else + { + result = FALSE; + + if (compiler.ast) + { + FILE *out; + char buffer[PATH_MAX]; + char outName[PATH_MAX]; + char luaName[PATH_MAX]; + int count; + + if (LUASL_DIFF_CHECK) + { + strcpy(outName, compiler.fileName); + strcat(outName, "2"); + out = fopen(outName, "w"); + if (out) + { + char diffName[PATH_MAX]; + + strcpy(diffName, compiler.fileName); + strcat(diffName, ".diff"); + outputLeaf(out, OM_LSL, compiler.ast); + fclose(out); + sprintf(buffer, "diff -u \"%s\" \"%s\" > \"%s\"", compiler.fileName, outName, diffName); + count = system(buffer); + if (0 != count) + PE("LSL output file is different - %s!", outName); + else + result = TRUE; + } + else + PC("Unable to open file %s for writing!", outName); + } + strcpy(luaName, compiler.fileName); + strcat(luaName, ".lua"); + out = fopen(luaName, "w"); + // Generate the Lua source code. + if (out) + { + lua_State *L; + int err; + char *ext; + + fprintf(out, "--// Generated code goes here.\n\n"); + fprintf(out, "local _bit = require(\"bit\")\n"); + fprintf(out, "local _LSL = require(\"LSL\")\n\n"); + fprintf(out, "local _SID = [=[%s]=]\n\n", compiler.SID); + strcpy(buffer, basename(compiler.fileName)); + if ((ext = rindex(buffer, '.'))) + ext[0] = '\0'; + fprintf(out, "local _scriptName = [=[%s]=]\n\n", buffer); + outputLeaf(out, OM_LUA, compiler.ast); + fprintf(out, "\n\n_LSL.mainLoop(_SID, _scriptName, _defaultState)\n"); // This actually starts the script running. + fprintf(out, "\n--// End of generated code.\n\n"); + fclose(out); + + // Compile the Lua source code. + L = luaL_newstate(); + luaL_openlibs(L); + // This ends up pushing a function onto the stack. The function is the compiled code. + err = luaL_loadfile(L, luaName); + if (err) + { + compiler.script.bugCount++; + if (LUA_ERRSYNTAX == err) + PE("Lua syntax error in %s: %s", luaName, lua_tostring(L, -1)); + else if (LUA_ERRFILE == err) + PE("Lua compile file error in %s: %s", luaName, lua_tostring(L, -1)); + else if (LUA_ERRMEM == err) + PE("Lua compile memory allocation error in %s: %s", luaName, lua_tostring(L, -1)); + } + else + { + // Write the compiled code to a file. + strcat(luaName, ".out"); + out = fopen(luaName, "w"); + if (out) + { + err = lua_dump(L, luaWriter, out); + if (err) + { + compiler.script.bugCount++; + PE("Lua compile file error writing to %s", luaName); + } + fclose(out); + } + else + PC("Unable to open file %s for writing!", luaName); + } + } + else + PC("Unable to open file %s for writing!", luaName); + } + + if (0 == compiler.script.bugCount) + result = TRUE; + } + + if (NULL != compiler.file) + { + fclose(compiler.file); + compiler.file = NULL; + } + + if (!doConstants) + burnLeaf(compiler.ast); + + return result; +} diff --git a/src/LuaSL/LuaSL_lemon_yaccer.y b/src/LuaSL/LuaSL_lemon_yaccer.y new file mode 100644 index 0000000..182789f --- /dev/null +++ b/src/LuaSL/LuaSL_lemon_yaccer.y @@ -0,0 +1,275 @@ +%include +{ + #include "LuaSL.h" +} + +%extra_argument {LuaSL_compiler *compiler} + +%stack_size 1024 + +%token_type {LSL_Leaf *} +%default_type {LSL_Leaf *} +%token_destructor {burnLeaf($$);} +%default_destructor {burnLeaf($$);} + +// The start symbol, just coz we need one. + +// Lemon does not like the start symbol to be on the RHS, so give it a dummy start symbol. +program ::= script LSL_SCRIPT(A). { if (NULL != A) A->left = compiler->ast; compiler->ast = A; } + +// Various forms of "space". The lexer takes care of them for us. + +%nonassoc LSL_SPACE LSL_COMMENT LSL_COMMENT_LINE LSL_UNKNOWN. + +// Basic script structure. + +%nonassoc LSL_SCRIPT. +script ::= script state(A). { if (NULL != A) A->left = compiler->ast; compiler->ast = A; } +script ::= script functionBody(A). { if (NULL != A) A->left = compiler->ast; compiler->ast = A; } +script ::= script statement(A). { if (NULL != A) A->left = compiler->ast; compiler->ast = A; } +script ::= . + +// State definitions. + +%nonassoc LSL_BLOCK_OPEN LSL_BLOCK_CLOSE LSL_STATE. +stateBlock(A) ::= beginBlock(L) functionList(B) LSL_BLOCK_CLOSE(R). { A = addBlock(compiler, L, B, R); } +state(A) ::= LSL_DEFAULT(I) stateBlock(B). { A = addState(compiler, NULL, I, B); } +state(A) ::= LSL_STATE_CHANGE(S) LSL_IDENTIFIER(I) stateBlock(B). { A = addState(compiler, S, I, B); } + +// Function definitions. + +%nonassoc LSL_PARAMETER LSL_PARAMETER_LIST LSL_FUNCTION. +functionList(A) ::= functionList(B) functionBody(C). { A = collectStatements(compiler, B, C); } +functionList(A) ::= functionBody(C). { A = collectStatements(compiler, NULL, C); } +// No such thing as a function list with no functions. +//functionList(A) ::= . { A = collectStatements(compiler, NULL, NULL); } + +functionBody(A) ::= function(F) block(B). { A = addFunctionBody(compiler, F, B); } // addFunctionBody returns an implied addStatement(compiler, NULL, F, NULL, F, NULL, NULL); + +parameterList(A) ::= parameterList(B) LSL_COMMA(C) parameter(D). { A = collectParameters(compiler, B, C, D); } +parameterList(A) ::= parameter(D). { A = collectParameters(compiler, NULL, NULL, D); } +parameterList(A) ::= . { A = collectParameters(compiler, NULL, NULL, NULL); } +parameter(A) ::= type(B) LSL_IDENTIFIER(C). { A = addParameter(compiler, B, C); } +function(A) ::= LSL_IDENTIFIER(C) LSL_PARENTHESIS_OPEN(D) parameterList(E) LSL_PARENTHESIS_CLOSE(F). { A = addFunction(compiler, NULL, C, D, E, F); } +function(A) ::= type(B) LSL_IDENTIFIER(C) LSL_PARENTHESIS_OPEN(D) parameterList(E) LSL_PARENTHESIS_CLOSE(F). { A = addFunction(compiler, B, C, D, E, F); } + +// Blocks. + +block(A) ::= beginBlock(L) statementList(B) LSL_BLOCK_CLOSE(R). { A = addBlock(compiler, L, B, R); } + +// Various forms of statement. + +%nonassoc LSL_STATEMENT. +statementList(A) ::= statementList(B) statement(C). { A = collectStatements(compiler, B, C); } +// This causes infinite loops (and about 150 conflicts) with - if () single statement; +//statementList(A) ::= statement(C). { A = collectStatements(compiler, NULL, C); } +// Yes, you can have an empty block. +statementList(A) ::= . { A = collectStatements(compiler, NULL, NULL); } + +%nonassoc LSL_DO LSL_FOR LSL_IF LSL_JUMP LSL_RETURN LSL_STATE_CHANGE LSL_WHILE. +%nonassoc LSL_ELSE LSL_ELSEIF. +statement(A) ::= LSL_DO(F) block(B) LSL_WHILE(W) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, L, E, R, B, W); } +statement(A) ::= LSL_FOR(F) LSL_PARENTHESIS_OPEN(L) expr(E0) LSL_STATEMENT(S0) expr(E1) LSL_STATEMENT(S1) expr(E2) LSL_PARENTHESIS_CLOSE(R) block(B). { A = addFor(compiler, NULL, F, L, E0, S0, E1, S1, E2, R, B); } +statement(A) ::= LSL_FOR(F) LSL_PARENTHESIS_OPEN(L) expr(E0) LSL_STATEMENT(S0) expr(E1) LSL_STATEMENT(S1) expr(E2) LSL_PARENTHESIS_CLOSE(R) statement(S). { A = addFor(compiler, NULL, F, L, E0, S0, E1, S1, E2, R, S); } + +statement(A) ::= ifBlock(B). { A = B; } +ifBlock(A) ::= ifBlock(B) elseIfBlock(E). { A = addIfElse(compiler, B, E); } +ifBlock(A) ::= ifBlock(B) elseBlock(E). { A = addIfElse(compiler, B, E); } +ifBlock(A) ::= LSL_IF(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) block(B). [LSL_ELSE] { A = addStatement(compiler, NULL, F, L, E, R, B, NULL); } +ifBlock(A) ::= LSL_IF(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) statement(S). [LSL_ELSE] { A = addStatement(compiler, NULL, F, L, E, R, S, NULL); } +elseIfBlock(A) ::= LSL_ELSEIF(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) block(B). [LSL_ELSEIF] { A = addStatement(compiler, NULL, F, L, E, R, B, NULL); } +elseIfBlock(A) ::= LSL_ELSEIF(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) statement(S). [LSL_ELSEIF] { A = addStatement(compiler, NULL, F, L, E, R, S, NULL); } +elseBlock(A) ::= LSL_ELSE(F) block(B). { A = addStatement(compiler, NULL, F, NULL, NULL, NULL, B, NULL); } +elseBlock(A) ::= LSL_ELSE(F) statement(S). { A = addStatement(compiler, NULL, F, NULL, NULL, NULL, S, NULL); } + +statement(A) ::= LSL_JUMP(F) LSL_IDENTIFIER(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, I); } +statement(A) ::= LSL_RETURN(F) expr(E) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, E, NULL, NULL, NULL); } +statement(A) ::= LSL_RETURN(F) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, NULL); } +statement(A) ::= LSL_STATE_CHANGE(F) LSL_DEFAULT(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, I); } +statement(A) ::= LSL_STATE_CHANGE(F) LSL_IDENTIFIER(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, I); } +statement(A) ::= LSL_WHILE(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) block(B). { A = addStatement(compiler, NULL, F, L, E, R, B, NULL); } +statement(A) ::= LSL_WHILE(F) LSL_PARENTHESIS_OPEN(L) expr(E) LSL_PARENTHESIS_CLOSE(R) statement(S). { A = addStatement(compiler, NULL, F, L, E, R, S, NULL); } + +%nonassoc LSL_LABEL. +statement(A) ::= LSL_LABEL(F) LSL_IDENTIFIER(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, F, NULL, NULL, NULL, NULL, I); } + +beginBlock(A) ::= LSL_BLOCK_OPEN(B). { A = beginBlock(compiler, B); } + +// This might be bogus, or might be valid LSL, but it lets us test the expression parser by evaluating them. +statement(A) ::= expr(E) LSL_STATEMENT(S). { A = addStatement(compiler, S, S, NULL, E, NULL, NULL, NULL); } + +// Various forms of expression. + +// Used for function call params, and list contents. +exprList(A) ::= exprList(B) LSL_COMMA(C) expr(D). { A = collectArguments(compiler, B, C, D); } +exprList(A) ::= expr(D). { A = collectArguments(compiler, NULL, NULL, D); } +exprList(A) ::= . { A = collectArguments(compiler, NULL, NULL, NULL); } + +// Assignments and variable declarations. + +%right LSL_ASSIGNMENT_CONCATENATE LSL_ASSIGNMENT_ADD LSL_ASSIGNMENT_SUBTRACT LSL_ASSIGNMENT_MULTIPLY LSL_ASSIGNMENT_MODULO LSL_ASSIGNMENT_DIVIDE LSL_ASSIGNMENT_PLAIN. +// Variables and dealing with them. + +expr(A) ::= identifier(B). { A = B; } + +// Yes, these can be expressions, and can happen in if statements and such. In Lua they are NOT expressions. sigh +expr(A) ::= identifier(B) LSL_ASSIGNMENT_CONCATENATE(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= identifier(B) LSL_ASSIGNMENT_ADD(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= identifier(B) LSL_ASSIGNMENT_SUBTRACT(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= identifier(B) LSL_ASSIGNMENT_MULTIPLY(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= identifier(B) LSL_ASSIGNMENT_MODULO(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= identifier(B) LSL_ASSIGNMENT_DIVIDE(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= identifier(B) LSL_ASSIGNMENT_PLAIN(C) expr(D). { A = addOperation(compiler, B, C, D); } + +// Hmm think this can have commas seperating the assignment parts, or is that only in C?. If so, best to separate them when converting to Lua, as it uses that syntax for something else. +// Well, not in OpenSim at least, nor in SL. So we are safe. B-) +// On the other hand, it might be legal to have comma separated bits in a for loop - for ((i = 1), (j=1); ... +statement(A) ::= type(T) LSL_IDENTIFIER(I) LSL_ASSIGNMENT_PLAIN(D) expr(E) LSL_STATEMENT(S). { A = addStatement(compiler, S, I, NULL, addVariable(compiler, T, I, D, E), NULL, NULL, I); } +statement(A) ::= type(T) LSL_IDENTIFIER(I) LSL_STATEMENT(S). { A = addStatement(compiler, S, I, NULL, addVariable(compiler, T, I, NULL, NULL), NULL, NULL, I); } + +// Basic operators. + +%right LSL_BOOL_AND. +expr(A) ::= expr(B) LSL_BOOL_AND(C) expr(D). { A = addOperation(compiler, B, C, D); } +%right LSL_BOOL_OR. +expr(A) ::= expr(B) LSL_BOOL_OR(C) expr(D). { A = addOperation(compiler, B, C, D); } + +%left LSL_BIT_AND LSL_BIT_XOR LSL_BIT_OR. +expr(A) ::= expr(B) LSL_BIT_OR(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_BIT_XOR(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_BIT_AND(C) expr(D). { A = addOperation(compiler, B, C, D); } + +%right LSL_EQUAL LSL_NOT_EQUAL. +expr(A) ::= expr(B) LSL_NOT_EQUAL(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_EQUAL(C) expr(D). { A = addOperation(compiler, B, C, D); } +%right LSL_LESS_THAN LSL_GREATER_THAN LSL_LESS_EQUAL LSL_GREATER_EQUAL. +expr(A) ::= expr(B) LSL_GREATER_EQUAL(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_LESS_EQUAL(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_GREATER_THAN(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_LESS_THAN(C) expr(D). { A = addOperation(compiler, B, C, D); } + +%left LSL_LEFT_SHIFT LSL_RIGHT_SHIFT. +expr(A) ::= expr(B) LSL_RIGHT_SHIFT(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_LEFT_SHIFT(C) expr(D). { A = addOperation(compiler, B, C, D); } + +%left LSL_SUBTRACT LSL_ADD LSL_CONCATENATE. +expr(A) ::= expr(B) LSL_ADD(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_SUBTRACT(C) expr(D). { A = addOperation(compiler, B, C, D); } +%left LSL_DIVIDE LSL_MODULO LSL_MULTIPLY LSL_DOT_PRODUCT LSL_CROSS_PRODUCT. +expr(A) ::= expr(B) LSL_MULTIPLY(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_MODULO(C) expr(D). { A = addOperation(compiler, B, C, D); } +expr(A) ::= expr(B) LSL_DIVIDE(C) expr(D). { A = addOperation(compiler, B, C, D); } + +%right LSL_BIT_NOT LSL_BOOL_NOT LSL_NEGATION. +expr(A) ::= LSL_BIT_NOT(B) expr(C). { A = addOperation(compiler, NULL, B, C); } +expr(A) ::= LSL_BOOL_NOT(B) expr(C). { A = addOperation(compiler, NULL, B, C); } +expr(A) ::= LSL_SUBTRACT(B) expr(C). [LSL_NEGATION] { A = addOperation(compiler, NULL, B, C); } + +// Types, typecasts, and expression reordering. + +%right LSL_TYPECAST_OPEN LSL_TYPECAST_CLOSE. +%nonassoc LSL_TYPE_FLOAT LSL_TYPE_INTEGER LSL_TYPE_KEY LSL_TYPE_LIST LSL_TYPE_ROTATION LSL_TYPE_STRING LSL_TYPE_VECTOR. +type(A) ::= LSL_TYPE_FLOAT(B). { B->basicType = OT_float; A = B; } +type(A) ::= LSL_TYPE_INTEGER(B). { B->basicType = OT_integer; A = B; } +type(A) ::= LSL_TYPE_KEY(B). { B->basicType = OT_key; A = B; } +type(A) ::= LSL_TYPE_LIST(B). { B->basicType = OT_list; A = B; } +type(A) ::= LSL_TYPE_ROTATION(B). { B->basicType = OT_rotation; A = B; } +type(A) ::= LSL_TYPE_STRING(B). { B->basicType = OT_string; A = B; } +type(A) ::= LSL_TYPE_VECTOR(B). { B->basicType = OT_vector; A = B; } + +%left LSL_ANGLE_OPEN LSL_ANGLE_CLOSE. +%nonassoc LSL_BRACKET_OPEN LSL_BRACKET_CLOSE. +%nonassoc LSL_PARENTHESIS_OPEN LSL_PARENTHESIS_CLOSE LSL_EXPRESSION. + +expr(A) ::= LSL_PARENTHESIS_OPEN(B) expr(C) LSL_PARENTHESIS_CLOSE(D). { A = addParenthesis(B, C, LSL_EXPRESSION, D); } +expr(A) ::= LSL_PARENTHESIS_OPEN(B) type(C) LSL_PARENTHESIS_CLOSE(D) expr(E). { A = addTypecast(B, C, D, E); } + +%right LSL_DOT LSL_IDENTIFIER LSL_FUNCTION_CALL LSL_VARIABLE. +identifier(A) ::= identifier(I) LSL_DOT(D) LSL_IDENTIFIER(B). { A = checkVariable(compiler, I, D, B); } +identifier(A) ::= LSL_IDENTIFIER(B). { A = checkVariable(compiler, B, NULL, NULL); } + +%right LSL_DECREMENT_PRE LSL_INCREMENT_PRE LSL_DECREMENT_POST LSL_INCREMENT_POST. +expr(A) ::= identifier(B) LSL_DECREMENT_PRE(C). { A = addCrement(compiler, B, C, LSL_DECREMENT_POST); } +expr(A) ::= identifier(B) LSL_INCREMENT_PRE(C). { A = addCrement(compiler, B, C, LSL_INCREMENT_POST); } +expr(A) ::= LSL_DECREMENT_PRE(C) identifier(B). { A = addCrement(compiler, B, C, LSL_DECREMENT_PRE); } +expr(A) ::= LSL_INCREMENT_PRE(C) identifier(B). { A = addCrement(compiler, B, C, LSL_INCREMENT_PRE); } + +// Function call. + +// Causes a conflict when exprList is empty with a function definition with no type and no parameters. +expr(A) ::= LSL_IDENTIFIER(B) LSL_PARENTHESIS_OPEN(C) exprList(D) LSL_PARENTHESIS_CLOSE(E). { A = addFunctionCall(compiler, B, C, D, E); } + +%nonassoc LSL_COMMA. + +// Values. + +%nonassoc LSL_FLOAT. +expr(A) ::= LSL_FLOAT(B). { A = addNumby(B); } +%nonassoc LSL_INTEGER. +expr(A) ::= LSL_INTEGER(B). { A = addNumby(B); } +%nonassoc LSL_KEY. +expr(A) ::= LSL_KEY(B). { B->basicType = OT_key; A = B; } +%nonassoc LSL_LIST. +expr(A) ::= LSL_BRACKET_OPEN(L) exprList(E) LSL_BRACKET_CLOSE(R). [LSL_BRACKET_OPEN] { A = addList(L, E, R); } +%nonassoc LSL_ROTATION LSL_VECTOR. +// Uses the same symbol for less than, greater than, and the rotation / vector delimiters. +expr(A) ::= LSL_LESS_THAN(L) exprList(E) LSL_GREATER_THAN(R). [LSL_ANGLE_OPEN] { A = addRotVec(L, E, R); } +%nonassoc LSL_STRING. +expr(A) ::= LSL_STRING(B). { B->basicType = OT_string; A = B; } + + +// Parser callbacks. + +%parse_accept +{ +// gameGlobals *ourGlobals = compiler->game; + +// PI("Parsing complete."); +} + +%parse_failure +{ + gameGlobals *ourGlobals = compiler->game; + + compiler->script.bugCount++; + PE("Giving up. Parser is hopelessly lost!"); +} + +%stack_overflow +{ + gameGlobals *ourGlobals = compiler->game; + + compiler->script.bugCount++; + PE("Giving up. Parser stack overflow @ line %d, column %d!", yypMinor->yy0->line, yypMinor->yy0->column); // Gotta love consistancy, if it ever happens. +} + +%syntax_error +{ + gameGlobals *ourGlobals = compiler->game; + + compiler->script.bugCount++; + PE("Syntax error @ line %d, column %d!", yyminor.yy0->line, yyminor.yy0->column); +} + + +/* Undocumented shit that might be useful later. Pffft + +** The next table maps tokens into fallback tokens. If a construct +** like the following: +**. +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. + +%wildcard +%code + +%ifdef +%endif +%ifndef +%endif + +*/ diff --git a/src/LuaSL/LuaSL_lexer.l b/src/LuaSL/LuaSL_lexer.l new file mode 100644 index 0000000..85b2821 --- /dev/null +++ b/src/LuaSL/LuaSL_lexer.l @@ -0,0 +1,164 @@ +%{ + +#define excludeLexer +#include "LuaSL.h" + +int common(YYSTYPE *lval, char *text, int len, LuaSL_compiler *compiler, boolean checkIgnorable, int type); + +%} + +%option reentrant never-interactive batch +%option bison-bridge 8bit +%option noreject noyymore +%option backup debug perf-report perf-report verbose warn +%option align full +%option extra-type="LuaSL_compiler *" + +HEX [[:xdigit:]] +DECIMAL [[:digit:]] + /* LSL has no octal integer type. */ +INTEGER ({DECIMAL}+)|(0[xX]{HEX}+) +EXPONANT [eE][+-]?{DECIMAL}+ + /* Floats can be "0." or".0", but "." is not valid. At least in OpenSim. A single dot should be caught by the LSL_Dot rule first anyway.*/ +FLOAT {DECIMAL}*"."{DECIMAL}*{EXPONANT}?[fF]? + /* Variable identifiers can have these extra characters at the end or beginning, they will be ignored- #$`'\? */ + /* Function identifiers can start with $ */ +IDENTIFIER (_|[[:alpha:]])(_|[[:alpha:]]|[[:digit:]])* +CHAR '(\\.|[^\\'\n])+' +KEY \"{HEX}{8}-{HEX}{4}-{HEX}{4}-{HEX}{4}-{HEX}{12}\" +STRING \"(\\.|[^\\"\n])*\" + +%% + + /* The order here is important, in mysterious ways. The more specific the lower in case of ambiguities like "floats contain integers". I think, not tested that well yet. */ + + /* Ignorables. */ +[[:space:]]+ %{ common(yylval, yytext, yyleng, yyextra, FALSE, LSL_SPACE); %} + /* Yes I know this will have problems with huge comments, just being simple to get it to work for now. */ +"/*"([^"*/"]*)"*/" %{ common(yylval, yytext, yyleng, yyextra, FALSE, LSL_COMMENT); %} +"//"[^\n]* %{ common(yylval, yytext, yyleng, yyextra, FALSE, LSL_COMMENT_LINE); %} + + /* Operations. */ +"&&" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BOOL_AND); } +"||" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BOOL_OR); } +"|" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BIT_OR); } +"^" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BIT_XOR); } +"&" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BIT_AND); } +"!=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_NOT_EQUAL); } +"==" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_EQUAL); } +">=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_GREATER_EQUAL); } +"<=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_LESS_EQUAL); } +">" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_GREATER_THAN); } +"<" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_LESS_THAN); } +">>" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_RIGHT_SHIFT); } +"<<" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_LEFT_SHIFT); } +"+" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ADD); } +"-" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_SUBTRACT); } +"*" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_MULTIPLY); } +"%" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_MODULO); } +"/" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DIVIDE); } +"!" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BOOL_NOT); } +"~" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BIT_NOT); } +"[" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BRACKET_OPEN); } +"]" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BRACKET_CLOSE); } +"(" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_PARENTHESIS_OPEN); } +")" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_PARENTHESIS_CLOSE); } +"+=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_ADD); } +"-=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_SUBTRACT); } +"*=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_MULTIPLY); } +"%=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_MODULO); } +"/=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_DIVIDE); } +"=" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ASSIGNMENT_PLAIN); } +"." { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DOT); } +"--" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DECREMENT_PRE); } +"++" { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_INCREMENT_PRE); } +"," { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_COMMA); } + + /* Other symbols. */ +"@" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_LABEL); %} +"{" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BLOCK_OPEN); %} +"}" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_BLOCK_CLOSE); %} +";" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_STATEMENT); %} + + /* Type keywords. */ +"float" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_FLOAT); %} +"integer" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_INTEGER); %} +"key" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_KEY); %} +"list" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_LIST); %} +"quaternion" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_ROTATION); %} +"rotation" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_ROTATION); %} +"string" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_STRING); %} +"vector" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_TYPE_VECTOR); %} + + /* Statement keywords. */ +"default" %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DEFAULT); %} +"do" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_DO); %} +"for" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_FOR); %} +"else" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ELSE); %} +"if" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_IF); %} +"else"[[:space:]]+"if" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_ELSEIF); %} /* TODO - deal with people that slap a comment in between them. */ +"jump" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_JUMP); %} +"return" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_RETURN); %} +"state" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_STATE_CHANGE); %} +"while" %{ return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_WHILE); %} + +{IDENTIFIER} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_IDENTIFIER); %} + + /* Types. */ +{INTEGER} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_INTEGER); %} +{FLOAT} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_FLOAT); %} +{KEY} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_KEY); %} +{STRING} %{ yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_STRING); %} + +<> { return common(yylval, yytext, yyleng, yyextra, TRUE, LSL_SCRIPT); } + + /* Everything else */ +. %{ printf(" unexpected character.\n"); yylval->value.stringValue = eina_stringshare_add_length(yytext, yyleng); common(yylval, yytext, yyleng, yyextra, TRUE, LSL_UNKNOWN); %} + +%% + +int common(YYSTYPE *lval, char *text, int len, LuaSL_compiler *compiler, boolean checkIgnorable, int type) +{ + char *p; + + lval->toKen = tokens[type - lowestToken]; + lval->line = compiler->line; + lval->column = compiler->column; + lval->len = len; + + for (p = text; *p; p++) + { + if ('\n' == *p) + { + compiler->line++; + compiler->column = 1; + } + else + compiler->column++; + } + +#if LUASL_DIFF_CHECK + if (checkIgnorable) + { + lval->ignorable = compiler->ignorable; + compiler->ignorable = eina_strbuf_new(); + } + else + eina_strbuf_append_length(compiler->ignorable, text, len); +#endif + + return type; +} + +int yywrap(yyscan_t yyscanner) // This as actually useless for our needs, as it is called BEFORE the last token is dealt with. +{ +#ifdef FLEX_SCANNER + #ifndef LL_WINDOWS + // Get gcc to stop complaining about lack of use of yyunput and input. + (void) yyunput; + (void) input; + #endif +#endif + + return 1; +} diff --git a/src/LuaSL/LuaSL_main.c b/src/LuaSL/LuaSL_main.c new file mode 100644 index 0000000..0a40712 --- /dev/null +++ b/src/LuaSL/LuaSL_main.c @@ -0,0 +1,708 @@ + +#include "LuaSL.h" + + +static int CPUs = 4; +static Eina_Strbuf *clientStream; + + +static Eina_Bool _sleep_timer_cb(void *data) +{ + script *script = data; + gameGlobals *ourGlobals = script->game; + + PD("Waking up %s", script->SID); + sendToChannel(ourGlobals, script->SID, "return 0.0"); + return ECORE_CALLBACK_CANCEL; +} + +static Eina_Bool _timer_timer_cb(void *data) +{ + script *script = data; + gameGlobals *ourGlobals = script->game; + + PD("Timer for %s", script->SID); + sendToChannel(ourGlobals, script->SID, "events.timer()"); + return ECORE_CALLBACK_RENEW; +} + +static script *findThem(gameGlobals *ourGlobals, const char *base, const char *text) +{ + char name[PATH_MAX]; + char *temp; + + strncpy(name, base, PATH_MAX); + if ((temp = rindex(name, '/'))) + temp[1] = '\0'; + strcat(name, text); + if ((temp = rindex(name, '"'))) + temp[0] = '\0'; + strcat(name, ".lsl"); + return eina_hash_find(ourGlobals->names, name); +} + +static void resetScript(script *victim) +{ + gameGlobals *ourGlobals = victim->game; + + PD("Resetting %s", victim->fileName); + // TODO - now what? +} + +void scriptSendBack(void * data) +{ + scriptMessage *message = data; + gameGlobals *ourGlobals = message->script->game; + + if (0 == strncmp(message->message, "llSleep(", 8)) + ecore_timer_add(atof(&(message->message)[8]), _sleep_timer_cb, message->script); + else if (0 == strncmp(message->message, "llSetTimerEvent(", 16)) + { + message->script->timerTime = atof(&(message->message)[16]); + if (0.0 == message->script->timerTime) + { + if (message->script->timer) + ecore_timer_del(message->script->timer); + message->script->timer = NULL; + } + else + message->script->timer = ecore_timer_add(message->script->timerTime, _timer_timer_cb, message->script); + } + else if (0 == strncmp(message->message, "llSetScriptState(", 17)) + { + script *them; + + if ((them = findThem(ourGlobals, message->script->fileName, &(message->message[18])))) + { + char *temp = rindex(&(message->message[18]), ','); + + if (temp) + { + temp++; + while (isspace(*temp)) + temp++; + if ('1' == *temp) + sendToChannel(ourGlobals, them->SID, "start()"); + else + sendToChannel(ourGlobals, them->SID, "stop()"); + PD("Stopped %s", them->fileName); + } + else + PE("Missing script state in llSetScriptState(%s, )", them->fileName); + } + else + { + char *temp = rindex(&(message->message[18]), '"'); + + if (temp) + *temp = '\0'; + PE("Can't stop script, can't find %s", &(message->message[18])); + } + } + else if (0 == strncmp(message->message, "llResetOtherScript(", 19)) + { + script *them; + + if ((them = findThem(ourGlobals, message->script->fileName, &(message->message[20])))) + resetScript(them); + } + else if (0 == strncmp(message->message, "llResetScript(", 14)) + resetScript(message->script); + else + sendBack(ourGlobals, message->script->client, message->script->SID, message->message); + free(message); +} + +static Eina_Bool _add(void *data, int type __UNUSED__, Ecore_Con_Event_Client_Add *ev) +{ + ecore_con_client_timeout_set(ev->client, 0); + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool _data(void *data, int type __UNUSED__, Ecore_Con_Event_Client_Data *ev) +{ + gameGlobals *ourGlobals = data; + char buf[PATH_MAX]; + char SID[PATH_MAX]; + const char *command; + char *ext; + + eina_strbuf_append_length(clientStream, ev->data, ev->size); + command = eina_strbuf_string_get(clientStream); + while ((ext = index(command, '\n'))) + { + int length = ext - command; + + strncpy(SID, command, length + 1); + SID[length] = '\0'; + eina_strbuf_remove(clientStream, 0, length + 1); + ext = index(SID, '.'); + if (ext) + { + ext[0] = '\0'; + command = ext + 1; + if (0 == strncmp(command, "compile(", 8)) + { + char *temp; + char *file; + + strcpy(buf, &command[8]); + temp = buf; + file = temp; + while (')' != temp[0]) + temp++; + temp[0] = '\0'; + + PD("Compiling %s, %s.", SID, file); + if (compileLSL(ourGlobals, ev->client, SID, file, FALSE)) + { + script *me = calloc(1, sizeof(script)); + + gettimeofday(&me->startTime, NULL); + strncpy(me->SID, SID, sizeof(me->SID)); + strncpy(me->fileName, file, sizeof(me->fileName)); + me->game = ourGlobals; + me->client = ev->client; + eina_hash_add(ourGlobals->scripts, me->SID, me); + eina_hash_add(ourGlobals->names, me->fileName, me); + sendBack(ourGlobals, ev->client, SID, "compiled(true)"); + } + else + sendBack(ourGlobals, ev->client, SID, "compiled(false)"); + } + else if (0 == strcmp(command, "run()")) + { + script *me; + char buf[PATH_MAX]; + + me = eina_hash_find(ourGlobals->scripts, SID); + if (me) + { + sprintf(buf, "%s.lua.out", me->fileName); + newProc(buf, TRUE, me); + } + } + else if (0 == strcmp(command, "exit()")) + { + PD("Told to exit."); + ecore_main_loop_quit(); + } + else + { + const char *status = NULL; + + status = sendToChannel(ourGlobals, SID, command); + if (status) + PE("Error sending command %s to script %s : %s", command, SID, status); + } + } + + // Get the next blob to check it. + command = eina_strbuf_string_get(clientStream); + } + + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool _del(void *data, int type __UNUSED__, Ecore_Con_Event_Client_Del *ev) +{ + gameGlobals *ourGlobals = data; + + if (ev->client) + { + PD("No more clients, exiting."); + ecore_con_client_del(ev->client); + ecore_main_loop_quit(); + } + return ECORE_CALLBACK_RENEW; +} + +int main(int argc, char **argv) +{ + gameGlobals ourGlobals; + int result = EXIT_FAILURE; + + memset(&ourGlobals, 0, sizeof(gameGlobals)); + ourGlobals.address = "127.0.0.1"; + ourGlobals.port = 8211; + + if (eina_init()) + { + ourGlobals.logDom = loggingStartup("LuaSL", ourGlobals.logDom); + ourGlobals.scripts = eina_hash_string_superfast_new(NULL); + ourGlobals.names = eina_hash_string_superfast_new(NULL); + if (ecore_con_init()) + { + if (ecore_con_init()) + { + if ((ourGlobals.server = ecore_con_server_add(ECORE_CON_REMOTE_TCP, ourGlobals.address, ourGlobals.port, &ourGlobals))) + { + ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_ADD, (Ecore_Event_Handler_Cb) _add, &ourGlobals); + ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DATA, (Ecore_Event_Handler_Cb) _data, &ourGlobals); + ecore_event_handler_add(ECORE_CON_EVENT_CLIENT_DEL, (Ecore_Event_Handler_Cb) _del, &ourGlobals); + ecore_con_server_timeout_set(ourGlobals.server, 0); + ecore_con_server_client_limit_set(ourGlobals.server, -1, 0); + ecore_con_server_timeout_set(ourGlobals.server, 10); + ecore_con_server_client_limit_set(ourGlobals.server, 3, 0); + clientStream = eina_strbuf_new(); + + if (edje_init()) + { + int i; + + result = 0; + compilerSetup(&ourGlobals); + luaprocInit(); + for (i = 0; i < CPUs; i++) + { + if ( sched_create_worker( ) != LUAPROC_SCHED_OK ) + PEm("Error creating luaproc worker thread."); + } + ecore_main_loop_begin(); + + // TODO - this is what hangs the system, should change from raw pthreads to ecore threads. + sched_join_workerthreads(); + edje_shutdown(); + } + else + PCm("Failed to init edje!"); + } + else + PCm("Failed to add server!"); + ecore_con_shutdown(); + } + else + PCm("Failed to init ecore_con!"); + ecore_shutdown(); + } + else + PCm("Failed to init ecore!"); + } + else + fprintf(stderr, "Failed to init eina!"); + + return result; +} + + +/* Stuff to be merged in later. + +#ifdef _WIN32 +# define FMT_SIZE_T "%Iu" +#else +# define FMT_SIZE_T "%zu" +#endif + +#define MAX_LUA_MEM (64 * 1024)) // LSL Mono is 64KB, edje Lua is 4MB. (4 * (1024 * 1024)) + +#define _edje_lua2_error(L, err_code) _edje_lua2_error_full(__FILE__, __FUNCTION__, __LINE__, L, err_code) + + +typedef struct _Edje_Lua_Alloc Edje_Lua_Alloc; + +struct _Edje_Lua_Alloc +{ + size_t max, cur; +}; + +static void * +_elua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) +{ + Edje_Lua_Alloc *ela = ud; + void *ptr2 = NULL; + + if (ela) + { + ela->cur += nsize - osize; + if (ela->cur > ela->max) + { + printf("Lua memory limit of " FMT_SIZE_T " bytes reached (" FMT_SIZE_T " allocated)", ela->max, ela->cur); + } + else if (nsize == 0) + { + free(ptr); + } + else + { + ptr2 = realloc(ptr, nsize); + if (NULL == ptr2) + printf("Lua cannot re-allocate " FMT_SIZE_T " bytes", nsize); + } + } + else + printf("Lua cannoct allocate memory, no Edje_Lua_Alloc"); + + return ptr2; +} + +static int panics = 0; +static int +_elua_custom_panic(lua_State *L) // Stack usage [-0, +0, m] +{ + // If we somehow manage to have multiple panics, it's likely due to being out + // of memory in the following lua_tostring() call. + panics++; + if (panics) + { + printf("Lua PANICS!!!!!"); + } + else + { + printf("Lua PANIC!!!!!: %s", lua_tostring(L, -1)); // Stack usage [-0, +0, m] + } + // The docs say that this will cause an exit(EXIT_FAILURE) if we return, + // and that we we should long jump some where to avoid that. This is only + // called for things not called from a protected environment. We always + // use pcalls though, except for the library load calls. If we can't load + // the standard libraries, then perhaps a crash is the right thing. + return 0; +} + +static void +_edje_lua2_error_full(const char *file, const char *fnc, int line, + lua_State *L, int err_code) // Stack usage [-0, +0, m] +{ + const char *err_type; + + switch (err_code) + { + case LUA_ERRRUN: + err_type = "runtime"; + break; + case LUA_ERRSYNTAX: + err_type = "syntax"; + break; + case LUA_ERRMEM: + err_type = "memory allocation"; + break; + case LUA_ERRERR: + err_type = "error handler"; + break; + default: + err_type = "unknown"; + break; + } + printf("Lua %s error: %s\n", err_type, lua_tostring(L, -1)); // Stack usage [-0, +0, m] +} + +static int errFunc(lua_State *L) +{ + int i = 0; + lua_Debug ar; + + while (lua_getstack(L, i++, &ar)) + { + if (lua_getinfo(L, "nSlu", &ar)) + { + if (NULL == ar.name) + ar.name = "DUNNO"; + printf("Lua error in the %s %s %s @ line %d in %s\n%s!", ar.what, ar.namewhat, ar.name, ar.currentline, ar.short_src, ar.source); + } + } + return 0; +} + +void runLuaFile(gameGlobals *ourGlobals, const char *filename) +{ + PD("Starting %s", filename); + newProc(filename, TRUE); + +// TODO, should set up our panic and errfunc as below. Plus the other TODO stuff. + + +// TODO - hack up LuaJIT so that we can limit memory per state. +// lua_setallocf(L, _elua_alloc, &ela); // LuaJIT uses a heavily hacked up dlmalloc. Seems that standard realloc is not so thread safe? + lua_atpanic(L, _elua_custom_panic); +// TODO - Sandbox out what this opens. See lib_init.c from LuaJIT. +// Just noticed this in the LuaJIT docs - "To change or extend the list of standard libraries to load, copy src/lib_init.c to your project and modify it accordingly. Make sure the jit library is loaded or the JIT compiler will not be activated." + luaL_openlibs(L); + + lua_pushcfunction(L, errFunc); + ... + if ((err = lua_pcall(L, 0, 0, -2))) + _edje_lua2_error(L, err); +} +*/ + + + + +/* What we need to do, and how we might do it. + * + * Get configuration info. + * + * Some of the configuration info we need is tucked away in OpenSim .ini files. So we have to read that. + * Eet is probably not so useful here, as we would have to write a UI tool, and there's no UI otherwise. + * + * Multi task! + * + * Luaproc starts up X worker threads. Each one takes the next ready Lua state and runs it until it blocks waiting for a channel message, or yields. + * The current mainloop waits for events and commands from the message channel, executes them, then goes back to waiting. + * So that is fine in general, so long as the LSL event handlers actually return in a reasonable time. + * We need to be able to yield at suitable places, or be able to force a yield. Both without slowing things down too much. + * + * Watchdog thread. + * + * It's easy enough to have a watchdog thread wake up every now and then to check if any given Lua state has been hogging it's CPU for too long. + * The hard part is forcing Lua states to yield cleanly, without slowing performance too much. + * + * Identifying scripts. - OpenSim/Region/ScriptEngine/Interfaces/IScriptInstance.cs + * + * We need to be able to identify scripts so that messages can get to the right ones. Also the objects they are in. + * And do it all in a way that OpenSim is happy with. + * Copies of the same script in the same prim would have the same asset UUID, but different name, though they would run independently. + * Copies of the same script in different prims could have the same asset UUID AND the same name. + * OpenSim seems to be using a UUID to identify single scripts, and a uint to identify single prims, when sending events for either case. + * The UUID is from the script item in the prims inventory. + * There is also an asset UUID (the one printed out on the console at script startup time) that points to the source code in the prim. + * Which will be identical to the asset UUID for the multiple copies of the same script. + * + * script assetID + * UUID of the script source in the grids asset database, also the script source in the prim. + * + * script itemID + * UUID of this instance of the running script. + * UUID of the scripts binary in the prims inventory. + * This is the one used to identify the running script. + * + * prim uint localID + * Some sort of number representing the prim the script is running in. + * Events are sometimes sent to this. + * + * path/filename + * An invention of LuaSL, coz we store stuff as files. + * + * OpenSim says "compile this assetID for this itemID, in this prim uint" + * Current infrastructure does not allow easy sending of the script source, but we don't have ROBUST code to get it either. + * ROBUST is the way to go though, coz we can sneakily start to suck other stuff, like prim contents across when needed. + * Though that sort of thing needs access to the local sim databases to lookup the prim and it's other contents. sigh + * I think that new script and notecard contents get new assetIDs anyway, so keeping an eye on assets.create_time or asset_access_time wont help much. + * + * OpenSim says "start / stop this itemID" + * Already catered for. + * + * What does OpenSim REALLY do? + * + * Region/Framework/Scenes/Scene.Inventory.cs - CapsUpdateTaskInventoryScriptAsset(IClientAPI remoteClient, UUID itemId, UUID primId, bool isScriptRunning, byte[] data) + * remoteClient + * itemID - UUID of the script source. + * primID - UUID of the prim it is in. + * isScriptRunning + * data - the script source code. + * Called when a user saves the script. itemID stays the same, but we get a new assetID, for the new source code asset. + * Looks up the item in the prim. + * AssetBase asset = CreateAsset(item.Name, item.Description, (sbyte)AssetType.LSLText, data, remoteClient.AgentId); + * AssetService.Store(asset); + * stashes the new assetID in the item + * updates the item in the prim + * if (isScriptRunning) + * part.Inventory.RemoveScriptInstance(item.ItemID, false); + * part.Inventory.CreateScriptInstance(item.ItemID, 0, false, DefaultScriptEngine, 0); + * errors = part.Inventory.GetScriptErrors(item.ItemID); + * + * CreateScriptInstance() is generally called to start scripts, part.ParentGroup.ResumeScripts(); is usually called after CreateScriptInstance() + * + * Region/Framework/Scenes/SceneObjectPartInventory.cs - CreateScriptInstance(UUID itemId, int startParam, bool postOnRez, string engine, int stateSource) + * looks up the itemID, then calls the real one - + * Region/Framework/Scenes/SceneObjectPartInventory.cs - CreateScriptInstance(TaskInventoryItem item, int startParam, bool postOnRez, string engine, int stateSource) + * get the asset from the asset database using the items assetID + * restores script state if needed + * converts asset.data to a string called script + * m_part.ParentGroup.Scene.EventManager.TriggerRezScript(m_part.LocalId, item.ItemID, script, startParam, postOnRez, engine, stateSource); + * QUIRK - if it's a sim border crossing, TriggerRezScript() gets called with empty script source. + * + * Region/ScriptEngine/XEngine/XEngine.cs - AddRegion(Scene scene) + * m_log.InfoFormat("[XEngine] Initializing scripts in region {0}", scene.RegionInfo.RegionName); + * gets the script config info, which is the same damn stuff for each sim. Pffft + * Think it relies on the scenes event manager to call OnRezScript() - + * m_Scene.EventManager.OnRezScript += OnRezScript; + * + * Region/Framework/Scenes/EventManager.cs - TriggerRezScript(uint localID, UUID itemID, string script, int startParam, bool postOnRez, string engine, int stateSource) + * Loops through Scene.EventManager.OnRezScript calling them. + * + * Region/ScriptEngine/XEngine/XEngine.cs - OnRezScript(uint localID, UUID itemID, string script, int startParam, bool postOnRez, string engine, int stateSource) + * Looks at the script source to figure out if it's an XEngine script. + * Either queues the script for later, or does it direct. + * Region/ScriptEngine/XEngine/XEngine.cs - DoOnRezScript() is passed an array holding - + * localID is a uint that represents the containing prim in the current scene + * itemID is the UUID of the script in the prims contents + * script is the script source code. + * startParam is the scripts startParam + * postOnRez + * stateSource is an integer saying how we where started, used to trigger the appropriate startup events. + * uses localID to look up the prim in the scene, then looks inside that for the itemID to find the assetID. + * m_Compiler.PerformScriptCompile(script, assetID.ToString(), item.OwnerID, out assembly, out linemap); + * Which is in Region/ScriptEngine/Shared/CodeTools/Compiler.cs + * instance = new ScriptInstance(this, part, itemID, assetID, assembly, m_AppDomains[appDomain], part.ParentGroup.RootPart.Name, item.Name, startParam, postOnRez, stateSource, m_MaxScriptQueue); + * Region/ScriptEngine/Shared/Instance/ScriptInstance.cs - ScriptInstance(IScriptEngine engine, SceneObjectPart part, UUID itemID, UUID assetID, string assembly, AppDomain dom, string primName, string scriptName, int startParam, bool postOnRez, StateSource stateSource, int maxScriptQueue) + * inits all the APIs + * loads in any saved state if it can find one + * m_log.DebugFormat("[XEngine] Loaded script {0}.{1}, script UUID {2}, prim UUID {3} @ {4}.{5}", part.ParentGroup.RootPart.Name, item.Name, assetID, part.UUID, part.ParentGroup.RootPart.AbsolutePosition, part.ParentGroup.Scene.RegionInfo.RegionName); + * + * Soooo, when a script is saved - + * the new source is saved in the asset database + * The script item in the prim gets the new assetID + * if the script is running - + * remove the old script instance (item.ItemID) + * create a new one (item.ItemID) + * get the source code from the asset database (item.assetID) + * restore script state + * TriggerRezOnScript() + * Loop through all those that are interested, incuding XEngine.onRezScript() + *** check the first line to see if it's an XEngine script + * sooner or later passes it to XEngine.DoOnRezScript() + * looks up localID to get the prim + * looks inside prim to get the script from itemID + * gets the assetID from the script item + * compiles the script + * creates the script instance + * loads up the APIs + * restores any script state + * calls instance.Init() which is Region/ScriptEngine/Shared/Instance/ScriptInstance.cs - Init() + * passes the usual startup events to the script. + * part.ParentGroup.ResumeScripts() + * + * At the *** marked point, LuaSL.onRezScript should - + * check the first line to see if it's an LuaSL script + * looks up localID to get the prim + * looks inside prim to get the script from itemID + * gets the assetID from the script item + * filename encode the sim name, object name, and script name + * replace anything less than 0x21, DEL " * / : < > ? \ | + [ ] - , . ( ) $ % # @ from - http://en.wikipedia.org/wiki/Filename plus a few more + * THEN reduce to 254 characters + * NOTE the object names might be identical, disambiguate them. + * write the script to a file - /script/engine/path/sim_name/objects/object_name/script_name + * send the itemID.compile(/script/engine/path/sim_name/objects/object_name/script_name) message to the script engine's socket + * + * + * Object inventory "cache". + * + * This code currently pretends that there is a local file based sim object store available. + * I think it would be a good idea to abuse the OpenSim cache system to produce that file based object store. + * It will help with the "damn OpenSim's asset database has to be a bottomless pit" monster design flaw. + * Prim contents must all be unique names anyway, and there are SOME constraints on contents names, so probably don't have to do much to convert an item name to a legal file name. + * Oops, names can have directory slashes in them. lol + * On the other hand, sim objects CAN have the same name. + * + * So we got sim directories, with an objects directory inside it, with object directories inside that. The object directories have object files in them. This is all like the test setup that is here. + * We need metadata. Sim metadata, object metadata, and object contents metadata. That can be done with a "foo.omg" file at each level. + * sim/index.omg - the list of object name.UUIDs, their X,Y,Z location, size, and rotation. + * sim/objects/objectName_UUID/index.omg - the list of contents names, item UUIDs, asset UUIDs, and types. + * sim/objects/objectName/subObjectName - the list of ITS contents names, item UUIDs, asset UUIDs, and types. + * + * Script start, stop, reset. - OpenSim/Region/ScriptEngine/Interfaces/IScriptEngine.cs + * + * Scripts and users have to be able to start, stop, and reset scripts. + * Users have to be able to see the start / stopped status of scripts from the viewer. + * Which means if the script does it, we have to tell OpenSim. + * Naturally, if OpenSim does it, it has to tell us. + * Should be able to do both by passing textual Lua function calls between OpenSim and LuaSL. + * + * Event handling, llDetect*() functions. + * + * OpenSim will generate events at random times and send us relevant information for llDetect*() functions and the handler calls. + * These should come through the scripts main loop eventually. + * + * Send messages from / to OpenSim and ROBUST. + * + * ROBUST uses HTTP for the communications, and some sort of XML, probably XML-RPC. + * OpenSim has some sort of generic mechanism for talking to script engines in C#. I want to turn that into a socket based, pass textual Lua function calls, type system. + * That assumes C# has some sort of semi decent introspection or reflection system. + * After a minimum of research, I have come to the conclusion that C# has suitable introspection, and will go ahead with my plans, leaving that problem to the C# coders. + * Looks like OpenSim is already using a bit of C# introspection for ll*() functions anyway. + * The scripts main loop can already deal with incoming commands as a string with a Lua function call in it. + * + * Send messages from / to Lua or C, and wait or not wait. + * + * Luaproc channels are distinct from Lua states, but some Lua state has to create them before they can be used. + * On the other hand, looks like broadcasting messages is not really catered for, it's first come first served. + * luaproc.send() can send multiple messages to a single channel. It blocks if no one is listening. + * This was done to simplify things for the luaproc programmers, who suggest creating more Lua states to deal with asynchronous message sending. + * luaprog.receive() gets a channel message. It can block waiting, or not block. + * I already hacked up C code to send and not block. I might have broken the luaproc.send() ability to send multiple messages. + * Before I hacked it up, actual message sending was done by copying the contents of the sending Lua states stack to the receiver states stack. + * This is the simple method for the luaproc programmers, as both states are in the context of a luaproc call, so both stacks are available. + * My hacked up version either takes one message from the sender, or is passed one from C. The C call just returns if there is no one waiting on that channel. + * luaproc.send() calls that C function after taking a single message from the stack, and block waits as usual if the C call cannot deliver. + * Don't think there is C to receive messages, luaproc seems to be lacking entirely in C side API. + * NOTE - Sending from C means that the message goes nowhere if no one is waiting for it. + * SOOOO, we may need to queue messages to. + * Just chuck them in a FIFO per channel, and destroy the FIFO when the channel get's destroyed. + * See if I can create the SID channel in C before I start the Lua state running. + * Edje messages might have to be used instead, or some hybrid. + * + * Main loop is waiting on messages, and that's the main driver. Luaproc is fine with that. Good for events. + * End of event handler - + * just wait for the next event. + * Stop a script from LSL - + * gotta find it's SID from it's name, and the prim UUID + * send the message + * wait for it to get the message - BUT we don't really want to wait. + * Stop a script from OpenSim - + * we should have it's SID from OpenSim, just send the message from C, no need to wait. + * Start a script - + * if it's stopped, it's already waiting for the message. + * if it's not stopped, then we don't care. BUT then we might be waiting for it to get the message if starting it from LSL. + * Reset a script - + * probably should be done from C anyway, and can reuse the libraries like luaproc likes to do. + * ask C to reset it. + * LSL calls a function we have to hand to OpenSim - + * send the message to C, wait. + * C eventually sends a message back. + * Sleep - + * tell C it's waiting for the wake up message. + * wait for the wake up message. + * + * C needs - + * Lua call for stop script. + * get the SID from the name, and the prim UUID. + * send the stop message to the SID. + * send something to OpenSim so it knows. + * return to Lua. + * Lua call for start script. + * get the SID from the name, and the prim UUID. + * send the start message to the SID. + * send something to OpenSim so it knows. + * return to Lua. + * Lua call for reset other script. + * get the SID from the name, and the prim UUID. + * figure out which Lua state it is. + * fall through to "reset this script", only with the script set to the found one. + * Lua call for reset this script. + * get luaproc to close this Lua state + * reload the script file + * start it again, reusing the previous Lua state, or which ever one luaproc wants to use. + * Lua call for sending a function to OpenSim. + * Lua first strings up the function call and args, with SID. + * C packs it off to opensim. + * C puts Lua state on the "waiting for message" queue if a return value is needed. + * OpenSim sends back the return value, business as usual. + * Lua call for sleep. + * setup an ecore timer callback + * put the Lua state into "waiting for message" queue. + * ecore timer callback sends the wake up message. + * + * Time and timers, plus deal with time dilation. + * + * Various LSL functions deal with time, that's no problem. + * Some might deal with time dilation, which means dealing with that info through OpenSim. + * There is a timer event, which should be done through ecore timers and whatever message system we end up with. + * Finally, a sleep call, which can be done with ecore timer and messages to. + * + * Deal directly with MySQL and SQLite databases. + * + * The script engine can run on the same computer as the sim server, that's how OpenSim does stuff. So we can directly access the database the sim server has, which gives us access to sim object metadata. + * Changing that metadata might require us to inform OpenSim about the changes. It's entirely possible that different changes do or do not need that. + * Esskyuehl may be suitable, though it's still in the prototype stage. + * + * Serialise the script state, send it somewhere. + * + * Lua can generally serialise itself as as a string of code to be executed at the destination. There might be some C side state that needs to be take care of as well. We shall see. + * + * Email, HTTP, XML-RPC? + * + * LSL has functions for using these as communications methods. We should implement them ourselves eventually, but use the existing OpenSim methods for now. + * Note that doing it ourselves may cause issues with OpenSim doing it for some other script engine. + * Azy might be suitable, but it's also in prototype. + * +*/ diff --git a/src/LuaSL/LuaSL_test.c b/src/LuaSL/LuaSL_test.c new file mode 100644 index 0000000..58d6225 --- /dev/null +++ b/src/LuaSL/LuaSL_test.c @@ -0,0 +1,467 @@ + +#include "LuaSL.h" + + +static Eina_Strbuf *clientStream; +static int scriptCount = 0; +static int compiledCount = 0; +static float compileTime = 0.0; +static struct timeval startTime; +static int timedEvent = 0; +static char *ownerKey = "12345678-1234-4321-abcd-0123456789ab"; +static char *ownerName = "onefang rejected"; + +static const char *names[] = +{ + "bub1", "sh1", + "bub2", "sh2", + "bub3", "sh3", +}; + + +static void +_edje_signal_cb(void *data, Evas_Object *obj __UNUSED__, const char *emission, const char *source) +{ +// gameGlobals *ourGlobals = data; +} + +static +Eina_Bool anim(void *data) +{ + gameGlobals *ourGlobals = data; + Evas_Object *bub, *sh; + Evas_Coord x, y, w, h, vw, vh; + double t, xx, yy, zz, r, fac; + double lx, ly; + unsigned int i; + + evas_output_viewport_get(ourGlobals->canvas, 0, 0, &vw, &vh); + r = 48; + t = ecore_loop_time_get(); + fac = 2.0 / (double)((sizeof(names) / sizeof(char *) / 2)); + evas_pointer_canvas_xy_get(ourGlobals->canvas, &x, &y); + lx = x; + ly = y; + + for (i = 0; i < (sizeof(names) / sizeof(char *) / 2); i++) + { + bub = evas_object_data_get(ourGlobals->bg, names[i * 2]); + sh = evas_object_data_get(ourGlobals->bg, names[(i * 2) + 1]); + zz = (((2 + sin(t * 6 + (M_PI * (i * fac)))) / 3) * 64) * 2; + xx = (cos(t * 4 + (M_PI * (i * fac))) * r) * 2; + yy = (sin(t * 6 + (M_PI * (i * fac))) * r) * 2; + + w = zz; + h = zz; + x = (vw / 2) + xx - (w / 2); + y = (vh / 2) + yy - (h / 2); + + evas_object_move(bub, x, y); + evas_object_resize(bub, w, h); + + x = x - ((lx - (x + (w / 2))) / 4); + y = y - ((ly - (y + (h / 2))) / 4); + + evas_object_move(sh, x, y); + evas_object_resize(sh, w, h); + evas_object_raise(sh); + evas_object_raise(bub); + } + return ECORE_CALLBACK_RENEW; +} + +static void +_on_delete(Ecore_Evas *ee __UNUSED__) +{ + ecore_main_loop_quit(); +} + +static void dirList_compile(const char *name, const char *path, void *data) +{ + gameGlobals *ourGlobals = data; + + char *ext = rindex(name, '.'); + + if (ext) + { + if (0 == strcmp(ext, ".lsl")) + { + script *me = calloc(1, sizeof(script)); + + scriptCount++; + gettimeofday(&me->startTime, NULL); + snprintf(me->SID, sizeof(me->SID), "%08lx-%04lx-%04lx-%04lx-%012lx", random(), random() % 0xFFFF, random() % 0xFFFF, random() % 0xFFFF, random()); + snprintf(me->fileName, sizeof(me->fileName), "%s/%s", path, name); + eina_hash_add(ourGlobals->scripts, me->SID, me); + sendForth(ourGlobals, me->SID, "compile(%s)", me->fileName); + } + } +} + +static Eina_Bool _timer_cb(void *data) +{ + gameGlobals *ourGlobals = data; + Eina_Iterator *scripts; + script *me; + boolean exit = FALSE; + + scripts = eina_hash_iterator_data_new(ourGlobals->scripts); + while(eina_iterator_next(scripts, (void **) &me)) + { + switch (timedEvent) + { + case 5 : + { + sendForth(ourGlobals, me->SID, "events.detectedKeys({\"%s\"})", ownerKey); + sendForth(ourGlobals, me->SID, "events.detectedNames({\"%s\"})", ownerName); + sendForth(ourGlobals, me->SID, "events.touch_start(1)"); + break; + } + case 9 : + { + sendForth(ourGlobals, me->SID, "quit()"); + break; + } + case 11 : + { + exit = TRUE; + break; + } + } + } + timedEvent++; + + if (exit) + { + sendForth(ourGlobals, ownerKey, "exit()"); + ecore_main_loop_quit(); + return ECORE_CALLBACK_CANCEL; + } + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool _add(void *data, int type __UNUSED__, Ecore_Con_Event_Server_Add *ev) +{ + gameGlobals *ourGlobals = data; + char buf[PATH_MAX]; + + ourGlobals->server = ev->server; + gettimeofday(&startTime, NULL); + snprintf(buf, sizeof(buf), "%s/media/Test sim/objects", PACKAGE_DATA_DIR); + eina_file_dir_list(buf, EINA_TRUE, dirList_compile, ourGlobals); + // Wait awhile, then start sending events for testing. + ecore_timer_add(0.5, _timer_cb, ourGlobals); + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool _data(void *data, int type __UNUSED__, Ecore_Con_Event_Server_Data *ev) +{ + gameGlobals *ourGlobals = data; + + char buf[PATH_MAX]; + char SID[PATH_MAX]; + const char *command; + char *ext; + + eina_strbuf_append_length(clientStream, ev->data, ev->size); + command = eina_strbuf_string_get(clientStream); + while ((ext = index(command, '\n'))) + { + int length = ext - command; + + strncpy(SID, command, length + 1); + SID[length] = '\0'; + eina_strbuf_remove(clientStream, 0, length + 1); + ext = index(SID, '.'); + if (ext) + { + script *me; + + ext[0] = '\0'; + command = ext + 1; + me = eina_hash_find(ourGlobals->scripts, SID); + if (0 == strncmp(command, "compilerWarning(", 16)) + { + char *temp; + char *line; + char *column; + char *text; + + strcpy(buf, &command[16]); + temp = buf; + line = temp; + while (',' != temp[0]) + temp++; + temp[0] = '\0'; + column = ++temp; + while (',' != temp[0]) + temp++; + temp[0] = '\0'; + text = ++temp; + while (')' != temp[0]) + temp++; + temp[0] = '\0'; + PW("%s @ line %s, column %s.", text, line, column); + if (me) + me->warnings++; + } + else if (0 == strncmp(command, "compilerError(", 14)) + { + char *temp; + char *line; + char *column; + char *text; + + strcpy(buf, &command[14]); + temp = buf; + line = temp; + while (',' != temp[0]) + temp++; + temp[0] = '\0'; + column = ++temp; + while (',' != temp[0]) + temp++; + temp[0] = '\0'; + text = ++temp; + while (')' != temp[0]) + temp++; + temp[0] = '\0'; + PE("%s @ line %s, column %s.", text, line, column); + if (me) + me->bugs++; + } + else if (0 == strcmp(command, "compiled(false)")) + PE("The compile of %s failed!", SID); + else if (0 == strcmp(command, "compiled(true)")) + { + if (me) + { + struct timeval now; + + me->compileTime = timeDiff(&now, &me->startTime); + me->running = TRUE; + compiledCount++; + compileTime += me->compileTime; +// PD("Average compile speed is %f scripts per second", compiledCount / compileTime); + if (compiledCount == scriptCount) + PD("TOTAL compile speed is %f scripts per second", compiledCount / timeDiff(&now, &startTime)); + } +// PD("The compile of %s worked, running it now.", SID); + sendForth(ourGlobals, SID, "run()"); + } + else + { + // Send back some random or fixed values for testing. + if (0 == strcmp(command, "llGetKey()")) + sendForth(ourGlobals, SID, "return \"%08lx-%04lx-%04lx-%04lx-%012lx\"", random(), random() % 0xFFFF, random() % 0xFFFF, random() % 0xFFFF, random()); + else if (0 == strcmp(command, "llGetOwner()")) + sendForth(ourGlobals, SID, "return \"%s\"", ownerKey); + else if (0 == strcmp(command, "llGetPos()")) + sendForth(ourGlobals, SID, "return {x=128.0, y=128.0, z=128.0}"); + else if (0 == strcmp(command, "llGetRot()")) + sendForth(ourGlobals, SID, "return {x=0.0, y=0.0, z=0.0, s=1.0}"); + else if (0 == strcmp(command, "llGetObjectDesc()")) + sendForth(ourGlobals, SID, "return \"\""); + else if (0 == strncmp(command, "llGetAlpha(", 11)) + sendForth(ourGlobals, SID, "return 1.0"); + else if (0 == strcmp(command, "llGetInventoryNumber(7)")) + sendForth(ourGlobals, SID, "return 3"); + else if (0 == strcmp(command, "llGetInventoryName(7, 2)")) + sendForth(ourGlobals, SID, "return \".readme\""); + else if (0 == strcmp(command, "llGetInventoryName(7, 1)")) + sendForth(ourGlobals, SID, "return \".POSITIONS\""); + else if (0 == strcmp(command, "llGetInventoryName(7, 0)")) + sendForth(ourGlobals, SID, "return \".MENUITEMS\""); + else + PI("Script %s sent command %s", SID, command); + } + } + + // Get the next blob to check it. + command = eina_strbuf_string_get(clientStream); + } + + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool _del(void *data, int type __UNUSED__, Ecore_Con_Event_Server_Del *ev) +{ + gameGlobals *ourGlobals = data; + + if (ev->server) + { + ourGlobals->server = NULL; + ecore_con_server_del(ev->server); + if (!ourGlobals->ui) + ecore_main_loop_quit(); + } + + return ECORE_CALLBACK_RENEW; +} + +int main(int argc, char **argv) +{ + /* put here any init specific to this app like parsing args etc. */ + gameGlobals ourGlobals; + char *programName = argv[0]; + boolean badArgs = FALSE; + int result = EXIT_FAILURE; + + memset(&ourGlobals, 0, sizeof(gameGlobals)); + ourGlobals.address = "127.0.0.1"; + ourGlobals.port = 8211; + + if (eina_init()) + { + ourGlobals.logDom = loggingStartup("LuaSL_test", ourGlobals.logDom); + ourGlobals.scripts = eina_hash_string_superfast_new(NULL); + + if (ecore_con_init()) + { + if ((ourGlobals.server = ecore_con_server_connect(ECORE_CON_REMOTE_TCP, ourGlobals.address, ourGlobals.port, &ourGlobals))) + { + ecore_event_handler_add(ECORE_CON_EVENT_SERVER_ADD, (Ecore_Event_Handler_Cb) _add, &ourGlobals); + ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA, (Ecore_Event_Handler_Cb) _data, &ourGlobals); + ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL, (Ecore_Event_Handler_Cb) _del, &ourGlobals); + clientStream = eina_strbuf_new(); + + if (ecore_evas_init()) + { + if (edje_init()) + { + // get the arguments passed in + while (--argc > 0 && *++argv != '\0') + { + if (*argv[0] == '-') + { + // point to the characters after the '-' sign + char *s = argv[0] + 1; + + switch (*s) + { + case 'u': + { + ourGlobals.ui = TRUE; + break; + } + default: + badArgs = TRUE; + } + } + else + badArgs = TRUE; + } + + if (badArgs) + { + // display the program usage to the user as they have it wrong + printf("Usage: %s [-u]\n", programName); + printf(" -u: Show the test UI.\n"); + } + else + // else if ((ourGlobals.config) && (ourGlobals.data)) + { + unsigned int i; + Evas_Object *bub, *sh; + Ecore_Animator *ani; + char *group = "main"; + char buf[PATH_MAX]; + + if (ourGlobals.ui) + { + /* this will give you a window with an Evas canvas under the first engine available */ + ourGlobals.ee = ecore_evas_new(NULL, 0, 0, WIDTH, HEIGHT, NULL); + if (!ourGlobals.ee) + { + PEm("You got to have at least one evas engine built and linked up to ecore-evas for this example to run properly."); + edje_shutdown(); + ecore_evas_shutdown(); + return -1; + } + ourGlobals.canvas = ecore_evas_get(ourGlobals.ee); + ecore_evas_title_set(ourGlobals.ee, "LuaSL test harness"); + ecore_evas_show(ourGlobals.ee); + + ourGlobals.bg = evas_object_rectangle_add(ourGlobals.canvas); + evas_object_color_set(ourGlobals.bg, 255, 255, 255, 255); /* white bg */ + evas_object_move(ourGlobals.bg, 0, 0); /* at canvas' origin */ + evas_object_size_hint_weight_set(ourGlobals.bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_resize(ourGlobals.bg, WIDTH, HEIGHT); /* covers full canvas */ + evas_object_show(ourGlobals.bg); + ecore_evas_object_associate(ourGlobals.ee, ourGlobals.bg, ECORE_EVAS_OBJECT_ASSOCIATE_BASE); + evas_object_focus_set(ourGlobals.bg, EINA_TRUE); + + ourGlobals.edje = edje_object_add(ourGlobals.canvas); + snprintf(buf, sizeof(buf), "%s/media/%s.edj", PACKAGE_DATA_DIR, "LuaSL"); + if (!edje_object_file_set(ourGlobals.edje, buf, group)) + { + int err = edje_object_load_error_get(ourGlobals.edje); + const char *errmsg = edje_load_error_str(err); + PEm("Could not load '%s' from %s: %s\n", group, buf, errmsg); + + evas_object_del(ourGlobals.edje); + ecore_evas_free(ourGlobals.ee); + edje_shutdown(); + ecore_evas_shutdown(); + return -2; + } + evas_object_move(ourGlobals.edje, 0, 0); + evas_object_resize(ourGlobals.edje, WIDTH, HEIGHT); + evas_object_show(ourGlobals.edje); + + snprintf(buf, sizeof(buf), "%s/media/bubble_sh.png", PACKAGE_DATA_DIR); + for (i = 0; i < (sizeof(names) / sizeof(char *) / 2); i++) + { + sh = evas_object_image_filled_add(ourGlobals.canvas); + evas_object_image_file_set(sh, buf, NULL); + evas_object_resize(sh, 64, 64); + evas_object_show(sh); + evas_object_data_set(ourGlobals.bg, names[(i * 2) + 1], sh); + } + + snprintf(buf, sizeof(buf), "%s/media/bubble.png", PACKAGE_DATA_DIR); + for (i = 0; i < (sizeof(names) / sizeof(char *) / 2); i++) + { + bub = evas_object_image_filled_add(ourGlobals.canvas); + evas_object_image_file_set(bub, buf, NULL); + evas_object_resize(bub, 64, 64); + evas_object_show(bub); + evas_object_data_set(ourGlobals.bg, names[(i * 2)], bub); + } + ani = ecore_animator_add(anim, &ourGlobals); + evas_object_data_set(ourGlobals.bg, "animator", ani); + + // Setup our callbacks. + ecore_evas_callback_delete_request_set(ourGlobals.ee, _on_delete); + edje_object_signal_callback_add(ourGlobals.edje, "*", "game_*", _edje_signal_cb, &ourGlobals); + } + + ecore_main_loop_begin(); + if (ourGlobals.ui) + { + ecore_animator_del(ani); + ecore_evas_free(ourGlobals.ee); + } + } + + edje_shutdown(); + } + else + PCm("Failed to init edje!"); + ecore_evas_shutdown(); + } + else + PCm("Failed to init ecore_evas!"); + } + else + PCm("Failed to connect to server!"); + ecore_con_shutdown(); + } + else + PCm("Failed to init ecore_con!"); + } + else + fprintf(stderr, "Failed to init eina!"); + + return result; +} diff --git a/src/LuaSL/LuaSL_threads.c b/src/LuaSL/LuaSL_threads.c new file mode 100644 index 0000000..234a7c5 --- /dev/null +++ b/src/LuaSL/LuaSL_threads.c @@ -0,0 +1,496 @@ +/* This code is heavily based on luaproc. + * + * The luaproc copyright notice and license is - + + *************************************************** + +Copyright 2008 Alexandre Skyrme, Noemi Rodriguez, Roberto Ierusalimschy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + **************************************************** + * + * Additions and changes Copyright 2012 by David Seikel, using the above license. + */ + + +/* This is a redesign of luaproc. The design goals and notes - + * + * In general use EFL where it is useful. + * Probably one fixed unique message channel per object, which each script in the object shares. + * But might be better to handle that C side anyway. + * Better integration with LuaSL. + * Use ecore threads instead of raw pthreads. + * Ecore threads pretty much wraps pthreads on posix, but has Windows support to. + * Merge in the edje Lua code, and keep an eye on that, coz we might want to actually add this to edje Lua in the future. + * Use my coding standards, or EFL ones. Pffft. + * + */ + +#include "LuaSL.h" + + +/* ready process queue insertion status */ +#define LUAPROC_SCHED_QUEUE_PROC_OK 0 +//#define LUAPROC_SCHED_QUEUE_PROC_ERR -1 + +/* process is idle */ +#define LUAPROC_STAT_IDLE 0 +/* process is ready to run */ +#define LUAPROC_STAT_READY 1 +/* process is blocked on send */ +#define LUAPROC_STAT_BLOCKED_SEND 2 +/* process is blocked on receive */ +#define LUAPROC_STAT_BLOCKED_RECV 3 + + +typedef struct +{ + Eina_Clist node; + lua_State *L; +} recycled; + +/********* +* globals +*********/ + +/* ready process list */ +Eina_Clist lpready; + +/* ready process queue access mutex */ +pthread_mutex_t mutex_queue_access = PTHREAD_MUTEX_INITIALIZER; + +/* wake worker up conditional variable */ +pthread_cond_t cond_wakeup_worker = PTHREAD_COND_INITIALIZER; + +/* active luaproc count access mutex */ +pthread_mutex_t mutex_lp_count = PTHREAD_MUTEX_INITIALIZER; + +/* no active luaproc conditional variable */ +pthread_cond_t cond_no_active_lp = PTHREAD_COND_INITIALIZER; + +/* number of active luaprocs */ +int lpcount = 0; + +/* no more lua processes flag */ +int no_more_processes = FALSE; + +/* channel operations mutex */ +pthread_mutex_t mutex_channel = PTHREAD_MUTEX_INITIALIZER; + +/* recycle list mutex */ +pthread_mutex_t mutex_recycle_list = PTHREAD_MUTEX_INITIALIZER; + +/* recycled lua process list */ +Eina_Clist recyclelp; + + +/****************************** +* library functions prototypes +******************************/ +/* send a message to a lua process */ +static int luaproc_send( lua_State *L ); +/* receive a message from a lua process */ +static int luaproc_receive( lua_State *L ); +/* send a message back to the main loop */ +static int luaproc_send_back( lua_State *L ); + +/* luaproc function registration array - main (parent) functions */ +static const struct luaL_reg luaproc_funcs_parent[] = { + { "sendback", luaproc_send_back }, + { NULL, NULL } +}; + +/* luaproc function registration array - newproc (child) functions */ +static const struct luaL_reg luaproc_funcs_child[] = { + { "send", luaproc_send }, + { "receive", luaproc_receive }, + { "sendback", luaproc_send_back }, + { NULL, NULL } +}; + + +/* increase active lua process count */ +static void sched_lpcount_inc(void) +{ + pthread_mutex_lock(&mutex_lp_count); + lpcount++; + pthread_mutex_unlock(&mutex_lp_count); +} + +/* decrease active lua process count */ +static void sched_lpcount_dec(void) +{ + pthread_mutex_lock(&mutex_lp_count); + lpcount--; + /* if count reaches zero, signal there are no more active processes */ + if (lpcount == 0) + pthread_cond_signal(&cond_no_active_lp); + pthread_mutex_unlock(&mutex_lp_count); +} + +/* worker thread main function */ +static void *workermain( void *args ) { + + script *lp; + int procstat; + + /* detach thread so resources are freed as soon as thread exits (no further joining) */ + pthread_detach( pthread_self( )); + + /* main worker loop */ + while ( 1 ) { + + /* get exclusive access to the ready process queue */ + pthread_mutex_lock( &mutex_queue_access ); + + /* wait until instructed to wake up (because there's work to do or because its time to finish) */ + while (( eina_clist_count( &lpready ) == 0 ) && ( no_more_processes == FALSE )) { + pthread_cond_wait( &cond_wakeup_worker, &mutex_queue_access ); + } + + /* pop the first node from the ready process queue */ + if ((lp = (script *) eina_clist_head(&lpready))) + eina_clist_remove(&(lp->node)); + else { + /* free access to the process ready queue */ + pthread_mutex_unlock( &mutex_queue_access ); + /* finished thread */ + pthread_exit( NULL ); + } + + /* free access to the process ready queue */ + pthread_mutex_unlock( &mutex_queue_access ); + + /* execute the lua code specified in the lua process struct */ + procstat = lua_resume(lp->L, lp->args); + /* reset the process argument count */ + lp->args = 0; + + /* check if process finished its whole execution, then recycle it */ + if (procstat == 0) + { + recycled *trash = malloc(sizeof(recycled)); + + if (trash) + { + trash->L = lp->L; + pthread_mutex_lock(&mutex_recycle_list); + eina_clist_add_tail(&recyclelp, &(trash->node)); + pthread_mutex_unlock(&mutex_recycle_list); + sched_lpcount_dec(); + } + lua_close(lp->L); + if (lp->timer) + ecore_timer_del(lp->timer); + free(lp); + } + + /* check if process yielded */ + else if ( procstat == LUA_YIELD ) { + + /* if so, further check if yield originated from an unmatched send/recv operation */ + if (lp->status == LUAPROC_STAT_BLOCKED_SEND) + { + } + else if (lp->status == LUAPROC_STAT_BLOCKED_RECV) + { + } + /* or if yield resulted from an explicit call to coroutine.yield in the lua code being executed */ + else + { + /* get exclusive access to the ready process queue */ + pthread_mutex_lock( &mutex_queue_access ); + /* re-insert the job at the end of the ready process queue */ + eina_clist_add_tail(&lpready, &(lp->node)); + /* free access to the process ready queue */ + pthread_mutex_unlock( &mutex_queue_access ); + } + } + /* check if there was any execution error (LUA_ERRRUN, LUA_ERRSYNTAX, LUA_ERRMEM or LUA_ERRERR) */ + else + { + /* print error message */ + fprintf( stderr, "close lua_State (error: %s)\n", luaL_checkstring(lp->L, -1 )); + /* close lua state */ + lua_close(lp->L); + /* decrease active lua process count */ + sched_lpcount_dec(); + } + } +} + +/* move process to ready queue (ie, schedule process) */ +static int sched_queue_proc( script *lp ) { + + /* get exclusive access to the ready process queue */ + pthread_mutex_lock( &mutex_queue_access ); + + /* add process to ready queue */ + eina_clist_add_tail(&lpready, &(lp->node)); + + lp->status = LUAPROC_STAT_READY; + + /* wake worker up */ + pthread_cond_signal( &cond_wakeup_worker ); + /* free access to the process ready queue */ + pthread_mutex_unlock( &mutex_queue_access ); + + return LUAPROC_SCHED_QUEUE_PROC_OK; +} + +/* synchronize worker threads */ +void sched_join_workerthreads( void ) { + + pthread_mutex_lock( &mutex_lp_count ); + + /* wait until there is no more active lua processes */ + while( lpcount != 0 ) { + pthread_cond_wait( &cond_no_active_lp, &mutex_lp_count ); + } + /* get exclusive access to the ready process queue */ + pthread_mutex_lock( &mutex_queue_access ); + /* set the no more active lua processes flag to true */ + no_more_processes = TRUE; + /* wake ALL workers up */ + pthread_cond_broadcast( &cond_wakeup_worker ); + /* free access to the process ready queue */ + pthread_mutex_unlock( &mutex_queue_access ); + +// We don't need this, as we only get here during shutdown. Linking this to EFL results in a hang otherwise anyway. + /* wait for (join) worker threads */ +// pthread_exit( NULL ); + + pthread_mutex_unlock( &mutex_lp_count ); + +} + +/* create a new worker pthread */ +int sched_create_worker(void) +{ + pthread_t worker; + + /* create a new pthread */ + if (pthread_create( &worker, NULL, workermain, NULL ) != 0) + return LUAPROC_SCHED_PTHREAD_ERROR; + return LUAPROC_SCHED_OK; +} + +void newProc(const char *code, int file, script *lp) +{ + int ret; + recycled *trash; + + // Try to recycle a Lua state, otherwise create one from scratch. + pthread_mutex_lock(&mutex_recycle_list); + /* pop list head */ + if ((trash = (recycled *) eina_clist_head(&recyclelp))) + { + eina_clist_remove(&(trash->node)); + lp->L = trash->L; + free(trash); + } + pthread_mutex_unlock(&mutex_recycle_list); + + if (NULL == lp->L) + { + lp->L = luaL_newstate(); + + /* store the script struct in its own Lua state */ + lua_pushlightuserdata(lp->L, lp); + lua_setfield(lp->L, LUA_REGISTRYINDEX, "_SELF"); + luaL_openlibs(lp->L); + luaL_register(lp->L, "luaproc", luaproc_funcs_child); + } + + lp->status = LUAPROC_STAT_IDLE; + lp->args = 0; + eina_clist_element_init(&(lp->node)); + eina_clist_init(&(lp->messages)); + + /* load process' code */ + if (file) + ret = luaL_loadfile(lp->L, code); + else + ret = luaL_loadstring(lp->L, code); + + /* in case of errors, destroy Lua process */ + if (ret != 0) + { + lua_close(lp->L); + lp->L = NULL; + } + + if (lp->L) + { + sched_lpcount_inc(); + + /* schedule luaproc */ + if (sched_queue_proc(lp) != LUAPROC_SCHED_QUEUE_PROC_OK) + { + printf( "[luaproc] error queueing Lua process\n" ); + sched_lpcount_dec(); + lua_close(lp->L); + } + } +} + +/* return the lua process associated with a given lua state */ +static script *luaproc_getself(lua_State *L) +{ + script *lp; + + lua_getfield(L, LUA_REGISTRYINDEX, "_SELF"); + lp = (script *) lua_touserdata(L, -1); + lua_pop(L, 1); + return lp; +} + +/* send a message to the client process */ +static int luaproc_send_back(lua_State *L) +{ + script *self = luaproc_getself(L); + const char *message = luaL_checkstring(L, 1); + + if (self) + { + scriptMessage *sm = calloc(1, sizeof(scriptMessage)); + + if (sm) + { + eina_clist_element_init(&(sm->node)); + sm->script = self; + strcpy((char *) sm->message, message); + ecore_main_loop_thread_safe_call_async(scriptSendBack, sm); + } + } + + return 0; +} + +/* error messages for the sendToChannel function */ +const char *sendToChannelErrors[] = +{ + "non-existent channel", + "error scheduling process" +}; + +/* send a message to a lua process */ +const char *sendToChannel(gameGlobals *ourGlobals, const char *SID, const char *message) +{ + const char *result = NULL; + script *dstlp; + + /* get exclusive access to operate on channels */ + pthread_mutex_lock(&mutex_channel); + + // Add the message to the queue. + if ((dstlp = eina_hash_find(ourGlobals->scripts, SID))) + { + scriptMessage *sm = NULL; + + if ((sm = malloc(sizeof(scriptMessage)))) + { + sm->script = dstlp; + strcpy((char *) sm->message, message); + eina_clist_add_tail(&(dstlp->messages), &(sm->node)); + } + + /* if it's already waiting, send the next message to it and (queue) wake it */ + if (dstlp->status == LUAPROC_STAT_BLOCKED_RECV) + { + scriptMessage *msg = (scriptMessage *) eina_clist_head(&(dstlp->messages)); + + // See if there's a message on the queue. Note, this may not be the same as the incoming message, if there was already a queue. + if (msg) + { + eina_clist_remove(&(msg->node)); + message = msg->message; + } + /* push the message onto the receivers stack */ + lua_pushstring(dstlp->L, message); + dstlp->args = lua_gettop(dstlp->L) - 1; + if (msg) + free(msg); + + if (sched_queue_proc(dstlp) != LUAPROC_SCHED_QUEUE_PROC_OK) + { + sched_lpcount_dec(); + lua_close(dstlp->L); + result = sendToChannelErrors[1]; + } + } + } + + pthread_mutex_unlock(&mutex_channel); + + return result; +} + +/* send a message to a lua process */ +static int luaproc_send(lua_State *L) +{ + script *self = luaproc_getself(L); + const char *result = sendToChannel(self->game, luaL_checkstring(L, 1), luaL_checkstring(L, 2)); + + if (result) + { + lua_pushnil(L); + lua_pushstring(L, result); + return 2; + } + + lua_pushboolean(L, TRUE); + return 1; +} + +/* receive a message from a lua process */ +static int luaproc_receive(lua_State *L) +{ + script *self; + const char *chname = luaL_checkstring(L, 1); + scriptMessage *msg; + + // First check if there are queued messages, and grab one. + self = luaproc_getself(L); + if ((msg = (scriptMessage *) eina_clist_head(&(self->messages)))) + { + eina_clist_remove(&(msg->node)); + lua_pushstring(L, msg->message); + free(msg); + return lua_gettop(L) - 1; + } + + /* if trying an asynchronous receive, return an error */ + if ( lua_toboolean( L, 2 )) + { + lua_pushnil(L); + lua_pushfstring(L, "no senders waiting on channel %s", chname); + return 2; + } + /* otherwise (synchronous receive) simply block process */ + self->status = LUAPROC_STAT_BLOCKED_RECV; + return lua_yield(L, lua_gettop(L)); +} + +void luaprocInit(void) +{ + eina_clist_init(&recyclelp); + eina_clist_init(&lpready); +} diff --git a/src/LuaSL/LuaSL_threads.h b/src/LuaSL/LuaSL_threads.h new file mode 100644 index 0000000..9a11b5c --- /dev/null +++ b/src/LuaSL/LuaSL_threads.h @@ -0,0 +1,54 @@ +/* This code is heavily based on luaproc. + * + * The luaproc copyright notice and license is - + + *************************************************** + +Copyright 2008 Alexandre Skyrme, Noemi Rodriguez, Roberto Ierusalimschy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + **************************************************** + * + * Additions and changes Copyright 2012 by David Seikel, using the above license. + */ + +#ifndef __LUASL_THREADS_H__ +#define __LUASL_THREADS_H__ + +/* scheduler function return constants */ +#define LUAPROC_SCHED_OK 0 +#define LUAPROC_SCHED_SOCKET_ERROR -1 +#define LUAPROC_SCHED_SETSOCKOPT_ERROR -2 +#define LUAPROC_SCHED_BIND_ERROR -3 +#define LUAPROC_SCHED_LISTEN_ERROR -4 +#define LUAPROC_SCHED_FORK_ERROR -5 +#define LUAPROC_SCHED_PTHREAD_ERROR -6 +#define LUAPROC_SCHED_INIT_ERROR -7 + + +void luaprocInit(void); +int sched_create_worker(void); +void newProc(const char *code, int file, script *lp); +const char *sendToChannel(gameGlobals *ourGlobals, const char *SID, const char *message); + +/* join all worker threads and exit */ +void sched_join_workerthreads(void); + +#endif diff --git a/src/LuaSL/LuaSL_utilities.c b/src/LuaSL/LuaSL_utilities.c new file mode 100644 index 0000000..40263df --- /dev/null +++ b/src/LuaSL/LuaSL_utilities.c @@ -0,0 +1,60 @@ +#include "LuaSL.h" + + +void sendBack(gameGlobals *ourGlobals, Ecore_Con_Client *client, const char *SID, const char *message, ...) +{ + va_list args; + char buf[PATH_MAX]; + int length = strlen(SID); + + strncpy(buf, SID, length); + buf[length++] = '.'; + va_start(args, message); + length += vsprintf(&buf[length], message, args); + va_end(args); + buf[length++] = '\n'; + buf[length++] = '\0'; + ecore_con_client_send(client, buf, strlen(buf)); + ecore_con_client_flush(client); +} + +void sendForth(gameGlobals *ourGlobals, const char *SID, const char *message, ...) +{ + va_list args; + char buf[PATH_MAX]; + int length = strlen(SID); + + strncpy(buf, SID, length); + buf[length++] = '.'; + va_start(args, message); + length += vsprintf(&buf[length], message, args); + va_end(args); + buf[length++] = '\n'; + buf[length++] = '\0'; + ecore_con_server_send(ourGlobals->server, buf, strlen(buf)); + ecore_con_server_flush(ourGlobals->server); +} + +float timeDiff(struct timeval *now, struct timeval *then) +{ + if (0 == gettimeofday(now, 0)) + { + struct timeval thisTime = { 0, 0 }; + double result = 0.0; + + thisTime.tv_sec = now->tv_sec; + thisTime.tv_usec = now->tv_usec; + if (thisTime.tv_usec < then->tv_usec) + { + thisTime.tv_sec--; + thisTime.tv_usec += 1000000; + } + thisTime.tv_usec -= then->tv_usec; + thisTime.tv_sec -= then->tv_sec; + result = ((double) thisTime.tv_usec) / ((double) 1000000.0); + result += thisTime.tv_sec; + return result; + } + else + return 0.0; +} diff --git a/src/LuaSL/build.lua b/src/LuaSL/build.lua new file mode 100755 index 0000000..f86715c --- /dev/null +++ b/src/LuaSL/build.lua @@ -0,0 +1,23 @@ +#!/usr/bin/env lua + +local dir = ... + +if 'nil' == type(dir) then + local build, err = loadfile('../../build.lua') + if build then + setfenv(build, getfenv(2)) + build(2) + else + print("ERROR - " .. err) + end + dir = workingDir +end + +removeFiles(dir, {'../../LuaSL', '*.o', '*.output', '*.backup', '../../media/LuaSL.edj', 'LuaSL_lexer.h', 'LuaSL_lexer.c', 'LuaSL_lemon_yaccer.h', 'LuaSL_lemon_yaccer.c', 'LuaSL_lemon_yaccer.out'}) + +-- Run lemon first, flex depends on it to define the symbol values. +runCommand('lemon', dir, '../../libraries/lemon/lemon -s -T../../libraries/lemon/lempar.c LuaSL_lemon_yaccer.y') +runCommand('flex', dir, 'flex -C --outfile=LuaSL_lexer.c --header-file=LuaSL_lexer.h LuaSL_lexer.l') +runCommand('edje_cc', dir, 'edje_cc ' .. EDJE_FLAGS .. ' LuaSL.edc ../../media/LuaSL.edj') +compileFiles('../../LuaSL', dir, {'LuaSL_main', 'LuaSL_compile', 'LuaSL_threads', 'LuaSL_utilities', 'LuaSL_lexer', 'LuaSL_lemon_yaccer'}) +compileFiles('LuaSL_test', dir, {'LuaSL_test', 'LuaSL_utilities'}) diff --git a/src/LuaSL/test.sh b/src/LuaSL/test.sh new file mode 100755 index 0000000..1c26ade --- /dev/null +++ b/src/LuaSL/test.sh @@ -0,0 +1,27 @@ +#! /bin/bash + +wd=$(pwd) + +# Kill any left overs. +killall -KILL LuaSL +export LUA_PATH="$wd/../../libraries/?.lua" + +case $@ in + + ddd) + ddd ../../LuaSL + ;; + + gdb) + gdb ../../LuaSL + ;; + + *) + echo "_______________ STARTING LuaSL _______________" + ../../LuaSL & + sleep 1 + echo "_______________ STARTING LuaSL_test _______________" + ./LuaSL_test + ;; + +esac -- cgit v1.1