spex-parser 0.6.2 → 0.7.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.
- package/README.md +428 -226
- package/dist/ast.d.ts +22 -36
- package/dist/ast.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lexer.d.ts +3 -8
- package/dist/lexer.d.ts.map +1 -1
- package/dist/lexer.js +21 -46
- package/dist/lexer.js.map +1 -1
- package/dist/parser.d.ts +0 -4
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +7 -49
- package/dist/parser.js.map +1 -1
- package/dist/visitor.d.ts +1 -5
- package/dist/visitor.d.ts.map +1 -1
- package/dist/visitor.js +48 -83
- package/dist/visitor.js.map +1 -1
- package/package.json +1 -1
- package/src/ast.ts +38 -44
- package/src/index.ts +3 -7
- package/src/lexer.ts +21 -44
- package/src/parser.ts +7 -58
- package/src/visitor.ts +50 -89
- package/tests/constants.test.ts +0 -4
- package/tests/e2e.test.ts +9 -9
- package/tests/lexer.test.ts +10 -31
- package/tests/parser.test.ts +31 -181
- package/tests/props/express_todo_web.spex +1 -2
- package/tests/props/express_web_env.spex +0 -2
- package/tests/props/flask_todo_web.spex +1 -2
- package/tests/props/flask_web_env.spex +0 -2
- package/tests/props/python_cli_env.spex +0 -2
- package/tests/props/python_todo_cli.spex +1 -2
- package/tests/props/todo.spex +0 -17
- package/tests/props/typescript_cli_env.spex +0 -2
- package/tests/props/typescript_todo_cli.spex +1 -2
- package/tests/visitor.test.ts +99 -265
package/tests/visitor.test.ts
CHANGED
|
@@ -3,20 +3,16 @@ import { parseToAst, parseConstraint } from '../src/visitor.js'
|
|
|
3
3
|
import type {
|
|
4
4
|
ObjectDeclaration,
|
|
5
5
|
ImportDeclaration,
|
|
6
|
-
ExportDeclaration,
|
|
7
6
|
GenerateDeclaration,
|
|
8
|
-
PackageDeclaration,
|
|
9
7
|
RealizeDeclaration,
|
|
10
8
|
IncludeDeclaration,
|
|
11
|
-
ExponentialPattern,
|
|
12
|
-
EnumObject,
|
|
13
9
|
Constraint,
|
|
14
10
|
} from '../src/ast.js'
|
|
15
11
|
|
|
16
12
|
describe('SpexParserVisitor', () => {
|
|
17
13
|
describe('lexing errors', () => {
|
|
18
14
|
it('should throw on unexpected characters', () => {
|
|
19
|
-
expect(() => parseToAst('
|
|
15
|
+
expect(() => parseToAst('create Foo as spex-parser;')).toThrow(
|
|
20
16
|
'Lexing errors: unexpected character: ->-<'
|
|
21
17
|
)
|
|
22
18
|
})
|
|
@@ -115,6 +111,7 @@ describe('SpexParserVisitor', () => {
|
|
|
115
111
|
kind: 'SubObject',
|
|
116
112
|
base: { kind: 'NamedObject', name: 'Number' },
|
|
117
113
|
constraint: {
|
|
114
|
+
type: 'NaturalLanguage',
|
|
118
115
|
raw: 'value is positive',
|
|
119
116
|
parts: [{ kind: 'ConstraintText', text: 'value is positive' }],
|
|
120
117
|
},
|
|
@@ -204,6 +201,7 @@ describe('SpexParserVisitor', () => {
|
|
|
204
201
|
kind: 'SubObject',
|
|
205
202
|
base: { kind: 'NamedObject', name: 'Number' },
|
|
206
203
|
constraint: {
|
|
204
|
+
type: 'NaturalLanguage',
|
|
207
205
|
raw: 'value is positive',
|
|
208
206
|
parts: [{ kind: 'ConstraintText', text: 'value is positive' }],
|
|
209
207
|
},
|
|
@@ -212,6 +210,7 @@ describe('SpexParserVisitor', () => {
|
|
|
212
210
|
kind: 'SubObject',
|
|
213
211
|
base: { kind: 'NamedObject', name: 'Number' },
|
|
214
212
|
constraint: {
|
|
213
|
+
type: 'NaturalLanguage',
|
|
215
214
|
raw: 'value is positive',
|
|
216
215
|
parts: [{ kind: 'ConstraintText', text: 'value is positive' }],
|
|
217
216
|
},
|
|
@@ -231,6 +230,7 @@ describe('SpexParserVisitor', () => {
|
|
|
231
230
|
kind: 'SubObject',
|
|
232
231
|
base: { kind: 'NamedObject', name: 'Number' },
|
|
233
232
|
constraint: {
|
|
233
|
+
type: 'NaturalLanguage',
|
|
234
234
|
raw: 'isPositive',
|
|
235
235
|
parts: [{ kind: 'ConstraintText', text: 'isPositive' }],
|
|
236
236
|
},
|
|
@@ -254,6 +254,7 @@ describe('SpexParserVisitor', () => {
|
|
|
254
254
|
right: { kind: 'NamedObject', name: 'TypeScript' },
|
|
255
255
|
},
|
|
256
256
|
constraint: {
|
|
257
|
+
type: 'NaturalLanguage',
|
|
257
258
|
raw: 'is an express app',
|
|
258
259
|
parts: [{ kind: 'ConstraintText', text: 'is an express app' }],
|
|
259
260
|
},
|
|
@@ -272,6 +273,7 @@ describe('SpexParserVisitor', () => {
|
|
|
272
273
|
kind: 'SubObject',
|
|
273
274
|
base: { kind: 'NamedObject', name: 'Number' },
|
|
274
275
|
constraint: {
|
|
276
|
+
type: 'NaturalLanguage',
|
|
275
277
|
raw: 'the number is positive',
|
|
276
278
|
parts: [{ kind: 'ConstraintText', text: 'the number is positive' }],
|
|
277
279
|
},
|
|
@@ -297,9 +299,10 @@ describe('SpexParserVisitor', () => {
|
|
|
297
299
|
},
|
|
298
300
|
},
|
|
299
301
|
constraint: {
|
|
302
|
+
type: 'NaturalLanguage',
|
|
300
303
|
raw: '@n is positive',
|
|
301
304
|
parts: [
|
|
302
|
-
{ kind: '
|
|
305
|
+
{ kind: 'ReferenceDirective', name: 'n' },
|
|
303
306
|
{ kind: 'ConstraintText', text: ' is positive' },
|
|
304
307
|
],
|
|
305
308
|
},
|
|
@@ -329,6 +332,7 @@ describe('SpexParserVisitor', () => {
|
|
|
329
332
|
base: { kind: 'NamedObject', name: 'Bool' },
|
|
330
333
|
},
|
|
331
334
|
constraint: {
|
|
335
|
+
type: 'NaturalLanguage',
|
|
332
336
|
raw: 'logs the given input',
|
|
333
337
|
parts: [{ kind: 'ConstraintText', text: 'logs the given input' }],
|
|
334
338
|
},
|
|
@@ -350,11 +354,13 @@ describe('SpexParserVisitor', () => {
|
|
|
350
354
|
kind: 'SubObject',
|
|
351
355
|
base: { kind: 'NamedObject', name: 'Number' },
|
|
352
356
|
constraint: {
|
|
357
|
+
type: 'NaturalLanguage',
|
|
353
358
|
raw: 'value is positive',
|
|
354
359
|
parts: [{ kind: 'ConstraintText', text: 'value is positive' }],
|
|
355
360
|
},
|
|
356
361
|
},
|
|
357
362
|
constraint: {
|
|
363
|
+
type: 'NaturalLanguage',
|
|
358
364
|
raw: 'value is odd',
|
|
359
365
|
parts: [{ kind: 'ConstraintText', text: 'value is odd' }],
|
|
360
366
|
},
|
|
@@ -446,6 +452,20 @@ describe('SpexParserVisitor', () => {
|
|
|
446
452
|
})
|
|
447
453
|
})
|
|
448
454
|
|
|
455
|
+
it('should convert basic object artifact to AST', () => {
|
|
456
|
+
const testCase = 'create MyObject as artifact;'
|
|
457
|
+
const ast = parseToAst(testCase)
|
|
458
|
+
const decl = ast.declarations[0] as ObjectDeclaration
|
|
459
|
+
expect(decl).toEqual({
|
|
460
|
+
kind: 'ObjectDeclaration',
|
|
461
|
+
name: 'MyObject',
|
|
462
|
+
object: {
|
|
463
|
+
kind: 'NamedObject',
|
|
464
|
+
name: 'artifact',
|
|
465
|
+
},
|
|
466
|
+
})
|
|
467
|
+
})
|
|
468
|
+
|
|
449
469
|
it('should convert array type declaration to AST', () => {
|
|
450
470
|
const testCase = 'create MyArray as string[];'
|
|
451
471
|
const ast = parseToAst(testCase)
|
|
@@ -559,18 +579,6 @@ describe('SpexParserVisitor', () => {
|
|
|
559
579
|
})
|
|
560
580
|
})
|
|
561
581
|
|
|
562
|
-
describe('export declaration', () => {
|
|
563
|
-
it('should convert export declaration to AST', () => {
|
|
564
|
-
const testCase = 'export EmailAddress;'
|
|
565
|
-
const ast = parseToAst(testCase)
|
|
566
|
-
const decl = ast.declarations[0] as ExportDeclaration
|
|
567
|
-
expect(decl).toEqual({
|
|
568
|
-
kind: 'ExportDeclaration',
|
|
569
|
-
name: 'EmailAddress',
|
|
570
|
-
})
|
|
571
|
-
})
|
|
572
|
-
})
|
|
573
|
-
|
|
574
582
|
describe('generate declaration', () => {
|
|
575
583
|
it('should convert generate declaration to AST', () => {
|
|
576
584
|
const testCase = 'generate Main;'
|
|
@@ -579,76 +587,18 @@ describe('SpexParserVisitor', () => {
|
|
|
579
587
|
expect(decl).toEqual({
|
|
580
588
|
kind: 'GenerateDeclaration',
|
|
581
589
|
name: 'Main',
|
|
582
|
-
|
|
583
|
-
})
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
describe('enum object', () => {
|
|
587
|
-
it('should convert enum object declaration to AST', () => {
|
|
588
|
-
const testCase = "create myEnum as enum ('v1', 'v2');"
|
|
589
|
-
const ast = parseToAst(testCase)
|
|
590
|
-
const decl = ast.declarations[0] as ObjectDeclaration
|
|
591
|
-
expect(decl).toEqual({
|
|
592
|
-
kind: 'ObjectDeclaration',
|
|
593
|
-
name: 'myEnum',
|
|
594
|
-
object: {
|
|
595
|
-
kind: 'EnumObject',
|
|
596
|
-
values: ['v1', 'v2'],
|
|
597
|
-
},
|
|
598
|
-
})
|
|
599
|
-
})
|
|
600
|
-
|
|
601
|
-
it('should convert single-value enum object to AST', () => {
|
|
602
|
-
const testCase = "create Status as enum ('ACTIVE');"
|
|
603
|
-
const ast = parseToAst(testCase)
|
|
604
|
-
const decl = ast.declarations[0] as ObjectDeclaration
|
|
605
|
-
expect(decl).toEqual({
|
|
606
|
-
kind: 'ObjectDeclaration',
|
|
607
|
-
name: 'Status',
|
|
608
|
-
object: {
|
|
609
|
-
kind: 'EnumObject',
|
|
610
|
-
values: ['ACTIVE'],
|
|
611
|
-
},
|
|
612
|
-
})
|
|
613
|
-
})
|
|
614
|
-
|
|
615
|
-
it('should convert enum object with mixed quote values to AST', () => {
|
|
616
|
-
const testCase = "create myEnum as enum (\"v1\", 'v2');"
|
|
617
|
-
const ast = parseToAst(testCase)
|
|
618
|
-
const decl = ast.declarations[0] as ObjectDeclaration
|
|
619
|
-
expect(decl).toEqual({
|
|
620
|
-
kind: 'ObjectDeclaration',
|
|
621
|
-
name: 'myEnum',
|
|
622
|
-
object: {
|
|
623
|
-
kind: 'EnumObject',
|
|
624
|
-
values: ['v1', 'v2'],
|
|
625
|
-
},
|
|
590
|
+
environment: { kind: 'NamedObject', name: 'environment' },
|
|
626
591
|
})
|
|
627
592
|
})
|
|
628
593
|
|
|
629
|
-
it('should convert
|
|
630
|
-
const testCase =
|
|
594
|
+
it('should convert generate declaration with an environment to AST', () => {
|
|
595
|
+
const testCase = 'generate Main in Python;'
|
|
631
596
|
const ast = parseToAst(testCase)
|
|
632
|
-
const decl = ast.declarations[0] as
|
|
597
|
+
const decl = ast.declarations[0] as GenerateDeclaration
|
|
633
598
|
expect(decl).toEqual({
|
|
634
|
-
kind: '
|
|
635
|
-
name: '
|
|
636
|
-
|
|
637
|
-
kind: 'EnumObject',
|
|
638
|
-
values: ["it's", 'a "b"', 'a\\b'],
|
|
639
|
-
},
|
|
640
|
-
})
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
it('should convert enum object inside a product object to AST', () => {
|
|
644
|
-
const testCase = "create Config as (kind: enum ('a', 'b'));"
|
|
645
|
-
const ast = parseToAst(testCase)
|
|
646
|
-
const decl = ast.declarations[0] as ObjectDeclaration
|
|
647
|
-
expect(decl.object).toEqual({
|
|
648
|
-
kind: 'ProductObject',
|
|
649
|
-
fields: {
|
|
650
|
-
kind: { kind: 'EnumObject', values: ['a', 'b'] },
|
|
651
|
-
},
|
|
599
|
+
kind: 'GenerateDeclaration',
|
|
600
|
+
name: 'Main',
|
|
601
|
+
environment: { kind: 'NamedObject', name: 'Python' },
|
|
652
602
|
})
|
|
653
603
|
})
|
|
654
604
|
})
|
|
@@ -1082,7 +1032,7 @@ describe('SpexParserVisitor', () => {
|
|
|
1082
1032
|
expect(result).toEqual<Constraint>({
|
|
1083
1033
|
raw: '@n is positive',
|
|
1084
1034
|
parts: [
|
|
1085
|
-
{ kind: '
|
|
1035
|
+
{ kind: 'ReferenceDirective', name: 'n' },
|
|
1086
1036
|
{ kind: 'ConstraintText', text: ' is positive' },
|
|
1087
1037
|
],
|
|
1088
1038
|
})
|
|
@@ -1094,7 +1044,7 @@ describe('SpexParserVisitor', () => {
|
|
|
1094
1044
|
raw: 'use @path for storage',
|
|
1095
1045
|
parts: [
|
|
1096
1046
|
{ kind: 'ConstraintText', text: 'use ' },
|
|
1097
|
-
{ kind: '
|
|
1047
|
+
{ kind: 'ReferenceDirective', name: 'path' },
|
|
1098
1048
|
{ kind: 'ConstraintText', text: ' for storage' },
|
|
1099
1049
|
],
|
|
1100
1050
|
})
|
|
@@ -1106,9 +1056,9 @@ describe('SpexParserVisitor', () => {
|
|
|
1106
1056
|
raw: 'return @z.real^2 + @z.imag^2',
|
|
1107
1057
|
parts: [
|
|
1108
1058
|
{ kind: 'ConstraintText', text: 'return ' },
|
|
1109
|
-
{ kind: '
|
|
1059
|
+
{ kind: 'ReferenceDirective', name: 'z.real' },
|
|
1110
1060
|
{ kind: 'ConstraintText', text: '^2 + ' },
|
|
1111
|
-
{ kind: '
|
|
1061
|
+
{ kind: 'ReferenceDirective', name: 'z.imag' },
|
|
1112
1062
|
{ kind: 'ConstraintText', text: '^2' },
|
|
1113
1063
|
],
|
|
1114
1064
|
})
|
|
@@ -1120,9 +1070,9 @@ describe('SpexParserVisitor', () => {
|
|
|
1120
1070
|
raw: 'call @LoadTodos using @path',
|
|
1121
1071
|
parts: [
|
|
1122
1072
|
{ kind: 'ConstraintText', text: 'call ' },
|
|
1123
|
-
{ kind: '
|
|
1073
|
+
{ kind: 'ReferenceDirective', name: 'LoadTodos' },
|
|
1124
1074
|
{ kind: 'ConstraintText', text: ' using ' },
|
|
1125
|
-
{ kind: '
|
|
1075
|
+
{ kind: 'ReferenceDirective', name: 'path' },
|
|
1126
1076
|
],
|
|
1127
1077
|
})
|
|
1128
1078
|
})
|
|
@@ -1132,7 +1082,7 @@ describe('SpexParserVisitor', () => {
|
|
|
1132
1082
|
expect(result).toEqual<Constraint>({
|
|
1133
1083
|
raw: '@validate the input',
|
|
1134
1084
|
parts: [
|
|
1135
|
-
{ kind: '
|
|
1085
|
+
{ kind: 'ReferenceDirective', name: 'validate' },
|
|
1136
1086
|
{ kind: 'ConstraintText', text: ' the input' },
|
|
1137
1087
|
],
|
|
1138
1088
|
})
|
|
@@ -1151,7 +1101,7 @@ describe('SpexParserVisitor', () => {
|
|
|
1151
1101
|
expect(result).toEqual<Constraint>({
|
|
1152
1102
|
raw: '@todo_item is valid',
|
|
1153
1103
|
parts: [
|
|
1154
|
-
{ kind: '
|
|
1104
|
+
{ kind: 'ReferenceDirective', name: 'todo_item' },
|
|
1155
1105
|
{ kind: 'ConstraintText', text: ' is valid' },
|
|
1156
1106
|
],
|
|
1157
1107
|
})
|
|
@@ -1180,6 +1130,7 @@ describe('SpexParserVisitor', () => {
|
|
|
1180
1130
|
kind: 'SubObject',
|
|
1181
1131
|
base: { kind: 'NamedObject', name: 'string' },
|
|
1182
1132
|
constraint: {
|
|
1133
|
+
type: 'NaturalLanguage',
|
|
1183
1134
|
raw: 'are valid -- like emails',
|
|
1184
1135
|
parts: [{ kind: 'ConstraintText', text: 'are valid -- like emails' }],
|
|
1185
1136
|
},
|
|
@@ -1194,10 +1145,11 @@ describe('SpexParserVisitor', () => {
|
|
|
1194
1145
|
kind: 'SubObject',
|
|
1195
1146
|
base: { kind: 'NamedObject', name: 'string' },
|
|
1196
1147
|
constraint: {
|
|
1148
|
+
type: 'NaturalLanguage',
|
|
1197
1149
|
raw: 'match /* strict */ @pattern',
|
|
1198
1150
|
parts: [
|
|
1199
1151
|
{ kind: 'ConstraintText', text: 'match /* strict */ ' },
|
|
1200
|
-
{ kind: '
|
|
1152
|
+
{ kind: 'ReferenceDirective', name: 'pattern' },
|
|
1201
1153
|
],
|
|
1202
1154
|
},
|
|
1203
1155
|
})
|
|
@@ -1213,6 +1165,7 @@ describe('SpexParserVisitor', () => {
|
|
|
1213
1165
|
kind: 'SubObject',
|
|
1214
1166
|
base: { kind: 'NamedObject', name: 'string' },
|
|
1215
1167
|
constraint: {
|
|
1168
|
+
type: 'NaturalLanguage',
|
|
1216
1169
|
raw: 'end with }',
|
|
1217
1170
|
parts: [{ kind: 'ConstraintText', text: 'end with }' }],
|
|
1218
1171
|
},
|
|
@@ -1227,6 +1180,7 @@ describe('SpexParserVisitor', () => {
|
|
|
1227
1180
|
kind: 'SubObject',
|
|
1228
1181
|
base: { kind: 'NamedObject', name: 'string' },
|
|
1229
1182
|
constraint: {
|
|
1183
|
+
type: 'NaturalLanguage',
|
|
1230
1184
|
raw: 'match {a}',
|
|
1231
1185
|
parts: [{ kind: 'ConstraintText', text: 'match {a}' }],
|
|
1232
1186
|
},
|
|
@@ -1241,6 +1195,7 @@ describe('SpexParserVisitor', () => {
|
|
|
1241
1195
|
kind: 'SubObject',
|
|
1242
1196
|
base: { kind: 'NamedObject', name: 'string' },
|
|
1243
1197
|
constraint: {
|
|
1198
|
+
type: 'NaturalLanguage',
|
|
1244
1199
|
raw: 'paths use \\',
|
|
1245
1200
|
parts: [{ kind: 'ConstraintText', text: 'paths use \\' }],
|
|
1246
1201
|
},
|
|
@@ -1255,10 +1210,11 @@ describe('SpexParserVisitor', () => {
|
|
|
1255
1210
|
kind: 'SubObject',
|
|
1256
1211
|
base: { kind: 'NamedObject', name: 'string' },
|
|
1257
1212
|
constraint: {
|
|
1213
|
+
type: 'NaturalLanguage',
|
|
1258
1214
|
raw: 'call @foo with }',
|
|
1259
1215
|
parts: [
|
|
1260
1216
|
{ kind: 'ConstraintText', text: 'call ' },
|
|
1261
|
-
{ kind: '
|
|
1217
|
+
{ kind: 'ReferenceDirective', name: 'foo' },
|
|
1262
1218
|
{ kind: 'ConstraintText', text: ' with }' },
|
|
1263
1219
|
],
|
|
1264
1220
|
},
|
|
@@ -1266,101 +1222,6 @@ describe('SpexParserVisitor', () => {
|
|
|
1266
1222
|
})
|
|
1267
1223
|
})
|
|
1268
1224
|
|
|
1269
|
-
describe('package declaration', () => {
|
|
1270
|
-
it('should convert package executable declaration to AST', () => {
|
|
1271
|
-
const testCase = 'package executable myapp as Main in Python;'
|
|
1272
|
-
const ast = parseToAst(testCase)
|
|
1273
|
-
const decl = ast.declarations[0] as PackageDeclaration
|
|
1274
|
-
expect(decl).toEqual({
|
|
1275
|
-
kind: 'PackageDeclaration',
|
|
1276
|
-
packageType: 'EXECUTABLE',
|
|
1277
|
-
name: 'myapp',
|
|
1278
|
-
objectName: { kind: 'NamedObject', name: 'Main' },
|
|
1279
|
-
environment: { kind: 'NamedObject', name: 'Python' },
|
|
1280
|
-
})
|
|
1281
|
-
})
|
|
1282
|
-
|
|
1283
|
-
it('should convert package module declaration to AST', () => {
|
|
1284
|
-
const testCase = 'package module mylib as utils in Node;'
|
|
1285
|
-
const ast = parseToAst(testCase)
|
|
1286
|
-
const decl = ast.declarations[0] as PackageDeclaration
|
|
1287
|
-
expect(decl).toEqual({
|
|
1288
|
-
kind: 'PackageDeclaration',
|
|
1289
|
-
packageType: 'MODULE',
|
|
1290
|
-
name: 'mylib',
|
|
1291
|
-
objectName: { kind: 'NamedObject', name: 'utils' },
|
|
1292
|
-
environment: { kind: 'NamedObject', name: 'Node' },
|
|
1293
|
-
})
|
|
1294
|
-
})
|
|
1295
|
-
|
|
1296
|
-
it('should convert package executable with complex object to AST', () => {
|
|
1297
|
-
const testCase = 'package executable cli as (path: string) -> unit in Python;'
|
|
1298
|
-
const ast = parseToAst(testCase)
|
|
1299
|
-
const decl = ast.declarations[0] as PackageDeclaration
|
|
1300
|
-
expect(decl).toEqual({
|
|
1301
|
-
kind: 'PackageDeclaration',
|
|
1302
|
-
packageType: 'EXECUTABLE',
|
|
1303
|
-
name: 'cli',
|
|
1304
|
-
objectName: {
|
|
1305
|
-
kind: 'ExponentialObject',
|
|
1306
|
-
exponent: {
|
|
1307
|
-
kind: 'ProductObject',
|
|
1308
|
-
fields: { path: { kind: 'NamedObject', name: 'string' } },
|
|
1309
|
-
},
|
|
1310
|
-
base: { kind: 'NamedObject', name: 'unit' },
|
|
1311
|
-
},
|
|
1312
|
-
environment: { kind: 'NamedObject', name: 'Python' },
|
|
1313
|
-
})
|
|
1314
|
-
})
|
|
1315
|
-
|
|
1316
|
-
it('should convert package executable with dotted name to AST', () => {
|
|
1317
|
-
const testCase = 'package executable myapp as app.Main in Python;'
|
|
1318
|
-
const ast = parseToAst(testCase)
|
|
1319
|
-
const decl = ast.declarations[0] as PackageDeclaration
|
|
1320
|
-
expect(decl).toEqual({
|
|
1321
|
-
kind: 'PackageDeclaration',
|
|
1322
|
-
packageType: 'EXECUTABLE',
|
|
1323
|
-
name: 'myapp',
|
|
1324
|
-
objectName: { kind: 'NamedObject', name: 'app.Main' },
|
|
1325
|
-
environment: { kind: 'NamedObject', name: 'Python' },
|
|
1326
|
-
})
|
|
1327
|
-
})
|
|
1328
|
-
|
|
1329
|
-
it('should convert package executable with array type to AST', () => {
|
|
1330
|
-
const testCase = 'package module mylib as string[] in Python;'
|
|
1331
|
-
const ast = parseToAst(testCase)
|
|
1332
|
-
const decl = ast.declarations[0] as PackageDeclaration
|
|
1333
|
-
expect(decl).toEqual({
|
|
1334
|
-
kind: 'PackageDeclaration',
|
|
1335
|
-
packageType: 'MODULE',
|
|
1336
|
-
name: 'mylib',
|
|
1337
|
-
objectName: { kind: 'ArrayObject', base: { kind: 'NamedObject', name: 'string' } },
|
|
1338
|
-
environment: { kind: 'NamedObject', name: 'Python' },
|
|
1339
|
-
})
|
|
1340
|
-
})
|
|
1341
|
-
|
|
1342
|
-
it('should convert mixed declarations with package to AST', () => {
|
|
1343
|
-
const testCase =
|
|
1344
|
-
'create Main as Number;\npackage executable myapp as Main in Python;'
|
|
1345
|
-
const ast = parseToAst(testCase)
|
|
1346
|
-
expect(ast.declarations).toHaveLength(2)
|
|
1347
|
-
const createDecl = ast.declarations[0] as ObjectDeclaration
|
|
1348
|
-
expect(createDecl).toEqual({
|
|
1349
|
-
kind: 'ObjectDeclaration',
|
|
1350
|
-
name: 'Main',
|
|
1351
|
-
object: { kind: 'NamedObject', name: 'Number' },
|
|
1352
|
-
})
|
|
1353
|
-
const packageDecl = ast.declarations[1] as PackageDeclaration
|
|
1354
|
-
expect(packageDecl).toEqual({
|
|
1355
|
-
kind: 'PackageDeclaration',
|
|
1356
|
-
packageType: 'EXECUTABLE',
|
|
1357
|
-
name: 'myapp',
|
|
1358
|
-
objectName: { kind: 'NamedObject', name: 'Main' },
|
|
1359
|
-
environment: { kind: 'NamedObject', name: 'Python' },
|
|
1360
|
-
})
|
|
1361
|
-
})
|
|
1362
|
-
})
|
|
1363
|
-
|
|
1364
1225
|
describe('realize declaration', () => {
|
|
1365
1226
|
it('should convert a realize declaration to AST', () => {
|
|
1366
1227
|
const testCase = 'realize Shape as Circle in environment;'
|
|
@@ -1479,103 +1340,76 @@ describe('SpexParserVisitor', () => {
|
|
|
1479
1340
|
})
|
|
1480
1341
|
})
|
|
1481
1342
|
|
|
1482
|
-
describe('
|
|
1483
|
-
it('should convert
|
|
1484
|
-
const testCase = 'create
|
|
1343
|
+
describe('subobject with structured and code constraints', () => {
|
|
1344
|
+
it('should convert subobject with a structured constraint to AST', () => {
|
|
1345
|
+
const testCase = 'create double as from number -> number select ```\nreturn n * 2\n```;'
|
|
1485
1346
|
const ast = parseToAst(testCase)
|
|
1486
1347
|
const decl = ast.declarations[0] as ObjectDeclaration
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
kind: '
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1348
|
+
expect(decl.object).toEqual({
|
|
1349
|
+
kind: 'SubObject',
|
|
1350
|
+
base: {
|
|
1351
|
+
kind: 'ExponentialObject',
|
|
1352
|
+
exponent: { kind: 'NamedObject', name: 'number' },
|
|
1353
|
+
base: { kind: 'NamedObject', name: 'number' },
|
|
1354
|
+
},
|
|
1355
|
+
constraint: {
|
|
1356
|
+
type: 'Structured',
|
|
1357
|
+
raw: 'return n * 2',
|
|
1358
|
+
parts: [{ kind: 'ConstraintText', text: 'return n * 2' }],
|
|
1497
1359
|
},
|
|
1498
|
-
language: 'python',
|
|
1499
|
-
body: 'return a + b',
|
|
1500
|
-
patterns: [],
|
|
1501
|
-
})
|
|
1502
|
-
})
|
|
1503
|
-
|
|
1504
|
-
it('should convert lambda with named base to AST', () => {
|
|
1505
|
-
const testCase = 'create double as lambda number -> number ```python\nreturn @n * 2\n```;'
|
|
1506
|
-
const ast = parseToAst(testCase)
|
|
1507
|
-
const decl = ast.declarations[0] as ObjectDeclaration
|
|
1508
|
-
const lambda = decl.object as ExponentialPattern
|
|
1509
|
-
expect(lambda).toEqual({
|
|
1510
|
-
kind: 'ExponentialPattern',
|
|
1511
|
-
base: { kind: 'NamedObject', name: 'number' },
|
|
1512
|
-
exponent: { kind: 'NamedObject', name: 'number' },
|
|
1513
|
-
language: 'python',
|
|
1514
|
-
body: 'return @n * 2',
|
|
1515
|
-
patterns: [],
|
|
1516
1360
|
})
|
|
1517
1361
|
})
|
|
1518
1362
|
|
|
1519
|
-
it('should
|
|
1520
|
-
const testCase =
|
|
1521
|
-
'create transform as lambda (x: number) -> number ```python\nif @x > 0:\n @{return sin(@x)}\nelse:\n @{return cos(@x)}\n```;'
|
|
1363
|
+
it('should convert subobject with a code constraint to AST', () => {
|
|
1364
|
+
const testCase = 'create double as from number -> number select ```python\nreturn @n * 2\n```;'
|
|
1522
1365
|
const ast = parseToAst(testCase)
|
|
1523
1366
|
const decl = ast.declarations[0] as ObjectDeclaration
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
{ kind: '
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
{ kind: 'ConstraintText', text: ')' },
|
|
1542
|
-
],
|
|
1543
|
-
start: expect.any(Number),
|
|
1544
|
-
end: expect.any(Number),
|
|
1367
|
+
expect(decl.object).toEqual({
|
|
1368
|
+
kind: 'SubObject',
|
|
1369
|
+
base: {
|
|
1370
|
+
kind: 'ExponentialObject',
|
|
1371
|
+
exponent: { kind: 'NamedObject', name: 'number' },
|
|
1372
|
+
base: { kind: 'NamedObject', name: 'number' },
|
|
1373
|
+
},
|
|
1374
|
+
constraint: {
|
|
1375
|
+
type: 'Code',
|
|
1376
|
+
language: 'python',
|
|
1377
|
+
body: 'return @n * 2',
|
|
1378
|
+
parts: [
|
|
1379
|
+
{ kind: 'ConstraintText', text: 'return ' },
|
|
1380
|
+
{ kind: 'ReferenceDirective', name: 'n' },
|
|
1381
|
+
{ kind: 'ConstraintText', text: ' * 2' },
|
|
1382
|
+
],
|
|
1383
|
+
},
|
|
1545
1384
|
})
|
|
1546
1385
|
})
|
|
1547
1386
|
|
|
1548
|
-
it('should
|
|
1549
|
-
const testCase =
|
|
1550
|
-
'create transform as lambda (x: number) -> number ```python\nif @x > 0:\n @{return sin(@x)}\nelse:\n @{return cos(@x)}\n```;'
|
|
1551
|
-
const ast = parseToAst(testCase)
|
|
1552
|
-
const decl = ast.declarations[0] as ObjectDeclaration
|
|
1553
|
-
const lambda = decl.object as ExponentialPattern
|
|
1554
|
-
expect(lambda.patterns[0].start).toBeLessThan(lambda.patterns[0].end)
|
|
1555
|
-
expect(lambda.patterns[1].start).toBeGreaterThan(lambda.patterns[0].end)
|
|
1556
|
-
expect(lambda.patterns[1].start).toBeLessThan(lambda.patterns[1].end)
|
|
1557
|
-
})
|
|
1558
|
-
|
|
1559
|
-
it('should convert lambda in product field to AST', () => {
|
|
1387
|
+
it('should convert a code constraint in a product field to AST', () => {
|
|
1560
1388
|
const testCase =
|
|
1561
|
-
'create Config as (handler:
|
|
1389
|
+
'create Config as (handler: from (x: string) -> string select ```typescript\nreturn x.toUpperCase();\n```, port: number);'
|
|
1562
1390
|
const ast = parseToAst(testCase)
|
|
1563
1391
|
const decl = ast.declarations[0] as ObjectDeclaration
|
|
1564
1392
|
expect(decl.object).toEqual({
|
|
1565
1393
|
kind: 'ProductObject',
|
|
1566
1394
|
fields: {
|
|
1567
1395
|
handler: {
|
|
1568
|
-
kind: '
|
|
1569
|
-
base: {
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1396
|
+
kind: 'SubObject',
|
|
1397
|
+
base: {
|
|
1398
|
+
kind: 'ExponentialObject',
|
|
1399
|
+
exponent: {
|
|
1400
|
+
kind: 'ProductObject',
|
|
1401
|
+
fields: {
|
|
1402
|
+
x: { kind: 'NamedObject', name: 'string' },
|
|
1403
|
+
},
|
|
1574
1404
|
},
|
|
1405
|
+
base: { kind: 'NamedObject', name: 'string' },
|
|
1406
|
+
},
|
|
1407
|
+
constraint: {
|
|
1408
|
+
type: 'Code',
|
|
1409
|
+
language: 'typescript',
|
|
1410
|
+
body: 'return x.toUpperCase();',
|
|
1411
|
+
parts: [{ kind: 'ConstraintText', text: 'return x.toUpperCase();' }],
|
|
1575
1412
|
},
|
|
1576
|
-
language: 'typescript',
|
|
1577
|
-
body: 'return x.toUpperCase();',
|
|
1578
|
-
patterns: [],
|
|
1579
1413
|
},
|
|
1580
1414
|
port: { kind: 'NamedObject', name: 'number' },
|
|
1581
1415
|
},
|