{{ message }}
Repository navigation
Expand file tree
/
Copy pathlanguage.ts
More file actions
264 lines (249 loc) · 9.7 KB
/
Copy pathlanguage.ts
File metadata and controls
264 lines (249 loc) · 9.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
choice,
def,
field,
language,
optional,
repeat,
seq,
tggComplement,
tggDefaultVal,
tggEq,
tggRule,
type QueryDB,
type SymbolEntry,
type SymbolId,
} from "@modelscript/dsl";
import { lintCsv, type CsvDiagnostic } from "./src/csv-parser.js";
export { lintCsv, type CsvDiagnostic };
/**
* Metadata attached to virtual CSV component symbols by the workspace indexer.
*/
interface CsvMetadata {
/** Inferred or explicitly declared Modelica type name (e.g., `"Real"`). */
typeSpecifier?: string | undefined;
/** Dimensions of the tabular column or matrix. */
arrayDimensions?: number[] | undefined;
/** Parsed literal array or scalar value extracted from the CSV table. */
csvValue?: unknown;
}
/**
* ModelScript CSV language specification.
*
* Defines grammar rules for delimiter-separated tables (`,`, `;`, `\t`),
* virtual component queries for polyglot workspace indexing, semantic table
* validation hooks, and Triple Graph Grammar (TGG) rules projecting CSV files
* into Modelica parameter packages.
*/
export const csvLanguage = language({
name: "csv",
fileExtensions: [".csv"],
lsp: {
fileExtensions: [".csv"],
icons: {
light: "./assets/csv/icon-light.png",
dark: "./assets/csv/icon-dark.png",
},
},
// In-place cell value writeback using byte offsets recorded on the symbol entry
writeback: (ctx) => {
const { entry, newValue } = ctx;
if (entry?.startOffset != null && entry?.endOffset != null && entry.endOffset >= entry.startOffset) {
return { startOffset: entry.startOffset, endOffset: entry.endOffset, newText: newValue.trim() };
}
return null;
},
// IDE and AI agent actions for telemetry plotting and Modelica package generation
actions: [
{
id: "plot_telemetry",
title: "Plot CSV Telemetry Data",
description: "Visualizes tabular time-series columns in the simulation plotting panel.",
category: "simulate",
ui: {
editorTitle: {
icon: "$(graph)",
group: "navigation@1",
},
editorContextMenu: {
group: "1_run@0",
},
explorerContextMenu: {
group: "modelscript_run@0",
},
languageModelTool: {
name: "csv_plot_telemetry",
displayName: "Plot CSV Telemetry",
modelDescription: "Visualizes tabular time-series data from CSV in the simulation plot panel.",
},
},
},
{
id: "import_as_modelica",
title: "Import as Modelica Parameter Package",
description: "Generates a strongly-typed Modelica package defining parameters and constants from table rows.",
category: "transform",
ui: {
editorContextMenu: {
group: "2_transform@1",
},
explorerContextMenu: {
group: "modelscript_convert@0",
},
languageModelTool: {
name: "csv_to_modelica",
displayName: "Import CSV as Modelica Package",
modelDescription: "Transforms CSV parameters and tabular rows into a Modelica package.",
},
},
},
],
rules: {
// =====================================================================
// Grammar Rules
// =====================================================================
SourceFile: ($) =>
def({
syntax: seq(
field("rows", $["Row"]!),
repeat(seq($["_newline"]!, field("rows", $["Row"]!))),
optional($["_newline"]!),
),
symbol: (self: Record<string, string>) => ({
kind: "Class",
name: self["rows"], // dummy access to record namePath
}),
queries: {
// Return all virtual column/matrix component symbols belonging to this CSV document
instantiate: (db: QueryDB, self: SymbolEntry): SymbolId[] => {
return db.childrenOf(self.id).map((c) => c.id);
},
// Resolve column component names within the CSV document scope
resolveSimpleName: (db: QueryDB, self: SymbolEntry) => {
const children = db.childrenOf(self.id);
const byNameMap = new Map<string, SymbolEntry>();
for (const child of children) {
byNameMap.set(child.name, child);
}
return (name: string) => byNameMap.get(name) ?? null;
},
// Validate header uniqueness, row column counts, numeric cells, and time monotonicity
lint__csvTableValidity: (db: QueryDB, self: SymbolEntry) => {
const text = db["cstText"](self.startOffset, self.endOffset, self);
if (!text) return null;
const diags = lintCsv(text, self.startOffset ?? 0);
if (diags.length === 0) return null;
return diags.map((d) => ({
severity: d.severity,
code: d.code,
message: d.message,
startOffset: d.startOffset,
endOffset: d.endOffset,
}));
},
},
}),
Row: ($) =>
choice(
seq(field("cells", $["Cell"]!), repeat(seq($["_delimiter"]!, optional(field("cells", $["Cell"]!))))),
seq($["_delimiter"]!, repeat(seq($["_delimiter"]!, optional(field("cells", $["Cell"]!))))),
),
Cell: ($) => choice($["_quoted_cell"]!, $["_unquoted_cell"]!),
_quoted_cell: () => /"([^"]|"")*"/,
_unquoted_cell: () => /[^,\r\n\t;"]+/,
_delimiter: () => choice(",", ";", "\t"),
_newline: () => choice("\r\n", "\n", "\r"),
// =====================================================================
// Dummy Rule for Virtual Component Symbols
// This allows query-hooks and AST classes to be generated for
// CSVVirtualComponent rule used by the workspace indexer.
// =====================================================================
CSVVirtualComponent: () =>
def({
syntax: "CSVVirtualComponent",
symbol: (self: Record<string, string>) => ({
kind: "Component",
name: self["syntax"] ?? "", // dummy access to record namePath
}),
queries: {
// Resolve the target scalar class (defaults to top-level "Real")
resolvedType: (db: QueryDB, self: SymbolEntry): SymbolEntry | null => {
const metadata = self.metadata as CsvMetadata | undefined;
const typeName = metadata?.typeSpecifier ?? "Real";
const entries = db.byName(typeName);
return entries.find((e) => e.kind === "Class" && e.parentId === null) ?? null;
},
// Map the virtual CSV column to its corresponding top-level class symbol ID
classInstance: (db: QueryDB, self: SymbolEntry): SymbolId | null => {
const metadata = self.metadata as CsvMetadata | undefined;
const typeName = metadata?.typeSpecifier ?? "Real";
const entries = db.byName(typeName);
const classSymbol = entries.find((e) => e.kind === "Class" && e.parentId === null);
return classSymbol ? classSymbol.id : null;
},
variability: (): string => "constant",
causality: (): string => "local",
isOuter: (): boolean => false,
isInner: (): boolean => false,
isProtected: (): boolean => false,
isFinal: (): boolean => false,
// Return raw numeric array dimensions attached by the workspace indexer
resolvedArrayDimensions: (_db: QueryDB, self: SymbolEntry): number[] | null => {
const metadata = self.metadata as CsvMetadata | undefined;
const dims = metadata?.arrayDimensions;
return dims ?? null;
},
// Wrap numeric dimensions as literal expressions for the Modelica flattener
arrayDimensions: (_db: QueryDB, self: SymbolEntry) => {
const metadata = self.metadata as CsvMetadata | undefined;
const dims = metadata?.arrayDimensions;
if (!dims || dims.length === 0) return null;
return dims.map((d: number) => ({ kind: "literal", value: d }));
},
// Expose parsed CSV cell/column values as a constant literal binding expression
effectiveModification: (_db: QueryDB, self: SymbolEntry) => {
const metadata = self.metadata as CsvMetadata | undefined;
const csvValue = metadata?.csvValue;
if (csvValue === undefined) return null;
return {
args: [],
bindingExpression: { kind: "literal", value: csvValue },
};
},
},
}),
},
// Polyglot TGG rules mapping CSV files and virtual columns to Modelica packages and constant components
polyglot: {
languages: ["modelica"],
rules: [
tggRule({
name: "CsvDocumentToModelicaPackage",
sourceLang: "csv",
targetLang: "modelica",
source: ($, v) => $.SourceFile({ rows: v("docName") }),
target: ($, v) => $.ClassDefinition({ name: v("docName"), classKind: "package" }),
where: (v) => [
tggEq(v("docName"), v("docName")),
tggDefaultVal(v("isAbstract"), false),
tggComplement(["rows", "headers"]),
],
}),
tggRule({
name: "CsvVirtualComponentToModelicaComponent",
sourceLang: "csv",
targetLang: "modelica",
source: ($, v) => $.CSVVirtualComponent({ name: v("varName"), typeSpecifier: v("typeName") }),
target: ($, v) => $.ComponentClause({ name: v("varName"), typeSpecifier: v("typeName") }),
where: (v) => [
tggEq(v("varName"), v("varName")),
tggEq(v("typeName"), v("typeName")),
tggDefaultVal(v("causality"), "local"),
tggDefaultVal(v("variability"), "constant"),
],
}),
],
},
});
export default csvLanguage;
You can’t perform that action at this time.
