diff --git a/doc/examples/calendar.sindri.json b/doc/examples/calendar.sindri.json new file mode 100644 index 0000000..84aaf25 --- /dev/null +++ b/doc/examples/calendar.sindri.json @@ -0,0 +1,142 @@ +{ + "domains": [ + { + "name": "calendar", + "key_field": { + "name": "id" + }, + "data_fields": [ + { + "name": "name", + "nullable": false, + "type": "string_short" + }, + { + "name": "description", + "nullable": true, + "type": "string_long" + }, + { + "name": "color", + "nullable": true, + "type": "string_short" + } + ] + }, + { + "name": "calendar_appointment", + "key_field": { + "name": "id" + }, + "data_fields": [ + { + "name": "calendar_id", + "nullable": false, + "type": "integer" + }, + { + "name": "title", + "nullable": false, + "type": "string_short" + }, + { + "name": "description", + "nullable": true, + "type": "string_long" + }, + { + "name": "location", + "nullable": true, + "type": "string_short" + }, + { + "name": "begin", + "description": "UNIX timestamp", + "nullable": false, + "type": "integer" + }, + { + "name": "end", + "description": "UNIX timestamp", + "nullable": true, + "type": "integer" + }, + { + "name": "contact", + "nullable": true, + "type": "string_short" + } + ], + "constraints": [ + { + "kind": "foreign_key", + "parameters": { + "fields": [ + "calendar_id" + ], + "reference": { + "name": "calendar", + "fields": [ + "id" + ] + } + } + } + ] + }, + { + "name": "organization", + "key_field": { + "name": "id" + }, + "data_fields": [ + ] + }, + { + "name": "organization_calendars", + "data_fields": [ + { + "name": "organization_id", + "nullable": false, + "type": "integer" + }, + { + "name": "calendar_id", + "nullable": false, + "type": "integer" + } + ], + "constraints": [ + { + "kind": "foreign_key", + "parameters": { + "fields": [ + "organization_id" + ], + "reference": { + "name": "organization", + "fields": [ + "id" + ] + } + } + }, + { + "kind": "foreign_key", + "parameters": { + "fields": [ + "calendar_id" + ], + "reference": { + "name": "calendar", + "fields": [ + "id" + ] + } + } + } + ] + } + ] +} + diff --git a/doc/sindri.schema.json b/doc/sindri.schema.json index 12b5c80..08f529d 100644 --- a/doc/sindri.schema.json +++ b/doc/sindri.schema.json @@ -126,4 +126,4 @@ "required": [ "domains" ] -} \ No newline at end of file +} diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index 7f1bf51..32d9f01 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -979,6 +979,12 @@ declare namespace lib_plankton.prog { constructor(value: struct_expression); } } +declare namespace lib_plankton.prog { + /** + */ + class struct_type_void extends struct_type { + } +} declare namespace lib_plankton.prog { /** */ @@ -1099,6 +1105,31 @@ declare namespace lib_plankton.prog { value: (null | struct_expression); } } +declare namespace lib_plankton.prog { + /** + */ + class struct_statement_function_definition extends struct_statement { + name: string; + arguments: Array<{ + name: string; + type: (null | struct_type); + }>; + output_type: (null | struct_type); + body: struct_statement; + constructor(name: string, arguments_: Array<{ + name: string; + type: (null | struct_type); + }>, output_type: (null | struct_type), body: struct_statement); + } +} +declare namespace lib_plankton.prog { + /** + */ + class struct_statement_return extends struct_statement { + expression: struct_expression; + constructor(expression: struct_expression); + } +} declare namespace lib_plankton.prog { /** */ @@ -1126,7 +1157,9 @@ declare namespace lib_plankton.prog { function render_type(type: struct_type): string; /** */ - function render_statement(statement: struct_statement): string; + function render_statement(statement: struct_statement, options?: { + indentation?: int; + }): string; /** */ function render_program(program: struct_program): string; diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index 780aa43..8260d1e 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -3110,6 +3110,41 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +You should have received a copy of the GNU Lesser General Public License +along with »bacterio-plankton:prog«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var prog; + (function (prog) { + /** + */ + var struct_type_void = /** @class */ (function (_super) { + __extends(struct_type_void, _super); + function struct_type_void() { + return _super !== null && _super.apply(this, arguments) || this; + } + return struct_type_void; + }(prog.struct_type)); + prog.struct_type_void = struct_type_void; + })(prog = lib_plankton.prog || (lib_plankton.prog = {})); +})(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:prog«. + +Copyright 2016-2023 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:prog« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»bacterio-plankton:prog« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with »bacterio-plankton:prog«. If not, see . */ @@ -3619,6 +3654,83 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +You should have received a copy of the GNU Lesser General Public License +along with »bacterio-plankton:prog«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var prog; + (function (prog) { + /** + */ + var struct_statement_function_definition = /** @class */ (function (_super) { + __extends(struct_statement_function_definition, _super); + function struct_statement_function_definition(name, arguments_, output_type, body) { + var _this = _super.call(this) || this; + _this.name = name; + _this.arguments = arguments_; + _this.output_type = output_type; + _this.body = body; + return _this; + } + return struct_statement_function_definition; + }(prog.struct_statement)); + prog.struct_statement_function_definition = struct_statement_function_definition; + })(prog = lib_plankton.prog || (lib_plankton.prog = {})); +})(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:prog«. + +Copyright 2016-2023 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:prog« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»bacterio-plankton:prog« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »bacterio-plankton:prog«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var prog; + (function (prog) { + /** + */ + var struct_statement_return = /** @class */ (function (_super) { + __extends(struct_statement_return, _super); + function struct_statement_return(expression) { + var _this = _super.call(this) || this; + _this.expression = expression; + return _this; + } + return struct_statement_return; + }(prog.struct_statement)); + prog.struct_statement_return = struct_statement_return; + })(prog = lib_plankton.prog || (lib_plankton.prog = {})); +})(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:prog«. + +Copyright 2016-2023 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:prog« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»bacterio-plankton:prog« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with »bacterio-plankton:prog«. If not, see . */ @@ -3718,6 +3830,10 @@ var lib_plankton; var type_literal = type; return render_expression(type_literal.value); } + else if (type instanceof prog.struct_type_void) { + var type_void = type; + return "void"; + } else if (type instanceof prog.struct_type_boolean) { var type_boolean = type; return "boolean"; @@ -3798,17 +3914,23 @@ var lib_plankton; prog.render_type = render_type; /** */ - function render_statement(statement) { + function render_statement(statement, options) { + if (options === void 0) { options = {}; } + options = Object.assign({ + "indentation": 0 + }, options); if (statement instanceof prog.struct_statement_type_definition) { var statement_type_definition = statement; - return lib_plankton.string.coin("type {{name}} = {{type}};\n", { + return lib_plankton.string.coin("{{indentation}}type {{name}} = {{type}};\n", { + "indentation": "\t".repeat(options.indentation), "name": statement_type_definition.name, "type": render_type(statement_type_definition.type) }); } - if (statement instanceof prog.struct_statement_declaration) { + else if (statement instanceof prog.struct_statement_declaration) { var statement_declaration = statement; - return lib_plankton.string.coin("{{kind}} {{name}}{{macro_type}}{{macro_value}};\n", { + return lib_plankton.string.coin("{{indentation}}{{kind}} {{name}}{{macro_type}}{{macro_value}};\n", { + "indentation": "\t".repeat(options.indentation), "kind": (statement_declaration.constant ? "const" : "let"), @@ -3825,13 +3947,46 @@ var lib_plankton; })) }); } - if (statement instanceof prog.struct_statement_assignment) { + else if (statement instanceof prog.struct_statement_assignment) { var statement_assignment = statement; - return lib_plankton.string.coin("{{name}} = {{value}};\n", { + return lib_plankton.string.coin("{{indentation}}{{name}} = {{value}};\n", { + "indentation": "\t".repeat(options.indentation), "name": statement_assignment.name, "value": render_expression(statement_assignment.value) }); } + else if (statement instanceof prog.struct_statement_function_definition) { + var statement_function_definition = statement; + return lib_plankton.string.coin("{{indentation}}function {{name}}({{arguments}}){{macro_output_type}} {\n{{body}}}\n", { + "indentation": "\t".repeat(options.indentation), + "name": statement_function_definition.name, + "arguments": (statement_function_definition.arguments + .map(function (argument) { return lib_plankton.string.coin("{{name}}{{macro_type}}", { + "name": argument.name, + "macro_type": ((argument.type === null) + ? "" + : lib_plankton.string.coin(" : {{type}}", { + "type": render_type(argument.type) + })) + }); }) + .join(", ")), + "macro_output_type": ((statement_function_definition.output_type === null) + ? "" + : lib_plankton.string.coin(" : {{type}}", { + "type": render_type(statement_function_definition.output_type) + })), + "body": render_statement(statement_function_definition.body, { + "indentation": (options.indentation + 1) + }) + }); + } + else if (statement instanceof prog.struct_statement_return) { + var statement_return = statement; + return lib_plankton.string.coin("{{indentation}}return {{expression}};\n", { + "indentation": "\t".repeat(options.indentation), + "expression": render_expression(statement_return.expression) + }); + } else { throw (new Error("unhandled statement: " + String(statement))); } @@ -3842,7 +3997,7 @@ var lib_plankton; function render_program(program) { return (program.statements .map(function (statement) { return render_statement(statement); }) - .join("")); + .join("\n")); } prog.render_program = render_program; /** diff --git a/source/main.ts b/source/main.ts index 4a757ef..5aaa8ae 100644 --- a/source/main.ts +++ b/source/main.ts @@ -5,10 +5,10 @@ async function main( ) : Promise { const outputs : Record = { - "sqlite": output_sqlite, - "mysql": output_mysql, - "typescript": output_typescript, "jsonschema": output_jsonschema, + "database-sqlite": output_database_sqlite, + "database-mysql": output_database_mysql, + "backend-typescript": output_backend_typescript, }; const arg_handler = new lib_plankton.args.class_handler( @@ -18,7 +18,7 @@ async function main( "type": lib_plankton.args.enum_type.string, "kind": lib_plankton.args.enum_kind.volatile, "mode": lib_plankton.args.enum_mode.replace, - "default": "sqlite", + "default": "database-sqlite", "parameters": { "indicators_long": ["format"], "indicators_short": ["f"], diff --git a/source/outputs/backend_typescript.ts b/source/outputs/backend_typescript.ts new file mode 100644 index 0000000..053c959 --- /dev/null +++ b/source/outputs/backend_typescript.ts @@ -0,0 +1,280 @@ +const output_backend_typescript : type_output = { + "render": function (input_data) { + const map_primitive_type = function (typename : string) : lib_plankton.prog.struct_type { + const mymap : Record = { + "boolean": new lib_plankton.prog.struct_type_boolean(), + "integer": new lib_plankton.prog.struct_type_integer(), + // "float": new lib_plankton.prog.struct_type_integer(), + "string_short": new lib_plankton.prog.struct_type_string(), + "string_medium": new lib_plankton.prog.struct_type_string(), + "string_long": new lib_plankton.prog.struct_type_string(), + }; + return mymap[typename]; + }; + const prog_output : lib_plankton.prog.type_output = lib_plankton.prog.output_typescript(); + const name_type = function (domain) : string {return ("type_" + domain.name);}; + const name_collection = function (domain) : string {return ("collection_" + domain.name);}; + const name_repository_function_list = function (domain) : string { + return lib_plankton.string.coin( + "repository_{{name}}_list", + { + "name": domain.name + } + ); + }; + const name_repository_function_read = function (domain) : string { + return lib_plankton.string.coin( + "repository_{{name}}_read", + { + "name": domain.name + } + ); + }; + const name_repository_function_create = function (domain) : string { + return lib_plankton.string.coin( + "repository_{{name}}_create", + { + "name": domain.name + } + ); + }; + const name_repository_function_update = function (domain) : string { + return lib_plankton.string.coin( + "repository_{{name}}_update", + { + "name": domain.name + } + ); + }; + const name_repository_function_delete = function (domain) : string { + return lib_plankton.string.coin( + "repository_{{name}}_delete", + { + "name": domain.name + } + ); + }; + return prog_output.render_program( + new lib_plankton.prog.struct_program( + [] + // base + .concat( + [] + ) + // lib (database) + .concat( + [] + ) + // entities + .concat( + input_data["domains"] + .map( + (domain) => new lib_plankton.prog.struct_statement_type_definition( + name_type(domain), + new lib_plankton.prog.struct_type_record( + domain.data_fields + .map( + (data_field) => ({ + "name": data_field.name, + "type": ( + data_field.nullable + ? new lib_plankton.prog.struct_type_union( + new lib_plankton.prog.struct_type_literal( + new lib_plankton.prog.struct_expression_literal( + null + ) + ), + map_primitive_type(data_field["type"]) + ) + : map_primitive_type(data_field["type"]) + ), + "mandatory": true, + }) + ) + ) + ) + ) + ) + // repositories + .concat( + input_data["domains"] + .map( + (domain) => ( + (domain.key_field === null) + ? [ + // TODO + ] + : [ + /* + // data + new lib_plankton.prog.struct_statement_declaration( + false, + name_collection(domain), + ( + (domain.key_field === null) + ? new lib_plankton.prog.struct_type_list( + new lib_plankton.prog.struct_type_construction( + name_type(domain), + null + ) + ) + : new lib_plankton.prog.struct_type_map( + new lib_plankton.prog.struct_type_integer(), + new lib_plankton.prog.struct_type_construction( + name_type(domain), + null + ) + ) + ), + ( + (domain.key_field === null) + ? new lib_plankton.prog.struct_expression_literal([]) + : new lib_plankton.prog.struct_expression_literal({}) + ) + ), + */ + // list + new lib_plankton.prog.struct_statement_function_definition( + name_repository_function_list(domain), + [], + new lib_plankton.prog.struct_type_construction( + "Promise", + [ + new lib_plankton.prog.struct_type_construction( + "Array", + [ + new lib_plankton.prog.struct_type_construction( + name_type(domain), + null + ), + ] + ) + ] + ), + new lib_plankton.prog.struct_statement_return( + // TODO + new lib_plankton.prog.struct_expression_literal(null) + ), + ), + // read + new lib_plankton.prog.struct_statement_function_definition( + name_repository_function_read(domain), + [ + { + "name": "key", + "type": new lib_plankton.prog.struct_type_integer( + ), + }, + ], + new lib_plankton.prog.struct_type_construction( + "Promise", + [ + new lib_plankton.prog.struct_type_construction( + name_type(domain), + null + ), + ] + ), + new lib_plankton.prog.struct_statement_return( + // TODO + new lib_plankton.prog.struct_expression_literal(null) + ), + ), + // create + new lib_plankton.prog.struct_statement_function_definition( + name_repository_function_create(domain), + [ + { + "name": "value", + "type": new lib_plankton.prog.struct_type_construction( + name_type(domain), + null + ), + }, + ], + new lib_plankton.prog.struct_type_construction( + "Promise", + [ + new lib_plankton.prog.struct_type_integer( + ), + ] + ), + new lib_plankton.prog.struct_statement_return( + // TODO + new lib_plankton.prog.struct_expression_literal(null) + ), + ), + // update + new lib_plankton.prog.struct_statement_function_definition( + name_repository_function_update(domain), + [ + { + "name": "key", + "type": new lib_plankton.prog.struct_type_integer( + ), + }, + { + "name": "value", + "type": new lib_plankton.prog.struct_type_construction( + name_type(domain), + null + ), + }, + ], + new lib_plankton.prog.struct_type_construction( + "Promise", + [ + new lib_plankton.prog.struct_type_void( + ), + ] + ), + new lib_plankton.prog.struct_statement_return( + // TODO + new lib_plankton.prog.struct_expression_literal(null) + ), + ), + // delete + new lib_plankton.prog.struct_statement_function_definition( + name_repository_function_delete(domain), + [ + { + "name": "key", + "type": new lib_plankton.prog.struct_type_integer( + ), + }, + ], + new lib_plankton.prog.struct_type_construction( + "Promise", + [ + new lib_plankton.prog.struct_type_void(), + ] + ), + new lib_plankton.prog.struct_statement_return( + // TODO + new lib_plankton.prog.struct_expression_literal(null) + ), + ), + ] + ) + ) + .reduce( + (x, y) => x.concat(y), + [] + ) + ) + // services + .concat( + [] + ) + // api + .concat( + [] + ) + // server + .concat( + [] + ) + ) + ); + }, +}; diff --git a/source/outputs/mysql.ts b/source/outputs/database_mysql.ts similarity index 96% rename from source/outputs/mysql.ts rename to source/outputs/database_mysql.ts index 6c8639d..9f6b354 100644 --- a/source/outputs/mysql.ts +++ b/source/outputs/database_mysql.ts @@ -1,4 +1,4 @@ -function mysql_value_encode( +function database_mysql_value_encode( value : any ) : string { @@ -27,7 +27,7 @@ function mysql_value_encode( } } -const output_mysql : type_output = { +const output_database_mysql : type_output = { "render": function (input_data) { return ( input_data.domains @@ -114,7 +114,7 @@ const output_mysql : type_output = { lib_plankton.string.coin( "DEFAULT {{value}}", { - "value": mysql_value_encode(data_field.default), + "value": database_mysql_value_encode(data_field.default), } ), ] diff --git a/source/outputs/sqlite.ts b/source/outputs/database_sqlite.ts similarity index 95% rename from source/outputs/sqlite.ts rename to source/outputs/database_sqlite.ts index 334420a..b13877b 100644 --- a/source/outputs/sqlite.ts +++ b/source/outputs/database_sqlite.ts @@ -1,4 +1,4 @@ -function sqlite_value_encode( +function database_sqlite_value_encode( value : any ) : string { @@ -27,7 +27,7 @@ function sqlite_value_encode( } } -const output_sqlite : type_output = { +const output_database_sqlite : type_output = { "render": function (input_data) { return ( input_data["domains"] @@ -92,7 +92,7 @@ const output_sqlite : type_output = { lib_plankton.string.coin( "DEFAULT {{value}}", { - "value": sqlite_value_encode(data_field.default), + "value": database_sqlite_value_encode(data_field.default), } ), ] diff --git a/source/outputs/typescript.ts b/source/outputs/typescript.ts deleted file mode 100644 index 3887bc5..0000000 --- a/source/outputs/typescript.ts +++ /dev/null @@ -1,77 +0,0 @@ -const output_typescript : type_output = { - "render": function (input_data) { - const map_primitive_type = function (typename : string) : lib_plankton.prog.struct_type { - const mymap : Record = { - "boolean": new lib_plankton.prog.struct_type_boolean(), - "integer": new lib_plankton.prog.struct_type_integer(), - // "float": new lib_plankton.prog.struct_type_integer(), - "string_short": new lib_plankton.prog.struct_type_string(), - "string_medium": new lib_plankton.prog.struct_type_string(), - "string_long": new lib_plankton.prog.struct_type_string(), - }; - return mymap[typename]; - }; - const name_type : string = ("type_" + domain.name); - const name_collection : string = ("collection_" + domain.name); - const prog_output : lib_plankton.prog.type_output = lib_plankton.prog.output_typescript(); - return prog_output.render_program( - new lib_plankton.prog.struct_program( - input_data["domains"] - .map( - (domain) => ([ - new lib_plankton.prog.struct_statement_type_definition( - name_type, - new lib_plankton.prog.struct_type_record( - domain.data_fields - .map( - (data_field) => ({ - "name": data_field.name, - "type": ( - data_field.nullable - ? new lib_plankton.prog.struct_type_union( - new lib_plankton.prog.struct_type_literal( - new lib_plankton.prog.struct_expression_literal( - null - ) - ), - map_primitive_type(data_field["type"]) - ) - : map_primitive_type(data_field["type"]) - ), - "mandatory": true, - }) - ) - ) - ), - new lib_plankton.prog.struct_statement_declaration( - false, - name_collection, - ( - (domain.key_field === null) - ? new lib_plankton.prog.struct_type_list( - new lib_plankton.prog.struct_type_construction( - name_type, - null - ) - ) - : new lib_plankton.prog.struct_type_map( - new lib_plankton.prog.struct_type_integer(), - new lib_plankton.prog.struct_type_construction( - name_type, - null - ) - ) - ), - ( - (domain.key_field === null) - ? new lib_plankton.prog.struct_expression_literal([]) - : new lib_plankton.prog.struct_expression_literal({}) - ) - ) - ]) - ) - .reduce((x, y) => x.concat(y), []) - ) - ); - }, -}; diff --git a/tools/makefile b/tools/makefile index db02ea5..75cd6ec 100644 --- a/tools/makefile +++ b/tools/makefile @@ -17,10 +17,10 @@ all: build/sindri temp/sindri-unlinked.js: \ lib/plankton/plankton.d.ts \ source/types.ts \ -source/outputs/sqlite.ts \ -source/outputs/mysql.ts \ -source/outputs/typescript.ts \ source/outputs/jsonschema.ts \ +source/outputs/database_sqlite.ts \ +source/outputs/database_mysql.ts \ +source/outputs/backend_typescript.ts \ source/conf.ts \ source/main.ts @ ${cmd_log} "compiling …"