From 8ec4827b6dfaaed0fc46e43cbbae41330221ef4b Mon Sep 17 00:00:00 2001 From: Jackson Tian Date: Fri, 9 Oct 2020 13:06:26 +0800 Subject: [PATCH] init darabonba2.0 --- index.js | 27 +- lib/analyser.js | 86 + lib/base_lexer.js | 55 + lib/base_parser.js | 158 + lib/builtin.js | 84 - lib/builtin/Darafile | 3 + lib/builtin/error.dara | 6 + lib/builtin/model.dara | 3 + lib/builtin/request.dara | 9 + lib/builtin/response.dara | 6 + lib/comment.js | 2 +- lib/common_analyser.js | 1309 ++++ lib/env.js | 2 +- lib/helper.js | 75 +- lib/interface_analyser.js | 46 + lib/keyword.js | 2 +- lib/lexer.js | 181 +- lib/main_analyser.js | 84 + lib/model_analyser.js | 39 + lib/module_analyser.js | 241 + lib/package.js | 175 + lib/parser.js | 1392 ++-- lib/semantic.js | 2357 ------ lib/tag.js | 15 +- lib/tokens.js | 57 +- lib/util.js | 12 +- package.json | 32 +- test/base_lexer.test.js | 53 + test/comment.test.js | 86 - test/env.test.js | 14 +- test/fixtures/clean_package/Darafile | 3 + .../darafile_with_comments/.libraries.json | 3 - test/fixtures/darafile_with_comments/Darafile | 6 - .../libraries/alibabacloud-OSS-0.0.1/Teafile | 4 - .../libraries/alibabacloud-OSS-0.0.1/oss.tea | 5 - .../fixtures/darafile_with_comments/main.dara | 1 - test/fixtures/declare_module_model/Darafile | 5 - test/fixtures/declare_module_model/main.dara | 9 - test/fixtures/declare_module_model/oss.dara | 6 - test/fixtures/empty_package/Darafile | 3 + test/fixtures/extends/Darafile | 5 - test/fixtures/extends/extends_unimported.dara | 5 - test/fixtures/extends/main.dara | 8 - test/fixtures/extends/oss.dara | 8 - test/fixtures/extends/super.dara | 7 - .../extends/super_types_mismatched.dara | 7 - test/fixtures/import_by_tea/.libraries.json | 3 - test/fixtures/import_by_tea/Teafile | 5 - .../libraries/alibabacloud-OSS-0.0.1/Teafile | 4 - .../libraries/alibabacloud-OSS-0.0.1/oss.tea | 5 - test/fixtures/import_by_tea/main.tea | 1 - test/fixtures/import_duplicate/Darafile | 5 - test/fixtures/import_duplicate/main.dara | 2 - test/fixtures/import_duplicate/oss.dara | 0 test/fixtures/import_init_params/Darafile | 6 - test/fixtures/import_init_params/main.dara | 6 - test/fixtures/import_init_params/oss.dara | 1 - .../import_installed_remote/.libraries.json | 3 - .../fixtures/import_installed_remote/Darafile | 5 - .../libraries/alibabacloud-OSS-0.0.1/Darafile | 4 - .../libraries/alibabacloud-OSS-0.0.1/oss.dara | 5 - .../import_installed_remote/main.dara | 1 - .../fixtures/import_method_undefined/Darafile | 6 - .../import_method_undefined/main.dara | 6 - .../fixtures/import_method_undefined/oss.dara | 1 - test/fixtures/import_module_call/Darafile | 6 - .../call_static_method.dara | 6 - test/fixtures/import_module_call/main.dara | 6 - .../mismatch_extern_model.dara | 6 - .../mismatch_module_instance.dara | 8 - test/fixtures/import_module_call/oss.dara | 18 - .../import_module_call/parameter_matched.dara | 6 - .../return_inexist_module_model.dara | 4 - .../import_module_call/return_module.dara | 5 - .../return_module_model.dara | 5 - .../import_module_call_static/Darafile | 5 - .../import_module_call_static/assert.dara | 8 - .../import_module_call_static/inexist.dara | 7 - .../import_module_call_static/mismatch.dara | 8 - .../import_module_call_static/non_static.dara | 7 - .../import_module_call_static/ok.dara | 8 - .../import_module_model/.libraries.json | 3 - test/fixtures/import_module_model/Darafile | 5 - .../libraries/alibabacloud-OSS-0.0.1/Darafile | 4 - .../libraries/alibabacloud-OSS-0.0.1/oss.dara | 7 - .../model_invalid_type.dara | 7 - .../import_module_model/model_no_field.dara | 7 - .../module_as_model_field.dara | 5 - .../import_module_model/module_call.dara | 7 - .../import_module_model/module_call_ok.dara | 7 - .../module_model_as_model_field.dara | 5 - .../undefined_aliasid.dara | 5 - .../import_module_model/undefined_model.dara | 5 - .../undefined_sub_model.dara | 12 - .../fixtures/import_no_package_json/main.dara | 1 - .../.libraries.json | 2 - .../import_not_installed_remote/Darafile | 5 - .../import_not_installed_remote/main.dara | 1 - test/fixtures/import_ok/Darafile | 6 - test/fixtures/import_ok/main.dara | 6 - test/fixtures/import_ok/oss.dara | 6 - .../import_ok/variable_undefined.dara | 6 - test/fixtures/import_remote/Darafile | 5 - test/fixtures/import_remote/main.dara | 1 - test/fixtures/import_undefined/Darafile | 4 - test/fixtures/import_undefined/main.dara | 1 - test/fixtures/import_without_init/Darafile | 6 - test/fixtures/import_without_init/main.dara | 6 - test/fixtures/import_without_init/oss.dara | 0 test/fixtures/module_assign/Darafile | 5 - test/fixtures/module_assign/main.dara | 28 - test/fixtures/module_assign/oss.dara | 25 - test/fixtures/module_instance/Darafile | 5 - test/fixtures/module_instance/main.dara | 5 - test/fixtures/module_instance/oss.dara | 10 - .../module_model_as_map_type/.libraries.json | 3 - .../module_model_as_map_type/Darafile | 5 - .../libraries/alibabacloud-OSS-0.0.1/Darafile | 4 - .../libraries/alibabacloud-OSS-0.0.1/oss.dara | 7 - .../module_model_as_map_type/main.dara | 9 - .../module_model_conflict/.libraries.json | 4 - test/fixtures/module_model_conflict/Darafile | 6 - .../libraries/alibabacloud-OSS-0.0.1/Darafile | 4 - .../libraries/alibabacloud-OSS-0.0.1/oss.dara | 7 - .../alibabacloud-Source-0.0.1/Darafile | 4 - .../alibabacloud-Source-0.0.1/source.dara | 5 - .../module_model_conflict_other.dara | 4 - .../module_model_in_function.dara | 14 - .../module_model_in_model.dara | 14 - .../module_model_in_params.dara | 7 - .../module_model_unuse.dara | 7 - .../{Darafile => non_package/.folder_hold} | 0 test/fixtures/package_with_dmain/Darafile | 3 + test/fixtures/package_with_dmain/test2.dara | 4 + .../package_with_duplicate_model/Darafile | 3 + .../package_with_duplicate_model/test.dara | 3 + .../package_with_duplicate_model/test2.dara | 3 + .../package_with_duplicate_module/Darafile | 3 + .../package_with_duplicate_module/test.dara | 3 + .../package_with_duplicate_module/test2.dara | 3 + test/fixtures/package_with_interface/Darafile | 3 + .../fixtures/package_with_interface/test.dara | 3 + .../package_with_invalid_darafile/Darafile | 4 + test/fixtures/package_with_libraries/Darafile | 6 + .../.libraries.json | 3 + .../package_with_libraries_installed/Darafile | 6 + .../libraries/std/Darafile | 3 + .../package_with_libraries_local/Darafile | 6 + .../libraries/std/Darafile | 3 + test/fixtures/package_with_model/Darafile | 3 + test/fixtures/package_with_model/test.dara | 3 + test/fixtures/package_with_module/Darafile | 3 + test/fixtures/package_with_module/test.dara | 3 + .../package_with_multi_dmain/Darafile | 3 + .../package_with_multi_dmain/test.dara | 3 + .../package_with_multi_dmain/test2.dara | 3 + test/fixtures/variables/Darafile | 5 - test/fixtures/variables/main.dara | 12 - test/fixtures/variables/oss.dara | 10 - test/import.test.js | 372 - test/interface_analyser.test.js | 66 + test/interface_parser.test.js | 223 + test/lexer.test.js | 723 +- test/main_analyser.test.js | 99 + test/main_parser.test.js | 159 + test/model_analyser.test.js | 184 + test/model_parser.test.js | 475 ++ test/module_analyser.test.js | 3939 ++++++++++ test/module_parser.test.js | 1961 +++++ test/package.test.js | 149 + test/parser.test.js | 6916 ----------------- test/semantic.test.js | 5684 -------------- test/tokens.test.js | 56 + test/util.test.js | 11 +- 174 files changed, 10836 insertions(+), 17486 deletions(-) create mode 100644 lib/analyser.js create mode 100644 lib/base_lexer.js create mode 100644 lib/base_parser.js delete mode 100644 lib/builtin.js create mode 100644 lib/builtin/Darafile create mode 100644 lib/builtin/error.dara create mode 100644 lib/builtin/model.dara create mode 100644 lib/builtin/request.dara create mode 100644 lib/builtin/response.dara create mode 100644 lib/common_analyser.js create mode 100644 lib/interface_analyser.js create mode 100644 lib/main_analyser.js create mode 100644 lib/model_analyser.js create mode 100644 lib/module_analyser.js create mode 100644 lib/package.js delete mode 100644 lib/semantic.js create mode 100644 test/base_lexer.test.js delete mode 100644 test/comment.test.js create mode 100644 test/fixtures/clean_package/Darafile delete mode 100644 test/fixtures/darafile_with_comments/.libraries.json delete mode 100644 test/fixtures/darafile_with_comments/Darafile delete mode 100644 test/fixtures/darafile_with_comments/libraries/alibabacloud-OSS-0.0.1/Teafile delete mode 100644 test/fixtures/darafile_with_comments/libraries/alibabacloud-OSS-0.0.1/oss.tea delete mode 100644 test/fixtures/darafile_with_comments/main.dara delete mode 100644 test/fixtures/declare_module_model/Darafile delete mode 100644 test/fixtures/declare_module_model/main.dara delete mode 100644 test/fixtures/declare_module_model/oss.dara create mode 100644 test/fixtures/empty_package/Darafile delete mode 100644 test/fixtures/extends/Darafile delete mode 100644 test/fixtures/extends/extends_unimported.dara delete mode 100644 test/fixtures/extends/main.dara delete mode 100644 test/fixtures/extends/oss.dara delete mode 100644 test/fixtures/extends/super.dara delete mode 100644 test/fixtures/extends/super_types_mismatched.dara delete mode 100644 test/fixtures/import_by_tea/.libraries.json delete mode 100644 test/fixtures/import_by_tea/Teafile delete mode 100644 test/fixtures/import_by_tea/libraries/alibabacloud-OSS-0.0.1/Teafile delete mode 100644 test/fixtures/import_by_tea/libraries/alibabacloud-OSS-0.0.1/oss.tea delete mode 100644 test/fixtures/import_by_tea/main.tea delete mode 100644 test/fixtures/import_duplicate/Darafile delete mode 100644 test/fixtures/import_duplicate/main.dara delete mode 100644 test/fixtures/import_duplicate/oss.dara delete mode 100644 test/fixtures/import_init_params/Darafile delete mode 100644 test/fixtures/import_init_params/main.dara delete mode 100644 test/fixtures/import_init_params/oss.dara delete mode 100644 test/fixtures/import_installed_remote/.libraries.json delete mode 100644 test/fixtures/import_installed_remote/Darafile delete mode 100644 test/fixtures/import_installed_remote/libraries/alibabacloud-OSS-0.0.1/Darafile delete mode 100644 test/fixtures/import_installed_remote/libraries/alibabacloud-OSS-0.0.1/oss.dara delete mode 100644 test/fixtures/import_installed_remote/main.dara delete mode 100644 test/fixtures/import_method_undefined/Darafile delete mode 100644 test/fixtures/import_method_undefined/main.dara delete mode 100644 test/fixtures/import_method_undefined/oss.dara delete mode 100644 test/fixtures/import_module_call/Darafile delete mode 100644 test/fixtures/import_module_call/call_static_method.dara delete mode 100644 test/fixtures/import_module_call/main.dara delete mode 100644 test/fixtures/import_module_call/mismatch_extern_model.dara delete mode 100644 test/fixtures/import_module_call/mismatch_module_instance.dara delete mode 100644 test/fixtures/import_module_call/oss.dara delete mode 100644 test/fixtures/import_module_call/parameter_matched.dara delete mode 100644 test/fixtures/import_module_call/return_inexist_module_model.dara delete mode 100644 test/fixtures/import_module_call/return_module.dara delete mode 100644 test/fixtures/import_module_call/return_module_model.dara delete mode 100644 test/fixtures/import_module_call_static/Darafile delete mode 100644 test/fixtures/import_module_call_static/assert.dara delete mode 100644 test/fixtures/import_module_call_static/inexist.dara delete mode 100644 test/fixtures/import_module_call_static/mismatch.dara delete mode 100644 test/fixtures/import_module_call_static/non_static.dara delete mode 100644 test/fixtures/import_module_call_static/ok.dara delete mode 100644 test/fixtures/import_module_model/.libraries.json delete mode 100644 test/fixtures/import_module_model/Darafile delete mode 100644 test/fixtures/import_module_model/libraries/alibabacloud-OSS-0.0.1/Darafile delete mode 100644 test/fixtures/import_module_model/libraries/alibabacloud-OSS-0.0.1/oss.dara delete mode 100644 test/fixtures/import_module_model/model_invalid_type.dara delete mode 100644 test/fixtures/import_module_model/model_no_field.dara delete mode 100644 test/fixtures/import_module_model/module_as_model_field.dara delete mode 100644 test/fixtures/import_module_model/module_call.dara delete mode 100644 test/fixtures/import_module_model/module_call_ok.dara delete mode 100644 test/fixtures/import_module_model/module_model_as_model_field.dara delete mode 100644 test/fixtures/import_module_model/undefined_aliasid.dara delete mode 100644 test/fixtures/import_module_model/undefined_model.dara delete mode 100644 test/fixtures/import_module_model/undefined_sub_model.dara delete mode 100644 test/fixtures/import_no_package_json/main.dara delete mode 100644 test/fixtures/import_not_installed_remote/.libraries.json delete mode 100644 test/fixtures/import_not_installed_remote/Darafile delete mode 100644 test/fixtures/import_not_installed_remote/main.dara delete mode 100644 test/fixtures/import_ok/Darafile delete mode 100644 test/fixtures/import_ok/main.dara delete mode 100644 test/fixtures/import_ok/oss.dara delete mode 100644 test/fixtures/import_ok/variable_undefined.dara delete mode 100644 test/fixtures/import_remote/Darafile delete mode 100644 test/fixtures/import_remote/main.dara delete mode 100644 test/fixtures/import_undefined/Darafile delete mode 100644 test/fixtures/import_undefined/main.dara delete mode 100644 test/fixtures/import_without_init/Darafile delete mode 100644 test/fixtures/import_without_init/main.dara delete mode 100644 test/fixtures/import_without_init/oss.dara delete mode 100644 test/fixtures/module_assign/Darafile delete mode 100644 test/fixtures/module_assign/main.dara delete mode 100644 test/fixtures/module_assign/oss.dara delete mode 100644 test/fixtures/module_instance/Darafile delete mode 100644 test/fixtures/module_instance/main.dara delete mode 100644 test/fixtures/module_instance/oss.dara delete mode 100644 test/fixtures/module_model_as_map_type/.libraries.json delete mode 100644 test/fixtures/module_model_as_map_type/Darafile delete mode 100644 test/fixtures/module_model_as_map_type/libraries/alibabacloud-OSS-0.0.1/Darafile delete mode 100644 test/fixtures/module_model_as_map_type/libraries/alibabacloud-OSS-0.0.1/oss.dara delete mode 100644 test/fixtures/module_model_as_map_type/main.dara delete mode 100644 test/fixtures/module_model_conflict/.libraries.json delete mode 100644 test/fixtures/module_model_conflict/Darafile delete mode 100644 test/fixtures/module_model_conflict/libraries/alibabacloud-OSS-0.0.1/Darafile delete mode 100644 test/fixtures/module_model_conflict/libraries/alibabacloud-OSS-0.0.1/oss.dara delete mode 100644 test/fixtures/module_model_conflict/libraries/alibabacloud-Source-0.0.1/Darafile delete mode 100644 test/fixtures/module_model_conflict/libraries/alibabacloud-Source-0.0.1/source.dara delete mode 100644 test/fixtures/module_model_conflict/module_model_conflict_other.dara delete mode 100644 test/fixtures/module_model_conflict/module_model_in_function.dara delete mode 100644 test/fixtures/module_model_conflict/module_model_in_model.dara delete mode 100644 test/fixtures/module_model_conflict/module_model_in_params.dara delete mode 100644 test/fixtures/module_model_conflict/module_model_unuse.dara rename test/fixtures/{Darafile => non_package/.folder_hold} (100%) create mode 100644 test/fixtures/package_with_dmain/Darafile create mode 100644 test/fixtures/package_with_dmain/test2.dara create mode 100644 test/fixtures/package_with_duplicate_model/Darafile create mode 100644 test/fixtures/package_with_duplicate_model/test.dara create mode 100644 test/fixtures/package_with_duplicate_model/test2.dara create mode 100644 test/fixtures/package_with_duplicate_module/Darafile create mode 100644 test/fixtures/package_with_duplicate_module/test.dara create mode 100644 test/fixtures/package_with_duplicate_module/test2.dara create mode 100644 test/fixtures/package_with_interface/Darafile create mode 100644 test/fixtures/package_with_interface/test.dara create mode 100644 test/fixtures/package_with_invalid_darafile/Darafile create mode 100644 test/fixtures/package_with_libraries/Darafile create mode 100644 test/fixtures/package_with_libraries_installed/.libraries.json create mode 100644 test/fixtures/package_with_libraries_installed/Darafile create mode 100644 test/fixtures/package_with_libraries_installed/libraries/std/Darafile create mode 100644 test/fixtures/package_with_libraries_local/Darafile create mode 100644 test/fixtures/package_with_libraries_local/libraries/std/Darafile create mode 100644 test/fixtures/package_with_model/Darafile create mode 100644 test/fixtures/package_with_model/test.dara create mode 100644 test/fixtures/package_with_module/Darafile create mode 100644 test/fixtures/package_with_module/test.dara create mode 100644 test/fixtures/package_with_multi_dmain/Darafile create mode 100644 test/fixtures/package_with_multi_dmain/test.dara create mode 100644 test/fixtures/package_with_multi_dmain/test2.dara delete mode 100644 test/fixtures/variables/Darafile delete mode 100644 test/fixtures/variables/main.dara delete mode 100644 test/fixtures/variables/oss.dara delete mode 100644 test/import.test.js create mode 100644 test/interface_analyser.test.js create mode 100644 test/interface_parser.test.js create mode 100644 test/main_analyser.test.js create mode 100644 test/main_parser.test.js create mode 100644 test/model_analyser.test.js create mode 100644 test/model_parser.test.js create mode 100644 test/module_analyser.test.js create mode 100644 test/module_parser.test.js create mode 100644 test/package.test.js delete mode 100644 test/parser.test.js delete mode 100644 test/semantic.test.js create mode 100644 test/tokens.test.js diff --git a/index.js b/index.js index db0cac0..158de9c 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,6 @@ 'use strict'; -const { - analyze, getChecker -} = require('./lib/semantic'); -const util = require('./lib/util'); -const Tag = require('./lib/tag'); -const builtin = require('./lib/builtin'); -const comment = require('./lib/comment'); -const pkg = require('./package.json'); - -function parse(source, filePath) { - const ast = analyze(source, filePath); - ast.parserVersion = pkg.version; - return ast; -} - -module.exports = { - parse, - Tag, - util, - builtin, - comment, - getChecker -}; +const { Tag } = require('./lib/tag'); +exports.Tag = Tag; +exports.Package = require('./lib/package'); +exports.comment = require('./lib/comment'); diff --git a/lib/analyser.js b/lib/analyser.js new file mode 100644 index 0000000..ff0e9b5 --- /dev/null +++ b/lib/analyser.js @@ -0,0 +1,86 @@ +'use strict'; + +const { Tag } = require('./tag'); + +class Analyser { + constructor(file, pkg) { + // 文件级别 + this.filename = file.filename; + this.source = file.source; + // 包级别 + this.pkg = pkg; + // 初始化其他内部状态 + this.dependencies = new Map(); + // 计数器 + this.usedTypes = new Map(); + this.usedPackages = new Map(); + this.usedComponents = new Map(); + // used to flag $dara + this.usedFeatures = new Map(); + } + + error(message, token) { + if (token) { + const loc = token.loc; + console.error(`${this.filename}:${loc.start.line}:${loc.start.column}`); + console.error(`${this.source.split('\n')[loc.start.line - 1]}`); + console.error(`${' '.repeat(loc.start.column - 1)}^`); + } + + throw new SyntaxError(message); + } + + checkImports(ast) { + if (ast.imports.length === 0) { + return; + } + + for (let i = 0; i < ast.imports.length; i++) { + const item = ast.imports[i]; + const aliasId = item.aliasId.lexeme; + + // checkpoint: 检查是否在 Darafile 中声明外部包 + if (!this.pkg.libraries.has(aliasId)) { + this.error(`the package '${aliasId}' not defined in Darafile`, item.aliasId); + } + + // checkpoint: 不允许重复引入外部包 + if (this.dependencies.has(aliasId)) { + this.error(`the package id '${aliasId}' has been imported`, item.aliasId); + } + + this.dependencies.set(aliasId, this.pkg.libraries.get(aliasId)); + this.usedPackages.set(aliasId, new Map()); + } + } + + checkType(ast) { + if (ast.tag === Tag.TYPE) { + this.usedTypes.set(ast.lexeme, true); + } else if (ast.tag === Tag.ID) { + // checkpoint: 只能是包内的 model/module + if (!this.pkg.components.has(ast.lexeme)) { + this.error(`the type '${ast.lexeme}' is undefined`, ast); + } + this.usedComponents.set(ast.lexeme, this.pkg.components.get(ast.lexeme)); + } else if (ast.type === 'array') { + this.checkType(ast.itemType); + } else if (ast.type === 'map') { + this.checkType(ast.keyType); + this.checkType(ast.valueType); + } else if (ast.type === 'extern_component') { + if (!this.dependencies.has(ast.aliasId.lexeme)) { + this.error(`the package '${ast.aliasId.lexeme}' is un-imported`, ast.aliasId); + } + const pkg = this.dependencies.get(ast.aliasId.lexeme); + if (!pkg.components.has(ast.component.lexeme)) { + this.error(`'${ast.component.lexeme}' is undefined in '${ast.aliasId.lexeme}'`, ast.component); + } + this.usedPackages.get(ast.aliasId.lexeme).set(ast.component.lexeme, true); + } else { + throw new Error('unimplemented'); + } + } +} + +module.exports = Analyser; diff --git a/lib/base_lexer.js b/lib/base_lexer.js new file mode 100644 index 0000000..db04d71 --- /dev/null +++ b/lib/base_lexer.js @@ -0,0 +1,55 @@ +'use strict'; + +class BaseLexer { + constructor(source, filename, offset = {}) { + this.source = source; + this.filename = filename; + + this.index = offset.index || -1; + this.peek = ' '; + this.words = new Map(); + this.line = offset.line || 1; + this.column = offset.column || 0; + } + + // read and consume a char + getch() { + if (this.peek === '\n') { + // line number + this.line++; + this.column = 0; + } + this.index++; + this.column++; + this.peek = this.source[this.index]; // 其它返回实际字节值 + } + + // read a char by offset + readch(i = 0) { + // 只读取,不消费 + return this.source[this.index + i]; + } + + ungetch() { + this.index--; + this.column--; + this.peek = this.source[this.index]; // 其它返回实际字节值 + } + + reserve(word) { + if (this.words.has(word.lexeme)) { + throw new Error(`duplicate reserved word: ${word.lexeme}`); + } + this.words.set(word.lexeme, word); + } + + skipWhitespaces() { + // 忽略空格,和TAB ch =='\n' + while (this.peek === ' ' || this.peek === '\t' || + this.peek === '\n' || this.peek === '\r') { + this.getch(); + } + } +} + +module.exports = BaseLexer; diff --git a/lib/base_parser.js b/lib/base_parser.js new file mode 100644 index 0000000..ac3fd6e --- /dev/null +++ b/lib/base_parser.js @@ -0,0 +1,158 @@ +'use strict'; + +const { Tag, tip } = require('./tag'); + +class Parser { + constructor(lexer) { + this.lexer = lexer; + this.look = null; + } + + move() { + do { + this.look = this.lexer.scan(); + } while (this.look.tag === Tag.COMMENT); + } + + tagTip(tag) { + return tip(tag); + } + + getIndex() { + return this.look.index; + } + + imports() { + var imports = []; + + while (this.is(Tag.IMPORT)) { + const begin = this.getIndex(); + this.move(); + const aliasId = this.look; + this.match(Tag.PACK_ID); + this.match(';'); + let end = this.getIndex(); + + imports.push({ + type: 'import', + aliasId: aliasId, + tokenRange: [begin, end] + }); + } + + return imports; + } + + externComponent() { + const begin = this.getIndex(); + let t = this.look; + this.move(); + // for $A.B + this.match('.'); + const id = this.look; + this.match(Tag.ID); + const end = this.getIndex(); + return { + type: 'extern_component', + aliasId: t, + component: id, + loc: { + start: t.loc.start, + end: id.loc.end + }, + tokenRange: [begin, end] + }; + } + + baseType() { + if (this.look.tag === '[') { + this.move(); + const t = this.baseType(); + this.match(']'); + return { + type: 'array', + itemType: t + }; + } + + if (this.isWord(Tag.TYPE, 'map')) { + let t = this.look; + this.move(); + this.match('['); + const keyType = this.baseType(); + this.match(']'); + const valueType = this.baseType(); + return { + loc: { + start: t.loc.start, + end: valueType.loc.end + }, + type: 'map', + keyType: keyType, + valueType: valueType + }; + } + + if (this.is(Tag.ID)) { + var t = this.look; + this.move(); + return t; + } + + if (this.is(Tag.PACK_ID)) { + return this.externComponent(); + } + + if (this.is(Tag.TYPE)) { + let t = this.look; + this.move(); + return t; + } + + this.error(`expect base type, model id or array form`); + } + + match(tag) { + if (this.look.tag === tag) { + this.move(); + } else { + this.error(`Expect ${this.tagTip(tag)}, but ${this.tokenTip(this.look)}`); + } + } + + matchWord(tag, lexeme) { + if (this.look.tag === tag && this.look.lexeme === lexeme) { + this.move(); + } else { + this.error(`Expect ${this.tagTip(tag)} ${lexeme}, but ${this.tokenTip(this.look)}`); + } + } + + is(tag) { + return this.look.tag === tag; + } + + isWord(tag, lexeme) { + return this.look.tag === tag && this.look.lexeme === lexeme; + } + + tokenTip(token) { + if (!token.tag) { + return 'EOF'; + } + + return this.look; + } + + error(message) { + const lexer = this.lexer; + const token = this.look; + console.log(`${lexer.filename}:${token.loc.start.line}:${token.loc.start.column}`); + console.log(`${lexer.source.split('\n')[token.loc.start.line - 1]}`); + console.log(`${' '.repeat(token.loc.start.column - 1)}^`); + const prefix = `Unexpected token: ${this.tokenTip(token)}.`; + throw new SyntaxError(`${prefix} ${message}`); + } +} + +module.exports = Parser; diff --git a/lib/builtin.js b/lib/builtin.js deleted file mode 100644 index 44a22e3..0000000 --- a/lib/builtin.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -const { Tag } = require('./tag'); - -function _model(name, fields) { - return { - type: 'model', - modelName: { - tag: Tag.ID, - lexeme: name - }, - modelBody: { - type: 'modelBody', - nodes: fields - } - }; -} - -function _field(name, type, required = false) { - return { - 'attrs': [], - 'fieldName': { - 'lexeme': name, - 'tag': Tag.ID, - }, - 'fieldValue': { - 'fieldType': type, - 'type': 'fieldType' - }, - 'required': required, - 'type': 'modelField' - }; -} - -function _mapfield(name, keyType, valueType, required = false) { - return { - 'attrs': [], - 'fieldName': { - 'lexeme': name, - 'tag': Tag.ID, - }, - 'fieldValue': { - 'fieldType': 'map', - 'type': 'fieldType', - 'keyType': { - 'lexeme': keyType, - 'tag': Tag.TYPE - }, - 'valueType': { - 'lexeme': valueType, - 'tag': Tag.TYPE - } - }, - 'required': required, - 'type': 'modelField' - }; -} - -const builtin = new Map(); -// built-in types, starts with $ -builtin.set('$Model', _model('$Model', [])); -builtin.set('$Response', _model('$Response', [ - _field('statusCode', 'number', true), - _field('statusMessage', 'string', true), - _mapfield('headers', 'string', 'string', true), - _field('body', 'readable') -])); -builtin.set('$Request', _model('$Request', [ - _field('protocol', 'string'), - _field('port', 'number'), - _field('method', 'string', true), - _field('pathname', 'string', true), - _mapfield('query', 'string', 'string'), - _mapfield('headers', 'string', 'string'), - _field('body', 'readable') -])); -builtin.set('$Error', _model('$Error', [ - _field('name', 'string'), - _field('message', 'string'), - _field('code', 'string'), - _field('stack', 'string') -])); - -module.exports = builtin; diff --git a/lib/builtin/Darafile b/lib/builtin/Darafile new file mode 100644 index 0000000..de08f65 --- /dev/null +++ b/lib/builtin/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} \ No newline at end of file diff --git a/lib/builtin/error.dara b/lib/builtin/error.dara new file mode 100644 index 0000000..7925917 --- /dev/null +++ b/lib/builtin/error.dara @@ -0,0 +1,6 @@ +model Error { + name: string, + message: string, + code: string, + stack: string +} diff --git a/lib/builtin/model.dara b/lib/builtin/model.dara new file mode 100644 index 0000000..fbe70c1 --- /dev/null +++ b/lib/builtin/model.dara @@ -0,0 +1,3 @@ +interface Model { + +} \ No newline at end of file diff --git a/lib/builtin/request.dara b/lib/builtin/request.dara new file mode 100644 index 0000000..386aafe --- /dev/null +++ b/lib/builtin/request.dara @@ -0,0 +1,9 @@ +model Request { + protocol?: string, + port?: int32, + method: string, + pathname: string, + query?: map[string]string, + headers?: map[string]string, + body: readable +} diff --git a/lib/builtin/response.dara b/lib/builtin/response.dara new file mode 100644 index 0000000..03bc44f --- /dev/null +++ b/lib/builtin/response.dara @@ -0,0 +1,6 @@ +model Response { + statusCode: int32, + statusMessage: string, + headers: map[string]string, + body: readable +} diff --git a/lib/comment.js b/lib/comment.js index e1e30cb..f26e7d3 100644 --- a/lib/comment.js +++ b/lib/comment.js @@ -41,4 +41,4 @@ module.exports = { getFrontComments, getBackComments, getBetweenComments -}; +}; \ No newline at end of file diff --git a/lib/common_analyser.js b/lib/common_analyser.js new file mode 100644 index 0000000..30d1dd9 --- /dev/null +++ b/lib/common_analyser.js @@ -0,0 +1,1309 @@ +'use strict'; + +const assert = require('assert'); + +const { Tag } = require('./tag'); +const Env = require('./env'); +const BaseAnalyser = require('./analyser'); +const { + isNumber, isInteger +} = require('./util'); +const { display, getComponentName, isSameType } = require('./helper'); + +function _basic(name) { + return { + type: 'basic', + name: name + }; +} + +function _extern(aliasId, name) { + return { + type: 'extern_component', + aliasId, + component: name + }; +} + +function isSameNumber(expect, actual) { + if (isNumber(expect.name) && isNumber(actual.name)) { + if (isInteger(expect.name) && actual.name === 'integer') { + return true; + } + + if ((expect.name === 'long' || expect.name === 'ulong') && isInteger(actual.name)) { + return true; + } + + if (expect.name === 'ulong' && actual.name === 'long') { + return true; + } + } + + return false; +} + +function isBuiltinModel(type) { + return type.type === 'model' && type.name === 'Model' && type.pkg === '$builtin'; +} + +function findProperty(model, propName) { + return model.modelBody.nodes.find((item) => { + return item.fieldName.lexeme === propName; + }); +} + +function getObjectName(object) { + if (object.type === 'id') { + return object.id.lexeme; + } + if (object.type === 'property') { + return `${getObjectName(object.object)}.${object.property.lexeme}`; + } +} + +function loadModule(pkg, module) { + const current = module.pkg ? pkg.libraries.get(module.pkg) : pkg; + return current.components.get(module.name); +} + +class Analyser extends BaseAnalyser { + constructor(ctx, pkg) { + super(ctx, pkg); + // 初始化其他内部状态 + this.consts = new Map(); + this.properties = new Map(); + this.methods = new Map(); + this.name = ''; + // prechecked status + this.prechecked = false; + this.calledParentModule = false; + } + + assertAsModule(node) { + this.checkType(node); + const type = this.getType(node); + if (type.type !== 'module') { + this.error(`'${getComponentName(type)}' is not a module`, node); + } + return type; + } + + assertAsModel(node) { + this.checkType(node); + const type = this.getType(node); + if (type.type !== 'model') { + this.error(`'${getComponentName(type)}' is not a model`, node); + } + return type; + } + + assertAsInterface(node) { + this.checkType(node); + const type = this.getType(node); + if (type.type !== 'interface') { + this.error(`'${getComponentName(type)}' is not a interface`, node); + } + return type; + } + + visitParams(ast, env) { + assert.strictEqual(ast.type, 'params'); + const paramMap = new Map(); + for (var i = 0; i < ast.params.length; i++) { + const node = ast.params[i]; + assert.strictEqual(node.type, 'param'); + const name = node.paramName.lexeme; + if (paramMap.has(name)) { + // checkpoint: 重复参数名检查 + this.error(`redefined parameter '${name}'`, node.paramName); + } + paramMap.set(name, true); + this.checkType(node.paramType); + env.set(name, this.getType(node.paramType)); + } + } + + visitFunction(ast) { + assert.strictEqual(ast.type, 'function'); + const env = new Env(); + this.visitParams(ast.params, env); + this.checkType(ast.returnType); + + if (ast.functionBody) { + if (!ast.isStatic) { + env.set('__this', { + type: 'module', + name: this.name + }); + } + + const ctx = { + returnType: this.getType(ast.returnType), + isStatic: ast.isStatic, + isAsync: ast.isAsync, + local: env, + variables: new Map() + }; + assert.strictEqual(ast.functionBody.type, 'functionBody'); + this.visitStmts(ast.functionBody.stmts, ctx); + // checkpoint: 检查是否有 return statement + this.checkReturnStmt(ast.functionBody.stmts, ctx, ast.functionName); + this.checkUnreachableCode(ast.functionBody.stmts); + this.checkUnusedVariable(ctx.variables); + } + } + + visitStmts(ast, ctx) { + assert.strictEqual(ast.type, 'stmts'); + for (var i = 0; i < ast.stmts.length; i++) { + const node = ast.stmts[i]; + this.visitStmt(node, ctx); + } + } + + visitStmt(ast, ctx) { + if (ast.type === 'return') { + this.visitReturn(ast, ctx); + } else if (ast.type === 'if') { + this.visitIf(ast, ctx); + } else if (ast.type === 'throw') { + this.visitThrow(ast, ctx); + } else if (ast.type === 'assign') { + this.visitAssign(ast, ctx); + } else if (ast.type === 'break') { + // unnecessary to check + } else if (ast.type === 'declare') { + this.visitDeclareStmt(ast, ctx); + } else if (ast.type === 'try') { + this.visitTry(ast, ctx); + } else if (ast.type === 'while') { + this.visitWhile(ast, ctx); + } else if (ast.type === 'for') { + this.visitFor(ast, ctx); + } else if (ast.type === 'for_of') { + this.visitForOf(ast, ctx); + } else { + this.visitExpr(ast, ctx); + const type = this.getExprType(ast, ctx); + if (type.type === 'method_def' || type.type === 'module_def' || type.type === 'model_def' || type.type === 'package') { + this.error(`invalid expression`, ast.expr); + } + } + } + + // statements + visitDeclareStmt(ast, ctx) { + assert.strictEqual(ast.type, 'declare'); + const id = ast.id.lexeme; + // 当前作用域是否定义过 + if (ctx.local.has(id)) { + this.error(`the id '${id}' was defined`, ast.id); + } + + this.visitExpr(ast.expr, ctx); + const type = this.getExprType(ast.expr, ctx); + + let expected; + if (type.type === 'basic' && type.name === 'null') { + if (!ast.expectedType) { + this.error(`must declare type when value is null`, ast.id); + } + expected = this.getType(ast.expectedType); + } else { + if (ast.expectedType) { + expected = this.getType(ast.expectedType); + if (!this.isAssignable(expected, type, ast.expr)) { + this.error(`declared variable with mismatched type, ` + + `expected: ${display(expected)}, actual: ${display(type)}`, ast.id); + } + } + } + + ctx.local.set(id, expected || type); + ctx.variables.set(id, { id: ast.id, used: false }); + ast.expr.inferred = expected || type; + } + + visitReturn(ast, ctx) { + assert.strictEqual(ast.type, 'return'); + this.visitExpr(ast.expr, ctx); + + if (ctx.isInitMethod) { + if (ast.expr.type !== 'empty') { + this.error(`should not have return value in init method`, ast.expr); + } + return; + } + + // return type check + const actual = this.getExprType(ast.expr, ctx); + const expect = ctx.returnType; + if (!this.isAssignable(expect, actual, ast.expr)) { + console.log(ast); + this.error(`the return type is not expected, expect: ${display(expect)}, actual: ${display(actual)}`, ast.expr); + } + } + + visitAssign(ast, ctx) { + assert.strictEqual(ast.type, 'assign'); + if (ast.left.type === 'id') { + this.checkId(ast.left.id, ctx); + } else if (ast.left.type === 'member') { + this.visitMember(ast.left, ctx); + } else if (ast.left.type === 'property') { + this.visitProperty(ast.left, ctx); + } else { + throw new Error('unimplemented'); + } + const expected = this.getExprType(ast.left, ctx); + ast.left.inferred = expected; + this.visitExpr(ast.expr, ctx); + const actual = this.getExprType(ast.expr, ctx); + if (!this.isAssignable(expected, actual, ast.expr)) { + this.error(`can not assign ${display(actual)} to ${display(expected)}`, ast.expr); + } + } + + visitIf(ast, ctx) { + assert.strictEqual(ast.type, 'if'); + + for (let i = 0; i < ast.branches.length; i++) { + const branch = ast.branches[i]; + if (branch.type === 'if_branch') { + this.visitExpr(branch.condition, ctx); + // TODO: branch condition should be boolean type + } + this.visitStmts(branch.stmts, ctx); + } + } + + visitThrow(ast, ctx) { + assert.strictEqual(ast.type, 'throw'); + this.visitMap(ast.expr, ctx); + this.usedFeatures.set('throw', true); + } + + visitWhile(ast, ctx) { + assert.strictEqual(ast.type, 'while'); + ctx.local = new Env(ctx.local); + this.visitExpr(ast.condition, ctx); + if (!this.isBooleanType(ast.condition, ctx)) { + this.error(`the condition expr must be boolean type`, ast.condition); + } + this.visitStmts(ast.stmts, ctx); + ctx.local = ctx.local.preEnv; + } + + visitFor(ast, ctx) { + assert.strictEqual(ast.type, 'for'); + ctx.local = new Env(ctx.local); + this.visitExpr(ast.init, ctx); + this.visitExpr(ast.test, ctx); + if (ast.test.type !== 'empty' && !this.isBooleanType(ast.test, ctx)) { + this.error(`the test expr must be boolean type`, ast.condition); + } + this.visitExpr(ast.update, ctx); + this.visitStmts(ast.stmts, ctx); + ctx.local = ctx.local.preEnv; + } + + visitForOf(ast, ctx) { + assert.strictEqual(ast.type, 'for_of'); + ctx.local = new Env(ctx.local); + this.visitExpr(ast.right, ctx); + const listType = this.getExprType(ast.right, ctx); + if (listType.type !== 'array') { + this.error(`the list in for must be array type`, ast.right); + } + ctx.local.set(ast.left.id.lexeme, listType.itemType); + this.visitStmts(ast.stmts, ctx); + ctx.local = ctx.local.preEnv; + } + + visitTry(ast, ctx) { + assert.strictEqual(ast.type, 'try'); + this.visitStmts(ast.tryBlock, ctx); + if (ast.catchBlock) { + // create new local + var local = new Env(ctx.local); + local.set(ast.catchId.lexeme, _extern('$builtin', 'Error')); + ctx.local = local; + this.visitStmts(ast.catchBlock, ctx); + // restore the local + ctx.local = ctx.local.preEnv; + } + + if (ast.finallyBlock) { + this.visitStmts(ast.finallyBlock, ctx); + } + } + + visitExpr(ast, ctx) { + if (ast.type === 'string') { + // noop(); + } else if (ast.type === 'number') { + // noop(); + } else if (ast.type === 'boolean') { + // noop(); + } else if (ast.type === 'null') { + // noop(); + } else if (ast.type === 'map') { + this.visitMap(ast, ctx); + } else if (ast.type === 'id') { + this.checkId(ast.id, ctx); + } else if (ast.type === 'template_string') { + for (var i = 0; i < ast.elements.length; i++) { + var item = ast.elements[i]; + if (item.type === 'expr') { + this.visitExpr(item.expr, ctx); + } + } + } else if (ast.type === 'call') { + this.visitCall(ast, ctx); + } else if (ast.type === 'construct_module') { + this.visitConstructModule(ast, ctx); + } else if (ast.type === 'construct_model') { + this.visitConstructModel(ast, ctx); + } else if (ast.type === 'array') { + this.visitArray(ast, ctx); + } else if (ast.type === 'logical') { + this.visitExpr(ast.left, ctx); + // the expr type should be boolean + if (!this.isBooleanType(ast.left, ctx)) { + this.error(`the left expr must be boolean type`, ast.left); + } + this.visitExpr(ast.right, ctx); + // the expr type should be boolean + if (!this.isBooleanType(ast.right, ctx)) { + this.error(`the right expr must be boolean type`, ast.right); + } + } else if (ast.type === 'not') { + this.visitExpr(ast.expr, ctx); + if (!this.isBooleanType(ast.expr, ctx)) { + this.error(`the expr after ! must be boolean type`, ast.expr); + } + } else if (ast.type === 'super') { + this.visitSuperCall(ast, ctx); + } else if (ast.type === 'member') { + this.visitMember(ast, ctx); + } else if (ast.type === 'property') { + this.visitProperty(ast, ctx); + } else if (ast.type === 'to') { + this.visitTo(ast, ctx); + } else if (ast.type === 'inline') { + this.visitInlineCall(ast, ctx); + } else if (ast.type === 'declare_expr') { + this.visitDeclareExpr(ast, ctx); + } else if (ast.type === 'empty') { + // no op + } else if (ast.type === 'binary') { + this.visitExpr(ast.left, ctx); + this.visitExpr(ast.right, ctx); + const leftType = this.getExprType(ast.left, ctx); + const rightType = this.getExprType(ast.right, ctx); + if (!isSameType(leftType, rightType)) { + this.error(`the right expr type(${display(rightType)}) mismatch with left expr type(${display(leftType)})`, ast.right); + } + + } else if (ast.type === 'assign') { + this.visitAssign(ast, ctx); + } else { + console.log(ast); + throw new Error('unimplemented.'); + } + ast.inferred = this.getExprType(ast, ctx); + } + + // expressions + visitConstructModel(ast, ctx) { + // model in current package + const type = this.assertAsModel(ast.component); + const modelName = getComponentName(type); + const model = this.loadComponent(type); + + for (let i = 0; i < ast.fields.fields.length; i++) { + const field = ast.fields.fields[i]; + const name = field.key.lexeme; + const modelField = findProperty(model.ast, name); + if (!modelField) { + this.error(`the field '${name}' is undefined in model '${modelName}'`, field.key); + } + + this.visitExpr(field.expr, ctx); + + const type = this.getExprType(field.expr, ctx); + let expected = this.getType(modelField.fieldType); + + if (!this.eql([expected], [type])) { + this.error(`the field type are mismatched. expected ` + + `${display(expected)}, but ${display(type)}`, field.key); + } + field.inferred = type; + field.expectedType = expected; + } + } + + visitConstructModule(ast, ctx) { + const actual = []; + for (let i = 0; i < ast.args.length; i++) { + const arg = ast.args[i]; + this.visitExpr(arg, ctx); + actual.push(arg.inferred); + } + + const type = this.assertAsModule(ast.component); + const module = this.loadComponent(type); + if (!module.analyser.init) { + this.error(`the module '${getComponentName(type)}' has no init`, ast.component); + } + + const expected = module.analyser.getParameterTypes(module.analyser.init); + if (!this.eql(expected, actual)) { + this.error(`the parameter` + + ` types are mismatched. expected ` + + `new ${getComponentName(type)}(${expected.map((item) => display(item)).join(', ')}), but ` + + `new ${getComponentName(type)}(${actual.map((item) => display(item)).join(', ')})`, ast.component); + } + } + + visitDeclareExpr(ast, ctx) { + assert.strictEqual(ast.type, 'declare_expr'); + const id = ast.id.lexeme; + // 当前作用域是否定义过 + if (ctx.local.has(id)) { + this.error(`the id '${id}' was defined`, ast.id); + } + + if (ast.expr) { + this.visitExpr(ast.expr, ctx); + const type = this.getExprType(ast.expr, ctx); + + let expected; + if (type.type === 'basic' && type.name === 'null') { + if (!ast.expectedType) { + this.error(`must declare type when value is null`, ast.id); + } + expected = this.getType(ast.expectedType); + } else { + if (ast.expectedType) { + expected = this.getType(ast.expectedType); + if (!this.isAssignable(expected, type, ast.expr)) { + this.error(`declared variable with mismatched type, ` + + `expected: ${display(expected)}, actual: ${display(type)}`, ast.id); + } + } + } + ctx.local.set(id, expected || type); + ctx.variables.set(id, { id: ast.id, used: false }); + ast.expr.inferred = expected || type; + } + } + + loadComponent(type) { + const pkg = type.pkg ? this.pkg.libraries.get(type.pkg) : this.pkg; + return pkg.components.get(type.name); + } + + visitSuperCall(ast, ctx) { + if (!ctx.isInitMethod) { + this.error(`super only allowed in init method`, ast); + } + + if (!this.parentModule) { + this.error(`this module have no parent module`, ast); + } + + let module = loadModule(this.pkg, this.parentModule); + if (!module.analyser.init) { + this.error(`the parent module '${getComponentName(this.parentModule)}' have no init method`, ast); + } + + const expected = this.getParameterTypes(module.analyser.init); + + const actual = []; + for (let i = 0; i < ast.args.length; i++) { + const arg = ast.args[i]; + this.visitExpr(arg, ctx); + const type = this.getExprType(arg, ctx); + actual.push(type); + } + + if (!this.eql(expected, actual)) { + this.error(`the parameter` + + ` types are mismatched. expected ` + + `${getComponentName(this.parentModule)}(${expected.map((item) => display(item)).join(', ')}), but ` + + `${getComponentName(this.parentModule)}(${actual.map((item) => display(item)).join(', ')})`, ast); + } + + this.calledParentModule = true; + } + + visitInlineCall(ast, ctx) { + assert.strictEqual(ast.type, 'inline'); + const actual = []; + for (let i = 0; i < ast.args.length; i++) { + const arg = ast.args[i]; + this.visitExpr(arg, ctx); + const type = this.getExprType(arg, ctx); + actual.push(type); + } + + const name = ast.name.lexeme; + switch (name) { + case '#append': + { + if (actual.length !== 2) { + this.error(`the #append parameter length expect 2, but ${actual.length}`, ast.name); + } + const [listType, itemType] = actual; + if (listType.type !== 'array') { + this.error(`must be array type`, ast.args[0]); + } + + if (!this.isAssignable(listType.itemType, itemType)) { + this.error(`the item type is not match with list`, ast.args[1]); + } + ast.inferred = listType; + } + this.usedFeatures.set('inline_append', true); + break; + case '#delete': + { + if (actual.length !== 2) { + this.error(`the #delete parameter length expect 2, but ${actual.length}`, ast.name); + } + const [mapType, keyType] = actual; + if (mapType.type !== 'map') { + this.error(`must be map type`, ast.args[0]); + } + if (!(keyType.type === 'basic' && keyType.name === 'string')) { + this.error(`must be string type`, ast.args[1]); + } + ast.inferred = _basic('void'); + } + this.usedFeatures.set('inline_delete', true); + break; + case '#length': + { + if (actual.length !== 1) { + this.error(`the #length parameter length expect 1, but ${actual.length}`, ast.name); + } + const [listType] = actual; + if (listType.type !== 'array') { + this.error(`must be array type`, ast.args[0]); + } + ast.inferred = _basic('int32'); + } + this.usedFeatures.set('inline_length', true); + break; + default: + this.error(`un-supported inline call(${name})`, ast.name); + } + } + + visitCall(ast, ctx) { + assert.strictEqual(ast.type, 'call'); + const actual = []; + for (let i = 0; i < ast.args.length; i++) { + const arg = ast.args[i]; + this.visitExpr(arg, ctx); + const type = this.getExprType(arg, ctx); + actual.push(type); + } + + if (ast.callee.type === 'id') { + const id = ast.callee.id; + if (id.tag === Tag.VID) { + this.error('can not call with property', id); + } + + if (id.tag === Tag.PACK_ID) { + this.error('can not call with package', id); + } + + if (id.tag === Tag.ID) { + // function + const name = id.lexeme; + if (!this.methods.has(name)) { + this.error(`the function '${name}' is undefined`, id); + } + + const def = this.methods.get(name); + if (def.type === 'function') { + if (!ctx.isAsync && def.isAsync) { + this.error(`the async function only can be used in async function`, id); + } + + if (ctx.isStatic && !def.isStatic) { + this.error(`the function can not be used in static function`, id); + } + } + + const expected = this.getParameterTypes(def); + + if (!this.eql(expected, actual)) { + this.error(`the parameter` + + ` types are mismatched. expected ` + + `${name}(${expected.map((item) => display(item)).join(', ')}), but ` + + `${name}(${actual.map((item) => display(item)).join(', ')})`, id); + } + + ast.isAsync = def.isAsync; + ast.isStatic = def.isStatic; + ast.hasThrow = def.isAsync || def.hasThrow; + ast.inferred = this.getType(def.returnType); + return; + } + } + + if (ast.callee.type === 'property') { + this.visitExpr(ast.callee, ctx); + const type = this.getExprType(ast.callee, ctx); + if (type.type !== 'method_def') { + this.error(`can not call with non-method`, ast.callee); + } + + const component = this.loadComponent({ name: type.module, pkg: type.pkg }); + const def = component.analyser.methods.get(type.name); + const expected = component.analyser.getParameterTypes(def); + const name = getObjectName(ast.callee); + if (!this.eql(expected, actual)) { + this.error(`the parameter` + + ` types are mismatched. expected ` + + `${name}(${expected.map((item) => display(item)).join(', ')}), but ` + + `${name}(${actual.map((item) => display(item)).join(', ')})`, ast.callee); + } + + ast.isAsync = def.isAsync; + ast.isStatic = def.isStatic; + ast.hasThrow = def.isAsync || def.hasThrow; + ast.inferred = component.analyser.getType(def.returnType); + if (ast.inferred.type === 'model') { + ast.inferred.pkg = type.pkg; + } + return; + } + + console.log(ast); + throw new Error('un-implemented'); + } + + visitMember(ast, ctx) { + assert.strictEqual(ast.type, 'member'); + this.visitExpr(ast.object, ctx); + this.visitExpr(ast.index, ctx); + + const objectType = this.getExprType(ast.object, ctx); + if (objectType.type !== 'map' && objectType.type !== 'array') { + this.error('the [] form only support map or array type', ast.object); + } + + if (objectType.type === 'map') { + if (!this.isStringType(ast.index, ctx)) { + this.error('the expr must be string type for map', ast.index); + } + } + + if (objectType.type === 'array') { + if (!this.isNumberType(ast.index, ctx)) { + this.error('the expr must be integer type for array', ast.index); + } + } + } + + visitProperty(ast, ctx) { + assert.strictEqual(ast.type, 'property'); + const prop = ast.property.lexeme; + + this.visitExpr(ast.object, ctx); + + const objectType = this.getExprType(ast.object, ctx); + if (objectType.type !== 'model' && objectType.type !== 'module' + && objectType.type !== 'module_def' && objectType.type !== 'package') { + this.error(`only can use '.' after ${getObjectName(ast.object)}`, ast.property); + } + + if (objectType.type === 'package') { + const pkg = this.pkg.libraries.get(objectType.name); + if (!pkg.components.has(prop)) { + this.error(`${prop} is undefined in package '${objectType.name}'`, ast.property); + } + const component = pkg.components.get(prop); + if (component.type !== 'module') { + this.error(`${prop} is not a module`, ast.property); + } + return; + } + + if (objectType.type === 'module_def') { + const module = loadModule(this.pkg, objectType); + if (!module.analyser.methods.has(prop)) { + this.error(`the method '${prop}' is undefined ` + + `in module '${getComponentName(objectType)}'`, ast.property); + } + const method = module.analyser.methods.get(prop); + if (!method.isStatic) { + this.error(`'${getComponentName(objectType)}.${prop}' is not static method`, ast.property); + } + return; + } + + if (objectType.type === 'module') { + const module = loadModule(this.pkg, objectType); + if (!module.analyser.methods.has(prop)) { + this.error(`the method '${prop}' is undefined ` + + `in module '${getComponentName(objectType)}'`, ast.property); + } + const method = module.analyser.methods.get(prop); + if (method.isStatic) { + this.error(`'${getComponentName(objectType)}.${prop}' is static method`, ast.property); + } + return; + } + + const component = this.loadComponent(objectType); + if (component.type === 'model') { + const find = findProperty(component.ast, prop); + if (!find) { + this.error(`the field '${prop}' is undefined ` + + `in model ${objectType.name}`, ast.property); + } + + return; + } + + console.log(ast); + throw new Error('un-implement'); + } + + visitMap(ast, ctx) { + assert.strictEqual(ast.type, 'map'); + for (var i = 0; i < ast.fields.length; i++) { + const field = ast.fields[i]; + if (field.type === 'mapField') { + this.visitExpr(field.expr, ctx); + } else if (field.type === 'expandField') { + this.visitExpr(field.expr, ctx); + const type = field.expr.inferred; + if (type.type !== 'map') { + this.error(`can not expand non-map expression`, field.expr); + } + } + } + ast.inferred = this.getExprType(ast, ctx); + } + + visitArray(ast, ctx) { + assert.strictEqual(ast.type, 'array'); + for (var i = 0; i < ast.items.length; i++) { + this.visitExpr(ast.items[i], ctx); + } + } + + visitTo(ast, ctx) { + assert.deepEqual(ast.type, 'to'); + this.visitExpr(ast.from, ctx); + const fromType = this.getExprType(ast.from, ctx); + if (fromType.type !== 'map') { + this.error(`only map can work with model`, ast.from); + } + this.checkType(ast.to); + const toType = this.getType(ast.to); + if (toType.type !== 'model') { + this.error(`'${getComponentName(toType)}' is not a model`, ast.to); + } + } + + // helpers + getExprType(ast, ctx) { + if (ast.inferred) { + return ast.inferred; + } + + if (ast.type === 'string') { + return _basic('string'); + } + + if (ast.type === 'number') { + return _basic(ast.value.type); + } + + if (ast.type === 'boolean') { + return _basic('boolean'); + } + + if (ast.type === 'map') { + return this.getMapType(ast, ctx); + } + + if (ast.type === 'id') { + return this.getIdType(ast.id, ctx); + } + + if (ast.type === 'null') { + return _basic('null'); + } + + if (ast.type === 'template_string') { + return _basic('string'); + } + + if (ast.type === 'super') { + return this.parentModule; + } + + if (ast.type === 'construct_module') { + return this.getType(ast.component); + } + + if (ast.type === 'construct_model') { + return this.getType(ast.component); + } + + if (ast.type === 'array') { + return this.getArrayType(ast, ctx); + } + + if (ast.type === 'not' || ast.type === 'logical') { + return _basic('boolean'); + } + + if (ast.type === 'binary') { + return _basic('boolean'); + } + + if (ast.type === 'member') { + const type = this.getExprType(ast.object); + if (type.type === 'map') { + return type.valueType; + } + + if (type.type === 'array') { + return type.itemType; + } + } + + if (ast.type === 'property') { + const type = this.getExprType(ast.object, ctx); + return this.getPropertyType(type, ast.property.lexeme); + } + + if (ast.type === 'to') { + return this.getType(ast.to); + } + + if (ast.type === 'empty') { + return _basic('void'); + } + + if (ast.type === 'declare_expr') { + return _basic('void'); + } + + if (ast.type === 'assign') { + return _basic('void'); + } + + console.log(ast); + throw new Error('can not get type'); + } + + getMapType(ast, ctx) { + if (ast.fields.length === 0) { + return { + type: 'map', + keyType: _basic('string'), + valueType: _basic('any') + }; + } + + var current; + var same = true; + for (let i = 0; i < ast.fields.length; i++) { + const field = ast.fields[i]; + if (field.type === 'mapField') { + let type = this.getExprType(field.expr, ctx); + if (current && !isSameType(current, type)) { + same = false; + break; + } + current = type; + } else if (field.type === 'expandField') { + let type = this.getExprType(field.expr, ctx); + if (current && !isSameType(current, type.valueType)) { + same = false; + break; + } + current = type.valueType; + } + } + + return { + type: 'map', + keyType: _basic('string'), + valueType: same ? current : _basic('any') + }; + } + + getArrayType(ast, ctx) { + if (ast.items.length === 0) { + return { + type: 'array', + itemType: _basic('any') + }; + } + + let current; + let same = true; + for (let i = 0; i < ast.items.length; i++) { + const type = this.getExprType(ast.items[i], ctx); + if (current && !isSameType(current, type)) { + same = false; + break; + } + current = type; + } + return { + type: 'array', + itemType: same ? current : _basic('any') + }; + } + + getType(t) { + if (t.type === 'array') { + return { + type: 'array', + itemType: this.getType(t.itemType) + }; + } + + if (t.type === 'map') { + return { + type: 'map', + keyType: this.getType(t.keyType), + valueType: this.getType(t.valueType) + }; + } + + if (t.tag === Tag.ID) { + if (this.pkg.components.has(t.lexeme)) { + const component = this.pkg.components.get(t.lexeme); + return { + type: component.type, + name: t.lexeme + }; + } + } + + if (t.type === 'extern_component') { + const pkg = this.pkg.libraries.get(t.aliasId.lexeme); + const component = pkg.components.get(t.component.lexeme); + return { + type: component.type, + name: t.component.lexeme, + pkg: t.aliasId.lexeme + }; + } + + if (t.tag === Tag.TYPE) { + return _basic(t.lexeme); + } + + console.log(t); + throw new Error('un-implemented'); + } + + checkPkgID(id) { + assert.strictEqual(id.tag, Tag.PACK_ID); + const name = id.lexeme; + if (!this.dependencies.has(name)) { + this.error(`the package '${name}' is un-imported`, id); + } + } + + checkId(id, env) { + const name = id.lexeme; + if (id.tag === Tag.VID) { + if (env.isStatic) { + this.error(`the module property can not used in static function`, id); + } + + if (!this.getInstanceProperty(name)) { + this.error(`the property '${name}' is undefined`, id); + } + + // checkpoint: 有继承关系时,构造方法中,访问实例属性前必须先调用 super + if (env.isInitMethod && this.parentModule && !this.calledParentModule) { + this.error(`'super' must be called before accessing property`, id); + } + + return; + } + + if (id.tag === Tag.PACK_ID) { + this.checkPkgID(id); + return; + } + + // 未定义变量检查 + if (env.local && env.local.hasDefined(name)) { + // 是否在作用域链上定义过 + // id.type = 'variable'; + if (env.variables.has(name)) { + env.variables.get(name).used = true; + } + return; + } + + if (this.pkg.components.has(name)) { + this.usedComponents.set(name, this.pkg.components.get(name)); + return; + } + + this.error(`id '${name}' undefined`, id); + } + + getIdType(id, env) { + const name = id.lexeme; + + if (id.tag === Tag.VID) { + const def = this.getInstanceProperty(name); + return this.getType(def.value); + } + + if (id.tag === Tag.PACK_ID) { + return { type: 'package', name: name }; + } + + if (env.local && env.local.hasDefined(name)) { + // 返回作用域链上定义的值 + return env.local.get(name); + } + + if (this.pkg.components.has(name)) { + const component = this.pkg.components.get(name); + if (component.type === 'module') { + return { type: 'module_def', name: name }; + } + + return { type: 'model_def', name: name }; + } + + throw new Error('Can not get the type for variable'); + } + + getPropertyType(type, propName) { + if (type.type === 'package') { + return { type: 'module_def', name: propName, pkg: type.name }; + } else if (type.type === 'module_def' || type.type === 'module') { + const module = loadModule(this.pkg, type); + const analyser = module.analyser; + if (analyser.methods.has(propName)) { + if (type.pkg) { + return { type: 'method_def', name: propName, module: type.name, pkg: type.pkg }; + } + return { type: 'method_def', name: propName, module: type.name }; + } + } else if (type.type === 'model') { + let model = this.loadComponent(type); + const find = findProperty(model.ast, propName); + return this.getType(find.fieldType); + } + + console.log(type); + throw new Error('un-implemented'); + } + + getParameterTypes(def) { + const expected = []; + const params = def.params.params; + for (let i = 0; i < params.length; i++) { + expected.push(this.getType(params[i].paramType)); + } + return expected; + } + + checkReturnStmt(ast, ctx, name) { + if (ctx.returnType.type === 'basic' && ctx.returnType.name === 'void') { + // no check for void + return; + } + + if (!this.hasReturnStmt(ast)) { + this.error(`no return statement`, name); + } + } + + hasReturnStmt(ast) { + assert.strictEqual(ast.type, 'stmts'); + + if (ast.stmts.length === 0) { + return false; + } + + const stmt = ast.stmts[ast.stmts.length - 1]; + if (stmt.type === 'return') { + return true; + } + + if (stmt.type === 'throw') { + return true; + } + + if (stmt.type === 'if') { + // only if + if (stmt.branches.length === 1) { + return false; + } + + for (let index = 0; index < stmt.branches.length; index++) { + const branch = stmt.branches[index]; + if (!this.hasReturnStmt(branch.stmts)) { + return false; + } + } + + return true; + } + + // TODO: try/catch/finally, for + + return false; + } + + checkUnreachableCode(ast) { + var breaked = false; + for (let index = 0; index < ast.stmts.length; index++) { + const stmt = ast.stmts[index]; + if (breaked) { + this.error('unreachable code', stmt); + } + if (this.hasBreaked(stmt)) { + breaked = true; + } + } + return breaked; + } + + checkUnusedVariable(variables) { + for (const [name, item] of variables) { + if (!item.used) { + this.error(`unused variable '${name}'`, item.id); + } + } + } + + hasBreaked(ast) { + if (ast.type === 'return') { + return true; + } + + if (ast.type === 'throw') { + return true; + } + + if (ast.type === 'if') { + // only if + if (ast.branches.length === 1) { + return false; + } + + for (let index = 0; index < ast.branches.length; index++) { + const branch = ast.branches[index]; + if (!this.checkUnreachableCode(branch.stmts)) { + return false; + } + } + + return true; + } + + return false; + } + + isBooleanType(expr, ctx) { + const type = this.getExprType(expr, ctx); + return type.type === 'basic' && type.name === 'boolean'; + } + + isStringType(expr, ctx) { + const type = this.getExprType(expr, ctx); + return type.type === 'basic' && type.name === 'string'; + } + + isNumberType(expr, env) { + const type = this.getExprType(expr, env); + return type.type === 'basic' && isNumber(type.name); + } + + isAssignable(expected, actual, expr) { + if (isSameType(expected, actual)) { + return true; + } + + if (isSameNumber(expected, actual)) { + return true; + } + + // actual is null + if (actual.type === 'basic' && actual.name === 'null') { + return true; + } + + if (expected.type === 'map' && actual.type === 'map') { + if (expr && expr.type === 'map' && expr.fields.length === 0) { + return true; + } + } + + if (expected.type === 'array' && actual.type === 'array') { + if (expr && expr.type === 'array' && expr.items.length === 0) { + return true; + } + } + + // any = other type + if (expected.type === 'basic' && expected.name === 'any') { + return true; + } + + // $Model vs model + if (isBuiltinModel(expected) && actual.type === 'model') { + return true; + } + + if (expected.type === 'interface' && actual.type === 'module') { + const current = loadModule(this.pkg, actual); + const find = current.analyser.implements.find((item) => { + return item.name === expected.name && item.pkg === expected.pkg; + }); + if (find) { + return true; + } + } + + return false; + } + + eql(expects, actuals) { + if (expects.length !== actuals.length) { + return false; + } + + for (var i = 0; i < expects.length; i++) { + const expect = expects[i]; + const actual = actuals[i]; + + if (this.isAssignable(expect, actual)) { + continue; + } + + return false; + } + + return true; + } +} + +module.exports = Analyser; diff --git a/lib/env.js b/lib/env.js index 2883518..244a69c 100644 --- a/lib/env.js +++ b/lib/env.js @@ -39,4 +39,4 @@ class Env { } } -module.exports = Env; +module.exports = Env; \ No newline at end of file diff --git a/lib/helper.js b/lib/helper.js index 7daded2..2670396 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -1,25 +1,68 @@ 'use strict'; -function isLetter(c) { - if (typeof c !== 'string') { - return false; - } - // letter = "A" … "Z" | "a" … "z" - var code = c.charCodeAt(0); - return (code >= 0x41 && code <= 0x5a || - code >= 0x61 && code <= 0x7a); +function getComponentName(m) { + if (m.pkg) { + return `${m.pkg}.${m.name}`; + } + return `${m.name}`; +} + +function display(item) { + if (item.type === 'basic') { + return item.name; + } + + if (item.type === 'map') { + return `map[${display(item.keyType)}]${display(item.valueType)}`; + } + + if (item.type === 'array') { + return `[${display(item.itemType)}]`; + } + + if (item.type === 'module') { + return getComponentName(item); + } + + if (item.type === 'model') { + return item.name; + } + + if (item.type === 'interface') { + return getComponentName(item); + } + + console.log(item); + throw new Error('unimplemented.'); } -function isDecimalDigit(c) { - if (typeof c !== 'string') { - return false; +function isSameType(expect, actual) { + if (expect.type === 'basic' && actual.type === 'basic') { + return expect.name === actual.name; + } + + if (expect.type === 'array' && actual.type === 'array') { + return isSameType(expect.itemType, actual.itemType); + } + + if (expect.type === 'map' && actual.type === 'map') { + return isSameType(expect.keyType, actual.keyType) && + isSameType(expect.valueType, actual.valueType); + } + + if (expect.type === 'module' && actual.type === 'module') { + return expect.name === actual.name; } - // decimalDigit = "0" … "9" - var code = c.charCodeAt(0); - return code >= 0x30 && code <= 0x39; + + if (expect.type === 'model' && actual.type === 'model') { + return expect.name === actual.name; + } + + return false; } module.exports = { - isLetter, - isDecimalDigit + display, + getComponentName, + isSameType }; diff --git a/lib/interface_analyser.js b/lib/interface_analyser.js new file mode 100644 index 0000000..da99cf0 --- /dev/null +++ b/lib/interface_analyser.js @@ -0,0 +1,46 @@ +'use strict'; + +const assert = require('assert'); +const debug = require('debug')('dara:analyser:interface'); + +class Analyser extends require('./common_analyser') { + constructor(ctx, pkg) { + super(ctx, pkg); + // 初始化其他内部状态 + this.methods = new Map(); + this.name = ''; + } + + check(ast) { + assert.strictEqual(ast.type, 'interface'); + debug(`start pre analyse module: ${this.filename}`); + this.name = ast.name.lexeme; + this.checkImports(ast); + // instance properties + this.preCheckMethods(ast); + this.prechecked = true; + } + + preCheckMethods(ast) { + // checkpoint: 不能重复定义 function + ast.interfaceBody.nodes.forEach((node) => { + const key = node.functionName.lexeme; + // 重复定义检查 + if (this.methods.has(key)) { + this.error(`redefined function '${key}'`, node.functionName); + } + + this.methods.set(key, node); + }); + } + + checkMethods(ast) { + assert.strictEqual(this.prechecked, true, 'must pre-check before check methods'); + debug(`start post analyse module: ${this.filename}`); + ast.interfaceBody.nodes.forEach((item) => { + this.visitFunction(item); + }); + } +} + +module.exports = Analyser; diff --git a/lib/keyword.js b/lib/keyword.js index a69d23a..8af94f9 100644 --- a/lib/keyword.js +++ b/lib/keyword.js @@ -7,4 +7,4 @@ class Keyword { } } -module.exports = Keyword; +module.exports = Keyword; \ No newline at end of file diff --git a/lib/lexer.js b/lib/lexer.js index 91eaf81..1e525f1 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -1,43 +1,59 @@ 'use strict'; -const { - isLetter, isDecimalDigit -} = require('./helper'); - const { Tag } = require('./tag'); const Keyword = require('./keyword'); -const { - Lexer: BaseLexer, - Token -} = require('@jacksontian/skyline'); +const BaseLexer = require('./base_lexer'); const { + Token, StringLiteral, NumberLiteral, Annotation, Comment, TemplateElement, WordToken, + LogicalToken, OperatorToken } = require('./tokens'); +function isLetter(c) { + if (typeof c !== 'string') { + return false; + } + // letter = "A" … "Z" | "a" … "z" + var code = c.charCodeAt(0); + return (code >= 0x41 && code <= 0x5a || + code >= 0x61 && code <= 0x7a); +} + +function isDecimalDigit(c) { + if (typeof c !== 'string') { + return false; + } + // decimalDigit = "0" … "9" + var code = c.charCodeAt(0); + return code >= 0x30 && code <= 0x39; +} + class Lexer extends BaseLexer { constructor(source, filename) { super(source, filename); + this.reserve(new Keyword('module', Tag.MODULE)); + this.reserve(new Keyword('model', Tag.MODEL)); this.reserve(new Keyword('import', Tag.IMPORT)); this.reserve(new Keyword('extends', Tag.EXTENDS)); this.reserve(new Keyword('super', Tag.SUPER)); + this.reserve(new Keyword('interface', Tag.INTERFACE)); + this.reserve(new Keyword('implements', Tag.IMPLEMENTS)); this.reserve(new Keyword('const', Tag.CONST)); - this.reserve(new Keyword('rpc', Tag.RPC)); this.reserve(new Keyword('static', Tag.STATIC)); // data types this.reserve(new Keyword('class', Tag.TYPE)); this.reserve(new Keyword('void', Tag.TYPE)); this.reserve(new Keyword('string', Tag.TYPE)); - this.reserve(new Keyword('number', Tag.TYPE)); this.reserve(new Keyword('integer', Tag.TYPE)); this.reserve(new Keyword('int8', Tag.TYPE)); this.reserve(new Keyword('int16', Tag.TYPE)); @@ -55,7 +71,6 @@ class Lexer extends BaseLexer { this.reserve(new Keyword('bytes', Tag.TYPE)); this.reserve(new Keyword('any', Tag.TYPE)); this.reserve(new Keyword('map', Tag.TYPE)); - this.reserve(new Keyword('object', Tag.TYPE)); this.reserve(new Keyword('writable', Tag.TYPE)); this.reserve(new Keyword('readable', Tag.TYPE)); // boolean @@ -70,8 +85,8 @@ class Lexer extends BaseLexer { this.reserve(new Keyword('throw', Tag.THROW)); this.reserve(new Keyword('while', Tag.WHILE)); this.reserve(new Keyword('for', Tag.FOR)); + this.reserve(new Keyword('of', Tag.OF)); this.reserve(new Keyword('break', Tag.BREAK)); - this.reserve(new Keyword('var', Tag.VAR)); // module @@ -82,8 +97,13 @@ class Lexer extends BaseLexer { this.reserve(new Keyword('catch', Tag.CATCH)); this.reserve(new Keyword('finally', Tag.FINALLY)); + // to + this.reserve(new Keyword('to', Tag.TO)); // the state for template string this.inTemplate = false; + + this.tokenIndex = 0; + this.comments = new Map(); } error(message) { @@ -159,7 +179,7 @@ class Lexer extends BaseLexer { return new StringLiteral(str, { start, end - }); + }, this.tokenIndex++); } parseTemplateString() { @@ -175,7 +195,7 @@ class Lexer extends BaseLexer { this.getch(); return new TemplateElement(tpl, false, { start, end - }); + }, this.tokenIndex++); } } @@ -185,7 +205,7 @@ class Lexer extends BaseLexer { this.getch(); return new TemplateElement(tpl, true, { start, end - }); + }, this.tokenIndex++); } tpl += this.peek; @@ -197,7 +217,7 @@ class Lexer extends BaseLexer { decimalLit() { let v = ''; - if(isDecimalDigit(this.peek)){ + if (isDecimalDigit(this.peek)) { do { v += this.peek; this.getch(); @@ -235,35 +255,18 @@ class Lexer extends BaseLexer { if (this.peek === 'f') { this.getch(); type = 'float'; - return new NumberLiteral(parseFloat(v), type, { - start, - end: this.loc() - }); } else if (this.peek === 'd') { this.getch(); type = 'double'; - return new NumberLiteral(parseFloat(v), type, { - start, - end: this.loc() - }); } else if (this.peek === 'L') { this.getch(); type = 'long'; - return new NumberLiteral(parseInt(v), type, { - start, - end: this.loc() - }); - } - if (type === 'integer') { - return new NumberLiteral(parseInt(v), type, { - start, - end: this.loc() - }); - } - return new NumberLiteral(parseFloat(v), type, { + } + + return new NumberLiteral(v, type, { start, end: this.loc() - }); + }, this.tokenIndex++); } scan() { @@ -281,10 +284,12 @@ class Lexer extends BaseLexer { this.getch(); } - return new Comment(str, { + const comment = new Comment(str, { start: start, end: this.loc() - }); + }, this.tokenIndex++); + this.comments.set(this.tokenIndex, comment); + return comment; } if (this.readch(1) === '*' && this.readch(2) === '*') { @@ -304,7 +309,7 @@ class Lexer extends BaseLexer { return new Annotation(str, { start: start, end: this.loc() - }); + }, this.tokenIndex++); } this.error(`Only '//' or '/**' allowed`); @@ -324,7 +329,7 @@ class Lexer extends BaseLexer { this.getch(); return new TemplateElement(str, false, { start, end - }); + }, this.tokenIndex++); } } @@ -334,7 +339,7 @@ class Lexer extends BaseLexer { this.getch(); return new TemplateElement(str, true, { start, end - }); + }, this.tokenIndex++); } str += this.peek; @@ -353,6 +358,10 @@ class Lexer extends BaseLexer { this.inTemplate = true; return this.parseTemplateString(); } + case '@': + case '$': + case '#': + return this.parsePrefixId(); } // number = optionalSign decimalLit optionalFraction optionalType @@ -365,16 +374,14 @@ class Lexer extends BaseLexer { return this.parseNumber(); } - if (isLetter(this.peek) || this.peek === '_' || - this.peek === '$') { + if (isLetter(this.peek) || this.peek === '_') { let str = ''; do { str += this.peek; this.getch(); } while (isLetter(this.peek) || - isDecimalDigit(this.peek) || - this.peek === '_' || - this.peek === '-'); + isDecimalDigit(this.peek) || + this.peek === '_'); // reserve words if (this.words.has(str)) { @@ -382,27 +389,23 @@ class Lexer extends BaseLexer { return new WordToken(keyword.tag, keyword.lexeme, { start: start, end: this.loc() - }); + }, this.tokenIndex++); } return new WordToken(Tag.ID, str, { start: start, end: this.loc() - }); - } - - if (this.peek === '@') { - return this.parseVID(); + }, this.tokenIndex++); } if (this.peek === '&') { this.getch(); if (this.peek === '&') { this.getch(); - return new OperatorToken(Tag.AND, '&&', { + return new LogicalToken('&&', { start, end: this.loc() - }); + }, this.tokenIndex++); } this.error(`Unexpect ${this.peek} after '&', expect '&'`); @@ -412,44 +415,90 @@ class Lexer extends BaseLexer { this.getch(); if (this.peek === '|') { this.getch(); - return new OperatorToken(Tag.OR, '||', { + return new LogicalToken('||', { start, end: this.loc() - }); + }, this.tokenIndex++); } this.error(`Unexpect ${this.peek} after '|', expect '|'`); } + if (this.peek === '<') { + this.getch(); + if (this.peek === '=') { + this.getch(); + return new OperatorToken('<=', { + start, + end: this.loc() + }, this.tokenIndex++); + } + + return new OperatorToken('<', { + start, + end: this.loc() + }, this.tokenIndex++); + } + + if (this.peek === '>') { + this.getch(); + if (this.peek === '=') { + this.getch(); + return new OperatorToken('>=', { + start, + end: this.loc() + }, this.tokenIndex++); + } + + return new OperatorToken('>', { + start, + end: this.loc() + }, this.tokenIndex++); + } + var tok = new Token(this.peek, { start, end: this.loc() - }); + }, this.tokenIndex++); this.peek = ' '; return tok; } - parseVID() { + parsePrefixId() { let start = this.loc(); - let str = '@'; + const prefix = this.peek; + let str = ''; this.getch(); if (!isLetter(this.peek)) { - this.error(`Unexpect ${this.peek} after @`); + this.error(`Unexpect ${this.peek} after ${prefix}`); } do { str += this.peek; this.getch(); } while (isLetter(this.peek) || - isDecimalDigit(this.peek) || - this.peek === '_'); + isDecimalDigit(this.peek) || + this.peek === '_'); + + let type; + switch (prefix) { + case '@': + type = Tag.VID; + break; + case '$': + type = Tag.PACK_ID; + break; + case '#': + type = Tag.INLINE_ID; + break; + } - return new WordToken(Tag.VID, str, { + return new WordToken(type, `${prefix}${str}`, { start, end: this.loc() - }); + }, this.tokenIndex++); } } -module.exports = Lexer; +module.exports = Lexer; \ No newline at end of file diff --git a/lib/main_analyser.js b/lib/main_analyser.js new file mode 100644 index 0000000..130fb31 --- /dev/null +++ b/lib/main_analyser.js @@ -0,0 +1,84 @@ +'use strict'; + +const assert = require('assert'); +const debug = require('debug')('dara:analyser:main'); + +const Env = require('./env'); + +class Analyser extends require('./common_analyser') { + constructor(ctx, pkg) { + super(ctx, pkg); + // 初始化其他内部状态 + this.methods = new Map(); + // prechecked status + this.prechecked = false; + } + + check(ast) { + assert.strictEqual(ast.type, 'main'); + debug(`start pre analyse main: ${this.filename}`); + this.checkImports(ast); + this.preCheckMethods(ast); + this.prechecked = true; + } + + preCheckMethods(ast) { + // checkpoint: 不能重复定义 function + ast.mainBody.nodes.forEach((node) => { + if (node.type === 'function') { + const key = node.functionName.lexeme; + // 重复定义检查 + if (this.methods.has(key)) { + this.error(`redefined function '${key}'`, node.functionName); + } + + this.methods.set(key, node); + } + }); + + const entries = ast.mainBody.nodes.filter((node) => { + return node.type === 'main'; + }); + + if (entries.length > 1) { + this.error(`only one entry is allowed`, entries[1].main); + } + } + + visitMain(ast) { + assert.equal(ast.type, 'main'); + const env = new Env(); + this.visitParams(ast.params, env); + // this.checkType(ast.returnType); + + if (ast.functionBody) { + const ctx = { + returnType: {type: 'basic', name: 'void'}, + isStatic: ast.isStatic, + isAsync: ast.isAsync, + local: env, + variables: new Map() + }; + assert.equal(ast.functionBody.type, 'functionBody'); + this.visitStmts(ast.functionBody.stmts, ctx); + // checkpoint: 检查是否有 return statement + this.checkReturnStmt(ast.functionBody.stmts, ctx, ast.main); + this.checkUnreachableCode(ast.functionBody.stmts); + this.checkUnusedVariable(ctx.variables); + } + } + + checkMethods(ast) { + assert.strictEqual(this.prechecked, true, 'must pre-check before check methods'); + debug(`start post analyse module: ${this.filename}`); + ast.mainBody.nodes.forEach((item) => { + if (item.type === 'function') { + this.visitFunction(item); + } else { + this.visitMain(item); + } + }); + } +} + +module.exports = Analyser; diff --git a/lib/model_analyser.js b/lib/model_analyser.js new file mode 100644 index 0000000..fabec9c --- /dev/null +++ b/lib/model_analyser.js @@ -0,0 +1,39 @@ +'use strict'; + +const assert = require('assert'); + +const BaseAnalyser = require('./analyser'); + +class Analyser extends BaseAnalyser { + constructor(ctx, pkg) { + super(ctx, pkg); + } + + check(ast) { + assert.strictEqual(ast.type, 'model'); + this.checkImports(ast); + this.visitModel(ast); + // save used types on ast + ast.usedTypes = this.usedTypes; + } + + visitModel(ast) { + assert.strictEqual(ast.type, 'model'); + this.usedFeatures.set('defined_model', true); + const modelName = ast.name.lexeme; + const modelBody = ast.modelBody; + const keys = new Map(); + for (var i = 0; i < modelBody.nodes.length; i++) { + const node = modelBody.nodes[i]; + const fieldName = node.fieldName.lexeme; + // checkpoint: 字段名不得重复 + if (keys.has(fieldName)) { + this.error(`redefined field "${fieldName}" in model "${modelName}"`, node.fieldName); + } + keys.set(fieldName, true); + this.checkType(node.fieldType); + } + } +} + +module.exports = Analyser; diff --git a/lib/module_analyser.js b/lib/module_analyser.js new file mode 100644 index 0000000..4cd4cf7 --- /dev/null +++ b/lib/module_analyser.js @@ -0,0 +1,241 @@ +'use strict'; + +const assert = require('assert'); +const debug = require('debug')('dara:analyser:module'); + +const { Tag } = require('./tag'); +const Env = require('./env'); +const { getComponentName, display, isSameType } = require('./helper'); + +function loadModule(pkg, module) { + const current = module.pkg ? pkg.libraries.get(module.pkg) : pkg; + return current.components.get(module.name); +} + +class Analyser extends require('./common_analyser') { + constructor(ctx, pkg) { + super(ctx, pkg); + // 初始化其他内部状态 + this.consts = new Map(); + this.properties = new Map(); + this.methods = new Map(); + this.name = ''; + this.implements = []; + // prechecked status + this.prechecked = false; + this.calledParentModule = false; + } + + check(ast) { + assert.strictEqual(ast.type, 'module'); + debug(`start pre analyse module: ${this.filename}`); + this.name = ast.name.lexeme; + this.checkImports(ast); + this.checkExtends(ast); + this.checkImplements(ast); + // instance properties + this.checkTypes(ast); + this.checkInit(ast); + this.preCheckMethods(ast); + this.prechecked = true; + } + + checkExtends(ast) { + if (!ast.extends) { + return; + } + + this.checkType(ast.extends); + const type = this.getType(ast.extends); + if (type.type !== 'module') { + this.error(`'${getComponentName(type)}' is not a module`, ast.extends); + } + + if (ast.extends.tag === Tag.ID) { + const moduleId = ast.extends.lexeme; + // checkpoint: 不能自己继承自己 + if (moduleId === this.name) { + this.error(`'${moduleId}' can not extends itself`, ast.extends); + } + } + + this.parentModule = type; + } + + checkImplements(ast) { + const impls = new Map(); + + ast.implements.forEach((item) => { + // checkpoint: 只能实现一个接口 + const type = this.assertAsInterface(item); + const name = getComponentName(type); + // checkpoint: 不能重复实现一个接口 + if (impls.has(name)) { + this.error(`duplicate interface`, item); + } + this.implements.push(type); + impls.set(name, 'true'); + }); + } + + checkTypes(ast) { + this.vidCounter = new Map(); + ast.moduleBody.nodes.filter((item) => { + return item.type === 'type'; + }).forEach((node) => { + this.checkType(node.value); + + const key = node.vid.lexeme; + if (this.properties.has(key)) { + // 重复定义检查 + this.error(`redefined type '${key}'`, node.vid); + } + this.properties.set(key, node); + this.vidCounter.set(key, 0); + }); + } + + checkInit(ast) { + const inits = ast.moduleBody.nodes.filter((item) => { + return item.type === 'init'; + }); + + if (inits.length > 1) { + this.error('only one init can be allowed.', inits[1]); + } + + const instanceMethod = ast.moduleBody.nodes.find((item) => { + return (item.type === 'function' && item.isStatic === false); + }); + + const init = ast.moduleBody.nodes.find((item) => { + return item.type === 'init'; + }); + + this.init = init; + + if (instanceMethod) { + if (!init && !this.parentModule) { + this.error('must have a init when there is a non-static function', ast.moduleName); + } + } + } + + preCheckMethods(ast) { + // checkpoint: 不能重复定义 function + ast.moduleBody.nodes.forEach((node) => { + if (node.type === 'function') { + const key = node.functionName.lexeme; + // 重复定义检查 + if (this.methods.has(key)) { + this.error(`redefined function '${key}'`, node.functionName); + } + this.methods.set(key, node); + } + }); + } + + checkMethods(ast) { + assert.strictEqual(this.prechecked, true, 'must pre-check before check methods'); + debug(`start post analyse module: ${this.filename}`); + ast.moduleBody.nodes.forEach((item) => { + if (item.type === 'function') { + this.visitFunction(item); + } else if (item.type === 'init') { + this.visitInit(item); + } + }); + + if (this.implements.length > 0) { + for (let i = 0; i < this.implements.length; i++) { + const type = this.implements[i]; + const interface_ = this.loadComponent(type); + const methods = interface_.analyser.methods; + for (const [key, methodInInterface] of methods) { + if (!this.methods.has(key)) { + this.error(`must implement method ${getComponentName(type)}.${key}()`, ast.name); + } + const methodInModule = this.methods.get(key); + if (methodInModule.isAsync !== methodInInterface.isAsync) { + this.error(`the async modifier mismatched`, ast.name); + } + + const returnTypeInModule = this.getType(methodInModule.returnType); + const returnTypeInInterface = this.getType(methodInInterface.returnType); + if (!isSameType(returnTypeInModule, returnTypeInInterface)) { + this.error(`the return type mismatched, expect: ${display(returnTypeInInterface)}, but ${display(returnTypeInModule)}`, ast.name); + } + + const expectedTypes = this.getParameterTypes(methodInInterface); + const actualTypes = this.getParameterTypes(methodInModule); + if (expectedTypes.length !== actualTypes.length) { + this.error(`the parameter types mismatched, expect ${key}(${expectedTypes.map((item) => display(item)).join(', ')}), but ` + + `${key}(${actualTypes.map((item) => display(item)).join(', ')})`, ast.name); + } + + for (let j = 0; j < expectedTypes.length; j++) { + if (!isSameType(expectedTypes[j], actualTypes[j])) { + this.error(`the parameter types mismatched, expect ${key}(${expectedTypes.map((item) => display(item)).join(', ')}), but ` + + `${key}(${actualTypes.map((item) => display(item)).join(', ')})`, ast.name); + } + } + } + } + } + } + + visitInit(ast) { + assert.strictEqual(ast.type, 'init'); + const env = new Env(); + this.visitParams(ast.params, env); + + if (ast.initBody) { + const ctx = { + isStatic: false, + isAsync: false, + isInitMethod: true, + local: env, + variables: new Map() + }; + this.visitStmts(ast.initBody, ctx); + // checkpoint: 如果有继承关系,必须有 super 调用 + if (this.parentModule && !this.hasSuperCall(ast.initBody)) { + this.error(`must contain 'super' call`, ast); + } + } + } + + // helpers + getInstanceProperty(vid) { + let current = this; + if (current.properties.has(vid)) { + // check B + return current.properties.get(vid); + } + + while (current.parentModule) { + const module = loadModule(current.pkg, current.parentModule); + current = module.analyser; + // check C, D, E + if (current.properties.has(vid)) { + return current.properties.get(vid); + } + } + + return null; + } + + hasSuperCall(ast) { + for (let i = 0; i < ast.stmts.length; i++) { + const stmt = ast.stmts[i]; + if (stmt.type === 'super') { + return true; + } + } + + return false; + } + +} + +module.exports = Analyser; diff --git a/lib/package.js b/lib/package.js new file mode 100644 index 0000000..6db7c6d --- /dev/null +++ b/lib/package.js @@ -0,0 +1,175 @@ +'use strict'; + +const fs = require('fs').promises; +const path = require('path'); +const util = require('util'); +const exists = util.promisify(require('fs').exists); + +const stripComments = require('strip-json-comments'); + +const Lexer = require('./lexer'); +const Parser = require('./parser'); + +const ModuleAnalyser = require('./module_analyser'); +const ModelAnalyser = require('./model_analyser'); +const MainAnalyser = require('./main_analyser'); +const InterfaceAnalyser = require('./interface_analyser'); + +class Package { + constructor(pkgDir) { + this.pkgDir = pkgDir; + this.pkgInfo = {}; + this.components = new Map(); + this.libraries = new Map(); + this.main = null; + } + + error(message, filename, source, token) { + if (token) { + const loc = token.loc; + console.error(`${filename}:${loc.start.line}:${loc.start.column}`); + console.error(`${source.split('\n')[loc.start.line - 1]}`); + console.error(`${' '.repeat(loc.start.column - 1)}^`); + } + + throw new SyntaxError(message); + } + + async checkLibraries() { + const libraries = this.pkgInfo.libraries || {}; + if (Object.keys(libraries).length === 0) { + return; + } + // load .libraries file + const lockFilePath = path.join(this.pkgDir, '.libraries.json'); + let mapping = {}; + if (await exists(lockFilePath)) { + const content = await fs.readFile(lockFilePath, 'utf-8'); + mapping = JSON.parse(content); + } + + const pkgIds = Object.keys(libraries); + for (let i = 0; i < pkgIds.length; i++) { + const pkgId = pkgIds[i]; + const pkgPath = libraries[pkgId]; + let lib; + if (pkgPath.startsWith('./') || pkgPath.startsWith('../')) { + lib = new Package(path.join(this.pkgDir, pkgPath)); + } else { + if (!mapping[pkgPath]) { + throw new Error(`the package(${pkgId}) has not installed, use 'dara install' first`); + } + lib = new Package(path.join(this.pkgDir, mapping[pkgPath])); + } + await lib.analyse(); + // add $ as prefix + this.libraries.set(`$${pkgId}`, lib); + } + + // $builtin + const $builtin = new Package(path.join(__dirname, 'builtin')); + await $builtin.analyse(); + this.libraries.set('$builtin', $builtin); + } + + async checkDarafile() { + const darafile = path.join(this.pkgDir, 'Darafile'); + const hasDarafile = await exists(darafile); + if (!hasDarafile) { + throw new Error(`the folder(${this.pkgDir}) is not a Darabonba package`); + } + const pkgContent = await fs.readFile(darafile, 'utf8'); + try { + this.pkgInfo = JSON.parse(stripComments(pkgContent)); + } catch (ex) { + throw new Error(`the darafile is invalid: ${darafile}`); + } + + if (this.pkgInfo.darabonba !== '2.0') { + throw new Error(`the darabonba version(${this.pkgInfo.darabonba}) is not support by current parser, Darafile: ${darafile}`); + } + } + + async parseFiles(files) { + // checkpoint: 检查 model、module 是否重复 + for (let i = 0; i < files.length; i++) { + const item = files[i]; + const filePath = path.join(this.pkgDir, item); + const source = await fs.readFile(filePath, 'utf8'); + const lexer = new Lexer(source, filePath); + const parser = new Parser(lexer); + const ast = parser.program(); + if (ast.type !== 'main') { + const name = ast.name.lexeme; + if (this.components.has(name)) { + this.error(`redefined '${name}'`, filePath, source, ast.name); + } + this.components.set(name, { + type: ast.type, + ast, + ctx: { + source, + filename: filePath + } + }); + } else { + // checkpoint: 不能有多个 dmain 文件 + if (this.main) { + throw new Error(`dmain files can not more than one`); + } + this.main = { + type: ast.type, + ast, + ctx: { + source, + filename: filePath + } + }; + } + } + } + + async analyse() { + await this.checkDarafile(); + await this.checkLibraries(); + const files = await fs.readdir(this.pkgDir); + + // parse files + await this.parseFiles(files.filter((item) => { + return item.endsWith('.dara'); + })); + + // pre analyse: 仅分析结构及声明 + for (const item of this.components.values()) { + if (item.type === 'module') { + const analyser = new ModuleAnalyser(item.ctx, this); + item.analyser = analyser; + analyser.check(item.ast); + } else if (item.type === 'model') { + const analyser = new ModelAnalyser(item.ctx, this); + item.analyser = analyser; + analyser.check(item.ast); + } else if (item.type === 'interface') { + const analyser = new InterfaceAnalyser(item.ctx, this); + item.analyser = analyser; + analyser.check(item.ast); + } + } + + // post analyse:分析方法体 + for (const item of this.components.values()) { + if (item.type === 'module' || item.type === 'interface') { + item.analyser.checkMethods(item.ast); + } + } + + if (this.main) { + const analyser = new MainAnalyser(this.main.ctx, this); + analyser.check(this.main.ast); + analyser.checkMethods(this.main.ast); + this.main.analyser = analyser; + } + } +} + +module.exports = Package; diff --git a/lib/parser.js b/lib/parser.js index c92ce38..792da0d 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -1,95 +1,161 @@ 'use strict'; -const { Tag, tip } = require('./tag'); +const { Tag } = require('./tag'); -const { Parser: BaseParser } = require('@jacksontian/skyline'); - -class Parser extends BaseParser { +class Parser extends require('./base_parser') { constructor(lexer) { super(lexer); - this.comments = new Map(); - this.index = 0; - } - - move() { - do { - this.look = this.lexer.scan(); - this.look.index = ++this.index; - if (this.look.tag === Tag.COMMENT) { - this.comments.set(this.look.index, this.look); - } - } while (this.look.tag === Tag.COMMENT); - } - - tagTip(tag) { - return tip(tag); - } - - getIndex() { - return this.look.index; } program() { this.move(); - return this.module(); - } - - extends() { - const begin = this.getIndex(); - this.match(Tag.EXTENDS); - const alias = this.look; - let end = this.getIndex(); - this.match(Tag.ID); - if (this.is(';')) { - end = this.getIndex(); - this.move(); - } - alias.tokenRange = [begin, end]; - return alias; - } - - module() { var annotation; if (this.is(Tag.ANNOTATION)) { annotation = this.look; this.move(); } - const imports = []; + const imports = this.imports(); - while (this.is(Tag.IMPORT)) { + if (this.is(Tag.MODULE)) { const begin = this.getIndex(); + // module "module" ID ";" this.move(); - const alias = this.look; - let end = this.getIndex(); + const moduleName = this.look; this.match(Tag.ID); - imports.push(alias); - if (this.is(';')) { - end = this.getIndex(); + + let extendFrom; + if (this.is(Tag.EXTENDS)) { this.move(); + extendFrom = this.component(); } - alias.tokenRange = [begin, end]; + + let implements_ = []; + if (this.is(Tag.IMPLEMENTS)) { + this.move(); + implements_.push(this.component()); + + while (this.look.tag === ',') { + this.move(); + implements_.push(this.component()); + } + } + + const body = this.moduleBody(); + const end = this.getIndex(); + this.match(undefined); + return { + annotation: annotation, + imports: imports, + extends: extendFrom, + implements: implements_, + name: moduleName, + type: 'module', + moduleBody: body, + tokenRange: [begin, end], + comments: this.lexer.comments + }; } - let extendOn; - if (this.is(Tag.EXTENDS)) { - extendOn = this.extends(); + if (this.is(Tag.INTERFACE)) { + const begin = this.getIndex(); + // interface = "interface" ID ";" + this.move(); + const interfaceName = this.look; + this.match(Tag.ID); + const interfaceBody = this.interfaceBody(); + const end = this.getIndex(); + this.match(undefined); + return { + annotation: annotation, + imports: imports, + name: interfaceName, + type: 'interface', + interfaceBody: interfaceBody, + tokenRange: [begin, end], + comments: this.lexer.comments + }; + } + + if (this.is(Tag.MODEL)) { + const begin = this.getIndex(); + this.move(); + const modelName = this.look; + this.match(Tag.ID); + const modelBody = this.modelBody(); + const end = this.getIndex(); + this.match(undefined); + + return { + annotation: annotation, + imports: imports, + name: modelName, + type: 'model', + modelBody: modelBody, + tokenRange: [begin, end], + comments: this.lexer.comments + }; } + if (this.isWord(Tag.ID, 'main')) { + const begin = this.getIndex(); + this.move(); + const mainBody = this.mainBody(); + const end = this.getIndex(); + this.match(undefined); + + return { + annotation: annotation, + imports: imports, + type: 'main', + mainBody: mainBody, + tokenRange: [begin, end], + comments: this.lexer.comments + }; + } + + this.error(`expect 'module', 'model', 'interface' or 'main'`); + } + + interfaceBody() { + // interfaceBody = "{" { function } "}" + const begin = this.getIndex(); + this.match('{'); + const nodes = []; + while (!this.is('}')) { + let node; + let annotation; + if (this.is(Tag.ANNOTATION)) { + annotation = this.look; + this.move(); + } + + if (this.isWord(Tag.ID, 'async') || this.isWord(Tag.ID, 'function')) { + node = this.interfaceFun(); + } else { + this.error('expect "function"'); + } + + node.annotation = annotation; + nodes.push(node); + } + + const end = this.getIndex(); + this.match('}'); + return { - annotation: annotation, - imports: imports, - extends: extendOn, - type: 'module', - moduleBody: this.moduleBody(), - comments: this.comments + type: 'interfaceBody', + nodes: nodes, + tokenRange: [begin, end] }; } - moduleBody() { - // moduleBody = "{" { const | type | model | api | function } "}" + mainBody() { + // mainBody = "{" { main | function } "}" + const begin = this.getIndex(); + this.match('{'); const nodes = []; - while (this.look.tag) { + while (!this.is('}')) { let node; let annotation; if (this.is(Tag.ANNOTATION)) { @@ -97,118 +163,82 @@ class Parser extends BaseParser { this.move(); } - if (this.is(Tag.CONST)) { - node = this.const(); - } else if (this.isWord(Tag.ID, 'model')) { - node = this.model(); - } else if (this.isWord(Tag.ID, 'api')) { - node = this.api(); - } else if (this.is(Tag.RPC)) { - node = this.rpc(); - } else if (this.isWord(Tag.ID, 'type')) { - node = this.type(); - } else if (this.isWord(Tag.ID, 'init')) { - node = this.init(); + if (this.isWord(Tag.ID, 'main')) { + node = this.main(); } else if (this.is(Tag.STATIC) || this.isWord(Tag.ID, 'async') || this.isWord(Tag.ID, 'function')) { node = this.fun(); } else { - this.error('expect "const", "type", "model", "function", "init" or "api"'); + this.error('expect "main" or "function"'); } node.annotation = annotation; nodes.push(node); } + const end = this.getIndex(); + this.match('}'); + return { - type: 'moduleBody', - nodes: nodes + type: 'mainBody', + nodes: nodes, + tokenRange: [begin, end] }; } - // rpc = [ Annotation ] "rpc" rpcName "(" [ params ] ")" returnType rpcBody - rpc() { - this.match(Tag.RPC); - const rpcName = this.look; - this.match(Tag.ID); + main() { + // function = [ Annotation ] "main" "(" [ params ] ")" functionBody + const begin = this.getIndex(); + const main = this.look; + this.matchWord(Tag.ID, 'main'); this.match('('); const params = this.params(); this.match(')'); - this.match(':'); - const returnType = this.baseType(); - const rpcBody = this.object(); - + let functionBody = this.functionBody(); + const end = this.getIndex(); return { - type: 'rpc', - rpcName: rpcName, + type: 'main', + main: main, params: params, - returnType: returnType, - rpcBody: rpcBody, + functionBody: functionBody, + tokenRange: [begin, end] }; } - baseType() { - if (this.look.tag === '[') { - this.move(); - const t = this.baseType(); - this.match(']'); - return { - type: 'array', - subType: t - }; - } - - if (this.isWord(Tag.TYPE, 'map')) { - let t = this.look; - this.move(); - this.match('['); - const keyType = this.baseType(); - this.match(']'); - const valueType = this.baseType(); - return { - loc: { - start: t.loc.start, - end: valueType.loc.end - }, - type: 'map', - keyType: keyType, - valueType: valueType - }; - } - - if (this.is(Tag.ID)) { - var t = this.look; - this.move(); - // for A.B - if (this.look.tag === '.') { - const path = [t]; - let id; - while (this.look.tag === '.') { - this.move(); - id = this.look; - path.push(id); - this.match(Tag.ID); - } + moduleBody() { + const begin = this.getIndex(); + this.match('{'); - return { - type: 'subModel_or_moduleModel', - path: path, - loc: { - start: t.loc.start, - end: id.loc.end - }, - }; + // moduleBody = "{" { type | function | init } "}" + const nodes = []; + while (!this.is('}')) { + let node; + let annotation; + if (this.is(Tag.ANNOTATION)) { + annotation = this.look; + this.move(); } - return t; - } + if (this.isWord(Tag.ID, 'type')) { + node = this.type(); + } else if (this.isWord(Tag.ID, 'init')) { + node = this.init(); + } else if (this.is(Tag.STATIC) || this.isWord(Tag.ID, 'async') || this.isWord(Tag.ID, 'function')) { + node = this.fun(); + } else { + this.error('expect "const", "type", "function" or "init"'); + } - if (this.is(Tag.TYPE)) { - let t = this.look; - this.move(); - return t; + node.annotation = annotation; + nodes.push(node); } - this.error(`expect base type, model id or array form`); + const end = this.getIndex(); + this.match('}'); + return { + type: 'moduleBody', + nodes: nodes, + tokenRange: [begin, end] + }; } init() { @@ -263,60 +293,6 @@ class Parser extends BaseParser { }; } - const() { - // const = "const" ID "=" constant ";" - const begin = this.getIndex(); - this.match(Tag.CONST); - const constName = this.look; - this.match(Tag.ID); - this.match('='); - - const constValue = this.look; - if (this.look.tag === Tag.STRING || - this.look.tag === Tag.NUMBER || - this.look.tag === Tag.BOOL) { - this.move(); - } else { - this.error('const value must be STRING/NUMBER/BOOLEAN'); - } - const end = this.getIndex(); - this.match(';'); - - return { - type: 'const', - constName, - constValue, - tokenRange: [begin, end] - }; - } - - model() { - // model = "model" modelName "=" modelBody - const begin = this.getIndex(); - this.matchWord(Tag.ID, 'model'); - const modelName = this.look; - this.match(Tag.ID); - // 可选的 = - if (this.look.tag === '=') { - this.move(); - } - - const body = this.modelBody(); - let end = body.tokenRange[1]; - if (this.is(';')) { - // 可选的 ; - end = this.getIndex(); - this.move(); - } - - return { - type: 'model', - modelName: modelName, - modelBody: body, - tokenRange: [begin, end] - }; - } - modelBody() { // modelBody = "{" [ modelFields ] "}" // modelFields = modelField { "," modelField } [","] @@ -324,7 +300,6 @@ class Parser extends BaseParser { this.match('{'); const nodes = []; - if (this.is('}')) { const end = this.getIndex(); this.move(); @@ -384,7 +359,7 @@ class Parser extends BaseParser { } this.match(':'); - const fieldValue = this.fieldValue(); + const fieldType = this.baseType(); const attrs = this.attrs(); const end = this.getIndex(); @@ -392,101 +367,12 @@ class Parser extends BaseParser { type: 'modelField', fieldName: fieldName, required: required, - fieldValue: fieldValue, + fieldType: fieldType, attrs: attrs, tokenRange: [begin, end] }; } - fieldValue() { - // fieldValue = ( type | arrayType | modelBody | mapType ) - // attrs = "(" attr { "," attr } ")" - // attr = attrName "=" constant - // type = "string" | "number" | "boolean" - // mapType = "map" "[" type "]" type - if (this.look.tag === '{') { - return this.modelBody(); - } - - if (this.look.tag === '[') { - const node = { - type: 'fieldType', - fieldType: 'array', - }; - this.move(); - if (this.look.tag === '[') { - node.fieldItemType = this.fieldValue(); - } else if (this.look.tag === Tag.TYPE) { - const type = this.baseType(); - node.fieldItemType = type; - } else if (this.look.tag === Tag.ID) { - const id = this.look; - node.fieldItemType = id; - this.move(); - } else if (this.look.tag === '{') { - node.fieldItemType = this.modelBody(); - } else { - this.error('expect type or model name'); - } - - this.match(']'); - return node; - } - - if (this.look.tag === Tag.TYPE) { - const type = this.look; - this.move(); - - if (type.lexeme === 'map') { - this.match('['); - const keyType = this.baseType(); - this.match(']'); - const valueType = this.baseType(); - return { - type: 'fieldType', - fieldType: type.lexeme, - keyType: keyType, - valueType: valueType - }; - } - - return { - type: 'fieldType', - fieldType: type.lexeme - }; - } - - if (this.look.tag === Tag.ID) { - const model = this.look; - this.move(); - // for A.B - if (this.look.tag === '.') { - const path = [model]; - while (this.look.tag === '.') { - this.move(); - const id = this.look; - path.push(id); - this.match(Tag.ID); - } - - return { - type: 'fieldType', - fieldType: { - type: 'subModel_or_moduleModel', - path: path - } - }; - } - - return { - type: 'fieldType', - fieldType: model - }; - } - - this.error('expect "{", "[", "string", "number", "map", ID'); - } - attrs() { var attrs = []; if (this.look.tag !== '(') { @@ -525,102 +411,6 @@ class Parser extends BaseParser { }; } - api() { - // api = "api" apiName "(" [ params ] ")" [returnType] apiBody [ returns ] - const begin = this.getIndex(); - this.matchWord(Tag.ID, 'api'); - const apiName = this.look; - this.match(Tag.ID); - this.match('('); - const params = this.params(); - this.match(')'); - this.match(':'); - const returnType = this.baseType(); - const apiBody = this.apiBody(); - let end = apiBody.tokenRange[1]; - var returnBody; - if (this.isWord(Tag.ID, 'returns')) { - // 可选的 - this.move(); - returnBody = this.returnBody(); - end = returnBody.tokenRange[1]; - } - - var runtimeBody; - if (this.isWord(Tag.ID, 'runtime')) { - this.move(); - runtimeBody = this.object(); - end = runtimeBody.tokenRange[1]; - } - - return { - type: 'api', - apiName: apiName, - params: params, - returnType: returnType, - apiBody: apiBody, - returns: returnBody, - runtimeBody: runtimeBody, - tokenRange: [begin, end] - }; - } - - params() { - if (this.look.tag === ')') { - return { - type: 'params', - params: [] - }; - } - - var params = []; - var param = this.param(); - params.push(param); - - while (this.look.tag !== ')') { - this.match(','); - params.push(this.param()); - } - - return { - type: 'params', - params: params - }; - } - - param() { - const paramName = this.look; - this.match(Tag.ID); - this.match(':'); - const paramType = this.baseType(); - - var defaultValue = null; - if (this.look.tag === '=') { - this.move(); - defaultValue = this.look; - this.match(Tag.STRING); - } - - return { - type: 'param', - paramName: paramName, - paramType: paramType, - defaultValue: defaultValue - }; - } - - apiBody() { - var stmts = this.blockStmts(); - return { - type: 'apiBody', - stmts: stmts, - tokenRange: [ - stmts.tokenRange[0], - stmts.tokenRange[1] - ] - }; - } - array() { // array = "[" [ arrayItems ] "]" // arrayItems = expr { "," expr } @@ -636,10 +426,10 @@ class Parser extends BaseParser { tokenRange: [begin, end] }; } - + var item = this.expr(); items.push(item); - + while (this.look.tag !== ']') { if (this.look.tag === ',') { this.move(); @@ -651,7 +441,7 @@ class Parser extends BaseParser { } const end = this.getIndex(); this.match(']'); - + return { type: 'array', items: items, @@ -659,9 +449,9 @@ class Parser extends BaseParser { }; } - object() { - // object = "{" [ objectFields ] "}" - // objectFields = objectField { "," objectField } [","] + modelLitBody() { + // modelBody = "{" [ modelFields ] "}" + // modelFields = modelField { "," modelField } [","] const begin = this.getIndex(); var start = this.lexer.loc(); this.match('{'); @@ -671,7 +461,7 @@ class Parser extends BaseParser { const end = this.getIndex(); this.move(); return { - type: 'object', + type: 'fields', fields: fields, loc: { start: start, @@ -680,19 +470,19 @@ class Parser extends BaseParser { tokenRange: [begin, end] }; } - - var field = this.objectField(); + + var field = this.modelLitField(); fields.push(field); while (this.look.tag !== '}') { if (this.look.tag === ',') { this.move(); - + if (this.look.tag === '}') { // only one fields break; } - let field = this.objectField(); + let field = this.modelLitField(); fields.push(field); } else { this.error('expect ","'); @@ -700,9 +490,78 @@ class Parser extends BaseParser { } const end = this.getIndex(); this.match('}'); + + return { + type: 'fields', + fields: fields, + loc: { + start: start, + end: this.lexer.loc() + }, + tokenRange: [begin, end] + }; + } + modelLitField() { + // modelField = key "=" expr + const begin = this.getIndex(); + var fieldName = this.look; + this.match(Tag.ID); + this.match('='); + var expr = this.expr(); + const end = this.getIndex(); return { - type: 'object', + type: 'modelField', + key: fieldName, + expr: expr, + tokenRange: [begin, end] + }; + } + + map() { + // map = "{" [ mapFields ] "}" + // mapFields = mapField { "," mapField } [","] + const begin = this.getIndex(); + var start = this.lexer.loc(); + this.match('{'); + + var fields = []; + if (this.look.tag === '}') { + const end = this.getIndex(); + this.move(); + return { + type: 'map', + fields: fields, + loc: { + start: start, + end: this.lexer.loc() + }, + tokenRange: [begin, end] + }; + } + + var field = this.mapField(); + fields.push(field); + + while (this.look.tag !== '}') { + if (this.look.tag === ',') { + this.move(); + + if (this.look.tag === '}') { + // only one fields + break; + } + let field = this.mapField(); + fields.push(field); + } else { + this.error('expect ","'); + } + } + const end = this.getIndex(); + this.match('}'); + + return { + type: 'map', fields: fields, loc: { start: start, @@ -712,23 +571,23 @@ class Parser extends BaseParser { }; } - objectField() { - // objectField = objectFieldName "=" expr + mapField() { + // mapField = mapKey "=" expr const begin = this.getIndex(); - if (this.look.tag === Tag.ID) { - var fieldName = this.look; + if (this.look.tag === Tag.STRING) { + var mapKey = this.look; this.move(); this.match('='); var expr = this.expr(); const end = this.getIndex(); return { - type: 'objectField', - fieldName: fieldName, + type: 'mapField', + key: mapKey, expr: expr, tokenRange: [begin, end] }; } - + if (this.look.tag === '.') { this.move(); this.match('.'); @@ -741,27 +600,27 @@ class Parser extends BaseParser { tokenRange: [begin, end] }; } - - this.error('expect "..." or ID'); + + this.error('expect "..." or key'); } args() { const args = []; - + if (this.look.tag === ')') { return args; } - + var arg = this.expr(); args.push(arg); while (this.look.tag !== ')') { this.match(','); args.push(this.expr()); } - + return args; } - + string() { var str = this.look; this.match(Tag.STRING); @@ -771,7 +630,7 @@ class Parser extends BaseParser { loc: str.loc }; } - + number() { var str = this.look; this.match(Tag.NUMBER); @@ -781,7 +640,7 @@ class Parser extends BaseParser { loc: str.loc }; } - + bool() { var v = this.look; this.match(Tag.BOOL); @@ -791,14 +650,14 @@ class Parser extends BaseParser { loc: v.loc }; } - + template() { var elements = []; elements.push({ type: 'element', value: this.look }); - + var last = this.look; this.move(); if (last.tag === Tag.TEMPLATE && last.tail === true) { @@ -807,7 +666,7 @@ class Parser extends BaseParser { elements: elements }; } - + for (; ;) { if (this.look.tag === Tag.TEMPLATE) { var current = this.look; @@ -827,318 +686,116 @@ class Parser extends BaseParser { }); } } - + return { type: 'template_string', elements: elements }; } - + construct() { + const new_ = this.look; this.move(); - const id = this.look; - this.match(Tag.ID); + let component = this.component(); if (this.look.tag === '(') { this.move(); const args = this.args(); this.match(')'); - return { - type: 'construct', - aliasId: id, - args: args + type: 'construct_module', + component: component, + args: args, + loc: { + start: new_.loc.start, + end: this.lexer.loc() + } }; } - const propertyPath = []; - while (this.look.tag === '.') { - this.move(); - const id = this.look; - propertyPath.push(id); - this.match(Tag.ID); - } - - let object = null; - if (this.look.tag === '{') { - object = this.object(); - } + const fields = this.modelLitBody(); return { type: 'construct_model', - aliasId: id, - propertyPath, - object: object + component: component, + fields: fields, + loc: { + start: new_.loc.start, + end: this.lexer.loc() + } }; } idThings() { var id = this.look; - this.match(Tag.ID); - - // id = xxx - if (this.look.tag === '=') { - this.move(); - let expr = this.expr(); - return { - type: 'assign', - left: { - type: 'variable', - id: id, - }, - expr - }; - } - - // id() - if (this.look.tag === '(') { - this.move(); - const args = this.args(); - this.match(')'); - return { - type: 'call', - left: { - type: 'method_call', - id: id - }, - args, - loc: { - start: id.loc.start, - end: this.lexer.loc() - } - }; - } - - if (this.look.tag === '[') { - this.move(); - let accessKey = this.expr(); - this.match(']'); - // @id.x[] - return this.mapAccess({ - type: 'map_access', - id: id, - accessKey: accessKey, - loc: { - start: id.loc.start, - end: this.lexer.loc() - } - }); - } - - // id.x = xxx, id.x(), id.x - if (this.look.tag === '.') { - const propertyPath = []; - while (this.look.tag === '.') { - this.move(); - var prop = this.look; - this.match(Tag.ID); - propertyPath.push(prop); - } - - // id.x() - if (this.look.tag === '(') { - this.move(); - const args = this.args(); - this.match(')'); - // Module.staticCall() or module.instanceCall() - return { - type: 'call', - left: { - type: 'static_or_instance_call', - id: id, - propertyPath: propertyPath - }, - args: args, - loc: { - start: id.loc.start, - end: this.lexer.loc() - }, - }; - } - - if (this.look.tag === '[') { - this.move(); - let accessKey = this.expr(); - this.match(']'); - // @id.x[] - return this.mapAccess({ - type: 'map_access', - id: id, - propertyPath: propertyPath, - accessKey: accessKey, - loc: { - start: id.loc.start, - end: this.lexer.loc() - } - }); - } - - // id.x = xxx - if (this.look.tag === '=') { - this.move(); - let expr = this.expr(); - return { - type: 'assign', - left: { - type: 'property', - id, - propertyPath - }, - expr: expr - }; - } - - // id.x - return { - type: 'property_access', - id: id, - propertyPath: propertyPath, - loc: { - start: id.loc.start, - end: this.lexer.loc() - } - }; - } - - return { - type: 'variable', + let left = { + type: 'id', id: id, loc: id.loc }; - } - mapAccess(mapAccessAst) { - if (this.look.tag === '=') { - this.move(); - let expr = this.expr(); - return { - type: 'assign', - left: mapAccessAst, - expr: expr - }; - } - return mapAccessAst; - } - - vidThings() { - var vid = this.look; - this.match(Tag.VID); - - // @id = xxx - if (this.look.tag === '=') { - this.move(); - var expr = this.expr(); - return { - type: 'assign', - left: { - type: 'virtualVariable', - vid: vid - }, - expr: expr, - loc: { - start: vid.loc.start, - end: this.lexer.loc() - } - }; - } - - // @vid.x = xxx, @id.x(), @id.x - if (this.look.tag === '.') { - const propertyPath = []; - while (this.look.tag === '.') { - this.move(); - var prop = this.look; - this.match(Tag.ID); - propertyPath.push(prop); - } - - // id.x() - if (this.look.tag === '(') { + this.move(); + for ( ; ; ) { + if (this.look.tag === '.') { this.move(); - const args = this.args(); - this.match(')'); - // Module.staticCall() or module.instanceCall() - return { - type: 'call', - left: { - type: 'static_or_instance_call', - id: vid, - propertyPath: propertyPath - }, - args: args, + const id = this.look; + this.match(Tag.ID); + left = { + type: 'property', + object: left, + property: id, loc: { - start: vid.loc.start, + start: left.loc.start, end: this.lexer.loc() - }, - }; - } - - // id.x = xxx - if (this.look.tag === '=') { - this.move(); - let expr = this.expr(); - return { - type: 'assign', - left: { - type: 'property', - id: vid, - propertyPath - }, - expr: expr + } }; - } - - if (this.look.tag === '[') { + } else if (this.look.tag === '[') { this.move(); - let accessKey = this.expr(); + const expr = this.exprItem(); this.match(']'); - // @id.x[] - return this.mapAccess({ - type: 'map_access', - id: vid, - propertyPath: propertyPath, - accessKey: accessKey, + left = { + type: 'member', + object: left, + index: expr, loc: { - start: vid.loc.start, + start: left.loc.start, end: this.lexer.loc() } - }); + }; + } else { + break; } + } - // id.x + // id.x() + if (this.look.tag === '(') { + this.move(); + const args = this.args(); + this.match(')'); + return { - type: 'property_access', - id: vid, - propertyPath: propertyPath, + type: 'call', + callee: left, + args: args, loc: { - start: vid.loc.start, + start: id.loc.start, end: this.lexer.loc() - } + }, }; } - - - if (this.look.tag === '[') { + + // id.x = xxx + if (this.look.tag === '=') { this.move(); - let accessKey = this.expr(); - this.match(']'); - // @id.x[] - return this.mapAccess({ - type: 'map_access', - id: vid, - accessKey: accessKey, - loc: { - start: vid.loc.start, - end: this.lexer.loc() - } - }); + let expr = this.expr(); + return { + type: 'assign', + left: left, + expr: expr + }; } - - return { - type: 'virtualVariable', - vid: vid, - loc: vid.loc - }; + + return left; } exprItem() { @@ -1149,15 +806,15 @@ class Parser extends BaseParser { if (this.look.tag === Tag.STRING) { return this.string(); } - + if (this.look.tag === Tag.NUMBER) { return this.number(); } - + if (this.look.tag === Tag.BOOL) { return this.bool(); } - + if (this.look.tag === Tag.NULL) { this.move(); return { @@ -1169,16 +826,16 @@ class Parser extends BaseParser { return this.construct(); } - if (this.look.tag === Tag.ID) { - return this.idThings(); + if (this.look.tag === Tag.VAR) { + return this.declareExpr(); } - if (this.look.tag === Tag.VID) { - return this.vidThings(); + if (this.look.tag === Tag.ID || this.look.tag === Tag.VID || this.is(Tag.PACK_ID)) { + return this.idThings(); } if (this.look.tag === '{') { - return this.object(); + return this.map(); } if (this.look.tag === '[') { @@ -1188,14 +845,28 @@ class Parser extends BaseParser { if (this.look.tag === Tag.TEMPLATE) { return this.template(); } - + if (this.look.tag === Tag.SUPER) { return this.superCall(); } + if (this.look.tag === Tag.INLINE_ID) { + return this.inlineCall(); + } + + if (this.is(';') || this.is(')')) { + return { + type: 'empty', + loc: { + start: this.lexer.loc(), + end: this.lexer.loc() + } + }; + } + this.error('expect valid expression'); } - + superCall() { var start = this.lexer.loc(); this.match(Tag.SUPER); @@ -1212,6 +883,24 @@ class Parser extends BaseParser { }; } + inlineCall() { + var start = this.lexer.loc(); + const name = this.look; + this.match(Tag.INLINE_ID); + this.match('('); + const args = this.args(); + this.match(')'); + return { + type: 'inline', + name: name, + args: args, + loc: { + start: start, + end: this.look.loc.end + } + }; + } + notExpr() { var start = this.lexer.loc(); this.match('!'); @@ -1225,16 +914,18 @@ class Parser extends BaseParser { } }; } - + expr() { const begin = this.getIndex(); const item = this.exprItem(); let end = this.getIndex(); - if (this.is(Tag.AND)) { + if (this.is(Tag.OPERATOR)) { + const op = this.look; this.move(); const right = this.expr(); return { - type: 'and', + type: 'binary', + operator: op.lexeme, left: item, right: right, loc: { @@ -1245,11 +936,13 @@ class Parser extends BaseParser { }; } - if (this.is(Tag.OR)) { + if (this.is(Tag.LOGICAL)) { + const op = this.look; this.move(); const right = this.expr(); return { - type: 'or', + type: 'logical', + operator: op.lexeme, left: item, right: right, loc: { @@ -1259,24 +952,28 @@ class Parser extends BaseParser { tokenRange: [begin, right.tokenRange[1]] }; } + + if (this.is(Tag.TO)) { + this.move(); + let model = this.component(); + const index = this.getIndex(); + + return { + type: 'to', + from: item, + to: model, + loc: { + start: item.loc.start, + end: this.look.loc.end + }, + tokenRange: [begin, index] + }; + } + item.tokenRange = [begin, end]; return item; } - returnBody() { - var start = this.lexer.loc(); - const stmts = this.blockStmts(); - return { - type: 'returnBody', - loc: { - start: start, - end: this.lexer.loc() - }, - stmts: stmts, - tokenRange: [stmts.tokenRange[0], stmts.tokenRange[1]] - }; - } - stmts() { if (this.look.tag === '}') { return { @@ -1284,7 +981,7 @@ class Parser extends BaseParser { stmts: [] }; } - + const stmts = []; var stmt = this.stmt(); stmts.push(stmt); @@ -1297,7 +994,7 @@ class Parser extends BaseParser { stmts: stmts }; } - + blockStmts() { const begin = this.getIndex(); this.match('{'); @@ -1307,18 +1004,20 @@ class Parser extends BaseParser { stmts.tokenRange = [begin, end]; return stmts; } - + ifStmt() { const begin = this.getIndex(); + const branches = []; this.match(Tag.IF); this.match('('); - var condition = this.expr(); + const condition = this.expr(); this.match(')'); - var stmts = this.blockStmts(); - var end = stmts.tokenRange[1]; - - var elseStmts; - var elseIfs = []; + branches.push({ + type: 'if_branch', + condition: condition, + stmts: this.blockStmts() + }); + while (this.look.tag === Tag.ELSE) { this.move(); if (this.look.tag === Tag.IF) { @@ -1326,36 +1025,35 @@ class Parser extends BaseParser { this.match('('); let condition = this.expr(); this.match(')'); - let stmts = this.blockStmts(); - end = stmts.tokenRange[1]; - elseIfs.push({ - type: 'elseif', + branches.push({ + type: 'if_branch', condition: condition, - stmts: stmts + stmts: this.blockStmts() }); } else if (this.look.tag === '{') { - elseStmts = this.blockStmts(); - end = elseStmts.tokenRange[1]; + branches.push({ + type: 'else_branch', + stmts: this.blockStmts() + }); break; } else { this.error('expect "if" or "{"'); } } - + + let end = branches[branches.length - 1].stmts.tokenRange[1]; + return { type: 'if', - condition: condition, - stmts: stmts, - elseIfs: elseIfs, - elseStmts: elseStmts, + branches: branches, tokenRange: [begin, end] }; } - + throwStmt() { const begin = this.getIndex(); this.match(Tag.THROW); - let expr = this.object(); + let expr = this.map(); let end = expr.tokenRange[1]; if (this.look.tag === ';') { end = this.getIndex(); @@ -1367,7 +1065,7 @@ class Parser extends BaseParser { tokenRange: [begin, end] }; } - + tryStmt() { const begin = this.getIndex(); this.match(Tag.TRY); @@ -1394,7 +1092,7 @@ class Parser extends BaseParser { } else { this.error('"try" expect "catch" or "finally"'); } - + return { type: 'try', tryBlock: tryStmts, @@ -1404,7 +1102,7 @@ class Parser extends BaseParser { tokenRange: [begin, end] }; } - + whileStmt() { const begin = this.getIndex(); this.match(Tag.WHILE); @@ -1420,53 +1118,60 @@ class Parser extends BaseParser { tokenRange: [begin, end] }; } - + forStmt() { const begin = this.getIndex(); this.match(Tag.FOR); this.match('('); - this.match(Tag.VAR); - var id = this.look; - this.match(Tag.ID); - this.match(':'); const expr = this.expr(); + if (this.is(';')) { + this.move(); + const test = this.expr(); + this.match(';'); + const update = this.expr(); + this.match(')'); + const stmts = this.blockStmts(); + const end = stmts.tokenRange[1]; + return { + type: 'for', + init: expr, + test: test, + update: update, + stmts: stmts, + tokenRange: [begin, end] + }; + } + + if (expr.type !== 'declare_expr') { + this.error(`must be declare expression`); + } + + this.match(Tag.OF); + const right = this.expr(); this.match(')'); const stmts = this.blockStmts(); const end = stmts.tokenRange[1]; return { - type: 'for', - id: id, - list: expr, + type: 'for_of', + left: expr, + right: right, stmts: stmts, tokenRange: [begin, end] }; } - + breakStmt() { const begin = this.getIndex(); this.match(Tag.BREAK); const end = this.getIndex(); this.match(';'); - + return { type: 'break', tokenRange: [begin, end] }; } - retryStmt() { - const token = this.look; - const begin = this.getIndex(); - this.matchWord(Tag.ID, 'retry'); - const end = this.getIndex(); - this.match(';'); - return { - type: 'retry', - loc: token.loc, - tokenRange: [begin, end] - }; - } - stmt() { if (this.look.tag === Tag.IF) { return this.ifStmt(); @@ -1475,23 +1180,19 @@ class Parser extends BaseParser { if (this.look.tag === Tag.WHILE) { return this.whileStmt(); } - + if (this.look.tag === Tag.FOR) { return this.forStmt(); } - + if (this.look.tag === Tag.TRY) { return this.tryStmt(); } - if (this.isWord(Tag.ID, 'retry')) { - return this.retryStmt(); - } - if (this.look.tag === Tag.BREAK) { return this.breakStmt(); } - + if (this.look.tag === Tag.RETURN) { return this.returnStmt(); } @@ -1501,7 +1202,7 @@ class Parser extends BaseParser { } if (this.look.tag === Tag.VAR) { - return this.declare(); + return this.declareStmt(); } let expr = this.expr(); @@ -1511,39 +1212,26 @@ class Parser extends BaseParser { returnStmt() { const begin = this.getIndex(); - var returnToken = this.look; - this.move(); - let end = this.getIndex(); - if (this.is(';')) { - this.move(); - return { - type: 'return', - loc: { - start: returnToken.loc.start, - end: this.look.loc.end - }, - tokenRange: [begin, end] - }; - } - - let expr = this.expr(); - end = this.getIndex(); + const start = this.look.loc; + this.match(Tag.RETURN); + const expr = this.expr(); + const end = this.look.loc; this.match(';'); return { type: 'return', expr: expr, loc: { - start: returnToken.loc.start, - end: this.look.loc.end + start: start.start, + end: end.end }, - tokenRange: [begin, end] + tokenRange: [begin, this.getIndex()] }; } - declare() { + declareStmt() { const begin = this.getIndex(); this.match(Tag.VAR); - let id = this.look; + const id = this.look; this.match(Tag.ID); let expectedType; if (this.look.tag === ':') { @@ -1551,7 +1239,7 @@ class Parser extends BaseParser { expectedType = this.baseType(); } this.match('='); - let expr = this.expr(); + const expr = this.expr(); const end = this.getIndex(); this.match(';'); return { @@ -1563,8 +1251,107 @@ class Parser extends BaseParser { }; } + declareExpr() { + const begin = this.getIndex(); + this.match(Tag.VAR); + const id = this.look; + this.match(Tag.ID); + let expectedType; + if (this.look.tag === ':') { + this.move(); + expectedType = this.baseType(); + } + let expr; + if (this.is('=')) { + this.move(); + expr = this.expr(); + } + const end = this.getIndex(); + return { + type: 'declare_expr', + id: id, + expectedType, + expr, + tokenRange: [begin, end] + }; + } + + params() { + if (this.look.tag === ')') { + return { + type: 'params', + params: [] + }; + } + + var params = []; + var param = this.param(); + params.push(param); + + while (this.look.tag !== ')') { + this.match(','); + params.push(this.param()); + } + + return { + type: 'params', + params: params + }; + } + + param() { + const paramName = this.look; + this.match(Tag.ID); + this.match(':'); + const paramType = this.baseType(); + + return { + type: 'param', + paramName: paramName, + paramType: paramType + }; + } + + interfaceFun() { + // function = [ Annotation ] ["static"] "funtion" functionName "(" [ params ] ")" returnType functionBody + const begin = this.getIndex(); + let isStatic = false; + let hasThrow = false; + let isAsync = false; + if (this.isWord(Tag.ID, 'async')) { + isAsync = true; + this.move(); + } + + this.matchWord(Tag.ID, 'function'); + const functionName = this.look; + this.match(Tag.ID); + this.match('('); + const params = this.params(); + this.match(')'); + if (this.isWord(Tag.ID, 'throws')) { + hasThrow = true; + this.move(); + } + this.match(':'); + const returnType = this.baseType(); + this.match(';'); + let end = this.getIndex(); + + return { + type: 'function', + isStatic: isStatic, + isAsync: isAsync, + hasThrow: hasThrow, + functionName: functionName, + params: params, + returnType: returnType, + tokenRange: [begin, end] + }; + } + fun() { - // function = [ Annotation ] ["static"] "funtion" apiName "(" [ params ] ")" returnType functionBody + // function = [ Annotation ] ["static"] "funtion" functionName "(" [ params ] ")" returnType functionBody const begin = this.getIndex(); let isStatic = false; let hasThrow = false; @@ -1626,6 +1413,19 @@ class Parser extends BaseParser { tokenRange: [stmts.tokenRange[0], stmts.tokenRange[1]] }; } + + component() { + let component; + if (this.is(Tag.ID)) { + component = this.look; + this.move(); + } else if (this.is(Tag.PACK_ID)) { + component = this.externComponent(); + } else { + this.error(`expect (extern) module/model/interface`); + } + return component; + } } -module.exports = Parser; +module.exports = Parser; \ No newline at end of file diff --git a/lib/semantic.js b/lib/semantic.js deleted file mode 100644 index 2c791d3..0000000 --- a/lib/semantic.js +++ /dev/null @@ -1,2357 +0,0 @@ -'use strict'; - -const path = require('path'); -const fs = require('fs'); -const assert = require('assert'); - -const stripComments = require('strip-json-comments'); - -const { Tag } = require('./tag'); -const Lexer = require('./lexer'); -const Parser = require('./parser'); -const Env = require('./env'); -const { - isBasicType, - isNumber, - isInteger, - getDarafile -} = require('./util'); - -const builtin = require('./builtin'); - -function replace(origin, target) { - Object.keys(origin).forEach((key) => { - delete origin[key]; - }); - - Object.keys(target).forEach((key) => { - origin[key] = target[key]; - }); -} - -function display(item) { - if (item.type === 'basic') { - return item.name; - } - - if (item.type === 'map') { - return `map[${display(item.keyType)}]${display(item.valueType)}`; - } - - if (item.type === 'array') { - return `[${display(item.itemType)}]`; - } - - if (item.type === 'model') { - if (item.moduleName) { - return `${item.moduleName}#${item.name}`; - } - - return item.name; - } - - if (item.type === 'class') { - if (item.moduleName) { - return `[${item.moduleName}#${item.name}]`; - } - - return `[${item.name}]`; - } - - if (item.type === 'module_instance') { - return item.name; - } - - console.log(item); - throw new Error('unimplemented.'); -} - -function _basic(name) { - return { - type: 'basic', - name: name - }; -} - -function _model(name, moduleName) { - return { - type: 'model', - name: name, - moduleName - }; -} - -function _type(id) { - if (id.tag === Tag.TYPE) { - return _basic(id.lexeme); - } - - if (id.tag === Tag.ID) { - return _model(id.lexeme); - } - - console.log(id); - throw new Error(`unsupported`); -} - -function isSameNumber(expect, actual){ - if (isNumber(expect.name) && isNumber(actual.name)) { - if (expect.name === 'number') { - return true; - } - - if (isInteger(expect.name) && actual.name === 'integer') { - return true; - } - - if ((expect.name === 'long' || expect.name === 'ulong') && isInteger(actual.name)) { - return true; - } - - if (expect.name === 'ulong' && actual.name === 'long') { - return true; - } - } - return false; -} - -function isSameType(expect, actual) { - if (expect.type === 'basic' && actual.type === 'basic') { - return expect.name === actual.name; - } - - if (expect.type === 'model' && actual.type === 'model') { - return expect.name === actual.name && expect.moduleName === actual.moduleName; - } - - if (expect.type === 'array' && actual.type === 'array') { - return isSameType(expect.itemType, actual.itemType); - } - - if (expect.type === 'map' && actual.type === 'map') { - return isSameType(expect.keyType, actual.keyType) && - isSameType(expect.valueType, actual.valueType); - } - - if (expect.type === 'module_instance' && actual.type === 'module_instance') { - return expect.name === actual.name; - } - - return false; -} - -function isAssignable(expected, actual, expr) { - if (isSameType(expected, actual)) { - return true; - } - - if (isSameNumber(expected, actual)) { - return true; - } - - // actual is null - if (actual.type === 'basic' && actual.name === 'null') { - return true; - } - - if (expected.type === 'map' && actual.type === 'map') { - if (expr && expr.type === 'object' && expr.fields.length === 0) { - return true; - } - - if (isAssignable(expected.valueType, actual.valueType)) { - return true; - } - } - - if (expected.type === 'array' && actual.type === 'array') { - - if (expr && expr.type === 'array' && expr.items.length === 0) { - return true; - } - - if (isAssignable(expected.itemType, actual.itemType)) { - return true; - } - } - - if (expected.type === 'basic' && expected.name === 'readable') { - // readable = string should ok - if (actual.type === 'basic' && actual.name === 'string') { - return true; - } - - // readable = bytes should ok - if (actual.type === 'basic' && actual.name === 'bytes') { - return true; - } - } - - if (expected.type === 'basic' && expected.name === 'any') { - // any = other type - return true; - } - - return false; -} - - -function eql(expects, actuals) { - if (expects.length !== actuals.length) { - return false; - } - - for (var i = 0; i < expects.length; i++) { - const expect = expects[i]; - const actual = actuals[i]; - - if (isSameType(expect, actual)) { - continue; - } - - if (isSameNumber(expect, actual)) { - continue; - } - - // actual is null - if (actual.type === 'basic' && actual.name === 'null') { - continue; - } - - // $Model vs model - if (expect.type === 'model' && expect.name === '$Model' && actual.type === 'model') { - continue; - } - - if (expect.type === 'map' && expect.keyType.name === 'string') { - // expect: object - // actual: model - if (actual.type === 'model') { - continue; - } - } - - if (expect.type === 'basic' && actual.type === 'basic') { - if (expect.name === 'integer' && actual.name === 'number') { - continue; - } - - if (expect.name === 'long' && actual.name === 'number') { - continue; - } - } - - if (expect.type === 'basic' && expect.name === 'any') { - // expect: any - continue; - } - - // Model vs object - if (expect.type === 'model' && actual.type === 'map') { - continue; - } - - // Model vs any - if (expect.type === 'model' && actual.type === 'basic' && actual.name === 'any') { - continue; - } - - if (expect.type !== actual.type) { - return false; - } - - const type = expect.type; - - if (type === 'map') { - if (expect.keyType.name === actual.keyType.name) { - if (expect.valueType.name === 'any') { - // map[string]any vs map[string]string - continue; - } - if (isAssignable(expect.valueType, actual.valueType)) { - continue; - } - } - } - - if (type === 'array') { - if (expect.itemType.name === 'any') { - // [any] vs [string] - continue; - } - if (isAssignable(expect.itemType, actual.itemType)) { - continue; - } - } - - return false; - } - - return true; -} - -// map to model -function isNeedToModel(expect, actual) { - if (isSameType(expect, actual)) { - return false; - } - - if (actual.type === 'basic' && actual.name === 'null') { - return false; - } - - if (expect.type !== 'model') { - return false; - } - - return true; -} - -// model to map -function isNeedToMap(expect, actual) { - if (isSameType(expect, actual)) { - return false; - } - - if (actual.type === 'basic' && actual.name === 'null') { - // model vs null - if (expect.type === 'model') { - return false; - } - } - - if (actual.type !== 'model') { - // only model can't be cast - return false; - } - - if (expect.type === 'model' && expect.name === '$Model' && actual.type === 'model') { - return false; - } - - return true; -} - -function findProperty(model, propName) { - return model.modelBody.nodes.find((item) => { - return item.fieldName.lexeme === propName; - }); -} - -class TypeChecker { - constructor(source, filename, root, libraries) { - this.source = source; - this.filename = filename; - // 方法: apis、functions - this.methods = new Map(); - // 属性 - this.properties = new Map(); - // 模型 - this.models = new Map(); - // 依赖 - this.dependencies = new Map(); - //执行编译的root - if (!root) { - this.root = path.dirname(this.filename); - } else { - this.root = root; - } - //libraries依赖安装路径表 - if (!libraries) { - const lockFilePath = path.join(this.root, '.libraries.json'); - if (fs.existsSync(lockFilePath)) { - this.libraries = JSON.parse(fs.readFileSync(lockFilePath, 'utf-8')); - } else { - this.libraries = {}; - } - } else { - this.libraries = libraries; - } - } - - error(message, token) { - if (token) { - const loc = token.loc; - console.error(`${this.filename}:${loc.start.line}:${loc.start.column}`); - console.error(`${this.source.split('\n')[loc.start.line - 1]}`); - console.error(`${' '.repeat(loc.start.column - 1)}^`); - } - - throw new SyntaxError(message); - } - - checkModels(ast) { - const models = ast.moduleBody.nodes.filter((item) => { - return item.type === 'model'; - }); - models.forEach((node) => { - const key = node.modelName.lexeme; - // 重复定义检查 - if (this.models.has(key)) { - this.error(`redefined model "${key}"`, node.modelName); - } - this.models.set(key, node); - }); - - // 允许先使用,后定义,所以先全部设置到 types 中,再进行检查 - models.forEach((node) => { - this.visitModel(node); - }); - } - - checkTypes(ast) { - this.vidCounter = new Map(); - ast.moduleBody.nodes.filter((item) => { - return item.type === 'type'; - }).forEach((node) => { - this.checkType(node.value); - - const key = node.vid.lexeme; - if (this.properties.has(key)) { - // 重复定义检查 - this.error(`redefined type "${key}"`, node.vid); - } - this.properties.set(key, node); - this.vidCounter.set(key, 0); - }); - } - - checkAPIs(ast) { - ast.moduleBody.nodes.forEach((item) => { - if (item.type === 'api') { - this.visitAPI(item); - } - }); - } - - checkFunctions(ast) { - ast.moduleBody.nodes.forEach((item) => { - if (item.type === 'function') { - this.visitFun(item); - } - }); - } - - checkInit(ast) { - const inits = ast.moduleBody.nodes.filter((item) => { - return item.type === 'init'; - }); - - if (inits.length > 1) { - this.error('Only one init can be allowed.', inits[1]); - } - } - - postCheckInit(ast) { - const api = ast.moduleBody.nodes.find((item) => { - return item.type === 'api'; - }); - const func = ast.moduleBody.nodes.find((item) => { - // non-static function - return item.type === 'function' && item.isStatic === false; - }); - - const init = ast.moduleBody.nodes.find((item) => { - return item.type === 'init'; - }); - - if (api || func) { - if (!init && !this.parentModuleId) { - this.error('Must have a init when there is a api or non-static function'); - } - } - - if (init) { - const paramMap = this.visitParams(init.params, { - needValidateParams: true - }); - - if (init.initBody) { - const local = new Env(); - // merge the parameters into local env - for (const [key, value] of paramMap) { - local.set(key, value); - } - const env = { - isStatic: false, - isAsync: false, - isInitMethod: true, - local: local - }; - this.visitStmts(init.initBody, env); - } - } - this.init = init; - } - - checkExtends(ast) { - if (!ast.extends) { - return; - } - - const aliasId = ast.extends.lexeme; - if (!this.dependencies.has(aliasId)) { - this.error(`the extends "${aliasId}" wasn't imported`, ast.extends); - } - - this.parentModuleId = aliasId; - } - - checkImports(ast) { - if (ast.imports.length === 0) { - return; - } - - const filePath = this.filename; - const pkgDir = path.dirname(filePath); - const pkgPath = getDarafile(pkgDir); - if (!fs.existsSync(pkgPath)) { - this.error(`the Darafile not exists`); - } - - const pkg = JSON.parse(stripComments(fs.readFileSync(pkgPath, 'utf-8'))); - pkg.libraries = pkg.libraries || {}; - - for (let i = 0; i < ast.imports.length; i++) { - const item = ast.imports[i]; - const aliasId = item.lexeme; - - if (!pkg.libraries[aliasId] && pkg.name !== aliasId) { - this.error(`the import "${aliasId}" not defined in Darafile`, item); - } - - if (this.dependencies.has(aliasId)) { - this.error(`the module id "${aliasId}" has been imported`, item); - } - - const specPath = pkg.libraries[aliasId] || pkg.main; - let realSpecPath; - if (specPath.startsWith('./') || specPath.startsWith('../')) { - if (specPath.endsWith('.dara') || specPath.endsWith('.tea') || specPath.endsWith('.spec')) { - realSpecPath = path.join(pkgDir, specPath); - } else { - const libMetaPath = getDarafile(path.join(pkgDir, specPath)); - const libMeta = JSON.parse(stripComments(fs.readFileSync(libMetaPath, 'utf-8'))); - realSpecPath = path.join(pkgDir, specPath, libMeta.main); - } - } else { - const key = `${specPath}`; - if (!this.libraries[key]) { - this.error(`the module id "${aliasId}" has not installed, use \`dara install\` first`, item); - } - const libPath = path.join(this.root, this.libraries[key]); - const libMetaPath = getDarafile(libPath); - const libMeta = JSON.parse(stripComments(fs.readFileSync(libMetaPath, 'utf-8'))); - realSpecPath = path.join(libPath, libMeta.main); - } - const source = fs.readFileSync(realSpecPath, 'utf-8'); - const lexer = new Lexer(source, realSpecPath); - const parser = new Parser(lexer); - const depAst = parser.program(); - const checker = new TypeChecker(source, realSpecPath, this.root, this.libraries).check(depAst); - this.dependencies.set(aliasId, checker); - this.usedExternModel.set(aliasId, new Set()); - } - } - - check(ast) { - assert.equal(ast.type, 'module'); - // 类型系统 - this.usedExternModel = new Map(); - this.usedTypes = new Map(); - this.checkImports(ast); - this.checkExtends(ast); - this.checkConsts(ast); - // models & sub-models - this.checkModels(ast); - // virtual variables & virtual methods - this.checkTypes(ast); - // Check module init - this.checkInit(ast); - this.preCheckMethods(ast); - // apis - this.checkAPIs(ast); - // functions - this.checkFunctions(ast); - // post check for init: if have any apis or non-static function, must have init - this.postCheckInit(ast); - // check unused virtualVariable & virtualMethod - this.postCheckTypes(ast); - ast.models = {}; - for (var [key, value] of this.models) { - ast.models[key] = value; - } - ast.usedExternModel = this.usedExternModel; - ast.conflictModels = this.resolveConflictModels(ast.usedExternModel); - ast.usedTypes = this.usedTypes; - // save the final ast in checker - this.ast = ast; - return this; - } - - resolveConflictModels(usedExternModel) { - var conflicts = new Map(); - var names = new Map(); - for (const [name] of this.models) { - names.set(name, true); - } - for (const [moduleName, models] of usedExternModel) { - for (var name of models) { - if (names.has(name)) { - conflicts.set(`${moduleName}:${name}`, true); - } - names.set(name, true); - } - } - return conflicts; - } - - checkConsts(ast) { - this.consts = new Map(); - ast.moduleBody.nodes.filter((item) => { - return item.type === 'const'; - }).forEach((node) => { - const tag = node.constValue.tag; - let type; - switch (tag) { - case Tag.STRING: - type = 'string'; - break; - case Tag.NUMBER: - type = 'number'; - break; - case Tag.BOOL: - type = 'boolean'; - break; - } - this.consts.set(node.constName.lexeme, { - type, - value: node.constValue - }); - }); - } - - postCheckTypes(ast) { - if (process.env.TEA_WARNING === '1') { - for (const [key, value] of this.vidCounter) { - if (value === 0) { - console.log(`the type ${key} is unused.`); - } - } - } - } - - preCheckMethods(ast) { - ast.moduleBody.nodes.forEach((node) => { - if (node.type === 'api') { - const key = node.apiName.lexeme; - // 重复定义检查 - if (this.methods.has(key)) { - this.error(`redefined api "${key}"`, node.apiName); - } - this.methods.set(key, node); - } else if (node.type === 'function') { - const key = node.functionName.lexeme; - // 重复定义检查 - if (this.methods.has(key)) { - this.error(`redefined function "${key}"`, node.functionName); - } - - this.methods.set(key, node); - } - }); - } - - visitFun(ast) { - assert.equal(ast.type, 'function'); - const paramMap = this.visitParams(ast.params, {}); - this.checkType(ast.returnType); - - const returnType = ast.returnType; - if (ast.functionBody) { - const local = new Env(); - // merge the parameters into local env - for (const [key, value] of paramMap) { - local.set(key, value); - } - const env = { - returnType, - isStatic: ast.isStatic, - isAsync: ast.isAsync, - local: local - }; - this.visitFunctionBody(ast.functionBody, env); - - if (returnType.tag === Tag.TYPE && returnType.lexeme === 'void') { - // no check for void - return; - } - - if (!this.hasReturnStmt(ast.functionBody.stmts)) { - this.error(`no return statement`, ast.functionName); - } - } - } - - visitFunctionBody(ast, env) { - assert.equal(ast.type, 'functionBody'); - this.visitStmts(ast.stmts, env); - } - - hasReturnStmt(ast) { - assert.equal(ast.type, 'stmts'); - - if (ast.stmts.length === 0) { - return false; - } - - const stmt = ast.stmts[ast.stmts.length - 1]; - if (stmt.type === 'return') { - return true; - } - - if (stmt.type === 'throw') { - return true; - } - - if (stmt.type === 'if') { - if (!this.hasReturnStmt(stmt.stmts)) { - return false; - } - - for (let index = 0; index < stmt.elseIfs.length; index++) { - const branch = stmt.elseIfs[index]; - if (!this.hasReturnStmt(branch.stmts)) { - return false; - } - } - - if (!stmt.elseStmts) { - return false; - } - - if (!this.hasReturnStmt(stmt.elseStmts)) { - return false; - } - - return true; - } - - - - if (stmt.type === 'try') { - let tryReturn = true; - let catchReturn = true; - let finallyReturn = true; - if (!this.hasReturnStmt(stmt.tryBlock)) { - tryReturn = false; - } - - if (stmt.catchBlock && !this.hasReturnStmt(stmt.catchBlock)) { - catchReturn = false; - } - - if (!stmt.finallyBlock || !this.hasReturnStmt(stmt.finallyBlock)) { - finallyReturn = false; - } - - if (finallyReturn) { - return true; - } - - if (!tryReturn) { - return false; - } - - if (!catchReturn) { - return false; - } - - return true; - } - - // TODO: while, for - if (stmt.type === 'while') { - return true; - } - - if (stmt.type === 'for') { - return true; - } - - return false; - } - - visitAPI(ast) { - assert.equal(ast.type, 'api'); - const paramMap = this.visitParams(ast.params, { - needValidateParams: true - }); - this.checkType(ast.returnType); - const returnType = ast.returnType; - const local = new Env(); - // merge the parameters into local env - for (const [key, value] of paramMap) { - local.set(key, value); - } - const env = { - returnType, - isAsync: true, - local: local - }; - if (ast.runtimeBody) { - this.visitObject(ast.runtimeBody, env); - } - env.local.set('__request', _model('$Request')); - this.visitAPIBody(ast.apiBody, env); - - ast.isAsync = true; - if (ast.returns) { - env.inReturnsBlock = true; - env.local.set('__response', _model('$Response')); - this.visitReturnBody(ast.returns, env); - if (returnType.tag === Tag.TYPE && returnType.lexeme === 'void') { - // no check for void - return; - } - - if (!this.hasReturnStmt(ast.returns.stmts)) { - this.error(`no return statement`, ast.apiName); - } - } - } - - visitParams(ast, env) { - assert.equal(ast.type, 'params'); - const paramMap = new Map(); - for (var i = 0; i < ast.params.length; i++) { - const node = ast.params[i]; - assert.equal(node.type, 'param'); - const name = node.paramName.lexeme; - if (paramMap.has(node.paramName.lexeme)) { - // 重复参数名检查 - this.error(`redefined parameter "${name}"`, node.paramName); - } - this.checkType(node.paramType); - - // TODO: 默认值类型检查 - const type = this.getType(node.paramType); - paramMap.set(name, type); - if (type.type === 'model' && env.needValidateParams) { - node.needValidate = true; - } - } - - return paramMap; - } - - - - checkType(node) { - if (node.type === 'array') { - this.checkType(node.subType); - return; - } - - if (node.type === 'map') { - this.checkType(node.valueType); - return; - } - - if (node.tag === Tag.TYPE) { - this.usedTypes.set(node.lexeme, true); - return; - } - - const modelName = node.lexeme; - if (node.tag === Tag.ID) { - const idType = this.getIdType(modelName); - if (idType) { - node.idType = idType; - return; - } - } - - if (node.type === 'subModel_or_moduleModel') { - const [mainId, ...rest] = node.path; - const idType = this.getIdType(mainId.lexeme); - mainId.idType = idType; - // submodel - if (idType === 'model') { - const typeName = node.path.map((item) => { - return item.lexeme; - }).join('.'); - if (!this.models.has(typeName)) { - this.error(`the submodel ${typeName} is inexist`, mainId); - } - node.type = 'subModel'; - return; - } - - if (idType === 'module') { - const checker = this.dependencies.get(mainId.lexeme); - const typeName = rest.map((item) => { - return item.lexeme; - }).join('.'); - if (!checker.models.has(typeName)) { - this.error(`the model ${typeName} is inexist in ${mainId.lexeme}`, mainId); - } - node.type = 'moduleModel'; - this.usedExternModel.get(mainId.lexeme).add(typeName); - return; - } - } - - this.error(`model "${modelName}" undefined`, node); - } - - visitAPIBody(ast, env) { - assert.equal(ast.type, 'apiBody'); - for (let i = 0; i < ast.stmts.stmts.length; i++) { - this.visitStmt(ast.stmts.stmts[i], env); - } - } - - visitStmt(ast, env) { - if (ast.type === 'return') { - this.visitReturn(ast, env); - } else if (ast.type === 'if') { - this.visitIf(ast, env); - } else if (ast.type === 'throw') { - this.visitThrow(ast, env); - } else if (ast.type === 'assign') { - this.visitAssign(ast, env); - } else if (ast.type === 'retry') { - if (!env.inReturnsBlock) { - this.error(`retry only can be in returns block`, ast); - } - } else if (ast.type === 'break') { - // unnecessary to check - } else if (ast.type === 'declare') { - this.visitDeclare(ast, env); - } else if (ast.type === 'try') { - this.visitTry(ast, env); - } else if (ast.type === 'while') { - this.visitWhile(ast, env); - } else if (ast.type === 'for') { - this.visitFor(ast, env); - } else { - this.visitExpr(ast, env); - } - } - - visitFor(ast, env) { - assert.equal(ast.type, 'for'); - env.local = new Env(env.local); - this.visitExpr(ast.list, env); - const listType = this.getExprType(ast.list, env); - if (listType.type !== 'array') { - this.error(`the list in for must be array type`, ast.list); - } - env.local.set(ast.id.lexeme, listType.itemType); - this.visitStmts(ast.stmts, env); - env.local = env.local.preEnv; - } - - isBooleanType(expr, env) { - const type = this.getExprType(expr, env); - return type.type === 'basic' && type.name === 'boolean'; - } - - isStringType(expr, env) { - const type = this.getExprType(expr, env); - return type.type === 'basic' && type.name === 'string'; - } - - isNumberType(expr, env) { - const type = this.getExprType(expr, env); - return type.type === 'basic' && isNumber(type.name); - } - - visitWhile(ast, env) { - assert.equal(ast.type, 'while'); - env.local = new Env(env.local); - this.visitExpr(ast.condition, env); - if (!this.isBooleanType(ast.condition, env)) { - this.error(`the condition expr must be boolean type`, ast.condition); - } - this.visitStmts(ast.stmts, env); - env.local = env.local.preEnv; - } - - visitTry(ast, env) { - assert.equal(ast.type, 'try'); - this.visitStmts(ast.tryBlock, env); - if (ast.catchBlock) { - // create new local - var local = new Env(env.local); - local.set(ast.catchId.lexeme, _model('$Error')); - env.local = local; - this.visitStmts(ast.catchBlock, env); - // restore the local - env.local = env.local.preEnv; - } - - if (ast.finallyBlock) { - this.visitStmts(ast.finallyBlock, env); - } - } - - getParameterType(type, moduleName) { - if (type.tag === Tag.TYPE && type.lexeme === 'object') { - return { - type: 'map', - keyType: _basic('string'), - valueType: _basic('any') - }; - } else if (type.type === 'map') { - return { - type: 'map', - keyType: _basic('string'), - valueType: this.getParameterType(type.valueType, moduleName) - }; - } else if (type.tag === Tag.TYPE) { - return _basic(type.lexeme); - } else if (type.tag === Tag.ID && type.lexeme.startsWith('$')) { - return _model(type.lexeme); - } else if (type.tag === Tag.ID && type.idType === 'model') { - return _model(type.lexeme, moduleName); - } else if (type.tag === Tag.ID && this.dependencies.has(type.lexeme)) { - return { - type: 'module_instance', - name: type.lexeme - }; - } else if (type.tag === Tag.ID) { - return _model(type.lexeme); - } else if ( - type.type === 'moduleModel' || - type.type === 'subModel' || - type.type === 'subModel_or_moduleModel') { - const [mainId, ...rest] = type.path; - const idType = this.getIdType(mainId.lexeme); - if (idType === 'module') { - return _model(rest.map((item) => { - return item.lexeme; - }).join('.'), mainId.lexeme); - } - - if (idType === 'model') { - return _model(type.path.map((item) => { - return item.lexeme; - }).join('.')); - } - - } else if (type.type === 'array') { - return { - type: 'array', - itemType: this.getParameterType(type.subType, moduleName) - }; - } - - console.log(type); - throw new Error('un-implemented'); - } - - getParameterTypes(def, moduleName) { - const expected = []; - const params = def.params.params; - for (let i = 0; i < params.length; i++) { - expected.push(this.getParameterType(params[i].paramType, moduleName)); - } - return expected; - } - - visitStaticCall(ast, env) { - assert.equal(ast.left.type, 'static_call'); - const moduleId = ast.left.id; - const moduleName = moduleId.lexeme; - const checker = this.dependencies.get(moduleName); - const method = ast.left.propertyPath[0]; - const methodName = method.lexeme; - - const def = checker.methods.get(methodName); - if (!def) { - this.error(`the static function "${methodName}" is undefined in ${moduleName}`, method); - } - - if (!def.isStatic) { - this.error(`the "${methodName}" is not static function`, method); - } - - const expected = this.getParameterTypes(def, moduleName); - - const actual = []; - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - this.visitExpr(arg, env); - const type = this.getExprType(arg, env); - actual.push(type); - } - - if (!eql(expected, actual)) { - this.error(`the parameter` + - ` types are mismatched. expected ` + - `${moduleName}.${methodName}(${expected.map((item) => display(item)).join(', ')}), but ` + - `${moduleName}.${methodName}(${actual.map((item) => display(item)).join(', ')})`, method); - } - - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - arg.needCast = isNeedToMap(expected[i], arg.inferred); - } - - ast.isAsync = def.isAsync; - ast.isStatic = def.isStatic; - ast.hasThrow = def.isAsync || def.hasThrow; - ast.inferred = this.getType(def.returnType, moduleName); - } - - getIdType(id) { - // 不检查普通变量,仅作为类型时的检查 - if (this.models.has(id)) { - return 'model'; - } else if (this.dependencies.has(id)) { - return 'module'; - } else if (builtin.has(id)) { - return 'builtin_model'; - } - return ''; - } - - checkId(id, env) { - if (id.tag === Tag.VID) { - return this.checkVid(id, env); - } - - // 未定义变量检查 - const name = id.lexeme; - - if (env.local && env.local.hasDefined(name)) { - // 是否在作用域链上定义过 - id.type = 'variable'; - return; - } - - if (this.models.has(name)) { - id.type = 'model'; - // model 类型 - return; - } - - if (this.dependencies.has(name)) { - // alias - id.type = 'module'; - return; - } - this.error(`variable "${name}" undefined`, id); - } - - getChecker(moduleName) { - if (this.dependencies.has(moduleName)) { - return this.dependencies.get(moduleName); - } - - let current = this; - while (current.parentModuleId) { - current = this.dependencies.get(current.parentModuleId); - - if (current.dependencies.has(moduleName)) { - return current.dependencies.get(moduleName); - } - } - - return null; - } - - getInstanceProperty(vid) { - let current = this; - if (current.properties.has(vid)) { - // check B - return current.properties.get(vid); - } - - while (current.parentModuleId) { - current = current.dependencies.get(current.parentModuleId); - // check C, D, E - if (current.properties.has(vid)) { - return current.properties.get(vid); - } - } - - return null; - } - - getArrayFiledType(field, modelName, moduleName) { - if (field.fieldItemType.tag === Tag.TYPE) { - return { - type: 'array', - itemType: _basic(field.fieldItemType.lexeme) - }; - } - // for filed: [{}] - if (field.fieldItemType.type === 'modelBody') { - return { - type: 'array', - itemType: _model(modelName, moduleName) - }; - } - // for filed: [[]] - if (field.fieldItemType.fieldType === 'array') { - return { - type: 'array', - itemType: this.getArrayFiledType(field.fieldItemType, modelName, moduleName) - }; - } - - return { - type: 'array', - itemType: _model(field.fieldItemType.lexeme, moduleName) - }; - } - - getFieldType(find, modelName, moduleName) { - if (find.fieldValue.fieldType === 'map') { - return { - type: 'map', - keyType: _basic(find.fieldValue.keyType.lexeme), - valueType: _basic(find.fieldValue.valueType.lexeme) - }; - } - - if (find.fieldValue.type === 'modelBody') { - return _model([modelName, find.fieldName.lexeme].join('.'), moduleName); - } - - if (find.fieldValue.fieldType === 'array') { - return this.getArrayFiledType(find.fieldValue, find.fieldValue.itemType, moduleName); - } - - if (find.fieldValue.fieldType.tag === Tag.ID) { - const id = find.fieldValue.fieldType.lexeme; - if (this.models.has(id)) { - return _model(id); - } - - if (this.dependencies.has(id)) { - return { - type: 'module_instance', - name: id - }; - } - return _model(find.fieldValue.fieldType.lexeme, moduleName); - } - - if (find.fieldValue.fieldType.type === 'moduleModel') { - const [mainId, ...rest] = find.fieldValue.fieldType.path; - let filedName = rest.map((tag) => { - return tag.lexeme; - }).join('.'); - return _model(filedName, mainId.lexeme); - } - - if (find.fieldValue.fieldType.type === 'subModel') { - let modelName = find.fieldValue.fieldType.path.map((tag) => { - return tag.lexeme; - }).join('.'); - return _model(modelName); - } - - return _basic(find.fieldValue.fieldType); - } - - getStaticMethod(name) { - let method = this.methods.get(name); - if (method && method.isStatic) { - return { method }; - } - - return null; - } - - getInstanceMethod(name, checkerName) { - let current = this; - if (current.methods.has(name)) { - // check B - let method = current.methods.get(name); - if (!method.isStatic) { - return { - method, - module: checkerName - }; - } - } - - while (current.parentModuleId) { - let moduleName = current.parentModuleId; - current = current.dependencies.get(moduleName); - // check C, D, E - if (current.methods.has(name)) { - // check B - let method = current.methods.get(name); - if (!method.isStatic) { - return { - method, - module: moduleName - }; - } - } - } - - return null; - } - - checkVid(vid, env) { - const name = vid.lexeme; - if (!this.getInstanceProperty(name)) { - this.error(`the type "${name}" is undefined`, vid); - } - - if (env.isStatic) { - this.error(`virtual variable can not used in static function`, vid); - } - - this.vidCounter.set(name, this.vidCounter.get(name) + 1); - } - - checkProperty(ast, env) { - if (ast.id.lexeme === '__module') { - const [key] = ast.propertyPath; - const target = this.consts.get(key.lexeme); - if (!target) { - this.error(`the const ${key.lexeme} is undefined`, key); - } - replace(ast, target); - return; - } - - this.checkId(ast.id, env); - if (this.models.has(ast.id.lexeme)) { - // submodel M.N - let current = this.models.get(ast.id.lexeme); - const currentPath = [ast.id.lexeme]; - for (let i = 0; i < ast.propertyPath.length; i++) { - let prop = ast.propertyPath[i]; - currentPath.push(prop.lexeme); - let find = findProperty(current, prop.lexeme); - if (!find) { - this.error(`The model ${currentPath.join('.')} is undefined`, prop); - } - current = find; - } - ast.realType = { - type: 'class', - name: currentPath.join('.') - }; - return; - } - - if (this.dependencies.has(ast.id.lexeme)) { - // Alias.M.N - return; - } - - // check property - const type = this.getVariableType(ast.id, env); - ast.id.inferred = type; - - if (type.type === 'model' || type.type === 'map') { - // { type: 'map', keyTypeName: 'string', valueTypeName: 'any' } - const currentPath = [ast.id.lexeme]; - let current = type; - ast.propertyPathTypes = new Array(ast.propertyPath.length); - for (let i = 0; i < ast.propertyPath.length; i++) { - let prop = ast.propertyPath[i]; - let find = this.getPropertyType(current, prop.lexeme); - if (!find) { - this.error(`The property ${prop.lexeme} is undefined ` + - `in model ${currentPath.join('.')}(${current.name})`, prop); - } - ast.propertyPathTypes[i] = find; - currentPath.push(prop.lexeme); - current = find; - } - - return; - } - - const name = ast.id.lexeme; - this.error(`The type of '${name}' must be model, object or map`, ast.id); - } - - visitExpr(ast, env) { - if (ast.type === 'string') { - // noop(); - } else if (ast.type === 'number') { - // noop(); - } else if (ast.type === 'boolean') { - // noop(); - } else if (ast.type === 'null') { - // noop(); - } else if (ast.type === 'property_access') { - this.checkProperty(ast, env); - } else if (ast.type === 'object') { - this.visitObject(ast, env); - } else if (ast.type === 'variable') { - this.checkId(ast.id, env); - } else if (ast.type === 'virtualVariable') { - this.checkVid(ast.vid, env); - } else if (ast.type === 'template_string') { - for (var i = 0; i < ast.elements.length; i++) { - var item = ast.elements[i]; - if (item.type === 'expr') { - this.visitExpr(item.expr, env); - } - } - } else if (ast.type === 'call') { - this.visitCall(ast, env); - } else if (ast.type === 'construct') { - this.visitConstruct(ast, env); - } else if (ast.type === 'construct_model') { - this.visitConstructModel(ast, env); - } else if (ast.type === 'array') { - this.visitArray(ast, env); - } else if (ast.type === 'and' || ast.type === 'or') { - this.visitExpr(ast.left, env); - // the expr type should be boolean - if (!this.isBooleanType(ast.left, env)) { - this.error(`the left expr must be boolean type`, ast.left); - } - this.visitExpr(ast.right, env); - // the expr type should be boolean - if (!this.isBooleanType(ast.right, env)) { - this.error(`the right expr must be boolean type`, ast.right); - } - } else if (ast.type === 'not') { - this.visitExpr(ast.expr, env); - if (!this.isBooleanType(ast.expr, env)) { - this.error(`the expr after ! must be boolean type`, ast.expr); - } - } else if (ast.type === 'super') { - this.visitSuperCall(ast, env); - } else if (ast.type === 'map_access') { - let mainType; - if (ast.propertyPath) { - this.checkProperty(ast, env); - mainType = this.calculatePropertyType(ast, env); - } else { - this.checkId(ast.id, env); - mainType = this.getVariableType(ast.id, env); - } - - this.visitExpr(ast.accessKey, env); - - if (mainType.type === 'map') { - if (!this.isStringType(ast.accessKey, env)) { - this.error(`The key expr type must be string type`, ast.accessKey); - } - } else if (mainType.type === 'array') { - ast.type = 'array_access'; - if (!this.isNumberType(ast.accessKey, env)) { - this.error(`The key expr type must be number type`, ast.accessKey); - } - } else { - this.error(`the [] form only support map or array type`, ast.accessKey); - } - } else { - throw new Error('unimplemented.'); - } - ast.inferred = this.getExprType(ast, env); - } - - visitSuperCall(ast, env) { - if (!env.isInitMethod) { - this.error(`super only allowed in init method`, ast); - } - - if (!this.parentModuleId) { - this.error(`this module have no parent module`, ast); - } - - const parent = this.getChecker(this.parentModuleId); - const expected = this.getParameterTypes(parent.init, this.parentModuleId); - - const actual = []; - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - this.visitExpr(arg, env); - const type = this.getExprType(arg, env); - actual.push(type); - } - - if (!eql(expected, actual)) { - this.error(`the parameter` + - ` types are mismatched. expected ` + - `${this.parentModuleId}(${expected.map((item) => display(item)).join(', ')}), but ` + - `${this.parentModuleId}(${actual.map((item) => display(item)).join(', ')})`, ast); - } - - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - arg.needCast = isNeedToMap(expected[i], arg.inferred); - } - } - - checkConstructModelFields(ast, model, modelName, env) { - if (!ast.object) { - return; - } - - const aliasId = ast.aliasId.lexeme; - this.visitObject(ast.object, env); - - for (let i = 0; i < ast.object.fields.length; i++) { - const field = ast.object.fields[i]; - const name = field.fieldName.lexeme; - const modelField = findProperty(model, name); - if (!modelField) { - this.error(`the property "${name}" is undefined in model "${aliasId}.${modelName}"`, field.fieldName); - } - - const type = this.getExprType(field.expr, env); - let expected; - if (ast.aliasId.isModel) { - expected = this.getFieldType(modelField, modelName); - } else { - expected = this.getFieldType(modelField, modelName, aliasId); - } - if (!eql([expected], [type])) { - this.error(`the field type are mismatched. expected ` + - `${display(expected)}, but ${display(type)}`, field.fieldName); - } - field.inferred = type; - field.expectedType = expected; - } - } - - visitConstructModel(ast, env) { - const aliasId = ast.aliasId.lexeme; - - if (this.dependencies.has(aliasId)) { - ast.aliasId.isModule = true; - const checker = this.dependencies.get(aliasId); - const modelId = ast.propertyPath.map((item) => { - return item.lexeme; - }).join('.'); - const model = checker.models.get(modelId); - if (!model) { - this.error(`the model "${modelId}" is undefined in module "${aliasId}"`, ast.propertyPath[0]); - } - - this.usedExternModel.get(aliasId).add(modelId); - this.checkConstructModelFields(ast, model, `${modelId}`, env); - return; - } - - if (this.models.has(aliasId)) { - const model = this.models.get(aliasId); - ast.aliasId.isModel = true; - if (ast.propertyPath.length === 0) { - this.checkConstructModelFields(ast, model, aliasId, env); - return; - } - - const fullPath = [aliasId, ...ast.propertyPath.map((item) => { - return item.lexeme; - })]; - - for (let i = 1; i < fullPath.length; i++) { - const subModelName = fullPath.slice(0, i + 1).join('.'); - if (!this.models.has(subModelName)) { - this.error(`the model "${subModelName}" is undefined`, ast.propertyPath[i]); - } - } - - const modelName = fullPath.join('.'); - const subModel = this.models.get(modelName); - this.checkConstructModelFields(ast, subModel, modelName, env); - return; - } - - this.error(`expected "${aliasId}" is module or model`, ast.aliasId); - } - - visitCall(ast, env) { - assert.ok(ast.type === 'call'); - if (ast.left.type === 'method_call') { - this.visitMethodCall(ast, env); - return; - } - - // need fix the type by id type - if (ast.left.type === 'static_or_instance_call') { - const id = ast.left.id; - this.checkId(id, env); - if (env.local.hasDefined(id.lexeme)) { - ast.left.type = 'instance_call'; - this.visitInstanceCall(ast, env); - return; - } - - if (this.dependencies.has(id.lexeme)) { - ast.left.type = 'static_call'; - this.visitStaticCall(ast, env); - return; - } - - if (id.tag === Tag.VID) { - this.checkVid(id, env); - const type = this.getVariableType(ast.left.id, env); - if (type.type === 'module_instance') { - ast.left.type = 'instance_call'; - this.visitInstanceCall(ast, env); - return; - } - } - } - - throw new Error('un-implemented'); - } - - visitInstanceCall(ast, env) { - assert.equal(ast.left.type, 'instance_call'); - let moduleName; - if (ast.left.id.type === Tag.ID) { - const moduleType = env.local.get(ast.left.id.lexeme); - moduleName = moduleType.name; - } else { - const type = this.getVariableType(ast.left.id, env); - moduleName = type.name; - } - - const checker = this.getChecker(moduleName); - const method = ast.left.propertyPath[0]; - const name = method.lexeme; - const def = checker.getInstanceMethod(name, moduleName); - if (!def) { - this.error(`the instance function/api "${name}" is undefined in ${moduleName}`, method); - } - - const { method: definedApi, module: moduleNameOfDef } = def; - - const actual = []; - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - this.visitExpr(arg, env); - const type = this.getExprType(arg, env); - actual.push(type); - } - - const expected = this.getParameterTypes(definedApi, moduleNameOfDef); - - if (!eql(expected, actual)) { - this.error(`the parameter` + - ` types are mismatched. expected ` + - `${moduleName}.${name}(${expected.map((item) => display(item)).join(', ')}), but ` + - `${moduleName}.${name}(${actual.map((item) => display(item)).join(', ')})`, method); - } - - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - arg.needCast = isNeedToMap(expected[i], arg.inferred); - } - - ast.inferred = this.getType(definedApi.returnType, moduleName); - ast.isAsync = definedApi.isAsync; - ast.isStatic = definedApi.isStatic; - ast.hasThrow = definedApi.isAsync || definedApi.hasThrow; - } - - visitMethodCall(ast, env) { - assert.equal(ast.left.type, 'method_call'); - const id = ast.left.id; - const name = id.lexeme; - const staticMethod = this.getStaticMethod(name); - const instanceMethod = this.getInstanceMethod(name); - const defined = staticMethod || instanceMethod; - if (!defined) { - this.error(`the api/function "${name}" is undefined`, id); - } - - const { method: definedApi, module: moduleName } = defined; - - if (definedApi.type === 'api' && !env.isAsync) { - this.error(`the api only can be used in async function`, id); - } - - if (definedApi.type === 'function') { - if (!env.isAsync && definedApi.isAsync) { - this.error(`the async function only can be used in async function`, id); - } - } - - if (ast.args.length !== definedApi.params.params.length) { - this.error(`the parameters are mismatched, expect ${definedApi.params.params.length} ` + - `parameters, actual ${ast.args.length}`, id); - } - - const expected = this.getParameterTypes(definedApi, moduleName); - - const actual = []; - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - this.visitExpr(arg, env); - const type = this.getExprType(arg, env); - actual.push(type); - } - - if (!eql(expected, actual)) { - this.error(`the parameter` + - ` types are mismatched. expected ` + - `${name}(${expected.map((item) => display(item)).join(', ')}), but ` + - `${name}(${actual.map((item) => display(item)).join(', ')})`, id); - } - - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - arg.needCast = isNeedToMap(expected[i], arg.inferred); - } - - ast.isStatic = definedApi.type === 'api' ? false : definedApi.isStatic; - ast.isAsync = definedApi.type === 'api' ? true : definedApi.isAsync; - ast.inferred = this.getType(definedApi.returnType); - ast.hasThrow = ast.isAsync || definedApi.hasThrow; - } - - visitConstruct(ast, env) { - const aliasId = ast.aliasId.lexeme; - if (!this.dependencies.has(aliasId)) { - this.error(`the module "${aliasId}" is not imported`, ast.aliasId); - } - - const checker = this.dependencies.get(aliasId); - if (!checker.init) { - this.error(`the module "${aliasId}" don't has init`, ast.aliasId); - } - - const actual = []; - for (let i = 0; i < ast.args.length; i++) { - const arg = ast.args[i]; - this.visitExpr(arg, env); - actual.push(arg.inferred); - } - - const expected = this.getParameterTypes(checker.init, aliasId); - - if (!eql(expected, actual)) { - this.error(`the parameter` + - ` types are mismatched. expected ` + - `new ${aliasId}(${expected.map((item) => display(item)).join(', ')}), but ` + - `new ${aliasId}(${actual.map((item) => display(item)).join(', ')})`, ast.aliasId); - } - } - - visitArray(ast, env) { - assert.equal(ast.type, 'array'); - for (var i = 0; i < ast.items.length; i++) { - this.visitExpr(ast.items[i], env); - } - } - - getVariableType(id, env) { - if (id.tag === Tag.VID) { - const def = this.getInstanceProperty(id.lexeme); - return this.getType(def.value); - } - - const name = id.lexeme; - - if (env.local && env.local.hasDefined(name)) { - // 返回作用域链上定义的值 - return env.local.get(name); - } - - if (this.models.has(name)) { - return _basic('class'); - } - - if (this.dependencies.has(name)) { - return _basic('class'); - } - - console.log(id); - throw new Error('Can not get the type for variable'); - } - - getPropertyType(type, propName) { - if (type.type === 'map') { - if (type.valueType.name === 'any') { - return _basic('any'); - } - - if (!type.valueType.name) { - return type.valueType; - } - - if (isBasicType(type.valueType.name)) { - return _basic(type.valueType.name); - } - - return _model(type.valueType.name); - } - - if (type.type === 'model') { - let model; - if (type.moduleName) { - const checker = this.dependencies.get(type.moduleName); - model = checker.models.get(type.name); - } else if (builtin.has(type.name)) { - model = builtin.get(type.name); - } else { - model = this.models.get(type.name); - } - - const find = findProperty(model, propName); - if (!find) { - return; - } - - return this.getFieldType(find, type.name, type.moduleName); - } - } - - calculatePropertyType(ast, env) { - if (this.models.has(ast.id.lexeme)) { - return _basic('class'); - } - - if (this.dependencies.has(ast.id.lexeme)) { - return _basic('class'); - } - - const type = this.getVariableType(ast.id, env); - if (type.type === 'model' || type.type === 'map') { - let current = type; - for (let i = 0; i < ast.propertyPath.length; i++) { - let prop = ast.propertyPath[i]; - current = this.getPropertyType(current, prop.lexeme); - } - - return current; - } - - console.log(ast); - throw new Error('unknown type'); - } - - getExprType(ast, env) { - if (!ast) { - return _basic('void'); - } - - if (ast.inferred) { - return ast.inferred; - } - - if (ast.type === 'property_access' || ast.type === 'property') { - return this.calculatePropertyType(ast, env); - } - - if (ast.type === 'map_access') { - let type; - if (ast.propertyPath) { - type = this.calculatePropertyType(ast, env); - } else { - type = this.getVariableType(ast.id, env); - } - - if (type.type === 'array') { - return type.itemType; - } else if (type.type === 'map') { - return type.valueType; - } - } - - if (ast.type === 'array_access') { - let type; - if (ast.propertyPath) { - type = this.calculatePropertyType(ast, env); - } else { - type = this.getVariableType(ast.id, env); - } - - if (type.type === 'array') { - return type.itemType; - } - } - - if (ast.type === 'string') { - return _basic('string'); - } - - if (ast.type === 'number') { - return _basic(ast.value.type); - } - - if (ast.type === 'boolean') { - return _basic('boolean'); - } - - if (ast.type === 'object') { - if (ast.fields.length === 0) { - return { - type: 'map', - keyType: _basic('string'), - valueType: _basic('any') - }; - } - - var current; - var same = true; - for (let i = 0; i < ast.fields.length; i++) { - const field = ast.fields[i]; - if (field.type === 'objectField') { - let type = this.getExprType(field.expr, env); - if (current && !isSameType(current, type)) { - same = false; - break; - } - current = type; - } else if (field.type === 'expandField') { - let type = this.getExprType(field.expr, env); - if (type.type === 'map') { - if (current && !isSameType(current, type.valueType)) { - same = false; - break; - } - current = type.valueType; - } else { - same = false; - break; - } - } - } - - return { - type: 'map', - keyType: _basic('string'), - valueType: same ? current : _basic('any') - }; - } - - if (ast.type === 'variable') { - return this.getVariableType(ast.id, env); - } - - if (ast.type === 'virtualVariable') { - const type = this.getInstanceProperty(ast.vid.lexeme); - return this.getType(type.value); - } - - if (ast.type === 'null') { - return _basic('null'); - } - - if (ast.type === 'template_string') { - return _basic('string'); - } - - if (ast.type === 'call') { - assert.ok(ast.inferred); - return ast.inferred; - } - - if (ast.type === 'super') { - return { - type: 'module_instance', - name: this.parentModuleId - }; - } - - if (ast.type === 'construct') { - return { - type: 'module_instance', - name: ast.aliasId.lexeme - }; - } - - if (ast.type === 'construct_model') { - if (ast.aliasId.isModel) { - return _model([ast.aliasId.lexeme, ...ast.propertyPath.map((item) => { - return item.lexeme; - })].join('.')); - } - - return _model(ast.propertyPath.map((item) => { - return item.lexeme; - }).join('.'), ast.aliasId.lexeme); - } - - if (ast.type === 'array') { - if (ast.items.length === 0) { - return { - type: 'array', - itemType: _basic('any') - }; - } - - let current; - let same = true; - for (let i = 0; i < ast.items.length; i++) { - const type = this.getExprType(ast.items[i], env); - if (current && !isSameType(current, type)) { - same = false; - break; - } - current = type; - } - return { - type: 'array', - itemType: same ? current : _basic('any') - }; - } - - if (ast.type === 'not') { - return _basic('boolean'); - } - - if (ast.type === 'and' || ast.type === 'or') { - return _basic('boolean'); - } - - console.log(ast); - throw new Error('can not get type'); - } - - getType(t, extern) { - if (t.tag === Tag.TYPE && t.lexeme === 'object') { - return { - type: 'map', - keyType: _basic('string'), - valueType: _basic('any') - }; - } - - if (t.tag === Tag.TYPE) { - return _basic(t.lexeme); - } - - if (t.tag === Tag.ID) { - if (t.lexeme.startsWith('$')) { - return _model(t.lexeme); - } - - if (t.idType === 'module') { - return { - type: 'module_instance', - name: t.lexeme - }; - } - - if (extern) { - return _model(t.lexeme, extern); - } - - if (this.dependencies.has(t.lexeme)) { - return { - type: 'module_instance', - name: t.lexeme - }; - } - - return _model(t.lexeme); - } - - if (t.type === 'array') { - return { - type: 'array', - itemType: this.getType(t.subType) - }; - } - - if (t.type === 'map') { - if (t.valueType.type === 'subModel_or_moduleModel') { - this.checkType(t.valueType); - return { - type: 'map', - keyType: _type(t.keyType), - valueType: t.valueType - }; - } - return { - type: 'map', - keyType: _type(t.keyType), - valueType: _type(t.valueType) - }; - } - - if (t.type === 'subModel_or_moduleModel') { - this.checkType(t); - } - - if (t.type === 'moduleModel') { - const [mainId, ...rest] = t.path; - return _model(rest.map((item) => { - return item.lexeme; - }).join('.'), mainId.lexeme); - } - - if (t.type === 'subModel') { - let modelName = t.path.map((tag) => { - return tag.lexeme; - }).join('.'); - return _model(modelName); - } - - // return _model(t.path.map((item) => { - // return item.lexeme; - // }).join('.')); - - console.log(t); - throw new Error('un-implemented'); - } - - visitObject(ast, env) { - assert.equal(ast.type, 'object'); - for (var i = 0; i < ast.fields.length; i++) { - this.visitObjectField(ast.fields[i], env); - } - ast.inferred = this.getExprType(ast, env); - } - - visitObjectField(ast, env) { - if (ast.type === 'objectField') { - this.visitExpr(ast.expr, env); - } else if (ast.type === 'expandField') { - this.visitExpr(ast.expr, env); - const type = ast.expr.inferred; - if (type.type === 'model' || type.type === 'map') { - return; - } - - const name = ast.expr.id.lexeme; - this.error(`the expand field "${name}" should be an ` + - `object or model`, ast.expr.id); - } - } - - visitReturnBody(ast, env) { - assert.equal(ast.type, 'returnBody'); - this.visitStmts(ast.stmts, env); - } - - visitStmts(ast, env) { - assert.equal(ast.type, 'stmts'); - for (var i = 0; i < ast.stmts.length; i++) { - const node = ast.stmts[i]; - this.visitStmt(node, env); - } - } - - visitReturn(ast, env) { - assert.equal(ast.type, 'return'); - if (ast.expr) { - this.visitExpr(ast.expr, env); - } - - if (env.isInitMethod && ast.expr) { - this.error(`should not have return value in init method`, ast); - } - - // return type check - var actual = this.getExprType(ast.expr, env); - var expect = this.getType(env.returnType); - ast.needCast = isNeedToModel(expect, actual); - if (!eql([expect], [actual])) { - this.error(`the return type is not expected, expect: ${display(expect)}, actual: ${display(actual)}`, ast); - } - if (ast.needCast) { - ast.expectedType = expect; - } - } - - visitAssign(ast, env) { - assert.equal(ast.type, 'assign'); - if (ast.left.type === 'virtualVariable') { - this.checkVid(ast.left.vid, env); - } else if (ast.left.type === 'variable') { - this.checkId(ast.left.id, env); - } else if (ast.left.type === 'property') { - this.checkProperty(ast.left, env); - } else if (ast.left.type === 'map_access') { - let mainType; - if (ast.left.propertyPath) { - this.checkProperty(ast.left, env); - mainType = this.calculatePropertyType(ast.left, env); - } else { - this.checkId(ast.left.id, env); - mainType = this.getVariableType(ast.left.id, env); - } - - if (mainType.type === 'array') { - ast.left.type = 'array_access'; - if (!this.isNumberType(ast.left.accessKey, env)) { - this.error(`The key expr type must be number type`, ast.accessKey); - } - } - } else { - throw new Error('unimplemented'); - } - const expected = this.getExprType(ast.left, env); - ast.left.inferred = expected; - this.visitExpr(ast.expr, env); - const actual = this.getExprType(ast.expr, env); - if (!isAssignable(expected, actual, ast.expr)) { - this.error(`can't assign ${display(actual)} to ${display(expected)}`, ast.expr); - } - - if (expected.type === 'basic' && expected.name === 'readable') { - if (actual.type === 'basic' && actual.name === 'bytes' || - actual.type === 'basic' && actual.name === 'string') { - ast.expr.needToReadable = true; - } - } - } - - visitDeclare(ast, env) { - const id = ast.id.lexeme; - // 当前作用域是否定义过 - if (env.local.has(id)) { - this.error(`the id "${id}" was defined`, ast.id); - } - this.visitExpr(ast.expr, env); - - const type = this.getExprType(ast.expr, env); - let expected; - - if (type.type === 'basic' && type.name === 'null') { - if (!ast.expectedType) { - this.error(`must declare type when value is null`, ast.id); - } - expected = this.getType(ast.expectedType); - } else { - if (ast.expectedType) { - expected = this.getType(ast.expectedType); - if (!isAssignable(expected, type, ast.expr)) { - this.error(`declared variable with mismatched type, ` + - `expected: ${display(expected)}, actual: ${display(type)}`, ast.id); - } - } - } - - env.local.set(id, expected || type); - ast.expr.inferred = expected || type; - } - - visitThrow(ast, env) { - this.visitObject(ast.expr, env); - } - - visitIf(ast, env) { - assert.equal(ast.type, 'if'); - this.visitExpr(ast.condition, env); - this.visitStmts(ast.stmts, env); - - for (let i = 0; i < ast.elseIfs.length; i++) { - const branch = ast.elseIfs[i]; - this.visitExpr(branch.condition, env); - this.visitStmts(branch.stmts, env); - } - - if (ast.elseStmts) { - this.visitStmts(ast.elseStmts, env); - } - } - - arrayFieldFlat(arrayField){ - if (arrayField.tag === Tag.ID) { - const typeId = arrayField.lexeme; - const type = this.getIdType(typeId); - if (!type) { - this.error(`the type "${typeId}" is undefined`, arrayField); - } - arrayField.idType = type; - } else if (arrayField.tag === Tag.TYPE) { - // TODO - } else if (arrayField.type === 'map') { - // TODO - } else if (arrayField.fieldType === 'array') { - return this.arrayFieldFlat(arrayField.fieldItemType); - } else { - return arrayField; - } - } - - flatModel(root, modelBody, modelName) { - const keys = new Map(); - for (var i = 0; i < modelBody.nodes.length; i++) { - const node = modelBody.nodes[i]; - const fieldName = node.fieldName.lexeme; - - if (keys.has(fieldName)) { - this.error(`redefined field "${fieldName}" in model "${modelName}"`, node.fieldName); - } - keys.set(fieldName, true); - const fieldValue = node.fieldValue; - - if (fieldValue.type === 'modelBody') { - this.flatModel(root, fieldValue, `${modelName}.${node.fieldName.lexeme}`); - } else if (fieldValue.type === 'fieldType') { - // check the type - if (fieldValue.fieldType.tag === Tag.ID) { - const typeId = fieldValue.fieldType.lexeme; - const type = this.getIdType(typeId); - if (!type) { - this.error(`the type "${typeId}" is undefined`, fieldValue.fieldType); - } - - fieldValue.fieldType.idType = type; - } - - if (fieldValue.fieldType === 'array') { - const modelBody = this.arrayFieldFlat(node.fieldValue.fieldItemType); - if (modelBody) { - const submodel = `${modelName}.${fieldName}`; - this.flatModel(root, modelBody, submodel); - node.fieldValue.itemType = submodel; - } - } - - if (fieldValue.fieldType === 'map') { - this.checkType(fieldValue.valueType); - } - - if (fieldValue.fieldType.type === 'subModel_or_moduleModel') { - this.checkType(fieldValue.fieldType); - } - - if (typeof fieldValue.fieldType === 'string') { - this.usedTypes.set(fieldValue.fieldType, true); - } - } else { - throw new Error('unimplemented'); - } - } - - this.models.set(modelName, { - type: 'model', - modelName: { - tag: Tag.ID, - lexeme: modelName - }, - modelBody: modelBody, - annotation: undefined - }); - } - - visitModel(ast) { - assert.equal(ast.type, 'model'); - const modelName = ast.modelName.lexeme; - this.flatModel(ast, ast.modelBody, modelName); - } -} - -function getChecker(source, filePath) { - const lexer = new Lexer(source, filePath); - const parser = new Parser(lexer); - const ast = parser.program(); - return new TypeChecker(source, filePath).check(ast); -} - -function analyze(source, filePath) { - const checker = getChecker(source, filePath); - return checker.ast; -} - -exports.analyze = analyze; - -exports.getChecker = getChecker; diff --git a/lib/tag.js b/lib/tag.js index ac3abf8..9d592b1 100644 --- a/lib/tag.js +++ b/lib/tag.js @@ -5,7 +5,8 @@ exports.Tag = Object.freeze({ ID: 2, VID: 3, // @ID CONST: 4, - MODEL: 5, + MODEL: 5, // model + MODULE: 6, // module EXTENDS: 7, // extends TYPE: 8, // string/number/bytes NUMBER: 9, // Number literal @@ -20,18 +21,24 @@ exports.Tag = Object.freeze({ ELSE: 18, // else ANNOTATION: 19, //annotation COMMENT: 20, // comments + PACK_ID: 21, // $ID, used for packageid IMPORT: 22, // import NEW: 23, // new - RPC: 24, // rpc + INTERFACE: 24, // interface STATIC: 25, // static - AND: 26, // && - OR: 27, // || + LOGICAL: 26, // &&, || + OPERATOR: 27, // <, <=, >, >= TRY: 28, // try CATCH: 29, // catch FINALLY: 30, // finally WHILE: 31, // while FOR: 32, // for BREAK: 33, // break + TO: 34, // to + IMPLEMENTS: 35, // implements + INLINE_ID: 36, // #id + OF: 37, // of + AS: 38, // as }); var TagTip = {}; diff --git a/lib/tokens.js b/lib/tokens.js index 39b6bb8..eb32899 100644 --- a/lib/tokens.js +++ b/lib/tokens.js @@ -4,13 +4,21 @@ const { Tag } = require('./tag'); -const { - Token -} = require('@jacksontian/skyline'); +class Token { + constructor(tag, loc, index) { + this.tag = tag; + this.loc = loc; + this.index = index; + } + + toString() { + return `${this.tag}`; + } +} class StringLiteral extends Token { - constructor(string, loc) { - super(Tag.STRING, loc); + constructor(string, loc, index) { + super(Tag.STRING, loc, index); this.string = string; } @@ -20,8 +28,8 @@ class StringLiteral extends Token { } class NumberLiteral extends Token { - constructor(value, type,loc) { - super(Tag.NUMBER, loc); + constructor(value, type, loc, index) { + super(Tag.NUMBER, loc, index); this.value = value; this.type = type; } @@ -32,8 +40,8 @@ class NumberLiteral extends Token { } class Annotation extends Token { - constructor(value, loc) { - super(Tag.ANNOTATION, loc); + constructor(value, loc, index) { + super(Tag.ANNOTATION, loc, index); this.value = value; } @@ -43,8 +51,8 @@ class Annotation extends Token { } class Comment extends Token { - constructor(value, loc) { - super(Tag.COMMENT, loc); + constructor(value, loc, index) { + super(Tag.COMMENT, loc, index); this.value = value; } @@ -54,8 +62,8 @@ class Comment extends Token { } class TemplateElement extends Token { - constructor(value, isTail, loc) { - super(Tag.TEMPLATE, loc); + constructor(value, isTail, loc, index) { + super(Tag.TEMPLATE, loc, index); this.string = value; this.tail = isTail; } @@ -66,8 +74,8 @@ class TemplateElement extends Token { } class WordToken extends Token { - constructor(tag, lexeme, loc) { - super(tag, loc); + constructor(tag, lexeme, loc, index) { + super(tag, loc, index); this.lexeme = lexeme; } @@ -76,9 +84,20 @@ class WordToken extends Token { } } +class LogicalToken extends Token { + constructor(lexeme, loc, index) { + super(Tag.LOGICAL, loc, index); + this.lexeme = lexeme; + } + + toString() { + return `Logical: \`${this.lexeme}\``; + } +} + class OperatorToken extends Token { - constructor(tag, lexeme, loc) { - super(tag, loc); + constructor(lexeme, loc, index) { + super(Tag.OPERATOR, loc, index); this.lexeme = lexeme; } @@ -88,11 +107,13 @@ class OperatorToken extends Token { } module.exports = { + Token, StringLiteral, NumberLiteral, Annotation, Comment, TemplateElement, WordToken, + LogicalToken, OperatorToken -}; +}; \ No newline at end of file diff --git a/lib/util.js b/lib/util.js index be7051f..3cb75dd 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,10 +1,8 @@ 'use strict'; -const fs = require('fs'); -const path = require('path'); -exports.isBasicType = function (type){ +exports.isBasicType = function (type) { const basicType = [ - 'void', 'string', 'number', 'integer', + 'void', 'string', 'integer', 'int8', 'int16', 'int32', 'int64', 'long', 'ulong', 'uint8', 'uint16', 'uint32', 'uint64', 'float', 'double', @@ -16,7 +14,7 @@ exports.isBasicType = function (type){ exports.isNumber = function (type) { const numberType = [ - 'number', 'integer', 'int8', 'int16', + 'integer', 'int8', 'int16', 'int32', 'int64', 'long', 'ulong', 'uint8', 'uint16', 'uint32', 'uint64', 'float', 'double' @@ -32,7 +30,3 @@ exports.isInteger = function (type) { ]; return integerType.indexOf(type) !== -1; }; - -exports.getDarafile = function (dir) { - return fs.existsSync(path.join(dir, 'Teafile')) ? path.join(dir, 'Teafile') : path.join(dir, 'Darafile'); -}; \ No newline at end of file diff --git a/package.json b/package.json index b374c23..10fa8d9 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,17 @@ { "name": "@darabonba/parser", - "version": "1.2.7", + "description": "Darabonba Parser", + "version": "2.0.0", "main": "index.js", "directories": { + "doc": "doc", "lib": "lib", "test": "test" }, + "dependencies": { + "debug": "^4.1.1", + "strip-json-comments": "^3.1.1" + }, "scripts": { "lint": "eslint --fix lib test *.js", "test": "mocha --inline-diffs -b -R spec test/*.test.js", @@ -15,20 +21,22 @@ "author": "Jackson Tian", "license": "MIT", "devDependencies": { - "codecov": "^3", - "eslint": "^7", - "expect.js": "^0.3.1", - "mocha": "^7", - "nyc": "^15" - }, - "dependencies": { - "@jacksontian/skyline": "^1.3.0", - "strip-json-comments": "^3.1.1" + "codecov": "^3.7.2", + "eslint": "^7.8.1", + "mocha": "^8.1.1", + "nyc": "^15.1.0" }, - "description": "", "files": [ "bin", "lib", "index.js" - ] + ], + "repository": { + "type": "git", + "url": "git+https://github.com/aliyun/darabonba.git" + }, + "bugs": { + "url": "https://github.com/aliyun/darabonba/issues" + }, + "homepage": "https://github.com/aliyun/darabonba#readme" } diff --git a/test/base_lexer.test.js b/test/base_lexer.test.js new file mode 100644 index 0000000..135e5f9 --- /dev/null +++ b/test/base_lexer.test.js @@ -0,0 +1,53 @@ +'use strict'; + +const assert = require('assert'); + +const BaseLexer = require('../lib/base_lexer'); +const Keyword = require('../lib/keyword'); + +describe('base lexer', function () { + it('base lexer should ok', function () { + const lexer = new BaseLexer(`source`, '__filename'); + lexer.getch(); + assert.deepStrictEqual(lexer.peek, 's'); + lexer.getch(); + assert.deepStrictEqual(lexer.peek, 'o'); + lexer.ungetch(); + assert.deepStrictEqual(lexer.peek, 's'); + }); + + it('loc should ok', function () { + const lexer = new BaseLexer(`key\nwords whitespace`, '__filename'); + lexer.getch(); + assert.deepStrictEqual(lexer.peek, 'k'); + assert.deepStrictEqual(lexer.line, 1); + assert.deepStrictEqual(lexer.column, 1); + lexer.getch(); + assert.deepStrictEqual(lexer.peek, 'e'); + assert.deepStrictEqual(lexer.line, 1); + assert.deepStrictEqual(lexer.column, 2); + lexer.getch(); + assert.deepStrictEqual(lexer.peek, 'y'); + assert.deepStrictEqual(lexer.line, 1); + assert.deepStrictEqual(lexer.column, 3); + lexer.getch(); + assert.deepStrictEqual(lexer.peek, '\n'); + assert.deepStrictEqual(lexer.line, 1); + assert.deepStrictEqual(lexer.column, 4); + lexer.getch(); + assert.deepStrictEqual(lexer.peek, 'w'); + assert.deepStrictEqual(lexer.line, 2); + assert.deepStrictEqual(lexer.column, 1); + }); + + it('duplicate reserved word should not ok', function () { + assert.throws(() => { + const lexer = new BaseLexer(`source`, '__filename'); + lexer.reserve(new Keyword('keyword', 1)); + lexer.reserve(new Keyword('keyword', 1)); + }, (ex) => { + assert.deepStrictEqual(ex.message, 'duplicate reserved word: keyword'); + return true; + }); + }); +}); \ No newline at end of file diff --git a/test/comment.test.js b/test/comment.test.js deleted file mode 100644 index f4ad02e..0000000 --- a/test/comment.test.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -const expect = require('expect.js'); - -const comment = require('../lib/comment'); -const Parser = require('../lib/parser'); -const Lexer = require('../lib/lexer'); - -function parse(source, filePath) { - const lexer = new Lexer(source, filePath); - const parser = new Parser(lexer); - return parser.program(); -} - -function pos(line, column) { - return { line, column }; -} - -function loc(startLine, startColumn, endLine, endColumn) { - return { - start: pos(startLine, startColumn), - end: pos(endLine, endColumn) - }; -} - -describe('comment util', function () { - let comments = {}; - let model = {}; - - before(function () { - let ast = parse(` - // front model comment one - // front model comment two - model M{ - // empty model one - // empty model two - } - // back model comment one - // back model comment two - `, '__filename'); - comments = ast.comments; - [model] = ast.moduleBody.nodes; - }); - - it('get model front comment should be ok', function () { - expect(comment.getFrontComments(comments, model.tokenRange[0])).to.eql([{ - 'index': 1, - 'loc': loc(2, 5, 2, 31), - 'value': '// front model comment one', - 'tag': 20 - }, { - 'index': 2, - 'loc': loc(3, 5, 3, 31), - 'value': '// front model comment two', - 'tag': 20 - }]); - }); - - it('get comment in model body should be', function () { - expect(comment.getBetweenComments(comments, model.tokenRange[0], model.tokenRange[1])).to.eql([{ - 'index': 6, - 'loc': loc(5, 7, 5, 25), - 'value': '// empty model one', - 'tag': 20 - }, { - 'index': 7, - 'loc': loc(6, 7, 6, 25), - 'value': '// empty model two', - 'tag': 20 - }]); - }); - - it('get comment behind model should be', function () { - expect(comment.getBackComments(comments, model.tokenRange[1])).to.eql([{ - 'index': 9, - 'loc': loc(8, 5, 8, 30), - 'value': '// back model comment one', - 'tag': 20 - }, { - 'index': 10, - 'loc': loc(9, 5, 9, 30), - 'value': '// back model comment two', - 'tag': 20 - }]); - }); -}); diff --git a/test/env.test.js b/test/env.test.js index ec4c291..3f1f13b 100644 --- a/test/env.test.js +++ b/test/env.test.js @@ -1,6 +1,6 @@ 'use strict'; -const expect = require('expect.js'); +const assert = require('assert'); const Env = require('../lib/env'); @@ -8,18 +8,18 @@ describe('env', function () { it('get ok', function () { var env = new Env(); env.set('a', 'a1'); - expect(env.get('a')).to.be('a1'); + assert.deepStrictEqual(env.get('a'), 'a1'); var env1 = new Env(env); - expect(env1.get('a')).to.be('a1'); - expect(env1.get('b')).to.be(null); + assert.deepStrictEqual(env1.get('a'), 'a1'); + assert.deepStrictEqual(env1.get('b'), null); }); it('hasDefined ok', function () { var env = new Env(); env.set('a', 'a1'); - expect(env.hasDefined('a')).to.be(true); + assert.deepStrictEqual(env.hasDefined('a'), true); var env1 = new Env(env); - expect(env1.hasDefined('a')).to.be(true); - expect(env1.hasDefined('b')).to.be(false); + assert.deepStrictEqual(env1.hasDefined('a'), true); + assert.deepStrictEqual(env1.hasDefined('b'), false); }); }); diff --git a/test/fixtures/clean_package/Darafile b/test/fixtures/clean_package/Darafile new file mode 100644 index 0000000..6b0dfa9 --- /dev/null +++ b/test/fixtures/clean_package/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "1.0" +} \ No newline at end of file diff --git a/test/fixtures/darafile_with_comments/.libraries.json b/test/fixtures/darafile_with_comments/.libraries.json deleted file mode 100644 index c3fe47f..0000000 --- a/test/fixtures/darafile_with_comments/.libraries.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "alibabacloud:OSS:*": "libraries/alibabacloud-OSS-0.0.1" -} \ No newline at end of file diff --git a/test/fixtures/darafile_with_comments/Darafile b/test/fixtures/darafile_with_comments/Darafile deleted file mode 100644 index c4dd561..0000000 --- a/test/fixtures/darafile_with_comments/Darafile +++ /dev/null @@ -1,6 +0,0 @@ -{ - // commits - "libraries": { - "OSS": "alibabacloud:OSS:*" - } -} \ No newline at end of file diff --git a/test/fixtures/darafile_with_comments/libraries/alibabacloud-OSS-0.0.1/Teafile b/test/fixtures/darafile_with_comments/libraries/alibabacloud-OSS-0.0.1/Teafile deleted file mode 100644 index d8b13a0..0000000 --- a/test/fixtures/darafile_with_comments/libraries/alibabacloud-OSS-0.0.1/Teafile +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "OSS", - "main": "./oss.tea" -} \ No newline at end of file diff --git a/test/fixtures/darafile_with_comments/libraries/alibabacloud-OSS-0.0.1/oss.tea b/test/fixtures/darafile_with_comments/libraries/alibabacloud-OSS-0.0.1/oss.tea deleted file mode 100644 index f36a9f6..0000000 --- a/test/fixtures/darafile_with_comments/libraries/alibabacloud-OSS-0.0.1/oss.tea +++ /dev/null @@ -1,5 +0,0 @@ -init(); - -function putObject(): void { - -} diff --git a/test/fixtures/darafile_with_comments/main.dara b/test/fixtures/darafile_with_comments/main.dara deleted file mode 100644 index d55a62f..0000000 --- a/test/fixtures/darafile_with_comments/main.dara +++ /dev/null @@ -1 +0,0 @@ -import OSS diff --git a/test/fixtures/declare_module_model/Darafile b/test/fixtures/declare_module_model/Darafile deleted file mode 100644 index 7e4f9a5..0000000 --- a/test/fixtures/declare_module_model/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "./oss.dara" - } -} diff --git a/test/fixtures/declare_module_model/main.dara b/test/fixtures/declare_module_model/main.dara deleted file mode 100644 index 2f0ffaf..0000000 --- a/test/fixtures/declare_module_model/main.dara +++ /dev/null @@ -1,9 +0,0 @@ -import OSS - -static function call(): void { - var fileNull: OSS.File = null; - var file: OSS.File = new OSS.File{}; - var m: map[string]any = { - file = file - }; -} diff --git a/test/fixtures/declare_module_model/oss.dara b/test/fixtures/declare_module_model/oss.dara deleted file mode 100644 index 525ed8c..0000000 --- a/test/fixtures/declare_module_model/oss.dara +++ /dev/null @@ -1,6 +0,0 @@ - -model File = {}; - -init(); - - diff --git a/test/fixtures/empty_package/Darafile b/test/fixtures/empty_package/Darafile new file mode 100644 index 0000000..a2159a7 --- /dev/null +++ b/test/fixtures/empty_package/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} diff --git a/test/fixtures/extends/Darafile b/test/fixtures/extends/Darafile deleted file mode 100644 index 7e4f9a5..0000000 --- a/test/fixtures/extends/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "./oss.dara" - } -} diff --git a/test/fixtures/extends/extends_unimported.dara b/test/fixtures/extends/extends_unimported.dara deleted file mode 100644 index b6ae0d5..0000000 --- a/test/fixtures/extends/extends_unimported.dara +++ /dev/null @@ -1,5 +0,0 @@ -extends OSS - -function callWrap(): void { - putObject(); -} diff --git a/test/fixtures/extends/main.dara b/test/fixtures/extends/main.dara deleted file mode 100644 index 58f90bf..0000000 --- a/test/fixtures/extends/main.dara +++ /dev/null @@ -1,8 +0,0 @@ -import OSS - -extends OSS - -function callWrap(): void { - putObject(); - @p; -} diff --git a/test/fixtures/extends/oss.dara b/test/fixtures/extends/oss.dara deleted file mode 100644 index 4a4f8c5..0000000 --- a/test/fixtures/extends/oss.dara +++ /dev/null @@ -1,8 +0,0 @@ - -type @p = string; - -init(a: string); - -function putObject(): void { - -} diff --git a/test/fixtures/extends/super.dara b/test/fixtures/extends/super.dara deleted file mode 100644 index 4e9bcb0..0000000 --- a/test/fixtures/extends/super.dara +++ /dev/null @@ -1,7 +0,0 @@ -import OSS - -extends OSS - -init() { - super("string"); -} diff --git a/test/fixtures/extends/super_types_mismatched.dara b/test/fixtures/extends/super_types_mismatched.dara deleted file mode 100644 index fde54a0..0000000 --- a/test/fixtures/extends/super_types_mismatched.dara +++ /dev/null @@ -1,7 +0,0 @@ -import OSS - -extends OSS - -init() { - super(1234); -} diff --git a/test/fixtures/import_by_tea/.libraries.json b/test/fixtures/import_by_tea/.libraries.json deleted file mode 100644 index c3fe47f..0000000 --- a/test/fixtures/import_by_tea/.libraries.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "alibabacloud:OSS:*": "libraries/alibabacloud-OSS-0.0.1" -} \ No newline at end of file diff --git a/test/fixtures/import_by_tea/Teafile b/test/fixtures/import_by_tea/Teafile deleted file mode 100644 index bd3751c..0000000 --- a/test/fixtures/import_by_tea/Teafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "alibabacloud:OSS:*" - } -} \ No newline at end of file diff --git a/test/fixtures/import_by_tea/libraries/alibabacloud-OSS-0.0.1/Teafile b/test/fixtures/import_by_tea/libraries/alibabacloud-OSS-0.0.1/Teafile deleted file mode 100644 index d8b13a0..0000000 --- a/test/fixtures/import_by_tea/libraries/alibabacloud-OSS-0.0.1/Teafile +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "OSS", - "main": "./oss.tea" -} \ No newline at end of file diff --git a/test/fixtures/import_by_tea/libraries/alibabacloud-OSS-0.0.1/oss.tea b/test/fixtures/import_by_tea/libraries/alibabacloud-OSS-0.0.1/oss.tea deleted file mode 100644 index f36a9f6..0000000 --- a/test/fixtures/import_by_tea/libraries/alibabacloud-OSS-0.0.1/oss.tea +++ /dev/null @@ -1,5 +0,0 @@ -init(); - -function putObject(): void { - -} diff --git a/test/fixtures/import_by_tea/main.tea b/test/fixtures/import_by_tea/main.tea deleted file mode 100644 index d55a62f..0000000 --- a/test/fixtures/import_by_tea/main.tea +++ /dev/null @@ -1 +0,0 @@ -import OSS diff --git a/test/fixtures/import_duplicate/Darafile b/test/fixtures/import_duplicate/Darafile deleted file mode 100644 index 7b19e8e..0000000 --- a/test/fixtures/import_duplicate/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "oss": "./oss.dara" - } -} \ No newline at end of file diff --git a/test/fixtures/import_duplicate/main.dara b/test/fixtures/import_duplicate/main.dara deleted file mode 100644 index 4e5134b..0000000 --- a/test/fixtures/import_duplicate/main.dara +++ /dev/null @@ -1,2 +0,0 @@ -import oss -import oss diff --git a/test/fixtures/import_duplicate/oss.dara b/test/fixtures/import_duplicate/oss.dara deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/import_init_params/Darafile b/test/fixtures/import_init_params/Darafile deleted file mode 100644 index e50c7a6..0000000 --- a/test/fixtures/import_init_params/Darafile +++ /dev/null @@ -1,6 +0,0 @@ -{ - "libraries": { - "oss": "path/to/oss", - "OSS": "./oss.dara" - } -} \ No newline at end of file diff --git a/test/fixtures/import_init_params/main.dara b/test/fixtures/import_init_params/main.dara deleted file mode 100644 index 4250b35..0000000 --- a/test/fixtures/import_init_params/main.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -function call(): void { - var oss = new OSS(true); - oss.putObject(); -} \ No newline at end of file diff --git a/test/fixtures/import_init_params/oss.dara b/test/fixtures/import_init_params/oss.dara deleted file mode 100644 index dd31d60..0000000 --- a/test/fixtures/import_init_params/oss.dara +++ /dev/null @@ -1 +0,0 @@ -init(id: string); \ No newline at end of file diff --git a/test/fixtures/import_installed_remote/.libraries.json b/test/fixtures/import_installed_remote/.libraries.json deleted file mode 100644 index c3fe47f..0000000 --- a/test/fixtures/import_installed_remote/.libraries.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "alibabacloud:OSS:*": "libraries/alibabacloud-OSS-0.0.1" -} \ No newline at end of file diff --git a/test/fixtures/import_installed_remote/Darafile b/test/fixtures/import_installed_remote/Darafile deleted file mode 100644 index bd3751c..0000000 --- a/test/fixtures/import_installed_remote/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "alibabacloud:OSS:*" - } -} \ No newline at end of file diff --git a/test/fixtures/import_installed_remote/libraries/alibabacloud-OSS-0.0.1/Darafile b/test/fixtures/import_installed_remote/libraries/alibabacloud-OSS-0.0.1/Darafile deleted file mode 100644 index 1279139..0000000 --- a/test/fixtures/import_installed_remote/libraries/alibabacloud-OSS-0.0.1/Darafile +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "OSS", - "main": "./oss.dara" -} \ No newline at end of file diff --git a/test/fixtures/import_installed_remote/libraries/alibabacloud-OSS-0.0.1/oss.dara b/test/fixtures/import_installed_remote/libraries/alibabacloud-OSS-0.0.1/oss.dara deleted file mode 100644 index f36a9f6..0000000 --- a/test/fixtures/import_installed_remote/libraries/alibabacloud-OSS-0.0.1/oss.dara +++ /dev/null @@ -1,5 +0,0 @@ -init(); - -function putObject(): void { - -} diff --git a/test/fixtures/import_installed_remote/main.dara b/test/fixtures/import_installed_remote/main.dara deleted file mode 100644 index d55a62f..0000000 --- a/test/fixtures/import_installed_remote/main.dara +++ /dev/null @@ -1 +0,0 @@ -import OSS diff --git a/test/fixtures/import_method_undefined/Darafile b/test/fixtures/import_method_undefined/Darafile deleted file mode 100644 index e50c7a6..0000000 --- a/test/fixtures/import_method_undefined/Darafile +++ /dev/null @@ -1,6 +0,0 @@ -{ - "libraries": { - "oss": "path/to/oss", - "OSS": "./oss.dara" - } -} \ No newline at end of file diff --git a/test/fixtures/import_method_undefined/main.dara b/test/fixtures/import_method_undefined/main.dara deleted file mode 100644 index 53a4b12..0000000 --- a/test/fixtures/import_method_undefined/main.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -function call(): void { - var oss = new OSS(); - oss.putObject(); -} \ No newline at end of file diff --git a/test/fixtures/import_method_undefined/oss.dara b/test/fixtures/import_method_undefined/oss.dara deleted file mode 100644 index 82dba14..0000000 --- a/test/fixtures/import_method_undefined/oss.dara +++ /dev/null @@ -1 +0,0 @@ -init(); diff --git a/test/fixtures/import_module_call/Darafile b/test/fixtures/import_module_call/Darafile deleted file mode 100644 index e50c7a6..0000000 --- a/test/fixtures/import_module_call/Darafile +++ /dev/null @@ -1,6 +0,0 @@ -{ - "libraries": { - "oss": "path/to/oss", - "OSS": "./oss.dara" - } -} \ No newline at end of file diff --git a/test/fixtures/import_module_call/call_static_method.dara b/test/fixtures/import_module_call/call_static_method.dara deleted file mode 100644 index 83c0611..0000000 --- a/test/fixtures/import_module_call/call_static_method.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -function call(): void { - var oss = new OSS(); - oss.staticPutObject(); -} \ No newline at end of file diff --git a/test/fixtures/import_module_call/main.dara b/test/fixtures/import_module_call/main.dara deleted file mode 100644 index 8f7defa..0000000 --- a/test/fixtures/import_module_call/main.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -function call(): void { - var oss = new OSS(); - oss.putObject("string"); -} \ No newline at end of file diff --git a/test/fixtures/import_module_call/mismatch_extern_model.dara b/test/fixtures/import_module_call/mismatch_extern_model.dara deleted file mode 100644 index 07e870d..0000000 --- a/test/fixtures/import_module_call/mismatch_extern_model.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -function call(): void { - var oss = new OSS(); - oss.put(); -} diff --git a/test/fixtures/import_module_call/mismatch_module_instance.dara b/test/fixtures/import_module_call/mismatch_module_instance.dara deleted file mode 100644 index 11f1078..0000000 --- a/test/fixtures/import_module_call/mismatch_module_instance.dara +++ /dev/null @@ -1,8 +0,0 @@ -import OSS - -function test(s: string): void { -} - -function call(): void { - test(new OSS()); -} diff --git a/test/fixtures/import_module_call/oss.dara b/test/fixtures/import_module_call/oss.dara deleted file mode 100644 index f96e90c..0000000 --- a/test/fixtures/import_module_call/oss.dara +++ /dev/null @@ -1,18 +0,0 @@ -init(); - -function putObject(a: boolean): void { - -} - -static function staticPutObject(): void { - -} - -model MyModel { - -} - -function put(m: MyModel): void { - -} - diff --git a/test/fixtures/import_module_call/parameter_matched.dara b/test/fixtures/import_module_call/parameter_matched.dara deleted file mode 100644 index bcc92ee..0000000 --- a/test/fixtures/import_module_call/parameter_matched.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -static function call(): void { - var oss = new OSS(); - oss.putObject(true); -} \ No newline at end of file diff --git a/test/fixtures/import_module_call/return_inexist_module_model.dara b/test/fixtures/import_module_call/return_inexist_module_model.dara deleted file mode 100644 index ed348c8..0000000 --- a/test/fixtures/import_module_call/return_inexist_module_model.dara +++ /dev/null @@ -1,4 +0,0 @@ -import OSS - -static function test(): OSS.InExistModel { -} diff --git a/test/fixtures/import_module_call/return_module.dara b/test/fixtures/import_module_call/return_module.dara deleted file mode 100644 index c8f7a9d..0000000 --- a/test/fixtures/import_module_call/return_module.dara +++ /dev/null @@ -1,5 +0,0 @@ -import OSS - -static function test(): OSS { - return new OSS(); -} diff --git a/test/fixtures/import_module_call/return_module_model.dara b/test/fixtures/import_module_call/return_module_model.dara deleted file mode 100644 index 850ac07..0000000 --- a/test/fixtures/import_module_call/return_module_model.dara +++ /dev/null @@ -1,5 +0,0 @@ -import OSS - -static function test(): OSS.MyModel { - return new OSS.MyModel; -} diff --git a/test/fixtures/import_module_call_static/Darafile b/test/fixtures/import_module_call_static/Darafile deleted file mode 100644 index 7f9e1af..0000000 --- a/test/fixtures/import_module_call_static/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "Assert": "./assert.dara" - } -} \ No newline at end of file diff --git a/test/fixtures/import_module_call_static/assert.dara b/test/fixtures/import_module_call_static/assert.dara deleted file mode 100644 index 75e24af..0000000 --- a/test/fixtures/import_module_call_static/assert.dara +++ /dev/null @@ -1,8 +0,0 @@ - -init(); - -function equal(expect: any, actual: any, message: string): void; - -static function staticEqual(expect: any, actual: any, message: string): void; - -static function arrayEqual(expect: [any], actual: [any], message: string): void; \ No newline at end of file diff --git a/test/fixtures/import_module_call_static/inexist.dara b/test/fixtures/import_module_call_static/inexist.dara deleted file mode 100644 index b810df4..0000000 --- a/test/fixtures/import_module_call_static/inexist.dara +++ /dev/null @@ -1,7 +0,0 @@ -import Assert; - -init(); - -function testEqual(): void { - Assert.inexist("A", "A", ""); -} diff --git a/test/fixtures/import_module_call_static/mismatch.dara b/test/fixtures/import_module_call_static/mismatch.dara deleted file mode 100644 index 074ec88..0000000 --- a/test/fixtures/import_module_call_static/mismatch.dara +++ /dev/null @@ -1,8 +0,0 @@ -import Assert; - -init(); - -function testEqual(): void { - // less parameters - Assert.staticEqual("A"); -} diff --git a/test/fixtures/import_module_call_static/non_static.dara b/test/fixtures/import_module_call_static/non_static.dara deleted file mode 100644 index 4cee333..0000000 --- a/test/fixtures/import_module_call_static/non_static.dara +++ /dev/null @@ -1,7 +0,0 @@ -import Assert; - -init(); - -function testEqual(): void { - Assert.equal("A", "A", ""); -} diff --git a/test/fixtures/import_module_call_static/ok.dara b/test/fixtures/import_module_call_static/ok.dara deleted file mode 100644 index 42d99dc..0000000 --- a/test/fixtures/import_module_call_static/ok.dara +++ /dev/null @@ -1,8 +0,0 @@ -import Assert; - -init(); - -function testEqual(): void { - Assert.staticEqual("A", "A", ""); - Assert.arrayEqual(["A"], ["A"], ""); -} diff --git a/test/fixtures/import_module_model/.libraries.json b/test/fixtures/import_module_model/.libraries.json deleted file mode 100644 index c3fe47f..0000000 --- a/test/fixtures/import_module_model/.libraries.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "alibabacloud:OSS:*": "libraries/alibabacloud-OSS-0.0.1" -} \ No newline at end of file diff --git a/test/fixtures/import_module_model/Darafile b/test/fixtures/import_module_model/Darafile deleted file mode 100644 index bd3751c..0000000 --- a/test/fixtures/import_module_model/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "alibabacloud:OSS:*" - } -} \ No newline at end of file diff --git a/test/fixtures/import_module_model/libraries/alibabacloud-OSS-0.0.1/Darafile b/test/fixtures/import_module_model/libraries/alibabacloud-OSS-0.0.1/Darafile deleted file mode 100644 index 1279139..0000000 --- a/test/fixtures/import_module_model/libraries/alibabacloud-OSS-0.0.1/Darafile +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "OSS", - "main": "./oss.dara" -} \ No newline at end of file diff --git a/test/fixtures/import_module_model/libraries/alibabacloud-OSS-0.0.1/oss.dara b/test/fixtures/import_module_model/libraries/alibabacloud-OSS-0.0.1/oss.dara deleted file mode 100644 index 6d2d83d..0000000 --- a/test/fixtures/import_module_model/libraries/alibabacloud-OSS-0.0.1/oss.dara +++ /dev/null @@ -1,7 +0,0 @@ -model Config { - accessKeyId: string -}; - -init(config: Config); - -function getAccessKeyId(): string; diff --git a/test/fixtures/import_module_model/model_invalid_type.dara b/test/fixtures/import_module_model/model_invalid_type.dara deleted file mode 100644 index a228a03..0000000 --- a/test/fixtures/import_module_model/model_invalid_type.dara +++ /dev/null @@ -1,7 +0,0 @@ -import OSS - -static function main() : void { - var config = new OSS.Config{ - accessKeyId = 100 - }; -} diff --git a/test/fixtures/import_module_model/model_no_field.dara b/test/fixtures/import_module_model/model_no_field.dara deleted file mode 100644 index e8671c2..0000000 --- a/test/fixtures/import_module_model/model_no_field.dara +++ /dev/null @@ -1,7 +0,0 @@ -import OSS - -static function main() : void { - var config = new OSS.Config{ - accessKeySecret = 'fake ak secret' - }; -} diff --git a/test/fixtures/import_module_model/module_as_model_field.dara b/test/fixtures/import_module_model/module_as_model_field.dara deleted file mode 100644 index 2a1c116..0000000 --- a/test/fixtures/import_module_model/module_as_model_field.dara +++ /dev/null @@ -1,5 +0,0 @@ -import OSS - -model M { - oss: OSS -} diff --git a/test/fixtures/import_module_model/module_call.dara b/test/fixtures/import_module_model/module_call.dara deleted file mode 100644 index acb8240..0000000 --- a/test/fixtures/import_module_model/module_call.dara +++ /dev/null @@ -1,7 +0,0 @@ -import OSS - -function main() : void { - var config = new OSS.Config{}; - var client = new OSS(config); - return client.getAccessKeyId(); -} diff --git a/test/fixtures/import_module_model/module_call_ok.dara b/test/fixtures/import_module_model/module_call_ok.dara deleted file mode 100644 index 7fb2610..0000000 --- a/test/fixtures/import_module_model/module_call_ok.dara +++ /dev/null @@ -1,7 +0,0 @@ -import OSS - -static function main() : void { - var config = new OSS.Config{ - accessKeyId = 'fake ak id' - }; -} diff --git a/test/fixtures/import_module_model/module_model_as_model_field.dara b/test/fixtures/import_module_model/module_model_as_model_field.dara deleted file mode 100644 index 6b3aeab..0000000 --- a/test/fixtures/import_module_model/module_model_as_model_field.dara +++ /dev/null @@ -1,5 +0,0 @@ -import OSS - -model M { - config: OSS.Config -} diff --git a/test/fixtures/import_module_model/undefined_aliasid.dara b/test/fixtures/import_module_model/undefined_aliasid.dara deleted file mode 100644 index e2b1a9c..0000000 --- a/test/fixtures/import_module_model/undefined_aliasid.dara +++ /dev/null @@ -1,5 +0,0 @@ -import OSS - -function main() : void { - var config = new OSSX.Config{}; -} diff --git a/test/fixtures/import_module_model/undefined_model.dara b/test/fixtures/import_module_model/undefined_model.dara deleted file mode 100644 index 618988b..0000000 --- a/test/fixtures/import_module_model/undefined_model.dara +++ /dev/null @@ -1,5 +0,0 @@ -import OSS - -function main() : void { - var config = new OSS.ConfigX{}; -} diff --git a/test/fixtures/import_module_model/undefined_sub_model.dara b/test/fixtures/import_module_model/undefined_sub_model.dara deleted file mode 100644 index 90d6b57..0000000 --- a/test/fixtures/import_module_model/undefined_sub_model.dara +++ /dev/null @@ -1,12 +0,0 @@ - -model M { - N: { - O: { - - } - } -} - -static function main() : void { - new M.N.X.Y; -} diff --git a/test/fixtures/import_no_package_json/main.dara b/test/fixtures/import_no_package_json/main.dara deleted file mode 100644 index 394507c..0000000 --- a/test/fixtures/import_no_package_json/main.dara +++ /dev/null @@ -1 +0,0 @@ -import oss diff --git a/test/fixtures/import_not_installed_remote/.libraries.json b/test/fixtures/import_not_installed_remote/.libraries.json deleted file mode 100644 index 7a73a41..0000000 --- a/test/fixtures/import_not_installed_remote/.libraries.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/test/fixtures/import_not_installed_remote/Darafile b/test/fixtures/import_not_installed_remote/Darafile deleted file mode 100644 index bd3751c..0000000 --- a/test/fixtures/import_not_installed_remote/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "alibabacloud:OSS:*" - } -} \ No newline at end of file diff --git a/test/fixtures/import_not_installed_remote/main.dara b/test/fixtures/import_not_installed_remote/main.dara deleted file mode 100644 index d55a62f..0000000 --- a/test/fixtures/import_not_installed_remote/main.dara +++ /dev/null @@ -1 +0,0 @@ -import OSS diff --git a/test/fixtures/import_ok/Darafile b/test/fixtures/import_ok/Darafile deleted file mode 100644 index e50c7a6..0000000 --- a/test/fixtures/import_ok/Darafile +++ /dev/null @@ -1,6 +0,0 @@ -{ - "libraries": { - "oss": "path/to/oss", - "OSS": "./oss.dara" - } -} \ No newline at end of file diff --git a/test/fixtures/import_ok/main.dara b/test/fixtures/import_ok/main.dara deleted file mode 100644 index a5069fe..0000000 --- a/test/fixtures/import_ok/main.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -static function call(): void { - var oss = new OSS(); - oss.putObject(); -} \ No newline at end of file diff --git a/test/fixtures/import_ok/oss.dara b/test/fixtures/import_ok/oss.dara deleted file mode 100644 index 1d2ebca..0000000 --- a/test/fixtures/import_ok/oss.dara +++ /dev/null @@ -1,6 +0,0 @@ - -init(); - -function putObject(): void { - -} diff --git a/test/fixtures/import_ok/variable_undefined.dara b/test/fixtures/import_ok/variable_undefined.dara deleted file mode 100644 index 68b2ae5..0000000 --- a/test/fixtures/import_ok/variable_undefined.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -function call(): void { - var oss = new OSS(); - oss.putObject(test); -} diff --git a/test/fixtures/import_remote/Darafile b/test/fixtures/import_remote/Darafile deleted file mode 100644 index bd3751c..0000000 --- a/test/fixtures/import_remote/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "alibabacloud:OSS:*" - } -} \ No newline at end of file diff --git a/test/fixtures/import_remote/main.dara b/test/fixtures/import_remote/main.dara deleted file mode 100644 index d55a62f..0000000 --- a/test/fixtures/import_remote/main.dara +++ /dev/null @@ -1 +0,0 @@ -import OSS diff --git a/test/fixtures/import_undefined/Darafile b/test/fixtures/import_undefined/Darafile deleted file mode 100644 index 9ead5ca..0000000 --- a/test/fixtures/import_undefined/Darafile +++ /dev/null @@ -1,4 +0,0 @@ -{ - "libraries": { - } -} \ No newline at end of file diff --git a/test/fixtures/import_undefined/main.dara b/test/fixtures/import_undefined/main.dara deleted file mode 100644 index 394507c..0000000 --- a/test/fixtures/import_undefined/main.dara +++ /dev/null @@ -1 +0,0 @@ -import oss diff --git a/test/fixtures/import_without_init/Darafile b/test/fixtures/import_without_init/Darafile deleted file mode 100644 index e50c7a6..0000000 --- a/test/fixtures/import_without_init/Darafile +++ /dev/null @@ -1,6 +0,0 @@ -{ - "libraries": { - "oss": "path/to/oss", - "OSS": "./oss.dara" - } -} \ No newline at end of file diff --git a/test/fixtures/import_without_init/main.dara b/test/fixtures/import_without_init/main.dara deleted file mode 100644 index 53a4b12..0000000 --- a/test/fixtures/import_without_init/main.dara +++ /dev/null @@ -1,6 +0,0 @@ -import OSS - -function call(): void { - var oss = new OSS(); - oss.putObject(); -} \ No newline at end of file diff --git a/test/fixtures/import_without_init/oss.dara b/test/fixtures/import_without_init/oss.dara deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/module_assign/Darafile b/test/fixtures/module_assign/Darafile deleted file mode 100644 index 7e4f9a5..0000000 --- a/test/fixtures/module_assign/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "./oss.dara" - } -} diff --git a/test/fixtures/module_assign/main.dara b/test/fixtures/module_assign/main.dara deleted file mode 100644 index 4e6d190..0000000 --- a/test/fixtures/module_assign/main.dara +++ /dev/null @@ -1,28 +0,0 @@ -import OSS - -model CallResponse{ - file: OSS.File -} - -static function call(): CallResponse { - var fileInfo = new OSS.FileInfo{ - mime = 'plain/txt' - }; - var author = new OSS.File.meta.author{ - name = 'test', - age = 27 - }; - var meta = new OSS.File.meta{ - size = 1000, - author = [author] - }; - var file = new OSS.File{ - filename = 'test', - info = fileInfo, - meta = meta - }; - - return new CallResponse{ - file = file - }; -} diff --git a/test/fixtures/module_assign/oss.dara b/test/fixtures/module_assign/oss.dara deleted file mode 100644 index 370bf9f..0000000 --- a/test/fixtures/module_assign/oss.dara +++ /dev/null @@ -1,25 +0,0 @@ - -type @p = string; - -init(); - -function putObject(): void { - -} - -model FileInfo = { - mime: string, -}; - -model File = { - filename: string, - info: FileInfo, - meta: { - size: number, - author: [{ - name: string, - age: number - }] - }, - -}; diff --git a/test/fixtures/module_instance/Darafile b/test/fixtures/module_instance/Darafile deleted file mode 100644 index 7e4f9a5..0000000 --- a/test/fixtures/module_instance/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "./oss.dara" - } -} diff --git a/test/fixtures/module_instance/main.dara b/test/fixtures/module_instance/main.dara deleted file mode 100644 index 615202d..0000000 --- a/test/fixtures/module_instance/main.dara +++ /dev/null @@ -1,5 +0,0 @@ -import OSS - -static function call(): OSS { - return new OSS(); -} diff --git a/test/fixtures/module_instance/oss.dara b/test/fixtures/module_instance/oss.dara deleted file mode 100644 index 41dbd80..0000000 --- a/test/fixtures/module_instance/oss.dara +++ /dev/null @@ -1,10 +0,0 @@ - -type @p = string; - -init(); - -function putObject(): void { - -} - -model N = {}; diff --git a/test/fixtures/module_model_as_map_type/.libraries.json b/test/fixtures/module_model_as_map_type/.libraries.json deleted file mode 100644 index c3fe47f..0000000 --- a/test/fixtures/module_model_as_map_type/.libraries.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "alibabacloud:OSS:*": "libraries/alibabacloud-OSS-0.0.1" -} \ No newline at end of file diff --git a/test/fixtures/module_model_as_map_type/Darafile b/test/fixtures/module_model_as_map_type/Darafile deleted file mode 100644 index bd3751c..0000000 --- a/test/fixtures/module_model_as_map_type/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "alibabacloud:OSS:*" - } -} \ No newline at end of file diff --git a/test/fixtures/module_model_as_map_type/libraries/alibabacloud-OSS-0.0.1/Darafile b/test/fixtures/module_model_as_map_type/libraries/alibabacloud-OSS-0.0.1/Darafile deleted file mode 100644 index 1279139..0000000 --- a/test/fixtures/module_model_as_map_type/libraries/alibabacloud-OSS-0.0.1/Darafile +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "OSS", - "main": "./oss.dara" -} \ No newline at end of file diff --git a/test/fixtures/module_model_as_map_type/libraries/alibabacloud-OSS-0.0.1/oss.dara b/test/fixtures/module_model_as_map_type/libraries/alibabacloud-OSS-0.0.1/oss.dara deleted file mode 100644 index 6d2d83d..0000000 --- a/test/fixtures/module_model_as_map_type/libraries/alibabacloud-OSS-0.0.1/oss.dara +++ /dev/null @@ -1,7 +0,0 @@ -model Config { - accessKeyId: string -}; - -init(config: Config); - -function getAccessKeyId(): string; diff --git a/test/fixtures/module_model_as_map_type/main.dara b/test/fixtures/module_model_as_map_type/main.dara deleted file mode 100644 index 78f7510..0000000 --- a/test/fixtures/module_model_as_map_type/main.dara +++ /dev/null @@ -1,9 +0,0 @@ -import OSS - -model M { - config: map[string]OSS.Config -} - -init(){ - var config: map[string]OSS.Config = {}; -} \ No newline at end of file diff --git a/test/fixtures/module_model_conflict/.libraries.json b/test/fixtures/module_model_conflict/.libraries.json deleted file mode 100644 index 040ee3c..0000000 --- a/test/fixtures/module_model_conflict/.libraries.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "alibabacloud:OSS:*": "libraries/alibabacloud-OSS-0.0.1", - "alibabacloud:Source:*": "libraries/alibabacloud-Source-0.0.1" -} \ No newline at end of file diff --git a/test/fixtures/module_model_conflict/Darafile b/test/fixtures/module_model_conflict/Darafile deleted file mode 100644 index 6004d16..0000000 --- a/test/fixtures/module_model_conflict/Darafile +++ /dev/null @@ -1,6 +0,0 @@ -{ - "libraries": { - "OSS": "alibabacloud:OSS:*", - "Source": "alibabacloud:Source:*" - } -} \ No newline at end of file diff --git a/test/fixtures/module_model_conflict/libraries/alibabacloud-OSS-0.0.1/Darafile b/test/fixtures/module_model_conflict/libraries/alibabacloud-OSS-0.0.1/Darafile deleted file mode 100644 index 1279139..0000000 --- a/test/fixtures/module_model_conflict/libraries/alibabacloud-OSS-0.0.1/Darafile +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "OSS", - "main": "./oss.dara" -} \ No newline at end of file diff --git a/test/fixtures/module_model_conflict/libraries/alibabacloud-OSS-0.0.1/oss.dara b/test/fixtures/module_model_conflict/libraries/alibabacloud-OSS-0.0.1/oss.dara deleted file mode 100644 index 6d2d83d..0000000 --- a/test/fixtures/module_model_conflict/libraries/alibabacloud-OSS-0.0.1/oss.dara +++ /dev/null @@ -1,7 +0,0 @@ -model Config { - accessKeyId: string -}; - -init(config: Config); - -function getAccessKeyId(): string; diff --git a/test/fixtures/module_model_conflict/libraries/alibabacloud-Source-0.0.1/Darafile b/test/fixtures/module_model_conflict/libraries/alibabacloud-Source-0.0.1/Darafile deleted file mode 100644 index c5bab63..0000000 --- a/test/fixtures/module_model_conflict/libraries/alibabacloud-Source-0.0.1/Darafile +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Source", - "main": "./source.dara" -} \ No newline at end of file diff --git a/test/fixtures/module_model_conflict/libraries/alibabacloud-Source-0.0.1/source.dara b/test/fixtures/module_model_conflict/libraries/alibabacloud-Source-0.0.1/source.dara deleted file mode 100644 index 49caafd..0000000 --- a/test/fixtures/module_model_conflict/libraries/alibabacloud-Source-0.0.1/source.dara +++ /dev/null @@ -1,5 +0,0 @@ -model Config { - SourceId: string -}; - -init(config: Config); diff --git a/test/fixtures/module_model_conflict/module_model_conflict_other.dara b/test/fixtures/module_model_conflict/module_model_conflict_other.dara deleted file mode 100644 index f8bc06e..0000000 --- a/test/fixtures/module_model_conflict/module_model_conflict_other.dara +++ /dev/null @@ -1,4 +0,0 @@ -import OSS -import Source - -init(config1: Source.Config, config2: OSS.Config) { } diff --git a/test/fixtures/module_model_conflict/module_model_in_function.dara b/test/fixtures/module_model_conflict/module_model_in_function.dara deleted file mode 100644 index ae527e5..0000000 --- a/test/fixtures/module_model_conflict/module_model_in_function.dara +++ /dev/null @@ -1,14 +0,0 @@ -import OSS - -model Config { - name: string -}; - -static function main() : void { - var moduleConfig = new OSS.Config{}; - var client = new OSS(moduleConfig); - var config = new Config{ - name = 'test' - }; - return; -} diff --git a/test/fixtures/module_model_conflict/module_model_in_model.dara b/test/fixtures/module_model_conflict/module_model_in_model.dara deleted file mode 100644 index 667cb3f..0000000 --- a/test/fixtures/module_model_conflict/module_model_in_model.dara +++ /dev/null @@ -1,14 +0,0 @@ -import OSS - -model Config { - subConfig: OSS.Config -}; - -static function main() : void { - var moduleConfig = new OSS.Config{}; - var config = new Config{ - subConfig = moduleConfig - }; - var client = new OSS(config.subConfig); - return; -} diff --git a/test/fixtures/module_model_conflict/module_model_in_params.dara b/test/fixtures/module_model_conflict/module_model_in_params.dara deleted file mode 100644 index bdc51a7..0000000 --- a/test/fixtures/module_model_conflict/module_model_in_params.dara +++ /dev/null @@ -1,7 +0,0 @@ -import OSS - -model Config { - name: string -}; - -init(config: Config, moduleConfig: OSS.Config) { } diff --git a/test/fixtures/module_model_conflict/module_model_unuse.dara b/test/fixtures/module_model_conflict/module_model_unuse.dara deleted file mode 100644 index e000f1c..0000000 --- a/test/fixtures/module_model_conflict/module_model_unuse.dara +++ /dev/null @@ -1,7 +0,0 @@ -import OSS - -model Config { - name: string -}; - -init(config: Config) { } diff --git a/test/fixtures/Darafile b/test/fixtures/non_package/.folder_hold similarity index 100% rename from test/fixtures/Darafile rename to test/fixtures/non_package/.folder_hold diff --git a/test/fixtures/package_with_dmain/Darafile b/test/fixtures/package_with_dmain/Darafile new file mode 100644 index 0000000..a2159a7 --- /dev/null +++ b/test/fixtures/package_with_dmain/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} diff --git a/test/fixtures/package_with_dmain/test2.dara b/test/fixtures/package_with_dmain/test2.dara new file mode 100644 index 0000000..b8ec981 --- /dev/null +++ b/test/fixtures/package_with_dmain/test2.dara @@ -0,0 +1,4 @@ +main { + main(args: [string]) { + } +} diff --git a/test/fixtures/package_with_duplicate_model/Darafile b/test/fixtures/package_with_duplicate_model/Darafile new file mode 100644 index 0000000..a2159a7 --- /dev/null +++ b/test/fixtures/package_with_duplicate_model/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} diff --git a/test/fixtures/package_with_duplicate_model/test.dara b/test/fixtures/package_with_duplicate_model/test.dara new file mode 100644 index 0000000..526ea11 --- /dev/null +++ b/test/fixtures/package_with_duplicate_model/test.dara @@ -0,0 +1,3 @@ +model M { + +} diff --git a/test/fixtures/package_with_duplicate_model/test2.dara b/test/fixtures/package_with_duplicate_model/test2.dara new file mode 100644 index 0000000..526ea11 --- /dev/null +++ b/test/fixtures/package_with_duplicate_model/test2.dara @@ -0,0 +1,3 @@ +model M { + +} diff --git a/test/fixtures/package_with_duplicate_module/Darafile b/test/fixtures/package_with_duplicate_module/Darafile new file mode 100644 index 0000000..a2159a7 --- /dev/null +++ b/test/fixtures/package_with_duplicate_module/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} diff --git a/test/fixtures/package_with_duplicate_module/test.dara b/test/fixtures/package_with_duplicate_module/test.dara new file mode 100644 index 0000000..c436f18 --- /dev/null +++ b/test/fixtures/package_with_duplicate_module/test.dara @@ -0,0 +1,3 @@ +module Module { + +} diff --git a/test/fixtures/package_with_duplicate_module/test2.dara b/test/fixtures/package_with_duplicate_module/test2.dara new file mode 100644 index 0000000..c436f18 --- /dev/null +++ b/test/fixtures/package_with_duplicate_module/test2.dara @@ -0,0 +1,3 @@ +module Module { + +} diff --git a/test/fixtures/package_with_interface/Darafile b/test/fixtures/package_with_interface/Darafile new file mode 100644 index 0000000..a2159a7 --- /dev/null +++ b/test/fixtures/package_with_interface/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} diff --git a/test/fixtures/package_with_interface/test.dara b/test/fixtures/package_with_interface/test.dara new file mode 100644 index 0000000..150d60e --- /dev/null +++ b/test/fixtures/package_with_interface/test.dara @@ -0,0 +1,3 @@ +interface I { + +} \ No newline at end of file diff --git a/test/fixtures/package_with_invalid_darafile/Darafile b/test/fixtures/package_with_invalid_darafile/Darafile new file mode 100644 index 0000000..d5b5092 --- /dev/null +++ b/test/fixtures/package_with_invalid_darafile/Darafile @@ -0,0 +1,4 @@ +{ + "darabonba": "2.0", + 'invalid darabonba file' +} diff --git a/test/fixtures/package_with_libraries/Darafile b/test/fixtures/package_with_libraries/Darafile new file mode 100644 index 0000000..287cb38 --- /dev/null +++ b/test/fixtures/package_with_libraries/Darafile @@ -0,0 +1,6 @@ +{ + "darabonba": "2.0", + "libraries": { + "std": "scope:name:1.0" + } +} diff --git a/test/fixtures/package_with_libraries_installed/.libraries.json b/test/fixtures/package_with_libraries_installed/.libraries.json new file mode 100644 index 0000000..e36ffe0 --- /dev/null +++ b/test/fixtures/package_with_libraries_installed/.libraries.json @@ -0,0 +1,3 @@ +{ + "scope:name:1.0": "libraries/std" +} diff --git a/test/fixtures/package_with_libraries_installed/Darafile b/test/fixtures/package_with_libraries_installed/Darafile new file mode 100644 index 0000000..287cb38 --- /dev/null +++ b/test/fixtures/package_with_libraries_installed/Darafile @@ -0,0 +1,6 @@ +{ + "darabonba": "2.0", + "libraries": { + "std": "scope:name:1.0" + } +} diff --git a/test/fixtures/package_with_libraries_installed/libraries/std/Darafile b/test/fixtures/package_with_libraries_installed/libraries/std/Darafile new file mode 100644 index 0000000..de08f65 --- /dev/null +++ b/test/fixtures/package_with_libraries_installed/libraries/std/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} \ No newline at end of file diff --git a/test/fixtures/package_with_libraries_local/Darafile b/test/fixtures/package_with_libraries_local/Darafile new file mode 100644 index 0000000..dd4da72 --- /dev/null +++ b/test/fixtures/package_with_libraries_local/Darafile @@ -0,0 +1,6 @@ +{ + "darabonba": "2.0", + "libraries": { + "std": "./libraries/std" + } +} diff --git a/test/fixtures/package_with_libraries_local/libraries/std/Darafile b/test/fixtures/package_with_libraries_local/libraries/std/Darafile new file mode 100644 index 0000000..de08f65 --- /dev/null +++ b/test/fixtures/package_with_libraries_local/libraries/std/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} \ No newline at end of file diff --git a/test/fixtures/package_with_model/Darafile b/test/fixtures/package_with_model/Darafile new file mode 100644 index 0000000..a2159a7 --- /dev/null +++ b/test/fixtures/package_with_model/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} diff --git a/test/fixtures/package_with_model/test.dara b/test/fixtures/package_with_model/test.dara new file mode 100644 index 0000000..526ea11 --- /dev/null +++ b/test/fixtures/package_with_model/test.dara @@ -0,0 +1,3 @@ +model M { + +} diff --git a/test/fixtures/package_with_module/Darafile b/test/fixtures/package_with_module/Darafile new file mode 100644 index 0000000..a2159a7 --- /dev/null +++ b/test/fixtures/package_with_module/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} diff --git a/test/fixtures/package_with_module/test.dara b/test/fixtures/package_with_module/test.dara new file mode 100644 index 0000000..c436f18 --- /dev/null +++ b/test/fixtures/package_with_module/test.dara @@ -0,0 +1,3 @@ +module Module { + +} diff --git a/test/fixtures/package_with_multi_dmain/Darafile b/test/fixtures/package_with_multi_dmain/Darafile new file mode 100644 index 0000000..a2159a7 --- /dev/null +++ b/test/fixtures/package_with_multi_dmain/Darafile @@ -0,0 +1,3 @@ +{ + "darabonba": "2.0" +} diff --git a/test/fixtures/package_with_multi_dmain/test.dara b/test/fixtures/package_with_multi_dmain/test.dara new file mode 100644 index 0000000..ac394fe --- /dev/null +++ b/test/fixtures/package_with_multi_dmain/test.dara @@ -0,0 +1,3 @@ +main { + +} \ No newline at end of file diff --git a/test/fixtures/package_with_multi_dmain/test2.dara b/test/fixtures/package_with_multi_dmain/test2.dara new file mode 100644 index 0000000..ac394fe --- /dev/null +++ b/test/fixtures/package_with_multi_dmain/test2.dara @@ -0,0 +1,3 @@ +main { + +} \ No newline at end of file diff --git a/test/fixtures/variables/Darafile b/test/fixtures/variables/Darafile deleted file mode 100644 index 7e4f9a5..0000000 --- a/test/fixtures/variables/Darafile +++ /dev/null @@ -1,5 +0,0 @@ -{ - "libraries": { - "OSS": "./oss.dara" - } -} diff --git a/test/fixtures/variables/main.dara b/test/fixtures/variables/main.dara deleted file mode 100644 index 55f69d1..0000000 --- a/test/fixtures/variables/main.dara +++ /dev/null @@ -1,12 +0,0 @@ -import OSS - -model M = { - N: {} -}; - -static function callWrap(): void { - var variable = {}; - variable.key; - M.N; - OSS.M; -} diff --git a/test/fixtures/variables/oss.dara b/test/fixtures/variables/oss.dara deleted file mode 100644 index 41dbd80..0000000 --- a/test/fixtures/variables/oss.dara +++ /dev/null @@ -1,10 +0,0 @@ - -type @p = string; - -init(); - -function putObject(): void { - -} - -model N = {}; diff --git a/test/import.test.js b/test/import.test.js deleted file mode 100644 index 2165f9b..0000000 --- a/test/import.test.js +++ /dev/null @@ -1,372 +0,0 @@ -'use strict'; - -const path = require('path'); -const fs = require('fs'); - -const expect = require('expect.js'); - -const { parse } = require('..'); - -function pos(line, column) { - return { line, column }; -} - -function loc(startLine, startColumn, endLine, endColumn) { - return { - start: pos(startLine, startColumn), - end: pos(endLine, endColumn) - }; -} - -function readAndParse(specPath) { - const filePath = path.join(__dirname, specPath); - return parse(fs.readFileSync(filePath, 'utf-8'), filePath); -} - -describe('import', function () { - - it('no package.json should not ok', function () { - expect(function () { - readAndParse('fixtures/import_no_package_json/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the Darafile not exists`); - }); - }); - - it('import module duplicate should not ok', function () { - expect(function () { - readAndParse('fixtures/import_duplicate/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the module id "oss" has been imported`); - }); - }); - - it('import undefined module should not ok', function () { - expect(function () { - readAndParse('fixtures/import_undefined/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the import "oss" not defined in Darafile`); - }); - }); - - it('module instance should ok', function () { - expect(function () { - parse(` -function callOSS(): string { - var client = new ossx(); - client.putObject(); - return "OK"; -}`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the module "ossx" is not imported`); - }); - }); - - it('module call should be ok', function () { - expect(function () { - readAndParse('fixtures/import_ok/main.dara'); - }).to.not.throwException(); - - expect(function () { - readAndParse('fixtures/import_ok/variable_undefined.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`variable "test" undefined`); - }); - }); - - it('no init should not ok', function () { - expect(function () { - readAndParse('fixtures/import_without_init/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the module "OSS" don't has init`); - }); - }); - - it('parameter mismatch(for init call) should not ok', function () { - expect(function () { - readAndParse('fixtures/import_init_params/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected new OSS(string), but new OSS(boolean)`); - }); - }); - - it('undefined api/function should not ok', function () { - expect(function () { - readAndParse('fixtures/import_method_undefined/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the instance function/api "putObject" is undefined in OSS`); - }); - }); - - it('parameter mismatch(for static call) should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected OSS.putObject(boolean), but OSS.putObject(string)`); - }); - }); - - it('parameter matched should ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call/parameter_matched.dara'); - }).to.not.throwException(); - }); - - it('mismatch(extern model) should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call/mismatch_extern_model.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected OSS.put(OSS#MyModel), but OSS.put()`); - }); - }); - - it('call static via instance call should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call/call_static_method.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the instance function/api "staticPutObject" is undefined in OSS`); - }); - }); - - it('mismatch(module instance) should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call/mismatch_module_instance.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected test(string), but test(OSS)`); - }); - }); - - it('return module should ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call/return_module.dara'); - }).to.not.throwException(); - }); - - it('return module model should ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call/return_module_model.dara'); - }).to.not.throwException(); - }); - - it('return inexist module model should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call/return_inexist_module_model.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the model InExistModel is inexist in OSS`); - }); - }); - - it('use inexist static method should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call_static/inexist.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the static function "inexist" is undefined in Assert`); - }); - }); - - it('use static method with non-static function should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call_static/non_static.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the "equal" is not static function`); - }); - }); - - it('mismatch types in static call should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call_static/mismatch.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected Assert.staticEqual(any, any, string), but Assert.staticEqual(string)`); - }); - }); - - it('static call should ok', function () { - expect(function () { - readAndParse('fixtures/import_module_call_static/ok.dara'); - }).to.not.throwException(); - }); - - it('use not installed remote module should not ok', function () { - expect(function () { - readAndParse('fixtures/import_remote/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the module id "OSS" has not installed, use \`dara install\` first`); - }); - }); - - it('use not installed remote module(with lock file) should ok', function () { - expect(function () { - readAndParse('fixtures/import_not_installed_remote/main.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the module id "OSS" has not installed, use \`dara install\` first`); - }); - }); - - it('use installed remote module should ok', function () { - expect(function () { - readAndParse('fixtures/import_installed_remote/main.dara'); - }).to.not.throwException(); - }); - - it('use module by old way should ok', function () { - expect(function () { - readAndParse('fixtures/import_by_tea/main.tea'); - }).to.not.throwException(); - }); - - it('use undefined aliasId in construct module model should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_model/undefined_aliasid.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`expected "OSSX" is module or model`); - }); - }); - - it('use undefined model in construct module model should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_model/undefined_model.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the model "ConfigX" is undefined in module "OSS"`); - }); - - expect(function () { - readAndParse('fixtures/import_module_model/undefined_sub_model.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the model "M.N.X" is undefined`); - }); - }); - - it('use undefined model in construct module model should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_model/module_call.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the return type is not expected, expect: void, actual: string`); - }); - }); - - it('use module as model field should ok', function () { - const ast = readAndParse('fixtures/import_module_model/module_as_model_field.dara'); - const [ field ] = ast.models.M.modelBody.nodes; - expect(field.fieldName.lexeme).to.be('oss'); - expect(field.fieldValue.fieldType.idType).to.be('module'); - }); - - it('use module model as model field should ok', function () { - const ast = readAndParse('fixtures/import_module_model/module_model_as_model_field.dara'); - const [ field ] = ast.models.M.modelBody.nodes; - expect(field.fieldName.lexeme).to.be('config'); - expect(field.fieldValue.fieldType.type).to.be('moduleModel'); - const [moduleId] = field.fieldValue.fieldType.path; - expect(moduleId.idType).to.be('module'); - }); - - it('construct module model should ok', function () { - expect(function () { - readAndParse('fixtures/import_module_model/module_call_ok.dara'); - }).to.not.throwException(); - }); - - it('set undefined field in construct module model should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_model/model_no_field.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the property "accessKeySecret" is undefined in model "OSS.Config"`); - }); - }); - - it('set mismatched type to field in construct module model should not ok', function () { - expect(function () { - readAndParse('fixtures/import_module_model/model_invalid_type.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the field type are mismatched. expected string, but integer`); - }); - }); - - it('extends unimported module should not ok', function () { - expect(function () { - readAndParse('fixtures/extends/extends_unimported.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the extends "OSS" wasn't imported`); - }); - }); - - it('extends imported module should ok', function () { - expect(function () { - readAndParse('fixtures/extends/main.dara'); - }).to.not.throwException(); - }); - - it('super call should ok', function () { - expect(function () { - readAndParse('fixtures/extends/super.dara'); - }).to.not.throwException(); - - expect(function () { - readAndParse('fixtures/extends/super_types_mismatched.dara'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected OSS(string), but OSS(integer)`); - }); - }); - - it('id type(module, model, variable) should ok', function () { - const ast = readAndParse('fixtures/variables/main.dara'); - const [, f1] = ast.moduleBody.nodes; - const [, p1, p2, p3] = f1.functionBody.stmts.stmts; - expect(p1.id.type).to.be('variable'); - expect(p2.id.type).to.be('model'); - expect(p3.id.type).to.be('module'); - }); - - it('module instance should ok', function () { - const ast = readAndParse('fixtures/module_instance/main.dara'); - const [f1] = ast.moduleBody.nodes; - const [s1] = f1.functionBody.stmts.stmts; - expect(s1).to.be.eql({ - 'expr': { - 'aliasId': { - 'lexeme': 'OSS', - 'index': 13, - 'loc': loc(4, 14, 4, 17), - 'tag': 2 - }, - 'args': [], - 'inferred': { - 'name': 'OSS', - 'type': 'module_instance' - }, - 'tokenRange': [12, 16], - 'type': 'construct' - }, - 'loc': loc(4, 3, 5, 1), - 'needCast': false, - 'tokenRange': [11, 16], - 'type': 'return' - }); - }); -}); diff --git a/test/interface_analyser.test.js b/test/interface_analyser.test.js new file mode 100644 index 0000000..fc012ab --- /dev/null +++ b/test/interface_analyser.test.js @@ -0,0 +1,66 @@ +'use strict'; +const assert = require('assert'); + +const Analyser = require('../lib/interface_analyser'); +const Parser = require('../lib/parser'); +const Lexer = require('../lib/lexer'); +const Package = require('../lib/package'); + +function parse(source, pkg = new Package()) { + const lexer = new Lexer(source, '__filename'); + const parser = new Parser(lexer); + const ast = parser.program(); + const anlyser = new Analyser({ source, filename: '__filename' }, pkg); + anlyser.check(ast); + anlyser.checkMethods(ast); + return ast; +} + +describe('interface analyser', function () { + describe('import', function () { + it('import undefined package should not ok', function () { + assert.throws(() => { + parse(`import $std; interface I {}`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' not defined in Darafile`); + return true; + }); + }); + + it('re-import package should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + pkg.libraries.set('$std', new Package()); + parse(`import $std; import $std; interface I {}`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package id '$std' has been imported`); + return true; + }); + }); + }); + + describe('define methods', function () { + it('redefine function should not ok', function () { + assert.throws(() => { + parse(` + interface I { + function getId(): string; + function getId(): string; + }`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `redefined function 'getId'`); + return true; + }); + }); + + it('define function should ok', function () { + parse(` + interface I { + function getId(): string; + }`); + }); + }); +}); diff --git a/test/interface_parser.test.js b/test/interface_parser.test.js new file mode 100644 index 0000000..ab0ae78 --- /dev/null +++ b/test/interface_parser.test.js @@ -0,0 +1,223 @@ +'use strict'; + +const assert = require('assert'); + +const Parser = require('../lib/parser'); +const Lexer = require('../lib/lexer'); +const { WordToken, Annotation } = require('../lib/tokens'); + +function parse(source, filePath) { + const lexer = new Lexer(source, filePath); + const parser = new Parser(lexer); + return parser.program(); +} + +function pos(line, column) { + return { line, column }; +} + +function loc(startLine, startColumn, endLine, endColumn) { + return { + start: pos(startLine, startColumn), + end: pos(endLine, endColumn) + }; +} + +describe('interface parser', function () { + it('empty interface file should not ok', function () { + assert.throws(() => { + parse('', '__filename'); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: EOF. expect 'module', 'model', 'interface' or 'main'`); + return true; + }); + }); + + it('interface should ok', function () { + assert.deepStrictEqual(parse('interface I {}', '__filename'), { + annotation: undefined, + comments: new Map(), + imports: [], + interfaceBody: { + nodes: [], + type: 'interfaceBody', + tokenRange: [2, 3] + }, + name: new WordToken(2, 'I', loc(1, 11, 1, 12), 1), + tokenRange: [0, 4], + type: 'interface' + }); + }); + + it('interface annotation should be ok', function () { + var ast = parse(` + /** + * module annotation + */ + interface I {} + `, '__filename'); + + assert.deepStrictEqual(ast.annotation, new Annotation('/**\n * module annotation\n */', loc(2, 5, 4, 8), 0)); + }); + + it('import with comma should ok', function () { + const ast = parse(`import $oss; + interface M {}`, '__filename'); + assert.deepStrictEqual(ast.imports, [ + { + type: 'import', + aliasId: new WordToken(21, '$oss', loc(1, 8, 1, 12), 1), + tokenRange: [0, 3] + } + ]); + }); + + it('only function should be ok', function () { + assert.throws(function () { + parse(` + interface M { + public + } + `, '__filename'); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'Unexpected token: Word: `public`. expect "function"'); + return true; + }); + }); + + describe('function', function () { + it('must be function after async', function () { + assert.throws(function () { + parse(` + interface M { + async functionx + } + `, '__filename'); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'Unexpected token: Word: `functionx`. Expect ID function, but Word: `functionx`'); + return true; + }); + }); + + it('function can not have body', function () { + var ast = parse(` + interface M { + function callId(): string; + function callId2(): string; + } + `, '__filename'); + + const [func1, func2] = ast.interfaceBody.nodes; + + assert.deepStrictEqual(func1, { + 'annotation': undefined, + 'isStatic': false, + 'isAsync': false, + 'hasThrow': false, + 'params': { + 'params': [], + 'type': 'params' + }, + 'returnType': new WordToken(8, 'string', loc(3, 30, 3, 36), 8), + 'tokenRange': [3, 10], + 'type': 'function', + 'functionName': new WordToken(2, 'callId', loc(3, 20, 3, 26), 4) + }); + assert.deepStrictEqual(func2, { + 'annotation': undefined, + 'isStatic': false, + 'isAsync': false, + 'hasThrow': false, + 'params': { + 'params': [], + 'type': 'params' + }, + 'returnType': new WordToken(8, 'string', loc(4, 31, 4, 37), 15), + 'tokenRange': [10, 17], + 'type': 'function', + 'functionName': new WordToken(2, 'callId2', loc(4, 20, 4, 27), 11) + }); + }); + + it('function should ok', function () { + var ast = parse(` + interface M { + function callId(): void; + } + `, '__filename'); + + const [func] = ast.interfaceBody.nodes; + + assert.deepStrictEqual(func, { + 'annotation': undefined, + 'functionName': new WordToken(2, 'callId', loc(3, 20, 3, 26), 4), + 'params': { + 'params': [], + 'type': 'params' + }, + 'returnType': new WordToken(8, 'void', loc(3, 30, 3, 34), 8), + 'tokenRange': [3, 10], + hasThrow: false, + isAsync: false, + isStatic: false, + 'type': 'function' + }); + }); + + it('function with throws should ok', function () { + var ast = parse(` + interface M { + function callId() throws : string; + function callId2(): string; + } + `, '__filename'); + + const [func, func2] = ast.interfaceBody.nodes; + assert.deepStrictEqual(func.hasThrow, true); + assert.deepStrictEqual(func2.hasThrow, false); + }); + + it('static function should not ok', function () { + assert.throws(() => { + parse(` + interface M { + static function equal(actual: any, expected: any, message: string): void; + }`, '__filename'); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'Unexpected token: Word: `static`. expect "function"'); + return true; + }); + }); + + it('async function should ok', function () { + var ast = parse(` + interface M { + async function equal(actual: any, expected: any, message: string): void; + function equal2(actual: any, expected: any, message: string): void; + } + `, '__filename'); + const [fun, fun2] = ast.interfaceBody.nodes; + assert.deepStrictEqual(fun.isAsync, true); + assert.deepStrictEqual(fun2.isAsync, false); + }); + + it('function annotation should be ok', function () { + const ast = parse(` + interface M { + /** + * description + * @param key key description + * @return returns value + */ + function hello(key: string): string; + } + `, '__filename'); + const [fun] = ast.interfaceBody.nodes; + assert.deepStrictEqual(fun.annotation, new Annotation('/**\n * description\n * @param key key description\n * @return returns value\n */', loc(3, 9, 7, 12), 3)); + }); + }); +}); diff --git a/test/lexer.test.js b/test/lexer.test.js index b064f5d..2c104a2 100644 --- a/test/lexer.test.js +++ b/test/lexer.test.js @@ -2,10 +2,11 @@ const fs = require('fs'); const path = require('path'); - -const expect = require('expect.js'); +const assert = require('assert').strict; const Lexer = require('../lib/lexer'); +const { Tag } = require('../lib/tag'); +const { Token, Comment, Annotation, StringLiteral, TemplateElement, WordToken, NumberLiteral } = require('../lib/tokens'); function lex(source, filename) { var lexer = new Lexer(source, filename); @@ -39,573 +40,371 @@ function token(t) { describe('lexer', function () { it('should ok', function () { - expect(lex('', '__filename')).to.be.eql([ - { - tag: undefined, - 'loc': loc(1, 1, 1, 1) - } + assert.deepStrictEqual(lex('', '__filename'), [ + new Token(undefined, loc(1, 1, 1, 1), 0) ]); }); it('should ok with comments', function () { - expect(lex('// abc\n', '__filename')).to.be.eql([ - { - tag: 20, - value: '// abc', - 'loc': loc(1, 1, 1, 7) - }, - { tag: undefined, 'loc': loc(2, 1, 2, 1) } + assert.deepStrictEqual(lex('// abc\n', '__filename'), [ + new Comment('// abc', loc(1, 1, 1, 7), 0), + new Token(undefined, loc(2, 1, 2, 1), 1) ]); - expect(lex('// abc', '__filename')).to.be.eql([ - { - tag: 20, - value: '// abc', - 'loc': loc(1, 1, 1, 7) - }, - { tag: undefined, 'loc': loc(1, 7, 1, 7) } + assert.deepStrictEqual(lex('// abc', '__filename'), [ + new Comment('// abc', loc(1, 1, 1, 7), 0), + new Token(undefined, loc(1, 7, 1, 7), 1) ]); }); it('should ok with Annotation', function () { - expect(lex('/** abc */', '__filename')).to.be.eql([ - { - tag: 19, - value: '/** abc */', - 'loc': loc(1, 1, 1, 11) - }, - { tag: undefined, 'loc': loc(1, 11, 1, 11) } + assert.deepStrictEqual(lex('/** abc */', '__filename'), [ + new Annotation('/** abc */', loc(1, 1, 1, 11), 0), + new Token(undefined, loc(1, 11, 1, 11), 1) ]); - expect(lex('/** abc */\n', '__filename')).to.be.eql([ - { - tag: 19, - value: '/** abc */', - 'loc': loc(1, 1, 1, 11) - }, - { tag: undefined, 'loc': loc(2, 1, 2, 1) } + assert.deepStrictEqual(lex('/** abc */\n', '__filename'), [ + new Annotation('/** abc */', loc(1, 1, 1, 11), 0), + new Token(undefined, loc(2, 1, 2, 1), 1) ]); - expect(lex('/** ab\n * c\n */\n', '__filename')).to.be.eql([ - { - tag: 19, - value: '/** ab\n * c\n */', - 'loc': loc(1, 1, 3, 4) - }, - { tag: undefined, 'loc': loc(4, 1, 4, 1) } + assert.deepStrictEqual(lex('/** ab\n * c\n */\n', '__filename'), [ + new Annotation('/** ab\n * c\n */', loc(1, 1, 3, 4), 0), + new Token(undefined, loc(4, 1, 4, 1), 1) ]); - expect(() => { + assert.throws(() => { lex('/* abcd */\n', '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be('Only \'//\' or \'/**\' allowed'); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Only \'//\' or \'/**\' allowed'); + return true; }); }); it('should ok with module', function () { - expect(lex('module {}', '__filename')).to.be.eql([ - { - tag: 2, - lexeme: 'module', - 'loc': loc(1, 1, 1, 7) - }, - { tag: '{', loc: loc(1, 8, 1, 8) }, - { tag: '}', loc: loc(1, 9, 1, 9) }, - { tag: undefined, loc: loc(1, 10, 1, 10) } + assert.deepStrictEqual(lex('module {}', '__filename'), [ + new WordToken(Tag.MODULE, 'module', loc(1, 1, 1, 7), 0), + new Token('{', loc(1, 8, 1, 8), 1), + new Token('}', loc(1, 9, 1, 9), 2), + new Token(undefined, loc(1, 10, 1, 10), 3) ]); }); it('should ok with string', function () { - expect(lex('"abcdef"', '__filename')).to.be.eql([ - { tag: 1, string: 'abcdef', - 'loc': loc(1, 2, 1, 8)}, - { tag: undefined, loc: loc(1, 9, 1, 9) } + assert.deepStrictEqual(lex('"abcdef"', '__filename'), [ + new StringLiteral('abcdef', loc(1, 2, 1, 8), 0), + new Token(undefined, loc(1, 9, 1, 9), 1) ]); - expect(lex('\'abcdef\'', '__filename')).to.be.eql([ - { tag: 1, string: 'abcdef', loc: loc(1, 2, 1, 8) }, - { tag: undefined, loc: loc(1, 9, 1, 9) } + assert.deepStrictEqual(lex('\'abcdef\'', '__filename'), [ + new StringLiteral('abcdef', loc(1, 2, 1, 8), 0), + new Token(undefined, loc(1, 9, 1, 9), 1) ]); - expect(() => { + assert.throws(() => { lex('\'', '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be('Unexpect end of file'); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpect end of file'); + return true; }); - expect(() => { + assert.throws(() => { lex('"', '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be('Unexpect end of file'); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpect end of file'); + return true; }); const txt = fs.readFileSync(path.join(__dirname, 'fixtures/escape.txt'), 'utf8'); - expect(lex(txt, '__filename')).to.be.eql([ - { tag: 1, string: 'doesn\'t match.', - 'loc': loc(1, 2, 1, 17) }, - { tag: undefined, - loc: loc(1, 18, 1, 18) - } + assert.deepStrictEqual(lex(txt, '__filename'), [ + new StringLiteral('doesn\'t match.', loc(1, 2, 1, 17), 0), + new Token(undefined, loc(1, 18, 1, 18), 1) ]); - expect(lex('"\\0\\b\\t\\n\\v\\f\\r\\\'\\\\"', '__filename')).to.be.eql([ - { tag: 1, string: '\u0000\b\t\n\u000b\f\r\'\\', - 'loc': loc(1, 2, 1, 20) - }, - { tag: undefined, - loc: loc(1, 21, 1, 21) - } + assert.deepStrictEqual(lex('"\\0\\b\\t\\n\\v\\f\\r\\\'\\\\"', '__filename'), [ + new StringLiteral('\u0000\b\t\n\u000b\f\r\'\\', loc(1, 2, 1, 20), 0), + new Token(undefined, loc(1, 21, 1, 21), 1) ]); - expect(() => { + assert.throws(() => { lex('"\\a"', '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be('Invalid char: \\0xa/\'\\0x97\''); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Invalid char: \\0xa/\'\\0x97\''); + return true; }); }); it('should ok with number', function () { - expect(lex('123456', '__filename')).to.be.eql([ - { tag: 9, value: 123456, type: 'integer', - 'loc': loc(1, 1, 1, 7) }, - { tag: undefined, loc: loc(1, 7, 1, 7) } + assert.deepStrictEqual(lex('123456', '__filename'), [ + new NumberLiteral('123456', 'integer', loc(1, 1, 1, 7), 0), + new Token(undefined, loc(1, 7, 1, 7), 1) ]); - expect(lex('-1', '__filename')).to.be.eql([ - { tag: 9, value: -1, type: 'integer', - 'loc': loc(1, 1, 1, 3) }, - { tag: undefined, loc: loc(1, 3, 1, 3) } + assert.deepStrictEqual(lex('-1', '__filename'), [ + new NumberLiteral('-1', 'integer', loc(1, 1, 1, 3), 0), + new Token(undefined, loc(1, 3, 1, 3), 1) ]); - expect(lex('0', '__filename')).to.be.eql([ - { tag: 9, value: 0, type: 'integer', - 'loc': loc(1, 1, 1, 2) - }, - { tag: undefined, loc: loc(1, 2, 1, 2) } + assert.deepStrictEqual(lex('0', '__filename'), [ + new NumberLiteral('0', 'integer', loc(1, 1, 1, 2), 0), + new Token(undefined, loc(1, 2, 1, 2), 1) ]); - expect(lex('123456L', '__filename')).to.be.eql([ - { - tag: 9, value: 123456, type: 'long', - 'loc': loc(1, 1, 1, 8) - }, - { tag: undefined, loc: loc(1, 8, 1, 8) } + assert.deepStrictEqual(lex('123456L', '__filename'), [ + new NumberLiteral('123456', 'long', loc(1, 1, 1, 8), 0), + new Token(undefined, loc(1, 8, 1, 8), 1) ]); - expect(lex('1.2345', '__filename')).to.be.eql([ - { - tag: 9, value: 1.2345, type: 'float', - 'loc': loc(1, 1, 1, 7) - }, - { tag: undefined, loc: loc(1, 7, 1, 7) } + assert.deepStrictEqual(lex('1.2345', '__filename'), [ + new NumberLiteral('1.2345', 'float', loc(1, 1, 1, 7), 0), + new Token(undefined, loc(1, 7, 1, 7), 1) ]); - expect(lex('-1.2345', '__filename')).to.be.eql([ - { - tag: 9, value: -1.2345, type: 'float', - 'loc': loc(1, 1, 1, 8) - }, - { tag: undefined, loc: loc(1, 8, 1, 8) } + assert.deepStrictEqual(lex('-1.2345', '__filename'), [ + new NumberLiteral('-1.2345', 'float', loc(1, 1, 1, 8), 0), + new Token(undefined, loc(1, 8, 1, 8), 1) ]); - expect(lex('1.2345f', '__filename')).to.be.eql([ - { - tag: 9, value: 1.2345, type: 'float', - 'loc': loc(1, 1, 1, 8) - }, - { tag: undefined, loc: loc(1, 8, 1, 8) } + assert.deepStrictEqual(lex('1.2345f', '__filename'), [ + new NumberLiteral('1.2345', 'float', loc(1, 1, 1, 8), 0), + new Token(undefined, loc(1, 8, 1, 8), 1) ]); - - expect(lex('1.2345d', '__filename')).to.be.eql([ - { - tag: 9, value: 1.2345, type: 'double', - 'loc': loc(1, 1, 1, 8) - }, - { tag: undefined, loc: loc(1, 8, 1, 8) } + assert.deepStrictEqual(lex('1.2345d', '__filename'), [ + new NumberLiteral('1.2345', 'double', loc(1, 1, 1, 8), 0), + new Token(undefined, loc(1, 8, 1, 8), 1) ]); - expect(lex('0.12345', '__filename')).to.be.eql([ - { - tag: 9, value: 0.12345, type: 'float', - 'loc': loc(1, 1, 1, 8) - }, - { tag: undefined, loc: loc(1, 8, 1, 8) } + assert.deepStrictEqual(lex('0.12345', '__filename'), [ + new NumberLiteral('0.12345', 'float', loc(1, 1, 1, 8), 0), + new Token(undefined, loc(1, 8, 1, 8), 1) ]); - expect(lex('0.0', '__filename')).to.be.eql([ - { - tag: 9, value: 0.0, type: 'float', - 'loc': loc(1, 1, 1, 4) - }, - { tag: undefined, loc: loc(1, 4, 1, 4) } + assert.deepStrictEqual(lex('0.0', '__filename'), [ + new NumberLiteral('0.0', 'float', loc(1, 1, 1, 4), 0), + new Token(undefined, loc(1, 4, 1, 4), 1) ]); - expect(lex('0', '__filename')).to.be.eql([ - { - tag: 9, value: 0, type: 'integer', - 'loc': loc(1, 1, 1, 2) - }, - { tag: undefined, loc: loc(1, 2, 1, 2) } + assert.deepStrictEqual(lex('0', '__filename'), [ + new NumberLiteral('0', 'integer', loc(1, 1, 1, 2), 0), + new Token(undefined, loc(1, 2, 1, 2), 1) ]); }); it('should ok with true/false', function () { - expect(lex('true', '__filename')).to.be.eql([ - { - lexeme: 'true', - tag: 13, - 'loc': loc(1, 1, 1, 5) - }, - { tag: undefined, loc: loc(1, 5, 1, 5) } + assert.deepStrictEqual(lex('true', '__filename'), [ + new WordToken(13, 'true', loc(1, 1, 1, 5), 0), + new Token(undefined, loc(1, 5, 1, 5), 1) ]); - expect(lex('false', '__filename')).to.be.eql([ - {lexeme: 'false', tag: 13, 'loc': loc(1, 1, 1, 6)}, - { tag: undefined, loc: loc(1, 6, 1, 6) } + assert.deepStrictEqual(lex('false', '__filename'), [ + new WordToken(13, 'false',loc(1, 1, 1, 6), 0), + new Token(undefined, loc(1, 6, 1, 6), 1) ]); }); it('should ok with virtual prop', function () { - expect(lex('@prop', '__filename')).to.be.eql([ - { tag: 3, lexeme: '@prop', - 'loc': loc(1, 1, 1, 6) }, - { tag: undefined, loc: loc(1, 6, 1, 6) } + assert.deepStrictEqual(lex('@prop', '__filename'), [ + new WordToken(3, '@prop', loc(1, 1, 1, 6), 0), + new Token(undefined, loc(1, 6, 1, 6), 1) ]); - expect(() => { + assert.throws(() => { lex('@123', '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be('Unexpect 1 after @'); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpect 1 after @'); + return true; }); }); it('should ok with variable', function () { - expect(lex('myname', '__filename')).to.be.eql([ - { tag: 2, lexeme: 'myname', - 'loc': loc(1, 1, 1, 7) }, - { tag: undefined, loc: loc(1, 7, 1, 7) } + assert.deepStrictEqual(lex('myname', '__filename'), [ + new WordToken(2, 'myname', loc(1, 1, 1, 7), 0), + new Token(undefined, loc(1, 7, 1, 7), 1) ]); }); it('should ok with virtual method', function () { - expect(lex('@prop()', '__filename')).to.be.eql([ - { tag: 3, lexeme: '@prop', - 'loc': loc(1, 1, 1, 6) - }, - { tag: '(', - 'loc': loc(1, 6, 1, 6) }, - { tag: ')', - 'loc': loc(1, 7, 1, 7) }, - { tag: undefined, loc: loc(1, 8, 1, 8) } + assert.deepStrictEqual(lex('@prop()', '__filename'), [ + new WordToken(3, '@prop', loc(1, 1, 1, 6), 0), + new Token('(', loc(1, 6, 1, 6), 1), + new Token(')', loc(1, 7, 1, 7), 2), + new Token(undefined, loc(1, 8, 1, 8), 3) ]); }); it('should ok with template', function () { - expect(lex('`abc${a}def`', '__filename')).to.be.eql([ - { - tag: 12, string: 'abc', tail: false, - 'loc': loc(1, 2, 1, 5) - }, - { tag: 2, lexeme: 'a', - 'loc': loc(1, 7, 1, 8) - }, - { tag: 12, string: 'def', tail: true, 'loc': loc(1, 9, 1, 12) }, - { tag: undefined, loc: loc(1, 13, 1, 13) } + assert.deepStrictEqual(lex('`abc${a}def`', '__filename'), [ + new TemplateElement('abc', false, loc(1, 2, 1, 5), 0), + new WordToken(2, 'a', loc(1, 7, 1, 8), 1), + new TemplateElement('def', true, loc(1, 9, 1, 12), 2), + new Token(undefined, loc(1, 13, 1, 13), 3) ]); - expect(lex('`abcdef`', '__filename')).to.be.eql([ - { tag: 12, string: 'abcdef', tail: true, - 'loc': loc(1, 2, 1, 8) - }, - { tag: undefined, loc: loc(1, 9, 1, 9) } + assert.deepStrictEqual(lex('`abcdef`', '__filename'), [ + new TemplateElement('abcdef', true, loc(1, 2, 1, 8), 0), + new Token(undefined, loc(1, 9, 1, 9), 1) ]); - expect(lex('``', '__filename')).to.be.eql([ - { tag: 12, string: '', tail: true, 'loc': loc(1, 2, 1, 2) }, - { tag: undefined, loc: loc(1, 3, 1, 3) } + assert.deepStrictEqual(lex('``', '__filename'), [ + new TemplateElement('', true, loc(1, 2, 1, 2), 0), + new Token(undefined, loc(1, 3, 1, 3), 1) ]); - expect(lex('`$${b}`', '__filename')).to.be.eql([ - { tag: 12, string: '$', tail: false, - 'loc': loc(1, 2, 1, 3) - }, - { tag: 2, lexeme: 'b', - 'loc': loc(1, 5, 1, 6) }, - { tag: 12, string: '', tail: true, - 'loc': loc(1, 7, 1, 7) }, - { tag: undefined, loc: loc(1, 8, 1, 8) } + assert.deepStrictEqual(lex('`$${b}`', '__filename'), [ + new TemplateElement('$', false, loc(1, 2, 1, 3), 0), + new WordToken(2, 'b', loc(1, 5, 1, 6), 1), + new TemplateElement('', true, loc(1, 7, 1, 7), 2), + new Token(undefined, loc(1, 8, 1, 8), 3) ]); - expect(lex('`abc${d}ef${g}h`', '__filename')).to.be.eql([ - { tag: 12, string: 'abc', tail: false, 'loc': loc(1, 2, 1, 5) }, - { tag: 2, lexeme: 'd', 'loc': loc(1, 7, 1, 8) }, - { tag: 12, string: 'ef', tail: false, 'loc': loc(1, 9, 1, 11) }, - { tag: 2, lexeme: 'g', 'loc': loc(1, 13, 1, 14) }, - { tag: 12, string: 'h', tail: true, 'loc': loc( 1, 15, 1, 16) }, - { tag: undefined, loc: loc(1, 17, 1, 17) } + assert.deepStrictEqual(lex('`abc${d}ef${g}h`', '__filename'), [ + new TemplateElement('abc', false, loc(1, 2, 1, 5), 0), + new WordToken(2, 'd', loc(1, 7, 1, 8), 1), + new TemplateElement('ef', false, loc(1, 9, 1, 11), 2), + new WordToken(2, 'g', loc(1, 13, 1, 14), 3), + new TemplateElement('h', true, loc(1, 15, 1, 16), 4), + new Token(undefined, loc(1, 17, 1, 17), 5) ]); - expect(lex('`abc${d}ef$gh`', '__filename')).to.be.eql([ - { tag: 12, string: 'abc', tail: false, 'loc': loc(1, 2, 1, 5) }, - { tag: 2, lexeme: 'd', 'loc': loc(1, 7, 1, 8) }, - { tag: 12, string: 'ef$gh', tail: true, 'loc': loc(1, 9, 1, 14) }, - { tag: undefined, loc: loc(1, 15, 1, 15) } + assert.deepStrictEqual(lex('`abc${d}ef$gh`', '__filename'), [ + new TemplateElement('abc', false, loc(1, 2, 1, 5), 0), + new WordToken(2, 'd', loc(1, 7, 1, 8), 1), + new TemplateElement('ef$gh', true, loc(1, 9, 1, 14), 2), + new Token(undefined, loc(1, 15, 1, 15), 3) ]); - expect(() => { + assert.throws(() => { lex('`', '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be('Unexpect end of file'); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpect end of file'); + return true; }); - expect(() => { + assert.throws(() => { lex('`abc${d}e', '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be('Unexpect end of file'); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpect end of file'); + return true; }); }); it('type should ok', function () { - expect(lex('type @default_unit_server = string', '__filename')).to.be.eql([ - { - lexeme: 'type', - tag: 2, - 'loc': loc(1, 1, 1, 5) - }, - { - lexeme: '@default_unit_server', - tag: 3, - 'loc': loc(1, 6, 1, 26) - }, - { - tag: '=', - loc: loc(1, 27, 1, 27) - }, - { - lexeme: 'string', - tag: 8, - 'loc': loc(1, 29, 1, 35) - }, - { tag: undefined, loc: loc(1, 35, 1, 35) } - ]); - - expect(lex('type @default = (string, string): string', '__filename')).to.be.eql([ - { lexeme: 'type', - tag: 2, - 'loc': loc(1, 1, 1, 5) - }, - { - lexeme: '@default', - tag: 3, - 'loc': loc(1, 6, 1, 14) - }, - { - tag: '=', - loc: loc(1, 15, 1, 15) - }, - { - tag: '(', - loc: loc(1, 17, 1, 17) - }, - { - lexeme: 'string', - tag: 8, - 'loc': loc(1, 18, 1, 24) - }, - { - tag: ',', - loc: loc(1, 24, 1, 24) - }, - { - lexeme: 'string', - tag: 8, - 'loc': loc(1, 26, 1, 32) - }, - { - tag: ')', - loc: loc(1, 32, 1, 32) - }, - { - tag: ':', - loc: loc(1, 33, 1, 33) - }, - { - lexeme: 'string', - tag: 8, - 'loc': loc(1, 35, 1, 41) - }, - { tag: undefined, loc: loc(1, 41, 1, 41) } - ]); - - expect(lex('type @default = async (string, string): string', '__filename')).to.be.eql([ - { lexeme: 'type', - tag: 2, - 'loc': loc(1, 1, 1, 5) - }, - { - lexeme: '@default', - tag: 3, - 'loc': loc(1, 6, 1, 14) - }, - { - tag: '=', - loc: loc(1, 15, 1, 15) - }, - { - lexeme: 'async', - tag: 2, - 'loc': loc(1, 17, 1, 22) - }, - { - tag: '(', - loc: loc(1, 23, 1, 23) - }, - { - lexeme: 'string', - tag: 8, - 'loc': loc(1, 24, 1, 30) - }, - { - tag: ',', - loc: loc(1, 30, 1, 30) - }, - { - lexeme: 'string', - tag: 8, - 'loc': loc(1, 32, 1, 38) - }, - { - tag: ')', - loc: loc(1, 38, 1, 38) - }, - { - tag: ':', - loc: loc(1, 39, 1, 39) - }, - { - lexeme: 'string', - tag: 8, - 'loc': loc(1, 41, 1, 47) - }, - { tag: undefined, loc: loc(1, 47, 1, 47) } - ]); - - expect(lex('type @default = [ string ]', '__filename')).to.be.eql([ - { lexeme: 'type', - tag: 2, - 'loc': loc(1, 1, 1, 5) - }, - { - lexeme: '@default', - tag: 3, - 'loc': loc(1, 6, 1, 14) - }, - { - tag: '=', - loc: loc(1, 15, 1, 15) - }, - { - tag: '[', - loc: loc(1, 17, 1, 17) - }, - { - lexeme: 'string', - tag: 8, - 'loc': loc(1, 19, 1, 25) - }, - { - tag: ']', - loc: loc(1, 26, 1, 26) - }, - { tag: undefined, loc: loc(1, 27, 1, 27) } + assert.deepStrictEqual(lex('type @default_unit_server = string', '__filename'), [ + new WordToken(2, 'type', loc(1, 1, 1, 5), 0), + new WordToken(3, '@default_unit_server', loc(1, 6, 1, 26), 1), + new Token('=', loc(1, 27, 1, 27), 2), + new WordToken(8, 'string', loc(1, 29, 1, 35), 3), + new Token(undefined, loc(1, 35, 1, 35), 4) + ]); + + assert.deepStrictEqual(lex('type @default = (string, string): string', '__filename'), [ + new WordToken(2, 'type', loc(1, 1, 1, 5), 0), + new WordToken(3, '@default', loc(1, 6, 1, 14), 1), + new Token('=', loc(1, 15, 1, 15), 2), + new Token('(', loc(1, 17, 1, 17), 3), + new WordToken(8, 'string', loc(1, 18, 1, 24), 4), + new Token(',', loc(1, 24, 1, 24), 5), + new WordToken(8, 'string', loc(1, 26, 1, 32), 6), + new Token(')', loc(1, 32, 1, 32), 7), + new Token(':', loc(1, 33, 1, 33), 8), + new WordToken(8, 'string', loc(1, 35, 1, 41), 9), + new Token(undefined, loc(1, 41, 1, 41), 10) + ]); + + assert.deepStrictEqual(lex('type @default = async (string, string): string', '__filename'), [ + new WordToken(2, 'type', loc(1, 1, 1, 5), 0), + new WordToken(3, '@default', loc(1, 6, 1, 14), 1), + new Token('=', loc(1, 15, 1, 15), 2), + new WordToken(2, 'async', loc(1, 17, 1, 22), 3), + new Token('(', loc(1, 23, 1, 23), 4), + new WordToken(8, 'string', loc(1, 24, 1, 30), 5), + new Token(',', loc(1, 30, 1, 30), 6), + new WordToken(8, 'string', loc(1, 32, 1, 38), 7), + new Token(')', loc(1, 38, 1, 38), 8), + new Token(':', loc(1, 39, 1, 39), 9), + new WordToken(8, 'string', loc(1, 41, 1, 47), 10), + new Token(undefined, loc(1, 47, 1, 47), 11) + ]); + + assert.deepStrictEqual(lex('type @default = [ string ]', '__filename'), [ + new WordToken(2, 'type', loc(1, 1, 1, 5), 0), + new WordToken(3, '@default', loc(1, 6, 1, 14), 1), + new Token('=', loc(1, 15, 1, 15), 2), + new Token('[', loc(1, 17, 1, 17), 3), + new WordToken(8, 'string', loc(1, 19, 1, 25), 4), + new Token(']', loc(1, 26, 1, 26), 5), + new Token(undefined, loc(1, 27, 1, 27), 6) ]); }); it('function should ok', function () { - expect(lex('function', '__filename')).to.be.eql([ - { - lexeme: 'function', - tag: 2, - 'loc': loc(1, 1, 1, 9) - }, - { - 'loc': { - 'end': { - 'column': 9, - 'line': 1, - }, - 'start': { - 'column': 9, - 'line': 1 - } - }, - 'tag': undefined - } + assert.deepStrictEqual(lex('function', '__filename'), [ + new WordToken(2, 'function', loc(1, 1, 1, 9), 0), + new Token(undefined, loc(1, 9, 1, 9), 1) ]); }); it('null should ok', function () { const tokens = lex('null', '__filename'); - expect(tokens).to.have.length(2); - expect(tokens.map((item) => token(item))).to.eql([ - {lexeme: 'null', tag: 14}, - {lexeme: undefined, tag: undefined} + assert.deepStrictEqual(tokens.length, 2); + assert.deepStrictEqual(tokens.map((item) => token(item)), [ + { lexeme: 'null', tag: 14 }, + { lexeme: undefined, tag: undefined } ]); }); it('&& should ok', function () { const tokens = lex('&&', '__filename'); - expect(tokens).to.have.length(2); - expect(tokens.map((item) => token(item))).to.eql([ - {lexeme: '&&', tag: 26}, - {lexeme: undefined, tag: undefined} + assert.deepStrictEqual(tokens.length, 2); + assert.deepStrictEqual(tokens.map((item) => token(item)), [ + { lexeme: '&&', tag: 26 }, + { lexeme: undefined, tag: undefined } ]); - expect(tokens[0].toString()).to.be('Operator: `&&`'); + assert.deepStrictEqual(tokens[0].toString(), 'Logical: `&&`'); }); it('& should not ok', function () { - expect(function () { + assert.throws(function () { lex('&', '__filename'); - }).to.throwException((e) => { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpect undefined after '&', expect '&'`); + }, (e) => { + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpect undefined after '&', expect '&'`); + return true; }); }); it('|| should ok', function () { const tokens = lex('||', '__filename'); - expect(tokens).to.have.length(2); - expect(tokens.map((item) => token(item))).to.eql([ - {lexeme: '||', tag: 27}, - {lexeme: undefined, tag: undefined} + assert.deepStrictEqual(tokens.length, 2); + assert.deepStrictEqual(tokens.map((item) => token(item)), [ + { lexeme: '||', tag: 26 }, + { lexeme: undefined, tag: undefined } ]); - expect(tokens[0].toString()).to.be('Operator: `||`'); + assert.deepStrictEqual(tokens[0].toString(), 'Logical: `||`'); }); it('| should not ok', function () { - expect(function () { + assert.throws(function () { lex('|', '__filename'); - }).to.throwException((e) => { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpect undefined after '|', expect '|'`); + }, (e) => { + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpect undefined after '|', expect '|'`); + return true; }); }); it('try/catch/finally shoul ok', function () { const tokens = lex(`try { - } catch (ex) { - } finally { - }`, '__filename'); - expect(tokens.map((item) => token(item))).to.eql([ + assert.deepStrictEqual(tokens.map((item) => token(item)), [ { 'lexeme': 'try', 'tag': 28 @@ -660,4 +459,78 @@ describe('lexer', function () { } ]); }); -}); + + it('$ID should ok', function () { + assert.deepStrictEqual(lex('import $ID;', '__filename').map((item) => token(item)), [ + { + 'lexeme': 'import', + 'tag': 22, + }, + { + 'lexeme': '$ID', + 'tag': 21, + }, + { + 'lexeme': undefined, + 'tag': ';', + }, + { + 'lexeme': undefined, + 'tag': undefined, + } + ]); + + assert.throws(() => { + lex('$123', '__filename'); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpect 1 after $'); + return true; + }); + }); + + it('#ID should ok', function () { + assert.deepStrictEqual(lex('#append(list, item);', '__filename').map((item) => token(item)), [ + { + 'lexeme': '#append', + 'tag': 36, + }, + { + 'lexeme': undefined, + 'tag': '(', + }, + { + 'lexeme': 'list', + 'tag': 2, + }, + { + 'lexeme': undefined, + 'tag': ',', + }, + { + 'lexeme': 'item', + 'tag': 2, + }, + { + 'lexeme': undefined, + 'tag': ')', + }, + { + 'lexeme': undefined, + 'tag': ';', + }, + { + 'lexeme': undefined, + 'tag': undefined, + } + ]); + + assert.throws(() => { + lex('#123', '__filename'); + }, function (e) { // get the exception object + assert(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpect 1 after #'); + return true; + }); + }); +}); \ No newline at end of file diff --git a/test/main_analyser.test.js b/test/main_analyser.test.js new file mode 100644 index 0000000..eb17e36 --- /dev/null +++ b/test/main_analyser.test.js @@ -0,0 +1,99 @@ +'use strict'; +const assert = require('assert'); + +const Analyser = require('../lib/main_analyser'); +const Parser = require('../lib/parser'); +const Lexer = require('../lib/lexer'); +const Package = require('../lib/package'); + +function parse(source, pkg = new Package()) { + const lexer = new Lexer(source, '__filename'); + const parser = new Parser(lexer); + const ast = parser.program(); + const anlyser = new Analyser({ source, filename: '__filename' }, pkg); + anlyser.check(ast); + anlyser.checkMethods(ast); + return ast; +} + +describe('main analyser', function () { + describe('import', function () { + it('import undefined package should not ok', function () { + assert.throws(() => { + parse(`import $std; main {}`); + }, (e) => { + + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' not defined in Darafile`); + return true; + }); + }); + + it('re-import package should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + pkg.libraries.set('$std', new Package()); + parse(`import $std; import $std; main {}`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package id '$std' has been imported`); + return true; + }); + }); + }); + + describe('define methods', function () { + it('redefine function should not ok', function () { + assert.throws(() => { + parse(` + main { + function getId(): string { + } + function getId(): string { + } + }`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `redefined function 'getId'`); + return true; + }); + }); + + it('define function should ok', function () { + parse(` + main { + function getId(): string { + return ''; + } + }`); + }); + }); + + describe('main', function () { + it('main should ok', function () { + parse(` + main { + main(args: [string]) { + + } + }`); + }); + + it('too many entries should not ok', function () { + assert.throws(() => { + parse(` + main { + main(args: [string]) { + } + + main(args: [string]) { + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'only one entry is allowed'); + return true; + }); + }); + }); +}); diff --git a/test/main_parser.test.js b/test/main_parser.test.js new file mode 100644 index 0000000..606ed8b --- /dev/null +++ b/test/main_parser.test.js @@ -0,0 +1,159 @@ +'use strict'; + +const assert = require('assert'); + +const Parser = require('../lib/parser'); +const Lexer = require('../lib/lexer'); +const { WordToken, Annotation } = require('../lib/tokens'); + +function parse(source, filePath) { + const lexer = new Lexer(source, filePath); + const parser = new Parser(lexer); + return parser.program(); +} + +function pos(line, column) { + return { line, column }; +} + +function loc(startLine, startColumn, endLine, endColumn) { + return { + start: pos(startLine, startColumn), + end: pos(endLine, endColumn) + }; +} + +describe('main parser', function () { + it('empty main file should ok', function () { + assert.deepStrictEqual(parse('main {}', '__filename'), { + annotation: undefined, + comments: new Map(), + imports: [], + mainBody: { + nodes: [], + tokenRange: [1, 2], + type: 'mainBody' + }, + tokenRange: [0, 3], + type: 'main' + }); + }); + + it('main should ok', function () { + const ast = parse(`main { main(args: [string]) {} }`, '__filename'); + assert.deepStrictEqual(ast.mainBody.nodes[0], { + annotation: undefined, + main: new WordToken(2, 'main', loc(1, 8, 1, 12), 2), + functionBody: { + loc: loc(1, 29, 1, 32), + stmts: { + type: 'stmts', + stmts: [], + tokenRange: [10, 11] + }, + tokenRange: [10, 11], + type: 'functionBody' + }, + params: { + params: [ + { + 'paramName': new WordToken(2, 'args', loc(1, 13, 1, 17), 4), + 'paramType': { + 'itemType': new WordToken(8, 'string', loc(1, 20, 1, 26), 7), + 'type': 'array' + }, + 'type': 'param' + } + ], + type: 'params' + }, + tokenRange: [2, 12], + type: 'main' + }); + }); + + it('module annotation should be ok', function () { + var ast = parse(` + /** + * module annotation + */ + main { + main(args: [string]) {} + } + `, '__filename'); + assert.deepStrictEqual(ast.annotation, new Annotation('/**\n * module annotation\n */', loc(2, 5, 4, 8), 0)); + }); + + it('import with comma should ok', function () { + const ast = parse(`import $oss; main {}`, '__filename'); + assert.deepStrictEqual(ast.imports, [ + { + type: 'import', + aliasId: new WordToken(21, '$oss', loc(1, 8, 1, 12), 1), + tokenRange: [0, 3] + } + ]); + }); + + it('only function/main should be ok', function () { + assert.throws(function () { + parse(`main { public }`, '__filename'); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'Unexpected token: Word: `public`. expect "main" or "function"'); + return true; + }); + }); + + it('function should ok', function () { + var ast = parse(` + main { + function callId(): void { + } + }`, '__filename'); + + const [func] = ast.mainBody.nodes; + + assert.deepStrictEqual(func, { + 'annotation': undefined, + 'functionName': new WordToken(2, 'callId', loc(3, 18, 3, 24), 3), + 'functionBody': { + 'loc': loc(3, 33, 5, 7), + 'stmts': { + 'stmts': [], + 'tokenRange': [8, 9], + 'type': 'stmts' + }, + 'tokenRange': [8, 9], + 'type': 'functionBody' + }, + 'params': { + 'params': [], + 'type': 'params' + }, + 'returnType': new WordToken(8, 'void', loc(3, 28, 3, 32), 7), + 'tokenRange': [2, 9], + hasThrow: false, + isAsync: false, + isStatic: false, + 'type': 'function' + }); + }); + + it('function with annotation should ok', function () { + const ast = parse(` + /** + * global annotation + */ + main { + /** + * function annotation + */ + function callId(): void { + } + }`, '__filename'); + + const [func] = ast.mainBody.nodes; + assert.deepStrictEqual(func.annotation, new Annotation('/**\n * function annotation\n */', loc(6, 9, 8, 12), 3)); + }); +}); diff --git a/test/model_analyser.test.js b/test/model_analyser.test.js new file mode 100644 index 0000000..4d6baa4 --- /dev/null +++ b/test/model_analyser.test.js @@ -0,0 +1,184 @@ +'use strict'; +const assert = require('assert'); + +const Analyser = require('../lib/model_analyser'); +const Parser = require('../lib/parser'); +const Lexer = require('../lib/lexer'); +const Package = require('../lib/package'); + +function parse(source, pkg = new Package()) { + const lexer = new Lexer(source, '__filename'); + const parser = new Parser(lexer); + const ast = parser.program(); + const anlyser = new Analyser({ source, filename: '__filename' }, pkg); + anlyser.check(ast); + return ast; +} + +describe('model analyser', function () { + it('redefined field in model should not ok', async function () { + assert.throws(function () { + parse(` + model M { + a: string, + a: string + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `redefined field "a" in model "M"`); + return true; + }); + }); + + it('undefined type in model should not ok', function () { + assert.throws(function () { + parse(` + model M { + a: json + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the type 'json' is undefined`); + return true; + }); + + assert.throws(function () { + parse(` + model M { + a: [ json ] + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the type 'json' is undefined`); + return true; + }); + }); + + it('another model should ok', function () { + const pkg = new Package(); + const lexer = new Lexer(`model N {}`, '__filename'); + const parser = new Parser(lexer); + const ast = parser.program(); + pkg.components.set('N', { + type: 'model', + ast, + ctx: { + source: lexer.source, + filename: lexer.filename + } + }); + parse(` + model M { + field: N + }`, pkg); + }); + + it('used types should ok', function () { + const ast = parse(` + model M { + r: readable + }`); + assert.deepStrictEqual(ast.usedTypes.has('readable'), true); + assert.deepStrictEqual(ast.usedTypes.has('writable'), false); + }); + + it('types should ok', function () { + parse(` + model M { + intNum: integer, + int8Num: int8, + uint8Num: uint8, + int16Num: int16, + uint16Num: uint16, + int32Num: int32, + uint32Num: uint32, + int64Num: int64, + uint64Num: uint64, + longNum: long, + ulongNum: ulong, + floatNum: float, + doubleNum: double, + intArr: [ integer ], + int8Arr: [ int8 ], + longArr: [ long ], + floatArr: [ float ], + intMap: map[string] integer, + int8Map: map[string] int8, + longMap: map[string] long, + floatMap: map[string] float, + }`); + }); + + it('import undefined package should not ok', function () { + assert.throws(() => { + parse(`import $std; model M {}`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' not defined in Darafile`); + return true; + }); + }); + + it('re-import package should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + pkg.libraries.set('$std', new Package()); + parse(`import $std; import $std; model M {}`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package id '$std' has been imported`); + return true; + }); + }); + + it('un-import package should not ok', function () { + assert.throws(() => { + parse(` + model M { + extern: $std.M + }`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' is un-imported`); + return true; + }); + }); + + it('undefined extern model should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + pkg.libraries.set('$std', new Package()); + parse(` + import $std; + model M { + extern: $std.M + }`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'M' is undefined in '$std'`); + return true; + }); + }); + + it('extern model should ok', function () { + const pkg = new Package(); + const $std = new Package(); + const lexer = new Lexer(`model M {}`, '__filename'); + const parser = new Parser(lexer); + const ast = parser.program(); + $std.components.set('M', { + type: 'model', + ast, + ctx: { + source: lexer.source, + filename: lexer.filename + } + }); + pkg.libraries.set('$std', $std); + parse(` + import $std; + model M { + extern: $std.M + }`, pkg); + }); +}); diff --git a/test/model_parser.test.js b/test/model_parser.test.js new file mode 100644 index 0000000..716d34d --- /dev/null +++ b/test/model_parser.test.js @@ -0,0 +1,475 @@ +'use strict'; + +const assert = require('assert'); + +const Parser = require('../lib/parser'); +const Lexer = require('../lib/lexer'); +const { + WordToken, + StringLiteral, + Annotation, + Comment } = require('../lib/tokens'); + +function parse(source, filePath) { + const lexer = new Lexer(source, filePath); + const parser = new Parser(lexer); + return parser.program(); +} + +function pos(line, column) { + return { line, column }; +} + +function loc(startLine, startColumn, endLine, endColumn) { + return { + start: pos(startLine, startColumn), + end: pos(endLine, endColumn) + }; +} + +describe('parser', function () { + + it('empty model should not ok', function () { + assert.throws(() => { + parse('', '__filename'); + }, (err) => { + assert.ok(err instanceof SyntaxError); + assert.deepStrictEqual(err.message, `Unexpected token: EOF. expect 'module', 'model', 'interface' or 'main'`); + return true; + }); + }); + + it('model should be ok', function () { + assert.throws(() => { + parse(`model`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: EOF. Expect ID, but EOF`); + return true; + }); + + assert.throws(() => { + parse('model id {', '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: EOF. only id is allowed`); + return true; + }); + + assert.deepStrictEqual(parse('model id {}', '__filename'), { + imports: [], + annotation: undefined, + comments: new Map(), + modelBody: { + 'nodes': [], + 'tokenRange': [2, 3], + 'type': 'modelBody' + }, + 'name': new WordToken(2, 'id', loc(1, 7, 1, 9), 1), + 'tokenRange': [0, 4], + 'type': 'model', + }); + }); + + it('model field should be ok', function () { + assert.throws(() => { + parse(`model id {?}`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: ?. only id is allowed`); + return true; + }); + + assert.throws(() => { + parse(`model id { name }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect :, but }`); + return true; + }); + + assert.throws(() => { + parse(`model id { name? }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect :, but }`); + return true; + }); + + assert.throws(() => { + parse(`model id { name?: }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. expect base type, model id or array form`); + return true; + }); + + assert.deepStrictEqual(parse(`model id { name?: string }`, '__filename'), { + type: 'model', + tokenRange: [0, 8], + annotation: undefined, + comments: new Map(), + imports: [], + modelBody: { + 'nodes': [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name', loc(1, 12, 1, 16), 3), + 'fieldType': new WordToken(8, 'string', loc(1, 19, 1, 25), 6), + 'required': false, + 'tokenRange': [3, 7], + 'type': 'modelField' + } + ], + 'tokenRange': [2, 7], + 'type': 'modelBody' + }, + name: new WordToken(2, 'id', loc(1, 7, 1, 9), 1) + }); + + assert.deepStrictEqual(parse(`model id { object?: string }`, __filename).modelBody, { + nodes: [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'object', loc(1, 12, 1, 18), 3), + 'fieldType': new WordToken(8, 'string', loc(1, 21, 1, 27), 6), + 'required': false, + 'tokenRange': [3, 7], + 'type': 'modelField' + } + ], + 'tokenRange': [2, 7], + 'type': 'modelBody' + }); + + assert.deepStrictEqual(parse(`model id { name?: ID }`, '__filename').modelBody.nodes, [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name', loc(1, 12, 1, 16), 3), + 'fieldType': new WordToken(2, 'ID', loc(1, 19, 1, 21), 6), + 'tokenRange': [3, 7], + 'required': false, + 'type': 'modelField' + } + ]); + + assert.deepStrictEqual(parse(`model id { name?: $Pack.M }`, '__filename').modelBody.nodes, [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name', loc(1, 12, 1, 16), 3), + 'fieldType': { + type: 'extern_component', + aliasId: new WordToken(21, '$Pack', loc(1, 19, 1, 24), 6), + component: new WordToken(2, 'M', loc(1, 25, 1, 26), 8), + loc: loc(1, 19, 1, 26), + tokenRange: [6, 9] + }, + 'tokenRange': [3, 9], + 'required': false, + 'type': 'modelField' + } + ]); + + assert.throws(() => { + parse(`model id { name?: [ }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. expect base type, model id or array form`); + return true; + }); + + assert.deepStrictEqual(parse(`model id { name?: [ string ] }`, '__filename').modelBody.nodes, [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name', loc(1, 12, 1, 16), 3), + 'fieldType': { + 'type': 'array', + 'itemType': new WordToken(8, 'string', loc(1, 21, 1, 27), 7) + }, + 'tokenRange': [3, 9], + 'required': false, + 'type': 'modelField' + } + ]); + + assert.deepStrictEqual(parse(`model id { name: string, }`, '__filename').modelBody.nodes, [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name', loc(1, 12, 1, 16), 3), + 'fieldType': new WordToken(8, 'string', loc(1, 18, 1, 24), 5), + 'required': true, + 'tokenRange': [3, 6], + 'type': 'modelField' + } + ]); + + assert.deepStrictEqual(parse(`model id { name: [ map[string]any ], }`, '__filename').modelBody.nodes, [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name', loc(1, 12, 1, 16), 3), + 'fieldType': { + 'itemType': { + 'keyType': new WordToken(8, 'string', loc(1, 24, 1, 30), 8), + 'type': 'map', + 'valueType': new WordToken(8, 'any', loc(1, 31, 1, 34), 10), + loc: loc(1, 20, 1, 34) + }, + 'type': 'array' + }, + 'tokenRange': [3, 12], + 'required': true, + 'type': 'modelField' + } + ]); + + assert.deepStrictEqual(parse(`model id { name?: string, name2: string }`, '__filename').modelBody.nodes, [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name', loc(1, 12, 1, 16), 3), + 'fieldType': new WordToken(8, 'string', loc(1, 19, 1, 25), 6), + 'tokenRange': [3, 7], + 'required': false, + 'type': 'modelField' + }, + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name2', loc(1, 27, 1, 32), 8), + 'fieldType': new WordToken(8, 'string', loc(1, 34, 1, 40), 10), + 'tokenRange': [8, 11], + 'required': true, + 'type': 'modelField' + } + ]); + + assert.deepStrictEqual(parse(`model id { name?: string, name2: string, }`, '__filenane').modelBody.nodes, [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name', loc(1, 12, 1, 16), 3), + 'fieldType': new WordToken(8, 'string', loc(1, 19, 1, 25), 6), + 'tokenRange': [3, 7], + 'required': false, + 'type': 'modelField' + }, + { + 'attrs': [], + 'fieldName': new WordToken(2, 'name2', loc(1, 27, 1, 32), 8), + 'fieldType': new WordToken(8, 'string', loc(1, 34, 1, 40), 10), + 'tokenRange': [8, 11], + 'required': true, + 'type': 'modelField' + } + ]); + + assert.deepStrictEqual(parse(`model id { string?: string }`, '__filename').modelBody.nodes, [ + { + 'attrs': [], + 'fieldName': new WordToken(2, 'string', loc(1, 12, 1, 18), 3), + 'fieldType': new WordToken(8, 'string', loc(1, 21, 1, 27), 6), + 'tokenRange': [3, 7], + 'required': false, + 'type': 'modelField' + } + ]); + + assert.throws(() => { + parse(`model id { name: string { }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: {. expect ","`); + return true; + }); + }); + + it('model filed attrs should be ok', function () { + assert.throws(() => { + parse(`model id { name: string( }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect ID, but }`); + return true; + }); + + assert.throws(() => { + parse(`model id { name: string(id }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect =, but }`); + return true; + }); + + assert.throws(() => { + parse(`model id { name: string(id= }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. expect string, number, bool`); + return true; + }); + + assert.throws(() => { + parse(`model id { name: string(id="attr_value" }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect ,, but }`); + return true; + }); + + assert.throws(() => { + parse(`model id { name: string(id="attr_value", }`, '__filename'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect ID, but }`); + return true; + }); + + function modelFieldAttrs(value) { + var ast = parse(` + model id { + name: string${value} + } + `, '__filename'); + return ast.modelBody.nodes[0].attrs; + } + + assert.deepStrictEqual(modelFieldAttrs(`(id="attr_value")`), [ + { + 'attrName': new WordToken(2, 'id', loc(3, 24, 3, 26), 7), + 'attrValue': new StringLiteral('attr_value', loc(3, 28, 3, 38), 9), + 'type': 'attr' + } + ]); + + assert.deepStrictEqual(modelFieldAttrs('(id="attr_value",id2="value2")'), [ + { + 'attrName': new WordToken(2, 'id', loc(3, 24, 3, 26), 7), + 'attrValue': new StringLiteral('attr_value', loc(3, 28, 3, 38), 9), + 'type': 'attr' + }, + { + 'attrName': new WordToken(2, 'id2', loc(3, 40, 3, 43), 11), + 'attrValue': new StringLiteral('value2', loc(3, 45, 3, 51), 13), + 'type': 'attr' + } + ]); + }); + + it('multi-dimentional array in model field should be ok', function () { + function modelField(value) { + var ast = parse(` + model id { + ${value} + } + `, '__filename'); + return ast.modelBody.nodes; + } + + assert.deepStrictEqual(modelField(`name?: [[ string ]]`), [ + { + 'type': 'modelField', + 'fieldName': new WordToken(2, 'name', loc(3, 11, 3, 15), 3), + 'required': false, + 'fieldType': { + 'type': 'array', + 'itemType': { + 'type': 'array', + 'itemType': new WordToken(8, 'string', loc(3, 21, 3, 27), 8) + } + }, + 'attrs': [], + 'tokenRange': [3, 11] + } + ]); + + assert.deepStrictEqual(modelField(`name?: [[[ string ]]]`), [ + { + 'type': 'modelField', + 'fieldName': new WordToken(2, 'name', loc(3, 11, 3, 15), 3), + 'required': false, + 'fieldType': { + 'type': 'array', + 'itemType': { + 'type': 'array', + 'itemType': { + 'type': 'array', + 'itemType': new WordToken(8, 'string', loc(3, 22, 3, 28), 9) + } + } + }, + 'attrs': [], + 'tokenRange': [3, 13] + } + ]); + }); + + it('import should ok', function () { + var ast = parse(`import $oss; + model id {} +`, '__filename'); + assert.deepStrictEqual(ast.imports, [ + { + type: 'import', + aliasId: new WordToken(21, '$oss', loc(1, 8, 1, 12), 1), + tokenRange: [0, 3] + } + ]); + }); + + it('model annotation should be ok', function () { + var ast = parse(` + /** + * model annotation + */ + model M {} + `, '__filename'); + + assert.deepStrictEqual(ast, { + 'type': 'model', + 'imports': [], + 'name': new WordToken(2, 'M', loc(5, 11, 5, 12), 2), + modelBody: { + 'nodes': [], + 'type': 'modelBody', + tokenRange: [3, 4] + }, + comments: new Map(), + tokenRange: [1, 5], + 'annotation': new Annotation('/**\n * model annotation\n */', loc(2, 5, 4, 8), 0) + }); + }); + + it('comment about model should ok', function () { + var ast = parse(` + // front model comment + model M { + // empty model + } + // back model comment + `, '__filename'); + const comments = new Map(); + comments.set(1, new Comment('// front model comment', loc(2, 5, 2, 27), 0)); + comments.set(5, new Comment('// empty model', loc(4, 7, 4, 21), 4)); + comments.set(7, new Comment('// back model comment', loc(6, 5, 6, 26), 6)); + assert.deepStrictEqual(ast, { + 'type': 'model', + 'annotation': undefined, + 'tokenRange': [1, 7], + 'comments': comments, + imports: [], + modelBody: { + 'nodes': [], + 'tokenRange': [3, 5], + 'type': 'modelBody' + }, + 'name': new WordToken(2, 'M', loc(3, 11, 3, 12), 2) + }); + }); + + it('word(function) as model field name should ok', function () { + assert.doesNotThrow(function () { + parse(` + model M { + function: string + } + `, '__filename'); + }); + }); +}); diff --git a/test/module_analyser.test.js b/test/module_analyser.test.js new file mode 100644 index 0000000..4311aba --- /dev/null +++ b/test/module_analyser.test.js @@ -0,0 +1,3939 @@ +'use strict'; +const assert = require('assert'); + +const Analyser = require('../lib/module_analyser'); +const InterfaceAnalyser = require('../lib/interface_analyser'); +const Parser = require('../lib/parser'); +const Lexer = require('../lib/lexer'); +const Package = require('../lib/package'); + +function parse(source, pkg = new Package()) { + const lexer = new Lexer(source, '__filename'); + const parser = new Parser(lexer); + const ast = parser.program(); + const name = ast.name.lexeme; + pkg.components.set(name, { + type: 'module', + ast, + ctx: { + source, + filename: '__filename' + } + }); + const anlyser = new Analyser({ source, filename: '__filename' }, pkg); + anlyser.check(ast); + pkg.components.get(name).analyser = anlyser; + anlyser.checkMethods(ast); + return ast; +} + +function addComponent(pkg, source, filename) { + const lexer = new Lexer(source, filename); + const parser = new Parser(lexer); + const ast = parser.program(); + const name = ast.name.lexeme; + pkg.components.set(name, { + type: ast.type, + ast, + ctx: { + source: lexer.source, + filename: lexer.filename + }, + }); + if (ast.type === 'module') { + const analyser = new Analyser({ source, filename: '__filename' }, pkg); + analyser.check(ast); + pkg.components.get(name).analyser = analyser; + } + + if (ast.type === 'interface') { + const analyser = new InterfaceAnalyser({ source, filename: '__filename' }, pkg); + analyser.check(ast); + pkg.components.get(name).analyser = analyser; + } +} + +describe('module analyser', function () { + describe('import', function () { + it('import undefined package should not ok', function () { + assert.throws(() => { + parse(`import $std; module M {}`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' not defined in Darafile`); + return true; + }); + }); + + it('re-import package should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + pkg.libraries.set('$std', new Package()); + parse(`import $std; import $std; module M { }`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package id '$std' has been imported`); + return true; + }); + }); + + it('un-imported package should not ok', function () { + assert.throws(() => { + parse(`module M extends $std.M {}`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' is un-imported`); + return true; + }); + }); + }); + + describe('extends', function () { + it('extends undefined extern module should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + pkg.libraries.set('$std', new Package()); + parse(` + import $std; + module M extends $std.M {}`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'M' is undefined in '$std'`); + return true; + }); + }); + + it('extends from extern model should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + const $std = new Package(); + addComponent($std, `model M {}`, '__filename'); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M extends $std.M {}`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'$std.M' is not a module`); + return true; + }); + }); + + it('extends extern module should ok', function () { + const pkg = new Package(); + const $std = new Package(); + addComponent($std, `module M {}`, '__filename'); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M extends $std.M {}`, pkg); + }); + + it('extends undefined module should not ok', function () { + assert.throws(() => { + parse(`module M extends N {}`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the type 'N' is undefined`); + return true; + }); + }); + + it('extends model should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + addComponent(pkg, `model N {}`, '__filename'); + parse(`module M extends N {}`, pkg); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'N' is not a module`); + return true; + }); + }); + + it('extends self module should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + addComponent(pkg, `module M extends M {}`, '__filename'); + // const parser = new Parser(lexer); + // const ast = parser.program(); + // pkg.components.set('M', { + // type: 'module', + // ast, + // ctx: { + // source: lexer.source, + // filename: lexer.filename + // } + // }); + parse(`module M extends M;`, pkg); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'M' can not extends itself`); + return true; + }); + }); + + it('extends module should ok', function () { + const pkg = new Package(); + addComponent(pkg, `module N {}`, '__filename'); + parse(`module M extends N {}`, pkg); + }); + }); + + describe('implements', function () { + it('implements undefined extern module should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + pkg.libraries.set('$std', new Package()); + parse(` + import $std; + module M implements $std.M {}`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'M' is undefined in '$std'`); + return true; + }); + }); + + it('implements from extern model should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + const $std = new Package(); + addComponent($std, `model M {}`, '__filename'); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M implements $std.M {}`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'$std.M' is not a interface`); + return true; + }); + }); + + it('implements extern inteface should ok', function () { + const pkg = new Package(); + const $std = new Package(); + addComponent($std, `interface M {}`, '__filename'); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M implements $std.M {}`, pkg); + }); + + it('implements undefined interface should not ok', function () { + assert.throws(() => { + parse(`module M implements N {}`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the type 'N' is undefined`); + return true; + }); + }); + + it('implements model should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + addComponent(pkg, `model N {}`, '__filename'); + parse(`module M implements N {}`, pkg); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'N' is not a interface`); + return true; + }); + }); + + it('implements interface should ok', function () { + const pkg = new Package(); + addComponent(pkg, `interface N {}`, '__filename'); + parse(`module M implements N {}`, pkg); + }); + + it('duplicate implements interface should ok', function () { + const pkg = new Package(); + addComponent(pkg, `interface N {}`, '__filename'); + assert.throws(() => { + parse(`module M implements N, N {}`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `duplicate interface`); + return true; + }); + }); + + it('implements interface(with methods) should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + interface N { + function hello(): void; + }`, '__filename'); + assert.throws(() => { + parse(`module M implements N {}`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `must implement method N.hello()`); + return true; + }); + }); + + it('async mismatch should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + interface N { + function hello(): void; + }`, '__filename'); + assert.throws(() => { + parse(`module M implements N { + init() {} + async function hello(): void { + + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `the async modifier mismatched`); + return true; + }); + }); + + it('return type mismatch should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + interface N { + function hello(): void; + }`, '__filename'); + assert.throws(() => { + parse(`module M implements N { + init() {} + function hello(): string { + return ''; + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `the return type mismatched, expect: void, but string`); + return true; + }); + }); + + it('parameters mismatch should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + interface N { + function hello(key: string, value: string): void; + }`, '__filename'); + assert.throws(() => { + parse(`module M implements N { + init() {} + function hello(key: string): void { + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `the parameter types mismatched, expect hello(string, string), but hello(string)`); + return true; + }); + }); + + it('parameter types mismatch should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + interface N { + function hello(value: string): void; + }`, '__filename'); + assert.throws(() => { + parse(`module M implements N { + init() {} + function hello(key: boolean): void { + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `the parameter types mismatched, expect hello(string), but hello(boolean)`); + return true; + }); + }); + }); + + describe('define type', function () { + it('redefine type should not ok', function () { + assert.throws(() => { + parse(` + module M { + type @id = string + type @id = string + }`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `redefined type '@id'`); + return true; + }); + }); + + it('type should ok', function () { + parse(` + module M { + type @id = string + }`); + }); + }); + + describe('define init', function () { + it('no init should ok', function () { + parse(`module M { }`); + }); + + it('no init with instance method should not ok', function () { + assert.throws(function () { + parse(` + module M { + function test(): void; + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `must have a init when there is a non-static function`); + return true; + }); + }); + + it('init without body should ok', function () { + parse(`module M { init(); }`); + }); + + it('more than one init should not ok', function () { + assert.throws(function () { + parse(` + module M { + init(); + init(); + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `only one init can be allowed.`); + return true; + }); + }); + + it('init body should ok', function () { + parse(` + module M { + init() { + } + }`); + }); + }); + + describe('define methods', function () { + it('redefine function should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + function getId(): string { + } + function getId(): string { + } + }`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `redefined function 'getId'`); + return true; + }); + }); + + it('duplicate parameter name should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + function call(a: string, a: string): void { + } + }`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `redefined parameter 'a'`); + return true; + }); + }); + + it('define function should ok', function () { + parse(` + module M { + init() {} + function getId(): string { + return ''; + } + }`); + }); + }); + + describe('statements', function () { + describe('return', function () { + it('return void should ok', function () { + parse(` + module M { + init() {} + function call(): void { + return; + } + }`); + }); + + it('only can return void in init', function () { + parse(` + module M { + init() { + return; + } + }`); + + assert.throws(() => { + parse(` + module M { + init() { + return ''; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `should not have return value in init method`); + return true; + }); + }); + + it('mismatch type should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + function call(): void { + return ''; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the return type is not expected, expect: void, actual: string`); + return true; + }); + }); + }); + + describe('declare', function () { + it('duplicated variable should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + var id = "id"; + var id = "id"; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the id 'id' was defined`); + return true; + }); + }); + + it('declare null without type should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + var id = null; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `must declare type when value is null`); + return true; + }); + }); + + it('declare null with type should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + init() {} + function call(): void { + var id: string = null; + id; + } + }`); + }); + }); + + it('mismatch type should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + var id: boolean = ''; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `declared variable with mismatched type, expected: boolean, actual: string`); + return true; + }); + }); + + it('match type should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + init() {} + function call(): void { + var id: boolean = true; + id; + } + }`); + }); + }); + }); + + describe('assign', function () { + describe('assign to variable', function () { + it('undefined variable should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + id = ''; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `id 'id' undefined`); + return true; + }); + }); + + it('mismatch type should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + var id: boolean = true; + id = ''; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `can not assign string to boolean`); + return true; + }); + }); + + it('match type should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + init() {} + function call(): void { + var id: boolean = true; + id = false; + } + }`); + }); + }); + }); + + describe('assign to property', function () { + it('undefined property should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + @id = ''; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the property '@id' is undefined`); + return true; + }); + }); + + it('mismatch type should not ok', function () { + assert.throws(function () { + parse(` + module M { + type @id = boolean; + init() {} + function call(): void { + @id = ''; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `can not assign string to boolean`); + return true; + }); + }); + + it('match type should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + type @id = boolean; + init() {} + function call(): void { + @id = false; + } + }`); + }); + }); + }); + + describe('assign to model field', function () { + it('assign to undefined model field should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Model {}`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init(); + function call(): void { + var m = new Model{}; + m.N = ''; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the field 'N' is undefined in model Model`); + return true; + }); + }); + + it('assign to model field with mismatch type should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Model { N: boolean }`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + var m = new Model{}; + m.N = ''; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `can not assign string to boolean`); + return true; + }); + }); + + it('assign to model field should ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Model { N: boolean }`, '__filename'); + + parse(` + module M { + init() {} + function call(): void { + var m = new Model{}; + m.N = true; + } + }`, pkg); + }); + }); + + describe('assign to map value', function () { + + }); + + describe('assign to array item', function () { + it('assign a value to array should be ok', function () { + parse(` + module M { + static function main(): void { + var configs = [1,2,3]; + configs[3] = 4; + } + }`); + }); + + it('multi-dimentional array assign check should be ok', function () { + parse(` + module M { + static function main(): void { + var mulArr: [[ string ]] = [ ['string'],['string'] ]; + mulArr[0][0] = 'string2'; + } + }`); + }); + }); + }); + + describe('if', function () { + it('if should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + init() {} + function call(): void { + if (true) { + + } + } + }`); + }); + + assert.doesNotThrow(function () { + parse(` + module M { + init() {} + function call(): void { + if (true) { + + } else { + + } + } + }`); + }); + }); + }); + + describe('throw', function () { + it('throw should be ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + init() {} + function call(): void { + throw {}; + } + }`); + }); + }); + }); + + describe('while', () => { + it('while condition must be a boolean expr', function () { + assert.throws(function () { + parse(` + module M { + static function main(): void { + while (123) { + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the condition expr must be boolean type'); + return true; + }); + }); + + it('condition is boolean should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + static function test(): void { + while (true) { + } + } + }`); + }); + }); + }); + + describe('for of', function () { + it('the list is not array type should not ok', function () { + assert.throws(function () { + parse(` + module M { + static function test(): void { + for (var i of 123) { + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the list in for must be array type'); + return true; + }); + }); + + it('the list is array type should be ok', function () { + parse(` + module M { + static function test(): void { + for (var i of []) { + } + } + }`); + }); + }); + + describe('for', function () { + it('for with empty should be ok', function () { + parse(` + module M { + static function test(): void { + for ( ; ; ) { + } + } + }`); + }); + + it('test expr is non-boolean should not ok', function () { + assert.throws(function () { + parse(` + module M { + static function call(): void { + + } + static function test(): void { + for ( ; call(); ) { + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the test expr must be boolean type'); + return true; + }); + }); + + it('init expr(declare) should ok', function () { + parse(` + module M { + static function test(): void { + for (var i = 0; ; ) { + i; + } + } + }`); + + assert.throws(function () { + parse(` + module M { + static function test(): void { + for (var i: string = 0; ; ) { + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: string, actual: integer'); + return true; + }); + }); + + it('init expr(declare with null) should not ok', function () { + assert.throws(function () { + parse(` + module M { + static function test(): void { + for (var i = null; ; ) { + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'must declare type when value is null'); + return true; + }); + + parse(` + module M { + static function test(): void { + for (var i: int32 = null; ; ) { + i; + } + } + }`); + }); + + it('init expr(assign) should ok', function () { + parse(` + module M { + static function test(): void { + var i = 0; + for (i = 0; ; ) { + i; + } + } + }`); + }); + + it('test expr should ok', function () { + parse(` + module M { + static function test(): void { + var i = 0; + for (i = 0; i < 10; ) { + i; + } + } + }`); + + assert.throws(function () { + parse(` + module M { + static function call(): void { + + } + static function test(): void { + var i = 0; + for (i = 0; i < 'string'; ) { + i; + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the right expr type(string) mismatch with left expr type(integer)'); + return true; + }); + }); + }); + + describe('break', function () { + it('while with break should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + static function test(): void { + while (true) { + break; + } + } + }`); + }); + }); + + it('for with break should be ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + static function test(): void { + for (var i of []) { + } + } + }`); + }); + }); + }); + + describe('try', function () { + it('try/catch should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + static function func(): void { + try { + } catch(ex) { + } + } + }`); + }); + }); + + it('try/finally should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + static function func(): void { + try { + } finally { + } + } + }`); + }); + }); + + it('try/catch/finally should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + static function func(): void { + try { + } catch (ex) { + } finally { + } + } + }`); + }); + }); + }); + + describe('expr', function () { + it('$pkg should not ok', function () { + const $std = new Package(); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + static function func(): void { + $std; + } + }`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'invalid expression'); + return true; + }); + }); + + it('Module should not ok', function () { + assert.throws(function () { + parse(` + module M { + static function func(): void { + M; + } + }`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'invalid expression'); + return true; + }); + }); + + it('Model should not ok', function () { + const pkg = new Package(); + addComponent(pkg, 'model X {}', '__filename'); + assert.throws(function () { + parse(` + module M { + static function func(): void { + X; + } + }`, pkg); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'invalid expression'); + return true; + }); + }); + }); + }); + + describe('sample expressions', function () { + it('string should ok', function () { + parse(` + module M { + init() {} + function call(): void { + 'string'; + } + }`); + }); + + it('number should ok', function () { + parse(` + module M { + init() {} + function call(): void { + 123; + } + }`); + }); + + it('boolean should ok', function () { + parse(` + module M { + init() {} + function call(): void { + true; + } + }`); + }); + + it('null should ok', function () { + parse(` + module M { + init() {} + function call(): void { + null; + } + }`); + }); + + it('template_string should ok', function () { + parse(` + module M { + init() {} + function call(): void { + \`\`; + } + }`); + + parse(` + module M { + init() {} + function call(): void { + \`abc\${'abc'}\`; + } + }`); + }); + + it('array should ok', function () { + parse(` + module M { + init() {} + function call(): void { + []; + } + }`); + + parse(` + module M { + init() {} + function call(): void { + [1]; + } + }`); + }); + + it('access array should ok', function () { + parse(` + module M { + static function call(): void { + var m = [[]]; + m[123]; + m[123][456]; + } + }`); + }); + + it('map should ok', function () { + parse(` + module M { + init() {} + function call(): void { + {}; + } + }`); + + parse(` + module M { + init() {} + function call(): void { + { + 'key' = 'value' + }; + } + }`); + + parse(` + module M { + init() {} + function call(): void { + { + ...{ + } + }; + } + }`); + + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + { + ...123 + }; + } + }`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'can not expand non-map expression'); + return true; + }); + }); + }); + + describe('complex expressions', function () { + describe('construct model', function () { + it('undefined model should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Model{}; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the type 'Model' is undefined`); + return true; + }); + }); + + it('module should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `module Model {}`, '__filename'); + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Model{}; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'Model' is not a model`); + return true; + }); + }); + + it('defined model should ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Model {}`, '__filename'); + + parse(` + module M { + init() {} + function call(): void { + new Model{}; + } + }`, pkg); + }); + + it('assign to undefined model field should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Model {}`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Model{ + N = '' + }; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the field 'N' is undefined in model 'Model'`); + return true; + }); + }); + + it('assign to model field with mismatch type should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Model { N: boolean }`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Model{ + N = '' + }; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the field type are mismatched. expected boolean, but string`); + return true; + }); + }); + + it('assign to model field should ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Model { N: boolean }`, '__filename'); + + assert.doesNotThrow(function () { + parse(` + module M { + init() {} + function call(): void { + new Model{ + N = true + }; + } + }`, pkg); + }); + }); + }); + + describe('construct module', function () { + it('undefined module should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Module(); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the type 'Module' is undefined`); + return true; + }); + }); + + it('model should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Module {}`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Module(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'Module' is not a module`); + return true; + }); + }); + + it('no init module should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `module Module {}`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Module(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the module 'Module' has no init`); + return true; + }); + }); + + it('init with mismatch types should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + module Module { + init(a: string) { + + } + }`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Module(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the parameter types are mismatched. expected new Module(string), but new Module()`); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new Module(true); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the parameter types are mismatched. expected new Module(string), but new Module(boolean)`); + return true; + }); + }); + + it('defined module should ok', function () { + const pkg = new Package(); + addComponent(pkg, `module Module { init() {} }`, '__filename'); + parse(` + module M { + init() {} + function call(): void { + new Module(); + } + }`, pkg); + }); + }); + + describe('call in module', function () { + it('call undefined function should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + function callId(): string { + return callx(); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the function 'callx' is undefined`); + return true; + }); + }); + + it('call with mismatch types should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + function callx(x: boolean): void { + } + function callId(): void { + callx('hehe'); + } + }`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the parameter types are mismatched. expected callx(boolean), but callx(string)`); + return true; + }); + }); + + it('call function should be ok', function () { + assert.doesNotThrow(() => { + parse(` + module M { + init() {} + function callx(x: boolean): void { + } + function callId(): void { + callx(true); + } + }`); + }); + }); + + it('async function in sync function should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + async function af(): void { + } + function sf(): void { + return af(); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the async function only can be used in async function`); + return true; + }); + }); + + it('sync function in async function should ok', function () { + parse(` + module M { + init() {} + function sf(): void { + } + async function af(): void { + return sf(); + } + }`); + }); + + it('instance function in static function should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function ifun(): void { + } + static function sfun(): void { + return ifun(); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the function can not be used in static function`); + return true; + }); + }); + + it('static function in instance function should ok', function () { + parse(` + module M { + init() {} + static function sf(): void { + } + function af(): void { + return sf(); + } + }`); + }); + }); + + describe('inline call in module', function () { + describe('#append', function () { + it('#append with invalid length should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var list: [string] = []; + #append(list, 'item', 'item2'); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the #append parameter length expect 2, but 3`); + return true; + }); + }); + + it('#append with invalid type should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var list: map[string]string = {}; + #append(list, 'item'); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `must be array type`); + return true; + }); + + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var list: [string] = []; + #append(list, 1); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the item type is not match with list`); + return true; + }); + }); + + it('#append should ok', function () { + parse(` + module M { + init() {} + static function test(): void { + var list: [string] = []; + #append(list, 'item'); + } + }`); + }); + }); + + describe('#delete', function () { + it('#delete with invalid length should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var m: map[string]string = {}; + #delete(m, 'item', 'item2'); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the #delete parameter length expect 2, but 3`); + return true; + }); + }); + + it('#delete with invalid type should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var m: [string] = []; + #delete(m, 'item'); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `must be map type`); + return true; + }); + + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var m: map[string]string = {}; + #delete(m, 1); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `must be string type`); + return true; + }); + }); + + it('#delete should ok', function () { + parse(` + module M { + init() {} + static function test(): void { + var m: map[string]string = {}; + #delete(m, 'item'); + } + }`); + }); + }); + + describe('#length', function () { + it('#length with invalid length should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var list: [string] = []; + #length(list, 1); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the #length parameter length expect 1, but 2`); + return true; + }); + }); + + it('#length with invalid type should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var m: map[string]string = {}; + #length(m); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `must be array type`); + return true; + }); + }); + + it('#length should ok', function () { + parse(` + module M { + init() {} + static function test(): void { + var list: [string] = []; + #length(list); + } + }`); + }); + }); + + describe('#unsupport', function () { + it('#unsupport should not ok', function () { + assert.throws(() => { + parse(` + module M { + init() {} + static function test(): void { + var m: map[string]string = {}; + #unsupport(m, 'item', 'item2'); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `un-supported inline call(#unsupport)`); + return true; + }); + }); + }); + }); + + describe('static call with another module', function () { + it('call undefined method should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `module M1 {}`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init(); + function call(): void { + M1.test(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the method 'test' is undefined in module 'M1'`); + return true; + }); + }); + + it('call non-static method should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + module M1 { + init() {} + function test(): void { + + } + }`, '__filename'); + + assert.throws(function () { + parse(` + module M { + init(); + function call(): void { + M1.test(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'M1.test' is not static method`); + return true; + }); + }); + + it('call static method should be ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + module M1 { + init() {} + static function test(): void { + } + }`, '__filename'); + + parse(` + module M { + init(); + function call(): void { + M1.test(); + } + }`, pkg); + }); + }); + + describe('instance call with another module', function () { + it('call instance method with undefined method should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `module Module { init() {} }`, '__filename'); + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + var m = new Module(); + m.create('name'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the method 'create' is undefined in module 'Module'`); + return true; + }); + }); + + it('call instance method with static method should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + module Module { + init() {} + static function create(b: boolean): void { + } + }`, '__filename'); + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + var m = new Module(); + m.create('name'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'Module.create' is static method`); + return true; + }); + }); + + it('call instance method with mismatched types should not ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + module Module { + init() {} + function create(b: boolean): void { + } + } + `, '__filename'); + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + var m = new Module(); + m.create('name'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the parameter types are mismatched. expected m.create(boolean), but m.create(string)`); + return true; + }); + }); + + it('call instance method should ok', function () { + const pkg = new Package(); + addComponent(pkg, ` + module Module { + init() {} + function create(b: boolean): void { + } + }`, '__filename'); + + parse(` + module M { + init() {} + function call(): void { + var m = new Module(); + m.create(true); + } + }`, pkg); + }); + }); + + describe('and/or expr', function () { + it('should check and/or expr', function () { + parse(` + module M { + static function less(a: int32, b: int32): boolean; + static function great(a: int32, b: int32): boolean; + static function between(input: int32, min: int32, max: int32): string { + if (great(input, min) && less(input, max)) { + return "yes"; + } + return "no"; + } + }`); + + assert.doesNotThrow(function () { + parse(` + module M { + static function is(a: int32, b: int32): boolean; + static function oneOf(input: int32, min: int32, max: int32): string { + if (is(input, max) || is(input, min)) { + return "yes"; + } + return "no"; + } + }`); + }); + + assert.throws(function () { + parse(` + module M { + static function oneOf(input: int32, min: int32, max: int32): string { + if ("string1" || "string2") { + return "yes"; + } + return "no"; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the left expr must be boolean type'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + static function oneOf(input: int32, min: int32, max: int32): string { + if (true || "string2") { + return "yes"; + } + return "no"; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the right expr must be boolean type'); + return true; + }); + }); + }); + + describe('not expr', function () { + it('not expr should ok', function () { + assert.doesNotThrow(function () { + parse(` + module M { + static function callOSS(): boolean { + return !true; + } + }`); + }); + + assert.throws(function () { + parse(` + module M { + static function callOSS(): boolean { + return !'string'; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the expr after ! must be boolean type'); + return true; + }); + }); + }); + + describe('property', function () { + it('access non-model/module/package with . should not ok', function () { + assert.throws(function () { + parse(` + module M { + static function hello(): void { + var a = { + }; + a.b; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `only can use '.' after a`); + return true; + }); + + assert.throws(() => { + parse(` + module M { + static function hello(a: string): void { + a.b; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `only can use '.' after a`); + return true; + }); + }); + + it('access model.x should not ok', function () { + const pkg = new Package(); + addComponent(pkg, 'model X {}', '__filename'); + assert.throws(function () { + parse(` + module M { + static function hello(): void { + X.hehe; + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `only can use '.' after X`); + return true; + }); + }); + }); + + describe('member', function () { + it('map access should be ok', function () { + assert.throws(function () { + parse(` + module M { + static function hello(): void { + var a = {}; + var key = 1; + a[key]; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the expr must be string type for map'); + return true; + }); + + parse(` + module M { + static function hello(): void { + var a = {}; + var key = 'str'; + a[key]; + } + }`); + + parse(` + module M { + static function hello(): void { + var a = { + 'b' = {} + }; + var key = 'str'; + a['b'][key]; + } + }`); + }); + + it('array access should be ok', function () { + assert.throws(function () { + parse(` + module M { + static function hello(): void { + var a = [1, 2, 3]; + var key = 'str'; + a[key]; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the expr must be integer type for array'); + return true; + }); + + parse(` + module M { + static function hello(): void { + var a = [1, 2, 3]; + var key = 1; + a[key]; + } + }`); + }); + + it('use [] with non-array/map should not ok', function () { + assert.throws(function () { + parse(` + module M { + static function hello(): void { + var a = 'hehe'; + var key = 'str'; + a[key]; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'the [] form only support map or array type'); + return true; + }); + }); + }); + + describe('to expr', function () { + it('left expr must be map', function () { + assert.throws(function () { + const pkg = new Package(); + addComponent(pkg, `model Model {}`, '__filename'); + parse(` + module M { + static function callOSS(): boolean { + return 123 to Model; + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'only map can work with model'); + return true; + }); + }); + + it('to type must be model', function () { + assert.throws(function () { + const pkg = new Package(); + addComponent(pkg, `module N {}`, '__filename'); + parse(` + module M { + static function callOSS(): boolean { + return {} to N; + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `'N' is not a model`); + return true; + }); + }); + + it('to expr should ok', function () { + const pkg = new Package(); + addComponent(pkg, `model Model {}`, '__filename'); + parse(` + module M { + static function call(): Model { + return {} to Model; + } + }`, pkg); + }); + }); + }); + + describe('static method & property', function () { + it('static method with property should not ok', function () { + assert.throws(function () { + parse(` + module M { + type @call = void; + static function func(): void { + return @call; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the module property can not used in static function`); + return true; + }); + }); + }); + + describe('super', function () { + it('super not in init should not ok', function () { + assert.throws(function () { + parse(` + module M { + static function hello(): void { + super(); + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'super only allowed in init method'); + return true; + }); + }); + + it('super without parent module should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() { + super(); + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'this module have no parent module'); + return true; + }); + }); + + it('parent module have no init should not ok', function () { + const pkg = new Package(); + addComponent(pkg, 'module N {}', '__filename'); + + assert.throws(function () { + parse(` + module M extends N { + init() { + super(); + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `the parent module 'N' have no init method`); + return true; + }); + }); + + it('super with mismatched types should not ok', function () { + const pkg = new Package(); + addComponent(pkg, 'module N { init(a: string) {} }', '__filename'); + + assert.throws(function () { + parse(` + module M extends N { + init() { + super(true); + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `the parameter types are mismatched. expected N(string), but N(boolean)`); + return true; + }); + }); + + it('super should ok', function () { + const pkg = new Package(); + addComponent(pkg, 'module N { init() {} }', '__filename'); + + parse(` + module M extends N { + init() { + super(); + } + }`, pkg); + }); + }); + + describe('__this', function () { + it('__this in function should ok', function () { + parse(` + module M { + init() {} + function func(): M { + return __this; + } + }`); + }); + + it('__this in static function should not ok', function () { + assert.throws(function () { + parse(` + module M { + static function func(): M { + return __this; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `id '__this' undefined`); + return true; + }); + }); + }); + + describe('type inferred', function () { + it('inferred for map should ok', function () { + function getMapInferred(expr) { + const ast = parse(` + module M { + static function hello(): void { + var a = ${expr}; + a; // use it + } + }`); + const [f1] = ast.moduleBody.nodes; + const [s1] = f1.functionBody.stmts.stmts; + return s1.expr.inferred; + } + + assert.deepStrictEqual(getMapInferred('{}'), { + 'keyType': { + 'name': 'string', + 'type': 'basic' + }, + 'type': 'map', + 'valueType': { + 'name': 'any', + 'type': 'basic' + } + }); + + assert.deepStrictEqual(getMapInferred(`{'key' = 'value'}`), { + 'keyType': { + 'name': 'string', + 'type': 'basic' + }, + 'type': 'map', + 'valueType': { + 'name': 'string', + 'type': 'basic' + } + }); + + assert.deepStrictEqual(getMapInferred(`{'key' = 'value', 'key2' = 1}`), { + 'keyType': { + 'name': 'string', + 'type': 'basic' + }, + 'type': 'map', + 'valueType': { + 'name': 'any', + 'type': 'basic' + } + }); + + function getMapInferredEx(expr1, expr2) { + const ast = parse(` + module M { + static function hello(): void { + var a = ${expr1}; + var b = ${expr2}; + a; b; // use them + } + }`); + const [f1] = ast.moduleBody.nodes; + const [ , s2] = f1.functionBody.stmts.stmts; + return s2.expr.inferred; + } + assert.deepStrictEqual(getMapInferredEx(`{'key' = 'value'}`, `{'key2' = 123, ...a}`), { + 'keyType': { + 'name': 'string', + 'type': 'basic' + }, + 'type': 'map', + 'valueType': { + 'name': 'any', + 'type': 'basic' + } + }); + }); + + it('inferred for array should ok', function () { + function getArrayInferred(expr) { + const ast = parse(` + module M { + static function hello(): void { + var a = ${expr}; + a; + } + }`); + const [f1] = ast.moduleBody.nodes; + const [s1] = f1.functionBody.stmts.stmts; + return s1.expr.inferred; + } + + assert.deepStrictEqual(getArrayInferred('[]'), { + 'type': 'array', + 'itemType': { + 'name': 'any', + 'type': 'basic' + } + }); + + assert.deepStrictEqual(getArrayInferred(`['value']`), { + 'type': 'array', + 'itemType': { + 'name': 'string', + 'type': 'basic' + } + }); + + assert.deepStrictEqual(getArrayInferred(`['value', 1]`), { + 'type': 'array', + 'itemType': { + 'name': 'any', + 'type': 'basic' + } + }); + }); + }); + + describe('assignable', function () { + it('var arr: [ string ] = [] should ok', function () { + parse(` + module M { + init() { + var empty: [string] = []; + } + }`); + }); + + it('var arr: [ string ] = [1] should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() { + var empty: [string] = [1]; + } + }`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'declared variable with mismatched type, expected: [string], actual: [integer]'); + return true; + }); + }); + + it('var arr: map[string]string = {} should ok', function () { + parse(` + module M { + init() { + var empty: map[string]string = {}; + } + }`); + }); + + it('var arr: map[string]string = { number } should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() { + var empty: map[string]string = { + 'key' = 1 + }; + } + }`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'declared variable with mismatched type, expected: map[string]string, actual: map[string]integer'); + return true; + }); + }); + + it('number type check in assign expr should ok', function () { + parse(` + module M { + init() { + var num: int32 = 123; + } + }`); + + parse(` + module M { + init() { + var num: [ integer ] = [ 123 ]; + } + }`); + + parse(` + module M { + init() { + var num: map[ string ] integer = { + 'val' = 123 + }; + } + }`); + + parse(` + module M { + init() { + var intNum: integer = 123; + } + }`); + + parse(` + module M { + init() { + var intArr: [ integer ] = [ 123 ]; + } + }`); + + parse(` + module M { + init() { + var intMap: map[string]integer = { + 'val' = 123 + }; + } + }`); + + parse(` + module M { + init() { + var int8Num: int8 = 123; + } + }`); + + parse(` + module M { + init() { + var uint8Num: uint8 = 123; + } + }`); + + parse(` + module M { + init() { + var int16Num: int16 = 123; + } + }`); + + parse(` + module M { + init() { + var uint16Num: uint16 = 123; + } + }`); + + parse(` + module M { + init() { + var int32Num: int32 = 123; + } + }`); + + parse(` + module M { + init() { + var uint32Num: uint32 = 123; + } + }`); + + parse(` + module M { + init() { + var int64Num: int64 = 123; + } + }`); + + parse(` + module M { + init() { + var uint64Num: uint64 = 123; + } + }`); + + parse(` + module M { + init() { + var longNum: long = 123L; + } + }`); + + parse(` + module M { + init() { + var longArr: [ long ] = [ 123L ]; + } + }`); + + parse(` + module M { + init() { + var longMap: map[string] long = { + 'val' = 123L + }; + } + }`); + + parse(` + module M { + init() { + var ulongNum: ulong = 123L; + } + }`); + + parse(` + module M { + init() { + var longNum: long = 123; + } + }`); + + parse(` + module M { + init() { + var ulongNum: ulong = 123; + } + }`); + + parse(` + module M { + init() { + var floatNum: float = 1.23; + } + }`); + + parse(` + module M { + init() { + var floatArr: [ float ] = [ 1.23 ]; + } + }`); + + parse(` + module M { + init() { + var floatMap: map[string] float = { + 'val' = 1.23 + }; + } + }`); + + parse(` + module M { + init() { + var doubleNum: double = 1.23d; + } + }`); + + assert.throws(function () { + parse(` + module M { + init() { + var intNum: integer = 1.23; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: integer, actual: float'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var intNum: integer = 1.23d; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: integer, actual: double'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var intNum: integer = 123L; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: integer, actual: long'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var floatNum: float = 123; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: float, actual: integer'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var floatNum: float = 1.23d; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: float, actual: double'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var floatNum: float = 123L; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: float, actual: long'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var doubleNum: double = 1.23; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: double, actual: float'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var doubleNum: double = 123; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: double, actual: integer'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var doubleNum: double = 123L; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: double, actual: long'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var longNum: long = 1.23; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: long, actual: float'); + return true; + }); + + assert.throws(function () { + parse(` + module M { + init() { + var longNum: long = 1.23d; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: long, actual: double'); + return true; + }); + }); + + it('assign to any should ok', function () { + parse(` + module M { + init() { + var num: any = 123; + } + }`); + }); + + it('assign null should ok', function () { + parse(` + module M { + init() { + var str: string = ''; + str = null; + } + }`); + }); + + it('assign to same model should ok', function () { + const pkg = new Package(); + addComponent(pkg, `model X {}`, '__filename'); + parse(` + module M { + init() { + var x: X = new X{}; + } + }`, pkg); + }); + + it('assign model to string should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `model X {}`, '__filename'); + assert.throws(function () { + parse(` + module M { + init() { + var str: string = new X{}; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `declared variable with mismatched type, expected: string, actual: X`); + return true; + }); + }); + + it('assign array to string should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() { + var str: string = []; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `declared variable with mismatched type, expected: string, actual: [any]`); + return true; + }); + }); + + it('assign map to string should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() { + var str: string = {}; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `declared variable with mismatched type, expected: string, actual: map[string]any`); + return true; + }); + }); + + it('assign model instance to model should ok', function () { + const pkg = new Package(); + addComponent(pkg, `model X {}`, '__filename'); + parse(` + module M { + init() { + var str: X = new X{}; + } + }`, pkg); + }); + + it('assign module instance to string should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `module X { init() {} }`, '__filename'); + assert.throws(function () { + parse(` + module M { + init() { + var str: string = new X(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `declared variable with mismatched type, expected: string, actual: X`); + return true; + }); + }); + + it('assign module instance to module should ok', function () { + const pkg = new Package(); + addComponent(pkg, `module X { init() {} }`, '__filename'); + parse(` + module M { + init() { + var str: X = new X(); + } + }`, pkg); + }); + + it('assign sub-module to module should not ok', function () { + const pkg = new Package(); + addComponent(pkg, `module X { init() {} }`, '__filename'); + addComponent(pkg, `module Y extends X { init() {} }`, '__filename'); + assert.throws(() => { + parse(` + module M { + init() { + var str: X = new Y(); + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'declared variable with mismatched type, expected: X, actual: Y'); + return true; + }); + }); + + it('assign model to $model should ok', function () { + const pkg = new Package(); + addComponent(pkg, `model X {}`, '__filename'); + const $builtin = new Package(); + addComponent($builtin, `model Model {}`, '__filename'); + pkg.libraries.set('$builtin', $builtin); + parse(` + import $builtin; + module M { + init() { + var str: $builtin.Model = new X{}; + } + }`, pkg); + }); + + it('assign model to model(extern model) should ok', function () { + const pkg = new Package(); + addComponent(pkg, `model X {}`, '__filename'); + const pkg2 = new Package(); + pkg2.libraries.set('$std', pkg); + parse(` + import $std; + module M { + init() { + var str: $std.X = new $std.X{}; + } + }`, pkg2); + }); + + it('assign module instance to interface should ok', function () { + const pkg = new Package(); + addComponent(pkg, `interface X { }`, '__filename'); + parse(` + module M implements X { + init() { + } + + static function test(): X { + return new M(); + } + }`, pkg); + }); + + it('assign module instance to interface(not implemented) should ok', function () { + const pkg = new Package(); + addComponent(pkg, `interface X { }`, '__filename'); + assert.throws(() => { + parse(` + module M { + init() { + } + + static function test(): X { + return new M(); + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `the return type is not expected, expect: X, actual: M`); + return true; + }); + }); + }); + + describe('call', function () { + it('call with vid should not ok', function () { + assert.throws(function () { + parse(` + module M { + type @test = string; + init(); + function call(): void { + @test(); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `can not call with property`); + return true; + }); + }); + + it('call with $package should not ok', function () { + const std = new Package(); + + assert.throws(function () { + const pkg = new Package(); + pkg.libraries.set('$std', std); + parse(` + import $std; + module M { + init(); + function call(): void { + $std(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `can not call with package`); + return true; + }); + }); + + it('call $pkg.test() should not ok', function () { + const $std = new Package(); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + $std.test(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `test is undefined in package '$std'`); + return true; + }); + }); + + it('call non-method should not ok', function () { + const pkg = new Package(); + addComponent(pkg, 'model X { Y: string }', '__filename'); + assert.throws(function () { + parse(` + module M { + type @id = X; + init() {} + function call(): void { + @id.Y(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `can not call with non-method`); + return true; + }); + }); + }); + + describe('module property', function () { + it('assign to vid should ok', function () { + parse(` + module M { + type @test = string; + init(); + function call(): void { + @test = 'hello world'; + } + }`); + }); + + it('assign to vid(defined in super module) should ok', function () { + const $std = new Package(); + addComponent($std, ` + module X { + type @test = string; + init() {} + }`,'__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module Y extends $std.X { + init() { + super(); + @test = 'hello world'; + } + }`, pkg); + }); + + it('assign to vid(defined in super-super module) should ok', function () { + const $std = new Package(); + addComponent($std, ` + module X { + type @test = string; + init() {} + }`,'__filename'); + addComponent($std, ` + module Y extends X { + init() {} + }`,'__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module Z extends $std.Y { + init() { + super(); + @test = 'hello world'; + } + }`, pkg); + }); + }); + + describe('use extern package', function () { + describe('call with extern package', function () { + it('call with undefined pkg should not ok', function () { + assert.throws(function () { + const pkg = new Package(); + parse(` + import $std; + module M { + init(); + function call(): void { + $std.M1.test(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' not defined in Darafile`); + return true; + }); + }); + + it('call with un-imported pkg should not ok', function () { + assert.throws(function () { + parse(` + module M { + init(); + function call(): void { + $std.M1.test(); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' is un-imported`); + return true; + }); + }); + + it('call with undefined module should not ok', function () { + const std = new Package(); + + assert.throws(function () { + const pkg = new Package(); + pkg.libraries.set('$std', std); + parse(` + import $std; + module M { + init(); + function call(): void { + $std.M1.test(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `M1 is undefined in package '$std'`); + return true; + }); + }); + + it('call with model should not ok', function () { + const std = new Package(); + addComponent(std, `model M1 {}`, '__filename'); + assert.throws(function () { + const pkg = new Package(); + pkg.libraries.set('$std', std); + parse(` + import $std; + module M { + init(); + function call(): void { + $std.M1.test(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `M1 is not a module`); + return true; + }); + }); + + it('call undefined method should not ok', function () { + const std = new Package(); + addComponent(std, `module M1 {}`, '__filename'); + + assert.throws(function () { + const pkg = new Package(); + pkg.libraries.set('$std', std); + parse(` + import $std; + module M { + init(); + function call(): void { + $std.M1.test(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the method 'test' is undefined in module '$std.M1'`); + return true; + }); + }); + + it('call non-static method should not ok', function () { + const std = new Package(); + addComponent(std, ` + module M1 { + init() {} + function test(): void {} + }`, '__filename'); + + assert.throws(function () { + const pkg = new Package(); + pkg.libraries.set('$std', std); + parse(` + import $std; + module M { + init(); + function call(): void { + $std.M1.test(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'$std.M1.test' is not static method`); + return true; + }); + }); + + it('call static method with mismatch types should not ok', function () { + const std = new Package(); + addComponent(std, ` + module M1 { + init() {} + function test(): void {} + }`, '__filename'); + + assert.throws(function () { + const pkg = new Package(); + pkg.libraries.set('$std', std); + parse(` + import $std; + module M { + init(); + function call(): void { + $std.M1.test(123); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'$std.M1.test' is not static method`); + return true; + }); + }); + + it('call static method should ok', function () { + const std = new Package(); + addComponent(std, ` + module M1 { + init() {} + static function test(): void {} + }`, '__filename'); + + const pkg = new Package(); + pkg.libraries.set('$std', std); + parse(` + import $std; + module M { + init(); + function call(): void { + $std.M1.test(); + } + }`, pkg); + }); + }); + + describe('construct extern module', function () { + it('construct undefined pkg should not ok', function () { + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Module(); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' not defined in Darafile`); + return true; + }); + }); + + it('construct un-imported pkg should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new $std.Module(); + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' is un-imported`); + return true; + }); + }); + + it('construct undefined module should not ok', function () { + const $std = new Package(); + assert.throws(function () { + const pkg = new Package(); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Module(); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'Module' is undefined in '$std'`); + return true; + }); + }); + + it('construct extern model should not ok', function () { + const $std = new Package(); + addComponent($std, `model Model {}`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Model('hello'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'$std.Model' is not a module`); + return true; + }); + }); + + it('construct extern module without init should not ok', function () { + const $std = new Package(); + addComponent($std, `module Module {}`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Module('hello'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the module '$std.Module' has no init`); + return true; + }); + }); + + it('construct extern module with mismatch types should not ok', function () { + const $std = new Package(); + addComponent($std, `module Module { init(ok: boolean) {} }`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Module('hello'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the parameter types are mismatched. expected new $std.Module(boolean), but new $std.Module(string)`); + return true; + }); + }); + + it('construct extern module should ok', function () { + const $std = new Package(); + addComponent($std, `module Module { init() {} }`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Module(); + } + }`, pkg); + }); + }); + + describe('call extern module method', function () { + it('undefined method should not ok', function () { + const $std = new Package(); + addComponent($std, `module Module { init() {} }`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + var m = new $std.Module(); + m.create('name'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the method 'create' is undefined in module '$std.Module'`); + return true; + }); + }); + + it('static method should not ok', function () { + const $std = new Package(); + addComponent($std, ` + module Module { + init() {} + static function create(): void { + } + }`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + var m = new $std.Module(); + m.create('name'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'$std.Module.create' is static method`); + return true; + }); + }); + + it('mismatch types should not ok', function () { + const $std = new Package(); + addComponent($std, ` + module Module { + init() {} + function create(b: boolean): void { + } + } + `, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + var m = new $std.Module(); + m.create('name'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the parameter types are mismatched. expected m.create(boolean), but m.create(string)`); + return true; + }); + }); + }); + + describe('super with extern module', function () { + it('mismatch types should not ok', function () { + const $std = new Package(); + addComponent($std, `module Module { init() {} }`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M extends $std.Module { + init() { + super('hello', 'world!'); + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the parameter types are mismatched. expected $std.Module(), but $std.Module(string, string)`); + return true; + }); + }); + + it('super with extern module should ok', function () { + const $std = new Package(); + addComponent($std, `module Module { init() {} }`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M extends $std.Module { + init() { + super(); + } + }`, pkg); + }); + }); + + describe('construct extern model', function () { + it('construct undefined pkg should not ok', function () { + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Model{}; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' not defined in Darafile`); + return true; + }); + }); + + it('construct un-imported pkg should not ok', function () { + assert.throws(function () { + parse(` + module M { + init() {} + function call(): void { + new $std.Model{}; + } + }`); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the package '$std' is un-imported`); + return true; + }); + }); + + it('construct undefined model should not ok', function () { + const $std = new Package(); + assert.throws(function () { + const pkg = new Package(); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Model{}; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'Model' is undefined in '$std'`); + return true; + }); + }); + + it('construct extern model should not ok', function () { + const $std = new Package(); + addComponent($std, `module Model {}`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Model{}; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `'$std.Model' is not a model`); + return true; + }); + }); + + it('construct extern model with mismatch types should not ok', function () { + const $std = new Package(); + addComponent($std, `model Model {}`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + assert.throws(function () { + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Model{ + x = '' + }; + } + }`, pkg); + }, function (e) { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `the field 'x' is undefined in model '$std.Model'`); + return true; + }); + }); + + it('construct extern model should ok', function () { + const $std = new Package(); + addComponent($std, `model Model {}`, '__filename'); + const pkg = new Package(); + pkg.libraries.set('$std', $std); + parse(` + import $std; + module M { + init() {} + function call(): void { + new $std.Model{}; + } + }`, pkg); + }); + }); + }); + + describe('no return check', function () { + it('void should ok', function () { + parse(` + module M { + init(); + function call(): void { + } + }`); + }); + + it('non-void should not ok', function () { + assert.throws(() => { + parse(` + module M { + init(); + function call(): string { + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'no return statement'); + return true; + }); + + assert.throws(() => { + parse(` + module M { + init(); + function call(): string { + '123'; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'no return statement'); + return true; + }); + }); + + it('check with branch should ok', function () { + assert.throws(() => { + parse(` + module M { + init(); + function call(): string { + if (true) { + + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'no return statement'); + return true; + }); + + assert.throws(() => { + parse(` + module M { + init(); + function call(): string { + if (true) { + return ''; + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'no return statement'); + return true; + }); + + assert.throws(() => { + parse(` + module M { + init(); + function call(): string { + if (true) { + return ''; + } else { + + } + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'no return statement'); + return true; + }); + + parse(` + module M { + init(); + function call(): string { + if (true) { + return ''; + } else { + return 'b'; + } + } + }`); + }); + + it('throws should ok', function () { + parse(` + module M { + init(); + function call(): string { + throw { + 'message' = 'hello' + }; + } + }`); + }); + }); + + describe('unreachable check', function () { + it('unreachable code should not ok', function () { + assert.throws(() => { + parse(` + module M { + init(); + function call(): void { + return; + true; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'unreachable code'); + return true; + }); + }); + + it('unreachable code with branches should not ok', function () { + assert.throws(() => { + parse(` + module M { + init(); + function call(): void { + if (true) { + return; + } else { + return; + } + true; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'unreachable code'); + return true; + }); + + parse(` + module M { + init(); + function call(): void { + if (true) { + return; + } else { + } + true; + } + }`); + }); + }); + + describe('unused variable check', function () { + it('unused variable should not ok', function () { + assert.throws(() => { + parse(` + module M { + init(); + function call(): void { + var x = 1; + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `unused variable 'x'`); + return true; + }); + }); + + it('used variable with map should ok', function () { + parse(` + module M { + init(); + function call(): void { + var x = {}; + var y = 'a'; + x[y]; + } + }`); + }); + }); + + describe('super call check', function () { + it('no super call should not ok', function () { + assert.throws(() => { + const pkg = new Package(); + addComponent(pkg, 'module X {}', '__filename'); + parse(` + module M extends X { + init() { + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `must contain 'super' call`); + return true; + }); + }); + + it('super call should ok', function () { + const pkg = new Package(); + addComponent(pkg, 'module X { init() {} }', '__filename'); + parse(` + module M extends X { + init() { + super(); + } + }`, pkg); + }); + + it('access property before super call should not ok', function () { + const pkg = new Package(); + addComponent(pkg, 'module X { init() {} }', '__filename'); + assert.throws(() => { + parse(` + module M extends X { + type @id = string; + init() { + @id = 'hello'; + super(); + } + }`, pkg); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, `'super' must be called before accessing property`); + return true; + }); + }); + + it('access property after super call should ok', function () { + const pkg = new Package(); + addComponent(pkg, 'module X { init() {} }', '__filename'); + parse(` + module M extends X { + type @id = string; + init() { + super(); + @id = 'hello'; + } + }`, pkg); + }); + }); +}); diff --git a/test/module_parser.test.js b/test/module_parser.test.js new file mode 100644 index 0000000..1d0ade6 --- /dev/null +++ b/test/module_parser.test.js @@ -0,0 +1,1961 @@ +'use strict'; + +const assert = require('assert'); + +const Parser = require('../lib/parser'); +const Lexer = require('../lib/lexer'); +const { WordToken, StringLiteral, NumberLiteral, Annotation, TemplateElement } = require('../lib/tokens'); + +function parse(source, filePath) { + const lexer = new Lexer(source, filePath); + const parser = new Parser(lexer); + return parser.program(); +} + +function pos(line, column) { + return { line, column }; +} + +function loc(startLine, startColumn, endLine, endColumn) { + return { + start: pos(startLine, startColumn), + end: pos(endLine, endColumn) + }; +} + +describe('module parser', function () { + it('empty module file should not ok', function () { + assert.throws(() => { + parse('', '__filename'); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: EOF. expect 'module', 'model', 'interface' or 'main'`); + return true; + }); + }); + + it('mini module should ok', function () { + assert.deepStrictEqual(parse('module M {}', '__filename'), { + annotation: undefined, + comments: new Map(), + implements: [], + extends: undefined, + imports: [], + moduleBody: { + nodes: [], + type: 'moduleBody', + tokenRange: [2, 3] + }, + name: new WordToken(2, 'M', loc(1, 8, 1, 9), 1), + tokenRange: [0, 4], + type: 'module' + }); + }); + + it('module annotation should be ok', function () { + var ast = parse(` + /** + * module annotation + */ + module M {} + `, '__filename'); + + assert.deepStrictEqual(ast.annotation, new Annotation('/**\n * module annotation\n */', loc(2, 5, 4, 8), 0)); + }); + + it('import with comma should ok', function () { + const ast = parse(`import $oss; + module M {}`, '__filename'); + assert.deepStrictEqual(ast.imports, [ + { + type: 'import', + aliasId: new WordToken(21, '$oss', loc(1, 8, 1, 12), 1), + tokenRange: [0, 3] + } + ]); + }); + + describe('extends', function () { + it('extends from module should ok', function () { + const ast = parse('module M extends Base {}', '__filename'); + assert.deepStrictEqual(ast.extends, new WordToken(2, 'Base', loc(1, 18, 1, 22), 3)); + }); + + it('extends from extern module should ok', function () { + const ast = parse('module M extends $oss.Base {}', '__filename'); + assert.deepStrictEqual(ast.extends, { + type: 'extern_component', + aliasId: new WordToken(21, '$oss', loc(1, 18, 1, 22), 3), + component: new WordToken(2, 'Base', loc(1, 23, 1, 27), 5), + loc: loc(1, 18, 1, 27), + tokenRange: [3, 6] + }); + }); + + it('extends from other should not ok', function () { + assert.throws(function () { + parse('module M extends 1 {}', '__filename'); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpected token: Number: 1. expect (extern) module/model/interface'); + return true; + }); + }); + }); + + it('only function/type/init should be ok', function () { + assert.throws(function () { + parse(` + module M { + public + } + `, '__filename'); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'Unexpected token: Word: `public`. expect "const", "type", "function" or "init"'); + return true; + }); + }); + + describe('type', function () { + function moduleBody(value) { + return parse(` + module M { + ${value} + } + `, '__filename').moduleBody.nodes; + } + + it('type should be ok', function () { + assert.throws(() => { + moduleBody('type'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect VID, but }`); + return true; + }); + + assert.throws(() => { + moduleBody('type @id'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect =, but }`); + return true; + }); + + assert.throws(() => { + moduleBody('type @id = '); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. expect base type, model id or array form`); + return true; + }); + }); + + it('type with string should ok', function () { + assert.deepStrictEqual(moduleBody('type @id = string'), [ + { + 'annotation': undefined, + 'type': 'type', + 'vid': new WordToken(3, '@id', loc(3, 14, 3, 17), 4), + 'value': new WordToken(8, 'string', loc(3, 20, 3, 26), 6), + 'tokenRange': [3, 7] + } + ]); + + // with ; should ok + assert.deepStrictEqual(moduleBody('type @id = string;'), [ + { + 'annotation': undefined, + 'type': 'type', + 'vid': new WordToken(3, '@id', loc(3, 14, 3, 17), 4), + 'value': new WordToken(8, 'string', loc(3, 20, 3, 26), 6), + 'tokenRange': [3, 7] + } + ]); + }); + + it('type with map should ok', function () { + assert.deepStrictEqual(moduleBody('type @id = map[string]string'), [ + { + 'annotation': undefined, + 'tokenRange': [3, 11], + 'type': 'type', + 'vid': new WordToken(3, '@id', loc(3, 14, 3, 17), 4), + 'value': { + keyType: new WordToken(8, 'string', loc(3, 24, 3, 30), 8), + valueType: new WordToken(8, 'string', loc(3, 31, 3, 37), 10), + type: 'map', + 'loc': loc(3, 20, 3, 37) + } + } + ]); + }); + + it('type with array should ok', function () { + assert.deepStrictEqual(moduleBody('type @id = [ string ]'), [ + { + 'annotation': undefined, + 'tokenRange': [3, 9], + 'type': 'type', + 'vid': new WordToken(3, '@id', loc(3, 14, 3, 17), 4), + 'value': { + 'itemType': new WordToken(8, 'string', loc(3, 22, 3, 28), 7), + 'type': 'array' + } + } + ]); + }); + }); + + describe('init', function () { + it('init should ok', function () { + var ast = parse(` + module M { + init(); + } + `, '__filename'); + const [init] = ast.moduleBody.nodes; + assert.deepStrictEqual(init.type, 'init'); + assert.deepStrictEqual(init.params, { + 'params': [], + 'type': 'params' + }); + }); + + it('init without comma should ok', function () { + var ast = parse(` + module M { + init() + } + `, '__filename'); + const [init] = ast.moduleBody.nodes; + assert.deepStrictEqual(init.type, 'init'); + assert.deepStrictEqual(init.params, { + 'params': [], + 'type': 'params' + }); + }); + + it('init(config) should ok', function () { + var ast = parse(` + module M { + init(config: Config); + } + `, '__filename'); + const [init] = ast.moduleBody.nodes; + assert.deepStrictEqual(init.type, 'init'); + assert.deepStrictEqual(init.params, { + 'params': [ + { + 'paramName': new WordToken(2, 'config', loc(3, 12, 3, 18), 5), + 'paramType': new WordToken(2, 'Config', loc(3, 20, 3, 26), 7), + 'type': 'param' + } + ], + 'type': 'params' + }); + }); + + it('init(config) {} should ok', function () { + var ast = parse(` + module M { + init() {} + } + `, '__filename'); + const [init] = ast.moduleBody.nodes; + assert.deepStrictEqual(init.type, 'init'); + assert.deepStrictEqual(init.params, { + 'params': [ + ], + 'type': 'params' + }); + assert.deepStrictEqual(init.initBody, { + 'stmts': [], + 'tokenRange': [6, 7], + 'type': 'stmts' + }); + }); + }); + + describe('function', function () { + it('function should ok', function () { + var ast = parse(` + module M { + function callId(): void { + } + } + `, '__filename'); + + const [func] = ast.moduleBody.nodes; + + assert.deepStrictEqual(func, { + 'annotation': undefined, + 'functionName': new WordToken(2, 'callId', loc(3, 20, 3, 26), 4), + 'functionBody': { + 'loc': loc(3, 35, 5, 9), + 'stmts': { + 'stmts': [], + 'tokenRange': [9, 10], + 'type': 'stmts' + }, + 'tokenRange': [ + 9, + 10 + ], + 'type': 'functionBody' + }, + 'params': { + 'params': [], + 'type': 'params' + }, + 'returnType': new WordToken(8, 'void', loc(3, 30, 3, 34), 8), + 'tokenRange': [3, 10], + hasThrow: false, + isAsync: false, + isStatic: false, + 'type': 'function' + }); + }); + + it('function should ok without body', function () { + var ast = parse(` + module M { + function callId(): string; + function callId2(): string + } + `, '__filename'); + + const [func1, func2] = ast.moduleBody.nodes; + + assert.deepStrictEqual(func1, { + 'annotation': undefined, + 'isStatic': false, + 'isAsync': false, + 'hasThrow': false, + 'params': { + 'params': [], + 'type': 'params' + }, + 'returnType': new WordToken(8, 'string', loc(3, 30, 3, 36), 8), + 'tokenRange': [3, 9], + 'type': 'function', + 'functionBody': null, + 'functionName': new WordToken(2, 'callId', loc(3, 20, 3, 26), 4) + }); + assert.deepStrictEqual(func2, { + 'annotation': undefined, + 'isStatic': false, + 'isAsync': false, + 'hasThrow': false, + 'params': { + 'params': [], + 'type': 'params' + }, + 'returnType': new WordToken(8, 'string', loc(4, 31, 4, 37), 15), + 'tokenRange': [10, 16], + 'type': 'function', + 'functionBody': null, + 'functionName': new WordToken(2, 'callId2', loc(4, 20, 4, 27), 11) + }); + }); + + it('function with throws should ok', function () { + var ast = parse(` + module M { + function callId() throws : string; + function callId2(): string; + } + `, '__filename'); + + const [func, func2] = ast.moduleBody.nodes; + assert.deepStrictEqual(func.hasThrow, true); + assert.deepStrictEqual(func2.hasThrow, false); + }); + + it('static function should ok', function () { + var ast = parse(` + module M { + static function equal(actual: any, expected: any, message: string): void; + function equal2(actual: any, expected: any, message: string): void; + } + `, '__filename'); + const [fun, fun2] = ast.moduleBody.nodes; + assert.deepStrictEqual(fun.isStatic, true); + assert.deepStrictEqual(fun2.isStatic, false); + }); + + it('async function should ok', function () { + var ast = parse(` + module M { + async function equal(actual: any, expected: any, message: string): void; + function equal2(actual: any, expected: any, message: string): void; + } + `, '__filename'); + const [fun, fun2] = ast.moduleBody.nodes; + assert.deepStrictEqual(fun.isAsync, true); + assert.deepStrictEqual(fun2.isAsync, false); + }); + + it('static async function should ok', function () { + var ast = parse(` + module M { + static async function equal(actual: any, expected: any, message: string): void; + } + `, '__filename'); + const [fun] = ast.moduleBody.nodes; + assert.deepStrictEqual(fun.isAsync, true); + assert.deepStrictEqual(fun.isStatic, true); + }); + + it('function annotation should be ok', function () { + const ast = parse(` + module M { + /** + * description + * @param key key description + * @return returns value + */ + static function hello(key: string): string; + } + `, '__filename'); + const [fun] = ast.moduleBody.nodes; + assert.deepStrictEqual(fun.annotation, new Annotation('/**\n * description\n * @param key key description\n * @return returns value\n */', loc(3, 9, 7, 12), 3)); + }); + }); + + describe('statements', function () { + function stmts(value) { + var ast = parse(` + module M { + function id(): void { + ${value} + } + } + `, '__filename'); + + return ast.moduleBody.nodes[0].functionBody.stmts; + } + + it('stmts should ok', function () { + assert.deepStrictEqual(stmts(''), { + stmts: [], + tokenRange: [9, 10], + type: 'stmts' + }); + + assert.throws(() => { + stmts('...'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: .. expect valid expression`); + return true; + }); + }); + + it('if stmt should ok', function () { + const ast = stmts(` + if (true) { + } + `); + assert.deepStrictEqual(ast, { + stmts: [ + { + type: 'if', + tokenRange: [10, 15], + branches: [ + { + 'condition': { + 'loc': loc(5, 13, 5, 17), + 'tokenRange': [12, 13], + 'type': 'boolean', + 'value': true + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [14, 15], + 'type': 'stmts' + }, + 'type': 'if_branch' + } + ] + } + ], + tokenRange: [9, 16], + type: 'stmts' + }); + }); + + it('if/elseif stmt should ok', function () { + const ast = stmts(` + if (true) { + } else if (true) { + } + `); + assert.deepStrictEqual(ast, { + stmts: [ + { + type: 'if', + tokenRange: [10, 22], + branches: [ + { + 'condition': { + 'loc': loc(5, 13, 5, 17), + 'tokenRange': [12, 13], + 'type': 'boolean', + 'value': true + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [14, 15], + 'type': 'stmts' + }, + 'type': 'if_branch' + }, + { + 'condition': { + 'loc': loc(6, 20, 6, 24), + 'tokenRange': [19, 20], + 'type': 'boolean', + 'value': true + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [21, 22], + 'type': 'stmts' + }, + 'type': 'if_branch' + } + ] + } + ], + tokenRange: [9, 23], + type: 'stmts' + }); + }); + + it('if/else stmt should ok', function () { + const ast = stmts(` + if (true) { + } else { + } + `); + assert.deepStrictEqual(ast, { + stmts: [ + { + type: 'if', + tokenRange: [10, 18], + branches: [ + { + 'condition': { + 'loc': loc(5, 13, 5, 17), + 'tokenRange': [12, 13], + 'type': 'boolean', + 'value': true + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [14, 15], + 'type': 'stmts' + }, + 'type': 'if_branch' + }, + { + 'stmts': { + 'stmts': [], + 'tokenRange': [17, 18], + 'type': 'stmts' + }, + 'type': 'else_branch' + } + ] + } + ], + tokenRange: [9, 19], + type: 'stmts' + }); + }); + + it('only if or { should be after else', function () { + assert.throws(function () { + stmts(`if (true) { + } else x { + } + }`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'Unexpected token: Word: `x`. expect "if" or "{"'); + return true; + }); + }); + + it('while should ok', function () { + var ast = stmts(` + while (true) { + }`); + assert.deepStrictEqual(ast, { + type: 'stmts', + tokenRange: [9, 16], + stmts: [ + { + 'type': 'while', + 'tokenRange': [10, 15], + 'condition': { + loc: loc(5, 16, 5, 20), + 'tokenRange': [12, 13], + 'type': 'boolean', + 'value': true + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [14, 15], + 'type': 'stmts' + } + } + ] + }); + }); + + it('for should ok', function () { + const ast = stmts(` + for ( ; ; ) { + }`); + assert.deepStrictEqual(ast.stmts, [ + { + 'type': 'for', + 'tokenRange': [10, 16], + 'init': { + 'tokenRange': [12, 12], + loc: loc(5, 15, 5, 15), + 'type': 'empty' + }, + 'test': { + 'tokenRange': [13, 13], + loc: loc(5, 17, 5, 17), + 'type': 'empty' + }, + 'update': { + 'tokenRange': [14, 14], + loc: loc(5, 19, 5, 19), + 'type': 'empty' + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [15, 16], + 'type': 'stmts' + } + } + ]); + + assert.deepStrictEqual(stmts(`for ( var i = 0; ; ) {}`).stmts, [ + { + 'type': 'for', + 'tokenRange': [10, 20], + 'init': { + 'tokenRange': [12, 16], + id: new WordToken(2, 'i', loc(4, 23, 4, 24), 13), + expr: { + type: 'number', + loc: loc(4, 27, 4, 28), + tokenRange: [15, 16], + value: new NumberLiteral('0', 'integer', loc(4, 27, 4, 28), 15), + }, + expectedType: undefined, + 'type': 'declare_expr' + }, + 'test': { + 'tokenRange': [17, 17], + loc: loc(4, 30, 4, 30), + 'type': 'empty' + }, + 'update': { + 'tokenRange': [18, 18], + loc: loc(4, 32, 4, 32), + 'type': 'empty' + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [19, 20], + 'type': 'stmts' + } + } + ]); + + assert.deepStrictEqual(stmts(`for ( var i: int32 = 0; ; ) {}`).stmts, [ + { + 'type': 'for', + 'tokenRange': [10, 22], + 'init': { + 'tokenRange': [12, 18], + id: new WordToken(2, 'i', loc(4, 23, 4, 24), 13), + expr: { + type: 'number', + loc: loc(4, 34, 4, 35), + tokenRange: [17, 18], + value: new NumberLiteral('0', 'integer', loc(4, 34, 4, 35), 17), + }, + expectedType: new WordToken(8, 'int32', loc(4, 26, 4, 31), 15), + 'type': 'declare_expr' + }, + 'test': { + 'tokenRange': [19, 19], + loc: loc(4, 37, 4, 37), + 'type': 'empty' + }, + 'update': { + 'tokenRange': [20, 20], + loc: loc(4, 39, 4, 39), + 'type': 'empty' + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [21, 22], + 'type': 'stmts' + } + } + ]); + }); + + it('for of should ok', function () { + const ast = stmts(` + for (var id of []) { + }`); + assert.deepStrictEqual(ast.stmts, [ + { + 'type': 'for_of', + 'tokenRange': [10, 19], + 'right': { + 'tokenRange': [15, 17], + 'items': [], + 'type': 'array' + }, + 'left': { + expectedType: undefined, + expr: undefined, + type: 'declare_expr', + tokenRange: [12, 14], + id: new WordToken(2, 'id', loc(5, 18, 5, 20), 13) + }, + 'stmts': { + 'stmts': [], + 'tokenRange': [18, 19], + 'type': 'stmts' + } + } + ]); + + assert.throws(() => { + stmts(`for (v of []) {}`); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'Unexpected token: Word: `of`. must be declare expression'); + return true; + }); + }); + + it('try/catch should ok', function () { + var ast = stmts(` + try { + } catch (ex) { + } + `); + assert.deepStrictEqual(ast.stmts, [ + { + 'type': 'try', + 'tryBlock': { + 'stmts': [], + 'tokenRange': [11, 12], + 'type': 'stmts' + }, + 'catchId': new WordToken(2, 'ex', loc(6, 18, 6, 20), 15), + 'catchBlock': { + stmts: [], + 'tokenRange': [17, 18], + 'type': 'stmts' + }, + 'tokenRange': [10, 18], + finallyBlock: null + } + ]); + }); + + it('try/catch/finally should ok', function () { + const ast = stmts(` + try { + } catch (ex) { + } finally { + } + `); + assert.deepStrictEqual(ast.stmts, [ + { + 'type': 'try', + 'tokenRange': [10, 21], + 'tryBlock': { + 'stmts': [], + 'tokenRange': [11, 12], + 'type': 'stmts' + }, + 'catchId': new WordToken(2, 'ex', loc(6, 18, 6, 20), 15), + 'catchBlock': { + stmts: [ + ], + 'tokenRange': [17, 18], + 'type': 'stmts' + }, + 'finallyBlock': { + 'stmts': [ + ], + 'tokenRange': [20, 21], + 'type': 'stmts' + } + } + ]); + }); + + it('try/finally should ok', function () { + const ast = stmts(` + try { + } finally { + } + `); + + assert.deepStrictEqual(ast.stmts, [ + { + 'type': 'try', + 'tokenRange': [10, 15], + 'tryBlock': { + 'stmts': [], + 'tokenRange': [11, 12], + 'type': 'stmts' + }, + 'catchId': null, + 'finallyBlock': { + stmts: [], + 'tokenRange': [14, 15], + 'type': 'stmts' + }, + 'catchBlock': null + } + ]); + }); + + it('only try should not ok', () => { + assert.throws(() => { + stmts(` + try { + Util.print("try block"); + } + `); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. "try" expect "catch" or "finally"`); + return true; + }); + }); + + it('break should ok', function () { + const ast = stmts(` + break; + `); + assert.deepStrictEqual(ast.stmts, [ + { + 'tokenRange': [10, 11], + 'type': 'break' + } + ]); + }); + + it('return should ok', function () { + var ast = stmts(`return;`); + assert.deepStrictEqual(ast.stmts, [ + { + 'tokenRange': [10, 12], + 'loc': loc(4, 13, 4, 19), + expr: { + 'loc': loc(4, 19, 4, 19), + type: 'empty', + tokenRange: [11, 11] + }, + 'type': 'return' + }]); + }); + + it('return null should ok', function () { + var ast = stmts(`return null;`); + assert.deepStrictEqual(ast.stmts, [ + { + 'loc': loc(4, 13, 4, 24), + 'tokenRange': [10, 13], + 'type': 'return', + expr: { + tokenRange: [11, 12], + type: 'null' + } + }]); + }); + + it('throw should ok', function () { + assert.deepStrictEqual(stmts('throw {}').stmts, [ + { + 'expr': { + 'fields': [], + 'type': 'map', + 'tokenRange': [11, 12], + loc: loc(4, 19, 5, 11) + }, + 'tokenRange': [10, 12], + 'type': 'throw' + } + ]); + + // with comma + assert.deepStrictEqual(stmts('throw {};').stmts, [ + { + 'expr': { + 'fields': [], + 'type': 'map', + 'tokenRange': [11, 12], + loc: loc(4, 19, 4, 21) + }, + 'tokenRange': [10, 13], + 'type': 'throw' + } + ]); + }); + + it('declare should ok', function () { + var ast = stmts(`var id = "random_id";`); + assert.deepStrictEqual(ast.stmts, [ + { + 'expr': { + type: 'string', + tokenRange: [13, 14], + loc: loc(4, 23, 4, 32), + value: new StringLiteral('random_id', loc(4, 23, 4, 32), 13), + }, + 'id': new WordToken(2, 'id', loc(4, 17, 4, 19), 11), + expectedType: undefined, + 'tokenRange': [10, 14], + 'type': 'declare' + } + ]); + }); + + it('declare with expected type should ok', function () { + var ast = stmts(`var a : string = "";`); + assert.deepStrictEqual(ast.stmts, [ + { + 'type': 'declare', + 'tokenRange': [10, 16], + 'id': new WordToken(2, 'a', loc(4, 17, 4, 18), 11), + 'expectedType': new WordToken(8, 'string', loc(4, 21, 4, 27), 13), + 'expr': { + 'loc': loc(4, 31, 4, 31), + 'type': 'string', + 'tokenRange': [15, 16], + 'value': new StringLiteral('', loc(4, 31, 4, 31), 15) + } + } + ]); + }); + + }); + + describe('expressions', function () { + function expr(value) { + var ast = parse(` + module M { + function test(): void { + ${value}; + } + } + `, '__filename'); + + return ast.moduleBody.nodes[0].functionBody.stmts.stmts[0]; + } + + it('empty should ok', function () { + assert.deepStrictEqual(expr(''), { + loc: loc(4, 11, 4, 11), + tokenRange: [10, 10], + type: 'empty' + }); + }); + + it('string should ok', function () { + assert.deepStrictEqual(expr(`'id2'`), { + 'value': new StringLiteral('id2', loc(4, 12, 4, 15), 10), + 'tokenRange': [10, 11], + loc: loc(4, 12, 4, 15), + 'type': 'string' + }); + }); + + it('number should ok', function () { + assert.deepStrictEqual(expr(`123`), { + 'value': new NumberLiteral('123', 'integer', loc(4, 11, 4, 14), 10), + 'tokenRange': [10, 11], + loc: loc(4, 11, 4, 14), + 'type': 'number' + }); + }); + + it('bool should ok', function () { + assert.deepStrictEqual(expr(`true`), { + 'tokenRange': [10, 11], + loc: loc(4, 11, 4, 15), + 'type': 'boolean', + value: true + }); + + assert.deepStrictEqual(expr(`false`), { + 'value': false, + 'tokenRange': [10, 11], + loc: loc(4, 11, 4, 16), + 'type': 'boolean' + }); + }); + + it('null should ok', function () { + assert.deepStrictEqual(expr(`null`), { + 'tokenRange': [10, 11], + 'type': 'null' + }); + }); + + it('template string should ok', function () { + assert.deepStrictEqual(expr('`abcdefg`'), { + 'elements': [ + { + type: 'element', + value: new TemplateElement('abcdefg', true, loc(4, 12, 4, 19), 10) + } + ], + 'tokenRange': [10, 11], + 'type': 'template_string' + }); + + assert.deepStrictEqual(expr('`abc${"abc"}defg`'), { + 'elements': [ + { + 'type': 'element', + 'value': new TemplateElement('abc', false, loc(4, 12, 4, 15), 10) + }, + { + 'expr': { + 'type': 'string', + 'value': new StringLiteral('abc', loc(4, 18, 4, 21), 11), + loc: loc(4, 18, 4, 21), + 'tokenRange': [11, 12], + }, + 'type': 'expr' + }, + { + 'type': 'element', + 'value': new TemplateElement('defg', true, loc(4, 23, 4, 27), 12) + } + ], + 'tokenRange': [10, 13], + 'type': 'template_string' + }); + + assert.deepStrictEqual(expr('`abc${"abc"}d${"e"}fg`'), { + 'elements': [ + { + 'type': 'element', + 'value': new TemplateElement('abc', false, loc(4, 12, 4, 15), 10) + }, + { + 'expr': { + 'type': 'string', + 'value': new StringLiteral('abc', loc(4, 18, 4, 21), 11), + 'tokenRange': [11, 12], + loc: loc(4, 18, 4, 21) + }, + 'type': 'expr' + }, + { + 'type': 'element', + 'value': new TemplateElement('d', false, loc(4, 23, 4, 24), 12) + }, + { + 'expr': { + 'type': 'string', + 'value': new StringLiteral('e', loc(4, 27, 4, 28), 13), + 'tokenRange': [13, 14], + loc: loc(4, 27, 4, 28) + }, + 'type': 'expr' + }, + { + 'type': 'element', + 'value': new TemplateElement('fg', true, loc(4, 30, 4, 32), 14) + } + ], + 'tokenRange': [10, 15], + 'type': 'template_string' + }); + }); + + it('super() should ok', function () { + var ast = parse(` + module M { + init() { + super(); + } + }`, '__filename'); + let [init] = ast.moduleBody.nodes; + const [expr] = init.initBody.stmts; + assert.deepStrictEqual(expr, { + 'args': [], + 'loc': loc(4, 18, 4, 20), + 'tokenRange': [7, 10], + 'type': 'super' + }); + }); + + it('new model should ok', function () { + var ast = expr(`new Config{}`); + assert.deepStrictEqual(ast, { + component: new WordToken(2, 'Config', loc(4, 15, 4, 21), 11), + 'fields': { + fields: [], + loc: loc(4, 21, 4, 23), + tokenRange: [12, 13], + type: 'fields' + }, + loc: loc(4, 11, 4, 23), + 'tokenRange': [10, 14], + 'type': 'construct_model' + }); + + assert.deepStrictEqual(expr(`new Config{ key = '' }`).fields, { + fields: [ + { + expr: { + type: 'string', + value: new StringLiteral('', loc(4, 30, 4, 30), 15), + loc: loc(4, 30, 4, 30), + tokenRange: [15, 16] + }, + key: new WordToken(2, 'key', loc(4, 23, 4, 26), 13), + 'type': 'modelField', + tokenRange: [13, 16] + } + ], + loc: loc(4, 21, 4, 33), + tokenRange: [12, 16], + type: 'fields' + }); + + assert.deepStrictEqual(expr(`new Config{ key = '', }`).fields, { + fields: [ + { + expr: { + type: 'string', + value: new StringLiteral('', loc(4, 30, 4, 30), 15), + loc: loc(4, 30, 4, 30), + tokenRange: [15, 16] + }, + key: new WordToken(2, 'key', loc(4, 23, 4, 26), 13), + 'type': 'modelField', + tokenRange: [13, 16] + } + ], + loc: loc(4, 21, 4, 34), + tokenRange: [12, 17], + type: 'fields' + }); + + assert.deepStrictEqual(expr(`new Config{ key = '', key2 = '' }`).fields, { + fields: [ + { + expr: { + type: 'string', + value: new StringLiteral('', loc(4, 30, 4, 30), 15), + loc: loc(4, 30, 4, 30), + tokenRange: [15, 16] + }, + key: new WordToken(2, 'key', loc(4, 23, 4, 26), 13), + 'type': 'modelField', + tokenRange: [13, 16] + }, + { + expr: { + type: 'string', + value: new StringLiteral('', loc(4, 41, 4, 41), 19), + loc: loc(4, 41, 4, 41), + tokenRange: [19, 20] + }, + key: new WordToken(2, 'key2', loc(4, 33, 4, 37), 17), + 'type': 'modelField', + tokenRange: [17, 20] + } + ], + loc: loc(4, 21, 4, 44), + tokenRange: [12, 20], + type: 'fields' + }); + + assert.throws(() => { + expr(`new 123`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpected token: Number: 123. expect (extern) module/model/interface'); + return true; + }); + + assert.throws(() => { + expr(`new Config{ key = '' 123 }`); + }, (e) => { + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, 'Unexpected token: Number: 123. expect ","'); + return true; + }); + }); + + it('new extern model should ok', function () { + var ast = expr(`new $oss.Config{}`); + assert.deepStrictEqual(ast, { + component: { + 'aliasId': new WordToken(21, '$oss', loc(4, 15, 4, 19), 11), + component: new WordToken(2, 'Config', loc(4, 20, 4, 26), 13), + tokenRange: [11, 14], + loc: loc(4, 15, 4, 26), + 'type': 'extern_component' + }, + 'fields': { + fields: [], + loc: loc(4, 26, 4, 28), + tokenRange: [14, 15], + type: 'fields' + }, + loc: loc(4, 11, 4, 28), + 'tokenRange': [10, 16], + 'type': 'construct_model' + }); + }); + + it('new module should ok', function () { + var ast = expr(`new Client()`); + assert.deepStrictEqual(ast, { + component: new WordToken(2, 'Client', loc(4, 15, 4, 21), 11), + 'args': [], + 'tokenRange': [10, 14], + loc: loc(4, 11, 4, 23), + 'type': 'construct_module' + }); + }); + + it('new extern module should ok', function () { + var ast = expr(`new $oss.Client()`); + assert.deepStrictEqual(ast, { + component: { + type: 'extern_component', + aliasId: new WordToken(21, '$oss', loc(4, 15, 4, 19), 11), + component: new WordToken(2, 'Client', loc(4, 20, 4, 26), 13), + loc: loc(4, 15, 4, 26), + tokenRange: [11, 14] + }, + 'args': [], + 'tokenRange': [10, 16], + loc: loc(4, 11, 4, 28), + 'type': 'construct_module' + }); + }); + + it('map should ok', function () { + assert.deepStrictEqual(expr('{}'), { + 'fields': [], + 'type': 'map', + 'tokenRange': [10, 12], + loc: loc(4, 11, 4, 13) + }); + + assert.deepStrictEqual(expr(`{'a' = 1}`), { + 'fields': [ + { + 'expr': { + 'type': 'number', + 'value': new NumberLiteral('1', 'integer', loc(4, 18, 4, 19), 13), + 'tokenRange': [13, 14], + loc: loc(4, 18, 4, 19) + }, + 'key': new StringLiteral('a', loc(4, 13, 4, 14), 11), + 'tokenRange': [11, 14], + 'type': 'mapField' + } + ], + 'type': 'map', + 'tokenRange': [10, 15], + loc: loc(4, 11, 4, 20) + }); + + assert.deepStrictEqual(expr(`{'a' = 1,}`), { + 'fields': [ + { + 'expr': { + 'type': 'number', + 'value': new NumberLiteral('1', 'integer', loc(4, 18, 4, 19), 13), + 'tokenRange': [13, 14], + loc: loc(4, 18, 4, 19) + }, + 'key': new StringLiteral('a', loc(4, 13, 4, 14), 11), + 'tokenRange': [11, 14], + 'type': 'mapField' + } + ], + 'type': 'map', + 'tokenRange': [10, 16], + loc: loc(4, 11, 4, 21) + }); + + assert.deepStrictEqual(expr(`{'a' = 1, 'b' = 2L, 'c' = 1.2}`), { + 'fields': [ + { + 'expr': { + 'type': 'number', + 'value': new NumberLiteral('1', 'integer', loc(4, 18, 4, 19), 13), + 'tokenRange': [13, 14], + loc: loc(4, 18, 4, 19) + }, + 'key': new StringLiteral('a', loc(4, 13, 4, 14), 11), + 'tokenRange': [11, 14], + 'type': 'mapField' + }, + { + 'expr': { + 'type': 'number', + 'value': new NumberLiteral('2', 'long', loc(4, 27, 4, 29), 17), + 'tokenRange': [17, 18], + loc: loc(4, 27, 4, 29) + }, + 'key': new StringLiteral('b', loc(4, 22, 4, 23), 15), + 'tokenRange': [15, 18], + 'type': 'mapField' + }, + { + 'expr': { + 'type': 'number', + 'value': new NumberLiteral('1.2', 'float', loc(4, 37, 4, 40), 21), + 'tokenRange': [21, 22], + loc: loc(4, 37, 4, 40) + }, + 'key': new StringLiteral('c', loc(4, 32, 4, 33), 19), + 'tokenRange': [19, 22], + 'type': 'mapField' + } + ], + 'tokenRange': [10, 23], + 'type': 'map', + loc: loc(4, 11, 4, 41) + }); + + assert.throws(() => { + expr(`{'a' = 1, 'b' = 2.}`); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: .. expect ","`); + return true; + }); + + assert.throws(() => { + expr('{.}'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect ., but }`); + return true; + }); + + assert.throws(() => { + expr('{..}'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. Expect ., but }`); + return true; + }); + + assert.throws(() => { + expr('{...}'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: }. expect valid expression`); + return true; + }); + + assert.throws(() => { + expr('{*}}'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: *. expect "..." or key`); + return true; + }); + }); + + it('map with expand should ok', function () { + assert.deepStrictEqual(expr('{...a}'), { + 'fields': [ + { + 'expr': { + 'id': new WordToken(2, 'a', loc(4, 15, 4, 16), 14), + 'tokenRange': [14, 15], + loc: loc(4, 15, 4, 16), + 'type': 'id' + }, + 'tokenRange': [11, 15], + 'type': 'expandField' + } + ], + 'tokenRange': [10, 16], + 'type': 'map', + loc: loc(4, 11, 4, 17) + }); + }); + + it('array should ok', function () { + assert.deepStrictEqual(expr('[]'), { + 'items': [], + 'tokenRange': [10, 12], + 'type': 'array' + }); + + assert.deepStrictEqual(expr('[1]'), { + 'items': [ + { + 'loc': loc(4, 12, 4, 13), + 'tokenRange': [11, 12], + 'type': 'number', + 'value': new NumberLiteral('1', 'integer', loc(4, 12, 4, 13), 11) + } + ], + 'tokenRange': [10, 13], + 'type': 'array' + }); + + assert.deepStrictEqual(expr('[1, 2]'), { + 'items': [ + { + 'loc': loc(4, 12, 4, 13), + 'tokenRange': [11, 12], + 'type': 'number', + 'value': new NumberLiteral('1', 'integer', loc(4, 12, 4, 13), 11) + }, + { + 'loc': loc(4, 15, 4, 16), + 'tokenRange': [13, 14], + 'type': 'number', + 'value': new NumberLiteral('2', 'integer', loc(4, 15, 4, 16), 13) + } + ], + 'tokenRange': [10, 15], + 'type': 'array' + }); + + assert.throws(() => { + expr('[1 1]'); + }, function (e) { // get the exception object + assert.ok(e instanceof SyntaxError); + assert.deepStrictEqual(e.message, `Unexpected token: Number: 1. expect ","`); + return true; + }); + }); + + it('id should ok', function () { + assert.deepStrictEqual(expr('id2'), { + 'id': new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + 'tokenRange': [10, 11], + loc: loc(4, 11, 4, 14), + 'type': 'id' + }); + + assert.deepStrictEqual(expr('@id2'), { + 'id': new WordToken(3, '@id2', loc(4, 11, 4, 15), 10), + 'tokenRange': [10, 11], + loc: loc(4, 11, 4, 15), + 'type': 'id' + }); + }); + + it('access path should ok', function () { + assert.deepStrictEqual(expr('id2.id2'), { + object: { + type: 'id', + loc: loc(4, 11, 4, 14), + id: new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + }, + property: new WordToken(2, 'id2', loc(4, 15, 4, 18), 12), + 'tokenRange': [10, 13], + loc: loc(4, 11, 4, 18), + 'type': 'property' + }); + + assert.deepStrictEqual(expr('@id2.id2'), { + object: { + type: 'id', + loc: loc(4, 11, 4, 15), + id: new WordToken(3, '@id2', loc(4, 11, 4, 15), 10), + }, + property: new WordToken(2, 'id2', loc(4, 16, 4, 19), 12), + 'tokenRange': [10, 13], + loc: loc(4, 11, 4, 19), + 'type': 'property' + }); + + assert.deepStrictEqual(expr('id2[id2]'), { + object: { + type: 'id', + loc: loc(4, 11, 4, 14), + id: new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + }, + index: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 15, 4, 18), 12), + loc: loc(4, 15, 4, 18) + }, + 'tokenRange': [10, 14], + loc: loc(4, 11, 4, 19), + 'type': 'member' + }); + + assert.deepStrictEqual(expr('@id2[id2]'), { + object: { + type: 'id', + loc: loc(4, 11, 4, 15), + id: new WordToken(3, '@id2', loc(4, 11, 4, 15), 10), + }, + index: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 16, 4, 19), 12), + loc: loc(4, 16, 4, 19) + }, + 'tokenRange': [10, 14], + loc: loc(4, 11, 4, 20), + 'type': 'member' + }); + + assert.deepStrictEqual(expr('id2[id2].id3'), { + object: { + type: 'member', + object: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + loc: loc(4, 11, 4, 14) + }, + index: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 15, 4, 18), 12), + loc: loc(4, 15, 4, 18) + }, + loc: loc(4, 11, 4, 19) + }, + property: new WordToken(2, 'id3', loc(4, 20, 4, 23), 15), + 'tokenRange': [10, 16], + loc: loc(4, 11, 4, 23), + 'type': 'property' + }); + + assert.deepStrictEqual(expr('@id2[id2].id3'), { + object: { + object: { + type: 'id', + id: new WordToken(3, '@id2', loc(4, 11, 4, 15), 10), + loc: loc(4, 11, 4, 15) + }, + index: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 16, 4, 19), 12), + loc: loc(4, 16, 4, 19) + }, + loc: loc(4, 11, 4, 20), + type: 'member' + }, + property: new WordToken(2, 'id3', loc(4, 21, 4, 24), 15), + 'tokenRange': [10, 16], + loc: loc(4, 11, 4, 24), + 'type': 'property' + }); + }); + + it('assign should ok', function () { + assert.deepStrictEqual(expr('id2 = 1'), { + left: { + 'id': new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + type: 'id', + loc: loc(4, 11, 4, 14) + }, + expr: { + loc: loc(4, 17, 4, 18), + tokenRange: [12, 13], + type: 'number', + value: new NumberLiteral('1', 'integer', loc(4, 17, 4, 18), 12) + }, + 'tokenRange': [10, 13], + 'type': 'assign' + }); + + assert.deepStrictEqual(expr('@id2 = 1'), { + left: { + 'id': new WordToken(3, '@id2', loc(4, 11, 4, 15), 10), + loc: loc(4, 11, 4, 15), + type: 'id' + }, + expr: { + loc: loc(4, 18, 4, 19), + tokenRange: [12, 13], + type: 'number', + value: new NumberLiteral('1', 'integer', loc(4, 18, 4, 19), 12) + }, + 'tokenRange': [10, 13], + 'type': 'assign' + }); + + assert.deepStrictEqual(expr('id2.id = 1'), { + left: { + object: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + loc: loc(4, 11, 4, 14) + }, + property: new WordToken(2, 'id', loc(4, 15, 4, 17), 12), + loc: loc(4, 11, 4, 18), + type: 'property' + }, + expr: { + loc: loc(4, 20, 4, 21), + tokenRange: [14, 15], + type: 'number', + value: new NumberLiteral('1', 'integer', loc(4, 20, 4, 21), 14) + }, + 'tokenRange': [10, 15], + 'type': 'assign' + }); + + assert.deepStrictEqual(expr('@id2.id = 1'), { + left: { + object: { + type: 'id', + id: new WordToken(3, '@id2', loc(4, 11, 4, 15), 10), + loc: loc(4, 11, 4, 15) + }, + property: new WordToken(2, 'id', loc(4, 16, 4, 18), 12), + loc: loc(4, 11, 4, 19), + type: 'property' + }, + expr: { + loc: loc(4, 21, 4, 22), + tokenRange: [14, 15], + type: 'number', + value: new NumberLiteral('1', 'integer', loc(4, 21, 4, 22), 14) + }, + 'tokenRange': [10, 15], + 'type': 'assign' + }); + + assert.deepStrictEqual(expr('id2[id] = 1'), { + left: { + object: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + loc: loc(4, 11, 4, 14) + }, + index: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 15, 4, 17), 12), + loc: loc(4, 15, 4, 17) + }, + loc: loc(4, 11, 4, 19), + type: 'member' + }, + expr: { + loc: loc(4, 21, 4, 22), + tokenRange: [15, 16], + type: 'number', + value: new NumberLiteral('1', 'integer', loc(4, 21, 4, 22), 15) + }, + 'tokenRange': [10, 16], + 'type': 'assign' + }); + + assert.deepStrictEqual(expr('@id2[id] = 1'), { + left: { + object: { + type: 'id', + id: new WordToken(3, '@id2', loc(4, 11, 4, 15), 10), + loc: loc(4, 11, 4, 15) + }, + index: { + type: 'id', + + id: new WordToken(2, 'id', loc(4, 16, 4, 18), 12), + loc: loc(4, 16, 4, 18) + }, + loc: loc(4, 11, 4, 20), + type: 'member' + }, + expr: { + loc: loc(4, 22, 4, 23), + tokenRange: [15, 16], + type: 'number', + value: new NumberLiteral('1', 'integer', loc(4, 22, 4, 23), 15) + }, + 'tokenRange': [10, 16], + 'type': 'assign' + }); + }); + + it('method call should ok', function () { + assert.deepStrictEqual(expr('id2()'), { + callee: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + loc: loc(4, 11, 4, 14) + }, + args: [], + 'tokenRange': [10, 13], + loc: loc(4, 11, 4, 16), + 'type': 'call' + }); + }); + + it('call should ok', function () { + assert.deepStrictEqual(expr('id2.id2()'), { + callee: { + type: 'property', + property: new WordToken(2, 'id2', loc(4, 15, 4, 18), 12), + object: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 11, 4, 14), 10), + loc: loc(4, 11, 4, 14) + }, + loc: loc(4, 11, 4, 18) + }, + args: [], + 'tokenRange': [10, 15], + loc: loc(4, 11, 4, 20), + 'type': 'call' + }); + }); + + it('args should ok', function () { + assert.deepStrictEqual(expr('id2()').args, []); + assert.deepStrictEqual(expr('id2(1)').args, [ + { + type: 'number', + loc: loc(4, 15, 4, 16), + tokenRange: [12, 13], + value: new NumberLiteral('1', 'integer', loc(4, 15, 4, 16), 12) + } + ]); + assert.deepStrictEqual(expr('id2(1, 2)').args, [ + { + type: 'number', + loc: loc(4, 15, 4, 16), + tokenRange: [12, 13], + value: new NumberLiteral('1', 'integer', loc(4, 15, 4, 16), 12) + }, + { + type: 'number', + loc: loc(4, 18, 4, 19), + tokenRange: [14, 15], + value: new NumberLiteral('2', 'integer', loc(4, 18, 4, 19), 14) + } + ]); + }); + + it('and should ok', function () { + assert.deepStrictEqual(expr('id && id2'), { + loc: loc(4, 11, 4, 20), + type: 'logical', + 'operator': '&&', + tokenRange: [10, 13], + left: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 11, 4, 13), 10), + loc: loc(4, 11, 4, 13), + }, + right: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 17, 4, 20), 12), + loc: loc(4, 17, 4, 20), + tokenRange: [12, 13] + } + }); + }); + + it('or should ok', function () { + assert.deepStrictEqual(expr('id || id2'), { + loc: loc(4, 11, 4, 20), + type: 'logical', + operator: '||', + tokenRange: [10, 13], + left: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 11, 4, 13), 10), + loc: loc(4, 11, 4, 13), + }, + right: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 17, 4, 20), 12), + loc: loc(4, 17, 4, 20), + tokenRange: [12, 13] + } + }); + }); + + it('not expr should ok', function () { + assert.deepStrictEqual(expr('!id2'), { + type: 'not', + tokenRange: [10, 12], + loc: loc(4, 11, 4, 15), + expr: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 12, 4, 15), 11), + loc: loc(4, 12, 4, 15) + } + }); + }); + + it('< should ok', function () { + assert.deepStrictEqual(expr('id < id2'), { + loc: loc(4, 11, 4, 19), + type: 'binary', + 'operator': '<', + tokenRange: [10, 13], + left: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 11, 4, 13), 10), + loc: loc(4, 11, 4, 13), + }, + right: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 16, 4, 19), 12), + loc: loc(4, 16, 4, 19), + tokenRange: [12, 13] + } + }); + }); + + it('<= should ok', function () { + assert.deepStrictEqual(expr('id <= id2'), { + loc: loc(4, 11, 4, 20), + type: 'binary', + 'operator': '<=', + tokenRange: [10, 13], + left: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 11, 4, 13), 10), + loc: loc(4, 11, 4, 13), + }, + right: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 17, 4, 20), 12), + loc: loc(4, 17, 4, 20), + tokenRange: [12, 13] + } + }); + }); + + it('> should ok', function () { + assert.deepStrictEqual(expr('id > id2'), { + loc: loc(4, 11, 4, 19), + type: 'binary', + 'operator': '>', + tokenRange: [10, 13], + left: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 11, 4, 13), 10), + loc: loc(4, 11, 4, 13), + }, + right: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 16, 4, 19), 12), + loc: loc(4, 16, 4, 19), + tokenRange: [12, 13] + } + }); + }); + + it('>= should ok', function () { + assert.deepStrictEqual(expr('id >= id2'), { + loc: loc(4, 11, 4, 20), + type: 'binary', + 'operator': '>=', + tokenRange: [10, 13], + left: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 11, 4, 13), 10), + loc: loc(4, 11, 4, 13), + }, + right: { + type: 'id', + id: new WordToken(2, 'id2', loc(4, 17, 4, 20), 12), + loc: loc(4, 17, 4, 20), + tokenRange: [12, 13] + } + }); + }); + + it(`to expr should ok`, function () { + assert.deepStrictEqual(expr('id to Model'), { + type: 'to', + tokenRange: [10, 13], + loc: loc(4, 11, 4, 22), + from: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 11, 4, 13), 10), + loc: loc(4, 11, 4, 13) + }, + to: new WordToken(2, 'Model', loc(4, 17, 4, 22), 12) + }); + + assert.deepStrictEqual(expr('id to $pkg.Model'), { + type: 'to', + tokenRange: [10, 15], + loc: loc(4, 11, 4, 27), + from: { + type: 'id', + id: new WordToken(2, 'id', loc(4, 11, 4, 13), 10), + loc: loc(4, 11, 4, 13) + }, + to: { + type: 'extern_component', + aliasId: new WordToken(21, '$pkg', loc(4, 17, 4, 21), 12), + component: new WordToken(2, 'Model', loc(4, 22, 4, 27), 14), + loc: loc(4, 17, 4, 27), + tokenRange: [12, 15] + } + }); + + assert.throws(() => { + expr('id to 123'); + }, (ex) => { + assert.ok(ex instanceof SyntaxError); + assert.deepStrictEqual(ex.message, 'Unexpected token: Number: 123. expect (extern) module/model/interface'); + return true; + }); + }); + + it(`inline call expr should ok`, function () { + assert.deepStrictEqual(expr('#append(list, item)'), { + type: 'inline', + tokenRange: [10, 16], + loc: loc(4, 18, 4, 30), + args: [ + { + type: 'id', + id: new WordToken(2, 'list', loc(4, 19, 4, 23), 12), + loc: loc(4, 19, 4, 23), + tokenRange: [12, 13] + }, + { + type: 'id', + id: new WordToken(2, 'item', loc(4, 25, 4, 29), 14), + loc: loc(4, 25, 4, 29), + tokenRange: [14, 15] + } + ], + name: new WordToken(36, '#append', loc(4, 11, 4, 18), 10) + }); + }); + }); +}); diff --git a/test/package.test.js b/test/package.test.js new file mode 100644 index 0000000..48b1048 --- /dev/null +++ b/test/package.test.js @@ -0,0 +1,149 @@ +'use strict'; + +Error.stackTraceLimit = 100; +const path = require('path'); +const assert = require('assert'); + +const Package = require('../lib/package'); + +describe('package', function () { + it('no Darabonba should not ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/non_package')); + try { + await pkg.analyse(); + } catch (ex) { + assert.ok(ex instanceof Error); + assert.ok(ex.message.endsWith('is not a Darabonba package')); + return; + } + assert.fail(); + }); + + it('invalid darafile should not ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_invalid_darafile')); + try { + await pkg.analyse(); + } catch (ex) { + assert.ok(ex instanceof Error); + assert.ok(ex.message.startsWith('the darafile is invalid: ')); + return; + } + assert.fail(); + }); + + it('non-darabonba 2.0 should not ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/clean_package')); + try { + await pkg.analyse(); + } catch (ex) { + assert.ok(ex instanceof Error); + assert.ok(ex.message.startsWith('the darabonba version(1.0) is not support by current parser')); + return; + } + assert.fail(); + }); + + it('empty package should ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/empty_package')); + await pkg.analyse(); + assert.deepStrictEqual(pkg.components, new Map()); + assert.deepStrictEqual(pkg.libraries, new Map()); + assert.deepStrictEqual(pkg.main, null); + }); + + it('package with model should ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_model')); + await pkg.analyse(); + assert.deepStrictEqual(pkg.libraries, new Map()); + assert.deepStrictEqual(pkg.main, null); + assert.ok(pkg.components.get('M')); + assert.deepStrictEqual(pkg.components.get('M').type, 'model'); + }); + + it('package with duplicate model should not ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_duplicate_model')); + try { + await pkg.analyse(); + } catch (ex) { + assert.ok(ex instanceof Error); + assert.deepStrictEqual(ex.message, `redefined 'M'`); + return; + } + assert.fail(); + }); + + it('package with interface should ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_interface')); + await pkg.analyse(); + assert.deepStrictEqual(pkg.libraries, new Map()); + assert.deepStrictEqual(pkg.main, null); + assert.ok(pkg.components.get('I')); + assert.deepStrictEqual(pkg.components.get('I').type, 'interface'); + }); + + it('package with module should ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_module')); + await pkg.analyse(); + assert.deepStrictEqual(pkg.libraries, new Map()); + assert.deepStrictEqual(pkg.main, null); + assert.ok(pkg.components.get('Module')); + assert.deepStrictEqual(pkg.components.get('Module').type, 'module'); + }); + + it('package with duplicate module should not ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_duplicate_module')); + try { + await pkg.analyse(); + } catch (ex) { + assert.ok(ex instanceof Error); + assert.deepStrictEqual(ex.message, `redefined 'Module'`); + return; + } + assert.fail(); + }); + + it('package with dmain should ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_dmain')); + await pkg.analyse(); + }); + + it('package with multi-dmain should not ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_multi_dmain')); + try { + await pkg.analyse(); + } catch (ex) { + assert.ok(ex instanceof Error); + assert.deepStrictEqual(ex.message, `dmain files can not more than one`); + return; + } + assert.fail(); + }); + + it('package with libraries should ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_libraries')); + try { + await pkg.analyse(); + } catch (ex) { + assert.ok(ex instanceof Error); + assert.deepStrictEqual(ex.message, `the package(std) has not installed, use 'dara install' first`); + return; + } + assert.fail(); + }); + + it('package with libraries(installed) should ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_libraries_installed')); + await pkg.analyse(); + assert.deepStrictEqual(pkg.components, new Map()); + assert.deepStrictEqual(pkg.main, null); + assert.ok(pkg.libraries.get('$std')); + }); + + it('package with libraries(local package) should ok', async function () { + const pkg = new Package(path.join(__dirname, 'fixtures/package_with_libraries_local')); + await pkg.analyse(); + assert.deepStrictEqual(pkg.components, new Map()); + assert.deepStrictEqual(pkg.main, null); + assert.ok(pkg.libraries.get('$std')); + }); +}); diff --git a/test/parser.test.js b/test/parser.test.js deleted file mode 100644 index 59188c9..0000000 --- a/test/parser.test.js +++ /dev/null @@ -1,6916 +0,0 @@ -'use strict'; - -const expect = require('expect.js'); - -const Parser = require('../lib/parser'); -const Lexer = require('../lib/lexer'); - -function parse(source, filePath) { - const lexer = new Lexer(source, filePath); - const parser = new Parser(lexer); - return parser.program(); -} - -function pos(line, column) { - return { line, column }; -} - -function loc(startLine, startColumn, endLine, endColumn) { - return { - start: pos(startLine, startColumn), - end: pos(endLine, endColumn) - }; -} - -function string(val, sl, sc, el, ec, ti, ts, te) { - return { - 'type': 'string', - 'loc': loc(sl, sc, el, ec), - 'tokenRange': [ts, te], - 'value': { - 'index': ti, - 'loc': loc(sl, sc, el, ec), - 'string': val, - 'tag': 1 - } - }; -} - -describe('parser', function () { - - it('empty module should ok', function () { - expect(parse('', '__filename')).to.be.eql({ - 'annotation': undefined, - 'comments': {}, - 'imports': [], - 'extends': undefined, - 'moduleBody': { - 'nodes': [], - 'type': 'moduleBody' - }, - 'type': 'module' - }); - - expect(parse(` -/** - * module description - */ -`, '__filename')).to.be.eql({ - 'imports': [], - 'extends': undefined, - 'comments': {}, - 'annotation': { - 'index': 1, - 'tag': 19, - 'value': '/**\n * module description\n */', - 'loc': loc(2, 1, 4, 4) - }, - 'moduleBody': { - 'nodes': [], - 'type': 'moduleBody' - }, - 'type': 'module' - }); - }); - - it('const should be ok', function () { - function moduleBody(value) { - var ast = parse(value, '__filename'); - return ast.moduleBody.nodes; - } - - expect(() => { - moduleBody('const'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect ID, but EOF`); - }); - - expect(() => { - moduleBody('const id'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect =, but EOF`); - }); - - expect(() => { - moduleBody('const id = '); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. const value must be STRING/NUMBER/BOOLEAN`); - }); - - expect(() => { - moduleBody('const id = "string"'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect ;, but EOF`); - }); - - expect(moduleBody('const id = "string";')).to.be.eql([ - { - 'annotation': undefined, - 'constName': { - 'index': 2, - 'lexeme': 'id', - 'tag': 2, - loc: loc(1, 7, 1, 9) - }, - 'constValue': { - 'index': 4, - 'string': 'string', - 'tag': 1, - loc: loc(1, 13, 1, 19) - }, - 'type': 'const', - 'tokenRange': [1, 5] - } - ]); - - expect(moduleBody('const id = 123;')).to.be.eql([ - { - 'annotation': undefined, - 'constName': { - 'index': 2, - 'lexeme': 'id', - 'tag': 2, - loc: loc(1, 7, 1, 9) - }, - 'constValue': { - 'index': 4, - 'value': 123, - 'type': 'integer', - 'tag': 9, - loc: loc(1, 12, 1, 15) - }, - 'type': 'const', - 'tokenRange': [1, 5] - } - ]); - }); - - it('type should be ok', function () { - function moduleBody(value) { - var ast = parse(` - ${value} - `, '__filename'); - return ast.moduleBody; - } - - expect(() => { - moduleBody('type'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect VID, but EOF`); - }); - - expect(() => { - moduleBody('type @id'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect =, but EOF`); - }); - - expect(() => { - moduleBody('type @id = '); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. expect base type, model id or array form`); - }); - - expect(moduleBody('type @id = string')).to.be.eql({ - 'nodes': [ - { - 'annotation': undefined, - 'type': 'type', - 'vid': { - 'index': 2, - 'lexeme': '@id', - 'tag': 3, - loc: loc(2, 14, 2, 17) - }, - 'value': { - 'index': 4, - 'lexeme': 'string', - 'tag': 8, - loc: loc(2, 20, 2, 26) - }, - 'tokenRange': [1, 5] - } - ], - 'type': 'moduleBody' - }); - - expect(moduleBody('type @id = map[string]string')).to.be.eql({ - 'nodes': [ - { - 'annotation': undefined, - 'tokenRange': [1, 9], - 'type': 'type', - 'vid': { - 'index': 2, - 'lexeme': '@id', - 'tag': 3, - loc: loc(2, 14, 2, 17) - }, - 'value': { - keyType: { - index: 6, - lexeme: 'string', - loc: loc(2, 24, 2, 30), - tag: 8 - }, - valueType: { - 'index': 8, - 'lexeme': 'string', - 'tag': 8, - loc: loc(2, 31, 2, 37) - }, - type: 'map', - 'loc': loc(2, 20, 2, 37) - } - } - ], - 'type': 'moduleBody' - }); - - expect(moduleBody('type @id = [ string ]')).to.be.eql({ - 'nodes': [ - { - 'annotation': undefined, - 'tokenRange': [1, 7], - 'type': 'type', - 'vid': { - 'index': 2, - 'lexeme': '@id', - 'tag': 3, - loc: loc(2, 14, 2, 17) - }, - 'value': { - 'subType': { - 'index': 5, - 'lexeme': 'string', - 'tag': 8, - loc: loc(2, 22, 2, 28) - }, - 'type': 'array' - } - } - ], - 'type': 'moduleBody' - }); - - // with ; should ok - expect(moduleBody('type @id = string;')).to.be.eql({ - 'nodes': [ - { - 'annotation': undefined, - 'tokenRange': [1, 5], - 'type': 'type', - 'vid': { - 'index': 2, - 'lexeme': '@id', - 'tag': 3, - loc: loc(2, 14, 2, 17) - }, - 'value': { - 'index': 4, - 'lexeme': 'string', - 'tag': 8, - loc: loc(2, 20, 2, 26) - } - } - ], - 'type': 'moduleBody' - }); - }); - - it('model should be ok', function () { - function moduleBody(value) { - var ast = parse(` - ${value} - `, '__filename'); - return ast.moduleBody; - } - - expect(() => { - moduleBody('model'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect ID, but EOF`); - }); - - expect(() => { - moduleBody('model id = {'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. only id is allowed`); - }); - - expect(moduleBody('model id = {}')).to.be.eql({ - 'nodes': [ - { - 'annotation': undefined, - 'tokenRange': [1, 5], - 'modelBody': { - 'nodes': [], - 'tokenRange': [4, 5], - 'type': 'modelBody' - }, - 'modelName': { - 'index': 2, - 'lexeme': 'id', - 'tag': 2, - loc: loc(2, 15, 2, 17) - }, - 'type': 'model', - } - ], - 'type': 'moduleBody' - }); - - expect(moduleBody('model id {}')).to.be.eql({ - 'nodes': [ - { - 'annotation': undefined, - 'modelBody': { - 'nodes': [], - 'tokenRange': [3, 4], - 'type': 'modelBody' - }, - 'modelName': { - 'index': 2, - 'lexeme': 'id', - 'tag': 2, - loc: loc(2, 15, 2, 17) - }, - 'tokenRange': [1, 4], - 'type': 'model', - } - ], - 'type': 'moduleBody' - }); - - expect(moduleBody('model id = {};')).to.be.eql({ - 'nodes': [ - { - 'annotation': undefined, - 'modelBody': { - 'nodes': [], - 'tokenRange': [4, 5], - 'type': 'modelBody' - }, - 'modelName': { - 'index': 2, - 'lexeme': 'id', - 'tag': 2, - loc: loc(2, 15, 2, 17) - }, - 'type': 'model', - 'tokenRange': [1, 6], - } - ], - 'type': 'moduleBody' - }); - }); - - it('model field should be ok', function () { - function modelField(value) { - var ast = parse(` - model id = { - ${value} - } - `, '__filename'); - return ast.moduleBody.nodes[0].modelBody; - } - - expect(() => { - modelField('?'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: ?. only id is allowed`); - }); - - expect(() => { - modelField(`name`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect :, but }`); - }); - - expect(() => { - modelField(`name?`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect :, but }`); - }); - - expect(() => { - modelField(`name?:`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect "{", "[", "string", "number", "map", ID`); - }); - - expect(modelField(`name?: string`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'name', - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'required': false, - 'tokenRange': [5, 9], - 'type': 'modelField' - } - ], - 'tokenRange': [4, 9], - 'type': 'modelBody' - }); - - expect(modelField(`object?: string`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'object', - 'tag': 2, - loc: loc(3, 11, 3, 17) - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'required': false, - 'tokenRange': [5, 9], - 'type': 'modelField' - } - ], - 'tokenRange': [4, 9], - 'type': 'modelBody' - }); - - expect(modelField(`name?: ID`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'name', - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'fieldType': { - 'index': 8, - 'lexeme': 'ID', - 'tag': 2, - loc: loc(3, 18, 3, 20) - }, - 'type': 'fieldType' - }, - 'tokenRange': [5, 9], - 'required': false, - 'type': 'modelField' - } - ], - 'tokenRange': [4, 9], - 'type': 'modelBody' - }); - - expect(modelField(`name?: {}`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'name', - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'nodes': [], - 'tokenRange': [8, 9], - 'type': 'modelBody' - }, - 'required': false, - 'tokenRange': [5, 10], - 'type': 'modelField' - } - ], - 'tokenRange': [4, 10], - 'type': 'modelBody' - }); - - expect(() => { - modelField(`name?: [`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect type or model name`); - }); - - expect(() => { - modelField(`name?: [ {`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect ], but EOF`); - }); - - expect(modelField(`name?: [{}]`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'name', - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'fieldType': 'array', - 'fieldItemType': { - 'nodes': [], - 'tokenRange': [9, 10], - 'type': 'modelBody' - }, - 'type': 'fieldType' - }, - 'tokenRange': [5, 12], - 'required': false, - 'type': 'modelField' - } - ], - 'tokenRange': [4, 12], - 'type': 'modelBody' - }); - - expect(modelField(`name?: [ string ]`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'name', - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'fieldType': 'array', - 'fieldItemType': { - 'index': 9, - 'lexeme': 'string', - 'tag': 8, - loc: loc(3, 20, 3, 26) - }, - 'type': 'fieldType' - }, - 'tokenRange': [5, 11], - 'required': false, - 'type': 'modelField' - } - ], - 'tokenRange': [4, 11], - 'type': 'modelBody' - }); - - expect(modelField(`name: string,`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'name', - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [5, 8], - 'type': 'modelField' - } - ], - 'tokenRange': [4, 9], - 'type': 'modelBody' - }); - - expect(modelField(`name: [ map[string]any ],`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'name', - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'fieldItemType': { - 'keyType': { - 'index': 10, - 'lexeme': 'string', - 'loc': loc(3, 23, 3, 29), - 'tag': 8 - }, - 'type': 'map', - 'valueType': { - 'index': 12, - 'lexeme': 'any', - 'loc': loc(3, 30, 3, 33), - 'tag': 8 - }, - loc: loc(3, 19, 3, 33) - }, - 'fieldType': 'array', - 'type': 'fieldType' - }, - 'tokenRange': [5, 14], - 'required': true, - 'type': 'modelField' - } - ], - 'tokenRange': [4, 15], - 'type': 'modelBody' - }); - - expect(modelField(`name?: string, name2: string`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'lexeme': 'name', - 'index': 5, - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'tokenRange': [5, 9], - 'required': false, - 'type': 'modelField' - }, - { - 'attrs': [], - 'fieldName': { - 'index': 10, - 'lexeme': 'name2', - 'tag': 2, - loc: loc(3, 26, 3, 31) - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'tokenRange': [10, 13], - 'required': true, - 'type': 'modelField' - } - ], - 'tokenRange': [4, 13], - 'type': 'modelBody' - }); - - expect(modelField(`name?: string, name2: string,`)).to.be.eql({ - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'name', - 'tag': 2, - loc: loc(3, 11, 3, 15) - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'tokenRange': [5, 9], - 'required': false, - 'type': 'modelField' - }, - { - 'attrs': [], - 'fieldName': { - 'index': 10, - 'lexeme': 'name2', - 'tag': 2, - loc: loc(3, 26, 3, 31) - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'tokenRange': [10, 13], - 'required': true, - 'type': 'modelField' - } - ], - 'tokenRange': [4, 14], - 'type': 'modelBody' - }); - - expect(() => { - modelField(`name: string {`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: {. expect ","`); - }); - }); - - it('model filed attrs should be ok', function () { - function modelFieldAttrs(value) { - var ast = parse(` - model id = { - name: string${value} - } - `, '__filename'); - return ast.moduleBody.nodes[0].modelBody.nodes[0].attrs; - } - - expect(() => { - modelFieldAttrs('('); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ID, but }`); - }); - - expect(() => { - modelFieldAttrs('(id'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect =, but }`); - }); - - expect(() => { - modelFieldAttrs('(id='); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect string, number, bool`); - }); - - expect(() => { - modelFieldAttrs('(id="attr_value"'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ,, but }`); - }); - - expect(() => { - modelFieldAttrs('(id="attr_value",'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ID, but }`); - }); - - expect(modelFieldAttrs('(id="attr_value")')).to.be.eql([ - { - 'attrName': { - 'index': 9, - 'lexeme': 'id', - 'tag': 2, - loc: loc(3, 24, 3, 26) - }, - 'attrValue': { - 'index': 11, - 'string': 'attr_value', - 'tag': 1, - loc: loc(3, 28, 3, 38) - }, - 'type': 'attr' - } - ]); - - expect(modelFieldAttrs('(id="attr_value",id2="value2")')).to.be.eql([ - { - 'attrName': { - 'index': 9, - 'lexeme': 'id', - 'tag': 2, - loc: loc(3, 24, 3, 26) - }, - 'attrValue': { - 'index': 11, - 'string': 'attr_value', - 'tag': 1, - loc: loc(3, 28, 3, 38) - }, - 'type': 'attr' - }, - { - 'attrName': { - 'index': 13, - 'lexeme': 'id2', - 'tag': 2, - loc: loc(3, 40, 3, 43) - }, - 'attrValue': { - 'index': 15, - 'string': 'value2', - 'tag': 1, - loc: loc(3, 45, 3, 51) - }, - 'type': 'attr' - } - ]); - }); - - it('api() should ok', function () { - function api(value) { - var ast = parse(` - api ${value} - `, '__filename'); - return ast.moduleBody.nodes[0].modelBody.nodes[0].attrs; - } - - expect(() => { - api(''); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect ID, but EOF`); - }); - - expect(() => { - api('id'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect (, but EOF`); - }); - - expect(() => { - api('id('); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect ID, but EOF`); - }); - - expect(() => { - api('id(name'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect :, but EOF`); - }); - - expect(() => { - api('id(name: '); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. expect base type, model id or array form`); - }); - - expect(() => { - api('id(name: string'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect ,, but EOF`); - }); - - expect(() => { - api('id(name: string ='); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect STRING, but EOF`); - }); - - expect(() => { - api('id()'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect :, but EOF`); - }); - }); - - it('api() params should ok', function () { - function params(value) { - var ast = parse(` - api id(${value}): string { - method = "GET"; - pathname = "/"; - } - `, '__filename'); - - const api = ast.moduleBody.nodes[0]; - return api.params.params; - } - - expect(params('')).to.eql([]); - - expect(params('id: string')).to.eql([ - { - 'defaultValue': null, - 'paramName': { - 'index': 4, - 'lexeme': 'id', - 'tag': 2, - loc: loc(2, 16, 2, 18) - }, - 'paramType': { - 'index': 6, - 'lexeme': 'string', - 'loc': loc(2, 20, 2, 26), - 'tag': 8 - }, - 'type': 'param' - } - ]); - }); - - it('api() return type should ok', function () { - expect(() => { - parse(` - api id(): { - - } - `, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: {. expect base type, model ` + - `id or array form`); - }); - - expect(() => { - parse(` - api id(): string { - - } - `, '__filename'); - }).to.not.throwException(); - }); - - it('api() returnBody should ok', function () { - function returns(value) { - var ast = parse(` - api id(): string { - __request.method = "GET"; - __request.pathname = "/"; - } returns ${value} - `, '__filename'); - - const api = ast.moduleBody.nodes[0]; - return api.apiBody; - } - - expect(() => { - returns(''); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. Expect {, but EOF`); - }); - - expect(() => { - returns('{'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. expect valid expression`); - }); - - expect(() => { - returns('{}'); - }).to.not.throwException(); - }); - - it('stmts should ok', function () { - function stmts(value) { - var ast = parse(` - api id(): string { - method = "GET"; - pathname = "/"; - } returns { - ${value} - } - `, '__filename'); - - const api = ast.moduleBody.nodes[0]; - return api.returns; - } - - expect(() => { - stmts('...'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: .. expect valid expression`); - }); - - expect(() => { - stmts('id'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ;, but }`); - }); - - expect(() => { - stmts('id = '); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect valid expression`); - }); - - expect(() => { - stmts('id = "string"'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ;, but }`); - }); - - expect(stmts('id = "string";')).to.eql({ - 'stmts': { - 'stmts': [ - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 21, - 'string': 'string', - 'tag': 1, - loc: loc(6, 17, 6, 23) - }, - 'tokenRange': [21, 22], - loc: loc(6, 17, 6, 23) - }, - left: { - type: 'variable', - id: { - 'index': 19, - 'lexeme': 'id', - 'tag': 2, - loc: loc(6, 11, 6, 13) - } - }, - 'tokenRange': [19, 22], - 'type': 'assign' - } - ], - 'tokenRange': [18, 23], - 'type': 'stmts' - }, - 'loc': loc(5, 19, 8, 7), - 'tokenRange': [18, 23], - 'type': 'returnBody' - }); - - expect(stmts('id.value = "string";')).to.eql({ - 'stmts': { - 'stmts': [ - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 23, - 'string': 'string', - 'tag': 1, - loc: loc(6, 23, 6, 29) - }, - 'tokenRange': [23, 24], - loc: loc(6, 23, 6, 29) - }, - left: { - type: 'property', - 'id': { - 'lexeme': 'id', - 'index': 19, - 'tag': 2, - loc: loc(6, 11, 6, 13) - }, - 'propertyPath': [ - { - 'index': 21, - 'lexeme': 'value', - 'tag': 2, - loc: loc(6, 14, 6, 19) - } - ] - }, - 'tokenRange': [19, 24], - 'type': 'assign' - } - ], - 'tokenRange': [18, 25], - 'type': 'stmts' - }, - 'tokenRange': [18, 25], - loc: loc(5, 19, 8, 7), - 'type': 'returnBody' - }); - - expect(() => { - stmts('var'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ID, but }`); - }); - - expect(() => { - stmts('var id'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect =, but }`); - }); - - expect(() => { - stmts('var id = '); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect valid expression`); - }); - - expect(() => { - stmts('var id = "string"'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ;, but }`); - }); - - expect(stmts('var id = "string";')).to.eql({ - 'stmts': { - 'stmts': [ - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 22, - 'string': 'string', - 'tag': 1, - loc: loc(6, 21, 6, 27) - }, - 'tokenRange': [22, 23], - loc: loc(6, 21, 6, 27) - }, - expectedType: undefined, - 'id': { - 'index': 20, - 'lexeme': 'id', - 'tag': 2, - loc: loc(6, 15, 6, 17) - }, - 'tokenRange': [19, 23], - 'type': 'declare' - } - ], - 'tokenRange': [18, 24], - 'type': 'stmts' - }, - 'tokenRange': [18, 24], - loc: loc(5, 19, 8, 7), - 'type': 'returnBody' - }); - - expect(() => { - stmts('return'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect valid expression`); - }); - - expect(() => { - stmts('return "string"'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ;, but }`); - }); - - expect(stmts('return "string";')).to.eql({ - 'stmts': { - 'stmts': [ - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 20, - 'string': 'string', - 'tag': 1, - loc: loc(6, 19, 6, 25) - }, - 'tokenRange': [20, 21], - loc: loc(6, 19, 6, 25) - }, - 'tokenRange': [19, 21], - 'type': 'return', - 'loc': loc(6, 11, 7, 9) - } - ], - 'tokenRange': [18, 22], - 'type': 'stmts' - }, - 'tokenRange': [18, 22], - loc: loc(5, 19, 8, 7), - 'type': 'returnBody' - }); - - expect(() => { - stmts('retry'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ;, but }`); - }); - - expect(stmts('retry;')).to.eql({ - 'stmts': { - 'stmts': [ - { - 'tokenRange': [19, 20], - 'type': 'retry', - 'loc': loc(6, 11, 6, 16) - } - ], - 'tokenRange': [18, 21], - 'type': 'stmts' - }, - 'tokenRange': [18, 21], - loc: loc(5, 19, 8, 7), - 'type': 'returnBody' - }); - - expect(stmts(` - retry; - retry; - `)).to.eql({ - 'stmts': { - 'stmts': [ - { - 'tokenRange': [19, 20], - 'type': 'retry', - 'loc': loc(7, 7, 7, 12) - }, - { - 'tokenRange': [21, 22], - 'type': 'retry', - 'loc': loc(8, 7, 8, 12) - } - ], - 'tokenRange': [18, 23], - 'type': 'stmts' - }, - 'tokenRange': [18, 23], - loc: loc(5, 19, 11, 7), - 'type': 'returnBody' - }); - - expect(() => { - stmts('throw'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect {, but }`); - }); - - expect(() => { - stmts('throw {'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. expect valid expression`); - }); - - expect(stmts('throw {}')).to.eql({ - 'stmts': { - 'stmts': [ - { - 'expr': { - 'fields': [], - 'type': 'object', - 'tokenRange': [20, 21], - loc: loc(6, 17, 7, 9) - }, - 'tokenRange': [19, 21], - 'type': 'throw' - } - ], - 'tokenRange': [18, 22], - 'type': 'stmts' - }, - 'tokenRange': [18, 22], - loc: loc(5, 19, 8, 7), - 'type': 'returnBody' - }); - - expect(stmts('throw {};')).to.eql({ - 'stmts': { - 'stmts': [ - { - 'expr': { - 'fields': [], - 'type': 'object', - 'tokenRange': [20, 21], - loc: loc(6, 17, 6, 19) - }, - 'tokenRange': [19, 22], - 'type': 'throw' - } - ], - 'tokenRange': [18, 23], - 'type': 'stmts' - }, - 'tokenRange': [18, 23], - loc: loc(5, 19, 8, 7), - 'type': 'returnBody' - }); - - expect(() => { - stmts('if'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect (, but }`); - }); - - expect(() => { - stmts('if ('); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect valid expression`); - }); - - expect(() => { - stmts('if (id'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ), but }`); - }); - - expect(() => { - stmts('if (id) '); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect {, but }`); - }); - - expect(() => { - stmts('if (id) {'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. expect valid expression`); - }); - - expect(stmts(`if (id) { - retry; - }`)).to.eql({ - 'stmts': { - 'stmts': [ - { - 'condition': { - 'id': { - 'index': 21, - 'lexeme': 'id', - 'tag': 2, - loc: loc(6, 15, 6, 17) - }, - 'tokenRange': [21, 22], - loc: loc(6, 15, 6, 17), - 'type': 'variable' - }, - 'elseIfs': [], - 'elseStmts': undefined, - 'stmts': { - 'stmts': [ - { - 'tokenRange': [24, 25], - 'type': 'retry', - 'loc': loc(7, 9, 7, 14) - } - ], - 'tokenRange': [23, 26], - 'type': 'stmts' - }, - 'tokenRange': [19, 26], - 'type': 'if' - } - ], - 'tokenRange': [18, 27], - 'type': 'stmts' - }, - 'tokenRange': [18, 27], - loc: loc(5, 19, 10, 7), - 'type': 'returnBody' - }); - - expect(() => { - stmts(`if (id) { - retry; - } else`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect "if" or "{"`); - }); - - expect(() => { - stmts(`if (id) { - retry; - } else if`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect (, but }`); - }); - - expect(() => { - stmts(`if (id) { - retry; - } else if (`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect valid expression`); - }); - - expect(() => { - stmts(`if (id) { - retry; - } else if (id2`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ), but }`); - }); - - expect(() => { - stmts(`if (id) { - retry; - } else if (id2)`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect {, but }`); - }); - - expect(() => { - stmts(`if (id) { - retry; - } else if (id2) {`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. expect valid expression`); - }); - - expect(() => { - stmts(`if (id) { - retry; - } else if (id2) { - retry;`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. expect valid expression`); - }); - - expect(stmts(`if (id) { - retry; - } else if (id2) { - retry; - }`)).to.eql({ - 'stmts': { - 'stmts': [ - { - 'condition': { - 'id': { - 'index': 21, - 'lexeme': 'id', - 'tag': 2, - loc: loc(6, 15, 6, 17) - }, - 'tokenRange': [21, 22], - loc: loc(6, 15, 6, 17), - 'type': 'variable' - }, - 'elseIfs': [ - { - 'condition': { - 'id': { - 'index': 30, - 'lexeme': 'id2', - 'tag': 2, - loc: loc(8, 18, 8, 21) - }, - loc: loc(8, 18, 8, 21), - 'tokenRange': [30, 31], - 'type': 'variable' - }, - 'stmts': { - 'stmts': [ - { - 'tokenRange': [33, 34], - 'type': 'retry', - 'loc': loc(9, 9, 9, 14) - } - ], - 'tokenRange': [32, 35], - 'type': 'stmts' - }, - 'type': 'elseif' - } - ], - 'elseStmts': undefined, - 'stmts': { - 'stmts': [ - { - 'type': 'retry', - 'tokenRange': [24, 25], - 'loc': loc(7, 9, 7, 14) - } - ], - 'tokenRange': [23, 26], - 'type': 'stmts' - }, - 'tokenRange': [19, 35], - 'type': 'if' - } - ], - 'tokenRange': [18, 36], - 'type': 'stmts' - }, - 'tokenRange': [18, 36], - loc: loc(5, 19, 12, 7), - 'type': 'returnBody' - }); - - expect(() => { - stmts(`if (id) { - retry; - } else {`); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: EOF. expect valid expression`); - }); - - expect(stmts(`if (id) { - retry; - } else { - retry; - }`)).to.eql({ - 'stmts': { - 'stmts': [ - { - 'condition': { - 'id': { - 'index': 21, - 'lexeme': 'id', - 'tag': 2, - loc: loc(6, 15, 6, 17) - }, - loc: loc(6, 15, 6, 17), - 'tokenRange': [21, 22], - 'type': 'variable' - }, - 'elseIfs': [], - 'elseStmts': { - 'stmts': [ - { - 'type': 'retry', - 'tokenRange': [29, 30], - 'loc': loc(9, 9, 9, 14) - } - ], - 'tokenRange': [28, 31], - 'type': 'stmts' - }, - 'stmts': { - 'stmts': [ - { - 'tokenRange': [24, 25], - 'type': 'retry', - 'loc': loc(7, 9, 7, 14) - } - ], - 'tokenRange': [23, 26], - 'type': 'stmts' - }, - 'tokenRange': [19, 31], - 'type': 'if' - } - ], - 'tokenRange': [18, 32], - 'type': 'stmts' - }, - 'tokenRange': [18, 32], - loc: loc(5, 19, 12, 7), - 'type': 'returnBody' - }); - - expect(stmts(`if (id) { - retry; - } else if (id2) { - retry; - } else { - retry; - }`)).to.eql({ - 'stmts': { - 'stmts': [ - { - 'condition': { - 'id': { - 'index': 21, - 'lexeme': 'id', - 'tag': 2, - loc: loc(6, 15, 6, 17) - }, - 'tokenRange': [21, 22], - loc: loc(6, 15, 6, 17), - 'type': 'variable' - }, - 'elseIfs': [ - { - 'condition': { - 'id': { - 'index': 30, - 'lexeme': 'id2', - 'tag': 2, - loc: loc(8, 18, 8, 21) - }, - loc: loc(8, 18, 8, 21), - 'tokenRange': [30, 31], - 'type': 'variable' - }, - 'stmts': { - 'stmts': [ - { - 'tokenRange': [33, 34], - 'type': 'retry', - 'loc': loc(9, 9, 9, 14) - } - ], - 'tokenRange': [32, 35], - 'type': 'stmts' - }, - 'type': 'elseif' - } - ], - 'elseStmts': { - 'stmts': [ - { - 'tokenRange': [38, 39], - 'type': 'retry', - 'loc': loc(11, 9, 11, 14) - } - ], - 'tokenRange': [37, 40], - 'type': 'stmts' - }, - 'stmts': { - 'stmts': [ - { - 'type': 'retry', - 'tokenRange': [24, 25], - 'loc': loc(7, 9, 7, 14) - } - ], - 'tokenRange': [23, 26], - 'type': 'stmts' - }, - 'tokenRange': [19, 40], - 'type': 'if' - } - ], - 'tokenRange': [18, 41], - 'type': 'stmts' - }, - loc: loc(5, 19, 14, 7), - 'tokenRange': [18, 41], - 'type': 'returnBody' - }); - }); - - it('expr should ok', function () { - function expr(value) { - var ast = parse(` - api id(): string { - method = "GET"; - pathname = "/"; - } returns { - id = ${value}; - } - `, '__filename'); - - const api = ast.moduleBody.nodes[0]; - return api.returns.stmts.stmts[0].expr; - } - - expect(() => { - expr(''); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: ;. expect valid expression`); - }); - - expect(expr('id2')).to.eql({ - 'id': { - 'index': 21, - 'lexeme': 'id2', - 'tag': 2, - loc: loc(6, 16, 6, 19) - }, - 'tokenRange': [21, 22], - loc: loc(6, 16, 6, 19), - 'type': 'variable' - }); - - expect(expr('id2.xprop')).to.eql({ - 'id': { - 'index': 21, - 'lexeme': 'id2', - 'tag': 2, - loc: loc(6, 16, 6, 19) - }, - 'loc': loc(6, 16, 6, 25), - 'propertyPath': [ - { - 'index': 23, - 'lexeme': 'xprop', - 'tag': 2, - loc: loc(6, 20, 6, 25) - } - ], - 'tokenRange': [21, 24], - 'type': 'property_access' - }); - - expect(expr('@vid')).to.eql({ - 'type': 'virtualVariable', - 'tokenRange': [21, 22], - 'vid': { - 'index': 21, - 'lexeme': '@vid', - 'tag': 3, - loc: loc(6, 16, 6, 20) - }, - 'loc': loc(6, 16, 6, 20) - }); - - expect(expr('@vid.x')).to.eql({ - 'id': { - 'index': 21, - 'lexeme': '@vid', - 'tag': 3, - loc: loc(6, 16, 6, 20) - }, - 'tokenRange': [21, 24], - 'type': 'property_access', - 'loc': loc(6, 16, 6, 22), - 'propertyPath': [ - { - 'index': 23, - 'lexeme': 'x', - 'loc': loc(6, 21, 6, 22), - 'tag': 2 - } - ] - }); - - expect(expr('@vid.x()')).to.eql({ - 'type': 'call', - 'tokenRange': [21, 26], - 'loc': loc(6, 16, 6, 24), - 'left': { - 'id': { - 'index': 21, - 'lexeme': '@vid', - 'tag': 3, - loc: loc(6, 16, 6, 20) - }, - 'propertyPath': [ - { - 'index': 23, - 'lexeme': 'x', - 'loc': loc(6, 21, 6, 22), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - }, - 'args': [] - }); - - expect(expr('@vid.x = 1')).to.eql({ - 'expr': { - 'loc': loc(6, 25, 6, 26), - 'tokenRange': [25, 26], - 'type': 'number', - 'value': { - 'index': 25, - 'value': 1, - 'type': 'integer', - 'tag': 9, - loc: loc(6, 25, 6, 26) - }, - }, - 'left': { - 'id': { - 'index': 21, - 'lexeme': '@vid', - 'tag': 3, - loc: loc(6, 16, 6, 20) - }, - 'propertyPath': [ - { - 'index': 23, - 'lexeme': 'x', - 'loc': loc(6, 21, 6, 22), - 'tag': 2 - } - ], - 'type': 'property' - }, - 'tokenRange': [21, 26], - 'type': 'assign' - }); - - expect(expr('`abcdefg`')).to.eql({ - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 21, - 'string': 'abcdefg', - 'tag': 12, - 'tail': true, - loc: loc(6, 17, 6, 24) - } - } - ], - 'tokenRange': [21, 22], - 'type': 'template_string' - }); - - expect(expr('`abc${"abc"}defg`')).to.eql({ - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 21, - 'string': 'abc', - 'tag': 12, - 'tail': false, - loc: loc(6, 17, 6, 20) - } - }, - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 22, - 'string': 'abc', - 'tag': 1, - loc: loc(6, 23, 6, 26) - }, - loc: loc(6, 23, 6, 26), - 'tokenRange': [22, 23], - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 23, - 'string': 'defg', - 'tag': 12, - 'tail': true, - loc: loc(6, 28, 6, 32) - } - } - ], - 'tokenRange': [21, 24], - 'type': 'template_string' - }); - - expect(expr('`abc${"abc"}d${"e"}fg`')).to.eql({ - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 21, - 'string': 'abc', - 'tag': 12, - 'tail': false, - loc: loc(6, 17, 6, 20) - } - }, - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 22, - 'string': 'abc', - 'tag': 1, - loc: loc(6, 23, 6, 26) - }, - 'tokenRange': [22, 23], - loc: loc(6, 23, 6, 26) - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 23, - 'string': 'd', - 'tag': 12, - 'tail': false, - loc: loc(6, 28, 6, 29) - } - }, - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 24, - 'string': 'e', - 'tag': 1, - loc: loc(6, 32, 6, 33) - }, - 'tokenRange': [24, 25], - loc: loc(6, 32, 6, 33) - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 25, - 'string': 'fg', - 'tag': 12, - 'tail': true, - loc: loc(6, 35, 6, 37) - } - } - ], - 'tokenRange': [21, 26], - 'type': 'template_string' - }); - - expect(expr('{}')).to.eql({ - 'fields': [], - 'type': 'object', - 'tokenRange': [21, 23], - loc: loc(6, 16, 6, 18) - }); - - expect(expr('{a = 1}')).to.eql({ - 'fields': [ - { - 'expr': { - 'type': 'number', - 'value': { - 'index': 24, - 'tag': 9, - 'value': 1, - 'type': 'integer', - loc: loc(6, 21, 6, 22) - }, - 'tokenRange': [24, 25], - loc: loc(6, 21, 6, 22) - }, - 'fieldName': { - 'index': 22, - 'lexeme': 'a', - 'tag': 2, - loc: loc(6, 17, 6, 18) - }, - 'tokenRange': [22, 25], - 'type': 'objectField' - } - ], - 'type': 'object', - 'tokenRange': [21, 26], - loc: loc(6, 16, 6, 23) - }); - - expect(expr('{a = 1,}')).to.eql({ - 'fields': [ - { - 'expr': { - 'type': 'number', - 'tokenRange': [24, 25], - 'value': { - 'index': 24, - 'tag': 9, - 'value': 1, - 'type': 'integer', - loc: loc(6, 21, 6, 22) - }, - loc: loc(6, 21, 6, 22) - }, - 'fieldName': { - 'index': 22, - 'lexeme': 'a', - 'tag': 2, - loc: loc(6, 17, 6, 18) - }, - 'tokenRange': [22, 25], - 'type': 'objectField' - } - ], - 'type': 'object', - 'tokenRange': [21, 27], - loc: loc(6, 16, 6, 24) - }); - - expect(expr('{a = 1, b = 2L, c = 1.2}')).to.eql({ - 'fields': [ - { - 'expr': { - 'type': 'number', - 'value': { - 'index': 24, - 'tag': 9, - 'type': 'integer', - 'value': 1, - loc: loc(6, 21, 6, 22) - }, - 'tokenRange': [24, 25], - loc: loc(6, 21, 6, 22) - }, - 'fieldName': { - 'index': 22, - 'lexeme': 'a', - 'tag': 2, - loc: loc(6, 17, 6, 18) - }, - 'tokenRange': [22, 25], - 'type': 'objectField' - }, - { - 'expr': { - 'type': 'number', - 'value': { - 'index': 28, - 'tag': 9, - 'type': 'long', - 'value': 2, - loc: loc(6, 28, 6, 30) - }, - 'tokenRange': [28, 29], - loc: loc(6, 28, 6, 30) - }, - 'fieldName': { - 'index': 26, - 'lexeme': 'b', - 'tag': 2, - loc: loc(6, 24, 6, 25) - }, - 'tokenRange': [26, 29], - 'type': 'objectField' - }, - { - 'expr': { - 'type': 'number', - 'value': { - 'index': 32, - 'tag': 9, - 'type': 'float', - 'value': 1.2, - loc: loc(6, 36, 6, 39) - }, - 'tokenRange': [32, 33], - loc: loc(6, 36, 6, 39) - }, - 'fieldName': { - 'index': 30, - 'lexeme': 'c', - 'tag': 2, - loc: loc(6, 32, 6, 33) - }, - 'tokenRange': [30, 33], - 'type': 'objectField' - } - ], - 'tokenRange': [21, 34], - 'type': 'object', - loc: loc(6, 16, 6, 40) - }); - - expect(expr('{a = 1, b = 2, c = 1.2d, d = 1.2f,}')).to.eql({ - 'fields': [ - { - 'expr': { - 'type': 'number', - 'value': { - 'index': 24, - 'tag': 9, - 'type': 'integer', - 'value': 1, - loc: loc(6, 21, 6, 22) - }, - 'tokenRange': [24, 25], - loc: loc(6, 21, 6, 22) - }, - 'fieldName': { - 'index': 22, - 'lexeme': 'a', - 'tag': 2, - loc: loc(6, 17, 6, 18) - }, - 'tokenRange': [22, 25], - 'type': 'objectField' - }, - { - 'expr': { - 'type': 'number', - 'value': { - 'index': 28, - 'tag': 9, - 'value': 2, - 'type': 'integer', - loc: loc(6, 28, 6, 29) - }, - 'tokenRange': [28, 29], - loc: loc(6, 28, 6, 29) - }, - 'fieldName': { - 'index': 26, - 'lexeme': 'b', - 'tag': 2, - loc: loc(6, 24, 6, 25) - }, - 'tokenRange': [26, 29], - 'type': 'objectField' - }, - { - 'expr': { - 'type': 'number', - 'value': { - 'index': 32, - 'tag': 9, - 'value': 1.2, - 'type': 'double', - loc: loc(6, 35, 6, 39) - }, - 'tokenRange': [32, 33], - loc: loc(6, 35, 6, 39) - }, - 'fieldName': { - 'index': 30, - 'lexeme': 'c', - 'tag': 2, - loc: loc(6, 31, 6, 32) - }, - 'tokenRange': [30, 33], - 'type': 'objectField' - }, - { - 'expr': { - 'type': 'number', - 'value': { - 'index': 36, - 'tag': 9, - 'value': 1.2, - 'type': 'float', - loc: loc(6, 45, 6, 49) - }, - 'tokenRange': [36, 37], - loc: loc(6, 45, 6, 49) - }, - 'fieldName': { - 'index': 34, - 'lexeme': 'd', - 'tag': 2, - loc: loc(6, 41, 6, 42) - }, - 'tokenRange': [34, 37], - 'type': 'objectField' - } - ], - 'tokenRange': [21, 39], - 'type': 'object', - loc: loc(6, 16, 6, 51) - }); - - expect(() => { - expr('{a = 1, b = 2.}'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: .. expect ","`); - }); - - expect(() => { - expr('{.}'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ., but }`); - }); - - expect(() => { - expr('{..}'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. Expect ., but }`); - }); - - expect(() => { - expr('{...}'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. expect valid expression`); - }); - - expect(() => { - expr('{*}}'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: *. expect "..." or ID`); - }); - - expect(expr('{...a}')).to.eql({ - 'fields': [ - { - 'expr': { - 'id': { - 'index': 25, - 'lexeme': 'a', - 'tag': 2, - loc: loc(6, 20, 6, 21) - }, - 'tokenRange': [25, 26], - loc: loc(6, 20, 6, 21), - 'type': 'variable' - }, - 'tokenRange': [22, 26], - 'type': 'expandField' - } - ], - 'tokenRange': [21, 27], - 'type': 'object', - loc: loc(6, 16, 6, 22) - }); - - expect(() => { - expr('[1 1]'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: Number: 1. expect ","`); - }); - - expect(expr('[]')).to.eql({ - 'items': [], - 'tokenRange': [21, 23], - 'type': 'array' - }); - }); - - it('global annotation should be ok', function () { - var ast = parse(` - /** - * global annotation - */ - `, '__filename'); - - expect(ast).to.eql({ - 'type': 'module', - 'extends': undefined, - 'imports': [], - 'moduleBody': { - 'nodes': [], - 'type': 'moduleBody' - }, - 'comments': {}, - 'annotation': { - 'index': 1, - loc: loc(2, 5, 4, 8), - tag: 19, - value: '/**\n * global annotation\n */' - } - }); - }); - - it('function annotation should be ok', function () { - var ast = parse(` - /** - * global annotation - */ - /** - * description - * @param key key description - * @return returns value - */ - static function hello(key: string): string; - `, '__filename'); - const [fun] = ast.moduleBody.nodes; - expect(fun.annotation).to.eql({ - 'index': 2, - loc: loc(5, 5, 9, 8), - tag: 19, - value: '/**\n * description\n * @param key key description\n * @return returns value\n */' - }); - }); - - it('only api/function/const/model should be ok', function () { - expect(function () { - parse(` - public - `, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('Unexpected token: Word: `public`. expect "const", "type", "model", "function", "init" or "api"'); - }); - }); - - it('runtime should ok', function () { - var ast = parse(` - api id(settings: object): string { - __request.method = 'GET'; - __request.pathname = '/'; - } runtime { - ignoreSSL = false, - timeout = { - value = 3000, - timeouted = 'retry' - }, - retry = { - retryable = true, - policy = 'simple', - max-retry = settings.max-attempts - }, - backoff = { - policy= 'no' - } - } - `, '__filename'); - - const api = ast.moduleBody.nodes[0]; - - expect(api).to.eql({ - 'annotation': undefined, - 'apiBody': { - 'type': 'apiBody', - 'tokenRange': [10, 23], - 'stmts': { - 'type': 'stmts', - 'tokenRange': [10, 23], - 'stmts': [ - { - 'expr': { - 'loc': { - 'end': { - 'column': 32, - 'line': 3 - }, - 'start': { - 'column': 29, - 'line': 3 - }, - }, - 'tokenRange': [15, 16], - 'type': 'string', - 'value': { - 'index': 15, - 'loc': { - 'end': { - 'column': 32, - 'line': 3 - }, - 'start': { - 'column': 29, - 'line': 3 - } - }, - 'string': 'GET', - 'tag': 1 - } - }, - 'left': { - 'id': { - 'index': 11, - 'lexeme': '__request', - 'loc': loc(3, 9, 3, 18), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 13, - 'lexeme': 'method', - 'tag': 2, - loc: loc(3, 19, 3, 25) - } - ], - 'type': 'property', - }, - 'tokenRange': [11, 16], - 'type': 'assign' - }, - { - 'expr': { - 'tokenRange': [21, 22], - 'type': 'string', - loc: loc(4, 31, 4, 32), - 'value': { - 'index': 21, - 'loc': loc(4, 31, 4, 32), - 'tag': 1, - 'string': '/' - }, - }, - 'left': { - 'id': { - 'index': 17, - 'lexeme': '__request', - 'loc': loc(4, 9, 4, 18), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 19, - lexeme: 'pathname', - 'loc': loc(4, 19, 4, 27), - 'tag': 2 - } - ], - 'type': 'property', - }, - 'tokenRange': [17, 22], - 'type': 'assign' - } - ] - } - }, - 'apiName': { - 'index': 2, - 'lexeme': 'id', - 'tag': 2, - loc: loc(2, 11, 2, 13) - }, - 'params': { - 'params': [ - { - 'defaultValue': null, - 'paramName': { - 'index': 4, - 'lexeme': 'settings', - 'tag': 2, - loc: loc(2, 14, 2, 22) - }, - 'paramType': { - 'index': 6, - 'lexeme': 'object', - 'loc': loc(2, 24, 2, 30), - 'tag': 8 - }, - 'type': 'param' - } - ], - 'type': 'params' - }, - 'returnType': { - 'index': 9, - 'lexeme': 'string', - 'tag': 8, - loc: loc(2, 33, 2, 39) - }, - 'returns': undefined, - 'runtimeBody': { - 'fields': [ - { - 'expr': { - 'type': 'boolean', - 'tokenRange': [28, 29], - 'value': false, - loc: loc(6, 21, 6, 26) - }, - 'fieldName': { - 'index': 26, - 'lexeme': 'ignoreSSL', - 'tag': 2, - loc: loc(6, 9, 6, 18) - }, - 'tokenRange': [26, 29], - 'type': 'objectField' - }, - { - 'expr': { - 'fields': [ - { - 'expr': { - 'type': 'number', - 'tokenRange': [35, 36], - 'value': { - 'index': 35, - 'tag': 9, - 'value': 3000, - 'type': 'integer', - loc: loc(8, 19, 8, 23) - }, - loc: loc(8, 19, 8, 23) - }, - 'fieldName': { - 'index': 33, - 'lexeme': 'value', - 'tag': 2, - loc: loc(8, 11, 8, 16) - }, - 'tokenRange': [33, 36], - 'type': 'objectField' - }, - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 39, - 'string': 'retry', - 'tag': 1, - loc: loc(9, 24, 9, 29) - }, - 'tokenRange': [39, 40], - loc: loc(9, 24, 9, 29) - }, - 'fieldName': { - 'index': 37, - 'lexeme': 'timeouted', - 'tag': 2, - loc: loc(9, 11, 9, 20) - }, - 'tokenRange': [37, 40], - 'type': 'objectField' - } - ], - loc: loc(7, 19, 10, 10), - 'tokenRange': [32, 41], - 'type': 'object' - }, - 'fieldName': { - 'index': 30, - 'lexeme': 'timeout', - 'tag': 2, - loc: loc(7, 9, 7, 16) - }, - 'tokenRange': [30, 41], - 'type': 'objectField' - }, - { - 'expr': { - 'fields': [ - { - 'expr': { - 'type': 'boolean', - 'tokenRange': [47, 48], - 'value': true, - loc: loc(12, 23, 12, 27) - }, - 'fieldName': { - 'index': 45, - 'lexeme': 'retryable', - 'tag': 2, - loc: loc(12, 11, 12, 20) - }, - 'tokenRange': [45, 48], - 'type': 'objectField' - }, - { - 'expr': { - 'type': 'string', - 'tokenRange': [51, 52], - 'value': { - 'index': 51, - 'string': 'simple', - 'tag': 1, - loc: loc(13, 21, 13, 27) - }, - loc: loc(13, 21, 13, 27) - }, - 'fieldName': { - 'index': 49, - 'lexeme': 'policy', - 'tag': 2, - loc: loc(13, 11, 13, 17) - }, - 'tokenRange': [49, 52], - 'type': 'objectField' - }, - { - 'expr': { - 'id': { - 'index': 55, - 'lexeme': 'settings', - 'tag': 2, - loc: loc(14, 23, 14, 31) - }, - 'propertyPath': [ - { - 'index': 57, - 'lexeme': 'max-attempts', - 'tag': 2, - loc: loc(14, 32, 14, 44) - } - ], - loc: loc(14, 23, 15, 9), - 'tokenRange': [55, 58], - 'type': 'property_access' - }, - 'fieldName': { - 'index': 53, - 'lexeme': 'max-retry', - 'tag': 2, - loc: loc(14, 11, 14, 20) - }, - 'tokenRange': [53, 58], - 'type': 'objectField' - } - ], - 'tokenRange': [44, 59], - 'type': 'object', - loc: loc(11, 17, 15, 10) - }, - 'fieldName': { - 'index': 42, - 'lexeme': 'retry', - 'tag': 2, - loc: loc(11, 9, 11, 14) - }, - 'tokenRange': [42, 59], - 'type': 'objectField' - }, - { - 'expr': { - 'fields': [ - { - 'expr': { - 'type': 'string', - 'tokenRange': [65, 66], - 'value': { - 'index': 65, - 'string': 'no', - 'tag': 1, - loc: loc(17, 20, 17, 22) - }, - loc: loc(17, 20, 17, 22) - }, - 'fieldName': { - 'index': 63, - 'lexeme': 'policy', - 'tag': 2, - loc: loc(17, 11, 17, 17) - }, - 'tokenRange': [63, 66], - 'type': 'objectField' - } - ], - 'tokenRange': [62, 67], - 'type': 'object', - loc: loc(16, 19, 19, 7) - }, - 'fieldName': { - 'index': 60, - 'lexeme': 'backoff', - 'tag': 2, - loc: loc(16, 9, 16, 16) - }, - 'tokenRange': [60, 67], - 'type': 'objectField' - } - ], - 'tokenRange': [25, 67], - 'type': 'object', - loc: loc(5, 17, 20, 5) - }, - 'tokenRange': [1, 67], - 'type': 'api' - }); - }); - - it('function should ok', function () { - var ast = parse(` - api id(): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - function callId(): string { - return id(); - } - - function callId2(): void { - return id(); - } - `, '__filename'); - - const [api, wrap, wrap2] = ast.moduleBody.nodes; - - expect(api).to.eql({ - 'annotation': undefined, - 'apiBody': { - 'stmts': { - 'stmts': [ - { - 'expr': { - 'type': 'string', - 'tokenRange': [12, 13], - 'value': { - 'index': 12, - 'string': 'GET', - 'tag': 1, - loc: loc(3, 29, 3, 32) - }, - loc: loc(3, 29, 3, 32) - }, - 'left': { - 'id': { - 'index': 8, - 'lexeme': '__request', - 'tag': 2, - loc: loc(3, 9, 3, 18) - }, - 'type': 'property', - 'propertyPath': [ - { - 'index': 10, - 'lexeme': 'method', - 'loc': { - 'end': { - 'column': 25, - 'line': 3 - }, - 'start': { - 'column': 19, - 'line': 3 - } - }, - 'tag': 2 - } - ] - }, - 'tokenRange': [8, 13], - 'type': 'assign' - }, - { - 'expr': { - 'type': 'string', - 'value': { - 'index': 18, - 'string': '/', - 'tag': 1, - loc: loc(4, 31, 4, 32) - }, - 'tokenRange': [18, 19], - loc: loc(4, 31, 4, 32) - }, - 'left': { - 'id': { - 'index': 14, - 'lexeme': '__request', - 'tag': 2, - loc: loc(4, 9, 4, 18) - }, - 'propertyPath': [ - { - 'index': 16, - 'lexeme': 'pathname', - 'loc': loc(4, 19, 4, 27), - 'tag': 2 - } - ], - 'type': 'property' - }, - 'tokenRange': [14, 19], - 'type': 'assign' - }, - ], - 'tokenRange': [7, 20], - 'type': 'stmts' - }, - 'tokenRange': [7, 20], - 'type': 'apiBody' - }, - 'apiName': { - 'index': 2, - 'lexeme': 'id', - 'tag': 2, - loc: loc(2, 11, 2, 13) - }, - 'params': { - 'params': [], - 'type': 'params' - }, - 'returnType': { - 'index': 6, - 'lexeme': 'string', - 'loc': loc(2, 17, 2, 23), - 'tag': 8 - }, - 'returns': undefined, - 'runtimeBody': undefined, - 'tokenRange': [1, 20], - 'type': 'api' - }); - - expect(wrap).to.eql({ - 'annotation': undefined, - 'isStatic': false, - 'isAsync': false, - 'hasThrow': false, - 'functionBody': { - 'type': 'functionBody', - 'tokenRange': [27, 33], - 'stmts': { - 'stmts': [ - { - 'expr': { - 'args': [], - left: { - 'type': 'method_call', - 'id': { - 'index': 29, - 'lexeme': 'id', - 'loc': loc(8, 16, 8, 18), - 'tag': 2 - }, - }, - 'tokenRange': [29, 32], - 'type': 'call', - 'loc': loc(8, 16, 8, 20) - }, - 'tokenRange': [28, 32], - 'type': 'return', - 'loc': loc(8, 9, 9, 7) - } - ], - 'tokenRange': [27, 33], - 'type': 'stmts' - }, - loc: loc(7, 33, 11, 15) - }, - 'functionName': { - 'index': 22, - 'lexeme': 'callId', - 'tag': 2, - loc: loc(7, 16, 7, 22) - }, - 'params': { - 'params': [], - 'type': 'params' - }, - 'returnType': { - 'index': 26, - 'lexeme': 'string', - 'tag': 8, - loc: loc(7, 26, 7, 32) - }, - 'tokenRange': [21, 33], - 'type': 'function' - }); - - expect(wrap2).to.eql({ - 'annotation': undefined, - 'isStatic': false, - 'isAsync': false, - 'hasThrow': false, - 'functionBody': { - 'type': 'functionBody', - 'tokenRange': [40, 46], - loc: loc(11, 32, 14, 5), - 'stmts': { - 'stmts': [ - { - 'expr': { - 'args': [], - left: { - 'id': { - 'index': 42, - 'lexeme': 'id', - 'loc': loc(12, 16, 12, 18), - 'tag': 2 - }, - 'type': 'method_call' - }, - 'tokenRange': [42, 45], - 'type': 'call', - 'loc': loc(12, 16, 12, 20) - }, - 'tokenRange': [41, 45], - 'type': 'return', - 'loc': loc(12, 9, 13, 7) - } - ], - 'tokenRange': [40, 46], - 'type': 'stmts' - } - }, - 'functionName': { - 'index': 35, - 'lexeme': 'callId2', - 'tag': 2, - loc: loc(11, 16, 11, 23) - }, - 'params': { - 'params': [], - 'type': 'params' - }, - 'returnType': { - 'index': 39, - 'lexeme': 'void', - 'loc': loc(11, 27, 11, 31), - 'tag': 8 - }, - 'tokenRange': [34, 46], - 'type': 'function' - }); - }); - - it('function with throws should ok', function () { - var ast = parse(` - function callId() throws : string; - function callId2(): string; - `, '__filename'); - - const [func, func2] = ast.moduleBody.nodes; - - expect(func.hasThrow).to.be(true); - expect(func2.hasThrow).to.be(false); - }); - - it('function should ok without body', function () { - var ast = parse(` - function callId(): string; - function callId2(): string - `, '__filename'); - - const [func1, func2] = ast.moduleBody.nodes; - - expect(func1).to.eql({ - 'annotation': undefined, - 'isStatic': false, - 'isAsync': false, - 'hasThrow': false, - 'params': { - 'params': [], - 'type': 'params' - }, - 'returnType': { - 'index': 6, - 'lexeme': 'string', - 'loc': loc(2, 28, 2, 34), - 'tag': 8 - }, - 'tokenRange': [1, 7], - 'type': 'function', - 'functionBody': null, - 'functionName': { - 'index': 2, - 'lexeme': 'callId', - 'loc': loc(2, 18, 2, 24), - 'tag': 2 - } - }); - expect(func2).to.eql({ - 'annotation': undefined, - 'isStatic': false, - 'isAsync': false, - 'hasThrow': false, - 'params': { - 'params': [], - 'type': 'params' - }, - 'returnType': { - 'index': 13, - 'lexeme': 'string', - 'loc': loc(3, 29, 3, 35), - 'tag': 8 - }, - 'tokenRange': [8, 14], - 'type': 'function', - 'functionBody': null, - 'functionName': { - 'index': 9, - 'lexeme': 'callId2', - 'loc': loc(3, 18, 3, 25), - 'tag': 2 - } - }); - }); - - it('map[string]string should ok', function () { - var ast = parse(` - model mymodel = { - key: map[string][string] - } - `, '__filename'); - - const [model] = ast.moduleBody.nodes; - - expect(model.type).to.be('model'); - const [field] = model.modelBody.nodes; - expect(field).to.eql({ - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'key', - 'loc': { - 'end': { - 'column': 12, - 'line': 3 - }, - 'start': { - 'column': 9, - 'line': 3 - }, - }, - 'tag': 2 - }, - 'fieldValue': { - 'fieldType': 'map', - 'keyType': { - 'index': 9, - 'lexeme': 'string', - 'loc': { - 'end': { - 'column': 24, - 'line': 3 - }, - 'start': { - 'column': 18, - 'line': 3 - }, - }, - 'tag': 8, - }, - 'type': 'fieldType', - 'valueType': { - 'subType': { - 'index': 12, - 'lexeme': 'string', - 'loc': loc(3, 26, 3, 32), - 'tag': 8 - }, - 'type': 'array', - }, - }, - 'required': true, - 'tokenRange': [5, 14], - 'type': 'modelField' - }); - }); - - it('map[string]moduleModel should ok', function(){ - var ast = parse(` - import oss; - - model A { - B: map[string]oss.C - } - `, '__filename'); - const [model] = ast.moduleBody.nodes; - expect(model.type).to.be('model'); - expect(model.modelBody.nodes[0].fieldValue.valueType).to.be.eql({ - 'type': 'subModel_or_moduleModel', - 'path': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 5, - 'column': 23 - }, - 'end': { - 'line': 5, - 'column': 26 - } - }, - 'lexeme': 'oss', - 'index': 13 - }, - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 5, - 'column': 27 - }, - 'end': { - 'line': 5, - 'column': 28 - } - }, - 'lexeme': 'C', - 'index': 15 - } - ], - 'loc': { - 'start': { - 'line': 5, - 'column': 23 - }, - 'end': { - 'line': 5, - 'column': 28 - } - } - }); - - ast = parse(` - import oss; - - init(){ - var test: map[string] oss.C = {}; - } - `, '__filename'); - const [init] = ast.moduleBody.nodes; - expect(init.initBody.stmts[0].expectedType).to.be.eql({ - 'loc': { - 'start': { - 'line': 5, - 'column': 19 - }, - 'end': { - 'line': 5, - 'column': 36 - } - }, - 'type': 'map', - 'keyType': { - 'tag': 8, - 'loc': { - 'start': { - 'line': 5, - 'column': 23 - }, - 'end': { - 'line': 5, - 'column': 29 - } - }, - 'lexeme': 'string', - 'index': 13 - }, - 'valueType': { - 'type': 'subModel_or_moduleModel', - 'path': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 5, - 'column': 31 - }, - 'end': { - 'line': 5, - 'column': 34 - } - }, - 'lexeme': 'oss', - 'index': 15 - }, - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 5, - 'column': 35 - }, - 'end': { - 'line': 5, - 'column': 36 - } - }, - 'lexeme': 'C', - 'index': 17 - } - ], - 'loc': { - 'start': { - 'line': 5, - 'column': 31 - }, - 'end': { - 'line': 5, - 'column': 36 - } - } - } - }); - }); - - it('import should ok', function () { - var ast = parse(`import oss - -`, '__filename'); - expect(ast.imports).to.eql([ - { - 'index': 2, - 'lexeme': 'oss', - 'tokenRange': [1, 2], - 'loc': loc(1, 8, 1, 11), - 'tag': 2 - } - ]); - }); - - it('import with comma should ok', function () { - var ast = parse(`import oss; - -`, '__filename'); - expect(ast.imports).to.eql([ - { - 'index': 2, - 'lexeme': 'oss', - 'loc': loc(1, 8, 1, 11), - 'tag': 2, - 'tokenRange': [1, 3], - } - ]); - }); - - it('init should ok', function () { - var ast = parse(` - init(); -`, '__filename'); - const [init] = ast.moduleBody.nodes; - expect(init.type).to.be('init'); - expect(init.params).to.eql({ - 'params': [], - 'type': 'params' - }); - }); - - it('init without comma should ok', function () { - var ast = parse(` - init() -`, '__filename'); - const [init] = ast.moduleBody.nodes; - expect(init.type).to.be('init'); - expect(init.params).to.eql({ - 'params': [], - 'type': 'params' - }); - }); - - it('init(config) should ok', function () { - var ast = parse(` - model Config = { - AK: string - }; - - init(config: Config); -`, '__filename'); - const [model, init] = ast.moduleBody.nodes; - expect(model.type).to.be('model'); - expect(model.modelName).to.eql({ - 'index': 2, - 'lexeme': 'Config', - 'loc': loc(2, 9, 2, 15), - 'tag': 2 - }); - expect(init.type).to.be('init'); - expect(init.params).to.eql({ - 'params': [ - { - 'defaultValue': null, - 'paramName': { - 'index': 12, - 'lexeme': 'config', - 'loc': loc(6, 8, 6, 14), - 'tag': 2 - }, - 'paramType': { - 'index': 14, - 'lexeme': 'Config', - 'loc': loc(6, 16, 6, 22), - 'tag': 2, - }, - 'type': 'param' - } - ], - 'type': 'params' - }); - }); - - it('import/new instance should ok', function () { - var ast = parse(`import oss - - - function callOSS(): string { - var client = new oss(); - client.putObject(); - return "OK"; - } - -`, '__filename'); - - const [fun] = ast.moduleBody.nodes; - expect(fun.type).to.be('function'); - expect(fun.functionName).to.eql({ - 'index': 4, - 'lexeme': 'callOSS', - 'loc': loc(4, 12, 4, 19), - 'tag': 2 - }); - expect(fun.functionBody).to.be.ok(); - expect(fun.functionBody.stmts.stmts).to.eql([ - { - 'expr': { - 'aliasId': { - 'index': 14, - 'lexeme': 'oss', - 'loc': loc(5, 22, 5, 25), - 'tag': 2 - }, - 'args': [], - 'tokenRange': [13, 17], - 'type': 'construct' - }, - 'id': { - 'index': 11, - 'lexeme': 'client', - 'loc': loc(5, 9, 5, 15), - 'tag': 2 - }, - expectedType: undefined, - 'tokenRange': [10, 17], - 'type': 'declare' - }, - { - 'args': [], - left: { - 'type': 'static_or_instance_call', - 'id': { - 'index': 18, - 'lexeme': 'client', - 'loc': loc(6, 5, 6, 11), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 20, - 'lexeme': 'putObject', - 'loc': loc(6, 12, 6, 21), - 'tag': 2 - } - ], - }, - loc: loc(6, 5, 6, 23), - 'tokenRange': [18, 23], - 'type': 'call' - }, - { - 'expr': { - 'type': 'string', - 'tokenRange': [25, 26], - 'value': { - 'index': 25, - 'loc': loc(7, 13, 7, 15), - 'string': 'OK', - 'tag': 1 - }, - 'loc': loc(7, 13, 7, 15) - }, - 'tokenRange': [24, 26], - 'type': 'return', - 'loc': loc(7, 5, 8, 3) - } - ]); - }); - - it('new extern model should ok', function () { - var ast = parse(`import oss - - - function callOSS(): string { - var config = new oss.Config; - return "OK"; - } - -`, '__filename'); - - const [fun] = ast.moduleBody.nodes; - expect(fun.type).to.be('function'); - expect(fun.functionName).to.eql({ - 'index': 4, - 'lexeme': 'callOSS', - 'loc': loc(4, 12, 4, 19), - 'tag': 2 - }); - expect(fun.functionBody).to.be.ok(); - expect(fun.functionBody.stmts.stmts).to.eql([ - { - 'expr': { - 'aliasId': { - 'index': 14, - 'lexeme': 'oss', - 'loc': loc(5, 22, 5, 25), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 16, - 'lexeme': 'Config', - 'loc': loc(5, 26, 5, 32), - 'tag': 2 - } - ], - 'object': null, - 'tokenRange': [13, 17], - 'type': 'construct_model' - }, - 'id': { - 'index': 11, - 'lexeme': 'config', - 'loc': loc(5, 9, 5, 15), - 'tag': 2 - }, - expectedType: undefined, - 'tokenRange': [10, 17], - 'type': 'declare' - }, - { - 'expr': { - 'type': 'string', - 'tokenRange': [19, 20], - 'value': { - 'index': 19, - 'loc': loc(6, 13, 6, 15), - 'string': 'OK', - 'tag': 1 - }, - 'loc': loc(6, 13, 6, 15) - }, - 'tokenRange': [18, 20], - 'type': 'return', - 'loc': loc(6, 5, 7, 3) - } - ]); - }); - - it('new extern model with literal should ok', function () { - var ast = parse(`import oss - - - function callOSS(): string { - var config = new oss.Config{ - accessKeyId = "ak", - }; - return "OK"; - } - -`, '__filename'); - - const [fun] = ast.moduleBody.nodes; - expect(fun.type).to.be('function'); - expect(fun.functionName).to.eql({ - 'index': 4, - 'lexeme': 'callOSS', - 'loc': loc(4, 12, 4, 19), - 'tag': 2 - }); - expect(fun.functionBody).to.be.ok(); - expect(fun.functionBody.stmts.stmts).to.eql([ - { - 'expr': { - 'aliasId': { - 'index': 14, - 'lexeme': 'oss', - 'loc': loc(5, 22, 5, 25), - 'tag': 2 - }, - propertyPath: [ - { - 'index': 16, - 'lexeme': 'Config', - 'loc': loc(5, 26, 5, 32), - 'tag': 2 - } - ], - 'object': { - 'fields': [ - { - 'expr': string('ak', 6, 22, 6, 24, 20, 20, 21), - 'fieldName': { - 'index': 18, - 'lexeme': 'accessKeyId', - 'loc': loc(6, 7, 6, 18), - 'tag': 2 - }, - 'tokenRange': [18, 21], - 'type': 'objectField' - } - ], - 'tokenRange': [17, 22], - 'type': 'object', - loc: loc(5, 32, 7, 6) - }, - 'tokenRange': [13, 23], - 'type': 'construct_model' - }, - 'id': { - 'index': 11, - 'lexeme': 'config', - 'loc': loc(5, 9, 5, 15), - 'tag': 2 - }, - expectedType: undefined, - 'tokenRange': [10, 23], - 'type': 'declare' - }, - { - 'expr': string('OK', 8, 13, 8, 15, 25, 25, 26), - 'type': 'return', - 'tokenRange': [24, 26], - 'loc': loc(8, 5, 9, 3) - } - ]); - }); - - it('module call in expr should ok', function () { - var ast = parse(`import oss - - function callOSS(): string { - var client = new oss(); - var result = client.putObject(); - return "OK"; - } -`, '__filename'); - - const [fun] = ast.moduleBody.nodes; - expect(fun.type).to.be('function'); - expect(fun.functionName).to.eql({ - 'index': 4, - 'lexeme': 'callOSS', - 'loc': loc(3, 12, 3, 19), - 'tag': 2 - }); - expect(fun.functionBody).to.be.ok(); - expect(fun.functionBody.stmts.stmts).to.eql([ - { - 'expr': { - 'aliasId': { - 'index': 14, - 'lexeme': 'oss', - 'loc': loc(4, 22, 4, 25), - 'tag': 2 - }, - 'args': [], - 'tokenRange': [13, 17], - 'type': 'construct' - }, - 'id': { - 'index': 11, - 'lexeme': 'client', - 'loc': loc(4, 9, 4, 15), - 'tag': 2 - }, - expectedType: undefined, - 'tokenRange': [10, 17], - 'type': 'declare' - }, - { - 'expr': { - 'args': [], - left: { - type: 'static_or_instance_call', - id: { - 'index': 21, - 'lexeme': 'client', - 'loc': loc(5, 18, 5, 24), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 23, - 'lexeme': 'putObject', - 'loc': loc(5, 25, 5, 34), - 'tag': 2 - } - ], - }, - 'tokenRange': [21, 26], - 'type': 'call', - 'loc': loc(5, 18, 5, 36) - }, - 'id': { - 'index': 19, - 'lexeme': 'result', - 'loc': loc(5, 9, 5, 15), - 'tag': 2 - }, - expectedType: undefined, - 'tokenRange': [18, 26], - 'type': 'declare' - }, - { - 'expr': string('OK', 6, 13, 6, 15, 28, 28, 29), - 'tokenRange': [27, 29], - 'type': 'return', - 'loc': loc(6, 5, 7, 3) - } - ]); - }); - - it('rpc() should ok', function () { - var ast = parse(`import oss - - - rpc rpcName(): string { - - } - - `, '__filename'); - const [rpc] = ast.moduleBody.nodes; - expect(rpc.type).to.be('rpc'); - expect(rpc.rpcName).to.eql({ - 'index': 4, - 'lexeme': 'rpcName', - 'loc': loc(4, 11, 4, 18), - 'tag': 2 - }); - }); - - it('declare in Request block should ok', function () { - var ast = parse(` - api apiName(): string { - var id = "random_id"; - } - `, '__filename'); - const [api] = ast.moduleBody.nodes; - expect(api.type).to.be('api'); - expect(api.apiBody.stmts.stmts).to.eql([ - { - 'expr': string('random_id', 3, 19, 3, 28, 11, 11, 12), - 'id': { - 'index': 9, - 'lexeme': 'id', - 'loc': loc(3, 13, 3, 15), - 'tag': 2 - }, - expectedType: undefined, - 'tokenRange': [8, 12], - 'type': 'declare' - } - ]); - }); - - it('declare with expected type should ok', function () { - var ast = parse(` - - function func(): void { - var a : string = ""; - } - `, '__filename'); - const [func] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'type': 'declare', - 'tokenRange': [8, 14], - 'id': { - 'index': 9, - 'lexeme': 'a', - loc: loc(4, 13, 4, 14), - 'tag': 2 - }, - 'expectedType': { - 'index': 11, - 'lexeme': 'string', - loc: loc(4, 17, 4, 23), - 'tag': 8 - }, - 'expr': { - 'loc': loc(4, 27, 4, 27), - 'type': 'string', - 'tokenRange': [13, 14], - 'value': { - 'index': 13, - 'loc': loc(4, 27, 4, 27), - 'string': '', - 'tag': 1 - } - } - } - ]); - }); - - it('static should ok', function () { - var ast = parse(` - - static function equal(actual: any, expected: any, message: string): void { - } - `, '__filename'); - const [fun] = ast.moduleBody.nodes; - expect(fun.type).to.be('function'); - expect(fun.isStatic).to.be(true); - }); - - it('async function should ok', function () { - var ast = parse(` - - async function equal(actual: any, expected: any, message: string): void { - } - `, '__filename'); - const [fun] = ast.moduleBody.nodes; - expect(fun.type).to.be('function'); - expect(fun.isAsync).to.be(true); - }); - - it('null should ok', function () { - var ast = parse(` - - function equal(): any { - return null; - } - `, '__filename'); - const [fun] = ast.moduleBody.nodes; - expect(fun).to.eql({ - 'annotation': undefined, - 'isStatic': false, - 'isAsync': false, - 'hasThrow': false, - 'params': { - 'params': [], - 'type': 'params' - }, - 'returnType': { - 'index': 6, - 'lexeme': 'any', - 'loc': loc(3, 25, 3, 28), - 'tag': 8 - }, - 'tokenRange': [1, 11], - 'type': 'function', - 'functionBody': { - 'loc': loc(3, 29, 6, 5), - 'stmts': { - 'stmts': [ - { - 'expr': { - 'tokenRange': [9, 10], - 'type': 'null' - }, - 'tokenRange': [8, 10], - 'type': 'return', - 'loc': loc(4, 9, 5, 7) - } - ], - 'tokenRange': [7, 11], - 'type': 'stmts' - }, - 'tokenRange': [7, 11], - 'type': 'functionBody' - }, - 'functionName': { - 'index': 2, - 'lexeme': 'equal', - 'loc': loc(3, 16, 3, 21), - 'tag': 2 - } - }); - }); - - it('subModel type should ok', function () { - var ast = parse(` - import oss - - model A { - B: { - str: string - } - } - - function equal(): A.B { - - } - `, '__filename'); - const [, fun] = ast.moduleBody.nodes; - expect(fun.type).to.be('function'); - expect(fun.returnType).to.be.eql({ - type: 'subModel_or_moduleModel', - loc: { - start: { line: 10, column: 25 }, - end: { line: 10, column: 28 } - }, - path: [ - { - tag: 2, - loc: loc(10, 25, 10, 26), - index: 19, - lexeme: 'A' - }, - { - tag: 2, - loc: loc(10, 27, 10, 28), - index: 21, - lexeme: 'B' - } - ] - }); - }); - - it('return void should ok', function () { - var ast = parse(` - - static function equal(actual: any, expected: any, message: string): void { - return; - } - `, '__filename'); - const [fun] = ast.moduleBody.nodes; - expect(fun.type).to.be('function'); - expect(fun.isStatic).to.be(true); - const [returnExpr] = fun.functionBody.stmts.stmts; - expect(returnExpr).to.eql({ - 'loc': loc(4, 9, 5, 7), - 'tokenRange': [20, 21], - 'type': 'return' - }); - }); - - it('if stmt in request block should ok', function () { - var ast = parse(` - import Util - api set_password(request: DefaultSetPasswordRequest): void { - __request.method = 'POST'; - __request.pathname = '/'; - if (Util.notEmpty(@accessToken)) { - __request.headers.authorization = \`Bearer \${@accessToken}\`; - } - } - `, '__filename'); - const [api] = ast.moduleBody.nodes; - expect(api.type).to.be('api'); - expect(api.apiBody.type).to.be('apiBody'); - const [, , ifBlock] = api.apiBody.stmts.stmts; - expect(ifBlock).to.eql({ - 'condition': { - 'left': { - 'id': { - 'index': 27, - 'lexeme': 'Util', - 'loc': loc(6, 13, 6, 17), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 29, - 'lexeme': 'notEmpty', - 'loc': loc(6, 18, 6, 26), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - }, - 'args': [ - { - 'type': 'virtualVariable', - 'vid': { - 'index': 31, - 'lexeme': '@accessToken', - 'loc': loc(6, 27, 6, 39), - 'tag': 3 - }, - 'tokenRange': [31, 32], - 'loc': loc(6, 27, 6, 39), - } - ], - 'loc': loc(6, 13, 6, 40), - 'tokenRange': [27, 33], - 'type': 'call' - }, - 'elseIfs': [], - 'elseStmts': undefined, - 'stmts': { - 'stmts': [ - { - 'expr': { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 41, - 'loc': loc(7, 46, 7, 53), - 'string': 'Bearer ', - 'tag': 12, - 'tail': false - } - }, - { - 'expr': { - 'type': 'virtualVariable', - 'tokenRange': [42, 43], - 'vid': { - 'index': 42, - 'lexeme': '@accessToken', - 'loc': loc(7, 55, 7, 67), - 'tag': 3 - }, - 'loc': loc(7, 55, 7, 67), - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 43, - 'loc': loc(7, 68, 7, 68), - 'string': '', - 'tag': 12, - 'tail': true - } - } - ], - 'tokenRange': [41, 44], - 'type': 'template_string' - }, - 'left': { - 'id': { - 'index': 35, - 'lexeme': '__request', - 'loc': loc(7, 11, 7, 20), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 37, - 'lexeme': 'headers', - 'loc': loc(7, 21, 7, 28), - 'tag': 2 - }, - { - 'index': 39, - 'lexeme': 'authorization', - 'loc': loc(7, 29, 7, 42), - 'tag': 2 - } - ], - 'type': 'property' - }, - 'tokenRange': [35, 44], - 'type': 'assign' - } - ], - 'tokenRange': [34, 45], - 'type': 'stmts' - }, - 'tokenRange': [25, 45], - 'type': 'if' - }); - }); - - it('if/elseif stmt in request block should ok', function () { - var ast = parse(` - import Util - api set_password(request: DefaultSetPasswordRequest): void { - __request.method = 'POST'; - __request.pathname = '/'; - if (Util.notEmpty(@accessToken)) { - __request.headers.authorization = \`Bearer \${@accessToken}\`; - } else if (Util.notEmpty(@accessKeyId)) { - __request.headers.authorization = \`Bearer \${@accessToken}\`; - } - } - `, '__filename'); - const [api] = ast.moduleBody.nodes; - expect(api.type).to.be('api'); - expect(api.apiBody.type).to.be('apiBody'); - const [, , ifBlock] = api.apiBody.stmts.stmts; - expect(ifBlock).to.eql({ - 'condition': { - 'args': [ - { - 'type': 'virtualVariable', - 'tokenRange': [31, 32], - 'vid': { - 'index': 31, - 'lexeme': '@accessToken', - 'loc': loc(6, 27, 6, 39), - 'tag': 3 - }, - 'loc': loc(6, 27, 6, 39), - } - ], - 'left': { - 'id': { - 'index': 27, - 'lexeme': 'Util', - 'loc': loc(6, 13, 6, 17), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 29, - 'lexeme': 'notEmpty', - loc: loc(6, 18, 6, 26), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - }, - 'loc': loc(6, 13, 6, 40), - 'tokenRange': [27, 33], - 'type': 'call' - }, - 'elseIfs': [ - { - 'condition': { - 'args': [ - { - 'type': 'virtualVariable', - 'tokenRange': [53, 54], - 'vid': { - 'index': 53, - 'lexeme': '@accessKeyId', - loc: loc(8, 34, 8, 46), - 'tag': 3 - }, - loc: loc(8, 34, 8, 46), - } - ], - loc: loc(8, 20, 8, 47), - 'left': { - 'id': { - 'index': 49, - 'lexeme': 'Util', - 'loc': loc(8, 20, 8, 24), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 51, - 'lexeme': 'notEmpty', - 'loc': loc(8, 25, 8, 33), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - }, - 'tokenRange': [49, 55], - 'type': 'call', - }, - 'stmts': { - 'stmts': [ - { - 'expr': { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 63, - 'loc': loc(9, 46, 9, 53), - 'string': 'Bearer ', - 'tag': 12, - 'tail': false - } - }, - { - 'expr': { - 'loc': loc(9, 55, 9, 67), - 'tokenRange': [64, 65], - 'type': 'virtualVariable', - 'vid': { - 'index': 64, - 'lexeme': '@accessToken', - 'loc': loc(9, 55, 9, 67), - 'tag': 3 - } - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 65, - 'loc': loc(9, 68, 9, 68), - 'string': '', - 'tag': 12, - 'tail': true - } - } - ], - 'tokenRange': [63, 66], - 'type': 'template_string' - }, - 'left': { - 'id': { - 'index': 57, - 'lexeme': '__request', - 'loc': loc(9, 11, 9, 20), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 59, - 'lexeme': 'headers', - loc: loc(9, 21, 9, 28), - 'tag': 2 - }, - { - 'index': 61, - 'lexeme': 'authorization', - 'loc': loc(9, 29, 9, 42), - 'tag': 2 - } - ], - 'type': 'property' - }, - 'tokenRange': [57, 66], - 'type': 'assign' - } - ], - 'tokenRange': [56, 67], - 'type': 'stmts' - }, - 'type': 'elseif' - } - ], - 'elseStmts': undefined, - 'stmts': { - 'stmts': [ - { - 'expr': { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 41, - loc: loc(7, 46, 7, 53), - 'string': 'Bearer ', - 'tag': 12, - 'tail': false - } - }, - { - 'expr': { - 'type': 'virtualVariable', - 'tokenRange': [42, 43], - 'vid': { - 'index': 42, - 'lexeme': '@accessToken', - loc: loc(7, 55, 7, 67), - 'tag': 3 - }, - loc: loc(7, 55, 7, 67), - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 43, - loc: loc(7, 68, 7, 68), - 'string': '', - 'tag': 12, - 'tail': true - } - } - ], - 'tokenRange': [41, 44], - 'type': 'template_string' - }, - 'left': { - 'id': { - 'index': 35, - 'lexeme': '__request', - loc: loc(7, 11, 7, 20), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 37, - 'lexeme': 'headers', - loc: loc(7, 21, 7, 28), - 'tag': 2 - }, - { - 'index': 39, - 'lexeme': 'authorization', - loc: loc(7, 29, 7, 42), - 'tag': 2 - } - ], - 'type': 'property' - }, - 'tokenRange': [35, 44], - 'type': 'assign' - } - ], - 'tokenRange': [34, 45], - 'type': 'stmts' - }, - 'tokenRange': [25, 67], - 'type': 'if' - }); - }); - - it('only if or { should be after else', function () { - expect(function () { - parse(` - import Util - api set_password(request: DefaultSetPasswordRequest): void { - method = 'POST'; - pathname = '/'; - if (Util.notEmpty(@accessToken)) { - headers.authorization = \`Bearer \${@accessToken}\`; - } else x { - headers.authorization = \`Bearer \${@accessToken}\`; - } - } - `, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('Unexpected token: Word: `x`. expect "if" or "{"'); - }); - }); - - it('assign should ok', function () { - var ast = parse(` - function func(): void { - @id = "string"; - } - `, '__filename'); - const [func] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func).to.eql({ - 'annotation': undefined, - 'isAsync': false, - 'isStatic': false, - 'hasThrow': false, - 'params': { - 'params': [], - 'type': 'params' - }, - 'returnType': { - 'index': 6, - 'lexeme': 'void', - loc: loc(2, 24, 2, 28), - 'tag': 8 - }, - 'type': 'function', - 'tokenRange': [1, 12], - 'functionBody': { - loc: loc(2, 30, 5, 5), - 'stmts': { - 'stmts': [ - { - 'expr': string('string', 3, 16, 3, 22, 10, 10, 11), - left: { - type: 'virtualVariable', - 'vid': { - 'index': 8, - 'lexeme': '@id', - loc: loc(3, 9, 3, 12), - 'tag': 3 - }, - }, - loc: loc(3, 9, 3, 23), - 'tokenRange': [8, 11], - 'type': 'assign' - } - ], - 'tokenRange': [7, 12], - 'type': 'stmts' - }, - 'tokenRange': [7, 12], - 'type': 'functionBody' - }, - 'functionName': { - 'index': 2, - 'lexeme': 'func', - loc: loc(2, 16, 2, 20), - 'tag': 2 - } - }); - }); - - it('try/catch should ok', function () { - var ast = parse(` - - import Util - - function func(): void { - try { - Util.print("try block"); - } catch (ex) { - Util.print(\`error message: \${ex.message}\`); - } - } - `, '__filename'); - const [func] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'type': 'try', - 'tryBlock': { - 'stmts': [ - { - 'args': [ - string('try block', 7, 23, 7, 32, 16, 16, 17) - ], - 'left': { - 'id': { - 'index': 12, - 'lexeme': 'Util', - 'loc': loc(7, 11, 7, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 14, - 'lexeme': 'print', - loc: loc(7, 16, 7, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - }, - loc: loc(7, 11, 7, 34), - 'tokenRange': [12, 18], - 'type': 'call', - } - ], - 'tokenRange': [11, 19], - 'type': 'stmts' - }, - 'catchId': { - 'index': 22, - 'lexeme': 'ex', - loc: loc(8, 18, 8, 20), - 'tag': 2 - }, - 'catchBlock': { - stmts: [ - { - 'args': [ - { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 29, - loc: loc(9, 23, 9, 38), - 'string': 'error message: ', - 'tag': 12, - 'tail': false - } - }, - { - 'expr': { - 'id': { - 'index': 30, - 'lexeme': 'ex', - loc: loc(9, 40, 9, 42), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 32, - 'lexeme': 'message', - loc: loc(9, 43, 9, 50), - 'tag': 2 - } - ], - loc: loc(9, 40, 9, 52), - 'tokenRange': [30, 33], - 'type': 'property_access' - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 33, - loc: loc(9, 51, 9, 51), - 'string': '', - 'tag': 12, - 'tail': true - } - } - ], - 'tokenRange': [29, 34], - 'type': 'template_string' - } - ], - 'left': { - 'id': { - 'index': 25, - 'lexeme': 'Util', - 'loc': loc(9, 11, 9, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 27, - 'lexeme': 'print', - loc: loc(9, 16, 9, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - }, - loc: loc(9, 11, 9, 53), - 'tokenRange': [25, 35], - 'type': 'call', - } - ], - 'tokenRange': [24, 36], - 'type': 'stmts' - }, - 'tokenRange': [10, 36], - finallyBlock: null - } - ]); - }); - - it('try/catch/finally should ok', function () { - var ast = parse(` - - import Util - - function func(): void { - try { - Util.print("try block"); - } catch (ex) { - Util.print(\`error message: \${ex.message}\`); - } finally { - Util.print("finally block"); - } - } - `, '__filename'); - let [func] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'type': 'try', - 'tokenRange': [10, 46], - 'tryBlock': { - 'stmts': [ - { - 'args': [ - string('try block', 7, 23, 7, 32, 16, 16, 17) - ], - loc: loc(7, 11, 7, 34), - 'type': 'call', - 'tokenRange': [12, 18], - 'left': { - 'id': { - 'index': 12, - 'lexeme': 'Util', - 'loc': loc(7, 11, 7, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 14, - 'lexeme': 'print', - 'loc': loc(7, 16, 7, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - } - } - ], - 'tokenRange': [11, 19], - 'type': 'stmts' - }, - 'catchId': { - 'index': 22, - 'lexeme': 'ex', - loc: loc(8, 18, 8, 20), - 'tag': 2 - }, - 'catchBlock': { - stmts: [ - { - 'args': [ - { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 29, - loc: loc(9, 23, 9, 38), - 'string': 'error message: ', - 'tag': 12, - 'tail': false - } - }, - { - 'expr': { - 'id': { - 'index': 30, - 'lexeme': 'ex', - loc: loc(9, 40, 9, 42), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 32, - 'lexeme': 'message', - loc: loc(9, 43, 9, 50), - 'tag': 2 - } - ], - loc: loc(9, 40, 9, 52), - 'tokenRange': [30, 33], - 'type': 'property_access' - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 33, - loc: loc(9, 51, 9, 51), - 'string': '', - 'tag': 12, - 'tail': true - } - } - ], - 'tokenRange': [29, 34], - 'type': 'template_string' - } - ], - loc: loc(9, 11, 9, 53), - 'type': 'call', - 'tokenRange': [25, 35], - 'left': { - 'id': { - 'index': 25, - 'lexeme': 'Util', - 'loc': loc(9, 11, 9, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 27, - 'lexeme': 'print', - 'loc': loc(9, 16, 9, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - } - } - ], - 'tokenRange': [24, 36], - 'type': 'stmts' - }, - 'finallyBlock': { - 'stmts': [ - { - 'args': [ - string('finally block', 11, 23, 11, 36, 43, 43, 44) - ], - loc: loc(11, 11, 11, 38), - 'type': 'call', - 'tokenRange': [39, 45], - 'left': { - 'id': { - 'index': 39, - 'lexeme': 'Util', - 'loc': loc(11, 11, 11, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 41, - 'lexeme': 'print', - 'loc': loc(11, 16, 11, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - } - } - ], - 'tokenRange': [38, 46], - 'type': 'stmts' - } - } - ]); - ast = parse(` - - import Util - - function func(): void { - try { - Util.print("try block"); - } finally { - Util.print("finally block"); - } - } - `, '__filename'); - - ast = parse(` - - import Util - - function func(): void { - try { - Util.print("try block"); - } catch (ex) { - Util.print(\`error message: \${ex.message}\`); - } - } - `, '__filename'); - [ func ] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'type': 'try', - 'tokenRange': [10, 36], - 'tryBlock': { - 'stmts': [ - { - 'args': [ - string('try block', 7, 23, 7, 32, 16, 16, 17) - ], - loc: loc(7, 11, 7, 34), - 'type': 'call', - 'tokenRange': [12, 18], - 'left': { - 'id': { - 'index': 12, - 'lexeme': 'Util', - 'loc': loc(7, 11, 7, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 14, - 'lexeme': 'print', - 'loc': loc(7, 16, 7, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - } - } - ], - 'tokenRange': [11, 19], - 'type': 'stmts' - }, - 'catchId': { - 'index': 22, - 'lexeme': 'ex', - loc: loc(8, 18, 8, 20), - 'tag': 2 - }, - 'catchBlock': { - stmts: [ - { - 'args': [ - { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 29, - loc: loc(9, 23, 9, 38), - 'string': 'error message: ', - 'tag': 12, - 'tail': false - } - }, - { - 'expr': { - 'id': { - 'index': 30, - 'lexeme': 'ex', - loc: loc(9, 40, 9, 42), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 32, - 'lexeme': 'message', - loc: loc(9, 43, 9, 50), - 'tag': 2 - } - ], - loc: loc(9, 40, 9, 52), - 'tokenRange': [30, 33], - 'type': 'property_access' - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 33, - loc: loc(9, 51, 9, 51), - 'string': '', - 'tag': 12, - 'tail': true - } - } - ], - 'tokenRange': [29, 34], - 'type': 'template_string' - } - ], - loc: loc(9, 11, 9, 53), - 'type': 'call', - 'tokenRange': [25, 35], - 'left': { - 'id': { - 'index': 25, - 'lexeme': 'Util', - 'loc': loc(9, 11, 9, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 27, - 'lexeme': 'print', - 'loc': loc(9, 16, 9, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - } - } - ], - 'tokenRange': [24, 36], - 'type': 'stmts' - }, - 'finallyBlock': null - } - ]); - ast = parse(` - - import Util - - function func(): void { - try { - Util.print("try block"); - } finally { - Util.print("finally block"); - } - } - `, '__filename'); - - [ func ] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'type': 'try', - 'tokenRange': [10, 29], - 'tryBlock': { - 'stmts': [ - { - 'args': [ - string('try block', 7, 23, 7, 32, 16, 16, 17) - ], - loc: loc(7, 11, 7, 34), - 'type': 'call', - 'tokenRange': [12, 18], - 'left': { - 'id': { - 'index': 12, - 'lexeme': 'Util', - 'loc': loc(7, 11, 7, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 14, - 'lexeme': 'print', - 'loc': loc(7, 16, 7, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - } - } - ], - 'tokenRange': [11, 19], - 'type': 'stmts' - }, - 'catchId': null, - 'catchBlock': null, - 'finallyBlock': { - 'stmts': [ - { - 'args': [ - string('finally block', 9, 23, 9, 36, 26, 26, 27) - ], - loc: loc(9, 11, 9, 38), - 'type': 'call', - 'tokenRange': [22, 28], - 'left': { - 'id': { - 'index': 22, - 'lexeme': 'Util', - 'loc': loc(9, 11, 9, 15), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 24, - 'lexeme': 'print', - 'loc': loc(9, 16, 9, 21), - 'tag': 2 - } - ], - 'type': 'static_or_instance_call' - } - } - ], - 'tokenRange': [21, 29], - 'type': 'stmts' - } - } - ]); - expect(() => { - parse(` - - import Util - - function func(): void { - try { - Util.print("try block"); - } - } - `, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Unexpected token: }. "try" expect "catch" or "finally"`); - }); - }); - - it('while should ok', function () { - var ast = parse(` - - function func(): void { - while (true) { - - } - } - `, '__filename'); - const [func] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'type': 'while', - 'tokenRange': [8, 13], - 'condition': { - loc: loc(4, 16, 4, 20), - 'tokenRange': [10, 11], - 'type': 'boolean', - 'value': true - }, - 'stmts': { - 'stmts': [], - 'tokenRange': [12, 13], - 'type': 'stmts' - } - } - ]); - }); - - it('for should ok', function () { - var ast = parse(` - - function func(): void { - for (var id : []) { - } - } - `, '__filename'); - const [func] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'type': 'for', - 'tokenRange': [8, 17], - 'list': { - 'tokenRange': [13, 15], - 'items': [], - 'type': 'array' - }, - 'id': { - 'index': 11, - 'lexeme': 'id', - loc: loc(4, 18, 4, 20), - 'tag': 2 - }, - 'stmts': { - 'stmts': [], - 'tokenRange': [16, 17], - 'type': 'stmts' - } - } - ]); - }); - - it('break should ok', function () { - var ast = parse(` - - function func(): void { - for (var id : []) { - break; - } - } - `, '__filename'); - const [func] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'type': 'for', - 'tokenRange': [8, 19], - 'list': { - 'items': [], - 'tokenRange': [13, 15], - 'type': 'array' - }, - 'id': { - 'index': 11, - 'lexeme': 'id', - loc: loc(4, 18, 4, 20), - 'tag': 2 - }, - 'stmts': { - 'stmts': [ - { - 'tokenRange': [17, 18], - 'type': 'break' - } - ], - 'tokenRange': [16, 19], - 'type': 'stmts' - } - } - ]); - }); - - it('support class in parameters', function () { - var ast = parse(` - model M = { - N: { - - } - } - static function test(a: class): void; - function func(): void { - test(M.N); - } - `, '__filename'); - - const [, type, func] = ast.moduleBody.nodes; - expect(type).to.eql({ - 'annotation': undefined, - 'type': 'function', - 'tokenRange': [10, 20], - 'functionBody': null, - 'functionName': { - 'index': 12, - 'lexeme': 'test', - 'loc': loc(7, 23, 7, 27), - 'tag': 2 - }, - 'isAsync': false, - 'isStatic': true, - 'hasThrow': false, - 'params': { - 'params': [ - { - 'defaultValue': null, - 'paramName': { - 'index': 14, - 'lexeme': 'a', - 'loc': loc(7, 28, 7, 29), - 'tag': 2 - }, - 'paramType': { - 'index': 16, - 'lexeme': 'class', - 'loc': loc(7, 31, 7, 36), - 'tag': 8 - }, - 'type': 'param' - } - ], - 'type': 'params' - }, - 'returnType': { - 'index': 19, - 'lexeme': 'void', - 'loc': loc(7, 39, 7, 43), - 'tag': 8 - } - }); - - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'args': [ - { - 'id': { - 'index': 30, - 'lexeme': 'M', - 'loc': loc(9, 14, 9, 15), - 'tag': 2 - }, - 'loc': loc(9, 14, 9, 17), - 'propertyPath': [ - { - 'index': 32, - 'lexeme': 'N', - loc: loc(9, 16, 9, 17), - 'tag': 2 - } - ], - 'tokenRange': [30, 33], - 'type': 'property_access' - } - ], - 'left': { - 'id': { - 'index': 28, - 'lexeme': 'test', - 'loc': loc(9, 9, 9, 13), - 'tag': 2, - }, - 'type': 'method_call' - }, - 'loc': loc(9, 9, 9, 18), - 'tokenRange': [28, 34], - 'type': 'call', - } - ]); - }); - - it('support not expr', function () { - var ast = parse(` - - static function func(): boolean { - return !true; - } - `, '__filename'); - - const [func] = ast.moduleBody.nodes; - expect(func.functionBody.stmts.stmts).to.eql([ - { - 'expr': { - 'expr': { - 'loc': loc(4, 17, 4, 21), - 'type': 'boolean', - 'value': true - }, - 'tokenRange': [10, 12], - 'type': 'not', - loc: loc(4, 16, 4, 21) - }, - loc: loc(4, 9, 5, 7), - 'tokenRange': [9, 12], - 'type': 'return' - } - ]); - }); - - it('support init body', function () { - var ast = parse(` - init(){ - }`, '__filename'); - - const [init] = ast.moduleBody.nodes; - expect(init.type).to.equal('init'); - expect(init.initBody).to.eql({ - type: 'stmts', - 'tokenRange': [4, 5], - stmts: [] - }); - }); - - it('extends should ok', function () { - var ast = parse(` - extends Base; - `, '__filename'); - - expect(ast.extends).to.eql({ - 'index': 2, - 'lexeme': 'Base', - 'loc': loc(2, 13, 2, 17), - 'tokenRange': [1, 3], - 'tag': 2 - }); - }); - - it('extends should ok(comma is optional)', function () { - var ast = parse(` - extends Base - `, '__filename'); - - expect(ast.extends).to.eql({ - 'index': 2, - 'lexeme': 'Base', - 'loc': loc(2, 13, 2, 17), - 'tag': 2, - 'tokenRange': [1, 2], - }); - }); - - it('comment around global annotation should ok', function () { - var ast = parse(` - // front annotation comment - /** - * global annotation - */ - // back annotation comment - `, '__filename'); - - expect(ast.annotation).to.eql({ - 'index': 2, - 'loc': loc(3, 5, 5, 7), - 'value': '/**\n * global annotation\n */', - 'tag': 19 - }); - - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 32), - 'value': '// front annotation comment', - 'tag': 20 - }); - expect(ast.comments.get(3)).to.eql({ - 'index': 3, - 'loc': loc(6, 5, 6, 31), - 'value': '// back annotation comment', - 'tag': 20 - }); - }); - - it('comment about import should ok', function () { - var ast = parse(` - // front import comment - import oss - // back import comment - `, '__filename'); - - expect(ast.imports).to.eql([{ - 'index': 3, - 'loc': loc(3, 12, 3, 15), - 'lexeme': 'oss', - 'tokenRange': [2, 3], - 'tag': 2 - }]); - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 28), - 'value': '// front import comment', - 'tag': 20 - }); - expect(ast.comments.get(4)).to.eql({ - 'index': 4, - 'loc': loc(4, 5, 4, 27), - 'value': '// back import comment', - 'tag': 20 - }); - - ast = parse(` - // front oss import comment - import oss - // front util import comment - import Util - // back import comment - `, '__filename'); - - expect(ast.imports).to.eql([{ - 'index': 3, - 'loc': loc(3, 12, 3, 15), - 'lexeme': 'oss', - 'tokenRange': [2, 3], - 'tag': 2 - }, { - 'index': 6, - 'loc': loc(5, 13, 5, 17), - 'lexeme': 'Util', - 'tokenRange': [5, 6], - 'tag': 2 - }]); - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 32), - 'value': '// front oss import comment', - 'tag': 20 - }); - expect(ast.comments.get(4)).to.eql({ - 'index': 4, - 'loc': loc(4, 5, 4, 33), - 'value': '// front util import comment', - 'tag': 20 - }); - expect(ast.comments.get(7)).to.eql({ - 'index': 7, - 'loc': loc(6, 5, 6, 27), - 'value': '// back import comment', - 'tag': 20 - }); - }); - - it('comment about model should ok', function () { - var ast = parse(` - // front model comment - model M{ - // empty model - } - // back model comment - `, '__filename'); - let [model] = ast.moduleBody.nodes; - expect(model).to.eql({ - 'type': 'model', - 'annotation': undefined, - 'tokenRange': [2, 6], - 'modelBody': { - 'nodes': [], - 'tokenRange': [4, 6], - 'type': 'modelBody' - }, - 'modelName': { - 'index': 3, - 'lexeme': 'M', - 'tag': 2, - loc: loc(3, 11, 3, 12) - }, - }); - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 27), - 'value': '// front model comment', - 'tag': 20 - }); - expect(ast.comments.get(5)).to.eql({ - 'index': 5, - 'loc': loc(4, 7, 4, 21), - 'value': '// empty model', - 'tag': 20 - }); - expect(ast.comments.get(7)).to.eql({ - 'index': 7, - 'loc': loc(6, 5, 6, 26), - 'value': '// back model comment', - 'tag': 20 - }); - - ast = parse(` - model M{ - // attr1 comment - attr1: string, - attr2: string - // attr2 comment - } - `, '__filename'); - - [model] = ast.moduleBody.nodes; - expect(model).to.eql({ - 'annotation': undefined, - 'type': 'model', - 'modelName': { - 'tag': 2, - 'loc': loc(2, 11, 2, 12), - 'lexeme': 'M', - 'index': 2 - }, - 'modelBody': { - 'type': 'modelBody', - 'nodes': [ - { - 'type': 'modelField', - 'fieldName': { - 'tag': 2, - 'loc': loc(4, 7, 4, 12), - 'lexeme': 'attr1', - 'index': 5 - }, - 'required': true, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': 'string' - }, - 'attrs': [], - 'tokenRange': [ - 5, - 8 - ] - }, - { - 'type': 'modelField', - 'fieldName': { - 'tag': 2, - 'loc': loc(5, 7, 5, 12), - 'lexeme': 'attr2', - 'index': 9 - }, - 'required': true, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': 'string' - }, - 'attrs': [], - 'tokenRange': [ - 9, - 13 - ] - } - ], - 'tokenRange': [ - 3, - 13 - ] - }, - 'tokenRange': [ - 1, - 13 - ] - }); - expect(ast.comments.get(4)).to.eql({ - 'index': 4, - 'loc': loc(3, 7, 3, 23), - 'value': '// attr1 comment', - 'tag': 20 - }); - expect(ast.comments.get(12)).to.eql({ - 'index': 12, - 'loc': loc(6, 7, 6, 23), - 'value': '// attr2 comment', - 'tag': 20 - }); - }); - - it('comment about init should ok', function () { - var ast = parse(` - // front init comment - init(){ - // empty init - } - // back init comment - `, '__filename'); - let [init] = ast.moduleBody.nodes; - expect(init).to.eql({ - 'annotation': undefined, - 'type': 'init', - 'loc': loc(3, 5, 7, 5), - 'params': { - 'type': 'params', - 'params': [] - }, - 'initBody': { - 'type': 'stmts', - 'stmts': [], - 'tokenRange': [ - 5, - 7 - ] - }, - 'tokenRange': [ - 2, - 7 - ] - }); - - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 26), - 'value': '// front init comment', - 'tag': 20 - }); - expect(ast.comments.get(6)).to.eql({ - 'index': 6, - 'loc': loc(4, 7, 4, 20), - 'value': '// empty init', - 'tag': 20 - }); - expect(ast.comments.get(8)).to.eql({ - 'index': 8, - 'loc': loc(6, 5, 6, 25), - 'value': '// back init comment', - 'tag': 20 - }); - - ast = parse(` - // delare @a - type @a = string - init(){ - // assign @a - @a = 'abc'; - // return void - return; - // end init - } - `, '__filename'); - let [type, initFunc] = ast.moduleBody.nodes; - expect(type).to.eql({ - 'annotation': undefined, - 'type': 'type', - 'vid': { - 'tag': 3, - 'loc': loc(3, 10, 3, 12), - 'lexeme': '@a', - 'index': 3 - }, - 'value': { - 'tag': 8, - 'loc': { - 'start': { - 'line': 3, - 'column': 15 - }, - 'end': { - 'line': 3, - 'column': 21 - } - }, - 'lexeme': 'string', - 'index': 5 - }, - 'tokenRange': [ - 2, - 6 - ] - }); - expect(initFunc).to.eql({ - 'annotation': undefined, - 'type': 'init', - 'loc': loc(4, 5, 11, 5), - 'params': { - 'type': 'params', - 'params': [] - }, - 'initBody': { - 'type': 'stmts', - 'stmts': [ - { - 'type': 'assign', - 'left': { - 'type': 'virtualVariable', - 'vid': { - 'tag': 3, - 'loc': loc(6, 7, 6, 9), - 'lexeme': '@a', - 'index': 11 - } - }, - 'expr': { - 'type': 'string', - 'value': { - 'tag': 1, - 'loc': loc(6, 13, 6, 16), - 'string': 'abc', - 'index': 13 - }, - 'loc': loc(6, 13, 6, 16), - 'tokenRange': [ - 13, - 14 - ] - }, - 'loc': loc(6, 7, 6, 17), - 'tokenRange': [ - 11, - 14 - ] - }, - { - 'type': 'return', - 'loc': loc(8, 7, 10, 5), - 'tokenRange': [ - 16, - 17 - ] - } - ], - 'tokenRange': [ - 9, - 19 - ] - }, - 'tokenRange': [ - 6, - 19 - ] - }); - - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 17), - 'value': '// delare @a', - 'tag': 20 - }); - expect(ast.comments.get(10)).to.eql({ - 'index': 10, - 'loc': loc(5, 7, 5, 19), - 'value': '// assign @a', - 'tag': 20 - }); - expect(ast.comments.get(15)).to.eql({ - 'index': 15, - 'loc': loc(7, 7, 7, 21), - 'value': '// return void', - 'tag': 20 - }); - expect(ast.comments.get(18)).to.eql({ - 'index': 18, - 'loc': loc(9, 7, 9, 18), - 'value': '// end init', - 'tag': 20 - }); - }); - - it('comment about api should ok', function () { - var ast = parse(` - // front api comment - api testAPI(): void{ - // empty api - } returns { - // empty return - } runtime { - // empty runtime - } - // back api comment - `, '__filename'); - let [emptyApi] = ast.moduleBody.nodes; - expect(emptyApi).to.eql({ - 'annotation': undefined, - 'type': 'api', - 'apiName': { - 'tag': 2, - 'loc': loc(3, 9, 3, 16), - 'lexeme': 'testAPI', - 'index': 3 - }, - 'params': { - 'type': 'params', - 'params': [] - }, - 'returnType': { - 'tag': 8, - 'loc': loc(3, 20, 3, 24), - 'lexeme': 'void', - 'index': 7 - }, - 'apiBody': { - 'type': 'apiBody', - 'stmts': { - 'type': 'stmts', - 'stmts': [], - 'tokenRange': [ - 8, - 10 - ] - }, - 'tokenRange': [ - 8, - 10 - ] - }, - 'returns': { - 'type': 'returnBody', - 'loc': loc(5, 15, 7, 14), - 'stmts': { - 'type': 'stmts', - 'stmts': [], - 'tokenRange': [ - 12, - 14 - ] - }, - 'tokenRange': [ - 12, - 14 - ] - }, - 'runtimeBody': { - 'type': 'object', - 'fields': [], - 'loc': loc(7, 15, 11, 5), - 'tokenRange': [ - 16, - 18 - ] - }, - 'tokenRange': [ - 2, - 18 - ] - }); - - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 25), - 'value': '// front api comment', - 'tag': 20 - }); - expect(ast.comments.get(9)).to.eql({ - 'index': 9, - 'loc': loc(4, 7, 4, 19), - 'value': '// empty api', - 'tag': 20 - }); - expect(ast.comments.get(13)).to.eql({ - 'index': 13, - 'loc': loc(6, 7, 6, 22), - 'value': '// empty return', - 'tag': 20 - }); - expect(ast.comments.get(17)).to.eql({ - 'index': 17, - 'loc': loc(8, 7, 8, 23), - 'value': '// empty runtime', - 'tag': 20 - }); - expect(ast.comments.get(19)).to.eql({ - 'index': 19, - 'loc': loc(10, 5, 10, 24), - 'value': '// back api comment', - 'tag': 20 - }); - - ast = parse(` - model M = { - test: string - }; - // front const comment - const con = 'abc'; - // back const comment - api testAPI(): void{ - // if judge comment - if (true){ - // catch the error - try{ - // while comment - while(1) { - // declare a constructor - var m = new M { - // init M.test - test = con - // end init - }; - // declare end and break loop - break; - // back break - } - }catch(err){ - // empty catch - } - // end if judge - } else { - // empty else - } - // api end - } returns { - // empty void - return; - // end returns - } runtime { - // runtime retry - retry = true - // end runtime - } - `, '__filename'); - let [, con, api] = ast.moduleBody.nodes; - - expect(con).to.eql({ - 'annotation': undefined, - 'type': 'const', - 'constName': { - 'tag': 2, - 'loc': loc(6, 11, 6, 14), - 'lexeme': 'con', - 'index': 12 - }, - 'constValue': { - 'tag': 1, - 'loc': loc(6, 18, 6, 21), - 'string': 'abc', - 'index': 14 - }, - 'tokenRange': [ - 11, - 15 - ] - }); - - expect(api).to.eql({ - 'annotation': undefined, - 'type': 'api', - 'apiName': { - 'tag': 2, - 'loc': loc(8, 9, 8, 16), - 'lexeme': 'testAPI', - 'index': 18 - }, - 'params': { - 'type': 'params', - 'params': [] - }, - 'returnType': { - 'tag': 8, - 'loc': loc(8, 20, 8, 24), - 'lexeme': 'void', - 'index': 22 - }, - 'apiBody': { - 'type': 'apiBody', - 'stmts': { - 'type': 'stmts', - 'stmts': [ - { - 'type': 'if', - 'condition': { - 'type': 'boolean', - 'value': true, - 'loc': loc(10, 11, 10, 15), - 'tokenRange': [ - 27, - 28 - ] - }, - 'stmts': { - 'type': 'stmts', - 'stmts': [ - { - 'type': 'try', - 'tryBlock': { - 'type': 'stmts', - 'stmts': [ - { - 'type': 'while', - 'condition': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': loc(14, 17, 14, 18), - 'value': 1, - 'type': 'integer', - 'index': 36 - }, - 'loc': loc(14, 17, 14, 18), - 'tokenRange': [ - 36, - 37 - ] - }, - 'stmts': { - 'type': 'stmts', - 'stmts': [ - { - 'expectedType': undefined, - 'type': 'declare', - 'id': { - 'tag': 2, - 'loc': loc(16, 17, 16, 18), - 'lexeme': 'm', - 'index': 41 - }, - 'expr': { - 'type': 'construct_model', - 'aliasId': { - 'tag': 2, - 'loc': loc(16, 25, 16, 26), - 'lexeme': 'M', - 'index': 44 - }, - 'propertyPath': [], - 'object': { - 'type': 'object', - 'fields': [ - { - 'type': 'objectField', - 'fieldName': { - 'tag': 2, - 'loc': loc(18, 15, 18, 19), - 'lexeme': 'test', - 'index': 47 - }, - 'expr': { - 'type': 'variable', - 'id': { - 'tag': 2, - 'loc': loc(18, 22, 18, 25), - 'lexeme': 'con', - 'index': 49 - }, - 'loc': loc(18, 22, 18, 25), - 'tokenRange': [ - 49, - 51 - ] - }, - 'tokenRange': [ - 47, - 51 - ] - } - ], - 'loc': loc(16, 27, 20, 14), - 'tokenRange': [ - 45, - 51 - ] - }, - 'tokenRange': [ - 43, - 52 - ] - }, - 'tokenRange': [ - 40, - 52 - ] - }, - { - 'type': 'break', - 'tokenRange': [ - 54, - 55 - ] - } - ], - 'tokenRange': [ - 38, - 57 - ] - }, - 'tokenRange': [ - 34, - 57 - ] - } - ], - 'tokenRange': [ - 32, - 58 - ] - }, - 'catchId': { - 'tag': 2, - 'loc': loc(25, 16, 25, 19), - 'lexeme': 'err', - 'index': 61 - }, - 'catchBlock': { - 'type': 'stmts', - 'stmts': [], - 'tokenRange': [ - 63, - 65 - ] - }, - 'finallyBlock': null, - 'tokenRange': [ - 31, - 65 - ] - } - ], - 'tokenRange': [ - 29, - 67 - ] - }, - 'elseIfs': [], - 'elseStmts': { - 'type': 'stmts', - 'stmts': [], - 'tokenRange': [ - 69, - 71 - ] - }, - 'tokenRange': [ - 25, - 71 - ] - } - ], - 'tokenRange': [ - 23, - 73 - ] - }, - 'tokenRange': [ - 23, - 73 - ] - }, - 'returns': { - 'type': 'returnBody', - 'loc': loc(33, 15, 37, 14), - 'stmts': { - 'type': 'stmts', - 'stmts': [ - { - 'type': 'return', - 'loc': loc(35, 7, 37, 5), - 'tokenRange': [ - 77, - 78 - ] - } - ], - 'tokenRange': [ - 75, - 80 - ] - }, - 'tokenRange': [ - 75, - 80 - ] - }, - 'runtimeBody': { - 'type': 'object', - 'fields': [ - { - 'type': 'objectField', - 'fieldName': { - 'tag': 2, - 'loc': loc(39, 7, 39, 12), - 'lexeme': 'retry', - 'index': 84 - }, - 'expr': { - 'type': 'boolean', - 'value': true, - 'loc': loc(39, 15, 39, 19), - 'tokenRange': [ - 86, - 88 - ] - }, - 'tokenRange': [ - 84, - 88 - ] - } - ], - 'loc': loc(37, 15, 42, 5), - 'tokenRange': [ - 82, - 88 - ] - }, - 'tokenRange': [ - 17, - 88 - ] - }); - expect(ast.comments.get(10)).to.eql({ - 'index': 10, - 'loc': loc(5, 5, 5, 27), - 'value': '// front const comment', - 'tag': 20 - }); - expect(ast.comments.get(16)).to.eql({ - 'index': 16, - 'loc': loc(7, 5, 7, 26), - 'value': '// back const comment', - 'tag': 20 - }); - expect(ast.comments.get(24)).to.eql({ - 'index': 24, - 'loc': loc(9, 7, 9, 26), - 'value': '// if judge comment', - 'tag': 20 - }); - expect(ast.comments.get(30)).to.eql({ - 'index': 30, - 'loc': loc(11, 9, 11, 27), - 'value': '// catch the error', - 'tag': 20 - }); - expect(ast.comments.get(33)).to.eql({ - 'index': 33, - 'loc': loc(13, 11, 13, 27), - 'value': '// while comment', - 'tag': 20 - }); - expect(ast.comments.get(39)).to.eql({ - 'index': 39, - 'loc': loc(15, 14, 15, 38), - 'value': '// declare a constructor', - 'tag': 20 - }); - expect(ast.comments.get(46)).to.eql({ - 'index': 46, - 'loc': loc(17, 15, 17, 29), - 'value': '// init M.test', - 'tag': 20 - }); - expect(ast.comments.get(50)).to.eql({ - 'index': 50, - 'loc': loc(19, 15, 19, 26), - 'value': '// end init', - 'tag': 20 - }); - expect(ast.comments.get(53)).to.eql({ - 'index': 53, - 'loc': loc(21, 13, 21, 42), - 'value': '// declare end and break loop', - 'tag': 20 - }); - expect(ast.comments.get(56)).to.eql({ - 'index': 56, - 'loc': loc(23, 13, 23, 26), - 'value': '// back break', - 'tag': 20 - }); - expect(ast.comments.get(64)).to.eql({ - 'index': 64, - 'loc': loc(26, 11, 26, 25), - 'value': '// empty catch', - 'tag': 20 - }); - expect(ast.comments.get(66)).to.eql({ - 'index': 66, - 'loc': loc(28, 9, 28, 24), - 'value': '// end if judge', - 'tag': 20 - }); - expect(ast.comments.get(70)).to.eql({ - 'index': 70, - 'loc': loc(30, 9, 30, 22), - 'value': '// empty else', - 'tag': 20 - }); - expect(ast.comments.get(72)).to.eql({ - 'index': 72, - 'loc': loc(32, 7, 32, 17), - 'value': '// api end', - 'tag': 20 - }); - expect(ast.comments.get(76)).to.eql({ - 'index': 76, - 'loc': loc(34, 7, 34, 20), - 'value': '// empty void', - 'tag': 20 - }); - expect(ast.comments.get(79)).to.eql({ - 'index': 79, - 'loc': loc(36, 7, 36, 21), - 'value': '// end returns', - 'tag': 20 - }); - expect(ast.comments.get(83)).to.eql({ - 'index': 83, - 'loc': loc(38, 7, 38, 23), - 'value': '// runtime retry', - 'tag': 20 - }); - expect(ast.comments.get(87)).to.eql({ - 'index': 87, - 'loc': loc(40, 7, 40, 21), - 'value': '// end runtime', - 'tag': 20 - }); - }); - - it('comment about fun should ok', function () { - var ast = parse(` - // front func comment - static function testFunc(): void{ - // empty func - } - // back func comment - `, '__filename'); - let [emptyFunc] = ast.moduleBody.nodes; - expect(emptyFunc).to.eql({ - 'annotation': undefined, - 'type': 'function', - 'isStatic': true, - 'isAsync': false, - 'hasThrow': false, - 'functionName': { - 'tag': 2, - 'loc': loc(3, 21, 3, 29), - 'lexeme': 'testFunc', - 'index': 4 - }, - 'params': { - 'type': 'params', - 'params': [] - }, - 'returnType': { - 'tag': 8, - 'loc': loc(3, 33, 3, 37), - 'lexeme': 'void', - 'index': 8 - }, - 'functionBody': { - 'type': 'functionBody', - 'loc': loc(3, 37, 7, 5), - 'stmts': { - 'type': 'stmts', - 'stmts': [], - 'tokenRange': [ - 9, - 11 - ] - }, - 'tokenRange': [ - 9, - 11 - ] - }, - 'tokenRange': [ - 2, - 11 - ] - }); - - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 26), - 'value': '// front func comment', - 'tag': 20 - }); - expect(ast.comments.get(10)).to.eql({ - 'index': 10, - 'loc': loc(4, 7, 4, 20), - 'value': '// empty func', - 'tag': 20 - }); - expect(ast.comments.get(12)).to.eql({ - 'index': 12, - 'loc': loc(6, 5, 6, 25), - 'value': '// back func comment', - 'tag': 20 - }); - - ast = parse(` - static function testFunc(): string{ - // declare a - var a = 'test'; - // return a - return a; - // end func - } - `, '__filename'); - - let [func] = ast.moduleBody.nodes; - expect(func).to.eql({ - 'annotation': undefined, - 'type': 'function', - 'isStatic': true, - 'isAsync': false, - 'hasThrow': false, - 'functionName': { - 'tag': 2, - 'loc': loc(2, 21, 2, 29), - 'lexeme': 'testFunc', - 'index': 3 - }, - 'params': { - 'type': 'params', - 'params': [] - }, - 'returnType': { - 'tag': 8, - 'loc': loc(2, 33, 2, 39), - 'lexeme': 'string', - 'index': 7 - }, - 'functionBody': { - 'type': 'functionBody', - 'loc': loc(2, 39, 9, 5), - 'stmts': { - 'type': 'stmts', - 'stmts': [ - { - 'expectedType': undefined, - 'type': 'declare', - 'id': { - 'tag': 2, - 'loc': loc(4, 11, 4, 12), - 'lexeme': 'a', - 'index': 11 - }, - 'expr': { - 'type': 'string', - 'value': { - 'tag': 1, - 'loc': loc(4, 16, 4, 20), - 'string': 'test', - 'index': 13 - }, - 'loc': loc(4, 16, 4, 20), - 'tokenRange': [ - 13, - 14 - ] - }, - 'tokenRange': [ - 10, - 14 - ] - }, - { - 'type': 'return', - 'expr': { - 'type': 'variable', - 'id': { - 'tag': 2, - 'loc': loc(6, 14, 6, 15), - 'lexeme': 'a', - 'index': 17 - }, - 'loc': loc(6, 14, 6, 15), - 'tokenRange': [ - 17, - 18 - ] - }, - 'loc': loc(6, 7, 8, 5), - 'tokenRange': [ - 16, - 18 - ] - } - ], - 'tokenRange': [ - 8, - 20 - ] - }, - 'tokenRange': [ - 8, - 20 - ] - }, - 'tokenRange': [ - 1, - 20 - ] - }); - - expect(ast.comments.get(9)).to.eql({ - 'index': 9, - 'loc': loc(3, 7, 3, 19), - 'value': '// declare a', - 'tag': 20 - }); - expect(ast.comments.get(15)).to.eql({ - 'index': 15, - 'loc': loc(5, 7, 5, 18), - 'value': '// return a', - 'tag': 20 - }); - expect(ast.comments.get(19)).to.eql({ - 'index': 19, - 'loc': loc(7, 7, 7, 18), - 'value': '// end func', - 'tag': 20 - }); - }); - - it('comment about rpc should ok', function () { - var ast = parse(` - // front rpc comment - rpc test(): void{ - // empty rpc - } - // back rpc comment - `, '__filename'); - let [emptyRpc] = ast.moduleBody.nodes; - expect(emptyRpc).to.eql({ - 'annotation': undefined, - 'type': 'rpc', - 'rpcName': { - 'tag': 2, - 'loc': loc(3, 9, 3, 13), - 'lexeme': 'test', - 'index': 3 - }, - 'params': { - 'type': 'params', - 'params': [] - }, - 'returnType': { - 'tag': 8, - 'loc': loc(3, 17, 3, 21), - 'lexeme': 'void', - 'index': 7 - }, - 'rpcBody': { - 'type': 'object', - 'fields': [], - 'loc': loc(3, 21, 7, 5), - 'tokenRange': [ - 8, - 10 - ] - } - }); - expect(ast.comments.get(1)).to.eql({ - 'index': 1, - 'loc': loc(2, 5, 2, 25), - 'value': '// front rpc comment', - 'tag': 20 - }); - expect(ast.comments.get(9)).to.eql({ - 'index': 9, - 'loc': loc(4, 7, 4, 19), - 'value': '// empty rpc', - 'tag': 20 - }); - expect(ast.comments.get(11)).to.eql({ - 'index': 11, - 'loc': loc(6, 5, 6, 24), - 'value': '// back rpc comment', - 'tag': 20 - }); - - ast = parse(` - rpc test(): void{ - // attr1 comment - attr1 = 'string', - attr2 = 123 - // attr2 comment - } - `, '__filename'); - - let [rpc] = ast.moduleBody.nodes; - expect(rpc).to.eql({ - 'annotation': undefined, - 'type': 'rpc', - 'rpcName': { - 'tag': 2, - 'loc': loc(2, 9, 2, 13), - 'lexeme': 'test', - 'index': 2 - }, - 'params': { - 'type': 'params', - 'params': [] - }, - 'returnType': { - 'tag': 8, - 'loc': loc(2, 17, 2, 21), - 'lexeme': 'void', - 'index': 6 - }, - 'rpcBody': { - 'type': 'object', - 'fields': [ - { - 'type': 'objectField', - 'fieldName': { - 'tag': 2, - 'loc': loc(4, 7, 4, 12), - 'lexeme': 'attr1', - 'index': 9 - }, - 'expr': { - 'type': 'string', - 'value': { - 'tag': 1, - 'loc': loc(4, 16, 4, 22), - 'string': 'string', - 'index': 11 - }, - 'loc': loc(4, 16, 4, 22), - 'tokenRange': [ - 11, - 12 - ] - }, - 'tokenRange': [ - 9, - 12 - ] - }, - { - 'type': 'objectField', - 'fieldName': { - 'tag': 2, - 'loc': loc(5, 7, 5, 12), - 'lexeme': 'attr2', - 'index': 13 - }, - 'expr': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': loc(5, 15, 5, 18), - 'value': 123, - 'type': 'integer', - 'index': 15 - }, - 'loc': loc(5, 15, 5, 18), - 'tokenRange': [ - 15, - 17 - ] - }, - 'tokenRange': [ - 13, - 17 - ] - } - ], - 'loc': loc(2, 21, 8, 5), - 'tokenRange': [ - 7, - 17 - ] - } - }); - expect(ast.comments.get(8)).to.eql({ - 'index': 8, - 'loc': loc(3, 7, 3, 23), - 'value': '// attr1 comment', - 'tag': 20 - }); - expect(ast.comments.get(16)).to.eql({ - 'index': 16, - 'loc': loc(6, 7, 6, 23), - 'value': '// attr2 comment', - 'tag': 20 - }); - }); - - it('map access(vid) should ok', function () { - var ast = parse(` - type @id = map[string]string - async function test(): void { - return @id['key']; - } - `, '__filename'); - let [, func1] = ast.moduleBody.nodes; - const [expr] = func1.functionBody.stmts.stmts; - expect(expr.expr).to.eql({ - 'accessKey': { - 'loc': loc(4, 21, 4, 24), - 'tokenRange': [20, 21], - 'type': 'string', - 'value': { - 'index': 20, - 'loc': loc(4, 21, 4, 24), - 'string': 'key', - 'tag': 1 - } - }, - 'id': { - 'index': 18, - 'lexeme': '@id', - 'loc': loc(4, 16, 4, 19), - 'tag': 3 - }, - loc: loc(4, 16, 4, 26), - 'tokenRange': [ - 18, - 22 - ], - 'type': 'map_access' - }); - }); - - it('map access(vid.property) should ok', function () { - var ast = parse(` - model M { - p: map[string]string - } - type @id = M; - async function test(): void { - return @id.p['key']; - } - `, '__filename'); - let [ , , func1] = ast.moduleBody.nodes; - const [expr] = func1.functionBody.stmts.stmts; - expect(expr.expr).to.eql({ - 'accessKey': { - 'loc': loc(7, 23, 7, 26), - 'tokenRange': [30, 31], - 'type': 'string', - 'value': { - 'index': 30, - 'loc': loc(7, 23, 7, 26), - 'string': 'key', - 'tag': 1 - } - }, - 'id': { - 'index': 26, - 'lexeme': '@id', - 'loc': loc(7, 16, 7, 19), - 'tag': 3 - }, - 'propertyPath': [ - { - 'index': 28, - 'lexeme': 'p', - 'loc': loc(7, 20, 7, 21), - 'tag': 2 - } - ], - loc: loc(7, 16, 7, 28), - 'tokenRange': [26, 32], - 'type': 'map_access' - }); - }); - - it('map access(id) should ok', function () { - var ast = parse(` - async function test(): void { - var id = { - key = 'value' - }; - return id['key']; - } - `, '__filename'); - let [func1] = ast.moduleBody.nodes; - const [ , expr] = func1.functionBody.stmts.stmts; - expect(expr.expr).to.eql({ - 'accessKey': { - 'loc': loc(6, 20, 6, 23), - 'tokenRange': [21, 22], - 'type': 'string', - 'value': { - 'index': 21, - 'loc': loc(6, 20, 6, 23), - 'string': 'key', - 'tag': 1 - } - }, - 'id': { - 'index': 19, - 'lexeme': 'id', - 'loc': loc(6, 16, 6, 18), - 'tag': 2 - }, - loc: loc(6, 16, 6, 25), - 'tokenRange': [ - 19, - 23 - ], - 'type': 'map_access' - }); - }); - - it('map access(id.property) should ok', function () { - var ast = parse(` - model M { - p: map[string]string - } - async function test(): void { - var id = new M{}; - return id.p['key']; - } - `, '__filename'); - let [ , func1] = ast.moduleBody.nodes; - const [ , expr] = func1.functionBody.stmts.stmts; - expect(expr.expr).to.eql({ - 'accessKey': { - 'loc': loc(7, 22, 7, 25), - 'tokenRange': [33, 34], - 'type': 'string', - 'value': { - 'index': 33, - 'loc': loc(7, 22, 7, 25), - 'string': 'key', - 'tag': 1 - } - }, - 'id': { - 'index': 29, - 'lexeme': 'id', - 'loc': loc(7, 16, 7, 18), - 'tag': 2 - }, - 'propertyPath': [ - { - 'index': 31, - 'lexeme': 'p', - 'loc': loc(7, 19, 7, 20), - 'tag': 2 - } - ], - loc: loc(7, 16, 7, 27), - 'tokenRange': [29, 35], - 'type': 'map_access' - }); - }); - - it('super() should ok', function () { - var ast = parse(` - init() { - super(); - } - `, '__filename'); - let [ init ] = ast.moduleBody.nodes; - const [expr] = init.initBody.stmts; - expect(expr).to.eql({ - 'args': [], - 'loc': loc(3, 14, 3, 16), - 'tokenRange': [5, 8], - 'type': 'super' - }); - }); - - it('word(api) as model field name should ok', function () { - expect(function() { - parse(` - model M { - api: string - } - `, '__filename'); - }).not.to.throwError(); - }); - - it('multi-dimentional array in model field should be ok', function () { - function modelField(value) { - var ast = parse(` - model id = { - ${value} - } - `, '__filename'); - return ast.moduleBody.nodes[0].modelBody; - } - - expect(modelField(`name?: [[{}]]`)).to.be.eql({ - 'type': 'modelBody', - 'nodes': [ - { - 'type': 'modelField', - 'fieldName': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 3, - 'column': 11 - }, - 'end': { - 'line': 3, - 'column': 15 - } - }, - 'lexeme': 'name', - 'index': 5 - }, - 'required': false, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'modelBody', - 'nodes': [], - 'tokenRange': [ - 10, - 11 - ] - } - } - }, - 'attrs': [], - 'tokenRange': [ - 5, - 14 - ] - } - ], - 'tokenRange': [ - 4, - 14 - ] - }); - - - - expect(modelField(`name?: [[{ - age: number - }]]`)).to.be.eql({ - 'type': 'modelBody', - 'nodes': [ - { - 'type': 'modelField', - 'fieldName': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 3, - 'column': 11 - }, - 'end': { - 'line': 3, - 'column': 15 - } - }, - 'lexeme': 'name', - 'index': 5 - }, - 'required': false, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'modelBody', - 'nodes': [ - { - 'type': 'modelField', - 'fieldName': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 4, - 'column': 7 - }, - 'end': { - 'line': 4, - 'column': 10 - } - }, - 'lexeme': 'age', - 'index': 11 - }, - 'required': true, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': 'number' - }, - 'attrs': [], - 'tokenRange': [ - 11, - 14 - ] - } - ], - 'tokenRange': [ - 10, - 14 - ] - } - } - }, - 'attrs': [], - 'tokenRange': [ - 5, - 17 - ] - } - ], - 'tokenRange': [ - 4, - 17 - ] - }); - - expect(modelField(`name?: [[[{}]]]`)).to.be.eql({ - 'type': 'modelBody', - 'nodes': [ - { - 'type': 'modelField', - 'fieldName': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 3, - 'column': 11 - }, - 'end': { - 'line': 3, - 'column': 15 - } - }, - 'lexeme': 'name', - 'index': 5 - }, - 'required': false, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'modelBody', - 'nodes': [], - 'tokenRange': [ - 11, - 12 - ] - } - } - } - }, - 'attrs': [], - 'tokenRange': [ - 5, - 16 - ] - } - ], - 'tokenRange': [ - 4, - 16 - ] - }); - - expect(modelField(`name?: [[ string ]]`)).to.be.eql({ - 'type': 'modelBody', - 'nodes': [ - { - 'type': 'modelField', - 'fieldName': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 3, - 'column': 11 - }, - 'end': { - 'line': 3, - 'column': 15 - } - }, - 'lexeme': 'name', - 'index': 5 - }, - 'required': false, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'tag': 8, - 'loc': { - 'start': { - 'line': 3, - 'column': 21 - }, - 'end': { - 'line': 3, - 'column': 27 - } - }, - 'lexeme': 'string', - 'index': 10 - } - } - }, - 'attrs': [], - 'tokenRange': [ - 5, - 13 - ] - } - ], - 'tokenRange': [ - 4, - 13 - ] - }); - - expect(modelField(`name?: [[[ string ]]]`)).to.be.eql({ - 'type': 'modelBody', - 'nodes': [ - { - 'type': 'modelField', - 'fieldName': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 3, - 'column': 11 - }, - 'end': { - 'line': 3, - 'column': 15 - } - }, - 'lexeme': 'name', - 'index': 5 - }, - 'required': false, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'type': 'fieldType', - 'fieldType': 'array', - 'fieldItemType': { - 'tag': 8, - 'loc': { - 'start': { - 'line': 3, - 'column': 22 - }, - 'end': { - 'line': 3, - 'column': 28 - } - }, - 'lexeme': 'string', - 'index': 11 - } - } - } - }, - 'attrs': [], - 'tokenRange': [ - 5, - 15 - ] - } - ], - 'tokenRange': [ - 4, - 15 - ] - }); - }); -}); diff --git a/test/semantic.test.js b/test/semantic.test.js deleted file mode 100644 index 606742e..0000000 --- a/test/semantic.test.js +++ /dev/null @@ -1,5684 +0,0 @@ -'use strict'; -const path = require('path'); -const fs = require('fs'); - -const expect = require('expect.js'); - -const { parse } = require('..'); - -function readAndParse(specPath) { - const filePath = path.join(__dirname, specPath); - return parse(fs.readFileSync(filePath, 'utf-8'), filePath); -} - -function pos(line, column) { - return { line, column }; -} - -function loc(startLine, startColumn, endLine, endColumn) { - return { - start: pos(startLine, startColumn), - end: pos(endLine, endColumn) - }; -} - -// function string(val, sl, sc, el, ec) { -// return { -// 'type': 'string', -// 'loc': loc(sl, sc, el, ec), -// 'value': { -// 'loc': loc(sl, sc, el, ec), -// 'string': val, -// 'tag': 1 -// } -// }; -// } - -describe('semantic', function () { - - it('virtualVariable without type should not ok', function () { - expect(() => { - parse(` - api id(): string { - __request.method = "GET"; - __request.pathname = "/"; - } returns { - var id = ""; - id = @vid; - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the type "@vid" is undefined`); - }); - }); - - it('virtualVariable in object expand field without type should not ok', function () { - expect(() => { - parse(` - api id(): string { - __request.method = "GET"; - __request.pathname = "/"; - } returns { - return { - ...@vid - }; - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the type "@vid" is undefined`); - }); - }); - - it('use undefined model in type should not ok', function () { - expect(() => { - parse(` - model defined = {}; - type @ddd = defined - type @id = modelname`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`model "modelname" undefined`); - }); - - expect(() => { - parse(` - model defined = {}; - type @call1 = defined - type @call2 = modelname`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`model "modelname" undefined`); - }); - - expect(() => { - parse(` - model defined = {}; - type @call1 = defined - type @call2 = map[string]modelname`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`model "modelname" undefined`); - }); - }); - - it('use model before define it should ok', function () { - expect(() => { - parse(`model defined2 = { - key: defined - }; - - model defined = {}; - `, '__filename'); - }).to.not.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`model "modelname" undefined`); - }); - }); - - it('redefine type should not ok', function () { - expect(() => { - parse(` - type @id = string - type @id = string`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`redefined type "@id"`); - }); - }); - - it('redefine model should not ok', function () { - expect(() => { - parse(` - model id = { key: string } - model id = { key: string }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`redefined model "id"`); - }); - }); - - it('redefine api should not ok', function () { - expect(() => { - parse(` - api getId(): string { - method = 'GET'; - pathname = '/'; - } - - api getId(): string { - method = 'GET'; - pathname = '/'; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`redefined api "getId"`); - }); - }); - - it('redefine function should not ok', function () { - expect(() => { - parse(` - function f(): void { - - } - function f(): void { - - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`redefined function "f"`); - }); - }); - - it('object vs model should ok', function () { - expect(() => { - parse(` - static function toJSONString(i: object): string; - model MyModel = {} - init(); - api call(v: MyModel): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - return { - id = toJSONString(v) - }; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('const should be ok', function () { - expect(() => { - parse(` - function xcall(a: string, b: string, c: string): string; - model m = {b: string, a: number} - const version = '2012'; - const n = 123; - const b = true; - api call(v: m): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = xcall(__module.version, __module.n, __module.b); - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter ` + - `types are mismatched. expected xcall(string, string, string), ` + - `but xcall(string, integer, boolean)`); - }); - - expect(() => { - parse(` - function xcall(a: string, b: string, c: string): string { - return __module.x; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the const x is undefined`); - }); - }); - - it('undefined variable should not ok', function () { - expect(() => { - parse(` - api call(): object { - __request.protocol = 'http'; - __request.port = the_port; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`variable "the_port" undefined`); - }); - - expect(() => { - parse(` - api call(): object { - __request.protocol = 'http'; - __request.port = the_port.b; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`variable "the_port" undefined`); - }); - }); - - it('non-object as expand field should not ok', function () { - expect(() => { - parse(` - api call(a: string): object { - __request.protocol = 'http'; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = { - ...a - }; - __request.headers = {}; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the expand field "a" should be an object or model`); - }); - - expect(() => { - parse(` - model m = {}; - init(); - api call(a: m): object { - __request.protocol = 'http'; - __request.method = 'GET'; - __request.pathname = '/'; - __request.headers = {}; - } returns { - var query = { - ...a - }; - return query; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('duplicate parameter name should not ok', function () { - expect(() => { - parse(` - type @id = string - api call(a: string, a: string): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - return { - id = xcall(@id) - }; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`redefined parameter "a"`); - }); - }); - - it('non-object/model property access should not ok', function () { - expect(() => { - parse(` - api call(a: string): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - var c = a.b; - return {}; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`The type of 'a' must be model, object or map`); - }); - }); - - it('undefined property should not ok', function () { - expect(() => { - parse(` - model m = {}; - api call(a: m): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - var c = a.b.c; - return {}; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`The property b is undefined in model a(m)`); - }); - - expect(() => { - parse(` - model m = {}; - api call(a: map[string]m): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - var c = a.b.c; - return {}; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`The property c is undefined in model a.b(m)`); - }); - }); - - it('undefined model should not ok', function () { - expect(() => { - parse(` - api call(val: modelname): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - return { - id = xcall(@id) - }; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`model "modelname" undefined`); - }); - - expect(() => { - parse(` - model defined = {}; - api call(a: [string], b: [defined], val: [modelname]): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - return { - id = xcall(@id) - }; - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`model "modelname" undefined`); - }); - }); - - it('runtime should not ok', function () { - expect(() => { - parse(` - api call(val: string): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - return {}; - } runtime { - ...val - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the expand field "val" should be an object or model`); - }); - - expect(() => { - parse(` - init(); - api call(val: object): object { - __request.protocol = 'http'; - __request.port = 80; - __request.method = 'GET'; - __request.pathname = '/'; - __request.query = {}; - __request.headers = {}; - __request.query.key = 'value'; - } returns { - return {}; - } runtime { - ...val - }`, '__filename'); - }).to.not.throwException(); - }); - - it('duplicate parameter name should not ok for function', function () { - expect(() => { - parse(` - function callId(a: string, a: string): string { - return id(); - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`redefined parameter "a"`); - }); - }); - - it('call undefined api should not ok', function () { - expect(() => { - parse(` - function callId(): string { - return callx(); - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the api/function "callx" is undefined`); - }); - }); - - it('inferred for call in function should ok', function () { - var ast = parse(` - api call(): object { - __request.method = 'GET'; - __request.pathname = '/'; - } returns { - return {}; - } - - async function callId(): object { - return call(); - } - init();`, '__filename'); - var func = ast.moduleBody.nodes.find((item) => { - return item.type === 'function'; - }); - var returnExpr = func.functionBody.stmts.stmts[0]; - expect(returnExpr).to.eql({ - type: 'return', - 'loc': loc(10, 11, 11, 9), - 'needCast': false, - 'tokenRange': [36, 40], - expr: { - type: 'call', - 'tokenRange': [37, 40], - isStatic: false, - isAsync: true, - 'hasThrow': true, - left: { - type: 'method_call', - id: { - 'index': 37, - tag: 2, - 'loc': loc(10, 18, 10, 22), - lexeme: 'call' - } - }, - args: [], - 'inferred': { - 'keyType': { name: 'string', type: 'basic' }, - 'type': 'map', - 'valueType': { name: 'any', type: 'basic' }, - }, - 'loc': loc(10, 18, 10, 24) - } - }); - }); - - it('submodel should ok', function () { - var ast = parse(` - model a = { - b: { - s: string - } - }`, '__filename'); - expect(ast.parserVersion).to.be.ok(); - const modelA = ast.models.a; - expect(modelA).to.eql({ - 'annotation': undefined, - 'modelBody': { - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'b', - 'loc': loc(3, 9, 3, 10), - 'tag': 2 - }, - 'fieldValue': { - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 8, - 'lexeme': 's', - 'loc': loc(4, 11, 4, 12), - 'tag': 2 - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [8, 11], - 'type': 'modelField' - } - ], - 'tokenRange': [7, 11], - 'type': 'modelBody' - }, - 'required': true, - 'tokenRange': [5, 12], - 'type': 'modelField' - } - ], - 'tokenRange': [4, 12], - 'type': 'modelBody' - }, - 'modelName': { - 'lexeme': 'a', - // 'loc': { - // 'end': { - // 'column': 14, - // 'line': 2 - // }, - // 'start': { - // 'column': 13, - // 'line': 2 - // } - // }, - 'tag': 2 - }, - 'type': 'model' - }); - const modelAB = ast.models['a.b']; - expect(modelAB).to.eql({ - 'annotation': undefined, - 'modelBody': { - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 8, - 'lexeme': 's', - 'loc': loc(4, 11, 4, 12), - 'tag': 2 - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [8, 11], - 'type': 'modelField' - } - ], - 'tokenRange': [7, 11], - 'type': 'modelBody' - }, - 'modelName': { - 'lexeme': 'a.b', - 'tag': 2 - }, - 'type': 'model' - }); - }); - - it('submodel with depth level should ok', function () { - var ast = parse(` - model GroupDetailResponse = { - abilities: { - read: boolean, - update: boolean, - destroy: boolean, - group_user: { - create: boolean, - update: boolean, - destroy: boolean - }, - repo: { - create: boolean, - update: boolean, - destroy: boolean - } - } - }`, '__filename'); - - const GroupDetailResponse = ast.models['GroupDetailResponse']; - expect(GroupDetailResponse).to.be.ok(); - expect(ast.models['GroupDetailResponse.abilities']).to.be.ok(); - expect(ast.models['GroupDetailResponse.abilities.group_user']).to.be.ok(); - expect(ast.models['GroupDetailResponse.abilities.repo']).to.be.ok(); - }); - - it('submodel with array should ok', function () { - var ast = parse(` - model GroupDetailResponse = { - abilities: [ - { - read: boolean, - update: boolean, - destroy: boolean, - } - ] - }`, '__filename'); - - const GroupDetailResponse = ast.models['GroupDetailResponse']; - expect(GroupDetailResponse).to.be.ok(); - expect(ast.models['GroupDetailResponse.abilities']).to.be.ok(); - expect(ast.models['GroupDetailResponse.abilities']).to.eql({ - type: 'model', - modelName: { tag: 2, lexeme: 'GroupDetailResponse.abilities' }, - 'annotation': undefined, - modelBody: { - type: 'modelBody', - 'tokenRange': [8, 21], - nodes: [ - { - 'attrs': [], - 'fieldName': { - 'index': 9, - 'lexeme': 'read', - 'loc': loc(5, 13, 5, 17), - 'tag': 2 - }, - 'fieldValue': { - 'fieldType': 'boolean', - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [9, 12], - 'type': 'modelField' - }, - { - 'attrs': [], - 'fieldName': { - 'index': 13, - 'lexeme': 'update', - 'loc': loc(6, 13, 6, 19), - 'tag': 2 - }, - 'fieldValue': { - 'fieldType': 'boolean', - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [13, 16], - 'type': 'modelField' - }, - { - 'attrs': [], - 'fieldName': { - 'index': 17, - 'lexeme': 'destroy', - 'loc': loc(7, 13, 7, 20), - 'tag': 2 - }, - 'fieldValue': { - 'fieldType': 'boolean', - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [17, 20], - 'type': 'modelField' - } - ] - } - }); - }); - - it('submodel with another model should ok', function () { - var ast = parse(` - model b = { - s: string - }; - model a = { - b: b, - c: $Model - }`, '__filename'); - expect(ast.parserVersion).to.be.ok(); - const modelA = ast.models.a; - expect(modelA).to.eql({ - 'annotation': undefined, - 'modelBody': { - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 14, - 'lexeme': 'b', - 'loc': loc(6, 9, 6, 10), - 'tag': 2 - }, - 'fieldValue': { - 'type': 'fieldType', - 'fieldType': { - 'index': 16, - 'idType': 'model', - 'lexeme': 'b', - 'loc': loc(6, 12, 6, 13), - 'tag': 2 - } - }, - 'required': true, - 'tokenRange': [14, 17], - 'type': 'modelField' - }, - { - 'attrs': [], - 'fieldName': { - 'index': 18, - 'lexeme': 'c', - 'loc': loc(7, 9, 7, 10), - 'tag': 2 - }, - 'fieldValue': { - 'fieldType': { - 'index': 20, - 'idType': 'builtin_model', - 'lexeme': '$Model', - 'loc': loc(7, 12, 7, 18), - 'tag': 2 - }, - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [18, 21], - 'type': 'modelField' - } - ], - 'tokenRange': [13, 21], - 'type': 'modelBody' - }, - 'modelName': { - 'lexeme': 'a', - 'tag': 2 - }, - 'type': 'model' - }); - const modelB = ast.models.b; - expect(modelB).to.eql({ - 'annotation': undefined, - 'modelBody': { - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 's', - 'loc': loc(3, 9, 3, 10), - 'tag': 2 - }, - 'fieldValue': { - 'fieldType': 'string', - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [5, 8], - 'type': 'modelField' - } - ], - 'tokenRange': [4, 8], - 'type': 'modelBody' - }, - 'modelName': { - 'lexeme': 'b', - 'tag': 2 - }, - 'type': 'model' - }); - }); - - it('submodel as return type should ok', function () { - expect(() => { - parse(` - model GroupDetailResponse = { - abilities: [ - { - read: boolean, - update: boolean, - destroy: boolean, - } - ] - } - - static function test(): GroupDetailResponse.abilities { - return new GroupDetailResponse.abilities{}; - }`, '__filename'); - }).to.not.throwError(); - - expect(() => { - parse(` - model GroupDetailResponse = { - abilities: [ - { - read: boolean, - update: boolean, - destroy: boolean, - } - ] - } - - static function test(): GroupDetailResponse.inexist { - - }`, '__filename'); - }).to.throwException(function (e) { // get the exception object - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the submodel GroupDetailResponse.inexist is inexist`); - }); - }); - - it('flatModel with array[basic type] should ok', function () { - var ast = parse(` - model GroupDetailResponse = { - abilities: [ - string - ] - }`, '__filename'); - - const GroupDetailResponse = ast.models['GroupDetailResponse']; - expect(GroupDetailResponse).to.be.eql({ - 'annotation': undefined, - 'modelBody': { - 'nodes': [ - { - 'attrs': [], - 'fieldName': { - 'index': 5, - 'lexeme': 'abilities', - 'loc': loc(3, 9, 3, 18), - 'tag': 2 - }, - 'fieldValue': { - 'fieldItemType': { - 'index': 8, - 'lexeme': 'string', - 'loc': loc(4, 11, 4, 17), - 'tag': 8 - }, - 'fieldType': 'array', - 'type': 'fieldType' - }, - 'required': true, - 'tokenRange': [5, 10], - 'type': 'modelField' - } - ], - 'tokenRange': [4, 10], - 'type': 'modelBody' - }, - 'modelName': { - 'lexeme': 'GroupDetailResponse', - 'tag': 2 - }, - 'type': 'model' - }); - - const GroupDetailResponseabilities = ast.models['GroupDetailResponse.abilities']; - expect(GroupDetailResponseabilities).to.not.be.ok(); - }); - - it('parameters should ok', function () { - expect(function () { - parse(` - api get(path: string): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(); - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameters are mismatched, expect 1 parameters, actual 0`); - }); - }); - - it('init should only one should ok', function () { - expect(function () { - parse(` - init(); - init();`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Only one init can be allowed.`); - }); - }); - - it('declare duplicated should not ok', function () { - expect(function () { - parse(` - function callOSS(): string { - var id = "id"; - var id = "id"; - return ""; - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the id "id" was defined`); - }); - }); - - it('declare duplicated in api should not ok', function () { - expect(function () { - parse(` - api callOSS(): string { - var id = "id"; - var id = "id"; - method = "GET"; - pathname = "/"; - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the id "id" was defined`); - }); - }); - - it('declare with model should ok', function () { - expect(function () { - parse(` - model M {} - static function callOSS(): void { - var id: M = new M{}; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('declare with subModel should ok', function () { - expect(function () { - parse(` - model M { - N: {} - } - static function callOSS(): void { - var id: M.N = new M.N{}; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('declare null without type should not ok', function () { - expect(function () { - parse(` - static function callOSS(): void { - var id = null; - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`must declare type when value is null`); - }); - }); - - it('declare null with type should ok', function () { - expect(function () { - parse(` - static function callOSS(): void { - var id: string = null; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function callOSS(): void { - var mapVal: map[string] string = null; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function callOSS(): void { - var arrVal: [ string ] = null; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function callOSS(): void { - var arrVal = [ - 'string' - ]; - arrVal = null; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function callOSS(): void { - var mapVal = { - key = 'string' - }; - mapVal = null; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('declare null with model should ok', function () { - expect(function () { - parse(` - model M {} - static function callOSS(): void { - var id: M = null; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('declare null with subModel should ok', function () { - expect(function () { - parse(` - model M { - N: {} - } - static function callOSS(): void { - var id: M.N = null; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('declare with moduleModel should ok', function () { - expect(function () { - readAndParse('fixtures/declare_module_model/main.dara'); - }).to.not.throwException(); - }); - - it('declare variable with mismatched type should not ok', function () { - expect(function () { - parse(` - static function callOSS(): void { - var id : string = true; - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`declared variable with mismatched type, expected: string, actual: boolean`); - }); - }); - - it('use undefined variable should not ok', function () { - expect(function () { - parse(` - function call(): string { - return id; - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`variable "id" undefined`); - }); - }); - - it('array with undefined variable should not ok', function () { - expect(function () { - parse(` - function call(): [string] { - return [id, id2]; - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`variable "id" undefined`); - }); - }); - - it('redefined field in model should not ok', function () { - expect(function () { - parse(` - model M = { - a: string, - a: string - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`redefined field "a" in model "M"`); - }); - }); - - it('undefined type in model should not ok', function () { - expect(function () { - parse(` - model M = { - a: json - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the type "json" is undefined`); - }); - - expect(function () { - parse(` - model M = { - a: [ json ] - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the type "json" is undefined`); - }); - }); - - it('static method with virtual variable should not ok', function () { - expect(function () { - parse(` - type @call = void - - static function func(): void { - return @call; - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`virtual variable can not used in static function`); - }); - }); - - it('api in sync function should not ok', function () { - expect(function () { - parse(` - api test(): void { - __request.method = 'GET'; - __request.pathname = '/'; - } - - function sf(): void { - return test(); - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the api only can be used in async function`); - }); - }); - - it('async function in sync function should not ok', function () { - expect(function () { - parse(` - async function af(): void { - } - - function sf(): void { - return af(); - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the async function only can be used in async function`); - }); - }); - - it('parameter check for call should ok', function () { - expect(function () { - parse(` - model M = {} - - static function test(m: M): void { - } - - static function func(): void { - return test(""); - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected test(M), but test(string)`); - }); - - expect(function () { - parse(` - model M = {} - - static function test(m: M): void { - } - - static function func(): void { - return test(new M); - }`, '__filename'); - }).to.not.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected test(M), but test(string)`); - }); - - expect(function () { - parse(` - model M = {} - - function test(m: class): void { - } - - function func(): void { - return test(""); - } -`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the parameter types are mismatched. expected test(class), but test(string)`); - }); - }); - - it('add need cast for argument should ok', function () { - const ast = parse(` model M = {} - - function test(m: M): void { - - } - - function func(): void { - return test({}); - } - init(); - `, '__filename'); - - const func = ast.moduleBody.nodes[2]; - expect(func).to.be.eql({ - 'annotation': undefined, - 'isStatic': false, - 'isAsync': false, - 'hasThrow': false, - 'params': { - 'params': [], - 'type': 'params', - }, - 'returnType': { - 'index': 22, - 'lexeme': 'void', - 'loc': loc(7, 24, 7, 28), - 'tag': 8 - }, - 'tokenRange': [17, 31], - 'type': 'function', - 'functionBody': { - 'loc': loc(7, 29, 10, 11), - 'stmts': { - 'stmts': [ - { - 'expr': { - 'args': [ - { - 'fields': [], - 'inferred': { - 'keyType': { name: 'string', type: 'basic' }, - 'type': 'map', - 'valueType': { name: 'any', type: 'basic' }, - }, - 'needCast': false, - 'tokenRange': [27, 29], - 'type': 'object', - loc: loc(8, 21, 8, 23) - } - ], - left: { - 'type': 'method_call', - 'id': { - 'index': 25, - 'lexeme': 'test', - 'loc': loc(8, 16, 8, 20), - 'tag': 2 - }, - }, - 'isAsync': false, - 'isStatic': false, - 'hasThrow': false, - 'inferred': { - 'name': 'void', - 'type': 'basic' - }, - 'tokenRange': [25, 30], - 'type': 'call', - 'loc': loc(8, 16, 8, 24) - }, - 'needCast': false, - 'tokenRange': [24, 30], - 'type': 'return', - 'loc': loc(8, 9, 9, 7) - } - ], - 'tokenRange': [23, 31], - 'type': 'stmts' - }, - 'tokenRange': [23, 31], - 'type': 'functionBody' - }, - 'functionName': { - 'index': 18, - 'lexeme': 'func', - 'loc': loc(7, 16, 7, 20), - 'tag': 2 - } - }); - }); - - it('return null should ok', function () { - const ast = parse(` - model M = {} - function func(): M { - return null; - } - init();`, '__filename'); - - const func = ast.moduleBody.nodes[1]; - expect(func).to.be.eql({ - 'annotation': undefined, - 'isStatic': false, - 'isAsync': false, - 'hasThrow': false, - 'params': { - 'params': [], - 'type': 'params', - }, - 'returnType': { - 'idType': 'model', - 'index': 11, - 'lexeme': 'M', - 'loc': loc(3, 24, 3, 25), - 'tag': 2 - }, - 'type': 'function', - 'tokenRange': [6, 16], - 'functionBody': { - 'loc': loc(3, 26, 6, 11), - 'stmts': { - 'stmts': [ - { - 'expr': { - 'type': 'null', - 'inferred': { - 'type': 'basic', - 'name': 'null' - }, - 'tokenRange': [14, 15], - }, - 'needCast': false, - 'tokenRange': [13, 15], - 'type': 'return', - 'loc': loc(4, 9, 5, 7) - } - ], - 'tokenRange': [12, 16], - 'type': 'stmts' - }, - 'tokenRange': [12, 16], - 'type': 'functionBody' - }, - 'functionName': { - 'index': 7, - 'lexeme': 'func', - 'loc': loc(3, 16, 3, 20), - 'tag': 2 - } - }); - }); - - it('must have init when there is non-static function or api', function () { - expect(function () { - parse(` - function test(): void { - - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Must have a init when there is a api or non-static function`); - }); - - expect(function () { - parse(` - init(); - function test(): void { - - }`, '__filename'); - }).to.not.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`Must have a init when there is a api or non-static function`); - }); - }); - - it('undefined vid in assign expr should not ok', function () { - expect(function () { - parse(` - function func(): void { - @id = "string"; - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`the type "@id" is undefined`); - }); - }); - - it('try/catch/finally should ok', function () { - var ast = parse(` - static function print(a: string): void; - - static function func(): void { - try { - print("try block"); - } catch (ex) { - print(\`error message: \${ex.message}\`); - } finally { - print("finally block"); - } - }`, '__filename'); - const [, func] = ast.moduleBody.nodes; - expect(func.type).to.be('function'); - const [tryStmt] = func.functionBody.stmts.stmts; - expect(tryStmt.type).to.be('try'); - expect(tryStmt.tryBlock).to.eql({ - 'stmts': [ - { - 'args': [ - { - 'inferred': { - 'name': 'string', - 'type': 'basic' - }, - 'needCast': false, - 'tokenRange': [24, 25], - 'type': 'string', - 'value': { - 'index': 24, - 'string': 'try block', - loc: loc(6, 18, 6, 27), - 'tag': 1 - }, - loc: loc(6, 18, 6, 27) - } - ], - 'inferred': { - 'name': 'void', - 'type': 'basic' - }, - 'loc': loc(6, 11, 6, 29), - isAsync: false, - isStatic: true, - 'hasThrow': false, - 'left': { - 'id': { - 'index': 22, - 'lexeme': 'print', - 'loc': loc(6, 11, 6, 16), - 'tag': 2 - }, - 'type': 'method_call' - }, - 'tokenRange': [22, 26], - 'type': 'call' - } - ], - 'tokenRange': [21, 27], - 'type': 'stmts' - }); - expect(tryStmt.catchId).to.eql({ - 'index': 30, - 'lexeme': 'ex', - 'loc': { - 'end': { - 'column': 20, - 'line': 7 - }, - 'start': { - 'column': 18, - 'line': 7 - } - }, - 'tag': 2 - }); - expect(tryStmt.catchBlock).to.eql({ - stmts: [ - { - 'args': [ - { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 35, - 'loc': loc(8, 18, 8, 33), - 'string': 'error message: ', - 'tag': 12, - 'tail': false - } - }, - { - 'expr': { - 'id': { - 'lexeme': 'ex', - loc: loc(8, 35, 8, 37), - 'type': 'variable', - 'tag': 2, - 'index': 36, - 'inferred': { - 'moduleName': undefined, - 'name': '$Error', - 'type': 'model', - } - }, - 'propertyPath': [ - { - 'index': 38, - 'lexeme': 'message', - 'loc': loc(8, 38, 8, 45), - 'tag': 2 - } - ], - 'propertyPathTypes': [ - { - 'name': 'string', - 'type': 'basic' - } - ], - loc: loc(8, 35, 8, 47), - 'tokenRange': [36, 39], - 'type': 'property_access', - 'inferred': { - 'name': 'string', - 'type': 'basic' - } - }, - 'type': 'expr' - }, - { - 'type': 'element', - 'value': { - 'index': 39, - 'loc': loc(8, 46, 8, 46), - 'string': '', - 'tag': 12, - 'tail': true - } - } - ], - 'inferred': { - 'name': 'string', - 'type': 'basic' - }, - 'needCast': false, - 'tokenRange': [35, 40], - 'type': 'template_string' - } - ], - 'inferred': { - 'name': 'void', - 'type': 'basic' - }, - 'isAsync': false, - 'isStatic': true, - 'hasThrow': false, - 'left': { - 'id': { - 'index': 33, - 'lexeme': 'print', - 'loc': loc(8, 11, 8, 16), - 'tag': 2 - }, - 'type': 'method_call' - }, - 'loc': loc(8, 11, 8, 48), - 'tokenRange': [33, 41], - 'type': 'call' - } - ], - 'tokenRange': [32, 42], - 'type': 'stmts' - }); - expect(tryStmt.finallyBlock).to.eql({ - 'stmts': [ - { - 'args': [ - { - 'inferred': { - 'name': 'string', - 'type': 'basic' - }, - loc: loc(10, 18, 10, 31), - 'needCast': false, - 'tokenRange': [47, 48], - 'type': 'string', - 'value': { - 'index': 47, - loc: loc(10, 18, 10, 31), - 'string': 'finally block', - 'tag': 1 - } - } - ], - 'loc': { - 'end': { - 'column': 33, - 'line': 10 - }, - 'start': { - 'column': 11, - 'line': 10 - } - }, - 'left': { - 'id': { - 'index': 45, - 'lexeme': 'print', - 'loc': { - 'end': { - 'column': 16, - 'line': 10 - }, - 'start': { - 'column': 11, - 'line': 10 - } - }, - 'tag': 2 - }, - 'type': 'method_call' - }, - 'isStatic': true, - 'isAsync': false, - 'hasThrow': false, - 'tokenRange': [45, 49], - 'type': 'call', - 'inferred': { - 'name': 'void', - 'type': 'basic' - } - } - ], - 'tokenRange': [44, 50], - 'type': 'stmts' - }); - }); - - it('set string to readable should ok', function () { - expect(function () { - parse(` - model M = { - body: readable - } - - static function func(m: M): void { - m.body = 'string'; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - model M = { - body: readable - } - - static function func(m: M, b: bytes): void { - m.body = b; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - model M = { - body: readable - } - - static function func(m: M): void { - m.body = 1234;}`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`can't assign integer to readable`); - }); - }); - - it('inferred for assign should ok', function () { - const ast = parse(` - init(); - - api get(): void { - __request.method = 'GET'; - __request.pathname = '/'; - __request.headers.key = \`abc\`; - if (true) { - __request.headers.authorization = \`acs \`; - } - }`, '__filename'); - const [, api] = ast.moduleBody.nodes; - const [, , headersStmt, ifstmt] = api.apiBody.stmts.stmts; - expect(headersStmt).to.eql({ - 'expr': { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 30, - 'loc': loc(7, 34, 7, 37), - 'string': 'abc', - 'tag': 12, - 'tail': true - } - } - ], - 'inferred': { - 'name': 'string', - 'type': 'basic' - }, - 'tokenRange': [30, 31], - 'type': 'template_string' - }, - 'left': { - 'id': { - 'inferred': { - 'moduleName': undefined, - 'name': '$Request', - 'type': 'model' - }, - 'index': 24, - 'lexeme': '__request', - 'loc': loc(7, 9, 7, 18), - 'tag': 2, - 'type': 'variable' - }, - 'inferred': { - 'name': 'string', - 'type': 'basic' - }, - 'propertyPath': [ - { - 'index': 26, - 'lexeme': 'headers', - loc: loc(7, 19, 7, 26), - 'tag': 2 - }, - { - 'index': 28, - 'lexeme': 'key', - 'loc': { - 'end': { - 'column': 30, - 'line': 7 - }, - 'start': { - 'column': 27, - 'line': 7 - } - }, - 'tag': 2 - } - ], - 'propertyPathTypes': [ - { - 'keyType': { - 'name': 'string', - 'type': 'basic' - }, - 'type': 'map', - 'valueType': { - 'name': 'string', - 'type': 'basic' - } - }, - { - 'name': 'string', - 'type': 'basic' - } - ], - 'type': 'property' - }, - 'tokenRange': [24, 31], - 'type': 'assign' - }); - expect(ifstmt.type).to.be('if'); - expect(ifstmt.stmts.stmts).to.eql([ - { - 'expr': { - 'elements': [ - { - 'type': 'element', - 'value': { - 'index': 43, - 'loc': loc(9, 46, 9, 50), - 'string': 'acs ', - 'tag': 12, - 'tail': true - } - } - ], - 'inferred': { - 'name': 'string', - 'type': 'basic' - }, - 'tokenRange': [43, 44], - 'type': 'template_string' - }, - 'left': { - 'id': { - 'loc': loc(9, 11, 9, 20), - 'tag': 2, - 'type': 'variable', - 'index': 37, - 'inferred': { - 'moduleName': undefined, - 'name': '$Request', - 'type': 'model' - }, - 'lexeme': '__request' - }, - 'inferred': { - 'name': 'string', - 'type': 'basic' - }, - 'propertyPath': [ - { - 'index': 39, - 'lexeme': 'headers', - 'loc': loc(9, 21, 9, 28), - 'tag': 2 - }, - { - 'index': 41, - 'lexeme': 'authorization', - 'loc': loc(9, 29, 9, 42), - 'tag': 2 - } - ], - 'propertyPathTypes': [ - { - 'keyType': { - 'name': 'string', - 'type': 'basic' - }, - 'type': 'map', - 'valueType': { - 'name': 'string', - 'type': 'basic' - } - }, - { - 'name': 'string', - 'type': 'basic' - } - ], - 'type': 'property', - }, - 'tokenRange': [37, 44], - 'type': 'assign', - } - ]); - }); - - it('should check and/or expr', function () { - expect(function () { - parse(` - static function less(a: number, b: number): boolean; - static function great(a: number, b: number): boolean; - static function between(input: number, min: number, max: number): string { - if (great(input, min) && less(input, max)) { - return "yes"; - } - return "no"; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function is(a: number, b: number): boolean; - static function oneOf(input: number, min: number, max: number): string { - if (is(input, max) || is(input, min)) { - return "yes"; - } - return "no"; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function oneOf(input: number, min: number, max: number): string { - if ("string1" || "string2") { - return "yes"; - } - return "no"; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the left expr must be boolean type'); - }); - - expect(function () { - parse(` - static function oneOf(input: number, min: number, max: number): string { - if (true || "string2") { - return "yes"; - } - return "no"; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the right expr must be boolean type'); - }); - }); - - it('if request assign: if/elseif/else should ok', function () { - expect(function () { - parse(` - init(); - - api get(): void { - __request.method = 'GET'; - __request.pathname = '/'; - if (true) { - __request.headers.authorization = \`acs \`; - } else if (false) { - __request.headers.authorization = \`acs \`; - } else { - __request.headers.authorization = \`acs \`; - } - }`, '__filename'); - }).to.not.throwException(); - }); - - it('request assign: if/elseif/else should ok', function () { - expect(function () { - parse(` static function main(): void { - var a = ''; - if (true) { - a = 'if'; - } else if (false) { - a = 'else if'; - } else { - a = 'else'; - } - }`, '__filename'); - }).to.not.throwException(); - }); - - it('if request assign with undefined property should not ok', function () { - expect(function () { - parse(` - init(); - api get(): void { - __request.method = 'GET'; - __request.pathname = '/'; - if (true) { - __request.hehe = '/'; - } - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be(`The property hehe is undefined in model __request($Request)`); - }); - - expect(function () { - parse(` - init(); - api get(): void { - __request.method = 'GET'; - __request.pathname = '/'; - __request.port = '7001'; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be(`can't assign string to number`); - }); - }); - - it('submodel as parameter should ok', function () { - expect(function () { - parse(` - - model M = { - N: { - - } - } - static function test(i: class): void {} - static function func(): void { - test(M.N); - }`, '__filename'); - }).to.not.throwException((ex) => { - }); - }); - - it('undefined submodel as parameter should not ok', function () { - expect(function () { - parse(` - - model M = { - N: { - - } - } - static function test(input: class): void { - - } - - function func(): void { - test(M.X); - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('The model M.X is undefined'); - }); - }); - - it('while condition must be a boolean expr', function () { - expect(function () { - parse(` - - static function main(): void { - while (123) { - - } - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the condition expr must be boolean type'); - }); - }); - - it('condition is boolean should ok', function () { - expect(function () { - parse(` - - static function main(): void { - while (true) { - - } - } -`, '__filename'); - }).to.not.throwException(); - }); - - it('the list is not array type should not ok', function () { - expect(function () { - parse(` - static function main(): void { - for (var i : {}) { - - } - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the list in for must be array type'); - }); - }); - - it('the list is array type should be ok', function () { - expect(function () { - parse(` - static function main(): void { - for (var i : []) { - - } - }`, '__filename'); - }).to.not.throwException(); - }); - - it('return expr should match with return type', function () { - expect(function () { - parse(`static function callOSS(): void { - return; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(`static function callOSS(): map[string]string { - var m = { - key = 'value', - }; - - return { - k = 'string', - ...m - }; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(`static function callOSS(): map[string]string { - var m = { - key = 123, - }; - - return { - k = 'string', - ...m - }; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the return type is not expected, expect: map[string]string, actual: map[string]any'); - }); - - expect(function () { - parse(` - static function callOSS(): [ string ] { - return [ '' ]; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function callOSS(): [ string ] { - return []; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the return type is not expected, expect: [string], actual: [any]'); - }); - - expect(function () { - parse(` - static function callOSS(): class { - return []; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the return type is not expected, expect: class, actual: [any]'); - }); - - expect(function () { - parse(` - static function callOSS(): [ string ] { - return ['', 123]; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the return type is not expected, expect: [string], actual: [any]'); - }); - - expect(function () { - parse(` - model M = {}; - static function callOSS(): $Model { - return new M; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - model M = {}; - static function callOSS(): M { - var v: any = null; - return v; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function callOSS(): map[string]any { - return {key = 'value'}; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - model M = {}; - static function callOSS(): map[string]any { - var m = new M{}; - return {key = m}; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function callOSS(): map[string]string { - return {key = 1}; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the return type is not expected, expect: map[string]string, actual: map[string]integer'); - }); - - expect(function () { - parse(` - static function callOSS(): integer { - return 0; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function callOSS(): number { - return 0; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('use function in api should ok', function () { - expect(function () { - parse(`init(); - function getPath(): string { - return "/"; - } - - api hello(): void { - __request.method = 'GET'; - __request.pathname = getPath(); - }`, '__filename'); - }).to.not.throwException(); - }); - - it('not expr should ok', function () { - expect(function () { - parse(`static function callOSS(): boolean { - return !true; - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(`static function callOSS(): boolean { - return !'string'; - }`, '__filename'); - }).to.throwException((ex) => { - expect(ex).to.be.a(SyntaxError); - expect(ex.message).to.be('the expr after ! must be boolean type'); - }); - }); - - it('object = map[string]string should ok', function () { - expect(function () { - parse(` - static function call(): void { - var params: object = { - grant_type = 'string', - }; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('replace __module.xxx on AST should ok', function () { - const ast = parse(` - const version = "2012"; - static function callOSS(): string { - return __module.version; - }`, '__filename'); - const [, func] = ast.moduleBody.nodes; - expect(func).to.be.eql({ - 'annotation': undefined, - 'functionBody': { - 'loc': { - 'end': { - 'column': 8, - 'line': 5 - }, - 'start': { - 'column': 41, - 'line': 3 - } - }, - 'stmts': { - 'stmts': [ - { - 'expr': { - 'type': 'string', - inferred: { - type: 'basic', - name: 'string' - }, - 'value': { - 'index': 4, - 'loc': loc(2, 24, 2, 28), - 'string': '2012', - 'tag': 1 - } - }, - 'loc': { - 'end': { - 'column': 7, - 'line': 5 - }, - 'start': { - 'column': 9, - 'line': 4 - } - }, - 'needCast': false, - 'tokenRange': [14, 18], - 'type': 'return' - } - ], - 'tokenRange': [13, 19], - 'type': 'stmts' - }, - 'tokenRange': [13, 19], - 'type': 'functionBody', - }, - 'functionName': { - 'index': 8, - 'lexeme': 'callOSS', - 'loc': { - 'end': { - 'column': 30, - 'line': 3 - }, - 'start': { - 'column': 23, - 'line': 3 - } - }, - 'tag': 2 - }, - 'isAsync': false, - 'isStatic': true, - 'hasThrow': false, - 'params': { - 'params': [], - 'type': 'params' - }, - 'returnType': { - 'index': 12, - 'lexeme': 'string', - 'loc': { - 'end': { - 'column': 40, - 'line': 3 - }, - 'start': { - 'column': 34, - 'line': 3 - } - }, - 'tag': 8 - }, - 'tokenRange': [6, 19], - 'type': 'function' - }); - }); - - it('support init body', function () { - var ast = parse(` - type @id = string; - - init(){ - @id = 'string'; - }`, '__filename'); - - const [typedef, init] = ast.moduleBody.nodes; - expect(typedef).to.eql({ - 'annotation': undefined, - 'type': 'type', - 'tokenRange': [1, 5], - 'value': { - 'index': 4, - 'lexeme': 'string', - 'loc': loc(2, 16, 2, 22), - 'tag': 8 - }, - 'vid': { - 'index': 2, - 'lexeme': '@id', - 'loc': loc(2, 10, 2, 13), - 'tag': 3 - } - }); - expect(init.type).to.equal('init'); - expect(init.initBody).to.eql({ - type: 'stmts', - 'tokenRange': [9, 14], - stmts: [ - { - 'expr': { - 'loc': loc(5, 14, 5, 20), - 'tokenRange': [12, 13], - 'type': 'string', - 'value': { - 'index': 12, - 'loc': loc(5, 14, 5, 20), - 'tag': 1, - 'string': 'string' - }, - 'inferred': { - 'name': 'string', - 'type': 'basic' - } - }, - 'left': { - 'inferred': { - 'name': 'string', - 'type': 'basic' - }, - 'type': 'virtualVariable', - 'vid': { - 'index': 10, - 'lexeme': '@id', - 'loc': loc(5, 7, 5, 10), - 'tag': 3 - } - }, - 'loc': loc(5, 7, 5, 21), - 'tokenRange': [10, 13], - 'type': 'assign' - } - ] - }); - }); - - it('init with parameter should ok', function () { - expect(function () { - parse(` - type @id = string; - - init(id: string){ - @id = idx; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('variable "idx" undefined'); - }); - - expect(function () { - parse(` - type @id = string; - - init(id: string){ - @id = id; - }`, '__filename'); - }).to.not.throwError(); - }); - - it('retry only can be in returns block', function () { - expect(function () { - parse(` - static function a(): void { - retry; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('retry only can be in returns block'); - }); - - expect(function () { - parse(` - init() {} - api hello(): void { - retry; - } returns { - - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('retry only can be in returns block'); - }); - - expect(function () { - parse(` - init() {} - api hello(): void { - - } returns { - retry; - }`, '__filename'); - }).to.not.throwError(); - }); - - it('throw should be ok', function () { - expect(function () { - parse(` - init() {} - api hello(): void { - throw {}; - } returns { - - }`, '__filename'); - }).to.not.throwError(); - }); - - describe('needToMap', function () { - it('needToMap should ok', function () { - const ast = parse(` - static function callA(a: any): void; - model M = {}; - static function main(): void { - callA(new M); - }`, '__filename'); - const [, , f2] = ast.moduleBody.nodes; - expect(f2.functionBody.stmts.stmts).to.be.eql([ - { - 'args': [ - { - 'aliasId': { - 'index': 29, - 'isModel': true, - 'lexeme': 'M', - 'loc': loc(5, 21, 5, 22), - 'tag': 2 - }, - 'inferred': { - 'moduleName': undefined, - 'name': 'M', - 'type': 'model' - }, - 'needCast': true, - 'object': null, - 'propertyPath': [], - 'tokenRange': [28, 30], - 'type': 'construct_model' - } - ], - 'inferred': { - 'name': 'void', - 'type': 'basic' - }, - 'isAsync': false, - 'isStatic': true, - 'hasThrow': false, - 'left': { - 'id': { - 'index': 26, - 'lexeme': 'callA', - 'loc': loc(5, 11, 5, 16), - 'tag': 2 - }, - 'type': 'method_call' - }, - 'loc': loc(5, 11, 5, 23), - 'tokenRange': [26, 31], - 'type': 'call' - } - ]); - }); - - it('needToMap(with null) should ok', function () { - const ast = parse(` - model M = {}; - static function callA(a: M): void; - static function main(): void { - callA(null); - }`, '__filename'); - const [, , f2] = ast.moduleBody.nodes; - expect(f2.functionBody.stmts.stmts).to.be.eql([ - { - 'args': [ - { - 'inferred': { - 'name': 'null', - 'type': 'basic' - }, - 'needCast': false, - 'tokenRange': [28, 29], - 'type': 'null' - } - ], - 'inferred': { - 'name': 'void', - 'type': 'basic' - }, - 'isAsync': false, - 'isStatic': true, - 'hasThrow': false, - 'left': { - 'id': { - 'index': 26, - 'lexeme': 'callA', - 'loc': loc(5, 11, 5, 16), - 'tag': 2 - }, - 'type': 'method_call' - }, - 'loc': loc(5, 11, 5, 22), - 'tokenRange': [26, 30], - 'type': 'call' - } - ]); - }); - - it('needToMap(with $Model) should ok', function () { - const ast = parse(` - model M = {}; - static function callA(a: $Model): void; - static function main(): void { - callA(new M); - }`, '__filename'); - const [, , f2] = ast.moduleBody.nodes; - expect(f2.functionBody.stmts.stmts).to.be.eql([ - { - 'args': [ - { - 'aliasId': { - 'index': 29, - 'isModel': true, - 'lexeme': 'M', - 'loc': loc(5, 21, 5, 22), - 'tag': 2 - }, - 'inferred': { - 'moduleName': undefined, - 'name': 'M', - 'type': 'model' - }, - 'needCast': false, - 'object': null, - 'propertyPath': [], - 'tokenRange': [28, 30], - 'type': 'construct_model' - } - ], - 'inferred': { - 'name': 'void', - 'type': 'basic' - }, - 'hasThrow': false, - 'isAsync': false, - 'isStatic': true, - 'left': { - 'id': { - 'index': 26, - 'lexeme': 'callA', - 'loc': loc(5, 11, 5, 16), - 'tag': 2 - }, - 'type': 'method_call' - }, - 'loc': loc(5, 11, 5, 23), - 'tokenRange': [26, 31], - 'type': 'call' - } - ]); - }); - }); - - it('inferred for object should ok', function () { - function getObjectInferred(expr) { - const ast = parse(` - static function hello(): void { - var a = ${expr}; - }`, '__filename'); - const [f1] = ast.moduleBody.nodes; - const [s1] = f1.functionBody.stmts.stmts; - return s1.expr.inferred; - } - - expect(getObjectInferred('{}')).to.eql({ - 'keyType': { - 'name': 'string', - 'type': 'basic' - }, - 'type': 'map', - 'valueType': { - 'name': 'any', - 'type': 'basic' - } - }); - - expect(getObjectInferred(`{key = 'value'}`)).to.eql({ - 'keyType': { - 'name': 'string', - 'type': 'basic' - }, - 'type': 'map', - 'valueType': { - 'name': 'string', - 'type': 'basic' - } - }); - - expect(getObjectInferred(`{key = 'value', key2 = 1}`)).to.eql({ - 'keyType': { - 'name': 'string', - 'type': 'basic' - }, - 'type': 'map', - 'valueType': { - 'name': 'any', - 'type': 'basic' - } - }); - }); - - it('construct model should be ok', function () { - expect(function () { - parse(` - model M { - key: [ string ], - fieldLong: long, - }; - - static function hello(): void { - new M{ - key = [ 'string' ], - fieldLong = 3600, - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model N {} - model M { - n: [N] - }; - - static function hello(): void { - new M{ - n = [ new N ] - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model N {} - model M { - n: N - }; - - static function hello(): void { - new M{ - n = new N - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - n: {} - }; - - static function hello(): void { - new M{ - n = new M.n - }; - }`, '__filename'); - }).to.not.throwError(); - }); - - it('construct model expectedType should be ok', function () { - - let ast = parse(` - model M { - key: [ string ], - fieldLong: long, - }; - - static function hello(): void { - new M{ - key = [ 'string' ], - fieldLong = 3600, - }; - }`, '__filename'); - let expectedType = ast.moduleBody.nodes[1].functionBody.stmts.stmts[0].object.fields[0].expectedType; - expect(expectedType).to.be.eql({ - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'string' - } - }); - - ast = parse(` - model N {} - model M { - n: [N] - }; - - static function hello(): void { - new M{ - n = [ new N ] - }; - }`, '__filename'); - expectedType = ast.moduleBody.nodes[2].functionBody.stmts.stmts[0].object.fields[0].expectedType; - expect(expectedType).to.be.eql({ - 'type': 'array', - 'itemType': { - 'moduleName': undefined, - 'type': 'model', - 'name': 'N' - } - }); - - ast = parse(` - model N {} - model M { - n: N - }; - - static function hello(): void { - new M{ - n = new N - }; - }`, '__filename'); - expectedType = ast.moduleBody.nodes[2].functionBody.stmts.stmts[0].object.fields[0].expectedType; - expect(expectedType).to.be.eql({ - 'moduleName': undefined, - 'type': 'model', - 'name': 'N' - }); - - ast = parse(` - model M { - n: map[string] any - }; - - static function hello(): void { - new M{ - n = {key = 'string'} - }; - }`, '__filename'); - expectedType = ast.moduleBody.nodes[1].functionBody.stmts.stmts[0].object.fields[0].expectedType; - expect(expectedType).to.be.eql({ - 'type': 'map', - 'keyType': { - 'type': 'basic', - 'name': 'string' - }, - 'valueType': { - 'type': 'basic', - 'name': 'any' - } - }); - ast = parse(` - model M { - n: {} - }; - - static function hello(): void { - new M{ - n = new M.n - }; - }`, '__filename'); - expectedType = ast.moduleBody.nodes[1].functionBody.stmts.stmts[0].object.fields[0].expectedType; - expect(expectedType).to.be.eql({ - 'moduleName': undefined, - 'type': 'model', - 'name': 'M.n' - }); - }); - - it('vid should be ok', function () { - expect(function () { - parse(` - model M { - key: { - key2: string - } - }; - - type @id = M; - init() { - @id = new M{ - key = new M.key{ - key2 = 'string' - } - }; - } - - function main(): void { - @id.key.key2 = 'key2'; - } - `, '__filename'); - }).to.not.throwError(); - }); - - it('vid with idType should be ok', function () { - const ast = parse(` - model M { - key: { - key2: string - } - }; - - type @id = M;`, '__filename'); - const [, id] = ast.moduleBody.nodes; - expect(id.value.idType).to.be('model'); - }); - - it('assign model instance to a model filed should ok', function () { - expect(function () { - parse(` - model M { - key: { - key2: string - } - }; - - model N { - key: M.key - } - - static function main(): void { - var mKey = new M.key{ - key2 = 'key2' - }; - var n = new N{ - key = mKey - }; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - key: { - key2: string - } - }; - - model N { - key: M - } - - static function main(): void { - var m = new M{ - key = new M.key{ - key2 = 'key2' - } - }; - var n = new N{ - key = m - }; - } - `, '__filename'); - }).to.not.throwError(); - - - expect(function () { - parse(` - model M { - key: { - key2: [{ - key3: string - }] - } - }; - - static function main(): void { - var key3 = new M.key.key2{ - key3 = 'key3' - }; - var key = new M.key{ - key2 = [key3] - }; - var m = new M{ - key = key - }; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - readAndParse('fixtures/module_assign/main.dara'); - }).to.not.throwError(); - }); - - it('super should be ok', function () { - expect(function () { - parse(` - static function hello(): void { - super(); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('super only allowed in init method'); - }); - - expect(function () { - parse(` - init() { - super(); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('this module have no parent module'); - }); - }); - - it('map[] should be ok', function () { - expect(function () { - parse(` - static function hello(): void { - var a = {}; - var key = 1; - a[key]; - a[key] = 'string'; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('The key expr type must be string type'); - }); - - expect(function () { - parse(` - static function hello(): void { - var a = {}; - var key = 'str'; - a[key]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - static function hello(): void { - var a = { - b = {} - }; - var key = 'str'; - a.b[key]; - }`, '__filename'); - - parse(` - model M = { N: map[string]any } - static function hello(): void { - var a = new M; - var key = 'str'; - a.N[key]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - static function hello(): void { - var a = [1, 2, 3]; - var key = 'str'; - a[key]; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('The key expr type must be number type'); - }); - - expect(function () { - parse(` - static function hello(): void { - var a = [1, 2, 3]; - var key = 1; - a[key]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - static function hello(): void { - var a = 'hehe'; - var key = 'str'; - a[key]; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the [] form only support map or array type'); - }); - }); - - it('caculate property path types should be ok', function () { - const ast = parse(` - model M { - N: { - A: string - } - } - static function hello(m: M): void { - m.N.A; - }`, '__filename'); - const [, func1] = ast.moduleBody.nodes; - const [stmt] = func1.functionBody.stmts.stmts; - expect(stmt.propertyPathTypes).to.eql([ - { - 'moduleName': undefined, - 'name': 'M.N', - 'type': 'model' - }, - { - 'name': 'string', - 'type': 'basic' - } - ]); - - const ast2 = parse(` - model N1 { - A: string - } - - model M1 { - N: N1 - } - - static function hello(m: M1): void { - m.N.A; - }`, '__filename'); - const [, , func2] = ast2.moduleBody.nodes; - const [stmt1] = func2.functionBody.stmts.stmts; - expect(stmt1.propertyPathTypes).to.eql([ - { - 'moduleName': undefined, - 'name': 'N1', - 'type': 'model' - }, - { - 'name': 'string', - 'type': 'basic' - } - ]); - }); - - it('return expr in init function is limited', function () { - expect(function () { - parse(` - init() { - return 'string'; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('should not have return value in init method'); - }); - }); - - it('var arr: [ string ] = [] should ok', function () { - expect(function () { - parse(` - init() { - var empty: [string] = []; - }`, '__filename'); - }).to.not.throwError(); - }); - - it('used types should ok', function () { - const ast = parse(` - type @r = readable; - `, '__filename'); - expect(ast.usedTypes.has('readable')).to.be(true); - expect(ast.usedTypes.has('writable')).to.be(false); - }); - - it('used types(in model) should ok', function () { - const ast = parse(` - model m { - r: readable - } - `, '__filename'); - expect(ast.usedTypes.has('readable')).to.be(true); - }); - - it('number type check in assign expr should ok', function () { - expect(function () { - parse(` - init() { - var num: number = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: number = 1.23; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: number = 1.23d; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: number = 123L; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: [ number ] = [ 123 ]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: [ number ] = [ 1.23 ]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: [ number ] = [ 1.23d ]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: [ number ] = [ 123L ]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: map[ string ] number = { - val = 123 - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: map[ string ] number = { - val = 1.23 - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: map[ string ] number = { - val = 1.23d - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var num: map[ string ] number = { - val = 123L - }; - }`, '__filename'); - }).to.not.throwError(); - - - expect(function () { - parse(` - init() { - var intNum: integer = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var intArr: [ integer ] = [ 123 ]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var intMap: map[string] integer = { - val = 123 - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var int8Num: int8 = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var int8Arr: [ int8 ] = [ 123 ]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var int8Map: map[string] int8 = { - val = 123 - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var uint8Num: uint8 = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var int16Num: int16 = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var uint16Num: uint16 = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var int32Num: int32 = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var uint32Num: uint32 = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var int64Num: int64 = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var uint64Num: uint64 = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var longNum: long = 123L; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var longArr: [ long ] = [ 123L ]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var longMap: map[string] long = { - val = 123L - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var ulongNum: ulong = 123L; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var longNum: long = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var ulongNum: ulong = 123; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var floatNum: float = 1.23; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var floatArr: [ float ] = [ 1.23 ]; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var floatMap: map[string] float = { - val = 1.23 - }; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var doubleNum: double = 1.23d; - }`, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - init() { - var intNum: integer = 1.23; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: integer, actual: float'); - }); - - expect(function () { - parse(` - init() { - var intNum: integer = 1.23d; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: integer, actual: double'); - }); - - expect(function () { - parse(` - init() { - var intNum: integer = 123L; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: integer, actual: long'); - }); - - expect(function () { - parse(` - init() { - var floatNum: float = 123; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: float, actual: integer'); - }); - - expect(function () { - parse(` - init() { - var floatNum: float = 1.23d; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: float, actual: double'); - }); - - expect(function () { - parse(` - init() { - var floatNum: float = 123L; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: float, actual: long'); - }); - - expect(function () { - parse(` - init() { - var doubleNum: double = 1.23; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: double, actual: float'); - }); - - expect(function () { - parse(` - init() { - var doubleNum: double = 123; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: double, actual: integer'); - }); - - expect(function () { - parse(` - init() { - var doubleNum: double = 123L; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: double, actual: long'); - }); - - expect(function () { - parse(` - init() { - var longNum: long = 1.23; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: long, actual: float'); - }); - - expect(function () { - parse(` - init() { - var longNum: long = 1.23d; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('declared variable with mismatched type, expected: long, actual: double'); - }); - }); - - it('number type check in modle construct should ok', function () { - expect(function () { - parse(` - model M { - intNum: integer, - int8Num: int8, - uint8Num: uint8, - int16Num: int16, - uint16Num: uint16, - int32Num: int32, - uint32Num: uint32, - int64Num: int64, - uint64Num: uint64, - longNum: long, - ulongNum: ulong, - floatNum: float, - doubleNum: double, - intArr: [ integer ], - int8Arr: [ int8 ], - longArr: [ long ], - floatArr: [ float ], - intMap: map[string] integer, - int8Map: map[string] int8, - longMap: map[string] long, - floatMap: map[string] float, - } - - init() { - var m = new M{ - intNum = 123, - int8Num = 123, - uint8Num = 123, - int16Num = 123, - uint16Num = 123, - int32Num = 123, - uint32Num = 123, - int64Num = 123, - uint64Num = 123, - longNum = 123L, - ulongNum = 123L, - floatNum = 1.23, - doubleNum = 1.23d, - intArr = [ 123 ], - int8Arr = [ 123 ], - longArr = [ 123L ], - floatArr = [ 1.23 ], - intMap = { - val = 123 - }, - int8Map = { - val = 123 - }, - longMap = { - val = 123L - }, - floatMap = { - val = 1.23 - }, - }; - }`, '__filename'); - }).to.not.throwError(); - - - expect(function () { - parse(` - model M { - intNum: integer - } - init() { - var m = new M{ - intNum = 1.23 - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected integer, but float'); - }); - - expect(function () { - parse(` - model M { - intNum: integer - } - init() { - var m = new M{ - intNum = 1.23d - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected integer, but double'); - }); - - expect(function () { - parse(` - model M { - intNum: integer - } - init() { - var m = new M{ - intNum = 123L - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected integer, but long'); - }); - - expect(function () { - parse(` - model M { - longNum: long - } - init() { - var m = new M{ - longNum = 1.23 - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected long, but float'); - }); - - expect(function () { - parse(` - model M { - longNum: long - } - init() { - var m = new M{ - longNum = 1.23d - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected long, but double'); - }); - - expect(function () { - parse(` - model M { - floatNum: float - } - init() { - var m = new M{ - floatNum = 123L - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected float, but long'); - }); - - expect(function () { - parse(` - model M { - floatNum: float - } - init() { - var m = new M{ - floatNum = 1.23d - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected float, but double'); - }); - - expect(function () { - parse(` - model M { - floatNum: float - } - init() { - var m = new M{ - floatNum = 123 - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected float, but integer'); - }); - - expect(function () { - parse(` - model M { - doubleNum: double - } - init() { - var m = new M{ - doubleNum = 123L - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected double, but long'); - }); - - expect(function () { - parse(` - model M { - doubleNum: double - } - init() { - var m = new M{ - doubleNum = 1.23 - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected double, but float'); - }); - - expect(function () { - parse(` - model M { - doubleNum: double - } - init() { - var m = new M{ - doubleNum = 123 - }; - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the field type are mismatched. expected double, but integer'); - }); - }); - - it('parameters number type check in method call should ok', function () { - expect(() => { - parse(` - api get(intNum: number, floatNum: number, longNum: long): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(123, 1.23, 123L); - } - - init() {}`, '__filename'); - }).to.not.throwError(); - - expect(() => { - parse(` - api get(num: integer): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(123); - } - - init() {}`, '__filename'); - }).to.not.throwError(); - - expect(() => { - parse(` - api get( - int8Num: int8, - uint8Num: uint8, - int16Num: int16, - uint16Num: uint16, - int32Num: int32, - uint32Num: uint32, - int64Num: int64, - uint64Num: uint64 - ): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - get( - 8, 8, - 16, 16, - 32, 32, - 64, 64 - ); - return ''; - } - - init() {}`, '__filename'); - }).to.not.throwError(); - - expect(() => { - parse(` - api get(longNum: long, ulongNum: ulong): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(123L, 123L); - } - - async function getOtherObject(): string { - return get(123, 123); - } - - init() {}`, '__filename'); - }).to.not.throwError(); - - expect(() => { - parse(` - api get(floatNum: float, doubleNum: double): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(1.23, 1.23d); - } - - init() {}`, '__filename'); - }).to.not.throwError(); - - expect(() => { - parse(` - api get(num: integer): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(1.23); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(integer), but get(float)'); - }); - - expect(() => { - parse(` - api get(num: integer): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(1.23d); - } - - init() {}`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(integer), but get(double)'); - }); - - expect(() => { - parse(` - api get(num: integer): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(123L); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(integer), but get(long)'); - }); - - expect(() => { - parse(` - api get(num: float): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(123); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(float), but get(integer)'); - }); - - expect(() => { - parse(` - api get(num: float): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(1.23d); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(float), but get(double)'); - }); - - expect(() => { - parse(` - api get(num: float): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(123L); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(float), but get(long)'); - }); - - expect(() => { - parse(` - api get(num: double): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(123); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(double), but get(integer)'); - }); - - expect(() => { - parse(` - api get(num: double): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(1.23); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(double), but get(float)'); - }); - - expect(() => { - parse(` - api get(num: double): string { - __request.method = 'GET'; - __request.pathname = '/'; - } - - async function getObject(): string { - return get(123L); - }`, '__filename'); - }).to.throwError((ex) => { - expect(ex).to.be.an(SyntaxError); - expect(ex.message).to.be('the parameter types are mismatched. expected get(double), but get(long)'); - }); - }); - - it('conflict models should ok', function () { - let ast = readAndParse('fixtures/module_model_conflict/module_model_in_function.dara'); - expect(ast.conflictModels.has('OSS:Config')).to.be(true); - ast = readAndParse('fixtures/module_model_conflict/module_model_in_model.dara'); - expect(ast.conflictModels.has('OSS:Config')).to.be(true); - ast = readAndParse('fixtures/module_model_conflict/module_model_in_params.dara'); - expect(ast.conflictModels.has('OSS:Config')).to.be(true); - ast = readAndParse('fixtures/module_model_conflict/module_model_conflict_other.dara'); - expect(ast.conflictModels.has('OSS:Config')).to.be(false); - expect(ast.conflictModels.has('Source:Config')).to.be(true); - ast = readAndParse('fixtures/module_model_conflict/module_model_unuse.dara'); - expect(ast.conflictModels.has('OSS:Config')).to.be(false); - }); - - it('multi-dimentional array assign check should be ok', function () { - expect(function () { - parse(` - static function main(): void { - var mulArr: [[ string ]] = [ ['string'],['string'] ]; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - static function main(): void { - var mulArr: [[[ string ]]] = [ [ ['string'] ],[ ['string'] ] ]; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - key: string - } - static function main(): void { - var m = new M{ - key = 'string' - }; - var mulArr: [[ M ]] = [ [ m ] ]; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - sub: { - key: string - } - } - static function main(): void { - var sub = new M.sub{ - key = 'string' - }; - var mulArr: [[ M.sub ]] = [ [ sub ] ]; - } - `, '__filename'); - }).to.not.throwError(); - - - expect(function () { - parse(` - type @test = [[ string ]] - init() { - @test = [ ['string'],['string'] ]; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - key: [[ string ]] - }; - - static function main(): void { - var m = new M{ - key = [[ 'string' ]] - }; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - key: [[ number ]] - }; - - static function main(): void { - var m = new M{ - key = [[ 2 ]] - }; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - key: [[{ - key2: string - }]] - }; - - static function main(): void { - var key = new M.key{ - key2 = 'key2' - }; - var m = new M{ - key = [[ key ]] - }; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - key: [[[{ - key2: string - }]]] - }; - - static function main(): void { - var key = new M.key{ - key2 = 'key2' - }; - var m = new M{ - key = [[[ key ]]] - }; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model M { - key: { - key2: [[{ - key3: string - }]] - } - }; - - static function main(): void { - var key3 = new M.key.key2{ - key3 = 'key3' - }; - var key = new M.key{ - key2 = [[key3]] - }; - var m = new M{ - key = key - }; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model N { - key: string - } - - model M { - n: [[ N ]] - }; - - static function main(): void { - var n = new N{ - key = 'string' - }; - var m = new M{ - n = [[ n ]] - }; - } - `, '__filename'); - }).to.not.throwError(); - - expect(function () { - parse(` - model N { - key: string - } - - model M { - n: [[[ N ]]] - }; - - static function main(): void { - var n = new N{ - key = 'string' - }; - var m = new M{ - n = [[[ n ]]] - }; - } - `, '__filename'); - }).to.not.throwError(); - }); - - it('get value from array to assign should be ok', function () { - let ast = parse(` - static function main(): void { - var configs = [1,2,3]; - var config = configs[0]; - }`, '__filename'); - let [, expr] = ast.moduleBody.nodes[0].functionBody.stmts.stmts; - expect(expr.expr).to.eql({ - 'type': 'array_access', - 'id': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 4, - 'column': 22 - }, - 'end': { - 'line': 4, - 'column': 29 - } - }, - 'lexeme': 'configs', - 'index': 23, - 'type': 'variable' - }, - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 4, - 'column': 30 - }, - 'end': { - 'line': 4, - 'column': 31 - } - }, - 'value': 0, - 'type': 'integer', - 'index': 25 - }, - 'loc': { - 'start': { - 'line': 4, - 'column': 30 - }, - 'end': { - 'line': 4, - 'column': 31 - } - }, - 'tokenRange': [ - 25, - 26 - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }, - 'loc': { - 'start': { - 'line': 4, - 'column': 22 - }, - 'end': { - 'line': 4, - 'column': 32 - } - }, - 'tokenRange': [ - 23, - 27 - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }); - - ast = parse(` - static function main(): void { - var data = { - configs = [1,2,3] - }; - var config = data.configs[0]; - }`, '__filename'); - [, expr] = ast.moduleBody.nodes[0].functionBody.stmts.stmts; - expect(expr.expr).to.eql({ - 'type': 'array_access', - 'id': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 6, - 'column': 22 - }, - 'end': { - 'line': 6, - 'column': 26 - } - }, - 'lexeme': 'data', - 'index': 27, - 'type': 'variable', - 'inferred': { - 'type': 'map', - 'keyType': { - 'type': 'basic', - 'name': 'string' - }, - 'valueType': { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'integer' - } - } - } - }, - 'propertyPath': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 6, - 'column': 27 - }, - 'end': { - 'line': 6, - 'column': 34 - } - }, - 'lexeme': 'configs', - 'index': 29 - } - ], - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 6, - 'column': 35 - }, - 'end': { - 'line': 6, - 'column': 36 - } - }, - 'value': 0, - 'type': 'integer', - 'index': 31 - }, - 'loc': { - 'start': { - 'line': 6, - 'column': 35 - }, - 'end': { - 'line': 6, - 'column': 36 - } - }, - 'tokenRange': [ - 31, - 32 - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }, - 'loc': { - 'start': { - 'line': 6, - 'column': 22 - }, - 'end': { - 'line': 6, - 'column': 37 - } - }, - 'tokenRange': [ - 27, - 33 - ], - 'propertyPathTypes': [ - { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'integer' - } - } - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }); - - ast = parse(` - model M { - configs: [ number ] - } - static function main(): void { - var m = new M{ - configs = [1,2,3] - }; - var config = m.configs[0]; - }`, '__filename'); - [, expr] = ast.moduleBody.nodes[1].functionBody.stmts.stmts; - expect(expr.expr).to.eql({ - 'type': 'array_access', - 'id': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 9, - 'column': 22 - }, - 'end': { - 'line': 9, - 'column': 23 - } - }, - 'lexeme': 'm', - 'index': 38, - 'type': 'variable', - 'inferred': { - 'type': 'model', - 'name': 'M', - 'moduleName': undefined - } - }, - 'propertyPath': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 9, - 'column': 24 - }, - 'end': { - 'line': 9, - 'column': 31 - } - }, - 'lexeme': 'configs', - 'index': 40 - } - ], - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 9, - 'column': 32 - }, - 'end': { - 'line': 9, - 'column': 33 - } - }, - 'value': 0, - 'type': 'integer', - 'index': 42 - }, - 'loc': { - 'start': { - 'line': 9, - 'column': 32 - }, - 'end': { - 'line': 9, - 'column': 33 - } - }, - 'tokenRange': [ - 42, - 43 - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }, - 'loc': { - 'start': { - 'line': 9, - 'column': 22 - }, - 'end': { - 'line': 9, - 'column': 34 - } - }, - 'tokenRange': [ - 38, - 44 - ], - 'propertyPathTypes': [ - { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'number' - } - } - ], - 'inferred': { - 'type': 'basic', - 'name': 'number' - } - }); - - ast = parse(` - static function f(config: number): void { - var config2 = config; - } - - static function main(): void { - var data = { - configs = [1,2,3] - }; - var config = f(data.configs[0]); - }`, '__filename'); - let [arg] = ast.moduleBody.nodes[1].functionBody.stmts.stmts[1].expr.args; - expect(arg).to.eql({ - 'type': 'array_access', - 'id': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 10, - 'column': 24 - }, - 'end': { - 'line': 10, - 'column': 28 - } - }, - 'lexeme': 'data', - 'index': 46, - 'type': 'variable', - 'inferred': { - 'type': 'map', - 'keyType': { - 'type': 'basic', - 'name': 'string' - }, - 'valueType': { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'integer' - } - } - } - }, - 'propertyPath': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 10, - 'column': 29 - }, - 'end': { - 'line': 10, - 'column': 36 - } - }, - 'lexeme': 'configs', - 'index': 48 - } - ], - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 10, - 'column': 37 - }, - 'end': { - 'line': 10, - 'column': 38 - } - }, - 'value': 0, - 'type': 'integer', - 'index': 50 - }, - 'loc': { - 'start': { - 'line': 10, - 'column': 37 - }, - 'end': { - 'line': 10, - 'column': 38 - } - }, - 'tokenRange': [ - 50, - 51 - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }, - 'loc': { - 'start': { - 'line': 10, - 'column': 24 - }, - 'end': { - 'line': 10, - 'column': 39 - } - }, - 'tokenRange': [ - 46, - 52 - ], - 'propertyPathTypes': [ - { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'integer' - } - } - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - }, - 'needCast': false - }); - - ast = parse(` - model M { - config: number - } - static function main(): void { - var data = { - configs = [1,2,3] - }; - var m = new M{ - config = data.configs[0] - }; - }`, '__filename'); - [expr] = ast.moduleBody.nodes[1].functionBody.stmts.stmts[1].expr.object.fields; - expect(expr).to.eql({ - 'type': 'objectField', - 'fieldName': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 10, - 'column': 11 - }, - 'end': { - 'line': 10, - 'column': 17 - } - }, - 'lexeme': 'config', - 'index': 37 - }, - 'expr': { - 'type': 'array_access', - 'id': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 10, - 'column': 20 - }, - 'end': { - 'line': 10, - 'column': 24 - } - }, - 'lexeme': 'data', - 'index': 39, - 'type': 'variable', - 'inferred': { - 'type': 'map', - 'keyType': { - 'type': 'basic', - 'name': 'string' - }, - 'valueType': { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'integer' - } - } - } - }, - 'propertyPath': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 10, - 'column': 25 - }, - 'end': { - 'line': 10, - 'column': 32 - } - }, - 'lexeme': 'configs', - 'index': 41 - } - ], - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 10, - 'column': 33 - }, - 'end': { - 'line': 10, - 'column': 34 - } - }, - 'value': 0, - 'type': 'integer', - 'index': 43 - }, - 'loc': { - 'start': { - 'line': 10, - 'column': 33 - }, - 'end': { - 'line': 10, - 'column': 34 - } - }, - 'tokenRange': [ - 43, - 44 - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }, - 'loc': { - 'start': { - 'line': 10, - 'column': 20 - }, - 'end': { - 'line': 11, - 'column': 9 - } - }, - 'tokenRange': [ - 39, - 45 - ], - 'propertyPathTypes': [ - { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'integer' - } - } - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }, - 'tokenRange': [ - 37, - 45 - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - }, - 'expectedType': { - 'type': 'basic', - 'name': 'number' - } - }); - - ast = parse(` - type @configs = [ number ]; - init(configs: [ number ]) { - @configs = configs; - var config = @configs[0]; - }`, '__filename'); - [, expr] = ast.moduleBody.nodes[1].initBody.stmts; - expect(expr.expr).to.eql({ - 'type': 'array_access', - 'id': { - 'tag': 3, - 'loc': { - 'start': { - 'line': 5, - 'column': 22 - }, - 'end': { - 'line': 5, - 'column': 30 - } - }, - 'lexeme': '@configs', - 'index': 24 - }, - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 5, - 'column': 31 - }, - 'end': { - 'line': 5, - 'column': 32 - } - }, - 'value': 0, - 'type': 'integer', - 'index': 26 - }, - 'loc': { - 'start': { - 'line': 5, - 'column': 31 - }, - 'end': { - 'line': 5, - 'column': 32 - } - }, - 'tokenRange': [ - 26, - 27 - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }, - 'loc': { - 'start': { - 'line': 5, - 'column': 22 - }, - 'end': { - 'line': 5, - 'column': 33 - } - }, - 'tokenRange': [ - 24, - 28 - ], - 'inferred': { - 'type': 'basic', - 'name': 'number' - } - }); - }); - - it('assign a value to array should be ok', function () { - let ast = parse(` - static function main(): void { - var configs = [1,2,3]; - configs[3] = 4; - }`, '__filename'); - let [, expr] = ast.moduleBody.nodes[0].functionBody.stmts.stmts; - expect(expr.left).to.eql({ - 'type': 'array_access', - 'id': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 4, - 'column': 9 - }, - 'end': { - 'line': 4, - 'column': 16 - } - }, - 'lexeme': 'configs', - 'index': 20, - 'type': 'variable' - }, - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 4, - 'column': 17 - }, - 'end': { - 'line': 4, - 'column': 18 - } - }, - 'value': 3, - 'type': 'integer', - 'index': 22 - }, - 'loc': { - 'start': { - 'line': 4, - 'column': 17 - }, - 'end': { - 'line': 4, - 'column': 18 - } - }, - 'tokenRange': [ - 22, - 23 - ] - }, - 'loc': { - 'start': { - 'line': 4, - 'column': 9 - }, - 'end': { - 'line': 4, - 'column': 20 - } - }, - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }); - - ast = parse(` - static function main(): void { - var data = { - configs = [1,2,3] - }; - data.configs[3] = 4; - }`, '__filename'); - [, expr] = ast.moduleBody.nodes[0].functionBody.stmts.stmts; - expect(expr.left).to.eql({ - 'type': 'array_access', - 'id': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 6, - 'column': 9 - }, - 'end': { - 'line': 6, - 'column': 13 - } - }, - 'lexeme': 'data', - 'index': 24, - 'type': 'variable', - 'inferred': { - 'type': 'map', - 'keyType': { - 'type': 'basic', - 'name': 'string' - }, - 'valueType': { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'integer' - } - } - } - }, - 'propertyPath': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 6, - 'column': 14 - }, - 'end': { - 'line': 6, - 'column': 21 - } - }, - 'lexeme': 'configs', - 'index': 26 - } - ], - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 6, - 'column': 22 - }, - 'end': { - 'line': 6, - 'column': 23 - } - }, - 'value': 3, - 'type': 'integer', - 'index': 28 - }, - 'loc': { - 'start': { - 'line': 6, - 'column': 22 - }, - 'end': { - 'line': 6, - 'column': 23 - } - }, - 'tokenRange': [ - 28, - 29 - ] - }, - 'loc': { - 'start': { - 'line': 6, - 'column': 9 - }, - 'end': { - 'line': 6, - 'column': 25 - } - }, - 'propertyPathTypes': [ - { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'integer' - } - } - ], - 'inferred': { - 'type': 'basic', - 'name': 'integer' - } - }); - - ast = parse(` - model M { - configs: [ number ] - } - static function main(): void { - var m = new M{ - configs = [1,2,3] - }; - m.configs[3] = 4; - }`, '__filename'); - [, expr] = ast.moduleBody.nodes[1].functionBody.stmts.stmts; - expect(expr.left).to.eql({ - 'type': 'array_access', - 'id': { - 'tag': 2, - 'loc': { - 'start': { - 'line': 9, - 'column': 9 - }, - 'end': { - 'line': 9, - 'column': 10 - } - }, - 'lexeme': 'm', - 'index': 35, - 'type': 'variable', - 'inferred': { - 'moduleName': undefined, - 'type': 'model', - 'name': 'M' - } - }, - 'propertyPath': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 9, - 'column': 11 - }, - 'end': { - 'line': 9, - 'column': 18 - } - }, - 'lexeme': 'configs', - 'index': 37 - } - ], - 'accessKey': { - 'type': 'number', - 'value': { - 'tag': 9, - 'loc': { - 'start': { - 'line': 9, - 'column': 19 - }, - 'end': { - 'line': 9, - 'column': 20 - } - }, - 'value': 3, - 'type': 'integer', - 'index': 39 - }, - 'loc': { - 'start': { - 'line': 9, - 'column': 19 - }, - 'end': { - 'line': 9, - 'column': 20 - } - }, - 'tokenRange': [ - 39, - 40 - ] - }, - 'loc': { - 'start': { - 'line': 9, - 'column': 9 - }, - 'end': { - 'line': 9, - 'column': 22 - } - }, - 'propertyPathTypes': [ - { - 'type': 'array', - 'itemType': { - 'type': 'basic', - 'name': 'number' - } - } - ], - 'inferred': { - 'type': 'basic', - 'name': 'number' - } - }); - expect(() => { - parse(` - static function main(): void { - var data = { - configs = [1,2,3] - }; - data.configs['3'] = 4; - }`, '__filename'); - }).to.throwException(function (e) { - expect(e).to.be.a(SyntaxError); - expect(e.message).to.be(`The key expr type must be number type`); - }); - }); - - it('moduleModel as map value type should ok', function () { - let ast = readAndParse('fixtures/module_model_as_map_type/main.dara'); - const [model, init] = ast.moduleBody.nodes; - expect(model.modelBody.nodes[0].fieldValue).to.eql({ - 'type': 'fieldType', - 'fieldType': 'map', - 'keyType': { - 'tag': 8, - 'loc': { - 'start': { - 'line': 4, - 'column': 15 - }, - 'end': { - 'line': 4, - 'column': 21 - } - }, - 'lexeme': 'string', - 'index': 10 - }, - 'valueType': { - 'type': 'moduleModel', - 'path': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 4, - 'column': 22 - }, - 'end': { - 'line': 4, - 'column': 25 - } - }, - 'lexeme': 'OSS', - 'index': 12, - 'idType': 'module' - }, - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 4, - 'column': 26 - }, - 'end': { - 'line': 4, - 'column': 32 - } - }, - 'lexeme': 'Config', - 'index': 14 - } - ], - 'loc': { - 'start': { - 'line': 4, - 'column': 22 - }, - 'end': { - 'line': 4, - 'column': 32 - } - } - } - }); - - expect(init.initBody.stmts[0].expectedType).to.eql({ - 'loc': { - 'start': { - 'line': 8, - 'column': 15 - }, - 'end': { - 'line': 8, - 'column': 36 - } - }, - 'type': 'map', - 'keyType': { - 'tag': 8, - 'loc': { - 'start': { - 'line': 8, - 'column': 19 - }, - 'end': { - 'line': 8, - 'column': 25 - } - }, - 'lexeme': 'string', - 'index': 25 - }, - 'valueType': { - 'type': 'moduleModel', - 'path': [ - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 8, - 'column': 26 - }, - 'end': { - 'line': 8, - 'column': 29 - } - }, - 'lexeme': 'OSS', - 'index': 27, - 'idType': 'module' - }, - { - 'tag': 2, - 'loc': { - 'start': { - 'line': 8, - 'column': 30 - }, - 'end': { - 'line': 8, - 'column': 36 - } - }, - 'lexeme': 'Config', - 'index': 29 - } - ], - 'loc': { - 'start': { - 'line': 8, - 'column': 26 - }, - 'end': { - 'line': 8, - 'column': 36 - } - } - } - }); - }); - - it('no return should not ok', function () { - expect(function() { - parse(` - static function main(): string { - }`, '__filename'); - }).to.throwException(function(ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - }); - - it('no return when void should ok', function () { - expect(function() { - parse(` - static function main(): void { - }`, '__filename'); - }).to.not.throwException(); - }); - - it('return string should ok', function () { - expect(function() { - parse(` - static function main(): string { - return ''; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('no return but throw error should ok', function () { - expect(function () { - parse(` - static function main(): string { - throw { - message = 'error' - } - }`, '__filename'); - }).to.not.throwException(); - }); - - it('return string should ok', function () { - expect(function() { - parse(` - static function main(): string { - if (true) { - return ''; - } else { - return ''; - } - }`, '__filename'); - }).to.not.throwException(); - - expect(function() { - parse(` - static function main(): string { - if (true) { - } - }`, '__filename'); - }).to.throwException(function(ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - - expect(function() { - parse(` - static function main(): string { - if (true) { - return ''; - } - }`, '__filename'); - }).to.throwException(function(ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - - expect(function() { - parse(` - static function main(): string { - if (true) { - return ''; - } else { - - } - }`, '__filename'); - }).to.throwException(function(ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - - expect(function() { - parse(` - static function main(): string { - if (true) { - return ''; - } else if (true) { - - } - }`, '__filename'); - }).to.throwException(function(ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - - expect(function() { - parse(` - static function main(): string { - if (true) { - return ''; - } else if (true) { - return ''; - } - }`, '__filename'); - }).to.throwException(function(ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - - expect(function() { - parse(` - static function main(): string { - ''; - }`, '__filename'); - }).to.throwException(function(ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - - expect(function () { - parse(` - static function main(): string { - try { - return ''; - } finally { - ''; - } - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function main(): string { - try { - ''; - } finally { - return ''; - } - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function main(): string { - try { - return ''; - } catch(err) { - return ''; - } - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function main(): string { - try { - return ''; - } catch(err) { - return ''; - } finally { - ''; - } - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function main(): string { - try { - ''; - } catch(err) { - ''; - } finally { - return ''; - } - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - static function main(): string { - try { - ''; - } catch(err) { - return ''; - } finally { - ''; - } - }`, '__filename'); - }).to.throwException(function (ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - - expect(function () { - parse(` - static function main(): string { - try { - return ''; - } catch(err) { - ''; - } finally { - ''; - } - }`, '__filename'); - }).to.throwException(function (ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - }); - - it('no return stmt in api should not ok', function () { - expect(function() { - parse(` - api hello(): string { - return ''; - } returns { - - }`, '__filename'); - }).to.throwException(function(ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('no return statement'); - }); - }); - - it('assign map[string]model to map[string]any should ok', function () { - expect(function () { - parse(` - model M{} - static function main(): void { - var m = new M{}; - var a: map[string]any = { - m = m - }; - }`, '__filename'); - }).to.not.throwException(); - }); - - it('runtime block\'s env local should be separated with apiBody & returnBody', function () { - expect(function () { - parse(` - init() {} - - api hello(): object { - __request.method = 'GET'; - __request.pathname = '/'; - __request.headers = { - host = 'www.test.com', - }; - var retry = false; - } returns { - if(retry){ - return __request.headers; - } - return {}; - } runtime { - - }`, '__filename'); - }).to.not.throwException(); - - expect(function () { - parse(` - init() {} - - api hello(): object { - __request.method = 'GET'; - __request.pathname = '/'; - __request.headers = { - host = 'www.test.com', - }; - var retry = false; - } returns { - if(retry){ - return __request.headers; - } - return {}; - } runtime { - retry = retry - }`, '__filename'); - }).to.throwException(function (ex) { - expect(ex).be.a(SyntaxError); - expect(ex.message).to.be('variable "retry" undefined'); - }); - - expect(function () { - parse(` - init() {} - - api hello(retry: boolean): object { - __request.method = 'GET'; - __request.pathname = '/'; - __request.headers = { - host = 'www.test.com', - }; - var test = retry; - } returns { - if(test){ - return __request.headers; - } - return {}; - } runtime { - retry = retry - }`, '__filename'); - }).to.not.throwException(); - }); -}); diff --git a/test/tokens.test.js b/test/tokens.test.js new file mode 100644 index 0000000..a995862 --- /dev/null +++ b/test/tokens.test.js @@ -0,0 +1,56 @@ +'use strict'; + +const assert = require('assert'); + +const { + Token, StringLiteral, NumberLiteral, Annotation, Comment, + TemplateElement, WordToken, LogicalToken, OperatorToken +} = require('../lib/tokens'); + +describe('tokens', function () { + it('Token should ok', function () { + const t = new Token('s'); + assert.deepStrictEqual(t.toString(), 's'); + }); + + it('StringLiteral should ok', function () { + const t = new StringLiteral('s'); + assert.deepStrictEqual(t.toString(), 'String: s'); + }); + + it('NumberLiteral should ok', function () { + const t = new NumberLiteral('s'); + assert.deepStrictEqual(t.toString(), 'Number: s'); + }); + + it('Annotation should ok', function () { + const t = new Annotation('s'); + assert.deepStrictEqual(t.toString(), 'Annotation: s'); + }); + + it('Comment should ok', function () { + const t = new Comment('s'); + assert.deepStrictEqual(t.toString(), 'Comment: s'); + }); + + it('TemplateElement should ok', function () { + const t = new TemplateElement('s'); + assert.deepStrictEqual(t.toString(), 'TemplateElement: `s`'); + }); + + it('WordToken should ok', function () { + const t = new WordToken(1, 's'); + assert.deepStrictEqual(t.toString(), 'Word: `s`'); + }); + + it('OperatorToken should ok', function () { + const t = new OperatorToken('<'); + assert.deepStrictEqual(t.toString(), 'Operator: `<`'); + }); + + it('LogicalToken should ok', function () { + const t = new LogicalToken('&&'); + assert.deepStrictEqual(t.toString(), 'Logical: `&&`'); + }); + +}); \ No newline at end of file diff --git a/test/util.test.js b/test/util.test.js index c0464c6..8470338 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -1,12 +1,17 @@ 'use strict'; -const expect = require('expect.js'); +const assert = require('assert'); const util = require('../lib/util'); describe('util', function () { it('isBasicType should ok', function () { - expect(util.isBasicType('Model')).to.be(false); - expect(util.isBasicType('$Request')).to.be(false); + assert.equal(util.isBasicType('Model'), false); + assert.equal(util.isBasicType('$Request'), false); + }); + + it('isInteger should ok', function () { + assert.equal(util.isInteger('integer'), true); + assert.equal(util.isInteger('string'), false); }); });