initial implementation of RainerScript functions & strlen()
authorRainer Gerhards <rgerhards@adiscon.com>
Tue, 10 Mar 2009 16:37:13 +0000 (17:37 +0100)
committerRainer Gerhards <rgerhards@adiscon.com>
Tue, 10 Mar 2009 16:37:13 +0000 (17:37 +0100)
- implemented function support in RainerScript. That means the engine
  parses and compile functions, as well as executes a few build-in
  ones. Dynamic loading and registration of functions is not yet
  supported - but we now have a good foundation to do that later on.
  NOTE: nested function calls are not yet supported due to a design
  issue with the function call VM instruction set design.
- implemented the strlen() RainerScript function

ChangeLog
runtime/conf.c
runtime/ctok.c
runtime/expr.c
runtime/rsyslog.h
runtime/vm.c
runtime/vmop.c
runtime/vmop.h

index f1bc354..7602bd4 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+- implemented function support in RainerScript. That means the engine
+  parses and compile functions, as well as executes a few build-in
+  ones. Dynamic loading and registration of functions is not yet
+  supported - but we now have a good foundation to do that later on.
+  NOTE: nested function calls are not yet supported due to a design
+  issue with the function call VM instruction set design.
+- implemented the strlen() RainerScript function
 ---------------------------------------------------------------------------
 Version 4.1.5  [DEVEL] (rgerhards), 2009-02-??
 - added ERE support in filter conditions
index a670c65..ede15cc 100644 (file)
@@ -794,6 +794,10 @@ dbgprintf("calling expression parser, pp %p ('%s')\n", *pline, *pline);
        CHKiRet(ctok.Getpp(tok, pline));
        CHKiRet(ctok.Destruct(&tok));
 
+       /* debug support - print vmprg after construction (uncomment to use) */
+       /* vmprgDebugPrint(f->f_filterData.f_expr->pVmprg); */
+       vmprgDebugPrint(f->f_filterData.f_expr->pVmprg);
+
        /* we now need to skip whitespace to the action part, else we confuse
         * the legacy rsyslog conf parser. -- rgerhards, 2008-02-25
         */
index ceab15b..d2cd8bb 100644 (file)
@@ -259,12 +259,17 @@ ctokGetVar(ctok_t *pThis, ctok_token_t *pToken)
        }
 
        CHKiRet(rsCStrConstruct(&pstrVal));
