spex-parser 0.1.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.
@@ -0,0 +1,463 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { parseToAst } from '../src/visitor.js'
3
+ import type {
4
+ ObjectDeclaration,
5
+ ImportDeclaration,
6
+ ExportDeclaration,
7
+ GenerateDeclaration,
8
+ } from '../src/ast.js'
9
+
10
+ describe('SpexParserVisitor', () => {
11
+ describe('object declaration', () => {
12
+ it('should convert named object declaration to AST', () => {
13
+ const testCase = 'create MyObject as Number;'
14
+ const ast = parseToAst(testCase)
15
+ const decl = ast.declarations[0] as ObjectDeclaration
16
+ expect(decl).toEqual({
17
+ kind: 'ObjectDeclaration',
18
+ name: 'MyObject',
19
+ object: {
20
+ kind: 'NamedObject',
21
+ name: 'Number',
22
+ },
23
+ })
24
+ })
25
+
26
+ it('should convert product object declaration to AST', () => {
27
+ const testCase = 'create MyProduct as (n: Number, s: String);'
28
+ const ast = parseToAst(testCase)
29
+ const decl = ast.declarations[0] as ObjectDeclaration
30
+ expect(decl).toEqual({
31
+ kind: 'ObjectDeclaration',
32
+ name: 'MyProduct',
33
+ object: {
34
+ kind: 'ProductObject',
35
+ fields: {
36
+ n: { kind: 'NamedObject', name: 'Number' },
37
+ s: { kind: 'NamedObject', name: 'String' },
38
+ },
39
+ },
40
+ })
41
+ })
42
+
43
+ it('should convert product object declaration with trailing commas to AST', () => {
44
+ const testCase = 'create MyProduct as (n: Number, s: String,);'
45
+ const ast = parseToAst(testCase)
46
+ const decl = ast.declarations[0] as ObjectDeclaration
47
+ expect(decl).toEqual({
48
+ kind: 'ObjectDeclaration',
49
+ name: 'MyProduct',
50
+ object: {
51
+ kind: 'ProductObject',
52
+ fields: {
53
+ n: { kind: 'NamedObject', name: 'Number' },
54
+ s: { kind: 'NamedObject', name: 'String' },
55
+ },
56
+ },
57
+ })
58
+ })
59
+
60
+ it('should convert product object declaration with exponential objects to AST', () => {
61
+ const testCase = 'create MyProduct as (f: Number -> String, n: Number);'
62
+ const ast = parseToAst(testCase)
63
+ const decl = ast.declarations[0] as ObjectDeclaration
64
+ expect(decl).toEqual({
65
+ kind: 'ObjectDeclaration',
66
+ name: 'MyProduct',
67
+ object: {
68
+ kind: 'ProductObject',
69
+ fields: {
70
+ f: {
71
+ kind: 'ExponentialObject',
72
+ exponent: { kind: 'NamedObject', name: 'Number' },
73
+ base: { kind: 'NamedObject', name: 'String' },
74
+ },
75
+ n: { kind: 'NamedObject', name: 'Number' },
76
+ },
77
+ },
78
+ })
79
+ })
80
+
81
+ it('should convert product object declaration with subobjects to AST', () => {
82
+ const testCase =
83
+ 'create MyProduct as (p: from Number select { value is positive }, n: Number);'
84
+ const ast = parseToAst(testCase)
85
+ const decl = ast.declarations[0] as ObjectDeclaration
86
+ expect(decl).toEqual({
87
+ kind: 'ObjectDeclaration',
88
+ name: 'MyProduct',
89
+ object: {
90
+ kind: 'ProductObject',
91
+ fields: {
92
+ p: {
93
+ kind: 'SubObject',
94
+ base: { kind: 'NamedObject', name: 'Number' },
95
+ constraint: 'value is positive',
96
+ },
97
+ n: { kind: 'NamedObject', name: 'Number' },
98
+ },
99
+ },
100
+ })
101
+ })
102
+
103
+ it('should convert exponential object declaration with named object to AST', () => {
104
+ const testCase = 'create MyExponential as Number -> Unit;'
105
+ const ast = parseToAst(testCase)
106
+ const decl = ast.declarations[0] as ObjectDeclaration
107
+ expect(decl).toEqual({
108
+ kind: 'ObjectDeclaration',
109
+ name: 'MyExponential',
110
+ object: {
111
+ kind: 'ExponentialObject',
112
+ exponent: { kind: 'NamedObject', name: 'Number' },
113
+ base: { kind: 'NamedObject', name: 'Unit' },
114
+ },
115
+ })
116
+ })
117
+
118
+ it('should convert exponential object declaration with product objects to AST', () => {
119
+ const testCase = 'create MyExponential as (n: Number) -> (s: String);'
120
+ const ast = parseToAst(testCase)
121
+ const decl = ast.declarations[0] as ObjectDeclaration
122
+ expect(decl).toEqual({
123
+ kind: 'ObjectDeclaration',
124
+ name: 'MyExponential',
125
+ object: {
126
+ kind: 'ExponentialObject',
127
+ exponent: {
128
+ kind: 'ProductObject',
129
+ fields: {
130
+ n: { kind: 'NamedObject', name: 'Number' },
131
+ },
132
+ },
133
+ base: {
134
+ kind: 'ProductObject',
135
+ fields: {
136
+ s: { kind: 'NamedObject', name: 'String' },
137
+ },
138
+ },
139
+ },
140
+ })
141
+ })
142
+
143
+ it('should convert exponential object declaration with exponential objects to AST', () => {
144
+ const testCase = 'create MyExponential as (f: Number -> String, n: Number) -> String;'
145
+ const ast = parseToAst(testCase)
146
+ const decl = ast.declarations[0] as ObjectDeclaration
147
+ expect(decl).toEqual({
148
+ kind: 'ObjectDeclaration',
149
+ name: 'MyExponential',
150
+ object: {
151
+ kind: 'ExponentialObject',
152
+ exponent: {
153
+ kind: 'ProductObject',
154
+ fields: {
155
+ f: {
156
+ kind: 'ExponentialObject',
157
+ exponent: { kind: 'NamedObject', name: 'Number' },
158
+ base: { kind: 'NamedObject', name: 'String' },
159
+ },
160
+ n: { kind: 'NamedObject', name: 'Number' },
161
+ },
162
+ },
163
+ base: { kind: 'NamedObject', name: 'String' },
164
+ },
165
+ })
166
+ })
167
+
168
+ it('should convert exponential object declaration with subobjects to AST', () => {
169
+ const testCase =
170
+ 'create MyExponential as from Number select { value is positive } -> from Number select { value is positive };'
171
+ const ast = parseToAst(testCase)
172
+ const decl = ast.declarations[0] as ObjectDeclaration
173
+ expect(decl).toEqual({
174
+ kind: 'ObjectDeclaration',
175
+ name: 'MyExponential',
176
+ object: {
177
+ kind: 'ExponentialObject',
178
+ exponent: {
179
+ kind: 'SubObject',
180
+ base: { kind: 'NamedObject', name: 'Number' },
181
+ constraint: 'value is positive',
182
+ },
183
+ base: {
184
+ kind: 'SubObject',
185
+ base: { kind: 'NamedObject', name: 'Number' },
186
+ constraint: 'value is positive',
187
+ },
188
+ },
189
+ })
190
+ })
191
+
192
+ it('should convert subobject declaration with named objects to AST', () => {
193
+ const testCase = 'create PositiveNumber as from Number select { isPositive };'
194
+ const ast = parseToAst(testCase)
195
+ const decl = ast.declarations[0] as ObjectDeclaration
196
+ expect(decl).toEqual({
197
+ kind: 'ObjectDeclaration',
198
+ name: 'PositiveNumber',
199
+ object: {
200
+ kind: 'SubObject',
201
+ base: { kind: 'NamedObject', name: 'Number' },
202
+ constraint: 'isPositive',
203
+ },
204
+ })
205
+ })
206
+
207
+ it('should convert subobject declaration with text constraint to AST', () => {
208
+ const testCase = 'create PositiveNumber as from Number select { the number is positive };'
209
+ const ast = parseToAst(testCase)
210
+ const decl = ast.declarations[0] as ObjectDeclaration
211
+ expect(decl).toEqual({
212
+ kind: 'ObjectDeclaration',
213
+ name: 'PositiveNumber',
214
+ object: {
215
+ kind: 'SubObject',
216
+ base: { kind: 'NamedObject', name: 'Number' },
217
+ constraint: 'the number is positive',
218
+ },
219
+ })
220
+ })
221
+
222
+ it('should convert subobject declaration with product objects to AST', () => {
223
+ const testCase =
224
+ 'create MySubobject as from (n: Number, s: String) select { @n is positive };'
225
+ const ast = parseToAst(testCase)
226
+ const decl = ast.declarations[0] as ObjectDeclaration
227
+ expect(decl).toEqual({
228
+ kind: 'ObjectDeclaration',
229
+ name: 'MySubobject',
230
+ object: {
231
+ kind: 'SubObject',
232
+ base: {
233
+ kind: 'ProductObject',
234
+ fields: {
235
+ n: { kind: 'NamedObject', name: 'Number' },
236
+ s: { kind: 'NamedObject', name: 'String' },
237
+ },
238
+ },
239
+ constraint: '@n is positive',
240
+ },
241
+ })
242
+ })
243
+
244
+ it('should convert subobject declaration with exponential objects to AST', () => {
245
+ const testCase =
246
+ 'create MySubobject as from (n: Number, s: String) -> Bool select { logs the given input };'
247
+ const ast = parseToAst(testCase)
248
+ const decl = ast.declarations[0] as ObjectDeclaration
249
+ expect(decl).toEqual({
250
+ kind: 'ObjectDeclaration',
251
+ name: 'MySubobject',
252
+ object: {
253
+ kind: 'SubObject',
254
+ base: {
255
+ kind: 'ExponentialObject',
256
+ exponent: {
257
+ kind: 'ProductObject',
258
+ fields: {
259
+ n: { kind: 'NamedObject', name: 'Number' },
260
+ s: { kind: 'NamedObject', name: 'String' },
261
+ },
262
+ },
263
+ base: { kind: 'NamedObject', name: 'Bool' },
264
+ },
265
+ constraint: 'logs the given input',
266
+ },
267
+ })
268
+ })
269
+
270
+ it('should convert subobject declaration with subobjects to AST', () => {
271
+ const testCase =
272
+ 'create MySubobject as from from Number select { value is positive } select { value is odd };'
273
+ const ast = parseToAst(testCase)
274
+ const decl = ast.declarations[0] as ObjectDeclaration
275
+ expect(decl).toEqual({
276
+ kind: 'ObjectDeclaration',
277
+ name: 'MySubobject',
278
+ object: {
279
+ kind: 'SubObject',
280
+ base: {
281
+ kind: 'SubObject',
282
+ base: { kind: 'NamedObject', name: 'Number' },
283
+ constraint: 'value is positive',
284
+ },
285
+ constraint: 'value is odd',
286
+ },
287
+ })
288
+ })
289
+
290
+ it('should convert basic object string to AST', () => {
291
+ const testCase = 'create MyObject as string;'
292
+ const ast = parseToAst(testCase)
293
+ const decl = ast.declarations[0] as ObjectDeclaration
294
+ expect(decl).toEqual({
295
+ kind: 'ObjectDeclaration',
296
+ name: 'MyObject',
297
+ object: {
298
+ kind: 'NamedObject',
299
+ name: 'string',
300
+ },
301
+ })
302
+ })
303
+
304
+ it('should convert basic object number to AST', () => {
305
+ const testCase = 'create MyObject as number;'
306
+ const ast = parseToAst(testCase)
307
+ const decl = ast.declarations[0] as ObjectDeclaration
308
+ expect(decl).toEqual({
309
+ kind: 'ObjectDeclaration',
310
+ name: 'MyObject',
311
+ object: {
312
+ kind: 'NamedObject',
313
+ name: 'number',
314
+ },
315
+ })
316
+ })
317
+
318
+ it('should convert basic object bool to AST', () => {
319
+ const testCase = 'create MyObject as bool;'
320
+ const ast = parseToAst(testCase)
321
+ const decl = ast.declarations[0] as ObjectDeclaration
322
+ expect(decl).toEqual({
323
+ kind: 'ObjectDeclaration',
324
+ name: 'MyObject',
325
+ object: {
326
+ kind: 'NamedObject',
327
+ name: 'bool',
328
+ },
329
+ })
330
+ })
331
+
332
+ it('should convert basic object unit to AST', () => {
333
+ const testCase = 'create MyObject as unit;'
334
+ const ast = parseToAst(testCase)
335
+ const decl = ast.declarations[0] as ObjectDeclaration
336
+ expect(decl).toEqual({
337
+ kind: 'ObjectDeclaration',
338
+ name: 'MyObject',
339
+ object: {
340
+ kind: 'NamedObject',
341
+ name: 'unit',
342
+ },
343
+ })
344
+ })
345
+
346
+ it('should convert array type declaration to AST', () => {
347
+ const testCase = 'create MyArray as string[];'
348
+ const ast = parseToAst(testCase)
349
+ const decl = ast.declarations[0] as ObjectDeclaration
350
+ expect(decl).toEqual({
351
+ kind: 'ObjectDeclaration',
352
+ name: 'MyArray',
353
+ object: {
354
+ kind: 'ArrayObject',
355
+ base: { kind: 'NamedObject', name: 'string' },
356
+ },
357
+ })
358
+ })
359
+
360
+ it('should convert array of product type to AST', () => {
361
+ const testCase = 'create MyArray as (n: Number)[];'
362
+ const ast = parseToAst(testCase)
363
+ const decl = ast.declarations[0] as ObjectDeclaration
364
+ expect(decl).toEqual({
365
+ kind: 'ObjectDeclaration',
366
+ name: 'MyArray',
367
+ object: {
368
+ kind: 'ArrayObject',
369
+ base: {
370
+ kind: 'ProductObject',
371
+ fields: {
372
+ n: { kind: 'NamedObject', name: 'Number' },
373
+ },
374
+ },
375
+ },
376
+ })
377
+ })
378
+
379
+ it('should convert dotted name to AST', () => {
380
+ const testCase =
381
+ 'create SignUp as (user: types.EmailAddress, pass: types.Password) -> string;'
382
+ const ast = parseToAst(testCase)
383
+ const decl = ast.declarations[0] as ObjectDeclaration
384
+ expect(decl).toEqual({
385
+ kind: 'ObjectDeclaration',
386
+ name: 'SignUp',
387
+ object: {
388
+ kind: 'ExponentialObject',
389
+ exponent: {
390
+ kind: 'ProductObject',
391
+ fields: {
392
+ user: { kind: 'NamedObject', name: 'types.EmailAddress' },
393
+ pass: { kind: 'NamedObject', name: 'types.Password' },
394
+ },
395
+ },
396
+ base: { kind: 'NamedObject', name: 'string' },
397
+ },
398
+ })
399
+ })
400
+ })
401
+
402
+ describe('import declaration', () => {
403
+ it('should convert named import to AST', () => {
404
+ const testCase = 'import EmailAddress from "types.spex";'
405
+ const ast = parseToAst(testCase)
406
+ const decl = ast.declarations[0] as ImportDeclaration
407
+ expect(decl).toEqual({
408
+ kind: 'ImportDeclaration',
409
+ name: 'EmailAddress',
410
+ source: 'types.spex',
411
+ alias: null,
412
+ })
413
+ })
414
+
415
+ it('should convert named import with alias to AST', () => {
416
+ const testCase = 'import EmailAddress from "types.spex" as Username;'
417
+ const ast = parseToAst(testCase)
418
+ const decl = ast.declarations[0] as ImportDeclaration
419
+ expect(decl).toEqual({
420
+ kind: 'ImportDeclaration',
421
+ name: 'EmailAddress',
422
+ source: 'types.spex',
423
+ alias: 'Username',
424
+ })
425
+ })
426
+
427
+ it('should convert module import to AST', () => {
428
+ const testCase = 'import "types.spex" as types;'
429
+ const ast = parseToAst(testCase)
430
+ const decl = ast.declarations[0] as ImportDeclaration
431
+ expect(decl).toEqual({
432
+ kind: 'ImportDeclaration',
433
+ name: null,
434
+ source: 'types.spex',
435
+ alias: 'types',
436
+ })
437
+ })
438
+ })
439
+
440
+ describe('export declaration', () => {
441
+ it('should convert export declaration to AST', () => {
442
+ const testCase = 'export EmailAddress;'
443
+ const ast = parseToAst(testCase)
444
+ const decl = ast.declarations[0] as ExportDeclaration
445
+ expect(decl).toEqual({
446
+ kind: 'ExportDeclaration',
447
+ name: 'EmailAddress',
448
+ })
449
+ })
450
+ })
451
+
452
+ describe('generate declaration', () => {
453
+ it('should convert generate declaration to AST', () => {
454
+ const testCase = 'generate Main;'
455
+ const ast = parseToAst(testCase)
456
+ const decl = ast.declarations[0] as GenerateDeclaration
457
+ expect(decl).toEqual({
458
+ kind: 'GenerateDeclaration',
459
+ name: 'Main',
460
+ })
461
+ })
462
+ })
463
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ "compilerOptions": {
4
+ // File Layout
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+
8
+ // Environment Settings
9
+ // See also https://aka.ms/tsconfig/module
10
+ "module": "nodenext",
11
+ "target": "es2022",
12
+ "types": ["node"],
13
+ "moduleResolution": "nodenext",
14
+ // For nodejs:
15
+ "lib": ["ES2022"],
16
+ // "types": ["node"],
17
+ // and npm install -D @types/node
18
+
19
+ // Other Outputs
20
+ "sourceMap": true,
21
+ "declaration": true,
22
+ "declarationMap": true,
23
+
24
+ // Stricter Typechecking Options
25
+ "noUncheckedIndexedAccess": true,
26
+ "exactOptionalPropertyTypes": true,
27
+
28
+ // Style Options
29
+ // "noImplicitReturns": true,
30
+ // "noImplicitOverride": true,
31
+ // "noUnusedLocals": true,
32
+ // "noUnusedParameters": true,
33
+ // "noFallthroughCasesInSwitch": true,
34
+ // "noPropertyAccessFromIndexSignature": true,
35
+
36
+ // Recommended Options
37
+ "strict": true,
38
+ "jsx": "react-jsx",
39
+ "verbatimModuleSyntax": true,
40
+ "isolatedModules": true,
41
+ "noUncheckedSideEffectImports": true,
42
+ "moduleDetection": "force",
43
+ "skipLibCheck": true,
44
+ },
45
+ "include": ["src/**/*"],
46
+ "exclude": ["dist/**/*"],
47
+ }