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 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 _spaces of valid implementations_ using familiar programming abstractions such as:
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
- - functions
23
- - dependencies
24
- - constraints
22
+ - patterns
23
+ - realizations
24
+ - environments
25
25
 
26
- The Spex runtime synthesizes concrete implementations based on these specifications.
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 type represents a space of possible implementations
35
- - constraints refine that space
36
- - reusable abstractions are represented as subtypes
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
- CREATE SecureEndpoint AS
42
- FROM HttpRequest -> HttpResponse
43
- SELECT {
44
- - the user is authenticated and authorised.
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
- `SecureEndpoint` now represents the set of all endpoint implementations satisfying those constraints.
55
+ `WebApplication` is a concept. It can be realized as a product of more specific concepts:
50
56
 
51
- Developers can build on top of these abstractions instead of repeatedly specifying common architectural concerns.
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
- Objects are analogous to types in a programming language. Objects can be translated to classes, structs, functions, etc.
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
- ## Basic Objects
116
+ ### Basic Objects
71
117
 
72
- Basic objects are provided by Spex natively. These objects represent the common basic types in a programming language:
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 object that represent an empty type. It is useful in defining functions that take no input or do not return anything.
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 abstract base objects. A `concept` represents an abstract specification of something that needs to be realized, while an `environment` describes the context in which concepts are realized. They are covered in depth in [Concepts, Environments, and Realization](#concepts-environments-and-realization).
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
- To represent an array:
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
- Product objects are created by combining other objects:
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
- Coproduct objects represent 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:
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
- CREATE Shape AS
170
+ create Shape as
127
171
  Point | Circle;
128
172
 
129
- CREATE Command AS
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
- CREATE Result AS
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
- CREATE X AS
189
+ create X as
146
190
  A -> B | C; -- (A -> B) | C
147
191
 
148
- CREATE Y AS
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
- CREATE X AS
199
+ create X as
156
200
  A -> (B | C);
157
201
 
158
- CREATE Y AS
202
+ create Y as
159
203
  (A UNION B) EXCEPT C;
160
204
  ```
161
205
 
162
206
  ## Exponentials
163
207
 
164
- Spex supports function types which are referred to as exponential objects. An exponential has a base (the result type) and an exponent (the parameter type), both of which must be objects:
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` on the other hand, is a function that takes nothing as input, but returns a 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
- ## Lambda Expressions
219
+ ## Subobjects
176
220
 
177
- A lambda expression defines an exponential pattern with an implementation body. It specifies the function type along with its implementation in a programming language:
221
+ Subobjecting selects a subset of the members of an existing universe while preserving their kind. For example:
178
222
 
179
- ````spex
180
- CREATE double AS
181
- lambda number -> number ```python
182
- return @n * 2
223
+ ```text
224
+ PositiveNumber Number
183
225
  ```
184
- ````
185
226
 
186
- The domain (left side of `->`) defines the parameter type, and the codomain (right side) defines the return type. The body is written in a fenced code block with a language identifier.
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
- Lambda expressions support pattern blocks using `@{...}` syntax. Pattern blocks are extracted from the body and can contain references to the function's parameters:
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
- CREATE transform AS
192
- lambda (x: number) -> number ```python
193
- if @x > 0:
194
- @{return sin(@x)}
195
- else:
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
- Pattern blocks are parsed into structured AST nodes with their positions tracked, making it easy to analyze and transform them programmatically. Multiple pattern blocks in a single body are distinguished by their start and end positions.
300
+ describes the subobject of `Number -> Bool` whose members behave this way. Natural language works just as well:
201
301
 
202
- ## Subobjects
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
- Subobjects are analogous to subsets. Subobjects refine an object by selecting memebers that satisfy some constraints. Constraints are defined through natural language:
358
+ Natural language patterns are written in braces:
205
359
 
206
360
  ```spex
207
- FROM string
208
- SELECT {
361
+ from string select {
209
362
  are email addresses
210
363
  }
364
+ ```
211
365
 
212
- FROM string -> number
213
- SELECT {
214
- return the length of the given string
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
- Subobjects are themselves objects so they could be subobjected as well. A good heuristic for writing constraints is to make the expression read as:
394
+ #### Code Patterns
219
395
 
220
- > "from `object` select those that `{constraint}`".
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
- CREATE EvenInt AS
228
- FROM int
229
- SELECT { are even };
427
+ create EvenInt as
428
+ from int
429
+ select { are even };
230
430
 
231
- CREATE PositiveInt AS
232
- FROM int
233
- SELECT { are positive };
431
+ create PositiveInt as
432
+ from int
433
+ select { are positive };
234
434
 
235
- CREATE EvenPositiveInt AS
435
+ create EvenPositiveInt as
236
436
  EvenInt INTERSECT PositiveInt;
237
- CREATE EvenOrPositive AS EvenInt UNION PositiveInt;
238
- CREATE EvenNotPositive AS EvenInt EXCEPT PositiveInt;
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
- CREATE X AS
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 object denotes a single value, and therefore represents the set containing exactly that value:
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 refine other objects or serve as alternatives in a coproduct:
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
- CREATE UserName AS
467
+ create UserName as
264
468
  string EXCEPT "root";
265
469
 
266
- CREATE Handedness AS
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 denotes the subobject of `string` containing exactly the strings that match it:
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 the string base object, it participates in set operations and coproducts like any other object:
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
- CREATE Digits AS /\d+/;
295
- CREATE Word AS /\w+/;
487
+ create Digits as /\d+/;
488
+ create Word as /\w+/;
296
489
 
297
- CREATE DigitOrWord AS Digits UNION Word;
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. This separation is captured by three core ideas: concepts, environments, and realizations.
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 object 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:
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 constraints on top of it.
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 second built-in base object. It describes the development and runtime context in which concepts are to be realized. An environment is independent from the application specification.
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
- - a subobject preserves the object's base type
363
- - a realization may cross abstraction or type boundaries
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
- Therefore, realizing a `Concept` does not mean that the resulting object is a subobject of that concept.
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 implementations:
615
+ The overall model connects the specification to concrete artifacts:
388
616
 
389
617
  ```text
390
- Concept
391
- |
392
- | realization in an Environment
393
- v
394
- Environment-specific representation
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
- | generation
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 connect the abstract specification to concrete representations.**
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
- CREATE Todo AS
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
- CREATE EmailAddress AS
432
- FROM string
433
- SELECT {
657
+ create EmailAddress as
658
+ from string
659
+ select {
434
660
  are email addresses
435
661
  };
436
662
 
437
- CREATE slugify AS
438
- FROM string -> string
439
- SELECT {
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
- 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.
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
- CREATE Todo AS
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
- CREATE validate AS
460
- FROM Todo -> bool
461
- SELECT {
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
- CREATE CreateTodo AS
466
- FROM Todo -> Bool
467
- SELECT {
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
- This forms an explicit software dependency graph between objects.
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 ", ref: LoadTodos, text: " using ", ref: path]
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
- CREATE ComplexNumber AS
710
+ create ComplexNumber as
487
711
  (
488
712
  real: number,
489
713
  imag: number
490
714
  );
491
715
 
492
- CREATE Abs AS
493
- FROM (z: ComplexNumber) -> number
494
- SELECT {
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 and Exporting
725
+ # Importing
502
726
 
503
- 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.
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
- CREATE EmailAddress AS
509
- FROM string
510
- SELECT {
732
+ create EmailAddress as
733
+ from string
734
+ select {
511
735
  are email addresses
512
736
  };
513
737
 
514
- CREATE Password AS
515
- FROM string
516
- SELECT {
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
- IMPORT EmailAddress FROM "types.spex";
752
+ import EmailAddress from "types.spex";
532
753
  ```
533
754
 
534
755
  Or give it a different alias:
535
756
 
536
757
  ```spex
537
- IMPORT EmailAddress FROM "types.spex" AS Username;
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
- IMPORT "types.spex" AS type;
764
+ import "types.spex" as type;
544
765
  ```
545
766
 
546
- In case the whole file is imported, it's objects could be referenced by:
767
+ In case the whole file is imported, its objects could be referenced by:
547
768
 
548
769
  ```spex
549
- IMPORT "types.spex" AS types;
770
+ import "types.spex" as types;
550
771
 
551
- CREATE SignUp AS
552
- FROM (user: types.EmailAddress, pass: types.Password) -> string
553
- SELECT {
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 `INCLUDE` declaration to bring a resource into scope:
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
- INCLUDE "config.json" AS config;
569
- INCLUDE "images/logo.png" AS logo;
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 referenced in constraints with `@`:
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
- INCLUDE "schema.sql" AS schema;
796
+ include "schema.sql" as schema;
576
797
 
577
- CREATE LoadSchema AS
578
- FROM unit -> string
579
- SELECT {
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
- INCLUDE "assets/" AS assets;
811
+ include "assets/" as assets;
591
812
 
592
- CREATE LoadConfig AS
593
- FROM unit -> Config
594
- SELECT {
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, `FROM <resource> SELECT { ... }` is not valid. This is because a resource represents a concrete external artifact, not a space of possible implementations.
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
- To specify what objects in an specification has to be generated as explicit code:
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
- PACKAGE EXECUTABLE <name> AS <object> IN <environment>
624
- PACKAGE MODULE <name> AS <object> IN <environment>
832
+ generate CreateTodo;
625
833
  ```
626
834
 
627
- `EXECUTABLE` packages the object as a standalone application entry point. `MODULE` packages it as a library or module that can be imported by other code. The object after `IN` is an environment describing where the package is realized.
835
+ An optional `in <environment>` clause focuses generation on a single environment:
628
836
 
629
837
  ```spex
630
- PACKAGE EXECUTABLE myapp AS Main IN Python;
631
- PACKAGE MODULE mylib AS utils IN Node;
838
+ generate CreateTodo in Python;
632
839
  ```
633
840
 
634
- The object can be any valid Spex expression:
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
- - implementation synthesis guided by constraints
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
- ## Domain Objects
884
+ ## Artifacts
683
885
 
684
886
  ```spex
685
- CREATE TodoTitle AS
686
- FROM string
687
- SELECT {
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
- CREATE Todo AS
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
- CREATE TodoFilePath AS
706
- FROM string
707
- SELECT {
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
- CREATE LoadTodos AS
712
- FROM (path: TodoFilePath) -> Todo[]
713
- SELECT {
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
- CREATE SaveTodos AS
721
- FROM (
922
+ create SaveTodos as
923
+ from (
722
924
  path: TodoFilePath,
723
925
  todos: Todo[]
724
926
  ) -> unit
725
- SELECT {
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
- CREATE CreateTodo AS
737
- FROM (
938
+ create CreateTodo as
939
+ from (
738
940
  title: TodoTitle
739
941
  ) -> Todo
740
- SELECT {
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
- CREATE AddTodo AS
753
- FROM (
954
+ create AddTodo as
955
+ from (
754
956
  path: TodoFilePath,
755
957
  title: TodoTitle
756
958
  ) -> Todo
757
- SELECT {
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
- CREATE ListTodos AS
772
- FROM (
973
+ create ListTodos as
974
+ from (
773
975
  path: TodoFilePath
774
976
  ) -> string
775
- SELECT {
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
- CREATE CompleteTodo AS
789
- FROM (
990
+ create CompleteTodo as
991
+ from (
790
992
  path: TodoFilePath,
791
993
  id: TodoId
792
994
  ) -> Todo
793
- SELECT {
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
- CREATE CliArgs AS
1010
+ create CliArgs as
809
1011
  (
810
1012
  command: string,
811
1013
  arguments: string[]
812
1014
  );
813
1015
 
814
- CREATE ParseCliArgs AS
815
- FROM string[] -> CliArgs
816
- SELECT {
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
- CREATE Main AS
829
- FROM string[] -> unit
830
- SELECT {
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
- package executable MyTodo as Main in Python;
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.