-       /* this loop is quite simple, a variable name is terminated by whitespace. */
-       while(!isspace(c)) {
+       /* this loop is quite simple, a variable name is terminated when a non-supported
+        * character is detected. Note that we currently permit a numerical digit as the
+        * first char, which is not permitted by ABNF. -- rgerhards, 2009-03-10
+        */
+       while(isalpha(c) || isdigit(c) || (c == '_') || (c == '-')) {
                CHKiRet(rsCStrAppendChar(pstrVal, tolower(c)));
                CHKiRet(ctokGetCharFromStream(pThis, &c));
        }
-       CHKiRet(rsCStrFinish(pStrB));
+       CHKiRet(ctokUngetCharFromStream(pThis, c)); /* put not processed char back */
+
+       CHKiRet(rsCStrFinish(pstrVal));
 
        CHKiRet(var.SetString(pToken->pVar, pstrVal));
        pstrVal = NULL;
@@ -389,6 +394,7 @@ ctokGetToken(ctok_t *pThis, ctok_token_t **ppToken)
        uchar c;
        uchar szWord[128];
        int bRetry = 0; /* retry parse? Only needed for inline comments... */
+       cstr_t *pstrVal;
 
        ISOBJ_TYPE_assert(pThis, ctok);
        ASSERT(ppToken != NULL);
@@ -512,7 +518,10 @@ ctokGetToken(ctok_t *pThis, ctok_token_t **ppToken)
                                                        /* push c back, higher level parser needs it */
                                                        CHKiRet(ctokUngetCharFromStream(pThis, c));
                                                        pToken->tok = ctok_FUNCTION;
-                                                       /* TODO: fill function name */
+                                                       /* fill function name */
+                                                       CHKiRet(rsCStrConstruct(&pstrVal));
+                                                       CHKiRet(rsCStrSetSzStr(pstrVal, szWord));
+                                                       CHKiRet(var.SetString(pToken->pVar, pstrVal));
                                                } else { /* give up... */
                                                        dbgprintf("parser has an invalid word (token) '%s'\n", szWord);
                                                        pToken->tok = ctok_INVALID;
index ee5b9e2..38ed1c6 100644 (file)
@@ -55,11 +55,63 @@ DEFobjCurrIf(ctok)
  * rgerhards, 2008-02-19
  */
 
-/* forward definiton - thanks to recursive ABNF, we can not avoid at least one ;) */
+/* forward definition - thanks to recursive ABNF, we can not avoid at least one ;) */
 static rsRetVal expr(expr_t *pThis, ctok_t *tok);
 
 
 static rsRetVal
+function(expr_t *pThis, ctok_t *tok)
+{
+       DEFiRet;
+       ctok_token_t *pToken = NULL;
+       int iNumArgs = 0;
+       var_t *pVar;
+
+       ISOBJ_TYPE_assert(pThis, expr);
+       ISOBJ_TYPE_assert(tok, ctok);
+
+       CHKiRet(ctok.GetToken(tok, &pToken));
+       /* note: pToken is destructed in finalize_it */
+
+       if(pToken->tok == ctok_LPAREN) {
+               CHKiRet(ctok_token.Destruct(&pToken)); /* token processed, "eat" it */
+               CHKiRet(ctok.GetToken(tok, &pToken)); /* get next one */
+       } else
+               ABORT_FINALIZE(RS_RET_FUNC_NO_LPAREN);
+
+       /* we first push all the params on the stack. Then we call the function */
+       while(pToken->tok != ctok_RPAREN) { 
+               ++iNumArgs;
+               CHKiRet(ctok.UngetToken(tok, pToken)); /* not for us, so let others process it */
+               CHKiRet(expr(pThis, tok));
+               CHKiRet(ctok.GetToken(tok, &pToken)); /* get next one, needed for while() check */
+               if(pToken->tok == ctok_COMMA) {
+                       CHKiRet(ctok_token.Destruct(&pToken)); /* token processed, "eat" it */
+                       CHKiRet(ctok.GetToken(tok, &pToken)); /* get next one */
+                       if(pToken->tok == ctok_RPAREN) {
+                               ABORT_FINALIZE(RS_RET_FUNC_MISSING_EXPR);
+                       }
+               }
+       }
+
+
+       /* now push number of arguments - this must be on top of the stack */
+       CHKiRet(var.Construct(&pVar));
+       CHKiRet(var.ConstructFinalize(pVar));
+       CHKiRet(var.SetNumber(pVar, iNumArgs));
+       CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_PUSHCONSTANT, pVar)); /* add to program */
+
+
+finalize_it:
+       if(pToken != NULL) {
+               ctok_token.Destruct(&pToken); /* "eat" processed token */
+       }
+
+       RETiRet;
+}
+
+
+static rsRetVal
 terminal(expr_t *pThis, ctok_t *tok)
 {
        DEFiRet;
@@ -85,8 +137,12 @@ terminal(expr_t *pThis, ctok_t *tok)
                        break;
                case ctok_FUNCTION:
                        dbgoprint((obj_t*) pThis, "function\n");
-                       /* TODO: vm: call - well, need to implement that first */
-                       ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED);
+                       CHKiRet(function(pThis, tok)); /* this creates the stack call frame */
+                       /* ... but we place the call instruction onto the stack ourselfs (because
+                        * we have all relevant information)
+                        */
+                       CHKiRet(ctok_token.UnlinkVar(pToken, &pVar));
+                       CHKiRet(vmprg.AddVarOperation(pThis->pVmprg, opcode_FUNC_CALL, pVar)); /* add to program */
                        break;
                case ctok_MSGVAR:
                        dbgoprint((obj_t*) pThis, "MSGVAR\n");
@@ -406,6 +462,7 @@ ENDobjQueryInterface(expr)
  */
 BEGINObjClassInit(expr, 1, OBJ_IS_CORE_MODULE) /* class, version */
        /* request objects we use */
+       CHKiRet(objUse(var, CORE_COMPONENT));
        CHKiRet(objUse(vmprg, CORE_COMPONENT));
        CHKiRet(objUse(var, CORE_COMPONENT));
        CHKiRet(objUse(ctok_token, CORE_COMPONENT));
index 00290ee..899f5e1 100644 (file)
@@ -254,6 +254,10 @@ enum rsRetVal_                             /** return value. All methods return this if not specified oth
        RS_RET_INVLD_TIME = -2107, /**< invalid timestamp (e.g. could not be parsed) */
        RS_RET_NO_ZIP = -2108, /**< ZIP functionality is not present */
        RS_RET_CODE_ERR = -2109, /**< program code (internal) error */
+       RS_RET_FUNC_NO_LPAREN = -2110, /**< left parenthesis missing after function call (rainerscript) */
+       RS_RET_FUNC_MISSING_EXPR = -2111, /**< no expression after comma in function call (rainerscript) */
+       RS_RET_INVLD_NBR_ARGUMENTS = -2112, /**< invalid number of arguments for function call (rainerscript) */
+       RS_RET_INVLD_FUNC = -2113, /**< invalid function name for function call (rainerscript) */
 
        /* RainerScript error messages (range 1000.. 1999) */
        RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */
