diff --git a/compiler/compiler.ts b/compiler/compiler.ts index 8a853478..3c94e22d 100644 --- a/compiler/compiler.ts +++ b/compiler/compiler.ts @@ -27,7 +27,7 @@ import { BinaryCode } from "./binary_codes.ts"; import { Scope } from "../types/scope.ts"; import { ProtocolDataType } from "./protocol_types.ts"; import { Quantity } from "../types/quantity.ts"; -import { EXTENDED_OBJECTS, INHERITED_PROPERTIES, VOID, SLOT_WRITE, SLOT_READ, SLOT_EXEC, NOT_EXISTING, SLOT_GET, SLOT_SET, DX_IGNORE } from "../runtime/constants.ts"; +import { EXTENDED_OBJECTS, INHERITED_PROPERTIES, VOID, SLOT_WRITE, SLOT_READ, SLOT_EXEC, NOT_EXISTING, SLOT_GET, SLOT_SET, DX_IGNORE, DX_BOUND_LOCAL_SLOT } from "../runtime/constants.ts"; import { arrayBufferToBase64, base64ToArrayBuffer, buffer2hex, hex2buffer } from "../utils/utils.ts"; import { RuntimePerformance } from "../runtime/performance_measure.ts"; import { Conjunction, Disjunction, Logical, Negation } from "../types/logic.ts"; @@ -1448,6 +1448,7 @@ export class Compiler { let index:number let insert_new = false; + if (!SCOPE.extract_var_indices) throw new CompilerError("Cannot extract variable in non child scope block") if (!SCOPE.extract_var_scope) throw new CompilerError("Cannot extract variable in non child scope block") @@ -1756,7 +1757,7 @@ export class Compiler { const return_data:{datex:string} = {datex: SCOPE.datex}; - const compiled = await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE}, false, true, extract_pointers, undefined, Infinity, brackets?1:2, SCOPE.current_data_index); + const compiled = await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE, to: Compiler.builder.getScopeReceiver(SCOPE)}, false, true, extract_pointers, undefined, Infinity, brackets?1:2, SCOPE.current_data_index); SCOPE.datex = return_data.datex; // update position in current datex script // insert scope block @@ -2076,7 +2077,7 @@ export class Compiler { addInitBlock: async (SCOPE:compiler_scope, brackets = false) => { const return_data:{datex:string} = {datex: SCOPE.datex}; - const compiled = await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE, preemptive_pointer_init:SCOPE.options.preemptive_pointer_init==false?false:true, pseudo_parent:true}, false, false, false, undefined, Infinity, brackets?1:2, SCOPE.current_data_index); + const compiled = await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE, preemptive_pointer_init:SCOPE.options.preemptive_pointer_init==false?false:true, pseudo_parent:true, to: Compiler.builder.getScopeReceiver(SCOPE)}, false, false, false, undefined, Infinity, brackets?1:2, SCOPE.current_data_index); SCOPE.datex = return_data.datex; // update position in current datex script // remove redundant ";" @@ -2088,9 +2089,8 @@ export class Compiler { addInitBlockForValue: (SCOPE:compiler_scope|extract_var_scope, value:any) => { - //const compiled = this.compile("?", [value], {parent_scope:SCOPE, abs_offset, preemptive_pointer_init:SCOPE.options.preemptive_pointer_init==false?false:true, pseudo_parent:true, collapse_first_inserted:true}, false, false, false, undefined, Infinity); // sync - - const compiled = this.compileValue(value, {parent_scope: SCOPE, preemptive_pointer_init:SCOPE.options.preemptive_pointer_init==false?false:true, pseudo_parent:true, collapse_first_inserted:true}, false); + //const compiled = this.compile("?", [value], {parent_scope:SCOPE, abs_offset, preemptive_pointer_init:SCOPE.options.preemptive_pointer_init==false?false:true, pseudo_parent:true, collapse_first_inserted:true}, false, false, false, undefined, Infinity); // sync + const compiled = this.compileValue(value, {parent_scope: SCOPE, preemptive_pointer_init:SCOPE.options.preemptive_pointer_init==false?false:true, pseudo_parent:true, collapse_first_inserted:true, to: Compiler.builder.getScopeReceiver(SCOPE)}, false); // insert scope block Compiler.builder.insertInitBlock(SCOPE, compiled); @@ -2149,7 +2149,7 @@ export class Compiler { }, // just $aabbcc = - addPointerNormal: (SCOPE:compiler_scope|extract_var_scope, id:string|Uint8Array, action_type:ACTION_TYPE = ACTION_TYPE.GET, action_specifier?:BinaryCode, init_brackets = false, value:any = NOT_EXISTING):Promise|void => { + addPointerNormal: (SCOPE:compiler_scope|extract_var_scope, id:string|Uint8Array, action_type:ACTION_TYPE = ACTION_TYPE.GET, action_specifier?:BinaryCode, init_brackets = false, value:any = NOT_EXISTING, transform_scope?: Scope):Promise|void => { Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1, SCOPE); Compiler.builder.valueIndex(SCOPE); @@ -2165,6 +2165,25 @@ export class Compiler { if (!SCOPE.datex) throw new CompilerError("cannot insert init block in scope, missing datex source code"); return Compiler.builder.addInitBlock(SCOPE, init_brackets) // async } + else if (transform_scope) { + const temp_scope = { + b_index: 0, + buffer: new ArrayBuffer(400), + inner_scope: {}, + dynamic_indices: [], + inserted_values: new Map(), + preemptive_pointers: new Map(), + assignment_end_indices: new Set(), + options: SCOPE.options + } + temp_scope.uint8 = new Uint8Array(temp_scope.buffer); + temp_scope.data_view = new DataView(temp_scope.buffer); + Compiler.builder.insert_transform_scope(temp_scope, transform_scope); + const compiled = temp_scope.uint8.slice(0,temp_scope.b_index) + + Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+compiled.byteLength+1, SCOPE); + Compiler.builder.insertInitBlock(SCOPE, compiled.buffer); + } else return Compiler.builder.addInitBlockForValue(SCOPE, value) // sync } }, @@ -2187,12 +2206,13 @@ export class Compiler { // TODO: enable const defer = false // alreadyInitializing && ancestorScopes.has(parentScope.preemptive_pointers.get(normalized_id)!); // defer if already loading pointer in direct ancestor + // preemptive value already exists and was not yet initialized in scope if (ptr?.value_initialized && !alreadyInitializing) { parentScope.preemptive_pointers.set(normalized_id, SCOPE); Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1, SCOPE); SCOPE.uint8[SCOPE.b_index++] = BinaryCode.SUBSCOPE_START; - Compiler.builder.addPointerNormal(SCOPE, id, ACTION_TYPE.INIT, undefined, true, ptr.val); // sync + Compiler.builder.addPointerNormal(SCOPE, id, ACTION_TYPE.INIT, undefined, true, ptr.val, (ptr.force_local_transform && ptr.transform_scope) ? ptr.transform_scope : undefined); // sync Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1, SCOPE); SCOPE.uint8[SCOPE.b_index++] = BinaryCode.CLOSE_AND_STORE; Compiler.builder.addPointerNormal(SCOPE, id, ACTION_TYPE.GET); // sync @@ -2409,7 +2429,7 @@ export class Compiler { // adds __123.xy = _456 - if has recursive assignments Compiler.builder.insertByteAtIndex(BinaryCode.SUBSCOPE_START, root_start_index, SCOPE) // add ( - for (let assignment of unassigned_children) { + for (const assignment of unassigned_children) { Compiler.builder.handleRequiredBufferSize(SCOPE.b_index, SCOPE); SCOPE.uint8[SCOPE.b_index++] = BinaryCode.CLOSE_AND_STORE; Compiler.builder.insertVariable(SCOPE, assignment[0], ACTION_TYPE.GET, undefined, BinaryCode.INTERNAL_VAR); // parent @@ -2631,6 +2651,28 @@ export class Compiler { // }, + insert_transform_scope: (SCOPE: compiler_scope|extract_var_scope, transform_scope: Scope) => { + const compiled = transform_scope.compiled; + + Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1, SCOPE); + + SCOPE.uint8[SCOPE.b_index++] = BinaryCode.TRANSFORM; + + for (const v of transform_scope.internal_vars) { + Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1, SCOPE); + SCOPE.uint8[SCOPE.b_index++] = BinaryCode.SUBSCOPE_START; + Compiler.builder.insert(v, SCOPE); + SCOPE.uint8[SCOPE.b_index++] = BinaryCode.SUBSCOPE_END; + } + + Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1+Uint32Array.BYTES_PER_ELEMENT+compiled.byteLength, SCOPE); + SCOPE.uint8[SCOPE.b_index++] = BinaryCode.SCOPE_BLOCK; + SCOPE.data_view.setUint32(SCOPE.b_index, compiled.byteLength, true); + SCOPE.b_index += Uint32Array.BYTES_PER_ELEMENT + SCOPE.uint8.set(new Uint8Array(compiled), SCOPE.b_index) + SCOPE.b_index += compiled.byteLength; + }, + // insert any value besides Maybes @@ -2642,6 +2684,38 @@ export class Compiler { } catch {} + const receiver = Compiler.builder.getScopeReceiver(SCOPE); + // bound local slot? (eg. #env) - only when sending to remote + const toRemote = receiver && receiver!==Runtime.endpoint && receiver !== LOCAL_ENDPOINT; + + if (toRemote && value?.[DX_BOUND_LOCAL_SLOT]) { + const v_name = value[DX_BOUND_LOCAL_SLOT]; + if (typeof v_name !== "string") throw new Error("Invalid DX_BOUND_LOCAL_SLOT, must be of type string"); + const mapped = Compiler.builder.mapInternalVarNameToByteCode(v_name, ACTION_TYPE.GET, SCOPE); + if (typeof mapped == "number") { + Compiler.builder.insertVariable(SCOPE, undefined, ACTION_TYPE.GET, undefined, mapped); + } + else { + throw new Error("Invalid DX_BOUND_LOCAL_SLOT: " + v_name); + } + return; + } + // bound pointer property (eg. #env->LANG) - only when sending to remote + if (toRemote && value instanceof PointerProperty && value.pointer.val?.[DX_BOUND_LOCAL_SLOT]) { + const v_name = value.pointer.val?.[DX_BOUND_LOCAL_SLOT]; + if (typeof v_name !== "string") throw new Error("Invalid DX_BOUND_LOCAL_SLOT, must be of type string"); + const mapped = Compiler.builder.mapInternalVarNameToByteCode(v_name, ACTION_TYPE.GET, SCOPE); + if (typeof mapped == "number") { + Compiler.builder.insertVariable(SCOPE, undefined, ACTION_TYPE.GET, undefined, mapped); + SCOPE.uint8[SCOPE.b_index++] = BinaryCode.CHILD_GET_REF + Compiler.builder.insert(value.key, SCOPE); + } + else { + throw new Error("Invalid DX_BOUND_LOCAL_SLOT: " + v_name); + } + return; + } + // handle and ReadableStream, if streaming (<<) if ((value instanceof Stream || value instanceof ReadableStream) && SCOPE.uint8[SCOPE.b_index-1] == BinaryCode.STREAM) return Compiler.builder.handleStream(value, SCOPE); @@ -2698,33 +2772,17 @@ export class Compiler { const option_collapse = SCOPE.options.collapse_pointers && !(SCOPE.options.keep_external_pointers && value instanceof Pointer && !value.is_origin); const no_proxify = value instanceof Ref && (((value instanceof Pointer && value.is_anonymous) || option_collapse) || skip_first_collapse); + // proxify pointer exceptions: if (no_proxify) { - + // handle pointers with transform (always ...) + // only if not ignore_first_collapse or, if ignore_first_collapse and keep_first_transform is enabled - if (!SCOPE.options.no_create_pointers && value instanceof Pointer && value.transform_scope && (!skip_first_collapse || SCOPE.options.keep_first_transform)) { + if (!SCOPE.options.no_create_pointers && value instanceof Pointer && value.transform_scope && (value.force_local_transform || !skip_first_collapse || SCOPE.options.keep_first_transform)) { SCOPE.options._first_insert_done = true; // set to true before next insert - const compiled = value.transform_scope.compiled; - - Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1, SCOPE); - - SCOPE.uint8[SCOPE.b_index++] = BinaryCode.TRANSFORM; - - for (const v of value.transform_scope.internal_vars) { - Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1, SCOPE); - SCOPE.uint8[SCOPE.b_index++] = BinaryCode.SUBSCOPE_START; - Compiler.builder.insert(v, SCOPE); - SCOPE.uint8[SCOPE.b_index++] = BinaryCode.SUBSCOPE_END; - } - - Compiler.builder.handleRequiredBufferSize(SCOPE.b_index+1+Uint32Array.BYTES_PER_ELEMENT+compiled.byteLength, SCOPE); - SCOPE.uint8[SCOPE.b_index++] = BinaryCode.SCOPE_BLOCK; - SCOPE.data_view.setUint32(SCOPE.b_index, compiled.byteLength, true); - SCOPE.b_index += Uint32Array.BYTES_PER_ELEMENT - SCOPE.uint8.set(new Uint8Array(compiled), SCOPE.b_index) - SCOPE.b_index += compiled.byteLength; + Compiler.builder.insert_transform_scope(SCOPE, value.transform_scope); return; } @@ -2931,8 +2989,31 @@ export class Compiler { Compiler.builder.insert(ext, SCOPE, is_root, parents, unassigned_children); // shallow clone parents set } } - } + }, + mapInternalVarNameToByteCode: (v_name: string, action_type: ACTION_TYPE, SCOPE: compiler_scope) => { + if (v_name == "result") return BinaryCode.VAR_RESULT + else if (v_name == "sub_result") return BinaryCode.VAR_SUB_RESULT + else if (v_name == "_origin") return BinaryCode._VAR_ORIGIN + else if (v_name == "it") return BinaryCode.VAR_IT + else if (v_name == "void") return BinaryCode.VAR_VOID + + else if (v_name == "origin") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #origin", SCOPE.stack); return BinaryCode.VAR_ORIGIN} + else if (v_name == "endpoint") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #endpoint", SCOPE.stack); return BinaryCode.VAR_ENDPOINT} + else if (v_name == "location") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #location", SCOPE.stack); return BinaryCode.VAR_LOCATION} + else if (v_name == "env") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #env", SCOPE.stack); return BinaryCode.VAR_ENV} + // else if (v_name == "timestamp") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #timestamp", SCOPE.stack); base_type = BinaryCode.VAR_TIMESTAMP; v_name = undefined} + // else if (v_name == "encrypted") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #encrypted", SCOPE.stack); base_type = BinaryCode.VAR_ENCRYPTED; v_name = undefined} + // else if (v_name == "signed") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #signed", SCOPE.stack); base_type = BinaryCode.VAR_SIGNED; v_name = undefined} + else if (v_name == "meta") { if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #meta", SCOPE.stack); return BinaryCode.VAR_META} + else if (v_name == "public") { if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #public", SCOPE.stack); return BinaryCode.VAR_PUBLIC} + else if (v_name == "this") { if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #this", SCOPE.stack); return BinaryCode.VAR_THIS} + else if (v_name == "remote") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #remote", SCOPE.stack); return BinaryCode.VAR_REMOTE} + else if (v_name == "entrypoint") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #entrypoint", SCOPE.stack); return BinaryCode.VAR_ENTRYPOINT} + else if (v_name == "std") { if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #std", SCOPE.stack); return BinaryCode.VAR_STD} + + return v_name; + } } @@ -4294,7 +4375,7 @@ export class Compiler { // veryyy inefficient way of doing things TODO let return_data:{datex:string} = {datex: SCOPE.datex}; // ignore result - very bad - await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE, pseudo_parent:true}, false, false, false, undefined, Infinity, 42 /*illegal*/, SCOPE.current_data_index); + await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE, pseudo_parent:true, to: Compiler.builder.getScopeReceiver(SCOPE)}, false, false, false, undefined, Infinity, 42 /*illegal*/, SCOPE.current_data_index); params[param_name][2] = SCOPE.datex.replace(return_data.datex, ""); if (return_data.datex[0] == ")") { SCOPE.datex = return_data.datex; @@ -4309,7 +4390,7 @@ export class Compiler { // veryyy inefficient way of doing things TODO let return_data:{datex:string} = {datex: SCOPE.datex}; // ignore result - very bad - await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE, pseudo_parent:true}, false, false, false, undefined, Infinity, 2, SCOPE.current_data_index); + await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE, pseudo_parent:true, to: Compiler.builder.getScopeReceiver(SCOPE)}, false, false, false, undefined, Infinity, 2, SCOPE.current_data_index); params[param_name][3] = SCOPE.datex.replace(return_data.datex, ""); if (return_data.datex[0] == ")") { SCOPE.datex = return_data.datex; @@ -4365,7 +4446,7 @@ export class Compiler { // TODO: optimize this, no full compilation required const brackets = true; const return_data:{datex:string} = {datex: SCOPE.datex}; - await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE}, false, true, false, undefined, Infinity, brackets?1:2, SCOPE.current_data_index); + await this.compile(return_data, SCOPE.data, {parent_scope:SCOPE, to: Compiler.builder.getScopeReceiver(SCOPE)}, false, true, false, undefined, Infinity, brackets?1:2, SCOPE.current_data_index); SCOPE.datex = return_data.datex; // update position in current datex script } @@ -4491,7 +4572,7 @@ export class Compiler { // INTERNAL_VAR or ROOT_VARIABLE or LABELED_POINTER else if ((m = SCOPE.datex.match(Regex.INTERNAL_VAR)) || (m = SCOPE.datex.match(Regex.ROOT_VARIABLE)) || (m = SCOPE.datex.match(Regex.LABELED_POINTER))) { - let v_name:string|number = m[2]; // get var name + let v_name:string|number|undefined = m[2]; // get var name const is_internal = m[1] == "#"; // is internal variable (#)? const is_label = m[1] == "$"; const is_hex = v_name.match(Regex.HEX_VARIABLE) && (is_internal || is_label); @@ -4507,8 +4588,7 @@ export class Compiler { if (is_property) SCOPE.datex = SCOPE.datex.substring(m[1].length + m[2].length + m[3].length); // pop datex (not "=" or "+=") else SCOPE.datex = SCOPE.datex.substring(m[0].length); // pop datex (also "=" or "+=") - - let [action_type, action_specifier] = is_property ? [ACTION_TYPE.GET] : Compiler.builder.getAssignAction(m[3]); + const [action_type, action_specifier] = is_property ? [ACTION_TYPE.GET] : Compiler.builder.getAssignAction(m[3]); if (is_hex) v_name = parseInt(v_name.replace(/[-_]/g,''),16) || 0; @@ -4520,26 +4600,11 @@ export class Compiler { // default internal variable shorthands if (is_internal) { - if (v_name == "result") {base_type = BinaryCode.VAR_RESULT; v_name = undefined} - else if (v_name == "sub_result") {base_type = BinaryCode.VAR_SUB_RESULT; v_name = undefined} - else if (v_name == "_origin") {base_type = BinaryCode._VAR_ORIGIN; v_name = undefined} - else if (v_name == "it") {base_type = BinaryCode.VAR_IT; v_name = undefined} - else if (v_name == "void") {base_type = BinaryCode.VAR_VOID; v_name = undefined} - - else if (v_name == "origin") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #origin", SCOPE.stack); base_type = BinaryCode.VAR_ORIGIN; v_name = undefined} - else if (v_name == "endpoint") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #endpoint", SCOPE.stack); base_type = BinaryCode.VAR_ENDPOINT; v_name = undefined} - else if (v_name == "location") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #location", SCOPE.stack); base_type = BinaryCode.VAR_LOCATION; v_name = undefined} - else if (v_name == "env") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #env", SCOPE.stack); base_type = BinaryCode.VAR_ENV; v_name = undefined} - // else if (v_name == "timestamp") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #timestamp", SCOPE.stack); base_type = BinaryCode.VAR_TIMESTAMP; v_name = undefined} - // else if (v_name == "encrypted") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #encrypted", SCOPE.stack); base_type = BinaryCode.VAR_ENCRYPTED; v_name = undefined} - // else if (v_name == "signed") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #signed", SCOPE.stack); base_type = BinaryCode.VAR_SIGNED; v_name = undefined} - else if (v_name == "meta") { if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #meta", SCOPE.stack); base_type = BinaryCode.VAR_META; v_name = undefined} - else if (v_name == "public") { if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #public", SCOPE.stack); base_type = BinaryCode.VAR_PUBLIC; v_name = undefined} - else if (v_name == "this") { if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #this", SCOPE.stack); base_type = BinaryCode.VAR_THIS; v_name = undefined} - else if (v_name == "remote") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #remote", SCOPE.stack); base_type = BinaryCode.VAR_REMOTE; v_name = undefined} - else if (v_name == "entrypoint") {if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #entrypoint", SCOPE.stack); base_type = BinaryCode.VAR_ENTRYPOINT; v_name = undefined} - else if (v_name == "std") { if (action_type != ACTION_TYPE.GET) throw new CompilerError("Invalid action on internal variable #std", SCOPE.stack); base_type = BinaryCode.VAR_STD; v_name = undefined} - + const mapped = typeof v_name == "string" ? Compiler.builder.mapInternalVarNameToByteCode(v_name, action_type, SCOPE) : undefined; + if (typeof mapped == "number") { + base_type = mapped; + v_name = undefined; + } // resolve internal var proxy name else if (typeof v_name == "string") { v_name = Compiler.builder.resolveInternalProxyName(SCOPE, v_name); @@ -5635,7 +5700,6 @@ export class Compiler { SCOPE.extract_var_indices.set(BinaryCode.LABEL, new Map()); SCOPE.extract_var_indices.set(BinaryCode.POINTER, new Map()); SCOPE.extract_var_indices.set(BinaryCode.INTERNAL_VAR, new Map()); // for parent internal var references - } SCOPE.inner_scope = SCOPE.subscopes[0]; diff --git a/docs/manual/10 The DATEX API.md b/docs/manual/10 The DATEX API.md index cca0ef85..5ea8b5a4 100644 --- a/docs/manual/10 The DATEX API.md +++ b/docs/manual/10 The DATEX API.md @@ -47,3 +47,13 @@ Inside function bodies, the [`datex.meta` object](https://github.com/unyt-org/da type:ProtocolDataType } ``` + +Example: + +```ts +@endpoint class MyInterface { + @property myMethod(a: number) { + console.log("myMethod called by endpoint " + datex.meta.sender) + } +} +``` \ No newline at end of file diff --git a/init.ts b/init.ts index cc9d945c..44a4a255 100644 --- a/init.ts +++ b/init.ts @@ -8,6 +8,9 @@ import { IndexedDBStorageLocation } from "./runtime/storage-locations/indexed-db import { LocalStorageLocation } from "./runtime/storage-locations/local-storage.ts"; import { DenoKVStorageLocation } from "./runtime/storage-locations/deno-kv.ts"; import { loadEternalValues } from "./utils/eternals.ts"; +import { DX_BOUND_LOCAL_SLOT } from "./runtime/constants.ts"; +import { verboseArg } from "./utils/logger.ts"; +import { MessageLogger } from "./utils/message_logger.ts"; /** @@ -67,6 +70,7 @@ export async function init() { // set Runtime ENV (not persistent if globalThis.NO_INIT) Runtime.ENV = globalThis.NO_INIT ? getDefaultEnv() : await Storage.loadOrCreate("Datex.Runtime.ENV", getDefaultEnv); + Runtime.ENV[DX_BOUND_LOCAL_SLOT] = "env" // workaround, should never happen if (!Runtime.ENV) { @@ -133,4 +137,7 @@ export async function init() { // @ts-ignore NO_INIT if (!globalThis.NO_INIT) await loadEternalValues(); + + // enables message logger when running with -v + if (verboseArg) MessageLogger.enable() } diff --git a/runtime/constants.ts b/runtime/constants.ts index d6738ee5..ff49c3be 100644 --- a/runtime/constants.ts +++ b/runtime/constants.ts @@ -24,9 +24,8 @@ export const DX_IGNORE: unique symbol = Symbol("DX_IGNORE"); // ignore in DX (wh export const DX_PREEMPTIVE: unique symbol = Symbol("DX_PREEMPTIVE"); // used to override the default loading behaviour for a pointer (fetching by id). Can be an arbitrary DATEX Script that can be resolved with datex.get. Currently only used by the interface generator for JS modules. export const DX_REACTIVE_METHODS: unique symbol = Symbol("DX_REACTIVE_METHODS"); // object containing reactive methods for this obj, used e.g. for x.$.map, x.$.filter, ... - export const INIT_PROPS: unique symbol = Symbol("INIT_PROPS"); // key for init props function passed to constructor to initialize properties of pointer immediately - +export const DX_BOUND_LOCAL_SLOT: unique symbol = Symbol("DX_BOUND_SLOT"); // local slot (e.g. #env) to which this value always gets serialized, instead of the value/pointer // ------------------------------------- export const DX_SLOTS: unique symbol = Symbol("DX_SLOTS"); diff --git a/runtime/pointers.ts b/runtime/pointers.ts index d369cc9d..14f6e23f 100644 --- a/runtime/pointers.ts +++ b/runtime/pointers.ts @@ -1505,8 +1505,10 @@ export class Pointer extends Ref { // create a new pointer with a transform value - static createTransform(observe_values:V, transform:TransformFunction, persistent_datex_transform?:string) { - return Pointer.create(undefined, NOT_EXISTING).handleTransform(observe_values, transform, persistent_datex_transform); + static createTransform(observe_values:V, transform:TransformFunction, persistent_datex_transform?:string, force_transform = false) { + const ptr = Pointer.create(undefined, NOT_EXISTING).handleTransform(observe_values, transform, persistent_datex_transform); + ptr.force_local_transform = force_transform; + return ptr; } /** @@ -1978,6 +1980,12 @@ export class Pointer extends Ref { */ public async subscribeForPointerUpdates(override_endpoint?:Endpoint, get_value = !this.#loaded, keep_pointer_origin = false):Promise { + + // never subscribe if pointer is bound to a transform function + if (this.transform_scope) { + return this; + } + if (this.#subscribed) { // logger.debug("already subscribed to " + this.idString()); return this; @@ -2437,8 +2445,8 @@ export class Pointer extends Ref { } // propagate updates via datex - if (this.origin && !this.is_origin) { - if (!this.#exclude_origin_from_updates) this.handleDatexUpdate(null, '#0=?;? = #0', [this.current_val, this], this.origin, true) + if (this.send_updates_to_origin) { + this.handleDatexUpdate(null, '#0=?;? = #0', [this.current_val, this], this.origin, true) } if (this.update_endpoints.size) { logger.debug("forwarding update to subscribers", this.update_endpoints); @@ -2463,6 +2471,9 @@ export class Pointer extends Ref { #transform_scope?:Scope; get transform_scope() {return this.#transform_scope} + #force_transform = false; // if true, the pointer transform function is always sent via DATEX + set force_local_transform(force_transform: boolean) {this.#force_transform = force_transform} + get force_local_transform() {return this.#force_transform} /** * transform observed values to update pointer value (using a transform function or DATEX transform scope) @@ -2707,7 +2718,12 @@ export class Pointer extends Ref { // TODO: JUST A WORKAROUND - if transform is a JS function, a DATEX Script can be provided to be stored as a transform method async setDatexTransform(datex_transform:string) { // TODO: fix and reenable - // this.#transform_scope = (await Runtime.executeDatexLocally(datex_transform)).transform_scope; + try { + this.#transform_scope = (await Runtime.executeDatexLocally(datex_transform)).transform_scope; + } + catch (e) { + console.log("transform error", e); + } } #registeredForGC = false; @@ -2910,7 +2926,13 @@ export class Pointer extends Ref { // updates are from datex (external) and should not be distributed again or local update -> should be distributed to subscribers #update_endpoints: Disjunction; // endpoint to update - #exclude_origin_from_updates:boolean; + get send_updates_to_origin() { + // assumes origin is not current endpoint + // don't send if exclude_origin_from_updates set or has a local transform_scope + return this.origin && !this.is_origin && !(this.#exclude_origin_from_updates || this.transform_scope) + } + + #exclude_origin_from_updates?:boolean; public excludeEndpointFromUpdates(endpoint:Endpoint) { // TODO origin equals id also for remote endpoints! if (this.origin.equals(endpoint)) this.#exclude_origin_from_updates = true; @@ -3399,8 +3421,8 @@ export class Pointer extends Ref { // get current value value = value ?? this.getProperty(key); - if (this.origin && !this.is_origin) { - if (!this.#exclude_origin_from_updates) this.handleDatexUpdate(key, Runtime.PRECOMPILED_DXB.SET_PROPERTY, [this, key, value], this.origin) + if (this.send_updates_to_origin) { + this.handleDatexUpdate(key, Runtime.PRECOMPILED_DXB.SET_PROPERTY, [this, key, value], this.origin) } if (this.update_endpoints.size) { this.handleDatexUpdate(key, Runtime.PRECOMPILED_DXB.SET_PROPERTY, [this, key, value], this.update_endpoints) @@ -3451,8 +3473,8 @@ export class Pointer extends Ref { // propagate updates via datex - if (this.origin && !this.is_origin) { - if (!this.#exclude_origin_from_updates) this.handleDatexUpdate(null, '? += ?', [this, value], this.origin) + if (this.send_updates_to_origin) { + this.handleDatexUpdate(null, '? += ?', [this, value], this.origin) } if (this.update_endpoints.size) { this.handleDatexUpdate(null, '? += ?', [this, value], this.update_endpoints) @@ -3482,9 +3504,9 @@ export class Pointer extends Ref { this.streaming.push(true); // also stream for all future subscribers - if (this.origin && !this.is_origin) { + if (this.send_updates_to_origin) { logger.info("streaming to parent " + this.origin); - if (!this.#exclude_origin_from_updates) this.handleDatexUpdate(null, '? << ?'/*DatexRuntime.PRECOMPILED_DXB.STREAM*/, [this, obj], this.origin) + this.handleDatexUpdate(null, '? << ?'/*DatexRuntime.PRECOMPILED_DXB.STREAM*/, [this, obj], this.origin) } if (this.update_endpoints.size) { logger.info("streaming to subscribers " + this.update_endpoints); @@ -3516,8 +3538,8 @@ export class Pointer extends Ref { // propagate updates via datex? - if (this.origin && !this.is_origin) { - if (!this.#exclude_origin_from_updates) this.handleDatexUpdate(null, Runtime.PRECOMPILED_DXB.CLEAR_WILDCARD, [this], this.origin) + if (this.send_updates_to_origin) { + this.handleDatexUpdate(null, Runtime.PRECOMPILED_DXB.CLEAR_WILDCARD, [this], this.origin) } if (this.update_endpoints.size) { this.handleDatexUpdate(null, Runtime.PRECOMPILED_DXB.CLEAR_WILDCARD, [this], this.update_endpoints) @@ -3572,11 +3594,9 @@ export class Pointer extends Ref { const ret = Array.prototype.splice.call(this.shadow_object, start_index, deleteCount, ...replace); // propagate updates via datex? - if (this.origin && !this.is_origin) { - if (!this.#exclude_origin_from_updates) { - if (!replace?.length) this.handleDatexUpdate(null, '#0 = ?0; #1 = count #0;#0.(?1..?2) = void;#0.(?1..#1) = #0.(?3..#1);', [this, start, end, start+size], this.origin) // no insert - else this.handleDatexUpdate(null, '#0=?0;#0.(?4..?1) = void; #0.(?2..((count #0) + ?3)) = #0.(?4..(count #0));#0.(?4..?5) = ?6;', [this, end, start-size+replace_length, replace_length, start, start+replace_length, replace], this.origin) // insert - } + if (this.send_updates_to_origin) { + if (!replace?.length) this.handleDatexUpdate(null, '#0 = ?0; #1 = count #0;#0.(?1..?2) = void;#0.(?1..#1) = #0.(?3..#1);', [this, start, end, start+size], this.origin) // no insert + else this.handleDatexUpdate(null, '#0=?0;#0.(?4..?1) = void; #0.(?2..((count #0) + ?3)) = #0.(?4..(count #0));#0.(?4..?5) = ?6;', [this, end, start-size+replace_length, replace_length, start, start+replace_length, replace], this.origin) // insert } if (this.update_endpoints.size) { if (!replace?.length) this.handleDatexUpdate(null, '#0 = ?0; #1 = count #0;#0.(?1..?2) = void;#0.(?1..#1) = #0.(?3..#1);', [this, start, end, start+size], this.update_endpoints) // no insert @@ -3638,8 +3658,8 @@ export class Pointer extends Ref { if ((res == INVALID || res == NOT_EXISTING) && this.shadow_object instanceof Array) key = BigInt(key); // change to for DATEX if - if (this.origin && !this.is_origin) { - if (!this.#exclude_origin_from_updates) this.handleDatexUpdate(null, '?.? = void', [this, key], this.origin) + if (this.send_updates_to_origin) { + this.handleDatexUpdate(null, '?.? = void', [this, key], this.origin) } if (this.update_endpoints.size) { this.handleDatexUpdate(null, '?.? = void', [this, key], this.update_endpoints) @@ -3677,8 +3697,8 @@ export class Pointer extends Ref { // propagate updates via datex - if (this.origin && !this.is_origin) { - if (!this.#exclude_origin_from_updates) this.handleDatexUpdate(null, '? -= ?', [this, value], this.origin) + if (this.send_updates_to_origin) { + this.handleDatexUpdate(null, '? -= ?', [this, value], this.origin) } if (this.update_endpoints.size) { logger.debug("forwarding delete to subscribers " + this.update_endpoints); diff --git a/runtime/runtime.ts b/runtime/runtime.ts index bd89ad6e..aaeeced7 100644 --- a/runtime/runtime.ts +++ b/runtime/runtime.ts @@ -275,7 +275,7 @@ export class Runtime { always ( if (local_map.(lang)) (local_map.(lang)) else ( - 'Could not translate to (lang)' + local_map.'en' default '?' /* val text = local_map.'en'; val language = lang; @@ -283,7 +283,7 @@ export class Runtime { */ ) ) - ` // used for persistent DATEX storage + `, true // used for persistent DATEX storage ); } @@ -308,7 +308,7 @@ export class Runtime { always ( if (local_map.(lang)) (local_map.(lang).(key)) else ( - 'Could not translate to (lang)' + local_map.'en' default '?' /* val text = local_map.'en'.(key); val language = lang; @@ -316,6 +316,7 @@ export class Runtime { */ ) )` : '' // used for persistent DATEX storage + , true ); // add to saved string if(!saved_strings) this.#saved_local_strings.set(local_map, saved_strings = new Map()); @@ -3048,7 +3049,7 @@ export class Runtime { // resolve if (p[1]?.resolve) { p[1].resolve(ptr) - Pointer.loading_pointers.delete(ptr.id); // TODO: only workaround, automaticall handle delete, but leads to promise rejection errors + Pointer.loading_pointers.delete(ptr.id); // TODO: only workaround, automatically handle delete, but leads to promise rejection errors } } else await Runtime.runtime_actions.handleAssignAction(SCOPE, p[1], null, null, el, p[0]); // other action on pointer @@ -5795,10 +5796,25 @@ export class Runtime { // TRANSFORM (always) if (INNER_SCOPE.scope_block_for == BinaryCode.TRANSFORM) { INNER_SCOPE.scope_block_for = null; - await this.runtime_actions.insertToScope( - SCOPE, - Ref.collapseValue(await Pointer.createTransformAsync(INNER_SCOPE.scope_block_vars, code_block)) - ) + const waiting = [...SCOPE.inner_scope.waiting_ptrs??[]].at(-1); + // assign always() to init ($xxx := always(...)) + if (waiting && typeof waiting[1] == "object") { // is init + const ptr = waiting[0] + ptr.handleTransformAsync(INNER_SCOPE.scope_block_vars, code_block); + // resolve + if (waiting[1]?.resolve) { + waiting[1].resolve(ptr) + Pointer.loading_pointers.delete(ptr.id); // TODO: only workaround, automatically handle delete, but leads to promise rejection errors + } + SCOPE.inner_scope.waiting_ptrs!.delete(waiting) + } + else { + await this.runtime_actions.insertToScope( + SCOPE, + Ref.collapseValue(await Pointer.createTransformAsync(INNER_SCOPE.scope_block_vars, code_block)) + ) + } + } // ASSERT diff --git a/utils/cookies.ts b/utils/cookies.ts index 0ad12fd9..e763db4d 100644 --- a/utils/cookies.ts +++ b/utils/cookies.ts @@ -4,6 +4,7 @@ import { client_type } from "./constants.ts"; const port = globalThis.location?.port; +const isSafari = (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)); export function deleteCookie(name: string) { @@ -28,7 +29,7 @@ export function setCookie(name: string, value: string, expDays?: number) { expiryDate.setTime(expiryDate.getTime() + (expDays * 24 * 60 * 60 * 1000)); } const expires = expDays == 0 ? "" : "expires=" + expiryDate.toUTCString() + ";"; - document.cookie = name + "=" + value + "; " + expires + " path=/;" + document.cookie = name + "=" + value + "; " + expires + " path=/; SameSite=None;" + (isSafari ? "" :" Secure;") } export function getCookie(name: string) { diff --git a/utils/message_logger.ts b/utils/message_logger.ts index ec689e43..358a2575 100644 --- a/utils/message_logger.ts +++ b/utils/message_logger.ts @@ -6,6 +6,7 @@ import { Logger } from "./logger.ts"; // WASM import {decompile as wasm_decompile} from "../wasm/adapter/pkg/datex_wasm.js"; +import { console } from "./ansi_compat.ts"; export class MessageLogger { diff --git a/wasm/adapter/pkg/datex_wasm_bg.wasm b/wasm/adapter/pkg/datex_wasm_bg.wasm index 4abb7e12..a4bf688c 100644 Binary files a/wasm/adapter/pkg/datex_wasm_bg.wasm and b/wasm/adapter/pkg/datex_wasm_bg.wasm differ