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/src/parser.ts CHANGED
@@ -7,18 +7,12 @@ import {
7
7
  SelectTok,
8
8
  GenerateTok,
9
9
  ImportTok,
10
- ExportTok,
11
- PackageTok,
12
- ExecutableTok,
13
- ModuleTok,
14
- EnumTok,
15
10
  UnionTok,
16
11
  IntersectTok,
17
12
  ExceptTok,
18
13
  RealizeTok,
19
14
  InTok,
20
15
  IncludeTok,
21
- LambdaTok,
22
16
  ArrowTok,
23
17
  PipeTok,
24
18
  SelectBlock,
@@ -41,6 +35,7 @@ import {
41
35
  NumberTok,
42
36
  BoolTok,
43
37
  UnitTok,
38
+ ArtifactTok,
44
39
  ConceptTok,
45
40
  EnvironmentTok,
46
41
  } from './lexer.js'
@@ -63,10 +58,6 @@ export class SpexParser extends CstParser {
63
58
  GATE: this.BACKTRACK(this.realizeDeclaration),
64
59
  ALT: () => this.SUBRULE(this.realizeDeclaration),
65
60
  },
66
- {
67
- GATE: this.BACKTRACK(this.packageDeclaration),
68
- ALT: () => this.SUBRULE(this.packageDeclaration),
69
- },
70
61
  {
71
62
  GATE: this.BACKTRACK(this.objectDeclaration),
72
63
  ALT: () => this.SUBRULE(this.objectDeclaration),
@@ -75,10 +66,6 @@ export class SpexParser extends CstParser {
75
66
  GATE: this.BACKTRACK(this.importDeclaration),
76
67
  ALT: () => this.SUBRULE(this.importDeclaration),
77
68
  },
78
- {
79
- GATE: this.BACKTRACK(this.exportDeclaration),
80
- ALT: () => this.SUBRULE(this.exportDeclaration),
81
- },
82
69
  {
83
70
  GATE: this.BACKTRACK(this.includeDeclaration),
84
71
  ALT: () => this.SUBRULE(this.includeDeclaration),
@@ -117,17 +104,6 @@ export class SpexParser extends CstParser {
117
104
  })
118
105
  })
119
106
 
