Skip to content

Commit 28c2e99

Browse files
committed
feat(mf2): Include CST reference in parsed data model, using cst Symbol key
1 parent e6566cf commit 28c2e99

File tree

5 files changed

+98
-47
lines changed

5 files changed

+98
-47
lines changed

packages/mf2-messageformat/src/cst-parser/as-data-model.ts

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type * as Model from '../data-model/types.js';
22
import { MessageSyntaxError } from '../errors.js';
33
import type * as CST from './cst-types.js';
44

5+
export const cst = Symbol.for('CST');
6+
57
/**
68
* Convert a CST message structure into its data model representation.
79
*
@@ -17,18 +19,21 @@ export function asDataModel(msg: CST.Message): Model.Message {
1719
type: 'select',
1820
declarations,
1921
selectors: msg.selectors.map(sel => asExpression(sel, false)),
20-
variants: msg.variants.map(cst => ({
21-
keys: cst.keys.map(key =>
22-
key.type === '*' ? { type: '*' } : asValue(key)
22+
variants: msg.variants.map(variant => ({
23+
keys: variant.keys.map(key =>
24+
key.type === '*' ? { type: '*', [cst]: key } : asValue(key)
2325
),
24-
value: asPattern(cst.value)
25-
}))
26+
value: asPattern(variant.value),
27+
[cst]: variant
28+
})),
29+
[cst]: msg
2630
};
2731
} else {
2832
return {
2933
type: 'message',
3034
declarations,
31-
pattern: asPattern(msg.pattern)
35+
pattern: asPattern(msg.pattern),
36+
[cst]: msg
3237
};
3338
}
3439
}
@@ -44,131 +49,138 @@ function asDeclaration(decl: CST.Declaration): Model.Declaration {
4449
return {
4550
type: 'input',
4651
name: value.arg.name,
47-
value: value as Model.Expression<Model.VariableRef>
52+
value: value as Model.Expression<Model.VariableRef>,
53+
[cst]: decl
4854
};
4955
}
5056
case 'local':
5157
return {
5258
type: 'local',
5359
name: asValue(decl.target).name,
54-
value: asExpression(decl.value, false)
60+
value: asExpression(decl.value, false),
61+
[cst]: decl
5562
};
5663
default:
5764
return {
5865
type: 'unsupported-statement',
5966
keyword: (decl.keyword?.value ?? '').substring(1),
6067
body: decl.body?.value || undefined,
61-
expressions: decl.values?.map(dv => asExpression(dv, true)) ?? []
68+
expressions: decl.values?.map(dv => asExpression(dv, true)) ?? [],
69+
[cst]: decl
6270
};
6371
}
6472
}
6573

66-
function asPattern(cst: CST.Pattern): Model.Pattern {
67-
const body: Model.Pattern['body'] = cst.body.map(el =>
74+
function asPattern(pattern: CST.Pattern): Model.Pattern {
75+
const body: Model.Pattern['body'] = pattern.body.map(el =>
6876
el.type === 'text' ? el.value : asExpression(el, true)
6977
);
7078
return { body };
7179
}
7280

7381
function asExpression(
74-
cst: CST.Expression | CST.Junk,
82+
exp: CST.Expression | CST.Junk,
7583
allowMarkup: false
7684
): Model.Expression;
7785
function asExpression(
78-
cst: CST.Expression | CST.Junk,
86+
exp: CST.Expression | CST.Junk,
7987
allowMarkup: true
8088
): Model.Expression | Model.Markup;
8189
function asExpression(
82-
cst: CST.Expression | CST.Junk,
90+
exp: CST.Expression | CST.Junk,
8391
allowMarkup: boolean
8492
): Model.Expression | Model.Markup {
85-
if (cst.type === 'expression') {
86-
if (allowMarkup && cst.markup) {
87-
const cm = cst.markup;
93+
if (exp.type === 'expression') {
94+
if (allowMarkup && exp.markup) {
95+
const cm = exp.markup;
8896
const name = asName(cm.name);
8997
if (cm.type === 'markup-close') {
90-
return { type: 'markup', kind: 'close', name };
98+
return { type: 'markup', kind: 'close', name, [cst]: exp };
9199
}
92100
const markup: Model.MarkupOpen | Model.MarkupStandalone = {
93101
type: 'markup',
94102
kind: cm.close ? 'standalone' : 'open',
95-
name
103+
name,
104+
[cst]: exp
96105
};
97106
if (cm.options.length > 0) markup.options = cm.options.map(asOption);
98107
return markup;
99108
}
100109

101-
const arg = cst.arg ? asValue(cst.arg) : undefined;
110+
const arg = exp.arg ? asValue(exp.arg) : undefined;
102111
let annotation:
103112
| Model.FunctionAnnotation
104113
| Model.UnsupportedAnnotation
105114
| undefined;
106115

107-
const ca = cst.annotation;
116+
const ca = exp.annotation;
108117
if (ca) {
109118
switch (ca.type) {
110119
case 'function':
111-
annotation = { type: 'function', name: asName(ca.name) };
120+
annotation = { type: 'function', name: asName(ca.name), [cst]: ca };
112121
if (ca.options.length > 0) {
113122
annotation.options = ca.options.map(asOption);
114123
}
124+
115125
break;
116126
case 'reserved-annotation':
117127
annotation = {
118128
type: 'unsupported-annotation',
119129
sigil: ca.sigil,
120-
source: ca.source.value
130+
source: ca.source.value,
131+
[cst]: ca
121132
};
122133
break;
123134
default:
124-
throw new MessageSyntaxError('parse-error', cst.start, cst.end);
135+
throw new MessageSyntaxError('parse-error', exp.start, exp.end);
125136
}
126137
}
127138
if (arg) {
128139
return annotation
129-
? { type: 'expression', arg, annotation }
130-
: { type: 'expression', arg };
140+
? { type: 'expression', arg, annotation, [cst]: exp }
141+
: { type: 'expression', arg, [cst]: exp };
131142
} else if (annotation) {
132-
return { type: 'expression', annotation };
143+
return { type: 'expression', annotation, [cst]: exp };
133144
}
134145
}
135-
throw new MessageSyntaxError('parse-error', cst.start, cst.end);
146+
throw new MessageSyntaxError('parse-error', exp.start, exp.end);
136147
}
137148

138-
const asOption = (cst: CST.Option): Model.Option => ({
139-
name: asName(cst.name),
140-
value: asValue(cst.value)
149+
const asOption = (option: CST.Option): Model.Option => ({
150+
name: asName(option.name),
151+
value: asValue(option.value),
152+
[cst]: option
141153
});
142154

143-
function asName(cst: CST.Identifier): string {
144-
switch (cst.length) {
155+
function asName(id: CST.Identifier): string {
156+
switch (id.length) {
145157
case 1:
146-
return cst[0].value;
158+
return id[0].value;
147159
case 3:
148-
return `${cst[0].value}:${cst[2].value}`;
160+
return `${id[0].value}:${id[2].value}`;
149161
default:
150162
throw new MessageSyntaxError(
151163
'parse-error',
152-
cst[0]?.start ?? -1,
153-
cst.at(-1)?.end ?? -1
164+
id[0]?.start ?? -1,
165+
id.at(-1)?.end ?? -1
154166
);
155167
}
156168
}
157169

158-
function asValue(cst: CST.Literal | CST.Junk): Model.Literal;
159-
function asValue(cst: CST.VariableRef | CST.Junk): Model.VariableRef;
170+
function asValue(value: CST.Literal | CST.Junk): Model.Literal;
171+
function asValue(value: CST.VariableRef | CST.Junk): Model.VariableRef;
160172
function asValue(
161-
cst: CST.Literal | CST.VariableRef | CST.Junk
173+
value: CST.Literal | CST.VariableRef | CST.Junk
162174
): Model.Literal | Model.VariableRef;
163175
function asValue(
164-
cst: CST.Literal | CST.VariableRef | CST.Junk
176+
value: CST.Literal | CST.VariableRef | CST.Junk
165177
): Model.Literal | Model.VariableRef {
166-
switch (cst.type) {
178+
switch (value.type) {
167179
case 'literal':
168-
return { type: 'literal', value: cst.value };
180+
return { type: 'literal', value: value.value, [cst]: value };
169181
case 'variable':
170-
return { type: 'variable', name: cst.name };
182+
return { type: 'variable', name: value.name, [cst]: value };
171183
default:
172-
throw new MessageSyntaxError('parse-error', cst.start, cst.end);
184+
throw new MessageSyntaxError('parse-error', value.start, value.end);
173185
}
174186
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import type * as CST from './cst-types.js';
22
export { CST };
3-
export { asDataModel } from './as-data-model.js';
3+
export { asDataModel, cst } from './as-data-model.js';
44
export { parseMessage } from './message.js';

packages/mf2-messageformat/src/data-model/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { cst } from '../cst-parser/as-data-model.js';
2+
import type * as CST from '../cst-parser/cst-types.js';
3+
14
/** A node in a message data model */
25
export type MessageNode =
36
| Declaration
@@ -30,6 +33,7 @@ export interface PatternMessage {
3033
declarations: Declaration[];
3134
pattern: Pattern;
3235
comment?: string;
36+
[cst]?: CST.SimpleMessage | CST.ComplexMessage;
3337
}
3438

