@eagleoutice/flowr 2.7.0 → 2.7.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.
Files changed (149) hide show
  1. package/README.md +20 -20
  2. package/abstract-interpretation/absint-visitor.d.ts +160 -0
  3. package/abstract-interpretation/absint-visitor.js +279 -0
  4. package/abstract-interpretation/data-frame/dataframe-domain.d.ts +2 -2
  5. package/abstract-interpretation/data-frame/dataframe-domain.js +23 -7
  6. package/abstract-interpretation/data-frame/mappers/access-mapper.d.ts +6 -6
  7. package/abstract-interpretation/data-frame/mappers/access-mapper.js +10 -14
  8. package/abstract-interpretation/data-frame/mappers/arguments.d.ts +10 -9
  9. package/abstract-interpretation/data-frame/mappers/arguments.js +7 -4
  10. package/abstract-interpretation/data-frame/mappers/function-mapper.d.ts +17 -17
  11. package/abstract-interpretation/data-frame/mappers/function-mapper.js +45 -50
  12. package/abstract-interpretation/data-frame/mappers/replacement-mapper.d.ts +7 -7
  13. package/abstract-interpretation/data-frame/mappers/replacement-mapper.js +25 -29
  14. package/abstract-interpretation/data-frame/semantics.js +2 -3
  15. package/abstract-interpretation/data-frame/shape-inference.d.ts +52 -28
  16. package/abstract-interpretation/data-frame/shape-inference.js +67 -90
  17. package/abstract-interpretation/domains/bounded-set-domain.d.ts +2 -2
  18. package/abstract-interpretation/domains/interval-domain.d.ts +2 -2
  19. package/abstract-interpretation/domains/set-range-domain.d.ts +10 -4
  20. package/abstract-interpretation/domains/set-range-domain.js +7 -1
  21. package/abstract-interpretation/domains/set-upper-bound-domain.d.ts +2 -2
  22. package/abstract-interpretation/domains/singleton-domain.d.ts +2 -2
  23. package/benchmark/slicer.js +13 -14
  24. package/cli/common/options.d.ts +431 -8
  25. package/cli/common/options.js +1 -1
  26. package/cli/common/scripts-info.d.ts +431 -7
  27. package/cli/flowr-main-options.d.ts +102 -2
  28. package/cli/flowr.d.ts +102 -2
  29. package/cli/repl/commands/repl-commands.d.ts +25 -0
  30. package/cli/repl/commands/repl-query.js +17 -5
  31. package/cli/wiki.d.ts +13 -0
  32. package/cli/wiki.js +7 -2
  33. package/config.d.ts +4 -4
  34. package/config.js +1 -1
  35. package/control-flow/basic-cfg-guided-visitor.js +7 -8
  36. package/control-flow/control-flow-graph.d.ts +1 -1
  37. package/control-flow/semantic-cfg-guided-visitor.d.ts +1 -1
  38. package/control-flow/semantic-cfg-guided-visitor.js +1 -1
  39. package/dataflow/eval/resolve/alias-tracking.js +1 -1
  40. package/dataflow/internal/linker.d.ts +2 -0
  41. package/dataflow/internal/linker.js +10 -12
  42. package/documentation/doc-capabilities.d.ts +1 -1
  43. package/documentation/doc-readme.d.ts +1 -1
  44. package/documentation/doc-util/doc-cfg.js +1 -1
  45. package/documentation/doc-util/doc-cli-option.d.ts +6 -6
  46. package/documentation/doc-util/doc-cli-option.js +3 -3
  47. package/documentation/doc-util/doc-dfg.d.ts +1 -1
  48. package/documentation/doc-util/doc-files.d.ts +3 -0
  49. package/documentation/doc-util/doc-files.js +4 -1
  50. package/documentation/doc-util/doc-normalized-ast.js +2 -2
  51. package/documentation/issue-linting-rule.d.ts +1 -1
  52. package/documentation/wiki-analyzer.d.ts +1 -1
  53. package/documentation/wiki-cfg.d.ts +1 -1
  54. package/documentation/wiki-core.d.ts +1 -1
  55. package/documentation/wiki-dataflow-graph.d.ts +1 -1
  56. package/documentation/wiki-dataflow-graph.js +6 -6
  57. package/documentation/wiki-engine.d.ts +1 -1
  58. package/documentation/wiki-engine.js +9 -10
  59. package/documentation/wiki-faq.d.ts +1 -1
  60. package/documentation/wiki-interface.d.ts +1 -1
  61. package/documentation/wiki-interface.js +12 -13
  62. package/documentation/wiki-linter.d.ts +1 -1
  63. package/documentation/wiki-linting-and-testing.d.ts +1 -1
  64. package/documentation/wiki-mk/doc-context.d.ts +54 -1
  65. package/documentation/wiki-mk/doc-context.js +17 -0
  66. package/documentation/wiki-mk/doc-maker.d.ts +5 -5
  67. package/documentation/wiki-mk/doc-maker.js +3 -1
  68. package/documentation/wiki-normalized-ast.d.ts +1 -1
  69. package/documentation/wiki-onboarding.d.ts +1 -1
  70. package/documentation/wiki-overview.d.ts +9 -0
  71. package/documentation/wiki-overview.js +248 -0
  72. package/documentation/wiki-query.d.ts +1 -1
  73. package/documentation/wiki-query.js +17 -1
  74. package/documentation/wiki-search.d.ts +1 -1
  75. package/documentation/wiki-setup.d.ts +9 -0
  76. package/documentation/wiki-setup.js +122 -0
  77. package/linter/rules/dataframe-access-validation.d.ts +1 -1
  78. package/linter/rules/dataframe-access-validation.js +8 -10
  79. package/linter/rules/unused-definition.js +1 -1
  80. package/package.json +1 -1
  81. package/project/context/flowr-analyzer-context.d.ts +4 -0
  82. package/project/context/flowr-analyzer-files-context.d.ts +9 -1
  83. package/project/context/flowr-analyzer-files-context.js +4 -0
  84. package/project/context/flowr-file.d.ts +2 -0
  85. package/project/context/flowr-file.js +2 -0
  86. package/project/plugins/file-plugins/{flowr-description-file.d.ts → files/flowr-description-file.d.ts} +1 -1
  87. package/project/plugins/file-plugins/files/flowr-description-file.js +75 -0
  88. package/project/plugins/file-plugins/files/flowr-news-file.d.ts +27 -0
  89. package/project/plugins/file-plugins/files/flowr-news-file.js +152 -0
  90. package/project/plugins/file-plugins/flowr-analyzer-description-file-plugin.d.ts +1 -1
  91. package/project/plugins/file-plugins/flowr-analyzer-description-file-plugin.js +1 -1
  92. package/project/plugins/file-plugins/flowr-analyzer-news-file-plugin.d.ts +23 -0
  93. package/project/plugins/file-plugins/flowr-analyzer-news-file-plugin.js +35 -0
  94. package/project/plugins/file-plugins/notebooks/flowr-analyzer-jupyter-file-plugin.d.ts +1 -1
  95. package/project/plugins/file-plugins/notebooks/flowr-analyzer-jupyter-file-plugin.js +1 -1
  96. package/project/plugins/file-plugins/notebooks/flowr-analyzer-qmd-file-plugin.d.ts +1 -1
  97. package/project/plugins/file-plugins/notebooks/flowr-analyzer-qmd-file-plugin.js +1 -1
  98. package/project/plugins/file-plugins/notebooks/flowr-analyzer-rmd-file-plugin.d.ts +1 -1
  99. package/project/plugins/file-plugins/notebooks/flowr-analyzer-rmd-file-plugin.js +1 -1
  100. package/project/plugins/flowr-analyzer-plugin-defaults.js +2 -0
  101. package/project/plugins/plugin-registry.d.ts +2 -1
  102. package/project/plugins/plugin-registry.js +2 -0
  103. package/project/plugins/project-discovery/flowr-analyzer-project-discovery-plugin.js +7 -1
  104. package/queries/catalog/df-shape-query/df-shape-query-executor.js +4 -2
  105. package/queries/catalog/files-query/files-query-executor.d.ts +6 -0
  106. package/queries/catalog/files-query/files-query-executor.js +49 -0
  107. package/queries/catalog/files-query/files-query-format.d.ts +36 -0
  108. package/queries/catalog/files-query/files-query-format.js +114 -0
  109. package/queries/catalog/linter-query/linter-query-format.js +1 -1
  110. package/queries/query.d.ts +10 -1
  111. package/queries/query.js +3 -1
  112. package/r-bridge/lang-4.x/ast/model/model.d.ts +1 -1
  113. package/r-bridge/lang-4.x/ast/model/processing/decorate.js +8 -8
  114. package/r-bridge/lang-4.x/ast/model/processing/role.d.ts +8 -8
  115. package/r-bridge/lang-4.x/ast/parser/main/internal/functions/normalize-parameter.js +0 -1
  116. package/r-bridge/lang-4.x/tree-sitter/tree-sitter-normalize.js +0 -1
  117. package/statistics/features/supported/data-access/data-access.js +1 -1
  118. package/util/containers.js +1 -1
  119. package/util/files.d.ts +0 -7
  120. package/util/files.js +0 -41
  121. package/util/mermaid/ast.d.ts +3 -2
  122. package/util/mermaid/ast.js +13 -7
  123. package/util/mermaid/cfg.d.ts +3 -2
  124. package/util/mermaid/cfg.js +26 -6
  125. package/util/mermaid/dfg.d.ts +1 -7
  126. package/util/mermaid/dfg.js +7 -3
  127. package/util/mermaid/info.d.ts +17 -0
  128. package/util/mermaid/info.js +5 -0
  129. package/util/prefix.d.ts +9 -5
  130. package/util/prefix.js +14 -6
  131. package/util/r-regex.d.ts +21 -0
  132. package/util/r-regex.js +25 -0
  133. package/util/text/args.js +12 -3
  134. package/util/version.js +1 -1
  135. package/abstract-interpretation/data-frame/absint-info.d.ts +0 -109
  136. package/abstract-interpretation/data-frame/absint-info.js +0 -31
  137. package/abstract-interpretation/data-frame/absint-visitor.d.ts +0 -57
  138. package/abstract-interpretation/data-frame/absint-visitor.js +0 -176
  139. package/abstract-interpretation/data-frame/mappers/assignment-mapper.d.ts +0 -21
  140. package/abstract-interpretation/data-frame/mappers/assignment-mapper.js +0 -34
  141. package/documentation/doc-util/doc-print.d.ts +0 -5
  142. package/documentation/doc-util/doc-print.js +0 -36
  143. package/project/plugins/file-plugins/flowr-description-file.js +0 -37
  144. package/project/plugins/file-plugins/notebooks/notebook.d.ts +0 -0
  145. package/project/plugins/file-plugins/notebooks/notebook.js +0 -2
  146. /package/project/plugins/file-plugins/{notebooks → files}/flowr-jupyter-file.d.ts +0 -0
  147. /package/project/plugins/file-plugins/{notebooks → files}/flowr-jupyter-file.js +0 -0
  148. /package/project/plugins/file-plugins/{notebooks → files}/flowr-rmarkdown-file.d.ts +0 -0
  149. /package/project/plugins/file-plugins/{notebooks → files}/flowr-rmarkdown-file.js +0 -0
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FilesQueryDefinition = void 0;
7
+ const ansi_1 = require("../../../util/text/ansi");
8
+ const joi_1 = __importDefault(require("joi"));
9
+ const files_query_executor_1 = require("./files-query-executor");
10
+ const flowr_file_1 = require("../../../project/context/flowr-file");
11
+ const json_1 = require("../../../util/json");
12
+ const retriever_1 = require("../../../r-bridge/retriever");
13
+ function summarizeObjectWithLimit(obj, limitChars = 500, limitLines = 10) {
14
+ const str = JSON.stringify(obj, json_1.jsonReplacer, 2);
15
+ if (str.split('\n').length > limitLines) {
16
+ const lines = str.split('\n').slice(0, limitLines);
17
+ return lines.join('\n') + '\n... (truncated)';
18
+ }
19
+ else if (str.length > limitChars) {
20
+ return str.slice(0, limitChars) + '... (truncated)';
21
+ }
22
+ else {
23
+ return str;
24
+ }
25
+ }
26
+ function rolesFromInput(rolesPart) {
27
+ return rolesPart
28
+ .reduce((acc, roleName) => {
29
+ roleName = roleName.trim();
30
+ // check if it is one of the values
31
+ if (Object.values(flowr_file_1.FileRole).includes(roleName)) {
32
+ acc.valid.push(roleName);
33
+ }
34
+ else {
35
+ acc.invalid.push(roleName);
36
+ }
37
+ return acc;
38
+ }, { valid: [], invalid: [] });
39
+ }
40
+ const rolePrefix = 'role:';
41
+ function filesQueryLineParser(output, line, _config) {
42
+ let roles = undefined;
43
+ let input = undefined;
44
+ if (line.length > 0 && line[0].startsWith(rolePrefix)) {
45
+ const rolesPart = line[0].slice(rolePrefix.length).split(',');
46
+ const parseResult = rolesFromInput(rolesPart);
47
+ if (parseResult.invalid.length > 0) {
48
+ output.stderr(`Invalid roles: ${parseResult.invalid.map(r => (0, ansi_1.bold)(r, output.formatter)).join(', ')}`
49
+ + `\nValid roles are: ${Object.values(flowr_file_1.FileRole).map(r => (0, ansi_1.bold)(r, output.formatter)).join(', ')}`);
50
+ }
51
+ roles = parseResult.valid;
52
+ input = line[1];
53
+ }
54
+ else if (line.length > 0) {
55
+ input = line[0];
56
+ }
57
+ return { query: [{ type: 'files', roles }], rCode: input };
58
+ }
59
+ function filesQueryCompleter(line, startingNewArg, _config) {
60
+ const rolesPrefixNotPresent = line.length == 0 || (line.length == 1 && line[0].length < rolePrefix.length);
61
+ const rolesNotFinished = line.length == 1 && line[0].startsWith(rolePrefix) && !startingNewArg;
62
+ const endOfRoles = line.length == 1 && startingNewArg || line.length == 2;
63
+ if (rolesPrefixNotPresent) {
64
+ return { completions: [`${rolePrefix}`] };
65
+ }
66
+ else if (endOfRoles) {
67
+ return { completions: [retriever_1.fileProtocol] };
68
+ }
69
+ else if (rolesNotFinished) {
70
+ const rolesWithoutPrefix = line[0].slice(rolePrefix.length);
71
+ const usedRoles = rolesWithoutPrefix.split(',').map(r => r.trim());
72
+ const allRoles = Object.values(flowr_file_1.FileRole);
73
+ const unusedRoles = allRoles.filter(r => !usedRoles.includes(r));
74
+ const lastRole = usedRoles[usedRoles.length - 1];
75
+ const lastRoleIsUnfinished = !allRoles.includes(lastRole);
76
+ if (lastRoleIsUnfinished) {
77
+ return { completions: unusedRoles, argumentPart: lastRole };
78
+ }
79
+ else if (unusedRoles.length > 0) {
80
+ return { completions: [','], argumentPart: '' };
81
+ }
82
+ else {
83
+ return { completions: [' '], argumentPart: '' };
84
+ }
85
+ }
86
+ return { completions: [] };
87
+ }
88
+ exports.FilesQueryDefinition = {
89
+ executor: files_query_executor_1.executeFileQuery,
90
+ asciiSummarizer: (formatter, _analyzer, queryResults, result) => {
91
+ const out = queryResults;
92
+ result.push(`Query: ${(0, ansi_1.bold)('files', formatter)} (${out['.meta'].timing.toFixed(0)}ms)`);
93
+ result.push(` ╰ Found ${out.files.length} file${out.files.length === 1 ? '' : 's'}`);
94
+ for (const f of out.files) {
95
+ result.push(` ╰ ${(0, ansi_1.bold)(f.path, formatter)}${f.role ? ` [role: ${f.role}]` : ''}:`);
96
+ const summary = summarizeObjectWithLimit(f.content);
97
+ for (const line of summary.split('\n')) {
98
+ result.push(` ${line}`);
99
+ }
100
+ }
101
+ return true;
102
+ },
103
+ completer: filesQueryCompleter,
104
+ fromLine: filesQueryLineParser,
105
+ schema: joi_1.default.object({
106
+ type: joi_1.default.string().valid('files').required().description('The type of the query.'),
107
+ roles: joi_1.default.array().optional().items(joi_1.default.string().valid(...Object.values(flowr_file_1.FileRole))).description('Optional roles of the files to query. If not provided, all roles are considered.'),
108
+ matchesPathRegex: joi_1.default.string().optional().description('An optional regular expression to match the file paths against.')
109
+ }).description('The file query finds files in the project based on their roles and path patterns.'),
110
+ flattenInvolvedNodes: (_) => {
111
+ return [];
112
+ }
113
+ };
114
+ //# sourceMappingURL=files-query-format.js.map
@@ -34,7 +34,7 @@ function linterQueryLineParser(output, line, _config) {
34
34
  const rulesPart = line[0].slice(rulesPrefix.length).split(',');
35
35
  const parseResult = rulesFromInput(output, rulesPart);
36
36
  if (parseResult.invalid.length > 0) {
37
- output.stdout(`Invalid linting rule name(s): ${parseResult.invalid.map(r => (0, ansi_1.bold)(r, output.formatter)).join(', ')}`
37
+ output.stderr(`Invalid linting rule name(s): ${parseResult.invalid.map(r => (0, ansi_1.bold)(r, output.formatter)).join(', ')}`
38
38
  + `\nValid rule names are: ${Object.keys(linter_rules_1.LintingRules).map(r => (0, ansi_1.bold)(r, output.formatter)).join(', ')}`);
39
39
  }
40
40
  rules = parseResult.valid;
@@ -28,10 +28,11 @@ import { type InspectHigherOrderQuery } from './catalog/inspect-higher-order-que
28
28
  import type { ReadonlyFlowrAnalysisProvider } from '../project/flowr-analyzer';
29
29
  import type { ReplOutput } from '../cli/repl/commands/repl-main';
30
30
  import type { CommandCompletions } from '../cli/repl/core';
31
+ import type { FilesQuery } from './catalog/files-query/files-query-format';
31
32
  /**
32
33
  * These are all queries that can be executed from within flowR
33
34
  */
34
- export type Query = CallContextQuery | ConfigQuery | SearchQuery | DataflowQuery | ControlFlowQuery | DataflowLensQuery | DfShapeQuery | NormalizedAstQuery | IdMapQuery | DataflowClusterQuery | StaticSliceQuery | DependenciesQuery | LocationMapQuery | HappensBeforeQuery | InspectHigherOrderQuery | ResolveValueQuery | ProjectQuery | OriginQuery | LinterQuery;
35
+ export type Query = CallContextQuery | ConfigQuery | SearchQuery | DataflowQuery | ControlFlowQuery | DataflowLensQuery | FilesQuery | DfShapeQuery | NormalizedAstQuery | IdMapQuery | DataflowClusterQuery | StaticSliceQuery | DependenciesQuery | LocationMapQuery | HappensBeforeQuery | InspectHigherOrderQuery | ResolveValueQuery | ProjectQuery | OriginQuery | LinterQuery;
35
36
  export type QueryArgumentsWithType<QueryType extends BaseQueryFormat['type']> = Query & {
36
37
  type: QueryType;
37
38
  };
@@ -105,6 +106,14 @@ export declare const SupportedQueries: {
105
106
  readonly schema: Joi.ObjectSchema<any>;
106
107
  readonly flattenInvolvedNodes: () => never[];
107
108
  };
109
+ readonly files: {
110
+ readonly executor: typeof import("./catalog/files-query/files-query-executor").executeFileQuery;
111
+ readonly asciiSummarizer: (formatter: OutputFormatter, _analyzer: ReadonlyFlowrAnalysisProvider<import("../r-bridge/parser").KnownParser>, queryResults: BaseQueryResult, result: string[]) => true;
112
+ readonly completer: (line: readonly string[], startingNewArg: boolean, _config: FlowrConfigOptions) => CommandCompletions;
113
+ readonly fromLine: (output: ReplOutput, line: readonly string[], _config: FlowrConfigOptions) => ParsedQueryLine<"files">;
114
+ readonly schema: Joi.ObjectSchema<any>;
115
+ readonly flattenInvolvedNodes: (_: BaseQueryResult) => NodeId[];
116
+ };
108
117
  readonly 'id-map': {
109
118
  readonly executor: typeof import("./catalog/id-map-query/id-map-query-executor").executeIdMapQuery;
110
119
  readonly asciiSummarizer: (formatter: OutputFormatter, _analyzer: ReadonlyFlowrAnalysisProvider<import("../r-bridge/parser").KnownParser>, queryResults: BaseQueryResult, result: string[]) => true;
package/queries/query.js CHANGED
@@ -35,6 +35,7 @@ const control_flow_query_format_1 = require("./catalog/control-flow-query/contro
35
35
  const df_shape_query_format_1 = require("./catalog/df-shape-query/df-shape-query-format");
36
36
  const inspect_higher_order_query_format_1 = require("./catalog/inspect-higher-order-query/inspect-higher-order-query-format");
37
37
  const log_1 = require("../util/log");
38
+ const files_query_format_1 = require("./catalog/files-query/files-query-format");
38
39
  exports.SupportedQueries = {
39
40
  'call-context': call_context_query_format_1.CallContextQueryDefinition,
40
41
  'config': config_query_format_1.ConfigQueryDefinition,
@@ -42,6 +43,7 @@ exports.SupportedQueries = {
42
43
  'dataflow': dataflow_query_format_1.DataflowQueryDefinition,
43
44
  'dataflow-lens': dataflow_lens_query_format_1.DataflowLensQueryDefinition,
44
45
  'df-shape': df_shape_query_format_1.DfShapeQueryDefinition,
46
+ 'files': files_query_format_1.FilesQueryDefinition,
45
47
  'id-map': id_map_query_format_1.IdMapQueryDefinition,
46
48
  'normalized-ast': normalized_ast_query_format_1.NormalizedAstQueryDefinition,
47
49
  'dataflow-cluster': cluster_query_format_1.ClusterQueryDefinition,
@@ -103,7 +105,7 @@ async function executeQueries(data, queries) {
103
105
  const results = [];
104
106
  for (const [type, group] of entries) {
105
107
  try {
106
- const result = await Promise.resolve(executeQueriesOfSameType(data, group));
108
+ const result = await executeQueriesOfSameType(data, group);
107
109
  results.push([type, result]);
108
110
  }
109
111
  catch (e) {
@@ -102,7 +102,7 @@ export interface Namespace {
102
102
  * The namespace attached to the given node
103
103
  * (e.g., a namespaced symbol in `x::y`).
104
104
  */
105
- namespace: NamespaceIdentifier | undefined;
105
+ namespace?: NamespaceIdentifier;
106
106
  }
107
107
  /**
108
108
  * This subtype of {@link RNode} represents all types of constants
@@ -187,7 +187,7 @@ function createFoldForUnaryOp(info) {
187
187
  info.idMap.set(id, decorated);
188
188
  const opInfo = operand.info;
189
189
  opInfo.parent = id;
190
- opInfo.role = "unary-operand" /* RoleInParent.UnaryOperand */;
190
+ opInfo.role = "unary-op" /* RoleInParent.UnaryOperand */;
191
191
  decorated.info.file = info.file;
192
192
  return decorated;
193
193
  };
@@ -208,7 +208,7 @@ function createFoldForAccess(info) {
208
208
  const curInfo = acc.info;
209
209
  curInfo.parent = id;
210
210
  curInfo.index = idx;
211
- curInfo.role = "index-access" /* RoleInParent.IndexAccess */;
211
+ curInfo.role = "index-acc" /* RoleInParent.IndexAccess */;
212
212
  }
213
213
  }
214
214
  }
@@ -223,11 +223,11 @@ function createFoldForForLoop(info) {
223
223
  info.idMap.set(id, decorated);
224
224
  const varInfo = variable.info;
225
225
  varInfo.parent = id;
226
- varInfo.role = "for-variable" /* RoleInParent.ForVariable */;
226
+ varInfo.role = "for-var" /* RoleInParent.ForVariable */;
227
227
  const vecInfo = vector.info;
228
228
  vecInfo.parent = id;
229
229
  vecInfo.index = 1;
230
- vecInfo.role = "for-vector" /* RoleInParent.ForVector */;
230
+ vecInfo.role = "for-vec" /* RoleInParent.ForVector */;
231
231
  const bodyInfo = body.info;
232
232
  bodyInfo.parent = id;
233
233
  bodyInfo.index = 2;
@@ -280,7 +280,7 @@ function createFoldForIfThenElse(info) {
280
280
  const otherwiseInfo = otherwise.info;
281
281
  otherwiseInfo.parent = id;
282
282
  otherwiseInfo.index = 2;
283
- otherwiseInfo.role = "if-otherwise" /* RoleInParent.IfOtherwise */;
283
+ otherwiseInfo.role = "if-other" /* RoleInParent.IfOtherwise */;
284
284
  }
285
285
  decorated.info.file = info.file;
286
286
  return decorated;
@@ -323,7 +323,7 @@ function createFoldForFunctionCall(info) {
323
323
  const argInfo = arg.info;
324
324
  argInfo.parent = id;
325
325
  argInfo.index = idx;
326
- argInfo.role = "call-argument" /* RoleInParent.FunctionCallArgument */;
326
+ argInfo.role = "call-arg" /* RoleInParent.FunctionCallArgument */;
327
327
  }
328
328
  }
329
329
  decorated.info.file = info.file;
@@ -340,12 +340,12 @@ function createFoldForFunctionDefinition(info) {
340
340
  const paramInfo = param.info;
341
341
  paramInfo.parent = id;
342
342
  paramInfo.index = idx++;
343
- paramInfo.role = "function-def-param" /* RoleInParent.FunctionDefinitionParameter */;
343
+ paramInfo.role = "param" /* RoleInParent.FunctionDefinitionParameter */;
344
344
  }
345
345
  const bodyInfo = body.info;
346
346
  bodyInfo.parent = id;
347
347
  bodyInfo.index = idx;
348
- bodyInfo.role = "function-def-body" /* RoleInParent.FunctionDefinitionBody */;
348
+ bodyInfo.role = "fun-body" /* RoleInParent.FunctionDefinitionBody */;
349
349
  decorated.info.file = info.file;
350
350
  return decorated;
351
351
  };
@@ -10,29 +10,29 @@ export declare const enum RoleInParent {
10
10
  Root = "root",
11
11
  IfCondition = "if-cond",
12
12
  IfThen = "if-then",
13
- IfOtherwise = "if-otherwise",
13
+ IfOtherwise = "if-other",
14
14
  WhileCondition = "while-cond",
15
15
  WhileBody = "while-body",
16
16
  RepeatBody = "repeat-body",
17
- ForVariable = "for-variable",
18
- ForVector = "for-vector",
17
+ ForVariable = "for-var",
18
+ ForVector = "for-vec",
19
19
  ForBody = "for-body",
20
20
  FunctionCallName = "call-name",
21
- FunctionCallArgument = "call-argument",
22
- FunctionDefinitionBody = "function-def-body",
23
- FunctionDefinitionParameter = "function-def-param",
21
+ FunctionCallArgument = "call-arg",
22
+ FunctionDefinitionBody = "fun-body",
23
+ FunctionDefinitionParameter = "param",
24
24
  ExpressionListChild = "expr-list-child",
25
25
  BinaryOperationLhs = "binop-lhs",
26
26
  BinaryOperationRhs = "binop-rhs",
27
27
  PipeLhs = "pipe-lhs",
28
28
  PipeRhs = "pipe-rhs",
29
- UnaryOperand = "unary-operand",
29
+ UnaryOperand = "unary-op",
30
30
  ParameterName = "param-name",
31
31
  ParameterDefaultValue = "param-value",
32
32
  ArgumentName = "arg-name",
33
33
  ArgumentValue = "arg-value",
34
34
  Accessed = "accessed",
35
- IndexAccess = "index-access"
35
+ IndexAccess = "index-acc"
36
36
  }
37
37
  /**
38
38
  * Returns the roles of the parents of the given node, starting with the parent-role of the node itself.
@@ -34,7 +34,6 @@ function tryNormalizeParameter(data, objs) {
34
34
  name: {
35
35
  type: type_1.RType.Symbol,
36
36
  location, content,
37
- namespace: undefined,
38
37
  lexeme: content,
39
38
  info: {
40
39
  fullRange: location,
@@ -483,7 +483,6 @@ function convertTreeNode(node) {
483
483
  name: {
484
484
  type: type_1.RType.Symbol,
485
485
  location: nameRange,
486
- namespace: undefined,
487
486
  content: name.text,
488
487
  lexeme: name.text,
489
488
  info: {
@@ -58,7 +58,7 @@ function visitAccess(info, input) {
58
58
  acc = true;
59
59
  break; // we only account for the first one
60
60
  }
61
- else if (role === "index-access" /* RoleInParent.IndexAccess */) {
61
+ else if (role === "index-acc" /* RoleInParent.IndexAccess */) {
62
62
  idxAcc = true;
63
63
  break;
64
64
  }
@@ -16,7 +16,7 @@ const type_1 = require("../r-bridge/lang-4.x/ast/model/type");
16
16
  function getAccessOperands(args) {
17
17
  const nonEmptyArgs = args.filter(arg => arg !== r_function_call_1.EmptyArgument);
18
18
  const accessedArg = nonEmptyArgs.find(arg => arg.info.role === "accessed" /* RoleInParent.Accessed */);
19
- const accessArg = nonEmptyArgs.find(arg => arg.info.role === "index-access" /* RoleInParent.IndexAccess */);
19
+ const accessArg = nonEmptyArgs.find(arg => arg.info.role === "index-acc" /* RoleInParent.IndexAccess */);
20
20
  return { accessedArg, accessArg };
21
21
  }
22
22
  /**
package/util/files.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { type PathLike } from 'fs';
2
2
  import type { RParseRequestFromFile } from '../r-bridge/retriever';
3
- import type { FlowrFileProvider } from '../project/context/flowr-file';
4
3
  /**
5
4
  * Represents a table, identified by a header and a list of rows.
6
5
  */
@@ -70,12 +69,6 @@ export declare function readLineByLineSync(filePath: string, onLine: (line: Buff
70
69
  * @param directory - The directory whose parent to return
71
70
  */
72
71
  export declare function getParentDirectory(directory: string): string;
73
- /**
74
- * Parses the given file in the 'Debian Control Format'.
75
- * @param file - The file to parse
76
- * @returns Map containing the keys and values of the provided file.
77
- */
78
- export declare function parseDCF(file: FlowrFileProvider): Map<string, string[]>;
79
72
  /**
80
73
  * Checks whether the given path-like object is a file that exists on the local filesystem.
81
74
  */
package/util/files.js CHANGED
@@ -44,7 +44,6 @@ exports.writeTableAsCsv = writeTableAsCsv;
44
44
  exports.readLineByLine = readLineByLine;
45
45
  exports.readLineByLineSync = readLineByLineSync;
46
46
  exports.getParentDirectory = getParentDirectory;
47
- exports.parseDCF = parseDCF;
48
47
  exports.isFilePath = isFilePath;
49
48
  const fs_1 = __importStar(require("fs"));
50
49
  const path_1 = __importDefault(require("path"));
@@ -201,46 +200,6 @@ function getParentDirectory(directory) {
201
200
  // apparently this is somehow the best way to do it in node, what
202
201
  return directory.split(path_1.default.sep).slice(0, -1).join(path_1.default.sep);
203
202
  }
204
- /**
205
- * Parses the given file in the 'Debian Control Format'.
206
- * @param file - The file to parse
207
- * @returns Map containing the keys and values of the provided file.
208
- */
209
- function parseDCF(file) {
210
- const result = new Map();
211
- let currentKey = '';
212
- let currentValue = '';
213
- const indentRegex = new RegExp(/^\s/);
214
- const firstColonRegex = new RegExp(/:(.*)/s);
215
- const fileContent = file.content().toString().split(/\r?\n/);
216
- for (const line of fileContent) {
217
- if (indentRegex.test(line)) {
218
- currentValue += '\n' + line.trim();
219
- }
220
- else {
221
- if (currentKey) {
222
- const values = currentValue ? cleanValues(currentValue) : [];
223
- result.set(currentKey, values);
224
- }
225
- const [key, rest] = line.split(firstColonRegex).map(s => s.trim());
226
- currentKey = key.trim();
227
- currentValue = rest.trim();
228
- }
229
- }
230
- if (currentKey) {
231
- const values = currentValue ? cleanValues(currentValue) : [];
232
- result.set(currentKey, values);
233
- }
234
- return result;
235
- }
236
- const cleanSplitRegex = /[\n,]+/;
237
- const cleanQuotesRegex = /'/g;
238
- function cleanValues(values) {
239
- return values
240
- .split(cleanSplitRegex)
241
- .map(s => s.trim().replace(cleanQuotesRegex, ''))
242
- .filter(s => s.length > 0);
243
- }
244
203
  /**
245
204
  * Checks whether the given path-like object is a file that exists on the local filesystem.
246
205
  */
@@ -1,10 +1,11 @@
1
1
  import type { ParentInformation, RNodeWithParent } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
2
2
  import type { RProject } from '../../r-bridge/lang-4.x/ast/model/nodes/r-project';
3
+ import type { MermaidGraphPrinterInfo } from './info';
3
4
  /**
4
5
  * Serialize the normalized AST to mermaid format
5
6
  */
6
- export declare function normalizedAstToMermaid(ast: RProject<ParentInformation> | RNodeWithParent, prefix?: string): string;
7
+ export declare function normalizedAstToMermaid(ast: RProject<ParentInformation> | RNodeWithParent, { prefix, markStyle, includeOnlyIds, mark }?: MermaidGraphPrinterInfo): string;
7
8
  /**
8
9
  * Use mermaid to visualize the normalized AST.
9
10
  */
10
- export declare function normalizedAstToMermaidUrl(ast: RProject<ParentInformation> | RNodeWithParent, prefix?: string): string;
11
+ export declare function normalizedAstToMermaidUrl(ast: RProject<ParentInformation> | RNodeWithParent, info?: MermaidGraphPrinterInfo): string;
@@ -7,6 +7,7 @@ const visitor_1 = require("../../r-bridge/lang-4.x/ast/model/processing/visitor"
7
7
  const type_1 = require("../../r-bridge/lang-4.x/ast/model/type");
8
8
  const r_function_call_1 = require("../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
9
9
  const flowr_file_1 = require("../../project/context/flowr-file");
10
+ const info_1 = require("./info");
10
11
  function identifyMermaidDirection(prefix) {
11
12
  const directionMatch = prefix.match(/flowchart (TD|LR|RL|BT)/);
12
13
  if (directionMatch) {
@@ -17,14 +18,20 @@ function identifyMermaidDirection(prefix) {
17
18
  /**
18
19
  * Serialize the normalized AST to mermaid format
19
20
  */
20
- function normalizedAstToMermaid(ast, prefix = 'flowchart TD\n') {
21
+ function normalizedAstToMermaid(ast, { prefix = 'flowchart TD\n', markStyle = info_1.MermaidDefaultMarkStyle, includeOnlyIds, mark } = {}) {
21
22
  let output = prefix;
22
23
  function showNode(n) {
24
+ if (includeOnlyIds && !includeOnlyIds.has(n.info.id)) {
25
+ return;
26
+ }
23
27
  const name = `${n.type} (${n.info.id})\\n${n.lexeme ?? ' '}`;
24
28
  output += ` n${n.info.id}(["${(0, mermaid_1.escapeMarkdown)(name)}"])\n`;
25
- if (n.info.parent !== undefined) {
29
+ if (mark?.has(n.info.id)) {
30
+ output += ` style n${n.info.id} ${markStyle.vertex}\n`;
31
+ }
32
+ if (n.info.parent !== undefined && (!includeOnlyIds || includeOnlyIds.has(n.info.parent))) {
26
33
  const context = n.info;
27
- const roleSuffix = context.role === "expr-list-child" /* RoleInParent.ExpressionListChild */ || context.role === "call-argument" /* RoleInParent.FunctionCallArgument */ || context.role === "function-def-param" /* RoleInParent.FunctionDefinitionParameter */ ? `-${context.index}` : '';
34
+ const roleSuffix = context.role === "expr-list-child" /* RoleInParent.ExpressionListChild */ || context.role === "call-arg" /* RoleInParent.FunctionCallArgument */ || context.role === "param" /* RoleInParent.FunctionDefinitionParameter */ ? `-${context.index}` : '';
28
35
  output += ` n${n.info.parent} -->|"${context.role}${roleSuffix}"| n${n.info.id}\n`;
29
36
  }
30
37
  if (n.type === type_1.RType.ExpressionList && n.grouping !== undefined) {
@@ -49,9 +56,8 @@ function normalizedAstToMermaid(ast, prefix = 'flowchart TD\n') {
49
56
  for (const f of ast.files) {
50
57
  // add a subgraph for each file
51
58
  if (ast.files.length !== 1 || (f.filePath && f.filePath !== flowr_file_1.FlowrFile.INLINE_PATH)) {
52
- output += ` subgraph "File: ${(0, mermaid_1.escapeMarkdown)(f.filePath ?? flowr_file_1.FlowrFile.INLINE_PATH)}"\n`;
53
59
  const direction = identifyMermaidDirection(prefix);
54
- output += ` direction ${direction}\n`;
60
+ output += ` subgraph "File: ${(0, mermaid_1.escapeMarkdown)(f.filePath ?? flowr_file_1.FlowrFile.INLINE_PATH)}" direction ${direction}\n`;
55
61
  showAst(f.root);
56
62
  output += ' end\n';
57
63
  }
@@ -68,7 +74,7 @@ function normalizedAstToMermaid(ast, prefix = 'flowchart TD\n') {
68
74
  /**
69
75
  * Use mermaid to visualize the normalized AST.
70
76
  */
71
- function normalizedAstToMermaidUrl(ast, prefix = 'flowchart TD\n') {
72
- return (0, mermaid_1.mermaidCodeToUrl)(normalizedAstToMermaid(ast, prefix));
77
+ function normalizedAstToMermaidUrl(ast, info) {
78
+ return (0, mermaid_1.mermaidCodeToUrl)(normalizedAstToMermaid(ast, info ?? {}));
73
79
  }
74
80
  //# sourceMappingURL=ast.js.map
@@ -1,5 +1,6 @@
1
1
  import type { NormalizedAst } from '../../r-bridge/lang-4.x/ast/model/processing/decorate';
2
2
  import { type ControlFlowInformation } from '../../control-flow/control-flow-graph';
3
+ import { type MermaidGraphPrinterInfo } from './info';
3
4
  /**
4
5
  * Convert the control flow graph to a mermaid string.
5
6
  * @param cfg - The control flow graph to convert.
@@ -7,8 +8,8 @@ import { type ControlFlowInformation } from '../../control-flow/control-flow-gra
7
8
  * @param prefix - The prefix to use for the mermaid string.
8
9
  * @param simplify - Whether to simplify the control flow graph (especially in the context of basic blocks).
9
10
  */
10
- export declare function cfgToMermaid(cfg: ControlFlowInformation, normalizedAst: NormalizedAst, prefix?: string, simplify?: boolean): string;
11
+ export declare function cfgToMermaid(cfg: ControlFlowInformation, normalizedAst: NormalizedAst, { prefix, simplify, markStyle, includeOnlyIds, mark }?: MermaidGraphPrinterInfo): string;
11
12
  /**
12
13
  * Use mermaid to visualize the normalized AST.
13
14
  */
14
- export declare function cfgToMermaidUrl(cfg: ControlFlowInformation, normalizedAst: NormalizedAst, prefix?: string): string;
15
+ export declare function cfgToMermaidUrl(cfg: ControlFlowInformation, normalizedAst: NormalizedAst, info?: MermaidGraphPrinterInfo): string;
@@ -7,6 +7,7 @@ const type_1 = require("../../r-bridge/lang-4.x/ast/model/type");
7
7
  const control_flow_graph_1 = require("../../control-flow/control-flow-graph");
8
8
  const reconstruct_1 = require("../../reconstruct/reconstruct");
9
9
  const auto_select_defaults_1 = require("../../reconstruct/auto-select/auto-select-defaults");
10
+ const info_1 = require("./info");
10
11
  function getLexeme(n) {
11
12
  return n ? n.info.fullLexeme ?? n.lexeme ?? '' : undefined;
12
13
  }
@@ -30,7 +31,7 @@ const getDirRegex = /flowchart\s+([A-Za-z]+)/;
30
31
  * @param prefix - The prefix to use for the mermaid string.
31
32
  * @param simplify - Whether to simplify the control flow graph (especially in the context of basic blocks).
32
33
  */
33
- function cfgToMermaid(cfg, normalizedAst, prefix = 'flowchart BT\n', simplify = false) {
34
+ function cfgToMermaid(cfg, normalizedAst, { prefix = 'flowchart BT\n', simplify = false, markStyle = info_1.MermaidDefaultMarkStyle, includeOnlyIds, mark } = {}) {
34
35
  let output = prefix;
35
36
  const dirIs = getDirRegex.exec(prefix)?.at(1) ?? 'LR';
36
37
  for (const [id, vertex] of cfg.graph.vertices(false)) {
@@ -52,6 +53,10 @@ function cfgToMermaid(cfg, normalizedAst, prefix = 'flowchart BT\n', simplify =
52
53
  output += ` direction ${dirIs}\n`;
53
54
  let last = undefined;
54
55
  for (const element of vertex.elems ?? []) {
56
+ if (includeOnlyIds && !includeOnlyIds.has(element.id)) {
57
+ last = undefined;
58
+ continue;
59
+ }
55
60
  const childNormalizedVertex = normalizedAst?.idMap.get(element.id);
56
61
  const childContent = getLexeme(childNormalizedVertex);
57
62
  output = cfgOfNode(vertex, childNormalizedVertex, element.id, childContent, output);
@@ -64,7 +69,7 @@ function cfgToMermaid(cfg, normalizedAst, prefix = 'flowchart BT\n', simplify =
64
69
  output += ' end\n';
65
70
  }
66
71
  }
67
- else {
72
+ else if (!includeOnlyIds || includeOnlyIds.has(id)) {
68
73
  output = cfgOfNode(vertex, normalizedVertex, id, content, output);
69
74
  }
70
75
  if (vertex.name === type_1.RType.ExpressionList && vertex.type === control_flow_graph_1.CfgVertexType.EndMarker) {
@@ -72,24 +77,39 @@ function cfgToMermaid(cfg, normalizedAst, prefix = 'flowchart BT\n', simplify =
72
77
  }
73
78
  }
74
79
  for (const [from, targets] of cfg.graph.edges()) {
80
+ if (includeOnlyIds && !includeOnlyIds.has(from)) {
81
+ continue;
82
+ }
75
83
  for (const [to, edge] of targets) {
84
+ if (includeOnlyIds && !includeOnlyIds.has(to)) {
85
+ continue;
86
+ }
76
87
  const edgeType = edge.label === 1 /* CfgEdgeType.Cd */ ? '-->' : '-.->';
77
88
  const edgeSuffix = edge.label === 1 /* CfgEdgeType.Cd */ ? ` (${edge.when})` : '';
78
89
  output += ` n${from} ${edgeType}|"${(0, mermaid_1.escapeMarkdown)((0, control_flow_graph_1.edgeTypeToString)(edge.label))}${edgeSuffix}"| n${to}\n`;
79
90
  }
80
91
  }
81
92
  for (const entryPoint of cfg.entryPoints) {
82
- output += ` style n${entryPoint} stroke:cyan,stroke-width:6.5px;`;
93
+ if (!includeOnlyIds || includeOnlyIds.has(entryPoint)) {
94
+ output += ` style n${entryPoint} stroke:cyan,stroke-width:6.5px;`;
95
+ }
83
96
  }
84
97
  for (const exitPoint of cfg.exitPoints) {
85
- output += ` style n${exitPoint} stroke:green,stroke-width:6.5px;`;
98
+ if (!includeOnlyIds || includeOnlyIds.has(exitPoint)) {
99
+ output += ` style n${exitPoint} stroke:green,stroke-width:6.5px;`;
100
+ }
101
+ }
102
+ if (mark) {
103
+ for (const id of mark.values()) {
104
+ output += ` style n${id} ${markStyle.vertex}`;
105
+ }
86
106
  }
87
107
  return output;
88
108
  }
89
109
  /**
90
110
  * Use mermaid to visualize the normalized AST.
91
111
  */
92
- function cfgToMermaidUrl(cfg, normalizedAst, prefix = 'flowchart BT\n') {
93
- return (0, mermaid_1.mermaidCodeToUrl)(cfgToMermaid(cfg, normalizedAst, prefix));
112
+ function cfgToMermaidUrl(cfg, normalizedAst, info) {
113
+ return (0, mermaid_1.mermaidCodeToUrl)(cfgToMermaid(cfg, normalizedAst, info ?? {}));
94
114
  }
95
115
  //# sourceMappingURL=cfg.js.map
@@ -2,13 +2,7 @@ import type { SourceRange } from '../range';
2
2
  import { type DataflowGraph } from '../../dataflow/graph/graph';
3
3
  import { type NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id';
4
4
  import { type IdentifierDefinition } from '../../dataflow/environments/identifier';
5
- type MarkVertex = NodeId;
6
- type MarkEdge = `${string}->${string}`;
7
- export type MermaidMarkdownMark = MarkVertex | MarkEdge;
8
- export interface MermaidMarkStyle {
9
- readonly vertex: string;
10
- readonly edge: string;
11
- }
5
+ import { type MermaidMarkdownMark, type MermaidMarkStyle } from './info';
12
6
  interface MermaidGraph {
13
7
  nodeLines: string[];
14
8
  edgeLines: string[];
@@ -15,6 +15,7 @@ const edge_1 = require("../../dataflow/graph/edge");
15
15
  const vertex_1 = require("../../dataflow/graph/vertex");
16
16
  const type_1 = require("../../r-bridge/lang-4.x/ast/model/type");
17
17
  const built_in_1 = require("../../dataflow/environments/built-in");
18
+ const info_1 = require("./info");
18
19
  /**
19
20
  * Prints a {@link SourceRange|range} as a human-readable string.
20
21
  */
@@ -147,7 +148,7 @@ function printEnvironmentToLines(env) {
147
148
  }
148
149
  return lines;
149
150
  }
150
- function vertexToMermaid(info, mermaid, id, idPrefix, mark) {
151
+ function vertexToMermaid(info, mermaid, id, idPrefix, mark, includeOnlyIds) {
151
152
  const fCall = info.tag === vertex_1.VertexType.FunctionCall;
152
153
  const { open, close } = mermaidNodeBrackets(info.tag);
153
154
  id = (0, mermaid_1.escapeId)(id);
@@ -184,6 +185,9 @@ function vertexToMermaid(info, mermaid, id, idPrefix, mark) {
184
185
  const artificialCdEdges = (info.cds ?? []).map(x => [x.id, { types: new Set([x.when ? 'CD-True' : 'CD-False']) }]);
185
186
  // eslint-disable-next-line prefer-const
186
187
  for (let [target, edge] of [...edges[1], ...artificialCdEdges]) {
188
+ if (includeOnlyIds && !includeOnlyIds.has(target)) {
189
+ continue;
190
+ }
187
191
  const originalTarget = target;
188
192
  target = (0, mermaid_1.escapeId)(target);
189
193
  const edgeTypes = typeof edge.types == 'number' ? new Set((0, edge_1.splitEdgeTypes)(edge.types)) : edge.types;
@@ -214,11 +218,11 @@ function vertexToMermaid(info, mermaid, id, idPrefix, mark) {
214
218
  }
215
219
  }
216
220
  // make the passing of root ids more performant again
217
- function graphToMermaidGraph(rootIds, { simplified, graph, prefix = 'flowchart BT', idPrefix = '', includeEnvironments = !simplified, mark, rootGraph, presentEdges = new Set(), markStyle = { vertex: 'stroke:teal,stroke-width:7px,stroke-opacity:.8;', edge: 'stroke:teal,stroke-width:4.2px,stroke-opacity:.8' } }) {
221
+ function graphToMermaidGraph(rootIds, { simplified, graph, prefix = 'flowchart BT', idPrefix = '', includeEnvironments = !simplified, mark, rootGraph, presentEdges = new Set(), markStyle = info_1.MermaidDefaultMarkStyle, includeOnlyIds }) {
218
222
  const mermaid = { nodeLines: prefix === null ? [] : [prefix], edgeLines: [], presentEdges, presentVertices: new Set(), mark, rootGraph: rootGraph ?? graph, includeEnvironments, markStyle, simplified };
219
223
  for (const [id, info] of graph.vertices(true)) {
220
224
  if (rootIds.has(id)) {
221
- vertexToMermaid(info, mermaid, id, idPrefix, mark);
225
+ vertexToMermaid(info, mermaid, id, idPrefix, mark, includeOnlyIds);
222
226
  }
223
227
  }
224
228
  return mermaid;