spex-parser 0.1.5 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +320 -9
  2. package/dist/ast.d.ts +71 -2
  3. package/dist/ast.d.ts.map +1 -1
  4. package/dist/constants.d.ts +20 -0
  5. package/dist/constants.d.ts.map +1 -0
  6. package/dist/constants.js +32 -0
  7. package/dist/constants.js.map +1 -0
  8. package/dist/index.d.ts +4 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/lexer.d.ts +17 -1
  13. package/dist/lexer.d.ts.map +1 -1
  14. package/dist/lexer.js +159 -5
  15. package/dist/lexer.js.map +1 -1
  16. package/dist/parser.d.ts +9 -0
  17. package/dist/parser.d.ts.map +1 -1
  18. package/dist/parser.js +105 -7
  19. package/dist/parser.js.map +1 -1
  20. package/dist/visitor.d.ts +11 -2
  21. package/dist/visitor.d.ts.map +1 -1
  22. package/dist/visitor.js +182 -8
  23. package/dist/visitor.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/ast.ts +94 -0
  26. package/src/constants.ts +40 -0
  27. package/src/index.ts +20 -3
  28. package/src/lexer.ts +150 -6
  29. package/src/parser.ts +130 -7
  30. package/src/visitor.ts +196 -11
  31. package/tests/constants.test.ts +102 -0
  32. package/tests/e2e.test.ts +15 -3
  33. package/tests/lexer.test.ts +230 -9
  34. package/tests/parser.test.ts +455 -10
  35. package/tests/props/express_todo_web.spex +108 -0
  36. package/tests/props/express_web_env.spex +16 -0
  37. package/tests/props/flask_todo_web.spex +111 -0
  38. package/tests/props/flask_web_env.spex +16 -0
  39. package/tests/props/python_cli_env.spex +16 -0
  40. package/tests/props/python_todo_cli.spex +70 -0
  41. package/tests/props/todo.spex +99 -68
  42. package/tests/props/typescript_cli_env.spex +16 -0
  43. package/tests/props/typescript_todo_cli.spex +70 -0
  44. package/tests/visitor.test.ts +905 -6
