@yuneta/gobj-ui 7.23.34 β 7.23.36
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/README.md +92 -10
- package/dist/gobj-ui.cjs.js +3299 -656
- package/dist/gobj-ui.es.js +3298 -657
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/c_yui_fsm_graph.css +128 -0
- package/src/c_yui_fsm_graph.js +967 -0
- package/src/c_yui_gclass.css +508 -0
- package/src/c_yui_gclass.js +1522 -0
- package/src/c_yui_gobj_tree_js.js +11 -27
- package/src/dev_machine_trace.test.js +25 -0
- package/src/gclass_view_model.js +421 -0
- package/src/gclass_view_model.test.js +296 -0
- package/src/yui_dev.js +12 -2
- package/src/yui_gclass_view.js +42 -15
|
@@ -743,10 +743,10 @@ function build_popover(gobj, $container)
|
|
|
743
743
|
|
|
744
744
|
/* What this gobj IS: its gclass, opened in the gclass viewer.
|
|
745
745
|
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
* logged once, at build time. */
|
|
746
|
+
* The viewer registers itself, so this is offered everywhere --
|
|
747
|
+
* but it is still asked, because a registration that failed
|
|
748
|
+
* must not leave a button that opens nothing, and the reason is
|
|
749
|
+
* then logged once, at build time. */
|
|
750
750
|
if(gclass_view_available()) {
|
|
751
751
|
let $gclass = document.createElement("button");
|
|
752
752
|
$gclass.type = "button";
|
|
@@ -772,8 +772,8 @@ function build_popover(gobj, $container)
|
|
|
772
772
|
priv.$popover_gclass = $gclass;
|
|
773
773
|
} else {
|
|
774
774
|
log_warning(
|
|
775
|
-
`${gobj_short_name(gobj)}: no
|
|
776
|
-
`the
|
|
775
|
+
`${gobj_short_name(gobj)}: no gclass viewer registered, ` +
|
|
776
|
+
`the control is not offered`
|
|
777
777
|
);
|
|
778
778
|
}
|
|
779
779
|
|
|
@@ -2603,6 +2603,11 @@ function ac_open_gclass(gobj, event, kw, src)
|
|
|
2603
2603
|
|
|
2604
2604
|
priv.gclass_view = open_gclass_view(gobj, node_data.gclass, {
|
|
2605
2605
|
title_prefix: gobj_yuno_name() || "",
|
|
2606
|
+
/* Which state THIS gobj is in. The description describes the
|
|
2607
|
+
* CLASS and cannot carry it, and the tree is the only party
|
|
2608
|
+
* that knows -- so the machine lights the column the reader
|
|
2609
|
+
* is actually standing in. */
|
|
2610
|
+
current_state: node_data.state || "",
|
|
2606
2611
|
on_close: () => {
|
|
2607
2612
|
if(gobj_is_destroying(gobj)) {
|
|
2608
2613
|
return;
|
|
@@ -2627,25 +2632,6 @@ function ac_gclass_closed(gobj, event, kw, src)
|
|
|
2627
2632
|
return 0;
|
|
2628
2633
|
}
|
|
2629
2634
|
|
|
2630
|
-
/************************************************************
|
|
2631
|
-
* The gclass viewer asked for a subtree.
|
|
2632
|
-
*
|
|
2633
|
-
* It cannot: the gclass description is a COMPLETE document,
|
|
2634
|
-
* it carries no `__collapsed__` stub, so nothing in it is
|
|
2635
|
-
* fetchable. The event is declared because the viewer is
|
|
2636
|
-
* hosted as a child and subscribes its host to everything it
|
|
2637
|
-
* publishes -- and it is logged, because arriving here means
|
|
2638
|
-
* the document was not the one we built.
|
|
2639
|
-
************************************************************/
|
|
2640
|
-
function ac_gclass_expand_path(gobj, event, kw, src)
|
|
2641
|
-
{
|
|
2642
|
-
log_error(
|
|
2643
|
-
`${gobj_short_name(gobj)}: EV_EXPAND_PATH on a complete document: ` +
|
|
2644
|
-
`${kw && kw.path}`
|
|
2645
|
-
);
|
|
2646
|
-
return -1;
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
2635
|
/************************************************************
|
|
2650
2636
|
* The app switched language.
|
|
2651
2637
|
*
|
|
@@ -2716,7 +2702,6 @@ function create_gclass(gclass_name)
|
|
|
2716
2702
|
["EV_HIDE", ac_hide, null],
|
|
2717
2703
|
["EV_OPEN_GCLASS", ac_open_gclass, null],
|
|
2718
2704
|
["EV_GCLASS_CLOSED", ac_gclass_closed, null],
|
|
2719
|
-
["EV_EXPAND_PATH", ac_gclass_expand_path, null],
|
|
2720
2705
|
["EV_LANGUAGE_CHANGED", ac_language_changed, null],
|
|
2721
2706
|
]]
|
|
2722
2707
|
];
|
|
@@ -2743,7 +2728,6 @@ function create_gclass(gclass_name)
|
|
|
2743
2728
|
["EV_HIDE", 0],
|
|
2744
2729
|
["EV_OPEN_GCLASS", 0],
|
|
2745
2730
|
["EV_GCLASS_CLOSED", 0],
|
|
2746
|
-
["EV_EXPAND_PATH", 0],
|
|
2747
2731
|
["EV_LANGUAGE_CHANGED", 0],
|
|
2748
2732
|
];
|
|
2749
2733
|
|
|
@@ -20,6 +20,31 @@ const STATE_CHANGE =
|
|
|
20
20
|
const NOT_DEFINED =
|
|
21
21
|
"π mach(C_X^x), st: ST_IDLE, ev: EV_TIMEOUT, ππERROR Event NOT DEFINED in stateππ, from(C_TIMER^t)";
|
|
22
22
|
|
|
23
|
+
/* The other four shapes the SIMPLER format emits (gobj-js 7.13.7 gave them
|
|
24
|
+
* their format-1 branch; before that they leaked `mach(β¦)` into it). */
|
|
25
|
+
const SIMPLE_PUBLISH = "ππ EV_STATE_CHANGED C_PROT_MQTT^input-2 ST_WAIT_FRAME_HEADER";
|
|
26
|
+
const SIMPLE_FORWARD = "ππ EV_TX_READY (EV_TX_READY) C_PROT_TCP4H^output-0";
|
|
27
|
+
const SIMPLE_INJECT = "π EV_ON_MESSAGE C_GATE^gate ST_IDLE";
|
|
28
|
+
const SIMPLE_NESTED = " π EV_ON_MESSAGE C_IOGATE^output-iogate-0 ST_IDLE from C_CHANNEL^output-0";
|
|
29
|
+
|
|
30
|
+
describe("the simpler format's other shapes", () => {
|
|
31
|
+
it("reads the event out of a publish, a forward and an injection", () => {
|
|
32
|
+
expect(machine_event_of(SIMPLE_PUBLISH)).toBe("EV_STATE_CHANGED");
|
|
33
|
+
expect(machine_event_of(SIMPLE_FORWARD)).toBe("EV_TX_READY");
|
|
34
|
+
expect(machine_event_of(SIMPLE_INJECT)).toBe("EV_ON_MESSAGE");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("the leading INDENTATION does not hide the event", () => {
|
|
38
|
+
expect(machine_event_of(SIMPLE_NESTED)).toBe("EV_ON_MESSAGE");
|
|
39
|
+
expect(log_is_periodic("debug", SIMPLE_NESTED)).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("a periodic one is still recognised in every shape", () => {
|
|
43
|
+
expect(log_is_periodic("debug", "ππ EV_TIMEOUT C_TIMER^t ST_IDLE")).toBe(true);
|
|
44
|
+
expect(log_is_periodic("debug", " π EV_TIMEOUT_PERIODIC C_YUNO^y ST_IDLE")).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
23
48
|
describe("machine_event_of", () => {
|
|
24
49
|
it("reads the event out of the verbose format, both lines of it", () => {
|
|
25
50
|
expect(machine_event_of(VERBOSE_IN)).toBe("EV_TIMEOUT");
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* gclass_view_model.js
|
|
3
|
+
*
|
|
4
|
+
* The description of a gclass, turned into what a viewer
|
|
5
|
+
* draws: zones, a matrix, and the states nothing declares a
|
|
6
|
+
* way into.
|
|
7
|
+
*
|
|
8
|
+
* Pure data, no DOM -- so the shape of the view is testable
|
|
9
|
+
* without a browser, and the gclass that draws it has nothing
|
|
10
|
+
* to decide.
|
|
11
|
+
*
|
|
12
|
+
* IT READS TWO DIALECTS. The C kernel (`gclass2json()` in
|
|
13
|
+
* `kernel/c/gobj-c/src/gobj.c`) and the browser registry
|
|
14
|
+
* (`gclass_describe.js`) answer the same DOCUMENT in
|
|
15
|
+
* different words, and a viewer that only knew one of them
|
|
16
|
+
* would draw half a page for the other side:
|
|
17
|
+
*
|
|
18
|
+
* key C kernel browser
|
|
19
|
+
* ------------------------------------------------------
|
|
20
|
+
* flag "SDF_RD|SDF_PERSIST" ["SDF_RD", ...]
|
|
21
|
+
* type "string" "DTP_STRING"
|
|
22
|
+
* command "command" "id"
|
|
23
|
+
* parameter "parameter" "id"
|
|
24
|
+
* traces "info_gclass_trace" "trace_levels"
|
|
25
|
+
* action "action" (a marker) the function's name
|
|
26
|
+
*
|
|
27
|
+
* The last one is not a rename: the C side cannot name an
|
|
28
|
+
* action at all -- `states2json()` writes the literal string
|
|
29
|
+
* "action" for every one of them. So a cell coming from a
|
|
30
|
+
* backend gclass says THAT there is an action and never which,
|
|
31
|
+
* and the model reports the difference instead of printing a
|
|
32
|
+
* name nobody wrote.
|
|
33
|
+
*
|
|
34
|
+
* Copyright (c) 2026, ArtGins.
|
|
35
|
+
* All Rights Reserved.
|
|
36
|
+
***********************************************************************/
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* What the C side writes where a function name would go. It is not a
|
|
40
|
+
* name -- every action in every backend gclass carries this one.
|
|
41
|
+
*/
|
|
42
|
+
const C_ANONYMOUS_ACTION = "action";
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/************************************************************
|
|
46
|
+
* Flags, from either dialect, as a list of names.
|
|
47
|
+
*
|
|
48
|
+
* The C side joins them with '|' (`bits2gbuffer`), the
|
|
49
|
+
* browser side keeps the array `bits2names()` built.
|
|
50
|
+
************************************************************/
|
|
51
|
+
function normalize_flags(flag)
|
|
52
|
+
{
|
|
53
|
+
if(Array.isArray(flag)) {
|
|
54
|
+
return flag.filter((f) => typeof f === "string" && f.length > 0);
|
|
55
|
+
}
|
|
56
|
+
if(typeof flag === "string" && flag.length > 0) {
|
|
57
|
+
return flag.split("|").map((f) => f.trim()).filter((f) => f.length > 0);
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/************************************************************
|
|
63
|
+
* A data type, short and lower case, from either dialect.
|
|
64
|
+
*
|
|
65
|
+
* An unknown type is reported as what it is: a viewer that
|
|
66
|
+
* silently drew "string" for something nobody registered
|
|
67
|
+
* would be lying about the schema.
|
|
68
|
+
************************************************************/
|
|
69
|
+
function normalize_type(type)
|
|
70
|
+
{
|
|
71
|
+
if(typeof type === "string" && type.length > 0) {
|
|
72
|
+
if(type.startsWith("DTP_")) {
|
|
73
|
+
return type.slice(4).toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
return type.toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
if(type === null || type === undefined) {
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
return String(type);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/************************************************************
|
|
84
|
+
* A default value as one short line.
|
|
85
|
+
*
|
|
86
|
+
* The C side stringifies everything, the browser side keeps
|
|
87
|
+
* the live value -- including `null` for a pointer, which
|
|
88
|
+
* prints as an empty cell unless it is spelled out.
|
|
89
|
+
************************************************************/
|
|
90
|
+
function normalize_default(value)
|
|
91
|
+
{
|
|
92
|
+
if(value === undefined) {
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
if(value === null) {
|
|
96
|
+
return "null";
|
|
97
|
+
}
|
|
98
|
+
if(typeof value === "string") {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
if(typeof value === "number" || typeof value === "boolean") {
|
|
102
|
+
return String(value);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return JSON.stringify(value);
|
|
106
|
+
} catch(e) {
|
|
107
|
+
return String(value);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/************************************************************
|
|
112
|
+
* One attr / parameter row, from either dialect.
|
|
113
|
+
************************************************************/
|
|
114
|
+
function normalize_field(row)
|
|
115
|
+
{
|
|
116
|
+
if(!row || typeof row !== "object") {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let id = row.id || row.parameter || row.name || "";
|
|
121
|
+
if(!id) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
id: id,
|
|
127
|
+
type: normalize_type(row.type),
|
|
128
|
+
flags: normalize_flags(row.flag),
|
|
129
|
+
default_value: normalize_default(row.default_value),
|
|
130
|
+
description: row.description || "",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/************************************************************
|
|
135
|
+
* The attrs zone.
|
|
136
|
+
************************************************************/
|
|
137
|
+
function normalize_attrs(attrs)
|
|
138
|
+
{
|
|
139
|
+
if(!Array.isArray(attrs)) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
let rows = [];
|
|
143
|
+
for(let it of attrs) {
|
|
144
|
+
let row = normalize_field(it);
|
|
145
|
+
if(row) {
|
|
146
|
+
rows.push(row);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return rows;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/************************************************************
|
|
153
|
+
* The commands zone.
|
|
154
|
+
*
|
|
155
|
+
* A command with no name is not a command: the C tables use
|
|
156
|
+
* `SDATACM(DTP_SCHEMA, "", ...)` rows as SECTION HEADINGS,
|
|
157
|
+
* carrying the heading in the description (c_iogate, c_agent
|
|
158
|
+
* and every large gclass do it). Dropping them would glue
|
|
159
|
+
* four unrelated groups of commands into one list; drawing
|
|
160
|
+
* them as commands would offer the reader a nameless one.
|
|
161
|
+
************************************************************/
|
|
162
|
+
function normalize_commands(commands)
|
|
163
|
+
{
|
|
164
|
+
if(!Array.isArray(commands)) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let rows = [];
|
|
169
|
+
for(let it of commands) {
|
|
170
|
+
if(!it || typeof it !== "object") {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let id = it.command || it.id || it.name || "";
|
|
175
|
+
if(!id) {
|
|
176
|
+
let label = String(it.description || "")
|
|
177
|
+
.split("\n")
|
|
178
|
+
.map((s) => s.trim())
|
|
179
|
+
.filter((s) => s.length > 0 && !/^[-=_]+$/.test(s))[0] || "";
|
|
180
|
+
if(label) {
|
|
181
|
+
rows.push({kind: "section", label: label});
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let parameters = [];
|
|
187
|
+
for(let p of (Array.isArray(it.parameters)? it.parameters:
|
|
188
|
+
(Array.isArray(it.schema)? it.schema: []))) {
|
|
189
|
+
let param = normalize_field(p);
|
|
190
|
+
if(param) {
|
|
191
|
+
parameters.push(param);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
rows.push({
|
|
196
|
+
kind: "command",
|
|
197
|
+
id: id,
|
|
198
|
+
alias: Array.isArray(it.alias)? it.alias.slice(): [],
|
|
199
|
+
flags: normalize_flags(it.flag),
|
|
200
|
+
description: it.description || "",
|
|
201
|
+
usage: it.usage || "",
|
|
202
|
+
parameters: parameters,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return rows;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/************************************************************
|
|
210
|
+
* The trace levels, from either dialect.
|
|
211
|
+
*
|
|
212
|
+
* C answers a dict {name: description} (`gobj_trace_level_list`),
|
|
213
|
+
* the browser hands over the `s_user_trace_level` array.
|
|
214
|
+
************************************************************/
|
|
215
|
+
function normalize_trace_levels(description)
|
|
216
|
+
{
|
|
217
|
+
let src = description.trace_levels;
|
|
218
|
+
if(src === undefined || src === null) {
|
|
219
|
+
src = description.info_gclass_trace;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let rows = [];
|
|
223
|
+
if(Array.isArray(src)) {
|
|
224
|
+
for(let it of src) {
|
|
225
|
+
if(!it) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if(typeof it === "string") {
|
|
229
|
+
rows.push({id: it, description: ""});
|
|
230
|
+
} else if(it.name || it.id) {
|
|
231
|
+
rows.push({id: it.name || it.id, description: it.description || ""});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} else if(src && typeof src === "object") {
|
|
235
|
+
for(let key of Object.keys(src)) {
|
|
236
|
+
rows.push({id: key, description: String(src[key] || "")});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return rows;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/************************************************************
|
|
243
|
+
* A list of method names, defensively.
|
|
244
|
+
************************************************************/
|
|
245
|
+
function normalize_methods(methods)
|
|
246
|
+
{
|
|
247
|
+
if(!Array.isArray(methods)) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
return methods.filter((m) => typeof m === "string" && m.length > 0);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/************************************************************
|
|
254
|
+
* One cell of the matrix, or null where the state declares
|
|
255
|
+
* nothing for that event.
|
|
256
|
+
*
|
|
257
|
+
* `has_action` and `action` are separate on purpose: a
|
|
258
|
+
* backend gclass has actions it cannot name.
|
|
259
|
+
************************************************************/
|
|
260
|
+
function normalize_cell(entry)
|
|
261
|
+
{
|
|
262
|
+
let action = (typeof entry.action === "string")? entry.action: "";
|
|
263
|
+
let named = (action.length > 0 && action !== C_ANONYMOUS_ACTION);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
action: named? action: "",
|
|
267
|
+
has_action: action.length > 0,
|
|
268
|
+
next_state: entry.next_state || "",
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/************************************************************
|
|
273
|
+
* The machine: a matrix, the events nobody handles, and the
|
|
274
|
+
* states nothing declares a way into.
|
|
275
|
+
*
|
|
276
|
+
* ROWS ARE EVENTS AND COLUMNS ARE STATES, not the other way
|
|
277
|
+
* round: a gclass almost always declares more events than
|
|
278
|
+
* states (12 against 3 in C_IEVENT_CLI, 86 commands and one
|
|
279
|
+
* state in c_agent), and a table grows better downwards than
|
|
280
|
+
* sideways.
|
|
281
|
+
************************************************************/
|
|
282
|
+
function build_fsm(fsm)
|
|
283
|
+
{
|
|
284
|
+
fsm = fsm || {};
|
|
285
|
+
|
|
286
|
+
let states_src = (fsm.states && typeof fsm.states === "object")? fsm.states: {};
|
|
287
|
+
let state_names = Object.keys(states_src);
|
|
288
|
+
|
|
289
|
+
/*
|
|
290
|
+
* Every event, in DECLARATION order -- which is the order the
|
|
291
|
+
* gclass author chose, and the only one that carries meaning.
|
|
292
|
+
* An event handled by a state but missing from the table is a
|
|
293
|
+
* defect in that gclass; it is appended rather than dropped, so
|
|
294
|
+
* the viewer shows it instead of hiding it.
|
|
295
|
+
*/
|
|
296
|
+
let order = [];
|
|
297
|
+
let flags_of = new Map();
|
|
298
|
+
for(let ev of (Array.isArray(fsm.events)? fsm.events: [])) {
|
|
299
|
+
if(!ev || !ev.event_name) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if(!flags_of.has(ev.event_name)) {
|
|
303
|
+
order.push(ev.event_name);
|
|
304
|
+
}
|
|
305
|
+
flags_of.set(ev.event_name, normalize_flags(ev.event_flag));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let cells_of = new Map(); // event -> Map<state, cell>
|
|
309
|
+
let incoming = new Set(); // states some transition names
|
|
310
|
+
let counts = new Map(); // state -> how many events it handles
|
|
311
|
+
|
|
312
|
+
for(let state of state_names) {
|
|
313
|
+
let actions = Array.isArray(states_src[state])? states_src[state]: [];
|
|
314
|
+
counts.set(state, actions.length);
|
|
315
|
+
|
|
316
|
+
for(let entry of actions) {
|
|
317
|
+
if(!entry || !entry.event_name) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if(!flags_of.has(entry.event_name)) {
|
|
321
|
+
order.push(entry.event_name);
|
|
322
|
+
flags_of.set(entry.event_name, []);
|
|
323
|
+
}
|
|
324
|
+
if(!cells_of.has(entry.event_name)) {
|
|
325
|
+
cells_of.set(entry.event_name, new Map());
|
|
326
|
+
}
|
|
327
|
+
let cell = normalize_cell(entry);
|
|
328
|
+
cells_of.get(entry.event_name).set(state, cell);
|
|
329
|
+
if(cell.next_state) {
|
|
330
|
+
incoming.add(cell.next_state);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let rows = [];
|
|
336
|
+
let published = [];
|
|
337
|
+
for(let event of order) {
|
|
338
|
+
let by_state = cells_of.get(event);
|
|
339
|
+
if(!by_state || by_state.size === 0) {
|
|
340
|
+
/*
|
|
341
|
+
* Declared and handled nowhere: an OUTPUT event. It has no
|
|
342
|
+
* place in a matrix -- it is not handled, it is published --
|
|
343
|
+
* and leaving it in would draw one empty row per output
|
|
344
|
+
* event across every column.
|
|
345
|
+
*/
|
|
346
|
+
published.push({event: event, flags: flags_of.get(event) || []});
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
rows.push({
|
|
350
|
+
event: event,
|
|
351
|
+
flags: flags_of.get(event) || [],
|
|
352
|
+
cells: state_names.map((state) => by_state.get(state) || null),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/*
|
|
357
|
+
* A state nothing declares a way into. The first state is where
|
|
358
|
+
* the machine starts, so it needs none.
|
|
359
|
+
*
|
|
360
|
+
* This is NOT always a defect: an action is free to jump with
|
|
361
|
+
* `gobj_change_state()`, and several do (C_IEVENT_CLI reaches
|
|
362
|
+
* ST_SESSION that way). The description cannot see inside an
|
|
363
|
+
* action, so the viewer marks the state instead of drawing it
|
|
364
|
+
* as dead -- the alternative is a picture in which the working
|
|
365
|
+
* half of a gclass looks unreachable.
|
|
366
|
+
*/
|
|
367
|
+
let unreachable = state_names.filter(
|
|
368
|
+
(state, i) => i > 0 && !incoming.has(state)
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
entry_state: state_names.length? state_names[0]: "",
|
|
373
|
+
states: state_names.map((name) => ({
|
|
374
|
+
name: name,
|
|
375
|
+
count: counts.get(name) || 0,
|
|
376
|
+
})),
|
|
377
|
+
rows: rows,
|
|
378
|
+
published: published,
|
|
379
|
+
unreachable: unreachable,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/************************************************************
|
|
384
|
+
* The whole model.
|
|
385
|
+
*
|
|
386
|
+
* A missing key is an EMPTY zone, never a thrown viewer: the
|
|
387
|
+
* two dialects do not carry the same keys, and a description
|
|
388
|
+
* can also arrive from an old backend.
|
|
389
|
+
************************************************************/
|
|
390
|
+
function gclass_view_model(description)
|
|
391
|
+
{
|
|
392
|
+
if(!description || typeof description !== "object") {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let instances = description.instances;
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
id: description.id || "",
|
|
400
|
+
gcflag: normalize_flags(description.gcflag),
|
|
401
|
+
instances: (typeof instances === "number")? instances: null,
|
|
402
|
+
priv_size: (typeof description.priv_size === "number")?
|
|
403
|
+
description.priv_size: null,
|
|
404
|
+
attrs: normalize_attrs(description.attrs),
|
|
405
|
+
commands: normalize_commands(description.commands),
|
|
406
|
+
methods: normalize_methods(description.gclass_methods),
|
|
407
|
+
internal_methods: normalize_methods(description.internal_methods),
|
|
408
|
+
trace_levels: normalize_trace_levels(description),
|
|
409
|
+
fsm: build_fsm(description.FSM),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export {
|
|
414
|
+
normalize_flags,
|
|
415
|
+
normalize_type,
|
|
416
|
+
normalize_default,
|
|
417
|
+
normalize_commands,
|
|
418
|
+
normalize_trace_levels,
|
|
419
|
+
build_fsm,
|
|
420
|
+
gclass_view_model,
|
|
421
|
+
};
|