spex-parser 0.1.5 → 0.6.2

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.
Files changed (44) hide show
  1. package/README.md +320 -9
  2. package/dist/ast.d.ts +71 -2
  3. package/dist/ast.d.ts.map +1 -1
  4. package/dist/constants.d.ts +20 -0
  5. package/dist/constants.d.ts.map +1 -0
  6. package/dist/constants.js +32 -0
  7. package/dist/constants.js.map +1 -0
  8. package/dist/index.d.ts +4 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/lexer.d.ts +17 -1
  13. package/dist/lexer.d.ts.map +1 -1
  14. package/dist/lexer.js +159 -5
  15. package/dist/lexer.js.map +1 -1
  16. package/dist/parser.d.ts +9 -0
  17. package/dist/parser.d.ts.map +1 -1
  18. package/dist/parser.js +105 -7
  19. package/dist/parser.js.map +1 -1
  20. package/dist/visitor.d.ts +11 -2
  21. package/dist/visitor.d.ts.map +1 -1
  22. package/dist/visitor.js +182 -8
  23. package/dist/visitor.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/ast.ts +94 -0
  26. package/src/constants.ts +40 -0
  27. package/src/index.ts +20 -3
  28. package/src/lexer.ts +150 -6
  29. package/src/parser.ts +130 -7
  30. package/src/visitor.ts +196 -11
  31. package/tests/constants.test.ts +102 -0
  32. package/tests/e2e.test.ts +15 -3
  33. package/tests/lexer.test.ts +230 -9
  34. package/tests/parser.test.ts +455 -10
  35. package/tests/props/express_todo_web.spex +108 -0
  36. package/tests/props/express_web_env.spex +16 -0
  37. package/tests/props/flask_todo_web.spex +111 -0
  38. package/tests/props/flask_web_env.spex +16 -0
  39. package/tests/props/python_cli_env.spex +16 -0
  40. package/tests/props/python_todo_cli.spex +70 -0
  41. package/tests/props/todo.spex +99 -68
  42. package/tests/props/typescript_cli_env.spex +16 -0
  43. package/tests/props/typescript_todo_cli.spex +70 -0
  44. package/tests/visitor.test.ts +905 -6
package/src/lexer.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createToken, Lexer } from 'chevrotain'
1
+ import { createToken, Lexer, type CustomPatternMatcherReturn } from 'chevrotain'
2
2
 