@@ -0,0 +1,111 @@
1
+ -- The Flask realization of the Todo Web app.
2
+ --
3
+ -- Imports the shared TodoWeb representation, the todo operations, and the
4
+ -- Flask environment. SQLite storage, the route handlers, and the main
5
+ -- exponential are defined as separate objects, and the concept is realized
6
+ -- as a product of them in the realize statement. Flask handlers receive a
7
+ -- request and return a response.
8
+
9
+ import TodoWeb from "todo.spex";
10
+ import Todo from "todo.spex";
11
+ import TodoId from "todo.spex";
12
+ import TodoTitle from "todo.spex";
13
+ import AddTodo from "todo.spex";
14
+ import ListTodos from "todo.spex";
15
+ import CompleteTodo from "todo.spex";
16
+ import HttpRequest from "todo.spex";
17
+ import HttpResponse from "todo.spex";
18
+ import FlaskWebEnv from "flask_web_env.spex";
19
+
20
+ -- TODO: realize the shared HttpRequest and HttpResponse objects using the
21
+ -- types provided by the flask dependency in FlaskWebEnv.
22
+
23
+ /* SQLite storage. */
24
+ create SqliteConnection as (
25
+ path: string
26
+ );
27
+
28
+ create OpenSqlite as
29
+ from (path: string) -> SqliteConnection
30
+ select {
31
+ 1. open the SQLite database at @path
32
+ 2. create the todos table if it does not exist
33
+ };
34
+
35
+ create SelectTodos as
36
+ from (connection: SqliteConnection) -> Todo[]
37
+ select {
38
+ 1. select the id, title, and completed columns from the todos table
39
+ 2. map each row to a @Todo
40
+ };
41
+
42
+ create InsertTodo as
43
+ from (connection: SqliteConnection, todo: Todo) -> unit
44
+ select {
45
+ 1. insert the id, title, and completed values of @todo into the todos table
46
+ 2. commit the transaction
47
+ };
48
+
49
+ create UpdateTodo as
50
+ from (connection: SqliteConnection, todo: Todo) -> unit
51
+ select {
52
+ 1. update the completed value of the row matching the id of @todo
53
+ 2. commit the transaction
54
+ };
55
+
56
+ /* Route handlers. */
57
+ create ListTodosHandler as
58
+ from (request: HttpRequest) -> HttpResponse
59
+ select {
60
+ 1. open the database with @OpenSqlite
61
+ 2. load the todos with @SelectTodos
62
+ 3. return a 200 response with the todos serialized as JSON
63
+ };
64
+
65
+ create AddTodoHandler as
66
+ from (request: HttpRequest) -> HttpResponse
67
+ select {
68
+ 1. parse the JSON body of @request into a @TodoTitle
69
+ 2. create the todo with @AddTodo
70
+ 3. open the database with @OpenSqlite
71
+ 4. persist the todo with @InsertTodo
72
+ 5. return a 201 response with the created todo serialized as JSON
73
+ };
74
+
75
+ create CompleteTodoHandler as
76
+ from (request: HttpRequest) -> HttpResponse
77
+ select {
78
+ 1. extract the todo id from the path of @request
79
+ 2. open the database with @OpenSqlite
80
+ 3. load the todos with @SelectTodos
81
+ 4. find the matching @Todo and complete it with @CompleteTodo
82
+ 5. persist it with @UpdateTodo
83
+ 6. return a 200 response with the updated todo serialized as JSON
84
+ };
85
+
86
+ /* The main entry exponential. */
87
+ create FlaskTodoWeb as
88
+ from unit -> unit
89
+ select {
90
+ 1. create the Flask application object
91
+ 2. register @ListTodosHandler on the GET /todos route
92
+ 3. register @AddTodoHandler on the POST /todos route
93
+ 4. register @CompleteTodoHandler on the PATCH /todos/:id route
94
+ 5. run the application
95
+ };
96
+
97
+ realize TodoWeb as (
98
+ list: ListTodosHandler,
99
+ add: AddTodoHandler,
100
+ complete: CompleteTodoHandler,
101
+ storage: (
102
+ open: OpenSqlite,
103
+ query: SelectTodos,
104
+ insert: InsertTodo,
105
+ update: UpdateTodo
106
+ ),
107
+ main: FlaskTodoWeb
108
+ ) in FlaskWebEnv;
109
+
110
+ generate FlaskTodoWeb;
111
+ package executable flasktodo as FlaskTodoWeb in FlaskWebEnv;
@@ -0,0 +1,16 @@
1
+ -- The Flask web environment.
2
+ --
3
+ -- A subobject of the shared Web environment intersected with the shared
4
+ -- Python environment.
5
+
6
+ import Web from "todo.spex";
7
+ import Python from "todo.spex";
8
+
9
+ create FlaskWebEnv as
10
+ from Web intersect Python
11
+ select {
12
+ dependencies: flask,
13
+ database: SQLite
14
+ };
15
+
16
+ export FlaskWebEnv;
@@ -0,0 +1,16 @@
1
+ -- The Python CLI environment.
2
+ --
3
+ -- A subobject of the shared Cli environment intersected with the shared
4
+ -- Python environment.
5
+
6
+ import Cli from "todo.spex";
7
+ import Python from "todo.spex";
8
+
9
+ create PythonCliEnv as
10
+ from Cli intersect Python
11
+ select {
12
+ dependencies: standard library,
13
+ storage: JSON file
14
+ };
15
+
16
+ export PythonCliEnv;
@@ -0,0 +1,70 @@
1
+ -- The Python realization of the Todo CLI.
2
+ --
3
+ -- Imports the shared TodoCli representation, the todo operations, and the
4
+ -- Python CLI environment. The argument parsing and JSON file storage are
5
+ -- defined as separate objects, and the concept is realized as a product of
6
+ -- them in the realize statement.
7
+
8
+ import TodoCli from "todo.spex";
9
+ import Todo from "todo.spex";
10
+ import TodoId from "todo.spex";
11
+ import TodoTitle from "todo.spex";
12
+ import AddTodo from "todo.spex";
13
+ import ListTodos from "todo.spex";
14
+ import CompleteTodo from "todo.spex";
15
+ import PythonCliEnv from "python_cli_env.spex";
16
+
17
+ /* Argument parsing. */
18
+ create CliCommand as (
19
+ command: string,
20
+ arguments: string[]
21
+ );
22
+
23
+ create ParseArgs as
24
+ from (args: string[]) -> CliCommand
25
+ select {
26
+ 1. create an argparse parser with the add, list, and complete subcommands
27
+ 2. parse @args into a command and its arguments
28
+ 3. return the parsed command
29
+ };
30
+
31
+ /* JSON file storage. */
32
+ create LoadTodosFile as
33
+ from (path: string) -> Todo[]
34
+ select {
35
+ 1. check whether the file at @path exists
36
+ 2. read the file at @path
37
+ 3. parse the JSON content into todos
38
+ 4. return an empty list if the file does not exist
39
+ 5. throw an exception if the JSON is invalid
40
+ };
41
+
42
+ create SaveTodosFile as
43
+ from (path: string, todos: Todo[]) -> unit
44
+ select {
45
+ 1. serialize @todos as formatted JSON
46
+ 2. write the JSON to @path
47
+ };
48
+
49
+ /* The main entry exponential. */
50
+ create PythonTodoCli as
51
+ from (args: string[]) -> unit
52
+ select {
53
+ 1. parse the command with @ParseArgs
54
+ 2. load the todos from the JSON file with @LoadTodosFile
55
+ 3. dispatch the command:
56
+ - add: call @AddTodo with the given @TodoTitle, then persist with @SaveTodosFile
57
+ - list: call @ListTodos and print the result to stdout
58
+ - complete: call @CompleteTodo with the given @TodoId, then persist with @SaveTodosFile
59
+ 4. print user-friendly error messages for exceptions
60
+ };
61
+
62
+ realize TodoCli as (
63
+ parse: ParseArgs,
64
+ load: LoadTodosFile,
65
+ save: SaveTodosFile,
66
+ main: PythonTodoCli
67
+ ) in PythonCliEnv;
68
+
69
+ generate PythonTodoCli;
70
+ package executable mytodocli as PythonTodoCli in PythonCliEnv;
@@ -1,94 +1,125 @@
1
- -- A Todo CLI application written in Spex.
1
+ -- The shared abstract model of a Todo application written in Spex.
2
+ --
3
+ -- This file defines the domain model, the todo operations, the application
4
+ -- concept, its CLI and Web representations, and the base environments that
5
+ -- are shared between multiple realizations. Concrete environments and
6
+ -- realizations live in separate files and import what they need from here.
2
7
 
