Observers emit events into Lua via a host-provided callback.
+ */
+public interface ObserverHost {
+
+ /**
+ * Callback used by the host to notify observers.
+ */
+ @FunctionalInterface
+ interface ObserverCallback {
+ /**
+ * Called on observer events.
+ *
+ * @param type observer type ("mutation","resize","intersection")
+ * @param targetId element id
+ * @param data payload map
+ */
+ void onEvent(String type, String targetId, Map data);
+ }
+
+ /**
+ * Sets the callback to receive observer events.
+ *
+ * @param callback callback (nullable to disable)
+ */
+ void setCallback(ObserverCallback callback);
+
+ /**
+ * Observes DOM mutations on an element (subtree).
+ *
+ * @param elementId element id
+ * @param subtree true to include subtree
+ * @param attributes true to observe attributes
+ * @param childList true to observe childList
+ * @param characterData true to observe text changes
+ */
+ void observeMutations(String elementId, boolean subtree, boolean attributes, boolean childList, boolean characterData);
+
+ /**
+ * Stops mutation observing for an element.
+ *
+ * @param elementId element id
+ */
+ void unobserveMutations(String elementId);
+
+ /**
+ * Observes resize changes of an element.
+ *
+ * @param elementId element id
+ */
+ void observeResize(String elementId);
+
+ /**
+ * Stops resize observing for an element.
+ *
+ * @param elementId element id
+ */
+ void unobserveResize(String elementId);
+
+ /**
+ * Observes intersection changes of an element with viewport.
+ *
+ * @param elementId element id
+ * @param threshold threshold in [0..1]
+ */
+ void observeIntersection(String elementId, double threshold);
+
+ /**
+ * Stops intersection observing for an element.
+ *
+ * @param elementId element id
+ */
+ void unobserveIntersection(String elementId);
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/hosts/SchedulerHost.java b/src/main/java/org/openautonomousconnection/luascript/hosts/SchedulerHost.java
new file mode 100644
index 0000000..858f15b
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/hosts/SchedulerHost.java
@@ -0,0 +1,53 @@
+package org.openautonomousconnection.luascript.hosts;
+
+/**
+ * Scheduling primitives comparable to JavaScript timers.
+ *
+ * No networking. This host only provides time-based callbacks.
+ */
+public interface SchedulerHost {
+
+ /**
+ * Schedules a one-shot callback after a delay.
+ *
+ * @param delayMillis delay in milliseconds (>= 0)
+ * @param callback callback to run on host-defined thread (typically Lua thread)
+ * @return handle id
+ */
+ long setTimeout(long delayMillis, Runnable callback);
+
+ /**
+ * Schedules a repeating callback with fixed rate.
+ *
+ * @param intervalMillis interval in milliseconds (> 0)
+ * @param callback callback to run
+ * @return handle id
+ */
+ long setInterval(long intervalMillis, Runnable callback);
+
+ /**
+ * Cancels a timeout/interval handle.
+ *
+ * @param handle handle id
+ * @return true if canceled
+ */
+ boolean clear(long handle);
+
+ /**
+ * Schedules a callback for the next animation frame.
+ *
+ * Comparable to requestAnimationFrame. The callback is invoked once.
+ *
+ * @param callback callback to run
+ * @return handle id
+ */
+ long requestAnimationFrame(Runnable callback);
+
+ /**
+ * Cancels a previously scheduled animation frame.
+ *
+ * @param handle handle id
+ * @return true if canceled
+ */
+ boolean cancelAnimationFrame(long handle);
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/hosts/SelectorHost.java b/src/main/java/org/openautonomousconnection/luascript/hosts/SelectorHost.java
new file mode 100644
index 0000000..957e140
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/hosts/SelectorHost.java
@@ -0,0 +1,45 @@
+package org.openautonomousconnection.luascript.hosts;
+
+import java.util.List;
+
+/**
+ * CSS selector based DOM querying/traversal.
+ *
+ * All returned elements are identified by stable element ids.
+ */
+public interface SelectorHost {
+
+ /**
+ * Returns the first element matching the selector, or null.
+ *
+ * @param selector CSS selector
+ * @return element id or null
+ */
+ String querySelector(String selector);
+
+ /**
+ * Returns all elements matching the selector.
+ *
+ * @param selector CSS selector
+ * @return list of element ids (never null)
+ */
+ List querySelectorAll(String selector);
+
+ /**
+ * Checks if an element matches a selector.
+ *
+ * @param elementId element id
+ * @param selector CSS selector
+ * @return true if matches
+ */
+ boolean matches(String elementId, String selector);
+
+ /**
+ * Returns the closest ancestor (including itself) matching selector, or null.
+ *
+ * @param elementId element id
+ * @param selector CSS selector
+ * @return closest element id or null
+ */
+ String closest(String elementId, String selector);
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/hosts/StorageHost.java b/src/main/java/org/openautonomousconnection/luascript/hosts/StorageHost.java
new file mode 100644
index 0000000..75244e1
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/hosts/StorageHost.java
@@ -0,0 +1,67 @@
+package org.openautonomousconnection.luascript.hosts;
+
+import java.util.List;
+
+/**
+ * Storage primitives comparable to localStorage/sessionStorage.
+ */
+public interface StorageHost {
+
+ /**
+ * @return keys in localStorage
+ */
+ List localKeys();
+
+ /**
+ * @param key key
+ * @return value or null
+ */
+ String localGet(String key);
+
+ /**
+ * @param key key
+ * @param value value (null removes)
+ */
+ void localSet(String key, String value);
+
+ /**
+ * Removes a key from localStorage.
+ *
+ * @param key key
+ */
+ void localRemove(String key);
+
+ /**
+ * Clears localStorage.
+ */
+ void localClear();
+
+ /**
+ * @return keys in sessionStorage
+ */
+ List sessionKeys();
+
+ /**
+ * @param key key
+ * @return value or null
+ */
+ String sessionGet(String key);
+
+ /**
+ * @param key key
+ * @param value value (null removes)
+ */
+ void sessionSet(String key, String value);
+
+ /**
+ * Removes a key from sessionStorage.
+ *
+ * @param key key
+ */
+ void sessionRemove(String key);
+
+ /**
+ * Clears sessionStorage.
+ */
+ void sessionClear();
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/hosts/UtilHost.java b/src/main/java/org/openautonomousconnection/luascript/hosts/UtilHost.java
new file mode 100644
index 0000000..c0abc90
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/hosts/UtilHost.java
@@ -0,0 +1,70 @@
+package org.openautonomousconnection.luascript.hosts;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility helpers commonly available in browsers.
+ *
+ * No networking.
+ */
+public interface UtilHost {
+
+ /**
+ * Encodes bytes (UTF-8) as Base64.
+ *
+ * @param text text
+ * @return base64
+ */
+ String base64Encode(String text);
+
+ /**
+ * Decodes Base64 into UTF-8 text.
+ *
+ * @param base64 base64
+ * @return decoded text
+ */
+ String base64Decode(String base64);
+
+ /**
+ * Generates cryptographically-strong random bytes and returns as hex string.
+ *
+ * @param numBytes number of bytes (>0)
+ * @return hex string
+ */
+ String randomHex(int numBytes);
+
+ /**
+ * Parses a URL string into components.
+ *
+ * @param url url string
+ * @return map containing scheme,host,port,path,query,fragment
+ */
+ Map parseUrl(String url);
+
+ /**
+ * Parses a query string into key->list(values).
+ *
+ * @param query query string (with or without leading '?')
+ * @return map key->values
+ */
+ Map> parseQuery(String query);
+
+ /**
+ * JSON.stringify via browser engine (returns JSON string).
+ *
+ * @param elementId element id that provides the JS context (ignored by some engines but kept for safety)
+ * @param jsExpr JS expression returning a JSON-serializable value
+ * @return JSON string
+ */
+ String jsonStringifyExpr(String elementId, String jsExpr);
+
+ /**
+ * JSON.parse via browser engine and returns normalized JSON string (stringify(parse(x))).
+ *
+ * @param elementId element id providing JS context
+ * @param json json string
+ * @return normalized json
+ */
+ String jsonNormalize(String elementId, String json);
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java b/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java
index cd47d0d..5f64f18 100644
--- a/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java
+++ b/src/main/java/org/openautonomousconnection/luascript/runtime/FxLuaScriptEngine.java
@@ -80,14 +80,27 @@ public final class FxLuaScriptEngine implements AutoCloseable {
.sandbox(true)
);
- HostServices.StdoutConsole console = new HostServices.StdoutConsole("[lua] ");
- FxUiHost uiHost = new FxUiHost(engine, dom);
- FxWebViewResourceHost resourceHost = new FxWebViewResourceHost(engine);
FxEventHost eventHost = new FxEventHost(dom);
- FxAudioHost audioHost = new FxAudioHost();
- FxVideoHost videoHost = new FxVideoHost(engine, dom);
- FxImageHost imageHost = new FxImageHost(engine, dom);
- HostServices services = new HostServices.Default(uiHost, dom, eventHost, resourceHost, console, audioHost, imageHost, videoHost);
+
+ HostServices services = new HostServices.Default(
+ new FxUiHost(engine, dom),
+ dom,
+ eventHost,
+ new FxWebViewResourceHost(engine),
+ new HostServices.StdoutConsole("[lua] "),
+ new FxAudioHost(),
+ new FxImageHost(engine, dom),
+ new FxVideoHost(engine, dom),
+ new FxSchedulerHost(),
+ new FxSelectorHost(engine, dom),
+ new FxGeometryHost(engine, dom),
+ new FxCssHost(engine, dom),
+ new FxStorageHost(engine, dom),
+ new FxUtilHost(engine, dom),
+ new FxClipboardHost(),
+ new FxObserverHost(engine, dom)
+ );
+
LuaRuntime rt = new LuaRuntime(globals, services, policy);
eventHost.setRouter(rt.eventRouter());
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/ClipboardTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/ClipboardTable.java
new file mode 100644
index 0000000..e1a113c
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/ClipboardTable.java
@@ -0,0 +1,43 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.*;
+import org.openautonomousconnection.luascript.hosts.ClipboardHost;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+/**
+ * Lua table: clipboard
+ *
+ * Functions:
+ *
+ * - clipboard.set(text)
+ * - clipboard.get() -> text
+ *
+ */
+public final class ClipboardTable extends ScriptTable {
+
+ public ClipboardTable() {
+ super("clipboard");
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ ClipboardHost host = services.clipboard().orElseThrow(() -> new IllegalStateException("ClipboardHost not provided"));
+
+ table().set("set", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue text) {
+ host.setText(text.isnil() ? "" : text.tojstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("get", new ZeroArgFunction() {
+ @Override
+ public LuaValue call() {
+ return LuaValue.valueOf(host.getText());
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/CssTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/CssTable.java
new file mode 100644
index 0000000..162eafb
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/CssTable.java
@@ -0,0 +1,86 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.*;
+import org.openautonomousconnection.luascript.events.JavaToLua;
+import org.openautonomousconnection.luascript.hosts.CssHost;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+import java.util.Map;
+
+/**
+ * Lua table: css
+ *
+ * Functions:
+ *
+ * - css.computed(id, prop) -> string
+ * - css.computedMany(id, {props...}) -> map
+ * - css.inlineGet(id, prop) -> string
+ * - css.inlineSet(id, prop, value)
+ * - css.varGet(id, name) -> string
+ * - css.varSet(id, name, value)
+ *
+ */
+public final class CssTable extends ScriptTable {
+
+ public CssTable() {
+ super("css");
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ CssHost host = services.css().orElseThrow(() -> new IllegalStateException("CssHost not provided"));
+
+ table().set("computed", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue prop) {
+ return LuaValue.valueOf(host.getComputedStyle(id.checkjstring(), prop.checkjstring()));
+ }
+ });
+
+ table().set("computedMany", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue propsTable) {
+ if (!propsTable.istable()) throw new IllegalArgumentException("props must be a table");
+ int n = propsTable.length();
+ String[] props = new String[n];
+ for (int i = 1; i <= n; i++) {
+ props[i - 1] = propsTable.get(i).checkjstring();
+ }
+ Map m = host.getComputedStyles(id.checkjstring(), props);
+ return JavaToLua.coerce(m);
+ }
+ });
+
+ table().set("inlineGet", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue prop) {
+ return LuaValue.valueOf(host.getInlineStyle(id.checkjstring(), prop.checkjstring()));
+ }
+ });
+
+ table().set("inlineSet", new ThreeArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue prop, LuaValue value) {
+ host.setInlineStyle(id.checkjstring(), prop.checkjstring(), value.isnil() ? "" : value.tojstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("varGet", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue name) {
+ return LuaValue.valueOf(host.getCssVariable(id.checkjstring(), name.checkjstring()));
+ }
+ });
+
+ table().set("varSet", new ThreeArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue name, LuaValue value) {
+ host.setCssVariable(id.checkjstring(), name.checkjstring(), value.isnil() ? "" : value.tojstring());
+ return LuaValue.NIL;
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/GeometryTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/GeometryTable.java
new file mode 100644
index 0000000..e30ac1c
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/GeometryTable.java
@@ -0,0 +1,70 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaTable;
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.OneArgFunction;
+import org.luaj.vm2.lib.TwoArgFunction;
+import org.luaj.vm2.lib.VarArgFunction;
+import org.luaj.vm2.Varargs;
+import org.openautonomousconnection.luascript.events.JavaToLua;
+import org.openautonomousconnection.luascript.hosts.GeometryHost;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+import java.util.Map;
+
+/**
+ * Lua table: geometry
+ *
+ * Functions:
+ *
+ * - geometry.rect(id) -> map
+ * - geometry.viewport() -> map
+ * - geometry.scrollTo(x,y)
+ * - geometry.scrollIntoView(id, align?)
+ *
+ */
+public final class GeometryTable extends ScriptTable {
+
+ public GeometryTable() {
+ super("geometry");
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ GeometryHost host = services.geometry().orElseThrow(() -> new IllegalStateException("GeometryHost not provided"));
+
+ table().set("rect", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id) {
+ Map m = host.getBoundingClientRect(id.checkjstring());
+ return JavaToLua.coerce(m);
+ }
+ });
+
+ table().set("viewport", new org.luaj.vm2.lib.ZeroArgFunction() {
+ @Override
+ public LuaValue call() {
+ return JavaToLua.coerce(host.getViewport());
+ }
+ });
+
+ table().set("scrollTo", new VarArgFunction() {
+ @Override
+ public Varargs invoke(Varargs args) {
+ host.scrollTo(args.arg(1).checkdouble(), args.arg(2).checkdouble());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("scrollIntoView", new VarArgFunction() {
+ @Override
+ public Varargs invoke(Varargs args) {
+ String id = args.arg(1).checkjstring();
+ String align = args.narg() >= 2 && !args.arg(2).isnil() ? args.arg(2).tojstring() : null;
+ host.scrollIntoView(id, align);
+ return LuaValue.NIL;
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/ObserversTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/ObserversTable.java
new file mode 100644
index 0000000..3491390
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/ObserversTable.java
@@ -0,0 +1,103 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaFunction;
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.Varargs;
+import org.luaj.vm2.lib.*;
+import org.openautonomousconnection.luascript.events.JavaToLua;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.hosts.ObserverHost;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+import java.util.Map;
+
+/**
+ * Lua table: observers
+ *
+ * Functions:
+ *
+ * - observers.on(fn(type, targetId, dataTable))
+ * - observers.mutationObserve(id, subtree, attributes, childList, characterData)
+ * - observers.mutationUnobserve(id)
+ * - observers.resizeObserve(id)
+ * - observers.resizeUnobserve(id)
+ * - observers.intersectionObserve(id, threshold)
+ * - observers.intersectionUnobserve(id)
+ *
+ */
+public final class ObserversTable extends ScriptTable {
+
+ public ObserversTable() {
+ super("observers");
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ ObserverHost host = services.observers().orElseThrow(() -> new IllegalStateException("ObserverHost not provided"));
+
+ table().set("on", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue fn) {
+ LuaFunction cb = fn.checkfunction();
+ host.setCallback((type, targetId, data) -> {
+ LuaValue luaData = JavaToLua.coerce(data);
+ cb.call(LuaValue.valueOf(type), LuaValue.valueOf(targetId), luaData);
+ });
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("mutationObserve", new VarArgFunction() {
+ @Override
+ public Varargs invoke(Varargs args) {
+ String id = args.arg(1).checkjstring();
+ boolean subtree = args.arg(2).optboolean(false);
+ boolean attributes = args.arg(3).optboolean(true);
+ boolean childList = args.arg(4).optboolean(true);
+ boolean characterData = args.arg(5).optboolean(false);
+ host.observeMutations(id, subtree, attributes, childList, characterData);
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("mutationUnobserve", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id) {
+ host.unobserveMutations(id.checkjstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("resizeObserve", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id) {
+ host.observeResize(id.checkjstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("resizeUnobserve", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id) {
+ host.unobserveResize(id.checkjstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("intersectionObserve", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue threshold) {
+ host.observeIntersection(id.checkjstring(), threshold.checkdouble());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("intersectionUnobserve", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id) {
+ host.unobserveIntersection(id.checkjstring());
+ return LuaValue.NIL;
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/SchedulerTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/SchedulerTable.java
new file mode 100644
index 0000000..5439b36
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/SchedulerTable.java
@@ -0,0 +1,78 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaFunction;
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.OneArgFunction;
+import org.luaj.vm2.lib.TwoArgFunction;
+import org.luaj.vm2.lib.VarArgFunction;
+import org.luaj.vm2.Varargs;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.hosts.SchedulerHost;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+/**
+ * Lua table: scheduler
+ *
+ * Functions:
+ *
+ * - scheduler.timeout(ms, fn) -> id
+ * - scheduler.interval(ms, fn) -> id
+ * - scheduler.clear(id) -> boolean
+ * - scheduler.raf(fn) -> id
+ * - scheduler.cancelRaf(id) -> boolean
+ *
+ */
+public final class SchedulerTable extends ScriptTable {
+
+ public SchedulerTable() {
+ super("scheduler");
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ SchedulerHost host = services.scheduler().orElseThrow(() -> new IllegalStateException("SchedulerHost not provided"));
+
+ table().set("timeout", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue ms, LuaValue fn) {
+ long delay = ms.checklong();
+ LuaFunction cb = fn.checkfunction();
+ long id = host.setTimeout(delay, () -> cb.call());
+ return LuaValue.valueOf(id);
+ }
+ });
+
+ table().set("interval", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue ms, LuaValue fn) {
+ long interval = ms.checklong();
+ LuaFunction cb = fn.checkfunction();
+ long id = host.setInterval(interval, () -> cb.call());
+ return LuaValue.valueOf(id);
+ }
+ });
+
+ table().set("clear", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id) {
+ return LuaValue.valueOf(host.clear(id.checklong()));
+ }
+ });
+
+ table().set("raf", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue fn) {
+ LuaFunction cb = fn.checkfunction();
+ long id = host.requestAnimationFrame(() -> cb.call());
+ return LuaValue.valueOf(id);
+ }
+ });
+
+ table().set("cancelRaf", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id) {
+ return LuaValue.valueOf(host.cancelAnimationFrame(id.checklong()));
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/SelectorTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/SelectorTable.java
new file mode 100644
index 0000000..bc7b9f2
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/SelectorTable.java
@@ -0,0 +1,74 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaTable;
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.TwoArgFunction;
+import org.luaj.vm2.lib.OneArgFunction;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.hosts.SelectorHost;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+import java.util.List;
+
+/**
+ * Lua table: selector
+ *
+ * Functions:
+ *
+ * - selector.one(css) -> id|nil
+ * - selector.all(css) -> { ids... }
+ * - selector.matches(id, css) -> boolean
+ * - selector.closest(id, css) -> id|nil
+ *
+ */
+public final class SelectorTable extends ScriptTable {
+
+ public SelectorTable() {
+ super("selector");
+ }
+
+ private static LuaValue toLuaArray(List values) {
+ LuaTable t = new LuaTable();
+ if (values == null || values.isEmpty()) return t;
+ int i = 1;
+ for (String v : values) {
+ t.set(i++, v == null ? LuaValue.NIL : LuaValue.valueOf(v));
+ }
+ return t;
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ SelectorHost host = services.selector().orElseThrow(() -> new IllegalStateException("SelectorHost not provided"));
+
+ table().set("one", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue css) {
+ String id = host.querySelector(css.checkjstring());
+ return id == null ? LuaValue.NIL : LuaValue.valueOf(id);
+ }
+ });
+
+ table().set("all", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue css) {
+ return toLuaArray(host.querySelectorAll(css.checkjstring()));
+ }
+ });
+
+ table().set("matches", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue css) {
+ return LuaValue.valueOf(host.matches(id.checkjstring(), css.checkjstring()));
+ }
+ });
+
+ table().set("closest", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue id, LuaValue css) {
+ String out = host.closest(id.checkjstring(), css.checkjstring());
+ return out == null ? LuaValue.NIL : LuaValue.valueOf(out);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/StorageTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/StorageTable.java
new file mode 100644
index 0000000..e4efa01
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/StorageTable.java
@@ -0,0 +1,121 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaTable;
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.*;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.hosts.StorageHost;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+import java.util.List;
+
+/**
+ * Lua table: storage
+ *
+ * Functions:
+ *
+ * - storage.localGet(key) -> string|nil
+ * - storage.localSet(key, value|nil)
+ * - storage.localKeys() -> {keys...}
+ * - storage.localRemove(key)
+ * - storage.localClear()
+ * - same for session*
+ *
+ */
+public final class StorageTable extends ScriptTable {
+
+ public StorageTable() {
+ super("storage");
+ }
+
+ private static LuaValue toLuaArray(List values) {
+ LuaTable t = new LuaTable();
+ if (values == null || values.isEmpty()) return t;
+ int i = 1;
+ for (String v : values) t.set(i++, v == null ? LuaValue.NIL : LuaValue.valueOf(v));
+ return t;
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ StorageHost host = services.storage().orElseThrow(() -> new IllegalStateException("StorageHost not provided"));
+
+ table().set("localKeys", new ZeroArgFunction() {
+ @Override
+ public LuaValue call() {
+ return toLuaArray(host.localKeys());
+ }
+ });
+
+ table().set("localGet", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue key) {
+ String v = host.localGet(key.checkjstring());
+ return v == null ? LuaValue.NIL : LuaValue.valueOf(v);
+ }
+ });
+
+ table().set("localSet", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue key, LuaValue value) {
+ host.localSet(key.checkjstring(), value.isnil() ? null : value.tojstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("localRemove", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue key) {
+ host.localRemove(key.checkjstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("localClear", new ZeroArgFunction() {
+ @Override
+ public LuaValue call() {
+ host.localClear();
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("sessionKeys", new ZeroArgFunction() {
+ @Override
+ public LuaValue call() {
+ return toLuaArray(host.sessionKeys());
+ }
+ });
+
+ table().set("sessionGet", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue key) {
+ String v = host.sessionGet(key.checkjstring());
+ return v == null ? LuaValue.NIL : LuaValue.valueOf(v);
+ }
+ });
+
+ table().set("sessionSet", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue key, LuaValue value) {
+ host.sessionSet(key.checkjstring(), value.isnil() ? null : value.tojstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("sessionRemove", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue key) {
+ host.sessionRemove(key.checkjstring());
+ return LuaValue.NIL;
+ }
+ });
+
+ table().set("sessionClear", new ZeroArgFunction() {
+ @Override
+ public LuaValue call() {
+ host.sessionClear();
+ return LuaValue.NIL;
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/tables/UtilTable.java b/src/main/java/org/openautonomousconnection/luascript/tables/UtilTable.java
new file mode 100644
index 0000000..43e51e7
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/luascript/tables/UtilTable.java
@@ -0,0 +1,83 @@
+package org.openautonomousconnection.luascript.tables;
+
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.lib.*;
+import org.openautonomousconnection.luascript.events.JavaToLua;
+import org.openautonomousconnection.luascript.hosts.HostServices;
+import org.openautonomousconnection.luascript.hosts.UtilHost;
+import org.openautonomousconnection.luascript.utils.ScriptTable;
+
+/**
+ * Lua table: util
+ *
+ * Functions:
+ *
+ * - util.base64Encode(text)
+ * - util.base64Decode(b64)
+ * - util.randomHex(bytes)
+ * - util.parseUrl(url) -> map
+ * - util.parseQuery(query) -> map(key->array)
+ * - util.jsonStringifyExpr(elementId, jsExpr) -> jsonString
+ * - util.jsonNormalize(elementId, json) -> jsonString
+ *
+ */
+public final class UtilTable extends ScriptTable {
+
+ public UtilTable() {
+ super("util");
+ }
+
+ @Override
+ protected void define(HostServices services) {
+ UtilHost host = services.util().orElseThrow(() -> new IllegalStateException("UtilHost not provided"));
+
+ table().set("base64Encode", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue text) {
+ return LuaValue.valueOf(host.base64Encode(text.isnil() ? "" : text.tojstring()));
+ }
+ });
+
+ table().set("base64Decode", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue b64) {
+ return LuaValue.valueOf(host.base64Decode(b64.isnil() ? "" : b64.tojstring()));
+ }
+ });
+
+ table().set("randomHex", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue bytes) {
+ return LuaValue.valueOf(host.randomHex(bytes.checkint()));
+ }
+ });
+
+ table().set("parseUrl", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue url) {
+ return JavaToLua.coerce(host.parseUrl(url.checkjstring()));
+ }
+ });
+
+ table().set("parseQuery", new OneArgFunction() {
+ @Override
+ public LuaValue call(LuaValue q) {
+ return JavaToLua.coerce(host.parseQuery(q.isnil() ? "" : q.tojstring()));
+ }
+ });
+
+ table().set("jsonStringifyExpr", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue elementId, LuaValue jsExpr) {
+ return LuaValue.valueOf(host.jsonStringifyExpr(elementId.checkjstring(), jsExpr.checkjstring()));
+ }
+ });
+
+ table().set("jsonNormalize", new TwoArgFunction() {
+ @Override
+ public LuaValue call(LuaValue elementId, LuaValue json) {
+ return LuaValue.valueOf(host.jsonNormalize(elementId.checkjstring(), json.checkjstring()));
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/luascript/values/LuaObject.java b/src/main/java/org/openautonomousconnection/luascript/values/LuaObject.java
deleted file mode 100644
index d5189cb..0000000
--- a/src/main/java/org/openautonomousconnection/luascript/values/LuaObject.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.openautonomousconnection.luascript.values;
-
-import org.luaj.vm2.LuaValue;
-
-public class LuaObject extends LuaValue {
- @Override
- public int type() {
- return 0;
- }
-
- @Override
- public String typename() {
- return "";
- }
-}