merge fix [ci skip]

with some parts from PR by Willy-JL
https://github.com/flipperdevices/flipperzero-firmware/pull/3980/files
This commit is contained in:
MX 2024-11-01 05:30:33 +03:00
parent 04c8093672
commit 3c6a621da2
No known key found for this signature in database
GPG Key ID: 7CCC66B7DBDD1C83
15 changed files with 64 additions and 489 deletions

View File

@ -1,93 +0,0 @@
let eventLoop = require("event_loop");
let gui = require("gui");
let dialog = require("gui/dialog");
let textInput = require("gui/text_input");
let loading = require("gui/loading");
let storage = require("storage");
// No eval() or exec() so need to run code from file, and filename must be unique
storage.makeDirectory("/ext/.tmp");
storage.makeDirectory("/ext/.tmp/js");
storage.rmrf("/ext/.tmp/js/repl")
storage.makeDirectory("/ext/.tmp/js/repl")
let ctx = {
tmpTemplate: "/ext/.tmp/js/repl/",
tmpNumber: 0,
persistentScope: {},
};
let views = {
dialog: dialog.makeWith({
header: "Interactive Console",
text: "Press OK to Start",
center: "Run Some JS"
}),
textInput: textInput.makeWith({
header: "Type JavaScript Code:",
minLength: 0,
maxLength: 256,
defaultText: "2+2",
defaultTextClear: true,
}),
loading: loading.make(),
};
eventLoop.subscribe(views.dialog.input, function (_sub, button, gui, views) {
if (button === "center") {
gui.viewDispatcher.switchTo(views.textInput);
}
}, gui, views);
eventLoop.subscribe(views.textInput.input, function (_sub, text, gui, views, ctx) {
gui.viewDispatcher.switchTo(views.loading);
let path = ctx.tmpTemplate + (ctx.tmpNumber++).toString();
let file = storage.openFile(path, "w", "create_always");
file.write(text);
file.close();
// Hide GUI before running, we want to see console and avoid deadlock if code fails
gui.viewDispatcher.sendTo("back");
let result = load(path, ctx.persistentScope); // Load runs JS and returns last value on stack
storage.remove(path);
// Must convert to string explicitly
if (result === null) { // mJS: typeof null === "null", ECMAScript: typeof null === "object", IDE complains when checking "null" type
result = "null";
} else if (typeof result === "string") {
result = "'" + result + "'";
} else if (typeof result === "number") {
result = result.toString();
} else if (typeof result === "bigint") { // mJS doesn't support BigInt() but might aswell check
result = "bigint";
} else if (typeof result === "boolean") {
result = result ? "true" : "false";
} else if (typeof result === "symbol") { // mJS doesn't support Symbol() but might aswell check
result = "symbol";
} else if (typeof result === "undefined") {
result = "undefined";
} else if (typeof result === "object") {
result = "object"; // JSON.stringify() is not implemented
} else if (typeof result === "function") {
result = "function";
} else {
result = "unknown type: " + typeof result;
}
gui.viewDispatcher.sendTo("front");
views.dialog.set("header", "JS Returned:");
views.dialog.set("text", result);
gui.viewDispatcher.switchTo(views.dialog);
views.textInput.set("defaultText", text);
}, gui, views, ctx);
eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, eventLoop) {
eventLoop.stop();
}, eventLoop);
gui.viewDispatcher.switchTo(views.dialog);
// Message behind GUI if something breaks
print("If you're stuck here, something went wrong, re-run the script")
eventLoop.run();
print("\n\nFinished correctly :)")

View File

@ -1,3 +0,0 @@
let math = load(__dirname + "/load_api.js");
let result = math.add(5, 10);
print(result);

View File

@ -1,9 +0,0 @@
let storage = require("storage");
print("script has __dirname of" + __dirname);
print("script has __filename of" + __filename);
if (storage.fileExists(__dirname + "/math.js")) {
print("math.js exist here.");
} else {
print("math.js does not exist here.");
}

View File

@ -1,29 +0,0 @@
let storage = require("storage");
let path = "/ext/storage.test";
print("File exists:", storage.fileExists(path));
print("Writing...");
let file = storage.openFile(path, "w", "create_always");
file.write("Hello ");
file.close();
print("File exists:", storage.fileExists(path));
file = storage.openFile(path, "w", "open_append");
file.write("World!");
file.close();
print("Reading...");
file = storage.openFile(path, "r", "open_existing");
let text = file.read("ascii", 128);
file.close();
print(text);
print("Removing...")
storage.remove(path);
print("Done")
// You don't need to close the file after each operation, this is just to show some different ways to use the API
// There's also many more functions and options, check type definitions in firmware repo