3
- /* The domain model: */
4
- create Todo as (
5
- id: string,
6
- title: string,
7
- description: string,
8
- completed: bool,
9
- createdAt: string
10
- );
8
+ /* The domain model. */
9
+ create TodoId as
10
+ from string
11
+ select {
12
+ are valid UUIDs
13
+ };
11
14
 
12
- create TodoTitle as from string select {
15
+ create TodoTitle as
16
+ from string
17
+ select {
13
18
  are not empty and are shorter than 120 characters
14
19
  };
15
20
 
16
- create TodoFilePath as from string select {
17
- represent a valid path to a JSON file storing todos
18
- };
21
+ create Todo as (
22
+ id: TodoId,
23
+ title: TodoTitle,
24
+ completed: bool
25
+ );
19
26
 
20
- create TodoId as from string select {
21
- are valid UUIDs
22
- };
27
+ create HttpRequest as (
28
+ method: string,
29
+ path: string,
30
+ body: string
31
+ );
23
32
 
24
- /* The CLI argument structure. */
25
- create CliArgs as (
26
- command: string,
27
- arguments: string[]
33
+ create HttpResponse as (
34
+ status: number,
35
+ body: string
28
36
  );
29
37
 
30
- -- Storage layer:
31
- create LoadTodos as from (path: TodoFilePath) -> Todo[] select {
32
- 1. read the JSON file at @path
33
- 2. return an empty list if the file does not exist
34
- 3. parse the JSON content into todos
35
- 4. throw an exception if the JSON is invalid
38
+ /* The todo operations, defined once and reused by every realization. */
39
+ create AddTodo as
40
+ from (title: TodoTitle) -> Todo
41
+ select {
42
+ 1. generate a UUID for the todo id
43
+ 2. create a todo with completed set to false
44
+ 3. return the created todo
36
45
  };
37
46
 
38
- create SaveTodos as from (path: TodoFilePath, todos: Todo[]) -> unit select {
39
- 1. serialize @todos as formatted JSON
40
- 2. write the JSON to @path
47
+ create ListTodos as
48
+ from unit -> Todo[]
49
+ select {
50
+ return all the todos
41
51
  };
42
52
 
43
- -- Todo creation:
44
- create CreateTodo as from (title: TodoTitle) -> Todo select {
45
- 1. generate a UUID for the todo id
46
- 2. create a todo with completed set to false
47
- 3. return the created todo
53
+ create CompleteTodo as
54
+ from (id: TodoId) -> Todo
55
+ select {
56
+ 1. mark the todo with the given id as completed
57
+ 2. return the updated todo
48
58
  };
49
59
 
50
- -- Commands:
51
- create AddTodo as from (path: TodoFilePath, title: TodoTitle) -> Todo select {
52
- 1. call @LoadTodos using @path
53
- 2. call @CreateTodo using @title
54
- 3. append the new todo to the loaded todos
55
- 4. call @SaveTodos to persist the updated todos
56
- 5. return the created todo
60
+ /* The application, specified as an abstract concept. */
61
+ create TodoApp as
62
+ from concept
63
+ select {
64
+ provide adding, listing, and completing todos by composing @AddTodo, @ListTodos, and @CompleteTodo
57
65
  };
58
66
 
59
- create ListTodos as from (path: TodoFilePath) -> string select {
60
- 1. load todos using @LoadTodos
61
- 2. return a formatted string representation of all todos
62
- 3. show completed todos with a checkmark
63
- 4. show incomplete todos with an empty checkbox
67
+ /* The concept realized into a CLI and a Web representation. */
68
+ create TodoCli as
69
+ from TodoApp
70
+ select {
71
+ expose the todo operations through a command-line interface
64
72
  };
65
73
 
66
- create CompleteTodo as from (path: TodoFilePath, id: TodoId) -> Todo select {
67
- 1. load todos using @LoadTodos
68
- 2. search for the todo matching @id
69
- 3. throw an exception if the todo does not exist
70
- 4. set the todo completed status to true
71
- 5. persist the updated todo list using @SaveTodos
72
- 6. return the updated todo
74
+ create TodoWeb as
75
+ from TodoApp
76
+ select {
77
+ expose the todo operations through HTTP endpoints
73
78
  };
74
79
 
75
- create ParseCliArgs as from string[] -> CliArgs select {
76
- 1. parse the command line arguments
77
- 2. extract the command name
78
- 3. extract the command arguments
80
+ realize TodoApp as TodoCli in Cli;
81
+ realize TodoApp as TodoWeb in Web;
82
+
83
+ /* The base environments shared between realizations. */
84
+ create Cli as
85
+ from environment
86
+ select {
87
+ interact with the user through a terminal
79
88
  };
80
89
 
81
- /* Entry point. */
82
- create Main as from string[] -> unit select {
83
- 1. parse process arguments using @ParseCliArgs
84
- 2. if the command is "add": call @AddTodo
85
- 3. if the command is "list": call @ListTodos and print the result to stdout
86
- 4. if the command is "complete": call @CompleteTodo
87
- 5. print a help message if the command is invalid
88
- 6. print user-friendly error messages for exceptions
90
+ create Web as
91
+ from environment
92
+ select {
93
+ serve HTTP requests and produce HTTP responses
89
94
  };
90
95
 
91
- -- Code generation:
92
- generate TodoTitle;
96
+ create Python as
97
+ from environment
98
+ select {
99
+ language: Python,
100
+ runtime: CPython 3.12
101
+ };
102
+
103
+ create TypeScript as
104
+ from environment
105
+ select {
106
+ language: TypeScript,
107
+ runtime: Node.js 20
108
+ };
93
109
 
94
- package executable MyTodo as Main;
110
+ /* Exports for the concrete realizations. */
111
+ export TodoId;
112
+ export TodoTitle;
113
+ export Todo;
114
+ export HttpRequest;
115
+ export HttpResponse;
116
+ export AddTodo;
117
+ export ListTodos;
118
+ export CompleteTodo;
119
+ export TodoApp;
120
+ export TodoCli;
121
+ export TodoWeb;
122
+ export Cli;
123
+ export Web;
124
+ export Python;
125
+ export TypeScript;
@@ -0,0 +1,16 @@
1
+ -- The TypeScript CLI environment.
2
+ --
3
+ -- A subobject of the shared Cli environment intersected with the shared
4
+ -- TypeScript environment.
5
+
6
+ import Cli from "todo.spex";
7
+ import TypeScript from "todo.spex";
8
+
9
+ create TypeScriptCliEnv as
10
+ from Cli intersect TypeScript
11
+ select {
12
+ dependencies: Node.js standard library,
13
+ storage: JSON file
14
+ };
15
+
16
+ export TypeScriptCliEnv;
@@ -0,0 +1,70 @@
1
+ -- The TypeScript realization of the Todo CLI.
2
+ --
3
+ -- Imports the shared TodoCli representation, the todo operations, and the
4
+ -- TypeScript CLI environment. The argument parsing and JSON file storage
5
+ -- are defined as separate objects, and the concept is realized as a product
6
+ -- of them in the realize statement.
7
+
8
+ import TodoCli from "todo.spex";
9
+ import Todo from "todo.spex";
10
+ import TodoId from "todo.spex";
11
+ import TodoTitle from "todo.spex";
12
+ import AddTodo from "todo.spex";
13
+ import ListTodos from "todo.spex";
14
+ import CompleteTodo from "todo.spex";
15
+ import TypeScriptCliEnv from "typescript_cli_env.spex";
16
+
17
+ /* Argument parsing. */
18
+ create CliCommand as (
19
+ command: string,
20
+ arguments: string[]
21
+ );
22
+
23
+ create ParseArgs as
24
+ from (args: string[]) -> CliCommand
25
+ select {
26
+ 1. read the command and its arguments from @args
27
+ 2. map the command to the add, list, or complete command
28
+ 3. return the parsed command
29
+ };
30
+
31
+ /* JSON file storage. */
32
+ create LoadTodosFile as
33
+ from (path: string) -> Todo[]
34
+ select {
35
+ 1. check whether the file at @path exists with the filesystem module
36
+ 2. read the file at @path
37
+ 3. parse the JSON content into todos
38
+ 4. return an empty list if the file does not exist
39
+ 5. throw an exception if the JSON is invalid
40
+ };
41
+
42
+ create SaveTodosFile as
43
+ from (path: string, todos: Todo[]) -> unit
44
+ select {
45
+ 1. serialize @todos as formatted JSON
46
+ 2. write the JSON to @path
47
+ };
48
+
49
+ /* The main entry exponential. */
50
+ create TypeScriptTodoCli as
51
+ from (args: string[]) -> unit
52
+ select {
53
+ 1. parse the command with @ParseArgs
54
+ 2. load the todos from the JSON file with @LoadTodosFile
55
+ 3. dispatch the command:
56
+ - add: call @AddTodo with the given @TodoTitle, then persist with @SaveTodosFile
57
+ - list: call @ListTodos and print the result to stdout
58
+ - complete: call @CompleteTodo with the given @TodoId, then persist with @SaveTodosFile
59
+ 4. print user-friendly error messages for exceptions
60
+ };
61
+
62
+ realize TodoCli as (
63
+ parse: ParseArgs,
64
+ load: LoadTodosFile,
65
+ save: SaveTodosFile,
66
+ main: TypeScriptTodoCli
67
+ ) in TypeScriptCliEnv;
68
+
69
+ generate TypeScriptTodoCli;
70
+ package executable mytotscli as TypeScriptTodoCli in TypeScriptCliEnv;