@reactvision/react-viro 2.57.0 → 2.57.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/react_viro/react_viro-release.aar +0 -0
- package/android/viro_renderer/viro_renderer-release.aar +0 -0
- package/components/AR/ViroARPlane.tsx +1 -4
- package/components/AR/ViroARPlaneSelector.tsx +10 -5
- package/components/AR/ViroARScene.tsx +2 -4
- package/components/Studio/StudioARScene.tsx +243 -48
- package/components/Studio/StudioSceneNavigator.tsx +15 -0
- package/components/Studio/VRTStudioModule.ts +10 -0
- package/components/Studio/domain/StudioSounds.tsx +46 -0
- package/components/Studio/domain/apiRequestHelpers.ts +612 -0
- package/components/Studio/domain/collisionBindingsRuntime.ts +17 -2
- package/components/Studio/domain/defaultApiRequestExecutor.ts +61 -0
- package/components/Studio/domain/expressionEvaluator.ts +603 -0
- package/components/Studio/domain/sceneNavigationHandler.ts +770 -20
- package/components/Studio/domain/soundManager.ts +141 -0
- package/components/Studio/domain/utils.ts +50 -0
- package/components/Studio/domain/variableStore.ts +74 -0
- package/components/Studio/domain/viroNodeFactory.tsx +175 -44
- package/components/Studio/domain/visibilityStore.ts +66 -0
- package/components/Studio/index.ts +1 -0
- package/components/Studio/types.ts +178 -1
- package/components/Utilities/VRModuleOpenXR.ts +35 -0
- package/components/Utilities/ViroVersion.ts +1 -1
- package/components/ViroAnimatedImage.tsx +1 -0
- package/components/ViroFlexView.tsx +1 -0
- package/components/ViroImage.tsx +1 -0
- package/components/ViroObjectDetector.tsx +13 -5
- package/components/ViroParticleEmitter.tsx +2 -0
- package/components/ViroPolygon.tsx +2 -1
- package/components/ViroQuad.tsx +2 -1
- package/components/ViroSurface.tsx +1 -0
- package/components/ViroVideo.tsx +1 -0
- package/components/ViroXRSceneNavigator.tsx +8 -0
- package/dist/components/AR/ViroARPlane.d.ts +1 -1
- package/dist/components/AR/ViroARPlane.js +1 -5
- package/dist/components/AR/ViroARPlaneSelector.d.ts +1 -1
- package/dist/components/AR/ViroARPlaneSelector.js +10 -5
- package/dist/components/AR/ViroARScene.d.ts +1 -1
- package/dist/components/AR/ViroARScene.js +2 -5
- package/dist/components/Studio/StudioARScene.d.ts +3 -0
- package/dist/components/Studio/StudioARScene.js +142 -15
- package/dist/components/Studio/StudioSceneNavigator.js +14 -0
- package/dist/components/Studio/VRTStudioModule.d.ts +6 -0
- package/dist/components/Studio/VRTStudioModule.js +10 -0
- package/dist/components/Studio/domain/StudioSounds.d.ts +11 -0
- package/dist/components/Studio/domain/StudioSounds.js +69 -0
- package/dist/components/Studio/domain/apiRequestHelpers.d.ts +180 -0
- package/dist/components/Studio/domain/apiRequestHelpers.js +496 -0
- package/dist/components/Studio/domain/collisionBindingsRuntime.d.ts +3 -1
- package/dist/components/Studio/domain/collisionBindingsRuntime.js +4 -3
- package/dist/components/Studio/domain/defaultApiRequestExecutor.d.ts +14 -0
- package/dist/components/Studio/domain/defaultApiRequestExecutor.js +58 -0
- package/dist/components/Studio/domain/expressionEvaluator.d.ts +108 -0
- package/dist/components/Studio/domain/expressionEvaluator.js +520 -0
- package/dist/components/Studio/domain/sceneNavigationHandler.d.ts +35 -8
- package/dist/components/Studio/domain/sceneNavigationHandler.js +564 -22
- package/dist/components/Studio/domain/soundManager.d.ts +52 -0
- package/dist/components/Studio/domain/soundManager.js +118 -0
- package/dist/components/Studio/domain/utils.d.ts +16 -0
- package/dist/components/Studio/domain/utils.js +48 -0
- package/dist/components/Studio/domain/variableStore.d.ts +21 -0
- package/dist/components/Studio/domain/variableStore.js +65 -0
- package/dist/components/Studio/domain/viroNodeFactory.d.ts +4 -9
- package/dist/components/Studio/domain/viroNodeFactory.js +84 -25
- package/dist/components/Studio/domain/visibilityStore.d.ts +33 -0
- package/dist/components/Studio/domain/visibilityStore.js +66 -0
- package/dist/components/Studio/index.d.ts +1 -1
- package/dist/components/Studio/types.d.ts +150 -1
- package/dist/components/Utilities/VRModuleOpenXR.d.ts +19 -0
- package/dist/components/Utilities/VRModuleOpenXR.js +16 -0
- package/dist/components/Utilities/ViroVersion.d.ts +1 -1
- package/dist/components/Utilities/ViroVersion.js +1 -1
- package/dist/components/ViroAnimatedImage.js +1 -0
- package/dist/components/ViroFlexView.js +1 -0
- package/dist/components/ViroImage.js +1 -0
- package/dist/components/ViroObjectDetector.d.ts +13 -5
- package/dist/components/ViroObjectDetector.js +13 -5
- package/dist/components/ViroParticleEmitter.js +2 -0
- package/dist/components/ViroPolygon.js +1 -0
- package/dist/components/ViroQuad.js +1 -0
- package/dist/components/ViroSurface.js +1 -0
- package/dist/components/ViroVideo.js +1 -0
- package/dist/components/ViroXRSceneNavigator.d.ts +8 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -1
- package/dist/plugins/withViroAndroid.js +18 -0
- package/index.ts +4 -3
- package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Shaders.dat +1 -1
- package/ios/dist/lib/libViroReact.a +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Studio expression evaluator: parses and evaluates the literal-or-expression
|
|
3
|
+
* values written by SET_VARIABLE steps (and, later, reused by Branch
|
|
4
|
+
* conditions via compare()). Hand-rolled tokenizer + recursive descent over a
|
|
5
|
+
* fixed grammar — never eval()/Function().
|
|
6
|
+
*
|
|
7
|
+
* Dual-maintained: this file exists in the Viro repo
|
|
8
|
+
* (components/Studio/domain/) and the ReactVisionStudio monorepo
|
|
9
|
+
* (packages/common/scene-assets/). Keep the two copies identical.
|
|
10
|
+
*
|
|
11
|
+
* Grammar (precedence low → high):
|
|
12
|
+
* or := and ( "||" and )* Boolean operands
|
|
13
|
+
* and := equality ( "&&" equality )* Boolean operands
|
|
14
|
+
* equality := relational ( ("=="|"!=") relational )* same-type operands
|
|
15
|
+
* relational := additive ( ("<"|"<="|">"|">=") additive )* Number only
|
|
16
|
+
* additive := multiplicative ( ("+"|"-") multiplicative )*
|
|
17
|
+
* "+" adds Numbers, or concatenates if either side is a String
|
|
18
|
+
* multiplicative := unary ( ("*"|"/") unary )* Number only
|
|
19
|
+
* unary := ("!"|"-") unary | primary
|
|
20
|
+
* primary := NUMBER | STRING | "true" | "false" | IDENT | "(" or ")"
|
|
21
|
+
*/
|
|
22
|
+
export type StudioVariableType = "BOOLEAN" | "NUMBER" | "STRING";
|
|
23
|
+
export type StudioVariableValue = boolean | number | string;
|
|
24
|
+
export declare const MAX_EXPRESSION_LENGTH = 500;
|
|
25
|
+
export declare const VARIABLE_NAME_PATTERN: RegExp;
|
|
26
|
+
type BinaryOp = "||" | "&&" | "==" | "!=" | "<" | "<=" | ">" | ">=" | "+" | "-" | "*" | "/";
|
|
27
|
+
type UnaryOp = "!" | "-";
|
|
28
|
+
export type ExpressionNode = {
|
|
29
|
+
kind: "literal";
|
|
30
|
+
value: StudioVariableValue;
|
|
31
|
+
} | {
|
|
32
|
+
kind: "variable";
|
|
33
|
+
name: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "unary";
|
|
36
|
+
op: UnaryOp;
|
|
37
|
+
operand: ExpressionNode;
|
|
38
|
+
} | {
|
|
39
|
+
kind: "binary";
|
|
40
|
+
op: BinaryOp;
|
|
41
|
+
left: ExpressionNode;
|
|
42
|
+
right: ExpressionNode;
|
|
43
|
+
};
|
|
44
|
+
export type ParseResult = {
|
|
45
|
+
ok: true;
|
|
46
|
+
ast: ExpressionNode;
|
|
47
|
+
refs: string[];
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
error: string;
|
|
51
|
+
};
|
|
52
|
+
export type TypeCheckResult = {
|
|
53
|
+
ok: true;
|
|
54
|
+
type: StudioVariableType;
|
|
55
|
+
} | {
|
|
56
|
+
ok: false;
|
|
57
|
+
error: string;
|
|
58
|
+
};
|
|
59
|
+
export type EvaluateResult = {
|
|
60
|
+
ok: true;
|
|
61
|
+
value: StudioVariableValue;
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
error: string;
|
|
65
|
+
};
|
|
66
|
+
export declare function parseExpression(src: string): ParseResult;
|
|
67
|
+
export declare function checkTypes(node: ExpressionNode, declared: Record<string, StudioVariableType>): TypeCheckResult;
|
|
68
|
+
/** Parse + type-check an expression against a target variable's type in one call. */
|
|
69
|
+
export declare function validateExpressionForTarget(src: string, targetType: StudioVariableType, declared: Record<string, StudioVariableType>): {
|
|
70
|
+
ok: true;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
error: string;
|
|
74
|
+
};
|
|
75
|
+
export type ComparisonOp = "==" | "!=" | "<" | "<=" | ">" | ">=";
|
|
76
|
+
/** Typed comparison shared with Branch conditions. Throws on operand mismatch. */
|
|
77
|
+
export declare function compare(op: ComparisonOp, left: StudioVariableValue, right: StudioVariableValue): boolean;
|
|
78
|
+
export type BranchComparison = "EQUALS" | "NOT_EQUALS" | "GREATER_THAN" | "LESS_THAN" | "GREATER_OR_EQUAL" | "LESS_OR_EQUAL" | "LIKE" | "ILIKE";
|
|
79
|
+
/** Structural input so this file stays standalone (no scene-type imports). */
|
|
80
|
+
export type BranchConditionInput = {
|
|
81
|
+
comparison: BranchComparison;
|
|
82
|
+
variable_name: string;
|
|
83
|
+
compare_literal: StudioVariableValue | null;
|
|
84
|
+
compare_variable_name: string | null;
|
|
85
|
+
};
|
|
86
|
+
export type BranchConditionResult = {
|
|
87
|
+
ok: true;
|
|
88
|
+
value: boolean;
|
|
89
|
+
} | {
|
|
90
|
+
ok: false;
|
|
91
|
+
error: string;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Evaluates a structured Branch condition against the runtime store.
|
|
95
|
+
* Never throws; callers warn + skip both arms on { ok: false }.
|
|
96
|
+
*/
|
|
97
|
+
export declare function evaluateBranchCondition(cond: BranchConditionInput, get: (name: string) => StudioVariableValue | undefined): BranchConditionResult;
|
|
98
|
+
export declare function evaluate(node: ExpressionNode, get: (name: string) => StudioVariableValue | undefined): EvaluateResult;
|
|
99
|
+
export declare function valueMatchesType(value: StudioVariableValue, type: StudioVariableType): boolean;
|
|
100
|
+
/** Serialize a literal value into expression syntax (the editor's literal mode). */
|
|
101
|
+
export declare function formatLiteral(value: StudioVariableValue): string;
|
|
102
|
+
/**
|
|
103
|
+
* Rewrite every reference to oldName in an expression's source, preserving all
|
|
104
|
+
* other formatting (token-span splicing). Returns the source unchanged if it
|
|
105
|
+
* does not tokenize. Powers variable rename in the editor.
|
|
106
|
+
*/
|
|
107
|
+
export declare function renameIdentifier(src: string, oldName: string, newName: string): string;
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Studio expression evaluator: parses and evaluates the literal-or-expression
|
|
4
|
+
* values written by SET_VARIABLE steps (and, later, reused by Branch
|
|
5
|
+
* conditions via compare()). Hand-rolled tokenizer + recursive descent over a
|
|
6
|
+
* fixed grammar — never eval()/Function().
|
|
7
|
+
*
|
|
8
|
+
* Dual-maintained: this file exists in the Viro repo
|
|
9
|
+
* (components/Studio/domain/) and the ReactVisionStudio monorepo
|
|
10
|
+
* (packages/common/scene-assets/). Keep the two copies identical.
|
|
11
|
+
*
|
|
12
|
+
* Grammar (precedence low → high):
|
|
13
|
+
* or := and ( "||" and )* Boolean operands
|
|
14
|
+
* and := equality ( "&&" equality )* Boolean operands
|
|
15
|
+
* equality := relational ( ("=="|"!=") relational )* same-type operands
|
|
16
|
+
* relational := additive ( ("<"|"<="|">"|">=") additive )* Number only
|
|
17
|
+
* additive := multiplicative ( ("+"|"-") multiplicative )*
|
|
18
|
+
* "+" adds Numbers, or concatenates if either side is a String
|
|
19
|
+
* multiplicative := unary ( ("*"|"/") unary )* Number only
|
|
20
|
+
* unary := ("!"|"-") unary | primary
|
|
21
|
+
* primary := NUMBER | STRING | "true" | "false" | IDENT | "(" or ")"
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.VARIABLE_NAME_PATTERN = exports.MAX_EXPRESSION_LENGTH = void 0;
|
|
25
|
+
exports.parseExpression = parseExpression;
|
|
26
|
+
exports.checkTypes = checkTypes;
|
|
27
|
+
exports.validateExpressionForTarget = validateExpressionForTarget;
|
|
28
|
+
exports.compare = compare;
|
|
29
|
+
exports.evaluateBranchCondition = evaluateBranchCondition;
|
|
30
|
+
exports.evaluate = evaluate;
|
|
31
|
+
exports.valueMatchesType = valueMatchesType;
|
|
32
|
+
exports.formatLiteral = formatLiteral;
|
|
33
|
+
exports.renameIdentifier = renameIdentifier;
|
|
34
|
+
exports.MAX_EXPRESSION_LENGTH = 500;
|
|
35
|
+
exports.VARIABLE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
36
|
+
class ExpressionError extends Error {
|
|
37
|
+
}
|
|
38
|
+
const IDENT_START = /[A-Za-z_]/;
|
|
39
|
+
const IDENT_PART = /[A-Za-z0-9_]/;
|
|
40
|
+
const DIGIT = /[0-9]/;
|
|
41
|
+
function tokenize(src) {
|
|
42
|
+
const tokens = [];
|
|
43
|
+
let i = 0;
|
|
44
|
+
while (i < src.length) {
|
|
45
|
+
const ch = src[i];
|
|
46
|
+
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const start = i;
|
|
51
|
+
if (DIGIT.test(ch)) {
|
|
52
|
+
let j = i + 1;
|
|
53
|
+
while (j < src.length && DIGIT.test(src[j]))
|
|
54
|
+
j++;
|
|
55
|
+
if (src[j] === "." && DIGIT.test(src[j + 1] ?? "")) {
|
|
56
|
+
j++;
|
|
57
|
+
while (j < src.length && DIGIT.test(src[j]))
|
|
58
|
+
j++;
|
|
59
|
+
}
|
|
60
|
+
tokens.push({ type: "number", value: parseFloat(src.slice(i, j)), start, end: j });
|
|
61
|
+
i = j;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (ch === '"' || ch === "'") {
|
|
65
|
+
const quote = ch;
|
|
66
|
+
let j = i + 1;
|
|
67
|
+
let text = "";
|
|
68
|
+
let closed = false;
|
|
69
|
+
while (j < src.length) {
|
|
70
|
+
const c = src[j];
|
|
71
|
+
if (c === "\\") {
|
|
72
|
+
const next = src[j + 1];
|
|
73
|
+
if (next === undefined)
|
|
74
|
+
break;
|
|
75
|
+
text += next === "n" ? "\n" : next === "t" ? "\t" : next;
|
|
76
|
+
j += 2;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (c === quote) {
|
|
80
|
+
closed = true;
|
|
81
|
+
j++;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
text += c;
|
|
85
|
+
j++;
|
|
86
|
+
}
|
|
87
|
+
if (!closed)
|
|
88
|
+
throw new ExpressionError(`Unterminated string at position ${start}`);
|
|
89
|
+
tokens.push({ type: "string", value: text, start, end: j });
|
|
90
|
+
i = j;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (IDENT_START.test(ch)) {
|
|
94
|
+
let j = i + 1;
|
|
95
|
+
while (j < src.length && IDENT_PART.test(src[j]))
|
|
96
|
+
j++;
|
|
97
|
+
const word = src.slice(i, j);
|
|
98
|
+
if (word === "true" || word === "false") {
|
|
99
|
+
tokens.push({ type: "boolean", value: word === "true", start, end: j });
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
tokens.push({ type: "ident", name: word, start, end: j });
|
|
103
|
+
}
|
|
104
|
+
i = j;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const two = src.slice(i, i + 2);
|
|
108
|
+
if (two === "||" || two === "&&" || two === "==" || two === "!=" || two === "<=" || two === ">=") {
|
|
109
|
+
tokens.push({ type: "op", op: two, start, end: i + 2 });
|
|
110
|
+
i += 2;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if ("+-*/<>!()".includes(ch)) {
|
|
114
|
+
tokens.push({ type: "op", op: ch, start, end: i + 1 });
|
|
115
|
+
i++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
throw new ExpressionError(`Unexpected character "${ch}" at position ${i}`);
|
|
119
|
+
}
|
|
120
|
+
tokens.push({ type: "eof", start: src.length, end: src.length });
|
|
121
|
+
return tokens;
|
|
122
|
+
}
|
|
123
|
+
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
124
|
+
class Parser {
|
|
125
|
+
tokens;
|
|
126
|
+
pos = 0;
|
|
127
|
+
constructor(tokens) {
|
|
128
|
+
this.tokens = tokens;
|
|
129
|
+
}
|
|
130
|
+
peek() {
|
|
131
|
+
return this.tokens[this.pos];
|
|
132
|
+
}
|
|
133
|
+
matchOp(...ops) {
|
|
134
|
+
const t = this.peek();
|
|
135
|
+
if (t.type === "op" && ops.includes(t.op)) {
|
|
136
|
+
this.pos++;
|
|
137
|
+
return t.op;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
parse() {
|
|
142
|
+
const node = this.parseOr();
|
|
143
|
+
const t = this.peek();
|
|
144
|
+
if (t.type !== "eof") {
|
|
145
|
+
throw new ExpressionError(`Unexpected token at position ${t.start}`);
|
|
146
|
+
}
|
|
147
|
+
return node;
|
|
148
|
+
}
|
|
149
|
+
parseBinaryLevel(ops, next) {
|
|
150
|
+
let left = next();
|
|
151
|
+
for (;;) {
|
|
152
|
+
const op = this.matchOp(...ops);
|
|
153
|
+
if (op === null)
|
|
154
|
+
break;
|
|
155
|
+
left = { kind: "binary", op: op, left, right: next() };
|
|
156
|
+
}
|
|
157
|
+
return left;
|
|
158
|
+
}
|
|
159
|
+
parseOr() {
|
|
160
|
+
return this.parseBinaryLevel(["||"], () => this.parseAnd());
|
|
161
|
+
}
|
|
162
|
+
parseAnd() {
|
|
163
|
+
return this.parseBinaryLevel(["&&"], () => this.parseEquality());
|
|
164
|
+
}
|
|
165
|
+
parseEquality() {
|
|
166
|
+
return this.parseBinaryLevel(["==", "!="], () => this.parseRelational());
|
|
167
|
+
}
|
|
168
|
+
parseRelational() {
|
|
169
|
+
return this.parseBinaryLevel(["<", "<=", ">", ">="], () => this.parseAdditive());
|
|
170
|
+
}
|
|
171
|
+
parseAdditive() {
|
|
172
|
+
return this.parseBinaryLevel(["+", "-"], () => this.parseMultiplicative());
|
|
173
|
+
}
|
|
174
|
+
parseMultiplicative() {
|
|
175
|
+
return this.parseBinaryLevel(["*", "/"], () => this.parseUnary());
|
|
176
|
+
}
|
|
177
|
+
parseUnary() {
|
|
178
|
+
const op = this.matchOp("!", "-");
|
|
179
|
+
if (op !== null) {
|
|
180
|
+
return { kind: "unary", op: op, operand: this.parseUnary() };
|
|
181
|
+
}
|
|
182
|
+
return this.parsePrimary();
|
|
183
|
+
}
|
|
184
|
+
parsePrimary() {
|
|
185
|
+
const t = this.peek();
|
|
186
|
+
if (t.type === "number" || t.type === "string" || t.type === "boolean") {
|
|
187
|
+
this.pos++;
|
|
188
|
+
return { kind: "literal", value: t.value };
|
|
189
|
+
}
|
|
190
|
+
if (t.type === "ident") {
|
|
191
|
+
this.pos++;
|
|
192
|
+
return { kind: "variable", name: t.name };
|
|
193
|
+
}
|
|
194
|
+
if (t.type === "op" && t.op === "(") {
|
|
195
|
+
this.pos++;
|
|
196
|
+
const node = this.parseOr();
|
|
197
|
+
if (this.matchOp(")") === null) {
|
|
198
|
+
throw new ExpressionError(`Missing ")" at position ${this.peek().start}`);
|
|
199
|
+
}
|
|
200
|
+
return node;
|
|
201
|
+
}
|
|
202
|
+
throw new ExpressionError(`Unexpected token at position ${t.start}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function collectRefs(node, acc) {
|
|
206
|
+
if (node.kind === "variable") {
|
|
207
|
+
acc.add(node.name);
|
|
208
|
+
}
|
|
209
|
+
else if (node.kind === "unary") {
|
|
210
|
+
collectRefs(node.operand, acc);
|
|
211
|
+
}
|
|
212
|
+
else if (node.kind === "binary") {
|
|
213
|
+
collectRefs(node.left, acc);
|
|
214
|
+
collectRefs(node.right, acc);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function parseExpression(src) {
|
|
218
|
+
if (typeof src !== "string" || src.trim().length === 0) {
|
|
219
|
+
return { ok: false, error: "Expression is empty" };
|
|
220
|
+
}
|
|
221
|
+
if (src.length > exports.MAX_EXPRESSION_LENGTH) {
|
|
222
|
+
return { ok: false, error: `Expression exceeds ${exports.MAX_EXPRESSION_LENGTH} characters` };
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const ast = new Parser(tokenize(src)).parse();
|
|
226
|
+
const refs = new Set();
|
|
227
|
+
collectRefs(ast, refs);
|
|
228
|
+
const refList = [];
|
|
229
|
+
refs.forEach((name) => refList.push(name));
|
|
230
|
+
return { ok: true, ast, refs: refList };
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// ─── Type checking (author-time) ─────────────────────────────────────────────
|
|
237
|
+
function inferType(node, declared) {
|
|
238
|
+
switch (node.kind) {
|
|
239
|
+
case "literal":
|
|
240
|
+
if (typeof node.value === "boolean")
|
|
241
|
+
return "BOOLEAN";
|
|
242
|
+
if (typeof node.value === "number")
|
|
243
|
+
return "NUMBER";
|
|
244
|
+
return "STRING";
|
|
245
|
+
case "variable": {
|
|
246
|
+
const t = declared[node.name];
|
|
247
|
+
if (t === undefined)
|
|
248
|
+
throw new ExpressionError(`Unknown variable "${node.name}"`);
|
|
249
|
+
return t;
|
|
250
|
+
}
|
|
251
|
+
case "unary": {
|
|
252
|
+
const t = inferType(node.operand, declared);
|
|
253
|
+
if (node.op === "!") {
|
|
254
|
+
if (t !== "BOOLEAN")
|
|
255
|
+
throw new ExpressionError(`"!" needs a Boolean operand`);
|
|
256
|
+
return "BOOLEAN";
|
|
257
|
+
}
|
|
258
|
+
if (t !== "NUMBER")
|
|
259
|
+
throw new ExpressionError(`"-" needs a Number operand`);
|
|
260
|
+
return "NUMBER";
|
|
261
|
+
}
|
|
262
|
+
case "binary": {
|
|
263
|
+
const l = inferType(node.left, declared);
|
|
264
|
+
const r = inferType(node.right, declared);
|
|
265
|
+
switch (node.op) {
|
|
266
|
+
case "||":
|
|
267
|
+
case "&&":
|
|
268
|
+
if (l !== "BOOLEAN" || r !== "BOOLEAN") {
|
|
269
|
+
throw new ExpressionError(`"${node.op}" needs Boolean operands`);
|
|
270
|
+
}
|
|
271
|
+
return "BOOLEAN";
|
|
272
|
+
case "==":
|
|
273
|
+
case "!=":
|
|
274
|
+
if (l !== r) {
|
|
275
|
+
throw new ExpressionError(`"${node.op}" needs operands of the same type`);
|
|
276
|
+
}
|
|
277
|
+
return "BOOLEAN";
|
|
278
|
+
case "<":
|
|
279
|
+
case "<=":
|
|
280
|
+
case ">":
|
|
281
|
+
case ">=":
|
|
282
|
+
if (l !== "NUMBER" || r !== "NUMBER") {
|
|
283
|
+
throw new ExpressionError(`"${node.op}" needs Number operands`);
|
|
284
|
+
}
|
|
285
|
+
return "BOOLEAN";
|
|
286
|
+
case "+":
|
|
287
|
+
if (l === "STRING" || r === "STRING")
|
|
288
|
+
return "STRING";
|
|
289
|
+
if (l === "NUMBER" && r === "NUMBER")
|
|
290
|
+
return "NUMBER";
|
|
291
|
+
throw new ExpressionError(`"+" needs Numbers, or at least one String`);
|
|
292
|
+
case "-":
|
|
293
|
+
case "*":
|
|
294
|
+
case "/":
|
|
295
|
+
if (l !== "NUMBER" || r !== "NUMBER") {
|
|
296
|
+
throw new ExpressionError(`"${node.op}" needs Number operands`);
|
|
297
|
+
}
|
|
298
|
+
return "NUMBER";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function checkTypes(node, declared) {
|
|
304
|
+
try {
|
|
305
|
+
return { ok: true, type: inferType(node, declared) };
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** Parse + type-check an expression against a target variable's type in one call. */
|
|
312
|
+
function validateExpressionForTarget(src, targetType, declared) {
|
|
313
|
+
const parsed = parseExpression(src);
|
|
314
|
+
if (!parsed.ok)
|
|
315
|
+
return parsed;
|
|
316
|
+
const typed = checkTypes(parsed.ast, declared);
|
|
317
|
+
if (!typed.ok)
|
|
318
|
+
return typed;
|
|
319
|
+
if (typed.type !== targetType) {
|
|
320
|
+
return {
|
|
321
|
+
ok: false,
|
|
322
|
+
error: `Expression result is ${typed.type}, expected ${targetType}`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return { ok: true };
|
|
326
|
+
}
|
|
327
|
+
/** Typed comparison shared with Branch conditions. Throws on operand mismatch. */
|
|
328
|
+
function compare(op, left, right) {
|
|
329
|
+
if (op === "==" || op === "!=") {
|
|
330
|
+
if (typeof left !== typeof right) {
|
|
331
|
+
throw new ExpressionError(`"${op}" needs operands of the same type`);
|
|
332
|
+
}
|
|
333
|
+
return op === "==" ? left === right : left !== right;
|
|
334
|
+
}
|
|
335
|
+
if (typeof left !== "number" || typeof right !== "number") {
|
|
336
|
+
throw new ExpressionError(`"${op}" needs Number operands`);
|
|
337
|
+
}
|
|
338
|
+
switch (op) {
|
|
339
|
+
case "<":
|
|
340
|
+
return left < right;
|
|
341
|
+
case "<=":
|
|
342
|
+
return left <= right;
|
|
343
|
+
case ">":
|
|
344
|
+
return left > right;
|
|
345
|
+
default:
|
|
346
|
+
return left >= right;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const BRANCH_COMPARISON_OPS = {
|
|
350
|
+
EQUALS: "==",
|
|
351
|
+
NOT_EQUALS: "!=",
|
|
352
|
+
GREATER_THAN: ">",
|
|
353
|
+
LESS_THAN: "<",
|
|
354
|
+
GREATER_OR_EQUAL: ">=",
|
|
355
|
+
LESS_OR_EQUAL: "<=",
|
|
356
|
+
};
|
|
357
|
+
/** SQL LIKE semantics: % = any sequence, _ = any single char; no escape syntax. */
|
|
358
|
+
function likeMatch(text, pattern, caseInsensitive) {
|
|
359
|
+
let regex = "";
|
|
360
|
+
for (const ch of pattern) {
|
|
361
|
+
if (ch === "%")
|
|
362
|
+
regex += "[\\s\\S]*";
|
|
363
|
+
else if (ch === "_")
|
|
364
|
+
regex += "[\\s\\S]";
|
|
365
|
+
else
|
|
366
|
+
regex += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
367
|
+
}
|
|
368
|
+
return new RegExp(`^${regex}$`, caseInsensitive ? "i" : "").test(text);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Evaluates a structured Branch condition against the runtime store.
|
|
372
|
+
* Never throws; callers warn + skip both arms on { ok: false }.
|
|
373
|
+
*/
|
|
374
|
+
function evaluateBranchCondition(cond, get) {
|
|
375
|
+
const left = get(cond.variable_name);
|
|
376
|
+
if (left === undefined) {
|
|
377
|
+
return { ok: false, error: `Variable "${cond.variable_name}" is not defined` };
|
|
378
|
+
}
|
|
379
|
+
let right;
|
|
380
|
+
if (cond.compare_variable_name !== null) {
|
|
381
|
+
right = get(cond.compare_variable_name);
|
|
382
|
+
if (right === undefined) {
|
|
383
|
+
return { ok: false, error: `Variable "${cond.compare_variable_name}" is not defined` };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else if (cond.compare_literal !== null) {
|
|
387
|
+
right = cond.compare_literal;
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
return { ok: false, error: "Condition has no operand" };
|
|
391
|
+
}
|
|
392
|
+
if (cond.comparison === "LIKE" || cond.comparison === "ILIKE") {
|
|
393
|
+
if (typeof left !== "string" || typeof right !== "string") {
|
|
394
|
+
return { ok: false, error: `"${cond.comparison}" needs String operands` };
|
|
395
|
+
}
|
|
396
|
+
return { ok: true, value: likeMatch(left, right, cond.comparison === "ILIKE") };
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
return { ok: true, value: compare(BRANCH_COMPARISON_OPS[cond.comparison], left, right) };
|
|
400
|
+
}
|
|
401
|
+
catch (e) {
|
|
402
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function guardFinite(n) {
|
|
406
|
+
if (!Number.isFinite(n))
|
|
407
|
+
throw new ExpressionError("Result is not a finite number");
|
|
408
|
+
return n;
|
|
409
|
+
}
|
|
410
|
+
function stringify(v) {
|
|
411
|
+
return typeof v === "string" ? v : String(v);
|
|
412
|
+
}
|
|
413
|
+
function evalNode(node, get) {
|
|
414
|
+
switch (node.kind) {
|
|
415
|
+
case "literal":
|
|
416
|
+
return node.value;
|
|
417
|
+
case "variable": {
|
|
418
|
+
const v = get(node.name);
|
|
419
|
+
if (v === undefined)
|
|
420
|
+
throw new ExpressionError(`Variable "${node.name}" is not set`);
|
|
421
|
+
return v;
|
|
422
|
+
}
|
|
423
|
+
case "unary": {
|
|
424
|
+
const v = evalNode(node.operand, get);
|
|
425
|
+
if (node.op === "!") {
|
|
426
|
+
if (typeof v !== "boolean")
|
|
427
|
+
throw new ExpressionError(`"!" needs a Boolean operand`);
|
|
428
|
+
return !v;
|
|
429
|
+
}
|
|
430
|
+
if (typeof v !== "number")
|
|
431
|
+
throw new ExpressionError(`"-" needs a Number operand`);
|
|
432
|
+
return guardFinite(-v);
|
|
433
|
+
}
|
|
434
|
+
case "binary": {
|
|
435
|
+
if (node.op === "||" || node.op === "&&") {
|
|
436
|
+
const l = evalNode(node.left, get);
|
|
437
|
+
if (typeof l !== "boolean")
|
|
438
|
+
throw new ExpressionError(`"${node.op}" needs Boolean operands`);
|
|
439
|
+
if (node.op === "||" && l)
|
|
440
|
+
return true;
|
|
441
|
+
if (node.op === "&&" && !l)
|
|
442
|
+
return false;
|
|
443
|
+
const r = evalNode(node.right, get);
|
|
444
|
+
if (typeof r !== "boolean")
|
|
445
|
+
throw new ExpressionError(`"${node.op}" needs Boolean operands`);
|
|
446
|
+
return r;
|
|
447
|
+
}
|
|
448
|
+
const l = evalNode(node.left, get);
|
|
449
|
+
const r = evalNode(node.right, get);
|
|
450
|
+
switch (node.op) {
|
|
451
|
+
case "==":
|
|
452
|
+
case "!=":
|
|
453
|
+
case "<":
|
|
454
|
+
case "<=":
|
|
455
|
+
case ">":
|
|
456
|
+
case ">=":
|
|
457
|
+
return compare(node.op, l, r);
|
|
458
|
+
case "+":
|
|
459
|
+
if (typeof l === "string" || typeof r === "string")
|
|
460
|
+
return stringify(l) + stringify(r);
|
|
461
|
+
if (typeof l === "number" && typeof r === "number")
|
|
462
|
+
return guardFinite(l + r);
|
|
463
|
+
throw new ExpressionError(`"+" needs Numbers, or at least one String`);
|
|
464
|
+
case "-":
|
|
465
|
+
case "*":
|
|
466
|
+
case "/": {
|
|
467
|
+
if (typeof l !== "number" || typeof r !== "number") {
|
|
468
|
+
throw new ExpressionError(`"${node.op}" needs Number operands`);
|
|
469
|
+
}
|
|
470
|
+
const out = node.op === "-" ? l - r : node.op === "*" ? l * r : l / r;
|
|
471
|
+
return guardFinite(out);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function evaluate(node, get) {
|
|
478
|
+
try {
|
|
479
|
+
return { ok: true, value: evalNode(node, get) };
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// ─── Editor helpers ───────────────────────────────────────────────────────────
|
|
486
|
+
function valueMatchesType(value, type) {
|
|
487
|
+
return ((type === "BOOLEAN" && typeof value === "boolean") ||
|
|
488
|
+
(type === "NUMBER" && typeof value === "number") ||
|
|
489
|
+
(type === "STRING" && typeof value === "string"));
|
|
490
|
+
}
|
|
491
|
+
/** Serialize a literal value into expression syntax (the editor's literal mode). */
|
|
492
|
+
function formatLiteral(value) {
|
|
493
|
+
if (typeof value === "string") {
|
|
494
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
495
|
+
}
|
|
496
|
+
return String(value);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Rewrite every reference to oldName in an expression's source, preserving all
|
|
500
|
+
* other formatting (token-span splicing). Returns the source unchanged if it
|
|
501
|
+
* does not tokenize. Powers variable rename in the editor.
|
|
502
|
+
*/
|
|
503
|
+
function renameIdentifier(src, oldName, newName) {
|
|
504
|
+
let tokens;
|
|
505
|
+
try {
|
|
506
|
+
tokens = tokenize(src);
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
return src;
|
|
510
|
+
}
|
|
511
|
+
let out = "";
|
|
512
|
+
let cursor = 0;
|
|
513
|
+
for (const t of tokens) {
|
|
514
|
+
if (t.type === "ident" && t.name === oldName) {
|
|
515
|
+
out += src.slice(cursor, t.start) + newName;
|
|
516
|
+
cursor = t.end;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return out + src.slice(cursor);
|
|
520
|
+
}
|
|
@@ -1,12 +1,39 @@
|
|
|
1
|
-
import { StudioAnimation, StudioSceneFunction } from "../types";
|
|
1
|
+
import { StudioAnimation, StudioApiRequestExecutor, StudioSceneFunction } from "../types";
|
|
2
|
+
import { StudioSoundManager } from "./soundManager";
|
|
3
|
+
import { StudioVariableStore } from "./variableStore";
|
|
4
|
+
import { StudioVisibilityStore } from "./visibilityStore";
|
|
2
5
|
type SceneNavigator = any;
|
|
6
|
+
export declare class SequenceScheduler {
|
|
7
|
+
private timers;
|
|
8
|
+
private appStateSub;
|
|
9
|
+
private backgrounded;
|
|
10
|
+
private activeSequences;
|
|
11
|
+
private generationCounter;
|
|
12
|
+
get generation(): number;
|
|
13
|
+
constructor();
|
|
14
|
+
beginSequence(id: string): boolean;
|
|
15
|
+
endSequence(id: string): void;
|
|
16
|
+
schedule(callback: () => void, ms: number): void;
|
|
17
|
+
private arm;
|
|
18
|
+
private pauseAll;
|
|
19
|
+
private resumeAll;
|
|
20
|
+
cancelAll(): void;
|
|
21
|
+
dispose(): void;
|
|
22
|
+
}
|
|
3
23
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
24
|
+
* Runtime context threaded through executeFunctionWithRelations: the Sequence
|
|
25
|
+
* scheduler plus the per-session variable store and API-request transport
|
|
26
|
+
* (optional so dispatch sites without them keep working).
|
|
6
27
|
*/
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
28
|
+
export type SequenceRuntimeContext = {
|
|
29
|
+
scheduler: SequenceScheduler;
|
|
30
|
+
variableStore?: StudioVariableStore;
|
|
31
|
+
apiRequestExecutor?: StudioApiRequestExecutor;
|
|
32
|
+
visibilityStore?: StudioVisibilityStore;
|
|
33
|
+
soundManager?: StudioSoundManager;
|
|
34
|
+
getAssetPosition?: (assetId: string) => [number, number, number] | undefined;
|
|
35
|
+
};
|
|
36
|
+
/** Used by onClick, onCollision, and on_load_function triggers. */
|
|
37
|
+
export declare function executeFunctionWithRelations(fn: StudioSceneFunction, sceneNavigator: SceneNavigator | undefined, animations: StudioAnimation[], onAnimationTrigger?: (targetAssetId: string, animationKey: string) => void, depth?: number, onSceneChange?: (sceneId: string, sceneName: string) => void, runtimeCtx?: SequenceRuntimeContext): void;
|
|
38
|
+
export declare function executeOnLoadFunction(functionId: string, functions: StudioSceneFunction[], sceneNavigator: SceneNavigator | undefined, animations: StudioAnimation[], onAnimationTrigger?: (targetAssetId: string, animationKey: string) => void, onSceneChange?: (sceneId: string, sceneName: string) => void, runtimeCtx?: SequenceRuntimeContext): void;
|
|
12
39
|
export {};
|