iobroker.statechart 0.2.0 → 0.3.0

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.
Binary file
@@ -10,5 +10,3 @@ body,
10
10
  width: 100%;
11
11
  margin: 0;
12
12
  }
13
-
14
- /* Add your styles here */
package/admin/tab_m.html CHANGED
@@ -17,7 +17,7 @@
17
17
  <link
18
18
  rel="stylesheet"
19
19
  type="text/css"
20
- href="style.css"
20
+ href="tab.css"
21
21
  />
22
22
  </head>
23
23
 
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var chart_analysis_exports = {};
30
+ __export(chart_analysis_exports, {
31
+ analyzeChart: () => analyzeChart
32
+ });
33
+ module.exports = __toCommonJS(chart_analysis_exports);
34
+ var acorn = __toESM(require("acorn"));
35
+ var walk = __toESM(require("acorn-walk"));
36
+ var import_chart_helpers = require("./chart-helpers");
37
+ var import_chart_model = require("./chart-model");
38
+ var import_chart_structure = require("./chart-structure");
39
+ function evaluateConstant(node) {
40
+ switch (node.type) {
41
+ case "Literal":
42
+ return { value: node.value };
43
+ case "Identifier":
44
+ if (node.name === "undefined") {
45
+ return { value: void 0 };
46
+ }
47
+ return node.name === "NaN" || node.name === "Infinity" ? { value: Number(node.name) } : void 0;
48
+ case "TemplateLiteral":
49
+ return node.expressions.length ? void 0 : { value: node.quasis[0]?.value.cooked };
50
+ case "UnaryExpression": {
51
+ const arg = evaluateConstant(node.argument);
52
+ if (!arg) {
53
+ return void 0;
54
+ }
55
+ const value = arg.value;
56
+ const results = {
57
+ "!": () => !value,
58
+ "-": () => -value,
59
+ "+": () => +value
60
+ };
61
+ return node.operator in results ? { value: results[node.operator]() } : void 0;
62
+ }
63
+ case "LogicalExpression": {
64
+ const left = evaluateConstant(node.left);
65
+ if (!left) {
66
+ return void 0;
67
+ }
68
+ if (node.operator === "&&" && !left.value) {
69
+ return left;
70
+ }
71
+ if (node.operator === "||" && left.value) {
72
+ return left;
73
+ }
74
+ if (node.operator === "??" && left.value != null) {
75
+ return left;
76
+ }
77
+ return evaluateConstant(node.right);
78
+ }
79
+ case "BinaryExpression": {
80
+ const left = node.left.type === "PrivateIdentifier" ? void 0 : evaluateConstant(node.left);
81
+ const right = left && evaluateConstant(node.right);
82
+ if (!left || !right) {
83
+ return void 0;
84
+ }
85
+ const [a, b] = [left.value, right.value];
86
+ const results = {
87
+ "==": () => a == b,
88
+ "!=": () => a != b,
89
+ "===": () => a === b,
90
+ "!==": () => a !== b,
91
+ "<": () => a < b,
92
+ "<=": () => a <= b,
93
+ ">": () => a > b,
94
+ ">=": () => a >= b,
95
+ "+": () => a + b,
96
+ "-": () => a - b,
97
+ "*": () => a * b,
98
+ "/": () => a / b,
99
+ "%": () => a % b
100
+ };
101
+ return node.operator in results ? { value: results[node.operator]() } : void 0;
102
+ }
103
+ case "ConditionalExpression": {
104
+ const test = evaluateConstant(node.test);
105
+ if (!test) {
106
+ return void 0;
107
+ }
108
+ return evaluateConstant(test.value ? node.consequent : node.alternate);
109
+ }
110
+ default:
111
+ return void 0;
112
+ }
113
+ }
114
+ function evaluateCondition(code) {
115
+ try {
116
+ const ast = acorn.parseExpressionAt(code, 0, { ecmaVersion: "latest" });
117
+ return code.substring(ast.end).trim() ? void 0 : evaluateConstant(ast);
118
+ } catch {
119
+ return void 0;
120
+ }
121
+ }
122
+ class Usage {
123
+ read = /* @__PURE__ */ new Set();
124
+ written = /* @__PURE__ */ new Set();
125
+ /**
126
+ * Adds the variables used by a condition or action
127
+ *
128
+ * @param code The code, ignored if it has syntax errors
129
+ * @param kind Condition (expression) or action (statements)
130
+ */
131
+ add(code, kind) {
132
+ if (!code?.trim()) {
133
+ return;
134
+ }
135
+ let ast;
136
+ try {
137
+ ast = kind === "statements" ? acorn.parse(code, { ecmaVersion: "latest", sourceType: "script" }) : acorn.parseExpressionAt(code, 0, { ecmaVersion: "latest" });
138
+ } catch {
139
+ return;
140
+ }
141
+ const declared = /* @__PURE__ */ new Set();
142
+ const references = [];
143
+ walk.fullAncestor(ast, (node, _state, ancestors, type) => {
144
+ if (node.type !== "Identifier") {
145
+ return;
146
+ }
147
+ const parent = ancestors[ancestors.length - 2];
148
+ if (type === "VariablePattern") {
149
+ if (parent?.type === "AssignmentExpression" && parent.left === node) {
150
+ references.push({ name: node.name, write: true });
151
+ } else {
152
+ declared.add(node.name);
153
+ }
154
+ } else {
155
+ references.push({ name: node.name, write: parent?.type === "UpdateExpression" });
156
+ }
157
+ });
158
+ walk.simple(ast, {
159
+ CallExpression: (node) => {
160
+ const arg = node.arguments[0];
161
+ if (node.callee.type === "Identifier" && import_chart_helpers.CHANGE_HELPERS.has(node.callee.name) && arg?.type === "Literal" && typeof arg.value === "string") {
162
+ this.read.add(arg.value);
163
+ }
164
+ }
165
+ });
166
+ for (const { name, write } of references) {
167
+ if (!declared.has(name)) {
168
+ (write ? this.written : this.read).add(name);
169
+ }
170
+ }
171
+ }
172
+ }
173
+ function analyzeChart(chart) {
174
+ const structure = new import_chart_structure.ChartStructure(chart);
175
+ const findings = [];
176
+ const add = (kind, id, severity, key, args = []) => {
177
+ findings.push({ kind, id, severity, key, args });
178
+ };
179
+ const stateName = (id) => structure.statesById.get(id)?.name ?? id;
180
+ const usage = new Usage();
181
+ const validBySource = /* @__PURE__ */ new Map();
182
+ for (const state of chart.states) {
183
+ for (const transition of structure.getTransitions(state.id)) {
184
+ let label;
185
+ try {
186
+ label = (0, import_chart_model.parseTransitionLabel)(transition.label);
187
+ } catch {
188
+ continue;
189
+ }
190
+ usage.add(label.condition, "expression");
191
+ usage.add(label.temporal?.condition, "expression");
192
+ usage.add(label.action, "statements");
193
+ for (const event of [label.event, label.temporal?.event]) {
194
+ if (event) {
195
+ usage.read.add(event);
196
+ }
197
+ }
198
+ if (!structure.statesById.has(transition.target) || structure.getCrossedParallel(transition) !== void 0) {
199
+ continue;
200
+ }
201
+ const condition = label.condition ? evaluateCondition(label.condition) : { value: true };
202
+ const temporalCondition = label.temporal?.condition && evaluateCondition(label.temporal.condition);
203
+ if (condition && !condition.value) {
204
+ add(
205
+ "transition",
206
+ transition.id,
207
+ "warning",
208
+ "The condition is never true, the transition is never taken"
209
+ );
210
+ continue;
211
+ }
212
+ if (temporalCondition && !temporalCondition.value) {
213
+ add(
214
+ "transition",
215
+ transition.id,
216
+ "warning",
217
+ "The condition is never true, the transition is never taken"
218
+ );
219
+ continue;
220
+ }
221
+ if (label.condition && condition) {
222
+ add("transition", transition.id, "info", "The condition is always true");
223
+ }
224
+ const valid = { transition, label, alwaysTrue: !!condition };
225
+ validBySource.set(state.id, [...validBySource.get(state.id) || [], valid]);
226
+ }
227
+ }
228
+ findUnreachable(structure, validBySource, add);
229
+ findShadowed(structure, validBySource, add, stateName);
230
+ for (const state of chart.states) {
231
+ if (structure.getChildren(state.id).length || state.during?.trim()) {
232
+ continue;
233
+ }
234
+ if (structure.getPath(state.id).every((id) => !validBySource.get(id)?.length)) {
235
+ add(
236
+ "state",
237
+ state.id,
238
+ "info",
239
+ "The chart stays in this state: neither it nor a state containing it has an outgoing transition"
240
+ );
241
+ }
242
+ }
243
+ for (const state of chart.states) {
244
+ usage.add(state.entry, "statements");
245
+ usage.add(state.during, "statements");
246
+ usage.add(state.exit, "statements");
247
+ }
248
+ for (const fn of chart.functions || []) {
249
+ usage.add(fn.code, "statements");
250
+ }
251
+ for (const symbol of chart.symbols) {
252
+ const { read, written } = { read: usage.read.has(symbol.name), written: usage.written.has(symbol.name) };
253
+ if (symbol.scope === "input" && !read && !written) {
254
+ add("symbol", symbol.name, "info", "The input is not used, it only wakes up the chart");
255
+ } else if (symbol.scope === "output" && !written) {
256
+ add("symbol", symbol.name, "warning", "The output is never written");
257
+ } else if (symbol.scope === "local" && !read) {
258
+ add(
259
+ "symbol",
260
+ symbol.name,
261
+ "info",
262
+ written ? "The variable is written but never read" : "The variable is not used"
263
+ );
264
+ }
265
+ }
266
+ return findings;
267
+ }
268
+ function findUnreachable(structure, validBySource, add) {
269
+ const reachable = /* @__PURE__ */ new Set();
270
+ const enterDefault = (id) => {
271
+ if (reachable.has(id)) {
272
+ return;
273
+ }
274
+ reachable.add(id);
275
+ const children = structure.getChildren(id);
276
+ const initial2 = structure.getInitialChild(id);
277
+ for (const child of structure.isParallel(id) ? children : children.filter((c) => c.id === initial2)) {
278
+ enterDefault(child.id);
279
+ }
280
+ };
281
+ const enter = (target) => {
282
+ for (const id of structure.getPath(target)) {
283
+ reachable.add(id);
284
+ const parent = structure.statesById.get(id)?.parent || null;
285
+ if (structure.isParallel(parent)) {
286
+ for (const sibling of structure.getChildren(parent)) {
287
+ if (sibling.id !== id) {
288
+ enterDefault(sibling.id);
289
+ }
290
+ }
291
+ }
292
+ }
293
+ reachable.delete(target);
294
+ enterDefault(target);
295
+ };
296
+ const topLevel = structure.getChildren(null);
297
+ const initial = structure.getInitialChild(null);
298
+ for (const state of structure.isParallel(null) ? topLevel : topLevel.filter((s) => s.id === initial)) {
299
+ enterDefault(state.id);
300
+ }
301
+ for (let changed = true; changed; ) {
302
+ changed = false;
303
+ for (const [source, transitions] of validBySource) {
304
+ if (!reachable.has(source)) {
305
+ continue;
306
+ }
307
+ for (const { transition } of transitions) {
308
+ const before = reachable.size;
309
+ enter(transition.target);
310
+ changed ||= reachable.size !== before;
311
+ }
312
+ }
313
+ }
314
+ for (const state of structure.chart.states) {
315
+ const parent = state.parent && structure.statesById.has(state.parent) ? state.parent : null;
316
+ if (!reachable.has(state.id) && (!parent || reachable.has(parent))) {
317
+ add("state", state.id, "warning", "The state never becomes active: no transition leads to it");
318
+ }
319
+ }
320
+ }
321
+ function findShadowed(structure, validBySource, add, stateName) {
322
+ for (const transitions of validBySource.values()) {
323
+ let always;
324
+ const byEvent = /* @__PURE__ */ new Map();
325
+ const byTrigger = /* @__PURE__ */ new Map();
326
+ const outer = transitions.filter((t) => !structure.getTransitionKind(t.transition).inner);
327
+ const inner = transitions.filter((t) => structure.getTransitionKind(t.transition).inner);
328
+ for (const { transition, label, alwaysTrue } of [...outer, ...inner]) {
329
+ const trigger = JSON.stringify([label.event, label.temporal, label.condition?.replace(/\s+/g, "")]);
330
+ const before = always ?? (label.event ? byEvent.get(label.event) : void 0);
331
+ if (before) {
332
+ add(
333
+ "transition",
334
+ transition.id,
335
+ "warning",
336
+ 'Never taken: the transition to "%s" before it is always valid',
337
+ [stateName(before.target)]
338
+ );
339
+ continue;
340
+ }
341
+ const same = byTrigger.get(trigger);
342
+ if (same) {
343
+ add(
344
+ "transition",
345
+ transition.id,
346
+ "warning",
347
+ 'Never taken: the transition to "%s" before it has the same event and condition',
348
+ [stateName(same.target)]
349
+ );
350
+ continue;
351
+ }
352
+ byTrigger.set(trigger, transition);
353
+ if (alwaysTrue && !label.temporal) {
354
+ if (label.event) {
355
+ byEvent.set(label.event, transition);
356
+ } else {
357
+ always = transition;
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+ // Annotate the CommonJS export names for ESM import in node:
364
+ 0 && (module.exports = {
365
+ analyzeChart
366
+ });
367
+ //# sourceMappingURL=chart-analysis.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/chart-analysis.ts"],
4
+ "sourcesContent": ["/*\n * Static analysis of a chart: finds logical problems without running it, e.g. states which can never become\n * active, transitions which are never taken or variables which are not used. Syntax errors and unknown names are\n * found by the validation, the analysis ignores the code and labels with errors.\n */\n\nimport * as acorn from \"acorn\";\nimport * as walk from \"acorn-walk\";\nimport { CHANGE_HELPERS } from \"./chart-helpers\";\nimport { parseTransitionLabel } from \"./chart-model\";\nimport type { Chart, ChartTransition, TransitionLabel } from \"./chart-model\";\nimport { ChartStructure } from \"./chart-structure\";\n\nexport type Severity = \"error\" | \"warning\" | \"info\";\n\n/** A problem of the chart as a whole, not at a position in a text */\nexport interface Finding {\n /** What the problem is about */\n kind: \"state\" | \"transition\" | \"symbol\";\n /** ID of the state or transition, name of the variable */\n id: string;\n /** How severe the problem is */\n severity: Severity;\n /** English message with %s placeholders, used to translate it */\n key: string;\n /** Values for the %s placeholders of the message */\n args: string[];\n}\n\n/** A transition the runtime executes, with its parsed label */\ninterface ValidTransition {\n transition: ChartTransition;\n label: TransitionLabel;\n /** The condition is always true or there is none */\n alwaysTrue: boolean;\n}\n\ntype Constant = { value: unknown } | undefined;\n\n/**\n * Evaluates an expression which only consists of literals and operators, e.g. `true` or `1 > 2`\n *\n * @param node The expression\n * @returns undefined if the value depends on variables or function calls\n */\nfunction evaluateConstant(node: acorn.AnyNode): Constant {\n switch (node.type) {\n case \"Literal\":\n return { value: node.value };\n case \"Identifier\":\n if (node.name === \"undefined\") {\n return { value: undefined };\n }\n return node.name === \"NaN\" || node.name === \"Infinity\" ? { value: Number(node.name) } : undefined;\n case \"TemplateLiteral\":\n return node.expressions.length ? undefined : { value: node.quasis[0]?.value.cooked };\n case \"UnaryExpression\": {\n const arg = evaluateConstant(node.argument);\n if (!arg) {\n return undefined;\n }\n const value = arg.value as any;\n const results: Record<string, () => unknown> = {\n \"!\": () => !value,\n \"-\": () => -value,\n \"+\": () => +value,\n };\n return node.operator in results ? { value: results[node.operator]() } : undefined;\n }\n case \"LogicalExpression\": {\n const left = evaluateConstant(node.left);\n if (!left) {\n return undefined;\n }\n // a constant left side may decide the result on its own\n if (node.operator === \"&&\" && !left.value) {\n return left;\n }\n if (node.operator === \"||\" && left.value) {\n return left;\n }\n if (node.operator === \"??\" && left.value != null) {\n return left;\n }\n return evaluateConstant(node.right);\n }\n case \"BinaryExpression\": {\n const left = node.left.type === \"PrivateIdentifier\" ? undefined : evaluateConstant(node.left);\n const right = left && evaluateConstant(node.right);\n if (!left || !right) {\n return undefined;\n }\n const [a, b] = [left.value as any, right.value as any];\n const results: Record<string, () => unknown> = {\n \"==\": () => a == b,\n \"!=\": () => a != b,\n \"===\": () => a === b,\n \"!==\": () => a !== b,\n \"<\": () => a < b,\n \"<=\": () => a <= b,\n \">\": () => a > b,\n \">=\": () => a >= b,\n \"+\": () => a + b,\n \"-\": () => a - b,\n \"*\": () => a * b,\n \"/\": () => a / b,\n \"%\": () => a % b,\n };\n return node.operator in results ? { value: results[node.operator]() } : undefined;\n }\n case \"ConditionalExpression\": {\n const test = evaluateConstant(node.test);\n if (!test) {\n return undefined;\n }\n return evaluateConstant(test.value ? node.consequent : node.alternate);\n }\n default:\n return undefined;\n }\n}\n\n/**\n * @param code A condition\n * @returns its value if it does not depend on anything, undefined otherwise or if it has syntax errors\n */\nfunction evaluateCondition(code: string): Constant {\n try {\n const ast = acorn.parseExpressionAt(code, 0, { ecmaVersion: \"latest\" });\n return code.substring(ast.end).trim() ? undefined : evaluateConstant(ast);\n } catch {\n return undefined;\n }\n}\n\n/** Which variables are read and written */\nclass Usage {\n readonly read = new Set<string>();\n readonly written = new Set<string>();\n\n /**\n * Adds the variables used by a condition or action\n *\n * @param code The code, ignored if it has syntax errors\n * @param kind Condition (expression) or action (statements)\n */\n add(code: string | undefined, kind: \"expression\" | \"statements\"): void {\n if (!code?.trim()) {\n return;\n }\n let ast;\n try {\n ast =\n kind === \"statements\"\n ? acorn.parse(code, { ecmaVersion: \"latest\", sourceType: \"script\" })\n : acorn.parseExpressionAt(code, 0, { ecmaVersion: \"latest\" });\n } catch {\n return;\n }\n // names declared in the code itself (let, const, function parameters, ...) hide the variables\n const declared = new Set<string>();\n const references: { name: string; write: boolean }[] = [];\n walk.fullAncestor(ast, (node, _state, ancestors, type) => {\n if (node.type !== \"Identifier\") {\n return;\n }\n const parent = ancestors[ancestors.length - 2] as acorn.AnyNode | undefined;\n if (type === \"VariablePattern\") {\n // acorn-walk uses \"VariablePattern\" for declarations and for assignment targets\n if (parent?.type === \"AssignmentExpression\" && parent.left === node) {\n references.push({ name: node.name, write: true });\n } else {\n declared.add(node.name);\n }\n } else {\n // count++ only changes the variable, its value is not used\n references.push({ name: node.name, write: parent?.type === \"UpdateExpression\" });\n }\n });\n walk.simple(ast, {\n CallExpression: node => {\n const arg = node.arguments[0];\n // the variable of hasChanged(\"name\")\n if (\n node.callee.type === \"Identifier\" &&\n CHANGE_HELPERS.has(node.callee.name) &&\n arg?.type === \"Literal\" &&\n typeof arg.value === \"string\"\n ) {\n this.read.add(arg.value);\n }\n },\n });\n for (const { name, write } of references) {\n if (!declared.has(name)) {\n (write ? this.written : this.read).add(name);\n }\n }\n }\n}\n\n/**\n * Finds the logical problems of a chart\n *\n * @param chart The chart\n */\nexport function analyzeChart(chart: Chart): Finding[] {\n const structure = new ChartStructure(chart);\n const findings: Finding[] = [];\n const add = (kind: Finding[\"kind\"], id: string, severity: Severity, key: string, args: string[] = []): void => {\n findings.push({ kind, id, severity, key, args });\n };\n const stateName = (id: string): string => structure.statesById.get(id)?.name ?? id;\n\n // the transitions the runtime executes, by source\n const usage = new Usage();\n const validBySource = new Map<string, ValidTransition[]>();\n for (const state of chart.states) {\n for (const transition of structure.getTransitions(state.id)) {\n let label;\n try {\n label = parseTransitionLabel(transition.label);\n } catch {\n continue;\n }\n usage.add(label.condition, \"expression\");\n usage.add(label.temporal?.condition, \"expression\");\n usage.add(label.action, \"statements\");\n for (const event of [label.event, label.temporal?.event]) {\n if (event) {\n usage.read.add(event);\n }\n }\n if (\n !structure.statesById.has(transition.target) ||\n structure.getCrossedParallel(transition) !== undefined\n ) {\n continue;\n }\n const condition = label.condition ? evaluateCondition(label.condition) : { value: true };\n const temporalCondition = label.temporal?.condition && evaluateCondition(label.temporal.condition);\n if (condition && !condition.value) {\n add(\n \"transition\",\n transition.id,\n \"warning\",\n \"The condition is never true, the transition is never taken\",\n );\n continue;\n }\n if (temporalCondition && !temporalCondition.value) {\n add(\n \"transition\",\n transition.id,\n \"warning\",\n \"The condition is never true, the transition is never taken\",\n );\n continue;\n }\n if (label.condition && condition) {\n add(\"transition\", transition.id, \"info\", \"The condition is always true\");\n }\n const valid = { transition, label, alwaysTrue: !!condition };\n validBySource.set(state.id, [...(validBySource.get(state.id) || []), valid]);\n }\n }\n\n findUnreachable(structure, validBySource, add);\n findShadowed(structure, validBySource, add, stateName);\n\n // leaf states which are never left\n for (const state of chart.states) {\n if (structure.getChildren(state.id).length || state.during?.trim()) {\n // a state with a during action may do its work as long as it is active\n continue;\n }\n if (structure.getPath(state.id).every(id => !validBySource.get(id)?.length)) {\n add(\n \"state\",\n state.id,\n \"info\",\n \"The chart stays in this state: neither it nor a state containing it has an outgoing transition\",\n );\n }\n }\n\n // variables which are not used\n for (const state of chart.states) {\n usage.add(state.entry, \"statements\");\n usage.add(state.during, \"statements\");\n usage.add(state.exit, \"statements\");\n }\n for (const fn of chart.functions || []) {\n usage.add(fn.code, \"statements\");\n }\n for (const symbol of chart.symbols) {\n const { read, written } = { read: usage.read.has(symbol.name), written: usage.written.has(symbol.name) };\n if (symbol.scope === \"input\" && !read && !written) {\n add(\"symbol\", symbol.name, \"info\", \"The input is not used, it only wakes up the chart\");\n } else if (symbol.scope === \"output\" && !written) {\n add(\"symbol\", symbol.name, \"warning\", \"The output is never written\");\n } else if (symbol.scope === \"local\" && !read) {\n add(\n \"symbol\",\n symbol.name,\n \"info\",\n written ? \"The variable is written but never read\" : \"The variable is not used\",\n );\n }\n }\n return findings;\n}\n\n/**\n * Finds the states which can never become active: the topmost ones, their sub states are not reported\n *\n * @param structure The structure of the chart\n * @param validBySource The transitions the runtime executes, by source\n * @param add Adds a finding\n */\nfunction findUnreachable(\n structure: ChartStructure,\n validBySource: Map<string, ValidTransition[]>,\n add: (kind: Finding[\"kind\"], id: string, severity: Severity, key: string) => void,\n): void {\n const reachable = new Set<string>();\n // enters a state like the runtime: with its initial sub state, or all of them if they are parallel\n const enterDefault = (id: string): void => {\n if (reachable.has(id)) {\n return;\n }\n reachable.add(id);\n const children = structure.getChildren(id);\n const initial = structure.getInitialChild(id);\n for (const child of structure.isParallel(id) ? children : children.filter(c => c.id === initial)) {\n enterDefault(child.id);\n }\n };\n // enters the target of a transition with its parents, and their parallel siblings\n const enter = (target: string): void => {\n for (const id of structure.getPath(target)) {\n reachable.add(id);\n const parent = structure.statesById.get(id)?.parent || null;\n if (structure.isParallel(parent)) {\n for (const sibling of structure.getChildren(parent)) {\n if (sibling.id !== id) {\n enterDefault(sibling.id);\n }\n }\n }\n }\n // the history is only set by sub states which were active before, so they are reachable anyway\n reachable.delete(target);\n enterDefault(target);\n };\n\n const topLevel = structure.getChildren(null);\n const initial = structure.getInitialChild(null);\n for (const state of structure.isParallel(null) ? topLevel : topLevel.filter(s => s.id === initial)) {\n enterDefault(state.id);\n }\n for (let changed = true; changed;) {\n changed = false;\n for (const [source, transitions] of validBySource) {\n if (!reachable.has(source)) {\n continue;\n }\n for (const { transition } of transitions) {\n const before = reachable.size;\n enter(transition.target);\n changed ||= reachable.size !== before;\n }\n }\n }\n\n for (const state of structure.chart.states) {\n const parent = state.parent && structure.statesById.has(state.parent) ? state.parent : null;\n if (!reachable.has(state.id) && (!parent || reachable.has(parent))) {\n add(\"state\", state.id, \"warning\", \"The state never becomes active: no transition leads to it\");\n }\n }\n}\n\n/**\n * Finds the transitions which are never taken because one before them is always valid: it has no condition (or one\n * which is always true) and no temporal logic, and no event or the same event. Like in the runtime, the transitions\n * leaving a state are checked before its inner transitions.\n *\n * @param structure The structure of the chart\n * @param validBySource The transitions the runtime executes, by source\n * @param add Adds a finding\n * @param stateName Returns the name of a state\n */\nfunction findShadowed(\n structure: ChartStructure,\n validBySource: Map<string, ValidTransition[]>,\n add: (kind: Finding[\"kind\"], id: string, severity: Severity, key: string, args: string[]) => void,\n stateName: (id: string) => string,\n): void {\n for (const transitions of validBySource.values()) {\n /** the transition which is always valid, for all wakeups */\n let always: ChartTransition | undefined;\n /** the transitions which are always valid, by event */\n const byEvent = new Map<string, ChartTransition>();\n /** the transitions by event, temporal logic and condition */\n const byTrigger = new Map<string, ChartTransition>();\n const outer = transitions.filter(t => !structure.getTransitionKind(t.transition).inner);\n const inner = transitions.filter(t => structure.getTransitionKind(t.transition).inner);\n for (const { transition, label, alwaysTrue } of [...outer, ...inner]) {\n const trigger = JSON.stringify([label.event, label.temporal, label.condition?.replace(/\\s+/g, \"\")]);\n const before = always ?? (label.event ? byEvent.get(label.event) : undefined);\n if (before) {\n add(\n \"transition\",\n transition.id,\n \"warning\",\n 'Never taken: the transition to \"%s\" before it is always valid',\n [stateName(before.target)],\n );\n continue;\n }\n const same = byTrigger.get(trigger);\n if (same) {\n add(\n \"transition\",\n transition.id,\n \"warning\",\n 'Never taken: the transition to \"%s\" before it has the same event and condition',\n [stateName(same.target)],\n );\n continue;\n }\n byTrigger.set(trigger, transition);\n if (alwaysTrue && !label.temporal) {\n if (label.event) {\n byEvent.set(label.event, transition);\n } else {\n always = transition;\n }\n }\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["initial"]
7
+ }
@@ -99,11 +99,11 @@ function generateChartCode(chart, chartId) {
99
99
  symbol.debounceMs ? `after ${symbol.debounceMs} ms without updates` : ""
100
100
  ].filter(Boolean).join(", ");
101
101
  lines.push(
102
- `let ${symbol.name} = getState(${JSON.stringify(symbol.stateId)}); // input, wakes up the chart ${trigger}`
102
+ `let ${symbol.name} = getState(${JSON.stringify(symbol.stateId)})?.val; // input, wakes up the chart ${trigger}`
103
103
  );
104
104
  } else {
105
105
  lines.push(
106
- `let ${symbol.name} = getState(${JSON.stringify(symbol.stateId)}); // output, see writeChangedOutputs()`
106
+ `let ${symbol.name} = getState(${JSON.stringify(symbol.stateId)})?.val; // output, see writeChangedOutputs()`
107
107
  );
108
108
  }
109
109
  }
@@ -126,7 +126,7 @@ function generateChartCode(chart, chartId) {
126
126
  `${INDENT}id: ${JSON.stringify(`chart_${chartId}`)},`
127
127
  );
128
128
  const transitionConfig = (transition, kind, label, level) => {
129
- const parts = [`target: ${JSON.stringify(`#${transition.target}`)}`];
129
+ const parts = [`target: ${JSON.stringify(`#${structure.getTargetId(transition)}`)}`];
130
130
  if (kind.reenter) {
131
131
  parts.push("reenter: true");
132
132
  }
@@ -176,17 +176,30 @@ function generateChartCode(chart, chartId) {
176
176
  }
177
177
  const children = structure.getChildren(state.id);
178
178
  if (children.length) {
179
- lines.push(`${i2}initial: ${JSON.stringify(structure.getInitialChild(state.id))},`, `${i2}states: {`);
179
+ lines.push(
180
+ structure.isParallel(state.id) ? `${i2}type: "parallel", // all sub states are active, entered in their execution order` : `${i2}initial: ${JSON.stringify(structure.getInitialChild(state.id))},`,
181
+ `${i2}states: {`
182
+ );
180
183
  for (const child of children) {
181
184
  buildState(child, level + 2);
182
185
  }
186
+ if (structure.hasHistory(state.id)) {
187
+ const id = `${state.id}${import_chart_structure.HISTORY_SUFFIX}`;
188
+ lines.push(
189
+ `${i3}// history junction: returns to the last active sub state, the first time to the initial one`,
190
+ `${i3}${JSON.stringify(import_chart_structure.HISTORY_SUFFIX)}: { id: ${JSON.stringify(id)}, type: "history" },`
191
+ );
192
+ }
183
193
  lines.push(`${i2}},`);
184
194
  }
185
195
  lines.push(`${i1}},`);
186
196
  };
187
197
  const topLevel = structure.getChildren(null);
188
198
  if (topLevel.length) {
189
- lines.push(`${INDENT}initial: ${JSON.stringify(structure.getInitialChild(null))},`, `${INDENT}states: {`);
199
+ lines.push(
200
+ structure.isParallel(null) ? `${INDENT}type: "parallel", // all top level states are active, entered in their execution order` : `${INDENT}initial: ${JSON.stringify(structure.getInitialChild(null))},`,
201
+ `${INDENT}states: {`
202
+ );
190
203
  for (const state of topLevel) {
191
204
  buildState(state, 2);
192
205
  }
@@ -206,6 +219,29 @@ function generateChartCode(chart, chartId) {
206
219
  ${INDENT.repeat(level + 1)}${prefix}process_${identifier(s.id)}(input);
207
220
  ${INDENT.repeat(level)}}`
208
221
  );
222
+ const processChildren = (parent, children) => {
223
+ if (!structure.isParallel(parent)) {
224
+ return [...callActive(children, 1, "return "), `${INDENT}return false;`];
225
+ }
226
+ const lines2 = [
227
+ `${INDENT}// all sub states are active (parallel), they are processed in their execution order.`,
228
+ `${INDENT}// A transition in one of them does not stop the others, unless it left this state.`,
229
+ ...parent ? [`${INDENT}const entries = entryCount(${JSON.stringify(parent)});`] : [],
230
+ `${INDENT}let taken = false;`
231
+ ];
232
+ children.forEach((child, i) => {
233
+ if (i && parent) {
234
+ lines2.push(
235
+ `${INDENT}if (!isActive(${JSON.stringify(parent)}) || entryCount(${JSON.stringify(parent)}) !== entries) {`,
236
+ `${INDENT}${INDENT}return true; // left (and maybe entered again) by the transition`,
237
+ `${INDENT}}`
238
+ );
239
+ }
240
+ lines2.push(`${INDENT}taken = process_${identifier(child.id)}(input) || taken;`);
241
+ });
242
+ lines2.push(`${INDENT}return taken;`);
243
+ return lines2;
244
+ };
209
245
  lines.push(
210
246
  "// ---------- Wakeup ----------",
211
247
  "",
@@ -235,18 +271,43 @@ ${INDENT.repeat(level)}}`
235
271
  " * @returns {boolean} true if a transition was taken",
236
272
  " */",
237
273
  "function step(input) {",
238
- ...callActive(topLevel, 1, "return "),
239
- `${INDENT}return false;`,
274
+ ...processChildren(null, topLevel),
240
275
  "}",
241
276
  ""
242
277
  );
278
+ const temporalCheck = (transition, temporal) => {
279
+ const id = JSON.stringify(transition.id);
280
+ const source = JSON.stringify(transition.source);
281
+ if (temporal.event !== void 0) {
282
+ const event = JSON.stringify(temporal.event);
283
+ const count = `inputCount(${source}, ${event})`;
284
+ const checks = {
285
+ after: `${count} >= ${temporal.count}`,
286
+ before: `${count} < ${temporal.count}`,
287
+ at: `${count} === ${temporal.count}`,
288
+ every: `${count} % ${temporal.count} === 0`
289
+ };
290
+ return `input === ${event} && ${checks[temporal.operator]}`;
291
+ }
292
+ if (temporal.condition !== void 0) {
293
+ return `trueFor(${id}) >= ${temporal.ms}`;
294
+ }
295
+ switch (temporal.operator) {
296
+ case "after":
297
+ return `timeInState(${source}) >= ${temporal.ms}`;
298
+ case "before":
299
+ return `timeInState(${source}) < ${temporal.ms}`;
300
+ default:
301
+ return `timerFired(${id})`;
302
+ }
303
+ };
243
304
  const check = (transition, label) => {
244
305
  const conditions = [];
245
306
  if (label.event) {
246
307
  conditions.push(`input === ${JSON.stringify(label.event)}`);
247
308
  }
248
- if (label.afterMs !== void 0) {
249
- conditions.push(`elapsed(${JSON.stringify(transition.source)}) >= ${label.afterMs}`);
309
+ if (label.temporal) {
310
+ conditions.push(temporalCheck(transition, label.temporal));
250
311
  }
251
312
  if (label.condition) {
252
313
  conditions.push(`(${label.condition})`);
@@ -292,9 +353,8 @@ ${indent(dedent(state.during), 2)}
292
353
  ${INDENT}}` : `${INDENT}// none`,
293
354
  `${INDENT}// 3. inner transitions (to own sub states, the state stays active)`,
294
355
  ...inner.length ? inner : [`${INDENT}// none`],
295
- `${INDENT}// 4. active sub state`,
296
- ...children.length ? callActive(children, 1, "return ") : [`${INDENT}// none`],
297
- `${INDENT}return false;`,
356
+ `${INDENT}// 4. ${structure.isParallel(state.id) && children.length ? "all sub states" : "active sub state"}`,
357
+ ...children.length ? processChildren(state.id, children) : [`${INDENT}// none`, `${INDENT}return false;`],
298
358
  "}",
299
359
  ""
300
360
  );
@@ -313,10 +373,17 @@ ${INDENT}}`
313
373
  "}",
314
374
  "",
315
375
  "// Provided by the adapter:",
316
- "// getState(id) current value of an ioBroker state",
317
376
  "// isActive(stateId) true if the state of the chart is active",
318
- "// elapsed(stateId) milliseconds since the state was entered; when it reaches the delay of an",
319
- "// after(...) of the state, a timer calls wakeup(null)",
377
+ "// entryCount(stateId) how often the state was entered",
378
+ "// timeInState(stateId) milliseconds since the state was entered; when it reaches the delay of an",
379
+ "// after(n, unit) or at(n, unit) of the state, a timer calls wakeup(null)",
380
+ '// timerFired(id) true in the wakeup(null) of the timer of at(n, unit), every(n, unit) or at("sunset")',
381
+ '// of the transition; at("sunset", offset) and at("22:30") have a timer for every day',
382
+ "// inputCount(stateId, input)",
383
+ "// the wakeups by the input since the state was entered, for e.g. after(3, button)",
384
+ "// trueFor(id) milliseconds the condition of after(n, unit, condition) of the transition has been true,",
385
+ "// checked at the start and at the end of every wakeup; a timer calls wakeup(null) when",
386
+ "// it reaches the delay",
320
387
  "// At the start of each wakeup, the adapter stores the values of the variables used in hasChanged(),",
321
388
  "// hasChangedFrom() and hasChangedTo(), these compare them with the values of the previous wakeup.",
322
389
  "//",