3539
/**
@@ -48,12 +52,14 @@ export interface InputDeclaration {
4852
type: 'input';
4953
name: string;
5054
value: Expression<VariableRef>;
55+
[cst]?: CST.Declaration;
5156
}
5257

5358
export interface LocalDeclaration {
5459
type: 'local';
5560
name: string;
5661
value: Expression;
62+
[cst]?: CST.Declaration;
5763
}
5864

5965
export interface UnsupportedStatement {
@@ -63,6 +69,7 @@ export interface UnsupportedStatement {
6369
value?: never;
6470
body?: string;
6571
expressions: (Expression | Markup)[];
72+
[cst]?: CST.Declaration;
6673
}
6774

6875
/**
@@ -83,13 +90,15 @@ export interface SelectMessage {
8390
selectors: Expression[];
8491
variants: Variant[];
8592
comment?: string;
93+
[cst]?: CST.SelectMessage;
8694
}
8795

8896
/** @beta */
8997
export interface Variant {
9098
type?: never;
9199
keys: Array<Literal | CatchallKey>;
92100
value: Pattern;
101+
[cst]?: CST.Variant;
93102
}
94103

95104
/**
@@ -100,6 +109,7 @@ export interface Variant {
100109
export interface CatchallKey {
101110
type: '*';
102111
value?: string;
112+
[cst]?: CST.CatchallKey;
103113
}
104114

105115
/**
@@ -129,11 +139,13 @@ export type Expression<
129139
type: 'expression';
130140
arg: A;
131141
annotation?: FunctionAnnotation | UnsupportedAnnotation;
142+
[cst]?: CST.Expression;
132143
}
133144
: {
134145
type: 'expression';
135146
arg?: never;
136147
annotation: FunctionAnnotation | UnsupportedAnnotation;
148+
[cst]?: CST.Expression;
137149
};
138150

139151
/**
@@ -149,6 +161,7 @@ export type Expression<
149161
export interface Literal {
150162
type: 'literal';
151163
value: string;
164+
[cst]?: CST.Literal;
152165
}
153166

154167
/**
@@ -167,6 +180,7 @@ export interface Literal {
167180
export interface VariableRef {
168181
type: 'variable';
169182
name: string;
183+
[cst]?: CST.VariableRef;
170184
}
171185

172186
/**
@@ -185,6 +199,7 @@ export interface FunctionAnnotation {
185199
type: 'function';
186200
name: string;
187201
options?: Option[];
202+
[cst]?: CST.FunctionRef;
188203
}
189204

190205
/**
@@ -203,6 +218,7 @@ export interface UnsupportedAnnotation {
203218
source: string;
204219
name?: never;
205220
options?: never;
221+
[cst]?: CST.ReservedAnnotation;
206222
}
207223

208224
/**
@@ -225,20 +241,23 @@ export interface MarkupOpen {
225241
kind: 'open';
226242
name: string;
227243
options?: Option[];
244+
[cst]?: CST.Expression;
228245
}
229246

230247
export interface MarkupStandalone {
231248
type: 'markup';
232249
kind: 'standalone';
233250
name: string;
234251
options?: Option[];
252+
[cst]?: CST.Expression;
235253
}
236254

237255
export interface MarkupClose {
238256
type: 'markup';
239257
kind: 'close';
240258
name: string;
241259
options?: never;
260+
[cst]?: CST.Expression;
242261
}
243262

244263
/**
@@ -251,4 +270,5 @@ export interface Option {
251270
type?: never;
252271
name: string;
253272
value: Literal | VariableRef;
273+
[cst]?: CST.Option;
254274
}

packages/mf2-messageformat/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ export type * from './data-model/types.js';
22
export type * from './formatted-parts.js';
33
export type * from './functions/index.js';
44

5-
export { asDataModel, parseMessage, type CST } from './cst-parser/index.js';
5+
export {
6+
type CST,
7+
asDataModel,
8+
cst,
9+
parseMessage
10+
} from './cst-parser/index.js';
611
export {
712
isCatchallKey,
813
isExpression,

0 commit comments

Comments
 (0)