diff --git a/modules/juce_core/json/juce_JSON.cpp b/modules/juce_core/javascript/juce_JSON.cpp similarity index 100% rename from modules/juce_core/json/juce_JSON.cpp rename to modules/juce_core/javascript/juce_JSON.cpp diff --git a/modules/juce_core/json/juce_JSON.h b/modules/juce_core/javascript/juce_JSON.h similarity index 100% rename from modules/juce_core/json/juce_JSON.h rename to modules/juce_core/javascript/juce_JSON.h diff --git a/modules/juce_core/javascript/juce_Javascript.cpp b/modules/juce_core/javascript/juce_Javascript.cpp new file mode 100644 index 0000000000..377a186967 --- /dev/null +++ b/modules/juce_core/javascript/juce_Javascript.cpp @@ -0,0 +1,1648 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + +#define JUCE_JS_OPERATORS(X) \ + X(semicolon, ";") X(dot, ".") X(comma, ",") \ + X(openParen, "(") X(closeParen, ")") X(openBrace, "{") X(closeBrace, "}") \ + X(openBracket, "[") X(closeBracket, "]") X(colon, ":") X(question, "?") \ + X(typeEquals, "===") X(equals, "==") X(assign, "=") \ + X(typeNotEquals, "!==") X(notEquals, "!=") X(logicalNot, "!") \ + X(plusEquals, "+=") X(plusplus, "++") X(plus, "+") \ + X(minusEquals, "-=") X(minusminus, "--") X(minus, "-") \ + X(timesEquals, "*=") X(times, "*") X(divideEquals, "/=") X(divide, "/") \ + X(moduloEquals, "%=") X(modulo, "%") X(xorEquals, "^=") X(bitwiseXor, "^") \ + X(andEquals, "&=") X(logicalAnd, "&&") X(bitwiseAnd, "&") \ + X(orEquals, "|=") X(logicalOr, "||") X(bitwiseOr, "|") \ + X(leftShiftEquals, "<<=") X(lessThanOrEqual, "<=") X(leftShift, "<<") X(lessThan, "<") \ + X(rightShiftUnsigned, ">>>") X(rightShiftEquals, ">>=") X(rightShift, ">>") X(greaterThanOrEqual, ">=") X(greaterThan, ">") + +#define JUCE_JS_KEYWORDS(X) \ + X(var, "var") X(if_, "if") X(else_, "else") X(do_, "do") X(null_, "null") \ + X(while_, "while") X(for_, "for") X(break_, "break") X(continue_, "continue") X(undefined, "undefined") \ + X(function, "function") X(return_, "return") X(true_, "true") X(false_, "false") X(new_, "new") + +namespace TokenTypes +{ + #define JUCE_DECLARE_JS_TOKEN(name, str) static const char* const name = str; + JUCE_JS_KEYWORDS (JUCE_DECLARE_JS_TOKEN) + JUCE_JS_OPERATORS (JUCE_DECLARE_JS_TOKEN) + JUCE_DECLARE_JS_TOKEN (eof, "$eof") + JUCE_DECLARE_JS_TOKEN (literal, "$literal") + JUCE_DECLARE_JS_TOKEN (identifier, "$identifier") +} + +//============================================================================== +struct JavascriptEngine::RootObject : public DynamicObject +{ + RootObject() + { + setMethod ("exec", exec); + setMethod ("eval", eval); + setMethod ("trace", trace); + setMethod ("charToInt", charToInt); + setMethod ("parseInt", IntegerClass::parseInt); + } + + typedef const var::NativeFunctionArgs& Args; + typedef const char* TokenType; + + void execute (const String& code) + { + ExpressionTreeBuilder tb (code); + ScopedPointer (tb.parseStatementList())->perform (Scope (nullptr, this, this), nullptr); + } + + var evaluate (const String& code) + { + ExpressionTreeBuilder tb (code); + return ExpPtr (tb.parseExpression())->getResult (Scope (nullptr, this, this)); + } + + Time timeout; + + //============================================================================== + static bool areTypeEqual (const var& a, const var& b) + { + return a.hasSameTypeAs (b) && isFunction (a) == isFunction (b) + && (((a.isUndefined() || a.isVoid()) && (b.isUndefined() || b.isVoid())) || a == b); + } + + static String getTokenName (TokenType t) { return t[0] == '$' ? String (t + 1) : ("'" + String (t) + "'"); } + static bool isFunction (const var& v) { return dynamic_cast (v.getObject()) != nullptr; } + static bool isNumericOrUndefined (const var& v) { return v.isInt() || v.isDouble() || v.isInt64() || v.isBool() || v.isUndefined(); } + static int64 getOctalValue (const String& s) { BigInteger b; b.parseString (s, 8); return b.toInt64(); } + static Identifier getPrototypeIdentifier() { static const Identifier i ("prototype"); return i; } + + //============================================================================== + struct CodeLocation + { + CodeLocation (const String& code) noexcept : program (code), location (program.getCharPointer()) {} + CodeLocation (const CodeLocation& other) noexcept : program (other.program), location (other.location) {} + + void throwError (const String& message) const + { + int col = 1, line = 1; + + for (String::CharPointerType i (program.getCharPointer()); i < location && ! i.isEmpty(); ++i) + { + ++col; + if (*i == '\n') { col = 1; ++line; } + } + + throw "Line " + String (line) + ", column " + String (col) + " : " + message; + } + + String program; + String::CharPointerType location; + }; + + //============================================================================== + struct Scope + { + Scope (const Scope* p, RootObject* r, DynamicObject* s) noexcept : parent (p), root (r), scope (s) {} + + const Scope* parent; + ReferenceCountedObjectPtr root; + DynamicObject::Ptr scope; + + var findFunctionCall (const CodeLocation& location, const var& targetObject, Identifier functionName) const + { + if (DynamicObject* o = targetObject.getDynamicObject()) + { + if (var* prop = o->getProperties().getVarPointer (functionName)) + return *prop; + + for (DynamicObject* p = o->getProperty (getPrototypeIdentifier()).getDynamicObject(); p != nullptr; + p = p->getProperty (getPrototypeIdentifier()).getDynamicObject()) + { + if (var* prop = p->getProperties().getVarPointer (functionName)) + return *prop; + } + } + + if (targetObject.isString()) + if (var* m = findRootClassProperty (StringClass::getClassName(), functionName)) + return *m; + + if (targetObject.isArray()) + if (var* m = findRootClassProperty (ArrayClass::getClassName(), functionName)) + return *m; + + if (var* m = findRootClassProperty (ObjectClass::getClassName(), functionName)) + return *m; + + location.throwError ("Unknown function '" + functionName.toString() + "'"); + return var(); + } + + var* findRootClassProperty (Identifier className, Identifier propName) const + { + if (DynamicObject* cls = root->getProperty (className).getDynamicObject()) + return cls->getProperties().getVarPointer (propName); + + return nullptr; + } + + var findSymbolInParentScopes (Identifier name) const + { + if (var* v = scope->getProperties().getVarPointer (name)) + return *v; + + return parent != nullptr ? parent->findSymbolInParentScopes (name) + : var::undefined(); + } + + void checkTimeOut (const CodeLocation& location) const + { + if (Time::getCurrentTime() > root->timeout) + location.throwError ("Execution timed-out"); + } + }; + + //============================================================================== + struct Statement + { + Statement (const CodeLocation& l) noexcept : location (l) {} + virtual ~Statement() {} + + enum ResultCode { ok = 0, returnWasHit, breakWasHit, continueWasHit }; + virtual ResultCode perform (const Scope&, var*) const { return ok; } + + CodeLocation location; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Statement) + }; + + struct Expression : public Statement + { + Expression (const CodeLocation& l) noexcept : Statement (l) {} + + virtual var getResult (const Scope&) const { return var::undefined(); } + virtual void assign (const Scope&, const var&) const { location.throwError ("Cannot assign to this expression!"); } + + ResultCode perform (const Scope& s, var*) const override { getResult (s); return ok; } + }; + + typedef ScopedPointer ExpPtr; + + struct BlockStatement : public Statement + { + BlockStatement (const CodeLocation& l) noexcept : Statement (l) {} + + ResultCode perform (const Scope& s, var* returnedValue) const override + { + for (int i = 0; i < statements.size(); ++i) + if (ResultCode r = statements.getUnchecked(i)->perform (s, returnedValue)) + return r; + + return ok; + } + + OwnedArray statements; + }; + + struct IfStatement : public Statement + { + IfStatement (const CodeLocation& l) noexcept : Statement (l) {} + + ResultCode perform (const Scope& s, var* returnedValue) const override + { + return (condition->getResult(s) ? trueBranch : falseBranch)->perform (s, returnedValue); + } + + ExpPtr condition; + ScopedPointer trueBranch, falseBranch; + }; + + struct VarStatement : public Statement + { + VarStatement (const CodeLocation& l) noexcept : Statement (l) {} + + ResultCode perform (const Scope& s, var*) const override + { + s.scope->setProperty (name, initialiser->getResult (s)); + return ok; + } + + Identifier name; + ExpPtr initialiser; + }; + + struct LoopStatement : public Statement + { + LoopStatement (const CodeLocation& l, bool isDo) noexcept : Statement (l), isDoLoop (isDo) {} + + ResultCode perform (const Scope& s, var* returnedValue) const override + { + initialiser->perform (s, nullptr); + + while (isDoLoop || condition->getResult (s)) + { + s.checkTimeOut (location); + ResultCode r = body->perform (s, returnedValue); + + if (r == returnWasHit) return r; + if (r == breakWasHit) break; + if (r == continueWasHit) continue; + + iterator->perform (s, nullptr); + + if (isDoLoop && ! condition->getResult (s)) + break; + } + + return ok; + } + + ScopedPointer initialiser, iterator, body; + ExpPtr condition; + bool isDoLoop; + }; + + struct ReturnStatement : public Statement + { + ReturnStatement (const CodeLocation& l, ExpPtr v) noexcept : Statement (l), returnValue (v) {} + + ResultCode perform (const Scope& s, var* ret) const override + { + if (ret != nullptr) *ret = returnValue->getResult (s); + return returnWasHit; + } + + ExpPtr returnValue; + }; + + struct BreakStatement : public Statement + { + BreakStatement (const CodeLocation& l) noexcept : Statement (l) {} + ResultCode perform (const Scope&, var*) const override { return breakWasHit; } + }; + + struct ContinueStatement : public Statement + { + ContinueStatement (const CodeLocation& l) noexcept : Statement (l) {} + ResultCode perform (const Scope&, var*) const override { return continueWasHit; } + }; + + struct LiteralValue : public Expression + { + LiteralValue (const CodeLocation& l, const var& v) noexcept : Expression (l), value (v) {} + var getResult (const Scope&) const override { return value; } + var value; + }; + + struct UnqualifiedName : public Expression + { + UnqualifiedName (const CodeLocation& l, Identifier n) noexcept : Expression (l), name (n) {} + + var getResult (const Scope& s) const override { return s.findSymbolInParentScopes (name); } + + void assign (const Scope& s, const var& newValue) const override + { + if (var* v = s.scope->getProperties().getVarPointer (name)) + *v = newValue; + else + s.root->setProperty (name, newValue); + } + + Identifier name; + }; + + struct DotOperator : public Expression + { + DotOperator (const CodeLocation& l, ExpPtr p, Identifier c) noexcept : Expression (l), parent (p), child (c) {} + + var getResult (const Scope& s) const override + { + var p (parent->getResult (s)); + static const Identifier lengthID ("length"); + + if (child == lengthID) + { + if (Array* array = p.getArray()) return array->size(); + if (p.isString()) return p.toString().length(); + } + + if (DynamicObject* o = p.getDynamicObject()) + if (var* v = o->getProperties().getVarPointer (child)) + return *v; + + return var::undefined(); + } + + void assign (const Scope& s, const var& newValue) const override + { + if (DynamicObject* o = parent->getResult (s).getDynamicObject()) + o->setProperty (child, newValue); + else + Expression::assign (s, newValue); + } + + ExpPtr parent; + Identifier child; + }; + + struct ArraySubscript : public Expression + { + ArraySubscript (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override + { + if (const Array* array = object->getResult (s).getArray()) + return (*array) [static_cast (index->getResult (s))]; + + return var::undefined(); + } + + void assign (const Scope& s, const var& newValue) const override + { + if (Array* array = object->getResult (s).getArray()) + { + const int i = index->getResult (s); + while (array->size() < i) + array->add (var::undefined()); + + array->set (i, newValue); + return; + } + + Expression::assign (s, newValue); + } + + ExpPtr object, index; + }; + + struct BinaryOperatorBase : public Expression + { + BinaryOperatorBase (const CodeLocation& l, ExpPtr a, ExpPtr b, TokenType op) noexcept + : Expression (l), lhs (a), rhs (b), operation (op) {} + + ExpPtr lhs, rhs; + TokenType operation; + }; + + struct BinaryOperator : public BinaryOperatorBase + { + BinaryOperator (const CodeLocation& l, ExpPtr a, ExpPtr b, TokenType op) noexcept + : BinaryOperatorBase (l, a, b, op) {} + + virtual var getWithUndefinedArg() const { return var::undefined(); } + virtual var getWithDoubles (double, double) const { return throwError ("Double"); } + virtual var getWithInts (int64, int64) const { return throwError ("Integer"); } + virtual var getWithArrayOrObject (const var& a, const var&) const { return throwError (a.isArray() ? "Array" : "Object"); } + virtual var getWithStrings (const String&, const String&) const { return throwError ("String"); } + + var getResult (const Scope& s) const override + { + var a (lhs->getResult (s)), b (rhs->getResult (s)); + + if ((a.isUndefined() || a.isVoid()) && (b.isUndefined() || b.isVoid())) + return getWithUndefinedArg(); + + if (isNumericOrUndefined (a) && isNumericOrUndefined (b)) + return (a.isDouble() || b.isDouble()) ? getWithDoubles (a, b) : getWithInts (a, b); + + if (a.isArray() || a.isObject()) + return getWithArrayOrObject (a, b); + + return getWithStrings (a.toString(), b.toString()); + } + + var throwError (const char* typeName) const + { location.throwError (getTokenName (operation) + " is not allowed on the " + typeName + " type"); return var(); } + }; + + struct EqualsOp : public BinaryOperator + { + EqualsOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::equals) {} + var getWithUndefinedArg() const override { return true; } + var getWithDoubles (double a, double b) const override { return a == b; } + var getWithInts (int64 a, int64 b) const override { return a == b; } + var getWithStrings (const String& a, const String& b) const override { return a == b; } + var getWithArrayOrObject (const var& a, const var& b) const override { return a == b; } + }; + + struct NotEqualsOp : public BinaryOperator + { + NotEqualsOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::notEquals) {} + var getWithUndefinedArg() const override { return false; } + var getWithDoubles (double a, double b) const override { return a != b; } + var getWithInts (int64 a, int64 b) const override { return a != b; } + var getWithStrings (const String& a, const String& b) const override { return a != b; } + var getWithArrayOrObject (const var& a, const var& b) const override { return a != b; } + }; + + struct LessThanOp : public BinaryOperator + { + LessThanOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::lessThan) {} + var getWithDoubles (double a, double b) const override { return a < b; } + var getWithInts (int64 a, int64 b) const override { return a < b; } + var getWithStrings (const String& a, const String& b) const override { return a < b; } + }; + + struct LessThanOrEqualOp : public BinaryOperator + { + LessThanOrEqualOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::lessThanOrEqual) {} + var getWithDoubles (double a, double b) const override { return a <= b; } + var getWithInts (int64 a, int64 b) const override { return a <= b; } + var getWithStrings (const String& a, const String& b) const override { return a <= b; } + }; + + struct GreaterThanOp : public BinaryOperator + { + GreaterThanOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::greaterThan) {} + var getWithDoubles (double a, double b) const override { return a > b; } + var getWithInts (int64 a, int64 b) const override { return a > b; } + var getWithStrings (const String& a, const String& b) const override { return a > b; } + }; + + struct GreaterThanOrEqualOp : public BinaryOperator + { + GreaterThanOrEqualOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::greaterThanOrEqual) {} + var getWithDoubles (double a, double b) const override { return a >= b; } + var getWithInts (int64 a, int64 b) const override { return a >= b; } + var getWithStrings (const String& a, const String& b) const override { return a >= b; } + }; + + struct AdditionOp : public BinaryOperator + { + AdditionOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::plus) {} + var getWithDoubles (double a, double b) const override { return a + b; } + var getWithInts (int64 a, int64 b) const override { return a + b; } + var getWithStrings (const String& a, const String& b) const override { return a + b; } + }; + + struct SubtractionOp : public BinaryOperator + { + SubtractionOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::minus) {} + var getWithDoubles (double a, double b) const override { return a - b; } + var getWithInts (int64 a, int64 b) const override { return a - b; } + }; + + struct MultiplyOp : public BinaryOperator + { + MultiplyOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::times) {} + var getWithDoubles (double a, double b) const override { return a * b; } + var getWithInts (int64 a, int64 b) const override { return a * b; } + }; + + struct DivideOp : public BinaryOperator + { + DivideOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::divide) {} + var getWithDoubles (double a, double b) const override { return a / b; } + var getWithInts (int64 a, int64 b) const override { return a / b; } + }; + + struct ModuloOp : public BinaryOperator + { + ModuloOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::modulo) {} + var getWithInts (int64 a, int64 b) const override { return a % b; } + }; + + struct BitwiseOrOp : public BinaryOperator + { + BitwiseOrOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::bitwiseOr) {} + var getWithInts (int64 a, int64 b) const override { return a | b; } + }; + + struct BitwiseAndOp : public BinaryOperator + { + BitwiseAndOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::bitwiseAnd) {} + var getWithInts (int64 a, int64 b) const override { return a & b; } + }; + + struct BitwiseXorOp : public BinaryOperator + { + BitwiseXorOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::bitwiseXor) {} + var getWithInts (int64 a, int64 b) const override { return a ^ b; } + }; + + struct LeftShiftOp : public BinaryOperator + { + LeftShiftOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::leftShift) {} + var getWithInts (int64 a, int64 b) const override { return ((int) a) << (int) b; } + }; + + struct RightShiftOp : public BinaryOperator + { + RightShiftOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::rightShift) {} + var getWithInts (int64 a, int64 b) const override { return ((int) a) >> (int) b; } + }; + + struct RightShiftUnsignedOp : public BinaryOperator + { + RightShiftUnsignedOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperator (l, a, b, TokenTypes::rightShiftUnsigned) {} + var getWithInts (int64 a, int64 b) const override { return (int) (((uint32) a) >> (int) b); } + }; + + struct LogicalAndOp : public BinaryOperatorBase + { + LogicalAndOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperatorBase (l, a, b, TokenTypes::logicalAnd) {} + var getResult (const Scope& s) const override { return lhs->getResult (s) && rhs->getResult (s); } + }; + + struct LogicalOrOp : public BinaryOperatorBase + { + LogicalOrOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperatorBase (l, a, b, TokenTypes::logicalOr) {} + var getResult (const Scope& s) const override { return lhs->getResult (s) || rhs->getResult (s); } + }; + + struct TypeEqualsOp : public BinaryOperatorBase + { + TypeEqualsOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperatorBase (l, a, b, TokenTypes::typeEquals) {} + var getResult (const Scope& s) const override { return areTypeEqual (lhs->getResult (s), rhs->getResult (s)); } + }; + + struct TypeNotEqualsOp : public BinaryOperatorBase + { + TypeNotEqualsOp (const CodeLocation& l, ExpPtr a, ExpPtr b) noexcept : BinaryOperatorBase (l, a, b, TokenTypes::typeNotEquals) {} + var getResult (const Scope& s) const override { return ! areTypeEqual (lhs->getResult (s), rhs->getResult (s)); } + }; + + struct ConditionalOp : public Expression + { + ConditionalOp (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override { return (condition->getResult (s) ? trueBranch : falseBranch)->getResult (s); } + void assign (const Scope& s, const var& v) const override { (condition->getResult (s) ? trueBranch : falseBranch)->assign (s, v); } + + ExpPtr condition, trueBranch, falseBranch; + }; + + struct Assignment : public Expression + { + Assignment (const CodeLocation& l, ExpPtr dest, ExpPtr source) noexcept : Expression (l), target (dest), newValue (source) {} + + var getResult (const Scope& s) const override + { + var value (newValue->getResult (s)); + target->assign (s, value); + return value; + } + + ExpPtr target, newValue; + }; + + struct SelfAssignment : public Expression + { + SelfAssignment (const CodeLocation& l, Expression* dest, ExpPtr source) noexcept + : Expression (l), target (dest), newValue (source) {} + + var getResult (const Scope& s) const override + { + var value (newValue->getResult (s)); + target->assign (s, value); + return value; + } + + Expression* target; // Careful! this pointer aliases a sub-term of newValue! + ExpPtr newValue; + TokenType op; + }; + + struct PostAssignment : public SelfAssignment + { + PostAssignment (const CodeLocation& l, Expression* dest, ExpPtr source) noexcept : SelfAssignment (l, dest, source) {} + + var getResult (const Scope& s) const override + { + var oldValue (target->getResult (s)); + target->assign (s, newValue->getResult (s)); + return oldValue; + } + }; + + struct FunctionCall : public Expression + { + FunctionCall (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override + { + var function (object->getResult (s)); + + if (DotOperator* dot = dynamic_cast (object.get())) + { + var thisObject = dot->parent->getResult (s); + return invokeFunction (s, s.findFunctionCall (location, thisObject, dot->child), thisObject); + } + + return invokeFunction (s, function, var (s.scope)); + } + + var invokeFunction (const Scope& s, const var& function, const var& thisObject) const + { + if (var::NativeFunction nativeFunction = function.getNativeFunction()) + { + Array args; + + for (int i = 0; i < arguments.size(); ++i) + args.add (arguments.getUnchecked(i)->getResult (s)); + + return nativeFunction (var::NativeFunctionArgs (thisObject, args.getRawDataPointer(), args.size())); + } + + var result; + + if (FunctionObject* fo = dynamic_cast (function.getObject())) + { + s.checkTimeOut (location); + DynamicObject::Ptr functionRoot (new DynamicObject()); + + static const Identifier thisIdent ("this"); + functionRoot->setProperty (thisIdent, thisObject); + + for (int i = 0; i < jmin (fo->parameters.size(), arguments.size()); ++i) + functionRoot->setProperty (fo->parameters[i], arguments.getUnchecked(i)->getResult (s)); + + fo->body->perform (Scope (&s, s.root, functionRoot), &result); + } + else + location.throwError ("This expression is not a function!"); + + return result; + } + + ExpPtr object; + OwnedArray arguments; + }; + + struct NewOperator : public FunctionCall + { + NewOperator (const CodeLocation& l) noexcept : FunctionCall (l) {} + + var getResult (const Scope& s) const override + { + var classOrFunc = object->getResult (s); + + const bool isFunc = isFunction (classOrFunc); + if (! (isFunc || classOrFunc.getDynamicObject() != nullptr)) + return var::undefined(); + + DynamicObject::Ptr newObject (new DynamicObject()); + + if (isFunc) + invokeFunction (s, classOrFunc, newObject.get()); + else + newObject->setProperty (getPrototypeIdentifier(), classOrFunc); + + return newObject.get(); + } + }; + + struct ObjectDeclaration : public Expression + { + ObjectDeclaration (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override + { + DynamicObject::Ptr newObject (new DynamicObject()); + + for (int i = 0; i < names.size(); ++i) + newObject->setProperty (names.getUnchecked(i), initialisers.getUnchecked(i)->getResult (s)); + + return newObject.get(); + } + + Array names; + OwnedArray initialisers; + }; + + struct ArrayDeclaration : public Expression + { + ArrayDeclaration (const CodeLocation& l) noexcept : Expression (l) {} + + var getResult (const Scope& s) const override + { + Array a; + + for (int i = 0; i < values.size(); ++i) + a.add (values.getUnchecked(i)->getResult (s)); + + return a; + } + + OwnedArray values; + }; + + //============================================================================== + struct FunctionObject : public DynamicObject + { + FunctionObject() noexcept {} + + FunctionObject (const FunctionObject& other) : functionCode (other.functionCode) + { + ExpressionTreeBuilder tb (functionCode); + tb.parseFunctionParamsAndBody (*this); + } + + DynamicObject::Ptr clone() override { return new FunctionObject (*this); } + + void writeAsJSON (OutputStream& out, int /*indentLevel*/, bool /*allOnOneLine*/) override + { + out << "function" << functionCode; + } + + String functionCode; + Array parameters; + ScopedPointer body; + }; + + //============================================================================== + struct TokenIterator + { + TokenIterator (const String& code) : location (code), p (code.getCharPointer()) { skip(); } + + void skip() + { + skipWhitespaceAndComments(); + location.location = p; + currentType = matchNextToken(); + } + + void match (TokenType expected) + { + if (currentType != expected) + location.throwError ("Found " + getTokenName (currentType) + " when expecting " + getTokenName (expected)); + + skip(); + } + + bool matchIf (TokenType expected) { if (currentType == expected) { skip(); return true; } return false; } + bool matchesAny (TokenType t1, TokenType t2) const { return currentType == t1 || currentType == t2; } + bool matchesAny (TokenType t1, TokenType t2, TokenType t3) const { return matchesAny (t1, t2) || currentType == t3; } + + CodeLocation location; + TokenType currentType; + var currentValue; + + private: + String::CharPointerType p; + + static bool isIdentifierStart (const juce_wchar c) noexcept { return CharacterFunctions::isLetter (c) || c == '_'; } + static bool isIdentifierBody (const juce_wchar c) noexcept { return CharacterFunctions::isLetterOrDigit (c) || c == '_'; } + + TokenType matchNextToken() + { + if (isIdentifierStart (*p)) + { + String::CharPointerType end (p); + while (isIdentifierBody (*++end)) {} + + const size_t len = end - p; + #define JUCE_JS_COMPARE_KEYWORD(name, str) if (len == sizeof (str) - 1 && matchToken (TokenTypes::name, len)) return TokenTypes::name; + JUCE_JS_KEYWORDS (JUCE_JS_COMPARE_KEYWORD) + + currentValue = String (p, end); p = end; + return TokenTypes::identifier; + } + + if (p.isDigit()) + { + if (parseHexLiteral() || parseFloatLiteral() || parseOctalLiteral() || parseDecimalLiteral()) + return TokenTypes::literal; + + location.throwError ("Syntax error in numeric constant"); + } + + if (parseStringLiteral (*p) || (*p == '.' && parseFloatLiteral())) + return TokenTypes::literal; + + #define JUCE_JS_COMPARE_OPERATOR(name, str) if (matchToken (TokenTypes::name, sizeof (str) - 1)) return TokenTypes::name; + JUCE_JS_OPERATORS (JUCE_JS_COMPARE_OPERATOR) + + if (! p.isEmpty()) + location.throwError ("Unexpected character '" + String::charToString (*p) + "' in source"); + + return TokenTypes::eof; + } + + bool matchToken (TokenType name, const size_t len) noexcept + { + if (p.compareUpTo (String::CharPointerType (name), len) != 0) return false; + p += len; return true; + } + + void skipWhitespaceAndComments() + { + for (;;) + { + p = p.findEndOfWhitespace(); + + if (*p == '/') + { + const juce_wchar c2 = p[1]; + + if (c2 == '/') { p = CharacterFunctions::find (p, (juce_wchar) '\n'); continue; } + + if (c2 == '*') + { + location.location = p; + p = CharacterFunctions::find (p + 2, CharPointer_ASCII ("*/")); + if (p.isEmpty()) location.throwError ("Unterminated '/*' comment"); + p += 2; continue; + } + } + + break; + } + } + + bool parseStringLiteral (juce_wchar quoteType) + { + if (quoteType != '"' && quoteType != '\'') + return false; + + Result r (JSON::parseQuotedString (p, currentValue)); + if (r.failed()) location.throwError (r.getErrorMessage()); + return true; + } + + bool parseHexLiteral() + { + if (*p != '0' || (p[1] != 'x' && p[1] != 'X')) return false; + + String::CharPointerType t (++p); + int64 v = CharacterFunctions::getHexDigitValue (*++t); + if (v < 0) return false; + + for (;;) + { + const int digit = CharacterFunctions::getHexDigitValue (*++t); + if (digit < 0) break; + v = v * 16 + digit; + } + + currentValue = v; p = t; + return true; + } + + bool parseFloatLiteral() + { + int numDigits = 0; + String::CharPointerType t (p); + while (t.isDigit()) { ++t; ++numDigits; } + + const bool hasPoint = (*t == '.'); + + if (hasPoint) + while ((++t).isDigit()) ++numDigits; + + if (numDigits == 0) + return false; + + juce_wchar c = *t; + const bool hasExponent = (c == 'e' || c == 'E'); + + if (hasExponent) + { + c = *++t; + if (c == '+' || c == '-') ++t; + if (! t.isDigit()) return false; + while ((++t).isDigit()) {} + } + + if (! (hasExponent || hasPoint)) return false; + + currentValue = CharacterFunctions::getDoubleValue (p); p = t; + return true; + } + + bool parseOctalLiteral() + { + String::CharPointerType t (p); + int64 v = *t - '0'; + if (v != 0) return false; // first digit of octal must be 0 + + for (;;) + { + const int digit = (int) (*++t - '0'); + if (isPositiveAndBelow (digit, 8)) v = v * 8 + digit; + else if (isPositiveAndBelow (digit, 10)) location.throwError ("Decimal digit in octal constant"); + else break; + } + + currentValue = v; p = t; + return true; + } + + bool parseDecimalLiteral() + { + int64 v = 0; + + for (;; ++p) + { + const int digit = (int) (*p - '0'); + if (isPositiveAndBelow (digit, 10)) v = v * 10 + digit; + else break; + } + + currentValue = v; + return true; + } + }; + + //============================================================================== + struct ExpressionTreeBuilder : private TokenIterator + { + ExpressionTreeBuilder (const String code) : TokenIterator (code) {} + + BlockStatement* parseStatementList() + { + ScopedPointer b (new BlockStatement (location)); + + while (currentType != TokenTypes::closeBrace && currentType != TokenTypes::eof) + b->statements.add (parseStatement()); + + return b.release(); + } + + void parseFunctionParamsAndBody (FunctionObject& fo) + { + match (TokenTypes::openParen); + + while (currentType != TokenTypes::closeParen) + { + fo.parameters.add (currentValue.toString()); + match (TokenTypes::identifier); + + if (currentType != TokenTypes::closeParen) + match (TokenTypes::comma); + } + + match (TokenTypes::closeParen); + fo.body = parseBlock(); + } + + Expression* parseExpression() + { + ExpPtr lhs (parseLogicOperator()); + + if (matchIf (TokenTypes::question)) return parseTerneryOperator (lhs); + if (matchIf (TokenTypes::assign)) return new Assignment (location, lhs, parseExpression()); + if (matchIf (TokenTypes::plusEquals)) return parseInPlaceOpExpression (lhs); + if (matchIf (TokenTypes::minusEquals)) return parseInPlaceOpExpression (lhs); + if (matchIf (TokenTypes::leftShiftEquals)) return parseInPlaceOpExpression (lhs); + if (matchIf (TokenTypes::rightShiftEquals)) return parseInPlaceOpExpression (lhs); + + return lhs.release(); + } + + private: + void throwError (const String& err) const { location.throwError (err); } + + template + Expression* parseInPlaceOpExpression (ExpPtr lhs) + { + ExpPtr rhs (parseExpression()); + Expression* bareLHS = lhs.release(); // careful - bare pointer is deliberately alised + return new SelfAssignment (location, bareLHS, new OpType (location, bareLHS, rhs)); + } + + BlockStatement* parseBlock() + { + match (TokenTypes::openBrace); + ScopedPointer b (parseStatementList()); + match (TokenTypes::closeBrace); + return b.release(); + } + + Statement* parseStatement() + { + if (currentType == TokenTypes::openBrace) return parseBlock(); + if (matchIf (TokenTypes::var)) return parseVar(); + if (matchIf (TokenTypes::if_)) return parseIf(); + if (matchIf (TokenTypes::while_)) return parseDoOrWhileLoop (false); + if (matchIf (TokenTypes::do_)) return parseDoOrWhileLoop (true); + if (matchIf (TokenTypes::for_)) return parseForLoop(); + if (matchIf (TokenTypes::return_)) return new ReturnStatement (location, matchIf (TokenTypes::semicolon) ? new Expression (location) : parseExpression()); + if (matchIf (TokenTypes::break_)) return new BreakStatement (location); + if (matchIf (TokenTypes::continue_)) return new ContinueStatement (location); + if (matchIf (TokenTypes::function)) return parseFunction(); + if (matchIf (TokenTypes::semicolon)) return new Statement (location); + if (matchIf (TokenTypes::plusplus)) return parsePreIncDec(); + if (matchIf (TokenTypes::minusminus)) return parsePreIncDec(); + + if (matchesAny (TokenTypes::openParen, TokenTypes::openBracket)) + return matchEndOfStatement (parseFactor()); + + if (matchesAny (TokenTypes::identifier, TokenTypes::literal, TokenTypes::minus)) + return matchEndOfStatement (parseExpression()); + + throwError ("Found " + getTokenName (currentType) + " when expecting a statement"); + return nullptr; + } + + Expression* matchEndOfStatement (ExpPtr e) { if (currentType != TokenTypes::eof) match (TokenTypes::semicolon); return e.release(); } + Expression* matchCloseParen (ExpPtr e) { match (TokenTypes::closeParen); return e.release(); } + + Statement* parseIf() + { + ScopedPointer s (new IfStatement (location)); + match (TokenTypes::openParen); + s->condition = parseExpression(); + match (TokenTypes::closeParen); + s->trueBranch = parseStatement(); + s->falseBranch = matchIf (TokenTypes::else_) ? parseStatement() : new Statement (location); + return s.release(); + } + + Statement* parseVar() + { + ScopedPointer s (new VarStatement (location)); + s->name = parseIdentifier(); + s->initialiser = matchIf (TokenTypes::assign) ? parseExpression() : new Expression (location); + + if (matchIf (TokenTypes::comma)) + { + ScopedPointer block (new BlockStatement (location)); + block->statements.add (s.release()); + block->statements.add (parseVar()); + return block.release(); + } + + match (TokenTypes::semicolon); + return s.release(); + } + + Statement* parseFunction() + { + Identifier name; + var fn = parseFunctionDefinition (name); + + if (name.isNull()) + throwError ("Functions defined at statement-level must have a name"); + + return new Assignment (location, new UnqualifiedName (location, name), new LiteralValue (location, fn)); + } + + Statement* parseForLoop() + { + ScopedPointer s (new LoopStatement (location, false)); + match (TokenTypes::openParen); + s->initialiser = parseStatement(); + + if (matchIf (TokenTypes::semicolon)) + s->condition = new LiteralValue (location, true); + else + { + s->condition = parseExpression(); + match (TokenTypes::semicolon); + } + + s->iterator = parseExpression(); + match (TokenTypes::closeParen); + s->body = parseStatement(); + return s.release(); + } + + Statement* parseDoOrWhileLoop (bool isDoLoop) + { + ScopedPointer s (new LoopStatement (location, isDoLoop)); + s->initialiser = new Statement (location); + s->iterator = new Statement (location); + + if (isDoLoop) + { + s->body = parseBlock(); + match (TokenTypes::while_); + } + + match (TokenTypes::openParen); + s->condition = parseExpression(); + match (TokenTypes::closeParen); + + if (! isDoLoop) + s->body = parseStatement(); + + return s.release(); + } + + Identifier parseIdentifier() + { + Identifier i; + if (currentType == TokenTypes::identifier) + i = currentValue.toString(); + + match (TokenTypes::identifier); + return i; + } + + var parseFunctionDefinition (Identifier& functionName) + { + const String::CharPointerType functionStart (location.location); + + if (currentType == TokenTypes::identifier) + functionName = parseIdentifier(); + + ScopedPointer fo (new FunctionObject()); + parseFunctionParamsAndBody (*fo); + fo->functionCode = String (functionStart, location.location); + return var (fo.release()); + } + + Expression* parseFunctionCall (ScopedPointer s, ExpPtr function) + { + s->object = function; + match (TokenTypes::openParen); + + while (currentType != TokenTypes::closeParen) + { + s->arguments.add (parseExpression()); + if (currentType != TokenTypes::closeParen) + match (TokenTypes::comma); + } + + return matchCloseParen (s.release()); + } + + Expression* parseSuffixes (ExpPtr input) + { + if (matchIf (TokenTypes::dot)) + return parseSuffixes (new DotOperator (location, input, parseIdentifier())); + + if (currentType == TokenTypes::openParen) + return parseSuffixes (parseFunctionCall (new FunctionCall (location), input)); + + if (matchIf (TokenTypes::openBracket)) + { + ScopedPointer s (new ArraySubscript (location)); + s->object = input; + s->index = parseExpression(); + match (TokenTypes::closeBracket); + return parseSuffixes (s.release()); + } + + return input.release(); + } + + Expression* parseFactor() + { + if (currentType == TokenTypes::identifier) return parseSuffixes (new UnqualifiedName (location, parseIdentifier())); + if (matchIf (TokenTypes::openParen)) return parseSuffixes (matchCloseParen (parseExpression())); + if (matchIf (TokenTypes::true_)) return parseSuffixes (new LiteralValue (location, (int) 1)); + if (matchIf (TokenTypes::false_)) return parseSuffixes (new LiteralValue (location, (int) 0)); + if (matchIf (TokenTypes::null_)) return parseSuffixes (new LiteralValue (location, var::null)); + if (matchIf (TokenTypes::undefined)) return parseSuffixes (new Expression (location)); + + if (currentType == TokenTypes::literal) + { + var v (currentValue); skip(); + return parseSuffixes (new LiteralValue (location, v)); + } + + if (matchIf (TokenTypes::openBrace)) + { + ScopedPointer e (new ObjectDeclaration (location)); + + while (currentType != TokenTypes::closeBrace) + { + e->names.add (currentValue.toString()); + match ((currentType == TokenTypes::literal && currentValue.isString()) + ? TokenTypes::literal : TokenTypes::identifier); + match (TokenTypes::colon); + e->initialisers.add (parseExpression()); + + if (currentType != TokenTypes::closeBrace) + match (TokenTypes::comma); + } + + match (TokenTypes::closeBrace); + return parseSuffixes (e.release()); + } + + if (matchIf (TokenTypes::openBracket)) + { + ScopedPointer e (new ArrayDeclaration (location)); + + while (currentType != TokenTypes::closeBracket) + { + e->values.add (parseExpression()); + + if (currentType != TokenTypes::closeBracket) + match (TokenTypes::comma); + } + + match (TokenTypes::closeBracket); + return parseSuffixes (e.release()); + } + + if (matchIf (TokenTypes::function)) + { + Identifier name; + var fn = parseFunctionDefinition (name); + + if (name.isValid()) + throwError ("Inline functions definitions cannot have a name"); + + return new LiteralValue (location, fn); + } + + if (matchIf (TokenTypes::new_)) + { + ExpPtr name (new UnqualifiedName (location, parseIdentifier())); + + while (matchIf (TokenTypes::dot)) + name = new DotOperator (location, name, parseIdentifier()); + + return parseFunctionCall (new NewOperator (location), name); + } + + throwError ("Found " + getTokenName (currentType) + " when expecting an expression"); + return nullptr; + } + + template + Expression* parsePreIncDec() + { + Expression* e = parseFactor(); // careful - bare pointer is deliberately alised + return new SelfAssignment (location, e, new OpType (location, e, new LiteralValue (location, (int) 1))); + } + + template + Expression* parsePostIncDec (ExpPtr lhs) + { + Expression* e = lhs.release(); // careful - bare pointer is deliberately alised + return new PostAssignment (location, e, new OpType (location, e, new LiteralValue (location, (int) 1))); + } + + Expression* parseUnary() + { + if (matchIf (TokenTypes::minus)) return new SubtractionOp (location, new LiteralValue (location, (int) 0), parseUnary()); + if (matchIf (TokenTypes::logicalNot)) return new EqualsOp (location, new LiteralValue (location, (int) 0), parseUnary()); + if (matchIf (TokenTypes::plusplus)) return parsePreIncDec(); + if (matchIf (TokenTypes::minusminus)) return parsePreIncDec(); + + return parseFactor(); + } + + Expression* parseMultiplyDivide() + { + ExpPtr a (parseUnary()); + + for (;;) + { + if (matchIf (TokenTypes::times)) a = new MultiplyOp (location, a, parseUnary()); + else if (matchIf (TokenTypes::divide)) a = new DivideOp (location, a, parseUnary()); + else if (matchIf (TokenTypes::modulo)) a = new ModuloOp (location, a, parseUnary()); + else break; + } + + return a.release(); + } + + Expression* parseAdditionSubtraction() + { + ExpPtr a (parseMultiplyDivide()); + + for (;;) + { + if (matchIf (TokenTypes::plus)) a = new AdditionOp (location, a, parseMultiplyDivide()); + else if (matchIf (TokenTypes::minus)) a = new SubtractionOp (location, a, parseMultiplyDivide()); + else if (matchIf (TokenTypes::plusplus)) a = parsePostIncDec (a); + else if (matchIf (TokenTypes::minusminus)) a = parsePostIncDec (a); + else break; + } + + return a.release(); + } + + Expression* parseShiftOperator() + { + ExpPtr a (parseAdditionSubtraction()); + + for (;;) + { + if (matchIf (TokenTypes::leftShift)) a = new LeftShiftOp (location, a, parseExpression()); + else if (matchIf (TokenTypes::rightShift)) a = new RightShiftOp (location, a, parseExpression()); + else if (matchIf (TokenTypes::rightShiftUnsigned)) a = new RightShiftUnsignedOp (location, a, parseExpression()); + else break; + } + + return a.release(); + } + + Expression* parseComparator() + { + ExpPtr a (parseShiftOperator()); + + for (;;) + { + if (matchIf (TokenTypes::equals)) a = new EqualsOp (location, a, parseShiftOperator()); + else if (matchIf (TokenTypes::notEquals)) a = new NotEqualsOp (location, a, parseShiftOperator()); + else if (matchIf (TokenTypes::typeEquals)) a = new TypeEqualsOp (location, a, parseShiftOperator()); + else if (matchIf (TokenTypes::typeNotEquals)) a = new TypeNotEqualsOp (location, a, parseShiftOperator()); + else if (matchIf (TokenTypes::lessThan)) a = new LessThanOp (location, a, parseShiftOperator()); + else if (matchIf (TokenTypes::lessThanOrEqual)) a = new LessThanOrEqualOp (location, a, parseShiftOperator()); + else if (matchIf (TokenTypes::greaterThan)) a = new GreaterThanOp (location, a, parseShiftOperator()); + else if (matchIf (TokenTypes::greaterThanOrEqual)) a = new GreaterThanOrEqualOp (location, a, parseShiftOperator()); + else break; + } + + return a.release(); + } + + Expression* parseLogicOperator() + { + ExpPtr a (parseComparator()); + + for (;;) + { + if (matchIf (TokenTypes::logicalAnd)) a = new LogicalAndOp (location, a, parseComparator()); + else if (matchIf (TokenTypes::logicalOr)) a = new LogicalOrOp (location, a, parseComparator()); + else if (matchIf (TokenTypes::bitwiseAnd)) a = new BitwiseAndOp (location, a, parseComparator()); + else if (matchIf (TokenTypes::bitwiseOr)) a = new BitwiseOrOp (location, a, parseComparator()); + else if (matchIf (TokenTypes::bitwiseXor)) a = new BitwiseXorOp (location, a, parseComparator()); + else break; + } + + return a.release(); + } + + Expression* parseTerneryOperator (ExpPtr condition) + { + ScopedPointer e (new ConditionalOp (location)); + e->condition = condition; + e->trueBranch = parseExpression(); + match (TokenTypes::colon); + e->falseBranch = parseExpression(); + return e.release(); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExpressionTreeBuilder) + }; + + //============================================================================== + static var get (Args a, int index) noexcept { return index < a.numArguments ? a.arguments[index] : var(); } + static bool isInt (Args a, int index) noexcept { return get (a, index).isInt() || get (a, index).isInt64(); } + static int getInt (Args a, int index) noexcept { return get (a, index); } + static double getDouble (Args a, int index) noexcept { return get (a, index); } + static String getString (Args a, int index) noexcept { return get (a, index).toString(); } + + //============================================================================== + struct ObjectClass : public DynamicObject + { + ObjectClass() + { + setMethod ("dump", dump); + setMethod ("clone", clone); + } + + static Identifier getClassName() { static const Identifier i ("Object"); return i; } + static var dump (Args a) { DBG (JSON::toString (a.thisObject)); return var::undefined(); } + static var clone (Args a) { return a.thisObject.clone(); } + }; + + //============================================================================== + struct ArrayClass : public DynamicObject + { + ArrayClass() + { + setMethod ("contains", contains); + setMethod ("remove", remove); + setMethod ("join", join); + } + + static Identifier getClassName() { static const Identifier i ("Array"); return i; } + + static var contains (Args a) + { + if (const Array* array = a.thisObject.getArray()) + return array->contains (get (a, 0)); + + return false; + } + + static var remove (Args a) + { + if (Array* array = a.thisObject.getArray()) + array->removeAllInstancesOf (get (a, 0)); + + return var::undefined(); + } + + static var join (Args a) + { + StringArray strings; + + if (const Array* array = a.thisObject.getArray()) + for (int i = 0; i < array->size(); ++i) + strings.add (array->getReference(i).toString()); + + return strings.joinIntoString (getString (a, 0)); + } + }; + + //============================================================================== + struct StringClass : public DynamicObject + { + StringClass() + { + setMethod ("substring", substring); + setMethod ("indexOf", indexOf); + setMethod ("charAt", charAt); + setMethod ("charCodeAt", charCodeAt); + setMethod ("fromCharCode", fromCharCode); + setMethod ("split", split); + } + + static Identifier getClassName() { static const Identifier i ("String"); return i; } + + static var fromCharCode (Args a) { return String::charToString (getInt (a, 0)); } + static var substring (Args a) { return a.thisObject.toString().substring (getInt (a, 0), getInt (a, 1)); } + static var indexOf (Args a) { return a.thisObject.toString().indexOf (getString (a, 0)); } + static var charCodeAt (Args a) { return (int) a.thisObject.toString() [getInt (a, 0)]; } + static var charAt (Args a) { int p = getInt (a, 0); return a.thisObject.toString().substring (p, p + 1); } + + static var split (Args a) + { + const String str (a.thisObject.toString()); + const String sep (getString (a, 0)); + StringArray strings; + + if (sep.isNotEmpty()) + strings.addTokens (str, sep.substring (0, 1), ""); + else // special-case for empty separator: split all chars separately + for (String::CharPointerType pos = str.getCharPointer(); ! pos.isEmpty(); ++pos) + strings.add (String::charToString (*pos)); + + var array; + for (int i = 0; i < strings.size(); ++i) + array.append (strings[i]); + + return array; + } + }; + + //============================================================================== + struct MathClass : public DynamicObject + { + MathClass() + { + setMethod ("abs", Math_abs); setMethod ("round", Math_round); + setMethod ("random", Math_random); setMethod ("randInt", Math_randInt); + setMethod ("min", Math_min); setMethod ("max", Math_max); + setMethod ("range", Math_range); setMethod ("sign", Math_sign); + setMethod ("PI", Math_pi); setMethod ("E", Math_e); + setMethod ("toDegrees", Math_toDegrees); setMethod ("toRadians", Math_toRadians); + setMethod ("sin", Math_sin); setMethod ("asin", Math_asin); + setMethod ("sinh", Math_sinh); setMethod ("asinh", Math_asinh); + setMethod ("cos", Math_cos); setMethod ("acos", Math_acos); + setMethod ("cosh", Math_cosh); setMethod ("acosh", Math_acosh); + setMethod ("tan", Math_tan); setMethod ("atan", Math_atan); + setMethod ("tanh", Math_tanh); setMethod ("atanh", Math_atanh); + setMethod ("log", Math_log); setMethod ("log10", Math_log10); + setMethod ("exp", Math_exp); setMethod ("pow", Math_pow); + setMethod ("sqr", Math_sqr); setMethod ("sqrt", Math_sqrt); + } + + static var Math_pi (Args) { return double_Pi; } + static var Math_e (Args) { return exp (1.0); } + static var Math_random (Args) { return Random::getSystemRandom().nextDouble(); } + static var Math_randInt (Args a) { return Random::getSystemRandom().nextInt (Range (getInt (a, 0), getInt (a, 1))); } + static var Math_abs (Args a) { return isInt (a, 0) ? var (std::abs (getInt (a, 0))) : var (std::abs (getDouble (a, 0))); } + static var Math_round (Args a) { return isInt (a, 0) ? var (roundToInt (getInt (a, 0))) : var (roundToInt (getDouble (a, 0))); } + static var Math_sign (Args a) { return isInt (a, 0) ? var (sign (getInt (a, 0))) : var (sign (getDouble (a, 0))); } + static var Math_range (Args a) { return isInt (a, 0) ? var (jlimit (getInt (a, 1), getInt (a, 2), getInt (a, 0))) : var (jlimit (getDouble (a, 1), getDouble (a, 2), getDouble (a, 0))); } + static var Math_min (Args a) { return (isInt (a, 0) && isInt (a, 1)) ? var (jmin (getInt (a, 0), getInt (a, 1))) : var (jmin (getDouble (a, 0), getDouble (a, 1))); } + static var Math_max (Args a) { return (isInt (a, 0) && isInt (a, 1)) ? var (jmax (getInt (a, 0), getInt (a, 1))) : var (jmax (getDouble (a, 0), getDouble (a, 1))); } + static var Math_toDegrees (Args a) { return (180.0 / double_Pi) * getDouble (a, 0); } + static var Math_toRadians (Args a) { return (double_Pi / 180.0) * getDouble (a, 0); } + static var Math_sin (Args a) { return sin (getDouble (a, 0)); } + static var Math_asin (Args a) { return asin (getDouble (a, 0)); } + static var Math_cos (Args a) { return cos (getDouble (a, 0)); } + static var Math_acos (Args a) { return acos (getDouble (a, 0)); } + static var Math_sinh (Args a) { return sinh (getDouble (a, 0)); } + static var Math_asinh (Args a) { return asinh (getDouble (a, 0)); } + static var Math_cosh (Args a) { return cosh (getDouble (a, 0)); } + static var Math_acosh (Args a) { return acosh (getDouble (a, 0)); } + static var Math_tan (Args a) { return tan (getDouble (a, 0)); } + static var Math_tanh (Args a) { return tanh (getDouble (a, 0)); } + static var Math_atan (Args a) { return atan (getDouble (a, 0)); } + static var Math_atanh (Args a) { return atanh (getDouble (a, 0)); } + static var Math_log (Args a) { return log (getDouble (a, 0)); } + static var Math_log10 (Args a) { return log10 (getDouble (a, 0)); } + static var Math_exp (Args a) { return exp (getDouble (a, 0)); } + static var Math_pow (Args a) { return pow (getDouble (a, 0), getDouble (a, 1)); } + static var Math_sqr (Args a) { double x = getDouble (a, 0); return x * x; } + static var Math_sqrt (Args a) { return std::sqrt (getDouble (a, 0)); } + + static Identifier getClassName() { static const Identifier i ("Math"); return i; } + template static Type sign (Type n) noexcept { return n > 0 ? (Type) 1 : (n < 0 ? (Type) -1 : 0); } + }; + + //============================================================================== + struct JSONClass : public DynamicObject + { + JSONClass() { setMethod ("stringify", stringify); } + static Identifier getClassName() { static const Identifier i ("JSON"); return i; } + static var stringify (Args a) { return JSON::toString (get (a, 0)); } + }; + + //============================================================================== + struct IntegerClass : public DynamicObject + { + IntegerClass() { setMethod ("parseInt", parseInt); } + static Identifier getClassName() { static const Identifier i ("Integer"); return i; } + + static var parseInt (Args a) + { + const String s (getString (a, 0).trim()); + + return s[0] == '0' ? (s[1] == 'x' ? s.substring(2).getHexValue64() : getOctalValue (s)) + : s.getLargeIntValue(); + } + }; + + //============================================================================== + static var trace (Args a) { Logger::outputDebugString (JSON::toString (a.thisObject)); return var::undefined(); } + static var charToInt (Args a) { return (int) (getString (a, 0)[0]); } + + static var exec (Args a) + { + if (RootObject* root = dynamic_cast (a.thisObject.getObject())) + root->execute (getString (a, 0)); + + return var::undefined(); + } + + static var eval (Args a) + { + if (RootObject* root = dynamic_cast (a.thisObject.getObject())) + return root->evaluate (getString (a, 0)); + + return var::undefined(); + } +}; + +//============================================================================== +JavascriptEngine::JavascriptEngine() : root (new RootObject()) +{ + registerNativeObject (RootObject::ObjectClass ::getClassName(), new RootObject::ObjectClass()); + registerNativeObject (RootObject::ArrayClass ::getClassName(), new RootObject::ArrayClass()); + registerNativeObject (RootObject::StringClass ::getClassName(), new RootObject::StringClass()); + registerNativeObject (RootObject::MathClass ::getClassName(), new RootObject::MathClass()); + registerNativeObject (RootObject::JSONClass ::getClassName(), new RootObject::JSONClass()); + registerNativeObject (RootObject::IntegerClass ::getClassName(), new RootObject::IntegerClass()); +} + +JavascriptEngine::~JavascriptEngine() {} + +Result JavascriptEngine::execute (const String& code, RelativeTime maximumRunTime) +{ + try + { + root->timeout = Time::getCurrentTime() + maximumRunTime; + root->execute (code); + } + catch (String& error) + { + return Result::fail (error); + } + + return Result::ok(); +} + +var JavascriptEngine::evaluate (const String& code, Result* message, RelativeTime maximumRunTime) +{ + try + { + if (message != nullptr) *message = Result::ok(); + root->timeout = Time::getCurrentTime() + maximumRunTime; + return root->evaluate (code); + } + catch (String& error) + { + if (message != nullptr) *message = Result::fail (error); + } + + return var::undefined(); +} + +void JavascriptEngine::registerNativeObject (Identifier name, DynamicObject* object) +{ + root->setProperty (name, object); +} diff --git a/modules/juce_core/javascript/juce_Javascript.h b/modules/juce_core/javascript/juce_Javascript.h new file mode 100644 index 0000000000..1ccfc941ef --- /dev/null +++ b/modules/juce_core/javascript/juce_Javascript.h @@ -0,0 +1,92 @@ +/* + ============================================================================== + + This file is part of the juce_core module of the JUCE library. + Copyright (c) 2013 - Raw Material Software Ltd. + + Permission to use, copy, modify, and/or distribute this software for any purpose with + or without fee is hereby granted, provided that the above copyright notice and this + permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ------------------------------------------------------------------------------ + + NOTE! This permissive ISC license applies ONLY to files within the juce_core module! + All other JUCE modules are covered by a dual GPL/commercial license, so if you are + using any other modules, be sure to check that you also comply with their license. + + For more details, visit www.juce.com + + ============================================================================== +*/ + + +/** + A simple javascript interpreter! + + It's not fully standards-compliant, and won't be as fast as the fancy JIT-compiled + engines that you get in browsers, but this is an extremely compact, low-overhead javascript + interpreter, which is integrated with the juce var and DynamicObject classes. If you need + a few simple bits of scripting in your app, and want to be able to easily let the JS + work with native objects defined as DynamicObject subclasses, then this might do the job. + + To use, simply create an instance of this class and call execute() to run your code. + Variables that the script sets can be retrieved with evaluate(), and if you need to provide + native objects for the script to use, you can add them with registerNativeObject(). + + One caveat: Because the values and objects that the engine works with are DynamicObject + and var objects, they use reference-counting rather than garbage-collection, so if your + script creates complex connections between objects, you run the risk of creating cyclic + dependencies and hence leaking. +*/ +class JavascriptEngine +{ +public: + /** Creates an instance of the engine. + This creates a root namespace and defines some basic Object, String, Array + and Math library methods. + */ + JavascriptEngine(); + + /** Destructor. */ + ~JavascriptEngine(); + + /** Attempts to parse and run a block of javascript code. + If there's a parse or execution error, the error description is returned in + the result. + You can specify a maximum time for which the program is allowed to run, and + it'll return with an error message if this time is exceeded. + */ + Result execute (const String& javascriptCode, + RelativeTime maximumRunTime = RelativeTime::seconds (10)); + + /** Attempts to parse and run a javascript expression, and returns the result. + If there's a syntax error, or the expression can't be evaluated, the return value + will be var::undefined(). The errorMessage parameter gives you a way to find out + any parsing errors. + You can specify a maximum time for which the program is allowed to run, and + it'll return with an error message if this time is exceeded. + */ + var evaluate (const String& javascriptCode, + Result* errorMessage = nullptr, + RelativeTime maximumRunTime = RelativeTime::seconds (10)); + + /** Adds a native object to the root namespace. + The object passed-in is reference-counted, and will be retained by the + engine until the engine is deleted. The name must be a simple JS identifier, + without any dots. + */ + void registerNativeObject (Identifier objectName, DynamicObject* object); + +private: + struct RootObject; + ReferenceCountedObjectPtr root; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JavascriptEngine) +}; diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index fa7c19c0da..2fca180a98 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -119,7 +119,8 @@ namespace juce #include "files/juce_FileOutputStream.cpp" #include "files/juce_FileSearchPath.cpp" #include "files/juce_TemporaryFile.cpp" -#include "json/juce_JSON.cpp" +#include "javascript/juce_JSON.cpp" +#include "javascript/juce_Javascript.cpp" #include "containers/juce_DynamicObject.cpp" #include "logging/juce_FileLogger.cpp" #include "logging/juce_Logger.cpp" diff --git a/modules/juce_core/juce_core.h b/modules/juce_core/juce_core.h index 3638878ca9..1355b44e3a 100644 --- a/modules/juce_core/juce_core.h +++ b/modules/juce_core/juce_core.h @@ -234,7 +234,8 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe #include "files/juce_TemporaryFile.h" #include "streams/juce_FileInputSource.h" #include "logging/juce_FileLogger.h" -#include "json/juce_JSON.h" +#include "javascript/juce_JSON.h" +#include "javascript/juce_Javascript.h" #include "maths/juce_BigInteger.h" #include "maths/juce_Expression.h" #include "maths/juce_Random.h" diff --git a/modules/juce_core/juce_module_info b/modules/juce_core/juce_module_info index 69fda5309e..d5feb345c1 100644 --- a/modules/juce_core/juce_module_info +++ b/modules/juce_core/juce_module_info @@ -25,7 +25,7 @@ "logging/*", "system/*", "xml/*", - "json/*", + "javascript/*", "zip/*", "unit_tests/*", "misc/*",