Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,42 @@
set(SCRIPTING_SOURCES
tiny-js/TinyJS_Functions.cpp
tiny-js/TinyJS_Functions.h
tiny-js/TinyJS_MathFunctions.cpp
tiny-js/TinyJS_MathFunctions.h
tiny-js/TinyJS.cpp
tiny-js/TinyJS.h
detail/scriptobject.h
detail/uidescscriptobject.h
detail/converters.h
detail/drawcontextobject.cpp
detail/drawcontextobject.h
detail/drawable.cpp
detail/drawable.h
detail/iscriptcontextinternal.h
detail/scriptingviewfactory.cpp
detail/scriptingviewfactory.h
detail/viewscriptobject.cpp
detail/viewscriptobject.h
uiscripting.cpp
uiscripting.h
uiscripting.md
)
add_library(vstgui_uiscripting STATIC ${SCRIPTING_SOURCES})
target_compile_definitions(vstgui_uiscripting ${VSTGUI_COMPILE_DEFINITIONS})
vstgui_set_cxx_version(vstgui_uiscripting ${VSTGUI_CXX_VERSION})
vstgui_source_group_by_folder(vstgui_uiscripting)
option(VSTGUI_SCRIPTING_TINYJS_TESTS "Add Tiny-JS test target" OFF)
if(VSTGUI_SCRIPTING_TINYJS_TESTS)
add_executable(uidescription_scripting_tiny_js_test
tiny-js/run_tests.cpp
tiny-js/TinyJS_Functions.cpp
tiny-js/TinyJS_Functions.h
tiny-js/TinyJS_MathFunctions.cpp
tiny-js/TinyJS_MathFunctions.h
tiny-js/TinyJS.cpp
tiny-js/TinyJS.h
)
target_compile_definitions(uidescription_scripting_tiny_js_test ${VSTGUI_COMPILE_DEFINITIONS})
vstgui_set_cxx_version(uidescription_scripting_tiny_js_test ${VSTGUI_CXX_VERSION})
endif(VSTGUI_SCRIPTING_TINYJS_TESTS)
@@ -0,0 +1,134 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "scriptobject.h"
#include "../../lib/vstguifwd.h"
#include "../../uidescription/uidescriptionfwd.h"
#include "../../lib/events.h"
#include "../../lib/crect.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
//------------------------------------------------------------------------
inline ScriptObject makeScriptRect (const CRect& rect)
{
using namespace std::literals;
ScriptObject obj;
obj.addChild ("left"sv, rect.left);
obj.addChild ("top"sv, rect.top);
obj.addChild ("right"sv, rect.right);
obj.addChild ("bottom"sv, rect.bottom);
obj.addFunc ("getWidth"sv, "{ return this.right - this.left; }"sv);
obj.addFunc ("getHeight"sv, "{ return this.bottom - this.top; }"sv);
return obj;
}
//------------------------------------------------------------------------
inline ScriptObject makeScriptPoint (const CPoint& point)
{
using namespace std::literals;
ScriptObject obj;
obj.addChild ("x"sv, point.x);
obj.addChild ("y"sv, point.y);
return obj;
}
//------------------------------------------------------------------------
inline CPoint fromScriptPoint (TJS::CScriptVar& var)
{
using namespace std::literals;
CPoint result {};
if (auto xVar = var.findChild ("x"sv))
result.x = xVar->getVar ()->getDouble ();
else
throw TJS::CScriptException ("Not a point object, missing 'x' member");
if (auto yVar = var.findChild ("y"sv))
result.y = yVar->getVar ()->getDouble ();
else
throw TJS::CScriptException ("Not a point object, missing 'y' member");
return result;
}
//------------------------------------------------------------------------
inline CRect fromScriptRect (TJS::CScriptVar& var)
{
using namespace std::literals;
CRect result {};
auto leftVar = var.findChild ("left"sv);
auto topVar = var.findChild ("top"sv);
auto rightVar = var.findChild ("right"sv);
auto bottomVar = var.findChild ("bottom"sv);
if (!leftVar || !topVar || !rightVar || !bottomVar)
throw TJS::CScriptException ("Expecting a rect object here");
result.left = leftVar->getVar ()->getDouble ();
result.top = topVar->getVar ()->getDouble ();
result.right = rightVar->getVar ()->getDouble ();
result.bottom = bottomVar->getVar ()->getDouble ();
return result;
}
//------------------------------------------------------------------------
inline ScriptObject makeScriptEvent (const Event& event)
{
using namespace std::literals;
ScriptObject obj;
if (auto modifierEvent = asModifierEvent (event))
{
ScriptObject mod;
if (modifierEvent->modifiers.has (ModifierKey::Shift))
mod.addChild ("shift"sv, true);
if (modifierEvent->modifiers.has (ModifierKey::Alt))
mod.addChild ("alt"sv, true);
if (modifierEvent->modifiers.has (ModifierKey::Control))
mod.addChild ("control"sv, true);
if (modifierEvent->modifiers.has (ModifierKey::Super))
mod.addChild ("super"sv, true);
obj.addChild ("modifiers"sv, std::move (mod));
}
if (auto mouseEvent = asMousePositionEvent (event))
{
obj.addChild ("mousePosition"sv, makeScriptPoint (mouseEvent->mousePosition));
}
if (auto mouseEvent = asMouseEvent (event))
{
ScriptObject buttons;
if (mouseEvent->buttonState.has (MouseButton::Left))
buttons.addChild ("left"sv, true);
if (mouseEvent->buttonState.has (MouseButton::Right))
buttons.addChild ("right"sv, true);
if (mouseEvent->buttonState.has (MouseButton::Middle))
buttons.addChild ("middle"sv, true);
obj.addChild ("mouseButtons"sv, std::move (buttons));
}
if (event.type == EventType::MouseWheel)
{
const auto& wheelEvent = castMouseWheelEvent (event);
ScriptObject wheel;
wheel.addChild ("deltaX"sv, wheelEvent.deltaX);
wheel.addChild ("deltaY"sv, wheelEvent.deltaY);
if (wheelEvent.flags & MouseWheelEvent::Flags::DirectionInvertedFromDevice)
wheel.addChild ("directionInvertedFromDevice"sv, true);
if (wheelEvent.flags & MouseWheelEvent::Flags::PreciseDeltas)
wheel.addChild ("preciceDelta"sv, true);
obj.addChild ("mouseWheel"sv, std::move (wheel));
}
if (auto keyEvent = asKeyboardEvent (event))
{
ScriptObject key;
key.addChild ("character"sv, static_cast<int> (keyEvent->character));
key.addChild ("virtual"sv, static_cast<int> (keyEvent->virt));
key.addChild ("isRepeat"sv, keyEvent->isRepeat);
obj.addChild ("key"sv, std::move (key));
}
obj.addChild ("consume"sv, 0);
return obj;
}
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,214 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "drawable.h"
#include "converters.h"
#include "../../lib/cframe.h"
#include "../../lib/cdrawcontext.h"
#include "../../lib/cgraphicspath.h"
#include "../../lib/cgraphicstransform.h"
#include "../../uidescription/detail/uiviewcreatorattributes.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
using namespace std::literals;
using namespace TJS;
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
void JavaScriptDrawable::onDraw (CDrawContext* context, const CRect& rect, const CRect& viewSize)
{
if (!scriptObject)
{
auto dashLength = std::round ((viewSize.getWidth () * 2 + viewSize.getHeight () * 2) / 40.);
CLineStyle ls (CLineStyle::kLineCapButt, CLineStyle::kLineJoinMiter, 0,
{dashLength, dashLength});
auto lineWidth = 1.;
auto size = viewSize;
size.inset (lineWidth / 2., lineWidth / 2.);
context->setLineStyle (ls);
context->setLineWidth (lineWidth);
context->setFrameColor (kBlackCColor);
context->drawRect (size, kDrawStroked);
ls.setDashPhase (dashLength * lineWidth);
context->setLineStyle (ls);
context->setFrameColor (kWhiteCColor);
context->drawRect (size, kDrawStroked);
return;
}
auto scriptContext = scriptObject->getContext ();
if (!scriptContext)
return;
context->saveGlobalState ();
drawContext.setDrawContext (context, scriptContext->getUIDescription ());
CDrawContext::Transform tm (*context, CGraphicsTransform ().translate (viewSize.getTopLeft ()));
auto rectVar = makeScriptRect (rect);
auto scriptRoot = scriptContext->getRoot ();
ScriptAddChildScoped scs (*scriptRoot, "view"sv, *scriptObject);
ScriptAddChildScoped scs2 (*scriptRoot, "context"sv, drawContext);
ScriptAddChildScoped scs3 (*scriptRoot, "rect"sv, rectVar);
scriptContext->evalScript ("view.draw(context, rect);"sv);
drawContext.setDrawContext (nullptr, nullptr);
context->restoreGlobalState ();
}
//------------------------------------------------------------------------
void JavaScriptDrawable::setup (ViewScriptObject* inObject) { scriptObject = inObject; }
//------------------------------------------------------------------------
bool JavaScriptDrawable::onDrawFocusOnTop ()
{
auto scriptContext = scriptObject->getContext ();
if (!scriptContext)
return false;
if (scriptObject->getVar ()->findChild ("drawFocusOnTop"sv) == nullptr)
return false;
auto scriptRoot = scriptContext->getRoot ();
ScriptAddChildScoped scs (*scriptRoot, "view"sv, *scriptObject);
auto boolResult = scriptContext->evalScript ("view.drawFocusOnTop();"sv);
return boolResult->isNumeric () ? boolResult->getInt () : false;
}
//------------------------------------------------------------------------
bool JavaScriptDrawable::onGetFocusPath (CGraphicsPath& outPath, CCoord focusWidth,
const CRect& viewSize)
{
if (auto scriptContext = scriptObject->getContext ())
{
if (scriptObject->getVar ()->findChild ("getFocusPath"sv) == nullptr)
{
auto r = viewSize;
outPath.addRect (r);
r.extend (focusWidth, focusWidth);
outPath.addRect (r);
return true;
}
auto scriptRoot = scriptContext->getRoot ();
auto path = makeOwned<CGraphicsPath> (outPath);
ScriptObject focusWidthVar;
focusWidthVar->setDouble (focusWidth);
ScriptAddChildScoped scs (*scriptRoot, "view"sv, *scriptObject);
ScriptAddChildScoped scs2 (*scriptRoot, "path"sv, makeGraphicsPathScriptObject (path));
ScriptAddChildScoped scs3 (*scriptRoot, "focusWidth"sv, focusWidthVar);
auto boolResult = scriptContext->evalScript ("view.getFocusPath(path, focusWidth);"sv);
if (boolResult->isNumeric ())
{
if (boolResult->getInt () == 1)
{
CGraphicsTransform tm;
tm.translate (viewSize.left, viewSize.top);
outPath.addPath (*path, &tm);
return true;
}
return false;
}
}
return false;
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
void JavaScriptDrawableView::drawRect (CDrawContext* context, const CRect& rect)
{
onDraw (context, rect, getViewSize ());
}
//------------------------------------------------------------------------
bool JavaScriptDrawableView::drawFocusOnTop ()
{
if (wantsFocus ())
return onDrawFocusOnTop ();
return false;
}
//------------------------------------------------------------------------
bool JavaScriptDrawableView::getFocusPath (CGraphicsPath& outPath)
{
if (wantsFocus ())
return onGetFocusPath (outPath, getFrame ()->getFocusWidth (), getViewSize ());
return false;
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
void JavaScriptDrawableControl::draw (CDrawContext* context) { drawRect (context, getViewSize ()); }
//------------------------------------------------------------------------
void JavaScriptDrawableControl::drawRect (CDrawContext* context, const CRect& rect)
{
onDraw (context, rect, getViewSize ());
}
//------------------------------------------------------------------------
bool JavaScriptDrawableControl::drawFocusOnTop ()
{
if (wantsFocus ())
return onDrawFocusOnTop ();
return false;
}
//------------------------------------------------------------------------
bool JavaScriptDrawableControl::getFocusPath (CGraphicsPath& outPath)
{
if (wantsFocus ())
return onGetFocusPath (outPath, getFrame ()->getFocusWidth (), getViewSize ());
return false;
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
IdStringPtr JavaScriptDrawableViewCreator::getViewName () const { return "JavaScriptDrawableView"; }
//------------------------------------------------------------------------
IdStringPtr JavaScriptDrawableViewCreator::getBaseViewName () const
{
return UIViewCreator::kCView;
}
//------------------------------------------------------------------------
CView* JavaScriptDrawableViewCreator::create (const UIAttributes& attributes,
const IUIDescription* description) const
{
return new JavaScriptDrawableView (CRect ());
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
IdStringPtr JavaScriptDrawableControlCreator::getViewName () const
{
return "JavaScriptDrawableControl";
}
//------------------------------------------------------------------------
IdStringPtr JavaScriptDrawableControlCreator::getBaseViewName () const
{
return UIViewCreator::kCControl;
}
//------------------------------------------------------------------------
CView* JavaScriptDrawableControlCreator::create (const UIAttributes& attributes,
const IUIDescription* description) const
{
return new JavaScriptDrawableControl (CRect ());
}
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,77 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "viewscriptobject.h"
#include "drawcontextobject.h"
#include "../../lib/cview.h"
#include "../../lib/controls/ccontrol.h"
#include "../../uidescription/iviewcreator.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
//------------------------------------------------------------------------
struct JavaScriptDrawable
{
void onDraw (CDrawContext* context, const CRect& rect, const CRect& viewSize);
bool onDrawFocusOnTop ();
bool onGetFocusPath (CGraphicsPath& outPath, CCoord focusWidth, const CRect& viewSize);
void setup (ViewScriptObject* object);
private:
ViewScriptObject* scriptObject {nullptr};
DrawContextObject drawContext;
};
//------------------------------------------------------------------------
struct JavaScriptDrawableView : CView,
IFocusDrawing,
JavaScriptDrawable
{
using CView::CView;
void drawRect (CDrawContext* context, const CRect& rect) override;
bool drawFocusOnTop () override;
bool getFocusPath (CGraphicsPath& outPath) override;
};
//------------------------------------------------------------------------
struct JavaScriptDrawableControl : CControl,
JavaScriptDrawable
{
using CControl::CControl;
void draw (CDrawContext* pContext) override;
void drawRect (CDrawContext* context, const CRect& rect) override;
bool drawFocusOnTop () override;
bool getFocusPath (CGraphicsPath& outPath) override;
CLASS_METHODS_NOCOPY (JavaScriptDrawableControl, CControl);
};
//------------------------------------------------------------------------
struct JavaScriptDrawableViewCreator : ViewCreatorAdapter
{
IdStringPtr getViewName () const override;
IdStringPtr getBaseViewName () const override;
CView* create (const UIAttributes& attributes,
const IUIDescription* description) const override;
};
//------------------------------------------------------------------------
struct JavaScriptDrawableControlCreator : ViewCreatorAdapter
{
IdStringPtr getViewName () const override;
IdStringPtr getBaseViewName () const override;
CView* create (const UIAttributes& attributes,
const IUIDescription* description) const override;
};
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,870 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "drawcontextobject.h"
#include "converters.h"
#include "../../lib/cdrawcontext.h"
#include "../../lib/cgraphicspath.h"
#include "../../lib/cgradient.h"
#include "../../uidescription/uidescription.h"
#include "../../uidescription/uiviewcreator.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
using namespace std::literals;
using namespace TJS;
//------------------------------------------------------------------------
template<typename T>
static T getFromVar (CScriptVar* var, const string& exceptionString)
{
auto customData = var->getCustomData ();
if (!customData.has_value ())
throw CScriptException (exceptionString);
try
{
auto result = std::any_cast<T> (customData);
return result;
}
catch (const std::bad_any_cast&)
{
throw CScriptException (exceptionString);
}
return {};
}
//------------------------------------------------------------------------
static SharedPointer<CGraphicsPath> getGraphicsPath (CScriptVar* var, std::string_view varName,
std::string_view signature)
{
auto pathVar = getArgument (var, varName, signature);
return getFromVar<SharedPointer<CGraphicsPath>> (pathVar, "Variable is not a graphics path");
}
//------------------------------------------------------------------------
static SharedPointer<CGradient> getGradient (CScriptVar* var, std::string_view varName,
std::string_view signature)
{
auto gradientVar = getArgument (var, varName, signature);
return getFromVar<SharedPointer<CGradient>> (gradientVar, "Variable is not a gradient");
}
//------------------------------------------------------------------------
static std::shared_ptr<CGraphicsTransform> getTransformMatrix (CScriptVar* var,
std::string_view varName,
std::string_view signature)
{
auto tmVar = getArgument (var, varName, signature);
return getFromVar<std::shared_ptr<CGraphicsTransform>> (tmVar,
"Variable is not a transform matrix");
}
//------------------------------------------------------------------------
static std::shared_ptr<CGraphicsTransform> getOptionalTransformMatrix (CScriptVar* var,
std::string_view varName,
std::string_view signature)
{
if (auto tmVar = getOptionalArgument (var, varName))
return getFromVar<std::shared_ptr<CGraphicsTransform>> (
tmVar, "Variable is not a transform matrix");
return {};
}
//------------------------------------------------------------------------
static CRect getRect (CScriptVar* var, std::string_view varName, std::string_view signature)
{
auto rectVar = getArgument (var, varName, signature);
auto rect = fromScriptRect (*rectVar);
return rect;
}
//------------------------------------------------------------------------
static CPoint getPoint (CScriptVar* var, std::string_view varName, std::string_view signature)
{
auto pointVar = getArgument (var, varName, signature);
auto point = fromScriptPoint (*pointVar);
return point;
}
//------------------------------------------------------------------------
static CPoint getOptionalPoint (CScriptVar* var, std::string_view varName,
std::string_view signature, CPoint defaultPoint = {})
{
if (auto pointVar = getOptionalArgument (var, varName))
return fromScriptPoint (*pointVar);
return defaultPoint;
}
//------------------------------------------------------------------------
static double getDouble (CScriptVar* var, std::string_view varName, std::string_view signature)
{
auto doubleVar = getArgument (var, varName, signature);
if (!doubleVar->isNumeric ())
{
TJS::string s ("'");
s.append (varName);
s.append ("' must be a number");
throw CScriptException (s);
}
return doubleVar->getDouble ();
}
//------------------------------------------------------------------------
static int64_t getInt (CScriptVar* var, std::string_view varName, std::string_view signature)
{
auto intVar = getArgument (var, varName, signature);
if (!intVar->isNumeric ())
{
TJS::string s ("'");
s.append (varName);
s.append ("' must be numeric");
throw CScriptException (s);
}
return intVar->getInt ();
}
//------------------------------------------------------------------------
static int64_t getOptionalInt (CScriptVar* var, std::string_view varName,
std::string_view signature, int64_t defaultInt = {})
{
if (auto intVar = getOptionalArgument (var, varName))
{
if (!intVar->isNumeric ())
{
TJS::string s ("'");
s.append (varName);
s.append ("' must be numeric");
throw CScriptException (s);
}
return intVar->getInt ();
}
return defaultInt;
}
//------------------------------------------------------------------------
static CColor getColor (CScriptVar* var, const IUIDescription* uiDesc, std::string_view varName,
std::string_view signature)
{
auto colorVar = getArgument (var, varName, signature);
auto colorStr = colorVar->getString ();
CColor color {};
if (!UIViewCreator::stringToColor (colorStr, color, uiDesc))
{
string str ("'");
str += colorStr;
str += "' is not a color in a call to ";
str += signature;
throw CScriptException (str);
}
return color;
}
//------------------------------------------------------------------------
struct TransformMatrixScriptObject : ScriptObject
{
using TransformMatrixPtr = std::shared_ptr<CGraphicsTransform>;
TransformMatrixScriptObject (TransformMatrixPtr tm = std::make_shared<CGraphicsTransform> ())
: ScriptObject (new CScriptVar ("", SCRIPTVAR_OBJECT))
{
scriptVar->setCustomData (tm);
addFunc ("concat"sv, [tm] (auto var) { concat (tm, var); }, {"transformMatrix"sv});
addFunc ("inverse"sv, [tm] (auto var) { inverse (tm, var); });
addFunc ("rotate"sv, [tm] (auto var) { rotate (tm, var); }, {"angle"sv, "center?"sv});
addFunc ("scale"sv, [tm] (auto var) { scale (tm, var); }, {"x"sv, "y"sv});
addFunc ("skewX"sv, [tm] (auto var) { skewX (tm, var); }, {"angle"sv});
addFunc ("skewY"sv, [tm] (auto var) { skewY (tm, var); }, {"angle"sv});
addFunc ("translate"sv, [tm] (auto var) { translate (tm, var); }, {"x"sv, "y"sv});
addFunc ("transform"sv, [tm] (auto var) { transform (tm, var); }, {"pointOrRect"sv});
}
static void inverse (const TransformMatrixPtr& tm, CScriptVar* var)
{
static constexpr auto signature = "matrix.inverse();"sv;
auto iTm = tm->inverse ();
TransformMatrixScriptObject obj (std::make_shared<CGraphicsTransform> (iTm));
var->setReturnVar (obj.take ());
}
static void concat (const TransformMatrixPtr& tm, CScriptVar* var)
{
static constexpr auto signature = "matrix.concat(transformMatrix);"sv;
auto other = getTransformMatrix (var, "transformMatrix"sv, signature);
auto result = *(tm.get ()) * *(other.get ());
*(tm.get ()) = result;
}
static void translate (const TransformMatrixPtr& tm, CScriptVar* var)
{
static constexpr auto signature = "matrix.translate(x, y);"sv;
auto x = getDouble (var, "x"sv, signature);
auto y = getDouble (var, "y"sv, signature);
tm->translate (x, y);
}
static void scale (const TransformMatrixPtr& tm, CScriptVar* var)
{
static constexpr auto signature = "matrix.scale(x, y);"sv;
auto x = getDouble (var, "x"sv, signature);
auto y = getDouble (var, "y"sv, signature);
tm->scale (x, y);
}
static void rotate (const TransformMatrixPtr& tm, CScriptVar* var)
{
static constexpr auto signature = "matrix.rotate(angle, center?);"sv;
auto angle = getDouble (var, "angle"sv, signature);
if (auto centerVar = getOptionalArgument (var, "center?"sv))
{
auto center = fromScriptPoint (*centerVar);
tm->rotate (angle, center);
}
else
{
tm->rotate (angle);
}
}
static void skewX (const TransformMatrixPtr& tm, CScriptVar* var)
{
static constexpr auto signature = "matrix.skewX(angle);"sv;
auto angle = getDouble (var, "angle"sv, signature);
tm->skewX (angle);
}
static void skewY (const TransformMatrixPtr& tm, CScriptVar* var)
{
static constexpr auto signature = "matrix.skewY(angle);"sv;
auto angle = getDouble (var, "angle"sv, signature);
tm->skewY (angle);
}
static void transform (const TransformMatrixPtr& tm, CScriptVar* var)
{
static constexpr auto signature = "matrix.transform(pointOrRect);"sv;
auto pointOrRect = getArgument (var, "pointOrRect"sv, signature);
auto xVar = getOptionalArgument (pointOrRect, "x"sv);
auto yVar = getOptionalArgument (pointOrRect, "y"sv);
if (xVar && yVar)
{
auto x = xVar->getDouble ();
auto y = yVar->getDouble ();
tm->transform (x, y);
xVar->setDouble (x);
yVar->setDouble (y);
return;
}
auto leftVar = getOptionalArgument (pointOrRect, "left"sv);
auto topVar = getOptionalArgument (pointOrRect, "top"sv);
auto rightVar = getOptionalArgument (pointOrRect, "right"sv);
auto bottomVar = getOptionalArgument (pointOrRect, "bottom"sv);
if (leftVar && topVar && rightVar && bottomVar)
{
CRect r (leftVar->getDouble (), topVar->getDouble (), rightVar->getDouble (),
bottomVar->getDouble ());
tm->transform (r);
leftVar->setDouble (r.left);
topVar->setDouble (r.top);
rightVar->setDouble (r.right);
bottomVar->setDouble (r.bottom);
return;
}
string s ("Argument is not a point or a rect in ");
s.append (signature);
throw CScriptException (std::move (s));
}
};
//------------------------------------------------------------------------
TJS::CScriptVar* makeTransformMatrixObject ()
{
TransformMatrixScriptObject obj;
return obj.take ();
}
//------------------------------------------------------------------------
struct GradientScriptObject : ScriptObject
{
GradientScriptObject (const SharedPointer<CGradient>& g, const IUIDescription* uiDesc)
: ScriptObject (new CScriptVar ("", SCRIPTVAR_OBJECT))
{
scriptVar->setCustomData (g);
addFunc ("addColorStop"sv, [g, uiDesc] (auto var) { addColorStop (g, uiDesc, var); },
{"position"sv, "color"sv});
}
static void addColorStop (const SharedPointer<CGradient>& g, const IUIDescription* uiDesc,
CScriptVar* var)
{
static constexpr auto signature = "gradient.addColorStop(position, color);"sv;
auto position = getDouble (var, "position"sv, signature);
auto color = getColor (var, uiDesc, "color"sv, signature);
g->addColorStop (position, color);
}
};
//------------------------------------------------------------------------
struct GraphicsPathScriptObject : ScriptObject
{
GraphicsPathScriptObject (const SharedPointer<CGraphicsPath>& p)
: ScriptObject (new CScriptVar ("", SCRIPTVAR_OBJECT))
{
scriptVar->setCustomData (p);
addFunc ("addEllipse"sv, [p] (auto var) { addEllipse (p, var); }, {"rect"sv});
addFunc ("addArc"sv, [p] (auto var) { addArc (p, var); },
{"rect"sv, "startAngle"sv, "endAngle"sv, "clockwise"sv});
addFunc ("addBezierCurve"sv, [p] (auto var) { addBezierCurve (p, var); },
{"control1"sv, "control2"sv, "end"sv});
addFunc ("addLine"sv, [p] (auto var) { addLine (p, var); }, {"to"sv});
addFunc ("addPath"sv, [p] (auto var) { addPath (p, var); },
{"path"sv, "transformMatrix?"sv});
addFunc ("addRect"sv, [p] (auto var) { addRect (p, var); }, {"rect"sv});
addFunc ("addRoundRect"sv, [p] (auto var) { addRoundRect (p, var); },
{"rect"sv, "radius"sv});
addFunc ("closeSubpath"sv, [p] (auto var) { closeSubpath (p); });
addFunc ("beginSubpath"sv, [p] (auto var) { beginSubpath (p, var); }, {"start"sv});
}
static void addPath (const SharedPointer<CGraphicsPath>& path, CScriptVar* var)
{
static constexpr auto signature = "path.addPath(path, transformMatrix?);"sv;
auto otherPath = getGraphicsPath (var, "path"sv, signature);
auto tm = getOptionalTransformMatrix (var, "transformMatrix?"sv, signature);
path->addPath (*otherPath.get (), tm ? tm.get () : nullptr);
}
static void addArc (const SharedPointer<CGraphicsPath>& path, CScriptVar* var)
{
static constexpr auto signature = "path.addArc(rect, startAngle, endAngle, clockwise);"sv;
auto rect = getRect (var, "rect"sv, signature);
auto startAngle = getDouble (var, "startAngle"sv, signature);
auto endAngle = getDouble (var, "endAngle"sv, signature);
auto clockwise = getInt (var, "clockwise"sv, signature);
path->addArc (rect, startAngle, endAngle, clockwise != 0 ? true : false);
}
static void addEllipse (const SharedPointer<CGraphicsPath>& path, CScriptVar* var)
{
static constexpr auto signature = "path.addEllipse(rect);"sv;
auto rect = getRect (var, "rect"sv, signature);
path->addEllipse (rect);
}
static void addRect (const SharedPointer<CGraphicsPath>& path, CScriptVar* var)
{
static constexpr auto signature = "path.addRect(rect);"sv;
auto rect = getRect (var, "rect"sv, signature);
path->addRect (rect);
}
static void addLine (const SharedPointer<CGraphicsPath>& path, CScriptVar* var)
{
static constexpr auto signature = "path.addLine(to);"sv;
auto point = getPoint (var, "to"sv, signature);
path->addLine (point);
}
static void addBezierCurve (const SharedPointer<CGraphicsPath>& path, CScriptVar* var)
{
static constexpr auto signature = "path.addBezierCurve(control1, control2, end);"sv;
auto control1 = getPoint (var, "control1"sv, signature);
auto control2 = getPoint (var, "control2"sv, signature);
auto end = getPoint (var, "end"sv, signature);
path->addBezierCurve (control1, control2, end);
}
static void beginSubpath (const SharedPointer<CGraphicsPath>& path, CScriptVar* var)
{
static constexpr auto signature = "path.beginSubpath(start);"sv;
auto start = getPoint (var, "start"sv, signature);
path->beginSubpath (start);
}
static void closeSubpath (const SharedPointer<CGraphicsPath>& path)
{
static constexpr auto signature = "path.closeSubpath();"sv;
path->closeSubpath ();
}
static void addRoundRect (const SharedPointer<CGraphicsPath>& path, CScriptVar* var)
{
static constexpr auto signature = "path.addRoundRect(rect, radius);"sv;
auto rect = getRect (var, "rect"sv, signature);
auto radius = getDouble (var, "radius"sv, signature);
path->addRoundRect (rect, radius);
}
};
//------------------------------------------------------------------------
ScriptObject makeGraphicsPathScriptObject (const SharedPointer<CGraphicsPath>& p)
{
return GraphicsPathScriptObject (p);
}
//------------------------------------------------------------------------
struct DrawContextObject::Impl
{
CDrawContext* context {nullptr};
IUIDescription* uiDesc {nullptr};
mutable int32_t globalStatesStored {0};
void setContext (CDrawContext* inContext, IUIDescription* inUIDesc)
{
if (context)
{
while (globalStatesStored > 0)
{
context->restoreGlobalState ();
--globalStatesStored;
}
}
context = inContext;
uiDesc = inUIDesc;
globalStatesStored = 0;
}
void checkContextOrThrow () const
{
if (!context)
throw CScriptException ("Native context is missing!");
}
CDrawStyle getDrawStyle (CScriptVar* styleVar) const
{
CDrawStyle style {};
auto string = styleVar->getString ();
if (string == "stroked"sv)
style = kDrawStroked;
else if (string == "filled"sv)
style = kDrawFilled;
else if (string == "filledAndStroked"sv)
style = kDrawFilledAndStroked;
else
throw CScriptException ("Unknown draw style: " + string);
return style;
}
CDrawContext::PathDrawMode getPathDrawMode (CScriptVar* var) const
{
CDrawContext::PathDrawMode mode {};
auto string = var->getString ();
if (string == "stroked"sv)
mode = CDrawContext::PathDrawMode::kPathStroked;
else if (string == "filled"sv)
mode = CDrawContext::PathDrawMode::kPathFilled;
else if (string == "filledEvenOdd"sv)
mode = CDrawContext::PathDrawMode::kPathFilledEvenOdd;
else
throw CScriptException ("Unknown path draw mode: " + string);
return mode;
}
void createRoundGraphicsPath (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.createRoundGraphicsPath(rect, radius);"sv;
checkContextOrThrow ();
auto rect = getRect (var, "rect"sv, signature);
auto radius = getDouble (var, "radius"sv, signature);
if (auto path = owned (context->createRoundRectGraphicsPath (rect, radius)))
{
GraphicsPathScriptObject obj (path);
var->setReturnVar (obj);
}
}
void createGraphicsPath (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.createGraphicsPath();"sv;
checkContextOrThrow ();
if (auto path = owned (context->createGraphicsPath ()))
{
GraphicsPathScriptObject obj (path);
var->setReturnVar (obj);
}
}
void createGradient (CScriptVar* var) const
{
static constexpr auto signature =
"drawContext.createGradient(startColorPosition, startColor, endColorPosition, endColor);"sv;
auto startColorPosition = getDouble (var, "startColorPosition", signature);
auto endColorPosition = getDouble (var, "endColorPosition", signature);
auto startColor = getColor (var, uiDesc, "startColor"sv, signature);
auto endColor = getColor (var, uiDesc, "endColor"sv, signature);
if (auto gradient = owned (
CGradient::create (startColorPosition, endColorPosition, startColor, endColor)))
{
GradientScriptObject obj (gradient, uiDesc);
var->setReturnVar (obj);
}
}
void drawGraphicsPath (CScriptVar* var) const
{
static constexpr auto signature =
"drawContext.drawGraphicsPath(path, mode?, transform?);"sv;
checkContextOrThrow ();
auto path = getGraphicsPath (var, "path"sv, signature);
auto modeVar = getOptionalArgument (var, "mode?");
auto mode = modeVar ? getPathDrawMode (modeVar) : CDrawContext::PathDrawMode::kPathFilled;
auto tm = getOptionalTransformMatrix (var, "transform?"sv, signature);
context->drawGraphicsPath (path, mode, tm ? tm.get () : nullptr);
}
void fillLinearGradient (CScriptVar* var) const
{
static constexpr auto signature =
"drawContext.fillLinearGradient(path, gradient, startPoint, endPoint, evenOdd?, transform?);"sv;
checkContextOrThrow ();
auto path = getGraphicsPath (var, "path"sv, signature);
auto gradient = getGradient (var, "gradient"sv, signature);
auto startPoint = getPoint (var, "startPoint"sv, signature);
auto endPoint = getPoint (var, "endPoint"sv, signature);
auto tm = getOptionalTransformMatrix (var, "transform?"sv, signature);
auto evenOdd = getOptionalInt (var, "evenOdd?", signature);
context->fillLinearGradient (path, *gradient, startPoint, endPoint, evenOdd > 0, tm.get ());
}
void fillRadialGradient (CScriptVar* var) const
{
static constexpr auto signature =
"drawContext.fillRadialGradient(path, gradient, centerPoint, radius, originOffsetPoint?, evenOdd?, transform?);"sv;
checkContextOrThrow ();
auto path = getGraphicsPath (var, "path"sv, signature);
auto gradient = getGradient (var, "gradient"sv, signature);
auto centerPoint = getPoint (var, "centerPoint"sv, signature);
auto radius = getDouble (var, "radius"sv, signature);
auto originOffsetPoint = getOptionalPoint (var, "originOffsetPoint?"sv, signature);
auto evenOdd = getOptionalInt (var, "evenOdd?", signature);
auto tm = getOptionalTransformMatrix (var, "transform?"sv, signature);
context->fillRadialGradient (path, *gradient, centerPoint, radius, originOffsetPoint,
evenOdd, tm.get ());
}
void drawLine (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.drawLine(from, to);"sv;
checkContextOrThrow ();
auto fromPoint = getArgument (var, "from"sv, signature);
auto toPoint = getArgument (var, "to"sv, signature);
auto from = fromScriptPoint (*fromPoint);
auto to = fromScriptPoint (*toPoint);
context->drawLine (from, to);
}
void drawRect (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.drawRect(rect, style);"sv;
checkContextOrThrow ();
auto rect = getRect (var, "rect"sv, signature);
auto styleVar = getArgument (var, "style"sv, signature);
auto style = getDrawStyle (styleVar);
context->drawRect (rect, style);
}
void drawEllipse (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.drawEllipse(rect, style);"sv;
checkContextOrThrow ();
auto rect = getRect (var, "rect"sv, signature);
auto styleVar = getArgument (var, "style"sv, signature);
auto style = getDrawStyle (styleVar);
context->drawEllipse (rect, style);
}
void drawArc (CScriptVar* var) const
{
static constexpr auto signature =
"drawContext.drawArc(rect, startAngle, endAngle, style);"sv;
checkContextOrThrow ();
auto rect = getRect (var, "rect"sv, signature);
auto startAngle = static_cast<float> (getDouble (var, "startAngle"sv, signature));
auto endAngle = static_cast<float> (getDouble (var, "endAngle"sv, signature));
auto styleVar = getArgument (var, "style"sv, signature);
auto style = getDrawStyle (styleVar);
context->drawArc (rect, startAngle, endAngle, style);
}
void clearRect (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.clearRect(rect);"sv;
checkContextOrThrow ();
auto rect = getRect (var, "rect"sv, signature);
context->clearRect (rect);
}
void drawPolygon (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.drawPolygon(points, style);"sv;
checkContextOrThrow ();
auto pointsVar = getArgument (var, "points"sv, signature);
auto styleVar = getArgument (var, "style"sv, signature);
if (!pointsVar->isArray ())
throw CScriptException ("`points` argument must be an array of points in "
"drawContext.drawPolygon(points, style);");
PointList points;
auto numPoints = pointsVar->getArrayLength ();
for (auto index = 0; index < numPoints; ++index)
{
auto pointVar = pointsVar->getArrayIndex (index);
vstgui_assert (pointVar != nullptr);
points.emplace_back (fromScriptPoint (*pointVar));
}
auto style = getDrawStyle (styleVar);
context->drawPolygon (points, style);
}
void setClipRect (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.setClipRect(rect);"sv;
checkContextOrThrow ();
auto rect = getRect (var, "rect"sv, signature);
context->setClipRect (rect);
}
void drawBitmap (CScriptVar* var) const
{
static constexpr auto signature =
"drawContext.drawBitmap(name, destRect, offsetPoint?, alpha?);"sv;
checkContextOrThrow ();
auto nameVar = getArgument (var, "name"sv, signature);
auto destRect = getRect (var, "destRect"sv, signature);
auto offsetPointVar = getOptionalArgument (var, "offsetPoint?"sv);
auto alphaVar = getOptionalArgument (var, "alpha?"sv);
auto bitmap = uiDesc->getBitmap (nameVar->getString ().data ());
if (!bitmap)
throw CScriptException ("bitmap not found in uiDescription");
auto offset = offsetPointVar ? fromScriptPoint (*offsetPointVar) : CPoint (0, 0);
auto alpha = static_cast<float> (alphaVar ? alphaVar->getDouble () : 1.);
context->drawBitmap (bitmap, destRect, offset, alpha);
}
void drawString (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.drawString(string, rect, align?);"sv;
checkContextOrThrow ();
auto stringVar = getArgument (var, "string"sv, signature);
auto rect = getRect (var, "rect"sv, signature);
auto alignVar = getOptionalArgument (var, "align?"sv);
auto string = stringVar->getString ().data ();
CHoriTxtAlign align = kCenterText;
if (!alignVar || alignVar->isUndefined ())
align = kCenterText;
else if (alignVar->getString () == "left"sv)
align = kLeftText;
else if (alignVar->getString () == "center"sv)
align = kCenterText;
else if (alignVar->getString () == "right"sv)
align = kRightText;
else
throw CScriptException (
"wrong `align` argument. Expecting 'left', 'center' or 'right'");
context->drawString (string, rect, align, true);
}
void setFont (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.setFont(name);"sv;
checkContextOrThrow ();
auto fontVar = getArgument (var, "name"sv, signature);
if (auto font = uiDesc->getFont (fontVar->getString ().data ()))
context->setFont (font);
}
void setFontColor (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.setFontColor(color);"sv;
checkContextOrThrow ();
auto colorVar = getArgument (var, "color"sv, signature);
CColor color {};
UIViewCreator::stringToColor (colorVar->getString (), color, uiDesc);
context->setFontColor (color);
}
void setFillColor (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.setFillColor(color);"sv;
checkContextOrThrow ();
auto color = getColor (var, uiDesc, "color"sv, signature);
context->setFillColor (color);
}
void setFrameColor (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.setFrameColor(color);"sv;
checkContextOrThrow ();
auto color = getColor (var, uiDesc, "color"sv, signature);
context->setFrameColor (color);
}
void setLineWidth (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.setLineWidth(width);"sv;
checkContextOrThrow ();
auto widthVar = getArgument (var, "width"sv, signature);
context->setLineWidth (widthVar->getDouble ());
}
void setLineStyle (CScriptVar* var) const
{
static constexpr auto signature =
"drawContext.setLineStyle(styleOrLineCap, lineJoin?, dashLengths?, dashPhase);"sv;
checkContextOrThrow ();
auto styleOrLineCapVar = getArgument (var, "styleOrLineCap"sv, signature);
auto lineJoinVar = getOptionalArgument (var, "lineJoin?"sv);
auto dashLengthsVar = getOptionalArgument (var, "dashLengths?"sv);
auto dashPhaseVar = getOptionalArgument (var, "dashPhase?"sv);
auto styleOrLineCap = styleOrLineCapVar->getString ();
std::unique_ptr<CLineStyle> lineStyle;
if (styleOrLineCap == "solid"sv)
{
lineStyle = std::make_unique<CLineStyle> (kLineSolid);
}
else if (styleOrLineCap == "dotted"sv)
{
lineStyle = std::make_unique<CLineStyle> (kLineOnOffDash);
}
else
{
if (styleOrLineCap == "butt"sv)
lineStyle = std::make_unique<CLineStyle> (CLineStyle::LineCap::kLineCapButt);
else if (styleOrLineCap == "round"sv)
lineStyle = std::make_unique<CLineStyle> (CLineStyle::LineCap::kLineCapRound);
else if (styleOrLineCap == "square"sv)
lineStyle = std::make_unique<CLineStyle> (CLineStyle::LineCap::kLineCapSquare);
else
throw CScriptException ("unknown `line cap` argument");
if (lineJoinVar)
{
auto lineJoin = lineJoinVar->getString ();
if (lineJoin == "miter"sv)
lineStyle->setLineJoin (CLineStyle::LineJoin::kLineJoinMiter);
else if (lineJoin == "round"sv)
lineStyle->setLineJoin (CLineStyle::LineJoin::kLineJoinRound);
else if (lineJoin == "bevel"sv)
lineStyle->setLineJoin (CLineStyle::LineJoin::kLineJoinBevel);
}
if (dashLengthsVar)
{
if (!dashLengthsVar->isArray ())
throw CScriptException ("`dashLengths` must be an array of numbers");
CLineStyle::CoordVector lengths;
auto numValues = dashLengthsVar->getArrayLength ();
for (auto index = 0; index < numValues; ++index)
{
if (auto lengthVar = dashLengthsVar->getArrayIndex (index))
lengths.push_back (lengthVar->getDouble ());
}
lineStyle->getDashLengths () = lengths;
}
if (dashPhaseVar)
{
lineStyle->setDashPhase (dashPhaseVar->getDouble ());
}
}
if (lineStyle)
context->setLineStyle (*lineStyle.get ());
}
void setGlobalAlpha (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.setGlobalAlpha(alpha);"sv;
checkContextOrThrow ();
auto alpha = static_cast<float> (getDouble (var, "alpha"sv, signature));
context->setGlobalAlpha (alpha);
}
void saveGlobalState () const
{
checkContextOrThrow ();
context->saveGlobalState ();
++globalStatesStored;
}
void restoreGlobalState () const
{
checkContextOrThrow ();
context->restoreGlobalState ();
--globalStatesStored;
}
void getStringWidth (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.getStringWidth(string);"sv;
checkContextOrThrow ();
auto stringVar = getArgument (var, "string"sv, signature);
auto width = context->getStringWidth (stringVar->getString ().data ());
var->getReturnVar ()->setDouble (width);
}
void setDrawMode (CScriptVar* var) const
{
static constexpr auto signature = "drawContext.setDrawMode(mode);"sv;
checkContextOrThrow ();
auto modeVar = getArgument (var, "mode"sv, signature);
if (modeVar->getString () == "aliasing"sv)
context->setDrawMode (kAliasing);
else if (modeVar->getString () == "anti-aliasing"sv)
context->setDrawMode (kAntiAliasing);
else
throw CScriptException ("`mode` must be `aliasing` or `anti-aliasing`");
}
};
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
DrawContextObject::DrawContextObject ()
{
impl = std::make_unique<Impl> ();
scriptVar->setLifeTimeObserver (this);
addFunc ("clearRect"sv, [this] (auto var) { impl->clearRect (var); }, {"rect"sv});
addFunc ("createRoundGraphicsPath"sv,
[this] (auto var) { impl->createRoundGraphicsPath (var); }, {"rect"sv, "radius"sv});
addFunc ("createGraphicsPath"sv, [this] (auto var) { impl->createGraphicsPath (var); });
addFunc ("createGradient"sv, [this] (auto var) { impl->createGradient (var); },
{"startColorPosition"sv, "startColor"sv, "endColorPosition"sv, "endColor"sv});
addFunc ("getStringWidth"sv, [this] (auto var) { impl->getStringWidth (var); }, {"string"sv});
addFunc ("drawArc"sv, [this] (auto var) { impl->drawArc (var); },
{"rect"sv, "startAngle"sv, "endAngle"sv, "style"sv});
addFunc ("drawBitmap"sv, [this] (auto var) { impl->drawBitmap (var); },
{"name"sv, "destRect"sv, "offsetPoint?"sv, "alpha?"sv});
addFunc ("drawEllipse"sv, [this] (auto var) { impl->drawEllipse (var); },
{"rect"sv, "style"sv});
addFunc ("drawGraphicsPath"sv, [this] (auto var) { impl->drawGraphicsPath (var); },
{"path"sv, "mode?"sv, "transform?"sv});
addFunc ("drawLine"sv, [this] (auto var) { impl->drawLine (var); }, {"from"sv, "to"sv});
addFunc ("drawPolygon"sv, [this] (auto var) { impl->drawPolygon (var); },
{"points"sv, "style"sv});
addFunc ("drawRect"sv, [this] (auto var) { impl->drawRect (var); }, {"rect"sv, "style"sv});
addFunc ("drawString"sv, [this] (auto var) { impl->drawString (var); },
{"string"sv, "rect"sv, "align?"sv});
addFunc ("fillLinearGradient"sv, [this] (auto var) { impl->fillLinearGradient (var); },
{"path"sv, "gradient"sv, "startPoint"sv, "endPoint"sv, "evenOdd?"sv, "transform?"sv});
addFunc ("fillRadialGradient"sv, [this] (auto var) { impl->fillRadialGradient (var); },
{"path"sv, "gradient"sv, "centerPoint"sv, "radius"sv, "originOffsetPoint?"sv,
"evenOdd?"sv, "transform?"sv});
addFunc ("restoreGlobalState"sv, [this] (auto var) { impl->restoreGlobalState (); });
addFunc ("saveGlobalState"sv, [this] (auto var) { impl->saveGlobalState (); });
addFunc ("setClipRect"sv, [this] (auto var) { impl->setClipRect (var); }, {"rect"sv});
addFunc ("setFont"sv, [this] (auto var) { impl->setFont (var); }, {"name"sv});
addFunc ("setFontColor"sv, [this] (auto var) { impl->setFontColor (var); }, {"color"sv});
addFunc ("setFillColor"sv, [this] (auto var) { impl->setFillColor (var); }, {"color"sv});
addFunc ("setFrameColor"sv, [this] (auto var) { impl->setFrameColor (var); }, {"color"sv});
addFunc ("setGlobalAlpha", [this] (auto var) { impl->setGlobalAlpha (var); }, {"alpha"sv});
addFunc ("setLineWidth"sv, [this] (auto var) { impl->setLineWidth (var); }, {"width"sv});
addFunc ("setLineStyle"sv, [this] (auto var) { impl->setLineStyle (var); },
{"styleOrLineCap"sv, "lineJoin?"sv, "dashLengths?"sv, "dashPhase?"sv});
addFunc ("setDrawMode"sv, [this] (auto var) { impl->setDrawMode (var); }, {"mode"sv});
}
//------------------------------------------------------------------------
DrawContextObject::~DrawContextObject () noexcept
{
if (scriptVar)
scriptVar->setLifeTimeObserver (nullptr);
}
//------------------------------------------------------------------------
void DrawContextObject::setDrawContext (CDrawContext* inContext, IUIDescription* inUIDesc)
{
impl->setContext (inContext, inUIDesc);
}
//------------------------------------------------------------------------
void DrawContextObject::onDestroy (CScriptVar* v)
{
v->setLifeTimeObserver (nullptr);
scriptVar = nullptr;
}
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,103 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "scriptobject.h"
#include "../../lib/vstguifwd.h"
#include "../../uidescription/uidescriptionfwd.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
TJS::CScriptVar* makeTransformMatrixObject ();
ScriptObject makeGraphicsPathScriptObject (const SharedPointer<CGraphicsPath>& p);
//------------------------------------------------------------------------
struct DrawContextObject : ScriptObject,
TJS::IScriptVarLifeTimeObserver
{
DrawContextObject ();
~DrawContextObject () noexcept override;
void setDrawContext (CDrawContext* context, IUIDescription* uiDesc);
void onDestroy (CScriptVar* v) override;
private:
// CanvasRenderingContext2D API
// clang-format off
/*
fillStyle: string | CanvasGradient | CanvasPattern;
filter: string
font: string;
fontKerning: string;
fontStretch: string;
fontVariantCaps: string;
globalAlpha: number;
globalCompositionOperation: string;
lineCap: string;
lineDashOffset: number;
lineJoin: string;
lineWidth: number;
miterLimit: number;
shadowBlur: number;
shadowColor: string;
shadowOffsetX: number;
shadowOffsetY: number;
strokeStyle: string;
textAlign: string;
textBaseline: string;
arc: (x: number, y: number, r: number, sAngle: number, eAngle: number, counterClockwise?: boolean) => void;
arcTo: (x1: number, y1: number, x2: number, y2: number, r: number) => void;
beginPath: () => void;
bezierCurveTo: (cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number) => void;
clearRect: (x: number, y: number, width: number, height: number) => void;
clip: () => void;
closePath: () => void;
createImageData: (width: number, height: number, imageData: ImageData) => void;
createLinearGradient: (x0: number, yo: number, x1: number, y1: number) => CanvasGradient;
createPattern: () => CanvasPattern;
createRadialGradient: (x0: number, y0: number, r0: number, x1: number, y1: number, r1: number) => CanvasGradient;
drawFocusIfNeeded: (html: HTMLElement) => void;
drawImage: (image: Image,dx: number,dy: number,sx?: number,sy?: number,sWidth?: number,sHeight?: number,dWidth?: number,dHeight?: number) => void;
ellipse: (x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean) => void;
fill: (Path2D?: Path2D, fillRule?: any) => void;
fillRect: (x: number, y: number, width: number, height: number) => void;
fillText: (text: string, x: number, y: number, maxWidth?: number) => void;
getImageData: (sx: number, sy: number, sw: number, sh: number) => Promise<ImageData>;
getLineDash: () => number[];
isPointInPath: (x: number, y: number, fillRule: any, path: Path2D) => boolean;
isPointInStroke: (x: number, y: number, path: Path2D) => boolean;
lineTo: (x: number, y: number) => void;
measureText: (text: string) => any;
moveTo: (x: number, y: number) => void;
putImageData: (imageData: ImageData, dx: number, dy: number, dirtyX?: number, dirtyY?: number, dirtyWidth?: number, dirtyHeight?: number) => void;
quadraticCurveTo: (cpx: number, cpy: number, x: number, y: number) => void;
rect: (x: number, y: number, width: number, height: number) => void;
reset: () => void
resetTransform: () => void
restore: () => void;
rotate: (angle: number) => void;
roundRect: (x: number, y: number, width: number, height: number, radii: number) => void
save: () => void;
scale: (x: number, y: number) => void;
setLineDash: (segments: number[]) => void;
setTransform: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
stroke: (path?: Path2D) => void;
strokeRect: (x: number, y: number, width: number, height: number) => void;
strokeText: (text: string, x: number, y: number, maxWidth?: number) => void;
transform: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
translate: (x: number, y: number) => void;
*/
// clang-format on
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,24 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../uiscripting.h"
#include <string>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
static const std::string kAttrScript = "script";
//------------------------------------------------------------------------
struct IScriptContextInternal : public IScriptContext
{
virtual void onViewCreated (CView* view, const std::string& script) = 0;
};
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,133 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "scriptingviewfactory.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
//------------------------------------------------------------------------
JavaScriptViewFactory::JavaScriptViewFactory (IScriptContextInternal* scripting,
IViewFactory* origFactory)
: Super (origFactory), scriptContext (scripting)
{
}
//------------------------------------------------------------------------
JavaScriptViewFactory::~JavaScriptViewFactory () noexcept
{
std::for_each (viewControllerLinks.begin (), viewControllerLinks.end (),
[this] (const auto& el) {
el.first->unregisterViewListener (this);
el.second->scriptContextDestroyed (scriptContext);
});
}
//------------------------------------------------------------------------
CView* JavaScriptViewFactory::createView (const UIAttributes& attributes,
const IUIDescription* description) const
{
if (auto view = Super::createView (attributes, description))
{
if (auto value = attributes.getAttributeValue (kAttrScript))
{
std::optional<std::string> verifiedScript;
if (auto scriptViewController =
dynamic_cast<IScriptControllerExtension*> (description->getController ()))
{
verifiedScript = scriptViewController->verifyScript (view, *value, scriptContext);
view->registerViewListener (const_cast<JavaScriptViewFactory*> (this));
viewControllerLinks.emplace_back (view, scriptViewController);
}
const auto& script = verifiedScript ? *verifiedScript : *value;
auto scriptSize = static_cast<uint32_t> (script.size () + 1);
view->setAttribute (scriptAttrID, scriptSize, script.data ());
if (!disabled)
{
scriptContext->onViewCreated (view, script);
}
}
return view;
}
return {};
}
//------------------------------------------------------------------------
bool JavaScriptViewFactory::getAttributeNamesForView (CView* view, StringList& attributeNames) const
{
if (Super::getAttributeNamesForView (view, attributeNames))
{
attributeNames.emplace_back (kAttrScript);
return true;
}
return false;
}
//------------------------------------------------------------------------
auto JavaScriptViewFactory::getAttributeType (CView* view, const std::string& attributeName) const
-> IViewCreator::AttrType
{
if (attributeName == kAttrScript)
return IViewCreator::kScriptType;
return Super::getAttributeType (view, attributeName);
}
//------------------------------------------------------------------------
bool JavaScriptViewFactory::getAttributeValue (CView* view, const std::string& attributeName,
std::string& stringValue,
const IUIDescription* desc) const
{
if (attributeName == kAttrScript)
{
uint32_t attrSize = 0;
if (view->getAttributeSize (scriptAttrID, attrSize) && attrSize > 0)
{
stringValue.resize (attrSize - 1);
if (!view->getAttribute (scriptAttrID, attrSize, stringValue.data (), attrSize))
stringValue = "";
return true;
}
return false;
}
return Super::getAttributeValue (view, attributeName, stringValue, desc);
}
//------------------------------------------------------------------------
bool JavaScriptViewFactory::applyAttributeValues (CView* view, const UIAttributes& attributes,
const IUIDescription* desc) const
{
if (auto value = attributes.getAttributeValue (kAttrScript))
{
if (value->empty ())
view->removeAttribute (scriptAttrID);
else
view->setAttribute (scriptAttrID, static_cast<uint32_t> (value->size () + 1),
value->data ());
if (!disabled)
scriptContext->onViewCreated (view, *value);
return true;
}
return Super::applyAttributeValues (view, attributes, desc);
}
//------------------------------------------------------------------------
void JavaScriptViewFactory::setScriptingDisabled (bool state) { disabled = state; }
//------------------------------------------------------------------------
void JavaScriptViewFactory::viewWillDelete (CView* view)
{
auto it = std::find_if (viewControllerLinks.begin (), viewControllerLinks.end (),
[view] (const auto& el) { return el.first == view; });
if (it != viewControllerLinks.end ())
{
it->second->scriptContextDestroyed (scriptContext);
viewControllerLinks.erase (it);
}
view->unregisterViewListener (this);
}
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,51 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "iscriptcontextinternal.h"
#include "../../uidescription/uiattributes.h"
#include "../../uidescription/uiviewfactory.h"
#include "../../lib/iviewlistener.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
//------------------------------------------------------------------------
struct JavaScriptViewFactory : ViewFactoryDelegate,
ViewListenerAdapter
{
static constexpr CViewAttributeID scriptAttrID = 'scri';
JavaScriptViewFactory (ScriptingInternal::IScriptContextInternal* scripting,
IViewFactory* origFactory);
~JavaScriptViewFactory () noexcept;
CView* createView (const UIAttributes& attributes,
const IUIDescription* description) const override;
bool getAttributeNamesForView (CView* view, StringList& attributeNames) const override;
IViewCreator::AttrType getAttributeType (CView* view,
const std::string& attributeName) const override;
bool getAttributeValue (CView* view, const std::string& attributeName, std::string& stringValue,
const IUIDescription* desc) const override;
bool applyAttributeValues (CView* view, const UIAttributes& attributes,
const IUIDescription* desc) const override;
void setScriptingDisabled (bool state);
private:
void viewWillDelete (CView* view) override;
using Super = ViewFactoryDelegate;
using ViewControllerLink = std::pair<CView*, IScriptControllerExtension*>;
using ViewControllerLinkVector = std::vector<ViewControllerLink>;
ScriptingInternal::IScriptContextInternal* scriptContext;
mutable ViewControllerLinkVector viewControllerLinks;
bool disabled {false};
};
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,218 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../tiny-js/TinyJS.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
//------------------------------------------------------------------------
struct ScriptAddChildScoped
{
using CScriptVar = TJS::CScriptVar;
using CScriptVarLink = TJS::CScriptVarLink;
ScriptAddChildScoped (CScriptVar& var, std::string_view name, CScriptVar* obj) : var (var)
{
if ((link = var.findChild (name)))
{
oldVar = TJS::owning (link->getVar ());
link->setVar (obj);
}
else
{
link = var.addChild (name, obj);
}
}
~ScriptAddChildScoped () noexcept
{
if (oldVar && link)
{
link->setVar (oldVar);
oldVar->release ();
}
else if (link)
{
var.removeLink (link);
}
}
private:
CScriptVar& var;
CScriptVarLink* link {nullptr};
CScriptVar* oldVar {nullptr};
};
//------------------------------------------------------------------------
inline TJS::CScriptVar* createJSFunction (TJS::JSCallback&& proc)
{
auto funcVar = new TJS::CScriptVar (TJS::TINYJS_BLANK_DATA,
TJS::SCRIPTVAR_FUNCTION | TJS::SCRIPTVAR_NATIVE);
funcVar->setCallback (std::move (proc));
return funcVar;
}
//------------------------------------------------------------------------
inline TJS::CScriptVar* createJSFunction (TJS::JSCallback&& proc,
const std::initializer_list<std::string_view>& argNames)
{
auto f = createJSFunction (std::move (proc));
for (auto name : argNames)
f->addChildNoDup (name);
return f;
}
//------------------------------------------------------------------------
inline TJS::CScriptVar* getArgument (TJS::CScriptVar* var, std::string_view argName,
std::string_view funcSignature)
{
auto child = var->findChild (argName);
auto result = child ? child->getVar () : nullptr;
if (!result || result->isUndefined ())
{
TJS::string s ("Missing `");
s.append (argName);
s.append ("` argument in ");
s.append (funcSignature);
throw TJS::CScriptException (std::move (s));
}
return result;
}
//------------------------------------------------------------------------
inline TJS::CScriptVar* getOptionalArgument (TJS::CScriptVar* var, std::string_view argName)
{
if (auto child = var->findChild (argName))
return child->getVar ();
return nullptr;
}
//------------------------------------------------------------------------
struct ScriptObject
{
using CScriptVar = TJS::CScriptVar;
ScriptObject () { scriptVar = TJS::owning (new CScriptVar ()); }
ScriptObject (CScriptVar* var) : scriptVar (TJS::owning (var)) {}
ScriptObject (ScriptObject&& o) { *this = std::move (o); }
ScriptObject& operator= (ScriptObject&& o)
{
if (scriptVar)
scriptVar->release ();
scriptVar = nullptr;
std::swap (scriptVar, o.scriptVar);
return *this;
}
virtual ~ScriptObject () noexcept
{
if (scriptVar)
{
scriptVar->release ();
}
}
operator CScriptVar* () const
{
validate ();
return scriptVar;
}
CScriptVar* operator->() const
{
validate ();
return scriptVar;
}
CScriptVar* getVar () const
{
validate ();
return scriptVar;
}
CScriptVar* take ()
{
auto v = scriptVar;
scriptVar = nullptr;
return v;
}
void addChild (std::string_view name, ScriptObject&& obj)
{
validate ();
scriptVar->addChild (name, obj);
}
void addChild (std::string_view name, double d)
{
validate ();
scriptVar->addChild (name, new CScriptVar (d));
}
void addChild (std::string_view name, int64_t i)
{
validate ();
scriptVar->addChild (name, new CScriptVar (i));
}
void addChild (std::string_view name, int32_t i)
{
validate ();
scriptVar->addChild (name, new CScriptVar (static_cast<int64_t> (i)));
}
void addChild (std::string_view name, std::string_view value)
{
validate ();
scriptVar->addChild (name, new CScriptVar (TJS::string {value.data (), value.size ()}));
}
void addFunc (std::string_view name, std::function<void (CScriptVar*)>&& func)
{
validate ();
scriptVar->addChild (name, createJSFunction (std::move (func)));
}
void addFunc (std::string_view name, std::function<void (CScriptVar*)>&& func,
const std::initializer_list<std::string_view>& argNames)
{
validate ();
scriptVar->addChild (name, createJSFunction (std::move (func), argNames));
}
void addFunc (std::string_view name, std::string_view code)
{
validate ();
auto funcVar = new TJS::CScriptVar (TJS::TINYJS_BLANK_DATA, TJS::SCRIPTVAR_FUNCTION);
funcVar->setFunctionScript (code);
scriptVar->addChild (name, funcVar);
}
using OnDestroyFunc = std::function<void (CScriptVar*)>;
void setOnDestroy (OnDestroyFunc&& f)
{
validate ();
scriptVar->setLifeTimeObserver (OnDestroy::make (std::move (f)));
}
protected:
struct OnDestroy : TJS::IScriptVarLifeTimeObserver
{
static OnDestroy* make (OnDestroyFunc f) { return new OnDestroy (std::move (f)); }
private:
OnDestroy (OnDestroyFunc f) : func (std::move (f)) {}
void onDestroy (CScriptVar* var)
{
func (var);
delete this;
}
OnDestroyFunc func;
};
void validate () const
{
if (scriptVar == nullptr)
scriptVar = TJS::owning (new CScriptVar ());
}
mutable CScriptVar* scriptVar {nullptr};
};
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,97 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "scriptobject.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
//------------------------------------------------------------------------
struct UIDescScriptObject : ScriptObject
{
using StringList = std::list<const std::string*>;
using CScriptException = TJS::CScriptException;
UIDescScriptObject () = default;
UIDescScriptObject (IUIDescription* desc, TJS::CTinyJS* scriptContext)
{
using namespace std::literals;
addFunc ("colorNames"sv, [desc] (CScriptVar* var) {
StringList names;
desc->collectColorNames (names);
var->setReturnVar (createArrayFromNames (names));
});
addFunc ("fontNames"sv, [desc] (CScriptVar* var) {
StringList names;
desc->collectFontNames (names);
var->setReturnVar (createArrayFromNames (names));
});
addFunc ("bitmapNames"sv, [desc] (CScriptVar* var) {
StringList names;
desc->collectBitmapNames (names);
var->setReturnVar (createArrayFromNames (names));
});
addFunc ("gradientNames"sv, [desc] (CScriptVar* var) {
StringList names;
desc->collectGradientNames (names);
var->setReturnVar (createArrayFromNames (names));
});
addFunc ("controlTagNames"sv, [desc] (CScriptVar* var) {
StringList names;
desc->collectControlTagNames (names);
var->setReturnVar (createArrayFromNames (names));
});
addFunc ("getTagForName"sv,
[desc] (CScriptVar* var) {
auto param = var->getParameter ("name"sv);
if (!param)
{
throw CScriptException ("Expect 'name' argument for getTagForName ");
}
auto name = param->getString ();
auto tag = desc->getTagForName (name.data ());
var->setReturnVar (new CScriptVar (static_cast<int64_t> (tag)));
},
{"name"});
addFunc ("lookupTagName"sv,
[desc] (CScriptVar* var) {
auto param = var->getParameter ("tag"sv);
if (!param)
{
throw CScriptException ("Expect 'tag' argument for lookupTagName ");
}
if (!param->isInt ())
{
throw CScriptException ("Expect 'tag' argument to be an integer ");
}
if (auto tagName =
desc->lookupControlTagName (static_cast<int32_t> (param->getInt ())))
{
var->setReturnVar (new CScriptVar (std::string (tagName)));
}
},
{"tag"});
}
static CScriptVar* createArrayFromNames (const StringList& names)
{
auto array = new CScriptVar ();
array->setArray ();
int index = 0;
for (auto name : names)
{
array->addChild (std::to_string (index), new CScriptVar (*name));
++index;
}
return array;
}
};
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,209 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "viewscriptobject.h"
#include "converters.h"
#include "drawable.h"
#include "../uiscripting.h"
#include "../../uidescription/iviewfactory.h"
#include "../../uidescription/uiattributes.h"
#include "../../lib/cview.h"
#include "../../lib/controls/ccontrol.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
using namespace std::literals;
using namespace TJS;
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
ViewScriptObject::ViewScriptObject (CView* view, IViewScriptObjectContext* context)
: view (view), context (context)
{
scriptVar->setLifeTimeObserver (this);
auto viewType = IViewFactory::getViewName (view);
scriptVar->addChild ("type"sv, new CScriptVar (std::string (viewType ? viewType : "unknown")));
addFunc ("setAttribute"sv,
[uiDesc = context->getUIDescription (), view] (CScriptVar* var) {
auto key = var->getParameter ("key"sv);
auto value = var->getParameter ("value"sv);
UIAttributes attr;
attr.setAttribute (key->getString ().data (), value->getString ().data ());
auto result = uiDesc->getViewFactory ()->applyAttributeValues (view, attr, uiDesc);
var->getReturnVar ()->setInt (result);
},
{"key", "value"});
addFunc ("getAttribute"sv,
[uiDesc = context->getUIDescription (), view] (CScriptVar* var) {
auto key = var->getParameter ("key"sv);
std::string result;
if (uiDesc->getViewFactory ()->getAttributeValue (view, key->getString ().data (),
result, uiDesc))
{
var->getReturnVar ()->setString (result);
}
else
{
var->getReturnVar ()->setUndefined ();
}
},
{"key"});
addFunc ("isTypeOf"sv,
[uiDesc = context->getUIDescription (), view] (CScriptVar* var) {
auto typeName = var->getParameter ("typeName"sv);
auto result =
uiDesc->getViewFactory ()->viewIsTypeOf (view, typeName->getString ().data ());
var->getReturnVar ()->setInt (result);
},
{"typeName"});
addFunc ("invalid"sv, [view] (CScriptVar* var) { view->invalid (); });
addFunc ("invalidRect"sv,
[view] (CScriptVar* var) {
auto rectVar = var->getParameter ("rect"sv);
if (!rectVar)
throw CScriptException ("Missing 'rect' argument in view.invalidRect(rect) ");
auto rect = fromScriptRect (*rectVar);
view->invalidRect (rect);
},
{"rect"});
addFunc ("getBounds"sv, [view] (CScriptVar* var) {
auto bounds = view->getViewSize ();
bounds.originize ();
var->setReturnVar (makeScriptRect (bounds));
});
addFunc ("getParent"sv, [view, context] (CScriptVar* var) {
auto parentView = view->getParentView ();
if (!parentView)
{
var->getReturnVar ()->setUndefined ();
return;
}
auto obj = context->addView (parentView);
vstgui_assert (obj);
var->setReturnVar (obj->getVar ());
obj->getVar ()->release ();
});
addFunc ("getControllerProperty"sv,
[view] (CScriptVar* var) {
auto viewController = getViewController (view, true);
auto controller = dynamic_cast<IScriptControllerExtension*> (viewController);
auto name = var->getParameter ("name"sv);
if (!controller || !name)
{
var->getReturnVar ()->setUndefined ();
return;
}
IScriptControllerExtension::PropertyValue value;
if (!controller->getProperty (view, name->getString (), value))
{
var->getReturnVar ()->setUndefined ();
return;
}
std::visit (
[&] (auto&& value) {
using T = std::decay_t<decltype (value)>;
if constexpr (std::is_same_v<T, int64_t>)
var->getReturnVar ()->setInt (value);
else if constexpr (std::is_same_v<T, double>)
var->getReturnVar ()->setDouble (value);
else if constexpr (std::is_same_v<T, std::string>)
var->getReturnVar ()->setString (value);
else if constexpr (std::is_same_v<T, std::nullptr_t>)
var->getReturnVar ()->setUndefined ();
},
value);
},
{"name"});
addFunc ("setControllerProperty"sv,
[view] (CScriptVar* var) {
auto viewController = getViewController (view, true);
auto controller = dynamic_cast<IScriptControllerExtension*> (viewController);
auto name = var->getParameter ("name"sv);
auto value = var->getParameter ("value"sv);
if (!controller || !name || !value || !(value->isNumeric () || value->isString ()))
{
var->getReturnVar ()->setUndefined ();
return;
}
IScriptControllerExtension::PropertyValue propValue;
if (value->isInt ())
propValue = value->getInt ();
else if (value->isDouble ())
propValue = value->getDouble ();
else if (value->isString ())
propValue = value->getString ().data ();
auto result = controller->setProperty (view, name->getString (), propValue);
var->getReturnVar ()->setInt (result);
},
{"name", "value"});
if (auto control = dynamic_cast<CControl*> (view))
{
addFunc ("setValue"sv,
[control] (CScriptVar* var) {
auto value = var->getParameter ("value"sv);
if (value->isNumeric ())
{
auto oldValue = control->getValue ();
control->setValue (static_cast<float> (value->getDouble ()));
if (oldValue != control->getValue ())
control->valueChanged ();
}
},
{"value"});
addFunc ("getValue"sv, [control] (CScriptVar* var) {
var->getReturnVar ()->setDouble (control->getValue ());
});
addFunc ("setValueNormalized"sv,
[control] (CScriptVar* var) {
auto value = var->getParameter ("value"sv);
if (value->isNumeric ())
{
auto oldValue = control->getValue ();
control->setValueNormalized (static_cast<float> (value->getDouble ()));
if (oldValue != control->getValue ())
control->valueChanged ();
}
},
{"value"});
addFunc ("getValueNormalized"sv, [control] (CScriptVar* var) {
var->getReturnVar ()->setDouble (control->getValueNormalized ());
});
addFunc ("beginEdit"sv, [control] (CScriptVar* var) { control->beginEdit (); });
addFunc ("endEdit"sv, [control] (CScriptVar* var) { control->endEdit (); });
addFunc ("getMinValue"sv, [control] (CScriptVar* var) {
var->getReturnVar ()->setDouble (control->getMin ());
});
addFunc ("getMaxValue"sv, [control] (CScriptVar* var) {
var->getReturnVar ()->setDouble (control->getMax ());
});
addFunc ("getTag"sv, [control] (CScriptVar* var) {
var->getReturnVar ()->setInt (control->getTag ());
});
}
if (auto drawable = dynamic_cast<JavaScriptDrawable*> (view))
drawable->setup (this);
}
//------------------------------------------------------------------------
ViewScriptObject::~ViewScriptObject () noexcept
{
if (scriptVar)
scriptVar->setLifeTimeObserver (nullptr);
}
//------------------------------------------------------------------------
void ViewScriptObject::onDestroy (CScriptVar* v)
{
v->setLifeTimeObserver (nullptr);
scriptVar = nullptr;
if (context)
context->removeView (view);
}
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,51 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "scriptobject.h"
#include "../../lib/vstguifwd.h"
#include "../../uidescription/iuidescription.h"
#include <unordered_map>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace ScriptingInternal {
struct IViewScriptObjectContext;
//------------------------------------------------------------------------
struct ViewScriptObject : ScriptObject,
TJS::IScriptVarLifeTimeObserver
{
ViewScriptObject (CView* view, IViewScriptObjectContext* context);
~ViewScriptObject () noexcept;
IViewScriptObjectContext* getContext () const { return context; }
void onDestroy (CScriptVar* v) override;
private:
CView* view {nullptr};
IViewScriptObjectContext* context {nullptr};
};
using ViewScriptMap = std::unordered_map<CView*, std::unique_ptr<ViewScriptObject>>;
//------------------------------------------------------------------------
struct IViewScriptObjectContext
{
virtual ~IViewScriptObjectContext () = default;
virtual IUIDescription* getUIDescription () const = 0;
virtual ViewScriptObject* addView (CView* view) = 0;
virtual ViewScriptMap::iterator removeView (CView* view) = 0;
virtual ScriptObject evalScript (std::string_view script) noexcept = 0;
virtual TJS::CScriptVar* getRoot () const = 0;
};
//------------------------------------------------------------------------
} // ScriptingInternal
} // VSTGUI
@@ -0,0 +1,3 @@
.vscode/
build/
build.*/
@@ -0,0 +1,45 @@
project (tiny-js)
cmake_minimum_required (VERSION 2.6)
set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
if (NOT CMAKE_BUILD_TYPE)
set (CMAKE_BUILD_TYPE "Debug")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
else()
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif(NOT CMAKE_BUILD_TYPE)
if (NOT WIN32)
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++14" COMPILER_SUPPORTS_CXX14)
if(COMPILER_SUPPORTS_CXX14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall")
else()
message(FATAL_ERROR "Compiler ${CMAKE_CXX_COMPILER} has no C++14 support.")
endif()
endif(NOT WIN32)
FILE(GLOB TINY_JS_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/TinyJS.h
)
FILE(GLOB TINY_JS_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/TinyJS.cpp
${CMAKE_CURRENT_LIST_DIR}/TinyJS_Functions.cpp
${CMAKE_CURRENT_LIST_DIR}/TinyJS_MathFunctions.cpp
)
add_library(tiny-js STATIC ${TINY_JS_HEADER_FILES} ${TINY_JS_SOURCE_FILES})
ADD_EXECUTABLE(tiny-js-cli Script.cpp ${TINY_JS_SOURCE_FILES})
ADD_EXECUTABLE(tiny-js-tests run_tests.cpp ${TINY_JS_SOURCE_FILES})
add_custom_command(
TARGET tiny-js-tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/tests
${CMAKE_CURRENT_BINARY_DIR}/tests)
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Gordon Williams
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,24 @@
CC=g++
CFLAGS=-c -g -Wall -rdynamic -D_DEBUG
LDFLAGS=-g -rdynamic
SOURCES= \
TinyJS.cpp \
TinyJS_Functions.cpp \
TinyJS_MathFunctions.cpp
OBJECTS=$(SOURCES:.cpp=.o)
all: run_tests Script
run_tests: run_tests.o $(OBJECTS)
$(CC) $(LDFLAGS) run_tests.o $(OBJECTS) -o $@
Script: Script.o $(OBJECTS)
$(CC) $(LDFLAGS) Script.o $(OBJECTS) -o $@
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
clean:
rm -f run_tests Script run_tests.o Script.o $(OBJECTS)
@@ -0,0 +1,53 @@
tiny-js
=======
(originally [on Google Code](https://code.google.com/p/tiny-js/))
This project aims to be an extremely simple (~2000 line) JavaScript interpreter, meant for
inclusion in applications that require a simple, familiar script language that can be included
with no dependencies other than normal C++ libraries. It currently consists of two source files:
one containing the interpreter, another containing built-in functions such as String.substring.
TinyJS is not designed to be fast or full-featured. However it is great for scripting simple
behaviour, or loading & saving settings.
I make absolutely no guarantees that this is compliant to JavaScript/EcmaScript standard.
In fact I am sure it isn't. However I welcome suggestions for changes that will bring it
closer to compliance without overly complicating the code, or useful test cases to add to
the test suite.
Currently TinyJS supports:
* Variables, Arrays, Structures
* JSON parsing and output
* Functions
* Calling C/C++ code from JavaScript
* Objects with Inheritance (not fully implemented)
Please see [CodeExamples](https://github.com/gfwilliams/tiny-js/blob/wiki/CodeExamples.md) for examples of code that works...
For a list of known issues, please see the comments at the top of the TinyJS.cpp file, as well as the [GitHub issues](https://github.com/gfwilliams/tiny-js/issues)
There is also the [42tiny-js branch](https://github.com/gfwilliams/tiny-js/tree/42tiny-js) - this is maintained by Armin and provides a more full-featured JavaScript implementation than GitHub master.
TinyJS is released under an MIT licence.
Internal Structure
------------------------
TinyJS uses a Recursive Descent Parser, so there is no 'Parser Generator' required. It does not
compile to an intermediate code, and instead executes directly from source code. This makes it
quite fast for code that is executed infrequently, and slow for loops.
Variables, Arrays and Objects are stored in a simple linked list tree structure (42tiny-js uses a C++ Map).
This is simple, but relatively slow for large structures or arrays.
JavaScript for Microcontrollers
--------------------------------
If you're after JavaScript for Microcontrollers, take a look at the
[Espruino JavaScript Interpreter](http://www.espruino.com ) - it is a complete re-write of TinyJS
targeted at processors with extremely low RAM (8kb or more). It is currently available for a range
of STM32 ARM Microcontrollers, including [two boards that have it pre-installed](http://www.espruino.com/Order).
@@ -0,0 +1,95 @@
/*
* TinyJS
*
* A single-file Javascript-alike engine
*
* Authored By Gordon Williams <gw@pur3.co.uk>
*
* Copyright (C) 2009 Pur3 Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* This is a simple program showing how to use TinyJS
*/
#include "TinyJS.h"
#include "TinyJS_Functions.h"
#include <assert.h>
#include <stdio.h>
// const char *code = "var a = 5; if (a==5) a=4; else a=3;";
// const char *code = "{ var a = 4; var b = 1; while (a>0) { b = b * 2; a = a - 1; } var c = 5; }";
// const char *code = "{ var b = 1; for (var i=0;i<4;i=i+1) b = b * 2; }";
const char* code = "function myfunc(x, y) { return x + y; } var a = myfunc(1,2); print(a);";
void js_print (CScriptVar* v, void* userdata)
{
printf ("> %s\n", v->getParameter ("text")->getString ().c_str ());
}
void js_dump (CScriptVar* v, void* userdata)
{
CTinyJS* js = (CTinyJS*)userdata;
js->root->trace ("> ");
}
int main (int argc, char** argv)
{
CTinyJS* js = new CTinyJS ();
/* add the functions from TinyJS_Functions.cpp */
registerFunctions (js);
/* Add a native function */
js->addNative ("function print(text)", &js_print, 0);
js->addNative ("function dump()", &js_dump, js);
/* Execute out bit of code - we could call 'evaluate' here if
we wanted something returned */
try
{
js->execute ("var lets_quit = 0; function quit() { lets_quit = 1; }");
js->execute ("print(\"Interactive mode... Type quit(); to exit, or print(...); to print "
"something, or dump() to dump the symbol table!\");");
}
catch (CScriptException* e)
{
printf ("ERROR: %s\n", e->text.c_str ());
}
while (js->evaluate ("lets_quit") == "0")
{
char buffer[2048];
fgets (buffer, sizeof (buffer), stdin);
try
{
js->execute (buffer);
}
catch (CScriptException* e)
{
printf ("ERROR: %s\n", e->text.c_str ());
}
}
delete js;
#ifdef _WIN32
#ifdef _DEBUG
_CrtDumpMemoryLeaks ();
#endif
#endif
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,526 @@
/*
* TinyJS
*
* A single-file Javascript-alike engine
*
* Authored By Gordon Williams <gw@pur3.co.uk>
*
* Copyright (C) 2009 Pur3 Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
// If defined, this keeps a note of all calls and where from in memory. This is slower, but good for
// debugging
#define TINYJS_CALL_STACK
#include <string>
#include <vector>
#include <functional>
#include <variant>
#include <limits>
#include <any>
#ifndef TRACE
#define TRACE printf
#endif // TRACE
//------------------------------------------------------------------------
namespace TJS {
constexpr int TINYJS_LOOP_MAX_ITERATIONS = 8192;
enum class LexType : int
{
Eof = 0,
ID = 256,
INT,
FLOAT,
STR,
EQUAL,
TYPEEQUAL,
NEQUAL,
NTYPEEQUAL,
LEQUAL,
LSHIFT,
LSHIFTEQUAL,
GEQUAL,
RSHIFT,
RSHIFTUNSIGNED,
RSHIFTEQUAL,
PLUSEQUAL,
MINUSEQUAL,
PLUSPLUS,
MINUSMINUS,
ANDEQUAL,
ANDAND,
OREQUAL,
OROR,
XOREQUAL,
// reserved words
R_LIST_START,
R_IF = R_LIST_START,
R_ELSE,
R_DO,
R_WHILE,
R_FOR,
R_BREAK,
R_CONTINUE,
R_FUNCTION,
R_RETURN,
R_VAR,
R_TRUE,
R_FALSE,
R_NULL,
R_UNDEFINED,
R_NEW,
LIST_END /* always the last entry */
};
inline constexpr int asInteger (LexType t) { return static_cast<int> (t); }
inline constexpr LexType asLexType (int t)
{
if (t < asInteger (LexType::ID) || t > (asInteger (LexType::LIST_END)))
return LexType::Eof;
return static_cast<LexType> (t);
}
enum SCRIPTVAR_FLAGS
{
SCRIPTVAR_UNDEFINED = 0,
SCRIPTVAR_FUNCTION = 1,
SCRIPTVAR_OBJECT = 2,
SCRIPTVAR_ARRAY = 4,
SCRIPTVAR_DOUBLE = 8, // floating point double
SCRIPTVAR_INTEGER = 16, // integer number
SCRIPTVAR_STRING = 32, // string
SCRIPTVAR_NULL = 64, // it seems null is its own data type
SCRIPTVAR_NATIVE = 128, // to specify this is a native function
SCRIPTVAR_NUMERICMASK = SCRIPTVAR_NULL | SCRIPTVAR_DOUBLE | SCRIPTVAR_INTEGER,
SCRIPTVAR_VARTYPEMASK = SCRIPTVAR_DOUBLE | SCRIPTVAR_INTEGER | SCRIPTVAR_STRING |
SCRIPTVAR_FUNCTION | SCRIPTVAR_OBJECT | SCRIPTVAR_ARRAY |
SCRIPTVAR_NULL,
};
static constexpr auto TINYJS_RETURN_VAR = "return";
static constexpr auto TINYJS_PROTOTYPE_CLASS = "prototype";
static constexpr auto TINYJS_TEMP_NAME = "";
static constexpr auto TINYJS_BLANK_DATA = "";
//------------------------------------------------------------------------
// Custom memory allocator
using AllocatorFunc = std::function<void*(size_t)>;
using DeallocatorFunc = std::function<void (void*, size_t)>;
extern AllocatorFunc allocator;
extern DeallocatorFunc deallocator;
void setCustomAllocator (AllocatorFunc&& allocator, DeallocatorFunc&& deallocator);
//------------------------------------------------------------------------
template<typename T>
struct Allocator
{
using value_type = T;
using propagate_on_container_move_assignment = std::true_type;
Allocator () = default;
template<class U>
constexpr Allocator (const Allocator<U>&) noexcept
{
}
[[nodiscard]] T* allocate (std::size_t n)
{
return static_cast<T*> (allocator (n * sizeof (T)));
}
void deallocate (T* p, std::size_t n) noexcept { deallocator (p, n); }
bool operator== (const Allocator& other) const { return &other == this; }
bool operator!= (const Allocator& other) const { return &other != this; }
};
using string = std::basic_string<char, std::char_traits<char>, Allocator<char>>;
using ostringstream = std::basic_ostringstream<char, std::char_traits<char>, Allocator<char>>;
/** convert the given string into a quoted string suitable for javascript */
string getJSString (std::string_view str);
/** convert the given string to an 64 bit integer supporting hex and octal written numbers */
int64_t stringToInteger (std::string_view str);
class CScriptException
{
public:
string text;
CScriptException (const string& exceptionText);
CScriptException (string&& exceptionText);
CScriptException (const CScriptException&) = default;
CScriptException (CScriptException&&) = default;
~CScriptException () noexcept;
static void* operator new (std::size_t count);
static void operator delete (void* ptr, std::size_t size);
};
class CScriptLex
{
public:
CScriptLex (std::string_view input);
~CScriptLex (void);
/** Get the string representation of the given token */
static string getTokenStr (int token);
/** Lexical match wotsit */
void match (int expected_tk);
void match (LexType expected_tk);
/** Reset this lex so we can start again */
void reset ();
int getToken () const { return token; }
size_t getTokenStart () const { return tokenStart; }
size_t getTokenEnd () const { return tokenEnd; }
const string& getTokenString () const { return tkStr; }
/** Return a sub-string from the given position up until right now */
string getSubString (size_t pos) const;
/** Return a sub-lexer from the given position up until right now */
CScriptLex* getSubLex (size_t lastPosition) const;
/** Return a string representing the position in lines and columns of the character pos given */
string getPosition (size_t pos = std::numeric_limits<size_t>::max ()) const;
static void* operator new (std::size_t count);
static void operator delete (void* ptr, std::size_t size);
private:
void getNextCh ();
/** Get the text token from our text string */
void getNextToken ();
/** The type of the token that we have */
int token;
/** Position in the data at the beginning of the token we have here */
size_t tokenStart;
/** Position in the data at the last character of the token we have here */
size_t tokenEnd;
/** Position in the data at the last character of the last token */
size_t tokenLastEnd;
/** Data contained in the token we have here */
string tkStr;
char currCh, nextCh;
/** Data string to get tokens from */
const char* data;
/** Start and end position in data string */
size_t dataEnd;
/** Position in data (we CAN go past the end of the string here) */
size_t dataPos;
std::vector<size_t, Allocator<size_t>> newLinePositions;
};
class CScriptVar;
using JSCallback = std::function<void (CScriptVar* var)>;
class CScriptVarLink
{
public:
CScriptVarLink (CScriptVar* var, const string& name = TINYJS_TEMP_NAME, bool own = false);
/** Copy constructor */
CScriptVarLink (const CScriptVarLink& link);
~CScriptVarLink ();
/** Replace the Variable pointed to */
void replaceWith (CScriptVar* newVar);
/** Replace the Variable pointed to (just dereferences) */
void replaceWith (CScriptVarLink* newVar);
/** Get the name as an integer (for arrays) */
int getIntName () const;
/** Set the name as an integer (for arrays) */
void setIntName (int n);
const string& getName () const { return name; }
void setNextSibling (CScriptVarLink* s) { nextSibling = s; }
void setPrevSibling (CScriptVarLink* s) { prevSibling = s; }
CScriptVarLink* getNextSibling () const { return nextSibling; }
CScriptVarLink* getPrevSibling () const { return prevSibling; }
void setVar (CScriptVar* v);
CScriptVar* getVar () const { return var; }
bool owned () const { return isOwned; }
static void* operator new (std::size_t count);
static void operator delete (void* ptr, std::size_t size);
private:
string name;
CScriptVarLink* nextSibling {nullptr};
CScriptVarLink* prevSibling {nullptr};
CScriptVar* var {nullptr};
bool isOwned {false};
};
struct IScriptVarLifeTimeObserver
{
virtual ~IScriptVarLifeTimeObserver () noexcept = default;
virtual void onDestroy (CScriptVar* var) = 0;
};
/** Variable class (containing a doubly-linked list of children) */
class CScriptVar
{
public:
/** Create undefined */
CScriptVar ();
/** User defined */
CScriptVar (const string& varData, int varFlags);
/** Create a string */
CScriptVar (std::string_view str);
/** Create a double */
CScriptVar (double varData);
/** Create an integer */
CScriptVar (int64_t val);
/** Create an integer */
CScriptVar (bool val);
virtual ~CScriptVar (void);
/** If this is a function, get the result value (for use by native functions) */
CScriptVar* getReturnVar ();
/** Set the result value. Use this when setting complex return data as it avoids a deepCopy() */
void setReturnVar (CScriptVar* var);
/** If this is a function, get the parameter with the given name (for use by native functions)
*/
CScriptVar* getParameter (std::string_view name);
/** Tries to find a child with the given name, may return 0 */
CScriptVarLink* findChild (std::string_view childName);
/** Tries to find a child with the given name, or will create it with the given flags */
CScriptVarLink* findChildOrCreate (std::string_view childName,
int varFlags = SCRIPTVAR_UNDEFINED);
/** Tries to find a child with the given path (separated by dots) */
CScriptVarLink* findChildOrCreateByPath (const string& path);
/** add a child if not already exist */
CScriptVarLink* addChild (std::string_view childName, CScriptVar* child = NULL);
/** add a child overwriting any with the same name */
CScriptVarLink* addChildNoDup (std::string_view childName, CScriptVar* child = NULL);
/** remove the child */
void removeChild (CScriptVar* child);
/** Remove a specific link (this is faster than finding via a child) */
void removeLink (CScriptVarLink* link);
void removeAllChildren ();
/** The the value at an array index */
CScriptVar* getArrayIndex (int idx);
/** Set the value at an array index */
void setArrayIndex (int idx, CScriptVar* value);
/** If this is an array, return the number of items in it (else 0) */
int getArrayLength ();
/** Get the number of children */
int getChildren ();
int64_t getInt ();
bool getBool () { return getInt () != 0; }
double getDouble ();
const string& getString ();
/** get Data as a parsable javascript string */
string getParsableString ();
void setInt (int64_t num);
void setDouble (double val);
void setString (std::string_view str);
void setUndefined ();
void setArray ();
bool equals (CScriptVar* v);
bool isInt () { return (flags & SCRIPTVAR_INTEGER) != 0; }
bool isDouble () { return (flags & SCRIPTVAR_DOUBLE) != 0; }
bool isString () { return (flags & SCRIPTVAR_STRING) != 0; }
bool isNumeric () { return (flags & SCRIPTVAR_NUMERICMASK) != 0; }
bool isFunction () { return (flags & SCRIPTVAR_FUNCTION) != 0; }
bool isObject () { return (flags & SCRIPTVAR_OBJECT) != 0; }
bool isArray () { return (flags & SCRIPTVAR_ARRAY) != 0; }
bool isNative () { return (flags & SCRIPTVAR_NATIVE) != 0; }
bool isUndefined () { return (flags & SCRIPTVAR_VARTYPEMASK) == SCRIPTVAR_UNDEFINED; }
bool isNull () { return (flags & SCRIPTVAR_NULL) != 0; }
/** Is this *not* an array/object/etc */
bool isBasic () { return firstChild == 0; }
/** do a maths op with another script variable */
CScriptVar* mathsOp (CScriptVar* b, int op);
/** copy the value from the value given */
void copyValue (CScriptVar* val);
/** deep copy this node and return the result */
CScriptVar* deepCopy ();
/** Dump out the contents of this using trace */
void trace (string indentStr = "", const string& name = "");
/** For debugging - just dump a string version of the flags */
string getFlagsAsString ();
/** Write out all the JS code needed to recreate this script variable to the stream (as JSON) */
void getJSON (std::ostream& destination, const string linePrefix = "");
/** Set the callback for native functions */
void setCallback (const JSCallback& callback);
/** Moves in the callback for native functions */
void setCallback (JSCallback&& callback);
void callCallback (CScriptVar* var);
void setFunctionScript (std::string_view str);
/// For memory management/garbage collection
/** Add reference to this variable */
CScriptVar* addRef ();
/** Remove a reference, and delete this variable if required */
void release ();
/** Get the number of references to this script variable */
int getRefs ();
void setLifeTimeObserver (IScriptVarLifeTimeObserver* obs) { lifeTimeObserver = obs; }
CScriptVarLink* getFirstChild () const { return firstChild; }
CScriptVarLink* getLastChild () const { return lastChild; }
void setCustomData (std::any&& cd) { customData = std::move (cd); }
const std::any& getCustomData () const { return customData; }
static void* operator new (std::size_t count);
static void operator delete (void* ptr, std::size_t size);
protected:
CScriptVarLink* firstChild {nullptr};
CScriptVarLink* lastChild {nullptr};
/** The number of references held to this - used for garbage collection */
int refs {0};
/** the flags determine the type of the variable - int/double/string/etc */
int flags {0};
std::variant<string, int64_t, double, JSCallback> variant;
std::any customData;
string dataStr;
/** Copy the basic data and flags from the variable given, with no
* children. Should be used internally only - by copyValue and deepCopy */
void copySimpleData (CScriptVar* val);
private:
IScriptVarLifeTimeObserver* lifeTimeObserver {nullptr};
};
inline CScriptVar* owning (CScriptVar* v) { return v->addRef (); }
class CTinyJS
{
public:
CTinyJS ();
~CTinyJS ();
void execute (const string& code);
/** Evaluate the given code and return a link to a javascript object,
* useful for (dangerous) JSON parsing. If nothing to return, will return
* 'undefined' variable type. CScriptVarLink is returned as this will
* automatically release the result as it goes out of scope. If you want to
* keep it, you must use addRef() and release() */
CScriptVarLink evaluateComplex (std::string_view code);
/** Evaluate the given code and return a string. If nothing to return, will return
* 'undefined' */
string evaluate (std::string_view code);
/** add a native function to be called from TinyJS
example:
\code
void scRandInt(CScriptVar *c, void *userdata) { ... }
tinyJS->addNative("function randInt(min, max)", scRandInt, 0);
\endcode
or
\code
void scSubstring(CScriptVar *c, void *userdata) { ... }
tinyJS->addNative("function String.substring(lo, hi)", scSubstring, 0);
\endcode
*/
void addNative (std::string_view funcDesc, const JSCallback& ptr);
/** Get the given variable specified by a path (var1.var2.etc), or return 0 */
CScriptVar* getScriptVariable (const string& path) const;
/** Get the value of the given variable, or return 0 */
const string* getVariable (const string& path) const;
/** set the value of the given variable, return trur if it exists and gets set */
bool setVariable (const string& path, const string& varData);
/** Send all variables to stdout */
void trace ();
CScriptVar* getRoot () const { return root; }
static void* operator new (std::size_t count);
static void operator delete (void* ptr, std::size_t size);
private:
/** root of symbol table */
CScriptVar* root {nullptr};
/** current lexer */
CScriptLex* lexer {nullptr};
/** stack of scopes when parsing */
std::vector<CScriptVar*> scopes;
#ifdef TINYJS_CALL_STACK
/** Names of places called so we can show when erroring */
std::vector<string> call_stack;
#endif
/** Built in string class */
CScriptVar* stringClass {nullptr};
/** Built in object class */
CScriptVar* objectClass {nullptr};
/** Built in array class */
CScriptVar* arrayClass {nullptr};
// parsing - in order of precedence
CScriptVarLink* functionCall (bool& execute, CScriptVarLink* function, CScriptVar* parent);
CScriptVarLink* factor (bool& execute);
CScriptVarLink* unary (bool& execute);
CScriptVarLink* term (bool& execute);
CScriptVarLink* expression (bool& execute);
CScriptVarLink* shift (bool& execute);
CScriptVarLink* condition (bool& execute);
CScriptVarLink* logic (bool& execute);
CScriptVarLink* ternary (bool& execute);
CScriptVarLink* base (bool& execute);
void block (bool& execute);
void statement (bool& execute);
// parsing utility functions
CScriptVarLink* parseFunctionDefinition ();
void parseFunctionArguments (CScriptVar* funcVar) const;
/** Finds a child, looking recursively up the scopes */
CScriptVarLink* findInScopes (const string& childName) const;
/** Look up in any parent classes of the given object */
CScriptVarLink* findInParentClasses (CScriptVar* object, const string& name) const;
};
//------------------------------------------------------------------------
} // TJS
@@ -0,0 +1,286 @@
/*
* TinyJS
*
* A single-file Javascript-alike engine
*
* - Useful language functions
*
* Authored By Gordon Williams <gw@pur3.co.uk>
*
* Copyright (C) 2009 Pur3 Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "TinyJS_Functions.h"
#include <cmath>
#include <cstdlib>
#include <sstream>
//------------------------------------------------------------------------
namespace TJS {
using namespace std;
using namespace std::literals;
// ----------------------------------------------- Actual Functions
void scTrace (CScriptVar* c, void* userdata)
{
CTinyJS* js = (CTinyJS*)userdata;
js->getRoot ()->trace ();
}
void scObjectDump (CScriptVar* c) { c->getParameter ("this"sv)->trace ("> "); }
void scObjectClone (CScriptVar* c)
{
CScriptVar* obj = c->getParameter ("this"sv);
c->getReturnVar ()->copyValue (obj);
}
void scMathRand (CScriptVar* c) { c->getReturnVar ()->setDouble ((double)rand () / RAND_MAX); }
void scMathRandInt (CScriptVar* c)
{
auto min = c->getParameter ("min"sv)->getInt ();
auto max = c->getParameter ("max"sv)->getInt ();
auto val = min + (int64_t)(rand () % (1 + max - min));
c->getReturnVar ()->setInt (val);
}
void scCharToInt (CScriptVar* c)
{
string str = c->getParameter ("ch"sv)->getString ();
;
int val = 0;
if (str.length () > 0)
val = (int)str.c_str ()[0];
c->getReturnVar ()->setInt (val);
}
void scStringIndexOf (CScriptVar* c)
{
string str = c->getParameter ("this"sv)->getString ();
string search = c->getParameter ("search"sv)->getString ();
size_t p = str.find (search);
auto val = (p == string::npos) ? -1 : p;
c->getReturnVar ()->setInt (val);
}
void scStringSubstring (CScriptVar* c)
{
string str = c->getParameter ("this"sv)->getString ();
auto lo = c->getParameter ("lo"sv)->getInt ();
auto hi = c->getParameter ("hi"sv)->getInt ();
auto l = hi - lo;
if (l > 0 && lo >= 0 && lo + l <= static_cast<int64_t> (str.length ()))
c->getReturnVar ()->setString (str.substr (lo, l));
else
c->getReturnVar ()->setString ("");
}
void scStringCharAt (CScriptVar* c)
{
string str = c->getParameter ("this"sv)->getString ();
auto p = c->getParameter ("pos"sv)->getInt ();
if (p >= 0 && p < static_cast<int64_t> (str.length ()))
c->getReturnVar ()->setString (str.substr (p, 1));
else
c->getReturnVar ()->setString ("");
}
void scStringCharCodeAt (CScriptVar* c)
{
string str = c->getParameter ("this"sv)->getString ();
auto p = c->getParameter ("pos"sv)->getInt ();
if (p >= 0 && p < static_cast<int64_t> (str.length ()))
c->getReturnVar ()->setInt (str.at (p));
else
c->getReturnVar ()->setInt (0);
}
void scStringSplit (CScriptVar* c)
{
string str = c->getParameter ("this"sv)->getString ();
string sep = c->getParameter ("separator"sv)->getString ();
CScriptVar* result = c->getReturnVar ();
result->setArray ();
int length = 0;
size_t pos = str.find (sep);
while (pos != string::npos)
{
result->setArrayIndex (length++, new CScriptVar (str.substr (0, pos)));
str = str.substr (pos + 1);
pos = str.find (sep);
}
if (str.size () > 0)
result->setArrayIndex (length++, new CScriptVar (str));
}
void scStringFromCharCode (CScriptVar* c)
{
char str[2];
str[0] = static_cast<char> (c->getParameter ("char"sv)->getInt ());
str[1] = 0;
c->getReturnVar ()->setString (str);
}
void scIntegerParseInt (CScriptVar* c)
{
string str = c->getParameter ("str"sv)->getString ();
auto val = stringToInteger (str);
c->getReturnVar ()->setInt (val);
}
void scIntegerValueOf (CScriptVar* c)
{
string str = c->getParameter ("str"sv)->getString ();
int val = 0;
if (str.length () == 1)
val = str[0];
c->getReturnVar ()->setInt (val);
}
void scJSONStringify (CScriptVar* c)
{
ostringstream result;
c->getParameter ("obj"sv)->getJSON (result);
c->getReturnVar ()->setString (result.str ());
}
void scExec (CScriptVar* c, void* data)
{
CTinyJS* tinyJS = (CTinyJS*)data;
string str = c->getParameter ("jsCode"sv)->getString ();
tinyJS->execute (str);
}
void scEval (CScriptVar* c, void* data)
{
CTinyJS* tinyJS = (CTinyJS*)data;
string str = c->getParameter ("jsCode"sv)->getString ();
c->setReturnVar (tinyJS->evaluateComplex (str).getVar ());
}
void scArrayContains (CScriptVar* c)
{
CScriptVar* obj = c->getParameter ("obj"sv);
CScriptVarLink* v = c->getParameter ("this"sv)->getFirstChild ();
bool contains = false;
while (v)
{
if (v->getVar ()->equals (obj))
{
contains = true;
break;
}
v = v->getNextSibling ();
}
c->getReturnVar ()->setInt (contains);
}
void scArrayRemove (CScriptVar* c)
{
CScriptVar* obj = c->getParameter ("obj"sv);
vector<int> removedIndices;
CScriptVarLink* v;
// remove
v = c->getParameter ("this"sv)->getFirstChild ();
while (v)
{
if (v->getVar ()->equals (obj))
{
removedIndices.push_back (v->getIntName ());
}
v = v->getNextSibling ();
}
// renumber
v = c->getParameter ("this"sv)->getFirstChild ();
while (v)
{
int n = v->getIntName ();
int newn = n;
for (size_t i = 0; i < removedIndices.size (); i++)
if (n >= removedIndices[i])
newn--;
if (newn != n)
v->setIntName (newn);
v = v->getNextSibling ();
}
}
void scArrayJoin (CScriptVar* c)
{
string sep = c->getParameter ("separator"sv)->getString ();
CScriptVar* arr = c->getParameter ("this"sv);
ostringstream sstr;
int l = arr->getArrayLength ();
for (int i = 0; i < l; i++)
{
if (i > 0)
sstr << sep;
sstr << arr->getArrayIndex (i)->getString ();
}
c->getReturnVar ()->setString (sstr.str ());
}
// ----------------------------------------------- Register Functions
void registerFunctions (CTinyJS* tinyJS)
{
tinyJS->addNative ("function exec(jsCode)"sv, [=] (auto scriptVar) {
scExec (scriptVar, tinyJS);
}); // execute the given code
tinyJS->addNative ("function eval(jsCode)"sv, [=] (auto scriptVar) {
scEval (scriptVar, tinyJS);
}); // execute the given string (an expression) and return the result
tinyJS->addNative ("function trace()"sv, [=] (auto scriptVar) { scTrace (scriptVar, tinyJS); });
tinyJS->addNative ("function Object.dump()"sv, scObjectDump);
tinyJS->addNative ("function Object.clone()"sv, scObjectClone);
tinyJS->addNative ("function Math.rand()"sv, scMathRand);
tinyJS->addNative ("function Math.randInt(min, max)"sv, scMathRandInt);
tinyJS->addNative ("function charToInt(ch)"sv,
scCharToInt); // convert a character to an int - get its value
tinyJS->addNative ("function String.indexOf(search)"sv,
scStringIndexOf); // find the position of a string in a string, -1 if not
tinyJS->addNative ("function String.substring(lo,hi)"sv, scStringSubstring);
tinyJS->addNative ("function String.charAt(pos)"sv, scStringCharAt);
tinyJS->addNative ("function String.charCodeAt(pos)"sv, scStringCharCodeAt);
tinyJS->addNative ("function String.fromCharCode(char)"sv, scStringFromCharCode);
tinyJS->addNative ("function String.split(separator)"sv, scStringSplit);
tinyJS->addNative ("function Integer.parseInt(str)"sv, scIntegerParseInt); // string to int
tinyJS->addNative ("function Integer.valueOf(str)"sv,
scIntegerValueOf); // value of a single character
tinyJS->addNative ("function JSON.stringify(obj, replacer)"sv,
scJSONStringify); // convert to JSON. replacer is ignored at the moment
// JSON.parse is left out as you can (unsafely!) use eval instead
tinyJS->addNative ("function Array.contains(obj)"sv, scArrayContains);
tinyJS->addNative ("function Array.remove(obj)"sv, scArrayRemove);
tinyJS->addNative ("function Array.join(separator)"sv, scArrayJoin);
}
//------------------------------------------------------------------------
} // TJS
@@ -0,0 +1,40 @@
/*
* TinyJS
*
* A single-file Javascript-alike engine
*
* Authored By Gordon Williams <gw@pur3.co.uk>
*
* Copyright (C) 2009 Pur3 Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include "TinyJS.h"
//------------------------------------------------------------------------
namespace TJS {
/// Register useful functions with the TinyJS interpreter
void registerFunctions (CTinyJS* tinyJS);
//------------------------------------------------------------------------
} // TJS
@@ -0,0 +1,271 @@
/*
* TinyJS
*
* A single-file Javascript-alike engine
*
* - Math and Trigonometry functions
*
* Authored By O.Z.L.B. <ozlbinfo@gmail.com>
*
* Copyright (C) 2011 O.Z.L.B.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <cmath>
#include <cstdlib>
#include <sstream>
#include "TinyJS_MathFunctions.h"
//------------------------------------------------------------------------
namespace TJS {
using namespace std;
#define k_E exp (1.0)
#define k_PI 3.1415926535897932384626433832795
#define F_ABS(a) ((a) >= 0 ? (a) : (-(a)))
#define F_MIN(a, b) ((a) > (b) ? (b) : (a))
#define F_MAX(a, b) ((a) > (b) ? (a) : (b))
#define F_SGN(a) ((a) > 0 ? 1 : ((a) < 0 ? -1 : 0))
#define F_RNG(a, min, max) ((a) < (min) ? min : ((a) > (max) ? max : a))
#define F_ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : (int)((a)-0.5))
// CScriptVar shortcut macro
#define scIsInt(a) (c->getParameter (a)->isInt ())
#define scIsDouble(a) (c->getParameter (a)->isDouble ())
#define scGetInt(a) (c->getParameter (a)->getInt ())
#define scGetDouble(a) (c->getParameter (a)->getDouble ())
#define scReturnInt(a) (c->getReturnVar ()->setInt (a))
#define scReturnDouble(a) (c->getReturnVar ()->setDouble (a))
#ifdef _MSC_VER
namespace {
double asinh (const double& value)
{
double returned;
if (value > 0)
returned = log (value + sqrt (value * value + 1));
else
returned = -log (-value + sqrt (value * value + 1));
return (returned);
}
double acosh (const double& value)
{
double returned;
if (value > 0)
returned = log (value + sqrt (value * value - 1));
else
returned = -log (-value + sqrt (value * value - 1));
return (returned);
}
}
#endif
// Math.abs(x) - returns absolute of given value
void scMathAbs (CScriptVar* c)
{
if (scIsInt ("a"))
{
scReturnInt (F_ABS (scGetInt ("a")));
}
else if (scIsDouble ("a"))
{
scReturnDouble (F_ABS (scGetDouble ("a")));
}
}
// Math.round(a) - returns nearest round of given value
void scMathRound (CScriptVar* c)
{
if (scIsInt ("a"))
{
scReturnInt (F_ROUND (scGetInt ("a")));
}
else if (scIsDouble ("a"))
{
scReturnDouble (F_ROUND (scGetDouble ("a")));
}
}
// Math.min(a,b) - returns minimum of two given values
void scMathMin (CScriptVar* c)
{
if ((scIsInt ("a")) && (scIsInt ("b")))
{
scReturnInt (F_MIN (scGetInt ("a"), scGetInt ("b")));
}
else
{
scReturnDouble (F_MIN (scGetDouble ("a"), scGetDouble ("b")));
}
}
// Math.max(a,b) - returns maximum of two given values
void scMathMax (CScriptVar* c)
{
if ((scIsInt ("a")) && (scIsInt ("b")))
{
scReturnInt (F_MAX (scGetInt ("a"), scGetInt ("b")));
}
else
{
scReturnDouble (F_MAX (scGetDouble ("a"), scGetDouble ("b")));
}
}
// Math.range(x,a,b) - returns value limited between two given values
void scMathRange (CScriptVar* c)
{
if ((scIsInt ("x")))
{
scReturnInt (F_RNG (scGetInt ("x"), scGetInt ("a"), scGetInt ("b")));
}
else
{
scReturnDouble (F_RNG (scGetDouble ("x"), scGetDouble ("a"), scGetDouble ("b")));
}
}
// Math.sign(a) - returns sign of given value (-1==negative,0=zero,1=positive)
void scMathSign (CScriptVar* c)
{
if (scIsInt ("a"))
{
scReturnInt (F_SGN (scGetInt ("a")));
}
else if (scIsDouble ("a"))
{
scReturnDouble (F_SGN (scGetDouble ("a")));
}
}
// Math.PI() - returns PI value
void scMathPI (CScriptVar* c) { scReturnDouble (k_PI); }
// Math.toDegrees(a) - returns degree value of a given angle in radians
void scMathToDegrees (CScriptVar* c) { scReturnDouble ((180.0 / k_PI) * (scGetDouble ("a"))); }
// Math.toRadians(a) - returns radians value of a given angle in degrees
void scMathToRadians (CScriptVar* c) { scReturnDouble ((k_PI / 180.0) * (scGetDouble ("a"))); }
// Math.sin(a) - returns trig. sine of given angle in radians
void scMathSin (CScriptVar* c) { scReturnDouble (sin (scGetDouble ("a"))); }
// Math.asin(a) - returns trig. arcsine of given angle in radians
void scMathASin (CScriptVar* c) { scReturnDouble (asin (scGetDouble ("a"))); }
// Math.cos(a) - returns trig. cosine of given angle in radians
void scMathCos (CScriptVar* c) { scReturnDouble (cos (scGetDouble ("a"))); }
// Math.acos(a) - returns trig. arccosine of given angle in radians
void scMathACos (CScriptVar* c) { scReturnDouble (acos (scGetDouble ("a"))); }
// Math.tan(a) - returns trig. tangent of given angle in radians
void scMathTan (CScriptVar* c) { scReturnDouble (tan (scGetDouble ("a"))); }
// Math.atan(a) - returns trig. arctangent of given angle in radians
void scMathATan (CScriptVar* c) { scReturnDouble (atan (scGetDouble ("a"))); }
// Math.sinh(a) - returns trig. hyperbolic sine of given angle in radians
void scMathSinh (CScriptVar* c) { scReturnDouble (sinh (scGetDouble ("a"))); }
// Math.asinh(a) - returns trig. hyperbolic arcsine of given angle in radians
void scMathASinh (CScriptVar* c) { scReturnDouble (asinh ((long double)scGetDouble ("a"))); }
// Math.cosh(a) - returns trig. hyperbolic cosine of given angle in radians
void scMathCosh (CScriptVar* c) { scReturnDouble (cosh (scGetDouble ("a"))); }
// Math.acosh(a) - returns trig. hyperbolic arccosine of given angle in radians
void scMathACosh (CScriptVar* c) { scReturnDouble (acosh ((long double)scGetDouble ("a"))); }
// Math.tanh(a) - returns trig. hyperbolic tangent of given angle in radians
void scMathTanh (CScriptVar* c) { scReturnDouble (tanh (scGetDouble ("a"))); }
// Math.atan(a) - returns trig. hyperbolic arctangent of given angle in radians
void scMathATanh (CScriptVar* c) { scReturnDouble (atan (scGetDouble ("a"))); }
// Math.E() - returns E Neplero value
void scMathE (CScriptVar* c) { scReturnDouble (k_E); }
// Math.log(a) - returns natural logaritm (base E) of given value
void scMathLog (CScriptVar* c) { scReturnDouble (log (scGetDouble ("a"))); }
// Math.log10(a) - returns logaritm(base 10) of given value
void scMathLog10 (CScriptVar* c) { scReturnDouble (log10 (scGetDouble ("a"))); }
// Math.exp(a) - returns e raised to the power of a given number
void scMathExp (CScriptVar* c) { scReturnDouble (exp (scGetDouble ("a"))); }
// Math.pow(a,b) - returns the result of a number raised to a power (a)^(b)
void scMathPow (CScriptVar* c) { scReturnDouble (pow (scGetDouble ("a"), scGetDouble ("b"))); }
// Math.sqr(a) - returns square of given value
void scMathSqr (CScriptVar* c) { scReturnDouble ((scGetDouble ("a") * scGetDouble ("a"))); }
// Math.sqrt(a) - returns square root of given value
void scMathSqrt (CScriptVar* c) { scReturnDouble (sqrt (scGetDouble ("a"))); }
// ----------------------------------------------- Register Functions
void registerMathFunctions (CTinyJS* tinyJS)
{
using namespace std::literals;
// --- Math and Trigonometry functions ---
tinyJS->addNative ("function Math.abs(a)"sv, scMathAbs);
tinyJS->addNative ("function Math.round(a)"sv, scMathRound);
tinyJS->addNative ("function Math.min(a,b)"sv, scMathMin);
tinyJS->addNative ("function Math.max(a,b)"sv, scMathMax);
tinyJS->addNative ("function Math.range(x,a,b)"sv, scMathRange);
tinyJS->addNative ("function Math.sign(a)"sv, scMathSign);
tinyJS->addNative ("function Math.PI()"sv, scMathPI);
tinyJS->addNative ("function Math.toDegrees(a)"sv, scMathToDegrees);
tinyJS->addNative ("function Math.toRadians(a)"sv, scMathToRadians);
tinyJS->addNative ("function Math.sin(a)"sv, scMathSin);
tinyJS->addNative ("function Math.asin(a)"sv, scMathASin);
tinyJS->addNative ("function Math.cos(a)"sv, scMathCos);
tinyJS->addNative ("function Math.acos(a)"sv, scMathACos);
tinyJS->addNative ("function Math.tan(a)"sv, scMathTan);
tinyJS->addNative ("function Math.atan(a)"sv, scMathATan);
tinyJS->addNative ("function Math.sinh(a)"sv, scMathSinh);
tinyJS->addNative ("function Math.asinh(a)"sv, scMathASinh);
tinyJS->addNative ("function Math.cosh(a)"sv, scMathCosh);
tinyJS->addNative ("function Math.acosh(a)"sv, scMathACosh);
tinyJS->addNative ("function Math.tanh(a)"sv, scMathTanh);
tinyJS->addNative ("function Math.atanh(a)"sv, scMathATanh);
tinyJS->addNative ("function Math.E()"sv, scMathE);
tinyJS->addNative ("function Math.log(a)"sv, scMathLog);
tinyJS->addNative ("function Math.log10(a)"sv, scMathLog10);
tinyJS->addNative ("function Math.exp(a)"sv, scMathExp);
tinyJS->addNative ("function Math.pow(a,b)"sv, scMathPow);
tinyJS->addNative ("function Math.sqr(a)"sv, scMathSqr);
tinyJS->addNative ("function Math.sqrt(a)"sv, scMathSqrt);
}
//------------------------------------------------------------------------
} // TJS
@@ -0,0 +1,12 @@
#pragma once
#include "TinyJS.h"
//------------------------------------------------------------------------
namespace TJS {
/// Register useful math. functions with the TinyJS interpreter
void registerMathFunctions (CTinyJS* tinyJS);
//------------------------------------------------------------------------
} // TJS
@@ -0,0 +1,352 @@
/*
* TinyJS
*
* A single-file Javascript-alike engine
*
* Authored By Gordon Williams <gw@pur3.co.uk>
*
* Copyright (C) 2009 Pur3 Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* This is a program to run all the tests in the tests folder...
*/
#include "TinyJS.h"
#include "TinyJS_Functions.h"
#include "TinyJS_MathFunctions.h"
#include <assert.h>
#include <sys/stat.h>
#include <string>
#include <sstream>
#include <stdio.h>
#if __APPLE_CC__
#include <unistd.h>
#endif
#ifdef MTRACE
#include <mcheck.h>
#endif
// #define INSANE_MEMORY_DEBUG
using namespace TJS;
#ifdef INSANE_MEMORY_DEBUG
// needs -rdynamic when compiling/linking
#include <execinfo.h>
#include <malloc.h>
#include <map>
#include <vector>
using namespace std;
void** get_stackframe ()
{
void** trace = (void**)malloc (sizeof (void*) * 17);
int trace_size = 0;
for (int i = 0; i < 17; i++)
trace[i] = (void*)0;
trace_size = backtrace (trace, 16);
return trace;
}
void print_stackframe (char* header, void** trace)
{
char** messages = (char**)NULL;
int trace_size = 0;
trace_size = 0;
while (trace[trace_size])
trace_size++;
messages = backtrace_symbols (trace, trace_size);
printf ("%s\n", header);
for (int i = 0; i < trace_size; ++i)
{
printf ("%s\n", messages[i]);
}
// free(messages);
}
/* Prototypes for our hooks. */
static void* my_malloc_hook (size_t, const void*);
static void my_free_hook (void*, const void*);
static void* (*old_malloc_hook) (size_t, const void*);
static void (*old_free_hook) (void*, const void*);
map<void*, void**> malloced;
static void* my_malloc_hook (size_t size, const void* caller)
{
/* Restore all old hooks */
__malloc_hook = old_malloc_hook;
__free_hook = old_free_hook;
/* Call recursively */
void* result = malloc (size);
/* we call malloc here, so protect it too. */
// printf ("malloc (%u) returns %p\n", (unsigned int) size, result);
malloced[result] = get_stackframe ();
/* Restore our own hooks */
__malloc_hook = my_malloc_hook;
__free_hook = my_free_hook;
return result;
}
static void my_free_hook (void* ptr, const void* caller)
{
/* Restore all old hooks */
__malloc_hook = old_malloc_hook;
__free_hook = old_free_hook;
/* Call recursively */
free (ptr);
/* we call malloc here, so protect it too. */
// printf ("freed pointer %p\n", ptr);
if (malloced.find (ptr) == malloced.end ())
{
/*fprintf(stderr, "INVALID FREE\n");
void *trace[16];
int trace_size = 0;
trace_size = backtrace(trace, 16);
backtrace_symbols_fd(trace, trace_size, STDERR_FILENO);*/
}
else
malloced.erase (ptr);
/* Restore our own hooks */
__malloc_hook = my_malloc_hook;
__free_hook = my_free_hook;
}
void memtracing_init ()
{
old_malloc_hook = __malloc_hook;
old_free_hook = __free_hook;
__malloc_hook = my_malloc_hook;
__free_hook = my_free_hook;
}
long gethash (void** trace)
{
unsigned long hash = 0;
while (*trace)
{
hash = (hash << 1) ^ (hash >> 63) ^ (unsigned long)*trace;
trace++;
}
return hash;
}
void memtracing_kill ()
{
/* Restore all old hooks */
__malloc_hook = old_malloc_hook;
__free_hook = old_free_hook;
map<long, void**> hashToReal;
map<long, int> counts;
map<void*, void**>::iterator it = malloced.begin ();
while (it != malloced.end ())
{
long hash = gethash (it->second);
hashToReal[hash] = it->second;
if (counts.find (hash) == counts.end ())
counts[hash] = 1;
else
counts[hash]++;
it++;
}
vector<pair<int, long>> sorting;
map<long, int>::iterator countit = counts.begin ();
while (countit != counts.end ())
{
sorting.push_back (pair<int, long> (countit->second, countit->first));
countit++;
}
// sort
bool done = false;
while (!done)
{
done = true;
for (int i = 0; i < sorting.size () - 1; i++)
{
if (sorting[i].first < sorting[i + 1].first)
{
pair<int, long> t = sorting[i];
sorting[i] = sorting[i + 1];
sorting[i + 1] = t;
done = false;
}
}
}
for (int i = 0; i < sorting.size (); i++)
{
long hash = sorting[i].second;
int count = sorting[i].first;
char header[256];
sprintf (header, "--------------------------- LEAKED %d", count);
print_stackframe (header, hashToReal[hash]);
}
}
#endif // INSANE_MEMORY_DEBUG
bool run_test (const char* filename)
{
printf ("TEST %s ", filename);
struct stat results;
if (!stat (filename, &results) == 0)
{
printf ("Cannot stat file! '%s'\n", filename);
return false;
}
int size = results.st_size;
FILE* file = fopen (filename, "rb");
/* if we open as text, the number of bytes read may be > the size we read */
if (!file)
{
printf ("Unable to open file! '%s'\n", filename);
return false;
}
char* buffer = new char[size + 1];
long actualRead = fread (buffer, 1, size, file);
buffer[actualRead] = 0;
buffer[size] = 0;
fclose (file);
CTinyJS s;
registerFunctions (&s);
registerMathFunctions (&s);
s.getRoot ()->addChild ("result", new CScriptVar ("0", SCRIPTVAR_INTEGER));
try
{
s.execute (buffer);
}
catch (CScriptException& e)
{
printf ("ERROR: %s\n", e.text.c_str ());
}
bool pass = s.getRoot ()->getParameter ("result")->getBool ();
if (pass)
printf ("PASS\n");
else
{
char fn[PATH_MAX];
sprintf (fn, "%s.fail.js", filename);
FILE* f = fopen (fn, "wt");
if (f)
{
std::ostringstream symbols;
s.getRoot ()->getJSON (symbols);
fprintf (f, "%s", symbols.str ().c_str ());
fclose (f);
}
printf ("FAIL - symbols written to %s\n", fn);
}
delete[] buffer;
return pass;
}
int main (int argc, char** argv)
{
#if __APPLE_CC__
struct LeakDetector
{
~LeakDetector () noexcept
{
char* env = getenv ("MallocStackLogging");
if (env && (!strcmp (env, "1") || !strcmp (env, "lite")))
{
char command[1024];
pid_t pid = getpid ();
snprintf (command, std::size (command), "leaks %d", pid);
system (command);
}
}
};
static LeakDetector gLeakDetector;
#endif
#ifdef MTRACE
mtrace ();
#endif
#ifdef INSANE_MEMORY_DEBUG
memtracing_init ();
#endif
printf ("TinyJS test runner\n");
printf ("USAGE:\n");
printf (" ./run_tests test.js : run just one test\n");
printf (" ./run_tests : run all tests\n");
if (argc == 2)
{
return !run_test (argv[1]);
}
std::string basePath (__FILE__);
auto pos = basePath.find_last_of ('/');
basePath.erase (pos);
int test_num = 1;
int count = 0;
int passed = 0;
while (test_num < 1000)
{
auto path = basePath; // copy
char fn[PATH_MAX];
snprintf (fn, std::size (fn), "/tests/test%03d.js", test_num);
// check if the file exists - if not, assume we're at the end of our tests
path.append (fn);
FILE* f = fopen (path.data (), "r");
if (!f)
break;
fclose (f);
if (run_test (path.data ()))
passed++;
count++;
test_num++;
}
printf ("Done. %d tests, %d pass, %d fail\n", count, passed, count - passed);
#ifdef INSANE_MEMORY_DEBUG
memtracing_kill ();
#endif
#ifdef _DEBUG
#ifdef _WIN32
_CrtDumpMemoryLeaks ();
#endif
#endif
#ifdef MTRACE
muntrace ();
#endif
return 0;
}
@@ -0,0 +1,83 @@
// switch-case-tests
////////////////////////////////////////////////////
// switch-test 1: case with break;
////////////////////////////////////////////////////
var a1 = 5;
var b1 = 6;
var r1 = 0;
switch (a1 + 5)
{
case 6:
r1 = 2;
break;
case b1 + 4:
r1 = 42;
break;
case 7:
r1 = 2;
break;
}
////////////////////////////////////////////////////
// switch-test 2: case with out break;
////////////////////////////////////////////////////
var a2 = 5;
var b2 = 6;
var r2 = 0;
switch (a2 + 4)
{
case 6:
r2 = 2;
break;
case b2 + 3:
r2 = 40;
// break;
case 7:
r2 += 2;
break;
}
////////////////////////////////////////////////////
// switch-test 3: case with default;
////////////////////////////////////////////////////
var a3 = 5;
var b3 = 6;
var r3 = 0;
switch (a3 + 44)
{
case 6:
r3 = 2;
break;
case b3 + 3:
r3 = 1;
break;
default:
r3 = 42;
break;
}
////////////////////////////////////////////////////
// switch-test 4: case default before case;
////////////////////////////////////////////////////
var a4 = 5;
var b4 = 6;
var r4 = 0;
switch (a4 + 44)
{
default:
r4 = 42;
break;
case 6:
r4 = 2;
break;
case b4 + 3:
r4 = 1;
break;
}
result = r1 == 42 && r2 == 42 && r3 == 42 && r4 == 42;
@@ -0,0 +1,13 @@
// function-closure
var a = 40; // a global var
function closure ()
{
var a = 39; // a local var;
return function () { return a; };
}
var b = closure (); // the local var a is now hidden
result = b () + 3 == 42 && a + 2 == 42;
@@ -0,0 +1,15 @@
// with-test
var a;
with (Math) a = PI;
var b = {get_member: function () { return this.member; }, member: 41};
with (b)
{
let a = get_member (); //<--- a is local for this block
var c = a + 1;
}
result = a == Math.PI && c == 42;
@@ -0,0 +1,29 @@
// generator-test
function fibonacci ()
{
var fn1 = 1;
var fn2 = 1;
while (1)
{
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
var reset = yield current;
if (reset)
{
fn1 = 1;
fn2 = 1;
}
}
}
var generator = fibonacci ();
generator.next(); // 1
generator.next(); // 1
generator.next(); // 2
generator.next(); // 3
generator.next(); // 5
result = generator.next() == 8 && generator.send(true) == 1;
@@ -0,0 +1,2 @@
// simply testing we can return the correct value
result = 1;
@@ -0,0 +1,3 @@
// comparison
var a = 42;
result = a == 42;
@@ -0,0 +1,6 @@
// simple for loop
var a = 0;
var i;
for (i = 1; i < 10; i++)
a = a + i;
result = a == 45;
@@ -0,0 +1,4 @@
// simple if
var a = 42;
if (a < 43)
result = 1;
@@ -0,0 +1,5 @@
// simple for loop containing initialisation, using +=
var a = 0;
for (var i = 1; i < 10; i++)
a += i;
result = a == 45;
@@ -0,0 +1,3 @@
// simple function
function add (x, y) { return x + y; }
result = add (3, 6) == 9;
@@ -0,0 +1,8 @@
// simple function scoping test
var a = 7;
function add (x, y)
{
var a = x + y;
return a;
}
result = add (3, 6) == 9 && a == 7;
@@ -0,0 +1,5 @@
// functions in variables
var bob = {};
bob.add = function (x, y) { return x + y; };
result = bob.add(3, 6) == 9;
@@ -0,0 +1,4 @@
// functions in variables using JSON-style initialisation
var bob = {add: function (x, y) { return x + y; }};
result = bob.add(3, 6) == 9;
@@ -0,0 +1,4 @@
// double function calls
function a (x) { return x + 2; }
function b (x) { return a (x) + 1; }
result = a (3) == 5 && b (3) == 6;
@@ -0,0 +1,8 @@
// recursion
function a (x)
{
if (x > 1)
return x * a (x - 1);
return 1;
}
result = a (5) == 1 * 2 * 3 * 4 * 5;
@@ -0,0 +1,6 @@
// if .. else
var a = 42;
if (a != 42)
result = 0;
else
result = 1;
@@ -0,0 +1,10 @@
// if .. else with blocks
var a = 42;
if (a != 42)
{
result = 0;
}
else
{
result = 1;
}
@@ -0,0 +1,16 @@
// Variable creation and scope from http://en.wikipedia.org/wiki/JavaScript_syntax
x = 0; // A global variable
var y = 'Hello!'; // Another global variable
z = 0; // yet another global variable
function f ()
{
var z = 'foxes'; // A local variable
twenty = 20; // Global because keyword var is not used
return x; // We can use x here because it is global
}
// The value of z is no longer available
// testing
blah = f ();
result = blah == 0 && z != 'foxes' && twenty == 20;
@@ -0,0 +1,9 @@
// Number definition from http://en.wikipedia.org/wiki/JavaScript_syntax
a = 345; // an "integer", although there is only one numeric type in JavaScript
b = 34.5; // a floating-point number
c = 3.45e2; // another floating-point, equivalent to 345
d = 0377; // an octal integer equal to 255
e = 0xFF; // a hexadecimal integer equal to 255, digits represented by the letters A-F may be upper
// or lowercase
result = a == 345 && b * 10 == 345 && c == 345 && d == 255 && e == 255;
@@ -0,0 +1,18 @@
// Undefined/null from http://en.wikipedia.org/wiki/JavaScript_syntax
var testUndefined; // variable declared but not defined, set to value of undefined
var testObj = {};
result = 1;
if (("" + testUndefined) != "undefined")
result = 0; // test variable exists but value not defined, displays undefined
if (("" + testObj.myProp) != "undefined")
result = 0; // testObj exists, property does not, displays undefined
if (!(undefined == null))
result = 0; // unenforced type during check, displays true
if (undefined === null)
result = 0; // enforce type during check, displays false
if (null != undefined)
result = 0; // unenforced type during check, displays true
if (null === undefined)
result = 0; // enforce type during check, displays false
@@ -0,0 +1,11 @@
// references for arrays
var a = [];
a[0] = 10;
a[1] = 22;
b = a;
b[0] = 5;
result = a[0] == 5 && a[1] == 22 && b[1] == 22;
@@ -0,0 +1,14 @@
// references with functions
var a = 42;
var b = [];
b[0] = 43;
function foo (myarray) { myarray[0]++; }
function bar (myvalue) { myvalue++; }
foo (b);
bar (a);
result = a == 42 && b[0] == 44;
@@ -0,0 +1,33 @@
// built-in functions
foo = "foo bar stuff";
// 42-tiny-js change begin --->
// in JavaScript this function is called Math.random()
// r = Math.rand();
r = Math.random();
//<--- 42-tiny-js change end
// 42-tiny-js change begin --->
// in JavaScript parseInt is a methode in the global scope (root-scope)
// parsed = Integer.parseInt("42");
parsed = parseInt ("42");
//<--- 42-tiny-js change end
aStr = "ABCD";
aChar = aStr.charAt(0);
obj1 = new Object ();
obj1.food = "cake";
obj1.desert = "pie";
obj2 = obj1.clone();
obj2.food = "kittens";
result = foo.length == 13 && foo.indexOf("bar") == 4 && foo.substring(8, 13) == "stuff" &&
parsed == 42 &&
// 42-tiny-js change begin --->
// in 42tiny-js the Integer-Objecte will be removed
// Integer.valueOf can be replaced by String.charCodeAt
// Integer.valueOf(aChar)==65 && obj1.food=="cake" && obj2.desert=="pie";
aChar.charCodeAt() == 65 && obj1.food == "cake" && obj2.desert == "pie";
//<--- 42-tiny-js change end
@@ -0,0 +1,23 @@
// built-in functions
foo = "foo bar stuff";
r = Math.rand();
parsed = Integer.parseInt("42");
parsedHex = Integer.parseInt("0xFF");
parsedOct = Integer.parseInt("011");
parsedBig = Integer.parseInt("4294967296");
aStr = "ABCD";
aChar = aStr.charAt(0);
obj1 = new Object ();
obj1.food = "cake";
obj1.desert = "pie";
obj2 = obj1.clone();
obj2.food = "kittens";
result = foo.length == 13 && foo.indexOf("bar") == 4 && foo.substring(8, 13) == "stuff" &&
parsed == 42 && Integer.valueOf(aChar) == 65 && obj1.food == "cake" &&
obj2.desert == "pie" && parsedHex == 255 && parsedOct == 9 && parsedBig == 4294967296;
@@ -0,0 +1,27 @@
// Test reported by sterowang, Variable attribute defines conflict with function.
/*
What steps will reproduce the problem?
1. function a (){};
2. b = {};
3. b.a = {};
4. a();
What is the expected output? What do you see instead?
Function "a" should be called. But the error message "Error Expecting 'a'
to be a function at (line: 1, col: 1)" received.
What version of the product are you using? On what operating system?
Version 1.6 is used on Cent OS 5.4
Please provide any additional information below.
When using dump() to show symbols, found the function "a" is reassigned to
"{}" by "b.a = {};" call.
*/
function a () {};
b = {};
b.a = {};
a ();
result = 1;
@@ -0,0 +1,12 @@
/* Javascript eval */
// 42-tiny-js change begin --->
// in JavaScript eval is not JSON.parse
// use parentheses or JSON.parse instead
// myfoo = eval("{ foo: 42 }");
myfoo = eval ("(" +
"{ foo: 42 }" +
")");
//<--- 42-tiny-js change end
result = eval ("4*10+2") == 42 && myfoo.foo == 42;
@@ -0,0 +1,5 @@
/* Javascript eval */
myfoo = eval ("{ foo: 42 }");
result = eval ("4*10+2") == 42 && myfoo.foo == 42;
@@ -0,0 +1,20 @@
/* Javascript eval */
mystructure = {
a: 39,
b: 3,
addStuff: function (c, d) { return c + d; }
};
mystring = JSON.stringify(mystructure, undefined);
// 42-tiny-js change begin --->
// in JavaScript eval is not JSON.parse
// use parentheses or JSON.parse instead
// mynewstructure = eval(mystring);
mynewstructure = eval ("(" + mystring + ")");
mynewstructure2 = JSON.parse(mystring);
//<--- 42-tiny-js change end
result = mynewstructure.addStuff(mynewstructure.a, mynewstructure.b) == 42 &&
mynewstructure2.addStuff(mynewstructure2.a, mynewstructure2.b) == 42;
@@ -0,0 +1,13 @@
/* Javascript eval */
mystructure = {
a: 39,
b: 3,
addStuff: function (c, d) { return c + d; }
};
mystring = JSON.stringify(mystructure, undefined);
mynewstructure = eval (mystring);
result = mynewstructure.addStuff(mynewstructure.a, mynewstructure.b);
@@ -0,0 +1,8 @@
// mikael.kindborg@mobilesorcery.com - Function symbol is evaluated in bracket-less body of false
// if-statement
var foo; // a var is only created automated by assignment
if (foo !== undefined)
foo ();
result = 1;
@@ -0,0 +1,67 @@
/* Mandelbrot! */
X1 = -2.0;
Y1 = -2.0;
X2 = 2.0;
Y2 = 2.0;
PX = 32;
PY = 32;
lines = [];
for (y = 0; y < PY; y++)
{
line = "";
for (x = 0; x < PX; x++)
{
Xr = 0;
Xi = 0;
Cr = X1 + ((X2 - X1) * x / PX);
Ci = Y1 + ((Y2 - Y1) * y / PY);
iterations = 0;
while ((iterations < 32) && ((Xr * Xr + Xi * Xi) < 4))
{
t = Xr * Xr - Xi * Xi + Cr;
Xi = 2 * Xr * Xi + Ci;
Xr = t;
iterations++;
}
if (iterations & 1)
line += "*";
else
line += " ";
}
lines[y] = line;
}
result = lines[0] == "********************************" &&
lines[1] == "*********** **********" &&
lines[2] == "********* ********" &&
lines[3] == "******* ******" &&
lines[4] == "****** *****" &&
lines[5] == "***** ****" &&
lines[6] == "**** ******* ***" &&
lines[7] == "*** ******* ** ** **" &&
lines[8] == "*** ****** * * * **" &&
lines[9] == "** ******* ** ** ** *" &&
lines[10] == "** ****** * * ** ** *" &&
lines[11] == "* ***** *** ** ** " &&
lines[12] == "****** *** ***** " &&
lines[13] == "*** * * * ** ** " &&
lines[14] == "* * * * * ** " &&
lines[15] == "* *** ** ** " &&
lines[16] == "* ** ** " &&
lines[17] == "* *** ** ** " &&
lines[18] == "* * * * * ** " &&
lines[19] == "*** * * * ** ** " &&
lines[20] == "****** *** ***** " &&
lines[21] == "* ***** *** ** ** " &&
lines[22] == "** ****** * * ** ** *" &&
lines[23] == "** ******* ** ** ** *" &&
lines[24] == "*** ****** * * * **" &&
lines[25] == "*** ******* ** ** **" &&
lines[26] == "**** ******* ***" &&
lines[27] == "***** ****" &&
lines[28] == "****** *****" &&
lines[29] == "******* ******" &&
lines[30] == "********* ********" &&
lines[31] == "*********** **********";
@@ -0,0 +1,7 @@
// Array length test
myArray = [1, 2, 3, 4, 5];
myArray2 = [1, 2, 3, 4, 5];
myArray2[8] = 42;
result = myArray.length == 5 && myArray2.length == 9;
@@ -0,0 +1,4 @@
// check for undefined-ness
a = undefined;
b = "foo";
result = a == undefined && b != undefined;
@@ -0,0 +1,3 @@
// test for postincrement working as expected
var foo = 5;
result = (foo++) == 5;
@@ -0,0 +1,5 @@
// test for array contains
var a = [1, 2, 4, 5, 7];
var b = ["bread", "cheese", "sandwich"];
result = a.contains(1) && !a.contains(42) && b.contains("cheese") && !b.contains("eggs");
@@ -0,0 +1,7 @@
// test for array remove
var a = [1, 2, 4, 5, 7];
a.remove(2);
a.remove(5);
result = a.length == 3 && a[0] == 1 && a[1] == 4 && a[2] == 7;
@@ -0,0 +1,4 @@
// test for array join
var a = [1, 2, 4, 5, 7];
result = a.join(",") == "1,2,4,5,7";
@@ -0,0 +1,5 @@
// test for string split
var b = "1,4,7";
var a = b.split(",");
result = a.length == 3 && a[0] == 1 && a[1] == 4 && a[2] == 7;
@@ -0,0 +1,13 @@
function Foo () { this.__proto__ = Foo.prototype; }
Foo.prototype = {
value: function () { return this.x + this.y; }
};
var a = {__proto__: Foo.prototype, x: 1, y: 2};
var b = new Foo ();
b.x = 2;
b.y = 3;
var result1 = a.value();
var result2 = b.value();
result = result1 == 3 && result2 == 5;
@@ -0,0 +1,10 @@
var Foo = {value: function () { return this.x + this.y; }};
var a = {prototype: Foo, x: 1, y: 2};
var b = new Foo ();
b.x = 2;
b.y = 3;
var result1 = a.value();
var result2 = b.value();
result = result1 == 3 && result2 == 5;
@@ -0,0 +1,5 @@
// test for shift
var a = (2 << 2);
var b = (16 >> 3);
var c = (-1 >>> 16);
result = a == 8 && b == 2 && c == 0xFFFF;
@@ -0,0 +1,3 @@
// test for ternary
result = (true ? 3 : 4) == 3 && (false ? 5 : 6) == 6;
@@ -0,0 +1,9 @@
function Person (name)
{
this.name = name;
this.kill = function () { this.name += " is dead"; };
}
var a = new Person ("Kenny");
a.kill();
result = a.name == "Kenny is dead";
@@ -0,0 +1,8 @@
// the 'lf' in the printf caused issues writing doubles on some compilers
var a = 5.0 / 10.0 * 100.0;
var b = 5.0 * 110.0;
var c = 50.0 / 10.0;
a.dump();
b.dump();
c.dump();
result = a == 50 && b == 550 && c == 5;
@@ -0,0 +1,803 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "uiscripting.h"
#include "detail/converters.h"
#include "detail/scriptobject.h"
#include "detail/uidescscriptobject.h"
#include "detail/drawcontextobject.h"
#include "detail/drawable.h"
#include "detail/iscriptcontextinternal.h"
#include "detail/scriptingviewfactory.h"
#include "../uidescription/uiattributes.h"
#include "../uidescription/uiviewfactory.h"
#include "../uidescription/uidescriptionaddonregistry.h"
#include "../lib/iviewlistener.h"
#include "../lib/cresourcedescription.h"
#include "../lib/cvstguitimer.h"
#include "../lib/platform/platformfactory.h"
#include "../lib/platform/iplatformresourceinputstream.h"
#include "tiny-js/TinyJS.h"
#include "tiny-js/TinyJS_Functions.h"
#include "tiny-js/TinyJS_MathFunctions.h"
#include <iostream>
#include <sstream>
//------------------------------------------------------------------------
namespace VSTGUI {
using namespace std::literals;
using namespace TJS;
namespace ScriptingInternal {
struct ViewScriptObject;
//------------------------------------------------------------------------
class ScriptContext : public IScriptContextInternal
{
public:
using OnScriptExceptionFunc = UIScripting::OnScriptExceptionFunc;
using ReadScriptContentsFunc = UIScripting::ReadScriptContentsFunc;
ScriptContext (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
ReadScriptContentsFunc&& readContentsFunc);
~ScriptContext () noexcept;
void init (const std::string& initScript);
void onViewCreated (CView* view, const std::string& script) override;
void reset ();
private:
std::string eval (std::string_view script) const override;
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
struct TimerScriptObject : ScriptObject
{
template<typename Proc>
TimerScriptObject (uint64_t fireTime, CScriptVar* _callback, Proc timerProc)
{
auto cb = owning (_callback);
auto t = makeOwned<CVSTGUITimer> (
[timerProc, cb] (auto timer) {
if (!timerProc (cb))
timer->stop ();
},
static_cast<uint32_t> (fireTime), false);
scriptVar->setCustomData (t);
addFunc ("invalid"sv, [t] (auto v) mutable {
t = nullptr;
v->setCustomData (nullptr);
});
addFunc ("start"sv, [t] (auto) { t->start (); });
addFunc ("stop"sv, [t] (auto) { t->stop (); });
setOnDestroy ([cb] (auto) { cb->release (); });
}
};
//------------------------------------------------------------------------
struct ScriptContext::Impl : ViewListenerAdapter,
ViewEventListenerAdapter,
ViewContainerListenerAdapter,
IControlListener,
ScriptingInternal::IViewScriptObjectContext
{
using ViewScriptObject = ScriptingInternal::ViewScriptObject;
using ScriptAddChildScoped = ScriptingInternal::ScriptAddChildScoped;
using ScriptObject = ScriptingInternal::ScriptObject;
using TimerScriptObject = ScriptingInternal::TimerScriptObject;
using UIDescScriptObject = ScriptingInternal::UIDescScriptObject;
IUIDescription* uiDesc {nullptr};
std::unique_ptr<CTinyJS> jsContext;
OnScriptExceptionFunc onScriptException;
ReadScriptContentsFunc readScriptContents;
using ViewScriptMap = ScriptingInternal::ViewScriptMap;
ViewScriptMap viewScriptMap;
UIDescScriptObject uiDescObject;
Impl (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
ReadScriptContentsFunc&& readContentsFunc)
: uiDesc (uiDesc)
, onScriptException (std::move (onExceptionFunc))
, readScriptContents (std::move (readContentsFunc))
{
init ();
}
~Impl () noexcept { reset (true); }
void init ()
{
jsContext = std::make_unique<CTinyJS> ();
uiDescObject = UIDescScriptObject (uiDesc, jsContext.get ());
registerFunctions (jsContext.get ());
registerMathFunctions (jsContext.get ());
jsContext->getRoot ()->addChild ("uiDesc"sv, uiDescObject);
jsContext->addNative (
"function createTimer(context, fireTime, callback)"sv, [this] (CScriptVar* var) {
auto context = var->getParameter ("context"sv);
auto fireTime = var->getParameter ("fireTime"sv);
auto callback = var->getParameter ("callback"sv);
if (!fireTime->isInt ())
{
throw CScriptException ("Expect integer as first parameter on timer creation");
}
if (!callback->isFunction ())
{
throw CScriptException (
"Expect function as second parameter on timer creation");
}
auto timerObj = TimerScriptObject (
fireTime->getInt (), callback->deepCopy (), [this, context] (auto callback) {
using namespace ScriptingInternal;
ScriptAddChildScoped scs (*jsContext->getRoot (), "timerContext"sv,
context);
return evalScript (callback, "timerCallback (timerContext);"sv,
"timerCallback");
});
var->setReturnVar (timerObj);
});
jsContext->addNative (
"function iterateSubViews(view, context, callback)"sv, [this] (CScriptVar* var) {
auto view = var->getParameter ("view"sv);
auto context = var->getParameter ("context"sv);
auto callback = var->getParameter ("callback"sv);
if (!view->isObject ())
{
throw CScriptException ("Expect object as first parameter on iterateSubViews");
}
if (!callback->isFunction ())
{
throw CScriptException (
"Expect function as second parameter on iterateSubViews");
}
auto it =
std::find_if (viewScriptMap.begin (), viewScriptMap.end (),
[&] (const auto& el) { return el.second->getVar () == view; });
if (it == viewScriptMap.end ())
throw CScriptException ("View not found in iterateSubViews");
auto container = it->first->asViewContainer ();
if (!container)
return; // no sub views
container->forEachChild ([&] (auto child) {
using namespace ScriptingInternal;
auto childScriptObject = viewScriptMap.find (child);
if (childScriptObject != viewScriptMap.end ())
{
ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
childScriptObject->second->getVar ());
ScriptAddChildScoped scs2 (*jsContext->getRoot (), "context"sv, context);
evalScript (callback, "callback (child, context);"sv, "callback");
}
else
{
auto scriptObj =
addView (child, std::make_unique<ViewScriptObject> (child, this));
ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
scriptObj->getVar ());
ScriptAddChildScoped scs2 (*jsContext->getRoot (), "context"sv, context);
evalScript (callback, "callback (child, context);"sv, "callback");
}
});
});
jsContext->addNative ("function log(obj)",
[this] (CScriptVar* var) { log (var->getParameter ("obj"sv)); });
jsContext->addNative ("function makeTransformMatrix()", [] (CScriptVar* var) {
auto tm = makeTransformMatrixObject ();
var->setReturnVar (tm);
tm->release ();
});
}
void reset (bool terminate = false)
{
for (auto it = viewScriptMap.begin (); it != viewScriptMap.end ();)
{
viewRemoved (it->first);
uninstallListeners (it->first);
it = eraseViewFromMap (it);
}
if (!terminate)
init ();
}
void initWithScript (const std::string* script)
{
if (script)
{
auto scriptContent = getScriptFileContent (*script);
if (!scriptContent.empty ())
script = &scriptContent;
evalScript (*script);
}
}
CScriptVar* getRoot () const override { return jsContext->getRoot (); }
IUIDescription* getUIDescription () const override { return uiDesc; }
void installListeners (CView* view)
{
view->registerViewListener (this);
view->registerViewEventListener (this);
if (auto viewContainer = view->asViewContainer ())
viewContainer->registerViewContainerListener (this);
else if (auto control = dynamic_cast<CControl*> (view))
control->registerControlListener (this);
}
void uninstallListeners (CView* view)
{
view->unregisterViewListener (this);
view->unregisterViewEventListener (this);
if (auto viewContainer = view->asViewContainer ())
viewContainer->unregisterViewContainerListener (this);
if (auto control = dynamic_cast<CControl*> (view))
control->unregisterControlListener (this);
}
ScriptObject evalScript (std::string_view script) noexcept override
{
try
{
auto result = jsContext->evaluateComplex (script);
#if 0 // DEBUG
if (result.getVar () && !result.getVar ()->isUndefined ())
{
DebugPrint ("%s\n", result.getVar ()->getString ().data ());
}
#endif
return result.getVar ();
}
catch (const CScriptException& exc)
{
#if DEBUG
DebugPrint ("Scripting Exception: %s\n", exc.text.data ());
#endif
if (onScriptException)
onScriptException (exc.text);
return {};
}
}
ScriptObject evalScript (CScriptVar* object, std::string_view script,
const std::string& objectName = "view") noexcept
{
if (!object || script.empty ())
return {};
vstgui_assert (object->getRefs () > 0);
object->addRef ();
ScriptAddChildScoped scs (*jsContext->getRoot (), objectName, object);
auto result = evalScript (script);
object->release ();
return result;
}
template<typename Proc>
void callWhenScriptHasFunction (CView* view, std::string_view funcName, Proc proc)
{
auto it = viewScriptMap.find (view);
if (it == viewScriptMap.end ())
return;
if (it->second->getVar ()->findChild (funcName))
proc (this, it->second);
}
void viewAttached (CView* view) override
{
callWhenScriptHasFunction (view, "onAttached"sv, [] (auto This, auto& obj) {
static constexpr auto script = R"(view.onAttached(view);)"sv;
This->evalScript (obj->getVar (), script);
});
}
void viewRemoved (CView* view) override
{
callWhenScriptHasFunction (view, "onRemoved"sv, [] (auto This, auto& obj) {
static constexpr auto script = R"(view.onRemoved(view);)"sv;
This->evalScript (obj->getVar (), script);
});
}
void viewSizeChanged (CView* view, const CRect& oldSize) override
{
callWhenScriptHasFunction (view, "onSizeChanged"sv, [&] (auto This, auto& obj) {
auto newSize = view->getViewSize ();
ScriptObject newSizeObject = ScriptingInternal::makeScriptRect (newSize);
ScriptAddChildScoped scs (*jsContext->getRoot (), "newSize"sv, newSizeObject);
static constexpr auto script = R"(view.onSizeChanged(view, newSize);)"sv;
This->evalScript (obj->getVar (), script);
});
}
void viewLostFocus (CView* view) override
{
callWhenScriptHasFunction (view, "onLostFocus"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onLostFocus(view);)"sv;
This->evalScript (obj->getVar (), script);
});
}
void viewTookFocus (CView* view) override
{
callWhenScriptHasFunction (view, "onTookFocus"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onTookFocus(view);)"sv;
This->evalScript (obj->getVar (), script);
});
}
void viewOnMouseEnabled (CView* view, bool state) override
{
callWhenScriptHasFunction (view, "onMouseEnabled"sv, [&] (auto This, auto& obj) {
static constexpr auto scriptEnabled = R"(view.onMouseEnabled(view, true);)"sv;
static constexpr auto scriptDisabled = R"(view.onMouseEnabled(view, false);)"sv;
This->evalScript (obj->getVar (), state ? scriptEnabled : scriptDisabled);
});
}
void callViewAddedOrRemoved (CViewContainer* container, CView* view, std::string_view function,
std::string_view script)
{
callWhenScriptHasFunction (container, function, [&] (auto This, auto& obj) {
auto childScriptObject = viewScriptMap.find (view);
if (childScriptObject != viewScriptMap.end ())
{
ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
childScriptObject->second->getVar ());
This->evalScript (obj->getVar (), script);
}
else
{
auto scriptObj = addView (view, std::make_unique<ViewScriptObject> (view, this));
ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv, scriptObj->getVar ());
This->evalScript (obj->getVar (), script);
}
});
}
void viewContainerViewAdded (CViewContainer* container, CView* view) override
{
callViewAddedOrRemoved (container, view, "onViewAdded"sv,
R"(view.onViewAdded(view, child);)"sv);
}
void viewContainerViewRemoved (CViewContainer* container, CView* view) override
{
callViewAddedOrRemoved (container, view, "onViewRemoved"sv,
R"(view.onViewRemoved(view, child);)"sv);
}
void checkEventConsumed (ScriptObject& obj, Event& event)
{
if (auto consume = obj->findChild ("consumed"))
{
if (consume->getVar ()->isInt () && consume->getVar ()->getInt ())
event.consumed = true;
}
}
void callEventFunction (CScriptVar* var, Event& event, std::string_view script) noexcept
{
auto scriptEvent = ScriptingInternal::makeScriptEvent (event);
ScriptAddChildScoped scs (*jsContext->getRoot (), "event"sv, scriptEvent);
evalScript (var, script);
checkEventConsumed (scriptEvent, event);
}
void viewOnEvent (CView* view, Event& event) override
{
auto applyEventMouseLocalPosition = [&] (auto proc) {
auto& mouseEvent = castMousePositionEvent (event);
auto oldPos = mouseEvent.mousePosition;
mouseEvent.mousePosition -= view->getViewSize ().getTopLeft ();
proc ();
mouseEvent.mousePosition = oldPos;
};
switch (event.type)
{
case EventType::MouseEnter:
{
applyEventMouseLocalPosition ([&] () {
callWhenScriptHasFunction (view, "onMouseEnter"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onMouseEnter(view, event);)"sv;
callEventFunction (obj->getVar (), event, script);
});
});
break;
}
case EventType::MouseExit:
{
applyEventMouseLocalPosition ([&] () {
callWhenScriptHasFunction (view, "onMouseExit"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onMouseExit(view, event);)"sv;
callEventFunction (obj->getVar (), event, script);
});
});
break;
}
case EventType::MouseDown:
{
applyEventMouseLocalPosition ([&] () {
callWhenScriptHasFunction (view, "onMouseDown"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onMouseDown(view, event);)"sv;
callEventFunction (obj->getVar (), event, script);
});
});
break;
}
case EventType::MouseUp:
{
applyEventMouseLocalPosition ([&] () {
callWhenScriptHasFunction (view, "onMouseUp"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onMouseUp(view, event);)"sv;
callEventFunction (obj->getVar (), event, script);
});
});
break;
}
case EventType::MouseMove:
{
applyEventMouseLocalPosition ([&] () {
callWhenScriptHasFunction (view, "onMouseMove"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onMouseMove(view, event);)"sv;
callEventFunction (obj->getVar (), event, script);
});
});
break;
}
case EventType::MouseWheel:
{
applyEventMouseLocalPosition ([&] () {
callWhenScriptHasFunction (view, "onMouseWheel"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onMouseWheel(view, event);)"sv;
callEventFunction (obj->getVar (), event, script);
});
});
break;
}
case EventType::KeyDown:
{
callWhenScriptHasFunction (view, "onKeyDown"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onKeyDown(view, event);)"sv;
callEventFunction (obj->getVar (), event, script);
});
break;
}
case EventType::KeyUp:
{
callWhenScriptHasFunction (view, "onKeyUp"sv, [&] (auto This, auto& obj) {
static constexpr auto script = R"(view.onKeyUp(view, event);)"sv;
callEventFunction (obj->getVar (), event, script);
});
break;
}
default:
break;
}
}
void valueChanged (CControl* control) override
{
callWhenScriptHasFunction (control, "onValueChanged"sv, [&] (auto This, auto& obj) {
ScriptObject controlValue;
controlValue->setDouble (control->getValue ());
ScriptAddChildScoped scs (*jsContext->getRoot (), "value"sv, controlValue);
static constexpr auto script = R"(view.onValueChanged(view, value);)"sv;
This->evalScript (obj->getVar (), script);
});
}
void controlBeginEdit (CControl* control) override
{
callWhenScriptHasFunction (control, "onBeginEdit"sv, [] (auto This, auto& obj) {
static constexpr auto script = R"(view.onBeginEdit(view);)"sv;
This->evalScript (obj->getVar (), script);
});
}
void controlEndEdit (CControl* control) override
{
callWhenScriptHasFunction (control, "onEndEdit"sv, [] (auto This, auto& obj) {
static constexpr auto script = R"(view.onEndEdit(view);)"sv;
This->evalScript (obj->getVar (), script);
});
}
void viewWillDelete (CView* view) override { removeView (view); }
ViewScriptObject* addView (CView* view, std::unique_ptr<ViewScriptObject>&& scriptObject)
{
installListeners (view);
viewScriptMap[view] = std::move (scriptObject);
return viewScriptMap[view].get ();
}
ViewScriptMap::iterator eraseViewFromMap (ViewScriptMap::iterator el)
{
return viewScriptMap.erase (el);
}
ViewScriptMap::iterator removeView (CView* view) override
{
uninstallListeners (view);
auto it = viewScriptMap.find (view);
if (it != viewScriptMap.end ())
return eraseViewFromMap (it);
return viewScriptMap.end ();
}
ViewScriptObject* addView (CView* view) override
{
auto it = viewScriptMap.find (view);
if (it != viewScriptMap.end ())
return it->second.get ();
return addView (view, std::make_unique<ViewScriptObject> (view, this));
}
void addView (CView* view, const std::string* script) noexcept
{
addView (view);
if (script)
{
auto scriptContent = getScriptFileContent (*script);
if (!scriptContent.empty ())
script = &scriptContent;
evalScript (viewScriptMap[view]->getVar (), *script);
}
}
std::string getScriptFileContent (const std::string& script) const
{
constexpr std::string_view scriptFileCommand ("//scriptfile ");
std::string_view scriptView (script);
if (scriptView.length () > scriptFileCommand.length ())
{
if (scriptView.substr (0, scriptFileCommand.length ()) == scriptFileCommand)
{
auto scriptFileStr = scriptView.substr (scriptFileCommand.length ());
if (scriptFileStr.length () < 255)
{
return readScriptContents (scriptFileStr);
}
}
}
return {};
}
void log (CScriptVar* var) const
{
#if DEBUG
static constexpr size_t bufferSize = 512;
struct DebugOutputStream : private std::streambuf,
public std::ostream
{
DebugOutputStream () : std::ostream (this) { data.reserve (bufferSize); }
~DebugOutputStream () noexcept
{
if (!data.empty ())
flush ();
}
private:
int overflow (int c) override
{
if (data.length () >= bufferSize)
flush ();
data += static_cast<char> (c);
return 0;
}
void flush ()
{
DebugPrint ("%s", data.data ());
data.clear ();
data.reserve (bufferSize);
}
std::string data;
};
DebugOutputStream logStream;
var->getJSON (logStream);
logStream << "\n";
#endif
}
};
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
ScriptContext::ScriptContext (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
ReadScriptContentsFunc&& readContentsFunc)
{
impl =
std::make_unique<Impl> (uiDesc, std::move (onExceptionFunc), std::move (readContentsFunc));
}
//------------------------------------------------------------------------
ScriptContext::~ScriptContext () noexcept {}
//------------------------------------------------------------------------
void ScriptContext::init (const std::string& initScript) { impl->initWithScript (&initScript); }
//------------------------------------------------------------------------
void ScriptContext::onViewCreated (CView* view, const std::string& script)
{
impl->addView (view, &script);
}
//------------------------------------------------------------------------
void ScriptContext::reset () { impl->reset (); }
//------------------------------------------------------------------------
std::string ScriptContext::eval (std::string_view script) const
{
if (!impl->jsContext)
return {};
try
{
auto result = impl->jsContext->evaluateComplex (script);
std::stringstream stream;
result.getVar ()->getJSON (stream);
return stream.str ();
}
catch (const CScriptException& exc)
{
if (impl->onScriptException)
impl->onScriptException (exc.text);
}
return {};
}
//------------------------------------------------------------------------
} // ScriptingInternal
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
struct UIScripting::Impl
{
using JSViewFactoryPtr = std::unique_ptr<ScriptingInternal::JavaScriptViewFactory>;
using ScriptContextPtr = std::unique_ptr<ScriptingInternal::ScriptContext>;
std::unordered_map<const IUIDescription*, std::pair<JSViewFactoryPtr, ScriptContextPtr>> map;
static OnScriptExceptionFunc onScriptExceptionFunc;
static ReadScriptContentsFunc readScriptContentsFunc;
static std::string readScriptContentsFromResource (std::string_view filename);
};
//------------------------------------------------------------------------
UIScripting::OnScriptExceptionFunc UIScripting::Impl::onScriptExceptionFunc;
UIScripting::ReadScriptContentsFunc UIScripting::Impl::readScriptContentsFunc;
//------------------------------------------------------------------------
std::string UIScripting::Impl::readScriptContentsFromResource (std::string_view filename)
{
std::string fileStr (filename);
CResourceDescription desc (fileStr.data ());
if (auto stream = getPlatformFactory ().createResourceInputStream (desc))
{
std::string fileContent;
while (true)
{
char buffer[256];
auto numRead = stream->readRaw (buffer, static_cast<uint32_t> (std::size (buffer)));
if (numRead == kStreamIOError)
return {};
fileContent.append (buffer, numRead);
if (numRead < std::size (buffer))
break;
}
return fileContent;
}
return {};
};
//------------------------------------------------------------------------
UIScripting::UIScripting () { impl = std::make_unique<Impl> (); }
//------------------------------------------------------------------------
UIScripting::~UIScripting () noexcept = default;
//------------------------------------------------------------------------
void UIScripting::afterParsing (IUIDescription* desc) {}
//------------------------------------------------------------------------
void UIScripting::beforeSaving (IUIDescription* desc) {}
//------------------------------------------------------------------------
void UIScripting::onDestroy (IUIDescription* desc)
{
auto it = impl->map.find (desc);
if (it != impl->map.end ())
impl->map.erase (it);
}
//------------------------------------------------------------------------
auto UIScripting::onCreateTemplateView (const IUIDescription* desc, const CreateTemplateViewFunc& f)
-> CreateTemplateViewFunc
{
return [=] (auto name, auto controller) {
return f (name, controller);
};
}
//------------------------------------------------------------------------
IViewFactory* UIScripting::getViewFactory (IUIDescription* desc, IViewFactory* originalFactory)
{
using namespace ScriptingInternal;
auto it = impl->map.find (desc);
if (it != impl->map.end ())
return it->second.first.get ();
auto onScriptException = Impl::onScriptExceptionFunc;
if (!onScriptException)
onScriptException = [] (std::string_view reason) {
std::cerr << reason << '\n';
};
auto readScriptContentsFunc = [] (auto filename) {
std::string result;
if (Impl::readScriptContentsFunc)
result = Impl::readScriptContentsFunc (filename);
if (result.empty ())
{
result = Impl::readScriptContentsFromResource (filename);
}
return result;
};
auto scripting = std::make_unique<ScriptContext> (desc, std::move (onScriptException),
std::move (readScriptContentsFunc));
auto viewFactory = std::make_unique<JavaScriptViewFactory> (scripting.get (), originalFactory);
auto result =
impl->map.emplace (desc, std::make_pair (std::move (viewFactory), std::move (scripting)));
return result.first->second.first.get ();
}
//------------------------------------------------------------------------
void UIScripting::onEditingStart (IUIDescription* desc)
{
auto it = impl->map.find (desc);
if (it != impl->map.end ())
{
it->second.second->reset ();
it->second.first->setScriptingDisabled (true);
}
}
//------------------------------------------------------------------------
void UIScripting::onEditingEnd (IUIDescription* desc)
{
auto it = impl->map.find (desc);
if (it != impl->map.end ())
it->second.first->setScriptingDisabled (false);
}
//------------------------------------------------------------------------
void UIScripting::init (const OnScriptExceptionFunc& onExceptionFunc,
const ReadScriptContentsFunc& readScriptContentsFunc)
{
if (onExceptionFunc)
Impl::onScriptExceptionFunc = onExceptionFunc;
if (readScriptContentsFunc)
Impl::readScriptContentsFunc = readScriptContentsFunc;
UIDescriptionAddOnRegistry::add (std::make_unique<UIScripting> ());
static ScriptingInternal::JavaScriptDrawableViewCreator jsViewCreator;
UIViewFactory::registerViewCreator (jsViewCreator);
static ScriptingInternal::JavaScriptDrawableControlCreator jsControlCreator;
UIViewFactory::registerViewCreator (jsControlCreator);
}
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,152 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../uidescription/icontroller.h"
#include "../uidescription/iviewfactory.h"
#include "../uidescription/iuidescription.h"
#include "../uidescription/iuidescriptionaddon.h"
#include <functional>
#include <optional>
#include <variant>
//------------------------------------------------------------------------
namespace VSTGUI {
//------------------------------------------------------------------------
/** UIDescription scripting support
*
* @ingroup new_in_4_15
*/
class UIScripting : public UIDescriptionAddOnAdapter
{
public:
using OnScriptExceptionFunc = std::function<void (std::string_view reason)>;
using ReadScriptContentsFunc = std::function<std::string (std::string_view filename)>;
/** Initialize the UIScripting library
*
* Must be called once before creating UIDescription objects
*
* @param onExceptionFunc [optional] Called when a script context throws an exception
* @param readScriptContentsFunc [optional] Called to load the script from a filename,
* uses the resource folder as default.
*/
static void init (const OnScriptExceptionFunc& onExceptionFunc = {},
const ReadScriptContentsFunc& readScriptContentsFunc = {});
~UIScripting () noexcept;
private:
UIScripting ();
void afterParsing (IUIDescription* desc) override;
void beforeSaving (IUIDescription* desc) override;
void onDestroy (IUIDescription* desc) override;
CreateTemplateViewFunc onCreateTemplateView (const IUIDescription* desc,
const CreateTemplateViewFunc& f) override;
IViewFactory* getViewFactory (IUIDescription* desc, IViewFactory* originalFactory) override;
void onEditingStart (IUIDescription* desc) override;
void onEditingEnd (IUIDescription* desc) override;
struct Impl;
std::unique_ptr<Impl> impl;
friend std::unique_ptr<UIScripting> std::make_unique<UIScripting> ();
};
//------------------------------------------------------------------------
/** Script context interface
*
* @ingroup new_in_4_15
*/
struct IScriptContext
{
virtual ~IScriptContext () = default;
/** Evaluate custom code in the script context
*
* @param script The script to execute
* @return Result object as json string
*/
virtual std::string eval (std::string_view script) const = 0;
};
//------------------------------------------------------------------------
/** Extends IController
*
* The script controller extension adds script related methods to the controller.
*
* It can alter the scripts for the views if needed and scripts can get and set properties.
*
* @ingroup new_in_4_15
*/
struct IScriptControllerExtension
{
/** A property value is either an integer, double, string or undefined (nullptr_t) */
using PropertyValue = std::variant<std::nullptr_t, int64_t, double, std::string>;
/** Verify the script for a view
*
* called before the script is executed
*
* @param view The view
* @param script The script
* @param context The script context where the script is executed in
* @return Optional new script. If the optional is empty the original script is used.
*/
virtual std::optional<std::string> verifyScript (CView* view, const std::string& script,
const IScriptContext* context) = 0;
/** Notification that the script context is destroyed
*
* don't call the context anymore after this call
*
* @param context The context which is destroyed
*/
virtual void scriptContextDestroyed (const IScriptContext* context) = 0;
/** Get a property
*
* called from a script
*
* if the propery exists, the value should be set and the return value should be true.
* Otherwise return false.
*
* @param view The view
* @param name The name of the property
* @param value The property value
* @return True on success.
*/
virtual bool getProperty (CView* view, std::string_view name, PropertyValue& value) const = 0;
/** Set a property
*
* called from a script
*
* @param view The view
* @param name The name of the property
* @param value The value of the property
* @return True on success.
*/
virtual bool setProperty (CView* view, std::string_view name, const PropertyValue& value) = 0;
};
//------------------------------------------------------------------------
/** Adapter for IScriptControllerExtension */
struct ScriptControllerExtensionAdapter : IScriptControllerExtension
{
std::optional<std::string> verifyScript (CView*, const std::string&,
const IScriptContext*) override
{
return {};
}
void scriptContextDestroyed (const IScriptContext* context) override {}
bool getProperty (CView*, std::string_view, PropertyValue&) const override { return false; }
bool setProperty (CView*, std::string_view, const PropertyValue&) override { return false; }
};
//------------------------------------------------------------------------
} // VSTGUI
@@ -0,0 +1,468 @@
## UI Description Scripting
### Introduction
There is simple scripting support added to the uidescription editor when the cmake
option `VSTGUI_UISCRIPTING` is turned on.
This scripting language is based on JavaScript via the TinyJS library and supports
variables, arrays, structures and objects with inheritance (not fully implemented).
### Getting started
You need to add the scripting library to your target. With cmake this is done by adding
`target_link_libraries(${target} PRIVATE vstgui_uiscripting)` to your CMakeLists.txt.
The library needs to be initialized once at runtime before any UIDescription object is created.
This is done with a call to `VSTGUI::UIScripting::init ();`. You need to include
`"vstgui/uiscripting/uiscripting.h"` for this symbol.
The init function accepts two optional parameters. The first parameter is a callback
function that will be called whenever an exception occurs within a script, allowing you to
handle any errors or exceptions that may arise. The second parameter is also a function
that is invoked when a script is loaded, providing its name as input. This feature can be
used, for instance, to load scripts directly from your source repository instead of the
default location in the resource folder of your plug-in or application.
```cpp
UIScripting::ReadScriptContentsFunc loadScriptFromRepositoryPath = {};
#if DEBUG
// in Debug mode, we want to load the scripts from the repository instead of from the app
// resource folder as the scripts in the app resource folder are only synchronized when we build
// the app and not in-between.
loadScriptFromRepositoryPath = [] (auto filename) -> std::string {
std::filesystem::path path (__FILE__);
if (!path.empty ())
{
path = path.parent_path ().parent_path ();
path.append ("resource");
path.append ("scripts");
path.append (filename);
if (std::filesystem::exists (path))
{
std::ifstream f (path, std::ios::in | std::ios::binary);
const auto sz = std::filesystem::file_size (path);
std::string result (sz, '\0');
f.read (result.data (), sz);
return result;
}
}
return {};
};
#endif
UIScripting::init ({}, loadScriptFromRepositoryPath);
```
All scripts of one UIDescription object will be executed in the same JavaScript context. So you
can access global variables from all your scripts if needed.
### The view script
A script runs when a view is created and has a script assigned to its `script` attribute.
The script attribute either contains the script text or a reference to a file inside
the resource directory of the plugin/application containing the script text.
A reference to a script file is done via
```js
//scriptfile $SCRIPT_FILENAME
```
The script is executed directly after the view was created and before its other attributes are set and before child views are added.
### The **view** variable
The script contains a view variable which among other things can be used to add listeners on actions on the view.
To install a function that is executed whenever the mouse enters the view do:
```js
view.onMouseEnter = function (view, event) {
// do something when the mouse enters this view...
};
```
See [**View Listeners**](#view-listeners) which listeners can be installed on views.
And see [**View methods and properties**](#view-methods-and-properties) for the other methods a view supports.
If the view is a control it additionally supports the methods described in [**Control methods and properties**](#control-methods-and-properties)
and the listeners as described in [**Control listeners**](#control-listeners).
If the view is a view container then it supports the additional listeners described in [**View container listeners**](#view-container-listeners).
### The JavaScriptDrawableView
You can add a `JavaScriptDrawableView` from the view types to your view hierarchy to create a view
that you need to completely program via the script.
Event handling is done via the previously mentioned listeners and drawing is done by implementing
the draw function of the view:
```js
view.draw = function(drawContext, dirtyRect) {
}
```
The [drawContext](#the-drawcontext-object) parameter is the object where you call its method to draw things into the view
and the dirtyRect parameter contains the rectangle that needs to be drawn.
Additionally the following two functions can be implemented to provide custom focus drawing:
```js
view.drawFocusOnTop = function () { return false; };
view.getFocusPath = function(path, focusWidth) {
var bounds = view.getBounds();
path.addRoundRect (bounds, 4);
bounds.left -= focusWidth;
bounds.right += focusWidth;
bounds.top -= focusWidth;
bounds.bottom += focusWidth;
path.addRoundRect (bounds, 4);
return true;
};
```
See the C++ IFocusDrawing interface for a description of these methods.
### The JavaScriptDrawableControl
You can add a `JavaScriptDrawableControl` from the view types to your view hierarchy to create a
control that you need to completely program via the script.
In addition to the `JavaScriptDrawableView` you can also use the
[**Control methods and properties**](#control-methods-and-properties) and
[**Control listeners**](#control-listeners) on this view.
### The **uiDesc** variable
The script also contains the uiDesc variable you can use to query information on the uiDesc file.
The following methods are implemented on that object:
|name |arguments |return type |comments |
|---------------|-------------|---------------|----------------------------------------------|
|colorNames | |`array<string>`|get all color names from the UIDesc file |
|fontNames | |`array<string>`|get all font names from the UIDesc file |
|bitmapNames | |`array<string>`|get all bitmap names from the UIDesc file |
|gradientNames | |`array<string>`|get all gradient names from the UIDesc file |
|controlTagNames| |`array<string>`|get all control tag names from the UIDesc file|
|getTagForName |name:`string`|`integer` |get the tag for the control tag name |
|lookupTagName |tag:`integer`|`string` |get the control tag name from the tag |
### Interacting with c++ code
To interact with the c++ code set a subcontroller on the view or its parents and extend the `IController` with `IScriptControllerExtension`
and implement its methods. From the script you can call the view methods `getControllerProperty` or `setControllerProperty`.
A property can either be an integer, floating point, string or undefined.
### View methods and properties
|type |name |arguments |return type|comments |
|--------|---------------------|------------------------------|-----------|----------------------------------------------------|
|property|type | |`string` |contains the type of the view like `CAnimKnob` |
|method |isTypeOf |name:`string` |`integer` |returns true if the view is of type `name` |
|method |getParent | |`object` |returns the parent view or undefined if it has none |
|method |getAttribute |key:`string` |`string` |get the attribute with name `key` |
|method |setAttribute |key:`string`,value:`string` |`string` |set the attribute value with name `key` to `value` |
|method |getControllerProperty|name:`string` |`property` |set a controller property. returns true if succeeded|
|method |setControllerProperty|name:`string`,value:`property`|`property` |get a controller property. returns a property |
|method |getBounds | |`object` |returns the bounds of the view as a rectangle |
For example to set the opacity attribute of a view write:
```js
view.setAttribute("opacity", 0.5);
```
You can set any attribute as shown in the WYSIWYG editor.
### View listeners
|name |arguments |
|--------------|-----------------------------|
|onAttached |view:`object` |
|onRemoved |view:`object` |
|onSizeChanged |view:`object`,newSize:`rect` |
|onLostFocus |view:`object` |
|onTookFocus |view:`object` |
|onMouseEnabled|view:`object`,state:`boolean`|
|onMouseEnter |view:`object`,event:`object` |
|onMouseExit |view:`object`,event:`object` |
|onMouseDown |view:`object`,event:`object` |
|onMouseUp |view:`object`,event:`object` |
|onMouseMove |view:`object`,event:`object` |
|onMouseWheel |view:`object`,event:`object` |
|onKeyDown |view:`object`,event:`object` |
|onKeyUp |view:`object`,event:`object` |
TODO: Describe Event object
### Control methods and properties
|type |name |arguments |return type|comments |
|--------|---------------------|--------------|-----------|----------------------------------------------------|
|method |setValue |value:`double`|`void` | set the control value to `value` |
|method |getValue | |`double` | get the control value |
|method |setValueNormalized |value:`double`|`void` | set the control value from the normalized `value` |
|method |getValueNormalized | |`double` | get the normalized control value |
|method |beginEdit | |`void` | begin editing the control |
|method |endEdit | |`void` | end editing the control |
|method |getMinValue | |`double` | get the minimum value of the control |
|method |getMaxValue | |`double` | get the maximum value of the control |
|method |getTag | |`integer` | get the control tag |
### Control listeners
|name |arguments |
|--------------|----------------------------|
|onValueChanged|view:`object`,value:`double`|
|onBeginEdit |view:`object` |
|onEndEdit |view:`object` |
### View container listeners
|name |arguments |
|-------------|----------------------------|
|onViewAdded |view:`object`,child:`object`|
|onViewRemoved|view:`object`,child:`object`|
### The drawcontext object
|name |arguments |return type|comments |
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|-----------|----------------------------------------------------|
|clearRect | rect:`Rect` |`void` |
|createRoundGraphicsPath | rect:`Rect`,radius:`double` |`Path` |
|createGraphicsPath | |`Path` |
|createGradient | startColorPosition:`double`,startColor:`Color`,endColorPosition:`double`,endColor:`Color` |`Gradient` |
|getStringWidth | string:`string` |`double` |
|drawArc | rect:`Rect`,startAngle:`double`,endAngle:`double`,style:`string` |`void` |style can be `stroked`,`filled` or `filledAndStroked`
|drawBitmap | name:`string`,destRect:`Rect`,offsetPoint?:`Point`,alpha?:`double` |`void` |name is the bitmapname as declared in the uidesc file
|drawEllipse | rect:`Rect`,style:`string` |`void` |style can be `stroked`,`filled` or `filledAndStroked`
|drawGraphicsPath | path:`Path`,mode?:`string`,transform?:`TransformMatrix` |`void` |mode can be `stroked`, `filled` or `filledEvenOdd`
|drawLine | from:`Point`,to:`Point` |`void` |
|drawPolygon | points:`PointArray`,style:`string` |`void` |style can be `stroked`,`filled` or `filledAndStroked`
|drawRect | rect:`Rect`,style:`string` |`void` |style can be `stroked`,`filled` or `filledAndStroked`
|drawString | string:`string`,rect:`Rect`,align?:`string` |`void` |align can be `left`, `center` or `right`
|fillLinearGradient | path:`Path`,gradient:`Gradient`,startPoint:`Point`,endPoint:`Point`,evenOdd?:`bool`,transform?:`TransformMatrix` |`void` |
|fillRadialGradient | path:`Path`,gradient:`Gradient`,centerPoint:`Point`,radius:`double`,originOffsetPoint?:`Point`,evenOdd?:`bool`,transform?:`TransformMatrix`|`void` |
|restoreGlobalState | |`void` |
|saveGlobalState | |`void` |
|setClipRect | rect:`Rect` |`void` |
|setFont | name:`string` |`void` |name is the fontname as declared in the uidesc file
|setFontColor | color:`Color` |`void` |
|setFillColor | color:`Color` |`void` |
|setFrameColor | color:`Color` |`void` |
|setGlobalAlpha | alpha:`double` |`void` |
|setLineWidth | width:`double` |`void` |
|setLineStyle | styleOrLineCap:`string`,lineJoin?:`string`,dashLengths?:`doubleArray`,dashPhase?:`double` |`void` |
|setDrawMode | mode:`string` |`void` |
The `setLineStyle` method takes either one argument: the `style which can be `solid` or `dotted`. Or it can take 4 arguments:
* lineCap [required] : `butt`, `round` or `square`
* lineJoin [opt] : `miter, `round` or `bevel`
* dashLength [opt] : `doubleArray`
* dashPhase [opt] : `double`
The `Rect` object has 4 properties: `left`, `top`, `right` and `bottom`.
The `Point` object has 2 properties: `x` and `y`.
The `Color` object is either a CSS color name, a colorname as described in the uidesc file or an rgba value in hex form: `#FF00FFAA`.
### The path object
The path object can be created via the drawcontext object methods `createGraphicsPath` and `createRoundGraphicsPath` and has these methods:
|name |arguments |
|----------------|------------------------------------------------------------------|
|addEllipse |rect:`Rect` |
|addArc |rect:`Rect`,startAngle:`double`,endAngle:`double`,clockwise:`bool`|
|addBezierCurve |control1:`Point`,control2:`Point`,end:`Point` |
|addLine |to:`Point` |
|addPath |path:`Path`,transformMatrix?:`TransformMatrix` |
|addRect |rect:`Rect` |
|addRoundRect |rect:`Rect`,radius:`double` |
|closeSubpath | |
|beginSubpath |start:`Point` |
### The TransformMatrix object
A transform matrix object is created via the global `makeTransformMatrix ()` function and has these methods:
|name |arguments |return type |
|--------------|---------------------------------|-----------------|
|concat |transformMatrix:`TransformMatrix`|`void` |
|inverse | |`TransformMatrix`|
|rotate |angle:`double`, center?:`Point` |`void` |
|scale |x:`double`, y:`double` |`void` |
|skewX |angle:`double` |`void` |
|skewY |angle:`double` |`void` |
|translate |x:`double`, y:`double` |`void` |
|transform |pointOrRect:`Point` or `Rect` |`Point` or `Rect`|
### Global functions
- `createTimer(context, fireTime, callback) -> TimerObject`
- creates a new stopped timer object
- fire time is in milliseconds
- the context will be provided on every timer callback
- the callback has the signature `function(context)`
- a timer object has the methods:
- start()
- stop()
- invalid()
- `iterateSubViews(view, context, callback) -> Void`
- calls the callback with context as parameter for every child view of view
- `log(obj) -> Void`
- logs the object to the debug console
- `makeTransformMatrix() -> TransformMatrix`
- see [TransformMatrix](#the-transformmatrix-object)
### Other built in functions (from the TinyJS library)
- `exec(jsCode)`
- `eval(jsCode)`
- `trace()`
- `charToInt(ch)`
- `Object.dump()`
- `Object.clone()`
- `String.indexOf(search)`
- `String.substring(lo,hi)`
- `String.charAt(pos)`
- `String.charCodeAt(pos)`
- `String.fromCharCode(cha`
- `String.split(separator)`
- `Integer.parseInt(str)`
- `Integer.valueOf(str)`
- `JSON.stringify(obj)`
- `Array.contains(obj)`
- `Array.remove(obj)`
- `Array.join(separator)`
- `Math.rand()`
- `Math.randInt(min, max)`
- `Math.abs(a)`
- `Math.round(a)`
- `Math.min(a,b)`
- `Math.max(a,b)`
- `Math.range(x,a,b)`
- `Math.sign(a)`
- `Math.PI()`
- `Math.toDegrees(a)`
- `Math.toRadians(a)`
- `Math.sin(a)`
- `Math.asin(a)`
- `Math.cos(a)`
- `Math.acos(a)`
- `Math.tan(a)`
- `Math.atan(a)`
- `Math.sinh(a)`
- `Math.asinh(a)`
- `Math.cosh(a)`
- `Math.acosh(a)`
- `Math.tanh(a)`
- `Math.atanh(a)`
- `Math.E()`
- `Math.log(a)`
- `Math.log10(a)`
- `Math.exp(a)`
- `Math.pow(a,b)`
- `Math.sqr(a)`
- `Math.sqrt(a)`
### Example
```js
// Hover Opacity Animation Script
// This example script changes the opacity of the view
// when the mouse enters or exits the view
/* the default opacity of the view is stored in view.default_opacity */
view.default_opacity = 0.6;
/* the current opacity of the view is stored in view.opacity */
view.opacity = view.default_opacity;
/* the timer to change the opacity is stored in view.opacity_timer */
view.opacity_timer = createTimer(view, 16, function(view) {
view.opacity += view.opacity_change;
if (view.opacity_change > 0)
{
if (view.opacity > 1)
{
view.opacity = 1;
view.opacity_timer.stop();
}
}
else
{
if (view.opacity <= view.default_opacity)
{
view.opacity = view.default_opacity;
view.opacity_timer.stop();
}
}
view.setAttribute("opacity", view.opacity);
});
/* the view will be shown with full opacity when focused so the state of the focus is stored in view.hasFocus */
view.has_focus = false;
/* to correctly restore the hover state after focus lost, the state if the mouse is inside the view or outside
is stored in view.mouseInside
*/
view.mouse_inside = false;
/* we install a mouse enter listener
when the mouse enters the view we start the opacity change timer
*/
view.onMouseEnter = function(view, event) {
view.mouse_inside = true;
if (view.has_focus)
return;
view.opacity_change = 0.075;
view.opacity_timer.start();
event.consume = true;
};
/* we also install a mouse exit listener
when the mouse exits the view we start the opacity change timer again
now with a negative opacity_change variable so that in the timer callback
the opacity is going back to the default opacity
*/
view.onMouseExit = function(view, event) {
view.mouse_inside = false;
if (view.has_focus)
return;
view.opacity_change = -0.05;
view.opacity_timer.start();
event.consumed = true;
};
/* we also install a view removed listener so that we can cleanup and stop the timer */
view.onRemoved = function(view) {
// cleanup, when the view is removed, stop the timer
view.opacity_timer.stop();
};
/* when the view takes focus we show the view with full opacity */
view.onTookFocus = function(view) {
view.has_focus = true;
view.opacity_timer.stop();
view.setAttribute("opacity", 1);
view.opacity = 1;
};
/* when the view lost focus we start the opacity animation when the mouse is not inside this view */
view.onLostFocus = function(view) {
view.has_focus = false;
if (!view.mouse_inside)
{
view.onMouseExit(view, undefind);
}
};
/* enable the mouse, otherwise no mouse listener is called */
view.setAttribute("mouse-enabled", true);
/* set the initial view opacity*/
view.setAttribute("opacity", view.opacity);
```