diff --git a/conf.d/javascript.conf.sample b/conf.d/javascript.conf.sample index f26de272..386fcbd3 100644 --- a/conf.d/javascript.conf.sample +++ b/conf.d/javascript.conf.sample @@ -1 +1,14 @@ [general] +; General settings for the Javascript module + +; scripts_dir: string: The absolute or relative path used by default to load +; scripts if no full path is specified +; Note that a trailing path separator should be added +;scripts_dir=share/scripts/ + + +[scripts] + +; routing: string: Name of the file holding the routing instructions +; Example: routing=route.js +;routing= diff --git a/libs/yscript/evaluator.cpp b/libs/yscript/evaluator.cpp index 0ecb7386..62313339 100644 --- a/libs/yscript/evaluator.cpp +++ b/libs/yscript/evaluator.cpp @@ -114,30 +114,44 @@ static const TokenDict s_unaryOps_sql[] = #undef MAKEOP #undef ASSIGN +RefObject* ExpExtender::refObj() +{ + return 0; +} -bool ExpExtender::runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +bool ExpExtender::hasField(ObjList& stack, const String& name, GenObject* context) const { return false; } -bool ExpExtender::runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +NamedString* ExpExtender::getField(ObjList& stack, const String& name, GenObject* context) const +{ + return 0; +} + +bool ExpExtender::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) { return false; } -bool ExpExtender::runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context) +bool ExpExtender::runField(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + return false; +} + +bool ExpExtender::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) { return false; } ExpEvaluator::ExpEvaluator(const TokenDict* operators, const TokenDict* unaryOps) - : m_operators(operators), m_unaryOps(unaryOps), m_extender(0) + : m_operators(operators), m_unaryOps(unaryOps), m_inError(false), m_extender(0) { } ExpEvaluator::ExpEvaluator(ExpEvaluator::Parser style) - : m_operators(0), m_unaryOps(0), m_extender(0) + : m_operators(0), m_unaryOps(0), m_inError(false), m_extender(0) { switch (style) { case C: @@ -153,12 +167,12 @@ ExpEvaluator::ExpEvaluator(ExpEvaluator::Parser style) ExpEvaluator::ExpEvaluator(const ExpEvaluator& original) : m_operators(original.m_operators), m_unaryOps(original.unaryOps()), - m_extender(0) + m_inError(false), m_extender(0) { extender(original.extender()); for (ObjList* l = original.m_opcodes.skipNull(); l; l = l->skipNext()) { const ExpOperation* o = static_cast(l->get()); - m_opcodes.append(new ExpOperation(*o)); + m_opcodes.append(o->clone()); } } @@ -171,18 +185,19 @@ void ExpEvaluator::extender(ExpExtender* ext) { if (ext == m_extender) return; - if (ext && !ext->ref()) + if (ext && ext->refObj() && !ext->refObj()->ref()) return; ExpExtender* tmp = m_extender; m_extender = ext; - TelEngine::destruct(tmp); + if (tmp) + TelEngine::destruct(tmp->refObj()); } char ExpEvaluator::skipWhites(const char*& expr) { if (!expr) return 0; - while (*expr==' ' || *expr=='\t') + while (*expr==' ' || *expr=='\t' || *expr=='\r' || *expr=='\n') expr++; return *expr; } @@ -193,10 +208,20 @@ bool ExpEvaluator::keywordChar(char c) const ('0' <= c && c <= '9') || (c == '_'); } +char ExpEvaluator::skipComments(const char*& expr, GenObject* context) const +{ + return skipWhites(expr); +} + +int ExpEvaluator::preProcess(const char*& expr, GenObject* context) +{ + return -1; +} + ExpEvaluator::Opcode ExpEvaluator::getOperator(const char*& expr, const TokenDict* operators, bool caseInsensitive) const { XDebug(this,DebugAll,"getOperator('%s',%p,%s)",expr,operators,String::boolText(caseInsensitive)); - skipWhites(expr); + skipComments(expr); if (operators) { bool kw = keywordChar(*expr); for (const TokenDict* o = operators; o->token; o++) { @@ -217,38 +242,49 @@ ExpEvaluator::Opcode ExpEvaluator::getOperator(const char*& expr, const TokenDic bool ExpEvaluator::gotError(const char* error, const char* text) const { - if (!error) + if (!error) { + if (!text) + return false; error = "unknown error"; - Debug(this,DebugWarn,"Evaluator got error: %s%s%s",error, + } + Debug(this,DebugWarn,"Evaluator error: %s%s%s",error, (text ? " at: " : ""), c_safe(text)); return false; } -bool ExpEvaluator::getInstruction(const char*& expr) +bool ExpEvaluator::gotError(const char* error, const char* text) +{ + m_inError = true; + return const_cast(this)->gotError(error,text); +} + +bool ExpEvaluator::getInstruction(const char*& expr, Opcode nested) { return false; } -bool ExpEvaluator::getOperand(const char*& expr) +bool ExpEvaluator::getOperand(const char*& expr, bool endOk) { + if (inError()) + return false; XDebug(this,DebugAll,"getOperand '%s'",expr); - char c = skipWhites(expr); + char c = skipComments(expr); if (!c) // end of string - return true; + return endOk; if (c == '(') { // parenthesized subexpression if (!runCompile(++expr,')')) return false; - if (skipWhites(expr) != ')') + if (skipComments(expr) != ')') return gotError("Expecting ')'",expr); expr++; return true; } Opcode op = getUnaryOperator(expr); if (op != OpcNone) { - if (!getOperand(expr)) + if (!getOperand(expr,false)) return false; addOpcode(op); return true; @@ -260,6 +296,8 @@ bool ExpEvaluator::getOperand(const char*& expr) bool ExpEvaluator::getNumber(const char*& expr) { + if (inError()) + return false; XDebug(this,DebugAll,"getNumber '%s'",expr); char* endp = 0; long int val = ::strtol(expr,&endp,0); @@ -273,8 +311,10 @@ bool ExpEvaluator::getNumber(const char*& expr) bool ExpEvaluator::getString(const char*& expr) { + if (inError()) + return false; XDebug(this,DebugAll,"getString '%s'",expr); - char c = skipWhites(expr); + char c = skipComments(expr); if (c == '"' || c == '\'') { char sep = c; const char* start = ++expr; @@ -286,6 +326,7 @@ bool ExpEvaluator::getString(const char*& expr) addOpcode(str); return true; } + expr--; return gotError("Expecting string end"); } return false; @@ -304,25 +345,27 @@ int ExpEvaluator::getKeyword(const char* str) const bool ExpEvaluator::getFunction(const char*& expr) { + if (inError()) + return false; XDebug(this,DebugAll,"getFunction '%s'",expr); - skipWhites(expr); + skipComments(expr); int len = getKeyword(expr); const char* s = expr+len; - skipWhites(expr); - if ((len <= 0) || (skipWhites(s) != '(')) + skipComments(expr); + if ((len <= 0) || (skipComments(s) != '(')) return false; s++; int argc = 0; // parameter list do { if (!runCompile(s,')')) { - if (!argc && (skipWhites(s) == ')')) + if (!argc && (skipComments(s) == ')')) break; return false; } argc++; } while (getSeparator(s,true)); - if (skipWhites(s) != ')') + if (skipComments(s) != ')') return gotError("Expecting ')' after function",s); String str(expr,len); expr = s+1; @@ -333,8 +376,10 @@ bool ExpEvaluator::getFunction(const char*& expr) bool ExpEvaluator::getField(const char*& expr) { + if (inError()) + return false; XDebug(this,DebugAll,"getField '%s'",expr); - skipWhites(expr); + skipComments(expr); int len = getKeyword(expr); if (len <= 0) return false; @@ -342,7 +387,7 @@ bool ExpEvaluator::getField(const char*& expr) return false; String str(expr,len); expr += len; - DDebug(this,DebugAll,"Found %s",str.safe()); + DDebug(this,DebugAll,"Found field '%s'",str.safe()); addOpcode(OpcField,str); return true; } @@ -432,14 +477,14 @@ bool ExpEvaluator::getRightAssoc(ExpEvaluator::Opcode oper) const bool ExpEvaluator::getSeparator(const char*& expr, bool remove) { - if (skipWhites(expr) != ',') + if (skipComments(expr) != ',') return false; if (remove) expr++; return true; } -bool ExpEvaluator::runCompile(const char*& expr, char stop) +bool ExpEvaluator::runCompile(const char*& expr, char stop, Opcode nested) { typedef struct { Opcode code; @@ -447,33 +492,43 @@ bool ExpEvaluator::runCompile(const char*& expr, char stop) } StackedOpcode; StackedOpcode stack[10]; unsigned int stackPos = 0; - DDebug(this,DebugInfo,"runCompile '%s'",expr); - if (skipWhites(expr) == ')') + DDebug(this,DebugInfo,"runCompile '%s' '%1s'",expr,&stop); + if (skipComments(expr) == ')') return false; + m_inError = false; if (expr[0] == '*' && !expr[1]) { expr++; addOpcode(OpcField,"*"); return true; } for (;;) { - while (skipWhites(expr) && getInstruction(expr)) + while (skipComments(expr) && getInstruction(expr,nested)) ; + if (inError()) + return false; + if (stop && (skipComments(expr) == stop)) + return true; if (!getOperand(expr)) return false; Opcode oper; while ((oper = getPostfixOperator(expr)) != OpcNone) addOpcode(oper); - char c = skipWhites(expr); + if (inError()) + return false; + char c = skipComments(expr); if (!c || c == stop || getSeparator(expr,false)) { while (stackPos) addOpcode(stack[--stackPos].code); return true; } + if (inError()) + return false; oper = getOperator(expr); if (oper == OpcNone) - return gotError("Operator expected",expr); + return gotError("Operator or separator expected",expr); int precedence = 2 * getPrecedence(oper); int precAdj = precedence; + // precedence being equal favor right associative operators if (getRightAssoc(oper)) precAdj++; while (stackPos && stack[stackPos-1].prec >= precAdj) @@ -523,7 +578,10 @@ bool ExpEvaluator::trySimplify() if (o->opcode() == OpcLAnd || o->opcode() == OpcAnd || o->opcode() == OpcMul) { if ((op1->opcode() == OpcPush && !op1->number() && op2->opcode() == OpcField) || (op2->opcode() == OpcPush && !op2->number() && op1->opcode() == OpcField)) { - (m_opcodes+i)->set(new ExpOperation(0)); + if (o->opcode() == OpcLAnd) + (m_opcodes+i)->set(new ExpOperation(false)); + else + (m_opcodes+i)->set(new ExpOperation((long int)0)); m_opcodes.remove(op1); m_opcodes.remove(op2); i -= 2; @@ -534,7 +592,7 @@ bool ExpEvaluator::trySimplify() if (o->opcode() == OpcLOr) { if ((op1->opcode() == OpcPush && op1->number() && op2->opcode() == OpcField) || (op2->opcode() == OpcPush && op2->number() && op1->opcode() == OpcField)) { - (m_opcodes+i)->set(new ExpOperation(1)); + (m_opcodes+i)->set(new ExpOperation(true)); m_opcodes.remove(op1); m_opcodes.remove(op2); i -= 2; @@ -544,8 +602,8 @@ bool ExpEvaluator::trySimplify() } if ((op1->opcode() == OpcPush) && (op2->opcode() == OpcPush)) { ObjList stack; - pushOne(stack,new ExpOperation(*op1)); - pushOne(stack,new ExpOperation(*op2)); + pushOne(stack,op1->clone()); + pushOne(stack,op2->clone()); if (runOperation(stack,*o)) { // replace operators and operation with computed constant (m_opcodes+i)->set(popOne(stack)); @@ -566,7 +624,7 @@ bool ExpEvaluator::trySimplify() continue; if (op->opcode() == OpcPush) { ObjList stack; - pushOne(stack,new ExpOperation(op)); + pushOne(stack,op->clone()); if (runOperation(stack,*o)) { // replace unary operator and operation with computed constant (m_opcodes+i)->set(popOne(stack)); @@ -591,7 +649,7 @@ bool ExpEvaluator::trySimplify() return done; } -void ExpEvaluator::addOpcode(ExpEvaluator::Opcode oper, bool barrier) +ExpOperation* ExpEvaluator::addOpcode(ExpEvaluator::Opcode oper, bool barrier) { DDebug(this,DebugAll,"addOpcode %u",oper); if (oper == OpcAs) { @@ -604,25 +662,49 @@ void ExpEvaluator::addOpcode(ExpEvaluator::Opcode oper, bool barrier) o->String::operator=(o->name()); } } - m_opcodes.append(new ExpOperation(oper,0,0,barrier)); + ExpOperation* op = new ExpOperation(oper,0,ExpOperation::nonInteger(),barrier); + m_opcodes.append(op); + return op; } -void ExpEvaluator::addOpcode(ExpEvaluator::Opcode oper, const String& name, long int value, bool barrier) +ExpOperation* ExpEvaluator::addOpcode(ExpEvaluator::Opcode oper, long int value, bool barrier) +{ + DDebug(this,DebugAll,"addOpcode %u %lu",oper,value); + ExpOperation* op = new ExpOperation(oper,0,value,barrier); + m_opcodes.append(op); + return op; +} + +ExpOperation* ExpEvaluator::addOpcode(ExpEvaluator::Opcode oper, const String& name, long int value, bool barrier) { DDebug(this,DebugAll,"addOpcode %u '%s' %ld",oper,name.c_str(),value); - m_opcodes.append(new ExpOperation(oper,name,value,barrier)); + ExpOperation* op = new ExpOperation(oper,name,value,barrier); + m_opcodes.append(op); + return op; } -void ExpEvaluator::addOpcode(const String& value) +ExpOperation* ExpEvaluator::addOpcode(const String& value) { DDebug(this,DebugAll,"addOpcode ='%s'",value.c_str()); - m_opcodes.append(new ExpOperation(value)); + ExpOperation* op = new ExpOperation(value); + m_opcodes.append(op); + return op; } -void ExpEvaluator::addOpcode(long int value) +ExpOperation* ExpEvaluator::addOpcode(long int value) { DDebug(this,DebugAll,"addOpcode =%ld",value); - m_opcodes.append(new ExpOperation(value)); + ExpOperation* op = new ExpOperation(value); + m_opcodes.append(op); + return op; +} + +ExpOperation* ExpEvaluator::addOpcode(bool value) +{ + DDebug(this,DebugAll,"addOpcode =%s",String::boolText(value)); + ExpOperation* op = new ExpOperation(value); + m_opcodes.append(op); + return op; } void ExpEvaluator::pushOne(ObjList& stack, ExpOperation* oper) @@ -642,11 +724,13 @@ ExpOperation* ExpEvaluator::popOne(ObjList& stack) stack.remove(); } if (o && o->barrier()) { - XDebug(DebugAll,"Not popping barrier %u: '%s'='%s'",o->opcode(),o->name().c_str(),o->c_str()); + XDebug(DebugInfo,"Not popping barrier %u: '%s'='%s'",o->opcode(),o->name().c_str(),o->c_str()); return 0; } stack.remove(o,false); - DDebug(DebugInfo,"Popped: %p",o); + XDebug(DebugAll,"Popped: %p%s%s",o, + (YOBJECT(ExpFunction,o) ? " function" : ""), + (YOBJECT(ExpWrapper,o) ? " wrapper" : "")); return o; } @@ -665,7 +749,7 @@ ExpOperation* ExpEvaluator::popAny(ObjList& stack) return o; } -ExpOperation* ExpEvaluator::popValue(ObjList& stack, void* context) const +ExpOperation* ExpEvaluator::popValue(ObjList& stack, GenObject* context) const { ExpOperation* oper = popOne(stack); if (!oper || (oper->opcode() != OpcField)) @@ -675,12 +759,18 @@ ExpOperation* ExpEvaluator::popValue(ObjList& stack, void* context) const return ok ? popOne(stack) : 0; } -bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* context) const +bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context) const { DDebug(this,DebugAll,"runOperation(%p,%u,%p) %s",&stack,oper.opcode(),context,getOperator(oper.opcode())); + XDebug(this,DebugAll,"stack: %s",dump(stack).c_str()); + bool boolRes = true; switch (oper.opcode()) { case OpcPush: - pushOne(stack,new ExpOperation(oper)); + case OpcField: + pushOne(stack,oper.clone()); + break; + case OpcNone: + case OpcLabel: break; case OpcAnd: case OpcOr: @@ -692,6 +782,8 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* case OpcMul: case OpcDiv: case OpcMod: + boolRes = false; + // fall through case OpcEq: case OpcNe: case OpcLt: @@ -781,8 +873,14 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* } TelEngine::destruct(op1); TelEngine::destruct(op2); - DDebug(this,DebugAll,"Numeric result: %lu",val); - pushOne(stack,new ExpOperation(val)); + if (boolRes) { + DDebug(this,DebugAll,"Bool result: '%s'",String::boolText(val != 0)); + pushOne(stack,new ExpOperation(val != 0)); + } + else { + DDebug(this,DebugAll,"Numeric result: %lu",val); + pushOne(stack,new ExpOperation(val)); + } } break; case OpcLAnd: @@ -809,7 +907,7 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* TelEngine::destruct(op1); TelEngine::destruct(op2); DDebug(this,DebugAll,"Bool result: '%s'",String::boolText(val)); - pushOne(stack,new ExpOperation(val ? 1 : 0)); + pushOne(stack,new ExpOperation(val)); } break; case OpcCat: @@ -837,7 +935,7 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* TelEngine::destruct(op2); return gotError("ExpEvaluator stack underflow"); } - pushOne(stack,new ExpOperation(*op1,*op2)); + pushOne(stack,op1->clone(*op2)); TelEngine::destruct(op1); TelEngine::destruct(op2); } @@ -853,25 +951,22 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* TelEngine::destruct(op); switch (oper.opcode()) { case OpcNeg: - val = -val; + pushOne(stack,new ExpOperation(-val)); break; case OpcNot: - val = ~val; + pushOne(stack,new ExpOperation(~val)); break; case OpcLNot: - val = val ? 0 : 1; + pushOne(stack,new ExpOperation(!val)); break; default: + pushOne(stack,new ExpOperation(val)); break; } - pushOne(stack,new ExpOperation(val)); } break; case OpcFunc: return runFunction(stack,oper,context) || gotError("Function call failed"); - case OpcField: - pushOne(stack,new ExpOperation(oper)); - break; case OpcIncPre: case OpcDecPre: case OpcIncPost: @@ -911,7 +1006,7 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* break; } (*fld) = num; - bool ok = runAssign(*fld,context); + bool ok = runAssign(stack,*fld,context); TelEngine::destruct(fld); if (!ok) { TelEngine::destruct(val); @@ -934,9 +1029,11 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* TelEngine::destruct(val); return gotError("Expecting LValue in assignment"); } - ExpOperation op(*val,fld->name()); + ExpOperation* op = val->clone(fld->name()); TelEngine::destruct(fld); - if (!runAssign(op,context)) { + bool ok = runAssign(stack,*op,context); + TelEngine::destruct(op); + if (!ok) { TelEngine::destruct(val); return gotError("Assignment failed"); } @@ -958,7 +1055,7 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* TelEngine::destruct(val); return gotError("Expecting LValue in assignment"); } - pushOne(stack,new ExpOperation(*fld)); + pushOne(stack,fld->clone()); pushOne(stack,fld); pushOne(stack,val); ExpOperation op((Opcode)(oper.opcode() & ~OpcAssign), @@ -975,7 +1072,7 @@ bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* return true; } -bool ExpEvaluator::runFunction(ObjList& stack, const ExpOperation& oper, void* context) const +bool ExpEvaluator::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) const { DDebug(this,DebugAll,"runFunction(%p,'%s' %ld, %p) ext=%p", &stack,oper.name().c_str(),oper.number(),context,(void*)m_extender); @@ -994,27 +1091,27 @@ bool ExpEvaluator::runFunction(ObjList& stack, const ExpOperation& oper, void* c if (oper.name() == YSTRING("now")) { if (oper.number()) return gotError("Function expects no arguments"); - pushOne(stack,new ExpOperation(Time::secNow())); + pushOne(stack,new ExpOperation((long int)Time::secNow())); return true; } - return m_extender && m_extender->runFunction(this,stack,oper,context); + return m_extender && m_extender->runFunction(stack,oper,context); } -bool ExpEvaluator::runField(ObjList& stack, const ExpOperation& oper, void* context) const +bool ExpEvaluator::runField(ObjList& stack, const ExpOperation& oper, GenObject* context) const { DDebug(this,DebugAll,"runField(%p,'%s',%p) ext=%p", &stack,oper.name().c_str(),context,(void*)m_extender); - return m_extender && m_extender->runField(this,stack,oper,context); + return m_extender && m_extender->runField(stack,oper,context); } -bool ExpEvaluator::runAssign(const ExpOperation& oper, void* context) const +bool ExpEvaluator::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) const { DDebug(this,DebugAll,"runAssign('%s'='%s',%p) ext=%p", oper.name().c_str(),oper.c_str(),context,(void*)m_extender); - return m_extender && m_extender->runAssign(this,oper,context); + return m_extender && m_extender->runAssign(stack,oper,context); } -bool ExpEvaluator::runEvaluate(const ObjList& opcodes, ObjList& stack, void* context) const +bool ExpEvaluator::runEvaluate(const ObjList& opcodes, ObjList& stack, GenObject* context) const { DDebug(this,DebugInfo,"runEvaluate(%p,%p,%p)",&opcodes,&stack,context); for (const ObjList* l = opcodes.skipNull(); l; l = l->skipNext()) { @@ -1025,7 +1122,7 @@ bool ExpEvaluator::runEvaluate(const ObjList& opcodes, ObjList& stack, void* con return true; } -bool ExpEvaluator::runEvaluate(const ObjVector& opcodes, ObjList& stack, void* context, unsigned int index) const +bool ExpEvaluator::runEvaluate(const ObjVector& opcodes, ObjList& stack, GenObject* context, unsigned int index) const { DDebug(this,DebugInfo,"runEvaluate(%p,%p,%p,%u)",&opcodes,&stack,context,index); for (; index < opcodes.length(); index++) { @@ -1036,12 +1133,12 @@ bool ExpEvaluator::runEvaluate(const ObjVector& opcodes, ObjList& stack, void* c return true; } -bool ExpEvaluator::runEvaluate(ObjList& stack, void* context) const +bool ExpEvaluator::runEvaluate(ObjList& stack, GenObject* context) const { return runEvaluate(m_opcodes,stack,context); } -bool ExpEvaluator::runAllFields(ObjList& stack, void* context) const +bool ExpEvaluator::runAllFields(ObjList& stack, GenObject* context) const { DDebug(this,DebugAll,"runAllFields(%p,%p)",&stack,context); bool ok = true; @@ -1065,20 +1162,31 @@ bool ExpEvaluator::runAllFields(ObjList& stack, void* context) const return ok; } -int ExpEvaluator::compile(const char* expr) +int ExpEvaluator::compile(const char* expr, GenObject* context) { - if (!skipWhites(expr)) + if (!skipComments(expr,context)) return 0; int res = 0; - do { + for (;;) { + int pre; + m_inError = false; + while ((pre = preProcess(expr,context)) >= 0) + res += pre; + if (inError()) + return 0; if (!runCompile(expr)) return 0; res++; - } while (getSeparator(expr,true)); - return skipWhites(expr) ? 0 : res; + bool sep = false; + while (getSeparator(expr,true)) + sep = true; + if (!sep) + break; + } + return skipComments(expr,context) ? 0 : res; } -bool ExpEvaluator::evaluate(ObjList* results, void* context) const +bool ExpEvaluator::evaluate(ObjList* results, GenObject* context) const { if (results) { results->clear(); @@ -1089,7 +1197,7 @@ bool ExpEvaluator::evaluate(ObjList* results, void* context) const return runEvaluate(res,context); } -int ExpEvaluator::evaluate(NamedList& results, unsigned int index, const char* prefix, void* context) const +int ExpEvaluator::evaluate(NamedList& results, unsigned int index, const char* prefix, GenObject* context) const { ObjList stack; if (!evaluate(stack,context)) @@ -1109,7 +1217,7 @@ int ExpEvaluator::evaluate(NamedList& results, unsigned int index, const char* p return column; } -int ExpEvaluator::evaluate(Array& results, unsigned int index, void* context) const +int ExpEvaluator::evaluate(Array& results, unsigned int index, GenObject* context) const { Debug(this,DebugStub,"Please implement ExpEvaluator::evaluate(Array)"); return -1; @@ -1146,6 +1254,33 @@ void ExpEvaluator::dump(const ObjList& codes, String& res) const } +ExpOperation* ExpFunction::clone(const char* name) const +{ + XDebug(DebugInfo,"ExpFunction::clone('%s') [%p]",name,this); + return new ExpFunction(name,number()); +} + + +ExpOperation* ExpWrapper::clone(const char* name) const +{ + XDebug(DebugInfo,"ExpWrapper::clone('%s') [%p]",name,this); + RefObject* r = YOBJECT(RefObject,object()); + if (r) + r->ref(); + return new ExpWrapper(object(),name); +} + +void* ExpWrapper::getObject(const String& name) const +{ + if (name == YSTRING("ExpWrapper")) + return const_cast(this); + void* obj = ExpOperation::getObject(name); + if (obj) + return obj; + return m_object ? m_object->getObject(name) : 0; +} + + TableEvaluator::TableEvaluator(const TableEvaluator& original) : m_select(original.m_select), m_where(original.m_where), m_limit(original.m_limit), m_limitVal(original.m_limitVal) @@ -1176,7 +1311,7 @@ void TableEvaluator::extender(ExpExtender* ext) m_limit.extender(ext); } -bool TableEvaluator::evalWhere(void* context) +bool TableEvaluator::evalWhere(GenObject* context) { if (m_where.null()) return true; @@ -1190,14 +1325,14 @@ bool TableEvaluator::evalWhere(void* context) return (o->opcode() == ExpEvaluator::OpcPush) && o->number(); } -bool TableEvaluator::evalSelect(ObjList& results, void* context) +bool TableEvaluator::evalSelect(ObjList& results, GenObject* context) { if (m_select.null()) return false; return m_select.evaluate(results,context); } -unsigned int TableEvaluator::evalLimit(void* context) +unsigned int TableEvaluator::evalLimit(GenObject* context) { if (m_limitVal == (unsigned int)-2) { m_limitVal = (unsigned int)-1; diff --git a/libs/yscript/javascript.cpp b/libs/yscript/javascript.cpp index 0c47d93b..8b6c1515 100644 --- a/libs/yscript/javascript.cpp +++ b/libs/yscript/javascript.cpp @@ -22,18 +22,25 @@ */ #include "yatescript.h" +#include using namespace TelEngine; namespace { // anonymous -class JsContext : public ScriptContext +class JsContext : public JsObject, public Mutex { - YCLASS(JsContext,ScriptContext) + YCLASS(JsContext,JsObject) public: - virtual bool runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context); - virtual bool runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context); - virtual bool runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context); + inline JsContext() + : JsObject("Context",this), Mutex(true,"JsContext") + { } + virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context); + virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context); + virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context); +private: + GenObject* resolveTop(ObjList& stack, const String& name, GenObject* context); + GenObject* resolve(ObjList& stack, String& name, GenObject* context); }; class JsCode : public ScriptCode, public ExpEvaluator @@ -44,6 +51,7 @@ public: OpcBegin = OpcPrivate + 1, OpcEnd, OpcIndex, + OpcTypeof, OpcNew, OpcFor, OpcWhile, @@ -51,6 +59,7 @@ public: OpcElse, OpcSwitch, OpcCase, + OpcDefault, OpcBreak, OpcCont, OpcIn, @@ -61,25 +70,64 @@ public: OpcFinally, OpcThrow, OpcReturn, + OpcJump, + OpcJumpTrue, + OpcJumpFalse, + OpcJRel, + OpcJRelTrue, + OpcJRelFalse, + OpcTrue, + OpcFalse, + OpcInclude, }; inline JsCode() - : ExpEvaluator(C), m_label(0) + : ExpEvaluator(C), m_label(0), m_depth(0) { debugName("JsCode"); } virtual bool initialize(ScriptContext* context) const; - virtual bool evaluate(ScriptContext& context, ObjList& results) const; + virtual bool evaluate(ScriptRun& runner, ObjList& results) const; + bool link(); protected: virtual bool keywordChar(char c) const; virtual int getKeyword(const char* str) const; - virtual bool getInstruction(const char*& expr); + virtual char skipComments(const char*& expr, GenObject* context = 0) const; + virtual int preProcess(const char*& expr, GenObject* context = 0); + virtual bool getInstruction(const char*& expr, Opcode nested); + virtual bool getNumber(const char*& expr); virtual Opcode getOperator(const char*& expr); virtual Opcode getUnaryOperator(const char*& expr); virtual Opcode getPostfixOperator(const char*& expr); virtual const char* getOperator(Opcode oper) const; virtual int getPrecedence(ExpEvaluator::Opcode oper) const; virtual bool getSeparator(const char*& expr, bool remove); - virtual bool runOperation(ObjList& stack, const ExpOperation& oper, void* context) const; + virtual bool runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context) const; + virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) const; + virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context) const; + virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) const; private: - int m_label; + ObjVector m_linked; + bool preProcessInclude(const char*& expr, GenObject* context); + bool evalList(ObjList& stack, GenObject* context) const; + bool evalVector(ObjList& stack, GenObject* context) const; + bool jumpToLabel(long int label, GenObject* context) const; + bool jumpRelative(long int offset, GenObject* context) const; + long int m_label; + int m_depth; +}; + +class JsRunner : public ScriptRun +{ + friend class JsCode; +public: + inline JsRunner(ScriptCode* code, ScriptContext* context) + : ScriptRun(code,context), + m_paused(false), m_opcode(0), m_index(0) + { } + virtual Status reset(); + virtual Status resume(); +private: + bool m_paused; + ObjList* m_opcode; + unsigned int m_index; }; #define MAKEOP(s,o) { s, JsCode::Opc ## o } @@ -91,6 +139,7 @@ static const TokenDict s_operators[] = static const TokenDict s_unaryOps[] = { MAKEOP("new", New), + MAKEOP("typeof", Typeof), { 0, 0 } }; @@ -110,6 +159,7 @@ static const TokenDict s_instr[] = MAKEOP("else", Else), MAKEOP("switch", Switch), MAKEOP("case", Case), + MAKEOP("default", Default), MAKEOP("break", Break), MAKEOP("continue", Cont), MAKEOP("in", In), @@ -122,28 +172,114 @@ static const TokenDict s_instr[] = MAKEOP("return", Return), { 0, 0 } }; + +static const TokenDict s_bools[] = +{ + MAKEOP("false", False), + MAKEOP("true", True), + { 0, 0 } +}; + +static const TokenDict s_preProc[] = +{ + MAKEOP("#include", Include), + { 0, 0 } +}; #undef MAKEOP - -bool JsContext::runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +GenObject* JsContext::resolveTop(ObjList& stack, const String& name, GenObject* context) { - return ScriptContext::runFunction(eval,stack,oper,context); + XDebug(DebugAll,"JsContext::resolveTop '%s'",name.c_str()); + for (ObjList* l = stack.skipNull(); l; l = l->skipNext()) { + JsObject* jso = YOBJECT(JsObject,l->get()); + if (jso && jso->hasField(stack,name,context)) + return jso; + } + return this; } -bool JsContext::runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +GenObject* JsContext::resolve(ObjList& stack, String& name, GenObject* context) { - if (!eval) - return false; - XDebug(DebugAll,"JsContext::runField '%s'",oper.name().c_str()); - return ScriptContext::runField(eval,stack,oper,context); + if (name.find('.') < 0) + return resolveTop(stack,name,context); + ObjList* list = name.split('.',true); + GenObject* obj = 0; + for (ObjList* l = list->skipNull(); l; ) { + const String* s = static_cast(l->get()); + l = l->skipNext(); + if (TelEngine::null(s)) { + // consecutive dots - not good + obj = 0; + break; + } + if (!obj) + obj = resolveTop(stack,*s,context); + if (!l) { + name = *s; + break; + } + ExpExtender* ext = YOBJECT(ExpExtender,obj); + if (ext) + obj = ext->getField(stack,*s,context); + } + TelEngine::destruct(list); + XDebug(DebugAll,"JsContext::resolve got '%s' %p for '%s'", + (obj ? obj->toString().c_str() : 0),obj,name.c_str()); + return obj; } -bool JsContext::runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context) +bool JsContext::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) { - if (!eval) - return false; - XDebug(DebugAll,"JsContext::runAssign '%s'='%s'",oper.name().c_str(),oper.c_str()); - return ScriptContext::runAssign(eval,oper,context); + XDebug(DebugAll,"JsContext::runFunction '%s' [%p]",oper.name().c_str(),this); + String name = oper.name(); + GenObject* o = resolve(stack,name,context); + if (o && o != this) { + ExpExtender* ext = YOBJECT(ExpExtender,o); + if (ext) { + ExpOperation op(oper,name); + return ext->runFunction(stack,op,context); + } + } + if (name == YSTRING("isNaN")) { + if (oper.number() != 1) + return false; + ExpOperation* op = ExpEvaluator::popOne(stack); + if (!op) + return false; + ExpEvaluator::pushOne(stack,new ExpOperation(!op->isInteger())); + return true; + } + return JsObject::runFunction(stack,oper,context); +} + +bool JsContext::runField(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + XDebug(DebugAll,"JsContext::runField '%s' [%p]",oper.name().c_str(),this); + String name = oper.name(); + GenObject* o = resolve(stack,name,context); + if (o && o != this) { + ExpExtender* ext = YOBJECT(ExpExtender,o); + if (ext) { + ExpOperation op(oper,name); + return ext->runField(stack,op,context); + } + } + return JsObject::runField(stack,oper,context); +} + +bool JsContext::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + XDebug(DebugAll,"JsContext::runAssign '%s'='%s' [%p]",oper.name().c_str(),oper.c_str(),this); + String name = oper.name(); + GenObject* o = resolve(stack,name,context); + if (o && o != this) { + ExpExtender* ext = YOBJECT(ExpExtender,o); + if (ext) { + ExpOperation op(oper,name); + return ext->runAssign(stack,op,context); + } + } + return JsObject::runAssign(stack,oper,context); } @@ -152,15 +288,60 @@ bool JsCode::initialize(ScriptContext* context) const { if (!context) return false; - JsObject::initialize(*context); + JsObject::initialize(context); return true; } -bool JsCode::evaluate(ScriptContext& context, ObjList& results) const +bool JsCode::evaluate(ScriptRun& runner, ObjList& results) const { if (null()) return false; - return ExpEvaluator::evaluate(results,&context); + bool ok = m_linked.length() ? evalVector(results,&runner) : evalList(results,&runner); + if (!ok) + return false; + if (static_cast(runner).m_paused) + return true; + if (!runAllFields(results,&runner)) + return gotError("Could not evaluate all fields"); + return true; +} + +// Convert list to vector and fix label relocations +bool JsCode::link() +{ + if (!m_opcodes.count()) + return false; + m_linked.assign(m_opcodes); + unsigned int n = m_linked.count(); + if (!n) + return false; + for (unsigned int i = 0; i < n; i++) { + const ExpOperation* l = static_cast(m_linked[i]); + if (!l || l->opcode() != OpcLabel) + continue; + long int lbl = l->number(); + for (unsigned int j = 0; j < n; i++) { + ExpOperation* jmp = static_cast(m_linked[j]); + if (!jmp || jmp->number() != lbl) + continue; + Opcode op = OpcNone; + switch (jmp->opcode()) { + case (Opcode)OpcJump: + op = (Opcode)OpcJRel; + break; + case (Opcode)OpcJumpTrue: + op = (Opcode)OpcJRelTrue; + break; + case (Opcode)OpcJumpFalse: + op = (Opcode)OpcJRelFalse; + break; + default: + continue; + } + long int offs = i - j; + m_linked.set(new ExpOperation(op,0,offs,jmp->barrier()),j); + } + } } bool JsCode::keywordChar(char c) const @@ -171,30 +352,120 @@ bool JsCode::keywordChar(char c) const int JsCode::getKeyword(const char* str) const { int len = 0; + const char*s = str; for (;; len++) { - char c = *str++; + char c = *s++; if (c <= ' ') break; if (keywordChar(c) || (len && (c == '.'))) continue; break; } - if (len > 1 && (str[-2] == '.')) + if (len > 1 && (s[-2] == '.')) len--; + if (len && ExpEvaluator::getOperator(str,s_instr) != OpcNone) + return 0; return len; } -bool JsCode::getInstruction(const char*& expr) +char JsCode::skipComments(const char*& expr, GenObject* context) const { - XDebug(this,DebugAll,"JsCode::getInstruction '%s'",expr); - if (skipWhites(expr) == '{') { - if (!runCompile(++expr,'}')) - return false; - if (skipWhites(expr) != '}') + char c = skipWhites(expr); + while (c == '/') { + if (expr[1] == '/') { + // comment to end of line + expr+=2; + while ((c = *expr) && (c != '\r') && (c != '\n')) + expr++; + c = skipWhites(expr); + } + else if (expr[1] == '*') { + /* comment to close */ + expr++; + while ((c = *expr) && (c != '*' || expr[1] != '/')) + expr++; + if (c) { + expr+=2; + c = skipWhites(expr); + } + } + else + break; + } + return c; +} + +bool JsCode::preProcessInclude(const char*& expr, GenObject* context) +{ + if (m_depth > 5) + return gotError("Possible recursive include"); + JsParser* parser = YOBJECT(JsParser,context); + if (!parser) + return false; + char c = skipComments(expr); + if (c == '"' || c == '\'') { + char sep = c; + const char* start = ++expr; + while ((c = *expr++)) { + if (c != sep) + continue; + String str(start,expr-start-1); + DDebug(this,DebugAll,"Found include '%s'",str.safe()); + parser->adjustPath(str); + m_depth++; + bool ok = parser->parseFile(str,true); + m_depth--; + return ok || gotError("Failed to include " + str); + } + expr--; + return gotError("Expecting string end"); + } + return gotError("Expecting include file",expr); +} + +int JsCode::preProcess(const char*& expr, GenObject* context) +{ + int rval = -1; + for (;;) { + switch ((JsOpcode)ExpEvaluator::getOperator(expr,s_preProc)) { + case OpcInclude: + if (preProcessInclude(expr,context)) { + if (rval < 0) + rval = 1; + else + rval++; + } + else + return -1; + break; + default: + return rval; + } + } +} + +bool JsCode::getInstruction(const char*& expr, Opcode nested) +{ + if (inError()) + return false; + XDebug(this,DebugAll,"JsCode::getInstruction '%s' %u",expr,nested); + if (skipComments(expr) == '{') { + expr++; + for (;;) { + if (!runCompile(expr,'}',nested)) + return false; + bool sep = false; + while (skipComments(expr) && getSeparator(expr,true)) + sep = true; + if (*expr == '}' || !sep) + break; + } + if (*expr != '}') return gotError("Expecting '}'",expr); expr++; return true; } + const char* saved = expr; Opcode op = ExpEvaluator::getOperator(expr,s_instr); switch ((JsOpcode)op) { case (JsOpcode)OpcNone: @@ -208,14 +479,105 @@ bool JsCode::getInstruction(const char*& expr) runCompile(expr); addOpcode(op); break; + case OpcIf: + if (skipComments(expr) != '(') + return gotError("Expecting '('",expr); + if (!runCompile(++expr,')')) + return false; + if (skipComments(expr) != ')') + return gotError("Expecting ')'",expr); + { + ExpOperation* cond = addOpcode((Opcode)OpcJumpFalse,++m_label); + if (!runCompile(++expr,';')) + return false; + const char* save = expr; + if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcElse) { + ExpOperation* jump = addOpcode((Opcode)OpcJump,++m_label); + addOpcode(OpcLabel,cond->number()); + if (!runCompile(++expr)) + return false; + addOpcode(OpcLabel,jump->number()); + } + else { + expr = save; + addOpcode(OpcLabel,cond->number()); + } + } + break; + case OpcElse: + expr = saved; + return false; + case OpcWhile: + { + ExpOperation* lbl = addOpcode(OpcLabel,++m_label); + if (skipComments(expr) != '(') + return gotError("Expecting '('",expr); + if (!runCompile(++expr,')',op)) + return false; + if (skipComments(expr) != ')') + return gotError("Expecting ')'",expr); + ExpOperation* jump = addOpcode((Opcode)OpcJumpFalse,++m_label); + if (!runCompile(++expr)) + return false; + addOpcode((Opcode)OpcJump,lbl->number()); + addOpcode(OpcLabel,jump->number()); + } + break; + case OpcCase: + if (OpcSwitch != (JsOpcode)nested) + return gotError("Case not in switch",saved); + break; + case OpcDefault: + if (OpcSwitch != (JsOpcode)nested) + return gotError("Default not in switch",saved); + break; + case OpcTry: + addOpcode(op); + if (!runCompile(expr,0,op)) + return false; + { + if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcCatch) { + if (skipComments(expr) != '(') + return gotError("Expecting '('",expr); + if (!getField(++expr)) + return gotError("Expecting formal argument",expr); + if (skipComments(expr) != ')') + return gotError("Expecting ')'",expr); + if (!runCompile(++expr)) + return false; + } + if ((JsOpcode)ExpEvaluator::getOperator(expr,s_instr) == OpcFinally) { + if (!runCompile(expr)) + return false; + } + } default: break; } return true; } +bool JsCode::getNumber(const char*& expr) +{ + if (inError()) + return false; + switch ((JsOpcode)ExpEvaluator::getOperator(expr,s_bools)) { + case OpcFalse: + addOpcode(false); + return true; + case OpcTrue: + addOpcode(true); + return true; + default: + break; + } + return ExpEvaluator::getNumber(expr); +} + ExpEvaluator::Opcode JsCode::getOperator(const char*& expr) { + if (inError()) + return OpcNone; XDebug(this,DebugAll,"JsCode::getOperator '%s'",expr); Opcode op = ExpEvaluator::getOperator(expr,s_operators); if (OpcNone != op) @@ -225,6 +587,8 @@ ExpEvaluator::Opcode JsCode::getOperator(const char*& expr) ExpEvaluator::Opcode JsCode::getUnaryOperator(const char*& expr) { + if (inError()) + return OpcNone; XDebug(this,DebugAll,"JsCode::getUnaryOperator '%s'",expr); Opcode op = ExpEvaluator::getOperator(expr,s_unaryOps); if (OpcNone != op) @@ -234,11 +598,13 @@ ExpEvaluator::Opcode JsCode::getUnaryOperator(const char*& expr) ExpEvaluator::Opcode JsCode::getPostfixOperator(const char*& expr) { + if (inError()) + return OpcNone; XDebug(this,DebugAll,"JsCode::getPostfixOperator '%s'",expr); - if (skipWhites(expr) == '[') { + if (skipComments(expr) == '[') { if (!runCompile(++expr,']')) return OpcNone; - if (skipWhites(expr) != ']') { + if (skipComments(expr) != ']') { gotError("Expecting ']'",expr); return OpcNone; } @@ -282,7 +648,9 @@ int JsCode::getPrecedence(ExpEvaluator::Opcode oper) const bool JsCode::getSeparator(const char*& expr, bool remove) { - switch (skipWhites(expr)) { + if (inError()) + return false; + switch (skipComments(expr)) { case ']': case ';': if (remove) @@ -292,11 +660,11 @@ bool JsCode::getSeparator(const char*& expr, bool remove) return ExpEvaluator::getSeparator(expr,remove); } -bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, void* context) const +bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context) const { switch ((JsOpcode)oper.opcode()) { case OpcBegin: - pushOne(stack,new ExpOperation(OpcBegin)); + pushOne(stack,new ExpOperation((Opcode)OpcBegin)); break; case OpcEnd: { @@ -334,15 +702,56 @@ bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, void* contex TelEngine::destruct(op2); } break; + case OpcTypeof: + { + ExpOperation* op = popValue(stack,context); + if (!op) + return gotError("Stack underflow"); + switch (op->opcode()) { + case OpcPush: + { + const char* txt = "string"; + ExpWrapper* w = YOBJECT(ExpWrapper,op); + if (w) + txt = w->object() ? "object" : "undefined"; + else if (op->isInteger()) + txt = "number"; + pushOne(stack,new ExpOperation(txt)); + } + break; + case OpcFunc: + pushOne(stack,new ExpOperation("function")); + break; + default: + pushOne(stack,new ExpOperation("internal")); + } + TelEngine::destruct(op); + } + break; case OpcNew: { ExpOperation* op = popOne(stack); if (!op) return gotError("Stack underflow"); - if (op->opcode() != OpcField) { - TelEngine::destruct(op); - return gotError("Expecting class name"); + switch (op->opcode()) { + case OpcField: + break; + case OpcPush: + { + ExpWrapper* w = YOBJECT(ExpWrapper,op); + if (w && w->object()) { + pushOne(stack,op); + return true; + } + } + // fall through + default: + TelEngine::destruct(op); + return gotError("Expecting class name"); } + ExpFunction ctr(op->name(),op->number()); + TelEngine::destruct(op); + return runOperation(stack,ctr,context); } break; case OpcThrow: @@ -360,7 +769,7 @@ bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, void* contex } } if (!ok) - return gotError("'try' not found"); + return gotError("Uncaught exception: " + *op); pushOne(stack,op); } break; @@ -381,27 +790,225 @@ bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, void* contex } if (!ok) { TelEngine::destruct(op); - return gotError("Function not found on stack"); + return gotError("Return outside function call"); } pushOne(stack,op); } break; + case OpcJumpTrue: + case OpcJumpFalse: + case OpcJRelTrue: + case OpcJRelFalse: + { + ExpOperation* op = popValue(stack,context); + if (!op) + return gotError("Stack underflow"); + bool val = op->number() != 0; + TelEngine::destruct(op); + switch ((JsOpcode)oper.opcode()) { + case OpcJumpTrue: + case OpcJRelTrue: + if (!val) + return true; + break; + case OpcJumpFalse: + case OpcJRelFalse: + if (val) + return true; + break; + default: + break; + } + } + // fall through + case OpcJump: + case OpcJRel: + switch ((JsOpcode)oper.opcode()) { + case OpcJump: + case OpcJumpTrue: + case OpcJumpFalse: + return jumpToLabel(oper.number(),context) || gotError("Label not found"); + case OpcJRel: + case OpcJRelTrue: + case OpcJRelFalse: + return jumpRelative(oper.number(),context) || gotError("Relative jump failed"); + default: + return false; + } + break; default: - return ExpEvaluator::runOperation(stack,oper); + return ExpEvaluator::runOperation(stack,oper,context); } return true; } +bool JsCode::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) const +{ + DDebug(this,DebugAll,"runFunction(%p,'%s' %ld, %p) ext=%p", + &stack,oper.name().c_str(),oper.number(),context,extender()); + if (context) { + ScriptRun* sr = static_cast(context); + if (sr->context()->runFunction(stack,oper,context)) + return true; + } + return extender() && extender()->runFunction(stack,oper,context); +} + +bool JsCode::runField(ObjList& stack, const ExpOperation& oper, GenObject* context) const +{ + DDebug(this,DebugAll,"runField(%p,'%s',%p) ext=%p", + &stack,oper.name().c_str(),context,extender()); + if (context) { + ScriptRun* sr = static_cast(context); + if (sr->context()->runField(stack,oper,context)) + return true; + } + return extender() && extender()->runField(stack,oper,context); +} + +bool JsCode::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) const +{ + DDebug(this,DebugAll,"runAssign('%s'='%s',%p) ext=%p", + oper.name().c_str(),oper.c_str(),context,extender()); + if (context) { + ScriptRun* sr = static_cast(context); + if (sr->context()->runAssign(stack,oper,context)) + return true; + } + return extender() && extender()->runAssign(stack,oper,context); +} + +bool JsCode::evalList(ObjList& stack, GenObject* context) const +{ + XDebug(this,DebugInfo,"evalList(%p,%p)",&stack,context); + JsRunner* runner = static_cast(context); + ObjList*& opcode = runner->m_opcode; + if (!opcode) + opcode = m_opcodes.skipNull(); + while (opcode) { + const ExpOperation* o = static_cast(opcode->get()); + opcode = opcode->skipNext(); + if (!runOperation(stack,*o,context)) + return false; + if (runner->m_paused) + break; + } + return true; +} + +bool JsCode::evalVector(ObjList& stack, GenObject* context) const +{ + XDebug(this,DebugInfo,"evalVector(%p,%p)",&stack,context); + JsRunner* runner = static_cast(context); + unsigned int& index = runner->m_index; + while (index < m_linked.length()) { + const ExpOperation* o = static_cast(m_linked[index++]); + if (o && !runOperation(stack,*o,context)) + return false; + if (runner->m_paused) + break; + } + return true; +} + +bool JsCode::jumpToLabel(long int label, GenObject* context) const +{ + if (!context) + return false; + JsRunner* runner = static_cast(context); + for (ObjList* l = m_opcodes.skipNull(); l; l = l->skipNext()) { + const ExpOperation* o = static_cast(l->get()); + if (o->opcode() == OpcLabel && o->number() == label) { + runner->m_opcode = l; + return true; + } + } + return false; +} + +bool JsCode::jumpRelative(long int offset, GenObject* context) const +{ + if (!context) + return false; + unsigned int& index = static_cast(context)->m_index; + long int i = index + offset; + if (i < 0 || i > m_linked.length()) + return false; + index = i; + return true; +} + + +ScriptRun::Status JsRunner::reset() +{ + Status s = ScriptRun::reset(); + m_opcode = 0; + m_index = 0; + return s; +} + +ScriptRun::Status JsRunner::resume() +{ + Lock mylock(this); + if (Running != state()) + return state(); + RefPointer c = code(); + if (!(c && context())) + return Invalid; + m_paused = false; + mylock.drop(); + if (!c->evaluate(*this,stack())) + return Failed; + return m_paused ? Incomplete : Succeeded; +} + }; // anonymous namespace +// Adjust a script file include path +void JsParser::adjustPath(String& script) +{ + if (script.null() || script.startsWith(Engine::pathSeparator())) + return; + script = m_basePath + script; +} + +// Create Javascript context +ScriptContext* JsParser::createContext() const +{ + return new JsContext; +} + +ScriptRun* JsParser::createRunner(ScriptCode* code, ScriptContext* context) const +{ + if (!code) + return 0; + ScriptContext* ctxt = 0; + if (!context) + context = ctxt = createContext(); + ScriptRun* runner = new JsRunner(code,context); + TelEngine::destruct(ctxt); + return runner; +} + // Parse a piece of Javascript text -bool JsParser::parse(const char* text) +bool JsParser::parse(const char* text, bool fragment) { if (TelEngine::null(text)) return false; - // TODO - return false; + if (fragment) + return code() && static_cast(code())->compile(text,this); + JsCode* code = new JsCode; + setCode(code); + code->deref(); + if (!code->compile(text,this)) { + setCode(0); + return false; + } + DDebug(DebugAll,"Compiled: %s",code->dump().c_str()); + code->simplify(); + DDebug(DebugAll,"Simplified: %s",code->dump().c_str()); + return true; } // Evaluate a string as expression or statement @@ -409,21 +1016,10 @@ ScriptRun::Status JsParser::eval(const String& text, ExpOperation** result, Scri { if (TelEngine::null(text)) return ScriptRun::Invalid; - JsCode* code = new JsCode; - if (!code->compile(text)) { - TelEngine::destruct(code); + JsParser parser; + if (!parser.parse(text)) return ScriptRun::Invalid; - } - DDebug(DebugAll,"Compiled: %s",code->dump().c_str()); - code->simplify(); - DDebug(DebugAll,"Simplified: %s",code->dump().c_str()); - ScriptContext* ctxt = 0; - if (!context) - context = ctxt = new JsContext(); - ScriptRun* runner = new ScriptRun(code,context); - TelEngine::destruct(ctxt); - code->extender(runner->context()); - TelEngine::destruct(code); + ScriptRun* runner = parser.createRunner(context); ScriptRun::Status rval = runner->run(); if (result && (ScriptRun::Succeeded == rval)) *result = ExpEvaluator::popOne(runner->stack()); diff --git a/libs/yscript/jsobjects.cpp b/libs/yscript/jsobjects.cpp index 851d138b..8b80deda 100644 --- a/libs/yscript/jsobjects.cpp +++ b/libs/yscript/jsobjects.cpp @@ -32,9 +32,8 @@ class JsNative : public JsObject { YCLASS(JsNative,JsObject) public: - inline JsNative(const char* name, NamedList* list) - : JsObject(name), - m_list(list) + inline JsNative(const char* name, NamedList* list, Mutex* mtx) + : JsObject(name,mtx), m_list(list) { } virtual NamedList& list() { return *m_list; } @@ -49,18 +48,8 @@ class JsArray : public JsObject { YCLASS(JsArray,JsObject) public: - inline JsArray() - : JsObject("Array") - { } -}; - -// Function object -class JsFunction : public JsObject -{ - YCLASS(JsFunction,JsObject) -public: - inline JsFunction() - : JsObject("Function") + inline JsArray(Mutex* mtx) + : JsObject("Array",mtx) { } }; @@ -69,20 +58,46 @@ class JsConstructor : public JsFunction { YCLASS(JsConstructor,JsFunction) public: - inline JsConstructor() + inline JsConstructor(Mutex* mtx) + : JsFunction(mtx) { } }; +// Object object +class JsObjectObj : public JsObject +{ + YCLASS(JsObjectObj,JsObject) +public: + inline JsObjectObj(Mutex* mtx) + : JsObject("Object",mtx,true) + { + params().addParam(new ExpFunction("constructor")); + } +protected: + bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); +}; + // Date object class JsDate : public JsObject { YCLASS(JsDate,JsObject) public: - inline JsDate() - : JsObject("Date") + inline JsDate(Mutex* mtx) + : JsObject("Date",mtx,true) { - addParam(new ExpOperation(ExpEvaluator::OpcFunc,"now")); + params().addParam(new ExpFunction("now")); + params().addParam(new ExpFunction("getDate")); + params().addParam(new ExpFunction("getDay")); + params().addParam(new ExpFunction("getFullYear")); + params().addParam(new ExpFunction("getHours")); + params().addParam(new ExpFunction("getMilliseconds")); + params().addParam(new ExpFunction("getMinutes")); + params().addParam(new ExpFunction("getMonth")); + params().addParam(new ExpFunction("getSeconds")); + params().addParam(new ExpFunction("getTime")); } +protected: + bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); }; // Math class - not really an object, all methods are static @@ -90,36 +105,232 @@ class JsMath : public JsObject { YCLASS(JsMath,JsObject) public: - inline JsMath() - : JsObject("Math") + inline JsMath(Mutex* mtx) + : JsObject("Math",mtx,true) { - addParam(new ExpOperation(ExpEvaluator::OpcFunc,"abs")); + params().addParam(new ExpFunction("abs")); + params().addParam(new ExpFunction("max")); + params().addParam(new ExpFunction("min")); } +protected: + bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); }; }; // anonymous namespace // Helper function that adds an object to a parent -static inline void addObject(NamedList& params, const char* name, NamedList* obj) +static inline void addObject(NamedList& params, const char* name, JsObject* obj) { params.addParam(new NamedPointer(name,obj,obj->toString())); } +JsObject::JsObject(const char* name, Mutex* mtx, bool frozen) + : ScriptContext(String("[Object ") + name + "]"), + m_frozen(frozen), m_mutex(mtx) +{ + XDebug(DebugAll,"JsObject::JsObject('%s',%p,%s) [%p]", + name,mtx,String::boolText(frozen),this); + params().addParam(new ExpFunction("freeze")); + params().addParam(new ExpFunction("isFrozen")); + params().addParam(new ExpFunction("toString")); +} + +JsObject::~JsObject() +{ + XDebug(DebugAll,"JsObject::~JsObject '%s' [%p]",toString().c_str(),this); +} + +bool JsObject::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + XDebug(DebugInfo,"JsObject::runFunction() '%s' in '%s' [%p]", + oper.name().c_str(),toString().c_str(),this); + NamedString* param = params().getParam(oper.name()); + if (!param) + return false; + ExpFunction* ef = YOBJECT(ExpFunction,param); + if (ef) + return runNative(stack,oper,context); + JsFunction* jf = YOBJECT(JsFunction,param); + if (jf) + return jf->runDefined(stack,oper,context); + JsObject* jso = YOBJECT(JsObject,param); + if (jso) { + ExpFunction op("constructor",oper.number()); + return jso->runFunction(stack,op,context); + } + return false; +} + +bool JsObject::runField(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + XDebug(DebugAll,"JsObject::runField() '%s' in '%s' [%p]", + oper.name().c_str(),toString().c_str(),this); + const String* param = params().getParam(oper.name()); + if (param) { + ExpFunction* ef = YOBJECT(ExpFunction,param); + if (ef) + ExpEvaluator::pushOne(stack,new ExpFunction(oper.name(),ef->number())); + else { + JsFunction* jf = YOBJECT(JsFunction,param); + if (jf) + ExpEvaluator::pushOne(stack,new ExpFunction(oper.name())); + else { + JsObject* jo = YOBJECT(JsObject,param); + if (jo) { + jo->ref(); + ExpEvaluator::pushOne(stack,new ExpWrapper(jo,oper.name())); + } + else + ExpEvaluator::pushOne(stack,new ExpOperation(*param,oper.name(),true)); + } + } + } + else + ExpEvaluator::pushOne(stack,new ExpWrapper(0,oper.name())); + return true; +} + +bool JsObject::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + XDebug(DebugAll,"JsObject::runAssign() '%s'='%s' in '%s' [%p]", + oper.name().c_str(),oper.c_str(),toString().c_str(),this); + if (frozen()) { + Debug(DebugNote,"Object '%s' is frozen",toString().c_str()); + return false; + } + ExpFunction* ef = YOBJECT(ExpFunction,&oper); + if (ef) + params().setParam(new ExpFunction(oper.name(),oper.number())); + else { + ExpWrapper* w = YOBJECT(ExpWrapper,&oper); + if (w) { + GenObject* o = w->object(); + RefObject* r = YOBJECT(RefObject,o); + if (r) + r->ref(); + if (o) + params().setParam(new NamedPointer(oper.name(),o,o->toString())); + else + params().clearParam(oper.name()); + } + else + params().setParam(oper.name(),oper); + } + return true; +} + +bool JsObject::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + XDebug(DebugAll,"JsObject::runNative() '%s' in '%s' [%p]", + oper.name().c_str(),toString().c_str(),this); + if (oper.name() == YSTRING("freeze")) + freeze(); + else if (oper.name() == YSTRING("isFrozen")) + ExpEvaluator::pushOne(stack,new ExpOperation(frozen())); + else if (oper.name() == YSTRING("toString")) + ExpEvaluator::pushOne(stack,new ExpOperation(params())); + else + return false; + return true; +} + +ExpOperation* JsObject::popValue(ObjList& stack, GenObject* context) +{ + ExpOperation* oper = ExpEvaluator::popOne(stack); + if (!oper || (oper->opcode() != ExpEvaluator::OpcField)) + return oper; + bool ok = runField(stack,*oper,context); + TelEngine::destruct(oper); + return ok ? ExpEvaluator::popOne(stack) : 0; +} // Initialize standard globals in the execution context -void JsObject::initialize(ScriptContext& context) +void JsObject::initialize(ScriptContext* context) { - NamedList& params = context.params(); - static_cast(params) = "[Object Global]"; - if (!params.getParam(YSTRING("Object"))) - addObject(params,"Object",new JsObject); - if (!params.getParam(YSTRING("Function"))) - addObject(params,"Function",new JsFunction); - if (!params.getParam(YSTRING("Date"))) - addObject(params,"Date",new JsDate); - if (!params.getParam(YSTRING("Math"))) - addObject(params,"Math",new JsMath); + if (!context) + return; + Mutex* mtx = context->mutex(); + Lock mylock(mtx); + NamedList& p = context->params(); + static_cast(p) = "[Object Global]"; + if (!p.getParam(YSTRING("Object"))) + addObject(p,"Object",new JsObjectObj(mtx)); + if (!p.getParam(YSTRING("Function"))) + addObject(p,"Function",new JsFunction(mtx)); + if (!p.getParam(YSTRING("Date"))) + addObject(p,"Date",new JsDate(mtx)); + if (!p.getParam(YSTRING("Math"))) + addObject(p,"Math",new JsMath(mtx)); + if (!p.getParam(YSTRING("isNaN"))) + p.addParam(new ExpFunction("isNaN")); +} + + +bool JsObjectObj::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + if (oper.name() == YSTRING("constructor")) + ExpEvaluator::pushOne(stack,new ExpWrapper(new JsObject("Object",mutex()))); + else + return JsObject::runNative(stack,oper,context); + return true; +} + +bool JsMath::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + if (oper.name() == YSTRING("abs")) { + if (!oper.number()) + return false; + long int n = 0; + for (long int i = oper.number(); i; i--) { + ExpOperation* op = popValue(stack,context); + if (op->isInteger()) + n = op->number(); + TelEngine::destruct(op); + } + if (n < 0) + n = -n; + ExpEvaluator::pushOne(stack,new ExpOperation(n)); + } + else if (oper.name() == YSTRING("max")) { + if (!oper.number()) + return false; + long int n = LONG_MIN; + for (long int i = oper.number(); i; i--) { + ExpOperation* op = popValue(stack,context); + if (op->isInteger() && op->number() > n) + n = op->number(); + TelEngine::destruct(op); + } + ExpEvaluator::pushOne(stack,new ExpOperation(n)); + } + else if (oper.name() == YSTRING("min")) { + if (!oper.number()) + return false; + long int n = LONG_MAX; + for (long int i = oper.number(); i; i--) { + ExpOperation* op = popValue(stack,context); + if (op->isInteger() && op->number() < n) + n = op->number(); + TelEngine::destruct(op); + } + ExpEvaluator::pushOne(stack,new ExpOperation(n)); + } + else + return JsObject::runNative(stack,oper,context); + return true; +} + + +bool JsDate::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + return JsObject::runNative(stack,oper,context); +} + + +bool JsFunction::runDefined(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + return false; } /* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yscript/script.cpp b/libs/yscript/script.cpp index f2f9d31a..a697b958 100644 --- a/libs/yscript/script.cpp +++ b/libs/yscript/script.cpp @@ -25,11 +25,46 @@ using namespace TelEngine; +namespace { // anonymous + +class BasicContext: public ScriptContext, public Mutex +{ + YCLASS(BasicContext,ScriptContext) +public: + inline explicit BasicContext() + : Mutex(true,"BasicContext") + { } + virtual Mutex* mutex() + { return this; } +}; + +}; // anonymous namespace + + ScriptParser::~ScriptParser() { TelEngine::destruct(m_code); } +bool ScriptParser::parseFile(const char* name, bool fragment) +{ + if (TelEngine::null(name)) + return false; + XDebug(DebugAll,"Opening script '%s'",name); + File f; + if (!f.openPath(name)) + return false; + int64_t len = f.length(); + if (len <= 0 || len > 65535) + return false; + DataBlock data(0,len+1); + char* text = (char*)data.data(); + if (f.readData(text,len) != len) + return false; + text[len] = '\0'; + return parse(text,fragment); +} + void ScriptParser::setCode(ScriptCode* code) { ScriptCode* tmp = m_code; @@ -41,25 +76,56 @@ void ScriptParser::setCode(ScriptCode* code) TelEngine::destruct(tmp); } +ScriptContext* ScriptParser::createContext() const +{ + return new BasicContext; +} -bool ScriptContext::runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +ScriptRun* ScriptParser::createRunner(ScriptCode* code, ScriptContext* context) const +{ + if (!code) + return 0; + ScriptContext* ctxt = 0; + if (!context) + context = ctxt = createContext(); + ScriptRun* runner = new ScriptRun(code,context); + TelEngine::destruct(ctxt); + return runner; +} + + +// RTTI Interface access +void* ScriptContext::getObject(const String& name) const +{ + if (name == YSTRING("ExpExtender")) + return const_cast(static_cast(this)); + return RefObject::getObject(name); +} + +bool ScriptContext::hasField(ObjList& stack, const String& name, GenObject* context) const +{ + return m_params.getParam(name) != 0; +} + +NamedString* ScriptContext::getField(ObjList& stack, const String& name, GenObject* context) const +{ + return m_params.getParam(name); +} + +bool ScriptContext::runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context) { return false; } -bool ScriptContext::runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +bool ScriptContext::runField(ObjList& stack, const ExpOperation& oper, GenObject* context) { - if (!eval) - return false; XDebug(DebugAll,"ScriptContext::runField '%s'",oper.name().c_str()); - ExpEvaluator::pushOne(stack,new ExpOperation(m_params[oper.name()],oper.name())); + ExpEvaluator::pushOne(stack,new ExpOperation(m_params[oper.name()],oper.name(),true)); return true; } -bool ScriptContext::runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context) +bool ScriptContext::runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context) { - if (!eval) - return false; XDebug(DebugAll,"ScriptContext::runAssign '%s'='%s'",oper.name().c_str(),oper.c_str()); m_params.setParam(oper.name(),oper); return true; @@ -80,19 +146,21 @@ ScriptRun::ScriptRun(ScriptCode* code, ScriptContext* context) : Mutex(true,"ScriptRun"), m_state(Invalid) { + XDebug(DebugAll,"ScriptRun::ScriptRun(%p,%p) [%p]",code,context,this); if (code) code->ref(); m_code = code; if (context) context->ref(); else - context = new ScriptContext; + context = new BasicContext; m_context = context; reset(); } ScriptRun::~ScriptRun() { + XDebug(DebugAll,"ScriptRun::~ScriptRun [%p]",this); lock(); m_state = Invalid; TelEngine::destruct(m_code); @@ -121,11 +189,10 @@ ScriptRun::Status ScriptRun::resume() if (Running != m_state) return m_state; RefPointer code = m_code; - RefPointer ctxt = m_context; - if (!(code && ctxt)) + if (!(code && context())) return Invalid; mylock.drop(); - return code->evaluate(*ctxt,stack()) ? Succeeded : Failed; + return code->evaluate(*this,stack()) ? Succeeded : Failed; } // Execute one or more instructions of code from where it was left @@ -156,4 +223,17 @@ ScriptRun::Status ScriptRun::run() return s; } +// Execute an assignment operation +bool ScriptRun::runAssign(const ExpOperation& oper, GenObject* context) +{ + Lock mylock(this); + if (Invalid == m_state || !m_code || !m_context) + return false; + RefPointer ctxt = m_context; + mylock.drop(); + ObjList noStack; + Lock ctxLock(ctxt->mutex()); + return ctxt->runAssign(noStack,oper,context); +} + /* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yscript/yatescript.h b/libs/yscript/yatescript.h index c264c9f3..15ff7b64 100644 --- a/libs/yscript/yatescript.h +++ b/libs/yscript/yatescript.h @@ -54,39 +54,60 @@ class ExpOperation; * This class allows extending ExpEvaluator to implement custom fields and functions * @short ExpEvaluator extending interface */ -class YSCRIPT_API ExpExtender : public RefObject +class YSCRIPT_API ExpExtender { - YCLASS(ExpExtender,RefObject) public: + /** + * Retrieve the reference counted object owning this interface + * @return Pointer to object owning the extender, NULL if no ownership + */ + virtual RefObject* refObj(); + + /** + * Check if a certain field is assigned in extender + * @param stack Evaluation stack in use + * @param name Name of the field to test + * @param context Pointer to arbitrary object passed from evaluation methods + * @return True if the field is present + */ + virtual bool hasField(ObjList& stack, const String& name, GenObject* context) const; + + /** + * Get a pointer to a field in extender + * @param stack Evaluation stack in use + * @param name Name of the field to retrieve + * @param context Pointer to arbitrary object passed from evaluation methods + * @return Pointer to field, NULL if not present + */ + virtual NamedString* getField(ObjList& stack, const String& name, GenObject* context) const; + /** * Try to evaluate a single function - * @param eval Pointer to the caller evaluator object * @param stack Evaluation stack in use, parameters are popped off this stack * and results are pushed back on stack * @param oper Function to evaluate - * @param context Pointer to arbitrary data passed from evaluation methods + * @param context Pointer to arbitrary object passed from evaluation methods * @return True if evaluation succeeded */ - virtual bool runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context); + virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context); /** * Try to evaluate a single field - * @param eval Pointer to the caller evaluator object * @param stack Evaluation stack in use, field value must be pushed on it * @param oper Field to evaluate - * @param context Pointer to arbitrary data passed from evaluation methods + * @param context Pointer to arbitrary object passed from evaluation methods * @return True if evaluation succeeded */ - virtual bool runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context); + virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context); /** * Try to assign a value to a single field - * @param eval Pointer to the caller evaluator object + * @param stack Evaluation stack in use * @param oper Field to assign to, contains the field name and new value - * @param context Pointer to arbitrary data passed from evaluation methods + * @param context Pointer to arbitrary object passed from evaluation methods * @return True if assignment succeeded */ - virtual bool runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context); + virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context); }; /** @@ -166,10 +187,12 @@ public: OpcField, // (A --- A) // Call of function with N parameters OpcFunc, // (... funcN --- func(...)) - // Private extension area for derived classes - OpcPrivate = 0x0100, + // Label for a jump + OpcLabel, // ( --- ) // Field assignment - can be ORed with other binary operators - OpcAssign = 0x1000 // (A B --- B,(&A=B)) + OpcAssign = 0x0100, // (A B --- B,(&A=B)) + // Private extension area for derived classes + OpcPrivate = 0x1000 }; /** @@ -199,25 +222,26 @@ public: /** * Parse and compile an expression * @param expr Pointer to expression to compile + * @param context Pointer to arbitrary object to be passed to called methods * @return Number of expressions compiled, zero on error */ - int compile(const char* expr); + int compile(const char* expr, GenObject* context = 0); /** * Evaluate the expression, optionally return results * @param results List to fill with results row - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if expression evaluation succeeded, false on failure */ - bool evaluate(ObjList* results, void* context = 0) const; + bool evaluate(ObjList* results, GenObject* context = 0) const; /** * Evaluate the expression, return computed results * @param results List to fill with results row - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if expression evaluation succeeded, false on failure */ - inline bool evaluate(ObjList& results, void* context = 0) const + inline bool evaluate(ObjList& results, GenObject* context = 0) const { return evaluate(&results,context); } /** @@ -225,19 +249,19 @@ public: * @param results List of parameters to populate with results row * @param index Index of result row, zero to not include an index * @param prefix Prefix to prepend to parameter names - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return Number of result columns, -1 on failure */ - int evaluate(NamedList& results, unsigned int index = 0, const char* prefix = 0, void* context = 0) const; + int evaluate(NamedList& results, unsigned int index = 0, const char* prefix = 0, GenObject* context = 0) const; /** * Evaluate the expression, return computed results * @param results Array of result rows to populate * @param index Index of result row, zero to just set column headers - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return Number of result columns, -1 on failure */ - int evaluate(Array& results, unsigned int index, void* context = 0) const; + int evaluate(Array& results, unsigned int index, GenObject* context = 0) const; /** * Simplify the expression, performs constant folding @@ -246,6 +270,13 @@ public: inline bool simplify() { return trySimplify(); } + /** + * Check if a parse or compile error was encountered + * @return True if the evaluator encountered an error + */ + inline bool inError() const + { return m_inError; } + /** * Check if the expression is empty (no operands or operators) * @return True if the expression is completely empty @@ -330,6 +361,14 @@ public: */ static ExpOperation* popAny(ObjList& stack); + /** + * Pops and evaluate the value of an operand off an evaluation stack, does not pop a barrier + * @param stack Evaluation stack to remove the operand from + * @param context Pointer to arbitrary object to be passed to called methods + * @return Value removed from stack, NULL if stack underflow or field not evaluable + */ + virtual ExpOperation* popValue(ObjList& stack, GenObject* context = 0) const; + protected: /** * Helper method to skip over whitespaces @@ -378,13 +417,38 @@ protected: */ bool gotError(const char* error = 0, const char* text = 0) const; + /** + * Helper method to set error flag and display debugging errors internally + * @param error Text of the error + * @param text Optional text that caused the error + * @return Always returns false + */ + bool gotError(const char* error = 0, const char* text = 0); + /** * Runs the parser and compiler for one (sub)expression * @param expr Pointer to text to parse, gets advanced * @param stop Optional character expected after the expression + * @param nested The instruction within this expression is nested * @return True if one expression was compiled and a separator follows */ - virtual bool runCompile(const char*& expr, char stop = 0); + virtual bool runCompile(const char*& expr, char stop = 0, Opcode nested = OpcNone); + + /** + * Skip over comments and whitespaces + * @param expr Pointer to expression cursor, gets advanced + * @param context Pointer to arbitrary object to be passed to called methods + * @return First character after comments or whitespaces where expr points + */ + virtual char skipComments(const char*& expr, GenObject* context = 0) const; + + /** + * Process top-level preprocessor directives + * @param expr Pointer to expression cursor, gets advanced + * @param context Pointer to arbitrary object to be passed to called methods + * @return Number of expressions compiled, negative if no more directives + */ + virtual int preProcess(const char*& expr, GenObject* context = 0); /** * Returns next operator in the parsed text @@ -439,16 +503,18 @@ protected: /** * Get an instruction or block, advance parsing pointer past it * @param expr Pointer to text to parse, gets advanced on success + * @param nested The instruction within this one is nested * @return True if succeeded, must add the operands internally */ - virtual bool getInstruction(const char*& expr); + virtual bool getInstruction(const char*& expr, Opcode nested = OpcNone); /** * Get an operand, advance parsing pointer past it * @param expr Pointer to text to parse, gets advanced on success + * @param endOk Consider reaching the end of string a success * @return True if succeeded, must add the operand internally */ - virtual bool getOperand(const char*& expr); + virtual bool getOperand(const char*& expr, bool endOk = true); /** * Get a numerical operand, advance parsing pointer past it @@ -481,21 +547,35 @@ protected: /** * Add a simple operator to the expression * @param oper Operator code to add - * @param barrier True to create an exavuator stack barrier + * @param barrier True to create an evaluator stack barrier */ - void addOpcode(Opcode oper, bool barrier = false); + ExpOperation* addOpcode(Opcode oper, bool barrier = false); + + /** + * Add a simple operator to the expression + * @param oper Operator code to add + * @param value Integer value to add + * @param barrier True to create an evaluator stack barrier + */ + ExpOperation* addOpcode(Opcode oper, long int value, bool barrier = false); /** * Add a string constant to the expression * @param value String value to add, will be pushed on execution */ - void addOpcode(const String& value); + ExpOperation* addOpcode(const String& value); /** * Add an integer constant to the expression * @param value Integer value to add, will be pushed on execution */ - void addOpcode(long int value); + ExpOperation* addOpcode(long int value); + + /** + * Add a boolean constant to the expression + * @param value Boolean value to add, will be pushed on execution + */ + ExpOperation* addOpcode(bool value); /** * Add a function or field to the expression @@ -504,7 +584,7 @@ protected: * @param value Numerical value used as parameter count to functions * @param barrier True to create an exavuator stack barrier */ - void addOpcode(Opcode oper, const String& name, long int value = 0, bool barrier = false); + ExpOperation* addOpcode(Opcode oper, const String& name, long int value = 0, bool barrier = false); /** * Try to apply simplification to the expression @@ -512,85 +592,78 @@ protected: */ virtual bool trySimplify(); - /** - * Pops and evaluate the value of an operand off an evaluation stack, does not pop a barrier - * @param stack Evaluation stack to remove the operand from - * @param context Pointer to arbitrary data to be passed to called methods - * @return Value removed from stack, NULL if stack underflow or field not evaluable - */ - virtual ExpOperation* popValue(ObjList& stack, void* context = 0) const; - /** * Try to evaluate a list of operation codes * @param opcodes List of operation codes to evaluate * @param stack Evaluation stack in use, results are left on stack - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if evaluation succeeded */ - virtual bool runEvaluate(const ObjList& opcodes, ObjList& stack, void* context = 0) const; + virtual bool runEvaluate(const ObjList& opcodes, ObjList& stack, GenObject* context = 0) const; /** * Try to evaluate a vector of operation codes * @param opcodes ObjVector of operation codes to evaluate * @param stack Evaluation stack in use, results are left on stack - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @param index Index in operation codes to start evaluation from * @return True if evaluation succeeded */ - virtual bool runEvaluate(const ObjVector& opcodes, ObjList& stack, void* context = 0, unsigned int index = 0) const; + virtual bool runEvaluate(const ObjVector& opcodes, ObjList& stack, GenObject* context = 0, unsigned int index = 0) const; /** * Try to evaluate the expression * @param stack Evaluation stack in use, results are left on stack - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if evaluation succeeded */ - virtual bool runEvaluate(ObjList& stack, void* context = 0) const; + virtual bool runEvaluate(ObjList& stack, GenObject* context = 0) const; /** * Convert all fields on the evaluation stack to their values * @param stack Evaluation stack to evaluate fields from - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if all fields on the stack were evaluated properly */ - virtual bool runAllFields(ObjList& stack, void* context = 0) const; + virtual bool runAllFields(ObjList& stack, GenObject* context = 0) const; /** * Try to evaluate a single operation * @param stack Evaluation stack in use, operands are popped off this stack * and results are pushed back on stack * @param oper Operation to execute - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if evaluation succeeded */ - virtual bool runOperation(ObjList& stack, const ExpOperation& oper, void* context = 0) const; + virtual bool runOperation(ObjList& stack, const ExpOperation& oper, GenObject* context = 0) const; /** * Try to evaluate a single function * @param stack Evaluation stack in use, parameters are popped off this stack * and results are pushed back on stack * @param oper Function to evaluate - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if evaluation succeeded */ - virtual bool runFunction(ObjList& stack, const ExpOperation& oper, void* context = 0) const; + virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context = 0) const; /** * Try to evaluate a single field * @param stack Evaluation stack in use, field value must be pushed on it * @param oper Field to evaluate - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if evaluation succeeded */ - virtual bool runField(ObjList& stack, const ExpOperation& oper, void* context = 0) const; + virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context = 0) const; /** * Try to assign a value to a single field + * @param stack Evaluation stack in use * @param oper Field to assign to, contains the field name and new value - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if assignment succeeded */ - virtual bool runAssign(const ExpOperation& oper, void* context = 0) const; + virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context = 0) const; /** * Internally used operator dictionary @@ -607,6 +680,11 @@ protected: */ ObjList m_opcodes; + /** + * Flag that we encountered a parse or compile error + */ + bool m_inError; + private: ExpExtender* m_extender; }; @@ -652,11 +730,23 @@ public: * Push String constructor * @param value String constant to push on stack on execution * @param name Optional of the newly created constant + * @param autoNum Automatically convert to number if possible */ - inline explicit ExpOperation(const String& value, const char* name = 0) + inline explicit ExpOperation(const String& value, const char* name = 0, bool autoNum = false) : NamedString(name,value), - m_opcode(ExpEvaluator::OpcPush), m_number(nonInteger()), + m_opcode(ExpEvaluator::OpcPush), + m_number(autoNum ? value.toLong(nonInteger()) : nonInteger()), m_barrier(false) + { if (autoNum && value.isBoolean()) m_number = value.toBoolean() ? 1 : 0; } + + /** + * Push literal string constructor + * @param value String constant to push on stack on execution + * @param name Optional of the newly created constant + */ + inline explicit ExpOperation(const char* value, const char* name = 0) + : NamedString(name,value), + m_opcode(ExpEvaluator::OpcPush), m_number(nonInteger()), m_barrier(false) { } /** @@ -669,6 +759,16 @@ public: m_opcode(ExpEvaluator::OpcPush), m_number(value), m_barrier(false) { String::operator=((int)value); } + /** + * Push Boolean constructor + * @param value Boolean constant to push on stack on execution + * @param name Optional of the newly created constant + */ + inline explicit ExpOperation(bool value, const char* name = 0) + : NamedString(name,String::boolText(value)), + m_opcode(ExpEvaluator::OpcPush), m_number(value ? 1 : 0), m_barrier(false) + { } + /** * Constructor from components * @param oper Operation code @@ -681,6 +781,18 @@ public: m_opcode(oper), m_number(value), m_barrier(barrier) { } + /** + * Constructor of non-integer operation from components + * @param oper Operation code + * @param name Name of the operation or result + * @param value String value of operation + * @param barrier True if the operation is an expression barrier on the stack + */ + inline ExpOperation(ExpEvaluator::Opcode oper, const char* name, const char* value, bool barrier = false) + : NamedString(name,value), + m_opcode(oper), m_number(nonInteger()), m_barrier(barrier) + { } + /** * Retrieve the code of this operation * @return Operation code as declared in the expression evaluator @@ -717,12 +829,101 @@ public: inline long int operator=(long int num) { m_number = num; String::operator=((int)num); return num; } + /** + * Clone and rename method + * @param name Name of the cloned operation + * @return New operation instance + */ + virtual ExpOperation* clone(const char* name) const + { return new ExpOperation(*this,name); } + + /** + * Clone method + * @return New operation instance + */ + inline ExpOperation* clone() const + { return clone(name()); } + private: ExpEvaluator::Opcode m_opcode; long int m_number; bool m_barrier; }; +/** + * Small helper class that simplifies declaring native functions + * @short Helper class to declare a native function + */ +class YSCRIPT_API ExpFunction : public ExpOperation +{ + YCLASS(ExpFunction,ExpOperation) +public: + /** + * Constructor + * @param name Name of the function + * @param argc Number of arguments expected by function + */ + inline ExpFunction(const char* name, long int argc = 0) + : ExpOperation(ExpEvaluator::OpcFunc,name,argc) + { if (name) (*this) << "[function " << name << "()]"; } + + /** + * Clone and rename method + * @param name Name of the cloned operation + * @return New operation instance + */ + virtual ExpOperation* clone(const char* name) const; +}; + +/** + * Helper class that allows wrapping entire objects in an operation + * @short Object wrapper for evaluation + */ +class YSCRIPT_API ExpWrapper : public ExpOperation +{ +public: + /** + * Constructor + * @param object Pointer to the object to wrap + * @param name Optional name of the wrapper + */ + inline ExpWrapper(GenObject* object, const char* name = 0) + : ExpOperation(ExpEvaluator::OpcPush,name, + object ? object->toString().c_str() : (const char*)0), + m_object(object) + { } + + /** + * Destuctor, deletes the held object + */ + virtual ~ExpWrapper() + { TelEngine::destruct(m_object); } + + /** + * Get a pointer to a derived class given that class name + * @param name Name of the class we are asking for + * @return Pointer to the requested class or NULL if this object doesn't implement it + */ + virtual void* getObject(const String& name) const; + + /** + * Clone and rename method + * @param name Name of the cloned operation + * @return New operation instance + */ + virtual ExpOperation* clone(const char* name) const; + + /** + * Object access method + * @return Pointer to the held object + */ + GenObject* object() const + { return m_object; } + +private: + GenObject* m_object; +}; + /** * An evaluator for multi-row (tables like in SQL) expressions * @short An SQL-like table evaluator @@ -756,25 +957,25 @@ public: /** * Evaluate the WHERE (selector) expression - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if the current row is part of selection */ - virtual bool evalWhere(void* context = 0); + virtual bool evalWhere(GenObject* context = 0); /** * Evaluate the SELECT (results) expression * @param results List to fill with results row - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return True if evaluation succeeded */ - virtual bool evalSelect(ObjList& results, void* context = 0); + virtual bool evalSelect(ObjList& results, GenObject* context = 0); /** * Evaluate the LIMIT expression and cache the result - * @param context Pointer to arbitrary data to be passed to called methods + * @param context Pointer to arbitrary object to be passed to called methods * @return Desired maximum number or result rows */ - virtual unsigned int evalLimit(void* context = 0); + virtual unsigned int evalLimit(GenObject* context = 0); /** * Set the expression extender to use in all evaluators @@ -791,13 +992,59 @@ protected: class ScriptRun; +/** + * A class used to build stack based (posifix) script parsers and evaluators + * @short A script parser and evaluator + */ +class YSCRIPT_API ScriptEvaluator : public ExpEvaluator +{ + YNOCOPY(ScriptEvaluator); +public: + + /** + * Constructs ascript evaluator from an operator dictionary + * @param operators Pointer to operator dictionary, longest strings first + * @param unaryOps Pointer to unary operators dictionary, longest strings first + */ + inline explicit ScriptEvaluator(const TokenDict* operators = 0, const TokenDict* unaryOps = 0) + : ExpEvaluator(operators,unaryOps) + { } + + /** + * Constructs a script evaluator from a parser style + * @param style Style of parsing to use + */ + inline explicit ScriptEvaluator(Parser style) + : ExpEvaluator(style) + { } + + /** + * Try to execute a single operation + * @param stack Evaluation stack in use, operands are popped off this stack + * and results are pushed back on stack + * @param oper Script operation to execute + * @param context Pointer to arbitrary object to be passed to called methods + * @return True if evaluation succeeded + */ + inline bool runOperation(ObjList &stack, const ExpOperation &oper, GenObject* context = 0) const + { return ExpEvaluator::runOperation(stack,oper,context); } + + /** + * Convert all fields on the evaluation stack to their values + * @param stack Evaluation stack to evaluate fields from + * @param context Pointer to arbitrary object to be passed to called methods + * @return True if all fields on the stack were evaluated properly + */ + inline bool runAllFields(ObjList& stack, GenObject* context = 0) const + { return ExpEvaluator::runAllFields(stack,context); } +}; + /** * A script execution context, holds global variables and objects * @short Script execution context */ -class YSCRIPT_API ScriptContext : public ExpExtender, public Mutex +class YSCRIPT_API ScriptContext : public RefObject, public ExpExtender { - YCLASS(ScriptContext,ExpExtender) public: /** * Constructor @@ -828,34 +1075,70 @@ public: virtual const String& toString() const { return m_params; } + /** + * Get a pointer to a derived class given that class name + * @param name Name of the class we are asking for + * @return Pointer to the requested class or NULL if this object doesn't implement it + */ + virtual void* getObject(const String& name) const; + + /** + * Retrieve the reference counted object owning this interface + * @return Pointer to this script context + */ + virtual RefObject* refObj() + { return this; } + + /** + * Retrieve the Mutex object used to serialize object access, if any + * @return Pointer to the mutex or NULL if none applies + */ + virtual Mutex* mutex() = 0; + + /** + * Check if a certain field is assigned in context + * @param stack Evaluation stack in use + * @param name Name of the field to test + * @param context Pointer to arbitrary object passed from evaluation methods + * @return True if the field is present + */ + virtual bool hasField(ObjList& stack, const String& name, GenObject* context) const; + + /** + * Get a pointer to a field in the context + * @param stack Evaluation stack in use + * @param name Name of the field to retrieve + * @param context Pointer to arbitrary object passed from evaluation methods + * @return Pointer to field, NULL if not present + */ + virtual NamedString* getField(ObjList& stack, const String& name, GenObject* context) const; + /** * Try to evaluate a single function in the context - * @param eval Pointer to the caller evaluator object * @param stack Evaluation stack in use, parameters are popped off this stack and results are pushed back on stack * @param oper Function to evaluate * @param context Pointer to context data passed from evaluation methods * @return True if evaluation succeeded */ - virtual bool runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context); + virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context); /** * Try to evaluate a single field in the context - * @param eval Pointer to the caller evaluator object * @param stack Evaluation stack in use, field value must be pushed on it * @param oper Field to evaluate * @param context Pointer to context data passed from evaluation methods * @return True if evaluation succeeded */ - virtual bool runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context); + virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context); /** * Try to assign a value to a single field - * @param eval Pointer to the caller evaluator object + * @param stack Evaluation stack in use * @param oper Field to assign to, contains the field name and new value * @param context Pointer to context data passed from evaluation methods * @return True if assignment succeeded */ - virtual bool runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context); + virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context); private: NamedList m_params; @@ -878,10 +1161,10 @@ public: /** * Evaluation of a single code expression - * @param context Reference to the context to use in evaluation + * @param runner Reference to the runtime to use in evaluation * @param results List to fill with expression results */ - virtual bool evaluate(ScriptContext& context, ObjList& results) const = 0; + virtual bool evaluate(ScriptRun& runner, ObjList& results) const = 0; }; /** @@ -917,7 +1200,9 @@ private: */ class YSCRIPT_API ScriptRun : public GenObject, public Mutex { + friend class ScriptCode; YCLASS(ScriptRun,GenObject) + YNOCOPY(ScriptRun); public: /** * Runtime states @@ -990,6 +1275,13 @@ public: inline const ObjList& stack() const { return m_stack; } + /** + * Create a duplicate of the runtime with its own stack and state + * @return New clone of the runtime + */ + inline ScriptRun* clone() const + { return new ScriptRun(code(),context()); } + /** * Resets code execution to the beginning, does not clear context * @return Status of the runtime after reset @@ -1008,6 +1300,14 @@ public: */ Status run(); + /** + * Try to assign a value to a single field in the script context + * @param oper Field to assign to, contains the field name and new value + * @param context Pointer to arbitrary object to be passed to called methods + * @return True if assignment succeeded + */ + bool runAssign(const ExpOperation& oper, GenObject* context = 0); + protected: /** * Resume script from where it was left, may stop and return Incomplete state @@ -1038,9 +1338,24 @@ public: /** * Parse a string as script source code * @param text Source code text + * @param fragment True if the code is just an included fragment * @return True if the text was successfully parsed */ - virtual bool parse(const char* text) = 0; + virtual bool parse(const char* text, bool fragment = false) = 0; + + /** + * Parse a file as script source code + * @param name Source file name + * @param fragment True if the code is just an included fragment + * @return True if the file was successfully parsed + */ + virtual bool parseFile(const char* name, bool fragment = false); + + /** + * Clear any existing parsed code + */ + inline void clear() + { setCode(0); } /** * Retrieve the currently stored parsed code @@ -1049,6 +1364,28 @@ public: inline ScriptCode* code() const { return m_code; } + /** + * Create a context adequate for the parsed code + * @return A new script context + */ + virtual ScriptContext* createContext() const; + + /** + * Create a runner adequate for a block of parsed code + * @param code Parsed code block + * @param context Script context, an empty one will be allocated if NULL + * @return A new script runner, NULL if code is NULL + */ + virtual ScriptRun* createRunner(ScriptCode* code, ScriptContext* context = 0) const; + + /** + * Create a runner adequate for the parsed code + * @param context Script context, an empty one will be allocated if NULL + * @return A new script runner, NULL if code is not yet parsed + */ + inline ScriptRun* createRunner(ScriptContext* context = 0) const + { return createRunner(code(),context); } + protected: /** * Default constructor for derived classes @@ -1071,33 +1408,65 @@ private: * Javascript Object class, base for all JS objects * @short Javascript Object */ -class YSCRIPT_API JsObject : public NamedList +class YSCRIPT_API JsObject : public ScriptContext { - YCLASS(JsObject,NamedList) + YCLASS(JsObject,ScriptContext) public: /** * Constructor * @param name Name of the object + * @param mtx Pointer to the mutex that serializes this object * @param frozen True if the object is to be frozen from creation */ - inline JsObject(const char* name = "Object", bool frozen = false) - : NamedList(String("[Object ") + name + "]"), - m_frozen(frozen) - { } + JsObject(const char* name = "Object", Mutex* mtx = 0, bool frozen = false); /** - * Access the list of object attributes and methods - * @return The list of attributes and functions + * Destructor */ - virtual NamedList& list() - { return *this; } + virtual ~JsObject(); /** - * Const access to the list of object attributes and methods - * @return The list of attributes and functions + * Retrieve the Mutex object used to serialize object access + * @return Pointer to the mutex of the context this object belongs to */ - virtual const NamedList& list() const - { return *this; } + virtual Mutex* mutex() + { return m_mutex; } + + /** + * Try to evaluate a single method + * @param stack Evaluation stack in use, parameters are popped off this stack + * and results are pushed back on stack + * @param oper Function to evaluate + * @param context Pointer to arbitrary object passed from evaluation methods + * @return True if evaluation succeeded + */ + virtual bool runFunction(ObjList& stack, const ExpOperation& oper, GenObject* context); + + /** + * Try to evaluate a single field + * @param stack Evaluation stack in use, field value must be pushed on it + * @param oper Field to evaluate + * @param context Pointer to arbitrary object passed from evaluation methods + * @return True if evaluation succeeded + */ + virtual bool runField(ObjList& stack, const ExpOperation& oper, GenObject* context); + + /** + * Try to assign a value to a single field if object is not frozen + * @param stack Evaluation stack in use + * @param oper Field to assign to, contains the field name and new value + * @param context Pointer to arbitrary object passed from evaluation methods + * @return True if assignment succeeded + */ + virtual bool runAssign(ObjList& stack, const ExpOperation& oper, GenObject* context); + + /** + * Pops and evaluate the value of an operand off an evaluation stack, does not pop a barrier + * @param stack Evaluation stack to remove the operand from + * @param context Pointer to arbitrary object to be passed to called methods + * @return Value removed from stack, NULL if stack underflow or field not evaluable + */ + virtual ExpOperation* popValue(ObjList& stack, GenObject* context = 0); /** * Retrieve the object frozen status (cannot modify attributes or methods) @@ -1116,10 +1485,51 @@ public: * Initialize the standard global objects in a context * @param context Script context to initialize */ - static void initialize(ScriptContext& context); + static void initialize(ScriptContext* context); + +protected: + /** + * Try to evaluate a single native method + * @param stack Evaluation stack in use, parameters are popped off this stack + * and results are pushed back on stack + * @param oper Function to evaluate + * @param context Pointer to arbitrary object passed from evaluation methods + * @return True if evaluation succeeded + */ + virtual bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); private: bool m_frozen; + Mutex* m_mutex; +}; + +/** + * Javascript Function class, implements user defined functions + * @short Javascript Function + */ +class YSCRIPT_API JsFunction : public JsObject +{ + YCLASS(JsFunction,JsObject) +public: + + /** + * Constructor + * @param mtx Pointer to the mutex that serializes this object + */ + inline JsFunction(Mutex* mtx = 0) + : JsObject("Function",mtx,true) + { } + + /** + * Try to evaluate a single user defined method + * @param stack Evaluation stack in use, parameters are popped off this stack + * and results are pushed back on stack + * @param oper Function to evaluate + * @param context Pointer to arbitrary object passed from evaluation methods + * @return True if evaluation succeeded + */ + virtual bool runDefined(ObjList& stack, const ExpOperation& oper, GenObject* context); + }; /** @@ -1133,9 +1543,52 @@ public: /** * Parse a string as Javascript source code * @param text Source code text + * @param fragment True if the code is just an included fragment * @return True if the text was successfully parsed */ - virtual bool parse(const char* text); + virtual bool parse(const char* text, bool fragment = false); + + /** + * Create a context adequate for Javascript code + * @return A new Javascript context + */ + virtual ScriptContext* createContext() const; + + /** + * Create a runner adequate for a block of parsed Javascript code + * @param code Parsed code block + * @param context Javascript context, an empty one will be allocated if NULL + * @return A new Javascript runner, NULL if code is NULL + */ + virtual ScriptRun* createRunner(ScriptCode* code, ScriptContext* context = 0) const; + + /** + * Create a runner adequate for the parsed Javascript code + * @param context Javascript context, an empty one will be allocated if NULL + * @return A new Javascript runner, NULL if code is not yet parsed + */ + inline ScriptRun* createRunner(ScriptContext* context = 0) const + { return createRunner(code(),context); } + + /** + * Adjust a file script path to include default if needed + * @param script File path to adjust + */ + void adjustPath(String& script); + + /** + * Retrieve the base script path + * @return Base path added to relative script paths + */ + inline const String& basePath() const + { return m_basePath; } + + /** + * Set the pase script path + * @param path Base path to add to relative script paths + */ + inline void basePath(const char* path) + { m_basePath = path; } /** * Parse and run a piece of Javascript code @@ -1145,6 +1598,9 @@ public: * @return Status of the runtime after code execution */ static ScriptRun::Status eval(const String& text, ExpOperation** result = 0, ScriptContext* context = 0); + +private: + String m_basePath; }; }; // namespace TelEngine diff --git a/modules/Makefile.in b/modules/Makefile.in index 3b9a95a8..f926ef9a 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -369,9 +369,9 @@ server/sipfeatures.yate: ../libs/yxml/libyatexml.a server/sipfeatures.yate: LOCALFLAGS = -I@top_srcdir@/libs/yxml server/sipfeatures.yate: LOCALLIBS = -L../libs/yxml -lyatexml -javascript.yate: ../libyatescript.so -javascript.yate: LOCALFLAGS = -I@top_srcdir@/libs/yscript -javascript.yate: LOCALLIBS = -lyatescript +javascript.yate: ../libyatescript.so ../libs/ypbx/libyatepbx.a +javascript.yate: LOCALFLAGS = -I@top_srcdir@/libs/yscript -I@top_srcdir@/libs/ypbx +javascript.yate: LOCALLIBS = -lyatescript -L../libs/ypbx -lyatepbx zlibcompress.yate: LOCALFLAGS = $(ZLIB_INC) zlibcompress.yate: LOCALLIBS = $(ZLIB_LIB) diff --git a/modules/javascript.cpp b/modules/javascript.cpp index 6a81efee..e6567080 100644 --- a/modules/javascript.cpp +++ b/modules/javascript.cpp @@ -2,7 +2,7 @@ * javascript.cpp * This file is part of the YATE Project http://YATE.null.ro * - * Javascript support based on libyscript + * Javascript channel support based on libyscript * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2011 Null Team @@ -22,26 +22,128 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include +#include #include using namespace TelEngine; namespace { // anonymous -class JavascriptModule : public Module +class JsModule : public ChanAssistList { public: - JavascriptModule(); - ~JavascriptModule(); + enum { + Preroute = AssistPrivate + }; + JsModule(); + virtual ~JsModule(); virtual void initialize(); + virtual void init(int priority); + virtual ChanAssist* create(Message& msg, const String& id); bool unload(); + virtual bool received(Message& msg, int id); + virtual bool received(Message& msg, int id, ChanAssist* assist); + inline JsParser& parser() + { return m_assistCode; } protected: virtual bool commandExecute(String& retVal, const String& line); virtual bool commandComplete(Message& msg, const String& partLine, const String& partWord); +private: + JsParser m_assistCode; }; +class JsAssist : public ChanAssist +{ +public: + inline JsAssist(ChanAssistList* list, const String& id, ScriptRun* runner) + : ChanAssist(list, id), m_runner(runner) + { } + virtual ~JsAssist(); + virtual void msgStartup(Message& msg); + virtual void msgHangup(Message& msg); + virtual void msgExecute(Message& msg); + virtual bool msgPreroute(Message& msg); + virtual bool msgRoute(Message& msg); + virtual bool msgDisconnect(Message& msg, const String& reason); + bool init(); +private: + bool runFunction(const char* name, Message& msg); + ScriptRun* m_runner; +}; -INIT_PLUGIN(JavascriptModule); +#define MKDEBUG(lvl) params().addParam(new ExpOperation((long int)Debug ## lvl,"Debug" # lvl)) +class JsEngine : public JsObject +{ + YCLASS(JsEngine,JsObject) +public: + inline JsEngine(Mutex* mtx) + : JsObject("Engine",mtx,true) + { + MKDEBUG(Fail); + MKDEBUG(GoOn); + MKDEBUG(Conf); + MKDEBUG(Stub); + MKDEBUG(Warn); + MKDEBUG(Mild); + MKDEBUG(Call); + MKDEBUG(Note); + MKDEBUG(Info); + MKDEBUG(All); + params().addParam(new ExpFunction("Output")); + params().addParam(new ExpFunction("Debug")); + } + static void initialize(ScriptContext* context); +protected: + bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); +}; +#undef MKDEBUG + +class JsMessage : public JsObject +{ + YCLASS(JsMessage,JsObject) +public: + inline JsMessage(Mutex* mtx) + : JsObject("Message",mtx,true), m_message(0) + { + XDebug(DebugAll,"JsMessage::JsMessage() [%p]",this); + params().addParam(new ExpFunction("constructor")); + params().addParam(new ExpFunction("enqueue")); + params().addParam(new ExpFunction("dispatch")); + } + inline JsMessage(Message* message, Mutex* mtx) + : JsObject("Message",mtx), m_message(message) + { + XDebug(DebugAll,"JsMessage::JsMessage(%p) [%p]",message,this); + params().addParam(new ExpFunction("broadcast")); + params().addParam(new ExpFunction("acknowledge")); + } + virtual ~JsMessage() + { + XDebug(DebugAll,"JsMessage::~JsMessage() [%p]",this); + } + static void initialize(ScriptContext* context); +protected: + bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); + Message* m_message; +}; + +class JsChannel : public JsObject +{ + YCLASS(JsChannel,JsObject) +public: + inline JsChannel(JsAssist* assist, Mutex* mtx) + : JsObject("Channel",mtx,true), m_assist(assist) + { + params().addParam(new ExpFunction("id")); + } + static void initialize(ScriptContext* context, JsAssist* assist); +protected: + bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context); + JsAssist* m_assist; +}; + +static String s_basePath; + +INIT_PLUGIN(JsModule); UNLOAD_PLUGIN(unloadNow) { @@ -50,35 +152,226 @@ UNLOAD_PLUGIN(unloadNow) return true; } -JavascriptModule::JavascriptModule() - : Module("javascript","misc",true) + +// Helper function that adds an object to a parent +static inline void addObject(NamedList& params, const char* name, JsObject* obj) +{ + params.addParam(new NamedPointer(name,obj,obj->toString())); +} + + +bool JsEngine::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + if (oper.name() == YSTRING("Output")) { + String str; + for (long int i = oper.number(); i; i--) { + ExpOperation* op = popValue(stack,context); + if (str) + str = *op + " " + str; + else + str = *op; + } + if (str) + Output("%s",str.c_str()); + } + else if (oper.name() == YSTRING("Debug")) { + int level = DebugNote; + String str; + for (long int i = oper.number(); i; i--) { + ExpOperation* op = popValue(stack,context); + if ((i == 1) && oper.number() > 1 && op->isInteger()) + level = op->number(); + else { + if (str) + str = *op + " " + str; + else + str = *op; + } + } + if (str) { + if (level > DebugAll) + level = DebugAll; + else if (level < DebugGoOn) + level = DebugGoOn; + Debug(&__plugin,level,"%s",str.c_str()); + } + } + else + return JsObject::runNative(stack,oper,context); + return true; +} + +void JsEngine::initialize(ScriptContext* context) +{ + if (!context) + return; + Mutex* mtx = context->mutex(); + Lock mylock(mtx); + NamedList& params = context->params(); + if (!params.getParam(YSTRING("Engine"))) + addObject(params,"Engine",new JsEngine(mtx)); +} + + +bool JsMessage::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + if (oper.name() == YSTRING("constructor")) { + ExpEvaluator::pushOne(stack,new ExpWrapper(new JsMessage(mutex()))); + } + else + return JsObject::runNative(stack,oper,context); + return true; +} + +void JsMessage::initialize(ScriptContext* context) +{ + if (!context) + return; + Mutex* mtx = context->mutex(); + Lock mylock(mtx); + NamedList& params = context->params(); + if (!params.getParam(YSTRING("Message"))) + addObject(params,"Message",new JsMessage(mtx)); +} + + +bool JsChannel::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context) +{ + if (oper.name() == YSTRING("id")) { + for (long int i = oper.number(); i; i--) { + TelEngine::destruct(ExpEvaluator::popOne(stack)); + } + RefPointer ja = m_assist; + if (ja) + ExpEvaluator::pushOne(stack,new ExpOperation(ja->id())); + else + return false; + } + else + return JsObject::runNative(stack,oper,context); + return true; +} + +void JsChannel::initialize(ScriptContext* context, JsAssist* assist) +{ + if (!context) + return; + Mutex* mtx = context->mutex(); + Lock mylock(mtx); + NamedList& params = context->params(); + if (!params.getParam(YSTRING("Channel"))) + addObject(params,"Channel",new JsChannel(assist,mtx)); +} + + +JsAssist::~JsAssist() +{ + TelEngine::destruct(m_runner); +} + +bool JsAssist::init() +{ + if (!m_runner) + return false; + JsObject::initialize(m_runner->context()); + JsEngine::initialize(m_runner->context()); + JsChannel::initialize(m_runner->context(),this); + JsMessage::initialize(m_runner->context()); + ScriptRun::Status rval = m_runner->run(); + m_runner->reset(); + return (ScriptRun::Succeeded == rval); +} + +bool JsAssist::runFunction(const char* name, Message& msg) +{ + if (!m_runner) + return false; + DDebug(&__plugin,DebugInfo,"Running function %s in '%s'",name,id().c_str()); + ScriptRun* runner = __plugin.parser().createRunner(m_runner->code(),m_runner->context()); + JsMessage* jm = new JsMessage(&msg,m_runner->context()->mutex()); + ExpWrapper mw(jm,"message"); + runner->runAssign(mw); + ScriptRun::Status rval = runner->run(); + TelEngine::destruct(runner); + return (ScriptRun::Succeeded == rval); +} + +void JsAssist::msgStartup(Message& msg) +{ + runFunction("onStartup",msg); +} + +void JsAssist::msgHangup(Message& msg) +{ + runFunction("onHangup",msg); +} + +void JsAssist::msgExecute(Message& msg) +{ + runFunction("onExecute",msg); +} + +bool JsAssist::msgPreroute(Message& msg) +{ + return false; +} + +bool JsAssist::msgRoute(Message& msg) +{ + return false; +} + +bool JsAssist::msgDisconnect(Message& msg, const String& reason) +{ + return false; +} + + +JsModule::JsModule() + : ChanAssistList("javascript",true) { Output("Loaded module Javascript"); } -JavascriptModule::~JavascriptModule() +JsModule::~JsModule() { Output("Unloading module Javascript"); } -bool JavascriptModule::commandExecute(String& retVal, const String& line) +bool JsModule::commandExecute(String& retVal, const String& line) { if (!line.startsWith("js ")) return false; String cmd = line.substr(3).trimSpaces(); if (cmd.null()) return false; - ExpOperation* rval = 0; - ScriptRun::Status st = JsParser::eval(cmd,&rval); - if (rval) - retVal << "'" << rval->name() << "'='" << *rval << "'\r\n"; + + + + JsParser parser; + parser.basePath(s_basePath); + if (!parser.parse(cmd)) { + retVal << "parsing failed\r\n"; + return true; + } + ScriptRun* runner = parser.createRunner(); + JsObject::initialize(runner->context()); + JsEngine::initialize(runner->context()); + JsMessage::initialize(runner->context()); + ScriptRun::Status st = runner->run(); + if (st == ScriptRun::Succeeded) { + while (ExpOperation* op = ExpEvaluator::popOne(runner->stack())) { + retVal << "'" << op->name() << "'='" << *op << "'\r\n"; + TelEngine::destruct(op); + } + } else retVal << ScriptRun::textState(st) << "\r\n"; - TelEngine::destruct(rval); + TelEngine::destruct(runner); return true; } -bool JavascriptModule::commandComplete(Message& msg, const String& partLine, const String& partWord) +bool JsModule::commandComplete(Message& msg, const String& partLine, const String& partWord) { if (partLine.null() && partWord.null()) return false; @@ -87,16 +380,105 @@ bool JavascriptModule::commandComplete(Message& msg, const String& partLine, con return Module::commandComplete(msg,partLine,partWord); } -bool JavascriptModule::unload() +bool JsModule::received(Message& msg, int id) +{ + String* chanId = msg.getParam("id"); + if (!TelEngine::null(chanId)) { + switch (id) { + case Preroute: + case Route: + { + Lock mylock(this); + RefPointer ca = static_cast(find(*chanId)); + switch (id) { + case Preroute: + if (ca) { + mylock.drop(); + return ca->msgPreroute(msg); + } + ca = static_cast(create(msg,*chanId)); + if (ca) { + calls().append(ca); + mylock.drop(); + ca->msgStartup(msg); + return ca->msgPreroute(msg); + } + return false; + case Route: + if (ca) { + mylock.drop(); + return ca->msgRoute(msg); + } + ca = static_cast(create(msg,*chanId)); + if (ca) { + calls().append(ca); + mylock.drop(); + ca->msgStartup(msg); + return ca->msgRoute(msg); + } + return false; + } + } + } + } + return ChanAssistList::received(msg,id); +} + +bool JsModule::received(Message& msg, int id, ChanAssist* assist) +{ + return ChanAssistList::received(msg,id,assist); +} + +ChanAssist* JsModule::create(Message& msg, const String& id) +{ + lock(); + ScriptRun* runner = m_assistCode.createRunner(); + unlock(); + if (!runner) + return 0; + DDebug(this,DebugAll,"Creating Javascript for '%s'",id.c_str()); + JsAssist* ca = new JsAssist(this,id,runner); + if (ca->init()) + return ca; + TelEngine::destruct(ca); + return 0; +} + +bool JsModule::unload() { uninstallRelays(); return true; } -void JavascriptModule::initialize() +void JsModule::initialize() { Output("Initializing module Javascript"); + ChanAssistList::initialize(); setup(); + Configuration cfg(Engine::configFile("javascript")); + String tmp = Engine::sharedPath(); + tmp << Engine::pathSeparator() << "scripts"; + tmp = cfg.getValue("general","scripts_dir",tmp); + if (!tmp.endsWith(Engine::pathSeparator())) + tmp += Engine::pathSeparator(); + s_basePath = tmp; + lock(); + m_assistCode.clear(); + m_assistCode.basePath(tmp); + tmp = cfg.getValue("scripts","routing"); + m_assistCode.adjustPath(tmp); + if (m_assistCode.parseFile(tmp)) + Debug(this,DebugInfo,"Parsed routing script: %s",tmp.c_str()); + else if (tmp) + Debug(this,DebugWarn,"Failed to parse script: %s",tmp.c_str()); + unlock(); +} + +void JsModule::init(int priority) +{ + ChanAssistList::init(priority); + installRelay(Route,priority); + Engine::install(new MessageRelay("call.preroute",this,Preroute,priority)); } }; // anonymous namespace