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.
- package/.github/workflows/ci.yml +37 -0
- package/.github/workflows/publish.yml +35 -0
- package/.prettierrc +8 -0
- package/LICENSE +7 -0
- package/README.md +513 -0
- package/dist/ast.d.ts +48 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +2 -0
- package/dist/ast.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer.d.ts +30 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +109 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser.d.ts +18 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +132 -0
- package/dist/parser.js.map +1 -0
- package/dist/visitor.d.ts +22 -0
- package/dist/visitor.d.ts.map +1 -0
- package/dist/visitor.js +154 -0
- package/dist/visitor.js.map +1 -0
- package/eslint.config.js +27 -0
- package/package.json +45 -0
- package/src/ast.ts +67 -0
- package/src/index.ts +13 -0
- package/src/lexer.ts +121 -0
- package/src/parser.ts +170 -0
- package/src/visitor.ts +179 -0
- package/tests/e2e.test.ts +17 -0
- package/tests/lexer.test.ts +123 -0
- package/tests/parser.test.ts +227 -0
- package/tests/props/todo.spex +83 -0
- package/tests/visitor.test.ts +463 -0
- package/tsconfig.json +47 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Setup Node.js
|
|
19
|
+
uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: '20'
|
|
22
|
+
cache: 'npm'
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Build
|
|
28
|
+
run: npm run build
|
|
29
|
+
|
|
30
|
+
- name: Type check
|
|
31
|
+
run: npx tsc --noEmit
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: npm test
|
|
35
|
+
|
|
36
|
+
- name: Check formatting
|
|
37
|
+
run: npm run format:check
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
name: Publish to npm
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Setup Node.js
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: '20'
|
|
20
|
+
registry-url: 'https://registry.npmjs.org'
|
|
21
|
+
cache: 'npm'
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm ci
|
|
25
|
+
|
|
26
|
+
- name: Build
|
|
27
|
+
run: npm run build
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: npm test
|
|
31
|
+
|
|
32
|
+
- name: Publish to npm
|
|
33
|
+
run: npm stage publish
|
|
34
|
+
env:
|
|
35
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/.prettierrc
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Freelansys
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
# Spex
|
|
2
|
+
|
|
3
|
+
Spex is a declarative language for AI-assisted software development. It addresses shortcomings of the chat interface commonly used in AI coding assistant tools.
|
|
4
|
+
|
|
5
|
+
In particular, Spex aims to solve the following problems:
|
|
6
|
+
|
|
7
|
+
- Instructions given to AI coding assistants contain valuable information, but this information is often lost among the noise produced during conversations.
|
|
8
|
+
- Professional software developers must adapt to a new mental model when programming through chat interfaces.
|
|
9
|
+
- Programs produced through chat interactions are difficult to reproduce because the exact prompts and their order are lost.
|
|
10
|
+
- Chat interfaces do not integrate well with existing software engineering tools such as version control systems.
|
|
11
|
+
- Referencing objects in the code base requires repetitive and verbose prompts.
|
|
12
|
+
- Because architecture and design are not persisted, AI agents must constantly read and reason about multiple files, leading to inefficient token usage.
|
|
13
|
+
- Reusability in chat interfaces is extremely limited and abstraction is arbitrary.
|
|
14
|
+
|
|
15
|
+
The idea behind chat interfaces in AI coding tools is that _everyone_ should be able to code. While admirable, this approach often makes the tools inadequate for professional developers.
|
|
16
|
+
|
|
17
|
+
Spex acknowledges that in serious software projects it is neither wise nor feasible to replace programmers with machines. Instead, Spex integrates with the mental model and ecosystem of professional programmers, enabling them to be significantly more efficient. For this reason, Spex is probably not suited to someone that is not familiar with programming. This is a conscious decision made to cater to the needs of professional programmers and not the general public.
|
|
18
|
+
|
|
19
|
+
For this reason, Spex syntax is intentionally close to common languages such as TypeScript and SQL. Instead of manually implementing software, developers describe *spaces of valid implementations* using familiar programming abstractions such as:
|
|
20
|
+
|
|
21
|
+
* objects
|
|
22
|
+
* functions
|
|
23
|
+
* dependencies
|
|
24
|
+
* constraints
|
|
25
|
+
|
|
26
|
+
The Spex runtime synthesizes concrete implementations based on these specifications.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# Core Idea
|
|
31
|
+
|
|
32
|
+
In Spex:
|
|
33
|
+
|
|
34
|
+
* a type represents a space of possible implementations
|
|
35
|
+
* constraints refine that space
|
|
36
|
+
* reusable abstractions are represented as subtypes
|
|
37
|
+
|
|
38
|
+
For example:
|
|
39
|
+
|
|
40
|
+
```spex
|
|
41
|
+
CREATE SecureEndpoint AS
|
|
42
|
+
FROM HttpRequest -> HttpResponse
|
|
43
|
+
SELECT {
|
|
44
|
+
- the user is authenticated and authorised.
|
|
45
|
+
- The call is rate limited.
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`SecureEndpoint` now represents the set of all endpoint implementations satisfying those constraints.
|
|
50
|
+
|
|
51
|
+
Developers can build on top of these abstractions instead of repeatedly specifying common architectural concerns.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# Design Goals
|
|
56
|
+
|
|
57
|
+
Spex is designed to:
|
|
58
|
+
|
|
59
|
+
* feel familiar to software developers
|
|
60
|
+
* resemble SQL-style declarative programming
|
|
61
|
+
* support compositional software synthesis
|
|
62
|
+
* enable reusable architectural abstractions
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# Objects
|
|
67
|
+
|
|
68
|
+
Objects are analogous to types in a programming language. Objects can be translated to classes, structs, functions, etc.
|
|
69
|
+
|
|
70
|
+
## Basic Objects
|
|
71
|
+
|
|
72
|
+
Basic objects are provided by Spex natively. These objects represent the common basic types in a programming language:
|
|
73
|
+
|
|
74
|
+
```spex
|
|
75
|
+
string
|
|
76
|
+
number
|
|
77
|
+
bool
|
|
78
|
+
unit
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`unit` is a special object that represent an empty type. It is useful in defining functions that take no input or do not return anything.
|
|
82
|
+
|
|
83
|
+
## Arrays
|
|
84
|
+
|
|
85
|
+
To represent an array:
|
|
86
|
+
|
|
87
|
+
```spex
|
|
88
|
+
string[]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Products
|
|
92
|
+
|
|
93
|
+
Product objects are created by combining other objects:
|
|
94
|
+
|
|
95
|
+
```spex
|
|
96
|
+
(
|
|
97
|
+
id: string,
|
|
98
|
+
done: bool
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`unit` objects in a product are ignored. Meaning, the following products are the same:
|
|
103
|
+
|
|
104
|
+
```spex
|
|
105
|
+
(
|
|
106
|
+
id: string,
|
|
107
|
+
foo: unit
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
(
|
|
111
|
+
id: string
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Consequently, `()` and `unit` are the same object.
|
|
116
|
+
|
|
117
|
+
## Exponentials
|
|
118
|
+
|
|
119
|
+
Spex support function types as well which are refered to as exponential objects. An exponential is defined by its domain and codomain which have to be objects themselves:
|
|
120
|
+
|
|
121
|
+
```spex
|
|
122
|
+
string -> number
|
|
123
|
+
(id: string) -> number
|
|
124
|
+
string -> unit
|
|
125
|
+
unit -> string
|
|
126
|
+
```
|
|
127
|
+
`string -> unit` represents all functions that take a string as input and do not return anything. `unit -> string` on the other hand, is a function that takes nothing as input, but returns a string.
|
|
128
|
+
|
|
129
|
+
## Subobjects
|
|
130
|
+
|
|
131
|
+
Subobjects are analogous to subsets. Subobjects refine an object by selecting memebers that satisfy some constraints. Constraints are defined through natural language:
|
|
132
|
+
|
|
133
|
+
```spex
|
|
134
|
+
FROM string
|
|
135
|
+
SELECT {
|
|
136
|
+
are email addresses
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
FROM string -> number
|
|
140
|
+
SELECT {
|
|
141
|
+
return the length of the given string
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Subobjects are themselves objects so they could be subobjected as well. A good heuristic for writing constraints is to make the expression read as:
|
|
146
|
+
|
|
147
|
+
> "from `object` select those that `{constraint}`".
|
|
148
|
+
|
|
149
|
+
# Named Objects
|
|
150
|
+
|
|
151
|
+
To name an object for reuse:
|
|
152
|
+
|
|
153
|
+
```spex
|
|
154
|
+
CREATE Todo AS
|
|
155
|
+
(
|
|
156
|
+
id: string,
|
|
157
|
+
title: string,
|
|
158
|
+
completed: bool,
|
|
159
|
+
created_at: string
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
CREATE EmailAddress AS
|
|
163
|
+
FROM string
|
|
164
|
+
SELECT {
|
|
165
|
+
are email addresses
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
CREATE slugify AS
|
|
169
|
+
FROM string -> string
|
|
170
|
+
SELECT {
|
|
171
|
+
return the slugified string
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
# Referencing
|
|
178
|
+
|
|
179
|
+
Spex allows referencing other objects in constraints using string interpolation as in template strings. The scope of a variable is determined using the same rules as in Typescript.
|
|
180
|
+
|
|
181
|
+
```spex
|
|
182
|
+
CREATE Todo AS
|
|
183
|
+
(
|
|
184
|
+
id: string,
|
|
185
|
+
title: string,
|
|
186
|
+
completed: bool,
|
|
187
|
+
created_at: string
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
CREATE validate AS
|
|
191
|
+
FROM Todo -> bool
|
|
192
|
+
SELECT {
|
|
193
|
+
return true if @created_at is a valid date and return false otherwise
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
CREATE CreateTodo AS
|
|
197
|
+
FROM Todo -> Bool
|
|
198
|
+
SELECT {
|
|
199
|
+
1. call @validate to validate the given todo
|
|
200
|
+
2. throw an exception if validation failed
|
|
201
|
+
3. insert the todo in the Todo table
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
This forms an explicit software dependency graph between objects.
|
|
206
|
+
|
|
207
|
+
Use `.` to reference a member of a product object:
|
|
208
|
+
|
|
209
|
+
```spex
|
|
210
|
+
CREATE ComplexNumber AS
|
|
211
|
+
(
|
|
212
|
+
real: number,
|
|
213
|
+
imag: number
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
CREATE Abs AS
|
|
217
|
+
FROM (z: ComplexNumber) -> number
|
|
218
|
+
SELECT {
|
|
219
|
+
return square root of @z.real^2 + @z.imag^2
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
# Importing and Exporting
|
|
226
|
+
|
|
227
|
+
If there is a need to reuse some object in other files, we have to export the object and then import it where it is needed.
|
|
228
|
+
|
|
229
|
+
Suppose we have a file `types.spex` with the following content:
|
|
230
|
+
|
|
231
|
+
```spex
|
|
232
|
+
CREATE EmailAddress AS
|
|
233
|
+
FROM string
|
|
234
|
+
SELECT {
|
|
235
|
+
are email addresses
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
CREATE Password AS
|
|
239
|
+
FROM string
|
|
240
|
+
SELECT {
|
|
241
|
+
- have at least 8 characters
|
|
242
|
+
- contain at least one upper case character
|
|
243
|
+
- contain at least one lower case character
|
|
244
|
+
- contain at least one number character
|
|
245
|
+
- contain at least one special character
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
EXPORT EmailAddress;
|
|
249
|
+
EXPORT Password;
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Then, we can import `EmailAddress` as itself in some other file:
|
|
253
|
+
|
|
254
|
+
```spex
|
|
255
|
+
IMPORT EmailAddress FROM "types.spex";
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Or give it a different alias:
|
|
259
|
+
|
|
260
|
+
```spex
|
|
261
|
+
IMPORT EmailAddress FROM "types.spex" AS Username;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Or import the whole file:
|
|
265
|
+
|
|
266
|
+
```spex
|
|
267
|
+
IMPORT "types.spex" AS type;
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
In case the whole file is imported, it's objects could be referenced by:
|
|
271
|
+
|
|
272
|
+
```spex
|
|
273
|
+
IMPORT "types.spex" AS types;
|
|
274
|
+
|
|
275
|
+
CREATE SignUp AS
|
|
276
|
+
FROM (user: types.EmailAddress, pass: types.Password) -> string
|
|
277
|
+
SELECT {
|
|
278
|
+
1. Check @user doesn't exists
|
|
279
|
+
2. throw an error if the user exists
|
|
280
|
+
3. add @user to the User table alongside the SHA-256 hash of @pass
|
|
281
|
+
4. return the id of the newly created user
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
# Generating Code
|
|
288
|
+
|
|
289
|
+
To specify what objects in an specification has to be generated as explicit code:
|
|
290
|
+
|
|
291
|
+
```spex
|
|
292
|
+
GENERATE CreateTodo
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Generation of some object naturally triggers generation of it's dependencies as well.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
# Why SQL?
|
|
300
|
+
|
|
301
|
+
Spex uses SQL-inspired syntax because developers already understand:
|
|
302
|
+
|
|
303
|
+
* schemas
|
|
304
|
+
* views
|
|
305
|
+
* refinement through selection
|
|
306
|
+
* declarative programming
|
|
307
|
+
* dependency relationships
|
|
308
|
+
|
|
309
|
+
This dramatically reduces the learning curve.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
# Long-Term Vision
|
|
314
|
+
|
|
315
|
+
Spex aims to provide:
|
|
316
|
+
|
|
317
|
+
* reusable semantic software abstractions
|
|
318
|
+
* compositional AI-assisted programming
|
|
319
|
+
* declarative architecture specification
|
|
320
|
+
* implementation synthesis guided by constraints
|
|
321
|
+
|
|
322
|
+
Instead of prompting LLMs directly, developers work with structured software semantics that can be analyzed, refined, verified, and synthesized.
|
|
323
|
+
|
|
324
|
+
# Example: Todo CLI App
|
|
325
|
+
|
|
326
|
+
This example demonstrates a simple command-line Todo application written in Spex.
|
|
327
|
+
|
|
328
|
+
The application supports:
|
|
329
|
+
|
|
330
|
+
- adding todos
|
|
331
|
+
- listing todos
|
|
332
|
+
- marking todos as completed
|
|
333
|
+
- persisting todos to disk
|
|
334
|
+
- validating input
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Domain Objects
|
|
339
|
+
|
|
340
|
+
```spex
|
|
341
|
+
CREATE TodoTitle AS
|
|
342
|
+
FROM string
|
|
343
|
+
SELECT {
|
|
344
|
+
- are not empty
|
|
345
|
+
- are shorter than 120 characters
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
CREATE Todo AS
|
|
349
|
+
(
|
|
350
|
+
id: string,
|
|
351
|
+
title: TodoTitle,
|
|
352
|
+
completed: bool
|
|
353
|
+
);
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Storage Layer
|
|
359
|
+
|
|
360
|
+
```spex
|
|
361
|
+
CREATE TodoFilePath AS
|
|
362
|
+
FROM string
|
|
363
|
+
SELECT {
|
|
364
|
+
represent a valid path to a JSON file storing todos
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
CREATE LoadTodos AS
|
|
368
|
+
FROM (path: TodoFilePath) -> Todo[]
|
|
369
|
+
SELECT {
|
|
370
|
+
1. read the JSON file at @path
|
|
371
|
+
2. return an empty list if the file does not exist
|
|
372
|
+
3. parse the JSON content into todos
|
|
373
|
+
4. throw an exception if the JSON is invalid
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
CREATE SaveTodos AS
|
|
377
|
+
FROM (
|
|
378
|
+
path: TodoFilePath,
|
|
379
|
+
todos: Todo[]
|
|
380
|
+
) -> unit
|
|
381
|
+
SELECT {
|
|
382
|
+
1. serialize @todos as formatted JSON
|
|
383
|
+
2. write the JSON to @path
|
|
384
|
+
};
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Todo Creation
|
|
390
|
+
|
|
391
|
+
```spex
|
|
392
|
+
CREATE CreateTodo AS
|
|
393
|
+
FROM (
|
|
394
|
+
title: TodoTitle
|
|
395
|
+
) -> Todo
|
|
396
|
+
SELECT {
|
|
397
|
+
1. generate a UUID for the todo id
|
|
398
|
+
2. create a todo with completed set to false
|
|
399
|
+
3. return the created todo
|
|
400
|
+
};
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Add Todo Command
|
|
406
|
+
|
|
407
|
+
```spex
|
|
408
|
+
CREATE AddTodo AS
|
|
409
|
+
FROM (
|
|
410
|
+
path: TodoFilePath,
|
|
411
|
+
title: TodoTitle
|
|
412
|
+
) -> Todo
|
|
413
|
+
SELECT {
|
|
414
|
+
1. call @LoadTodos using @path
|
|
415
|
+
2. call @CreateTodo using @title
|
|
416
|
+
3. append the new todo to the loaded todos
|
|
417
|
+
4. call @SaveTodos to persist the updated todos
|
|
418
|
+
5. return the created todo
|
|
419
|
+
};
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## List Todos Command
|
|
425
|
+
|
|
426
|
+
```spex
|
|
427
|
+
CREATE ListTodos AS
|
|
428
|
+
FROM (
|
|
429
|
+
path: TodoFilePath
|
|
430
|
+
) -> string
|
|
431
|
+
SELECT {
|
|
432
|
+
1. load todos using @LoadTodos
|
|
433
|
+
2. return a formatted string representation of all todos
|
|
434
|
+
3. show completed todos with a checkmark
|
|
435
|
+
4. show incomplete todos with an empty checkbox
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Complete Todo Command
|
|
442
|
+
|
|
443
|
+
```spex
|
|
444
|
+
CREATE CompleteTodo AS
|
|
445
|
+
FROM (
|
|
446
|
+
path: TodoFilePath,
|
|
447
|
+
id: TodoId
|
|
448
|
+
) -> Todo
|
|
449
|
+
SELECT {
|
|
450
|
+
1. load todos using @LoadTodos
|
|
451
|
+
2. search for the todo matching @id
|
|
452
|
+
3. throw an exception if the todo does not exist
|
|
453
|
+
4. set the todo completed status to true
|
|
454
|
+
5. persist the updated todo list using @SaveTodos
|
|
455
|
+
6. return the updated todo
|
|
456
|
+
};
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## CLI Parsing
|
|
462
|
+
|
|
463
|
+
```spex
|
|
464
|
+
CREATE CliArgs AS
|
|
465
|
+
(
|
|
466
|
+
command: string,
|
|
467
|
+
arguments: string[]
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
CREATE ParseCliArgs AS
|
|
471
|
+
FROM string[] -> CliArgs
|
|
472
|
+
SELECT {
|
|
473
|
+
1. parse the command line arguments
|
|
474
|
+
2. extract the command name
|
|
475
|
+
3. extract the command arguments
|
|
476
|
+
};
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## CLI Entry Point
|
|
482
|
+
|
|
483
|
+
```spex
|
|
484
|
+
CREATE Main AS
|
|
485
|
+
FROM string[] -> unit
|
|
486
|
+
SELECT {
|
|
487
|
+
1. parse process arguments using @ParseCliArgs
|
|
488
|
+
|
|
489
|
+
2. if the command is "add":
|
|
490
|
+
- call @AddTodo
|
|
491
|
+
|
|
492
|
+
3. if the command is "list":
|
|
493
|
+
- call @ListTodos
|
|
494
|
+
- print the result to stdout
|
|
495
|
+
|
|
496
|
+
4. if the command is "complete":
|
|
497
|
+
- call @CompleteTodo
|
|
498
|
+
|
|
499
|
+
5. print a help message if the command is invalid
|
|
500
|
+
|
|
501
|
+
6. print user-friendly error messages for exceptions
|
|
502
|
+
};
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Code Generation
|
|
508
|
+
|
|
509
|
+
```spex
|
|
510
|
+
GENERATE Main
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
This triggers generation of the complete CLI application and all required dependencies.
|
package/dist/ast.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type SpexFile = {
|
|
2
|
+
kind: 'SpexFile';
|
|
3
|
+
declarations: Declaration[];
|
|
4
|
+
};
|
|
5
|
+
export type Declaration = ObjectDeclaration | ImportDeclaration | ExportDeclaration | GenerateDeclaration;
|
|
6
|
+
export type ObjectDeclaration = {
|
|
7
|
+
kind: 'ObjectDeclaration';
|
|
8
|
+
name: string;
|
|
9
|
+
object: ObjectExpression;
|
|
10
|
+
};
|
|
11
|
+
export type ImportDeclaration = {
|
|
12
|
+
kind: 'ImportDeclaration';
|
|
13
|
+
name: string | null;
|
|
14
|
+
source: string;
|
|
15
|
+
alias: string | null;
|
|
16
|
+
};
|
|
17
|
+
export type ExportDeclaration = {
|
|
18
|
+
kind: 'ExportDeclaration';
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
export type GenerateDeclaration = {
|
|
22
|
+
kind: 'GenerateDeclaration';
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
export type ObjectExpression = NamedObject | ProductObject | ExponentialObject | SubObject | ArrayObject;
|
|
26
|
+
export type NamedObject = {
|
|
27
|
+
kind: 'NamedObject';
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
30
|
+
export type ProductObject = {
|
|
31
|
+
kind: 'ProductObject';
|
|
32
|
+
fields: Record<string, ObjectExpression>;
|
|
33
|
+
};
|
|
34
|
+
export type ExponentialObject = {
|
|
35
|
+
kind: 'ExponentialObject';
|
|
36
|
+
base: ObjectExpression;
|
|
37
|
+
exponent: ObjectExpression;
|
|
38
|
+
};
|
|
39
|
+
export type SubObject = {
|
|
40
|
+
kind: 'SubObject';
|
|
41
|
+
base: ObjectExpression;
|
|
42
|
+
constraint: string;
|
|
43
|
+
};
|
|
44
|
+
export type ArrayObject = {
|
|
45
|
+
kind: 'ArrayObject';
|
|
46
|
+
base: ObjectExpression;
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=ast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,UAAU,CAAA;IAChB,YAAY,EAAE,WAAW,EAAE,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,WAAW,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,mBAAmB,CAAA;AAEvB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,mBAAmB,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,gBAAgB,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,mBAAmB,CAAA;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,mBAAmB,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,qBAAqB,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,gBAAgB,GACxB,WAAW,GACX,aAAa,GACb,iBAAiB,GACjB,SAAS,GACT,WAAW,CAAA;AAEf,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,eAAe,CAAA;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACzC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,mBAAmB,CAAA;IACzB,IAAI,EAAE,gBAAgB,CAAA;IACtB,QAAQ,EAAE,gBAAgB,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,gBAAgB,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,gBAAgB,CAAA;CACvB,CAAA"}
|
package/dist/ast.js
ADDED
package/dist/ast.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SpexLexer } from './lexer.js';
|
|
2
|
+
export { SpexParser } from './parser.js';
|
|
3
|
+
export { SpexParserVisitor, parseToAst } from './visitor.js';
|
|
4
|
+
export type { SpexFile, Declaration, ObjectDeclaration, ObjectExpression, NamedObject, ProductObject, ExponentialObject, SubObject, } from './ast.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC5D,YAAY,EACV,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,SAAS,GACV,MAAM,UAAU,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA"}
|