View File

@ -1,19 +0,0 @@
let sampleText = "Hello, World!";
let lengthOfText = "Length of text: " + sampleText.length.toString();
print(lengthOfText);
let start = 7;
let end = 12;
let substringResult = sampleText.slice(start, end);
print(substringResult);
let searchStr = "World";
let result2 = sampleText.indexOf(searchStr).toString();
print(result2);
let upperCaseText = "Text in upper case: " + sampleText.toUpperCase();
print(upperCaseText);
let lowerCaseText = "Text in lower case: " + sampleText.toLowerCase();
print(lowerCaseText);

View File

@ -244,30 +244,6 @@ static int32_t js_thread(void* arg) {
struct mjs* mjs = mjs_create(worker);
worker->modules = js_modules_create(mjs, worker->resolver);
mjs_val_t global = mjs_get_global(mjs);
if(worker->path) {
FuriString* dirpath = furi_string_alloc();
path_extract_dirname(furi_string_get_cstr(worker->path), dirpath);
mjs_set(
mjs,
global,
"__filename",
~0,
mjs_mk_string(
mjs, furi_string_get_cstr(worker->path), furi_string_size(worker->path), true));
mjs_set(
mjs,
global,
"__dirname",
~0,
mjs_mk_string(mjs, furi_string_get_cstr(dirpath), furi_string_size(dirpath), true));
furi_string_free(dirpath);
}
mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print));
mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay));
mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address));
mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require));
mjs_set(mjs, global, "parseInt", ~0, MJS_MK_FN(js_parse_int));
mjs_val_t console_obj = mjs_mk_object(mjs);
if(worker->path) {
@ -292,7 +268,6 @@ static int32_t js_thread(void* arg) {
JS_ASSIGN_MULTI(mjs, global) {
JS_FIELD("print", MJS_MK_FN(js_print));
JS_FIELD("delay", MJS_MK_FN(js_delay));
JS_FIELD("toString", MJS_MK_FN(js_global_to_string));
JS_FIELD("parseInt", MJS_MK_FN(js_parse_int));
JS_FIELD("ffi_address", MJS_MK_FN(js_ffi_address));
JS_FIELD("require", MJS_MK_FN(js_require));

View File

@ -120,6 +120,7 @@ declare function checkSdkCompatibility(expectedMajor: number, expectedMinor: num
* recognized as a baseline feature. For more info, consult the module
* documentation.
* @param features Array of named features to query
* @version Added in JS SDK 0.1
*/
declare function doesSdkSupport(features: string[]): boolean;
@ -131,6 +132,7 @@ declare function doesSdkSupport(features: string[]): boolean;
* features that are now recognized as baseline features. For more
* info, consult the module documentation.
* @param features Array of named features to query
* @version Added in JS SDK 0.1
*/
declare function checkSdkFeatures(features: string[]): void | never;
@ -150,15 +152,48 @@ declare function delay(ms: number): void;
declare function print(...args: any[]): void;
/**
* @brief Reads a JS value from a file
*
* Reads a file at the specified path, interprets it as a JS value and returns
* said value.
*
* @param path The path to the file
* @brief Converts a string to a number
* @param text The string to convert to a number
* @param base Integer base (`2`...`16`), default: 10
* @version Added in JS SDK 0.1
*/
declare function load(path: string): any;
declare function parseInt(text: string, base?: number): number;
/**
* @brief Path to the directory containing the current script
* @version Added in JS SDK 0.1
*/
declare const __dirname: string;
/**
* @brief Path to the current script file
* @version Added in JS SDK 0.1
*/
declare const __filename: string;
/**
* @brief Runs a JS file and returns value from it
*
* Reads a file at the specified path and runs it as JS, returning the last evaluated value.
*
* The result is cached and this filepath will not re-evaluated on future
* load() calls for this session.
*
* @param path The path to the file
* @param scope An object to use as global scope while running this file
* @version Added in JS SDK 0.1
*/
declare function load(path: string, scope?: object): any;
/**
* @brief Return 1-byte string whose ASCII code is the integer `n`
*
* If `n` is not numeric or outside of `0-255` range, `null` is returned
*
* @param n The ASCII code to convert to string
* @version Added in JS SDK 0.1
*/
declare function chr(n: number): string | null;
/**
* @brief Loads a natively implemented module

View File

@ -30,7 +30,7 @@
* |----------------------|------------------|
* | `button_menu` | |
* | `button_panel` | |
* | `byte_input` | |
* | `byte_input` | |
* | `dialog_ex` | (as `dialog`) |
* | `empty_screen` | |
* | `file_browser` | |
@ -122,11 +122,26 @@ import type { Contract } from "../event_loop";
type Properties = { [K: string]: any };
export declare class View<Props extends Properties> {
/**
* Assign value to property by name
* @param property Name of the property
* @param value Value to assign
* @version Added in JS SDK 0.1
*/
set<P extends keyof Props>(property: P, value: Props[P]): void;
}
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
/**
* Create view instance with default values, can be changed later with set()
* @version Added in JS SDK 0.1
*/
make(): V;
/**
* Create view instance with custom values, can be changed later with set()
* @param initial Dictionary of property names to values
* @version Added in JS SDK 0.1
*/
makeWith(initial: Partial<Props>): V;
}
@ -144,6 +159,11 @@ declare class ViewDispatcher {
* @version Added in JS SDK 0.1
*/
navigation: Contract;
/**
* View object currently shown
* @version Added in JS SDK 0.1
*/
currentView: View<any>;
/**
* Sends a number to the custom event handler
* @param event number to send

View File

@ -14,6 +14,7 @@ export declare function success(): void;
/**
* @brief Signals failure to the user via the color LED, speaker and vibration
* motor
* @version Added in JS SDK 0.1
*/
export declare function error(): void;

View File

@ -1,226 +0,0 @@
/**
* @brief Pauses JavaScript execution for a while
* @param ms How many milliseconds to pause the execution for
*/
declare function delay(ms: number): void;
/**
* @brief Prints to the GUI console view
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the console view
*/
declare function print(...args: any[]): void;
/**
* @brief Converts a string to a number
* @param text The string to convert to a number
* @param base Integer base (`2`...`16`), default: 10
*/
declare function parseInt(text: string, base?: number): number;
/**
* @brief Path to the directory containing the current script
*/
declare const __dirname: string;
/**
* @brief Path to the current script file
*/
declare const __filename: string;
/**
* @brief Reads a JS value from a file
*
* Reads a file at the specified path and runs it as JS, returning the last evaluated value.
*
* The result is cached and this filepath will not re-evaluated on future
* load() calls for this session.
*
* @param path The path to the file
* @param scope An object to use as global scope while running this file
*/
declare function load(path: string, scope?: object): any;
/**
* @brief Return 1-byte string whose ASCII code is the integer `n`
*
* If `n` is not numeric or outside of `0-255` range, `null` is returned
*
* @param n The ASCII code to convert to string
*/
declare function chr(n: number): string | null;
/**
* @brief mJS Foreign Pointer type
*
* JavaScript code cannot do anything with values of `RawPointer` type except
* acquire them from native code and pass them right back to other parts of
* native code. These values cannot be turned into something meaningful, nor can
* be they modified.
*/
declare type RawPointer = symbol & { "__tag__": "raw_ptr" };
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
/**
* @brief Holds raw bytes
*/
declare class ArrayBuffer {
/**
* @brief The pointer to the byte buffer
* @note Like other `RawPointer` values, this value is essentially useless
* to JS code.
*/
getPtr: RawPointer;
/**
* @brief The length of the buffer in bytes
*/
byteLength: number;
/**
* @brief Creates an `ArrayBuffer` that contains a sub-part of the buffer
* @param start The index of the byte in the source buffer to be used as the
* start for the new buffer
* @param end The index of the byte in the source buffer that follows the
* byte to be used as the last byte for the new buffer
*/
slice(start: number, end?: number): ArrayBuffer;
}
declare function ArrayBuffer(): ArrayBuffer;
declare type ElementType = "u8" | "i8" | "u16" | "i16" | "u32" | "i32";
declare class TypedArray<E extends ElementType> {
/**
* @brief The length of the buffer in bytes
*/
byteLength: number;
/**
* @brief The length of the buffer in typed elements
*/
length: number;
/**
* @brief The underlying `ArrayBuffer`
*/
buffer: ArrayBuffer;
}
declare class Uint8Array extends TypedArray<"u8"> { }
declare class Int8Array extends TypedArray<"i8"> { }
declare class Uint16Array extends TypedArray<"u16"> { }
declare class Int16Array extends TypedArray<"i16"> { }
declare class Uint32Array extends TypedArray<"u32"> { }
declare class Int32Array extends TypedArray<"i32"> { }
declare function Uint8Array(data: ArrayBuffer | number | number[]): Uint8Array;
declare function Int8Array(data: ArrayBuffer | number | number[]): Int8Array;
declare function Uint16Array(data: ArrayBuffer | number | number[]): Uint16Array;
declare function Int16Array(data: ArrayBuffer | number | number[]): Int16Array;
declare function Uint32Array(data: ArrayBuffer | number | number[]): Uint32Array;
declare function Int32Array(data: ArrayBuffer | number | number[]): Int32Array;
declare const console: {
/**
* @brief Prints to the UART logs at the `[I]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
*/
log(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[D]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
*/
debug(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[W]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
*/
warn(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[E]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
*/
error(...args: any[]): void;
};
declare class Array<T> {
/**
* @brief Takes items out of the array
*
* Removes elements from the array and returns them in a new array
*
* @param start The index to start taking elements from
* @param deleteCount How many elements to take
* @returns The elements that were taken out of the original array as a new
* array
*/
splice(start: number, deleteCount: number): T[];
/**
* @brief Adds a value to the end of the array
* @param value The value to add
* @returns New length of the array
*/
push(value: T): number;
/**
* @brief How many elements there are in the array
*/
length: number;
}
declare class String {
/**
* @brief How many characters there are in the string
*/
length: number;
/**
* @brief Returns the character code at an index in the string
* @param index The index to consult
*/
charCodeAt(index: number): number;
/**
* See `charCodeAt`
*/
at(index: number): number;
/**
* @brief Return index of first occurence of substr within the string or `-1` if not found
* @param substr The string to search for
* @param fromIndex The index to start searching from
*/
indexOf(substr: string, fromIndex?: number): number;
/**
* @brief Return a substring between two indices
* @param start The index to start substring at
* @param end The index to end substring at
*/
slice(start: number, end?: number): string;
/**
* @brief Return this string transformed to upper case
*/
toUpperCase(): string;
/**
* @brief Return this string transformed to lower case
*/
toLowerCase(): string;
}
declare class Boolean { }
declare class Function { }
declare class Number {
/**
* @brief Converts this number to a string
* @param base Integer base (`2`...`16`), default: 10
*/
toString(base?: number): string;
}
declare class Object { }
declare class RegExp { }
declare interface IArguments { }
declare type Partial<O extends object> = { [K in keyof O]?: O[K] };

View File

@ -1,14 +0,0 @@
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
header: string,
length: number,
defaultData: Uint8Array | ArrayBuffer,
}
declare class ByteInput extends View<Props> {
input: Contract<string>;
}
declare class ByteInputFactory extends ViewFactory<Props, ByteInput> { }
declare const factory: ByteInputFactory;
export = factory;

View File

@ -1,6 +0,0 @@
/**
* @brief Displays a file picker and returns the selected file, or undefined if cancelled
* @param basePath The path to start at
* @param extension The file extension(s) to show (like `.sub`, `.iso|.img`, `*`)
*/
export declare function pickFile(basePath: string, extension: string): string | undefined;

View File

@ -1,57 +0,0 @@
import type { Contract } from "../event_loop";
type Properties = { [K: string]: any };
export declare class View<Props extends Properties> {
/**
* Assign value to property by name
* @param property Name of the property
* @param value Value to assign
*/
set<P extends keyof Props>(property: P, value: Props[P]): void;
}
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
/**
* Create view instance with default values, can be changed later with set()
*/
make(): V;
/**
* Create view instance with custom values, can be changed later with set()
* @param initial Dictionary of property names to values
*/
makeWith(initial: Partial<Props>): V;
}
declare class ViewDispatcher {
/**
* Event source for `sendCustom` events
*/
custom: Contract<number>;
/**
* Event source for navigation events (back key presses)
*/
navigation: Contract;
/**
* View object currently shown
*/
currentView: View<any>;
/**
* Sends a number to the custom event handler
* @param event number to send
*/
sendCustom(event: number): void;
/**
* Switches to a view
* @param assoc View-ViewDispatcher association as returned by `add`
*/
switchTo(assoc: View<any>): void;
/**
* Sends this ViewDispatcher to the front or back, above or below all other
* GUI viewports
* @param direction Either `"front"` or `"back"`
*/
sendTo(direction: "front" | "back"): void;
}
export const viewDispatcher: ViewDispatcher;