From a313014b18c3d391774ceaa34afc7147355e4b00 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Tue, 27 Sep 2022 23:35:09 +0800 Subject: [PATCH 1/5] feat: schema progress --- packages/schema/.gitignore | 1 + packages/schema/out/cli/cli-util.js | 61 - packages/schema/out/cli/cli-util.js.map | 1 - packages/schema/out/cli/generator.js | 24 - packages/schema/out/cli/generator.js.map | 1 - packages/schema/out/cli/index.js | 44 - packages/schema/out/cli/index.js.map | 1 - packages/schema/out/extension.js | 81 -- packages/schema/out/extension.js.map | 1 - .../out/language-server/generated/ast.js | 215 --- .../out/language-server/generated/ast.js.map | 1 - .../out/language-server/generated/grammar.js | 900 ------------ .../language-server/generated/grammar.js.map | 1 - .../out/language-server/generated/module.js | 23 - .../language-server/generated/module.js.map | 1 - packages/schema/out/language-server/main.js | 12 - .../schema/out/language-server/main.js.map | 1 - .../out/language-server/zmodel-module.js | 40 - .../out/language-server/zmodel-module.js.map | 1 - .../out/language-server/zmodel-validator.js | 25 - .../language-server/zmodel-validator.js.map | 1 - packages/schema/package.json | 2 +- .../src/language-server/generated/ast.ts | 248 +++- .../src/language-server/generated/grammar.ts | 1205 ++++++++++++++--- .../schema/src/language-server/stdlib.zmodel | 9 + .../schema/src/language-server/zmodel.langium | 135 +- .../schema/syntaxes/zmodel.tmLanguage.json | 2 +- packages/schema/tests/basic.test.ts | 117 -- packages/schema/tests/parser.test.ts | 422 ++++++ packages/schema/tests/utils.ts | 10 +- pnpm-lock.yaml | 2 +- .../todo/.zenstack/server/data/todo-list.ts | 2 +- samples/todo/.zenstack/types.ts | 4 +- 33 files changed, 1731 insertions(+), 1863 deletions(-) create mode 100644 packages/schema/.gitignore delete mode 100644 packages/schema/out/cli/cli-util.js delete mode 100644 packages/schema/out/cli/cli-util.js.map delete mode 100644 packages/schema/out/cli/generator.js delete mode 100644 packages/schema/out/cli/generator.js.map delete mode 100644 packages/schema/out/cli/index.js delete mode 100644 packages/schema/out/cli/index.js.map delete mode 100644 packages/schema/out/extension.js delete mode 100644 packages/schema/out/extension.js.map delete mode 100644 packages/schema/out/language-server/generated/ast.js delete mode 100644 packages/schema/out/language-server/generated/ast.js.map delete mode 100644 packages/schema/out/language-server/generated/grammar.js delete mode 100644 packages/schema/out/language-server/generated/grammar.js.map delete mode 100644 packages/schema/out/language-server/generated/module.js delete mode 100644 packages/schema/out/language-server/generated/module.js.map delete mode 100644 packages/schema/out/language-server/main.js delete mode 100644 packages/schema/out/language-server/main.js.map delete mode 100644 packages/schema/out/language-server/zmodel-module.js delete mode 100644 packages/schema/out/language-server/zmodel-module.js.map delete mode 100644 packages/schema/out/language-server/zmodel-validator.js delete mode 100644 packages/schema/out/language-server/zmodel-validator.js.map create mode 100644 packages/schema/src/language-server/stdlib.zmodel delete mode 100644 packages/schema/tests/basic.test.ts create mode 100644 packages/schema/tests/parser.test.ts diff --git a/packages/schema/.gitignore b/packages/schema/.gitignore new file mode 100644 index 000000000..6a3417b8d --- /dev/null +++ b/packages/schema/.gitignore @@ -0,0 +1 @@ +/out/ diff --git a/packages/schema/out/cli/cli-util.js b/packages/schema/out/cli/cli-util.js deleted file mode 100644 index dcf3fb812..000000000 --- a/packages/schema/out/cli/cli-util.js +++ /dev/null @@ -1,61 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.extractDestinationAndName = exports.extractAstNode = exports.extractDocument = void 0; -const colors_1 = __importDefault(require("colors")); -const path_1 = __importDefault(require("path")); -const fs_1 = __importDefault(require("fs")); -const vscode_uri_1 = require("vscode-uri"); -function extractDocument(fileName, services) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - const extensions = services.LanguageMetaData.fileExtensions; - if (!extensions.includes(path_1.default.extname(fileName))) { - console.error(colors_1.default.yellow(`Please choose a file with one of these extensions: ${extensions}.`)); - process.exit(1); - } - if (!fs_1.default.existsSync(fileName)) { - console.error(colors_1.default.red(`File ${fileName} does not exist.`)); - process.exit(1); - } - const document = services.shared.workspace.LangiumDocuments.getOrCreateDocument(vscode_uri_1.URI.file(path_1.default.resolve(fileName))); - yield services.shared.workspace.DocumentBuilder.build([document], { validationChecks: 'all' }); - const validationErrors = ((_a = document.diagnostics) !== null && _a !== void 0 ? _a : []).filter(e => e.severity === 1); - if (validationErrors.length > 0) { - console.error(colors_1.default.red('There are validation errors:')); - for (const validationError of validationErrors) { - console.error(colors_1.default.red(`line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]`)); - } - process.exit(1); - } - return document; - }); -} -exports.extractDocument = extractDocument; -function extractAstNode(fileName, services) { - var _a; - return __awaiter(this, void 0, void 0, function* () { - return (_a = (yield extractDocument(fileName, services)).parseResult) === null || _a === void 0 ? void 0 : _a.value; - }); -} -exports.extractAstNode = extractAstNode; -function extractDestinationAndName(filePath, destination) { - filePath = filePath.replace(/\..*$/, '').replace(/[.-]/g, ''); - return { - destination: destination !== null && destination !== void 0 ? destination : path_1.default.join(path_1.default.dirname(filePath), 'generated'), - name: path_1.default.basename(filePath) - }; -} -exports.extractDestinationAndName = extractDestinationAndName; -//# sourceMappingURL=cli-util.js.map \ No newline at end of file diff --git a/packages/schema/out/cli/cli-util.js.map b/packages/schema/out/cli/cli-util.js.map deleted file mode 100644 index d545d06e0..000000000 --- a/packages/schema/out/cli/cli-util.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"cli-util.js","sourceRoot":"","sources":["../../src/cli/cli-util.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,oDAA4B;AAC5B,gDAAwB;AACxB,4CAAoB;AAEpB,2CAAiC;AAEjC,SAAsB,eAAe,CAAC,QAAgB,EAAE,QAAyB;;;QAC7E,MAAM,UAAU,GAAG,QAAQ,CAAC,gBAAgB,CAAC,cAAc,CAAC;QAC5D,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE;YAC9C,OAAO,CAAC,KAAK,CAAC,gBAAM,CAAC,MAAM,CAAC,sDAAsD,UAAU,GAAG,CAAC,CAAC,CAAC;YAClG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACnB;QAED,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;YAC1B,OAAO,CAAC,KAAK,CAAC,gBAAM,CAAC,GAAG,CAAC,QAAQ,QAAQ,kBAAkB,CAAC,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACnB;QAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,gBAAG,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAClH,MAAM,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;QAE/F,MAAM,gBAAgB,GAAG,CAAC,MAAA,QAAQ,CAAC,WAAW,mCAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;QACpF,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE;YAC7B,OAAO,CAAC,KAAK,CAAC,gBAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC,CAAC;YAC1D,KAAK,MAAM,eAAe,IAAI,gBAAgB,EAAE;gBAC5C,OAAO,CAAC,KAAK,CAAC,gBAAM,CAAC,GAAG,CACpB,QAAQ,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,KAAK,eAAe,CAAC,OAAO,KAAK,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,CACvI,CAAC,CAAC;aACN;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACnB;QAED,OAAO,QAAQ,CAAC;;CACnB;AA3BD,0CA2BC;AAED,SAAsB,cAAc,CAAoB,QAAgB,EAAE,QAAyB;;;QAC/F,OAAO,MAAA,CAAC,MAAM,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,WAAW,0CAAE,KAAU,CAAC;;CAC9E;AAFD,wCAEC;AAOD,SAAgB,yBAAyB,CAAC,QAAgB,EAAE,WAA+B;IACvF,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC9D,OAAO;QACH,WAAW,EAAE,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC;QAC1E,IAAI,EAAE,cAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;KAChC,CAAC;AACN,CAAC;AAND,8DAMC"} \ No newline at end of file diff --git a/packages/schema/out/cli/generator.js b/packages/schema/out/cli/generator.js deleted file mode 100644 index d30cd2d46..000000000 --- a/packages/schema/out/cli/generator.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.generateJavaScript = void 0; -const fs_1 = __importDefault(require("fs")); -const langium_1 = require("langium"); -const path_1 = __importDefault(require("path")); -const cli_util_1 = require("./cli-util"); -function generateJavaScript(model, filePath, destination) { - const data = (0, cli_util_1.extractDestinationAndName)(filePath, destination); - const generatedFilePath = `${path_1.default.join(data.destination, data.name)}.js`; - const fileNode = new langium_1.CompositeGeneratorNode(); - fileNode.append('"use strict";', langium_1.NL, langium_1.NL); - // model.greetings.forEach(greeting => fileNode.append(`console.log('Hello, ${greeting.person.ref?.name}!');`, NL)); - if (!fs_1.default.existsSync(data.destination)) { - fs_1.default.mkdirSync(data.destination, { recursive: true }); - } - fs_1.default.writeFileSync(generatedFilePath, (0, langium_1.processGeneratorNode)(fileNode)); - return generatedFilePath; -} -exports.generateJavaScript = generateJavaScript; -//# sourceMappingURL=generator.js.map \ No newline at end of file diff --git a/packages/schema/out/cli/generator.js.map b/packages/schema/out/cli/generator.js.map deleted file mode 100644 index 07c96109d..000000000 --- a/packages/schema/out/cli/generator.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/cli/generator.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,qCAA2E;AAC3E,gDAAwB;AAExB,yCAAuD;AAEvD,SAAgB,kBAAkB,CAC9B,KAAY,EACZ,QAAgB,EAChB,WAA+B;IAE/B,MAAM,IAAI,GAAG,IAAA,oCAAyB,EAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC9D,MAAM,iBAAiB,GAAG,GAAG,cAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;IAEzE,MAAM,QAAQ,GAAG,IAAI,gCAAsB,EAAE,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,YAAE,EAAE,YAAE,CAAC,CAAC;IACzC,oHAAoH;IAEpH,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;QAClC,YAAE,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;KACvD;IACD,YAAE,CAAC,aAAa,CAAC,iBAAiB,EAAE,IAAA,8BAAoB,EAAC,QAAQ,CAAC,CAAC,CAAC;IACpE,OAAO,iBAAiB,CAAC;AAC7B,CAAC;AAjBD,gDAiBC"} \ No newline at end of file diff --git a/packages/schema/out/cli/index.js b/packages/schema/out/cli/index.js deleted file mode 100644 index 2cbd0bd69..000000000 --- a/packages/schema/out/cli/index.js +++ /dev/null @@ -1,44 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.generateAction = void 0; -const colors_1 = __importDefault(require("colors")); -const commander_1 = require("commander"); -const module_1 = require("../language-server/generated/module"); -const zmodel_module_1 = require("../language-server/zmodel-module"); -const cli_util_1 = require("./cli-util"); -const generator_1 = require("./generator"); -const generateAction = (fileName, opts) => __awaiter(void 0, void 0, void 0, function* () { - const services = (0, zmodel_module_1.createZModelServices)().ZModel; - const model = yield (0, cli_util_1.extractAstNode)(fileName, services); - const generatedFilePath = (0, generator_1.generateJavaScript)(model, fileName, opts.destination); - console.log(colors_1.default.green(`JavaScript code generated successfully: ${generatedFilePath}`)); -}); -exports.generateAction = generateAction; -function default_1() { - const program = new commander_1.Command(); - program - // eslint-disable-next-line @typescript-eslint/no-var-requires - .version(require('../../package.json').version); - const fileExtensions = module_1.ZModelLanguageMetaData.fileExtensions.join(', '); - program - .command('generate') - .argument('', `source file (possible file extensions: ${fileExtensions})`) - .option('-d, --destination ', 'destination directory of generating') - .description('generates JavaScript code that prints "Hello, {name}!" for each greeting in a source file') - .action(exports.generateAction); - program.parse(process.argv); -} -exports.default = default_1; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/schema/out/cli/index.js.map b/packages/schema/out/cli/index.js.map deleted file mode 100644 index 0293f48b3..000000000 --- a/packages/schema/out/cli/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,oDAA4B;AAC5B,yCAAoC;AAEpC,gEAA6E;AAC7E,oEAAwE;AACxE,yCAA4C;AAC5C,2CAAiD;AAE1C,MAAM,cAAc,GAAG,CAC1B,QAAgB,EAChB,IAAqB,EACR,EAAE;IACf,MAAM,QAAQ,GAAG,IAAA,oCAAoB,GAAE,CAAC,MAAM,CAAC;IAC/C,MAAM,KAAK,GAAG,MAAM,IAAA,yBAAc,EAAQ,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC9D,MAAM,iBAAiB,GAAG,IAAA,8BAAkB,EACxC,KAAK,EACL,QAAQ,EACR,IAAI,CAAC,WAAW,CACnB,CAAC;IACF,OAAO,CAAC,GAAG,CACP,gBAAM,CAAC,KAAK,CACR,2CAA2C,iBAAiB,EAAE,CACjE,CACJ,CAAC;AACN,CAAC,CAAA,CAAC;AAhBW,QAAA,cAAc,kBAgBzB;AAMF;IACI,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;IAE9B,OAAO;QACH,8DAA8D;SAC7D,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,OAAO,CAAC,CAAC;IAEpD,MAAM,cAAc,GAAG,+BAAsB,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,OAAO;SACF,OAAO,CAAC,UAAU,CAAC;SACnB,QAAQ,CACL,QAAQ,EACR,0CAA0C,cAAc,GAAG,CAC9D;SACA,MAAM,CACH,yBAAyB,EACzB,qCAAqC,CACxC;SACA,WAAW,CACR,2FAA2F,CAC9F;SACA,MAAM,CAAC,sBAAc,CAAC,CAAC;IAE5B,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAxBD,4BAwBC"} \ No newline at end of file diff --git a/packages/schema/out/extension.js b/packages/schema/out/extension.js deleted file mode 100644 index 2c985d64f..000000000 --- a/packages/schema/out/extension.js +++ /dev/null @@ -1,81 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.deactivate = exports.activate = void 0; -const vscode = __importStar(require("vscode")); -const path = __importStar(require("path")); -const node_1 = require("vscode-languageclient/node"); -let client; -// This function is called when the extension is activated. -function activate(context) { - client = startLanguageClient(context); -} -exports.activate = activate; -// This function is called when the extension is deactivated. -function deactivate() { - if (client) { - return client.stop(); - } - return undefined; -} -exports.deactivate = deactivate; -function startLanguageClient(context) { - const serverModule = context.asAbsolutePath(path.join('out', 'language-server', 'main')); - // The debug options for the server - // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging. - // By setting `process.env.DEBUG_BREAK` to a truthy value, the language server will wait until a debugger is attached. - const debugOptions = { - execArgv: [ - '--nolazy', - `--inspect${process.env.DEBUG_BREAK ? '-brk' : ''}=${process.env.DEBUG_SOCKET || '6009'}`, - ], - }; - // If the extension is launched in debug mode then the debug server options are used - // Otherwise the run options are used - const serverOptions = { - run: { module: serverModule, transport: node_1.TransportKind.ipc }, - debug: { - module: serverModule, - transport: node_1.TransportKind.ipc, - options: debugOptions, - }, - }; - const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.zmodel'); - context.subscriptions.push(fileSystemWatcher); - // Options to control the language client - const clientOptions = { - documentSelector: [{ scheme: 'file', language: 'zmodel' }], - synchronize: { - // Notify the server about file changes to files contained in the workspace - fileEvents: fileSystemWatcher, - }, - }; - // Create the language client and start the client. - const client = new node_1.LanguageClient('zmodel', 'ZenStack Model', serverOptions, clientOptions); - // Start the client. This will also launch the server - client.start(); - return client; -} -//# sourceMappingURL=extension.js.map \ No newline at end of file diff --git a/packages/schema/out/extension.js.map b/packages/schema/out/extension.js.map deleted file mode 100644 index efc599158..000000000 --- a/packages/schema/out/extension.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,2CAA6B;AAC7B,qDAKoC;AAEpC,IAAI,MAAsB,CAAC;AAE3B,2DAA2D;AAC3D,SAAgB,QAAQ,CAAC,OAAgC;IACrD,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC;AAFD,4BAEC;AAED,6DAA6D;AAC7D,SAAgB,UAAU;IACtB,IAAI,MAAM,EAAE;QACR,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;KACxB;IACD,OAAO,SAAS,CAAC;AACrB,CAAC;AALD,gCAKC;AAED,SAAS,mBAAmB,CAAC,OAAgC;IACzD,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,CACvC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAC9C,CAAC;IACF,mCAAmC;IACnC,8GAA8G;IAC9G,sHAAsH;IACtH,MAAM,YAAY,GAAG;QACjB,QAAQ,EAAE;YACN,UAAU;YACV,YAAY,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAC7C,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAChC,EAAE;SACL;KACJ,CAAC;IAEF,oFAAoF;IACpF,qCAAqC;IACrC,MAAM,aAAa,GAAkB;QACjC,GAAG,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAa,CAAC,GAAG,EAAE;QAC3D,KAAK,EAAE;YACH,MAAM,EAAE,YAAY;YACpB,SAAS,EAAE,oBAAa,CAAC,GAAG;YAC5B,OAAO,EAAE,YAAY;SACxB;KACJ,CAAC;IAEF,MAAM,iBAAiB,GACnB,MAAM,CAAC,SAAS,CAAC,uBAAuB,CAAC,aAAa,CAAC,CAAC;IAC5D,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAE9C,yCAAyC;IACzC,MAAM,aAAa,GAA0B;QACzC,gBAAgB,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QAC1D,WAAW,EAAE;YACT,2EAA2E;YAC3E,UAAU,EAAE,iBAAiB;SAChC;KACJ,CAAC;IAEF,mDAAmD;IACnD,MAAM,MAAM,GAAG,IAAI,qBAAc,CAC7B,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,aAAa,CAChB,CAAC;IAEF,qDAAqD;IACrD,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,OAAO,MAAM,CAAC;AAClB,CAAC"} \ No newline at end of file diff --git a/packages/schema/out/language-server/generated/ast.js b/packages/schema/out/language-server/generated/ast.js deleted file mode 100644 index d98cf5683..000000000 --- a/packages/schema/out/language-server/generated/ast.js +++ /dev/null @@ -1,215 +0,0 @@ -"use strict"; -/****************************************************************************** - * This file was generated by langium-cli 0.4.0. - * DO NOT EDIT MANUALLY! - ******************************************************************************/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.reflection = exports.ZModelAstReflection = exports.isSimpleInvocationExpr = exports.SimpleInvocationExpr = exports.isModel = exports.Model = exports.isLiteralExpr = exports.LiteralExpr = exports.isFragmentExpansion = exports.FragmentExpansion = exports.isFragment = exports.Fragment = exports.isEnumField = exports.EnumField = exports.isEnum = exports.Enum = exports.isDataSourceField = exports.DataSourceField = exports.isDataSource = exports.DataSource = exports.isDataModelFieldType = exports.DataModelFieldType = exports.isDataModelFieldAttribute = exports.DataModelFieldAttribute = exports.isDataModelField = exports.DataModelField = exports.isDataModel = exports.DataModel = exports.isSimpleExpr = exports.SimpleExpr = exports.isDeclaration = exports.Declaration = void 0; -/* eslint-disable @typescript-eslint/array-type */ -/* eslint-disable @typescript-eslint/no-empty-interface */ -const langium_1 = require("langium"); -exports.Declaration = 'Declaration'; -function isDeclaration(item) { - return exports.reflection.isInstance(item, exports.Declaration); -} -exports.isDeclaration = isDeclaration; -exports.SimpleExpr = 'SimpleExpr'; -function isSimpleExpr(item) { - return exports.reflection.isInstance(item, exports.SimpleExpr); -} -exports.isSimpleExpr = isSimpleExpr; -exports.DataModel = 'DataModel'; -function isDataModel(item) { - return exports.reflection.isInstance(item, exports.DataModel); -} -exports.isDataModel = isDataModel; -exports.DataModelField = 'DataModelField'; -function isDataModelField(item) { - return exports.reflection.isInstance(item, exports.DataModelField); -} -exports.isDataModelField = isDataModelField; -exports.DataModelFieldAttribute = 'DataModelFieldAttribute'; -function isDataModelFieldAttribute(item) { - return exports.reflection.isInstance(item, exports.DataModelFieldAttribute); -} -exports.isDataModelFieldAttribute = isDataModelFieldAttribute; -exports.DataModelFieldType = 'DataModelFieldType'; -function isDataModelFieldType(item) { - return exports.reflection.isInstance(item, exports.DataModelFieldType); -} -exports.isDataModelFieldType = isDataModelFieldType; -exports.DataSource = 'DataSource'; -function isDataSource(item) { - return exports.reflection.isInstance(item, exports.DataSource); -} -exports.isDataSource = isDataSource; -exports.DataSourceField = 'DataSourceField'; -function isDataSourceField(item) { - return exports.reflection.isInstance(item, exports.DataSourceField); -} -exports.isDataSourceField = isDataSourceField; -exports.Enum = 'Enum'; -function isEnum(item) { - return exports.reflection.isInstance(item, exports.Enum); -} -exports.isEnum = isEnum; -exports.EnumField = 'EnumField'; -function isEnumField(item) { - return exports.reflection.isInstance(item, exports.EnumField); -} -exports.isEnumField = isEnumField; -exports.Fragment = 'Fragment'; -function isFragment(item) { - return exports.reflection.isInstance(item, exports.Fragment); -} -exports.isFragment = isFragment; -exports.FragmentExpansion = 'FragmentExpansion'; -function isFragmentExpansion(item) { - return exports.reflection.isInstance(item, exports.FragmentExpansion); -} -exports.isFragmentExpansion = isFragmentExpansion; -exports.LiteralExpr = 'LiteralExpr'; -function isLiteralExpr(item) { - return exports.reflection.isInstance(item, exports.LiteralExpr); -} -exports.isLiteralExpr = isLiteralExpr; -exports.Model = 'Model'; -function isModel(item) { - return exports.reflection.isInstance(item, exports.Model); -} -exports.isModel = isModel; -exports.SimpleInvocationExpr = 'SimpleInvocationExpr'; -function isSimpleInvocationExpr(item) { - return exports.reflection.isInstance(item, exports.SimpleInvocationExpr); -} -exports.isSimpleInvocationExpr = isSimpleInvocationExpr; -class ZModelAstReflection { - getAllTypes() { - return ['DataModel', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Declaration', 'Enum', 'EnumField', 'Fragment', 'FragmentExpansion', 'LiteralExpr', 'Model', 'SimpleExpr', 'SimpleInvocationExpr']; - } - isInstance(node, type) { - return (0, langium_1.isAstNode)(node) && this.isSubtype(node.$type, type); - } - isSubtype(subtype, supertype) { - if (subtype === supertype) { - return true; - } - switch (subtype) { - case exports.DataModel: - case exports.Enum: - case exports.Fragment: { - return this.isSubtype(exports.Declaration, supertype); - } - case exports.LiteralExpr: - case exports.SimpleInvocationExpr: { - return this.isSubtype(exports.SimpleExpr, supertype); - } - default: { - return false; - } - } - } - getReferenceType(referenceId) { - switch (referenceId) { - case 'DataModelFieldType:reference': { - return exports.Declaration; - } - case 'FragmentExpansion:value': { - return exports.Fragment; - } - default: { - throw new Error(`${referenceId} is not a valid reference id.`); - } - } - } - getTypeMetaData(type) { - switch (type) { - case 'DataModel': { - return { - name: 'DataModel', - mandatory: [ - { name: 'fields', type: 'array' }, - { name: 'fragments', type: 'array' } - ] - }; - } - case 'DataModelField': { - return { - name: 'DataModelField', - mandatory: [ - { name: 'attributes', type: 'array' } - ] - }; - } - case 'DataModelFieldAttribute': { - return { - name: 'DataModelFieldAttribute', - mandatory: [ - { name: 'args', type: 'array' } - ] - }; - } - case 'DataModelFieldType': { - return { - name: 'DataModelFieldType', - mandatory: [ - { name: 'array', type: 'boolean' }, - { name: 'optional', type: 'boolean' } - ] - }; - } - case 'DataSource': { - return { - name: 'DataSource', - mandatory: [ - { name: 'fields', type: 'array' } - ] - }; - } - case 'Enum': { - return { - name: 'Enum', - mandatory: [ - { name: 'fields', type: 'array' } - ] - }; - } - case 'Fragment': { - return { - name: 'Fragment', - mandatory: [ - { name: 'fields', type: 'array' } - ] - }; - } - case 'Model': { - return { - name: 'Model', - mandatory: [ - { name: 'datasources', type: 'array' }, - { name: 'enums', type: 'array' }, - { name: 'fragments', type: 'array' }, - { name: 'models', type: 'array' } - ] - }; - } - case 'SimpleInvocationExpr': { - return { - name: 'SimpleInvocationExpr', - mandatory: [ - { name: 'args', type: 'array' } - ] - }; - } - default: { - return { - name: type, - mandatory: [] - }; - } - } - } -} -exports.ZModelAstReflection = ZModelAstReflection; -exports.reflection = new ZModelAstReflection(); -//# sourceMappingURL=ast.js.map \ No newline at end of file diff --git a/packages/schema/out/language-server/generated/ast.js.map b/packages/schema/out/language-server/generated/ast.js.map deleted file mode 100644 index d9fbe15dd..000000000 --- a/packages/schema/out/language-server/generated/ast.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ast.js","sourceRoot":"","sources":["../../../src/language-server/generated/ast.ts"],"names":[],"mappings":";AAAA;;;gFAGgF;;;AAEhF,kDAAkD;AAClD,0DAA0D;AAC1D,qCAAqF;AAIxE,QAAA,WAAW,GAAG,aAAa,CAAC;AAEzC,SAAgB,aAAa,CAAC,IAAa;IACvC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,mBAAW,CAAC,CAAC;AACpD,CAAC;AAFD,sCAEC;AAIY,QAAA,UAAU,GAAG,YAAY,CAAC;AAEvC,SAAgB,YAAY,CAAC,IAAa;IACtC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,kBAAU,CAAC,CAAC;AACnD,CAAC;AAFD,oCAEC;AASY,QAAA,SAAS,GAAG,WAAW,CAAC;AAErC,SAAgB,WAAW,CAAC,IAAa;IACrC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,iBAAS,CAAC,CAAC;AAClD,CAAC;AAFD,kCAEC;AASY,QAAA,cAAc,GAAG,gBAAgB,CAAC;AAE/C,SAAgB,gBAAgB,CAAC,IAAa;IAC1C,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,sBAAc,CAAC,CAAC;AACvD,CAAC;AAFD,4CAEC;AAQY,QAAA,uBAAuB,GAAG,yBAAyB,CAAC;AAEjE,SAAgB,yBAAyB,CAAC,IAAa;IACnD,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,+BAAuB,CAAC,CAAC;AAChE,CAAC;AAFD,8DAEC;AAUY,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAEvD,SAAgB,oBAAoB,CAAC,IAAa;IAC9C,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,0BAAkB,CAAC,CAAC;AAC3D,CAAC;AAFD,oDAEC;AAOY,QAAA,UAAU,GAAG,YAAY,CAAC;AAEvC,SAAgB,YAAY,CAAC,IAAa;IACtC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,kBAAU,CAAC,CAAC;AACnD,CAAC;AAFD,oCAEC;AAQY,QAAA,eAAe,GAAG,iBAAiB,CAAC;AAEjD,SAAgB,iBAAiB,CAAC,IAAa;IAC3C,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,uBAAe,CAAC,CAAC;AACxD,CAAC;AAFD,8CAEC;AAQY,QAAA,IAAI,GAAG,MAAM,CAAC;AAE3B,SAAgB,MAAM,CAAC,IAAa;IAChC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,YAAI,CAAC,CAAC;AAC7C,CAAC;AAFD,wBAEC;AAOY,QAAA,SAAS,GAAG,WAAW,CAAC;AAErC,SAAgB,WAAW,CAAC,IAAa;IACrC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,iBAAS,CAAC,CAAC;AAClD,CAAC;AAFD,kCAEC;AAQY,QAAA,QAAQ,GAAG,UAAU,CAAC;AAEnC,SAAgB,UAAU,CAAC,IAAa;IACpC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,gBAAQ,CAAC,CAAC;AACjD,CAAC;AAFD,gCAEC;AAOY,QAAA,iBAAiB,GAAG,mBAAmB,CAAC;AAErD,SAAgB,mBAAmB,CAAC,IAAa;IAC7C,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,yBAAiB,CAAC,CAAC;AAC1D,CAAC;AAFD,kDAEC;AAOY,QAAA,WAAW,GAAG,aAAa,CAAC;AAEzC,SAAgB,aAAa,CAAC,IAAa;IACvC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,mBAAW,CAAC,CAAC;AACpD,CAAC;AAFD,sCAEC;AASY,QAAA,KAAK,GAAG,OAAO,CAAC;AAE7B,SAAgB,OAAO,CAAC,IAAa;IACjC,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,aAAK,CAAC,CAAC;AAC9C,CAAC;AAFD,0BAEC;AAQY,QAAA,oBAAoB,GAAG,sBAAsB,CAAC;AAE3D,SAAgB,sBAAsB,CAAC,IAAa;IAChD,OAAO,kBAAU,CAAC,UAAU,CAAC,IAAI,EAAE,4BAAoB,CAAC,CAAC;AAC7D,CAAC;AAFD,wDAEC;AAMD,MAAa,mBAAmB;IAE5B,WAAW;QACP,OAAO,CAAC,WAAW,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,mBAAmB,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,sBAAsB,CAAC,CAAC;IAChQ,CAAC;IAED,UAAU,CAAC,IAAa,EAAE,IAAY;QAClC,OAAO,IAAA,mBAAS,EAAC,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/D,CAAC;IAED,SAAS,CAAC,OAAe,EAAE,SAAiB;QACxC,IAAI,OAAO,KAAK,SAAS,EAAE;YACvB,OAAO,IAAI,CAAC;SACf;QACD,QAAQ,OAAO,EAAE;YACb,KAAK,iBAAS,CAAC;YACf,KAAK,YAAI,CAAC;YACV,KAAK,gBAAQ,CAAC,CAAC;gBACX,OAAO,IAAI,CAAC,SAAS,CAAC,mBAAW,EAAE,SAAS,CAAC,CAAC;aACjD;YACD,KAAK,mBAAW,CAAC;YACjB,KAAK,4BAAoB,CAAC,CAAC;gBACvB,OAAO,IAAI,CAAC,SAAS,CAAC,kBAAU,EAAE,SAAS,CAAC,CAAC;aAChD;YACD,OAAO,CAAC,CAAC;gBACL,OAAO,KAAK,CAAC;aAChB;SACJ;IACL,CAAC;IAED,gBAAgB,CAAC,WAA+B;QAC5C,QAAQ,WAAW,EAAE;YACjB,KAAK,8BAA8B,CAAC,CAAC;gBACjC,OAAO,mBAAW,CAAC;aACtB;YACD,KAAK,yBAAyB,CAAC,CAAC;gBAC5B,OAAO,gBAAQ,CAAC;aACnB;YACD,OAAO,CAAC,CAAC;gBACL,MAAM,IAAI,KAAK,CAAC,GAAG,WAAW,+BAA+B,CAAC,CAAC;aAClE;SACJ;IACL,CAAC;IAED,eAAe,CAAC,IAAY;QACxB,QAAQ,IAAI,EAAE;YACV,KAAK,WAAW,CAAC,CAAC;gBACd,OAAO;oBACH,IAAI,EAAE,WAAW;oBACjB,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;wBACjC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE;qBACvC;iBACJ,CAAC;aACL;YACD,KAAK,gBAAgB,CAAC,CAAC;gBACnB,OAAO;oBACH,IAAI,EAAE,gBAAgB;oBACtB,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE;qBACxC;iBACJ,CAAC;aACL;YACD,KAAK,yBAAyB,CAAC,CAAC;gBAC5B,OAAO;oBACH,IAAI,EAAE,yBAAyB;oBAC/B,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;qBAClC;iBACJ,CAAC;aACL;YACD,KAAK,oBAAoB,CAAC,CAAC;gBACvB,OAAO;oBACH,IAAI,EAAE,oBAAoB;oBAC1B,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;wBAClC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE;qBACxC;iBACJ,CAAC;aACL;YACD,KAAK,YAAY,CAAC,CAAC;gBACf,OAAO;oBACH,IAAI,EAAE,YAAY;oBAClB,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;qBACpC;iBACJ,CAAC;aACL;YACD,KAAK,MAAM,CAAC,CAAC;gBACT,OAAO;oBACH,IAAI,EAAE,MAAM;oBACZ,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;qBACpC;iBACJ,CAAC;aACL;YACD,KAAK,UAAU,CAAC,CAAC;gBACb,OAAO;oBACH,IAAI,EAAE,UAAU;oBAChB,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;qBACpC;iBACJ,CAAC;aACL;YACD,KAAK,OAAO,CAAC,CAAC;gBACV,OAAO;oBACH,IAAI,EAAE,OAAO;oBACb,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE;wBACtC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE;wBAChC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE;wBACpC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;qBACpC;iBACJ,CAAC;aACL;YACD,KAAK,sBAAsB,CAAC,CAAC;gBACzB,OAAO;oBACH,IAAI,EAAE,sBAAsB;oBAC5B,SAAS,EAAE;wBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE;qBAClC;iBACJ,CAAC;aACL;YACD,OAAO,CAAC,CAAC;gBACL,OAAO;oBACH,IAAI,EAAE,IAAI;oBACV,SAAS,EAAE,EAAE;iBAChB,CAAC;aACL;SACJ;IACL,CAAC;CACJ;AAnID,kDAmIC;AAEY,QAAA,UAAU,GAAG,IAAI,mBAAmB,EAAE,CAAC"} \ No newline at end of file diff --git a/packages/schema/out/language-server/generated/grammar.js b/packages/schema/out/language-server/generated/grammar.js deleted file mode 100644 index 596a899bf..000000000 --- a/packages/schema/out/language-server/generated/grammar.js +++ /dev/null @@ -1,900 +0,0 @@ -"use strict"; -/****************************************************************************** - * This file was generated by langium-cli 0.4.0. - * DO NOT EDIT MANUALLY! - ******************************************************************************/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ZModelGrammar = void 0; -const langium_1 = require("langium"); -let loadedZModelGrammar; -const ZModelGrammar = () => loadedZModelGrammar || (loadedZModelGrammar = (0, langium_1.loadGrammar)(`{ - "$type": "Grammar", - "isDeclared": true, - "name": "ZModel", - "rules": [ - { - "$type": "ParserRule", - "name": "Model", - "entry": true, - "alternatives": { - "$type": "Alternatives", - "elements": [ - { - "$type": "Assignment", - "feature": "datasources", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataSource" - }, - "arguments": [] - } - }, - { - "$type": "Assignment", - "feature": "models", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataModel" - }, - "arguments": [] - } - }, - { - "$type": "Assignment", - "feature": "enums", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "Enum" - }, - "arguments": [] - } - }, - { - "$type": "Assignment", - "feature": "fragments", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "Fragment" - }, - "arguments": [] - } - } - ], - "cardinality": "*" - }, - "definesHiddenTokens": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "DataSource", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "datasource" - }, - { - "$type": "Keyword", - "value": "{" - }, - { - "$type": "Assignment", - "feature": "fields", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataSourceField" - }, - "arguments": [] - }, - "cardinality": "+" - }, - { - "$type": "Keyword", - "value": "}" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "DataSourceField", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "name", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": "=" - }, - { - "$type": "Assignment", - "feature": "value", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "SimpleExpr" - }, - "arguments": [] - } - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "SimpleExpr", - "alternatives": { - "$type": "Alternatives", - "elements": [ - { - "$type": "RuleCall", - "rule": { - "$refText": "SimpleInvocationExpr" - }, - "arguments": [] - }, - { - "$type": "RuleCall", - "rule": { - "$refText": "LiteralExpr" - }, - "arguments": [] - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "SimpleInvocationExpr", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "function", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": "(" - }, - { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "args", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "SimpleExpr" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": "," - } - ], - "cardinality": "*" - }, - { - "$type": "Assignment", - "feature": "args", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "SimpleExpr" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": ")" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "LiteralExpr", - "alternatives": { - "$type": "Assignment", - "feature": "value", - "operator": "=", - "terminal": { - "$type": "Alternatives", - "elements": [ - { - "$type": "RuleCall", - "rule": { - "$refText": "BOOLEAN" - }, - "arguments": [] - }, - { - "$type": "RuleCall", - "rule": { - "$refText": "INT" - }, - "arguments": [] - }, - { - "$type": "RuleCall", - "rule": { - "$refText": "STRING" - }, - "arguments": [] - } - ] - } - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "Enum", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "enum" - }, - { - "$type": "Assignment", - "feature": "name", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": "{" - }, - { - "$type": "Assignment", - "feature": "fields", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "EnumField" - }, - "arguments": [] - }, - "cardinality": "+" - }, - { - "$type": "Keyword", - "value": "}" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "EnumField", - "alternatives": { - "$type": "Assignment", - "feature": "value", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "DataModel", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "model" - }, - { - "$type": "Assignment", - "feature": "name", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": "{" - }, - { - "$type": "Assignment", - "feature": "fragments", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "FragmentExpansion" - }, - "arguments": [] - }, - "cardinality": "*" - }, - { - "$type": "Assignment", - "feature": "fields", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataModelField" - }, - "arguments": [] - }, - "cardinality": "+" - }, - { - "$type": "Keyword", - "value": "}" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "FragmentExpansion", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "..." - }, - { - "$type": "Assignment", - "feature": "value", - "operator": "=", - "terminal": { - "$type": "CrossReference", - "type": { - "$refText": "Fragment" - }, - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - }, - "deprecatedSyntax": false - } - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "DataModelField", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "name", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - { - "$type": "Assignment", - "feature": "fieldType", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataModelFieldType" - }, - "arguments": [] - } - }, - { - "$type": "Assignment", - "feature": "attributes", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataModelFieldAttribute" - }, - "arguments": [] - }, - "cardinality": "*" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "DataModelFieldType", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Alternatives", - "elements": [ - { - "$type": "RuleCall", - "rule": { - "$refText": "FieldType" - }, - "arguments": [] - }, - { - "$type": "Assignment", - "feature": "reference", - "operator": "=", - "terminal": { - "$type": "CrossReference", - "type": { - "$refText": "Declaration" - }, - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - }, - "deprecatedSyntax": false - } - } - ] - }, - { - "$type": "Assignment", - "feature": "array", - "operator": "?=", - "terminal": { - "$type": "Keyword", - "value": "[]" - }, - "cardinality": "?" - }, - { - "$type": "Assignment", - "feature": "optional", - "operator": "?=", - "terminal": { - "$type": "Keyword", - "value": "?" - }, - "cardinality": "?" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "Declaration", - "alternatives": { - "$type": "Alternatives", - "elements": [ - { - "$type": "RuleCall", - "rule": { - "$refText": "DataModel" - }, - "arguments": [] - }, - { - "$type": "RuleCall", - "rule": { - "$refText": "Fragment" - }, - "arguments": [] - }, - { - "$type": "RuleCall", - "rule": { - "$refText": "Enum" - }, - "arguments": [] - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "DataModelFieldAttribute", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "@" - }, - { - "$type": "Assignment", - "feature": "name", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "(" - }, - { - "$type": "Group", - "elements": [ - { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "args", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "LiteralExpr" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": "," - } - ], - "cardinality": "*" - }, - { - "$type": "Assignment", - "feature": "args", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "LiteralExpr" - }, - "arguments": [] - } - } - ], - "cardinality": "?" - }, - { - "$type": "Keyword", - "value": ")" - } - ], - "cardinality": "?" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "Fragment", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "fragment" - }, - { - "$type": "Assignment", - "feature": "name", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": "{" - }, - { - "$type": "Assignment", - "feature": "fields", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataModelField" - }, - "arguments": [] - }, - "cardinality": "+" - }, - { - "$type": "Keyword", - "value": "}" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "FieldType", - "fragment": true, - "alternatives": { - "$type": "Assignment", - "feature": "type", - "operator": "=", - "terminal": { - "$type": "Alternatives", - "elements": [ - { - "$type": "Keyword", - "value": "String" - }, - { - "$type": "Keyword", - "value": "Boolean" - }, - { - "$type": "Keyword", - "value": "Int" - }, - { - "$type": "Keyword", - "value": "Float" - }, - { - "$type": "Keyword", - "value": "DateTime" - }, - { - "$type": "Keyword", - "value": "JSON" - } - ] - } - }, - "definesHiddenTokens": false, - "entry": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "TerminalRule", - "hidden": true, - "name": "WS", - "terminal": { - "$type": "RegexToken", - "regex": "\\\\s+" - }, - "fragment": false - }, - { - "$type": "TerminalRule", - "name": "BOOLEAN", - "type": { - "$type": "ReturnType", - "name": "boolean" - }, - "terminal": { - "$type": "RegexToken", - "regex": "true|false" - }, - "fragment": false, - "hidden": false - }, - { - "$type": "TerminalRule", - "name": "ID", - "terminal": { - "$type": "RegexToken", - "regex": "[_a-zA-Z][\\\\w_]*" - }, - "fragment": false, - "hidden": false - }, - { - "$type": "TerminalRule", - "name": "STRING", - "terminal": { - "$type": "RegexToken", - "regex": "\\"[^\\"]*\\"|'[^']*'" - }, - "fragment": false, - "hidden": false - }, - { - "$type": "TerminalRule", - "name": "INT", - "type": { - "$type": "ReturnType", - "name": "number" - }, - "terminal": { - "$type": "RegexToken", - "regex": "[0-9]+" - }, - "fragment": false, - "hidden": false - }, - { - "$type": "TerminalRule", - "hidden": true, - "name": "ML_COMMENT", - "terminal": { - "$type": "RegexToken", - "regex": "\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\/" - }, - "fragment": false - }, - { - "$type": "TerminalRule", - "hidden": true, - "name": "SL_COMMENT", - "terminal": { - "$type": "RegexToken", - "regex": "\\\\/\\\\/[^\\\\n\\\\r]*" - }, - "fragment": false - } - ], - "definesHiddenTokens": false, - "hiddenTokens": [], - "imports": [], - "interfaces": [], - "types": [], - "usedGrammars": [] -}`)); -exports.ZModelGrammar = ZModelGrammar; -//# sourceMappingURL=grammar.js.map \ No newline at end of file diff --git a/packages/schema/out/language-server/generated/grammar.js.map b/packages/schema/out/language-server/generated/grammar.js.map deleted file mode 100644 index cbf60d100..000000000 --- a/packages/schema/out/language-server/generated/grammar.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"grammar.js","sourceRoot":"","sources":["../../../src/language-server/generated/grammar.ts"],"names":[],"mappings":";AAAA;;;gFAGgF;;;AAEhF,qCAA+C;AAE/C,IAAI,mBAAwC,CAAC;AACtC,MAAM,aAAa,GAAG,GAAY,EAAE,CAAC,mBAAmB,IAAG,CAAC,mBAAmB,GAAG,IAAA,qBAAW,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAw3BnG,CAAC,CAAC,CAAC;AAx3BQ,QAAA,aAAa,iBAw3BrB"} \ No newline at end of file diff --git a/packages/schema/out/language-server/generated/module.js b/packages/schema/out/language-server/generated/module.js deleted file mode 100644 index 552e10fac..000000000 --- a/packages/schema/out/language-server/generated/module.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; -/****************************************************************************** - * This file was generated by langium-cli 0.4.0. - * DO NOT EDIT MANUALLY! - ******************************************************************************/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ZModelGeneratedModule = exports.ZModelGeneratedSharedModule = exports.ZModelLanguageMetaData = void 0; -const ast_1 = require("./ast"); -const grammar_1 = require("./grammar"); -exports.ZModelLanguageMetaData = { - languageId: 'zmodel', - fileExtensions: ['.zmodel'], - caseInsensitive: false -}; -exports.ZModelGeneratedSharedModule = { - AstReflection: () => new ast_1.ZModelAstReflection() -}; -exports.ZModelGeneratedModule = { - Grammar: () => (0, grammar_1.ZModelGrammar)(), - LanguageMetaData: () => exports.ZModelLanguageMetaData, - parser: {} -}; -//# sourceMappingURL=module.js.map \ No newline at end of file diff --git a/packages/schema/out/language-server/generated/module.js.map b/packages/schema/out/language-server/generated/module.js.map deleted file mode 100644 index 9ee12c7f2..000000000 --- a/packages/schema/out/language-server/generated/module.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"module.js","sourceRoot":"","sources":["../../../src/language-server/generated/module.ts"],"names":[],"mappings":";AAAA;;;gFAGgF;;;AAGhF,+BAA4C;AAC5C,uCAA0C;AAE7B,QAAA,sBAAsB,GAAqB;IACpD,UAAU,EAAE,QAAQ;IACpB,cAAc,EAAE,CAAC,SAAS,CAAC;IAC3B,eAAe,EAAE,KAAK;CACzB,CAAC;AAEW,QAAA,2BAA2B,GAAkE;IACtG,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,yBAAmB,EAAE;CACjD,CAAC;AAEW,QAAA,qBAAqB,GAAsD;IACpF,OAAO,EAAE,GAAG,EAAE,CAAC,IAAA,uBAAa,GAAE;IAC9B,gBAAgB,EAAE,GAAG,EAAE,CAAC,8BAAsB;IAC9C,MAAM,EAAE,EAAE;CACb,CAAC"} \ No newline at end of file diff --git a/packages/schema/out/language-server/main.js b/packages/schema/out/language-server/main.js deleted file mode 100644 index fd4549151..000000000 --- a/packages/schema/out/language-server/main.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const langium_1 = require("langium"); -const node_1 = require("vscode-languageserver/node"); -const zmodel_module_1 = require("./zmodel-module"); -// Create a connection to the client -const connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all); -// Inject the shared services and language-specific services -const { shared } = (0, zmodel_module_1.createZModelServices)({ connection }); -// Start the language server with the shared services -(0, langium_1.startLanguageServer)(shared); -//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/packages/schema/out/language-server/main.js.map b/packages/schema/out/language-server/main.js.map deleted file mode 100644 index 4a761c11b..000000000 --- a/packages/schema/out/language-server/main.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/language-server/main.ts"],"names":[],"mappings":";;AAAA,qCAA8C;AAC9C,qDAAgF;AAChF,mDAAuD;AAEvD,oCAAoC;AACpC,MAAM,UAAU,GAAG,IAAA,uBAAgB,EAAC,uBAAgB,CAAC,GAAG,CAAC,CAAC;AAE1D,4DAA4D;AAC5D,MAAM,EAAE,MAAM,EAAE,GAAG,IAAA,oCAAoB,EAAC,EAAE,UAAU,EAAE,CAAC,CAAC;AAExD,qDAAqD;AACrD,IAAA,6BAAmB,EAAC,MAAM,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/schema/out/language-server/zmodel-module.js b/packages/schema/out/language-server/zmodel-module.js deleted file mode 100644 index 7ccb1d0de..000000000 --- a/packages/schema/out/language-server/zmodel-module.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createZModelServices = exports.ZModelModule = void 0; -const langium_1 = require("langium"); -const module_1 = require("./generated/module"); -const zmodel_validator_1 = require("./zmodel-validator"); -/** - * Dependency injection module that overrides Langium default services and contributes the - * declared custom services. The Langium defaults can be partially specified to override only - * selected services, while the custom services must be fully specified. - */ -exports.ZModelModule = { - validation: { - ValidationRegistry: (services) => new zmodel_validator_1.ZModelValidationRegistry(services), - ZModelValidator: () => new zmodel_validator_1.ZModelValidator(), - }, -}; -/** - * Create the full set of services required by Langium. - * - * First inject the shared services by merging two modules: - * - Langium default shared services - * - Services generated by langium-cli - * - * Then inject the language-specific services by merging three modules: - * - Langium default language-specific services - * - Services generated by langium-cli - * - Services specified in this file - * - * @param context Optional module context with the LSP connection - * @returns An object wrapping the shared services and the language-specific services - */ -function createZModelServices(context) { - const shared = (0, langium_1.inject)((0, langium_1.createDefaultSharedModule)(context), module_1.ZModelGeneratedSharedModule); - const ZModel = (0, langium_1.inject)((0, langium_1.createDefaultModule)({ shared }), module_1.ZModelGeneratedModule, exports.ZModelModule); - shared.ServiceRegistry.register(ZModel); - return { shared, ZModel }; -} -exports.createZModelServices = createZModelServices; -//# sourceMappingURL=zmodel-module.js.map \ No newline at end of file diff --git a/packages/schema/out/language-server/zmodel-module.js.map b/packages/schema/out/language-server/zmodel-module.js.map deleted file mode 100644 index fbe1336c4..000000000 --- a/packages/schema/out/language-server/zmodel-module.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"zmodel-module.js","sourceRoot":"","sources":["../../src/language-server/zmodel-module.ts"],"names":[],"mappings":";;;AAAA,qCASiB;AACjB,+CAG4B;AAC5B,yDAA+E;AAiB/E;;;;GAIG;AACU,QAAA,YAAY,GAGrB;IACA,UAAU,EAAE;QACR,kBAAkB,EAAE,CAAC,QAAQ,EAAE,EAAE,CAC7B,IAAI,2CAAwB,CAAC,QAAQ,CAAC;QAC1C,eAAe,EAAE,GAAG,EAAE,CAAC,IAAI,kCAAe,EAAE;KAC/C;CACJ,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,SAAgB,oBAAoB,CAAC,OAAoC;IAIrE,MAAM,MAAM,GAAG,IAAA,gBAAM,EACjB,IAAA,mCAAyB,EAAC,OAAO,CAAC,EAClC,oCAA2B,CAC9B,CAAC;IACF,MAAM,MAAM,GAAG,IAAA,gBAAM,EACjB,IAAA,6BAAmB,EAAC,EAAE,MAAM,EAAE,CAAC,EAC/B,8BAAqB,EACrB,oBAAY,CACf,CAAC;IACF,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC9B,CAAC;AAfD,oDAeC"} \ No newline at end of file diff --git a/packages/schema/out/language-server/zmodel-validator.js b/packages/schema/out/language-server/zmodel-validator.js deleted file mode 100644 index a474a1e0a..000000000 --- a/packages/schema/out/language-server/zmodel-validator.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ZModelValidator = exports.ZModelValidationRegistry = void 0; -const langium_1 = require("langium"); -/** - * Registry for validation checks. - */ -class ZModelValidationRegistry extends langium_1.ValidationRegistry { - constructor(services) { - super(services); - const validator = services.validation.ZModelValidator; - const checks = { - // Person: validator.checkPersonStartsWithCapital - }; - this.register(checks, validator); - } -} -exports.ZModelValidationRegistry = ZModelValidationRegistry; -/** - * Implementation of custom validations. - */ -class ZModelValidator { -} -exports.ZModelValidator = ZModelValidator; -//# sourceMappingURL=zmodel-validator.js.map \ No newline at end of file diff --git a/packages/schema/out/language-server/zmodel-validator.js.map b/packages/schema/out/language-server/zmodel-validator.js.map deleted file mode 100644 index 8554d5963..000000000 --- a/packages/schema/out/language-server/zmodel-validator.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"zmodel-validator.js","sourceRoot":"","sources":["../../src/language-server/zmodel-validator.ts"],"names":[],"mappings":";;;AAAA,qCAIiB;AAIjB;;GAEG;AACH,MAAa,wBAAyB,SAAQ,4BAAkB;IAC5D,YAAY,QAAwB;QAChC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChB,MAAM,SAAS,GAAG,QAAQ,CAAC,UAAU,CAAC,eAAe,CAAC;QACtD,MAAM,MAAM,GAAoC;QAC5C,iDAAiD;SACpD,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACrC,CAAC;CACJ;AATD,4DASC;AAED;;GAEG;AACH,MAAa,eAAe;CAS3B;AATD,0CASC"} \ No newline at end of file diff --git a/packages/schema/package.json b/packages/schema/package.json index 48f4facc7..bc974cbfe 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -65,7 +65,7 @@ }, "devDependencies": { "@types/jest": "^29.0.3", - "@types/node": "^14.17.3", + "@types/node": "^14.18.29", "@types/uuid": "^8.3.4", "@types/vscode": "^1.56.0", "@typescript-eslint/eslint-plugin": "^4.14.1", diff --git a/packages/schema/src/language-server/generated/ast.ts b/packages/schema/src/language-server/generated/ast.ts index 800ccd7e7..65c2fac6f 100644 --- a/packages/schema/src/language-server/generated/ast.ts +++ b/packages/schema/src/language-server/generated/ast.ts @@ -7,26 +7,69 @@ /* eslint-disable @typescript-eslint/no-empty-interface */ import { AstNode, AstReflection, Reference, isAstNode, TypeMetaData } from 'langium'; -export type Declaration = DataModel | Enum | Fragment; +export type Expression = ArrayExpr | BinaryExpr | EnumMemberExpr | InvocationExpr | LiteralExpr | ReferenceExpr | UnaryExpr; -export const Declaration = 'Declaration'; +export const Expression = 'Expression'; -export function isDeclaration(item: unknown): item is Declaration { - return reflection.isInstance(item, Declaration); +export function isExpression(item: unknown): item is Expression { + return reflection.isInstance(item, Expression); } -export type SimpleExpr = LiteralExpr | SimpleInvocationExpr; +export type ReferenceTarget = DataModelField | Function | FunctionParam; -export const SimpleExpr = 'SimpleExpr'; +export const ReferenceTarget = 'ReferenceTarget'; -export function isSimpleExpr(item: unknown): item is SimpleExpr { - return reflection.isInstance(item, SimpleExpr); +export function isReferenceTarget(item: unknown): item is ReferenceTarget { + return reflection.isInstance(item, ReferenceTarget); +} + +export type TypeDeclaration = DataModel | Enum; + +export const TypeDeclaration = 'TypeDeclaration'; + +export function isTypeDeclaration(item: unknown): item is TypeDeclaration { + return reflection.isInstance(item, TypeDeclaration); +} + +export interface ArrayExpr extends AstNode { + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + items: Array +} + +export const ArrayExpr = 'ArrayExpr'; + +export function isArrayExpr(item: unknown): item is ArrayExpr { + return reflection.isInstance(item, ArrayExpr); +} + +export interface Attribute extends AstNode { + readonly $container: Model; + name: string +} + +export const Attribute = 'Attribute'; + +export function isAttribute(item: unknown): item is Attribute { + return reflection.isInstance(item, Attribute); +} + +export interface BinaryExpr extends AstNode { + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + left: Expression + operator: '!=' | '&&' | '*' | '+' | '-' | '/' | '<' | '<=' | '==' | '>' | '>=' | '||' + right: Expression +} + +export const BinaryExpr = 'BinaryExpr'; + +export function isBinaryExpr(item: unknown): item is BinaryExpr { + return reflection.isInstance(item, BinaryExpr); } export interface DataModel extends AstNode { readonly $container: Model; + attributes: Array fields: Array - fragments: Array name: string } @@ -36,8 +79,20 @@ export function isDataModel(item: unknown): item is DataModel { return reflection.isInstance(item, DataModel); } +export interface DataModelAttribute extends AstNode { + readonly $container: DataModel; + args: Array + decl: Reference +} + +export const DataModelAttribute = 'DataModelAttribute'; + +export function isDataModelAttribute(item: unknown): item is DataModelAttribute { + return reflection.isInstance(item, DataModelAttribute); +} + export interface DataModelField extends AstNode { - readonly $container: DataModel | Fragment; + readonly $container: DataModel; attributes: Array fieldType: DataModelFieldType name: string @@ -51,8 +106,8 @@ export function isDataModelField(item: unknown): item is DataModelField { export interface DataModelFieldAttribute extends AstNode { readonly $container: DataModelField; - args: Array - name: string + args: Array + decl: Reference } export const DataModelFieldAttribute = 'DataModelFieldAttribute'; @@ -65,7 +120,7 @@ export interface DataModelFieldType extends AstNode { readonly $container: DataModelField; array: boolean optional: boolean - reference?: Reference + reference?: Reference type?: 'Boolean' | 'DateTime' | 'Float' | 'Int' | 'JSON' | 'String' } @@ -78,6 +133,7 @@ export function isDataModelFieldType(item: unknown): item is DataModelFieldType export interface DataSource extends AstNode { readonly $container: Model; fields: Array + name: string } export const DataSource = 'DataSource'; @@ -89,7 +145,7 @@ export function isDataSource(item: unknown): item is DataSource { export interface DataSourceField extends AstNode { readonly $container: DataSource; name: string - value: SimpleExpr + value: Expression } export const DataSourceField = 'DataSourceField'; @@ -121,31 +177,56 @@ export function isEnumField(item: unknown): item is EnumField { return reflection.isInstance(item, EnumField); } -export interface Fragment extends AstNode { +export interface EnumMemberExpr extends AstNode { + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + decl: Reference + member: string +} + +export const EnumMemberExpr = 'EnumMemberExpr'; + +export function isEnumMemberExpr(item: unknown): item is EnumMemberExpr { + return reflection.isInstance(item, EnumMemberExpr); +} + +export interface Function extends AstNode { readonly $container: Model; - fields: Array + expression: Expression name: string + params: Array } -export const Fragment = 'Fragment'; +export const Function = 'Function'; -export function isFragment(item: unknown): item is Fragment { - return reflection.isInstance(item, Fragment); +export function isFunction(item: unknown): item is Function { + return reflection.isInstance(item, Function); } -export interface FragmentExpansion extends AstNode { - readonly $container: DataModel; - value: Reference +export interface FunctionParam extends AstNode { + readonly $container: Function; + name: string } -export const FragmentExpansion = 'FragmentExpansion'; +export const FunctionParam = 'FunctionParam'; -export function isFragmentExpansion(item: unknown): item is FragmentExpansion { - return reflection.isInstance(item, FragmentExpansion); +export function isFunctionParam(item: unknown): item is FunctionParam { + return reflection.isInstance(item, FunctionParam); +} + +export interface InvocationExpr extends AstNode { + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + args: Array + function: string +} + +export const InvocationExpr = 'InvocationExpr'; + +export function isInvocationExpr(item: unknown): item is InvocationExpr { + return reflection.isInstance(item, InvocationExpr); } export interface LiteralExpr extends AstNode { - readonly $container: DataModelFieldAttribute | DataSourceField | SimpleInvocationExpr; + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; value: boolean | number | string } @@ -156,9 +237,10 @@ export function isLiteralExpr(item: unknown): item is LiteralExpr { } export interface Model extends AstNode { + attributes: Array datasources: Array enums: Array - fragments: Array + functions: Array models: Array } @@ -168,26 +250,37 @@ export function isModel(item: unknown): item is Model { return reflection.isInstance(item, Model); } -export interface SimpleInvocationExpr extends AstNode { - readonly $container: DataModelFieldAttribute | DataSourceField | SimpleInvocationExpr; - args: Array - function: string +export interface ReferenceExpr extends AstNode { + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + target: Reference +} + +export const ReferenceExpr = 'ReferenceExpr'; + +export function isReferenceExpr(item: unknown): item is ReferenceExpr { + return reflection.isInstance(item, ReferenceExpr); } -export const SimpleInvocationExpr = 'SimpleInvocationExpr'; +export interface UnaryExpr extends AstNode { + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + arg: Expression + operator: '!' | '+' | '-' +} -export function isSimpleInvocationExpr(item: unknown): item is SimpleInvocationExpr { - return reflection.isInstance(item, SimpleInvocationExpr); +export const UnaryExpr = 'UnaryExpr'; + +export function isUnaryExpr(item: unknown): item is UnaryExpr { + return reflection.isInstance(item, UnaryExpr); } -export type ZModelAstType = 'DataModel' | 'DataModelField' | 'DataModelFieldAttribute' | 'DataModelFieldType' | 'DataSource' | 'DataSourceField' | 'Declaration' | 'Enum' | 'EnumField' | 'Fragment' | 'FragmentExpansion' | 'LiteralExpr' | 'Model' | 'SimpleExpr' | 'SimpleInvocationExpr'; +export type ZModelAstType = 'ArrayExpr' | 'Attribute' | 'BinaryExpr' | 'DataModel' | 'DataModelAttribute' | 'DataModelField' | 'DataModelFieldAttribute' | 'DataModelFieldType' | 'DataSource' | 'DataSourceField' | 'Enum' | 'EnumField' | 'EnumMemberExpr' | 'Expression' | 'Function' | 'FunctionParam' | 'InvocationExpr' | 'LiteralExpr' | 'Model' | 'ReferenceExpr' | 'ReferenceTarget' | 'TypeDeclaration' | 'UnaryExpr'; -export type ZModelAstReference = 'DataModelFieldType:reference' | 'FragmentExpansion:value'; +export type ZModelAstReference = 'DataModelAttribute:decl' | 'DataModelFieldAttribute:decl' | 'DataModelFieldType:reference' | 'EnumMemberExpr:decl' | 'ReferenceExpr:target'; export class ZModelAstReflection implements AstReflection { getAllTypes(): string[] { - return ['DataModel', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Declaration', 'Enum', 'EnumField', 'Fragment', 'FragmentExpansion', 'LiteralExpr', 'Model', 'SimpleExpr', 'SimpleInvocationExpr']; + return ['ArrayExpr', 'Attribute', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'EnumMemberExpr', 'Expression', 'Function', 'FunctionParam', 'InvocationExpr', 'LiteralExpr', 'Model', 'ReferenceExpr', 'ReferenceTarget', 'TypeDeclaration', 'UnaryExpr']; } isInstance(node: unknown, type: string): boolean { @@ -199,14 +292,23 @@ export class ZModelAstReflection implements AstReflection { return true; } switch (subtype) { + case ArrayExpr: + case BinaryExpr: + case EnumMemberExpr: + case InvocationExpr: + case LiteralExpr: + case ReferenceExpr: + case UnaryExpr: { + return this.isSubtype(Expression, supertype); + } case DataModel: - case Enum: - case Fragment: { - return this.isSubtype(Declaration, supertype); + case Enum: { + return this.isSubtype(TypeDeclaration, supertype); } - case LiteralExpr: - case SimpleInvocationExpr: { - return this.isSubtype(SimpleExpr, supertype); + case DataModelField: + case Function: + case FunctionParam: { + return this.isSubtype(ReferenceTarget, supertype); } default: { return false; @@ -216,11 +318,20 @@ export class ZModelAstReflection implements AstReflection { getReferenceType(referenceId: ZModelAstReference): string { switch (referenceId) { + case 'DataModelAttribute:decl': { + return Attribute; + } + case 'DataModelFieldAttribute:decl': { + return Attribute; + } case 'DataModelFieldType:reference': { - return Declaration; + return TypeDeclaration; + } + case 'EnumMemberExpr:decl': { + return Enum; } - case 'FragmentExpansion:value': { - return Fragment; + case 'ReferenceExpr:target': { + return ReferenceTarget; } default: { throw new Error(`${referenceId} is not a valid reference id.`); @@ -230,12 +341,28 @@ export class ZModelAstReflection implements AstReflection { getTypeMetaData(type: string): TypeMetaData { switch (type) { + case 'ArrayExpr': { + return { + name: 'ArrayExpr', + mandatory: [ + { name: 'items', type: 'array' } + ] + }; + } case 'DataModel': { return { name: 'DataModel', mandatory: [ - { name: 'fields', type: 'array' }, - { name: 'fragments', type: 'array' } + { name: 'attributes', type: 'array' }, + { name: 'fields', type: 'array' } + ] + }; + } + case 'DataModelAttribute': { + return { + name: 'DataModelAttribute', + mandatory: [ + { name: 'args', type: 'array' } ] }; } @@ -280,30 +407,31 @@ export class ZModelAstReflection implements AstReflection { ] }; } - case 'Fragment': { + case 'Function': { return { - name: 'Fragment', + name: 'Function', mandatory: [ - { name: 'fields', type: 'array' } + { name: 'params', type: 'array' } ] }; } - case 'Model': { + case 'InvocationExpr': { return { - name: 'Model', + name: 'InvocationExpr', mandatory: [ - { name: 'datasources', type: 'array' }, - { name: 'enums', type: 'array' }, - { name: 'fragments', type: 'array' }, - { name: 'models', type: 'array' } + { name: 'args', type: 'array' } ] }; } - case 'SimpleInvocationExpr': { + case 'Model': { return { - name: 'SimpleInvocationExpr', + name: 'Model', mandatory: [ - { name: 'args', type: 'array' } + { name: 'attributes', type: 'array' }, + { name: 'datasources', type: 'array' }, + { name: 'enums', type: 'array' }, + { name: 'functions', type: 'array' }, + { name: 'models', type: 'array' } ] }; } diff --git a/packages/schema/src/language-server/generated/grammar.ts b/packages/schema/src/language-server/generated/grammar.ts index ac84c1365..9ee0a5760 100644 --- a/packages/schema/src/language-server/generated/grammar.ts +++ b/packages/schema/src/language-server/generated/grammar.ts @@ -56,12 +56,24 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "Assignment", - "feature": "fragments", + "feature": "functions", "operator": "+=", "terminal": { "$type": "RuleCall", "rule": { - "$refText": "Fragment" + "$refText": "Function" + }, + "arguments": [] + } + }, + { + "$type": "Assignment", + "feature": "attributes", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "Attribute" }, "arguments": [] } @@ -85,6 +97,18 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "$type": "Keyword", "value": "datasource" }, + { + "$type": "Assignment", + "feature": "name", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] + } + }, { "$type": "Keyword", "value": "{" @@ -144,7 +168,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "terminal": { "$type": "RuleCall", "rule": { - "$refText": "SimpleExpr" + "$refText": "Expression" }, "arguments": [] } @@ -160,25 +184,13 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "SimpleExpr", + "name": "Expression", "alternatives": { - "$type": "Alternatives", - "elements": [ - { - "$type": "RuleCall", - "rule": { - "$refText": "SimpleInvocationExpr" - }, - "arguments": [] - }, - { - "$type": "RuleCall", - "rule": { - "$refText": "LiteralExpr" - }, - "arguments": [] - } - ] + "$type": "RuleCall", + "rule": { + "$refText": "LogicalExpr" + }, + "arguments": [] }, "definesHiddenTokens": false, "entry": false, @@ -189,63 +201,98 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "SimpleInvocationExpr", + "name": "LiteralExpr", "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "function", - "operator": "=", - "terminal": { + "$type": "Assignment", + "feature": "value", + "operator": "=", + "terminal": { + "$type": "Alternatives", + "elements": [ + { "$type": "RuleCall", "rule": { - "$refText": "ID" + "$refText": "BOOLEAN" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "NUMBER" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "STRING" }, "arguments": [] } - }, + ] + } + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "ArrayExpr", + "alternatives": { + "$type": "Group", + "elements": [ { "$type": "Keyword", - "value": "(" + "value": "[" }, { "$type": "Group", "elements": [ { "$type": "Assignment", - "feature": "args", + "feature": "items", "operator": "+=", "terminal": { "$type": "RuleCall", "rule": { - "$refText": "SimpleExpr" + "$refText": "Expression" }, "arguments": [] } }, { - "$type": "Keyword", - "value": "," + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "," + }, + { + "$type": "Assignment", + "feature": "items", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "Expression" + }, + "arguments": [] + } + } + ], + "cardinality": "*" } ], - "cardinality": "*" - }, - { - "$type": "Assignment", - "feature": "args", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "SimpleExpr" - }, - "arguments": [] - } + "cardinality": "?" }, { "$type": "Keyword", - "value": ")" + "value": "]" } ] }, @@ -258,36 +305,24 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "LiteralExpr", + "name": "ReferenceExpr", "alternatives": { "$type": "Assignment", - "feature": "value", + "feature": "target", "operator": "=", "terminal": { - "$type": "Alternatives", - "elements": [ - { - "$type": "RuleCall", - "rule": { - "$refText": "BOOLEAN" - }, - "arguments": [] - }, - { - "$type": "RuleCall", - "rule": { - "$refText": "INT" - }, - "arguments": [] + "$type": "CrossReference", + "type": { + "$refText": "ReferenceTarget" + }, + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ID" }, - { - "$type": "RuleCall", - "rule": { - "$refText": "STRING" - }, - "arguments": [] - } - ] + "arguments": [] + }, + "deprecatedSyntax": false } }, "definesHiddenTokens": false, @@ -299,17 +334,13 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "Enum", + "name": "InvocationExpr", "alternatives": { "$type": "Group", "elements": [ - { - "$type": "Keyword", - "value": "enum" - }, { "$type": "Assignment", - "feature": "name", + "feature": "function", "operator": "=", "terminal": { "$type": "RuleCall", @@ -321,24 +352,19 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "Keyword", - "value": "{" + "value": "(" }, { - "$type": "Assignment", - "feature": "fields", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "EnumField" - }, - "arguments": [] + "$type": "RuleCall", + "rule": { + "$refText": "ArgumentList" }, - "cardinality": "+" + "arguments": [], + "cardinality": "?" }, { "$type": "Keyword", - "value": "}" + "value": ")" } ] }, @@ -351,81 +377,86 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "EnumField", - "alternatives": { - "$type": "Assignment", - "feature": "value", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "DataModel", + "name": "EnumMemberExpr", "alternatives": { "$type": "Group", "elements": [ - { - "$type": "Keyword", - "value": "model" - }, { "$type": "Assignment", - "feature": "name", + "feature": "decl", "operator": "=", "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" + "$type": "CrossReference", + "type": { + "$refText": "Enum" }, - "arguments": [] + "deprecatedSyntax": false } }, { "$type": "Keyword", - "value": "{" + "value": "." }, { "$type": "Assignment", - "feature": "fragments", - "operator": "+=", + "feature": "member", + "operator": "=", "terminal": { "$type": "RuleCall", "rule": { - "$refText": "FragmentExpansion" + "$refText": "ID" }, "arguments": [] - }, - "cardinality": "*" + } + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "UnaryExpr", + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "Assignment", + "feature": "operator", + "operator": "=", + "terminal": { + "$type": "Alternatives", + "elements": [ + { + "$type": "Keyword", + "value": "+" + }, + { + "$type": "Keyword", + "value": "-" + }, + { + "$type": "Keyword", + "value": "!" + } + ] + } }, { "$type": "Assignment", - "feature": "fields", - "operator": "+=", + "feature": "arg", + "operator": "=", "terminal": { "$type": "RuleCall", "rule": { - "$refText": "DataModelField" + "$refText": "Expression" }, "arguments": [] - }, - "cardinality": "+" - }, - { - "$type": "Keyword", - "value": "}" + } } ] }, @@ -438,32 +469,556 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "FragmentExpansion", + "name": "MultDivExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, "alternatives": { "$type": "Group", "elements": [ { - "$type": "Keyword", - "value": "..." + "$type": "RuleCall", + "rule": { + "$refText": "PrimaryExpr" + }, + "arguments": [] }, { - "$type": "Assignment", - "feature": "value", - "operator": "=", - "terminal": { - "$type": "CrossReference", - "type": { - "$refText": "Fragment" + "$type": "Group", + "elements": [ + { + "$type": "Action", + "inferredType": { + "$type": "InferredType", + "name": "BinaryExpr" + }, + "feature": "left", + "operator": "=" + }, + { + "$type": "Assignment", + "feature": "operator", + "operator": "=", + "terminal": { + "$type": "Alternatives", + "elements": [ + { + "$type": "Keyword", + "value": "*" + }, + { + "$type": "Keyword", + "value": "/" + } + ] + } + }, + { + "$type": "Assignment", + "feature": "right", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "PrimaryExpr" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "AddSubExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "MultDivExpr" + }, + "arguments": [] + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Action", + "inferredType": { + "$type": "InferredType", + "name": "BinaryExpr" + }, + "feature": "left", + "operator": "=" + }, + { + "$type": "Assignment", + "feature": "operator", + "operator": "=", + "terminal": { + "$type": "Alternatives", + "elements": [ + { + "$type": "Keyword", + "value": "+" + }, + { + "$type": "Keyword", + "value": "-" + } + ] + } + }, + { + "$type": "Assignment", + "feature": "right", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "MultDivExpr" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "ComparisonExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "AddSubExpr" + }, + "arguments": [] + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Action", + "inferredType": { + "$type": "InferredType", + "name": "BinaryExpr" + }, + "feature": "left", + "operator": "=" + }, + { + "$type": "Assignment", + "feature": "operator", + "operator": "=", + "terminal": { + "$type": "Alternatives", + "elements": [ + { + "$type": "Keyword", + "value": ">" + }, + { + "$type": "Keyword", + "value": "<" + }, + { + "$type": "Keyword", + "value": ">=" + }, + { + "$type": "Keyword", + "value": "<=" + } + ] + } + }, + { + "$type": "Assignment", + "feature": "right", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "AddSubExpr" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "EqualityExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "ComparisonExpr" + }, + "arguments": [] + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Action", + "inferredType": { + "$type": "InferredType", + "name": "BinaryExpr" + }, + "feature": "left", + "operator": "=" + }, + { + "$type": "Assignment", + "feature": "operator", + "operator": "=", + "terminal": { + "$type": "Alternatives", + "elements": [ + { + "$type": "Keyword", + "value": "==" + }, + { + "$type": "Keyword", + "value": "!=" + } + ] + } + }, + { + "$type": "Assignment", + "feature": "right", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ComparisonExpr" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "LogicalExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "EqualityExpr" + }, + "arguments": [] + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Action", + "inferredType": { + "$type": "InferredType", + "name": "BinaryExpr" + }, + "feature": "left", + "operator": "=" + }, + { + "$type": "Assignment", + "feature": "operator", + "operator": "=", + "terminal": { + "$type": "Alternatives", + "elements": [ + { + "$type": "Keyword", + "value": "&&" + }, + { + "$type": "Keyword", + "value": "||" + } + ] + } + }, + { + "$type": "Assignment", + "feature": "right", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "EqualityExpr" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "PrimaryExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, + "alternatives": { + "$type": "Alternatives", + "elements": [ + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "(" }, - "terminal": { + { "$type": "RuleCall", "rule": { - "$refText": "ID" + "$refText": "Expression" }, "arguments": [] }, - "deprecatedSyntax": false + { + "$type": "Keyword", + "value": ")" + } + ] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "LiteralExpr" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "InvocationExpr" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "ArrayExpr" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "EnumMemberExpr" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "ReferenceExpr" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "UnaryExpr" + }, + "arguments": [] + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "ArgumentList", + "fragment": true, + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "Assignment", + "feature": "args", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "Expression" + }, + "arguments": [] + } + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "," + }, + { + "$type": "Assignment", + "feature": "args", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "Expression" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "DataModel", + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "model" + }, + { + "$type": "Assignment", + "feature": "name", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] } + }, + { + "$type": "Keyword", + "value": "{" + }, + { + "$type": "Alternatives", + "elements": [ + { + "$type": "Assignment", + "feature": "fields", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "DataModelField" + }, + "arguments": [] + } + }, + { + "$type": "Assignment", + "feature": "attributes", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "DataModelAttribute" + }, + "arguments": [] + } + } + ], + "cardinality": "+" + }, + { + "$type": "Keyword", + "value": "}" } ] }, @@ -549,7 +1104,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "terminal": { "$type": "CrossReference", "type": { - "$refText": "Declaration" + "$refText": "TypeDeclaration" }, "terminal": { "$type": "RuleCall", @@ -594,30 +1149,46 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "Declaration", + "name": "Enum", "alternatives": { - "$type": "Alternatives", + "$type": "Group", "elements": [ { - "$type": "RuleCall", - "rule": { - "$refText": "DataModel" - }, - "arguments": [] + "$type": "Keyword", + "value": "enum" }, { - "$type": "RuleCall", - "rule": { - "$refText": "Fragment" - }, - "arguments": [] + "$type": "Assignment", + "feature": "name", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] + } }, { - "$type": "RuleCall", - "rule": { - "$refText": "Enum" + "$type": "Keyword", + "value": "{" + }, + { + "$type": "Assignment", + "feature": "fields", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "EnumField" + }, + "arguments": [] }, - "arguments": [] + "cardinality": "+" + }, + { + "$type": "Keyword", + "value": "}" } ] }, @@ -630,13 +1201,35 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "DataModelFieldAttribute", + "name": "EnumField", + "alternatives": { + "$type": "Assignment", + "feature": "value", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] + } + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "Function", "alternatives": { "$type": "Group", "elements": [ { "$type": "Keyword", - "value": "@" + "value": "function" }, { "$type": "Assignment", @@ -650,59 +1243,73 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "arguments": [] } }, + { + "$type": "Keyword", + "value": "(" + }, { "$type": "Group", "elements": [ { - "$type": "Keyword", - "value": "(" + "$type": "Assignment", + "feature": "params", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "FunctionParam" + }, + "arguments": [] + } }, { "$type": "Group", "elements": [ { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "args", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "LiteralExpr" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": "," - } - ], - "cardinality": "*" + "$type": "Keyword", + "value": "," }, { "$type": "Assignment", - "feature": "args", + "feature": "params", "operator": "+=", "terminal": { "$type": "RuleCall", "rule": { - "$refText": "LiteralExpr" + "$refText": "FunctionParam" }, "arguments": [] } } ], - "cardinality": "?" - }, - { - "$type": "Keyword", - "value": ")" + "cardinality": "*" } ], "cardinality": "?" + }, + { + "$type": "Keyword", + "value": ")" + }, + { + "$type": "Keyword", + "value": "{" + }, + { + "$type": "Assignment", + "feature": "expression", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "Expression" + }, + "arguments": [] + } + }, + { + "$type": "Keyword", + "value": "}" } ] }, @@ -715,13 +1322,35 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "Fragment", + "name": "FunctionParam", + "alternatives": { + "$type": "Assignment", + "feature": "name", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] + } + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "Attribute", "alternatives": { "$type": "Group", "elements": [ { "$type": "Keyword", - "value": "fragment" + "value": "attribute" }, { "$type": "Assignment", @@ -739,22 +1368,115 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "$type": "Keyword", "value": "{" }, + { + "$type": "Keyword", + "value": "}" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "DataModelFieldAttribute", + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "@" + }, { "$type": "Assignment", - "feature": "fields", - "operator": "+=", + "feature": "decl", + "operator": "=", "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataModelField" + "$type": "CrossReference", + "type": { + "$refText": "Attribute" }, - "arguments": [] - }, - "cardinality": "+" + "deprecatedSyntax": false + } }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "(" + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "ArgumentList" + }, + "arguments": [], + "cardinality": "?" + }, + { + "$type": "Keyword", + "value": ")" + } + ], + "cardinality": "?" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "DataModelAttribute", + "alternatives": { + "$type": "Group", + "elements": [ { "$type": "Keyword", - "value": "}" + "value": "@@" + }, + { + "$type": "Assignment", + "feature": "decl", + "operator": "=", + "terminal": { + "$type": "CrossReference", + "type": { + "$refText": "Attribute" + }, + "deprecatedSyntax": false + } + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "(" + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "ArgumentList" + }, + "arguments": [], + "cardinality": "?" + }, + { + "$type": "Keyword", + "value": ")" + } + ], + "cardinality": "?" } ] }, @@ -855,14 +1577,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "TerminalRule", - "name": "INT", + "name": "NUMBER", "type": { "$type": "ReturnType", "name": "number" }, "terminal": { "$type": "RegexToken", - "regex": "[0-9]+" + "regex": "[+-]?[0-9]+(\\\\.[0-9]+)?" }, "fragment": false, "hidden": false @@ -888,10 +1610,63 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "fragment": false } ], + "types": [ + { + "$type": "Type", + "typeAlternatives": [ + { + "$type": "AtomType", + "refType": { + "$refText": "FunctionParam" + }, + "isArray": false, + "isRef": false + }, + { + "$type": "AtomType", + "refType": { + "$refText": "Function" + }, + "isArray": false, + "isRef": false + }, + { + "$type": "AtomType", + "refType": { + "$refText": "DataModelField" + }, + "isArray": false, + "isRef": false + } + ], + "name": "ReferenceTarget" + }, + { + "$type": "Type", + "typeAlternatives": [ + { + "$type": "AtomType", + "refType": { + "$refText": "DataModel" + }, + "isArray": false, + "isRef": false + }, + { + "$type": "AtomType", + "refType": { + "$refText": "Enum" + }, + "isArray": false, + "isRef": false + } + ], + "name": "TypeDeclaration" + } + ], "definesHiddenTokens": false, "hiddenTokens": [], "imports": [], "interfaces": [], - "types": [], "usedGrammars": [] }`)); diff --git a/packages/schema/src/language-server/stdlib.zmodel b/packages/schema/src/language-server/stdlib.zmodel new file mode 100644 index 000000000..ab5704e10 --- /dev/null +++ b/packages/schema/src/language-server/stdlib.zmodel @@ -0,0 +1,9 @@ +function auth() {} +function some(collection, predicate) {} + +attribute id() {} +attribute default() {} +attribute unique() {} +attribute cascade() {} +attribute allow() {} +attribute deny() {} diff --git a/packages/schema/src/language-server/zmodel.langium b/packages/schema/src/language-server/zmodel.langium index dc40d1a7d..28d2a99ed 100644 --- a/packages/schema/src/language-server/zmodel.langium +++ b/packages/schema/src/language-server/zmodel.langium @@ -1,56 +1,133 @@ grammar ZModel entry Model: - (datasources+=DataSource | models+=DataModel | enums+=Enum | fragments+=Fragment )*; + ( + datasources+=DataSource | + models+=DataModel | + enums+=Enum | + functions+=Function | + attributes+=Attribute + )*; // datasource DataSource: - 'datasource' '{' (fields+=DataSourceField)+ '}'; + 'datasource' name=ID '{' (fields+=DataSourceField)+ '}'; DataSourceField: - (name=ID '=' value=SimpleExpr); + (name=ID '=' value=Expression); -SimpleExpr: - SimpleInvocationExpr | LiteralExpr; - -SimpleInvocationExpr: - function=ID '(' (args+=SimpleExpr ',')* args+=SimpleExpr ')'; +// expression +Expression: + LogicalExpr; LiteralExpr: - value=(BOOLEAN | INT | STRING); - -// enum -Enum: - 'enum' name=ID '{' (fields+=EnumField)+ '}'; - -EnumField: - value=ID; + value=(BOOLEAN | NUMBER | STRING); + +ArrayExpr: + '[' (items+=Expression (',' items+=Expression)*)? ']'; + +type ReferenceTarget = FunctionParam | Function | DataModelField; + +ReferenceExpr: + target=[ReferenceTarget:ID]; + +InvocationExpr: + function=ID '(' ArgumentList? ')'; + +EnumMemberExpr: + decl=[Enum] '.' member=ID; + +UnaryExpr: + operator=('+'|'-'|'!') arg=Expression; + +MultDivExpr infers Expression: + PrimaryExpr ( + {infer BinaryExpr.left=current} + operator=('*'|'/') + right=PrimaryExpr + )*; + +AddSubExpr infers Expression: + MultDivExpr ( + {infer BinaryExpr.left=current} + operator=('+'|'-') + right=MultDivExpr + )*; + +ComparisonExpr infers Expression: + AddSubExpr ( + {infer BinaryExpr.left=current} + operator=('>'|'<'|'>='|'<=') + right=AddSubExpr + )*; + +EqualityExpr infers Expression: + ComparisonExpr ( + {infer BinaryExpr.left=current} + operator=('=='|'!=') + right=ComparisonExpr + )*; + +LogicalExpr infers Expression: + EqualityExpr ( + {infer BinaryExpr.left=current} + operator=('&&'|'||') + right=EqualityExpr + )*; + +PrimaryExpr infers Expression: + '(' Expression ')' | + LiteralExpr | + InvocationExpr | + ArrayExpr| + EnumMemberExpr | + ReferenceExpr | + UnaryExpr; + +fragment ArgumentList: + args+=Expression (',' args+=Expression)*; // model DataModel: - 'model' name=ID '{' (fragments+=FragmentExpansion)* (fields+=DataModelField)+ '}'; - -FragmentExpansion: - '...' value=[Fragment:ID]; + 'model' name=ID '{' ( + fields+=DataModelField + | attributes+=DataModelAttribute + )+ + '}'; DataModelField: name=ID fieldType=DataModelFieldType (attributes+=DataModelFieldAttribute)*; DataModelFieldType: (FieldType - | reference=[Declaration:ID] + | reference=[TypeDeclaration:ID] ) (array?='[]')? (optional?='?')?; -Declaration: - DataModel | Fragment | Enum; +// enum +Enum: + 'enum' name=ID '{' (fields+=EnumField)+ '}'; + +EnumField: + value=ID; + +// function +Function: + 'function' name=ID '(' (params+=FunctionParam (',' params+=FunctionParam)*)? ')' '{' expression=Expression '}'; + +FunctionParam: + name=ID; + +// attribute +Attribute: + 'attribute' name=ID '{' '}'; + +type TypeDeclaration = DataModel | Enum; DataModelFieldAttribute: - '@' name=ID - ('(' ((args+=LiteralExpr ',')* args+=LiteralExpr)? ')')?; + '@' decl=[Attribute] ('(' ArgumentList? ')')?; -// fragment -Fragment: - 'fragment' name=ID '{' (fields+=DataModelField)+ '}'; +DataModelAttribute: + '@@' decl=[Attribute] ('(' ArgumentList? ')')?; fragment FieldType: type=('String'|'Boolean'|'Int'|'Float'|'DateTime'|'JSON'); @@ -59,6 +136,6 @@ hidden terminal WS: /\s+/; terminal BOOLEAN returns boolean: /true|false/; terminal ID: /[_a-zA-Z][\w_]*/; terminal STRING: /"[^"]*"|'[^']*'/; -terminal INT returns number: /[0-9]+/; +terminal NUMBER returns number: /[+-]?[0-9]+(\.[0-9]+)?/; hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//; hidden terminal SL_COMMENT: /\/\/[^\n\r]*/; diff --git a/packages/schema/syntaxes/zmodel.tmLanguage.json b/packages/schema/syntaxes/zmodel.tmLanguage.json index e21238bc3..df6feea5e 100644 --- a/packages/schema/syntaxes/zmodel.tmLanguage.json +++ b/packages/schema/syntaxes/zmodel.tmLanguage.json @@ -10,7 +10,7 @@ }, { "name": "keyword.control.zmodel", - "match": "\\b(Boolean|datasource|DateTime|enum|Float|fragment|Int|JSON|model|String)\\b" + "match": "\\b(attribute|Boolean|datasource|DateTime|enum|Float|function|Int|JSON|model|String)\\b" }, { "name": "string.quoted.double.zmodel", diff --git a/packages/schema/tests/basic.test.ts b/packages/schema/tests/basic.test.ts deleted file mode 100644 index 01fb32f69..000000000 --- a/packages/schema/tests/basic.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { parse } from './utils'; - -describe('Basic Tests', () => { - it('functions', async () => { - const content = ` - function userInSpace(user, space) { - exists(SpaceUser, $.space == space && $.user == user) - } - `; - await parse(content); - }); - - it('feature coverage', async () => { - const content = ` - datasource { - provider = 'postgresql' - url = env('DATABASE_URL') - } - - fragment CommonFields { - id String @id - createdBy User @createdBy - updatedBy User @updatedBy - createdAt DateTime @createdAt - updatedAt DateTime @updatedAt - } - - model Space - @deny('all', auth() == null) - @allow('create', true) - @allow('read', userInSpace(auth(), $this)) - @allow('update,delete', userIsSpaceAdmin(auth(), $this)) { - ...CommonFields - name String - slug String @unique - members SpaceUser[] @cascade - todoLists TodoList[] @cascade - } - - enum SpaceUserRole { - USER - ADMIN - } - - model SpaceUser - @deny('all', auth() == null) - @allow('create,update,delete', userIsSpaceAdmin(auth(), $this.space)) - @allow('read', userInSpace(auth(), $this.space)) { - ...CommonFields - space Space - user User - role SpaceUserRole - } - - model User - @deny('all', auth() == null) - @allow('create', true) - @allow('read', userInAnySpace(auth(), spaces)) - @allow('update,delete', auth() == $this) { - ...CommonFields - email String @unique - name String? - todoList TodoList[] - spaces SpaceUser[] @cascade - profile Profile? @cascade - } - - model Profile - @deny('all', auth() == null) - @allow('read', userInAnySpace(auth(), $this.user.spaces)) - @allow('create,update,delete', $this.user == auth()) { - ...CommonFields - user User @unique - avatar String? - } - - model TodoList - @deny('all', auth() == null) - @allow('read', $this.owner == auth() || (userInSpace(auth(), $this.space) && !$this.private)) - @allow('create,update,delete', $this.owner == auth() && userInSpace(auth(), $this.space)) { - ...CommonFields - space Space - owner User - title String - content String - private Boolean @default(true) - todos Todo[] @cascade - } - - model Todo - @deny('all', auth() == null) - @allow('all', $this.todoList.owner == auth() || (userInSpace(auth(), $this.todoList.space) && !$this.todoList.private)) { - ...CommonFields - owner User - todoList TodoList - title String - completedAt DateTime? - } - - function userInSpace(user, space) { - exists(SpaceUser, $.space == space && $.user == user) - } - - function userIsSpaceAdmin(user, space) { - exists(SpaceUser, $.space == space && $.user == user && $.role == ADMIN) - } - - function userInAnySpace(user, spaces) { - find(spaces, $.user == user) - } - `; - - const model = await parse(content); - - console.log('Dump AST:', model); - }); -}); diff --git a/packages/schema/tests/parser.test.ts b/packages/schema/tests/parser.test.ts new file mode 100644 index 000000000..3f7d33a8f --- /dev/null +++ b/packages/schema/tests/parser.test.ts @@ -0,0 +1,422 @@ +import { + ArrayExpr, + BinaryExpr, + EnumMemberExpr, + ReferenceExpr, + LiteralExpr, + UnaryExpr, + InvocationExpr, +} from '../src/language-server/generated/ast'; +import { parse } from './utils'; + +describe('Basic Tests', () => { + it('data source', async () => { + const content = ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + `; + const doc = await parse(content); + expect(doc.datasources).toHaveLength(1); + const ds = doc.datasources[0]; + + expect(ds.name).toBe('db'); + expect(ds.fields).toHaveLength(2); + + expect(ds.fields[0]).toEqual( + expect.objectContaining({ + name: 'provider', + value: expect.objectContaining({ value: 'postgresql' }), + }) + ); + expect(ds.fields[1]).toEqual( + expect.objectContaining({ + name: 'url', + value: expect.objectContaining({ + function: 'env', + args: expect.arrayContaining([ + expect.objectContaining({ + value: 'DATABASE_URL', + }), + ]), + }), + }) + ); + }); + + it('enum', async () => { + const content = ` + enum UserRole { + USER + ADMIN + } + + model User { + role UserRole @default(UserRole.USER) + } + `; + const doc = await parse(content); + expect(doc.enums).toHaveLength(1); + expect(doc.enums[0]).toEqual( + expect.objectContaining({ + name: 'UserRole', + fields: expect.arrayContaining([ + expect.objectContaining({ + value: 'USER', + }), + expect.objectContaining({ + value: 'ADMIN', + }), + ]), + }) + ); + + expect(doc.models[0].fields[0].fieldType.reference?.ref?.name).toBe( + 'UserRole' + ); + + expect(doc.models[0].fields[0].attributes[0].args[0].$type).toBe( + EnumMemberExpr + ); + }); + + it('model field types', async () => { + const content = ` + model User { + id String + age Int + salery Float + activated Boolean + createdAt DateTime + metadata JSON + } + `; + const doc = await parse(content); + expect(doc.models[0].fields).toHaveLength(6); + expect(doc.models[0].fields.map((f) => f.fieldType.type)).toEqual( + expect.arrayContaining([ + 'String', + 'Int', + 'Boolean', + 'JSON', + 'DateTime', + 'Float', + ]) + ); + }); + + it('model field modifiers', async () => { + const content = ` + model User { + name String? + tags String[] + } + `; + const doc = await parse(content); + expect(doc.models[0].fields[0].fieldType.optional).toBeTruthy(); + expect(doc.models[0].fields[1].fieldType.array).toBeTruthy(); + }); + + it('model field attributes', async () => { + const content = ` + model User { + id String @id + activated Boolean @default(false) @unique + } + `; + const doc = await parse(content); + expect(doc.models[0].fields[0].attributes[0].decl.ref?.name).toBe('id'); + expect(doc.models[0].fields[1].attributes[0]).toEqual( + expect.objectContaining({ + args: expect.arrayContaining([ + expect.objectContaining({ value: false }), + ]), + }) + ); + expect(doc.models[0].fields[1].attributes[1].decl.ref?.name).toBe( + 'unique' + ); + }); + + it('model attributes', async () => { + const content = ` + model Model { + a String + b String + @@unique([a, b]) + } + `; + const doc = await parse(content); + expect(doc.models[0].attributes).toHaveLength(1); + expect(doc.models[0].attributes[0].decl.ref?.name).toBe('unique'); + expect( + (doc.models[0].attributes[0].args[0] as ArrayExpr).items.map( + (item: any) => item.target?.ref?.name + ) + ).toEqual(expect.arrayContaining(['a', 'b'])); + }); + + it('model relation', async () => { + const content = ` + model User { + id String + posts Post[] + } + + model Post { + id String + owner User @cascade + } + `; + const doc = await parse(content); + expect( + doc.models[0].fields[1].fieldType.reference?.ref?.name === 'Post' + ); + expect( + doc.models[1].fields[1].fieldType.reference?.ref?.name === 'User' + ); + }); + + it('policy expressions', async () => { + const content = ` + model Model { + a Int + b Int + c Boolean + + @@deny(!c) + @@deny(a < 0) + @@deny(a + b < 10) + } + `; + const doc = await parse(content); + const attrs = doc.models[0].attributes; + + expect(attrs[0].args[0].$type).toBe(UnaryExpr); + expect((attrs[0].args[0] as UnaryExpr).arg.$type).toBe(ReferenceExpr); + + expect(attrs[1].args[0].$type).toBe(BinaryExpr); + expect((attrs[1].args[0] as BinaryExpr).left.$type).toBe(ReferenceExpr); + expect((attrs[1].args[0] as BinaryExpr).right.$type).toBe(LiteralExpr); + + expect(attrs[1].args[0].$type).toBe(BinaryExpr); + expect((attrs[1].args[0] as BinaryExpr).left.$type).toBe(ReferenceExpr); + expect((attrs[1].args[0] as BinaryExpr).right.$type).toBe(LiteralExpr); + + expect(attrs[2].args[0].$type).toBe(BinaryExpr); + expect((attrs[2].args[0] as BinaryExpr).left.$type).toBe(BinaryExpr); + }); + + it('policy expression precedence', async () => { + const content = ` + model Model { + a Int + b Int + @@deny(a + b * 2 > 0) + @@deny((a + b) * 2 > 0) + @@deny(a > 0 && b < 0) + @@deny(a >= 0 && b <= 0) + @@deny(a == 0 || b != 0) + } + `; + const doc = await parse(content); + const attrs = doc.models[0].attributes; + + expect(attrs[0].args[0].$type).toBe(BinaryExpr); + + // 1: a + b * 2 > 0 + + // > + expect((attrs[0].args[0] as BinaryExpr).operator).toBe('>'); + + // a + b * 2 + expect((attrs[0].args[0] as BinaryExpr).left.$type).toBe(BinaryExpr); + + // 0 + expect((attrs[0].args[0] as BinaryExpr).right.$type).toBe(LiteralExpr); + + // + + expect( + ((attrs[0].args[0] as BinaryExpr).left as BinaryExpr).operator + ).toBe('+'); + + // a + expect( + ((attrs[0].args[0] as BinaryExpr).left as BinaryExpr).left.$type + ).toBe(ReferenceExpr); + + // b * 2 + expect( + ((attrs[0].args[0] as BinaryExpr).left as BinaryExpr).right.$type + ).toBe(BinaryExpr); + + // 2: (a + b) * 2 > 0 + + // > + expect((attrs[1].args[0] as BinaryExpr).operator).toBe('>'); + + // (a + b) * 2 + expect((attrs[1].args[0] as BinaryExpr).left.$type).toBe(BinaryExpr); + + // 0 + expect((attrs[1].args[0] as BinaryExpr).right.$type).toBe(LiteralExpr); + + // * + expect( + ((attrs[1].args[0] as BinaryExpr).left as BinaryExpr).operator + ).toBe('*'); + + // (a + b) + expect( + ((attrs[1].args[0] as BinaryExpr).left as BinaryExpr).left.$type + ).toBe(BinaryExpr); + + // a + expect( + ( + ((attrs[1].args[0] as BinaryExpr).left as BinaryExpr) + .left as BinaryExpr + ).left.$type + ).toBe(ReferenceExpr); + + // b + expect( + ( + ((attrs[1].args[0] as BinaryExpr).left as BinaryExpr) + .left as BinaryExpr + ).right.$type + ).toBe(ReferenceExpr); + + // 2 + expect( + ((attrs[1].args[0] as BinaryExpr).left as BinaryExpr).right.$type + ).toBe(LiteralExpr); + }); + + it('function', async () => { + const content = ` + model Model { + a Int + b Int + @@deny(check(a, b)) + } + + function check(a, b) { + a > b + } + `; + const doc = await parse(content); + + expect(doc.functions[0].name).toBe('check'); + expect(doc.functions[0].params).toHaveLength(2); + expect(doc.functions[0].expression.$type).toBe(BinaryExpr); + + expect(doc.models[0].attributes[0].args[0].$type).toBe(InvocationExpr); + }); + + // it('feature coverage', async () => { + // const content = ` + // datasource { + // provider = 'postgresql' + // url = env('DATABASE_URL') + // } + + // fragment CommonFields { + // id String @id + // createdBy User @createdBy + // updatedBy User @updatedBy + // createdAt DateTime @createdAt + // updatedAt DateTime @updatedAt + // } + + // model Space + // @deny('all', auth() == null) + // @allow('create', true) + // @allow('read', userInSpace(auth(), $this)) + // @allow('update,delete', userIsSpaceAdmin(auth(), $this)) { + // ...CommonFields + // name String + // slug String @unique + // members SpaceUser[] @cascade + // todoLists TodoList[] @cascade + // } + + // enum SpaceUserRole { + // USER + // ADMIN + // } + + // model SpaceUser + // @deny('all', auth() == null) + // @allow('create,update,delete', userIsSpaceAdmin(auth(), $this.space)) + // @allow('read', userInSpace(auth(), $this.space)) { + // ...CommonFields + // space Space + // user User + // role SpaceUserRole + // } + + // model User + // @deny('all', auth() == null) + // @allow('create', true) + // @allow('read', userInAnySpace(auth(), spaces)) + // @allow('update,delete', auth() == $this) { + // ...CommonFields + // email String @unique + // name String? + // todoList TodoList[] + // spaces SpaceUser[] @cascade + // profile Profile? @cascade + // } + + // model Profile + // @deny('all', auth() == null) + // @allow('read', userInAnySpace(auth(), $this.user.spaces)) + // @allow('create,update,delete', $this.user == auth()) { + // ...CommonFields + // user User @unique + // avatar String? + // } + + // model TodoList + // @deny('all', auth() == null) + // @allow('read', $this.owner == auth() || (userInSpace(auth(), $this.space) && !$this.private)) + // @allow('create,update,delete', $this.owner == auth() && userInSpace(auth(), $this.space)) { + // ...CommonFields + // space Space + // owner User + // title String + // content String + // private Boolean @default(true) + // todos Todo[] @cascade + // } + + // model Todo + // @deny('all', auth() == null) + // @allow('all', $this.todoList.owner == auth() || (userInSpace(auth(), $this.todoList.space) && !$this.todoList.private)) { + // ...CommonFields + // owner User + // todoList TodoList + // title String + // completedAt DateTime? + // } + + // function userInSpace(user, space) { + // exists(SpaceUser, $.space == space && $.user == user) + // } + + // function userIsSpaceAdmin(user, space) { + // exists(SpaceUser, $.space == space && $.user == user && $.role == ADMIN) + // } + + // function userInAnySpace(user, spaces) { + // find(spaces, $.user == user) + // } + // `; + + // const model = await parse(content); + + // console.log('Dump AST:', model); + // }); +}); diff --git a/packages/schema/tests/utils.ts b/packages/schema/tests/utils.ts index c8166a1ce..6adfee1cc 100644 --- a/packages/schema/tests/utils.ts +++ b/packages/schema/tests/utils.ts @@ -2,13 +2,21 @@ import { DefaultLangiumDocumentFactory } from 'langium'; import { createZModelServices } from '../src/language-server/zmodel-module'; import { URI } from 'vscode-uri'; import { v4 as uuid } from 'uuid'; +import * as fs from 'fs'; +import * as path from 'path'; import { Model } from '../src/language-server/generated/ast'; export async function parse(content: string) { const { shared } = createZModelServices(); const factory = new DefaultLangiumDocumentFactory(shared); + const stdLib = factory.fromString( + fs.readFileSync('src/language-server/stdlib.zmodel', { + encoding: 'utf-8', + }), + URI.file(path.resolve('src/language-server/stdlib.zmodel')) + ); const doc = factory.fromString(content, URI.parse(`zmodel://${uuid()}`)); - await shared.workspace.DocumentBuilder.build([doc], { + await shared.workspace.DocumentBuilder.build([stdLib, doc], { validationChecks: 'all', }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c9d2c3f0..57ee7e03d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,7 +29,7 @@ importers: packages/schema: specifiers: '@types/jest': ^29.0.3 - '@types/node': ^14.17.3 + '@types/node': ^14.18.29 '@types/uuid': ^8.3.4 '@types/vscode': ^1.56.0 '@typescript-eslint/eslint-plugin': ^4.14.1 diff --git a/samples/todo/.zenstack/server/data/todo-list.ts b/samples/todo/.zenstack/server/data/todo-list.ts index e77b08c81..5d036d329 100644 --- a/samples/todo/.zenstack/server/data/todo-list.ts +++ b/samples/todo/.zenstack/server/data/todo-list.ts @@ -90,8 +90,8 @@ async function handlePut( } const r = await client.prisma.todoList.update({ - where: { id: path[0] }, ...body, + where: { id: path[0] }, }); res.status(200).send(r); diff --git a/samples/todo/.zenstack/types.ts b/samples/todo/.zenstack/types.ts index 9bbbbf323..9ba1eeef5 100644 --- a/samples/todo/.zenstack/types.ts +++ b/samples/todo/.zenstack/types.ts @@ -33,14 +33,14 @@ export type TodoListGetArgs = P.SelectSubset< TodoListGetArgsInput >; -export type TodoListUpdateArgs = P.TodoListUpdateArgs; +export type TodoListUpdateArgs = Omit; export type TodoListUpdateResult = P.CheckSelect< T, TodoList, P.TodoListGetPayload >; -export type TodoListDeleteArgs = P.TodoListDeleteArgs; +export type TodoListDeleteArgs = Omit; export type TodoListDeleteResult = P.CheckSelect< T, TodoList, From 0d0270988598a24ddddcb78b0b41536c08d6f36c Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 28 Sep 2022 16:03:56 +0800 Subject: [PATCH 2/5] WIP --- packages/schema/package.json | 2 + .../src/language-server/generated/ast.ts | 86 +++-- .../src/language-server/generated/grammar.ts | 300 +++++++++++------- .../src/language-server/zmodel-module.ts | 5 + .../src/language-server/zmodel-scope.ts | 52 +++ .../schema/src/language-server/zmodel.langium | 40 ++- packages/schema/tests/parser.test.ts | 136 +++++--- packages/schema/tests/utils.ts | 6 +- packages/schema/tsconfig.json | 37 +-- pnpm-lock.yaml | 106 ++++++- 10 files changed, 537 insertions(+), 233 deletions(-) create mode 100644 packages/schema/src/language-server/zmodel-scope.ts diff --git a/packages/schema/package.json b/packages/schema/package.json index bc974cbfe..02f4ce95e 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -66,6 +66,7 @@ "devDependencies": { "@types/jest": "^29.0.3", "@types/node": "^14.18.29", + "@types/tmp": "^0.2.3", "@types/uuid": "^8.3.4", "@types/vscode": "^1.56.0", "@typescript-eslint/eslint-plugin": "^4.14.1", @@ -74,6 +75,7 @@ "eslint": "^7.19.0", "jest": "^29.0.3", "langium-cli": "^0.4.0", + "tmp": "^0.2.1", "ts-jest": "^29.0.1", "ts-node": "^10.9.1", "typescript": "^4.6.2" diff --git a/packages/schema/src/language-server/generated/ast.ts b/packages/schema/src/language-server/generated/ast.ts index 65c2fac6f..316d7ab35 100644 --- a/packages/schema/src/language-server/generated/ast.ts +++ b/packages/schema/src/language-server/generated/ast.ts @@ -7,7 +7,15 @@ /* eslint-disable @typescript-eslint/no-empty-interface */ import { AstNode, AstReflection, Reference, isAstNode, TypeMetaData } from 'langium'; -export type Expression = ArrayExpr | BinaryExpr | EnumMemberExpr | InvocationExpr | LiteralExpr | ReferenceExpr | UnaryExpr; +export type AbstractDeclaration = Attribute | DataModel | DataSource | Enum | Function; + +export const AbstractDeclaration = 'AbstractDeclaration'; + +export function isAbstractDeclaration(item: unknown): item is AbstractDeclaration { + return reflection.isInstance(item, AbstractDeclaration); +} + +export type Expression = ArrayExpr | BinaryExpr | InvocationExpr | LiteralExpr | ReferenceExpr | UnaryExpr; export const Expression = 'Expression'; @@ -15,7 +23,9 @@ export function isExpression(item: unknown): item is Expression { return reflection.isInstance(item, Expression); } -export type ReferenceTarget = DataModelField | Function | FunctionParam; +export type QualifiedName = string; + +export type ReferenceTarget = DataModelField | EnumField | Function | FunctionParam; export const ReferenceTarget = 'ReferenceTarget'; @@ -94,8 +104,8 @@ export function isDataModelAttribute(item: unknown): item is DataModelAttribute export interface DataModelField extends AstNode { readonly $container: DataModel; attributes: Array - fieldType: DataModelFieldType name: string + type: DataModelFieldType } export const DataModelField = 'DataModelField'; @@ -168,7 +178,7 @@ export function isEnum(item: unknown): item is Enum { export interface EnumField extends AstNode { readonly $container: Enum; - value: string + name: string } export const EnumField = 'EnumField'; @@ -177,18 +187,6 @@ export function isEnumField(item: unknown): item is EnumField { return reflection.isInstance(item, EnumField); } -export interface EnumMemberExpr extends AstNode { - readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; - decl: Reference - member: string -} - -export const EnumMemberExpr = 'EnumMemberExpr'; - -export function isEnumMemberExpr(item: unknown): item is EnumMemberExpr { - return reflection.isInstance(item, EnumMemberExpr); -} - export interface Function extends AstNode { readonly $container: Model; expression: Expression @@ -205,6 +203,7 @@ export function isFunction(item: unknown): item is Function { export interface FunctionParam extends AstNode { readonly $container: Function; name: string + type: FunctionParamType } export const FunctionParam = 'FunctionParam'; @@ -213,6 +212,19 @@ export function isFunctionParam(item: unknown): item is FunctionParam { return reflection.isInstance(item, FunctionParam); } +export interface FunctionParamType extends AstNode { + readonly $container: FunctionParam; + array: boolean + reference?: Reference + type?: 'Boolean' | 'DateTime' | 'Float' | 'Int' | 'JSON' | 'String' +} + +export const FunctionParamType = 'FunctionParamType'; + +export function isFunctionParamType(item: unknown): item is FunctionParamType { + return reflection.isInstance(item, FunctionParamType); +} + export interface InvocationExpr extends AstNode { readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; args: Array @@ -237,11 +249,7 @@ export function isLiteralExpr(item: unknown): item is LiteralExpr { } export interface Model extends AstNode { - attributes: Array - datasources: Array - enums: Array - functions: Array - models: Array + declarations: Array } export const Model = 'Model'; @@ -273,14 +281,14 @@ export function isUnaryExpr(item: unknown): item is UnaryExpr { return reflection.isInstance(item, UnaryExpr); } -export type ZModelAstType = 'ArrayExpr' | 'Attribute' | 'BinaryExpr' | 'DataModel' | 'DataModelAttribute' | 'DataModelField' | 'DataModelFieldAttribute' | 'DataModelFieldType' | 'DataSource' | 'DataSourceField' | 'Enum' | 'EnumField' | 'EnumMemberExpr' | 'Expression' | 'Function' | 'FunctionParam' | 'InvocationExpr' | 'LiteralExpr' | 'Model' | 'ReferenceExpr' | 'ReferenceTarget' | 'TypeDeclaration' | 'UnaryExpr'; +export type ZModelAstType = 'AbstractDeclaration' | 'ArrayExpr' | 'Attribute' | 'BinaryExpr' | 'DataModel' | 'DataModelAttribute' | 'DataModelField' | 'DataModelFieldAttribute' | 'DataModelFieldType' | 'DataSource' | 'DataSourceField' | 'Enum' | 'EnumField' | 'Expression' | 'Function' | 'FunctionParam' | 'FunctionParamType' | 'InvocationExpr' | 'LiteralExpr' | 'Model' | 'ReferenceExpr' | 'ReferenceTarget' | 'TypeDeclaration' | 'UnaryExpr'; -export type ZModelAstReference = 'DataModelAttribute:decl' | 'DataModelFieldAttribute:decl' | 'DataModelFieldType:reference' | 'EnumMemberExpr:decl' | 'ReferenceExpr:target'; +export type ZModelAstReference = 'DataModelAttribute:decl' | 'DataModelFieldAttribute:decl' | 'DataModelFieldType:reference' | 'FunctionParamType:reference' | 'ReferenceExpr:target'; export class ZModelAstReflection implements AstReflection { getAllTypes(): string[] { - return ['ArrayExpr', 'Attribute', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'EnumMemberExpr', 'Expression', 'Function', 'FunctionParam', 'InvocationExpr', 'LiteralExpr', 'Model', 'ReferenceExpr', 'ReferenceTarget', 'TypeDeclaration', 'UnaryExpr']; + return ['AbstractDeclaration', 'ArrayExpr', 'Attribute', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'Expression', 'Function', 'FunctionParam', 'FunctionParamType', 'InvocationExpr', 'LiteralExpr', 'Model', 'ReferenceExpr', 'ReferenceTarget', 'TypeDeclaration', 'UnaryExpr']; } isInstance(node: unknown, type: string): boolean { @@ -294,22 +302,28 @@ export class ZModelAstReflection implements AstReflection { switch (subtype) { case ArrayExpr: case BinaryExpr: - case EnumMemberExpr: case InvocationExpr: case LiteralExpr: case ReferenceExpr: case UnaryExpr: { return this.isSubtype(Expression, supertype); } + case Attribute: + case DataSource: { + return this.isSubtype(AbstractDeclaration, supertype); + } case DataModel: case Enum: { - return this.isSubtype(TypeDeclaration, supertype); + return this.isSubtype(AbstractDeclaration, supertype) || this.isSubtype(TypeDeclaration, supertype); } case DataModelField: - case Function: + case EnumField: case FunctionParam: { return this.isSubtype(ReferenceTarget, supertype); } + case Function: { + return this.isSubtype(AbstractDeclaration, supertype) || this.isSubtype(ReferenceTarget, supertype); + } default: { return false; } @@ -327,8 +341,8 @@ export class ZModelAstReflection implements AstReflection { case 'DataModelFieldType:reference': { return TypeDeclaration; } - case 'EnumMemberExpr:decl': { - return Enum; + case 'FunctionParamType:reference': { + return TypeDeclaration; } case 'ReferenceExpr:target': { return ReferenceTarget; @@ -415,6 +429,14 @@ export class ZModelAstReflection implements AstReflection { ] }; } + case 'FunctionParamType': { + return { + name: 'FunctionParamType', + mandatory: [ + { name: 'array', type: 'boolean' } + ] + }; + } case 'InvocationExpr': { return { name: 'InvocationExpr', @@ -427,11 +449,7 @@ export class ZModelAstReflection implements AstReflection { return { name: 'Model', mandatory: [ - { name: 'attributes', type: 'array' }, - { name: 'datasources', type: 'array' }, - { name: 'enums', type: 'array' }, - { name: 'functions', type: 'array' }, - { name: 'models', type: 'array' } + { name: 'declarations', type: 'array' } ] }; } diff --git a/packages/schema/src/language-server/generated/grammar.ts b/packages/schema/src/language-server/generated/grammar.ts index 9ee0a5760..2d4496c27 100644 --- a/packages/schema/src/language-server/generated/grammar.ts +++ b/packages/schema/src/language-server/generated/grammar.ts @@ -15,73 +15,70 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "$type": "ParserRule", "name": "Model", "entry": true, + "alternatives": { + "$type": "Assignment", + "feature": "declarations", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "AbstractDeclaration" + }, + "arguments": [] + }, + "cardinality": "*" + }, + "definesHiddenTokens": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "AbstractDeclaration", "alternatives": { "$type": "Alternatives", "elements": [ { - "$type": "Assignment", - "feature": "datasources", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataSource" - }, - "arguments": [] - } + "$type": "RuleCall", + "rule": { + "$refText": "DataSource" + }, + "arguments": [] }, { - "$type": "Assignment", - "feature": "models", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "DataModel" - }, - "arguments": [] - } + "$type": "RuleCall", + "rule": { + "$refText": "DataModel" + }, + "arguments": [] }, { - "$type": "Assignment", - "feature": "enums", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "Enum" - }, - "arguments": [] - } + "$type": "RuleCall", + "rule": { + "$refText": "Enum" + }, + "arguments": [] }, { - "$type": "Assignment", - "feature": "functions", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "Function" - }, - "arguments": [] - } + "$type": "RuleCall", + "rule": { + "$refText": "Function" + }, + "arguments": [] }, { - "$type": "Assignment", - "feature": "attributes", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "Attribute" - }, - "arguments": [] - } + "$type": "RuleCall", + "rule": { + "$refText": "Attribute" + }, + "arguments": [] } - ], - "cardinality": "*" + ] }, "definesHiddenTokens": false, + "entry": false, "fragment": false, "hiddenTokens": [], "parameters": [], @@ -318,7 +315,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "terminal": { "$type": "RuleCall", "rule": { - "$refText": "ID" + "$refText": "QualifiedName" }, "arguments": [] }, @@ -375,49 +372,6 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "parameters": [], "wildcard": false }, - { - "$type": "ParserRule", - "name": "EnumMemberExpr", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "decl", - "operator": "=", - "terminal": { - "$type": "CrossReference", - "type": { - "$refText": "Enum" - }, - "deprecatedSyntax": false - } - }, - { - "$type": "Keyword", - "value": "." - }, - { - "$type": "Assignment", - "feature": "member", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, { "$type": "ParserRule", "name": "UnaryExpr", @@ -881,13 +835,6 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, "arguments": [] }, - { - "$type": "RuleCall", - "rule": { - "$refText": "EnumMemberExpr" - }, - "arguments": [] - }, { "$type": "RuleCall", "rule": { @@ -1049,7 +996,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "Assignment", - "feature": "fieldType", + "feature": "type", "operator": "=", "terminal": { "$type": "RuleCall", @@ -1093,7 +1040,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG { "$type": "RuleCall", "rule": { - "$refText": "FieldType" + "$refText": "BuiltinType" }, "arguments": [] }, @@ -1204,7 +1151,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "name": "EnumField", "alternatives": { "$type": "Assignment", - "feature": "value", + "feature": "name", "operator": "=", "terminal": { "$type": "RuleCall", @@ -1324,16 +1271,89 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "$type": "ParserRule", "name": "FunctionParam", "alternatives": { - "$type": "Assignment", - "feature": "name", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" + "$type": "Group", + "elements": [ + { + "$type": "Assignment", + "feature": "name", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] + } }, - "arguments": [] - } + { + "$type": "Assignment", + "feature": "type", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "FunctionParamType" + }, + "arguments": [] + } + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "FunctionParamType", + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "Alternatives", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "BuiltinType" + }, + "arguments": [] + }, + { + "$type": "Assignment", + "feature": "reference", + "operator": "=", + "terminal": { + "$type": "CrossReference", + "type": { + "$refText": "TypeDeclaration" + }, + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] + }, + "deprecatedSyntax": false + } + } + ] + }, + { + "$type": "Assignment", + "feature": "array", + "operator": "?=", + "terminal": { + "$type": "Keyword", + "value": "[]" + }, + "cardinality": "?" + } + ] }, "definesHiddenTokens": false, "entry": false, @@ -1489,7 +1509,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "FieldType", + "name": "BuiltinType", "fragment": true, "alternatives": { "$type": "Assignment", @@ -1531,6 +1551,46 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "parameters": [], "wildcard": false }, + { + "$type": "ParserRule", + "name": "QualifiedName", + "dataType": "string", + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "." + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "ID" + }, + "arguments": [] + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, { "$type": "TerminalRule", "hidden": true, @@ -1637,6 +1697,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, "isArray": false, "isRef": false + }, + { + "$type": "AtomType", + "refType": { + "$refText": "EnumField" + }, + "isArray": false, + "isRef": false } ], "name": "ReferenceTarget" diff --git a/packages/schema/src/language-server/zmodel-module.ts b/packages/schema/src/language-server/zmodel-module.ts index 76fc332f6..3248ef193 100644 --- a/packages/schema/src/language-server/zmodel-module.ts +++ b/packages/schema/src/language-server/zmodel-module.ts @@ -12,6 +12,7 @@ import { ZModelGeneratedModule, ZModelGeneratedSharedModule, } from './generated/module'; +import { ZModelScopeComputation } from './zmodel-scope'; import { ZModelValidationRegistry, ZModelValidator } from './zmodel-validator'; /** @@ -38,6 +39,10 @@ export const ZModelModule: Module< ZModelServices, PartialLangiumServices & ZModelAddedServices > = { + references: { + ScopeComputation: (services) => new ZModelScopeComputation(services), + // NameProvider: () => new ZModelNameProvider(), + }, validation: { ValidationRegistry: (services) => new ZModelValidationRegistry(services), diff --git a/packages/schema/src/language-server/zmodel-scope.ts b/packages/schema/src/language-server/zmodel-scope.ts new file mode 100644 index 000000000..ddd5bb8fc --- /dev/null +++ b/packages/schema/src/language-server/zmodel-scope.ts @@ -0,0 +1,52 @@ +import { + DefaultScopeComputation, + LangiumDocument, + LangiumServices, + PrecomputedScopes, + streamAllContents, +} from 'langium'; +import { CancellationToken } from 'vscode-jsonrpc'; +import { Model, isEnum, Enum, isReferenceExpr } from './generated/ast'; + +export class ZModelScopeComputation extends DefaultScopeComputation { + constructor(services: LangiumServices) { + super(services); + } + + async computeScope( + document: LangiumDocument, + cancelToken = CancellationToken.None + ): Promise { + const scopes = await super.computeScope(document, cancelToken); + + const model = document.parseResult.value as Model; + const enumDecls = model.declarations.filter((d) => isEnum(d)) as Enum[]; + + const qualifiedEnumFieldDescriptions = enumDecls + .map((enumDecl) => + enumDecl.fields.map((enumField) => + this.descriptions.createDescription( + enumField, + enumDecl.name + '.' + enumField.name, + document + ) + ) + ) + .flat(); + + // add enum fields' qualified names to scopes of containers of any ReferenceExpr so that + // fully qualified references can be resolved + const allDecls = streamAllContents(model) + .filter((node) => isReferenceExpr(node) && node.$container) + .map((node) => node.$container!) + .distinct(); + + allDecls.forEach((decl) => { + qualifiedEnumFieldDescriptions.forEach((desc) => + scopes.add(decl, desc) + ); + }); + + return scopes; + } +} diff --git a/packages/schema/src/language-server/zmodel.langium b/packages/schema/src/language-server/zmodel.langium index 28d2a99ed..bf533b361 100644 --- a/packages/schema/src/language-server/zmodel.langium +++ b/packages/schema/src/language-server/zmodel.langium @@ -2,13 +2,12 @@ grammar ZModel entry Model: ( - datasources+=DataSource | - models+=DataModel | - enums+=Enum | - functions+=Function | - attributes+=Attribute + declarations+=AbstractDeclaration )*; +AbstractDeclaration: + DataSource | DataModel | Enum | Function | Attribute; + // datasource DataSource: 'datasource' name=ID '{' (fields+=DataSourceField)+ '}'; @@ -26,20 +25,23 @@ LiteralExpr: ArrayExpr: '[' (items+=Expression (',' items+=Expression)*)? ']'; -type ReferenceTarget = FunctionParam | Function | DataModelField; +type ReferenceTarget = FunctionParam | Function | DataModelField | EnumField; ReferenceExpr: - target=[ReferenceTarget:ID]; + target=[ReferenceTarget:QualifiedName]; InvocationExpr: function=ID '(' ArgumentList? ')'; -EnumMemberExpr: - decl=[Enum] '.' member=ID; +// EnumMemberExpr: +// decl=[Enum] '.' member=ID; UnaryExpr: operator=('+'|'-'|'!') arg=Expression; +// operator precedence follow Javascript's rules: +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table + MultDivExpr infers Expression: PrimaryExpr ( {infer BinaryExpr.left=current} @@ -80,7 +82,7 @@ PrimaryExpr infers Expression: LiteralExpr | InvocationExpr | ArrayExpr| - EnumMemberExpr | + // EnumMemberExpr | ReferenceExpr | UnaryExpr; @@ -96,26 +98,27 @@ DataModel: '}'; DataModelField: - name=ID fieldType=DataModelFieldType (attributes+=DataModelFieldAttribute)*; + name=ID type=DataModelFieldType (attributes+=DataModelFieldAttribute)*; DataModelFieldType: - (FieldType - | reference=[TypeDeclaration:ID] - ) (array?='[]')? (optional?='?')?; + (BuiltinType | reference=[TypeDeclaration:ID]) (array?='[]')? (optional?='?')?; // enum Enum: 'enum' name=ID '{' (fields+=EnumField)+ '}'; EnumField: - value=ID; + name=ID; // function Function: 'function' name=ID '(' (params+=FunctionParam (',' params+=FunctionParam)*)? ')' '{' expression=Expression '}'; FunctionParam: - name=ID; + name=ID type=FunctionParamType; + +FunctionParamType: + (BuiltinType | reference=[TypeDeclaration:ID]) (array?='[]')?; // attribute Attribute: @@ -129,9 +132,12 @@ DataModelFieldAttribute: DataModelAttribute: '@@' decl=[Attribute] ('(' ArgumentList? ')')?; -fragment FieldType: +fragment BuiltinType: type=('String'|'Boolean'|'Int'|'Float'|'DateTime'|'JSON'); +QualifiedName returns string: + ID ('.' ID)*; + hidden terminal WS: /\s+/; terminal BOOLEAN returns boolean: /true|false/; terminal ID: /[_a-zA-Z][\w_]*/; diff --git a/packages/schema/tests/parser.test.ts b/packages/schema/tests/parser.test.ts index 3f7d33a8f..dd284be46 100644 --- a/packages/schema/tests/parser.test.ts +++ b/packages/schema/tests/parser.test.ts @@ -1,11 +1,14 @@ import { ArrayExpr, BinaryExpr, - EnumMemberExpr, ReferenceExpr, LiteralExpr, UnaryExpr, InvocationExpr, + DataSource, + Enum, + DataModel, + Function, } from '../src/language-server/generated/ast'; import { parse } from './utils'; @@ -18,8 +21,8 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - expect(doc.datasources).toHaveLength(1); - const ds = doc.datasources[0]; + expect(doc.declarations).toHaveLength(1); + const ds = doc.declarations[0] as DataSource; expect(ds.name).toBe('db'); expect(ds.fields).toHaveLength(2); @@ -57,28 +60,28 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - expect(doc.enums).toHaveLength(1); - expect(doc.enums[0]).toEqual( + const enumDecl = doc.declarations[0] as Enum; + expect(enumDecl).toEqual( expect.objectContaining({ name: 'UserRole', fields: expect.arrayContaining([ expect.objectContaining({ - value: 'USER', + name: 'USER', }), expect.objectContaining({ - value: 'ADMIN', + name: 'ADMIN', }), ]), }) ); - expect(doc.models[0].fields[0].fieldType.reference?.ref?.name).toBe( - 'UserRole' - ); + const model = doc.declarations[1] as DataModel; + expect(model.fields[0].type.reference?.ref?.name).toBe('UserRole'); - expect(doc.models[0].fields[0].attributes[0].args[0].$type).toBe( - EnumMemberExpr - ); + const attrVal = model.fields[0].attributes[0].args[0] as ReferenceExpr; + expect(attrVal.$type).toBe(ReferenceExpr); + expect(attrVal.target.ref?.name).toBe('USER'); + expect((attrVal.target.ref?.$container as Enum).name).toBe('UserRole'); }); it('model field types', async () => { @@ -93,8 +96,9 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - expect(doc.models[0].fields).toHaveLength(6); - expect(doc.models[0].fields.map((f) => f.fieldType.type)).toEqual( + const model = doc.declarations[0] as DataModel; + expect(model.fields).toHaveLength(6); + expect(model.fields.map((f) => f.type.type)).toEqual( expect.arrayContaining([ 'String', 'Int', @@ -114,8 +118,9 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - expect(doc.models[0].fields[0].fieldType.optional).toBeTruthy(); - expect(doc.models[0].fields[1].fieldType.array).toBeTruthy(); + const model = doc.declarations[0] as DataModel; + expect(model.fields[0].type.optional).toBeTruthy(); + expect(model.fields[1].type.array).toBeTruthy(); }); it('model field attributes', async () => { @@ -126,17 +131,16 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - expect(doc.models[0].fields[0].attributes[0].decl.ref?.name).toBe('id'); - expect(doc.models[0].fields[1].attributes[0]).toEqual( + const model = doc.declarations[0] as DataModel; + expect(model.fields[0].attributes[0].decl.ref?.name).toBe('id'); + expect(model.fields[1].attributes[0]).toEqual( expect.objectContaining({ args: expect.arrayContaining([ expect.objectContaining({ value: false }), ]), }) ); - expect(doc.models[0].fields[1].attributes[1].decl.ref?.name).toBe( - 'unique' - ); + expect(model.fields[1].attributes[1].decl.ref?.name).toBe('unique'); }); it('model attributes', async () => { @@ -148,10 +152,11 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - expect(doc.models[0].attributes).toHaveLength(1); - expect(doc.models[0].attributes[0].decl.ref?.name).toBe('unique'); + const model = doc.declarations[0] as DataModel; + expect(model.attributes).toHaveLength(1); + expect(model.attributes[0].decl.ref?.name).toBe('unique'); expect( - (doc.models[0].attributes[0].args[0] as ArrayExpr).items.map( + (model.attributes[0].args[0] as ArrayExpr).items.map( (item: any) => item.target?.ref?.name ) ).toEqual(expect.arrayContaining(['a', 'b'])); @@ -170,12 +175,9 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - expect( - doc.models[0].fields[1].fieldType.reference?.ref?.name === 'Post' - ); - expect( - doc.models[1].fields[1].fieldType.reference?.ref?.name === 'User' - ); + const models = doc.declarations as DataModel[]; + expect(models[0].fields[1].type.reference?.ref?.name === 'Post'); + expect(models[1].fields[1].type.reference?.ref?.name === 'User'); }); it('policy expressions', async () => { @@ -191,7 +193,8 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - const attrs = doc.models[0].attributes; + const model = doc.declarations[0] as DataModel; + const attrs = model.attributes; expect(attrs[0].args[0].$type).toBe(UnaryExpr); expect((attrs[0].args[0] as UnaryExpr).arg.$type).toBe(ReferenceExpr); @@ -221,7 +224,7 @@ describe('Basic Tests', () => { } `; const doc = await parse(content); - const attrs = doc.models[0].attributes; + const attrs = (doc.declarations[0] as DataModel).attributes; expect(attrs[0].args[0].$type).toBe(BinaryExpr); @@ -296,23 +299,76 @@ describe('Basic Tests', () => { it('function', async () => { const content = ` - model Model { + model M { a Int b Int - @@deny(check(a, b)) + c N[] + @@deny(foo(a, b)) + @@deny(bar(c)) + } + + model N { + x Int } - function check(a, b) { + function foo(a Int, b Int) { a > b } + + function bar(items N[]) { + true + } + `; + const doc = await parse(content); + const model = doc.declarations[0] as DataModel; + const foo = doc.declarations[2] as Function; + const bar = doc.declarations[3] as Function; + + expect(foo.name).toBe('foo'); + expect(foo.params.map((p) => p.type.type)).toEqual( + expect.arrayContaining(['Int', 'Int']) + ); + expect(foo.expression.$type).toBe(BinaryExpr); + + expect(bar.name).toBe('bar'); + expect(bar.params[0].type.reference?.ref?.name).toBe('N'); + expect(bar.params[0].type.array).toBeTruthy(); + + expect(model.attributes[0].args[0].$type).toBe(InvocationExpr); + }); + + it('member access', async () => { + const content = ` + model M { + a N + @@deny(a.x < 0) + @@deny(foo(a)) + } + + model N { + x Int + } + + function foo(n N) { + n.x < 0 + } `; const doc = await parse(content); + const model = doc.declarations[0] as DataModel; + const foo = doc.declarations[2] as Function; + const bar = doc.declarations[3] as Function; + + expect(foo.name).toBe('foo'); + expect(foo.params.map((p) => p.type.type)).toEqual( + expect.arrayContaining(['Int', 'Int']) + ); + expect(foo.expression.$type).toBe(BinaryExpr); - expect(doc.functions[0].name).toBe('check'); - expect(doc.functions[0].params).toHaveLength(2); - expect(doc.functions[0].expression.$type).toBe(BinaryExpr); + expect(bar.name).toBe('bar'); + expect(bar.params[0].type.reference?.ref?.name).toBe('N'); + expect(bar.params[0].type.array).toBeTruthy(); - expect(doc.models[0].attributes[0].args[0].$type).toBe(InvocationExpr); + expect(model.attributes[0].args[0].$type).toBe(InvocationExpr); }); // it('feature coverage', async () => { diff --git a/packages/schema/tests/utils.ts b/packages/schema/tests/utils.ts index 6adfee1cc..41619429b 100644 --- a/packages/schema/tests/utils.ts +++ b/packages/schema/tests/utils.ts @@ -1,12 +1,14 @@ import { DefaultLangiumDocumentFactory } from 'langium'; import { createZModelServices } from '../src/language-server/zmodel-module'; import { URI } from 'vscode-uri'; -import { v4 as uuid } from 'uuid'; import * as fs from 'fs'; import * as path from 'path'; import { Model } from '../src/language-server/generated/ast'; +import * as tmp from 'tmp'; export async function parse(content: string) { + const { name: docPath } = tmp.fileSync({ postfix: '.zmodel' }); + fs.writeFileSync(docPath, content); const { shared } = createZModelServices(); const factory = new DefaultLangiumDocumentFactory(shared); const stdLib = factory.fromString( @@ -15,7 +17,7 @@ export async function parse(content: string) { }), URI.file(path.resolve('src/language-server/stdlib.zmodel')) ); - const doc = factory.fromString(content, URI.parse(`zmodel://${uuid()}`)); + const doc = factory.fromString(content, URI.file(docPath)); await shared.workspace.DocumentBuilder.build([stdLib, doc], { validationChecks: 'all', }); diff --git a/packages/schema/tsconfig.json b/packages/schema/tsconfig.json index ca965e342..7e1e74338 100644 --- a/packages/schema/tsconfig.json +++ b/packages/schema/tsconfig.json @@ -1,23 +1,18 @@ { - "compilerOptions": { - "target": "ES6", - "module": "commonjs", - "lib": ["ESNext"], - "sourceMap": true, - "outDir": "out", - "strict": true, - "noUnusedLocals": true, - "noImplicitReturns": true, - "moduleResolution": "node", - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "out", - "node_modules" - ] + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "lib": ["ESNext"], + "sourceMap": true, + "outDir": "out", + "strict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"], + "exclude": ["out", "node_modules"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57ee7e03d..05919c30c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,7 @@ importers: specifiers: '@types/jest': ^29.0.3 '@types/node': ^14.18.29 + '@types/tmp': ^0.2.3 '@types/uuid': ^8.3.4 '@types/vscode': ^1.56.0 '@typescript-eslint/eslint-plugin': ^4.14.1 @@ -42,6 +43,7 @@ importers: jest: ^29.0.3 langium: ^0.4.0 langium-cli: ^0.4.0 + tmp: ^0.2.1 ts-jest: ^29.0.1 ts-node: ^10.9.1 typescript: ^4.6.2 @@ -61,6 +63,7 @@ importers: devDependencies: '@types/jest': 29.0.3 '@types/node': 14.18.29 + '@types/tmp': 0.2.3 '@types/uuid': 8.3.4 '@types/vscode': 1.71.0 '@typescript-eslint/eslint-plugin': 4.33.0_qkm6m2dvh7633pj2jigehwm774 @@ -69,7 +72,8 @@ importers: eslint: 7.32.0 jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 langium-cli: 0.4.0 - ts-jest: 29.0.1_3v2of4mknskhifwzp4doj5a3fa + tmp: 0.2.1 + ts-jest: 29.0.1_poggjixajg6vd6yquly7s7dsj4 ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa typescript: 4.8.3 @@ -142,6 +146,11 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/compat-data/7.19.3: + resolution: {integrity: sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/core/7.19.1: resolution: {integrity: sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==} engines: {node: '>=6.9.0'} @@ -165,6 +174,29 @@ packages: - supports-color dev: true + /@babel/core/7.19.3: + resolution: {integrity: sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.19.3 + '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.19.3 + '@babel/helper-module-transforms': 7.19.0 + '@babel/helpers': 7.19.0 + '@babel/parser': 7.19.3 + '@babel/template': 7.18.10 + '@babel/traverse': 7.19.3 + '@babel/types': 7.19.3 + convert-source-map: 1.8.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/generator/7.19.0: resolution: {integrity: sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==} engines: {node: '>=6.9.0'} @@ -174,6 +206,15 @@ packages: jsesc: 2.5.2 dev: true + /@babel/generator/7.19.3: + resolution: {integrity: sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.19.3 + '@jridgewell/gen-mapping': 0.3.2 + jsesc: 2.5.2 + dev: true + /@babel/helper-compilation-targets/7.19.1_@babel+core@7.19.1: resolution: {integrity: sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==} engines: {node: '>=6.9.0'} @@ -187,6 +228,19 @@ packages: semver: 6.3.0 dev: true + /@babel/helper-compilation-targets/7.19.3_@babel+core@7.19.3: + resolution: {integrity: sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.19.3 + '@babel/core': 7.19.3 + '@babel/helper-validator-option': 7.18.6 + browserslist: 4.21.4 + semver: 6.3.0 + dev: true + /@babel/helper-environment-visitor/7.18.9: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} @@ -292,6 +346,14 @@ packages: '@babel/types': 7.19.0 dev: true + /@babel/parser/7.19.3: + resolution: {integrity: sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.19.3 + dev: true + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.19.1: resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -462,6 +524,24 @@ packages: - supports-color dev: true + /@babel/traverse/7.19.3: + resolution: {integrity: sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.19.3 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.19.3 + '@babel/types': 7.19.3 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/types/7.19.0: resolution: {integrity: sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==} engines: {node: '>=6.9.0'} @@ -471,6 +551,15 @@ packages: to-fast-properties: 2.0.0 dev: true + /@babel/types/7.19.3: + resolution: {integrity: sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.18.10 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + dev: true + /@bcoe/v8-coverage/0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true @@ -1135,6 +1224,10 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true + /@types/tmp/0.2.3: + resolution: {integrity: sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==} + dev: true + /@types/uuid/8.3.4: resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} dev: true @@ -4382,6 +4475,13 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /tmp/0.2.1: + resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} + engines: {node: '>=8.17.0'} + dependencies: + rimraf: 3.0.2 + dev: true + /tmpl/1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true @@ -4402,7 +4502,7 @@ packages: hasBin: true dev: true - /ts-jest/29.0.1_3v2of4mknskhifwzp4doj5a3fa: + /ts-jest/29.0.1_poggjixajg6vd6yquly7s7dsj4: resolution: {integrity: sha512-htQOHshgvhn93QLxrmxpiQPk69+M1g7govO1g6kf6GsjCv4uvRV0znVmDrrvjUrVCnTYeY4FBxTYYYD4airyJA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -4423,7 +4523,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.19.1 + '@babel/core': 7.19.3 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 From 4f33c4abf789ba3306b05f604d66c0047be8e7c8 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 28 Sep 2022 23:27:26 +0800 Subject: [PATCH 3/5] WIP: member resolution --- .../src/language-server/generated/ast.ts | 55 +++-- .../src/language-server/generated/grammar.ts | 116 +++++++--- .../schema/src/language-server/stdlib.zmodel | 3 +- .../src/language-server/zmodel-linker.ts | 219 ++++++++++++++++++ .../src/language-server/zmodel-module.ts | 2 + .../src/language-server/zmodel-scope.ts | 24 +- .../schema/src/language-server/zmodel.langium | 28 +-- .../schema/syntaxes/zmodel.tmLanguage.json | 2 +- packages/schema/tests/parser.test.ts | 49 ++-- packages/schema/tests/utils.ts | 2 + 10 files changed, 390 insertions(+), 110 deletions(-) create mode 100644 packages/schema/src/language-server/zmodel-linker.ts diff --git a/packages/schema/src/language-server/generated/ast.ts b/packages/schema/src/language-server/generated/ast.ts index 316d7ab35..f3b44179f 100644 --- a/packages/schema/src/language-server/generated/ast.ts +++ b/packages/schema/src/language-server/generated/ast.ts @@ -15,7 +15,7 @@ export function isAbstractDeclaration(item: unknown): item is AbstractDeclaratio return reflection.isInstance(item, AbstractDeclaration); } -export type Expression = ArrayExpr | BinaryExpr | InvocationExpr | LiteralExpr | ReferenceExpr | UnaryExpr; +export type Expression = ArrayExpr | BinaryExpr | InvocationExpr | LiteralExpr | MemberAccessExpr | ReferenceExpr | UnaryExpr; export const Expression = 'Expression'; @@ -25,7 +25,7 @@ export function isExpression(item: unknown): item is Expression { export type QualifiedName = string; -export type ReferenceTarget = DataModelField | EnumField | Function | FunctionParam; +export type ReferenceTarget = DataModelField | EnumField | FunctionParam; export const ReferenceTarget = 'ReferenceTarget'; @@ -42,7 +42,7 @@ export function isTypeDeclaration(item: unknown): item is TypeDeclaration { } export interface ArrayExpr extends AstNode { - readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; items: Array } @@ -64,7 +64,7 @@ export function isAttribute(item: unknown): item is Attribute { } export interface BinaryExpr extends AstNode { - readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; left: Expression operator: '!=' | '&&' | '*' | '+' | '-' | '/' | '<' | '<=' | '==' | '>' | '>=' | '||' right: Expression @@ -131,7 +131,7 @@ export interface DataModelFieldType extends AstNode { array: boolean optional: boolean reference?: Reference - type?: 'Boolean' | 'DateTime' | 'Float' | 'Int' | 'JSON' | 'String' + type?: 'Boolean' | 'DateTime' | 'Int' | 'JSON' | 'String' } export const DataModelFieldType = 'DataModelFieldType'; @@ -155,7 +155,7 @@ export function isDataSource(item: unknown): item is DataSource { export interface DataSourceField extends AstNode { readonly $container: DataSource; name: string - value: Expression + value: InvocationExpr | LiteralExpr } export const DataSourceField = 'DataSourceField'; @@ -216,7 +216,7 @@ export interface FunctionParamType extends AstNode { readonly $container: FunctionParam; array: boolean reference?: Reference - type?: 'Boolean' | 'DateTime' | 'Float' | 'Int' | 'JSON' | 'String' + type?: 'Boolean' | 'DateTime' | 'Int' | 'JSON' | 'String' } export const FunctionParamType = 'FunctionParamType'; @@ -226,9 +226,9 @@ export function isFunctionParamType(item: unknown): item is FunctionParamType { } export interface InvocationExpr extends AstNode { - readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; args: Array - function: string + function: Reference } export const InvocationExpr = 'InvocationExpr'; @@ -238,7 +238,7 @@ export function isInvocationExpr(item: unknown): item is InvocationExpr { } export interface LiteralExpr extends AstNode { - readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; value: boolean | number | string } @@ -248,6 +248,18 @@ export function isLiteralExpr(item: unknown): item is LiteralExpr { return reflection.isInstance(item, LiteralExpr); } +export interface MemberAccessExpr extends AstNode { + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; + member: Reference + operand: Expression +} + +export const MemberAccessExpr = 'MemberAccessExpr'; + +export function isMemberAccessExpr(item: unknown): item is MemberAccessExpr { + return reflection.isInstance(item, MemberAccessExpr); +} + export interface Model extends AstNode { declarations: Array } @@ -259,7 +271,7 @@ export function isModel(item: unknown): item is Model { } export interface ReferenceExpr extends AstNode { - readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; target: Reference } @@ -270,7 +282,7 @@ export function isReferenceExpr(item: unknown): item is ReferenceExpr { } export interface UnaryExpr extends AstNode { - readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | UnaryExpr; + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; arg: Expression operator: '!' | '+' | '-' } @@ -281,14 +293,14 @@ export function isUnaryExpr(item: unknown): item is UnaryExpr { return reflection.isInstance(item, UnaryExpr); } -export type ZModelAstType = 'AbstractDeclaration' | 'ArrayExpr' | 'Attribute' | 'BinaryExpr' | 'DataModel' | 'DataModelAttribute' | 'DataModelField' | 'DataModelFieldAttribute' | 'DataModelFieldType' | 'DataSource' | 'DataSourceField' | 'Enum' | 'EnumField' | 'Expression' | 'Function' | 'FunctionParam' | 'FunctionParamType' | 'InvocationExpr' | 'LiteralExpr' | 'Model' | 'ReferenceExpr' | 'ReferenceTarget' | 'TypeDeclaration' | 'UnaryExpr'; +export type ZModelAstType = 'AbstractDeclaration' | 'ArrayExpr' | 'Attribute' | 'BinaryExpr' | 'DataModel' | 'DataModelAttribute' | 'DataModelField' | 'DataModelFieldAttribute' | 'DataModelFieldType' | 'DataSource' | 'DataSourceField' | 'Enum' | 'EnumField' | 'Expression' | 'Function' | 'FunctionParam' | 'FunctionParamType' | 'InvocationExpr' | 'LiteralExpr' | 'MemberAccessExpr' | 'Model' | 'ReferenceExpr' | 'ReferenceTarget' | 'TypeDeclaration' | 'UnaryExpr'; -export type ZModelAstReference = 'DataModelAttribute:decl' | 'DataModelFieldAttribute:decl' | 'DataModelFieldType:reference' | 'FunctionParamType:reference' | 'ReferenceExpr:target'; +export type ZModelAstReference = 'DataModelAttribute:decl' | 'DataModelFieldAttribute:decl' | 'DataModelFieldType:reference' | 'FunctionParamType:reference' | 'InvocationExpr:function' | 'MemberAccessExpr:member' | 'ReferenceExpr:target'; export class ZModelAstReflection implements AstReflection { getAllTypes(): string[] { - return ['AbstractDeclaration', 'ArrayExpr', 'Attribute', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'Expression', 'Function', 'FunctionParam', 'FunctionParamType', 'InvocationExpr', 'LiteralExpr', 'Model', 'ReferenceExpr', 'ReferenceTarget', 'TypeDeclaration', 'UnaryExpr']; + return ['AbstractDeclaration', 'ArrayExpr', 'Attribute', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'Expression', 'Function', 'FunctionParam', 'FunctionParamType', 'InvocationExpr', 'LiteralExpr', 'MemberAccessExpr', 'Model', 'ReferenceExpr', 'ReferenceTarget', 'TypeDeclaration', 'UnaryExpr']; } isInstance(node: unknown, type: string): boolean { @@ -304,12 +316,14 @@ export class ZModelAstReflection implements AstReflection { case BinaryExpr: case InvocationExpr: case LiteralExpr: + case MemberAccessExpr: case ReferenceExpr: case UnaryExpr: { return this.isSubtype(Expression, supertype); } case Attribute: - case DataSource: { + case DataSource: + case Function: { return this.isSubtype(AbstractDeclaration, supertype); } case DataModel: @@ -321,9 +335,6 @@ export class ZModelAstReflection implements AstReflection { case FunctionParam: { return this.isSubtype(ReferenceTarget, supertype); } - case Function: { - return this.isSubtype(AbstractDeclaration, supertype) || this.isSubtype(ReferenceTarget, supertype); - } default: { return false; } @@ -344,6 +355,12 @@ export class ZModelAstReflection implements AstReflection { case 'FunctionParamType:reference': { return TypeDeclaration; } + case 'InvocationExpr:function': { + return Function; + } + case 'MemberAccessExpr:member': { + return DataModelField; + } case 'ReferenceExpr:target': { return ReferenceTarget; } diff --git a/packages/schema/src/language-server/generated/grammar.ts b/packages/schema/src/language-server/generated/grammar.ts index 2d4496c27..b545955c4 100644 --- a/packages/schema/src/language-server/generated/grammar.ts +++ b/packages/schema/src/language-server/generated/grammar.ts @@ -163,11 +163,23 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "feature": "value", "operator": "=", "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "Expression" - }, - "arguments": [] + "$type": "Alternatives", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "LiteralExpr" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "InvocationExpr" + }, + "arguments": [] + } + ] } } ] @@ -216,7 +228,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG { "$type": "RuleCall", "rule": { - "$refText": "NUMBER" + "$refText": "INT" }, "arguments": [] }, @@ -315,7 +327,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "terminal": { "$type": "RuleCall", "rule": { - "$refText": "QualifiedName" + "$refText": "ID" }, "arguments": [] }, @@ -340,11 +352,11 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "feature": "function", "operator": "=", "terminal": { - "$type": "RuleCall", - "rule": { - "$refText": "ID" + "$type": "CrossReference", + "type": { + "$refText": "Function" }, - "arguments": [] + "deprecatedSyntax": false } }, { @@ -423,7 +435,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "MultDivExpr", + "name": "MemberAccessExpr", "inferredType": { "$type": "InferredType", "name": "Expression" @@ -438,6 +450,68 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, "arguments": [] }, + { + "$type": "Group", + "elements": [ + { + "$type": "Action", + "inferredType": { + "$type": "InferredType", + "name": "MemberAccessExpr" + }, + "feature": "operand", + "operator": "=" + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "." + }, + { + "$type": "Assignment", + "feature": "member", + "operator": "=", + "terminal": { + "$type": "CrossReference", + "type": { + "$refText": "DataModelField" + }, + "deprecatedSyntax": false + } + } + ] + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "MultDivExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "MemberAccessExpr" + }, + "arguments": [] + }, { "$type": "Group", "elements": [ @@ -475,7 +549,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "terminal": { "$type": "RuleCall", "rule": { - "$refText": "PrimaryExpr" + "$refText": "MemberAccessExpr" }, "arguments": [] } @@ -1530,10 +1604,6 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "$type": "Keyword", "value": "Int" }, - { - "$type": "Keyword", - "value": "Float" - }, { "$type": "Keyword", "value": "DateTime" @@ -1637,14 +1707,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "TerminalRule", - "name": "NUMBER", + "name": "INT", "type": { "$type": "ReturnType", "name": "number" }, "terminal": { "$type": "RegexToken", - "regex": "[+-]?[0-9]+(\\\\.[0-9]+)?" + "regex": "[+-]?[0-9]+" }, "fragment": false, "hidden": false @@ -1682,14 +1752,6 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "isArray": false, "isRef": false }, - { - "$type": "AtomType", - "refType": { - "$refText": "Function" - }, - "isArray": false, - "isRef": false - }, { "$type": "AtomType", "refType": { diff --git a/packages/schema/src/language-server/stdlib.zmodel b/packages/schema/src/language-server/stdlib.zmodel index ab5704e10..db5f54317 100644 --- a/packages/schema/src/language-server/stdlib.zmodel +++ b/packages/schema/src/language-server/stdlib.zmodel @@ -1,5 +1,6 @@ +function env() function auth() {} -function some(collection, predicate) {} +function some() {} attribute id() {} attribute default() {} diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts new file mode 100644 index 000000000..43f8cd39d --- /dev/null +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -0,0 +1,219 @@ +import { + AstNode, + AstNodeDescription, + AstNodeDescriptionProvider, + DefaultLinker, + DocumentState, + interruptAndCheck, + isReference, + LangiumDocument, + LangiumServices, + LinkingError, + Reference, + streamContents, +} from 'langium'; +import { CancellationToken } from 'vscode-jsonrpc'; +import { + AbstractDeclaration, + DataModelField, + isArrayExpr, + isBinaryExpr, + isDataModel, + isEnumField, + isExpression, + isInvocationExpr, + isLiteralExpr, + isMemberAccessExpr, + isReferenceExpr, + isUnaryExpr, +} from './generated/ast'; + +interface DefaultReference extends Reference { + _ref?: AstNode | LinkingError; + _nodeDescription?: AstNodeDescription; +} + +export class ZModelLinker extends DefaultLinker { + private readonly descriptions: AstNodeDescriptionProvider; + + constructor(services: LangiumServices) { + super(services); + this.descriptions = services.workspace.AstNodeDescriptionProvider; + } + + async link( + document: LangiumDocument, + cancelToken = CancellationToken.None + ): Promise { + for (const node of streamContents(document.parseResult.value)) { + await interruptAndCheck(cancelToken); + this.resolve(node, document); + } + document.state = DocumentState.Linked; + } + + resolve(node: AstNode, document: LangiumDocument) { + type TypedNode = AstNode & { + $resolvedType?: { + decl?: string | AbstractDeclaration; + array?: boolean; + }; + }; + const _node: TypedNode = node; + + if (isExpression(node)) { + if (isLiteralExpr(node)) { + _node.$resolvedType = { + decl: + typeof node.value === 'string' + ? 'String' + : typeof node.value === 'boolean' + ? 'Boolean' + : typeof node.value === 'number' + ? 'Int' + : undefined, + }; + } else if (isInvocationExpr(node)) { + this.doLink( + { + reference: node.function, + container: node, + property: 'function', + }, + document + ); + node.args.forEach((arg) => this.resolve(arg, document)); + _node.$resolvedType = { decl: 'Boolean' }; + } else if (isArrayExpr(node)) { + node.items.forEach((item) => this.resolve(item, document)); + _node.$resolvedType = { + decl: (node.items[0] as TypedNode).$resolvedType?.decl, + array: true, + }; + } else if (isReferenceExpr(node)) { + // resolve reference: enum field, data model field + this.doLink( + { + reference: node.target, + container: node, + property: 'target', + }, + document + ); + + if (node.target.ref) { + // resolve type + if (isEnumField(node.target.ref)) { + _node.$resolvedType = { + decl: node.target.ref.$container, + }; + } else { + if (node.target.ref.type.type) { + _node.$resolvedType = { + decl: node.target.ref.type.type, + array: node.target.ref.type.array, + }; + } else if (node.target.ref.type.reference) { + _node.$resolvedType = { + decl: node.target.ref.type.reference.ref, + array: node.target.ref.type.array, + }; + } + } + } else { + throw new Error( + `Unresolved reference: ${node.target.$refText}` + ); + } + } else if (isMemberAccessExpr(node)) { + this.resolve(node.operand, document); + const operandResolved = (node.operand as TypedNode) + .$resolvedType; + + const extraScope: DataModelField[] = []; + if (operandResolved && !operandResolved.array) { + if (isDataModel(operandResolved.decl)) { + extraScope.push(...operandResolved.decl.fields); + } + } + + const fromExtraScope = extraScope.find( + (d) => d.name === node.member.$refText + ); + if (fromExtraScope) { + const ref = node.member as DefaultReference; + ref._ref = fromExtraScope; + ref._nodeDescription = this.descriptions.createDescription( + fromExtraScope, + fromExtraScope.name, + document + ); + } else { + this.doLink( + { + reference: node.member, + container: node, + property: 'member', + }, + document + ); + } + if (node.member.ref) { + const targetDecl = + node.member.ref.type.type || + node.member.ref.type.reference?.ref; + _node.$resolvedType = { + decl: targetDecl, + array: node.member.ref.type.array, + }; + } else { + throw new Error( + `Unresolved member: ${node.member.$refText}` + ); + } + } else if (isBinaryExpr(node)) { + this.resolve(node.left, document); + this.resolve(node.right, document); + switch (node.operator) { + case '+': + case '-': + case '*': + case '/': + _node.$resolvedType = { decl: 'Int' }; + break; + + case '>': + case '>=': + case '<': + case '<=': + case '==': + case '!=': + case '&&': + case '||': + _node.$resolvedType = { decl: 'Boolean' }; + break; + } + } else if (isUnaryExpr(node)) { + this.resolve(node.arg, document); + _node.$resolvedType = (node.arg as TypedNode).$resolvedType; + } + } else { + for (const property of Object.keys(node)) { + if (!property.startsWith('$')) { + const value = (node as any)[property]; + if (isReference(value)) { + const info = { + reference: value, + container: node, + property, + }; + this.doLink(info, document); + } + } + } + for (const child of streamContents(node)) { + this.resolve(child, document); + } + } + } +} diff --git a/packages/schema/src/language-server/zmodel-module.ts b/packages/schema/src/language-server/zmodel-module.ts index 3248ef193..14e7850ad 100644 --- a/packages/schema/src/language-server/zmodel-module.ts +++ b/packages/schema/src/language-server/zmodel-module.ts @@ -12,6 +12,7 @@ import { ZModelGeneratedModule, ZModelGeneratedSharedModule, } from './generated/module'; +import { ZModelLinker } from './zmodel-linker'; import { ZModelScopeComputation } from './zmodel-scope'; import { ZModelValidationRegistry, ZModelValidator } from './zmodel-validator'; @@ -41,6 +42,7 @@ export const ZModelModule: Module< > = { references: { ScopeComputation: (services) => new ZModelScopeComputation(services), + Linker: (services) => new ZModelLinker(services), // NameProvider: () => new ZModelNameProvider(), }, validation: { diff --git a/packages/schema/src/language-server/zmodel-scope.ts b/packages/schema/src/language-server/zmodel-scope.ts index ddd5bb8fc..2d59b6a6b 100644 --- a/packages/schema/src/language-server/zmodel-scope.ts +++ b/packages/schema/src/language-server/zmodel-scope.ts @@ -20,31 +20,25 @@ export class ZModelScopeComputation extends DefaultScopeComputation { const scopes = await super.computeScope(document, cancelToken); const model = document.parseResult.value as Model; - const enumDecls = model.declarations.filter((d) => isEnum(d)) as Enum[]; - const qualifiedEnumFieldDescriptions = enumDecls - .map((enumDecl) => - enumDecl.fields.map((enumField) => - this.descriptions.createDescription( - enumField, - enumDecl.name + '.' + enumField.name, - document - ) + const enumDecls = model.declarations.filter((d) => isEnum(d)) as Enum[]; + const enumFieldDescriptions = enumDecls + .map((d) => + d.fields.map((f) => + this.descriptions.createDescription(f, f.name, document) ) ) .flat(); - // add enum fields' qualified names to scopes of containers of any ReferenceExpr so that + // add enum field names to scopes of containers of any ReferenceExpr so that // fully qualified references can be resolved - const allDecls = streamAllContents(model) + const refContainers = streamAllContents(model) .filter((node) => isReferenceExpr(node) && node.$container) .map((node) => node.$container!) .distinct(); - allDecls.forEach((decl) => { - qualifiedEnumFieldDescriptions.forEach((desc) => - scopes.add(decl, desc) - ); + refContainers.forEach((c) => { + enumFieldDescriptions.forEach((desc) => scopes.add(c, desc)); }); return scopes; diff --git a/packages/schema/src/language-server/zmodel.langium b/packages/schema/src/language-server/zmodel.langium index bf533b361..1f5f50f2a 100644 --- a/packages/schema/src/language-server/zmodel.langium +++ b/packages/schema/src/language-server/zmodel.langium @@ -13,28 +13,25 @@ DataSource: 'datasource' name=ID '{' (fields+=DataSourceField)+ '}'; DataSourceField: - (name=ID '=' value=Expression); + (name=ID '=' value=(LiteralExpr|InvocationExpr)); // expression Expression: LogicalExpr; LiteralExpr: - value=(BOOLEAN | NUMBER | STRING); + value=(BOOLEAN | INT | STRING); ArrayExpr: '[' (items+=Expression (',' items+=Expression)*)? ']'; -type ReferenceTarget = FunctionParam | Function | DataModelField | EnumField; +type ReferenceTarget = FunctionParam | DataModelField | EnumField; ReferenceExpr: - target=[ReferenceTarget:QualifiedName]; + target=[ReferenceTarget:ID]; InvocationExpr: - function=ID '(' ArgumentList? ')'; - -// EnumMemberExpr: -// decl=[Enum] '.' member=ID; + function=[Function] '(' ArgumentList? ')'; UnaryExpr: operator=('+'|'-'|'!') arg=Expression; @@ -42,11 +39,17 @@ UnaryExpr: // operator precedence follow Javascript's rules: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table -MultDivExpr infers Expression: +MemberAccessExpr infers Expression: PrimaryExpr ( + {infer MemberAccessExpr.operand=current} + ('.' member=[DataModelField]) + )*; + +MultDivExpr infers Expression: + MemberAccessExpr ( {infer BinaryExpr.left=current} operator=('*'|'/') - right=PrimaryExpr + right=MemberAccessExpr )*; AddSubExpr infers Expression: @@ -82,7 +85,6 @@ PrimaryExpr infers Expression: LiteralExpr | InvocationExpr | ArrayExpr| - // EnumMemberExpr | ReferenceExpr | UnaryExpr; @@ -133,7 +135,7 @@ DataModelAttribute: '@@' decl=[Attribute] ('(' ArgumentList? ')')?; fragment BuiltinType: - type=('String'|'Boolean'|'Int'|'Float'|'DateTime'|'JSON'); + type=('String'|'Boolean'|'Int'|'DateTime'|'JSON'); QualifiedName returns string: ID ('.' ID)*; @@ -142,6 +144,6 @@ hidden terminal WS: /\s+/; terminal BOOLEAN returns boolean: /true|false/; terminal ID: /[_a-zA-Z][\w_]*/; terminal STRING: /"[^"]*"|'[^']*'/; -terminal NUMBER returns number: /[+-]?[0-9]+(\.[0-9]+)?/; +terminal INT returns number: /[+-]?[0-9]+/; hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//; hidden terminal SL_COMMENT: /\/\/[^\n\r]*/; diff --git a/packages/schema/syntaxes/zmodel.tmLanguage.json b/packages/schema/syntaxes/zmodel.tmLanguage.json index df6feea5e..552dee1a3 100644 --- a/packages/schema/syntaxes/zmodel.tmLanguage.json +++ b/packages/schema/syntaxes/zmodel.tmLanguage.json @@ -10,7 +10,7 @@ }, { "name": "keyword.control.zmodel", - "match": "\\b(attribute|Boolean|datasource|DateTime|enum|Float|function|Int|JSON|model|String)\\b" + "match": "\\b(attribute|Boolean|datasource|DateTime|enum|function|Int|JSON|model|String)\\b" }, { "name": "string.quoted.double.zmodel", diff --git a/packages/schema/tests/parser.test.ts b/packages/schema/tests/parser.test.ts index dd284be46..9eb94964a 100644 --- a/packages/schema/tests/parser.test.ts +++ b/packages/schema/tests/parser.test.ts @@ -33,18 +33,12 @@ describe('Basic Tests', () => { value: expect.objectContaining({ value: 'postgresql' }), }) ); - expect(ds.fields[1]).toEqual( - expect.objectContaining({ - name: 'url', - value: expect.objectContaining({ - function: 'env', - args: expect.arrayContaining([ - expect.objectContaining({ - value: 'DATABASE_URL', - }), - ]), - }), - }) + expect(ds.fields[1].name).toBe('url'); + expect((ds.fields[1].value as InvocationExpr).function.ref?.name).toBe( + 'env' + ); + expect((ds.fields[1].value as InvocationExpr).args[0].$type).toBe( + LiteralExpr ); }); @@ -56,7 +50,7 @@ describe('Basic Tests', () => { } model User { - role UserRole @default(UserRole.USER) + role UserRole @default(USER) } `; const doc = await parse(content); @@ -89,7 +83,6 @@ describe('Basic Tests', () => { model User { id String age Int - salery Float activated Boolean createdAt DateTime metadata JSON @@ -97,7 +90,7 @@ describe('Basic Tests', () => { `; const doc = await parse(content); const model = doc.declarations[0] as DataModel; - expect(model.fields).toHaveLength(6); + expect(model.fields).toHaveLength(5); expect(model.fields.map((f) => f.type.type)).toEqual( expect.arrayContaining([ 'String', @@ -105,7 +98,6 @@ describe('Basic Tests', () => { 'Boolean', 'JSON', 'DateTime', - 'Float', ]) ); }); @@ -341,34 +333,23 @@ describe('Basic Tests', () => { const content = ` model M { a N - @@deny(a.x < 0) + @@deny(a.x.y < 0) @@deny(foo(a)) } model N { - x Int + x P + } + + model P { + y Int } function foo(n N) { n.x < 0 } `; - const doc = await parse(content); - const model = doc.declarations[0] as DataModel; - const foo = doc.declarations[2] as Function; - const bar = doc.declarations[3] as Function; - - expect(foo.name).toBe('foo'); - expect(foo.params.map((p) => p.type.type)).toEqual( - expect.arrayContaining(['Int', 'Int']) - ); - expect(foo.expression.$type).toBe(BinaryExpr); - - expect(bar.name).toBe('bar'); - expect(bar.params[0].type.reference?.ref?.name).toBe('N'); - expect(bar.params[0].type.array).toBeTruthy(); - - expect(model.attributes[0].args[0].$type).toBe(InvocationExpr); + await parse(content); }); // it('feature coverage', async () => { diff --git a/packages/schema/tests/utils.ts b/packages/schema/tests/utils.ts index 41619429b..7a835f207 100644 --- a/packages/schema/tests/utils.ts +++ b/packages/schema/tests/utils.ts @@ -18,6 +18,8 @@ export async function parse(content: string) { URI.file(path.resolve('src/language-server/stdlib.zmodel')) ); const doc = factory.fromString(content, URI.file(docPath)); + shared.workspace.LangiumDocuments.addDocument(stdLib); + shared.workspace.LangiumDocuments.addDocument(doc); await shared.workspace.DocumentBuilder.build([stdLib, doc], { validationChecks: 'all', }); From 206a7faf2da767e12590dd6eb1ed1c44c2884c77 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Thu, 29 Sep 2022 19:37:58 +0800 Subject: [PATCH 4/5] WIP: type-checker is working now --- .../src/language-server/generated/ast.ts | 38 +- .../src/language-server/generated/grammar.ts | 244 +++++++-- .../schema/src/language-server/stdlib.zmodel | 22 +- .../src/language-server/zmodel-linker.ts | 474 ++++++++++++------ .../schema/src/language-server/zmodel.langium | 29 +- packages/schema/tests/parser.test.ts | 126 +---- packages/schema/tests/todo-sample.test.ts | 120 +++++ samples/todo/schema.zmodel | 95 ++-- 8 files changed, 738 insertions(+), 410 deletions(-) create mode 100644 packages/schema/tests/todo-sample.test.ts diff --git a/packages/schema/src/language-server/generated/ast.ts b/packages/schema/src/language-server/generated/ast.ts index f3b44179f..7fc948c51 100644 --- a/packages/schema/src/language-server/generated/ast.ts +++ b/packages/schema/src/language-server/generated/ast.ts @@ -15,7 +15,7 @@ export function isAbstractDeclaration(item: unknown): item is AbstractDeclaratio return reflection.isInstance(item, AbstractDeclaration); } -export type Expression = ArrayExpr | BinaryExpr | InvocationExpr | LiteralExpr | MemberAccessExpr | ReferenceExpr | UnaryExpr; +export type Expression = ArrayExpr | BinaryExpr | InvocationExpr | LiteralExpr | MemberAccessExpr | ReferenceExpr | ThisExpr | UnaryExpr; export const Expression = 'Expression'; @@ -23,8 +23,6 @@ export function isExpression(item: unknown): item is Expression { return reflection.isInstance(item, Expression); } -export type QualifiedName = string; - export type ReferenceTarget = DataModelField | EnumField | FunctionParam; export const ReferenceTarget = 'ReferenceTarget'; @@ -55,6 +53,7 @@ export function isArrayExpr(item: unknown): item is ArrayExpr { export interface Attribute extends AstNode { readonly $container: Model; name: string + params: Array } export const Attribute = 'Attribute'; @@ -66,7 +65,7 @@ export function isAttribute(item: unknown): item is Attribute { export interface BinaryExpr extends AstNode { readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; left: Expression - operator: '!=' | '&&' | '*' | '+' | '-' | '/' | '<' | '<=' | '==' | '>' | '>=' | '||' + operator: '!' | '!=' | '&&' | '*' | '+' | '-' | '/' | '<' | '<=' | '==' | '>' | '>=' | '?' | '||' right: Expression } @@ -189,9 +188,10 @@ export function isEnumField(item: unknown): item is EnumField { export interface Function extends AstNode { readonly $container: Model; - expression: Expression + expression?: Expression name: string params: Array + returnType: FunctionParamType } export const Function = 'Function'; @@ -201,7 +201,7 @@ export function isFunction(item: unknown): item is Function { } export interface FunctionParam extends AstNode { - readonly $container: Function; + readonly $container: Attribute | Function; name: string type: FunctionParamType } @@ -213,7 +213,7 @@ export function isFunctionParam(item: unknown): item is FunctionParam { } export interface FunctionParamType extends AstNode { - readonly $container: FunctionParam; + readonly $container: Function | FunctionParam; array: boolean reference?: Reference type?: 'Boolean' | 'DateTime' | 'Int' | 'JSON' | 'String' @@ -281,6 +281,17 @@ export function isReferenceExpr(item: unknown): item is ReferenceExpr { return reflection.isInstance(item, ReferenceExpr); } +export interface ThisExpr extends AstNode { + readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; + value: string +} + +export const ThisExpr = 'ThisExpr'; + +export function isThisExpr(item: unknown): item is ThisExpr { + return reflection.isInstance(item, ThisExpr); +} + export interface UnaryExpr extends AstNode { readonly $container: ArrayExpr | BinaryExpr | DataModelAttribute | DataModelFieldAttribute | DataSourceField | Function | InvocationExpr | MemberAccessExpr | UnaryExpr; arg: Expression @@ -293,14 +304,14 @@ export function isUnaryExpr(item: unknown): item is UnaryExpr { return reflection.isInstance(item, UnaryExpr); } -export type ZModelAstType = 'AbstractDeclaration' | 'ArrayExpr' | 'Attribute' | 'BinaryExpr' | 'DataModel' | 'DataModelAttribute' | 'DataModelField' | 'DataModelFieldAttribute' | 'DataModelFieldType' | 'DataSource' | 'DataSourceField' | 'Enum' | 'EnumField' | 'Expression' | 'Function' | 'FunctionParam' | 'FunctionParamType' | 'InvocationExpr' | 'LiteralExpr' | 'MemberAccessExpr' | 'Model' | 'ReferenceExpr' | 'ReferenceTarget' | 'TypeDeclaration' | 'UnaryExpr'; +export type ZModelAstType = 'AbstractDeclaration' | 'ArrayExpr' | 'Attribute' | 'BinaryExpr' | 'DataModel' | 'DataModelAttribute' | 'DataModelField' | 'DataModelFieldAttribute' | 'DataModelFieldType' | 'DataSource' | 'DataSourceField' | 'Enum' | 'EnumField' | 'Expression' | 'Function' | 'FunctionParam' | 'FunctionParamType' | 'InvocationExpr' | 'LiteralExpr' | 'MemberAccessExpr' | 'Model' | 'ReferenceExpr' | 'ReferenceTarget' | 'ThisExpr' | 'TypeDeclaration' | 'UnaryExpr'; export type ZModelAstReference = 'DataModelAttribute:decl' | 'DataModelFieldAttribute:decl' | 'DataModelFieldType:reference' | 'FunctionParamType:reference' | 'InvocationExpr:function' | 'MemberAccessExpr:member' | 'ReferenceExpr:target'; export class ZModelAstReflection implements AstReflection { getAllTypes(): string[] { - return ['AbstractDeclaration', 'ArrayExpr', 'Attribute', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'Expression', 'Function', 'FunctionParam', 'FunctionParamType', 'InvocationExpr', 'LiteralExpr', 'MemberAccessExpr', 'Model', 'ReferenceExpr', 'ReferenceTarget', 'TypeDeclaration', 'UnaryExpr']; + return ['AbstractDeclaration', 'ArrayExpr', 'Attribute', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'Expression', 'Function', 'FunctionParam', 'FunctionParamType', 'InvocationExpr', 'LiteralExpr', 'MemberAccessExpr', 'Model', 'ReferenceExpr', 'ReferenceTarget', 'ThisExpr', 'TypeDeclaration', 'UnaryExpr']; } isInstance(node: unknown, type: string): boolean { @@ -318,6 +329,7 @@ export class ZModelAstReflection implements AstReflection { case LiteralExpr: case MemberAccessExpr: case ReferenceExpr: + case ThisExpr: case UnaryExpr: { return this.isSubtype(Expression, supertype); } @@ -380,6 +392,14 @@ export class ZModelAstReflection implements AstReflection { ] }; } + case 'Attribute': { + return { + name: 'Attribute', + mandatory: [ + { name: 'params', type: 'array' } + ] + }; + } case 'DataModel': { return { name: 'DataModel', diff --git a/packages/schema/src/language-server/generated/grammar.ts b/packages/schema/src/language-server/generated/grammar.ts index b545955c4..1d3fc98dd 100644 --- a/packages/schema/src/language-server/generated/grammar.ts +++ b/packages/schema/src/language-server/generated/grammar.ts @@ -238,6 +238,13 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "$refText": "STRING" }, "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$refText": "NULL" + }, + "arguments": [] } ] } @@ -312,6 +319,28 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "parameters": [], "wildcard": false }, + { + "$type": "ParserRule", + "name": "ThisExpr", + "alternatives": { + "$type": "Assignment", + "feature": "value", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "THIS" + }, + "arguments": [] + } + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, { "$type": "ParserRule", "name": "ReferenceExpr", @@ -497,7 +526,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "ParserRule", - "name": "MultDivExpr", + "name": "CollectionPredicateExpr", "inferredType": { "$type": "InferredType", "name": "Expression" @@ -512,6 +541,85 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, "arguments": [] }, + { + "$type": "Group", + "elements": [ + { + "$type": "Action", + "inferredType": { + "$type": "InferredType", + "name": "BinaryExpr" + }, + "feature": "left", + "operator": "=" + }, + { + "$type": "Assignment", + "feature": "operator", + "operator": "=", + "terminal": { + "$type": "Alternatives", + "elements": [ + { + "$type": "Keyword", + "value": "?" + }, + { + "$type": "Keyword", + "value": "!" + } + ] + } + }, + { + "$type": "Keyword", + "value": "[" + }, + { + "$type": "Assignment", + "feature": "right", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "Expression" + }, + "arguments": [] + } + }, + { + "$type": "Keyword", + "value": "]" + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "MultDivExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, + "alternatives": { + "$type": "Group", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$refText": "CollectionPredicateExpr" + }, + "arguments": [] + }, { "$type": "Group", "elements": [ @@ -549,7 +657,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "terminal": { "$type": "RuleCall", "rule": { - "$refText": "MemberAccessExpr" + "$refText": "CollectionPredicateExpr" }, "arguments": [] } @@ -888,6 +996,13 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG } ] }, + { + "$type": "RuleCall", + "rule": { + "$refText": "ThisExpr" + }, + "arguments": [] + }, { "$type": "RuleCall", "rule": { @@ -1312,6 +1427,18 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "$type": "Keyword", "value": ")" }, + { + "$type": "Assignment", + "feature": "returnType", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "FunctionParamType" + }, + "arguments": [] + } + }, { "$type": "Keyword", "value": "{" @@ -1326,7 +1453,8 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "$refText": "Expression" }, "arguments": [] - } + }, + "cardinality": "?" }, { "$type": "Keyword", @@ -1460,11 +1588,51 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG }, { "$type": "Keyword", - "value": "{" + "value": "(" + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Assignment", + "feature": "params", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "FunctionParam" + }, + "arguments": [] + } + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "," + }, + { + "$type": "Assignment", + "feature": "params", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$refText": "FunctionParam" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + } + ], + "cardinality": "?" }, { "$type": "Keyword", - "value": "}" + "value": ")" } ] }, @@ -1621,46 +1789,6 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "parameters": [], "wildcard": false }, - { - "$type": "ParserRule", - "name": "QualifiedName", - "dataType": "string", - "alternatives": { - "$type": "Group", - "elements": [ - { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - }, - { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "." - }, - { - "$type": "RuleCall", - "rule": { - "$refText": "ID" - }, - "arguments": [] - } - ], - "cardinality": "*" - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, { "$type": "TerminalRule", "hidden": true, @@ -1685,6 +1813,32 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ||(loadedZModelG "fragment": false, "hidden": false }, + { + "$type": "TerminalRule", + "name": "NULL", + "terminal": { + "$type": "CharacterRange", + "left": { + "$type": "Keyword", + "value": "null" + } + }, + "fragment": false, + "hidden": false + }, + { + "$type": "TerminalRule", + "name": "THIS", + "terminal": { + "$type": "CharacterRange", + "left": { + "$type": "Keyword", + "value": "this" + } + }, + "fragment": false, + "hidden": false + }, { "$type": "TerminalRule", "name": "ID", diff --git a/packages/schema/src/language-server/stdlib.zmodel b/packages/schema/src/language-server/stdlib.zmodel index db5f54317..da8deb07b 100644 --- a/packages/schema/src/language-server/stdlib.zmodel +++ b/packages/schema/src/language-server/stdlib.zmodel @@ -1,10 +1,14 @@ -function env() -function auth() {} -function some() {} +function env() String {} +function auth() User {} -attribute id() {} -attribute default() {} -attribute unique() {} -attribute cascade() {} -attribute allow() {} -attribute deny() {} +attribute id() +attribute default() +attribute createdAt() +attribute updatedAt() +attribute unique() +attribute cascade() +attribute allow() +attribute deny() +attribute email() +attribute url() +attribute length() diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index 43f8cd39d..a5c5c6eed 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -15,17 +15,20 @@ import { import { CancellationToken } from 'vscode-jsonrpc'; import { AbstractDeclaration, - DataModelField, - isArrayExpr, - isBinaryExpr, isDataModel, isEnumField, - isExpression, - isInvocationExpr, - isLiteralExpr, - isMemberAccessExpr, - isReferenceExpr, - isUnaryExpr, + Function, + FunctionParamType, + DataModelFieldType, + ReferenceTarget, + MemberAccessExpr, + DataModel, + LiteralExpr, + InvocationExpr, + ArrayExpr, + ReferenceExpr, + UnaryExpr, + BinaryExpr, } from './generated/ast'; interface DefaultReference extends Reference { @@ -33,6 +36,15 @@ interface DefaultReference extends Reference { _nodeDescription?: AstNodeDescription; } +type TypedNode = AstNode & { + $resolvedType?: { + decl?: string | AbstractDeclaration; + array?: boolean; + }; +}; + +type ScopeProvider = (name: string) => ReferenceTarget | undefined; + export class ZModelLinker extends DefaultLinker { private readonly descriptions: AstNodeDescriptionProvider; @@ -45,6 +57,13 @@ export class ZModelLinker extends DefaultLinker { document: LangiumDocument, cancelToken = CancellationToken.None ): Promise { + if ( + document.parseResult.lexerErrors?.length > 0 || + document.parseResult.parserErrors?.length > 0 + ) { + return; + } + for (const node of streamContents(document.parseResult.value)) { await interruptAndCheck(cancelToken); this.resolve(node, document); @@ -52,168 +71,299 @@ export class ZModelLinker extends DefaultLinker { document.state = DocumentState.Linked; } - resolve(node: AstNode, document: LangiumDocument) { - type TypedNode = AstNode & { - $resolvedType?: { - decl?: string | AbstractDeclaration; - array?: boolean; - }; - }; - const _node: TypedNode = node; + linkReference( + container: AstNode, + property: string, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + if ( + !this.resolveFromScopeProviders( + container, + property, + document, + extraScopes + ) + ) { + const reference: Reference = (container as any)[property]; + this.doLink({ reference, container, property }, document); + } + } + + resolveFromScopeProviders( + node: AstNode, + property: string, + document: LangiumDocument, + providers: ScopeProvider[] + ) { + const reference: DefaultReference = (node as any)[property]; + for (const provider of providers) { + const target = provider(reference.$refText); + if (target) { + reference._ref = target; + reference._nodeDescription = + this.descriptions.createDescription( + target, + target.name, + document + ); + return target; + } + } + return null; + } - if (isExpression(node)) { - if (isLiteralExpr(node)) { - _node.$resolvedType = { - decl: - typeof node.value === 'string' - ? 'String' - : typeof node.value === 'boolean' - ? 'Boolean' - : typeof node.value === 'number' - ? 'Int' - : undefined, - }; - } else if (isInvocationExpr(node)) { - this.doLink( - { - reference: node.function, - container: node, - property: 'function', - }, - document + resolve( + node: AstNode, + document: LangiumDocument, + extraScopes: ScopeProvider[] = [] + ) { + switch (node.$type) { + case LiteralExpr: + this.resolveLiteral(node as LiteralExpr); + break; + + case InvocationExpr: + this.resolveInvocation( + node as InvocationExpr, + document, + extraScopes ); - node.args.forEach((arg) => this.resolve(arg, document)); - _node.$resolvedType = { decl: 'Boolean' }; - } else if (isArrayExpr(node)) { - node.items.forEach((item) => this.resolve(item, document)); - _node.$resolvedType = { - decl: (node.items[0] as TypedNode).$resolvedType?.decl, - array: true, - }; - } else if (isReferenceExpr(node)) { - // resolve reference: enum field, data model field - this.doLink( - { - reference: node.target, - container: node, - property: 'target', - }, - document + break; + + case ArrayExpr: + this.resolveArray(node as ArrayExpr, document, extraScopes); + break; + + case ReferenceExpr: + this.resolveReference( + node as ReferenceExpr, + document, + extraScopes ); + break; - if (node.target.ref) { - // resolve type - if (isEnumField(node.target.ref)) { - _node.$resolvedType = { - decl: node.target.ref.$container, - }; - } else { - if (node.target.ref.type.type) { - _node.$resolvedType = { - decl: node.target.ref.type.type, - array: node.target.ref.type.array, - }; - } else if (node.target.ref.type.reference) { - _node.$resolvedType = { - decl: node.target.ref.type.reference.ref, - array: node.target.ref.type.array, - }; - } - } - } else { - throw new Error( - `Unresolved reference: ${node.target.$refText}` - ); - } - } else if (isMemberAccessExpr(node)) { - this.resolve(node.operand, document); - const operandResolved = (node.operand as TypedNode) - .$resolvedType; - - const extraScope: DataModelField[] = []; - if (operandResolved && !operandResolved.array) { - if (isDataModel(operandResolved.decl)) { - extraScope.push(...operandResolved.decl.fields); - } + case MemberAccessExpr: + this.resolveMemberAccess( + node as MemberAccessExpr, + document, + extraScopes + ); + break; + + case UnaryExpr: + this.resolveUnary(node as UnaryExpr, document, extraScopes); + break; + + case BinaryExpr: + this.resolveBinary(node as BinaryExpr, document, extraScopes); + break; + + default: + this.resolveDefault(node, document, extraScopes); + break; + } + } + + resolveDefault( + node: AstNode, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + for (const property of Object.keys(node)) { + if (!property.startsWith('$')) { + const value = (node as any)[property]; + if (isReference(value)) { + this.linkReference(node, property, document, extraScopes); } + } + } + for (const child of streamContents(node)) { + this.resolve(child, document, extraScopes); + } + } - const fromExtraScope = extraScope.find( - (d) => d.name === node.member.$refText + resolveBinary( + node: BinaryExpr, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + this.resolve(node.left, document, extraScopes); + this.resolve(node.right, document, extraScopes); + switch (node.operator) { + case '+': + case '-': + case '*': + case '/': + this.resolveToBuiltinTypeOrDecl(node, 'Int'); + break; + + case '>': + case '>=': + case '<': + case '<=': + case '==': + case '!=': + case '&&': + case '||': + this.resolveToBuiltinTypeOrDecl(node, 'Boolean'); + break; + + case '?': + this.resolveCollectionPredicate(node, document, extraScopes); + break; + + default: + throw Error(`Unsupported binary operator: ${node.operator}`); + } + } + + resolveUnary( + node: UnaryExpr, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + this.resolve(node.arg, document, extraScopes); + (node as TypedNode).$resolvedType = ( + node.arg as TypedNode + ).$resolvedType; + } + + resolveReference( + node: ReferenceExpr, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + this.linkReference(node, 'target', document, extraScopes); + + if (node.target.ref) { + // resolve type + if (isEnumField(node.target.ref)) { + this.resolveToBuiltinTypeOrDecl( + node, + node.target.ref.$container ); - if (fromExtraScope) { - const ref = node.member as DefaultReference; - ref._ref = fromExtraScope; - ref._nodeDescription = this.descriptions.createDescription( - fromExtraScope, - fromExtraScope.name, - document - ); - } else { - this.doLink( - { - reference: node.member, - container: node, - property: 'member', - }, - document - ); - } - if (node.member.ref) { - const targetDecl = - node.member.ref.type.type || - node.member.ref.type.reference?.ref; - _node.$resolvedType = { - decl: targetDecl, - array: node.member.ref.type.array, - }; - } else { - throw new Error( - `Unresolved member: ${node.member.$refText}` - ); - } - } else if (isBinaryExpr(node)) { - this.resolve(node.left, document); - this.resolve(node.right, document); - switch (node.operator) { - case '+': - case '-': - case '*': - case '/': - _node.$resolvedType = { decl: 'Int' }; - break; - - case '>': - case '>=': - case '<': - case '<=': - case '==': - case '!=': - case '&&': - case '||': - _node.$resolvedType = { decl: 'Boolean' }; - break; - } - } else if (isUnaryExpr(node)) { - this.resolve(node.arg, document); - _node.$resolvedType = (node.arg as TypedNode).$resolvedType; + } else { + this.resolveToDeclaredType(node, node.target.ref.type); } + } + } + + resolveArray( + node: ArrayExpr, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + node.items.forEach((item) => this.resolve(item, document, extraScopes)); + + const itemType = (node.items[0] as TypedNode).$resolvedType; + if (itemType?.decl) { + this.resolveToBuiltinTypeOrDecl(node, itemType.decl, true); + } + } + + resolveInvocation( + node: InvocationExpr, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + this.linkReference(node, 'function', document, extraScopes); + node.args.forEach((arg) => this.resolve(arg, document, extraScopes)); + const funcDecl = node.function.ref as Function; + this.resolveToDeclaredType(node, funcDecl.returnType); + } + + resolveLiteral(node: LiteralExpr) { + const type = + typeof node.value === 'string' + ? 'String' + : typeof node.value === 'boolean' + ? 'Boolean' + : typeof node.value === 'number' + ? 'Int' + : undefined; + + if (type) { + this.resolveToBuiltinTypeOrDecl(node, type); + } + } + + resolveMemberAccess( + node: MemberAccessExpr, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + this.resolve(node.operand, document, extraScopes); + const operandResolved = (node.operand as TypedNode).$resolvedType; + + if ( + operandResolved && + !operandResolved.array && + isDataModel(operandResolved.decl) + ) { + const modelDecl = operandResolved.decl as DataModel; + const provider = (name: string) => + modelDecl.fields.find((f) => f.name === name); + extraScopes = [provider, ...extraScopes]; + } + + this.linkReference(node, 'member', document, extraScopes); + if (node.member.ref) { + this.resolveToDeclaredType(node, node.member.ref.type); + } + } + + resolveCollectionPredicate( + node: BinaryExpr, + document: LangiumDocument, + extraScopes: ScopeProvider[] + ) { + this.resolve(node.left, document, extraScopes); + + const resolvedType = this.getResolvedType(node.left); + if ( + resolvedType && + isDataModel(resolvedType.decl) && + resolvedType.array + ) { + const dataModelDecl = resolvedType.decl; + const provider = (name: string) => + dataModelDecl.fields.find((f) => f.name === name); + extraScopes = [provider, ...extraScopes]; + this.resolve(node.right, document, extraScopes); + this.resolveToBuiltinTypeOrDecl(node, 'Boolean'); } else { - for (const property of Object.keys(node)) { - if (!property.startsWith('$')) { - const value = (node as any)[property]; - if (isReference(value)) { - const info = { - reference: value, - container: node, - property, - }; - this.doLink(info, document); - } - } - } - for (const child of streamContents(node)) { - this.resolve(child, document); - } + // TODO: how to attach type-checking error? + throw new Error(`Unresolved collection predicate`); } } + + // utils + + getResolvedType(node: AstNode) { + return (node as TypedNode).$resolvedType; + } + + resolveToDeclaredType( + node: AstNode, + type: FunctionParamType | DataModelFieldType + ) { + const _node: TypedNode = node; + if (type.type) { + _node.$resolvedType = { decl: type.type, array: type.array }; + } else if (type.reference) { + _node.$resolvedType = { + decl: type.reference.ref, + array: type.array, + }; + } + } + + resolveToBuiltinTypeOrDecl( + node: AstNode, + type: string | AbstractDeclaration, + array = false + ) { + (node as TypedNode).$resolvedType = { decl: type, array }; + } } diff --git a/packages/schema/src/language-server/zmodel.langium b/packages/schema/src/language-server/zmodel.langium index 1f5f50f2a..0b547c5f6 100644 --- a/packages/schema/src/language-server/zmodel.langium +++ b/packages/schema/src/language-server/zmodel.langium @@ -20,13 +20,16 @@ Expression: LogicalExpr; LiteralExpr: - value=(BOOLEAN | INT | STRING); + value=(BOOLEAN | INT | STRING | NULL); ArrayExpr: '[' (items+=Expression (',' items+=Expression)*)? ']'; type ReferenceTarget = FunctionParam | DataModelField | EnumField; +ThisExpr: + value=THIS; + ReferenceExpr: target=[ReferenceTarget:ID]; @@ -36,7 +39,7 @@ InvocationExpr: UnaryExpr: operator=('+'|'-'|'!') arg=Expression; -// operator precedence follow Javascript's rules: +// binary operator precedence follow Javascript's rules: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table MemberAccessExpr infers Expression: @@ -45,11 +48,18 @@ MemberAccessExpr infers Expression: ('.' member=[DataModelField]) )*; -MultDivExpr infers Expression: +CollectionPredicateExpr infers Expression: MemberAccessExpr ( + {infer BinaryExpr.left=current} + operator=('?'|'!') + '[' right=Expression ']' + )*; + +MultDivExpr infers Expression: + CollectionPredicateExpr ( {infer BinaryExpr.left=current} operator=('*'|'/') - right=MemberAccessExpr + right=CollectionPredicateExpr )*; AddSubExpr infers Expression: @@ -82,6 +92,7 @@ LogicalExpr infers Expression: PrimaryExpr infers Expression: '(' Expression ')' | + ThisExpr | LiteralExpr | InvocationExpr | ArrayExpr| @@ -114,7 +125,7 @@ EnumField: // function Function: - 'function' name=ID '(' (params+=FunctionParam (',' params+=FunctionParam)*)? ')' '{' expression=Expression '}'; + 'function' name=ID '(' (params+=FunctionParam (',' params+=FunctionParam)*)? ')' returnType=FunctionParamType '{' (expression=Expression)? '}'; FunctionParam: name=ID type=FunctionParamType; @@ -124,7 +135,7 @@ FunctionParamType: // attribute Attribute: - 'attribute' name=ID '{' '}'; + 'attribute' name=ID '(' (params+=FunctionParam (',' params+=FunctionParam)*)? ')'; type TypeDeclaration = DataModel | Enum; @@ -137,11 +148,13 @@ DataModelAttribute: fragment BuiltinType: type=('String'|'Boolean'|'Int'|'DateTime'|'JSON'); -QualifiedName returns string: - ID ('.' ID)*; +// QualifiedName returns string: +// ID ('.' ID)*; hidden terminal WS: /\s+/; terminal BOOLEAN returns boolean: /true|false/; +terminal NULL: 'null'; +terminal THIS: 'this'; terminal ID: /[_a-zA-Z][\w_]*/; terminal STRING: /"[^"]*"|'[^']*'/; terminal INT returns number: /[+-]?[0-9]+/; diff --git a/packages/schema/tests/parser.test.ts b/packages/schema/tests/parser.test.ts index 9eb94964a..a076992d9 100644 --- a/packages/schema/tests/parser.test.ts +++ b/packages/schema/tests/parser.test.ts @@ -303,12 +303,10 @@ describe('Basic Tests', () => { x Int } - function foo(a Int, b Int) { - a > b + function foo(a Int, b Int) Boolean { } - function bar(items N[]) { - true + function bar(items N[]) Boolean { } `; const doc = await parse(content); @@ -320,7 +318,6 @@ describe('Basic Tests', () => { expect(foo.params.map((p) => p.type.type)).toEqual( expect.arrayContaining(['Int', 'Int']) ); - expect(foo.expression.$type).toBe(BinaryExpr); expect(bar.name).toBe('bar'); expect(bar.params[0].type.reference?.ref?.name).toBe('N'); @@ -345,115 +342,24 @@ describe('Basic Tests', () => { y Int } - function foo(n N) { + function foo(n N) Boolean { n.x < 0 } `; await parse(content); }); - // it('feature coverage', async () => { - // const content = ` - // datasource { - // provider = 'postgresql' - // url = env('DATABASE_URL') - // } - - // fragment CommonFields { - // id String @id - // createdBy User @createdBy - // updatedBy User @updatedBy - // createdAt DateTime @createdAt - // updatedAt DateTime @updatedAt - // } - - // model Space - // @deny('all', auth() == null) - // @allow('create', true) - // @allow('read', userInSpace(auth(), $this)) - // @allow('update,delete', userIsSpaceAdmin(auth(), $this)) { - // ...CommonFields - // name String - // slug String @unique - // members SpaceUser[] @cascade - // todoLists TodoList[] @cascade - // } - - // enum SpaceUserRole { - // USER - // ADMIN - // } - - // model SpaceUser - // @deny('all', auth() == null) - // @allow('create,update,delete', userIsSpaceAdmin(auth(), $this.space)) - // @allow('read', userInSpace(auth(), $this.space)) { - // ...CommonFields - // space Space - // user User - // role SpaceUserRole - // } - - // model User - // @deny('all', auth() == null) - // @allow('create', true) - // @allow('read', userInAnySpace(auth(), spaces)) - // @allow('update,delete', auth() == $this) { - // ...CommonFields - // email String @unique - // name String? - // todoList TodoList[] - // spaces SpaceUser[] @cascade - // profile Profile? @cascade - // } - - // model Profile - // @deny('all', auth() == null) - // @allow('read', userInAnySpace(auth(), $this.user.spaces)) - // @allow('create,update,delete', $this.user == auth()) { - // ...CommonFields - // user User @unique - // avatar String? - // } - - // model TodoList - // @deny('all', auth() == null) - // @allow('read', $this.owner == auth() || (userInSpace(auth(), $this.space) && !$this.private)) - // @allow('create,update,delete', $this.owner == auth() && userInSpace(auth(), $this.space)) { - // ...CommonFields - // space Space - // owner User - // title String - // content String - // private Boolean @default(true) - // todos Todo[] @cascade - // } - - // model Todo - // @deny('all', auth() == null) - // @allow('all', $this.todoList.owner == auth() || (userInSpace(auth(), $this.todoList.space) && !$this.todoList.private)) { - // ...CommonFields - // owner User - // todoList TodoList - // title String - // completedAt DateTime? - // } - - // function userInSpace(user, space) { - // exists(SpaceUser, $.space == space && $.user == user) - // } - - // function userIsSpaceAdmin(user, space) { - // exists(SpaceUser, $.space == space && $.user == user && $.role == ADMIN) - // } - - // function userInAnySpace(user, spaces) { - // find(spaces, $.user == user) - // } - // `; - - // const model = await parse(content); - - // console.log('Dump AST:', model); - // }); + it('collection predicate', async () => { + const content = ` + model M { + a N[] + @@deny(a?[x < 0]) + } + + model N { + x Int + } + `; + await parse(content); + }); }); diff --git a/packages/schema/tests/todo-sample.test.ts b/packages/schema/tests/todo-sample.test.ts new file mode 100644 index 000000000..5619027bc --- /dev/null +++ b/packages/schema/tests/todo-sample.test.ts @@ -0,0 +1,120 @@ +import { parse } from './utils'; + +describe('Basic Tests', () => { + it('sample todo schema', async () => { + const content = ` + /* + * A sample model for a collaborative Todo app + */ + + // Datasource + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + enum SpaceUserRole { + USER + ADMIN + } + + model Space { + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt + name String @length(1, 100) + slug String @unique @length(1, 20) + members SpaceUser[] + + // require login + @@deny('all', auth() == null) + // everyone can create a space + @@allow('create', true) + + // any user in the space can read the space + @@allow('read', members?[user == auth()]) + + // space admin can update and delete + @@allow('update,delete', members?[user == auth() && role == ADMIN]) + } + + model SpaceUser { + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt + space Space @cascade + user User @cascade + role SpaceUserRole + todoLists TodoList[] + + @@unique([user, space]) + + // require login + @@deny('all', auth() == null) + + // space admin can create/update/delete + @@allow('create,update,delete', space.members?[user == auth() && role == ADMIN]) + + // user can read entries for spaces which he's a member of + @@allow('read', space.members?[user == auth()]) + } + + model User { + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt + email String @unique @email + name String? @length(1, 100) + spaces SpaceUser[] + image String? @url + todoLists TodoList[] + + // can be created by anyone, even not logged in + @@allow('create', true) + + // can be read by users sharing any space + @@allow('read', spaces?[auth() == user]) + + // can only be updated and deleted by himeself + @@allow('update,delete', auth() == this) + } + + model TodoList { + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt + space Space @cascade + owner User + title String @length(1, 20) + private Boolean @default(false) + todos Todo[] + + // require login + @@deny('all', auth() == null) + + // can be read by owner or space members (only if not private) + @@allow('read', owner == auth() || (space.members?[user == auth()] && !private)) + + // can be created/updated/deleted by owner + @@allow('create,update,delete', owner == auth() && space.members?[user == auth()]) + } + + model Todo { + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt + owner User + todoList TodoList @cascade + title String + completedAt DateTime? + + // require login + @@deny('all', auth() == null) + + // owner has full access, also space members have full access (if the parent TodoList is not private) + @@allow('all', todoList.owner == auth() || (todoList.space.members?[user == auth()] && !todoList.private)) + } + `; + await parse(content); + }); +}); diff --git a/samples/todo/schema.zmodel b/samples/todo/schema.zmodel index c21e8f5c4..8e9e8b3be 100644 --- a/samples/todo/schema.zmodel +++ b/samples/todo/schema.zmodel @@ -1,6 +1,6 @@ /* - * A sample model for a collaborative Todo app - */ +* A sample model for a collaborative Todo app +*/ // Datasource datasource db { @@ -13,95 +13,71 @@ enum SpaceUserRole { ADMIN } -// Fragments are used to define fields shared across data models -fragment CommonFields { +model Space { id String @id createdAt DateTime @createdAt updatedAt DateTime @updatedAt -} - -model Space { - ...CommonFields - - // @length is a field validation rule, enforced at create/update time name String @length(1, 100) - - // @unique denotes single field unique index slug String @unique @length(1, 20) - members SpaceUser[] - todoLists TodoList[] - - // auth() is a built-in function which returns current user identity - // @@allow and @@deny are for defining access policies: - // - a request is denied if any @@deny rule evaluates to true - // - a request is denied if there isn't any @@allow rule evaluating to true - // - // Policies are enforced at db query/update/delete time // require login - @@deny('all', auth() == null) - + @@deny('all', auth() == null) // everyone can create a space @@allow('create', true) // any user in the space can read the space - // $this denotes current entity - // 'userInSpace' is a helper function defined subsequently - @@allow('read', userInSpace(auth(), $this)) + @@allow('read', members?[user == auth()]) // space admin can update and delete - @@allow('update,delete', userIsSpaceAdmin(auth(), $this)) + @@allow('update,delete', members?[user == auth() && role == ADMIN]) } -// SpaceUser is a 'join table' which models many-to-many relationship between -// Space and User model SpaceUser { - ...CommonFields - - // @cascade indicates cascading deletion from Space + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt space Space @cascade user User @cascade role SpaceUserRole - - // multi-field unique index + todoLists TodoList[] + @@unique([user, space]) // require login @@deny('all', auth() == null) // space admin can create/update/delete - @@allow('create,update,delete', userIsSpaceAdmin(auth(), space)) + @@allow('create,update,delete', space.members?[user == auth() && role == ADMIN]) // user can read entries for spaces which he's a member of - @@allow('read', userInSpace(auth(), space)) + @@allow('read', space.members?[user == auth()]) } model User { - ...CommonFields - - // @email is another validation rule + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt email String @unique @email - name String? @length(1, 100) - todoList TodoList[] spaces SpaceUser[] - - // @url is another validation rule image String? @url + todoLists TodoList[] // can be created by anyone, even not logged in @@allow('create', true) // can be read by users sharing any space - @@allow('read', userInAnySpace(auth(), spaces)) + @@allow('read', spaces?[auth() == user]) // can only be updated and deleted by himeself - @@allow('update,delete', auth() == $this) + @@allow('update,delete', auth() == this) } model TodoList { - ...CommonFields + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt space Space @cascade owner User title String @length(1, 20) @@ -112,14 +88,16 @@ model TodoList { @@deny('all', auth() == null) // can be read by owner or space members (only if not private) - @@allow('read', owner == auth() || (userInSpace(auth(), space) && !private)) + @@allow('read', owner == auth() || (space.members?[user == auth()] && !private)) // can be created/updated/deleted by owner - @@allow('create,update,delete', owner == auth() && userInSpace(auth(), space)) + @@allow('create,update,delete', owner == auth() && space.members?[user == auth()]) } model Todo { - ...CommonFields + id String @id + createdAt DateTime @createdAt + updatedAt DateTime @updatedAt owner User todoList TodoList @cascade title String @@ -129,22 +107,5 @@ model Todo { @@deny('all', auth() == null) // owner has full access, also space members have full access (if the parent TodoList is not private) - @@allow('all', todoList.owner == auth() || (userInSpace(auth(), todoList.space) && !todoList.private)) + @@allow('all', todoList.owner == auth() || (todoList.space.members?[user == auth()] && !todoList.private)) } - -// Functions are for reusing policy rules, and are inlined during code generation - -// if the user is in the space -function userInSpace(user, space) { - some(space.members, $.user == user) -} - -// if the user is the admin of the space -function userIsSpaceAdmin(user, space) { - some(space.members, $.user == user && $.role == ADMIN) -} - -// if the user is a member of any of the given spaces -function userInAnySpace(user, spaces) { - some(spaces, $.user == user) -} \ No newline at end of file From cb7ac25c3abdfac455cb392f4ff0af5f680af797 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Thu, 29 Sep 2022 20:57:16 +0800 Subject: [PATCH 5/5] add logo, improve sample --- packages/schema/asset/logo-dark.png | Bin 0 -> 27897 bytes packages/schema/asset/logo-light.png | Bin 0 -> 21433 bytes packages/schema/package.json | 6 +++++- samples/todo/schema.zmodel | 4 ++-- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 packages/schema/asset/logo-dark.png create mode 100644 packages/schema/asset/logo-light.png diff --git a/packages/schema/asset/logo-dark.png b/packages/schema/asset/logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d884fdcf043a631bf1a4bfe33e4a5ce992d8169b GIT binary patch literal 27897 zcmXtR@N)8Ruh)Sml5>j76atP@ZkZzEW4y8K<38h;=x$}$PFTk~Ca4yet&OV>LPq?e zA6(W&ZQe>Z#_7q;YN}>aWmJ2lVin+1Zs_Zw{KL z%U#i$dMaEFGXFdTNp7{_cq~tT$#Z@OX@t)@Ag~!U3tCJ zZ#gbf91Oc2PTAU{HglI+r=ON?SA8^&dZG(XLBg&Z>Qdk8?WSMMe{=K@9+hz4(|-Qt zMg9G8tiOKy!`+{hLWeLQN}V4x3zx(r_M`S+y=47w;Q7E)pOq*9eK>!BoCTvGA1x;ZaNEIAJ!H=xvf+J$x*NcaK)_14b z4-@|22l1bN$oASPN$a$5PoWh>WapwCi7ugxqY?V@1}!0hgF~x>k(1{*WRHr2un0F& zmp`(qmEzNg=oe+UM7-D^l{{771)oRd8~X(xO5owLU&fHa%*Q$Fvm$IDlosvs$!=L~ znau5P!l|pxY)6NMhH>zZ@vJJV#$F18s^77`p>+@&Dhuxusgu-usDrEZpolWbU zoz;@q7rOJ9|LRa~^scdYuFbdkjcM8KT7T>`B*(1X&v4(Y-Zn|taY^#dBVELy3=^MP zpTX}k_j$QteX(7kM!JaY_Rqpj!|z@7r0Ry`nrX9tK5L|xPMi6gjIOzkN)?s9tgJ|F zwV$o|J+J*oXI?p$zT5%b647)1?cJjI9XB<)GQ|C1GuvNUuS{RSZP9JofRYqE>uW1f z*?)Ff3@^~p;uJ|DaYieo-YtlgSJV!5pRD0&l%QMlg<1}9H5@)o`>1@XY6)+Gu2ahijjfBiHddu zOOxg&HRdN@9p*njdQnE{K6rQbON;!|)oiV`k$ec=pkS!z;(sK$%|x4I6u%2p6Y6pg zOTSe7gveL;Kin+O5p;|L_<;|-BprABQp^6wy%bZe7fWB-*+Wp9G(t8xBJUO`%L=*m z)dGNlJk>`gO?6(|g~@eBF`0DjH{m4gWu~oO2Jzgt)ggaJ{qLXsNE05~S!k?%vjIM4 zSvw;A%h^ebE!UAJq3-n?C?le8hzq}my9>k1GLz;P%Gd`>trF&cxeb-8o2bW`t@&58JbWsUg&yL9A2y}6$W4ATCd()p zwtBg_{r*5r^ze-5*6|<*?L}I@~7dD)IIwOF5PKOGFd}^UC)muKGd- z9<;Sp8pjrk*3%J*&a|U`p`t>H&R0brhL>7{U2ks=+bi!L^bgwa{u=FnQEu(GI-0Yq zRLuC5DrjB$Zn0@Q#+(cBmZNIJTa<5hP8R{xm~R-?+ZKipGE^)EEc={B6HNMUW_px? zr8juRBYh!=<3v_;>dS9x+X)CxkO9@B?vax|qx??rF~$lPjO z@{hXaI)wFgO2IIon2=5IDF3=xak~VsMESvDGnU<>$WQA01_nX~)lFXO14J0E#E@YE zf}pudl@)1zaS16I7`BDy`ziIiKaND0OhTRnolJ-Gt=`|Bw|bo;jRrotk--2$HXJA& zr4?AArf#oJe<}R}OOK+%Yz=EmcXuN30Q0at0Uv!+@)h32N&6Mv5{7ib=!YvJ$v{HH zAu}91m*3;SSUEtQUR{1n0Ya)WZ>f>vXmAYctKayMZp7eo6mZ^!%U8f{7eo0##H_T% z-Oi*KvGu-Nq{@EwG2MATuR1ce(W;p-Cv*0X!CY@NrJ5&PoI6k%8nA*lMu7R0ILh?> zpCs+n$FCDf3YbKF2@fQu)DsySgzFZ4xe*xM@HWi!0#k=z|Zw~kLC;j0nR(Foi$O8;=PZ3~FkLxKK6}yj&3xXrqGANI+U5q=t z1ICZPRJJkEJ<*SR$37Z#gPzySvbxe4MAl4&dlj!tUk&#Bk0R9WTVt}q_A_HmyA{oK z4_VS3HZUg2aBgYJxr&&6n!Ldte*P#`BV!-yubliAE%J~kbC;|H>n##@n1BM#XGHe+ zOFxo8ZbS4#v`AzP?%KKN<&p$Wfy1%a^q=9nI+s6B-TLV~wvuIzdq}20g^=Yw7eCVM zakw-FcC?~ye~pQySH+g45Byws8js;K=(G;zGj}%^%Jq(O_NqO)5r3|$pM1IN@7!Z(kf{h8mm0(ay`WI#T@o`^;UDj2!W6uQekx3aD8FeFO zd8Z#MHX)WhIsa{rWY#xnO&B+!@x5!idK0}X>U;ekU1E0(-__M#MHjy*H|c~A@97p0 z)Lqt9?8E1@vdR-T1EEaG%*r2Z^SxQEDEW~t%0-+MLBU7wSM*i_@Kk`rUm6=H0XaEj z4V?_M0+zvEx5xeG0wGS;I62U2oQH?fa0vrb?kam*^ggR&P+u5l*FH(tW#*%^0>0CH zR$hUBwIiI1r(;mM-8FkCm6cd+{JU^Ae}I{2&B~zy{V>ahXFCN)lv4sk66)^@{92Ct zXiC9CO#U9^$70D8 z|B;L**RCJEFJ-74I$jC=>gNmWje`5tXC@x*Aa zYO&d4x|y8vc44Rj`5TQ;k-IVi=Z!ibo=>NY%)mo+f77*AJ$kqF-cdOb4+R`KdrlMAA3KeGba-zrzPZvLiuLa^27Z_%!Xzj$2iBB z{*gQX11)|Goy10Qw0)i%0$|bxlCqCX%6@iOSN}Sgtz~CTAjFhW*II!eTK%?|SPgYD zMcYzD4Tw@mvJ8G) z(f|1M_pMrqVFFF2*JEHyqo6N+A55f$kp}4sx{9)H=~WvmRHKN_3Z8PiJhE`G*;{V+ zZ*${+GferRV;gL+Pp4GaHYz*t6v8}iFP)#acseVuUH8k}=N8bLHoNbOol1C({7^s} zueTnhzk}^a;AY`|{^Q7h+rV$j2m{4rVkM#Ot^DSZ;ioqt*d%QF;A?7LRGOAcL1#Tv zhjCAHzekp%f8+9JUVj8CzEHMEavt4ag?tPIUL(bvi%it$IIJ9Ht(NqN%tQ0$@BSB# zv_O#$BFN?$mN7XI$0gc}EBk`hvqmI-h2Pz^xG}kT)cNkT6>E%`^miLj2KU%b6vw}j z8_69n5O3y-#3dUqL2XTbNw!pG*42)B55EOOj;I&fyvCO8A z`}>hQ7+T;n04+LM*=gFugA+hD^k}ecH*dV3!CNMyfgA>hS9zt!yWeWiSpH7F^s|tA zpPUWFSxZO>F3R>ZJRD&|;FR7*i!K!pWel~_@=&G+YfP6LC6}rSY*xGP&s{et8VK1F z5(lB2AgBvV4dV+wl zPJhk)mNg#luUfQlR* z9(#qjyI@^7czb#a?zYDBpM2kfWWHI6ZP;O%n98c;>y&H>T}Py#p>@cSpTE#Esm(qy zW>H5eZ1n6}5349#haGj}+wy7r9pY=>ny`D=I60 zlvYrCHnTWyf3E)7o)e5%HH(ogS22=&PMqi6<6MlxK7WE*T#_Mzu-!OjSei086`YIR zABD;kcLre@QMQfQ!&CgGfxY;ME+?2vjPnFf;#^2nd;zC!EtQ1QbI9i${9!zeD~4E{ zq(mzL!hRmgMZ9mHdN}90%m*H9+=i=XJR6n=joG^!Or_E%A*k>^Ypn@dB^s97`f1a2 zM|H~4D!gu%y^wLXP^MH+t{QU>3?=i8{vzLBsn{9I4Z6QQ^nYlsC}3`G-s|Jm))~aM zd)@g&K&3OI=dLzIzbbG$wsjR0NQBi~g96A^%=JtrZm06WcdWDS8=mc#tGILyFdmyo z_gh}TlE>ZMv(M-6to3H<7Z5(^3MfJOmet-cIaC{2F*mG#R9mg^-^lRJO(-x*&mAj! z0+lwUnM%9~|;id+_u+Y3jqR<|8zNWRw<qWI!3!y6ER?8IWBj@M&HlGt!FnnPZg->rZpf;447}#FFO6j(eqvv~ z{*|b_`3%L`{Bo)8#u>GLn-%{09Hk!Kcd@;NFGo-b-n^RQ7W zcW<YHxUye91ADDATsPy9W40)ccjjYOS?0kGj41eXoWYsLAdqnz0?28X7$|hEpeu zqQ2wOh}di6QwzcDUbPtQaoZSdRL4r!@W#?fe0F92Nv4#=@w-+R7|xV@#!wX4_d6K+_lV+#mOo_nJ4udioKo-Tmn9^Hqb7=9B$F^q7&__up)#@xE(%)?6Q ziK8OVf2kz=w>Kle<2h4Uq@J30d~fD+toZRo*PYE$OQVPi(^-Q8#-|lZTl5u!4zn1t zjLjd4k2bAU%YHwZL!w>>0sOHNOi*YM@}44yZH~n67M%4%wnJ%>n6oJrdhH>8X$zR@W%;E=gUR8j~bp2<4 zAy?psVWHd0^`-e(n#bGdn0RB_=6f$}5ZuUMyiKjXhn+8#J08xaf9$nb5wn?49+en; z%XE~%7S|)5D*_vMFiYeHB&e$RiTvp-DCnHZcP>^`JN-JIT!4e}oLb-t(V2x!a85P* z_fz{4tP_kmDNh@5~PR2 z7`9mUXA+Wodc%`D_T(R>K8gRj#yg=vtuZMGj>|fG@Ah2yd0v>Zc!MD1Bx1&$>zdjn}vO`XDIZ}px zs+FNJQ?Uq@9D-3C7kF3T)#~F|&UZ_W)Hqj0IRl={jhk6t`Xf9-O$=7~dh{Q;lRnYa zG1#6ma{Qspu+U=T_8AZmhSat*vGlabJ*^2AZ>D-#dZ@9@r1E+*mR_o~0)&=f#5BXw z?tJ7Vkz)>wxIW+Y!_2(4S>3={c0;wrBA$Pn+b{;0As;|Qk#=?X^A+v5_wXb>$T}&@9ayxJlEdS<2n zKZq;hq-tB%aA)qxtM>q+r#F)syaqK2CO*f#)SBv$3}Q0KNycExaQwPK7>~jfrZPkX zXHedJUnK*UM!H<@xGZf}hK6Q6^J<;aR#bEDhVPyE6kJBLg>dj^$M&f~y}l)W1l_am z=G`y~lmB$us?k)Vm+$L-X%1b`Rn5aN^(48sxRt`Sk}2I%RSe8?FhIe;vOu2n-axG3 zBnzayl7}517pg3}eGdOLFuiSsG+Z?=-|?=tz}F%8!x*7QD|wFSSOifGc27+3Qdw*Haup{+jwsj5<$IQCv>?WPmwBlHk-=bBluO zdSdX@bUEQ1f-<48N-Lhom=#!16pvhX>c)>aE%xjCkxE!jI!u3jtN@=?W8~&9zwn+kAJc)X3;w)i=iNes?u~ z1V0otED6F5#=3hXNnaLGEoHz2!!Ua#^W&?Btn9+|R8^r^he~SI&0!k99fphU1wRcX zO2@gboXw;Rh-FGAoJWH1D)Rzt#*&HN*H}M`UAW~HGwUHCHr@G zsGYEss5S@fbN1%2R8MqG25IXe1(8F$v77*=eum$j5=8tB2~1Rs0tNBZQ8KBuZAcW+ zDC*c1?D^Q|jPZU&H>u!s{c;}HTpIe=Dj7i|fh?JCJC^*<`dj--Ag)}2t-O|?^N?T0 z$|CHtH^h!m9Fd`HI58{juXt^%x+I!+ z4UDUTQeJ1;#V6|s^FazV;jVw+FlIS{+vM)AB&K>E=aY@!8Y&{_#GM2V0#IV~j_O#T z)RV%l14{QEB%fqYuq}P+lHWwK;psn1e9QN;!dPijD4#D@dCzm151e+{?NekK{#t8S zs}>u4wKvGB{%DB{3tRNJ^QV8Ame^t*&H-@>b7;M#%K4Ji#Om~=X#uUG9ROAD8LPBk)B1VI)k6xtw^XbhduFsRd# zJPv+w=zlZ69MW;m{$4TSl@npSwN+nK`25#QY#0>?`|916eJ>jQM5scZZq}~ihg2YOZ_b;OCMkWE{uNs+I5Bgm-7)<6sw zJM#cq9d;pd<^3sVpbp)+ftCCj?pw#*$a-rd2ju*;N}|{mztDvTZ+!!~8ms*iHZQ{F zp!aNvs@?f0MqGT^BMbpzNK&iY?u!q{*gU`&(E}xN1TN$UM94(c8F$(Lp|ye-S~qJ~ z+4_k+Sj;4ngXGWx9Z=JT_!TUq6i6xWJd^w(8`aW){^XQ|YJmD9Oy2td>rni3Sn#w= zmbj;iwMYJ$QZ16CV_yZSUsm=@@24mi22hGfczsaJ77tDjg{68WdGVG%!YVG7ytU+2 z!wb#2bXcoHU8zWzaAoIuYGSW>KVJXRk||V3o~$sD(2#LA5gb$4BXB=|2lDMw(3iNz zQ-#1Gawphg^Teu`AcJm7O8{ZItsx$r_{5}ZiLu_c?G4x%o&}qB7aPNzw!)o41A5E* zpO_gp3|GH0RN5`-`3O&zT}RkKWINuVS&_)`&?uMa7M=vB=I;+yKmJs$+d^bRUpJ^k zhTqSWbT-_iC?6#gjvPnQ8Gf}d>9*gmke0I(xU6nY-~K9R%oeF?k=Oe)GN5kO^jn}Z)kE&NDbjp@g*81$nLz)chz$dWrIUD5K=^nI_ug8m~4d693?vgA2r zsxtwy<4Z{Yt|{i`Z&WmJhnBJr3+n>=0gTm=qr;^GC)}>#@HAn&7oPXCs2LkLavY~V z0{Qr6h4lURq4(b(p&_|NOByEfBG>IGlCxSdGjew1#LOFzH}T;Dg+k4kB(X!`r{-T? zl>ZkzheAU?p@OTT7|DV2X0T1Z&uOh9NlAUZy1RL|hb+Dh=6Gg5F+tmsG1E0S1v0ie zBmJNG<6Q%o(2d=XYP{hoFSzR>5@)&v(9s!B-%MG+#hhCpjqxGBg&$jVK`{jTC0tTm zd&CPg28~pvZ(kk7;8VlL5b?dGM!X%PJfbK+BU`08-g~Nj}VJLEeGdFXrTSjyAJO$4&=pgAiXTjI44q}7=a9r z$DfaC*d3nOdS!*Pg#ihKpMgWD@n|`6Bwea*{?lITrr0rP+F2Prou?j*_#g&)ZHk@4 z3O=8UmrrQ@1$9=XYYs7Cz(>hdvIIm3O3;@@HaL#>^QMoM2qUHcn-OG3!*)K92Al?H z09#^Xd4E(wv_FXl+`8|+sEZBsF!yBR{U98ep1OuUs8mO{y};)%;K9T~Y_J=yJ$V5^ zxk_34McC<8nl3u~gAOHUGzECQ?%>SQbM zU=!*4pKc{@{2*?Yvl5yuf0`V_OS^1oU!e%WWRmWjTG8!HGjDtqPPXoZDra5`Cs$tu zJCaU?c*-m-x8Fa5$(x^9dO9zc8Pt+mi$z$>Ja^G{gT+TDz79=Pwt?5lSH&VGQmmic zf6528WM6r(c9rRUd!1~}$f)n6_=kz?CacYKIDUD)!<_{JP%tnypz>DSlen^$rI#q5 zOuL4+qTG7Lk<1-V(G+rs9;W)&m>H^O0{TTwcm_%oTbIXc;UST;Sb2X@JzhJ%hePiE zmjx&(I$2bve8aNL86b9fp!SKIib)Q4RZWGRcn&(c;veN3j}nE^cAaol8T}1sZoSO( zSi)xRdfaJ4-8lA(s?ebY!)T_B{kQdao}z`PlO+aV&brwZsTLKeyA2CKWuonqp>=Ek zRua{sRcr>8je};KCd!-+3%FhH=IT@x1Fe}TH$lKZ^vi!C7zE~Hm{i(<&cBGr?b=xy zv2gi*w-mos*q%K^FGHt@z4Q!kRZfVbgx2A?6HJJ+NMBff31j1L$x$9tS|#0DZf~!s z!ef4FjJmvd`#dGZ?RTQV+Ih$iBw}H)0pqPGMT2mmWZKWZe4--V!lyf+%@cX`uBJV# z>yd7OO506XCVTevY9t?lzp@)bS3G=-Ih}wkwl)RPva+TuwH6v~j+R4^<+1d>;w@?e zZCw@p#I=&z5sI}u^+PbUQDJ*k}YcrsWz5BH#$9gP80>_*L zRqaF);SlyCgc`@5pmle~8H#p#fp=7g~u|(Q?T^awy7uJIOPvi~odyOH7Cd zJHkwIt!&&#Yp~&1sh8z;JpzTnOZd3ZHQF-`7zbmcier~Zcnz09O+m~u)_(y<<{w{& z5y(GR#yO3ew~T3U*XPJn9V5rxt{LK0!=-CYJIkRP>T-%C(jE)`XQ9R;l|nd^S%O$` z^&cVFR`8=}pFAe~uG8|K|5^OA4_NV^K1hHO2gS=>!BI()ybQsCNA6DQ5&|>ZI^$^b z9Wx=v(1D7p)6LCo!4QRDNS~MJ0Fj{XtVEH6Vly8PE`^#T=97`cCwmKx&x&4tj8O~h1#24ZfuO*H z!v3J9dg4n-QF?0FJ4DS191liz`#?z$e|<~7yUU*KBgf$g8%D~7>8wFDaVFK7APJel zD_6rhs5q0Qkt<{F3t|L0;oOi$<=b@O3-A*KV*c6CS>zjw0|D^*lXkBq^Md$*%2=5M zeftsN>Z-AUQxU<#VHIib`36}0)aBvYbFX?Cqf$N2)@~B_!SMih1;hF1AZ}t#;{diU zL9qQj#oae9Ph5F3O$t26nU7z8tAc(Um$%RoR?pmP*^BB4qO_5s#Dy9V;(uyareaptX7rCDh|@w;sx7^^-!`@6Y2RsLnk zYqvbCAA=JTC_s2C>2s&~)Fk&_kP(RzoYnW*C!UqeLP;U_B@Y3+wyE2Wp?z0>|%V~m-xd|Q&$n3C^LUYYDfrUe@7+k|5>8`pv+&b44T zZVmw>YiA@WC-K(>FV#y=5)z+mQygyN2JL+1bVZF;aZK1f12e>l&Ge~DK|Jnh?VHgj zo3x{sQ?(&`nQSlVwV}@WP8EI|sn$X+1W(R`FLZ6t_Pk>-8&sz%%@_^{t!qIpr?|)9 zj^^%vG*c{U{pqL34Pyr_DPd<=C=RQl%qy1?EEUyvH|_uL8~x!@cOE8)O{ zL%ycil$_|rQr#nb@4!@+dYXU2JH$`(l6OJN*(UUC%1Il0S^{`-o&s|gFz7PfvV<&w zt_fB&@Ok%Vp<06@)ZTufK}V45g5)S6A%Ud!5QK0!J>SSBJ&DJV>SI`8Diespd~7Dj ziIL?k#_8gjN`aSd(BC`x0PwMVL~UdfP)P~ImyL?8MHO6$Zeud(lz?on=_ePt|AIe1 zjgxWDW&&vd8T~vEAl9%B>xpl;FH}p`9b{jq1gX1S62lr6o}}}t0{^)@$QD_+MJ*px z?|KZiY4H57TT0nm)bv6v`_K#gdhPhj?orMFvEiuUah>0pjmIhERv5j9#NRpjc3 z`Nf|MDbRDX+nFe7VhCPHqjF3#fSSdLdNoZR}8A##WvL&~>ul1cCGbjFtbW;~=f|jw) zecps1ALXmSm`mGw^wnM`fzZbWA6QFY7f-NpoqJpKXL}T6qh!2dc;PIzVdKnc*IFwC zaPt|`F{CHMPs1*samX+vhBP{7kAgl7ba`zq55QE0w`PI@JG&=uEh&JJ_R|3}{yG$M z)c)}PaDPw<)2nIbi8p!x=@%hlnQKh>`FM|;JKU8$LkH(>xVs1AfxA)sy0SznnoKx%{Kv8kKjqPEpT=yl07aNH>1lKa>1N(iFb&aSdq74 zEKhc&?`gT-`65-Q%9KDXxyFD4@;wTi1*Ac<-xy@fUZAGi>IjpPj86PN6`Mu2Gwp(H zPNT17A%k)zU4Pz$=rBQhoC1Wi537ja*ea>^l*D0`AXGHczg3-}0?lRKjz%yvXY!hY zR=P_V|uigV}T^_W3oNw>=c~U3IJ;| zC)11FiIN0Id~^6K5{vSl(#!OBk5sydY3$2LZ}jg@>^t)xRj)DS=KanJKVv!i48G)g zvEfQV0#Q$~k46))4%Ze**VEABm%U{zG6#biSJxv%3ab~rP6^Gso6X0#N)E~E2vLy~0x``YmnmA4S2M3tv zM^sV4;cmQ&5(P|rVIoVZ>L=J^j;cz!!2MH^I` z1M$pcN9r?-9kvO9F~@yWK4wRp9ccx`NvWwgQBu`B2+f;g7@%dIK`PqBGV&yqM+*vs zi$X_JB);DL+3={DPx~MQVmDPc`Y&KEByF(Q6!jsC_72pFb{=%4BeT}o<@lZpYGkG( z2R%e7gES}73H(3VQF5IW>uLNJkTAKdu}9SHZLIN|fn400_3oolU z;hgLrh#oH2VxLBQzl?`8%&7W%pc^FD0W$s+5jM;bz1Dmt1!0|hp8XpnUnE|4(* zH0mfu&--^j4?IvrK7g@_^DDej;P?&(;KlL_c76B@r&f`NqRl*HEW#-v?3yN!fd8km z--q~aoO7YR5gKUrhLri?9Kse`0oBaGCN*kz3d&PbEkQ4u9p**)efBT=RDuduu#OZy z<_$DTrD1kRA>TPGb&fGv|F_^9*g?!W&{;RKCY)m`ULhYtO57p8tezaUy(@YXZHsk~ z9&nR2ad}q<1h#pqp&{kQpMnDvl__&#UAmO_*Kd3$Il|$ZiOUmB<_XM3M_@e{nD*4g z10*TXBv+S1k7pM1d!Hs;DKE+NQt2G`3WvcN{j(!YXk}4-sBylJX}s9VA9H&Jjaw)> z;rLBA>w^h%8Y(x9yh8X!vi|n&wcnf6{cf#w9;t{hO8|pa_Op* z94@c;#qwo>ro=fDkB^BLs!cTZuttC7(kWtZlXS6xy^5Q); zRengY*u42zqWV6L5FA_TV5GiMShhK(CG(hKk}ddT<9`**J0R7fV1WBb8BZ z$uxE!s{;nc4TPNa9>6vn8bUxA8#OCDRrN|7_YLBS+r zfD8lJaj%!^eOq|yi1h?9P~C58(tGb;p^a?^sBEbSEYeB=l-po_sB3Lxh8v4dQv%~% z(>7Z)@9PT%@G9N@M|}CILNk(%piOq37fig_fI7AbasnDmc@-DIRJmXv9!IQFCAsnR zR8)d@SkihVW31)@E}HFP0L%>Rjnnyu8?pvf%>OOkFNSc zj^SwLkeJVQfq@sV*fE$UKMMtoywaGPw)(6T37ESo+O&cm1GTybD14W20ok!=5pCCG zHo5TC94YU=dVu#z*IHRD*>V(|6wMqHVgXwx7RK^WW*Ovy07DL-+sj0dGT6h~;C*or zjA8bfO~ljj4F}NDikwQ95_4V+?rS9-xEem=Jv7pgpcWPcA>bD-JM=aZ{7ohcB@Qr1 z4Uyx@0+4X07)X^l82-UAmT`BUAT2Aif!wC>B2#GPF^3}pv`;R7c%=5@F3G0*u)T-G zU!R{(o2_A}cn4NZy3B zgF6TjnUYHwu5Dm*2Yj~{{70^8zL3b_!Nc};^!ZtNQDL9K6Bg2Ag)l@r?4D##4Tb!@ z0)LMY$Oj`*ewo$0QeDORuZq~5`S_aFhd~?3aRkzpQe*8jpItUnO#7x{n~ShxQ+Spiea6!+7_hkeZW z6`l*j5>PW02?riEfu}4iK?pQNB3ptGn=>tV8-26IjQVuXMXaOeW*Sn;J0No# zZH-dzSa{U_ySjUUzYzumMi1L`u&RC9s#8D49H5HQ4^SB>*1vym_U=PYFx2!h35jI- z9VtRFMfdzK{yG>R(pj&2B`%7@JLvK_g6lN?^Dm87`UKHeJxRqYa2?_4U2rOtlx+mV?$O6LsE5Nje@O@tCTN_4@BfCj1?U{Y; zgtg?MR!n9vI>42H|2QOhJJiVu!e61A?@fpY{G&qY929WPf2EyhzjY)y%CY^kx z-`eg}t)zK_Lc)3207}EK&L+MEKnUVIr>i$cO1|p81#+3bwpx ze@c8;--m#J(IHjrx$p7;U^}!M#%xrBGY`Im<1Z>u68fIb0n%jW%8fb0AtMTbGFRe#KT@#}6v85>EYyAKqzKgM{Q z+#UVtio7_Cz9s)i2Vi;BHM?(4WB;_QSAVHW?-|z|5lZ`-O(P8`rfCBAe)I|=rk#aR z|MHDaa&q>a<3%UDk78fRsXlsD0HtF`R|du~m82xmjlSBW9r9_lhu~4=<)(m|PPTA)A=+uSBIdy12Ofr)^xL{98{abi!K* z<$c)t5)3(yw&DX&Qp#u(1|uLR1Kb1piJyuIr_?PyZs5tbCqLYpuFOsobIyCQU9u#f ziG;?|0eZ&q3;hDG!Lq#a?d>=7f`U*zA~Bn9N7^06hr}dEFA3YP%e>e>yD?Gc?I7?y z3iC!o5y7pxz|dH%GXW_b-HS+kl(AKmYs0qFY;IQmeRDKEKL(2BK|NhW!{{3On8&cL zc!YfT!wErms4ut$WXSJ(Hm=NwNZ%%`=mF)b*iZXgk+-t`ppbr!)(q{Iw+7DXD*9TYFJ-dL(vgE#6c8V^B$i9 ztwph$-L^PS=fl>E9g}wJ(JaOj0@NY-KGVz7vC8lQcZ}ovch-o8WloGXaM^=S4|AP7 zwe-YT(q6r2zi3rj2u(HK?5lk$xPsQxxeKDP+ggG<@en(LZ_ zYckiQ+MqU(^jULXED91QhV?YFFu}uW!F<7VCcptcs((vILOn-ujPgQjR#oXwTtADn zfeBP=a#Z#@{m{Yw84TCNDsSLQc_#|gz#ckSzLge~Mt{wu& z(B^Dl*TGqV@R_!F(znfmD~Wkk<=MQ>m9FX(>heF6vG6EY4q%8NYB8rACSpQ2S}O*1 zuU@j3!R6Wf{0Hso$_f~e4y~c8m2-?>PQBXz`gx%Kb5hs|o;N)UwXRmmZ@Ld|}Kw*pnzhbB7a!vHBM^B;d z_{3wR2X4YFyahfgIDz{08KsU4UK4Ms5^*X!)qB4b_fjkjq5!{?@W5HDnw=Q~zUIBh z%dau!IC=Yl@rcfoJusXsfrf~dzFY~yiIcsT@wu$(Q=s5r5mjo^!pU zv$5j_oNaM}kW2&ySlGw|MDfB`rd6|_9x{>6R<+LT|wWg3>xXahv%UBTG4V3cL$ z)-E^^%*XCrdXrG#YimK#ukI*}on!+GVjlDhxD^WM2GxSG`W+b?m~&7PINC&!w_Z2T z{73-Hgf&q&G=5`{r6zPq9zZ+5STC9!3>~KhBr-ToKSY;2<%QoZ=>H1HT*jOHBj`-Q z7D2M|%8*3{xr2iU3wsI3!jL^+2pcNbfz24?P5TVB-f*$(FT;g_thVe;l+xkl4FbYo z$bOJisx2nJm~$;S9Se_R%FoZUh;j z@H(=KH#|lGK z&>J+(le0nif9NC|Z)U^GWdXI9m8SSMs^H~jv~;TE!m0Wk9$XH_+~-8ZU(yXredx;umyC~b4Pa|-Ug=8oPI?;*+Z!39&% zg9hEH+??MKEwm~$wERN7EWD{$2=ZF#wF7a=RE5bGFfp;AiGB%+an1@V+vT=a!z%ND zH-5I4;ge?HiEs%J;7SqNBW`M&&}6P)b-AVR=kBvLuNjfl|Lk8Fz!y$iNA?-0r~~Dr zzM0=u;a9X3@|L46+|EVUD1&$cmk0+u9^jiNX!*LCjAAkP@C%^*TbbC$hXaH+sY~~b z`kTi@g2)rn*n01wQtOT zs6^*G1(sjVgL=(5jVb&5be_V^+kUE&(9A@YUeQscNf! z2oHixfp^_O6YP*Lq2NYxJFg?5&*Jl!CVz11ADOTu8Urz#z(x#%%+g%F-AhXdN_)ki z_Dvz_!@_>N>=8uZF-{FXBoaqS*meR2MK--KWb_&ZJKq>T&-d|vNyZt<)@_)5Chdp2 zI0Tl~kvs?gfsCaqxU86!0(4*=foa?8r@%l{f{H>5nVIi-62xfi;DAHHPKeSLhZFgm zdSR|3&<(`VR#vD_JUi`XP#}uWw-ctKDj1Og{&$x%0l26+h?gco{VGKP=&nl45)UjGppXq>M?x@Ls<&k3c@;3BB82In=@;4hn*nVt-}>7s;p#Sassz>hBu z^IvDs7O;V8G{imm{6`ZsdwR4K>MVoQg_xE&_|D0XOZ=sg(6G~GN>PvP%u1#%uAfdlnG z#i<^UQ%a(SRLG^$-1~1obqPaN%nK~=!Wm-DuwX1KodC@ess)Nzh`D*(R*16$n5O{u zTw1wLOxV;@R@BfC{lq7)imS-5Ik^rF!UE8RA=6kN#0v?;@zU+%MnQ(ov*20G)_|LE zOc^%*p)1b@9rQaUathH@Y+7E^`5Y!fSo(DApGA!R2W|@50f`l-Ypl1>U>Tyu`0v5} zWWc{{95%FuAJC=Ih9VCPBsinZK`lZq@fXAqpSL%saLuXF{y46rJ}HG0t0%64;*{Pa z$}giON9#TxutD0ts3}S0(2)ZPxM|Q5OfoCmi>K4;%;RKzuoUd_SDuW#uIA{-k2xo2 zd$Z33OBVmcZ{^IEW9uTT!nj5qEObbcQvA3L>r}Al1vrE4Og(>paPCjBc?jCXp}^P} z&8|!nb$o|CERxTDmVy6qOJByM=7uC`zRW<74hwOPHtg5;nqW1HiZ)!N0NphggT<~P zpH4UG2_{^>Gj$Pq;UuRnae6A_^h%Z&MY4l&(NGAK98Vj&YUqC{(=Eg~GJlt@x_j62 z{cQV}a`K_X`NT7lUqILy`c_b#`3SM4F?`J=k6G@ig?>R_PQiK8TeaXgT87IX2ZTbN z+B6;btU=zH+NfX#JX>&^#v~>F){ttZt|;JlI2JsP6&dB z()~V!eay5N zvGZ!l?`Gek?dngmSz?amO*o1eQ$3rb7Ay8&OgQ}iIy&p9Cf_!WZ-c>L#OQ{Bl!|oc z=#*AMKuSPV1W`IhN;gWEfZ$J%ZX^W}BqRhxTDn70c<;Ucd5-6JJbSkLzOHY4p2v`e zNvA#F!4gBmU1z;l%^c2b1PFGDVHGXpq*xY(dV;1adUA+T5OF!EQB(n|$jE|yRrNYV zoLC-S@%q*7Z1%TFpSz$+tOHJXVK#I*8!uGPwU8S_#Kh!R1?u1+4P+b9FskvlBO`)3 zv;&1OCe&J?obwm{i+|pU`9h53y;=G-=t3YpohT<(1 zmCob0zDPCsogLU*B&EPTpS0-GiAgfXGf51Bo8YOjJQ$a8nY(0cf-5+hDsXn_`Omhi zBKpv`l7W51Q1%Tl=Mr+7t$_+%JrJXk2d8xDIzmpUb%;?A{6jkg&kQrGbzuM=Osq8C zBi4q6ydA_9vKk;xKg+Yt8(j12u99Ki^X*Vlk|_4!IOzP_^;HWs8Ke=oB_K=0}K@!o%L_~AX? zY}zu9>++%v>Dq_r#37pY2kr_XpU-k(rsp;<^7aesflA#B3Qeh35)^cGKi}0MA!C8! zPKL{Nt+&e|75_=wT3lRR>&|Gsa^Xn%3*D2>$IWz=t_CMI`XB-7Il#S??T*H3bd3gyrrx?&1+FvJPHt<$YYn)5oYA)^!q~6o z=ZaRbtx6^}b<83v;E)}|wYjQKc#*oXM*s$3))D#=oeIYk9SRCF5-QARfmMf(FUU?Y z{ssm?)g!0z090m2!&rcpuTUJ`T$s_Uj8`czj7#&Z-SR09+60}>hxi_@Otcyz_Z z1@WR!W#jH9s|dUf$Y#-)ZT0r18Mdptxz}U(5XIh20l~|4LX$|F+jE>OQL>9BF^AtS zZ{SdIkziD*bsmSY&p&4!FvWR(AHWBn+WSv^fnfuBstKteBGx6GB~A)FWp^I2BAwLL zF^Oh->_l)4`8)#3PJ&q`A8x%v*k$g}wos6~VLOw*?<5=(k7mz-9wTmWCo>UovfJI} z_p1EFD+mIhVy^S;vM`7a7BFNkz(_Dczt};iaJh$vk(}^9`Rxc)(QixwkXUeJDvCJ` zW7zOe_^&p`D(ir}u6FduL|0sOvX2-hKHX(8){+rp~C6q_82Xx8Re1Whd`hU2)RQFNlh3r zpr1m0>Yuk``xGR>OT>Vra_3pYWRT5gV2 zH-`+Qi>SNRwbS%!PuYBG-jV+470`z=+{4rNcHLdb;FQk?Pu%?Y6Ie%fn2t#r2+JTX zkiK|YWv&xDCQxo5{A~2IM#F)mqx!0-J@fhJQUq(VPcjbufk9i8M!nK}!&oq-m786+ z>NA2Dis*{?MXHD$3+g!KOJxWyeyvyoDLep=`#2F_06I-HXFvyms9Q)j5PY#b9W&B1 z)8RfTkaD$WFNZFVfhKgPm|Pa6bc28luON5^@GTZ5t+)J^!+c^0vgu-GxqpZ&c7!E0 z1dOM#ORu(0)!ff#eRt(lg+hU|u=d*n6uYJOAxL}a{Slja(8Sm*;Nt$UCo$(|_dQ*N zkSVPESI@r6Q#@vpDBkJ%i27DSC0V`Q!Ak=+10h|a$rI&qiT&zdJjBu?t ze5KL;Rc2XkJq$ji6&DJ7?RY+`tL~-HHOE^k%gd%kTB$E;!dZUXv+=1?2<0R`?rp>q zyTmmU>WetY(2SKf&QVy(QZf6lC>FYQN6@%A2g+lKO;@-X*paK-)mKejfdb>4ulSOh z!EzFku!Al>P)L3%khiwlM|Qr#0tcX;dRBrGWk-jmAy_KKl=u^m!2b`vLSp+s>Doy|$#Y3vzHWX34x8%5FCA z?t=4M{w0VbZVDeZa;KoQghXpnc z1cQyaXf6-G^JakbfQK@W`~y^|@G!_6J1>G>$(lmscc~E%R|f{b4t}~i0)=-(9G^#p zWT(%CxW;Kj$sjNMJCwbh(RS7G*_3v`7cJ?2Y8reP$sI4?OL%Y4c5fOPBAGe9XGu}v zWr%->ypCKpX;E?~*cll; ztn@Z2-mBvT9s2*@Y?giCF}FlSVBCA0^}M#Ra~tq$v%DnDaRbVKLZ7h36x|+ zD3**~5OUkqQ1li(pP{RI<)eOD_;l@40wBF2^W}n1;$v^7x;zso`Nkcg7VY)|;F_eh zf8K2_cZsvb4u7C@eE+f;`uz%nz=y0}f;yv?9`A;Nnx@j55z0Zp#o9B#oPRqbgW0Yv z)EiTk>*oiD(-7j>EPqYZ9PRsFh<{QX4||H*%1Zgy6DSWNN~&Bead=katr~xvx=#nE z@8Uyc*2C_4+mAgrh(!=<)!FKE;ES*)da*)XK@n9ebdwQU;u@;KD(@#b22%c(vs=Kq z>3ds`vFHMnqM~)f8YcrW0OkmeN-8$uup`dVyaNa01AKdlCqnJkRhsE_fbq#TwIXC# z4>k)(K}$Z_O#$d1Df(jYw|Ng|I7<#nR2m~S7lc4g3-6V{r9zKFlH4$XijxpQB+~jL zm?zN(BUI8w9#Vv5&G`^3K4CNWJC{BIntANSoSFQZ?Og<`V+YUUhLXd616xNd@XGJI zN&pIm3bP|p!k)vy12!0Q%K>F$%Yd^#=K=gr@epJ!#P}!{fJ35Ud^B2ci^Ttt2!w}n zJn-z`Pd-ox^eNEc4iO_mn~U|O1*J}q{k`jVt#j31VmFovR~++DTqb2pmvN6ocO-HA zDBR@h8Q8*zxiv~=7240=%(rt`nlYbexqB0kG!%GeCJ?@nr$7SEJQ2`Rj82N-Y%^P( z3%Iz`J03dWq(rXD_!H2$%J`eNs;Eh({F4{sIOVZ^@cN2}pi*WsNsi`khT3%Q_)^F& zm=J3`(-tQrcH{?DE#&pnhHifLHmT2s)yf#{nYfP8FyPsOYuc4f7@qo6OFa%cMy!*9 zAqnr~1*p2L_cbqM3_AYx8DVEoi_49m8tibqF@Afj+4H&TG8>~Jcg)Yo7%js<3K+8q z=%Q^&Ue8_0>(PnrkDYSdjrMB_!){%va7Ll?$dh^WxG#oA_qm0=J4DjowS#|(8X}Ac zYTo_)OZw$nLOEM{BWq)z$Zsxr#rgg1j^{29je;Oul~O zpMz+dgez2~7ufZ}{#`~=-tppZ=EalzcN|2Sn zy$-@MQ0d{i$*Q}tpwSI8hSEf1(H6G?8kcA61rFBUUcvuqu)qOUie2@a7qC!7^}|!@ zL7-|^OxIqV2omYGKtB&JYz6tdVhVb$pv6T=^x1i7=E~X~ByCn<23Z!a@v> z16Dt^U41w_v!Nfn1~IUqQM^9(zVA0Q-FGQ{S)3+7yLCU*Q|>Y64?zGBDaH_e<6O%8 zhs*N5niN1PS`QQjnA?t`WdC>)&j97zd7(3GK{~1A5?C8n^vtxJxAhBOcp9~l^USn? zSwHFBBGInT4-L_8Hm`~hJmn2KNLY0R??2VWY6yZ*4)AqyehisaQwGPW6=8IB<8B!g zgogV&z9sy;1K=487k@OIR(?3*NtyMwr+^c6lxp_4Fwry=PXtSZ-iVr`hZ|W+cCV{M zW#WP;rr}1q$oKzK!D`aSc;%bF|O*zmi=c-$~-#mkyi zP_6BKVR+ELF0I+mkt_Tts=U;t=|N$8$!J{hf&6(`EbLPlet04h3%$upo22#4_oukC z{NC>rU%cXSFgD@>95)3I29VbD2QrsryLWTVqG5mgrn|A;;A!;?xV+LHsX;)b$D3X~ zVd3VBc}~0j^4DX5owrK}!_>BIWG8+IgqyiN42Od8!NpzJBjIk7(O3A-ICX>6+5C-T z8AQS%0xZmUF?7Rw-6taThWroOoi9xnBz~-!WeUpwQ+**W131 z!8aC9*i)VO?c-zVg*aXU9MgXQl<^?14r;1^D^bYipIReJ$;?X}Z@ zG&P9xqwkgbD*x?t$xRZwlD^1gp^#+A;QDWW<;~|qzcTMxYgrC}<8UXw`JL0g>k-}k zW%?OFCz=4_mvXzN5lgx|BNa*aB+kF{$Z)`8F&gOw4;A%<5*J}CU+c06`ok0Su)#YL zQe$^dE&6iFJL-254dP899X7AHIFq+~5^x5~H>q8ws=gf7{_zgk=WfEawatSA4iMcN z)!pQI;rVew`FwG}>#BODWm`7Vc)IKQFVM3i6)*ur=?hmt=m6qkAwQ3`y&biC;g0DZ=$ zXPVN!Kp83n`%Req#g<4ICgJzPbFxvRqjzBH%7EH9@vM!PRv9!(5hJSZ-5R`7%8lW} z5RDvd!bC2cRt*UzRGfNgLN{U8X2`6&rf@2lD+ z1}w!&2n!QVVJ@8f3w?~htyQ}C6fpmo6rCwpqiK7{`n}Dfybt7Zf^f=KU=9+G_SLEB z@5kz*(gLE8!$&S82~P#-2SI@)rAjQRDm*XBon&)Ap~jl~xQiXn#Z zdH^5q9+)TYavFS+%O%Yf<^7_aW?__^V9gmvZrDmslj43GNpzg*`Uv&ZPdFv!6%JA7 zJlo>UxXoZHP%Rvn_Xi^*PIGgUrmPxVg;lh7%nNHp2iN*A!I3LG6$b;Ag1!J$SmO`- z>DKa5IZhBE+Gn~A+$XJ7*`in(Gp>d)j6M7#6yHk01|hlLmg!p0=IM{Z$j%&?sEq4uOZ=gHxLg`z zIK{D9l>IV?)as#Vq(|xl8grxxKYrD)bo#!Ag3fX7=NBXtHmKVay5f*6TJZnB+t2*| z50`GF4**BqYkK+keZAv9|0;-?WQl)CtV2zP7t=7AeNX_f?#JYh##@SDLqUf`j9q-XPnD}$+&hGw-UZlW$?rq1TvPNi1S6b=gA4JpfJQbEd*4ryKKC z^c|SvAe*49_fb^)d)%URS@mWDBl;yH^x*d)UnbPuG<5g+?t<+!2j}D0dv1{wLchd9 z`a}1n{<8B8f~BWs@Ui*BmCy#*GFCeD&;6JX-JCl*C)CDIyTD%>*&J~e#8Ykqnk(4? z!xRoh`CrwOKoHKl56sCLbc{WyR`OWugm!{E`eiLoDQsJn)0uNyY3y`J{zAv3bjAN~ zY|K^PNXQ(_j5{^zM`N5n4}{(iY?H4N)oLIDW7RJyMOk)*J!6gZQ)nFt)!m~r?@>^`_&u5%I=`lld zml`R&>a_rnMM24`tMX$i!H$A_3fMyl*Eed28+yBA2K&=^>Cq~^j3l%j+Zf9Qkn z|G~CJPlob*W6(~4=ua?6IL5LmSUFl%9U8Ts6}`3kL82BTuM6M-gbjtouUyd4s;Ek!U*C~?AB#uZXW0xEm9 zHdU)%t{cKJ>aKr^unfAlcDkS9OIqU66yqOK;}Y8YOig z6g~*znviJzCJiP%8*WiZK=S(_eMJ;1d;OJYt|cTQjIEw!UIM`j(clQ9ivDYDYYg<9 zHwgzZd$wv;8{zqybsb{7ZZiiT<+O*6x|$xhp=Xdr3D>iLt2;NubHoamH5@^N*OCmr z_{-S2dar%k-+}nbOFgk%gz_!EBCEN62ayCXwkIp!>7Ls3IzF41R}E83M)N&Duisr0 zkn(MYmc+iHLZj>o%w$1SUz#tyHYz`HSE{q;+s%2i&J+7@&Jmo66iIy#$NjmILX zoBZ(B01P7Sy7OrD(7j@qRAMI2bv5xT-XZ02j|CZ<{ET^(eWX>i`|995)3fb5wiY&U zc+`-+f$i$iKbUz}X#39RhE9Wu(Y#a*c4l~?B)?%q95%H*^@q#;_l%#ty1Jmv`@BGy z*r3pt8oD&pLnk>eQQ@-2Gb@Oo5C9MuaRyX4p*fP%W7o<{$HX}ap^Qiy0p6#gL&ficaGfeZr=SZO(0 zBatC;%JJjf4ADvuJ1>{Oo7(^Ghm_ePTxbKUkf&Jcih0POL+Wh~6Dg13=SF!N54{r4 zWdD-vT!3!PM<8Wyz>3PHlY=uwW}y;x9I;+FFl`nWBuCeR&t*0-_M}R1>QNqiRqt}m zDmCsPm=*n3CAex9Y-WQ5s&{)&U*Bs3Zu^3mS>uaG+t-Z4anXL=AXwk9#-CENS>ZJm~^0tChXN%B&&^5m4 zH-roS!3Fh8;q4*6Q>f=a$ZOLHV}Z4(BKk^!WueaxM()@Bav61WpVsU?A*Z^5wRVkT zE+hwwv4Q3y=cs1s5n<`1A@g!cj z{eEU07PPfO+R_rG%B@|^$erpg$U_;E<&4IS4nD}`bj@uDTf~Vcr0=xZ4xgu<*8`NW&4+1 zkYAO!!Tv?JMt>}z!_T#P!pQ%*8trS75ZuqAU|GaORC8;*`En*MCC2tOl_GY!-&-PE zJP53pxBRB*9~-3h1qFf;OBn|l1Zw$@`_C!uCslDx7Q%b!(n-dE1fmDR zus^u^Hyd2={Li%P(G5d!rtf)xMV0(va|LQo^O=Fv<>-h|-f^++syVA>VMkLAFFPBNvdg$u6?GOLKUU5x@@jVsN zD(bqe59StCG`Wi;eMB0*k`GicU!3z_hg>+#70_;*jOl{E$reNY#6F2%O;^`7ZAfWBo$;bF>?3teK0_XgD#(ym*qU*Mw z%>efx$}&^hPxsgRBy#m20CRkDoRw|z@hGKjF-FgxrAJwmaK^8*TabTuDKM&9f2a~{ zQPOf5$dK&3_09@P0qfn&VU3yaNp?wH*lpckEg_toPwBh!INcvg59^-x9&xM3cS8*LaYL0(h=hav+CktGjcZh zEXTXJ#8T_oN~X7bS9bqdmb(x5J|BAm`Pk6D)8?lQT~GF7XVFO-e7nK82R3@B`#)|S$w~idoK=Lgp>bXUz>r!#1kewRq%6nnz^{PgK8riw+-_0gA z>k>ob#VAa651NpZ(WMgrbe-?9C_e8^UX}_NSPDKeOY)rj1AH3gXHF^+A5ANj*tFYs zZ?I$!J8x+_aqMe_MRJE!ph!avU*HJ;pCrs4dh*Y5L?5@-$oyU1+zK?E3C9;4u*QcQ!Wpj^RI=&HdP8Tq=dg8?sb-XdqM3L@ z70P%Ym0Go6agzU0VUEF>>--7cOWi`VLi2r)^B5m-3ME+vD!>5o?6)Xg?j{8A1wY*A zBfecjA3a0Tu9*_oCu>sgV)<;po(T`9oPE7em${Ka7Q(dZnG^!vQSiZz5ae4sH&iHgJu zK{Yu>@E<_URdK#!_@}Dkcai*^AZqHAS}tf6fRy1XMHT{B%HL36f0s-~+H5qsuZ9C% zzYx1sa9$;4(oGA2bEo>K{*f5>4t*`yf={0=jsoRf5(kG~x(BL2nWY)ekpFjNl>HpC zbeL%eeb#wYBhdElF~o1kw8?Wf=bmipz3jUwk#;PEoZ++2MtW2(l0K|%#jx-8rI4bh z+$bp!2LkGl(K5PHOQkR=U+vmluz5YX#I)dN?KehnIROnre(95GB}@jE-??x+htuZL z+;nwyCvzY$5sJvfG}dq)&!C!@TO{*zPbVwbH&1VFN@soT-1t!xUEnOR!#RkBec^QG zdB$ztB$aqoc))Nu#ivWlg>2AB)!1y=758Q~4z!Yp#Bq0G*pL-|#&&Ri*5<1+Jc zj$DX|6)3BAz2r42j;Zp!4Z1+ZT`BO;iH{QPevt-=ONAdZfl0Nlp;$%vylK7oW1;(f z4`~AeMiB>+%6;j;2f|-Ddu4u!K{ScpSfctRNq`G_6*I68)D4%OWza0wlDTUJEDndqMId?S+#Nw7-Jw1| zzI@v5`{K6T>#ziTgxiDy02;Bx-WfU0h+C}+fVf0qBy{x`E3a_uPK&UTDCg9u~Fu0d9Sx3uw84Xn?s}b8P z1HF}yl|Bzrh)*;N)=d+8S9)au#MDPzWc^kJiPq~uxZ`C_ogm5L^HoyIt0xVm)o9*2r~k)GW(WYk!|@7NC54v~1M4b#X?u2v zz?BSA%pTleH^1m6_B2=Y4`9hbbvB%yx;##gyg&|MD7mb_czHBMl_iM}*?4FFBV9dI z<(J;7yP%FVaoGH?_hZN4DlQ$Ga$+`}t$#YT=p>hjY~P1SFU^WnOB$R+o+^j+wyOol ztGL@1-Z)AN&Ii`X_^K~G<;phT8P9kwnW3-IhLQq0@n{N+o)HAAuHkbGoVLZ!H?j}6 zzcdriU@5tX_l#7sA~ziQu`0iGPTWgssJL}Brm}QF2@zTaP3GVw%9yHup1XO1OQUPK z9u|oL9R7fAjzVc0{Q+VTdIeyka}&F1pn^xhz+QfXF8}99Gle849Xs#jI9FXR%R^^4 z2c)|pVt&W=)e3nqx-m-2d9lKJJ0 zD~#VuAfDXBuHd;5!^I)9^cKzt%Nn8rNGT20wIfggWJhHmRpH*BvY9|e5@-RAVh>TP zEj&YK61*FXgdY_6Rq%;e*%ePxU*UaL1kyBfZSGg|!+sHX05*~`!~DolUV23Q$63}- zr~;6h?g?@_waR$z==fyIUhZ`{PgdBNZ#z1^-#P(c9c5goMZBl-m)f_SU)7HBx~Rng zJsS4rYk9`l$8X=U@G2WxMyPOjcy6F%fydg!56xff4&Rg7P&lUH2eHb+-ps*9y%oza zhdh@^6`$L+fA+MiwN<~}a{pEQ>*DT=w3}+A&9@pu_j2%#lgjt*jo|$<*;U#)SD+(j z3BG7_FYy}b#4?a}?9IMl)9>2u+lk2&8rlc@>nMzVJnNQHC{iYW4i^Z)Dka0%WkWvb z@k2^v!Tei4>$OC>cwB1`f0gz{6-xUUNFeV#rPkx;c5jN}-F4vb*qJE><(BvU?c7(g z&YbsmjX5ZYYHCv2JGFFc*lyh+>9IY*`z(8O$Eu})|46fn)F2}3aB5uhTD3@`Y!%D_ zM(@;Ozg1sVbm^~A_wcCaW;d(FGRFPnP4gc}R@sP52<^aeu{Jz3)YYgGVlX6e!)e}S z#-&M1o?O*Amq$^R8I8?t28@LjNbFa7M+rOcm8va?c8e#vNqesVP_R)0 zq=>D702B{C{G=*sDV)enu1jhp6t$r@AI`6&K}iTY4Woa*1&`)-lox92Mu(N5Owisu zIC5#6S$W4o^j*^rzTWUNY2&`CKh?Y7tr6Vt=>@u)|$6aoV}mkN3ykqQ2Vi&tURYEN{ca+s%}Nwm4rs6C?Gb}!EL#lc001!;UKh4{3tJGQ$bcxZx0 zI?d}{%JMsN0?r?YWst|vDRtn(6fp@LIy;a^Wowi}26uWuU_K9)jtaCX-C3px@RoRb zefWb^9Q=K}OGNFGikr&zvpE*2rw6+KN%_fz|b(Z)@B$& z4pfGvAUwjC&G-mc>iI*QS}_DaI|n77yQi(`g%6r$Sx~)pT5s<>xWB7w`i`r3=871m zC;6WtfB(|Yi{eHg9&OP3&)4qNHdn6nqn$zII`Q>Qyw@XSuX6Zgf5cn^X0+3EkZz2# zUJSHtq zo-Rhun%wpGoJcFU>DOfNx{9F2lJ=Jwiax_;M=OV~v8@p!$imd&T*d}sqjT8=3@=@;1qoJcN0bv*ZO)^Gu1E0Ke$oH^|I45nQ zF^ZE7oZjgV&sw(vKI5x`+k6kmLeeje!dI0N0+r`5pwJ*d!2@8)J_N2nY7 z3?3CS7!@u7I4*L+fzg2*{em%qxn=R17h_0MNshnnYM}XADXZi%H{krH+gXQ?&Y>+4 zBF7<^uIFcB37&_%0{)sl%n!Ko9n*&$wg~PH!ess7W^_>-@?5go%@2GkJ!Z&rX8raX z;4NV7HGCF|h-0i@0Y A8UO$Q literal 0 HcmV?d00001 diff --git a/packages/schema/asset/logo-light.png b/packages/schema/asset/logo-light.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3d4e0d9c20f099da9c81809bf246838a3d5e41 GIT binary patch literal 21433 zcmW(+WmHw&7QJ*g5?8uI2?+t|29Yl5?(XhRX+#>NB}BTL4@m{-?(VL)dH&I%L%5u? z_gu49q@uhO1}YR40)b%2NQ)~&ATZ#MFc4%!@SBcPi5Uc<5+EZkqUw=(;EUvutoH92 zg96(MR~D7)%Ntr48es})oCK;r5@o1CQB(}v@FF+cWz zum3JS3NB84F9}$BKL2o^?S9;PE?;Cq$C0$Ke7+!Ow)NKi$a}15JJVYhCoeBg$s&(9 zAWV`n%nSmBh7x7KSXkg2Ss>;nF!#rkvszDaYxC>MN zQYw}w{hFQ4lAoX7+wpYCdU}7{5pDWAb=R{XPf0NW{0pmoyBzc5>H5HBR8^kRctTdz z+XkCiDvb(Fn4q+g46dZUXu`CFgoLx5i2|pU&cF;D@V#kt8XW>7_~_}SQd#x&%uGxM z>#V1he-97SNMaudT3J|l1onhu{ZzF*xrt&0a)C5X&NI9gx5Gs6pHk9Tbe*(9MP}T#3%`H=ugQ5^w&IMP zo&8staVV==8Gf6QYsY%rhn;7mzNm?)`02I2=-&|$$ak&M2~0H(Xk&gmi7uZ-r4{VPeR#Qwh;7ga_u8rr3v1|_w zr7)W=xBFh|{23p#x0$K#((#mvCwu$ty*-PHiVCT^4Z$kCLiT|H#qj7T_j7n}wa=~7 zZ5I;xxOp0@evz$6#YwqF)xYwVFv)5d95V5Y^mK-Tf`Z6u%Ix=6lhWL_bIHA4tz(0O zU)0J}6S%DZCC@k5yMJuesMK*ZfY18io+=|FvjLl#nJIS_$mhEEX?c<@;x7KqgP;MIrg=~Jt*>K2Avun8-JQVuWKU-^V>gCmJSvnB4RH^%6cc?q( zH5E1WCdU}RF|Knc>MOZ*vP%7SAJ04T7#Q)mDM4CV3H2&H-JOrUkqn9Y2z}@uz2V|gder|&YcZSZyl>% ztMpnc!7kT9Zr1y5?ECBEv}OP2Ht@W|Uryi1n6a^O-_7Z|#QoKwgsf~-Pi<&Phd~#t z@2ml+&-9%W-=NZ z8TEo^yeRk=<283cWQ5T3?HCm$Wgyjq-9n=Xj=$+pG7jy>RQ%_s>kb)Z2JQNah`2a{ z!@0VG{cHhmb*=Acu_vK^Ul7oJ@2^s(D|M-r$T6&f*t$JFCNqevwMS7&L@6?B{VE}b zjk)fNdR1@^!$`}O8E@Q)-^teL@aNB;o%ChD`%jZ)Y9EKcy;m4x#E5;61V74u5{(uU z`5PlhTwQY&N`55tovUqb1EqN#H~JVT?JwZqMpbl$_){(NzI*r1;F{NE=gYyL?`Tb1XXeij+d{i_F1vpUi^b5xZ&>habZwhp zn1U`f3GPp`*m`|R5E|?j-?`)YJ>8lslWV3Jynx5(^I9~kG~X?N#oc`K(aZ^S%8=Yk zE^=&A(zongc?!piEgB{!Ch$Ha+haNLY8sQ}8VVJPMJzq7k$ z9;^^FBLoq+MzW(&MA+@I)9iR-x~}cB`wvHr!Rc^I>mS&#xw44TH)3DE-|;x*<$ z|DmOp$>%CNCJ{S604}<8Z|Kek@2dkJ-H}ulU0EtFsl7a`gF}rXIbz(V!(K=CPg94K zOU~jH{zwx!YH&XB-UO_A3zUgpj>o3E*h$6${LaiDtVZ4yN)lRGgpkB^Lap%V<zKV)BDFZ*}QJAQpWvq1VN^Uzq`8+x91so79Me-XoSj%!v-|1GY$4R;B9f(9;%fncG37@ zXUnoZSx}gi?*-hKgbc|AxWjt9hvW+pDSTgHPNt zT0f-J1Poa{f#_*{|HTJ^FN)b7YbYb|oH}qJ4VHiDw7#;P-p8@=Fnk-Sm zT&r%b>klEq!&{&Dm)-CuU@?_)|7@6Hvd9cmbiTvC!#2a|gUivvR~pzDu8**99A|7r ze&PqM=TdbnqTA^cQ-=@uP0^M=Nu!H#N5>MKR{Og z1H6GZgoOiOgrzKx&1>PFx<&<^v;YpMBgg2-D;tA@e?PwcC+_f8=XvA5qzoKB-6XBk-uqb9L5IDGu!aQMiGd z^L{1C@Mxhaxk-v4tSTe~{@fRU%gA#R|MiFC)^!>?<1=lIUI(j+Ka-^@QqX3;RWMw7 z(b+Mvr`22|Xvuu^zstTts#zjJi|^D+HmGh?*V2M+>bH8F=9?jgB4FRnHZPSZQG1@O zbOi`J930rp0t$2Pm+mnpc>iX%CVj_1IDPoW|LNft^O4Qz@;9?a<(Db}Jb3kSK$_m~ zgk+{6X60zO8JbA$jpxD1@KH;}6_P>>Ro?_LEa!^Ag`carhW2^uu(GpHP_G}BSGCJt zYyo2|{GTIQO*POs`?Lz6F`C8t=WXQ;rq^;BL^Yx6j%oS&bx72n2U;XsW z);D|3U}RbYwbrvT77H@2 z))NblrHu2!l zS4pSyq|@xFld9JPRBp<8vpx`?2BxK})5EGQdn#D``hI#K$}*BdosqeeD}bqR-G zhpy$jCjo$bDe9%LF^=Q8B4jvB93SHvR@h}eeWHz*WV_$a@n0e_z9`nH(#t^EpwC4T zMdChNJ{NO>orH0fs02Xhv->^GCf;-_U_G{?jm2@b8{xmt9*$&mK~vuH40YE6x1WBe zu@?Uqy&o8Hs#T>Y@Rx67-h|0eTuSN#Hv2UUdSsT_&7FPQC4nt(rEW{ApFR&8q`a64 zk3`S?Xn}ERF030l!{`$2Tr4F$27MS8H@L+d#`X7}-H0D%jKVDKrp~ zj1%!5FthJSdbMVsG~g7x3V%k;z~Z4`;U@J6sG`sl%y#9P_2tVKVR3j-u6OHm zqNktSVad)SaHuyX3ZzWOvIT5)4Q5q7#oDQ8EG+~~rT@64P;5bI^Lz9PUw)I^N$g4w z;oGCn@eys_{X!`=*b#uCW#xa{#*Db~Q8G`MiyKi($Q+I#;;6JZJD>f7|5NKl|Y4%Kw<4*|v6L#bV)@m~!e2ZUp6X17jsJNp!b+l6oa4Cp}o-f=4S`nr=eNd)GpO zk+A-7ot(rV9R%pMxFwV{n2(V)W2(b$-L(40o_jbMybB5l$0XgO$(W<$<(<`G&-S~s zWLo?_M;K61@n&g??JI^lJ3*|_%?jKcuFrbx`eLyiwfDusIk@;3yR%Uw<5U|S6 zK!fh>D+oP`_X#tpmIyQUIgprNFshX$11$&j{rAgKh23RGu^CH!8X#8G{&ZC|Ku48_ z$76rO5AH{twOGb64j9%~hOc(e%-OroVSxDgc)fWbvJ_AYU41nM19ShNEi+m4{x{q=55mi{MI6nBqQE=9X_O&Ui|$xZ@9Vt& z|7RR8u+?i?gDUn0aoSzO4bAKQc6lwFx+hmlZ1bFh5!%9bf)pA1qMHCj_wR{QErKfa zp?$$ct;cvIPvlOqq3eWb<;!s zWDqr~9^N>2@ULKu4>J9@<|I`Z1Z;K^}TbMKc0sDT-m8~IW-=Lo2{&DaX+T35|$ISRz#)WRn$NrF_wei0sn+bt@H02 zF4Ub2peWrv$?UmGlQ5FbznW(=IwV>CtN47NzP}RE!wo;xHmiZywUmH_Ns>!SSobp_ zixY>C(7Z*$CM~Hei4;%g_v<<(YQo7%-EWAm@_i*G!ydpS%R+QpXmqUa7YzvZq5G6! zB9GppCXUg%DqyZ(?Ygg?;hz{R*&vgN?EU%0=Qep-?MrxP}mktj;#zZXB^~h4q@+{qSnm9opRr;%-DhAsPm{HGMHK zYaFOu=mq$mJihvMR%%ha4dQXUTm%ZQ=t)FHMT0nF63F1L*}FAu1D9co`U$N30RC9z zWwV9BBu(=Biwm#uo}7o^8;CzN+5Rh}298$Jo4NTEv){Jz-;0U}_RKTPFOC-H@yyYh zfMHW?9D}XhK=PUy;ojev%VN1*z!M)@cf?^mg2~&ckf^gLU{jY$7E3s((l3wz?Ufg@!0Hip}MYKOp zESy1Ab%b@W^k>%>P1Kb`Njc#K!m_{yKQtSX*X%|iNobBDhy;+S)$rf3n?*xSNhC9v zionQ;`p+OpUAI{6zfLwXzJRJ#`A=Q9{MQQ-Syq|>UA2`ZOj^GzM6f#dCZ-x4jlD|t z^aKa?Yo&7da2f^92B|SK`PkVLj4p56rY1rTG5`6A=o*kFsv$3hhuK+Al3t(!vTc+1 zHV=U@GO3;o&U_0qEXw?MpX$tK+1Ot$Q!{DsYXauhTYW(t>rF-~wu6*^{!9r>4)^bv z+j+j>#J5>$<()3XwEkC7v$!`8?3SkH`A@L?Z~VriQc~3|tyQPHL(ydD2gEA08(3~J zOO~}Cv19IFx0t%~1xJsktMoh1a0uvVXlQug4;=dP(Fj?1DweW6$kt|^t5cLB2j!IP z8WP98y&qe>HH7JYbUw$ap7*-oJXg&_=v{AE^7_50_W>_4h1pqM_ZOvD7|&GK`g0dW zE>-mnqM4D2uVd$bN%V{=*wM@XCiS=zOt%CmY>BdiIU@<5FUhfcsab_S;mm zrmO)i+OSbRi?6rhhxb#aJPB^*lvJlQ_j*bV?xXTL0jEu_gJC=ym}Pva4c@}(L?&Do z;+pji9%Hwc)B44YUi)11;1c^GCinqf&vLKQ;peO62+S9`XCLkdOL&4zGt#nG1EL;l zb;Xs#9&T1k63ks^qFpeOWgDelgj%mF|7+oA`rAd)m5(w zj-1f{oFX!QdV0E5TSx7uF)CTcmynrRu^sUWOW@)Ql0gdtl26aGks@jV+I8?J7nD)Q-!aXb!T9o~O={w+~IdxV4F z01!@e5f$t?m0kY}fPwR&`v+zQ9~%K%W*Elny>LP*MU88K26(mGQN2&f2$OpX{8daE zm2%1y97J_b1$DC_ZwMdWnXq;|{G2C`5b@p4a<_?hu5sg4w*z{gl*i$k@_jPrphcQ-1jHO?;m?03YpNh#SFD@;{eWzt5|l>A&bhnTT~E|TGbUUC0yOF1yLlgQ zuSu^w!ZBW(U5ey}M}_yrP`nSz!_kq;5zH#&v^K=A@8^K=7OTjPIIke^c-^CD;2sl4 zCa`xEt_xxcjwe1t;6r7`&GtsUVzwC1;IgK3KVJH@tqx3o12Sm0XP=HXMCd^pKCl4t{(V(d_vOpDKaLE{I_VoK&$-uoX}PzIL6D)X#yQ0iK2L zg5pR&WJCvR!S|8_Iqi71Tby$hbrV`6aLCl6rnw3XlcJV=5Q^{*g$2Eux?4|stG#7% z0oN7WQdCr=thSP-@Ao-~IN*fW$QsCIGZJ1wxYIl3VH_)j%3!q|3;{&v31p6HoG&B^&FR3#*8>?}JS(8I$XE)y=SNlSc9v6&p%F-x~Tm!_bY8_oKV6ukqiM_J!)F1DGn%aqy!(N>MW zN~>KZWP#9Cw7Oho?ISoP6qM=g_x9N!IH^(ChHi(_dkCp8Zg1dnUry?fjmhu}lwMf% zfox~+TfB-$?W>tlp23>=%mF{(?ylamagsp(ONg%2y0rJl5h5TYcx)T-L-#U7yd0#C z4_R(~E$G8zJMdPwIT^7lLuCZDV2zcMgF{8|{-CygVEqF+datK_{$B3ABkm{=7k@j; zT(`))57^`As8`y!-Zl;h=~2Jw+})U(!S-i|?7QW+ctAdE1zHn1BK~2E3&#?^aNV0^ zPqM2Hj%yhz1!Uhv#zqGo9^Ug}M=AcJuE;~q2X_{B%KD2r2NOJ?Yq*o70mX|K_UE|2QtjEqedo^2sSyWw>FMdem3sq@ zWLe#DvxasqWq8?8G*8CsMAPQKS6VD><4#Ljvh(l9Yf@(rReXT|ER0}a_uL7GHGTvu zg~`N*_kPF6HWc;1moQPTGi`7tHz2YdBqZV$WeP$UyUyFA6W1Ws!xS9zuwNWIo~fb6 zSp$^AlGglsaH0N@%yR>0Zf_kH+v*Df133%;zOBKNIqVZ(2A8nVAS-Too#DkWNQva;lTjKP=CVr50QZxEmKaX4Gx z8;O4mpRc$B2z_uicLuD`)_~W_ghQk5JXN(EvBGf*{A}FQH6FGg5E+hNzsO!*$n5N_ z%Nz4>wLbbhnt%Y4&azOp1IUnpHNLU~!M37JtH#Ze>i> zJ47-sRBVdD@Bs0);LT?}TF4t6&0*1ZyTf;v`&mAv6Q6t1W+#6*$ocrRW^$Ov-BMTJ zxPp+e(27?slb4s*&flR}K1;3`#W2MC=cWGC{s~zbvU$v=pkYAWPE}XPSc-pe+gW)u z@tE#qi7C*Un`ASBIy8;QC<{4mFO{$_C$nwPB_1fy;uz=S043!7&HAT z(g>ii`jHZQm;I!w?Mkas$Ys4h)EMke8d+d*!M{^mj%oTQ7(ucpI+uRdYG<*Sj( zlU#)<6s=3ntcv4*va*U`dy0>Z#mE{`4&z$pRMsqSbaK;USwqfx0<(d}OYE3Mr6?x& zCiZx;5c!2uYjdptgEcP;R{C`+D!a>D^o;L_)iI#?$w8L1$6p$y5mU=pH)w1q1{(onz0lZu=e>Nl+Ks?`fe@f`JtFWqmty& zfB)ZGcC+F12wm}gpwlz#VQSrGJS;IFSz^Kpp z$a`wLrB+7SG?ur1H>*f@tsnrKM1m}@W^|X##;^N5oJuvn*W=<;MJHkl&||mn^^f@@ za)`CF4_^Kn2tkUk)7SkU*2#nC=!59Ji>*{i)2{oi1Pr_3*Gx1cc?k`Um)b}Z?L884 z5SS3ck;}`F#h(BJ%6!tuifL3OEX3&}Dp5kN0Uij`@Lc76^i;$K%F)>l}-`n(Uk8x+^=8xZFjy*Tcqn`i|mMLC)ysc`#8(M{ybs?bl z>#CB`Aeli@okrz}cpP%YVy}ebVkJFtf!C>8xp?OZY+5m^&4zanE*T60=>h_gCqv*w zkbRIY@q2Fl;|ygfaQn7fg%uR;;;{RN)(AK1Rch`QKE%1kn5^F%)8leV<3H($E)_IB zD+>d8Eov12K1?ru%sFE+Q6Ljc$^1^4yLEe~v|92$LFDF#AT8-y^Ret-p~dQZufjsf z2EculUh;o_tYG;9gOyWrWLGzO_IU7srmUhevL3TSj)4KYH3=wG)>a5kHrY>fNc8vB z?#zyKcH=OyL<|kM!hiDX&arD`;uWNPE^LvhYOvTH(1M(l6xtmBC(os;FJ-ER13o9W z2eY+|%++(|WSk02FxL2jjYATvwt^iGR&PklMO<&gfZhFbEm(806sAnxZsU%+1rpH! z!W;2wk1?;NETtmwu8K5r76lrI&+{(;=0ukf`h#P639 zJcB@fu+95A3h=xrP}fk3j<|`}hUlGU-#dT=n;o3K9hZEN51I)9y+PC7)8=w(it!pX!=Qmy{Zt$rN+5vs9d%6#>bf)4J}w!#D+nx`SM)VYinTyMLV~@+Ho|fUw*4 z$fkiXK080YWToml4pJJ~${o*7EfG;s`Oh_4&v*M(Md2$iqatS+c!q^RgM-q}DH;<% zvm>_?&>)5F?IKyOfd)^*VNX{u((VGll)t=CXk~!sFL2ny#{j)20EP%c?c-z1Lcf+tChU7gH3~F@nOL#0`v5+qb9m>&(`gm6Vh)B{6#ZmpN&)#5hmM z%0IMgIHPMC@VTIq@~M7=Kv@8O5G6|guv)7z9vIdn$P;fz)J9!t5AXw2T} z3GWd*91*uo`mgM8OtDyt$&!R0#cImRxGCy>!1>=Hau>?(^VX0gsl?F#p~Yo6Njg66 z5UEvX)il(+UX6|LK{!Iq+)Ne&%9$(Zd{q(9DLrU8KRG!smn_MSbw<2`=`1?Bv0+l2 zxeKDrByyjVz@E#BX?I$f#4#@IrYB4u*yQ^}98urxSf{ zakJfX1}b(K{Bmt4Ja=_)s^$J^3#l3(0Yx1NkW=n*?5s4{FBd)M)dI|V&VBZOywv!k z4}u2CyW7kC>Au(Yt%ljuPsaQ-`@VKJp#89;^8qBae`EIkj5~yHjeL_Lg5QD+>;G%< zlt0VFBoL;S(*{+7x4x;s*~e*wfiWL$Cwi?%3 z-@r7M-m_wn@Z{1$Fyix?E6Ynh*V2tNC?>+Iet_OJ8ug(z8FU48ZZz?DUsXtY%z4UFkSVVXj*wb@ zqXo-@A_>Fd)Y=8KHL=B-ViX*tzat>G-KZ`b1E!&&+Dqx_5gB_A*w8y9-1Q})uXwlg ze{5-ilroL|5%dG-z2P(v{*9mDzZhCCmPXe5BPxS)?@f(wiI-8`2ag%M8KeRHhchqb z7EsMPFxhRO!+I6vnxYbQeqUHfb51n6^m`z1J&_+BuB0?V*eO>vk;Or3? z0A+cG;h?%S%R(_G^xP`8S!%$gXYrLqSWK}&0Owx74S_q#GYW_ethRV(_diX4KNX8fM5SxL5q|01)cN0T5;_T(t@tm* zk2M5UE!W#>EBKd%g~91G#Tv`MpoE#QR(0n^e^qYz^L@uN9`FNC$1o_|&SlY+NSRye zY?SrH3FFMw@%%9XWB3mxSj;E1Mk2yF50Qu)UR(I?WyI?}a`{L;KRuG_%o6KF(9Mjf zZRP{`yfI&L7I>FOb6TDHJY)@y1^zCRk3t+yiSqZT-Pvi7QXq*@c?)f-Qpz(w)&9 ztn7&r5Ei4R;BneO9$_b(4-}#G(?Ac1T_64x6FTn=8ft+kD%|!<3$1~)`;}v#eoSiD z7)e-e5V{D<$;C9y1>*%vN9Zoy-F$>Eu5nVUgKbm%99h zfQd^jY=8p#ArGMFk*})FWi^@mI+y{J9WC2a8I-!qN1cP>fQ4fPvQENO9++l@6ehKZ z|4w|+{q71FzcgOND5}R*KBxBQByNV2x41g6>C|&%tDzoKI-3OSoiSJ9)WyO}fT> zCi@-be+=!^0szm9b2)mcB?OOxOiN5&1lS@76A3KYpbtoe)cf43u0ZE{t1f!Y#st%T?v$T$l-6trtsVl>@8JxgE#1JT`Q0-n0 zCQx6@)!WV^=b*00XK>1xnVUQ7`X$27gy;OoA@_f%`7E@UHYEgt$%Awp56T>`J;u8V zfGbuqY$~vW5(j_Ak&!{cbQ65PVQDo<266c21bAdD{*!>?3&Z4(RQcN{x(`^VKHVWG zE{68ZB~FJOz*Ng14Sv#Z^Kv~WVtdUDhuvi3>!D2~BG#hlEEFD^e z3ETilzn&AtI;tIb`)T2iZY;xkt8LdyN#nK4zSp`*2B%i|G7g=9ySM&~a4MyBQ5~t! zDv-ozz5?p@y94W)HE0RjuQZj4esaQN8p%&>4AG8W1o;Mova&K2PZNcdNZ8m0p9%tUrS+N~f8>A^SD1*Dt7nbH5_F>(g&gDR0t<5hJ4eHHBfZw%093E0W<^&p74n! zVL*+;swm$rC?H{mjIK4Md3kkaoU(Cp!os=^3r;OTkhR(Pj_fI{tF)U32B7>vqbc|wxq1v>Fix^J zQixR7=Cg?gqFCbV7-ZM_``Cb=(p7XM`@YdI|Kj|a|x#Eag^2T`sGKKw*psA%7! z;!dz+S0B)pZ|m)s$qwv1;Prk<#gWLAQ)$edp`34z6-T#T{Q2kvxc}LN6nRQ%X=$PP zg5v-w=AAz%f99X>3agY%y%5rvNf*#AE{ z;htmU4IfNS;3jW8$^f^9UUctoF%~ZzNFU{o<{K11Sis}(Iu$m*puiO9Y?4MLiUy>q zIUB_3zvIZ9G{;e;Iibq6Nst38a}N`^nR?rA3pK__wCg2Wbxeh@Ai%}Y@&$@$UAxX| zsyFqmHq}dM?T+lzr}IPmmG(v{K<(O&Kx>c#CN>}+P;)@HUv(XYO%rjL#r!q5BkqjE zqc3T_emQ6$hVBFEb9fFyVG}V=(G7@EtR85Xe%$`_^&RcnzaQE-fi!-GAqWM5r)(Uc zNlH_x53c(1^S%GeSFA>xa{mnxJ{hmdv%fxG7NVk~lbrma{hkUS`S2z^J_-akC~9ZN zqUvvmCmfxJG>ri~sC8q^v~hBVuor(!VR{ALA4bj@fdjO9_iwSvqd?u4Vk=*BU4BZ+ zt~WE3F<+L2rxZ2Z$*vA&#ncpIIai_3zz~(IJ;l=ywbvHd7oFSzI;}*`&owvaz@(HZ zXcr8HbN<9hcLeA;QvUX@Ri++faTO;d?3j0JP$&%CmQdP|aR?RMirK~q{!1Yc&$dww zW@ZqH{mI_{{`J&Ki`OO7cN9EHfpF?otJkI9|CC~;B8G7gMl^?9~~Wy zw)4n|`kqC<{Y~E0)zRX&CGrn$a(0W&TpI+W|E@(EDvCgsJ95jTz={mJt)X*qbvS=P z=JnoA5tI$?NJ5_1B#p8V4D#a{)Omb5V7+!Ks!0}4KI|LYg0 zBR~^`7HxThjNX0F#!t&OtRs^Wk*Lf!Npdp!{w&dakGf!(&drQwypSPGE{Il|#@OX-nZ=YP+O@Na2RN zonEbq`N#r+4KNZU(G>VtZx^n?ExO#}gF>^7{uX_rS**93igJT{x|{BhG+1&n)?7w{ z%WVUT0DrE0)z#JE@9aVc&?)d61Uh4Kol1gr0XLn)og7Do2eaV6(d6uGGNmRbQ^$uJ zN8%_Li#Ja|PW@G#_P*Fzb;;C5?7Tk^dN>iIxU<~4M}oPB4n?28v?-qbfgr_s@idL% z{Eo`P5Z{J?$({Gis?$gT1@j3f$`wZV!T|C`U6>sB-obNg|NmmyK5_<6hDnUVq0N36~UT^Gjl6A`#77w)6%+@j%wj7@PDb++kCD8J)v2^(B{_{ z-a*_R+Z@-!h(CdRrIjsE=;<^*AJp55_8n@B`dW=3>T3hmZf^BAAZ!smjXOxY<5E8h^b@4Hrs(AVC@1HVuAQevtXWqKw8fNZa)tJ5h`MQn05JD)&bLOWYqIbH z9XjKyI_s*9#KK|CFS)-WHNL(h1QlgKT$n*zPHO}T@PNUcPqJcQd@vX-E-K>lAt&W`~zj3w+P?OQBR=vl?)69LRw^?Us2s~@6r5ythQC+}^98kPDC0|@T{_6Lz z$@S{>>yN(hJ$rw?N1MSyJzvC#>FwF3X}S6jIVc;p3zhp+XGJIEC1ugE>@3g>05)$* z5XDSQk5eV@9zd*7@8avN!2qQ5#RygeWHD6_tRKPBBT+120uc3(wt>?k zhRgvS{`meno`hGFaQoLhV8|o2^ai=myH~`-mQGt>d&+_?caP{#Jkt^{%tLqXi0oFhug|l|&ud zsTfESX+S(y`86QyBMHC9_AuK89X8&)zy5}Qn+eR;B52Q{`=)xFb%=+#8GK1;+ z0R7L8ACAWJf_tKxy|3-Ug~_Om*z|eG{4RhcHv)qlroi0}=WI~$os9;W(K(v z7S#()vR3%=zd$ld5DQp-`8hSs)x(bwXfDz&_ZU0G?FO7#Q_vFIrK6Qi&K1uKW3jF5 zyzPQ_Ajf_TghP@$vL?FCEQI(}I$5ZtXjU5p%E`;860+!w13m~tvcq9B!{%&P5-l6? z9<1?^rt@M1mFYCS!+|!g0=M@e`*o-8IzQybA8;>aR}IN;T-K>Jmm*bZZLfZ z%neju8n&}AF%Cu;7~LS_n_2G5B!ojmN9I;)vC*7-DK2v2fK|hpxpZ)oN-RB*a731? zD=TihAe{jYW1#&0H8GJ22tGS!DcG|A5~!stggY~N^dZi!smPr?4w*Hs`FSEclcjhg zSoK9%`#slz^f=K8f6{7K553|tJ?eY~CcT)>`{B+Yzg;1eYH&mfGyu9uT zeD#-|DlRoxGcDG(3tSnr8w>-3FX)}{dj5~DV8x*q=q`)wmjaw0@YOl#(|0~!qI1{G zbES6I{`?6m6#x3Yot*e2m=;uT2;wJ)`EMsWeJJ`luGgo*J`%6rg9kVdL}u`cFv2I` zr>L|XW6O-{1$z{46|(rg6N^yxf%zWU854?YO$2okQN1Q7u#tlv)rL%Ywr*IK9gulxF`CIs^UCF5Mu{U(G;FF|MZ6i#|`T&h2NX1fb8)|h?&KBc8T*9MHztSoD!Q;e0 z~d>5S|=YquseG!{L97uq20hO zq@sp@@d|yI&NAq;hH!yHE>MG<^ys8HoibdX54NWjO&}y_0ng{I?|)N}Q4tXco&6@A znMU}cffI- z;#<;%9J3?Y4TRC4B9G-C8n8&h#)id))+UcmWdAGtG2 zNPeUJ0EFTe^ikh=oc{t;Z}O|+ES+JMur*^b&~kjO&j+<*!}vo!m3xGcgBKMS02={H z-4o~P8uHWubQMsfmCbLP%b3_X=@CFXS0fN+o`U?R2{R^vA_Nf!V|%6HWtl}3W3WSM za!|p5y_5fQn-`bBIzWYp`asX8tp+lp+uO4C99s2y&p8RM0is?8io}JA4aSWsSTMUmyUM)$Ra2Lv-&GP5(7Hk;C@8oS+!HHMaMHnQ z*%5P4W#+lr8Ivwq-C6gR*D@^Og8B_~%-XL{)LcdjmtQ!>$XJIhm`?Qw*^;6Pj?=h5 zW}L_%mcNS)Rt1y?;RXJJzH!of>*|>4cFv`%^#cu<*yn4+@nRlzp)b6!&epAW&hUiS ztDw4y5-ait846glf-@x-ms&QzXX3WlILh4o?T_XK?W|={7_&dU1_uQ{?FQ^JZPM7T z+_+E~pH9Ms#uqxHJa5lT>DREr1^f*o>oMk*{O#D@C1ku+MGx*i(Nw*@=fB#n?Cp?xk03WF*duaXO+h9O=*n25Rsk!q|AbulJvpptmr{9}SfZca&Z+ zseXB}&s28JV^5?&F?NTyO1nYc4p_U+0vWMtqFkwhB+#u>z*TVt&(%f>qXIG28>@BK zk?eOupa}47UYAr@<3bOsSQk2=&?XXTG)2X|I;ncs^$Z*k%$!?P_85&N#i5d+-m%%h zU(5boppWO1i|7fn)g3(6O*y1MG{0+gY28Z3<3RDSWeMWDNm*vfy6v(f&EZU5l_l3{ z-7}!LFgry4Q3(I&dC&s;)f?BDt=#zAIV}=V#KOS4x5!4U*wCOj1(Nxr;jA6XcN^v% zLIqSe<1oz9=+F=>imtZP{?`}iu{{2U_*V`Vni1WUZ4HWhk6?%Y<45P$i_o8%byg{G zt4Q~hC8gPicll9~;CU5$1wUvDK3=F;_Z!AibR+L2fDo!cv}z!p{5^-@DsS!4*wa&< z=IV5%pFMbHTvd0ds;V_#Ox;A> z)&Xf1#NL3uE+>76yzy_wAA*JZEEe-dy&~OavIIxq%gl@>lF}!798htr_)Hy+PAp0# zlvWB_2-yq2ER#OtWQdvag5C1Yn{e}rBh1^6YQp3^wS?eB(0rLQzk z$TDw%he!v9{k(a>61dJ5cy8Z|MsNqZ-s=mXqHPJEgHn+w`uVXba3{xei&$&oYp%do z|G=M0p46Wm%*qYo8L}V$&j=Ejoe9cyt~=v_iH5?c5Eaw>zVo$h#5#0ksQ;1t*#;2-4ux^v z`{`bLD8Gk8R~9vxL~cYUK?^)eD1NMTwlAAUDCUJK)E*f<)n9@iW#Owz##V%oi!Fq70>-`zW00 z>9uwb9mn9Ij~--e_^V1uc1u;F-GSWTc-wdeU(kf4$rHPLZF2jC1OC7WNM-}IepPk?y)69a#dh85*S zIz<43SPNSHM(^CtHC>e| zHWHaGWg))`!D6JSijI=K(LoX;QkJo?sQ`MR~hOUqwc#-Y!EAN}{Qi}~3+$JY!OCmrr1ga`4Cd(>1ix3a!*>7}~) zHT%u*AJ$KGBPEP|URMV)5>h(sXqRo!AU%q<$A>^c-#hrM|8;QP@lf!8{Eq94NStz3 z$f1<7os8_H62jRTxgl#80e z>(zAQP0{IEY|MXVkDmJ;WrA+}69b}s`0TH4F10sCdsShM$BY@|t>|K@qmn!CJr+3R zpG4AofBy2tajrLQp<3PL$7&O{BwD64|J?~*Str3uaXi|Y$_#dsRDc6rd#}i3`=SEV zcIm;oI&MpX6w<|EVySND$BWhuJR-^@=(|0$Tao^SX|x#=scgfJ!)+phh&Z_S$xn0C zd>($LnbGh$#*-hIt)Ydb8Z`X!aThQO`5&c1O!>4>mfmo!%3shNI{@7(N@}aJ{R#;s z)&7>BrPz3y6SxFkK5)qnYRw!GA5hHA+NLX)R2_1Q0O{~W6rZG2vmB`R@K7IdEFq*~ zjOX~TNO2?4P1XBJifdhe;Vn$6KoU+WBq1XIfd6A=cQs!H(b}Eq2d6=oxv?&J!h~|w zI)3}X?|K?V@%#2M3$qHBZ8x<-8DY$?R z(fFrNwcCdZEr$kHzZx>PgO}@@70ZCrRU5~`!F{l46t|nE-5nGsfaWLrOEYIK=PPMo z6)&a+l@S){#w|B)5sBne*`jMK^6Oz5Ev>Cut*tG?+vOgK7bnD@${8Qu&Wnq@wSP_|L zM#F{X!fYjI>-j(9ZB0^54|ST&)@hhjR3EcixAl_ybsvbE_l~q*O_FTlOf%_(&sLt` z8w_9T=z9s6A{!ZnGN*aqc;6CR^IR1`75MH1Z-WeW>g&Bx6Do5t)mbG39@m7zlj8zf z>hz|Zk%oD_gkk=0V+G^s|JyRr_sV{bH@rH#>wV6u%@+Xuf1YBTb=YwlTfaO98#sQf zDL8lXH&p6uynSwSUr>_b)i^XG$R~X7Md78FVmb~)BY!@veW*3SdvEx>XUtU;kfoAf zzxl*B(adq#X{hQ}Vi)5|?XO<{N+z0Gz7ls#;lc$Ao=3`=G(L(_V_r_&*xz0Kw<{0q zfqr6iA6zFK8V&FxY`zWTbKN-K)bd-M99?a#Od4U1<72*TcJ4n6MZ!Q%ikG!Ri7;)Z zQO>$XUW9@l&|++4g47Uf!IdG@v@S&}p6vHi^Y|D;@rj_9toE~{RM_pLF-awcN3{Ib zr?gidKS{~tSxH}#X`+<`{rO`p<*lFIjEboa@@cGLs(y z7-_N>Aoz#o8XRD?YT+4L70)yNJ;@bbbOM9X{Hs!bgB;69Nt!EUXD3Gv?*2kLXGS!S z4NN4eJyz2Cr>JG_be4XaUX+o!v!V75?B8Agu{=feVb0X*lLcCv6wlg9F+ zRNbyoZZ=Y|r6f9yuoUD1{o>)x11X0#gA3;`Dr4fP2vK@56PD~L;b5mOy<i!GY`UL3OaDP7zih8OltsUt~eZtw$#soJu)$Q~)?LGg^`To3>aP{5+0m=FS z?0w$_V7TjFsD!hUg`4WFZ$;~6=nB#(IU+(Gr4rj*tJIiVu6bygDf=idl0zL*6bnjn z*991zA+V5jDcxXE+9K-uDWk{YNG)ycAb zMJ(gfazyFVnPyrw1IK9c_>hubO2TFDRJJRM~*AGBCze3voz;*M}E-)=GD9=r3bx7*eiV-jG zceg`sKkc)pm%13bnLZ+3W$`4zKJNYwcVfXM8Arv1zp*r2&k^u8Q~y+*p+Y@GFpbg*KKJ7*W!_* zADtg=gk5EY0Qsl#BKp6)sR^Im4$ph>$+cx<4BB%XdMnN@iese9e>UmG`z#2bW@)2B zLB{fGd3gnfM@u<&asK_dhLN(Xw3KokQzpx*Q3rkLHKfGzei1>6#l6Gkh3e!?gSl9$ zTIo{!Yz2W&KTb2mXDTFvA3XI=7#S$mx$>_c30(j8Crv_01&5S}cYBM{FA<5Nxm;#@ zVF}`_;%(r8>AXG(B}^3oioTmU%h1*1^GI(yl~K!#Bc`nV^-YGW!hu0iS*GO4&L4M^ zoN{DEj~Cc)HvoUFo|azKpdhsxie}B+4^+U4|7Z=yQ(9?*v1v8YA-)sy_T@5-!aE zJ2kt$*M!68iWOH-mA0!(9hy;Ua!wT;N7Wz>ud z391}__-#@se}8h)I1%;a)%0N4)amB}crdYWPj%--2cI>5NOJ5JvPA#Ig!uY$qDFX5?SP^VzxAz?q7FJTt zej*%o&)+Nx0wGj!&N8@ZC&R&x2*~;h#A~!R#CFsU8oa(ihUrc=wqOcHcF# zLj@Q?s}!Mr6bnQZ3zD%nuA64Z137*c6qApBVDC(1 z4io>qr%Z>{2eLj*d~?dYoI#)_?)AD91ZkfGP#YxOG_!5Ze{sgJglXgjaln}66zG4K zw|Jy)>AvK!f^|a5f5t{5RMzH|{{0hQw?Xwuy7tdKeQ6+k1vu zBqUN|$3bHh!S(~!I}3dNwL6*;=bX2p_m0X1m0DSZl$5}imHBkZk)-neoJXn!eNzVp zUFQSA0(WHSt;unc2@I*~e?~8eE$zUfm-aUR{zEbWUhu-1ORr&J@IF`@a1gz76mJLP zS`_m^rogn2=cg>K&89y!4a>byk!)3RU7O| z-dNQzni)tTkNI}URrMTDECPekjAlJYdLGhK_h3uxyskFdZ#P#Az8`b|pIq}%T3*A1 zgt0XR1aDa%+4z^3UwSwH2|Zd_GK2;{>1M^8op?(kTkAE9pGy%+tnp z=WEyK2$w~(8c&d&XzewQ^F6)2&r(ATG==**y1FtiM=+tS%{*~aF7KRgSlcLh%nL%6 zd|ui@;ybO_Z!B<&8*(s03_=+L6D>2IC6H~}!Bf<88SCNcf49qzQ6M#V`4Cml{X^2f#YBB1vvd2AYn|Bv}1JRU0VI1VJ69}*1L3jcl_WSKr$2C zb~8<3TtDcO#mc{p*YfJY2qmL`6FXU%TEuHsPGpVRTGmfZOw^J`=-vQEmU5;cTnBFt zx@cnJjZ>;zV2*f=j{sr33z7<7^dlGj*!lT?8e5xbY6`QIIQ5_0sGnwTj)f`s%; zJ$tm+`sb~mWvq9doJh#B@^VL*8j9tQl8t4=#3H;d4u=SUdUjHEj%C6(06ORFqG{%Z z4gLAWR7y4s@&6S1e75nHG8XsmOZzOF7l8GSXEKClV#bZZ+n&It8nfkmBpj%A!$y5K zKTGfQN?q9vR=6gD9;Hwa;!CR-k^