From e8499c6d33d09f6d8b42df72da1661be0ef0f088 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Tue, 10 Mar 2009 17:37:13 +0100 Subject: initial implementation of RainerScript functions & strlen() - 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 --- runtime/conf.c | 4 ++++ runtime/ctok.c | 17 +++++++++++---- runtime/expr.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- runtime/rsyslog.h | 4 ++++ runtime/vm.c | 28 +++++++++++++++++++++++++ runtime/vmop.c | 3 +++ runtime/vmop.h | 33 ++++++++++++++++++++++++++--- 7 files changed, 142 insertions(+), 10 deletions(-) (limited to 'runtime') diff --git a/runtime/conf.c b/runtime/conf.c index a670c65b..ede15cc7 100644 --- a/runtime/conf.c +++ b/runtime/conf.c @@ -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 */ diff --git a/runtime/ctok.c b/runtime/ctok.c index ceab15bd..d2cd8bbd 100644 --- a/runtime/ctok.c +++ b/runtime/ctok.c @@ -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; diff --git a/runtime/expr.c b/runtime/expr.c index ee5b9e2c..38ed1c68 100644 --- a/runtime/expr.c +++ b/runtime/expr.c @@ -55,10 +55,62 @@ 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) { @@ -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)); diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index 00290ee5..899f5e13 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -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) */ diff --git a/runtime/vm.c b/runtime/vm.c index bc6c3dd2..113a9d59 100644 --- a/runtime/vm.c +++ b/runtime/vm.c @@ -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); diff --git a/runtime/vmop.c b/runtime/vmop.c index fcacb15b..705acdf2 100644 --- a/runtime/vmop.c +++ b/runtime/vmop.c @@ -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; diff --git a/runtime/vmop.h b/runtime/vmop.h index c3d5d5f4..938b08fd 100644 --- a/runtime/vmop.h +++ b/runtime/vmop.h @@ -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; -- cgit