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/README.md
CHANGED
|
@@ -16,14 +16,14 @@ The idea behind chat interfaces in AI coding tools is that _everyone_ should be
|
|
|
16
16
|
|
|
17
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
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
|
|
19
|
+
For this reason, Spex syntax is intentionally close to common languages such as TypeScript and SQL. Instead of manually implementing software, developers describe what they want using familiar programming abstractions:
|
|
20
20
|
|
|
21
21
|
- objects
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
22
|
+
- patterns
|
|
23
|
+
- realizations
|
|
24
|
+
- environments
|
|
25
25
|
|
|
26
|
-
The Spex runtime synthesizes concrete implementations
|
|
26
|
+
The central operation in Spex is **realization**: a concept is decomposed into other concepts and/or artifacts, gradually becoming more concrete. The Spex runtime synthesizes concrete implementations by following these realization paths to their artifact endpoints.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -31,24 +31,50 @@ The Spex runtime synthesizes concrete implementations based on these specificati
|
|
|
31
31
|
|
|
32
32
|
In Spex:
|
|
33
33
|
|
|
34
|
-
- a
|
|
35
|
-
-
|
|
36
|
-
-
|
|
34
|
+
- a **concept** describes something that needs to be realized
|
|
35
|
+
- **realization** decomposes a concept into other concepts and/or artifacts
|
|
36
|
+
- **subobjecting** restricts the members of an artifact universe
|
|
37
|
+
- an **environment** constrains which realization paths are available
|
|
38
|
+
|
|
39
|
+
The fundamental model is:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Concept ── realization in Environment ──> Concept / Artifact
|
|
43
|
+
```
|
|
37
44
|
|
|
38
45
|
For example:
|
|
39
46
|
|
|
40
47
|
```spex
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- The call is rate limited.
|
|
48
|
+
create WebApplication as
|
|
49
|
+
from concept
|
|
50
|
+
select {
|
|
51
|
+
serve HTTP requests and respond with JSON
|
|
46
52
|
};
|
|
47
53
|
```
|
|
48
54
|
|
|
49
|
-
`
|
|
55
|
+
`WebApplication` is a concept. It can be realized as a product of more specific concepts:
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
```text
|
|
58
|
+
WebApplication
|
|
59
|
+
── realized-by ──>
|
|
60
|
+
(
|
|
61
|
+
database: SQLSchemaDescription,
|
|
62
|
+
backend: ExpressAppDescription,
|
|
63
|
+
frontend: ReactUIDescription
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Each of those can be realized further until concrete artifacts are reached. Realization is recursive:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
Concept
|
|
71
|
+
│
|
|
72
|
+
└── realization ──> Concept
|
|
73
|
+
│
|
|
74
|
+
└── realization ──> Artifact
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Developers build on these abstractions instead of repeatedly specifying common architectural concerns.
|
|
52
78
|
|
|
53
79
|
---
|
|
54
80
|
|
|
@@ -65,28 +91,46 @@ Spex is designed to:
|
|
|
65
91
|
|
|
66
92
|
# Objects
|
|
67
93
|
|
|
68
|
-
|
|
94
|
+
Spex describes software using three fundamental kinds of object: **artifacts**, **concepts**, and **environments**. These are the base universes of the model. Every named or expression-level object belongs to exactly one of them.
|
|
95
|
+
|
|
96
|
+
## The Artifact Universe
|
|
97
|
+
|
|
98
|
+
`artifact` is the universe of concrete, producible things. Artifacts are not synonymous with source code or programs. An artifact can be a string, a number, a file, a structured object, or any other concrete representation that can be produced, manipulated, or used as a realization.
|
|
99
|
+
|
|
100
|
+
The artifact universe contains many sub-kinds:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
artifact
|
|
104
|
+
├── string
|
|
105
|
+
├── number
|
|
106
|
+
├── bool
|
|
107
|
+
├── unit
|
|
108
|
+
├── product
|
|
109
|
+
├── exponential
|
|
110
|
+
├── array
|
|
111
|
+
├── pattern
|
|
112
|
+
├── literal
|
|
113
|
+
└── ...
|
|
114
|
+
```
|
|
69
115
|
|
|
70
|
-
|
|
116
|
+
### Basic Objects
|
|
71
117
|
|
|
72
|
-
|
|
118
|
+
Spex provides several built-in artifact objects:
|
|
73
119
|
|
|
74
120
|
```spex
|
|
75
121
|
string
|
|
76
122
|
number
|
|
77
123
|
bool
|
|
78
124
|
unit
|
|
79
|
-
concept
|
|
80
|
-
environment
|
|
81
125
|
```
|
|
82
126
|
|
|
83
|
-
`unit` is a special
|
|
127
|
+
`string`, `number`, and `bool` represent the familiar value universes. `unit` is a special artifact that represents the empty product — a space with exactly one member. It is useful in defining functions that take no input or do not return anything.
|
|
84
128
|
|
|
85
|
-
`concept` and `environment` are
|
|
129
|
+
`concept` and `environment` are separate base universes, not sub-kinds of artifact. They are covered in [Concepts, Environments, and Realization](#concepts-environments-and-realization).
|
|
86
130
|
|
|
87
131
|
## Arrays
|
|
88
132
|
|
|
89
|
-
|
|
133
|
+
An array is an artifact whose members are sequences of some base artifact:
|
|
90
134
|
|
|
91
135
|
```spex
|
|
92
136
|
string[]
|
|
@@ -94,7 +138,7 @@ string[]
|
|
|
94
138
|
|
|
95
139
|
## Products
|
|
96
140
|
|
|
97
|
-
|
|
141
|
+
A product is an artifact formed by combining other artifacts into a structured record:
|
|
98
142
|
|
|
99
143
|
```spex
|
|
100
144
|
(
|
|
@@ -120,20 +164,20 @@ Consequently, `()` and `unit` are the same object.
|
|
|
120
164
|
|
|
121
165
|
## Coproducts
|
|
122
166
|
|
|
123
|
-
|
|
167
|
+
A coproduct is an artifact that represents a choice between alternatives. Where a product means "both", a coproduct means "either". A value of a coproduct holds exactly one of the alternatives and records which one. Coproducts are also called sum types or tagged unions:
|
|
124
168
|
|
|
125
169
|
```spex
|
|
126
|
-
|
|
170
|
+
create Shape as
|
|
127
171
|
Point | Circle;
|
|
128
172
|
|
|
129
|
-
|
|
173
|
+
create Command as
|
|
130
174
|
AddTodo | ListTodos | CompleteTodo;
|
|
131
175
|
```
|
|
132
176
|
|
|
133
177
|
Coproducts combine with any other object form:
|
|
134
178
|
|
|
135
179
|
```spex
|
|
136
|
-
|
|
180
|
+
create Result as
|
|
137
181
|
string | (error: string) | unit;
|
|
138
182
|
```
|
|
139
183
|
|
|
@@ -142,26 +186,26 @@ Because the alternatives are disjoint, a coproduct needs no common universe: `A
|
|
|
142
186
|
Operator precedence, from loosest to tightest, is: set operations, then `|`, then `->`:
|
|
143
187
|
|
|
144
188
|
```spex
|
|
145
|
-
|
|
189
|
+
create X as
|
|
146
190
|
A -> B | C; -- (A -> B) | C
|
|
147
191
|
|
|
148
|
-
|
|
192
|
+
create Y as
|
|
149
193
|
A UNION B | C; -- A UNION (B | C)
|
|
150
194
|
```
|
|
151
195
|
|
|
152
196
|
Use parentheses to group any expression and override the default precedence. A `(` opens a group unless it is followed by a field name and a colon, in which case it opens a product:
|
|
153
197
|
|
|
154
198
|
```spex
|
|
155
|
-
|
|
199
|
+
create X as
|
|
156
200
|
A -> (B | C);
|
|
157
201
|
|
|
158
|
-
|
|
202
|
+
create Y as
|
|
159
203
|
(A UNION B) EXCEPT C;
|
|
160
204
|
```
|
|
161
205
|
|
|
162
206
|
## Exponentials
|
|
163
207
|
|
|
164
|
-
|
|
208
|
+
An exponential is an artifact that represents a function space. It has a base (the result type) and an exponent (the parameter type), both of which must be artifacts:
|
|
165
209
|
|
|
166
210
|
```spex
|
|
167
211
|
string -> number
|
|
@@ -170,72 +214,232 @@ string -> unit
|
|
|
170
214
|
unit -> string
|
|
171
215
|
```
|
|
172
216
|
|
|
173
|
-
`string -> unit` represents all functions that take a string as input and do not return anything. `unit -> string`
|
|
217
|
+
`string -> unit` represents all functions that take a string as input and do not return anything. `unit -> string` is a function that takes nothing as input but returns a string.
|
|
174
218
|
|
|
175
|
-
##
|
|
219
|
+
## Subobjects
|
|
176
220
|
|
|
177
|
-
|
|
221
|
+
Subobjecting selects a subset of the members of an existing universe while preserving their kind. For example:
|
|
178
222
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
lambda number -> number ```python
|
|
182
|
-
return @n * 2
|
|
223
|
+
```text
|
|
224
|
+
PositiveNumber ⊆ Number
|
|
183
225
|
```
|
|
184
|
-
````
|
|
185
226
|
|
|
186
|
-
|
|
227
|
+
Every member of `PositiveNumber` is still a `Number`. No decomposition has occurred and no information-bearing structure has been replaced by parts. The universe has simply been restricted.
|
|
228
|
+
|
|
229
|
+
This is fundamentally different from **realization**, which decomposes an object into other objects (see [Concepts, Environments, and Realization](#concepts-environments-and-realization)):
|
|
230
|
+
|
|
231
|
+
```text
|
|
232
|
+
SUBOBJECTING
|
|
233
|
+
restricts membership within a universe
|
|
234
|
+
|
|
235
|
+
REALIZATION
|
|
236
|
+
decomposes an object into other objects
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### The `from ... select ...` Syntax
|
|
240
|
+
|
|
241
|
+
The universal mechanism for subobjecting is:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
from X select P
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
read as: "from the universe `X`, select the members described by pattern `P`". The pattern determines membership — it describes how to establish whether an object belongs to the subobject. Patterns come in three forms — natural language, structured, and code — described in [Pattern Kinds](#pattern-kinds) below.
|
|
248
|
+
|
|
249
|
+
For example:
|
|
250
|
+
|
|
251
|
+
```spex
|
|
252
|
+
from number select {
|
|
253
|
+
are positive
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
names the subobject of `number` containing exactly the positive numbers.
|
|
258
|
+
|
|
259
|
+
What subobjecting means depends on the universe being restricted.
|
|
260
|
+
|
|
261
|
+
### Subobjecting Artifacts
|
|
262
|
+
|
|
263
|
+
Most subobjecting happens within the artifact universe. The pattern restricts which artifacts — strings, numbers, products, functions, and so on — belong to the subobject.
|
|
264
|
+
|
|
265
|
+
#### Non-Exponential Artifacts
|
|
266
|
+
|
|
267
|
+
For non-exponential artifacts such as `string`, `number`, `bool`, and products, a pattern behaves like a membership test on the members themselves:
|
|
268
|
+
|
|
269
|
+
```spex
|
|
270
|
+
from string select {
|
|
271
|
+
are email addresses
|
|
272
|
+
}
|
|
273
|
+
```
|
|
187
274
|
|
|
188
|
-
|
|
275
|
+
defines the subobject of `string` containing exactly the strings that are email addresses. Conceptually, the pattern is a classifier over the source universe:
|
|
276
|
+
|
|
277
|
+
```text
|
|
278
|
+
number
|
|
279
|
+
│
|
|
280
|
+
│ classifier
|
|
281
|
+
▼
|
|
282
|
+
{ n ∈ number | classifier(n) = true }
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
When a structured or code pattern is used, the subobject of a non-exponential artifact is realized as a classifier function over the source universe.
|
|
286
|
+
|
|
287
|
+
#### Exponentials
|
|
288
|
+
|
|
289
|
+
An exponential is a function space, so subobjecting an exponential restricts which functions belong to the subobject. Because a function is not inspected member-by-member the way a value is, the pattern describes the function's computational representation — what it computes and how:
|
|
189
290
|
|
|
190
291
|
````spex
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@{return cos(@x)}
|
|
292
|
+
from Number -> Bool select ```python
|
|
293
|
+
if x > 0:
|
|
294
|
+
return True
|
|
295
|
+
else:
|
|
296
|
+
return False
|
|
197
297
|
```
|
|
198
298
|
````
|
|
199
299
|
|
|
200
|
-
|
|
300
|
+
describes the subobject of `Number -> Bool` whose members behave this way. Natural language works just as well:
|
|
201
301
|
|
|
202
|
-
|
|
302
|
+
```spex
|
|
303
|
+
from string -> number select {
|
|
304
|
+
return the length of the given string
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
describes the subobject of `string -> number` containing exactly the functions that return the length of their input.
|
|
309
|
+
|
|
310
|
+
The grammar does not treat exponentials specially: `from A -> B select ...` and `from number select ...` are the same subobjecting operation. Deciding whether a pattern is a classifier over values or a description of function behavior is a compile-time semantic concern, not a grammar restriction. The compiler infers a pattern's signature where possible, checks it against the source object, and rejects ambiguous or incompatible patterns (see [Code Patterns](#code-patterns)).
|
|
311
|
+
|
|
312
|
+
### Subobjecting Concepts
|
|
313
|
+
|
|
314
|
+
A concept describes something that still needs realization. Subobjecting a concept produces a more specific concept — it restricts which implementations the concept stands for without decomposing it:
|
|
315
|
+
|
|
316
|
+
```spex
|
|
317
|
+
create HttpApi as
|
|
318
|
+
from concept
|
|
319
|
+
select {
|
|
320
|
+
serve HTTP requests and respond with JSON
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
create EchoApi as
|
|
324
|
+
from HttpApi
|
|
325
|
+
select {
|
|
326
|
+
return the request body unchanged
|
|
327
|
+
};
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Because `EchoApi` is a subobject of `HttpApi`, it inherits everything `HttpApi` stands for and only adds membership restrictions on top of it. Concepts are covered in depth in [Concepts, Environments, and Realization](#concepts-environments-and-realization).
|
|
331
|
+
|
|
332
|
+
### Subobjecting Environments
|
|
333
|
+
|
|
334
|
+
An environment describes the context in which realizations take place. Subobjecting an environment produces a more specific environment:
|
|
335
|
+
|
|
336
|
+
```spex
|
|
337
|
+
create Python as
|
|
338
|
+
from environment
|
|
339
|
+
select {
|
|
340
|
+
language: Python
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
create FastAPI as
|
|
344
|
+
from Python
|
|
345
|
+
select {
|
|
346
|
+
dependencies: fastapi, uvicorn
|
|
347
|
+
};
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`FastAPI` is a subobject of `Python`: every environment satisfying `FastAPI`'s pattern is also a Python environment.
|
|
351
|
+
|
|
352
|
+
### Pattern Kinds
|
|
353
|
+
|
|
354
|
+
A pattern can be written in three forms. All three can be used with any source universe, but they differ in how — and how precisely — they determine membership.
|
|
355
|
+
|
|
356
|
+
#### Natural Language Patterns
|
|
203
357
|
|
|
204
|
-
|
|
358
|
+
Natural language patterns are written in braces:
|
|
205
359
|
|
|
206
360
|
```spex
|
|
207
|
-
|
|
208
|
-
SELECT {
|
|
361
|
+
from string select {
|
|
209
362
|
are email addresses
|
|
210
363
|
}
|
|
364
|
+
```
|
|
211
365
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
366
|
+
A natural language pattern describes the membership criterion in prose. It carries no structure that can be checked mechanically, so it is **not provable** in the generated artifact: it is a statement of intent to be honored when producing members of the subobject.
|
|
367
|
+
|
|
368
|
+
#### Structured Patterns
|
|
369
|
+
|
|
370
|
+
Structured patterns are fenced code blocks without a language identifier. They are written in **SKIT** (Structured Kernel Implementation Template), a minimal language of programming directives that is supported by every modern programming language — `if` expressions, loops, `try`/`catch`, and similar. Because SKIT is universal, a structured pattern can be satisfied by a realization produced in any programming language:
|
|
371
|
+
|
|
372
|
+
````spex
|
|
373
|
+
from number select ```
|
|
374
|
+
if &number > 0 {
|
|
375
|
+
return true
|
|
215
376
|
}
|
|
216
377
|
```
|
|
378
|
+
````
|
|
379
|
+
|
|
380
|
+
Unlike natural language patterns, structured patterns are **(partially) provable**: during generation the produced artifact is checked against the pattern, and the provable parts are validated automatically.
|
|
381
|
+
|
|
382
|
+
Calling an exponential in a structured or code constraint is the **only** method to provably add a function call to a generated artifact. Where a structured or code pattern calls an exponential object, the generated artifact provably contains that call. In a natural-language pattern the same mention is intent, not proof: it is honored when members are produced, but nothing mechanical guarantees it.
|
|
383
|
+
|
|
384
|
+
**Generation directives** mark the positions in a structured or code pattern where unprovable code is generated. A generation directive is a comment starting with `gen:` followed by a natural-language description of what to generate. In the example below, the generated artifact must contain an `if` block that checks `x > 0`; the body of that block is a generation directive, so exactly what it computes is not provable from the pattern:
|
|
385
|
+
|
|
386
|
+
````spex
|
|
387
|
+
from artifact select ```
|
|
388
|
+
if x > 0 {
|
|
389
|
+
// gen: let $y be the square root of &x
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
````
|
|
217
393
|
|
|
218
|
-
|
|
394
|
+
#### Code Patterns
|
|
219
395
|
|
|
220
|
-
|
|
396
|
+
Code patterns are fenced code blocks with a language identifier. They are similar to structured patterns but apply to one specific language, so they may also use features that are specific to that language. The language identifier is recorded alongside the body in the AST:
|
|
397
|
+
|
|
398
|
+
````spex
|
|
399
|
+
from number select ```python
|
|
400
|
+
if &number > 0:
|
|
401
|
+
return True
|
|
402
|
+
else:
|
|
403
|
+
return False
|
|
404
|
+
```
|
|
405
|
+
````
|
|
406
|
+
|
|
407
|
+
The body of a code pattern must be syntactically valid in the language it specifies. A pattern with `python` in the fence, for example, should be valid Python.
|
|
408
|
+
|
|
409
|
+
Whether a code pattern is meaningful for the source universe is a compile-time semantic/type-checking concern, not a grammar restriction:
|
|
410
|
+
|
|
411
|
+
1. Parse the code pattern.
|
|
412
|
+
2. Infer its signature when possible.
|
|
413
|
+
3. Check compatibility with the source object.
|
|
414
|
+
4. Reject ambiguous or incompatible patterns at compile time.
|
|
415
|
+
|
|
416
|
+
A code pattern may be syntactically valid in the grammar but semantically invalid in a particular `from ... select ...` context.
|
|
417
|
+
|
|
418
|
+
Subobjects are themselves objects so they can be subobjected further. A good heuristic is to make the expression read as:
|
|
419
|
+
|
|
420
|
+
> "from `object` select those that `{pattern}`".
|
|
221
421
|
|
|
222
422
|
## Set Operations
|
|
223
423
|
|
|
224
424
|
Objects that live in a common universe can be combined with the set operations `UNION`, `INTERSECT`, and `EXCEPT`:
|
|
225
425
|
|
|
226
426
|
```spex
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
427
|
+
create EvenInt as
|
|
428
|
+
from int
|
|
429
|
+
select { are even };
|
|
230
430
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
431
|
+
create PositiveInt as
|
|
432
|
+
from int
|
|
433
|
+
select { are positive };
|
|
234
434
|
|
|
235
|
-
|
|
435
|
+
create EvenPositiveInt as
|
|
236
436
|
EvenInt INTERSECT PositiveInt;
|
|
237
|
-
|
|
238
|
-
|
|
437
|
+
|
|
438
|
+
create EvenOrPositive as
|
|
439
|
+
EvenInt UNION PositiveInt;
|
|
440
|
+
|
|
441
|
+
create EvenNotPositive as
|
|
442
|
+
EvenInt EXCEPT PositiveInt;
|
|
239
443
|
```
|
|
240
444
|
|
|
241
445
|
`UNION` keeps members that satisfy either side, `INTERSECT` keeps members that satisfy both sides, and `EXCEPT` removes the members of the right side from the left side.
|
|
@@ -243,13 +447,13 @@ CREATE EvenNotPositive AS EvenInt EXCEPT PositiveInt;
|
|
|
243
447
|
Set operations bind loosest of all object operators and chain left-to-right:
|
|
244
448
|
|
|
245
449
|
```spex
|
|
246
|
-
|
|
450
|
+
create X as
|
|
247
451
|
A UNION B EXCEPT C; -- (A UNION B) EXCEPT C
|
|
248
452
|
```
|
|
249
453
|
|
|
250
454
|
## Literals
|
|
251
455
|
|
|
252
|
-
A literal
|
|
456
|
+
A literal denotes a single value, and therefore represents the set containing exactly that value:
|
|
253
457
|
|
|
254
458
|
```spex
|
|
255
459
|
"root" -- the string root
|
|
@@ -257,30 +461,19 @@ A literal object denotes a single value, and therefore represents the set contai
|
|
|
257
461
|
true -- the boolean true
|
|
258
462
|
```
|
|
259
463
|
|
|
260
|
-
Literals can
|
|
464
|
+
Literals can participate in subobjecting or serve as alternatives in a coproduct. A named set of allowed values is simply a coproduct of literals:
|
|
261
465
|
|
|
262
466
|
```spex
|
|
263
|
-
|
|
467
|
+
create UserName as
|
|
264
468
|
string EXCEPT "root";
|
|
265
469
|
|
|
266
|
-
|
|
470
|
+
create Handedness as
|
|
267
471
|
"left" | "right";
|
|
268
472
|
```
|
|
269
473
|
|
|
270
|
-
## Enums
|
|
271
|
-
|
|
272
|
-
An enum object declares a named set of allowed string values:
|
|
273
|
-
|
|
274
|
-
```spex
|
|
275
|
-
CREATE Color AS
|
|
276
|
-
ENUM ('red', 'green', 'blue');
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
An enum constrains a value to one of the listed strings.
|
|
280
|
-
|
|
281
474
|
## Patterns
|
|
282
475
|
|
|
283
|
-
A pattern literal
|
|
476
|
+
A regex pattern literal is a pattern that defines a subobject of `string` — its members are precisely the strings matching the regex:
|
|
284
477
|
|
|
285
478
|
```spex
|
|
286
479
|
/\d+/
|
|
@@ -288,22 +481,37 @@ A pattern literal denotes the subobject of `string` containing exactly the strin
|
|
|
288
481
|
/'([^'\\]|\\.)*'|"([^"\\]|\\.)*"/
|
|
289
482
|
```
|
|
290
483
|
|
|
291
|
-
The source is kept verbatim and flags such as `i` (case-insensitive) follow the closing slash. Because a pattern is a subobject of
|
|
484
|
+
The source is kept verbatim and flags such as `i` (case-insensitive) follow the closing slash. Because a pattern is itself an artifact (a subobject of `string`), it participates in set operations and coproducts like any other artifact:
|
|
292
485
|
|
|
293
486
|
```spex
|
|
294
|
-
|
|
295
|
-
|
|
487
|
+
create Digits as /\d+/;
|
|
488
|
+
create Word as /\w+/;
|
|
296
489
|
|
|
297
|
-
|
|
490
|
+
create DigitOrWord as Digits UNION Word;
|
|
298
491
|
```
|
|
299
492
|
|
|
493
|
+
This illustrates a general principle: different artifact kinds have different pattern representations. Regex patterns are the simplest example — a regex expression directly denotes a subobject of `string`. Code and structured patterns extend this idea to other artifact universes by expressing membership predicates as code.
|
|
494
|
+
|
|
300
495
|
# Concepts, Environments, and Realization
|
|
301
496
|
|
|
302
|
-
Spex distinguishes between _what_ software should be and _where_ and _how_ it is realized.
|
|
497
|
+
Spex distinguishes between _what_ software should be and _where_ and _how_ it is realized. The central operation is **realization**: a concept is decomposed into other concepts and/or artifacts, gradually becoming more concrete. This is fundamentally different from subobjecting, which restricts membership within a universe without decomposition.
|
|
498
|
+
|
|
499
|
+
```text
|
|
500
|
+
SUBOBJECTING
|
|
501
|
+
restricts membership within a universe
|
|
502
|
+
|
|
503
|
+
REALIZATION
|
|
504
|
+
decomposes an object into other objects
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
```text
|
|
508
|
+
PositiveNumber ⊆ Number -- subobjecting
|
|
509
|
+
WebApplication → (db, be, fe) -- realization
|
|
510
|
+
```
|
|
303
511
|
|
|
304
512
|
## Concept
|
|
305
513
|
|
|
306
|
-
A `concept` is a built-in base
|
|
514
|
+
A `concept` is a built-in base universe that represents an abstract specification of something that needs to be realized. Concepts are ordinary Spex objects and can be subobjected just like any other object:
|
|
307
515
|
|
|
308
516
|
```spex
|
|
309
517
|
create HttpApi as
|
|
@@ -319,13 +527,13 @@ select {
|
|
|
319
527
|
};
|
|
320
528
|
```
|
|
321
529
|
|
|
322
|
-
Because `EchoApi` is a subobject of `HttpApi`, it inherits everything `HttpApi` stands for and only adds
|
|
530
|
+
Because `EchoApi` is a subobject of `HttpApi`, it inherits everything `HttpApi` stands for and only adds membership restrictions on top of it.
|
|
323
531
|
|
|
324
532
|
A concept can be abstract and can itself be composed of other abstract concepts. It does not need to directly correspond to executable code. The goal of concepts is to allow specifications to remain independent of implementation details: a concept describes _what_ the software should be, leaving _how_ it is built to be decided later.
|
|
325
533
|
|
|
326
534
|
## Environment
|
|
327
535
|
|
|
328
|
-
An `environment` is a
|
|
536
|
+
An `environment` is a built-in base universe. It describes the development and runtime context in which realizations take place. An environment is independent from the application specification.
|
|
329
537
|
|
|
330
538
|
An environment may specify:
|
|
331
539
|
|
|
@@ -351,18 +559,38 @@ select {
|
|
|
351
559
|
};
|
|
352
560
|
```
|
|
353
561
|
|
|
354
|
-
An environment is itself something that can be realized into an _environment artifact_: a reproducible description of the environment, such as a Dockerfile. Docker is not the only possible backend; any artifact that reproducibly describes the environment can serve this role.
|
|
355
|
-
|
|
356
|
-
Environment construction is separate from application-code generation. Preparing the context in which the software runs is a distinct concern from generating the software itself.
|
|
562
|
+
An environment determines or constrains which realization paths are available. Different environments can therefore provide different realization paths for the same concept. An environment is itself something that can be realized into an _environment artifact_: a reproducible description of the environment, such as a Dockerfile. Docker is not the only possible backend; any artifact that reproducibly describes the environment can serve this role.
|
|
357
563
|
|
|
358
564
|
## Realization
|
|
359
565
|
|
|
360
566
|
Realization is the mechanism that connects an abstract concept to a more concrete representation. It is fundamentally different from subobjecting:
|
|
361
567
|
|
|
362
|
-
-
|
|
363
|
-
-
|
|
568
|
+
- **Subobjecting** preserves the object's base universe. Every member of `PositiveNumber` is still a `Number`.
|
|
569
|
+
- **Realization** may cross universe boundaries. Realizing a `Concept` does not mean the result is a subobject of that concept.
|
|
364
570
|
|
|
365
|
-
|
|
571
|
+
A realization decomposes a concept into other concepts and/or artifacts:
|
|
572
|
+
|
|
573
|
+
```text
|
|
574
|
+
Concept
|
|
575
|
+
│
|
|
576
|
+
└── realization ──> Concept
|
|
577
|
+
│
|
|
578
|
+
└── realization ──> Artifact
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
For example, a `WebApplication` concept might be realized as a product of more specific concepts:
|
|
582
|
+
|
|
583
|
+
```text
|
|
584
|
+
WebApplication
|
|
585
|
+
── realized-by ──>
|
|
586
|
+
(
|
|
587
|
+
database: SQLSchemaDescription,
|
|
588
|
+
backend: ExpressAppDescription,
|
|
589
|
+
frontend: ReactUIDescription
|
|
590
|
+
)
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Each component is a part/decomposition of the `WebApplication` — not a more specific `WebApplication`.
|
|
366
594
|
|
|
367
595
|
A realization is associated with an environment because different environments may realize the same abstract concept differently. The same abstract `HttpApi`, for example, might be realized using Flask in a Python environment or Express in a TypeScript environment:
|
|
368
596
|
|
|
@@ -384,18 +612,14 @@ Realization is recursive: an abstract concept can be realized into objects that
|
|
|
384
612
|
|
|
385
613
|
## Relationship Between the Three
|
|
386
614
|
|
|
387
|
-
The overall model connects the specification to concrete
|
|
615
|
+
The overall model connects the specification to concrete artifacts:
|
|
388
616
|
|
|
389
617
|
```text
|
|
390
|
-
Concept
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
|
396
|
-
| generation
|
|
397
|
-
v
|
|
398
|
-
Concrete implementation/code
|
|
618
|
+
Concept ── realization in Environment ──> Concept / Artifact
|
|
619
|
+
│
|
|
620
|
+
│ subobjecting
|
|
621
|
+
▼
|
|
622
|
+
constrained artifact
|
|
399
623
|
```
|
|
400
624
|
|
|
401
625
|
Environments follow the same path towards a concrete artifact:
|
|
@@ -403,7 +627,7 @@ Environments follow the same path towards a concrete artifact:
|
|
|
403
627
|
```text
|
|
404
628
|
Environment
|
|
405
629
|
|
|
|
406
|
-
|
|
|
630
|
+
| realization
|
|
407
631
|
v
|
|
408
632
|
Environment artifact
|
|
409
633
|
(e.g. Dockerfile)
|
|
@@ -413,14 +637,16 @@ The important distinction is:
|
|
|
413
637
|
|
|
414
638
|
**Concepts describe what the software should be.
|
|
415
639
|
Environments describe where/how it is to be realized.
|
|
416
|
-
Realizations
|
|
640
|
+
Realizations decompose the abstract specification into concrete representations.**
|
|
641
|
+
|
|
642
|
+
Synthia can use this graph to choose a path from an abstract concept toward concrete artifacts.
|
|
417
643
|
|
|
418
644
|
# Named Objects
|
|
419
645
|
|
|
420
646
|
To name an object for reuse:
|
|
421
647
|
|
|
422
648
|
```spex
|
|
423
|
-
|
|
649
|
+
create Todo as
|
|
424
650
|
(
|
|
425
651
|
id: string,
|
|
426
652
|
title: string,
|
|
@@ -428,15 +654,15 @@ CREATE Todo AS
|
|
|
428
654
|
created_at: string
|
|
429
655
|
);
|
|
430
656
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
657
|
+
create EmailAddress as
|
|
658
|
+
from string
|
|
659
|
+
select {
|
|
434
660
|
are email addresses
|
|
435
661
|
};
|
|
436
662
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
663
|
+
create slugify as
|
|
664
|
+
from string -> string
|
|
665
|
+
select {
|
|
440
666
|
return the slugified string
|
|
441
667
|
};
|
|
442
668
|
```
|
|
@@ -445,10 +671,10 @@ SELECT {
|
|
|
445
671
|
|
|
446
672
|
# Referencing
|
|
447
673
|
|
|
448
|
-
|
|
674
|
+
A `@ref` in a constraint — whether natural language, structured, or code — is a directive to bring the named object into the **context for generation**. It makes that object available to the generator while it produces members of the subobject. The scope of a `@ref` directive is determined using the same rules as in TypeScript.
|
|
449
675
|
|
|
450
676
|
```spex
|
|
451
|
-
|
|
677
|
+
create Todo as
|
|
452
678
|
(
|
|
453
679
|
id: string,
|
|
454
680
|
title: string,
|
|
@@ -456,101 +682,96 @@ CREATE Todo AS
|
|
|
456
682
|
created_at: string
|
|
457
683
|
);
|
|
458
684
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
685
|
+
create validate as
|
|
686
|
+
from Todo -> bool
|
|
687
|
+
select {
|
|
462
688
|
return true if @created_at is a valid date and return false otherwise
|
|
463
689
|
};
|
|
464
690
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
691
|
+
create CreateTodo as
|
|
692
|
+
from Todo -> Bool
|
|
693
|
+
select {
|
|
468
694
|
1. call @validate to validate the given todo
|
|
469
695
|
2. throw an exception if validation failed
|
|
470
696
|
3. insert the todo in the Todo table
|
|
471
697
|
}
|
|
472
698
|
```
|
|
473
699
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
The parser automatically extracts references from constraints into structured AST nodes, making it easy to analyze dependencies programmatically. Each constraint is parsed into a sequence of text segments and reference nodes:
|
|
700
|
+
The parser automatically extracts `@ref` directives from every constraint kind into structured AST nodes (`ReferenceDirective`), making it easy to analyze dependencies programmatically. Each constraint is parsed into a sequence of text segments and directive nodes:
|
|
477
701
|
|
|
478
702
|
```spex
|
|
479
703
|
"call @LoadTodos using @path"
|
|
480
|
-
→ [text: "call ",
|
|
704
|
+
→ [text: "call ", directive: LoadTodos, text: " using ", directive: path]
|
|
481
705
|
```
|
|
482
706
|
|
|
483
707
|
Use `.` to reference a member of a product object:
|
|
484
708
|
|
|
485
709
|
```spex
|
|
486
|
-
|
|
710
|
+
create ComplexNumber as
|
|
487
711
|
(
|
|
488
712
|
real: number,
|
|
489
713
|
imag: number
|
|
490
714
|
);
|
|
491
715
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
716
|
+
create Abs as
|
|
717
|
+
from (z: ComplexNumber) -> number
|
|
718
|
+
select {
|
|
495
719
|
return square root of @z.real^2 + @z.imag^2
|
|
496
720
|
}
|
|
497
721
|
```
|
|
498
722
|
|
|
499
723
|
---
|
|
500
724
|
|
|
501
|
-
# Importing
|
|
725
|
+
# Importing
|
|
502
726
|
|
|
503
|
-
|
|
727
|
+
Any defined object can be reused in another file by importing it where it is needed.
|
|
504
728
|
|
|
505
729
|
Suppose we have a file `types.spex` with the following content:
|
|
506
730
|
|
|
507
731
|
```spex
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
732
|
+
create EmailAddress as
|
|
733
|
+
from string
|
|
734
|
+
select {
|
|
511
735
|
are email addresses
|
|
512
736
|
};
|
|
513
737
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
738
|
+
create Password as
|
|
739
|
+
from string
|
|
740
|
+
select {
|
|
517
741
|
- have at least 8 characters
|
|
518
742
|
- contain at least one upper case character
|
|
519
743
|
- contain at least one lower case character
|
|
520
744
|
- contain at least one number character
|
|
521
745
|
- contain at least one special character
|
|
522
746
|
};
|
|
523
|
-
|
|
524
|
-
EXPORT EmailAddress;
|
|
525
|
-
EXPORT Password;
|
|
526
747
|
```
|
|
527
748
|
|
|
528
749
|
Then, we can import `EmailAddress` as itself in some other file:
|
|
529
750
|
|
|
530
751
|
```spex
|
|
531
|
-
|
|
752
|
+
import EmailAddress from "types.spex";
|
|
532
753
|
```
|
|
533
754
|
|
|
534
755
|
Or give it a different alias:
|
|
535
756
|
|
|
536
757
|
```spex
|
|
537
|
-
|
|
758
|
+
import EmailAddress from "types.spex" as Username;
|
|
538
759
|
```
|
|
539
760
|
|
|
540
761
|
Or import the whole file:
|
|
541
762
|
|
|
542
763
|
```spex
|
|
543
|
-
|
|
764
|
+
import "types.spex" as type;
|
|
544
765
|
```
|
|
545
766
|
|
|
546
|
-
In case the whole file is imported,
|
|
767
|
+
In case the whole file is imported, its objects could be referenced by:
|
|
547
768
|
|
|
548
769
|
```spex
|
|
549
|
-
|
|
770
|
+
import "types.spex" as types;
|
|
550
771
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
772
|
+
create SignUp as
|
|
773
|
+
from (user: types.EmailAddress, pass: types.Password) -> string
|
|
774
|
+
select {
|
|
554
775
|
1. Check @user doesn't exists
|
|
555
776
|
2. throw an error if the user exists
|
|
556
777
|
3. add @user to the User table alongside the SHA-256 hash of @pass
|
|
@@ -562,21 +783,21 @@ SELECT {
|
|
|
562
783
|
|
|
563
784
|
# Including Resources
|
|
564
785
|
|
|
565
|
-
A _resource_ is an external artifact that is not generated, such as an image, a JSON file, or a folder of assets. Use the `
|
|
786
|
+
A _resource_ is an external artifact that is not generated, such as an image, a JSON file, or a folder of assets. Use the `include` declaration to bring a resource into scope:
|
|
566
787
|
|
|
567
788
|
```spex
|
|
568
|
-
|
|
569
|
-
|
|
789
|
+
include "config.json" as config;
|
|
790
|
+
include "images/logo.png" as logo;
|
|
570
791
|
```
|
|
571
792
|
|
|
572
|
-
The address is a string literal pointing to a file or folder. The name becomes a first-class object in the current scope and can be
|
|
793
|
+
The address is a string literal pointing to a file or folder. The name becomes a first-class object in the current scope and can be brought into the generation context with `@ref` in constraints:
|
|
573
794
|
|
|
574
795
|
```spex
|
|
575
|
-
|
|
796
|
+
include "schema.sql" as schema;
|
|
576
797
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
798
|
+
create LoadSchema as
|
|
799
|
+
from unit -> string
|
|
800
|
+
select {
|
|
580
801
|
1. read the SQL file at @schema
|
|
581
802
|
2. return its contents as a string
|
|
582
803
|
};
|
|
@@ -587,11 +808,11 @@ SELECT {
|
|
|
587
808
|
When the address points to a folder, the resource is treated as a product object whose fields correspond to the files inside it:
|
|
588
809
|
|
|
589
810
|
```spex
|
|
590
|
-
|
|
811
|
+
include "assets/" as assets;
|
|
591
812
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
813
|
+
create LoadConfig as
|
|
814
|
+
from unit -> Config
|
|
815
|
+
select {
|
|
595
816
|
1. read @assets.config.json
|
|
596
817
|
2. return its content as a Config object
|
|
597
818
|
};
|
|
@@ -599,44 +820,25 @@ SELECT {
|
|
|
599
820
|
|
|
600
821
|
## Constraints
|
|
601
822
|
|
|
602
|
-
Resources cannot be subobjected. That is, `
|
|
823
|
+
Resources cannot be subobjected. That is, `from <resource> select { ... }` is not valid. This is because a resource represents a concrete external artifact, not a space of possible implementations.
|
|
603
824
|
|
|
604
825
|
---
|
|
605
826
|
|
|
606
827
|
# Generating Code
|
|
607
828
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
```spex
|
|
611
|
-
GENERATE CreateTodo
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
Generation of some object naturally triggers generation of it's dependencies as well.
|
|
615
|
-
|
|
616
|
-
---
|
|
617
|
-
|
|
618
|
-
# Packaging Code
|
|
619
|
-
|
|
620
|
-
To specify how generated code should be packaged, use the `PACKAGE` declaration:
|
|
829
|
+
A `generate` command tells the compiler to produce all realizations of the named concept or artifact, in every environment in which it is realized:
|
|
621
830
|
|
|
622
831
|
```spex
|
|
623
|
-
|
|
624
|
-
PACKAGE MODULE <name> AS <object> IN <environment>
|
|
832
|
+
generate CreateTodo;
|
|
625
833
|
```
|
|
626
834
|
|
|
627
|
-
|
|
835
|
+
An optional `in <environment>` clause focuses generation on a single environment:
|
|
628
836
|
|
|
629
837
|
```spex
|
|
630
|
-
|
|
631
|
-
PACKAGE MODULE mylib AS utils IN Node;
|
|
838
|
+
generate CreateTodo in Python;
|
|
632
839
|
```
|
|
633
840
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
```spex
|
|
637
|
-
PACKAGE EXECUTABLE cli AS (path: string) -> unit IN Python;
|
|
638
|
-
PACKAGE MODULE mylib AS app.handlers IN Node;
|
|
639
|
-
```
|
|
841
|
+
A concept or artifact may be realized in several environments. Without `in`, issuing `generate` for it yields each of its realizations in each of those environments; with `in`, only the realizations in that environment are produced. Generation of some object naturally triggers generation of its dependencies as well. Generation is a consequence of selecting a realization path that ends in concrete artifacts — the fundamental semantic operation of Spex is realization, not code generation.
|
|
640
842
|
|
|
641
843
|
---
|
|
642
844
|
|
|
@@ -661,7 +863,7 @@ Spex aims to provide:
|
|
|
661
863
|
- reusable semantic software abstractions
|
|
662
864
|
- compositional AI-assisted programming
|
|
663
865
|
- declarative architecture specification
|
|
664
|
-
-
|
|
866
|
+
- realization graphs that guide implementation synthesis
|
|
665
867
|
|
|
666
868
|
Instead of prompting LLMs directly, developers work with structured software semantics that can be analyzed, refined, verified, and synthesized.
|
|
667
869
|
|
|
@@ -679,17 +881,17 @@ The application supports:
|
|
|
679
881
|
|
|
680
882
|
---
|
|
681
883
|
|
|
682
|
-
##
|
|
884
|
+
## Artifacts
|
|
683
885
|
|
|
684
886
|
```spex
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
887
|
+
create TodoTitle as
|
|
888
|
+
from string
|
|
889
|
+
select {
|
|
688
890
|
- are not empty
|
|
689
891
|
- are shorter than 120 characters
|
|
690
892
|
};
|
|
691
893
|
|
|
692
|
-
|
|
894
|
+
create Todo as
|
|
693
895
|
(
|
|
694
896
|
id: string,
|
|
695
897
|
title: TodoTitle,
|
|
@@ -702,27 +904,27 @@ CREATE Todo AS
|
|
|
702
904
|
## Storage Layer
|
|
703
905
|
|
|
704
906
|
```spex
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
907
|
+
create TodoFilePath as
|
|
908
|
+
from string
|
|
909
|
+
select {
|
|
708
910
|
represent a valid path to a JSON file storing todos
|
|
709
911
|
};
|
|
710
912
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
913
|
+
create LoadTodos as
|
|
914
|
+
from (path: TodoFilePath) -> Todo[]
|
|
915
|
+
select {
|
|
714
916
|
1. read the JSON file at @path
|
|
715
917
|
2. return an empty list if the file does not exist
|
|
716
918
|
3. parse the JSON content into todos
|
|
717
919
|
4. throw an exception if the JSON is invalid
|
|
718
920
|
};
|
|
719
921
|
|
|
720
|
-
|
|
721
|
-
|
|
922
|
+
create SaveTodos as
|
|
923
|
+
from (
|
|
722
924
|
path: TodoFilePath,
|
|
723
925
|
todos: Todo[]
|
|
724
926
|
) -> unit
|
|
725
|
-
|
|
927
|
+
select {
|
|
726
928
|
1. serialize @todos as formatted JSON
|
|
727
929
|
2. write the JSON to @path
|
|
728
930
|
};
|
|
@@ -733,11 +935,11 @@ SELECT {
|
|
|
733
935
|
## Todo Creation
|
|
734
936
|
|
|
735
937
|
```spex
|
|
736
|
-
|
|
737
|
-
|
|
938
|
+
create CreateTodo as
|
|
939
|
+
from (
|
|
738
940
|
title: TodoTitle
|
|
739
941
|
) -> Todo
|
|
740
|
-
|
|
942
|
+
select {
|
|
741
943
|
1. generate a UUID for the todo id
|
|
742
944
|
2. create a todo with completed set to false
|
|
743
945
|
3. return the created todo
|
|
@@ -749,12 +951,12 @@ SELECT {
|
|
|
749
951
|
## Add Todo Command
|
|
750
952
|
|
|
751
953
|
```spex
|
|
752
|
-
|
|
753
|
-
|
|
954
|
+
create AddTodo as
|
|
955
|
+
from (
|
|
754
956
|
path: TodoFilePath,
|
|
755
957
|
title: TodoTitle
|
|
756
958
|
) -> Todo
|
|
757
|
-
|
|
959
|
+
select {
|
|
758
960
|
1. call @LoadTodos using @path
|
|
759
961
|
2. call @CreateTodo using @title
|
|
760
962
|
3. append the new todo to the loaded todos
|
|
@@ -768,11 +970,11 @@ SELECT {
|
|
|
768
970
|
## List Todos Command
|
|
769
971
|
|
|
770
972
|
```spex
|
|
771
|
-
|
|
772
|
-
|
|
973
|
+
create ListTodos as
|
|
974
|
+
from (
|
|
773
975
|
path: TodoFilePath
|
|
774
976
|
) -> string
|
|
775
|
-
|
|
977
|
+
select {
|
|
776
978
|
1. load todos using @LoadTodos
|
|
777
979
|
2. return a formatted string representation of all todos
|
|
778
980
|
3. show completed todos with a checkmark
|
|
@@ -785,12 +987,12 @@ SELECT {
|
|
|
785
987
|
## Complete Todo Command
|
|
786
988
|
|
|
787
989
|
```spex
|
|
788
|
-
|
|
789
|
-
|
|
990
|
+
create CompleteTodo as
|
|
991
|
+
from (
|
|
790
992
|
path: TodoFilePath,
|
|
791
993
|
id: TodoId
|
|
792
994
|
) -> Todo
|
|
793
|
-
|
|
995
|
+
select {
|
|
794
996
|
1. load todos using @LoadTodos
|
|
795
997
|
2. search for the todo matching @id
|
|
796
998
|
3. throw an exception if the todo does not exist
|
|
@@ -805,15 +1007,15 @@ SELECT {
|
|
|
805
1007
|
## CLI Parsing
|
|
806
1008
|
|
|
807
1009
|
```spex
|
|
808
|
-
|
|
1010
|
+
create CliArgs as
|
|
809
1011
|
(
|
|
810
1012
|
command: string,
|
|
811
1013
|
arguments: string[]
|
|
812
1014
|
);
|
|
813
1015
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1016
|
+
create ParseCliArgs as
|
|
1017
|
+
from string[] -> CliArgs
|
|
1018
|
+
select {
|
|
817
1019
|
1. parse the command line arguments
|
|
818
1020
|
2. extract the command name
|
|
819
1021
|
3. extract the command arguments
|
|
@@ -825,9 +1027,9 @@ SELECT {
|
|
|
825
1027
|
## CLI Entry Point
|
|
826
1028
|
|
|
827
1029
|
```spex
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1030
|
+
create Main as
|
|
1031
|
+
from string[] -> unit
|
|
1032
|
+
select {
|
|
831
1033
|
1. parse process arguments using @ParseCliArgs
|
|
832
1034
|
|
|
833
1035
|
2. if the command is "add":
|
|
@@ -851,7 +1053,7 @@ SELECT {
|
|
|
851
1053
|
## Code Generation
|
|
852
1054
|
|
|
853
1055
|
```spex
|
|
854
|
-
|
|
1056
|
+
generate MyTodo;
|
|
855
1057
|
```
|
|
856
1058
|
|
|
857
|
-
This triggers generation of the complete CLI application and all required dependencies.
|
|
1059
|
+
This triggers generation of the complete CLI application — in every environment in which it is realized — and all required dependencies.
|