120
- private enumObject = this.RULE('enumObject', () => {
121
- this.CONSUME(EnumTok)
122
- this.CONSUME(LParen)
123
- this.CONSUME(StringLiteral)
124
- this.MANY(() => {
125
- this.CONSUME(Comma)
126
- this.CONSUME2(StringLiteral)
127
- })
128
- this.CONSUME(RParen)
129
- })
130
-
131
107
  private literalObject = this.RULE('literalObject', () => {
132
108
  this.OR([
133
109
  { ALT: () => this.CONSUME(StringLiteral) },
@@ -150,16 +126,9 @@ export class SpexParser extends CstParser {
150
126
  {
151
127
  ALT: () => this.SUBRULE(this.literalObject),
152
128
  },
153
- {
154
- ALT: () => this.SUBRULE(this.enumObject),
155
- },
156
129
  {
157
130
  ALT: () => this.SUBRULE(this.patternObject),
158
131
  },
159
- {
160
- GATE: this.BACKTRACK(this.lambdaObject),
161
- ALT: () => this.SUBRULE(this.lambdaObject),
162
- },
163
132
  {
164
133
  GATE: this.BACKTRACK(this.subObject),
165
134
  ALT: () => this.SUBRULE(this.subObject),
@@ -199,6 +168,7 @@ export class SpexParser extends CstParser {
199
168
  { ALT: () => this.CONSUME(NumberTok) },
200
169
  { ALT: () => this.CONSUME(BoolTok) },
201
170
  { ALT: () => this.CONSUME(UnitTok) },
171
+ { ALT: () => this.CONSUME(ArtifactTok) },
202
172
  { ALT: () => this.CONSUME(ConceptTok) },
203
173
  { ALT: () => this.CONSUME(EnvironmentTok) },
204
174
  ])
@@ -223,7 +193,7 @@ export class SpexParser extends CstParser {
223
193
  this.CONSUME(FromTok)
224
194
  this.SUBRULE(this.setObject, { LABEL: 'base' })
225
195
  this.CONSUME(SelectTok)
226
- this.CONSUME(SelectBlock)
196
+ this.OR([{ ALT: () => this.CONSUME(SelectBlock) }, { ALT: () => this.CONSUME(CodeBlock) }])
227
197
  })
228
198
 
229
199
  private importDeclaration = this.RULE('importDeclaration', () => {
@@ -256,26 +226,13 @@ export class SpexParser extends CstParser {
256
226
  this.CONSUME(Identifier)
257
227
  })
258
228
 
259
- private exportDeclaration = this.RULE('exportDeclaration', () => {
260
- this.CONSUME(ExportTok)
261
- this.CONSUME(Identifier)
262
- this.CONSUME(Semicolon)
263
- })
264
-
265
229
  private generateDeclaration = this.RULE('generateDeclaration', () => {
266
230
  this.CONSUME(GenerateTok)
267
231
  this.CONSUME(Identifier)
268
- this.CONSUME(Semicolon)
269
- })
270
-
271
- private packageDeclaration = this.RULE('packageDeclaration', () => {
272
- this.CONSUME(PackageTok)
273
- this.OR([{ ALT: () => this.CONSUME(ExecutableTok) }, { ALT: () => this.CONSUME(ModuleTok) }])
274
- this.CONSUME(Identifier)
275
- this.CONSUME(AsTok)
276
- this.SUBRULE(this.setObject)
277
- this.CONSUME(InTok)
278
- this.SUBRULE2(this.setObject, { LABEL: 'environment' })
232
+ this.OPTION(() => {
233
+ this.CONSUME(InTok)
234
+ this.SUBRULE(this.setObject, { LABEL: 'environment' })
235
+ })
279
236
  this.CONSUME(Semicolon)
280
237
  })
281
238
 
@@ -298,12 +255,4 @@ export class SpexParser extends CstParser {
298
255
  this.CONSUME(Identifier)
299
256
  this.CONSUME(Semicolon)
300
257
  })
301
-
302
- private lambdaObject = this.RULE('lambdaObject', () => {
303
- this.CONSUME(LambdaTok)
304
- this.SUBRULE(this.objectOperand, { LABEL: 'base' })
305
- this.CONSUME(ArrowTok)
306
- this.SUBRULE(this.setObject, { LABEL: 'exponent' })
307
- this.CONSUME(CodeBlock)
308
- })
309
258
  }
package/src/visitor.ts CHANGED
@@ -4,21 +4,17 @@ import type {
4
4
  Declaration,
5
5
  ObjectDeclaration,
6
6
  ImportDeclaration,
7
- ExportDeclaration,
8
7
  GenerateDeclaration,
9
- PackageDeclaration,
10
8
  RealizeDeclaration,
11
9
  IncludeDeclaration,
12
- EnumObject,
13
10
  LiteralObject,
14
11
  SetObject,
15
12
  CoproductObject,
16
13
  PatternLiteralObject,
17
- ExponentialPattern,
18
- PatternBlock,
19
14
  ObjectExpression,
20
15
  NamedObject,
21
16
  SubObject,
17
+ SubObjectConstraint,
22
18
  ArrayObject,
23
19
  Constraint,
24
20
  ConstraintPart,
@@ -49,7 +45,7 @@ export function parseConstraint(raw: string): Constraint {
49
45
  if (match.index > lastIndex) {
50
46
  parts.push({ kind: 'ConstraintText', text: raw.slice(lastIndex, match.index) })
51
47
  }
52
- parts.push({ kind: 'ConstraintReference', name: match[1]! })
48
+ parts.push({ kind: 'ReferenceDirective', name: match[1]! })
53
49
  lastIndex = match.index + match[0].length
54
50
  }
55
51
 
@@ -60,6 +56,35 @@ export function parseConstraint(raw: string): Constraint {
60
56
  return { raw, parts }
61
57
  }
62
58
 
59
+ function extractCodeLanguage(image: string): string {
60
+ const langEnd = image.indexOf('\n')
61
+ if (langEnd === -1) return ''
62
+ return image.slice(3, langEnd).trim()
63
+ }
64
+
65
+ function extractCodeBody(image: string): string {
66
+ const firstNewline = image.indexOf('\n')
67
+ return image.slice(firstNewline + 1, -3).trimEnd()
68
+ }
69
+
70
+ function codeConstraint(image: string): SubObjectConstraint {
71
+ const language = extractCodeLanguage(image)
72
+ const body = extractCodeBody(image)
73
+ if (language === '') {
74
+ return {
75
+ type: 'Structured',
76
+ raw: body,
77
+ parts: parseConstraint(body).parts,
78
+ }
79
+ }
80
+ return {
81
+ type: 'Code',
82
+ language,
83
+ body,
84
+ parts: parseConstraint(body).parts,
85
+ }
86
+ }
87
+
63
88
  export class SpexParserVisitor extends BaseSpexVisitor implements ICstVisitor<any, any> {
64
89
  constructor() {
65
90
  super()
@@ -75,31 +100,18 @@ export class SpexParserVisitor extends BaseSpexVisitor implements ICstVisitor<an
75
100
  if (ctx.realizeDeclaration) {
76
101
  return this.visit(ctx.realizeDeclaration)
77
102
  }
78
- if (ctx.packageDeclaration) {
79
- return this.visit(ctx.packageDeclaration)
80
- }
81
103
  if (ctx.objectDeclaration) {
82
104
  return this.visit(ctx.objectDeclaration)
83
105
  }
84
106
  if (ctx.importDeclaration) {
85
107
  return this.visit(ctx.importDeclaration)
86
108
  }
87
- if (ctx.exportDeclaration) {
88
- return this.visit(ctx.exportDeclaration)
89
- }
90
109
  if (ctx.includeDeclaration) {
91
110
  return this.visit(ctx.includeDeclaration)
92
111
  }
93
112
  return this.visit(ctx.generateDeclaration)
94
113
  }
95
114
 
96
- enumObject(ctx: any): EnumObject {
97
- return {
98
- kind: 'EnumObject',
99
- values: ctx.StringLiteral.map((s: any) => stringLiteralValue(s.image)),
100
- }
101
- }
102
-
103
115
  literalObject(ctx: any): LiteralObject {
104
116
  if (ctx.StringLiteral) {
105
117
  return {
@@ -169,12 +181,8 @@ export class SpexParserVisitor extends BaseSpexVisitor implements ICstVisitor<an
169
181
  expr = this.visit(ctx.parenthesizedObject)
170
182
  } else if (ctx.productObject) {
171
183
  expr = this.visit(ctx.productObject)
172
- } else if (ctx.enumObject) {
173
- expr = this.visit(ctx.enumObject)
174
184
  } else if (ctx.patternObject) {
175
185
  expr = this.visit(ctx.patternObject)
176
- } else if (ctx.lambdaObject) {
177
- expr = this.visit(ctx.lambdaObject)
178
186
  } else if (ctx.literalObject) {
179
187
  expr = this.visit(ctx.literalObject)
180
188
  } else {
@@ -212,6 +220,8 @@ export class SpexParserVisitor extends BaseSpexVisitor implements ICstVisitor<an
212
220
  parts = [ctx.BoolTok[0].image, ...(ctx.Identifier ?? []).map((id: any) => id.image)]
213
221
  } else if (ctx.UnitTok) {
214
222
  parts = [ctx.UnitTok[0].image, ...(ctx.Identifier ?? []).map((id: any) => id.image)]
223
+ } else if (ctx.ArtifactTok) {
224
+ parts = [ctx.ArtifactTok[0].image, ...(ctx.Identifier ?? []).map((id: any) => id.image)]
215
225
  } else if (ctx.ConceptTok) {
216
226
  parts = [ctx.ConceptTok[0].image, ...(ctx.Identifier ?? []).map((id: any) => id.image)]
217
227
  } else if (ctx.EnvironmentTok) {
@@ -243,12 +253,24 @@ export class SpexParserVisitor extends BaseSpexVisitor implements ICstVisitor<an
243
253
  }
244
254
 
245
255
  subObject(ctx: any): SubObject {
256
+ const base = this.visit(ctx.base)
257
+ if (ctx.CodeBlock) {
258
+ return {
259
+ kind: 'SubObject',
260
+ base,
261
+ constraint: codeConstraint(ctx.CodeBlock[0].image),
262
+ }
263
+ }
246
264
  const rawText: string = ctx.SelectBlock[0].image
247
265
  const rawConstraint = unescapeConstraint(rawText.slice(1, -1).trim())
248
266
  return {
249
267
  kind: 'SubObject',
250
- base: this.visit(ctx.base),
251
- constraint: parseConstraint(rawConstraint),
268
+ base,
269
+ constraint: {
270
+ type: 'NaturalLanguage',
271
+ raw: rawConstraint,
272
+ parts: parseConstraint(rawConstraint).parts,
273
+ },
252
274
  }
253
275
  }
254
276
 
@@ -282,28 +304,13 @@ export class SpexParserVisitor extends BaseSpexVisitor implements ICstVisitor<an
282
304
  }
283
305
  }
284
306
 
285
- exportDeclaration(ctx: any): ExportDeclaration {
286
- return {
287
- kind: 'ExportDeclaration',
288
- name: ctx.Identifier[0].image,
289
- }
290
- }
291
-
292
- packageDeclaration(ctx: any): PackageDeclaration {
293
- const packageType = ctx.ExecutableTok ? ('EXECUTABLE' as const) : ('MODULE' as const)
294
- return {
295
- kind: 'PackageDeclaration',
296
- packageType,
297
- name: ctx.Identifier[0].image,
298
- objectName: this.visit(ctx.setObject),
299
- environment: this.visit(ctx.environment),
300
- }
301
- }
302
-
303
307
  generateDeclaration(ctx: any): GenerateDeclaration {
304
308
  return {
305
309
  kind: 'GenerateDeclaration',
306
310
  name: ctx.Identifier[0].image,
311
+ environment: ctx.environment
312
+ ? this.visit(ctx.environment)
313
+ : { kind: 'NamedObject', name: 'environment' },
307
314
  }
308
315
  }
309
316
 
@@ -327,52 +334,6 @@ export class SpexParserVisitor extends BaseSpexVisitor implements ICstVisitor<an
327
334
  address,
328
335
  }
329
336
  }
330
-
331
- lambdaObject(ctx: any): ExponentialPattern {
332
- const image: string = ctx.CodeBlock[0].image
333
- const firstNewline = image.indexOf('\n')
334
- const langEnd = image.indexOf('\n')
335
- const language = image.slice(3, langEnd)
336
- const body = image.slice(firstNewline + 1, -3).trimEnd()
337
-
338
- const patterns: PatternBlock[] = []
339
- let i = 0
340
- while (i < body.length) {
341
- if (body[i] === '@' && body[i + 1] === '{') {
342
- const start = i
343
- let depth = 1
344
- i += 2
345
- while (i < body.length && depth > 0) {
346
- if (body[i] === '\\') {
347
- i += 2
348
- continue
349
- }
350
- if (body[i] === '{') depth++
351
- if (body[i] === '}') depth--
352
- i++
353
- }
354
- const end = i
355
- const raw = body.slice(start + 2, end - 1).trim()
356
- patterns.push({
357
- raw,
358
- parts: parseConstraint(raw).parts,
359
- start,
360
- end,
361
- })
362
- } else {
363
- i++
364
- }
365
- }
366
-
367
- return {
368
- kind: 'ExponentialPattern',
369
- base: this.visit(ctx.exponent),
370
- exponent: this.visit(ctx.base),
371
- language,
372
- body,
373
- patterns,
374
- }
375
- }
376
337
  }
377
338
 
378
339
  export function parseToAst(text: string): SpexFile {
@@ -74,10 +74,6 @@ describe('constantOf', () => {
74
74
  expect(constantOf(firstObject('create K as from string select { are positive };'))).toBeNull()
75
75
  })
76
76
 
77
- it('should reject enums', () => {
78
- expect(constantOf(firstObject("create K as enum ('a', 'b');"))).toBeNull()
79
- })
80
-
81
77
  it('should reject arrays', () => {
82
78
  expect(constantOf(firstObject('create K as string[];'))).toBeNull()
83
79
  })
package/tests/e2e.test.ts CHANGED
@@ -8,15 +8,15 @@ const __filename = fileURLToPath(import.meta.url)
8
8
  const __dirname = dirname(__filename)
9
9
 
10
10
  const fixtures: [string, number][] = [
11
- ['todo.spex', 32],
12
- ['python_cli_env.spex', 4],
13
- ['typescript_cli_env.spex', 4],
14
- ['flask_web_env.spex', 4],
15
- ['express_web_env.spex', 4],
16
- ['python_todo_cli.spex', 16],
17
- ['typescript_todo_cli.spex', 16],
18
- ['flask_todo_web.spex', 22],
19
- ['express_todo_web.spex', 22],
11
+ ['todo.spex', 17],
12
+ ['python_cli_env.spex', 3],
13
+ ['typescript_cli_env.spex', 3],
14
+ ['flask_web_env.spex', 3],
15
+ ['express_web_env.spex', 3],
16
+ ['python_todo_cli.spex', 15],
17
+ ['typescript_todo_cli.spex', 15],
18
+ ['flask_todo_web.spex', 21],
19
+ ['express_todo_web.spex', 21],
20
20
  ]
21
21
 
22
22
  describe('end-to-end', () => {
@@ -5,7 +5,7 @@ describe('SpexLexer', () => {
5
5
  describe('tokenization', () => {
6
6
  it('should tokenize keywords', () => {
7
7
  const result = SpexLexer.tokenize(
8
- 'create as from select generate import export package executable module enum realize in'
8
+ 'create as from select generate import realize in'
9
9
  )
10
10
  expect(result.errors).toHaveLength(0)
11
11
  expect(result.tokens.map((t) => t.tokenType.name)).toEqual([
@@ -15,11 +15,6 @@ describe('SpexLexer', () => {
15
15
  'SelectTok',
16
16
  'GenerateTok',
17
17
  'ImportTok',
18
- 'ExportTok',
19
- 'PackageTok',
20
- 'ExecutableTok',
21
- 'ModuleTok',
22
- 'EnumTok',
23
18
  'RealizeTok',
24
19
  'InTok',
25
20
  ])
@@ -27,7 +22,7 @@ describe('SpexLexer', () => {
27
22
 
28
23
  it('should tokenize keywords case-insensitively', () => {
29
24
  const result = SpexLexer.tokenize(
30
- 'CREATE AS FROM SELECT GENERATE IMPORT EXPORT PACKAGE EXECUTABLE MODULE ENUM UNION INTERSECT EXCEPT REALIZE IN'
25
+ 'CREATE AS FROM SELECT GENERATE IMPORT UNION INTERSECT EXCEPT REALIZE IN'
31
26
  )
32
27
  expect(result.errors).toHaveLength(0)
33
28
  expect(result.tokens.map((t) => t.tokenType.name)).toEqual([
@@ -37,11 +32,6 @@ describe('SpexLexer', () => {
37
32
  'SelectTok',
38
33
  'GenerateTok',
39
34
  'ImportTok',
40
- 'ExportTok',
41
- 'PackageTok',
42
- 'ExecutableTok',
43
- 'ModuleTok',
44
- 'EnumTok',
45
35
  'UnionTok',
46
36
  'IntersectTok',
47
37
  'ExceptTok',
@@ -50,23 +40,6 @@ describe('SpexLexer', () => {
50
40
  ])
51
41
  })
52
42
 
53
- it('should tokenize enum object declarations', () => {
54
- const result = SpexLexer.tokenize("create myEnum as enum ('v1', 'v2');")
55
- expect(result.errors).toHaveLength(0)
56
- expect(result.tokens.map((t) => t.tokenType.name)).toEqual([
57
- 'CreateTok',
58
- 'Identifier',
59
- 'AsTok',
60
- 'EnumTok',
61
- 'LParen',
62
- 'StringLiteral',
63
- 'Comma',
64
- 'StringLiteral',
65
- 'RParen',
66
- 'Semicolon',
67
- ])
68
- })
69
-
70
43
  it('should tokenize symbols', () => {
71
44
  const result = SpexLexer.tokenize('->[]():;,.|')
72
45
  expect(result.errors).toHaveLength(0)
@@ -120,10 +93,10 @@ describe('SpexLexer', () => {
120
93
 
121
94
  it('should handle keywords with word boundary', () => {
122
95
  const result = SpexLexer.tokenize(
123
- 'createfoo foocreate asfoo fooas fooselect selectfoo foofrom fromfoo generatefoo foogenerate importfoo fooimport exportfoo fooexport packagefoo fopackage executablefoo foexecutable modulefoo fomodule conceptfoo fooconcept environmentfoo fooenvironment realizefoo foorealize info fooin'
96
+ 'createfoo foocreate asfoo fooas fooselect selectfoo foofrom fromfoo generatefoo foogenerate importfoo fooimport artifactfoo fooartifact conceptfoo fooconcept environmentfoo fooenvironment realizefoo foorealize info fooin'
124
97
  )
125
98
  expect(result.errors).toHaveLength(0)
126
- expect(result.tokens.map((t) => t.tokenType.name)).toEqual(Array(28).fill('Identifier'))
99
+ expect(result.tokens.map((t) => t.tokenType.name)).toEqual(Array(22).fill('Identifier'))
127
100
  })
128
101
 
129
102
  it('should tokenize the concept and environment base types', () => {
@@ -135,6 +108,12 @@ describe('SpexLexer', () => {
135
108
  ])
136
109
  })
137
110
 
111
+ it('should tokenize the artifact base type', () => {
112
+ const result = SpexLexer.tokenize('artifact')
113
+ expect(result.errors).toHaveLength(0)
114
+ expect(result.tokens.map((t) => t.tokenType.name)).toEqual(['ArtifactTok'])
115
+ })
116
+
138
117
  it('should tokenize the text between braces', () => {
139
118
  const result = SpexLexer.tokenize('{hello\nworld}')
140
119
  expect(result.errors).toHaveLength(0)