From 0031104e510fdecf5bab4ed0202cbe5f3747553b Mon Sep 17 00:00:00 2001 From: peze Date: Thu, 7 Dec 2023 17:34:14 +0800 Subject: [PATCH] add builtin module --- builtin/array.dara | 29 ++++ builtin/bytes.dara | 13 ++ builtin/crypto.dara | 41 +++++ builtin/date.dara | 33 ++++ builtin/entry.dara | 6 + builtin/env.dara | 18 ++ builtin/file.dara | 19 +++ builtin/form.dara | 19 +++ builtin/json.dara | 11 ++ builtin/logger.dara | 43 +++++ builtin/map.dara | 9 + builtin/number.dara | 23 +++ builtin/stream.dara | 28 ++++ builtin/string.dara | 42 +++++ builtin/url.dara | 27 +++ builtin/xml.dara | 14 ++ lib/builtin.js | 46 +++++ lib/lexer.js | 4 + lib/parser.js | 27 +++ lib/semantic.js | 179 ++++++++++++++++++-- lib/util.js | 4 +- test/fixtures/builtin_module/Darafile | 1 + test/fixtures/builtin_module/array.dara | 21 +++ test/fixtures/builtin_module/bytes.dara | 17 ++ test/fixtures/builtin_module/date.dara | 18 ++ test/fixtures/builtin_module/env.dara | 4 + test/fixtures/builtin_module/file.dara | 14 ++ test/fixtures/builtin_module/form.dara | 15 ++ test/fixtures/builtin_module/json.dara | 15 ++ test/fixtures/builtin_module/logger.dara | 7 + test/fixtures/builtin_module/map.dara | 24 +++ test/fixtures/builtin_module/number.dara | 36 ++++ test/fixtures/builtin_module/stream.dara | 12 ++ test/fixtures/builtin_module/string.dara | 41 +++++ test/fixtures/builtin_module/url.dara | 17 ++ test/fixtures/builtin_module/xml.dara | 15 ++ test/parser.test.js | 205 +++++++++++++++++++++++ test/semantic.test.js | 130 +++++++++++++- 38 files changed, 1207 insertions(+), 20 deletions(-) create mode 100644 builtin/array.dara create mode 100644 builtin/bytes.dara create mode 100644 builtin/crypto.dara create mode 100644 builtin/date.dara create mode 100644 builtin/entry.dara create mode 100644 builtin/env.dara create mode 100644 builtin/file.dara create mode 100644 builtin/form.dara create mode 100644 builtin/json.dara create mode 100644 builtin/logger.dara create mode 100644 builtin/map.dara create mode 100644 builtin/number.dara create mode 100644 builtin/stream.dara create mode 100644 builtin/string.dara create mode 100644 builtin/url.dara create mode 100644 builtin/xml.dara create mode 100644 test/fixtures/builtin_module/Darafile create mode 100644 test/fixtures/builtin_module/array.dara create mode 100644 test/fixtures/builtin_module/bytes.dara create mode 100644 test/fixtures/builtin_module/date.dara create mode 100644 test/fixtures/builtin_module/env.dara create mode 100644 test/fixtures/builtin_module/file.dara create mode 100644 test/fixtures/builtin_module/form.dara create mode 100644 test/fixtures/builtin_module/json.dara create mode 100644 test/fixtures/builtin_module/logger.dara create mode 100644 test/fixtures/builtin_module/map.dara create mode 100644 test/fixtures/builtin_module/number.dara create mode 100644 test/fixtures/builtin_module/stream.dara create mode 100644 test/fixtures/builtin_module/string.dara create mode 100644 test/fixtures/builtin_module/url.dara create mode 100644 test/fixtures/builtin_module/xml.dara diff --git a/builtin/array.dara b/builtin/array.dara new file mode 100644 index 0000000..b1a7ae8 --- /dev/null +++ b/builtin/array.dara @@ -0,0 +1,29 @@ +init(data: [any]){} + +function contains(data: any): boolean; + +function index(data: any): integer; + +function length(): integer; + +function get(index: integer): $type; + +function join(sep: string): string; + +function full(data: any): [ $type ]; + +function shift(): $type; + +function pop(): $type; + +function push(data: any): integer; + +function unshift(data: any): integer; + +function concat(data: any): [ $type ]; + +function sort(order: string): [ $type ]; + +function append(data: any, index: integer): void; + +function remove(data: any): void; diff --git a/builtin/bytes.dara b/builtin/bytes.dara new file mode 100644 index 0000000..2d8efe2 --- /dev/null +++ b/builtin/bytes.dara @@ -0,0 +1,13 @@ +init(data: bytes){} + +function toString(): string; + +function toHex(): string; + +function toBase64(): string; + +function length(): integer; + +function toJSON(): string; + +static function from(data: string, type: string): bytes; \ No newline at end of file diff --git a/builtin/crypto.dara b/builtin/crypto.dara new file mode 100644 index 0000000..a1b68c8 --- /dev/null +++ b/builtin/crypto.dara @@ -0,0 +1,41 @@ +/** + * HmacSHA1 Signature + * @param stringToSign string + * @param secret string + * @return signed bytes + */ +static function HmacSHA1Sign(stringToSign: string, secret: string): bytes; + + +/** + * HmacSHA256 Signature + * @param stringToSign string + * @param secret string + * @return signed bytes + */ +static function HmacSHA256Sign(stringToSign: string, secret: string): bytes; + + +/** + * HmacSM3 Signature + * @param stringToSign string + * @param secret string + * @return signed bytes + */ +static function HmacSM3Sign(stringToSign: string, secret: string): bytes; + + +/** + * SHA256withRSA Signature + * @param stringToSign string + * @param secret string + * @return signed bytes + */ +static function SHA256withRSASign(stringToSign: string, secret: string): bytes; + +/** + * MD5 Signature + * @param stringToSign string + * @return signed bytes + */ +static function MD5Sign(stringToSign: string): bytes; \ No newline at end of file diff --git a/builtin/date.dara b/builtin/date.dara new file mode 100644 index 0000000..3739935 --- /dev/null +++ b/builtin/date.dara @@ -0,0 +1,33 @@ +init(date: string) {} + +function format(format: string): string; + +function unix(): integer; + +function diff(unit: string, date: $Date): integer; + +function UTC(): string; + +function add(unit: string, timeLong: integer): $Date; + +function sub(unit: string, timeLong: integer): $Date; + +function hour(): integer; + +function minute(): integer; + +function second(): integer; + +function dayOfYear(): integer; + +function dayOfMonth(): integer; + +function dayOfWeek(): integer; + +function weekOfYear(): integer; + +function weekOfMonth(): integer; + +function month(): integer; + +function year(): integer; \ No newline at end of file diff --git a/builtin/entry.dara b/builtin/entry.dara new file mode 100644 index 0000000..efac31d --- /dev/null +++ b/builtin/entry.dara @@ -0,0 +1,6 @@ +init(key: string, value: any) {} + +function key(): string; + +function value(): $type; + diff --git a/builtin/env.dara b/builtin/env.dara new file mode 100644 index 0000000..b54219e --- /dev/null +++ b/builtin/env.dara @@ -0,0 +1,18 @@ +/** + * This is a env module + */ + +/** + * Get environment variable according to the key + * @param key the name of environment variable + * @return environment variable + */ +static function get(key: string): string; + +/** + * Get environment variable according to the key + * @param key the name of environment variable + * @param value the value of environment variable + * @return void + */ +static function set(key: string, value: string) throws: void; \ No newline at end of file diff --git a/builtin/file.dara b/builtin/file.dara new file mode 100644 index 0000000..6a9693f --- /dev/null +++ b/builtin/file.dara @@ -0,0 +1,19 @@ +init(path: string) {} + +function path(): string; + +function length(): integer; + +function createTime(): $Date; + +function modifyTime(): $Date; + +function read(size: number): bytes; + +function write(data: bytes): void; + +static async function createReadStream(path: string): readable; + +static async function createWriteStream(path: string): writable; + +static async function exists(path: string): boolean; \ No newline at end of file diff --git a/builtin/form.dara b/builtin/form.dara new file mode 100644 index 0000000..53126e3 --- /dev/null +++ b/builtin/form.dara @@ -0,0 +1,19 @@ +/** + * Format a map to form string, like a=a%20b%20c + * @return the form string + */ +static function toFormString(val: object): string; + +/** + * Gets a boundary string + * @return the random boundary string + */ +static function getBoundary(): string; + +/** + * Give a form and boundary string, wrap it to a readable stream + * @param form The form map + * @param boundary the boundary string + * @return the readable from file form + */ +static function toFileForm(form: object, boundary: string): readable; \ No newline at end of file diff --git a/builtin/json.dara b/builtin/json.dara new file mode 100644 index 0000000..919ef2c --- /dev/null +++ b/builtin/json.dara @@ -0,0 +1,11 @@ +/** + * Parse it by JSON format + * @return the parsed result + */ +static function parseJSON(val: string): any; + +/** + * Stringify a value by JSON format + * @return the JSON format string + */ +static function stringify(val: any): string; \ No newline at end of file diff --git a/builtin/logger.dara b/builtin/logger.dara new file mode 100644 index 0000000..52220b1 --- /dev/null +++ b/builtin/logger.dara @@ -0,0 +1,43 @@ +/** + * This is a console module + */ + +/** + * Console val with log level into stdout + * @param val the printing string + * @return void + * @example \[LOG\] tea console example + */ +static function log(val: string): void; + +/** + * Console val with info level into stdout + * @param val the printing string + * @return void + * @example \[INFO\] tea console example + */ +static function info(val: string): void; + +/** + * Console val with warning level into stdout + * @param val the printing string + * @return void + * @example \[WARNING\] tea console example + */ +static function warning(val: string): void; + +/** + * Console val with debug level into stdout + * @param val the printing string + * @return void + * @example \[DEBUG\] tea console example + */ +static function debug(val: string): void; + +/** + * Console val with error level into stderr + * @param val the printing string + * @return void + * @example \[ERROR\] tea console example + */ +static function error(val: string): void; diff --git a/builtin/map.dara b/builtin/map.dara new file mode 100644 index 0000000..4259fb8 --- /dev/null +++ b/builtin/map.dara @@ -0,0 +1,9 @@ +init(data: any){} + +function length(): integer; + +function keySet(): [ string ]; + +function entries(): [ entry[ $type ] ]; + +function toJSON(): string; \ No newline at end of file diff --git a/builtin/number.dara b/builtin/number.dara new file mode 100644 index 0000000..6dda24d --- /dev/null +++ b/builtin/number.dara @@ -0,0 +1,23 @@ +init(data: number){} + +function parseInt(): integer; + +function parseLong(): long; + +function parseFloat(): float; + +function parseDouble(): double; + +function itol(): long; + +function ltoi(): integer; + +static function random(): number; + +static function floor(num: number): integer; + +static function round(num: number): integer; + +static function min(a: number, b: number): number; + +static function max(a: number, b: number): number; \ No newline at end of file diff --git a/builtin/stream.dara b/builtin/stream.dara new file mode 100644 index 0000000..fee3b7f --- /dev/null +++ b/builtin/stream.dara @@ -0,0 +1,28 @@ +init(){} + +function read(size: number): bytes; + +function write(data: bytes): void; + +function pipe(rs: writable): void; + +/** + * Read data from a readable stream, and compose it to a bytes + * @param stream the readable stream + * @return the bytes result + */ +static async function readAsBytes(stream: readable): bytes; + +/** + * Read data from a readable stream, and parse it by JSON format + * @param stream the readable stream + * @return the parsed result + */ +static async function readAsJSON(stream: readable): any ; + +/** + * Read data from a readable stream, and compose it to a string + * @param stream the readable stream + * @return the string result + */ +static async function readAsString(stream: readable): string; \ No newline at end of file diff --git a/builtin/string.dara b/builtin/string.dara new file mode 100644 index 0000000..16c6bd4 --- /dev/null +++ b/builtin/string.dara @@ -0,0 +1,42 @@ +init(data: string){} + +function split(sep: string, limit: number): [ string ]; + +function replace(oldStr: string, newStr: string, count: integer): string; + +function contains(substr: string): boolean; + +function length(): integer; + +function hasPrefix(prefix: string): boolean; + +function hasSuffix(substr: string): boolean; + +function index(substr: string): integer; + +function toLower(): string; + +function toUpper(): string; + +function subString(start: integer, end: integer): string; + +function equals(actual: string): boolean; + +function trim(): string; + +/** + * @param encoding string, eg: UTF-8 + * @return result bytes + */ + +function toBytes(encoding: string): bytes; + +function empty(): boolean; + +function parseInt(): integer; + +function parseLong(): long; + +function parseFloat(): float; + +function parseDouble(): double; \ No newline at end of file diff --git a/builtin/url.dara b/builtin/url.dara new file mode 100644 index 0000000..834a8d4 --- /dev/null +++ b/builtin/url.dara @@ -0,0 +1,27 @@ +init(url: string) {} + +function format(type: string): string; + +function path(): string; + +function pathname(): string; + +function protocol(): string; + +function hostname(): string; + +function hash(): string; + +function search(): string; + +function href(): string; + +function auth(): string; + +static function parse(url: string): $URL; + +static function urlEncode(url: string): string; + +static function percentEncode(raw: string): string; + +static function pathEncode(path: string): string; \ No newline at end of file diff --git a/builtin/xml.dara b/builtin/xml.dara new file mode 100644 index 0000000..c61d2c5 --- /dev/null +++ b/builtin/xml.dara @@ -0,0 +1,14 @@ +/** + * Parse body into the response, and put the resposne into a object + * @param body source content + * @param response target model + * @return the final object + */ +static function parseXml(body: string, response: class): object; + +/** + * Parse body as a xml string + * @param body source body + * @return the xml string + */ +static function toXML(body: object): string; \ No newline at end of file diff --git a/lib/builtin.js b/lib/builtin.js index 44a22e3..309fe85 100644 --- a/lib/builtin.js +++ b/lib/builtin.js @@ -1,6 +1,19 @@ 'use strict'; const { Tag } = require('./tag'); +const Lexer = require('./lexer'); +const Parser = require('./parser'); +const fs = require('fs'); +const path = require('path'); + +function _module(name) { + const filePath = path.join(__dirname, '../builtin', `${name}.dara`); + const source = fs.readFileSync(filePath, 'utf-8'); + const lexer = new Lexer(source, filePath); + const parser = new Parser(lexer); + const ast = parser.program(); + return ast; +} function _model(name, fields) { return { @@ -81,4 +94,37 @@ builtin.set('$Error', _model('$Error', [ _field('stack', 'string') ])); +builtin.set('$URL', _module('url')); + +builtin.set('$File', _module('file')); + +builtin.set('$JSON', _module('json')); + +builtin.set('$Form', _module('form')); + +builtin.set('$Logger', _module('logger')); + +builtin.set('$XML', _module('xml')); + +builtin.set('$Env', _module('env')); + +// TODO +//builtin.set('Crypto', _module('crypto')); + +builtin.set('$Stream', _module('stream')); + +builtin.set('$Date', _module('date')); + +builtin.set('$String', _module('string')); + +builtin.set('$Array', _module('array')); + +builtin.set('$Bytes', _module('bytes')); + +builtin.set('$Entry', _module('entry')); + +builtin.set('$Map', _module('map')); + +builtin.set('$Number', _module('number')); + module.exports = builtin; diff --git a/lib/lexer.js b/lib/lexer.js index 3e4e4f0..4327cf8 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -55,6 +55,7 @@ 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('entry', Tag.TYPE)); this.reserve(new Keyword('object', Tag.TYPE)); this.reserve(new Keyword('writable', Tag.TYPE)); this.reserve(new Keyword('readable', Tag.TYPE)); @@ -86,6 +87,9 @@ class Lexer extends BaseLexer { this.reserve(new Keyword('catch', Tag.CATCH)); this.reserve(new Keyword('finally', Tag.FINALLY)); + // add $type to assign instance + this.reserve(new Keyword('$type', Tag.TYPE)); + // the state for template string this.inTemplate = false; } diff --git a/lib/parser.js b/lib/parser.js index 04672a1..b0d3815 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -231,6 +231,22 @@ class Parser extends BaseParser { }; } + if (this.isWord(Tag.TYPE, 'entry')) { + let t = this.look; + this.move(); + this.match('['); + const valueType = this.baseType(); + this.match(']'); + return { + loc: { + start: t.loc.start, + end: valueType.loc.end + }, + type: 'entry', + valueType: valueType + }; + } + if (this.isWord(Tag.TYPE, 'iterator') || this.isWord(Tag.TYPE, 'asyncIterator')) { let t = this.look; this.move(); @@ -754,6 +770,17 @@ class Parser extends BaseParser { }; } + if (type.lexeme === 'entry') { + this.match('['); + const valueType = this.baseType(); + this.match(']'); + return { + type: 'fieldType', + fieldType: type.lexeme, + valueType: valueType + }; + } + if (type.lexeme === 'iterator' || type.lexeme === 'asyncIterator') { this.match('['); const valueType = this.baseType(); diff --git a/lib/semantic.js b/lib/semantic.js index 5ad386f..efa7ec8 100644 --- a/lib/semantic.js +++ b/lib/semantic.js @@ -39,6 +39,10 @@ function display(item) { return `map[${display(item.keyType)}]${display(item.valueType)}`; } + if (item.type === 'entry') { + return `entry[${display(item.valueType)}]`; + } + if (item.type === 'array') { return `[${display(item.itemType)}]`; } @@ -145,6 +149,10 @@ function isSameNumber(expect, actual){ } function isSameType(expect, actual) { + if(actual.type === 'basic' && actual.name === '$type') { + return true; + } + if (expect.type === 'basic' && actual.type === 'basic') { return expect.name === actual.name; } @@ -186,6 +194,7 @@ function isSameType(expect, actual) { } function isAssignable(expected, actual, expr) { + if (isSameType(expected, actual)) { return true; } @@ -209,6 +218,16 @@ function isAssignable(expected, actual, expr) { } } + if (expected.type === 'entry' && actual.type === 'entry') { + 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) { @@ -261,7 +280,6 @@ function eql(expects, actuals) { for (var i = 0; i < expects.length; i++) { const expect = expects[i]; const actual = actuals[i]; - if (isSameType(expect, actual)) { continue; } @@ -330,6 +348,16 @@ function eql(expects, actuals) { } } } + + if (type === 'entry') { + if (expect.valueType.name === 'any') { + // entry[any] vs entry[string] + continue; + } + if (isAssignable(expect.valueType, actual.valueType)) { + continue; + } + } if (type === 'array') { if (expect.itemType.name === 'any') { @@ -460,7 +488,6 @@ class TypeChecker { extendModel = checker.models.get(typeName); } else if (model.extendOn.idType === 'builtin_model') { extendModel = builtin.get(model.extendOn.lexeme); - } else { extendModel = this.models.get(model.extendOn.lexeme); } @@ -1188,6 +1215,16 @@ class TypeChecker { }; } else if (type.tag === Tag.TYPE) { return _basic(type.lexeme); + } else if (type.tag === Tag.ID && builtin.has(type.lexeme)) { + const ast = builtin.get(type.lexeme); + if(ast.type === 'model') { + return _model(type.lexeme); + } + return { + type: 'module_instance', + name: type.lexeme + }; + } else if (type.tag === Tag.ID && type.lexeme.startsWith('$')) { return _model(type.lexeme); } else if (type.tag === Tag.ID && type.idType === 'model') { @@ -1268,7 +1305,7 @@ class TypeChecker { assert.equal(ast.left.type, 'static_call'); const moduleId = ast.left.id; const moduleName = moduleId.lexeme; - const checker = this.dependencies.get(moduleName); + const checker = this.dependencies.get(moduleName) || builtin.get(moduleName); const method = ast.left.propertyPath[0]; const methodName = method.lexeme; @@ -1317,7 +1354,13 @@ class TypeChecker { } else if (this.dependencies.has(id)) { return 'module'; } else if (builtin.has(id)) { - return 'builtin_model'; + const ast = builtin.get(id); + if(ast.type === 'model') { + return 'builtin_model'; + } else if(ast.type === '$type') { + return '$type'; + } + return 'builtin_module'; } else if (this.enums.has(id)) { return 'enum'; } else if (this.typedefs.has(id)) { @@ -1345,6 +1388,12 @@ class TypeChecker { return; } + if (builtin.has(name)) { + // alias + id.type = 'builtin_module'; + return; + } + if (this.dependencies.has(name)) { // alias id.type = 'module'; @@ -1360,6 +1409,10 @@ class TypeChecker { } getChecker(moduleName) { + if(builtin.has(moduleName)) { + return builtin.get(moduleName); + } + if (this.dependencies.has(moduleName)) { return this.dependencies.get(moduleName); } @@ -1468,6 +1521,25 @@ class TypeChecker { }; } + if (find.fieldValue.fieldType === 'entry') { + if (find.fieldValue.valueType.idType === 'model') { + return { + type: 'entry', + valueType: _model(find.fieldValue.valueType.lexeme, moduleName) + }; + } + if (find.fieldValue.valueType.type === 'array') { + return { + type: 'entry', + valueType: this.getType(find.fieldValue.valueType, moduleName) + }; + } + return { + type: 'entry', + valueType: _basic(find.fieldValue.valueType.lexeme) + }; + } + if (find.fieldValue.type === 'modelBody') { return _model([modelName, find.fieldName.lexeme].join('.'), moduleName); } @@ -1543,6 +1615,26 @@ class TypeChecker { return null; } + getBuiltinModule(moduleName) { + if(isNumber(moduleName)) { + return '$Number'; + } else if(moduleName === 'readable' || moduleName === 'writable') { + return '$Stream'; + } else if(moduleName === 'string') { + return '$String'; + } else if(moduleName === 'array') { + return '$Array'; + } else if(moduleName === 'bytes') { + return '$Bytes'; + } else if(moduleName === 'map') { + return '$Map'; + } else if(moduleName === 'entry') { + return '$Entry'; + } + + throw new Error('un-implemented'); + } + getInstanceMethod(name, checkerName) { let current = this; if (current.methods.has(name)) { @@ -1718,7 +1810,8 @@ class TypeChecker { const leftType = this.getExprType(ast.left, env); this.visitExpr(ast.right, env); const rightType = this.getExprType(ast.right, env); - if(ast.type !== 'eq' && (!(leftType.type === 'basic' && isNumber(leftType.name)) || + if(ast.type !== 'eq' && ast.type !== 'neq' && + (!(leftType.type === 'basic' && isNumber(leftType.name)) || !(rightType.type === 'basic' && isNumber(rightType.name)))) { this.error(`${ast.type} can only operate number type, ` + `but left: ${display(leftType)} and right: ${display(rightType)}`, ast); @@ -1912,7 +2005,7 @@ class TypeChecker { return; } - if (this.dependencies.has(id.lexeme)) { + if (this.dependencies.has(id.lexeme) || builtin.has(id.lexeme)) { ast.left.type = 'static_call'; this.visitStaticCall(ast, env); return; @@ -1941,6 +2034,11 @@ class TypeChecker { } else { const type = this.getVariableType(ast.left.id, env); moduleName = type.name; + if(isBasicType(type.type)) { + moduleName = this.getBuiltinModule(type.type); + } else if(type.type === 'basic' && isBasicType(type.name)) { + moduleName = this.getBuiltinModule(type.name); + } } const checker = this.getChecker(moduleName); @@ -1962,7 +2060,6 @@ class TypeChecker { } const expected = this.getParameterTypes(definedApi, moduleNameOfDef); - if (!eql(expected, actual)) { this.error(`the parameter` + ` types are mismatched. expected ` + @@ -1976,7 +2073,14 @@ class TypeChecker { arg.expectedType = expected[i]; } - ast.inferred = this.getType(definedApi.returnType, moduleName); + + + if(definedApi.returnType.tag === Tag.TYPE && definedApi.returnType.lexeme === '$type') { + ast.inferred = this.getInstanceType(ast.left.id, env); + } else { + ast.inferred = this.getType(definedApi.returnType, moduleName); + } + ast.isAsync = definedApi.isAsync; ast.isStatic = definedApi.isStatic; ast.hasThrow = definedApi.isAsync || definedApi.hasThrow; @@ -2041,11 +2145,11 @@ class TypeChecker { visitConstruct(ast, env) { const aliasId = ast.aliasId.lexeme; - if (!this.dependencies.has(aliasId)) { + if (!this.dependencies.has(aliasId) && !builtin.has(aliasId)) { this.error(`the module "${aliasId}" is not imported`, ast.aliasId); } - const checker = this.dependencies.get(aliasId); + const checker = this.dependencies.get(aliasId) || builtin.get(aliasId); if (!checker.init) { this.error(`the module "${aliasId}" don't has init`, ast.aliasId); } @@ -2376,6 +2480,26 @@ class TypeChecker { throw new Error('can not get type'); } + getInstanceType(id, env) { + const type = this.getVariableType(id, env); + + if(!isBasicType(type.type)) { + this.error('$type can only be used as basic type instacne call.', type); + } + if(type.type === 'array') { + return type.itemType; + } + + if(type.type === 'map') { + return type.valueType; + } + + if(type.type === 'entry') { + return type.valueType; + } + return type; + } + getType(t, extern) { if (t.tag === Tag.TYPE && t.lexeme === 'object') { return { @@ -2390,11 +2514,7 @@ class TypeChecker { } if (t.tag === Tag.ID) { - if (t.lexeme.startsWith('$')) { - return _model(t.lexeme); - } - - if (t.idType === 'module') { + if (t.idType === 'module' || t.idType === 'builtin_module') { return { type: 'module_instance', name: t.lexeme, @@ -2402,6 +2522,11 @@ class TypeChecker { }; } + + if (t.lexeme.startsWith('$')) { + return _model(t.lexeme); + } + if (extern) { this.usedExternModel.get(extern).add(t.lexeme); if (t.idType === 'typedef') { @@ -2455,6 +2580,20 @@ class TypeChecker { }; } + if (t.type === 'entry') { + if (t.valueType.type === 'subModel_or_moduleModel') { + this.checkType(t.valueType); + return { + type: 'entry', + valueType: t.valueType + }; + } + return { + type: 'entry', + valueType: this.getType(t.valueType) + }; + } + if (t.type === 'iterator' || t.type === 'asyncIterator') { if (t.valueType.type === 'subModel_or_moduleModel') { this.checkType(t.valueType); @@ -2838,6 +2977,16 @@ function getChecker(source, filePath) { } function analyze(source, filePath) { + const it = builtin.keys(); + let key = it.next(); + + while(!key.done) { + const ast = builtin.get(key.value); + if(ast.type === 'module') { + builtin.set(key.value, new TypeChecker(source, filePath).check(ast)); + } + key = it.next(); + } const checker = getChecker(source, filePath); return checker.ast; } diff --git a/lib/util.js b/lib/util.js index d5d838a..53b0018 100644 --- a/lib/util.js +++ b/lib/util.js @@ -8,8 +8,8 @@ exports.isBasicType = function (type){ 'int8', 'int16', 'int32', 'int64', 'long', 'ulong', 'uint8', 'uint16', 'uint32', 'uint64', 'float', 'double', - 'boolean', 'bytes', 'any', 'map', - 'object', 'writable', 'readable', + 'boolean', 'bytes', 'any', 'map', 'array', + 'entry', 'object', 'writable', 'readable', 'asyncIterator', 'iterator' ]; return basicType.indexOf(type) !== -1; diff --git a/test/fixtures/builtin_module/Darafile b/test/fixtures/builtin_module/Darafile new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/test/fixtures/builtin_module/Darafile @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/fixtures/builtin_module/array.dara b/test/fixtures/builtin_module/array.dara new file mode 100644 index 0000000..26eb05e --- /dev/null +++ b/test/fixtures/builtin_module/array.dara @@ -0,0 +1,21 @@ +static async function main(args: [string]): void { + if((args.length() > 0) && args.contains('cn-hanghzou')) { + var index = args.index('cn-hanghzou'); + var regionId: string = args.get(index); + var all = args.join(','); + var first = args.shift(); + var last = args.pop(); + var length1 = args.unshift(first); + var length2 = args.push(last); + var length3:integer = length1 + length2; + var longStr = 'long' + first + last; + var fullStr = args.join(','); + var newArr = ['test']; + var cArr: [ string ] = newArr.concat(args); + var acsArr = newArr.sort('acs'); + var descArr = newArr.sort('desc'); + var llArr: [string] = acsArr.concat(descArr); + llArr.append('test', 10); + llArr.remove('test'); + } +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/bytes.dara b/test/fixtures/builtin_module/bytes.dara new file mode 100644 index 0000000..da12651 --- /dev/null +++ b/test/fixtures/builtin_module/bytes.dara @@ -0,0 +1,17 @@ +static async function main(args: [string]): void { + var fullStr = args.join(','); + var data = fullStr.toBytes('utf8'); + var newFullStr = data.toString(); + if(fullStr != newFullStr) { + return; + } + var hexStr = data.toHex(); + + var base64Str = data.toBase64(); + + var length: integer = data.length(); + + var obj = data.toJSON(); + + var data2 = $Bytes.from(fullStr, 'base64'); +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/date.dara b/test/fixtures/builtin_module/date.dara new file mode 100644 index 0000000..b837f4b --- /dev/null +++ b/test/fixtures/builtin_module/date.dara @@ -0,0 +1,18 @@ +static async function main(args: [string]): void { + var date = new $Date('2023-09-12 17:47:31.916000 +0800 UTC'); + var dateStr = date.format('YYYY-MM-DD HH:mm:ss'); + var timestamp = date.unix(); + var yesterday = date.sub('day', 1); + var oneDay = date.diff('day', yesterday); + var tomorrow = date.add('day', 1); + var twoDay = tomorrow.diff('day', date) + oneDay; + var hour = date.hour(); + var minute = date.minute(); + var second = date.second(); + var dayOfMonth = date.dayOfMonth(); + var dayOfWeek = date.dayOfWeek(); + var weekOfMonth = date.weekOfMonth(); + var weekOfYear = date.weekOfYear(); + var month = date.month(); + var year = date.year(); +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/env.dara b/test/fixtures/builtin_module/env.dara new file mode 100644 index 0000000..b54d3a8 --- /dev/null +++ b/test/fixtures/builtin_module/env.dara @@ -0,0 +1,4 @@ +static async function main(args: [string]): void { + var es = $Env.get('TEST'); + var ma = $Env.set('TEST', es + 'test'); +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/file.dara b/test/fixtures/builtin_module/file.dara new file mode 100644 index 0000000..2050d88 --- /dev/null +++ b/test/fixtures/builtin_module/file.dara @@ -0,0 +1,14 @@ +static async function main(args: [string]): void { + if($File.exists('/tmp/test')) { + var file = new $File('/tmp/test'); + var path = file.path(); + var length = file.length() + 10; + var createTime = file.createTime(); + var modifyTime = file.modifyTime(); + var timeLong = modifyTime.diff('minute', createTime); + var data = file.read(300); + file.write($Bytes.from('test', 'utf8')); + var rs:readable = $File.createReadStream('/tmp/test'); + var ws:writable = $File.createWriteStream('/tmp/test'); + } +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/form.dara b/test/fixtures/builtin_module/form.dara new file mode 100644 index 0000000..3e8a814 --- /dev/null +++ b/test/fixtures/builtin_module/form.dara @@ -0,0 +1,15 @@ +static async function main(args: [string]): void { + var m = { + key1 = 'test1', + key2 = 'test2', + key3 = 3, + key4 = { + key5 = 123, + key6 = '321' + } + }; + var form = $Form.toFormString(m); + form = form + "&key7=23233&key8=" + $Form.getBoundary(); + + var r:readable = $Form.toFileForm(m, $Form.getBoundary()); +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/json.dara b/test/fixtures/builtin_module/json.dara new file mode 100644 index 0000000..924aa81 --- /dev/null +++ b/test/fixtures/builtin_module/json.dara @@ -0,0 +1,15 @@ +static async function main(args: [string]): void { + var m = { + key1 = 'test1', + key2 = 'test2', + key3 = 3, + key4 = { + key5 = 123, + key6 = '321' + } + }; + var ms = $JSON.stringify(m); + var ma = $JSON.parseJSON(ms); + var arrStr = '[1,2,3,4]'; + var arr = $JSON.parseJSON(arrStr); +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/logger.dara b/test/fixtures/builtin_module/logger.dara new file mode 100644 index 0000000..6b2b5f2 --- /dev/null +++ b/test/fixtures/builtin_module/logger.dara @@ -0,0 +1,7 @@ +static async function main(args: [string]): void { + $Logger.log("test"); + $Logger.info("test"); + $Logger.warning("test"); + $Logger.debug("test"); + $Logger.error("test"); +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/map.dara b/test/fixtures/builtin_module/map.dara new file mode 100644 index 0000000..31b502b --- /dev/null +++ b/test/fixtures/builtin_module/map.dara @@ -0,0 +1,24 @@ +static async function main(args: [string]): void { + var mapTest = { + key1 = 'value1', + key2 = 'value2', + key3 = 'value3', + }; + + var length = mapTest.length(); + var num = length + 3; + var keys = mapTest.keySet(); + var allKey = ''; + for(var key : keys) { + allKey = allKey + key; + } + var entries: [ entry[string] ] = mapTest.entries(); + var newKey = ''; + var newValue = ''; + for(var e : entries) { + newKey = newKey + e.key(); + newValue = newValue + e.value(); + } + + var json = mapTest.toJSON(); +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/number.dara b/test/fixtures/builtin_module/number.dara new file mode 100644 index 0000000..d9f2a77 --- /dev/null +++ b/test/fixtures/builtin_module/number.dara @@ -0,0 +1,36 @@ +static async function main(args: [string]): void { + var num = 3.2; + var inum: integer = num.parseInt(); + var lnum: long = num.parseLong(); + var fnum: float = num.parseFloat(); + var dnum: double = num.parseDouble(); + + inum = inum.parseInt(); + lnum = inum.parseLong(); + fnum = inum.parseFloat(); + dnum = inum.parseDouble(); + + inum = lnum.parseInt(); + lnum = lnum.parseLong(); + fnum = lnum.parseFloat(); + dnum = lnum.parseDouble(); + + inum = fnum.parseInt(); + lnum = fnum.parseLong(); + fnum = fnum.parseFloat(); + dnum = fnum.parseDouble(); + + inum = dnum.parseInt(); + lnum = dnum.parseLong(); + fnum = dnum.parseFloat(); + dnum = dnum.parseDouble(); + + lnum = inum.itol(); + inum = lnum.ltoi(); + + var randomNum = $Number.random(); + inum = $Number.floor(inum); + inum = $Number.round(inum); + var min = $Number.min(inum, fnum); + var max = $Number.max(inum, fnum); +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/stream.dara b/test/fixtures/builtin_module/stream.dara new file mode 100644 index 0000000..d24882b --- /dev/null +++ b/test/fixtures/builtin_module/stream.dara @@ -0,0 +1,12 @@ +static async function main(args: [string]): void { + if($File.exists('/tmp/test')) { + var rs:readable = $File.createReadStream('/tmp/test'); + var ws:writable = $File.createWriteStream('/tmp/test'); + var data = rs.read(30); + ws.write(data); + rs.pipe(ws); + data = $Stream.readAsBytes(rs); + var obj = $Stream.readAsJSON(rs); + var jsonStr = $Stream.readAsString(rs); + } +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/string.dara b/test/fixtures/builtin_module/string.dara new file mode 100644 index 0000000..de8ec83 --- /dev/null +++ b/test/fixtures/builtin_module/string.dara @@ -0,0 +1,41 @@ +static async function main(args: [string]): void { + var fullStr = args.join(','); + args = fullStr.split(',', 10); + + if((fullStr.length() > 0) && fullStr.contains('hangzhou')) { + var newStr1 = fullStr.replace('hangzhou', 'beijing', 1); + } + + if(fullStr.hasPrefix('cn')) { + var newStr2 = fullStr.replace('cn', 'zh', 1); + } + + if(fullStr.hasPrefix('beijing')) { + var newStr3 = fullStr.replace('beijing', 'chengdu', -1); + } + + var start = fullStr.index('beijing'); + + var end = start + 7; + + var region = fullStr.subString(start, end); + + var lowerRegion = region.toLower(); + var upperRegion = region.toUpper(); + + if(region.equals('beijing')) { + region = region + ' '; + region = region.trim(); + } + + var tb: bytes = fullStr.toBytes('utf8'); + var em = 'xxx'; + if(em.empty()) { + return; + } + var num = '32.0a'; + var inum = num.parseInt() + 3; + var lnum:long = num.parseLong(); + var fnum:float = num.parseFloat() + 1.0; + var dnum:double = num.parseDouble() + 1.0d; +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/url.dara b/test/fixtures/builtin_module/url.dara new file mode 100644 index 0000000..5d85fd2 --- /dev/null +++ b/test/fixtures/builtin_module/url.dara @@ -0,0 +1,17 @@ +static async function main(args: [string]): void { + var url = new $URL(args[0]); + var path = url.path(); + var pathname = url.pathname(); + var protocol = url.protocol(); + var hostname = url.hostname(); + var hash = url.hash(); + var search = url.search(); + var href = url.href(); + var auth = url.auth(); + var url2 = $URL.parse(args[1]); + path = url2.path(); + var newUrl = $URL.urlEncode(args[2]); + var newSearch = $URL.percentEncode(search); + var newPath = $URL.pathEncode(pathname); + var all = 'test' + path + protocol + hostname + hash + search + href + auth + newUrl + newSearch + newPath; +} \ No newline at end of file diff --git a/test/fixtures/builtin_module/xml.dara b/test/fixtures/builtin_module/xml.dara new file mode 100644 index 0000000..42e7fa6 --- /dev/null +++ b/test/fixtures/builtin_module/xml.dara @@ -0,0 +1,15 @@ +static async function main(args: [string]): void { + var m = { + key1 = 'test1', + key2 = 'test2', + key3 = 3, + key4 = { + key5 = 123, + key6 = '321' + } + }; + var xml = $XML.toXML(m); + xml = xml + "132"; + + var respMap : map[string]any = $XML.parseXml(xml, null); +} \ No newline at end of file diff --git a/test/parser.test.js b/test/parser.test.js index b590953..9305e7b 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -3313,6 +3313,178 @@ describe('parser', function () { }); }); + it('entry[[string]] should ok', function () { + var ast = parse(` + model mymodel = { + key: entry[[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': 'entry', + 'type': 'fieldType', + 'valueType': { + 'subType': { + 'index': 10, + 'lexeme': 'string', + 'loc': loc(3, 21, 3, 27), + 'tag': 8 + }, + 'loc': loc(3, 20, 3, 27), + 'type': 'array', + }, + }, + 'required': true, + 'tokenRange': [5, 13], + 'type': 'modelField' + }); + }); + + it('entry[moduleModel] should ok', function(){ + var ast = parse(` + import oss; + + model A { + B: entry[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': 18 + }, + 'end': { + 'line': 5, + 'column': 21 + } + }, + 'lexeme': 'oss', + 'index': 11 + }, + { + 'tag': 2, + 'loc': { + 'start': { + 'line': 5, + 'column': 22 + }, + 'end': { + 'line': 5, + 'column': 23 + } + }, + 'lexeme': 'C', + 'index': 13 + } + ], + 'loc': { + 'start': { + 'line': 5, + 'column': 18 + }, + 'end': { + 'line': 5, + 'column': 23 + } + } + }); + + ast = parse(` + import oss; + + init(){ + var test: entry[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': 30 + } + }, + 'type': 'entry', + 'valueType': { + 'type': 'subModel_or_moduleModel', + 'path': [ + { + 'tag': 2, + 'loc': { + 'start': { + 'line': 5, + 'column': 25 + }, + 'end': { + 'line': 5, + 'column': 28 + } + }, + 'lexeme': 'oss', + 'index': 13 + }, + { + 'tag': 2, + 'loc': { + 'start': { + 'line': 5, + 'column': 29 + }, + 'end': { + 'line': 5, + 'column': 30 + } + }, + 'lexeme': 'C', + 'index': 15 + } + ], + 'loc': { + 'start': { + 'line': 5, + 'column': 25 + }, + 'end': { + 'line': 5, + 'column': 30 + } + } + } + }); + }); + it('iterator[string] should ok', function () { var ast = parse(` model mymodel = { @@ -8862,6 +9034,39 @@ describe('parser', function () { 'type': 'exceptionBody' }); + expect(exceptionField(`name: [ entry[string] ],`)).to.be.eql({ + 'nodes': [ + { + 'attrs': [], + 'fieldName': { + 'index': 5, + 'lexeme': 'name', + 'tag': 2, + loc: loc(3, 11, 3, 15) + }, + 'fieldValue': { + 'fieldItemType': { + 'type': 'entry', + 'valueType': { + 'index': 10, + 'lexeme': 'string', + 'loc': loc(3, 25, 3, 31), + 'tag': 8 + }, + loc: loc(3, 19, 3, 31) + }, + 'fieldType': 'array', + 'type': 'fieldType' + }, + 'tokenRange': [5, 13], + 'required': true, + 'type': 'exceptionField' + } + ], + 'tokenRange': [4, 14], + 'type': 'exceptionBody' + }); + expect(exceptionField(`name?: string, name2: string`)).to.be.eql({ 'nodes': [ { diff --git a/test/semantic.test.js b/test/semantic.test.js index 570416c..571977f 100644 --- a/test/semantic.test.js +++ b/test/semantic.test.js @@ -1134,6 +1134,13 @@ describe('semantic', function () { }`, '__filename'); }).to.not.throwException(); + expect(function () { + parse(` + static function callOSS(): void { + var entryVal: entry[string] = null; + }`, '__filename'); + }).to.not.throwException(); + expect(function () { parse(` static function callOSS(): void { @@ -2312,6 +2319,18 @@ describe('semantic', function () { }`, '__filename'); }).to.not.throwException(); + expect(function () { + parse(`static function callOSS(): entry[string] { + var m = { + key = 'value', + }; + + var entries: [ entry[string] ] = m.entries(); + + return entries[0]; + }`, '__filename'); + }).to.not.throwException(); + expect(function () { parse(`static function callOSS(): map[string]string { var m = { @@ -2398,6 +2417,23 @@ describe('semantic', function () { }`, '__filename'); }).to.not.throwException(); + expect(function () { + parse(` + model M = {}; + model M2 = { + en: entry[M] + }; + static function callOSS(): entry[any] { + var m = {key = new M}; + var entries = m.entries(); + var m2 = { + key = new M2{ en = entries[0] } + }; + entries = m2.entries(); + return entries[0]; + }`, '__filename'); + }).to.not.throwException(); + expect(function () { parse(` static function callOSS(): map[string]string { @@ -2818,17 +2854,21 @@ describe('semantic', function () { status: number, message: string, code: string, - data: map[string]string + data: map[string]string, + en: entry[string] }; init() {} api hello(): void { + var m = { key = 'test'}; + var entries = m.entries(); throw new Error{ status = 200, message = 'error message', code = 'Error', data = { test = 'test', - } + }, + en = entries[0] }; } returns { @@ -6887,4 +6927,88 @@ describe('semantic', function () { }).to.not.throwException(); }); -}); + + it('use array builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/array.dara'); + }).to.not.throwException(); + }); + + it('use string builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/string.dara'); + }).to.not.throwException(); + }); + + it('use bytes builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/bytes.dara'); + }).to.not.throwException(); + }); + + it('use number builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/number.dara'); + }).to.not.throwException(); + }); + + it('use map builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/map.dara'); + }).to.not.throwException(); + }); + + it('use url builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/url.dara'); + }).to.not.throwException(); + }); + + it('use xml builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/xml.dara'); + }).to.not.throwException(); + }); + + it('use form builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/form.dara'); + }).to.not.throwException(); + }); + + it('use json builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/json.dara'); + }).to.not.throwException(); + }); + + it('use env builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/env.dara'); + }).to.not.throwException(); + }); + + it('use date builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/date.dara'); + }).to.not.throwException(); + }); + + it('use file builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/file.dara'); + }).to.not.throwException(); + }); + + it('use stream builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/stream.dara'); + }).to.not.throwException(); + }); + + it('use loggers builtin module shoule be ok', function(){ + expect(function () { + readAndParse('fixtures/builtin_module/logger.dara'); + }).to.not.throwException(); + }); +}); \ No newline at end of file