3
3
  export const WhiteSpace = createToken({
4
4
  name: 'WhiteSpace',
@@ -59,9 +59,42 @@ export const ModuleTok = createToken({
59
59
  name: 'ModuleTok',
60
60
  pattern: /module\b/i,
61
61
  })
62
+ export const EnumTok = createToken({
63
+ name: 'EnumTok',
64
+ pattern: /enum\b/i,
65
+ })
66
+ export const UnionTok = createToken({
67
+ name: 'UnionTok',
68
+ pattern: /union\b/i,
69
+ })
70
+ export const IntersectTok = createToken({
71
+ name: 'IntersectTok',
72
+ pattern: /intersect\b/i,
73
+ })
74
+ export const ExceptTok = createToken({
75
+ name: 'ExceptTok',
76
+ pattern: /except\b/i,
77
+ })
78
+ export const RealizeTok = createToken({
79
+ name: 'RealizeTok',
80
+ pattern: /realize\b/i,
81
+ })
82
+ export const InTok = createToken({
83
+ name: 'InTok',
84
+ pattern: /in\b/i,
85
+ })
86
+ export const IncludeTok = createToken({
87
+ name: 'IncludeTok',
88
+ pattern: /include\b/i,
89
+ })
90
+ export const LambdaTok = createToken({
91
+ name: 'LambdaTok',
92
+ pattern: /lambda\b/i,
93
+ })
62
94
 
63
95
  // Symbols
64
96
  export const ArrowTok = createToken({ name: 'ArrowTok', pattern: /->/ })
97
+ export const PipeTok = createToken({ name: 'PipeTok', pattern: /\|/ })
65
98
  export const LCurly = createToken({ name: 'LCurly', pattern: /{/ })
66
99
  export const RCurly = createToken({ name: 'RCurly', pattern: /}/ })
67
100
  export const LBracket = createToken({ name: 'LBracket', pattern: /\[/ })
@@ -76,13 +109,100 @@ export const Dot = createToken({ name: 'Dot', pattern: /\./ })
76
109
  // Brace text block (for SELECT { ... })
77
110
  export const SelectBlock = createToken({
78
111
  name: 'SelectBlock',
79
- pattern: /\{[^}]+\}/,
112
+ line_breaks: true,
113
+ pattern: (text: string, startOffset: number): CustomPatternMatcherReturn | null => {
114
+ let i = startOffset
115
+ if (text[i] !== '{') return null
116
+ for (i++; i < text.length; i++) {
117
+ if (text[i] === '\\') {
118
+ i++
119
+ continue
120
+ }
121
+ if (text[i] === '}') {
122
+ return [text.slice(startOffset, i + 1)]
123
+ }
124
+ }
125
+ return null
126
+ },
127
+ })
128
+
129
+ // Regex literal (JS-style): /pattern/flags
130
+ export const PatternLiteral = createToken({
131
+ name: 'PatternLiteral',
132
+ line_breaks: true,
133
+ pattern: (text: string, startOffset: number): CustomPatternMatcherReturn | null => {
134
+ if (text[startOffset] !== '/') return null
135
+ let inClass = false
136
+ let i = startOffset + 1
137
+ while (i < text.length) {
138
+ const ch = text[i]
139
+ if (ch === '\\') {
140
+ i += 2
141
+ continue
142
+ }
143
+ if (inClass) {
144
+ if (ch === ']') inClass = false
145
+ } else if (ch === '[') {
146
+ inClass = true
147
+ } else if (ch === '/') {
148
+ let j = i + 1
149
+ while (j < text.length && /[a-z]/.test(text[j] ?? '')) j++
150
+ return [text.slice(startOffset, j)]
151
+ }
152
+ i++
153
+ }
154
+ return null
155
+ },
156
+ })
157
+
158
+ // Code block: ```language\n...content...```
159
+ export const CodeBlock = createToken({
160
+ name: 'CodeBlock',
161
+ line_breaks: true,
162
+ pattern: (text: string, startOffset: number): CustomPatternMatcherReturn | null => {
163
+ if (text[startOffset] !== '`' || text[startOffset + 1] !== '`' || text[startOffset + 2] !== '`')
164
+ return null
165
+ let i = startOffset + 3
166
+ // require at least one language identifier character
167
+ if (i >= text.length || !/[a-zA-Z0-9_]/.test(text[i]!)) return null
168
+ const langStart = i
169
+ while (i < text.length && text[i] !== '\n' && text[i] !== '\r') i++
170
+ if (i === langStart) return null // no language identifier
171
+ if (i >= text.length) return null
172
+ // skip line ending
173
+ if (text[i] === '\r' && text[i + 1] === '\n') i += 2
174
+ else i++
175
+ // find closing ```
176
+ while (i < text.length - 2) {
177
+ if (text[i] === '\\') {
178
+ i += 2
179
+ continue
180
+ }
181
+ if (text[i] === '`' && text[i + 1] === '`' && text[i + 2] === '`') {
182
+ return [text.slice(startOffset, i + 3)]
183
+ }
184
+ i++
185
+ }
186
+ return null
187
+ },
80
188
  })
81
189
 
82
190
  // Literals
83
- export const PathLiteral = createToken({
84
- name: 'PathLiteral',
85
- pattern: /"([^"\\]|\\.)*"/,
191
+ export const StringLiteral = createToken({
192
+ name: 'StringLiteral',
193
+ pattern: /'([^'\\]|\\.)*'|"([^"\\]|\\.)*"/,
194
+ })
195
+ export const NumberLiteral = createToken({
196
+ name: 'NumberLiteral',
197
+ pattern: /\d+(\.\d+)?/,
198
+ })
199
+ export const TrueTok = createToken({
200
+ name: 'TrueTok',
201
+ pattern: /true\b/i,
202
+ })
203
+ export const FalseTok = createToken({
204
+ name: 'FalseTok',
205
+ pattern: /false\b/i,
86
206
  })
