diff --git a/conf.d/javascript.conf.sample b/conf.d/javascript.conf.sample new file mode 100644 index 00000000..f26de272 --- /dev/null +++ b/conf.d/javascript.conf.sample @@ -0,0 +1 @@ +[general] diff --git a/configure.in b/configure.in index c676bf03..0b70e450 100644 --- a/configure.in +++ b/configure.in @@ -1417,6 +1417,7 @@ AC_CONFIG_FILES([packing/rpm/yate.spec libs/yiax/Makefile libs/yxml/Makefile libs/yjabber/Makefile + libs/yscript/Makefile libs/ymgcp/Makefile libs/ysig/Makefile libs/ypbx/Makefile diff --git a/libs/yscript/Makefile.in b/libs/yscript/Makefile.in new file mode 100644 index 00000000..b07ae0c7 --- /dev/null +++ b/libs/yscript/Makefile.in @@ -0,0 +1,67 @@ +# Makefile +# This file holds the make rules for the libyatescript + +DEBUG := + +CXX := @CXX@ -Wall +AR := ar +DEFS := +INCLUDES := -I@top_srcdir@ -I../.. -I@srcdir@ +CFLAGS := @CFLAGS@ @MODULE_CPPFLAGS@ @INLINE_FLAGS@ +LDFLAGS:= @LDFLAGS@ +SONAME_OPT := @SONAME_OPT@ +YATELIBS := -L../.. -lyate @LIBS@ +INCFILES := @top_srcdir@/yateclass.h @srcdir@/yatescript.h + +PROGS= +LIBS = libyatescript.a +OBJS = evaluator.o script.o javascript.o jsobjects.o +LIBD_DEV:= libyatescript.so +LIBD_VER:= $(LIBD_DEV).@PACKAGE_VERSION@ +LIBD:= ../../$(LIBD_VER) ../../$(LIBD_DEV) + +LOCALFLAGS = +LOCALLIBS = +COMPILE = $(CXX) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS) +LINK = $(CXX) $(LDFLAGS) + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# include optional local make rules +-include YateLocal.mak + +.PHONY: all debug ddebug xdebug +all: $(LIBS) $(LIBD) $(PROGS) + +debug: + $(MAKE) all DEBUG=-g3 MODSTRIP= + +ddebug: + $(MAKE) all DEBUG='-g3 -DDEBUG' MODSTRIP= + +xdebug: + $(MAKE) all DEBUG='-g3 -DXDEBUG' MODSTRIP= + +.PHONY: strip +strip: all + strip --strip-debug --discard-locals $(PROGS) + +.PHONY: clean +clean: + @-$(RM) $(PROGS) $(LIBS) $(LIBD) $(OBJS) core 2>/dev/null + +%.o: @srcdir@/%.cpp $(INCFILES) + $(COMPILE) -c $< + +Makefile: @srcdir@/Makefile.in ../../config.status + cd ../.. && ./config.status + +../../$(LIBD_VER): $(OBJS) + $(LINK) -o $@ $(SONAME_OPT)$(LIBD_VER) $^ $(YATELIBS) + +../../$(LIBD_DEV): ../../$(LIBD_VER) + cd ../.. && ln -sf $(LIBD_VER) $(LIBD_DEV) + +$(LIBS): $(OBJS) + $(AR) rcs $@ $^ diff --git a/libs/yscript/evaluator.cpp b/libs/yscript/evaluator.cpp new file mode 100644 index 00000000..782d7a80 --- /dev/null +++ b/libs/yscript/evaluator.cpp @@ -0,0 +1,1225 @@ +/** + * Evaluator.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2004-2006 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "yateclass.h" +#include "yatescript.h" + +#include +#include + +using namespace TelEngine; + +#define MAKEOP(s,o) { s, ExpEvaluator::Opc ## o } +#define ASSIGN(s,o) { s "=", ExpEvaluator::Opc ## o | ExpEvaluator::OpcAssign } +static const TokenDict s_operators_c[] = +{ + ASSIGN("<<",Shl), + ASSIGN(">>",Shr), + ASSIGN("+", Add), + ASSIGN("-", Sub), + ASSIGN("*", Mul), + ASSIGN("/", Div), + ASSIGN("%", Mod), + ASSIGN("&", And), + ASSIGN("|", Or), + ASSIGN("^", Xor), + MAKEOP("<<",Shl), + MAKEOP(">>",Shr), + MAKEOP("==",Eq), + MAKEOP("!=",Ne), + MAKEOP("<=",Le), + MAKEOP(">=",Ge), + MAKEOP("<",Lt), + MAKEOP(">",Gt), + MAKEOP("&&",LAnd), + MAKEOP("||",LOr), + MAKEOP("^^",LXor), + MAKEOP("+", Add), + MAKEOP("-", Sub), + MAKEOP("*", Mul), + MAKEOP("/", Div), + MAKEOP("%", Mod), + MAKEOP("&", And), + MAKEOP("|", Or), + MAKEOP("^", Xor), + MAKEOP(".", Cat), + MAKEOP("@", As), + MAKEOP("=", Assign), + { 0, 0 } +}; + +static const TokenDict s_unaryOps_c[] = +{ + MAKEOP("++", IncPre), + MAKEOP("--", DecPre), + MAKEOP("!", LNot), + MAKEOP("~", Not), + MAKEOP("-", Neg), + { 0, 0 } +}; + +const TokenDict s_operators_sql[] = +{ + MAKEOP("AND",LAnd), + MAKEOP("OR",LOr), + MAKEOP("<<",Shl), + MAKEOP(">>",Shr), + MAKEOP("<>",Ne), + MAKEOP("!=",Ne), + MAKEOP("<=",Le), + MAKEOP(">=",Ge), + MAKEOP("<",Lt), + MAKEOP(">",Gt), + MAKEOP("||",Cat), + MAKEOP("AS",As), + MAKEOP("+", Add), + MAKEOP("-", Sub), + MAKEOP("*", Mul), + MAKEOP("/", Div), + MAKEOP("%", Mod), + MAKEOP("&", And), + MAKEOP("|", Or), + MAKEOP("^", Xor), + MAKEOP("=",Eq), + { 0, 0 } +}; + +static const TokenDict s_unaryOps_sql[] = +{ + MAKEOP("NOT", LNot), + MAKEOP("~", Not), + MAKEOP("-", Neg), + { 0, 0 } +}; + +#undef MAKEOP +#undef ASSIGN + + +bool ExpExtender::runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +{ + return false; +} + +bool ExpExtender::runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +{ + return false; +} + +bool ExpExtender::runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context) +{ + return false; +} + + +ExpEvaluator::ExpEvaluator(const TokenDict* operators, const TokenDict* unaryOps) + : m_operators(operators), m_unaryOps(unaryOps), m_extender(0) +{ +} + +ExpEvaluator::ExpEvaluator(ExpEvaluator::Parser style) + : m_operators(0), m_unaryOps(0), m_extender(0) +{ + switch (style) { + case C: + m_operators = s_operators_c; + m_unaryOps = s_unaryOps_c; + break; + case SQL: + m_operators = s_operators_sql; + m_unaryOps = s_unaryOps_sql; + break; + } +} + +ExpEvaluator::ExpEvaluator(const ExpEvaluator& original) + : m_operators(original.m_operators), m_unaryOps(original.unaryOps()), + 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)); + } +} + +ExpEvaluator::~ExpEvaluator() +{ + extender(0); +} + +void ExpEvaluator::extender(ExpExtender* ext) +{ + if (ext == m_extender) + return; + if (ext && !ext->ref()) + return; + ExpExtender* tmp = m_extender; + m_extender = ext; + TelEngine::destruct(tmp); +} + +char ExpEvaluator::skipWhites(const char*& expr) +{ + if (!expr) + return 0; + while (*expr==' ' || *expr=='\t') + expr++; + return *expr; +} + +bool ExpEvaluator::keywordChar(char c) const +{ + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || + ('0' <= c && c <= '9') || (c == '_'); +} + +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); + if (operators) { + bool kw = keywordChar(*expr); + for (const TokenDict* o = operators; o->token; o++) { + const char* s1 = o->token; + const char* s2 = expr; + do { + if (!*s1) { + if (kw && keywordChar(*s2)) + break; + expr = s2; + return (ExpEvaluator::Opcode)o->value; + } + } while (condLower(*s1++,caseInsensitive) == condLower(*s2++,caseInsensitive)); + } + } + return OpcNone; +} + +bool ExpEvaluator::gotError(const char* error, const char* text) const +{ + if (!error) + error = "unknown error"; + Debug(this,DebugWarn,"Evaluator got error: %s%s%s",error, + (text ? " at: " : ""), + c_safe(text)); + return false; +} + +bool ExpEvaluator::getInstruction(const char*& expr) +{ + return false; +} + +bool ExpEvaluator::getOperand(const char*& expr) +{ + XDebug(this,DebugAll,"getOperand '%s'",expr); + char c = skipWhites(expr); + if (!c) + // end of string + return true; + if (c == '(') { + // parenthesized subexpression + if (!runCompile(++expr,')')) + return false; + if (skipWhites(expr) != ')') + return gotError("Expecting ')'",expr); + expr++; + return true; + } + Opcode op = getUnaryOperator(expr); + if (op != OpcNone) { + if (!getOperand(expr)) + return false; + addOpcode(op); + return true; + } + if (getString(expr) || getNumber(expr) || getFunction(expr) || getField(expr)) + return true; + return gotError("Expecting operand",expr); +} + +bool ExpEvaluator::getNumber(const char*& expr) +{ + XDebug(this,DebugAll,"getNumber '%s'",expr); + char* endp = 0; + long int val = ::strtol(expr,&endp,0); + if (!endp || (endp == expr)) + return false; + expr = endp; + DDebug(this,DebugAll,"Found %ld",val); + addOpcode(val); + return true; +} + +bool ExpEvaluator::getString(const char*& expr) +{ + XDebug(this,DebugAll,"getString '%s'",expr); + char c = skipWhites(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 '%s'",str.safe()); + addOpcode(str); + return true; + } + return gotError("Expecting string end"); + } + return false; +} + +int ExpEvaluator::getKeyword(const char* str) const +{ + int len = 0; + for (;; len++) { + char c = *str++; + if (c <= ' ' || !keywordChar(c)) + break; + } + return len; +} + +bool ExpEvaluator::getFunction(const char*& expr) +{ + XDebug(this,DebugAll,"getFunction '%s'",expr); + skipWhites(expr); + int len = getKeyword(expr); + const char* s = expr+len; + skipWhites(expr); + if ((len <= 0) || (skipWhites(s) != '(')) + return false; + s++; + int argc = 0; + // parameter list + do { + if (!runCompile(s,')')) { + if (!argc && (skipWhites(s) == ')')) + break; + return false; + } + argc++; + } while (getSeparator(s,true)); + if (skipWhites(s) != ')') + return gotError("Expecting ')' after function",s); + String str(expr,len); + expr = s+1; + DDebug(this,DebugAll,"Found %s()",str.safe()); + addOpcode(OpcFunc,str,argc); + return true; +} + +bool ExpEvaluator::getField(const char*& expr) +{ + XDebug(this,DebugAll,"getField '%s'",expr); + skipWhites(expr); + int len = getKeyword(expr); + if (len <= 0) + return false; + if (expr[len] == '(') + return false; + String str(expr,len); + expr += len; + DDebug(this,DebugAll,"Found %s",str.safe()); + addOpcode(OpcField,str); + return true; +} + +ExpEvaluator::Opcode ExpEvaluator::getOperator(const char*& expr) +{ + return getOperator(expr,m_operators); +} + +ExpEvaluator::Opcode ExpEvaluator::getUnaryOperator(const char*& expr) +{ + return getOperator(expr,m_unaryOps); +} + +ExpEvaluator::Opcode ExpEvaluator::getPostfixOperator(const char*& expr) +{ + return OpcNone; +} + +const char* ExpEvaluator::getOperator(ExpEvaluator::Opcode oper) const +{ + const char* res = lookup(oper,m_operators); + return res ? res : lookup(oper,m_unaryOps); +} + +int ExpEvaluator::getPrecedence(ExpEvaluator::Opcode oper) const +{ + switch (oper) { + case OpcIncPre: + case OpcDecPre: + case OpcIncPost: + case OpcDecPost: + case OpcNeg: + case OpcNot: + return 11; + case OpcMul: + case OpcDiv: + case OpcMod: + case OpcAnd: + return 10; + case OpcAdd: + case OpcSub: + case OpcOr: + case OpcXor: + return 9; + case OpcShl: + case OpcShr: + return 8; + case OpcCat: + return 7; + // ANY, ALL, SOME = 6 + case OpcLNot: + return 5; + case OpcLt: + case OpcGt: + case OpcLe: + case OpcGe: + case OpcEq: + case OpcNe: + return 4; + // IN, BETWEEN, LIKE, MATCHES = 3 + case OpcLAnd: + return 2; + case OpcLOr: + case OpcLXor: + return 1; + default: + return 0; + } +} + +bool ExpEvaluator::getRightAssoc(ExpEvaluator::Opcode oper) const +{ + if (oper & OpcAssign) + return true; + switch (oper) { + case OpcIncPre: + case OpcDecPre: + case OpcNeg: + case OpcNot: + case OpcLNot: + return true; + default: + return false; + } +} + +bool ExpEvaluator::getSeparator(const char*& expr, bool remove) +{ + if (skipWhites(expr) != ',') + return false; + if (remove) + expr++; + return true; +} + +bool ExpEvaluator::runCompile(const char*& expr, char stop) +{ + typedef struct { + Opcode code; + int prec; + } StackedOpcode; + StackedOpcode stack[10]; + unsigned int stackPos = 0; + DDebug(this,DebugInfo,"runCompile '%s'",expr); + if (skipWhites(expr) == ')') + return false; + if (expr[0] == '*' && !expr[1]) { + expr++; + addOpcode(OpcField,"*"); + return true; + } + for (;;) { + while (skipWhites(expr) && getInstruction(expr)) + ; + if (!getOperand(expr)) + return false; + Opcode oper; + while ((oper = getPostfixOperator(expr)) != OpcNone) + addOpcode(oper); + char c = skipWhites(expr); + if (!c || c == stop || getSeparator(expr,false)) { + while (stackPos) + addOpcode(stack[--stackPos].code); + return true; + } + oper = getOperator(expr); + if (oper == OpcNone) + return gotError("Operator expected",expr); + int precedence = 2 * getPrecedence(oper); + int precAdj = precedence; + if (getRightAssoc(oper)) + precAdj++; + while (stackPos && stack[stackPos-1].prec >= precAdj) + addOpcode(stack[--stackPos].code); + if (stackPos >= (sizeof(stack)/sizeof(StackedOpcode))) + return gotError("Compiler stack overflow"); + stack[stackPos].code = oper; + stack[stackPos].prec = precedence; + stackPos++; + } +} + +bool ExpEvaluator::trySimplify() +{ + DDebug(this,DebugInfo,"trySimplify"); + bool done = false; + for (unsigned int i = 0; i < m_opcodes.length(); i++) { + ExpOperation* o = static_cast(m_opcodes[i]); + if (!o || o->barrier()) + continue; + switch (o->opcode()) { + case OpcLAnd: + case OpcLOr: + case OpcLXor: + case OpcAnd: + case OpcOr: + case OpcXor: + case OpcShl: + case OpcShr: + case OpcAdd: + case OpcSub: + case OpcMul: + case OpcDiv: + case OpcMod: + case OpcCat: + case OpcEq: + case OpcNe: + case OpcLt: + case OpcGt: + case OpcLe: + case OpcGe: + if (i >= 2) { + ExpOperation* op2 = static_cast(m_opcodes[i-1]); + ExpOperation* op1 = static_cast(m_opcodes[i-2]); + if (!op1 || !op2) + continue; + 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)); + m_opcodes.remove(op1); + m_opcodes.remove(op2); + i -= 2; + done = true; + continue; + } + } + 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.remove(op1); + m_opcodes.remove(op2); + i -= 2; + done = true; + continue; + } + } + if ((op1->opcode() == OpcPush) && (op2->opcode() == OpcPush)) { + ObjList stack; + pushOne(stack,new ExpOperation(*op1)); + pushOne(stack,new ExpOperation(*op2)); + if (runOperation(stack,*o)) { + // replace operators and operation with computed constant + (m_opcodes+i)->set(popOne(stack)); + m_opcodes.remove(op1); + m_opcodes.remove(op2); + i -= 2; + done = true; + } + } + } + break; + case OpcNeg: + case OpcNot: + case OpcLNot: + if (i >= 1) { + ExpOperation* op = static_cast(m_opcodes[i-1]); + if (!op) + continue; + if (op->opcode() == OpcPush) { + ObjList stack; + pushOne(stack,new ExpOperation(op)); + if (runOperation(stack,*o)) { + // replace unary operator and operation with computed constant + (m_opcodes+i)->set(popOne(stack)); + m_opcodes.remove(op); + i--; + done = true; + } + } + else if (op->opcode() == o->opcode() && op->opcode() != OpcLNot) { + // minus or bit negation applied twice - remove both operators + m_opcodes.remove(o); + m_opcodes.remove(op); + i--; + done = true; + } + } + break; + default: + break; + } + } + return done; +} + +void ExpEvaluator::addOpcode(ExpEvaluator::Opcode oper, bool barrier) +{ + DDebug(this,DebugAll,"addOpcode %u",oper); + if (oper == OpcAs) { + // the second operand is used just for the field name + ExpOperation* o = 0; + for (ObjList* l = m_opcodes.skipNull(); l; l=l->skipNext()) + o = static_cast(l->get()); + if (o && (o->opcode() == OpcField)) { + o->m_opcode = OpcPush; + o->String::operator=(o->name()); + } + } + m_opcodes.append(new ExpOperation(oper,0,0,barrier)); +} + +void 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)); +} + +void ExpEvaluator::addOpcode(const String& value) +{ + DDebug(this,DebugAll,"addOpcode ='%s'",value.c_str()); + m_opcodes.append(new ExpOperation(value)); +} + +void ExpEvaluator::addOpcode(long int value) +{ + DDebug(this,DebugAll,"addOpcode =%ld",value); + m_opcodes.append(new ExpOperation(value)); +} + +void ExpEvaluator::pushOne(ObjList& stack, ExpOperation* oper) +{ + if (oper) + stack.insert(oper); +} + +ExpOperation* ExpEvaluator::popOne(ObjList& stack) +{ + ExpOperation* o = 0; + for (;;) { + o = static_cast(stack.get()); + if (o || !stack.next()) + break; + // non-terminal NULL - remove the list entry + stack.remove(); + } + if (o && o->barrier()) { + XDebug(DebugAll,"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); + return o; +} + +ExpOperation* ExpEvaluator::popAny(ObjList& stack) +{ + ExpOperation* o = 0; + for (;;) { + o = static_cast(stack.get()); + if (o || !stack.next()) + break; + // non-terminal NULL - remove the list entry + stack.remove(); + } + stack.remove(o,false); + DDebug(DebugInfo,"Popped: %p",o); + return o; +} + +ExpOperation* ExpEvaluator::popValue(ObjList& stack, void* context) const +{ + ExpOperation* oper = popOne(stack); + if (!oper || (oper->opcode() != OpcField)) + return oper; + bool ok = runField(stack,*oper,context); + TelEngine::destruct(oper); + return ok ? popOne(stack) : 0; +} + +bool ExpEvaluator::runOperation(ObjList& stack, const ExpOperation& oper, void* context) const +{ + DDebug(this,DebugAll,"runOperation(%p,%u,%p) %s",&stack,oper.opcode(),context,getOperator(oper.opcode())); + switch (oper.opcode()) { + case OpcPush: + pushOne(stack,new ExpOperation(oper)); + break; + case OpcAnd: + case OpcOr: + case OpcXor: + case OpcShl: + case OpcShr: + case OpcAdd: + case OpcSub: + case OpcMul: + case OpcDiv: + case OpcMod: + case OpcEq: + case OpcNe: + case OpcLt: + case OpcGt: + case OpcLe: + case OpcGe: + { + ExpOperation* op2 = popValue(stack,context); + ExpOperation* op1 = popValue(stack,context); + if (!op1 || !op2) { + TelEngine::destruct(op1); + TelEngine::destruct(op2); + return gotError("ExpEvaluator stack underflow"); + } + switch (oper.opcode()) { + case OpcDiv: + case OpcMod: + if (!op2->number()) + return gotError("Division by zero"); + case OpcAdd: + if (op1->isInteger() && op2->isInteger()) + break; + // turn addition into concatenation + { + String val = *op1 + *op2; + TelEngine::destruct(op1); + TelEngine::destruct(op2); + DDebug(this,DebugAll,"String result: '%s'",val.c_str()); + pushOne(stack,new ExpOperation(val)); + return true; + } + default: + break; + } + long int val = 0; + switch (oper.opcode()) { + case OpcAnd: + val = op1->number() & op2->number(); + break; + case OpcOr: + val = op1->number() | op2->number(); + break; + case OpcXor: + val = op1->number() ^ op2->number(); + break; + case OpcShl: + val = op1->number() << op2->number(); + break; + case OpcShr: + val = op1->number() >> op2->number(); + break; + case OpcAdd: + val = op1->number() + op2->number(); + break; + case OpcSub: + val = op1->number() - op2->number(); + break; + case OpcMul: + val = op1->number() * op2->number(); + break; + case OpcDiv: + val = op1->number() / op2->number(); + break; + case OpcMod: + val = op1->number() % op2->number(); + break; + case OpcLt: + val = (op1->number() < op2->number()) ? 1 : 0; + break; + case OpcGt: + val = (op1->number() > op2->number()) ? 1 : 0; + break; + case OpcLe: + val = (op1->number() <= op2->number()) ? 1 : 0; + break; + case OpcGe: + val = (op1->number() >= op2->number()) ? 1 : 0; + break; + case OpcEq: + val = (*op1 == *op2) ? 1 : 0; + break; + case OpcNe: + val = (*op1 != *op2) ? 1 : 0; + break; + default: + break; + } + TelEngine::destruct(op1); + TelEngine::destruct(op2); + DDebug(this,DebugAll,"Numeric result: %lu",val); + pushOne(stack,new ExpOperation(val)); + } + break; + case OpcLAnd: + case OpcLOr: + { + ExpOperation* op2 = popValue(stack,context); + ExpOperation* op1 = popValue(stack,context); + if (!op1 || !op2) { + TelEngine::destruct(op1); + TelEngine::destruct(op2); + return gotError("ExpEvaluator stack underflow"); + } + bool val = false; + switch (oper.opcode()) { + case OpcLAnd: + val = op1->number() && op2->number(); + break; + case OpcLOr: + val = op1->number() || op2->number(); + break; + default: + break; + } + TelEngine::destruct(op1); + TelEngine::destruct(op2); + DDebug(this,DebugAll,"Bool result: '%s'",String::boolText(val)); + pushOne(stack,new ExpOperation(val ? 1 : 0)); + } + break; + case OpcCat: + { + ExpOperation* op2 = popValue(stack,context); + ExpOperation* op1 = popValue(stack,context); + if (!op1 || !op2) { + TelEngine::destruct(op1); + TelEngine::destruct(op2); + return gotError("ExpEvaluator stack underflow"); + } + String val = *op1 + *op2; + TelEngine::destruct(op1); + TelEngine::destruct(op2); + DDebug(this,DebugAll,"String result: '%s'",val.c_str()); + pushOne(stack,new ExpOperation(val)); + } + break; + case OpcAs: + { + ExpOperation* op2 = popOne(stack); + ExpOperation* op1 = popOne(stack); + if (!op1 || !op2) { + TelEngine::destruct(op1); + TelEngine::destruct(op2); + return gotError("ExpEvaluator stack underflow"); + } + pushOne(stack,new ExpOperation(*op1,*op2)); + TelEngine::destruct(op1); + TelEngine::destruct(op2); + } + break; + case OpcNeg: + case OpcNot: + case OpcLNot: + { + ExpOperation* op = popValue(stack,context); + if (!op) + return gotError("ExpEvaluator stack underflow"); + long int val = op->number(); + TelEngine::destruct(op); + switch (oper.opcode()) { + case OpcNeg: + val = -val; + break; + case OpcNot: + val = ~val; + break; + case OpcLNot: + val = val ? 0 : 1; + break; + default: + 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: + case OpcDecPost: + { + ExpOperation* fld = popOne(stack); + if (!fld) + return gotError("ExpEvaluator stack underflow"); + if (fld->opcode() != OpcField) { + TelEngine::destruct(fld); + return gotError("Expecting LValue in operator"); + } + ExpOperation* val = 0; + if (!(runField(stack,*fld,context) && (val = popOne(stack)))) { + TelEngine::destruct(fld); + return false; + } + long int num = val->number(); + switch (oper.opcode()) { + case OpcIncPre: + num++; + (*val) = num; + break; + case OpcDecPre: + num--; + (*val) = num; + break; + case OpcIncPost: + (*val) = num; + num++; + break; + case OpcDecPost: + (*val) = num; + num--; + break; + default: + break; + } + (*fld) = num; + bool ok = runAssign(*fld,context); + TelEngine::destruct(fld); + if (!ok) { + TelEngine::destruct(val); + return gotError("Assignment failed"); + } + pushOne(stack,val); + } + break; + case OpcAssign: + { + ExpOperation* val = popValue(stack,context); + ExpOperation* fld = popOne(stack); + if (!fld || !val) { + TelEngine::destruct(fld); + TelEngine::destruct(val); + return gotError("ExpEvaluator stack underflow"); + } + if (fld->opcode() != OpcField) { + TelEngine::destruct(fld); + TelEngine::destruct(val); + return gotError("Expecting LValue in assignment"); + } + ExpOperation op(*val,fld->name()); + TelEngine::destruct(fld); + if (!runAssign(op,context)) { + TelEngine::destruct(val); + return gotError("Assignment failed"); + } + pushOne(stack,val); + } + break; + default: + if (oper.opcode() & OpcAssign) { + // assignment by operation + ExpOperation* val = popValue(stack,context); + ExpOperation* fld = popOne(stack); + if (!fld || !val) { + TelEngine::destruct(fld); + TelEngine::destruct(val); + return gotError("ExpEvaluator stack underflow"); + } + if (fld->opcode() != OpcField) { + TelEngine::destruct(fld); + TelEngine::destruct(val); + return gotError("Expecting LValue in assignment"); + } + pushOne(stack,new ExpOperation(*fld)); + pushOne(stack,fld); + pushOne(stack,val); + ExpOperation op((Opcode)(oper.opcode() & ~OpcAssign), + oper.name(),oper.number(),oper.barrier()); + if (!runOperation(stack,op,context)) + return false; + static const ExpOperation assign(OpcAssign); + return runOperation(stack,assign,context); + } + Debug(this,DebugStub,"Please implement operation %u '%s'", + oper.opcode(),getOperator(oper.opcode())); + return false; + } + return true; +} + +bool ExpEvaluator::runFunction(ObjList& stack, const ExpOperation& oper, void* context) const +{ + DDebug(this,DebugAll,"runFunction(%p,'%s' %ld, %p) ext=%p", + &stack,oper.name().c_str(),oper.number(),context,(void*)m_extender); + if (oper.name() == YSTRING("chr")) { + String res; + for (long int i = oper.number(); i; i--) { + ExpOperation* o = popValue(stack,context); + if (!o) + return gotError("ExpEvaluator stack underflow"); + res = String((char)o->number()) + res; + TelEngine::destruct(o); + } + pushOne(stack,new ExpOperation(res)); + return true; + } + if (oper.name() == YSTRING("now")) { + if (oper.number()) + return gotError("Function expects no arguments"); + pushOne(stack,new ExpOperation(Time::secNow())); + return true; + } + return m_extender && m_extender->runFunction(this,stack,oper,context); +} + +bool ExpEvaluator::runField(ObjList& stack, const ExpOperation& oper, void* 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); +} + +bool ExpEvaluator::runAssign(const ExpOperation& oper, void* 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); +} + +bool ExpEvaluator::runEvaluate(const ObjList& opcodes, ObjList& stack, void* context) const +{ + DDebug(this,DebugInfo,"runEvaluate(%p,%p,%p)",&opcodes,&stack,context); + for (const ObjList* l = opcodes.skipNull(); l; l = l->skipNext()) { + const ExpOperation* o = static_cast(l->get()); + if (!runOperation(stack,*o,context)) + return false; + } + return true; +} + +bool ExpEvaluator::runEvaluate(const ObjVector& opcodes, ObjList& stack, void* context, unsigned int index) const +{ + DDebug(this,DebugInfo,"runEvaluate(%p,%p,%p,%u)",&opcodes,&stack,context,index); + for (; index < opcodes.length(); index++) { + const ExpOperation* o = static_cast(opcodes[index]); + if (o && !runOperation(stack,*o,context)) + return false; + } + return true; +} + +bool ExpEvaluator::runEvaluate(ObjList& stack, void* context) const +{ + return runEvaluate(m_opcodes,stack,context); +} + +bool ExpEvaluator::runAllFields(ObjList& stack, void* context) const +{ + DDebug(this,DebugAll,"runAllFields(%p,%p)",&stack,context); + bool ok = true; + for (ObjList* l = stack.skipNull(); l; l = l->skipNext()) { + ExpOperation* o = static_cast(l->get()); + if (o->barrier()) + break; + if (o->opcode() != OpcField) + continue; + ObjList tmp; + if (runField(tmp,*o,context)) { + ExpOperation* val = popOne(tmp); + if (val) + l->set(val); + else + ok = false; + } + else + ok = false; + } + return ok; +} + +int ExpEvaluator::compile(const char* expr) +{ + if (!skipWhites(expr)) + return 0; + int res = 0; + do { + if (!runCompile(expr)) + return 0; + res++; + } while (getSeparator(expr,true)); + return skipWhites(expr) ? 0 : res; +} + +bool ExpEvaluator::evaluate(ObjList* results, void* context) const +{ + if (results) { + results->clear(); + return runEvaluate(*results,context) && + (runAllFields(*results,context) || gotError("Could not evaluate all fields")); + } + ObjList res; + return runEvaluate(res,context); +} + +int ExpEvaluator::evaluate(NamedList& results, unsigned int index, const char* prefix, void* context) const +{ + ObjList stack; + if (!evaluate(stack,context)) + return -1; + String idx(prefix); + if (index) + idx << index << "."; + int column = 0; + for (ObjList* r = stack.skipNull(); r; r = r->skipNext()) { + column++; + const ExpOperation* res = static_cast(r->get()); + String name = res->name(); + if (name.null()) + name = column; + results.setParam(idx+name,*res); + } + return column; +} + +int ExpEvaluator::evaluate(Array& results, unsigned int index, void* context) const +{ + Debug(this,DebugStub,"Please implement ExpEvaluator::evaluate(Array)"); + return -1; +} + +void ExpEvaluator::dump(const ObjList& codes, String& res) const +{ + for (const ObjList* l = codes.skipNull(); l; l = l->skipNext()) { + if (res) + res << " "; + const ExpOperation* o = static_cast(l->get()); + const char* oper = getOperator(o->opcode()); + if (oper) { + res << oper; + continue; + } + switch (o->opcode()) { + case OpcPush: + if (o->isInteger()) + res << (int)o->number(); + else + res << "'" << *o << "'"; + break; + case OpcField: + res << o->name(); + break; + case OpcFunc: + res << o->name() << "(" << (int)o->number() << ")"; + break; + default: + res << "[" << o->opcode() << "]"; + } + } +} + + +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) +{ + extender(original.m_select.extender()); +} + +TableEvaluator::TableEvaluator(ExpEvaluator::Parser style) + : m_select(style), m_where(style), + m_limit(style), m_limitVal((unsigned int)-2) +{ +} + +TableEvaluator::TableEvaluator(const TokenDict* operators, const TokenDict* unaryOps) + : m_select(operators,unaryOps), m_where(operators,unaryOps), + m_limit(operators,unaryOps), m_limitVal((unsigned int)-2) +{ +} + +TableEvaluator::~TableEvaluator() +{ +} + +void TableEvaluator::extender(ExpExtender* ext) +{ + m_select.extender(ext); + m_where.extender(ext); + m_limit.extender(ext); +} + +bool TableEvaluator::evalWhere(void* context) +{ + if (m_where.null()) + return true; + ObjList res; + if (!m_where.evaluate(res,context)) + return false; + ObjList* first = res.skipNull(); + if (!first) + return false; + const ExpOperation* o = static_cast(first->get()); + return (o->opcode() == ExpEvaluator::OpcPush) && o->number(); +} + +bool TableEvaluator::evalSelect(ObjList& results, void* context) +{ + if (m_select.null()) + return false; + return m_select.evaluate(results,context); +} + +unsigned int TableEvaluator::evalLimit(void* context) +{ + if (m_limitVal == (unsigned int)-2) { + m_limitVal = (unsigned int)-1; + // hack: use a loop so we can break out of it + while (!m_limit.null()) { + ObjList res; + if (!m_limit.evaluate(res,context)) + break; + ObjList* first = res.skipNull(); + if (!first) + break; + const ExpOperation* o = static_cast(first->get()); + if (o->opcode() != ExpEvaluator::OpcPush) + break; + int lim = o->number(); + if (lim < 0) + lim = 0; + m_limitVal = lim; + break; + } + } + return m_limitVal; +} + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yscript/javascript.cpp b/libs/yscript/javascript.cpp new file mode 100644 index 00000000..0c47d93b --- /dev/null +++ b/libs/yscript/javascript.cpp @@ -0,0 +1,434 @@ +/** + * javascript.cpp + * Yet Another (Java)script library + * This file is part of the YATE Project http://YATE.null.ro + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2011 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "yatescript.h" + +using namespace TelEngine; + +namespace { // anonymous + +class JsContext : public ScriptContext +{ + YCLASS(JsContext,ScriptContext) +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); +}; + +class JsCode : public ScriptCode, public ExpEvaluator +{ + YCLASS(JsCode,ScriptCode) +public: + enum JsOpcode { + OpcBegin = OpcPrivate + 1, + OpcEnd, + OpcIndex, + OpcNew, + OpcFor, + OpcWhile, + OpcIf, + OpcElse, + OpcSwitch, + OpcCase, + OpcBreak, + OpcCont, + OpcIn, + OpcVar, + OpcWith, + OpcTry, + OpcCatch, + OpcFinally, + OpcThrow, + OpcReturn, + }; + inline JsCode() + : ExpEvaluator(C), m_label(0) + { debugName("JsCode"); } + virtual bool initialize(ScriptContext* context) const; + virtual bool evaluate(ScriptContext& context, ObjList& results) const; +protected: + virtual bool keywordChar(char c) const; + virtual int getKeyword(const char* str) const; + virtual bool getInstruction(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; +private: + int m_label; +}; + +#define MAKEOP(s,o) { s, JsCode::Opc ## o } +static const TokenDict s_operators[] = +{ + { 0, 0 } +}; + +static const TokenDict s_unaryOps[] = +{ + MAKEOP("new", New), + { 0, 0 } +}; + +static const TokenDict s_postfixOps[] = +{ + MAKEOP("++", IncPost), + MAKEOP("--", DecPost), + { 0, 0 } +}; + +static const TokenDict s_instr[] = +{ + MAKEOP("function", Func), + MAKEOP("for", For), + MAKEOP("while", While), + MAKEOP("if", If), + MAKEOP("else", Else), + MAKEOP("switch", Switch), + MAKEOP("case", Case), + MAKEOP("break", Break), + MAKEOP("continue", Cont), + MAKEOP("in", In), + MAKEOP("var", Var), + MAKEOP("with", With), + MAKEOP("try", Try), + MAKEOP("catch", Catch), + MAKEOP("finally", Finally), + MAKEOP("throw", Throw), + MAKEOP("return", Return), + { 0, 0 } +}; +#undef MAKEOP + + +bool JsContext::runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +{ + return ScriptContext::runFunction(eval,stack,oper,context); +} + +bool JsContext::runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +{ + if (!eval) + return false; + XDebug(DebugAll,"JsContext::runField '%s'",oper.name().c_str()); + return ScriptContext::runField(eval,stack,oper,context); +} + +bool JsContext::runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context) +{ + if (!eval) + return false; + XDebug(DebugAll,"JsContext::runAssign '%s'='%s'",oper.name().c_str(),oper.c_str()); + return ScriptContext::runAssign(eval,oper,context); +} + + +// Initialize standard globals in the execution context +bool JsCode::initialize(ScriptContext* context) const +{ + if (!context) + return false; + JsObject::initialize(*context); + return true; +} + +bool JsCode::evaluate(ScriptContext& context, ObjList& results) const +{ + if (null()) + return false; + return ExpEvaluator::evaluate(results,&context); +} + +bool JsCode::keywordChar(char c) const +{ + return ExpEvaluator::keywordChar(c) || (c == '$'); +} + +int JsCode::getKeyword(const char* str) const +{ + int len = 0; + for (;; len++) { + char c = *str++; + if (c <= ' ') + break; + if (keywordChar(c) || (len && (c == '.'))) + continue; + break; + } + if (len > 1 && (str[-2] == '.')) + len--; + return len; +} + +bool JsCode::getInstruction(const char*& expr) +{ + XDebug(this,DebugAll,"JsCode::getInstruction '%s'",expr); + if (skipWhites(expr) == '{') { + if (!runCompile(++expr,'}')) + return false; + if (skipWhites(expr) != '}') + return gotError("Expecting '}'",expr); + expr++; + return true; + } + Opcode op = ExpEvaluator::getOperator(expr,s_instr); + switch ((JsOpcode)op) { + case (JsOpcode)OpcNone: + return false; + case OpcThrow: + if (!runCompile(expr)) + return false; + addOpcode(op); + break; + case OpcReturn: + runCompile(expr); + addOpcode(op); + break; + default: + break; + } + return true; +} + +ExpEvaluator::Opcode JsCode::getOperator(const char*& expr) +{ + XDebug(this,DebugAll,"JsCode::getOperator '%s'",expr); + Opcode op = ExpEvaluator::getOperator(expr,s_operators); + if (OpcNone != op) + return op; + return ExpEvaluator::getOperator(expr); +} + +ExpEvaluator::Opcode JsCode::getUnaryOperator(const char*& expr) +{ + XDebug(this,DebugAll,"JsCode::getUnaryOperator '%s'",expr); + Opcode op = ExpEvaluator::getOperator(expr,s_unaryOps); + if (OpcNone != op) + return op; + return ExpEvaluator::getUnaryOperator(expr); +} + +ExpEvaluator::Opcode JsCode::getPostfixOperator(const char*& expr) +{ + XDebug(this,DebugAll,"JsCode::getPostfixOperator '%s'",expr); + if (skipWhites(expr) == '[') { + if (!runCompile(++expr,']')) + return OpcNone; + if (skipWhites(expr) != ']') { + gotError("Expecting ']'",expr); + return OpcNone; + } + expr++; + return (Opcode)OpcIndex; + } + Opcode op = ExpEvaluator::getOperator(expr,s_postfixOps); + if (OpcNone != op) + return op; + return ExpEvaluator::getPostfixOperator(expr); +} + +const char* JsCode::getOperator(Opcode oper) const +{ + if (oper < OpcPrivate) + return ExpEvaluator::getOperator(oper); + if ((int)oper == (int)OpcIndex) + return "[]"; + const char* tmp = lookup(oper,s_operators); + if (!tmp) { + tmp = lookup(oper,s_unaryOps); + if (!tmp) { + tmp = lookup(oper,s_postfixOps); + if (!tmp) + tmp = lookup(oper,s_instr); + } + } + return tmp; +} + +int JsCode::getPrecedence(ExpEvaluator::Opcode oper) const +{ + switch (oper) { + case OpcNew: + case OpcIndex: + return 12; + default: + return ExpEvaluator::getPrecedence(oper); + } +} + +bool JsCode::getSeparator(const char*& expr, bool remove) +{ + switch (skipWhites(expr)) { + case ']': + case ';': + if (remove) + expr++; + return true; + } + return ExpEvaluator::getSeparator(expr,remove); +} + +bool JsCode::runOperation(ObjList& stack, const ExpOperation& oper, void* context) const +{ + switch ((JsOpcode)oper.opcode()) { + case OpcBegin: + pushOne(stack,new ExpOperation(OpcBegin)); + break; + case OpcEnd: + { + ExpOperation* op = popOne(stack); + ObjList* b = 0; + for (ObjList* l = stack.skipNull(); l; l=l->skipNext()) { + ExpOperation* o = static_cast(l->get()); + if (o && (o->opcode() == (Opcode)OpcBegin)) + b = l; + } + if (!b) { + TelEngine::destruct(op); + return gotError("ExpEvaluator stack underflow"); + } + b->clear(); + pushOne(stack,op); + } + break; + case OpcIndex: + { + ExpOperation* op2 = popValue(stack,context); + ExpOperation* op1 = popOne(stack); + if (!op1 || !op2) { + TelEngine::destruct(op1); + TelEngine::destruct(op2); + return gotError("Stack underflow"); + } + if (op1->opcode() != OpcField) { + TelEngine::destruct(op1); + TelEngine::destruct(op2); + return gotError("Expecting field name"); + } + pushOne(stack,new ExpOperation(OpcField,op1->name() + "." + *op2)); + TelEngine::destruct(op1); + TelEngine::destruct(op2); + } + 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"); + } + } + break; + case OpcThrow: + { + ExpOperation* op = popOne(stack); + if (!op) + return gotError("Stack underflow"); + bool ok = false; + while (ExpOperation* drop = popAny(stack)) { + JsOpcode c = (JsOpcode)drop->opcode(); + TelEngine::destruct(drop); + if (c == OpcTry) { + ok = true; + break; + } + } + if (!ok) + return gotError("'try' not found"); + pushOne(stack,op); + } + break; + case OpcReturn: + { + ExpOperation* op = popOne(stack); + bool ok = false; + while (ExpOperation* drop = popAny(stack)) { + ok = drop->opcode() == OpcFunc; + int n = drop->number(); + TelEngine::destruct(drop); + if (ok) { + DDebug(this,DebugAll,"return popping %d off stack",n); + while (n-- > 0) + TelEngine::destruct(popAny(stack)); + break; + } + } + if (!ok) { + TelEngine::destruct(op); + return gotError("Function not found on stack"); + } + pushOne(stack,op); + } + break; + default: + return ExpEvaluator::runOperation(stack,oper); + } + return true; +} + +}; // anonymous namespace + + +// Parse a piece of Javascript text +bool JsParser::parse(const char* text) +{ + if (TelEngine::null(text)) + return false; + // TODO + return false; +} + +// Evaluate a string as expression or statement +ScriptRun::Status JsParser::eval(const String& text, ExpOperation** result, ScriptContext* context) +{ + if (TelEngine::null(text)) + return ScriptRun::Invalid; + JsCode* code = new JsCode; + if (!code->compile(text)) { + TelEngine::destruct(code); + 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::Status rval = runner->run(); + if (result && (ScriptRun::Succeeded == rval)) + *result = ExpEvaluator::popOne(runner->stack()); + TelEngine::destruct(runner); + return rval; +} + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yscript/jsobjects.cpp b/libs/yscript/jsobjects.cpp new file mode 100644 index 00000000..851d138b --- /dev/null +++ b/libs/yscript/jsobjects.cpp @@ -0,0 +1,125 @@ +/** + * jsobject.cpp + * Yet Another (Java)script library + * This file is part of the YATE Project http://YATE.null.ro + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2011 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "yatescript.h" + +using namespace TelEngine; + +namespace { // anonymous + +// Base class for all native objects that hold a NamedList +class JsNative : public JsObject +{ + YCLASS(JsNative,JsObject) +public: + inline JsNative(const char* name, NamedList* list) + : JsObject(name), + m_list(list) + { } + virtual NamedList& list() + { return *m_list; } + virtual const NamedList& list() const + { return *m_list; } +private: + NamedList* m_list; +}; + +// Array object +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") + { } +}; + +// Object constructor +class JsConstructor : public JsFunction +{ + YCLASS(JsConstructor,JsFunction) +public: + inline JsConstructor() + { } +}; + +// Date object +class JsDate : public JsObject +{ + YCLASS(JsDate,JsObject) +public: + inline JsDate() + : JsObject("Date") + { + addParam(new ExpOperation(ExpEvaluator::OpcFunc,"now")); + } +}; + +// Math class - not really an object, all methods are static +class JsMath : public JsObject +{ + YCLASS(JsMath,JsObject) +public: + inline JsMath() + : JsObject("Math") + { + addParam(new ExpOperation(ExpEvaluator::OpcFunc,"abs")); + } +}; + +}; // anonymous namespace + + +// Helper function that adds an object to a parent +static inline void addObject(NamedList& params, const char* name, NamedList* obj) +{ + params.addParam(new NamedPointer(name,obj,obj->toString())); +} + + +// Initialize standard globals in the execution 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); +} + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yscript/script.cpp b/libs/yscript/script.cpp new file mode 100644 index 00000000..f2f9d31a --- /dev/null +++ b/libs/yscript/script.cpp @@ -0,0 +1,159 @@ +/** + * script.cpp + * Yet Another (Java)script library + * This file is part of the YATE Project http://YATE.null.ro + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2011 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "yatescript.h" + +using namespace TelEngine; + +ScriptParser::~ScriptParser() +{ + TelEngine::destruct(m_code); +} + +void ScriptParser::setCode(ScriptCode* code) +{ + ScriptCode* tmp = m_code; + if (tmp == code) + return; + if (code) + code->ref(); + m_code = code; + TelEngine::destruct(tmp); +} + + +bool ScriptContext::runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context) +{ + return false; +} + +bool ScriptContext::runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* 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())); + return true; +} + +bool ScriptContext::runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* 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; +} + +#define MAKE_NAME(x) { #x, ScriptRun::x } +static const TokenDict s_states[] = { + MAKE_NAME(Invalid), + MAKE_NAME(Running), + MAKE_NAME(Incomplete), + MAKE_NAME(Succeeded), + MAKE_NAME(Failed), + { 0, 0 } +}; +#undef MAKE_NAME + +ScriptRun::ScriptRun(ScriptCode* code, ScriptContext* context) + : Mutex(true,"ScriptRun"), + m_state(Invalid) +{ + if (code) + code->ref(); + m_code = code; + if (context) + context->ref(); + else + context = new ScriptContext; + m_context = context; + reset(); +} + +ScriptRun::~ScriptRun() +{ + lock(); + m_state = Invalid; + TelEngine::destruct(m_code); + TelEngine::destruct(m_context); + unlock(); +} + +const char* ScriptRun::textState(Status state) +{ + return lookup(state,s_states,"Unknown"); +} + +// Reset script (but not the context) to initial state +ScriptRun::Status ScriptRun::reset() +{ + Lock mylock(this); + // TODO + m_stack.clear(); + return (m_state = (m_code && m_code->initialize(m_context)) ? Incomplete : Invalid); +} + +// Resume execution, run one or more instructions of code +ScriptRun::Status ScriptRun::resume() +{ + Lock mylock(this); + if (Running != m_state) + return m_state; + RefPointer code = m_code; + RefPointer ctxt = m_context; + if (!(code && ctxt)) + return Invalid; + mylock.drop(); + return code->evaluate(*ctxt,stack()) ? Succeeded : Failed; +} + +// Execute one or more instructions of code from where it was left +ScriptRun::Status ScriptRun::execute() +{ + Lock mylock(this); + if (Incomplete != m_state) + return m_state; + m_state = Running; + mylock.drop(); + Status st = resume(); + if (Running == st) + st = Incomplete; + lock(); + if (Running == m_state) + m_state = st; + unlock(); + return st; +} + +// Execute instructions until succeeds or fails +ScriptRun::Status ScriptRun::run() +{ + reset(); + ScriptRun::Status s = state(); + while (Incomplete == s) + s = execute(); + return s; +} + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yscript/yatescript.h b/libs/yscript/yatescript.h new file mode 100644 index 00000000..50435071 --- /dev/null +++ b/libs/yscript/yatescript.h @@ -0,0 +1,1154 @@ +/* + * yatescript.h + * Yet Another (Java)script library + * This file is part of the YATE Project http://YATE.null.ro + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2011 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __YATESCRIPT_H +#define __YATESCRIPT_H + +#include + +#ifdef _WINDOWS + +#ifdef LIBYSCRIPT_EXPORTS +#define YSCRIPT_API __declspec(dllexport) +#else +#ifndef LIBYSCRIPT_STATIC +#define YSCRIPT_API __declspec(dllimport) +#endif +#endif + +#endif /* _WINDOWS */ + +#ifndef YSCRIPT_API +#define YSCRIPT_API +#endif + +/** + * Holds all Telephony Engine related classes. + */ +namespace TelEngine { + +class ExpEvaluator; +class ExpOperation; + +/** + * This class allows extending ExpEvaluator to implement custom fields and functions + * @short ExpEvaluator extending interface + */ +class YATE_API ExpExtender : public RefObject +{ + YCLASS(ExpExtender,RefObject) +public: + /** + * 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 + * @return True if evaluation succeeded + */ + virtual bool runFunction(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* 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 + * @return True if evaluation succeeded + */ + virtual bool runField(const ExpEvaluator* eval, ObjList& stack, const ExpOperation& oper, void* context); + + /** + * Try to assign a value to a single field + * @param eval Pointer to the caller evaluator object + * @param oper Field to assign to, contains the field name and new value + * @param context Pointer to arbitrary data passed from evaluation methods + * @return True if assignment succeeded + */ + virtual bool runAssign(const ExpEvaluator* eval, const ExpOperation& oper, void* context); +}; + +/** + * A class used to build stack based (posifix) expression parsers and evaluators + * @short An expression parser and evaluator + */ +class YATE_API ExpEvaluator : public DebugEnabler +{ +public: + /** + * Parsing styles + */ + enum Parser { + C, + SQL, + }; + + /** + * Operation codes + */ + enum Opcode { + // FORTH style notation of effect on stack, C-syntax expression + OpcNone = 0,// ( --- ) + OpcNull, // ( --- A) + OpcPush, // ( --- A) + OpcDrop, // (A --- ) + OpcDup, // (A --- A A) + OpcSwap, // (A B --- B A) + OpcRot, // (A B C --- B C A) + OpcOver, // (A B --- A B A) + // Arithmetic operators + OpcAdd, // (A B --- A+B) + OpcSub, // (A B --- A-B) + OpcMul, // (A B --- A*B) + OpcDiv, // (A B --- A/B) + OpcMod, // (A B --- A%B) + OpcNeg, // (A --- -A) + OpcIncPre, // (A --- ++A) + OpcDecPre, // (A --- --A) + OpcIncPost, // (A --- A++) + OpcDecPost, // (A --- A--) + // Bitwise logic operators + OpcAnd, // (A B --- A&B) + OpcOr, // (A B --- A|B) + OpcXor, // (A B --- A^B) + OpcNot, // (A --- ~A) + OpcShl, // (A B --- A<>B) + // Boolean logic operators + OpcLAnd, // (A B --- A&&B) + OpcLOr, // (A B --- A||B) + OpcLXor, // (A B --- A^^B) + OpcLNot, // (A --- !A) + // String concatenation + OpcCat, // (A B --- A.B) + // String matching + OpcReM, // (A B --- Amatch/B/) + OpcReIM, // (A B --- Amatch_insensitive/B/) + OpcReNm, // (A B --- A!match/B/) + OpcReINm, // (A B --- A!match_insensitive/B/) + OpcLike, // (A B --- AlikeB) + OpcILike, // (A B --- Alike_insensitiveB) + OpcNLike, // (A B --- A!likeB) + OpcNIlike, // (A B --- A!like_insensitiveB) + // Comparation operators + OpcEq, // (A B --- A==B) + OpcNe, // (A B --- A!=B) + OpcGt, // (A B --- A>B) + OpcLt, // (A B --- A=B) + OpcLe, // (A B --- A<=B) + // Ternary conditional operator + OpcCond, // (A B C --- A?B:C) + // Field naming operator + OpcAs, // (A B --- A[name=B]) + // Field replacement + OpcField, // (A --- A) + // Call of function with N parameters + OpcFunc, // (... funcN --- func(...)) + // Private extension area for derived classes + OpcPrivate = 0x0100, + // Field assignment - can be ORed with other binary operators + OpcAssign = 0x1000 // (A B --- B,(&A=B)) + }; + + /** + * Constructs an evaluator from an operator dictionary + * @param operators Pointer to operator dictionary, longest strings first + * @param unaryOps Pointer to unary operators dictionary, longest strings first + */ + explicit ExpEvaluator(const TokenDict* operators = 0, const TokenDict* unaryOps = 0); + + /** + * Constructs an evaluator from a parser style + * @param style Style of parsing to use + */ + explicit ExpEvaluator(Parser style); + + /** + * Copy constructor + * @param original Evaluator to copy the operation list from + */ + ExpEvaluator(const ExpEvaluator& original); + + /** + * Destructor + */ + virtual ~ExpEvaluator(); + + /** + * Parse and compile an expression + * @param expr Pointer to expression to compile + * @return Number of expressions compiled, zero on error + */ + int compile(const char* expr); + + /** + * 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 + * @return True if expression evaluation succeeded, false on failure + */ + bool evaluate(ObjList* results, void* 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 + * @return True if expression evaluation succeeded, false on failure + */ + inline bool evaluate(ObjList& results, void* context = 0) const + { return evaluate(&results,context); } + + /** + * Evaluate the expression, return computed results + * @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 + * @return Number of result columns, -1 on failure + */ + int evaluate(NamedList& results, unsigned int index = 0, const char* prefix = 0, void* 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 + * @return Number of result columns, -1 on failure + */ + int evaluate(Array& results, unsigned int index, void* context = 0) const; + + /** + * Simplify the expression, performs constant folding + * @return True if the expression was simplified + */ + inline bool simplify() + { return trySimplify(); } + + /** + * Check if the expression is empty (no operands or operators) + * @return True if the expression is completely empty + */ + inline bool null() const + { return m_opcodes.count() == 0; } + + /** + * Dump a list of operations according to current operators dictionary + * @param codes List of operation codes + * @param res Result string representation of operations + */ + void dump(const ObjList& codes, String& res) const; + + /** + * Dump the postfix expression according to current operators dictionary + * @param res Result string representation of operations + */ + inline void dump(String& res) const + { return dump(m_opcodes,res); } + + /** + * Dump a list of operations according to current operators dictionary + * @param codes List of operation codes + * @result String representation of operations + */ + inline String dump(const ObjList& codes) const + { String s; dump(codes,s); return s; } + + /** + * Dump the postfix expression according to current operators dictionary + * @result String representation of operations + */ + inline String dump() const + { String s; dump(s); return s; } + + /** + * Retrieve the internally used operator dictionary + * @return Pointer to operators dictionary in use + */ + inline const TokenDict* operators() const + { return m_operators; } + + /** + * Retrieve the internally used unary operators dictionary + * @return Pointer to unary operators dictionary in use + */ + inline const TokenDict* unaryOps() const + { return m_unaryOps; } + + /** + * Retrieve the internally used expression extender + * @return Pointer to the extender in use, NULL if none + */ + inline ExpExtender* extender() const + { return m_extender; } + + /** + * Set the expression extender to use in evaluation + * @param ext Pointer to the extender to use, NULL to remove current + */ + void extender(ExpExtender* ext); + + /** + * Push an operand on an evaluation stack + * @param stack Evaluation stack to remove the operand from + * @param oper Operation to push on stack, NULL will not be pushed + */ + static void pushOne(ObjList& stack, ExpOperation* oper); + + /** + * Pops an operand off an evaluation stack, does not pop a barrier + * @param stack Evaluation stack to remove the operand from + * @return Operator removed from stack, NULL if stack underflow + */ + static ExpOperation* popOne(ObjList& stack); + + /** + * Pops any operand (including barriers) off an evaluation stack + * @param stack Evaluation stack to remove the operand from + * @return Operator removed from stack, NULL if stack underflow + */ + static ExpOperation* popAny(ObjList& stack); + +protected: + /** + * Helper method to skip over whitespaces + * @param expr Pointer to expression cursor, gets advanced + * @return First character after whitespaces where expr points + */ + static char skipWhites(const char*& expr); + + /** + * Helper method to conditionally convert to lower case + * @param chr Character to convert + * @param makeLower True to convert chr to lower case + * @return Converted character or original if conversion not requested + */ + inline static char condLower(char chr, bool makeLower) + { return (makeLower && ('A' <= chr) && (chr <= 'Z')) ? (chr + ('a' - 'A')) : chr; } + + /** + * Helper method to return next operator in the parsed text + * @param expr Pointer to text to parse, gets advanced if succeeds + * @param operators Pointer to operators table to use + * @param caseInsensitive Match case-insensitive if set + * @return Operator code, OpcNone on failure + */ + Opcode getOperator(const char*& expr, const TokenDict* operators, bool caseInsensitive = false) const; + + /** + * Check if a character can be part of a keyword or identifier + * @param c Character to check + * @return True if the character can be part of a keyword or identifier + */ + virtual bool keywordChar(char c) const; + + /** + * Helper method to count characters making a keyword + * @param str Pointer to text without whitespaces in front + * @return Length of the keyword, 0 if a valid keyword doesn't follow + */ + virtual int getKeyword(const char* str) const; + + /** + * Helper method to 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) const; + + /** + * 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 + * @return True if one expression was compiled and a separator follows + */ + virtual bool runCompile(const char*& expr, char stop = 0); + + /** + * Returns next operator in the parsed text + * @param expr Pointer to text to parse, gets advanced if succeeds + * @return Operator code, OpcNone on failure + */ + virtual Opcode getOperator(const char*& expr); + + /** + * Returns next unary operator in the parsed text + * @param expr Pointer to text to parse, gets advanced if succeeds + * @return Operator code, OpcNone on failure + */ + virtual Opcode getUnaryOperator(const char*& expr); + + /** + * Returns next unary postfix operator in the parsed text + * @param expr Pointer to text to parse, gets advanced if succeeds + * @return Operator code, OpcNone on failure + */ + virtual Opcode getPostfixOperator(const char*& expr); + + /** + * Helper method to get the canonical name of an operator + * @param oper Operator code + * @return name of the operator, NULL if it doesn't have one + */ + virtual const char* getOperator(Opcode oper) const; + + /** + * Get the precedence of an operator + * @param oper Operator code + * @return Precedence of the operator, zero (lowest) if unknown + */ + virtual int getPrecedence(Opcode oper) const; + + /** + * Get the associativity of an operator + * @param oper Operator code + * @return True if the operator is right-to-left associative, false if left-to-right + */ + virtual bool getRightAssoc(Opcode oper) const; + + /** + * Check if we are at an expression separator and optionally skip past it + * @param expr Pointer to text to check, gets advanced if asked to remove separator + * @param remove True to skip past the found separator + * @return True if a separator was found + */ + virtual bool getSeparator(const char*& expr, bool remove); + + /** + * Get an instruction or block, advance parsing pointer past it + * @param expr Pointer to text to parse, gets advanced on success + * @return True if succeeded, must add the operands internally + */ + virtual bool getInstruction(const char*& expr); + + /** + * Get an operand, advance parsing pointer past it + * @param expr Pointer to text to parse, gets advanced on success + * @return True if succeeded, must add the operand internally + */ + virtual bool getOperand(const char*& expr); + + /** + * Get a numerical operand, advance parsing pointer past it + * @param expr Pointer to text to parse, gets advanced on success + * @return True if succeeded, must add the operand internally + */ + virtual bool getNumber(const char*& expr); + + /** + * Get a string operand, advance parsing pointer past it + * @param expr Pointer to text to parse, gets advanced on success + * @return True if succeeded, must add the operand internally + */ + virtual bool getString(const char*& expr); + + /** + * Get a function call, advance parsing pointer past it + * @param expr Pointer to text to parse, gets advanced on success + * @return True if succeeded, must add the operand internally + */ + virtual bool getFunction(const char*& expr); + + /** + * Get a field keyword, advance parsing pointer past it + * @param expr Pointer to text to parse, gets advanced on success + * @return True if succeeded, must add the operand internally + */ + virtual bool getField(const char*& expr); + + /** + * Add a simple operator to the expression + * @param oper Operator code to add + * @param barrier True to create an exavuator stack barrier + */ + void addOpcode(Opcode oper, 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); + + /** + * Add an integer constant to the expression + * @param value Integer value to add, will be pushed on execution + */ + void addOpcode(long int value); + + /** + * Add a function or field to the expression + * @param oper Operator code to add, must be OpcField or OpcFunc + * @param name Name of the field or function, case sensitive + * @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); + + /** + * Try to apply simplification to the expression + * @return True if the expression was simplified + */ + 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 + * @return True if evaluation succeeded + */ + virtual bool runEvaluate(const ObjList& opcodes, ObjList& stack, void* 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 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; + + /** + * 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 + * @return True if evaluation succeeded + */ + virtual bool runEvaluate(ObjList& stack, void* 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 + * @return True if all fields on the stack were evaluated properly + */ + virtual bool runAllFields(ObjList& stack, void* 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 + * @return True if evaluation succeeded + */ + virtual bool runOperation(ObjList& stack, const ExpOperation& oper, void* 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 + * @return True if evaluation succeeded + */ + virtual bool runFunction(ObjList& stack, const ExpOperation& oper, void* 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 + * @return True if evaluation succeeded + */ + virtual bool runField(ObjList& stack, const ExpOperation& oper, void* context = 0) const; + + /** + * Try to assign a value to a single field + * @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 + * @return True if assignment succeeded + */ + virtual bool runAssign(const ExpOperation& oper, void* context = 0) const; + + /** + * Internally used operator dictionary + */ + const TokenDict* m_operators; + + /** + * Internally used unary operators dictionary + */ + const TokenDict* m_unaryOps; + + /** + * Internally used list of operands and operator codes + */ + ObjList m_opcodes; + +private: + ExpExtender* m_extender; +}; + +/** + * This class describes a single operation in an expression evaluator + * @short A single operation in an expression + */ +class YATE_API ExpOperation : public NamedString +{ + friend class ExpEvaluator; + YCLASS(ExpOperation,NamedString) +public: + /** + * Special value that is not recognized as an integer value + * @return A value that indicates a non-integer value + */ + inline static long int nonInteger() + { return LONG_MIN; } + + /** + * Copy constructor + * @param original Operation to copy + */ + inline ExpOperation(const ExpOperation& original) + : NamedString(original.name(),original), + m_opcode(original.opcode()), m_number(original.number()), + m_barrier(original.barrier()) + { } + + /** + * Copy constructor with renaming, to be used for named results + * @param original Operation to copy + * @param name Name of the newly created operation + */ + inline ExpOperation(const ExpOperation& original, const char* name) + : NamedString(name,original), + m_opcode(original.opcode()), m_number(original.number()), + m_barrier(original.barrier()) + { } + + /** + * Push String constructor + * @param value String constant to push on stack on execution + * @param name Optional of the newly created constant + */ + inline explicit ExpOperation(const String& value, const char* name = 0) + : NamedString(name,value), + m_opcode(ExpEvaluator::OpcPush), m_number(nonInteger()), + m_barrier(false) + { } + + /** + * Push Number constructor + * @param value Integer constant to push on stack on execution + * @param name Optional of the newly created constant + */ + inline explicit ExpOperation(long int value, const char* name = 0) + : NamedString(name,""), + m_opcode(ExpEvaluator::OpcPush), m_number(value), m_barrier(false) + { String::operator=((int)value); } + + /** + * Constructor from components + * @param oper Operation code + * @param name Optional name of the operation or result + * @param value Optional integer constant used as function parameter count + * @param barrier True if the operation is an expression barrier on the stack + */ + inline ExpOperation(ExpEvaluator::Opcode oper, const char* name = 0, long int value = nonInteger(), bool barrier = false) + : NamedString(name,""), + m_opcode(oper), m_number(value), m_barrier(barrier) + { } + + /** + * Retrieve the code of this operation + * @return Operation code as declared in the expression evaluator + */ + inline ExpEvaluator::Opcode opcode() const + { return m_opcode; } + + /** + * Check if an integer value is stored + * @return True if an integer value is stored + */ + inline bool isInteger() const + { return m_number != nonInteger(); } + + /** + * Retrieve the number stored in this operation + * @return Stored number + */ + inline long int number() const + { return m_number; } + + /** + * Check if this operation acts as an evaluator barrier on the stack + * @return True if an expression should not pop this operation off the stack + */ + inline bool barrier() const + { return m_barrier; } + + /** + * Number assignment operator + * @param num Numeric value to assign to the operation + * @return Assigned number + */ + inline long int operator=(long int num) + { m_number = num; String::operator=((int)num); return num; } + +private: + ExpEvaluator::Opcode m_opcode; + long int m_number; + bool m_barrier; +}; + +/** + * An evaluator for multi-row (tables like in SQL) expressions + * @short An SQL-like table evaluator + */ +class YATE_API TableEvaluator +{ +public: + /** + * Copy constructor, duplicates current state of original + * @param original Evaluator to copy + */ + TableEvaluator(const TableEvaluator& original); + + /** + * Constructor from a parser synatx style + * @param style Style of evaluator to create + */ + TableEvaluator(ExpEvaluator::Parser style); + + /** + * Constructor from operator description table + * @param operators Pointer to operators synatx table + * @param unaryOps Pointer to unary operators dictionary + */ + TableEvaluator(const TokenDict* operators, const TokenDict* unaryOps); + + /** + * Destructor + */ + virtual ~TableEvaluator(); + + /** + * Evaluate the WHERE (selector) expression + * @param context Pointer to arbitrary data to be passed to called methods + * @return True if the current row is part of selection + */ + virtual bool evalWhere(void* 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 + * @return True if evaluation succeeded + */ + virtual bool evalSelect(ObjList& results, void* context = 0); + + /** + * Evaluate the LIMIT expression and cache the result + * @param context Pointer to arbitrary data to be passed to called methods + * @return Desired maximum number or result rows + */ + virtual unsigned int evalLimit(void* context = 0); + + /** + * Set the expression extender to use in all evaluators + * @param ext Pointer to the extender to use, NULL to remove current + */ + void extender(ExpExtender* ext); + +protected: + ExpEvaluator m_select; + ExpEvaluator m_where; + ExpEvaluator m_limit; + unsigned int m_limitVal; +}; + +class ScriptRun; + +/** + * A script execution context, holds global variables and objects + * @short Script execution context + */ +class YSCRIPT_API ScriptContext : public ExpExtender, public Mutex +{ + YCLASS(ScriptContext,ExpExtender) +public: + /** + * Constructor + * @param name Name of the context + */ + inline explicit ScriptContext(const char* name = 0) + : m_params(name) + { } + + /** + * Access to the NamedList operator + * @return Reference to the internal named list + */ + inline NamedList& params() + { return m_params; } + + /** + * Const access to the NamedList operator + * @return Reference to the internal named list + */ + inline const NamedList& params() const + { return m_params; } + + /** + * Override GenObject's method to return the internal name of the named list + * @return A reference to the context name + */ + virtual const String& toString() const + { return m_params; } + + /** + * 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); + + /** + * 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); + + /** + * Try to assign a value to a single field + * @param eval Pointer to the caller evaluator object + * @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); + +private: + NamedList m_params; +}; + +/** + * Preparsed script code fragment ready to be executed + * @short Script parsed code + */ +class YSCRIPT_API ScriptCode : public RefObject +{ + YCLASS(ScriptCode,RefObject) +public: + /** + * Context initializer for language specific globals + * @param context Pointer to the context to initialize + * @return True if context was properly populated with globals + */ + virtual bool initialize(ScriptContext* context) const = 0; + + /** + * Evaluation of a single code expression + * @param context Reference to the context to use in evaluation + * @param results List to fill with expression results + */ + virtual bool evaluate(ScriptContext& context, ObjList& results) const = 0; +}; + +/** + * A stack for a script running instance + * @short Script runtime stack + */ +class YSCRIPT_API ScriptStack : public ObjList +{ + YCLASS(ScriptStack,ObjList) + YNOCOPY(ScriptStack); +public: + /** + * Constructor + * @param owner The script running instance that will own this stack + */ + ScriptStack(ScriptRun* owner) + : m_runner(owner) + { } + + /** + * Retrieve the script running instance that owns this stack + * @return Pointer to owner script instance + */ + inline ScriptRun* runner() + { return m_runner; } +private: + ScriptRun* m_runner; +}; + +/** + * An instance of script code and data, status machine run by a single thread at a time + * @short Script runtime execution + */ +class YSCRIPT_API ScriptRun : public GenObject, public Mutex +{ + YCLASS(ScriptRun,GenObject) +public: + /** + * Runtime states + */ + enum Status { + Invalid, + Running, + Incomplete, + Succeeded, + Failed, + }; + + /** + * Constructor + * @param code Code fragment to execute + * @param context Script context, an empty one will be allocated if NULL + */ + ScriptRun(ScriptCode* code, ScriptContext* context = 0); + + /** + * Destructor, disposes the code and context + */ + virtual ~ScriptRun(); + + /** + * Retrieve the parsed code being executed + * @return Pointer to ScriptCode object + */ + inline ScriptCode* code() const + { return m_code; } + + /** + * Retrieve the execution context associated with the runtime + * @return Pointer to ScriptContext object + */ + inline ScriptContext* context() const + { return m_context; } + + /** + * Current state of the runtime + */ + inline Status state() const + { return m_state; } + + /** + * Get the text description of a runtime state + * @param state State to describe + * @return Description of the runtime state + */ + static const char* textState(Status state); + + /** + * Get the text description of the current runtime state + * @return Description of the runtime state + */ + inline const char* textState() const + { return textState(m_state); } + + /** + * Access the runtime execution stack + * @return The internal execution stack + */ + inline ObjList& stack() + { return m_stack; } + + /** + * Const access the runtime execution stack + * @return The internal execution stack + */ + inline const ObjList& stack() const + { return m_stack; } + + /** + * Resets code execution to the beginning, does not clear context + * @return Status of the runtime after reset + */ + Status reset(); + + /** + * Execute script from where it was left, may stop and return Incomplete state + * @return Status of the runtime after code execution + */ + Status execute(); + + /** + * Execute script from the beginning until it returns a final state + * @return Final status of the runtime after code execution + */ + Status run(); + +protected: + /** + * Resume script from where it was left, may stop and return Incomplete state + * @return Status of the runtime after code execution + */ + Status resume(); + +private: + ScriptCode* m_code; + ScriptContext* m_context; + Status m_state; + ObjList m_stack; +}; + +/** + * Abstract parser, base class for each language parser + * @short Abstract script parser + */ +class YSCRIPT_API ScriptParser : public GenObject +{ + YCLASS(ScriptParser,GenObject) +public: + /** + * Destructor, releases code + */ + virtual ~ScriptParser(); + + /** + * Parse a string as script source code + * @param text Source code text + * @return True if the text was successfully parsed + */ + virtual bool parse(const char* text) = 0; + + /** + * Retrieve the currently stored parsed code + * @return Parsed code block, may be NULL + */ + inline ScriptCode* code() const + { return m_code; } + +protected: + /** + * Default constructor for derived classes + */ + inline ScriptParser() + : m_code(0) + { } + + /** + * Set the just parsed block of code + * @param code Parsed code block, may be NULL + */ + void setCode(ScriptCode* code); + +private: + ScriptCode* m_code; +}; + +/** + * Javascript Object class, base for all JS objects + * @short Javascript Object + */ +class YSCRIPT_API JsObject : public NamedList +{ + YCLASS(JsObject,NamedList) +public: + /** + * Constructor + * @param name Name of the 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) + { } + + /** + * Access the list of object attributes and methods + * @return The list of attributes and functions + */ + virtual NamedList& list() + { return *this; } + + /** + * Const access to the list of object attributes and methods + * @return The list of attributes and functions + */ + virtual const NamedList& list() const + { return *this; } + + /** + * Retrieve the object frozen status (cannot modify attributes or methods) + * @return True if the object is frozen + */ + inline bool frozen() const + { return m_frozen; } + + /** + * Freeze the Javascript object preventing external changes to it + */ + inline void freeze() + { m_frozen = true; } + + /** + * Initialize the standard global objects in a context + * @param context Script context to initialize + */ + static void initialize(ScriptContext& context); + +private: + bool m_frozen; +}; + +/** + * Javascript parser, takes source code and generates preparsed code + * @short Javascript parser + */ +class YSCRIPT_API JsParser : public ScriptParser +{ + YCLASS(JsParser,ScriptParser) +public: + /** + * Parse a string as Javascript source code + * @param text Source code text + * @return True if the text was successfully parsed + */ + virtual bool parse(const char* text); + + /** + * Parse and run a piece of Javascript code + * @param text Source code fragment to execute + * @param result Pointer to an optional pointer to store returned value + * @param context Script context, an empty one will be allocated if NULL + * @return Status of the runtime after code execution + */ + static ScriptRun::Status eval(const String& text, ExpOperation** result = 0, ScriptContext* context = 0); +}; + +}; // namespace TelEngine + +#endif /* __YATESCRIPT_H */ + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/ysig/layer3.cpp b/libs/ysig/layer3.cpp index ad6aa1dd..db7e2527 100644 --- a/libs/ysig/layer3.cpp +++ b/libs/ysig/layer3.cpp @@ -1196,13 +1196,14 @@ void SS7MTP3::notify(SS7Layer2* link) } } countLinks(); + String text; + text << "Linkset has " << m_active << " active, "; + text << m_checked << " checked of " << m_total << " links"; #ifdef DEBUG String tmp; if (link) tmp << "Link '" << link->toString() << "' is " << (link->operational()?"":"not ") << "operational. "; - Debug(this,DebugInfo,"%sLinkset has %u/%u/%u active/checked links [%p]", - tmp.null()?"":tmp.c_str(), - m_active,m_checked,m_total,this); + Debug(this,DebugInfo,"%s%s [%p]",tmp.safe(),text.c_str(),this); #endif // if operational status of a link changed notify upper layer if (act != m_active || chk != m_checked) { @@ -1252,7 +1253,7 @@ void SS7MTP3::notify(SS7Layer2* link) notif.addParam("total",String(m_total)); notif.addParam("link", link ? link->toString() : ""); notif.addParam("linkup", link ? String::boolText(link->operational()) : ""); - + notif.addParam("text", text); mylock.drop(); SS7Layer3::notify(sls); engine()->notify(this,notif); diff --git a/modules/Makefile.in b/modules/Makefile.in index 6f4553a7..75aea509 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -368,6 +368,10 @@ 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 + zlibcompress.yate: LOCALFLAGS = $(ZLIB_INC) zlibcompress.yate: LOCALLIBS = $(ZLIB_LIB) @@ -405,6 +409,9 @@ server/ysnmpagent.yate: LOCALLIBS = -L../libs/yasn -lyasn -L../libs/ysnmp -lysnm ../libyatejabber.so ../libs/yjabber/libyatejabber.a: @top_srcdir@/libs/yjabber/xmpputils.h @top_srcdir@/libs/yjabber/yatejabber.h @top_srcdir@/libs/yjabber/yatejingle.h $(MAKE) -C ../libs/yjabber +../libyatescript.so ../libs/yjabber/libyatescript.a: @top_srcdir@/libs/yscript/yatescript.h + $(MAKE) -C ../libs/yscript + ../libs/ypbx/libyatepbx.a: @top_srcdir@/libs/ypbx/yatepbx.h $(MAKE) -C ../libs/ypbx diff --git a/modules/javascript.cpp b/modules/javascript.cpp new file mode 100644 index 00000000..6a81efee --- /dev/null +++ b/modules/javascript.cpp @@ -0,0 +1,104 @@ +/** + * javascript.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Javascript support based on libyscript + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2011 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +using namespace TelEngine; +namespace { // anonymous + +class JavascriptModule : public Module +{ +public: + JavascriptModule(); + ~JavascriptModule(); + virtual void initialize(); + bool unload(); +protected: + virtual bool commandExecute(String& retVal, const String& line); + virtual bool commandComplete(Message& msg, const String& partLine, const String& partWord); +}; + + +INIT_PLUGIN(JavascriptModule); + +UNLOAD_PLUGIN(unloadNow) +{ + if (unloadNow) + return __plugin.unload(); + return true; +} + +JavascriptModule::JavascriptModule() + : Module("javascript","misc",true) +{ + Output("Loaded module Javascript"); +} + +JavascriptModule::~JavascriptModule() +{ + Output("Unloading module Javascript"); +} + +bool JavascriptModule::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"; + else + retVal << ScriptRun::textState(st) << "\r\n"; + TelEngine::destruct(rval); + return true; +} + +bool JavascriptModule::commandComplete(Message& msg, const String& partLine, const String& partWord) +{ + if (partLine.null() && partWord.null()) + return false; + if (partLine.null() || (partLine == "help")) + itemComplete(msg.retValue(),"js",partWord); + return Module::commandComplete(msg,partLine,partWord); +} + +bool JavascriptModule::unload() +{ + uninstallRelays(); + return true; +} + +void JavascriptModule::initialize() +{ + Output("Initializing module Javascript"); + setup(); +} + +}; // anonymous namespace + +/* vi: set ts=8 sw=4 sts=4 noet: */