index bc6c3dd..113a9d5 100644 (file)
@@ -331,6 +331,33 @@ finalize_it:
 ENDop(PUSHSYSVAR)
 
 
+/* The function call operation is only very roughly implemented. While the plumbing
+ * to reach this instruction is fine, the instruction itself currently supports only
+ * functions with a single argument AND with a name that we know.
+ * TODO: later, we can add here the real logic, that involves looking up function
+ * names, loading them dynamically ... and all that...
+ * implementation begun 2009-03-10 by rgerhards
+ */
+BEGINop(FUNC_CALL) /* remember to set the instruction also in the ENDop macro! */
+       var_t *numOperands;
+       var_t *operand1;
+       int iStrlen;
+CODESTARTop(FUNC_CALL)
+       vmstk.PopNumber(pThis->pStk, &numOperands);
+       if(numOperands->val.num != 1)
+               ABORT_FINALIZE(RS_RET_INVLD_NBR_ARGUMENTS);
+       vmstk.PopString(pThis->pStk, &operand1); /* guess there's just one ;) */
+       if(!rsCStrSzStrCmp(pOp->operand.pVar->val.pStr, (uchar*) "strlen", 6)) { /* only one supported so far ;) */
+RUNLOG_VAR("%s", rsCStrGetSzStr(operand1->val.pStr));
+               iStrlen = strlen((char*) rsCStrGetSzStr(operand1->val.pStr));
+RUNLOG_VAR("%d", iStrlen);
+       } else
+               ABORT_FINALIZE(RS_RET_INVLD_FUNC);
+       PUSHRESULTop(operand1, iStrlen); // TODO: dummy, FIXME
+finalize_it:
+ENDop(FUNC_CALL)
+
+
 /* ------------------------------ end instruction set implementation ------------------------------ */
 
 
@@ -412,6 +439,7 @@ execProg(vm_t *pThis, vmprg_t *pProg)
                        doOP(DIV);
                        doOP(MOD);
                        doOP(UNARY_MINUS);
+                       doOP(FUNC_CALL);
                        default:
                                ABORT_FINALIZE(RS_RET_INVALID_VMOP);
                                dbgoprint((obj_t*) pThis, "invalid instruction %d in vmprg\n", pCurrOp->opcode);
index fcacb15..705acdf 100644 (file)
@@ -217,6 +217,9 @@ vmopOpcode2Str(vmop_t *pThis, uchar **ppName)
                case opcode_STRADD:
                        *ppName = (uchar*) "STRADD";
                        break;
+               case opcode_FUNC_CALL:
+                       *ppName = (uchar*) "FUNC_CALL";
+                       break;
                default:
                        *ppName = (uchar*) "INVALID opcode";
                        break;
index c3d5d5f..938b08f 100644 (file)
@@ -59,17 +59,44 @@ typedef enum {       /* do NOT start at 0 to detect uninitialized types after calloc(
        opcode_PUSHMSGVAR      = 1002,   /* requires var operand */
        opcode_PUSHCONSTANT    = 1003,   /* requires var operand */
        opcode_UNARY_MINUS     = 1010,
-       opcode_END_PROG        = 1011
+       opcode_FUNC_CALL       = 1012,
+       opcode_END_PROG        = 2000
 } opcode_t;
 
 
+/* Additional doc, operation specific
+
+  FUNC_CALL
+  All parameter passing is via the stack. Parameters are placed onto the stack in reverse order,
+  that means the last parameter is on top of the stack, the first at the bottom location. 
+  At the actual top of the stack is the number of parameters. This permits functions to be
+  called with variable number of arguments. The function itself is responsible for poping
+  the right number of parameters of the stack and complaining if the number is incorrect.
+  On exit, a single return value must be pushed onto the stack. The FUNC_CALL operation 
+  is generic. Its pVar argument contains the function name string (TODO: very slow, make
+  faster in later releases).
+
+  Sample Function call:  sampleFunc(p1, p2, p3) ; returns number 4711 (sample)
+  Stacklayout on entry (order is top to bottom):
+  3
+  p3
+  p2
+  p1
+  ... other vars ...
+
+  Stack on exit
+  4711
+  ... other vars ...
+  
+ */
+
+
 /* the vmop object */
 typedef struct vmop_s {
        BEGINobjInstance;       /* Data to implement generic object - MUST be the first data element! */
        opcode_t opcode;
        union {
-               var_t *pVar;
-               /* TODO: add function pointer */
+               var_t *pVar;    /* for function call, this is the name (string) of function to be called */
        } operand;
        struct vmop_s *pNext; /* next operation or NULL, if end of program (logically this belongs to vmprg) */
 } vmop_t;