87
207
 
88
208
  // Basic objects (native types)
@@ -102,6 +222,14 @@ export const UnitTok = createToken({
102
222
  name: 'UnitTok',
103
223
  pattern: /unit\b/i,
104
224
  })
225
+ export const ConceptTok = createToken({
226
+ name: 'ConceptTok',
227
+ pattern: /concept\b/i,
228
+ })
229
+ export const EnvironmentTok = createToken({
230
+ name: 'EnvironmentTok',
231
+ pattern: /environment\b/i,
232
+ })
105
233
 
106
234
  // Identifiers
107
235
  export const Identifier = createToken({
@@ -124,8 +252,17 @@ export const allTokens = [
124
252
  PackageTok,
125
253
  ExecutableTok,
126
254
  ModuleTok,
255
+ EnumTok,
256
+ UnionTok,
257
+ IntersectTok,
258
+ ExceptTok,
259
+ RealizeTok,
260
+ InTok,
261
+ IncludeTok,
262
+ LambdaTok,
127
263
 
128
264
  ArrowTok,
265
+ PipeTok,
129
266
  SelectBlock,
130
267
  LCurly,
131
268
  RCurly,
@@ -137,12 +274,19 @@ export const allTokens = [
137
274
  Comma,
138
275
  Semicolon,
139
276
  Dot,
140
- PathLiteral,
277
+ StringLiteral,
278
+ NumberLiteral,
279
+ TrueTok,
280
+ FalseTok,
281
+ PatternLiteral,
282
+ CodeBlock,
141
283
 
142
284
  StringTok,
143
285
  NumberTok,
144
286
  BoolTok,
145
287
  UnitTok,
288
+ ConceptTok,
289
+ EnvironmentTok,
146
290
 
147
291
  Identifier,
148
292
  ]
package/src/parser.ts CHANGED
@@ -11,8 +11,18 @@ import {
11
11
  PackageTok,
12
12
  ExecutableTok,
13
13
  ModuleTok,
14
+ EnumTok,
15
+ UnionTok,
16
+ IntersectTok,
17
+ ExceptTok,
18
+ RealizeTok,
19
+ InTok,
20
+ IncludeTok,
21
+ LambdaTok,
14
22
  ArrowTok,
23
+ PipeTok,
15
24
  SelectBlock,
25
+ CodeBlock,
16
26
  LBracket,
17
27
  RBracket,
18
28
  LParen,
@@ -22,11 +32,17 @@ import {
22
32
  Semicolon,
23
33
  Dot,
24
34
  Identifier,
25
- PathLiteral,
35
+ StringLiteral,
36
+ NumberLiteral,
37
+ TrueTok,
38
+ FalseTok,
39
+ PatternLiteral,
26
40
  StringTok,
27
41
  NumberTok,
28
42
  BoolTok,
29
43
  UnitTok,
44
+ ConceptTok,
45
+ EnvironmentTok,
30
46
  } from './lexer.js'
31
47
 
32
48
  export class SpexParser extends CstParser {
@@ -43,6 +59,10 @@ export class SpexParser extends CstParser {
43
59
 
44
60
  private declaration = this.RULE('declaration', () => {
45
61
  this.OR([
62
+ {
63
+ GATE: this.BACKTRACK(this.realizeDeclaration),
64
+ ALT: () => this.SUBRULE(this.realizeDeclaration),
65
+ },
46
66
  {
47
67
  GATE: this.BACKTRACK(this.packageDeclaration),
48
68
  ALT: () => this.SUBRULE(this.packageDeclaration),
@@ -59,6 +79,10 @@ export class SpexParser extends CstParser {
59
79
  GATE: this.BACKTRACK(this.exportDeclaration),
60
80
  ALT: () => this.SUBRULE(this.exportDeclaration),
61
81
  },
82
+ {
83
+ GATE: this.BACKTRACK(this.includeDeclaration),
84
+ ALT: () => this.SUBRULE(this.includeDeclaration),
85
+ },
62
86
  {
63
87
  ALT: () => this.SUBRULE(this.generateDeclaration),
64
88
  },
@@ -69,10 +93,50 @@ export class SpexParser extends CstParser {
69
93
  this.CONSUME(CreateTok)
70
94
  this.CONSUME(Identifier)
71
95
  this.CONSUME(AsTok)
72
- this.SUBRULE(this.objectExpression)
96
+ this.SUBRULE(this.setObject)
73
97
  this.CONSUME(Semicolon)
74
98
  })
75
99
 
100
+ private setObject = this.RULE('setObject', () => {
101
+ this.SUBRULE(this.coproductObject)
102
+ this.MANY(() => {
103
+ this.OR([
104
+ { ALT: () => this.CONSUME(UnionTok, { LABEL: 'op' }) },
105
+ { ALT: () => this.CONSUME(IntersectTok, { LABEL: 'op' }) },
106
+ { ALT: () => this.CONSUME(ExceptTok, { LABEL: 'op' }) },
107
+ ])
108
+ this.SUBRULE2(this.coproductObject)
109
+ })
110
+ })
111
+
112
+ private coproductObject = this.RULE('coproductObject', () => {
113
+ this.SUBRULE(this.objectExpression)
114
+ this.MANY(() => {
115
+ this.CONSUME(PipeTok, { LABEL: 'op' })
116
+ this.SUBRULE2(this.objectExpression)
117
+ })
118
+ })
119
+
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
+ private literalObject = this.RULE('literalObject', () => {
132
+ this.OR([
133
+ { ALT: () => this.CONSUME(StringLiteral) },
134
+ { ALT: () => this.CONSUME(NumberLiteral) },
135
+ { ALT: () => this.CONSUME(TrueTok) },
136
+ { ALT: () => this.CONSUME(FalseTok) },
137
+ ])
138
+ })
139
+
76
140
  private objectExpression = this.RULE('objectExpression', () => {
77
141
  this.SUBRULE(this.objectOperand, { LABEL: 'base' })
78
142
  this.OPTION(() => {
@@ -83,10 +147,27 @@ export class SpexParser extends CstParser {
83
147
 
84
148
  private objectOperand = this.RULE('objectOperand', () => {
85
149
  this.OR([
150
+ {
151
+ ALT: () => this.SUBRULE(this.literalObject),
152
+ },
153
+ {
154
+ ALT: () => this.SUBRULE(this.enumObject),
155
+ },
156
+ {
157
+ ALT: () => this.SUBRULE(this.patternObject),
158
+ },
159
+ {
160
+ GATE: this.BACKTRACK(this.lambdaObject),
161
+ ALT: () => this.SUBRULE(this.lambdaObject),
162
+ },
86
163
  {
87
164
  GATE: this.BACKTRACK(this.subObject),
88
165
  ALT: () => this.SUBRULE(this.subObject),
89
166
  },
167
+ {
168
+ GATE: this.BACKTRACK(this.parenthesizedObject),
169
+ ALT: () => this.SUBRULE(this.parenthesizedObject),
170
+ },
90
171
  {
91
172
  GATE: this.BACKTRACK(this.productObject),
92
173
  ALT: () => this.SUBRULE(this.productObject),
@@ -101,6 +182,16 @@ export class SpexParser extends CstParser {
101
182
  })
102
183
  })
103
184
 
185
+ private parenthesizedObject = this.RULE('parenthesizedObject', () => {
186
+ this.CONSUME(LParen)
187
+ this.SUBRULE(this.setObject)
188
+ this.CONSUME(RParen)
189
+ })
190
+
191
+ private patternObject = this.RULE('patternObject', () => {
192
+ this.CONSUME(PatternLiteral)
193
+ })
194
+
104
195
  private namedObject = this.RULE('namedObject', () => {
105
196
  this.OR([
106
197
  { ALT: () => this.CONSUME(Identifier) },
@@ -108,6 +199,8 @@ export class SpexParser extends CstParser {
108
199
  { ALT: () => this.CONSUME(NumberTok) },
109
200
  { ALT: () => this.CONSUME(BoolTok) },
110
201
  { ALT: () => this.CONSUME(UnitTok) },
202
+ { ALT: () => this.CONSUME(ConceptTok) },
203
+ { ALT: () => this.CONSUME(EnvironmentTok) },
111
204
  ])
112
205
  this.MANY(() => {
113
206
  this.CONSUME(Dot)
@@ -120,7 +213,7 @@ export class SpexParser extends CstParser {
120
213
  this.MANY(() => {
121
214
  this.CONSUME(Identifier)
122
215
  this.CONSUME(Colon)
123
- this.SUBRULE(this.objectExpression)
216
+ this.SUBRULE(this.setObject)
124
217
  this.OPTION(() => this.CONSUME(Comma))
125
218
  })
126
219
  this.CONSUME(RParen)
@@ -128,7 +221,7 @@ export class SpexParser extends CstParser {
128
221
 
129
222
  private subObject = this.RULE('subObject', () => {
130
223
  this.CONSUME(FromTok)
131
- this.SUBRULE(this.objectExpression, { LABEL: 'base' })
224
+ this.SUBRULE(this.setObject, { LABEL: 'base' })
132
225
  this.CONSUME(SelectTok)
133
226
  this.CONSUME(SelectBlock)
134
227
  })
@@ -150,7 +243,7 @@ export class SpexParser extends CstParser {
150
243
  private namedImport = this.RULE('namedImport', () => {
151
244
  this.CONSUME(Identifier)
152
245
  this.CONSUME(FromTok)
153
- this.CONSUME(PathLiteral)
246
+ this.CONSUME(StringLiteral)
154
247
  this.OPTION(() => {
155
248
  this.CONSUME(AsTok)
156
249
  this.CONSUME2(Identifier)
@@ -158,7 +251,7 @@ export class SpexParser extends CstParser {
158
251
  })
159
252
 
160
253
  private moduleImport = this.RULE('moduleImport', () => {
161
- this.CONSUME(PathLiteral)
254
+ this.CONSUME(StringLiteral)
162
255
  this.CONSUME(AsTok)
163
256
  this.CONSUME(Identifier)
164
257
  })
@@ -180,7 +273,37 @@ export class SpexParser extends CstParser {
180
273
  this.OR([{ ALT: () => this.CONSUME(ExecutableTok) }, { ALT: () => this.CONSUME(ModuleTok) }])
181
274
  this.CONSUME(Identifier)
182
275
  this.CONSUME(AsTok)
183
- this.SUBRULE(this.objectExpression)
276
+ this.SUBRULE(this.setObject)
277
+ this.CONSUME(InTok)
278
+ this.SUBRULE2(this.setObject, { LABEL: 'environment' })
184
279
  this.CONSUME(Semicolon)
185
280
  })
281
+
282
+ private realizeDeclaration = this.RULE('realizeDeclaration', () => {
283
+ this.CONSUME(RealizeTok)
284
+ this.SUBRULE(this.setObject, { LABEL: 'object' })
285
+ this.CONSUME(AsTok)
286
+ this.SUBRULE2(this.setObject, { LABEL: 'target' })
287
+ this.OPTION(() => {
288
+ this.CONSUME(InTok)
289
+ this.SUBRULE3(this.setObject, { LABEL: 'environment' })
290
+ })
291
+ this.CONSUME(Semicolon)
292
+ })
293
+
294
+ private includeDeclaration = this.RULE('includeDeclaration', () => {
295
+ this.CONSUME(IncludeTok)
296
+ this.CONSUME(StringLiteral)
297
+ this.CONSUME(AsTok)
298
+ this.CONSUME(Identifier)
299
+ this.CONSUME(Semicolon)
300
+ })
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
+ })
186
309
  }