spex-parser 0.1.4 → 0.6.1

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 +312 -1
  2. package/dist/ast.d.ts +70 -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 +19 -1
  13. package/dist/lexer.d.ts.map +1 -1
  14. package/dist/lexer.js +172 -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 +103 -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 +181 -8
  23. package/dist/visitor.js.map +1 -1
  24. package/package.json +1 -1
  25. package/src/ast.ts +93 -0
  26. package/src/constants.ts +40 -0
  27. package/src/index.ts +20 -3
  28. package/src/lexer.ts +164 -6
  29. package/src/parser.ts +128 -7
  30. package/src/visitor.ts +195 -11
  31. package/tests/constants.test.ts +102 -0
  32. package/tests/e2e.test.ts +15 -3
  33. package/tests/lexer.test.ts +291 -9
  34. package/tests/parser.test.ts +489 -0
  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 +100 -60
  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 +892 -0
@@ -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;
@@ -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;
@@ -1,85 +1,125 @@
1
- create Todo as (
2
- id: string,
3
- title: string,
4
- description: string,
5
- completed: bool,
6
- createdAt: string
7
- );
8
-
9
- create TodoTitle as from string select {
10
- are not empty and are shorter than 120 characters
11
- };
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.
12
7
 
13
- create TodoFilePath as from string select {
14
- represent a valid path to a JSON file storing todos
8
+ /* The domain model. */
9
+ create TodoId as
10
+ from string
11
+ select {
12
+ are valid UUIDs
15
13
  };
16
14
 
17
- create TodoId as from string select {
18
- are valid UUIDs
15
+ create TodoTitle as
16
+ from string
17
+ select {
18
+ are not empty and are shorter than 120 characters
19
19
  };
20
20
 
21
- create CliArgs as (
22
- command: string,
23
- arguments: string[]
21
+ create Todo as (
22
+ id: TodoId,
23
+ title: TodoTitle,
24
+ completed: bool
24
25
  );
25
26
 
26
- create LoadTodos as from (path: TodoFilePath) -> Todo[] select {
27
- 1. read the JSON file at @path
28
- 2. return an empty list if the file does not exist
29
- 3. parse the JSON content into todos
30
- 4. throw an exception if the JSON is invalid
31
- };
27
+ create HttpRequest as (
28
+ method: string,
29
+ path: string,
30
+ body: string
31
+ );
32
32
 
33
- create SaveTodos as from (path: TodoFilePath, todos: Todo[]) -> unit select {
34
- 1. serialize @todos as formatted JSON
35
- 2. write the JSON to @path
36
- };
33
+ create HttpResponse as (
34
+ status: number,
35
+ body: string
36
+ );
37
37
 
38
- create CreateTodo as from (title: TodoTitle) -> Todo select {
38
+ /* The todo operations, defined once and reused by every realization. */
39
+ create AddTodo as
40
+ from (title: TodoTitle) -> Todo
41
+ select {
39
42
  1. generate a UUID for the todo id
40
43
  2. create a todo with completed set to false
41
44
  3. return the created todo
42
45
  };
43
46
 
44
- create AddTodo as from (path: TodoFilePath, title: TodoTitle) -> Todo select {
45
- 1. call @LoadTodos using @path
46
- 2. call @CreateTodo using @title
47
- 3. append the new todo to the loaded todos
48
- 4. call @SaveTodos to persist the updated todos
49
- 5. return the created todo
47
+ create ListTodos as
48
+ from unit -> Todo[]
49
+ select {
50
+ return all the todos
51
+ };
52
+
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
58
+ };
59
+
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
50
65
  };
51
66
 
52
- create ListTodos as from (path: TodoFilePath) -> string select {
53
- 1. load todos using @LoadTodos
54
- 2. return a formatted string representation of all todos
55
- 3. show completed todos with a checkmark
56
- 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
57
72
  };
58
73
 
59
- create CompleteTodo as from (path: TodoFilePath, id: TodoId) -> Todo select {
60
- 1. load todos using @LoadTodos
61
- 2. search for the todo matching @id
62
- 3. throw an exception if the todo does not exist
63
- 4. set the todo completed status to true
64
- 5. persist the updated todo list using @SaveTodos
65
- 6. return the updated todo
74
+ create TodoWeb as
75
+ from TodoApp
76
+ select {
77
+ expose the todo operations through HTTP endpoints
66
78
  };
67
79
 
68
- create ParseCliArgs as from string[] -> CliArgs select {
69
- 1. parse the command line arguments
70
- 2. extract the command name
71
- 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
72
88
  };
73
89
 
74
- create Main as from string[] -> unit select {
75
- 1. parse process arguments using @ParseCliArgs
76
- 2. if the command is "add": call @AddTodo
77
- 3. if the command is "list": call @ListTodos and print the result to stdout
78
- 4. if the command is "complete": call @CompleteTodo
79
- 5. print a help message if the command is invalid
80
- 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
81
94
  };
82
95
 
83
- 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
+ };
84
109
 
85
- 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;