import { RepresentationTypeIdentifier } from '../Data/RepresentationType.js'; import TemplateType from '../Resource/Template/TemplateType.js'; import Warehouse from '../Resource/Warehouse.js'; export default class TemplateGenerator { static toLiteral(input) { if (input == null) return "null"; let literal = ""; literal += "\""; input.split('').forEach((c) => { switch (c) { case '"': literal += "\\\""; break; case '\\': literal += "\\\\"; break; case '\0': literal += "\\0"; break; case '\b': literal += "\\b"; break; case '\f': literal += "\\f"; break; case '\n': literal += "\\n"; break; case '\r': literal += "\\r"; break; case '\t': literal += "\\t"; break; case '\v': literal += "\\v"; break; default: literal += c; break; } }); literal += "\""; return literal; } static generateRecord(template, templates) { let className = template.className.split('.').slice(-1)[0]; let rt = ""; let parentName; let dependencies = []; if (template.parentId != null) { let parentClassName = templates .find((x) => (x.classId.valueOf() == template.parentId.valueOf()) && (x.type == TemplateType.Record)) .className; parentName = this._translateClassName(parentClassName); dependencies.push(parentClassName); rt += `export default class ${className} extends ${parentName} {\r\n`; } else { rt += `export default class ${className} extends Esiur.Data.IRecord { \r\n`; } template.properties.forEach((p) => { if (p.inherited) return; let ptTypeName = this.getDecoratedTypeName(template, p.valueType, templates); rt += `${ptTypeName} ${p.name};\r\n\r\n`; }); rt += "\r\n"; // add template var descProps = template.properties .map((p) => { var ptTypeName = this.getTypeName(template, p.valueType, templates, dependencies); return `new Esiur.Resource.Template.Prop('${p.name}', ${ptTypeName}, ${this._escape(p.readAnnotation)}, ${this._escape(p.writeAnnotation)})`; }); let cls = template.className.split('.'); let namespace = cls.slice(0, cls.length - 1).join('.'); rt += `\r\nstatic get template() {return new Esiur.Resource.Template.TemplateDescriber('${namespace}', [\r\n${descProps.join(',\r\n')}], \r\n${parentName}, ${template.version}, ${this.toLiteral(template.annotation)}, Esiur.Data.Guid.parse('${template.classId.toString()}'), '${className}');\r\n}`; rt += "\r\n}"; rt += `\r\nnew Esiur.Resource.Template.TypeTemplate(${className}, true);\r\n` rt = this._getDependenciesImports(dependencies) + rt; return rt; } static _translateClassName(className) { var cls = className.split('.'); return cls.join('_'); } static getDecoratedTypeName(forTemplate, representationType, templates){ return `/* ${this.getTypeName(forTemplate, representationType, templates)} */`; } static getTypeName(forTemplate, representationType, templates, dependencies) { let name; if (representationType.identifier == RepresentationTypeIdentifier.TypedResource) { if (representationType.guid.valueOf() == forTemplate.classId.valueOf()) name = forTemplate.className.split('.').slice(-1)[0]; else { let className = templates .find((x) => x.classId.valueOf() == representationType.guid.valueOf() && (x.type == TemplateType.Resource)) .className; if (!dependencies?.includes(className)) dependencies?.push(className); name = this._translateClassName(className); } } else if (representationType.identifier == RepresentationTypeIdentifier.TypedRecord) { if (representationType.guid.valueOf() == forTemplate.classId.valueOf()) name = forTemplate.className.split('.').slice(-1)[0]; else { let className = templates .find((x) => x.classId.valueOf() == representationType.guid.valueOf() && x.type == TemplateType.Record) .className; if (!dependencies?.includes(className)) dependencies?.push(className); name = this._translateClassName(className); } } else if (representationType.identifier == RepresentationTypeIdentifier.Enum) { if (representationType.guid.valueOf() == forTemplate.classId.valueOf()) name = forTemplate.className.split('.').slice(-1)[0]; else { let className = templates .find((x) => x.classId.valueOf() == representationType.guid.valueOf() && x.type == TemplateType.Enum) .className; if (!dependencies?.includes(className)) dependencies?.push(className); name = this._translateClassName(className); } } else if (representationType.identifier == RepresentationTypeIdentifier.TypedList) name = "Esiur.Data.TypedList.of(" + this.getTypeName(forTemplate, representationType.subTypes[0], templates, dependencies) + ")"; else if (representationType.identifier == RepresentationTypeIdentifier.TypedMap) name = "Esiur.Data.TypedMap.of(" + this.getTypeName(forTemplate, representationType.subTypes[0], templates, dependencies) + "," + this.getTypeName(forTemplate, representationType.subTypes[1], templates, dependencies) + ")"; else if (representationType.identifier == RepresentationTypeIdentifier.Tuple2 || representationType.identifier == RepresentationTypeIdentifier.Tuple3 || representationType.identifier == RepresentationTypeIdentifier.Tuple4 || representationType.identifier == RepresentationTypeIdentifier.Tuple5 || representationType.identifier == RepresentationTypeIdentifier.Tuple6 || representationType.identifier == RepresentationTypeIdentifier.Tuple7) name = "Esiur.Data.Tuple.of(" + representationType.subTypes.map( x => this.getTypeName(forTemplate, x, templates, dependencies)).join(',') + ")"; else { switch (representationType.identifier) { case RepresentationTypeIdentifier.Dynamic: name = "Object"; break; case RepresentationTypeIdentifier.Bool: name = "Boolean"; break; case RepresentationTypeIdentifier.Char: name = "String"; break; case RepresentationTypeIdentifier.DateTime: name = "Date"; break; case RepresentationTypeIdentifier.Decimal: name = "Esiur.Data.Float128"; break; case RepresentationTypeIdentifier.Float32: name = "Esiur.Data.Float32"; break; case RepresentationTypeIdentifier.Float64: name = "Esiur.Data.Float64"; break; case RepresentationTypeIdentifier.Int16: name = "Esiur.Data.Int16"; break; case RepresentationTypeIdentifier.Int32: name = "Esiur.Data.Int32"; break; case RepresentationTypeIdentifier.Int64: name = "Esiur.Data.Int64"; break; case RepresentationTypeIdentifier.Int8: name = "Esiur.Data.Int8"; break; case RepresentationTypeIdentifier.String: name = "String"; break; case RepresentationTypeIdentifier.Map: name = "Map"; break; case RepresentationTypeIdentifier.UInt16: name = "Esiur.Data.UInt16"; break; case RepresentationTypeIdentifier.UInt32: name = "Esiur.Data.UInt32"; break; case RepresentationTypeIdentifier.UInt64: name = "Esiur.Data.UInt64"; break; case RepresentationTypeIdentifier.UInt8: name = "Esiur.Data.UInt8"; break; case RepresentationTypeIdentifier.List: name = "Esiur.Data.List"; break; case RepresentationTypeIdentifier.Resource: name = "Esiur.Resource.IResource"; break; case RepresentationTypeIdentifier.Record: name = "Esiur.Data.IRecord"; break; default: name = "Object"; } } return (representationType.nullable) ? `Esiur.Data.Nullable.of(${name})` : name; } static isNullOrEmpty(v) { return v == null || v == ""; } static async getTemplate(url, dir, username, password, asyncSetters = true) { const fs = await import("fs"); // var fs = require('fs'); const _urlRegex = /^(?:([^\s|:]*):\/\/([^/]*)\/?(.*))/; // /^(?:([^\s|:]*):\/\/([^/]*)\/?)/; if (!_urlRegex.test(url)) throw Error(`Invalid IIP URL '${url}'`); let path = url.split(_urlRegex); let con = await Warehouse.get(path[1] + "://" + path[2], username != null ? {"username": username, "password": password ?? ""} : null); if (con == null) throw Error("Can't connect to server"); if (dir == null || dir == "") dir = path[2].replaceAll(":", "_"); let templates = await con.getLinkTemplates(path[3]); // no longer needed Warehouse.remove(con); let dstDir = `lib/${dir}`; if (!fs.existsSync(dstDir)){ fs.mkdirSync(dstDir, { recursive: true }); } var makeImports = (skipTemplate) => { let imports = ""; // make import names templates.forEach((tmp) => { if (tmp != skipTemplate) { let cls = tmp.className.split('.'); imports += `import ${cls.join('_')} from './${tmp.className}.g.js';\r\n`; } }); imports += "\r\n\r\n"; return imports; }; // make sources templates.forEach(async (tmp) => { console.log(`Generating '${tmp.className}'.`); let filePath = `${dstDir}/${tmp.className}.g.js`; var source = ""; if (tmp.type == TemplateType.Resource) { source = this.generateClass(tmp, templates, asyncSetters); } else if (tmp.type == TemplateType.Record) { source = this.generateRecord(tmp, templates); } else if (tmp.type == TemplateType.Enum) { source = this.generateEnum(tmp, templates); } fs.writeFileSync(filePath, source); }); // make module let modulePath = `${dstDir}/init.g.js`; let module = makeImports() + `\r\nlet module = {}; \r\n`; templates.forEach((tmp) => { let typeName = tmp.className.split('.').join('_'); module += `Esiur.define(module, ${typeName}, '${tmp.className}');\r\n`; }); module += "\r\nexport default module;"; fs.writeFileSync(modulePath, module); return dstDir; } static _escape(str) { if (str == null) return "null"; else return `'${str}'`; } static generateEnum(template, templates) { let className = template.className.split('.').slice(-1)[0]; let rt = ""; let dependencies = []; rt += `export default class ${className} extends Esiur.Data.IEnum {\r\n`; template.constants.forEach((c) => { rt += `static ${c.name} = new ${className}(${c.index}, ${c.value}, '${c.name}');\r\n`; }); rt += "\r\n"; // add template var descConsts = template.constants.map((p) => { var ctTypeName = this.getTypeName(template, p.valueType, templates, dependencies); return `new Esiur.Resource.Template.Const('${p.name}', ${ctTypeName}, ${p.value}, ${this._escape(p.annotation)})`; }); let cls = template.className.split('.'); let namespace = cls.slice(0, cls.length - 1).join('.'); rt += `\r\nstatic get template() {return new Esiur.Resource.Template.TemplateDescriber('${namespace}', [\r\n${descConsts.join(',\r\n')}], \r\n null, ${template.version}, ${this.toLiteral(template.annotation)}, Esiur.Data.Guid.parse('${template.classId.toString()}'), '${className}');\r\n}`; rt += "\r\n}"; rt += `\r\nnew Esiur.Resource.Template.TypeTemplate(${className}, true);\r\n` rt = this._getDependenciesImports(dependencies) + rt; return rt; } static _getDependenciesImports(dependencies){ let rt = ""; dependencies.forEach(className => { rt += `import ${className.split('.').join('_')} from './${className}.g.js';\r\n`; }); return rt + "\r\n"; } static generateClass(template, templates, asyncSetters = true) { let className = template.className.split('.').slice(-1)[0]; let parentName = null; let rt = ""; let dependencies = []; if (template.parentId != null) { let parentClassName = templates .find((x) => (x.classId.valueOf() == template.parentId.valueOf()) && (x.type == TemplateType.Resource)) .className; parentName = this._translateClassName(parentClassName); dependencies.push(parentClassName); rt += `export default class ${className} extends ${parentName} {\r\n`; } else { rt += `export default class ${className} extends Esiur.Net.IIP.DistributedResource {\r\n`; } // rt += `constructor() {`; // template.events.filter((e) => !e.inherited).forEach((e) => { // rt += `on('${e.name}', (x) => _${e.name}Controller.add(x));`; // }); // rt += "}\r\n"; template.constants.forEach((c) => { var ctTypeName = this.getTypeName(template, c.valueType, templates, dependencies); rt += `static ${c.name} = new ${ctTypeName}(${c.value});\r\n`; }); template.functions.filter((f) => !f.inherited).forEach((f) => { var rtTypeName = this.getDecoratedTypeName(template, f.returnType, templates); var positionalArgs = f.args.filter((x) => !x.optional); var optionalArgs = f.args.filter((x) => x.optional); if (f.isStatic) { //rt += `static AsyncReply<${rtTypeName}> ${f.name}(DistributedConnection connection`; rt += `${rtTypeName} \r\n static ${f.name}(connection`; if (positionalArgs.length > 0) rt += `, ${positionalArgs.map((a) => this.getDecoratedTypeName(template, a.type, templates) + " " + a.name).join(',')}`; if (optionalArgs.length > 0) { rt += `, [${optionalArgs.map((a) => this.getDecoratedTypeName(template, a.type.toNullable(), templates) + " " + a.name).join(',')}]`; } } else { //rt += `AsyncReply<${rtTypeName}> ${f.name}(`; rt += `${rtTypeName} \r\n ${f.name}(`; if (positionalArgs.length > 0) rt += `${positionalArgs.map((a) => this.getDecoratedTypeName(template, a.type, templates) + " " + a.name).join(',')}`; if (optionalArgs.length > 0) { if (positionalArgs.length > 0) rt += ","; //rt += `[${optionalArgs.map((a) => this.getTypeName(template, a.type.toNullable(), templates) + " " + a.name).join(',')}]`; rt += `${optionalArgs.map((a) => this.getDecoratedTypeName(template, a.type.toNullable(), templates) + " " + a.name + " = null").join(',')}`; } } rt += ") {\r\n"; // var argsMap = new (TypedMap.of(UInt8, Object)); rt += "var args = new (Esiur.Data.TypedMap.of(Esiur.Data.UInt8, Object))();\r\n"; rt += `${positionalArgs.map((e) => `args.set(new Esiur.Data.UInt8(${e.index.toString()}), ${e.name});`).join('\r\n')}\r\n`; optionalArgs.forEach((a) => { rt += `if (${a.name} != null) args.set(new Esiur.Data.UInt8(${a.index}), ${a.name});\r\n`; }); //rt += `var rt = new AsyncReply<${rtTypeName}>();\r\n`; rt += `var rt = new Esiur.Core.AsyncReply();\r\n`; if (f.isStatic) { rt += `connection.staticCall(Guid.parse('${template.classId.toString()}'), ${f.index}, args)`; } else { rt += `this._invoke(${f.index}, args)`; } rt += `.then((x) => rt.trigger(x))\r\n`; rt += `.error((x) => rt.triggerError(x))\r\n`; rt += `.chunk((x) => rt.triggerChunk(x));\r\n`; rt += `return rt; \r\n}\r\n`; }); template.properties.filter((p) => !p.inherited).forEach((p) => { let ptTypeName = this.getDecoratedTypeName(template, p.valueType, templates); rt += `${ptTypeName} get ${p.name}() { return this._get(${p.index}); }\r\n`; if (asyncSetters) rt += `set ${p.name}(${ptTypeName} value) { this._set(${p.index}, value); }\r\n`; else rt += `set ${p.name}(${ptTypeName} value) { this._setSync(${p.index}, value); }\r\n`; }); // template.events.filter((e) => !e.inherited).forEach((e) => { // var etTypeName = this.getTypeName(template, e.argumentType, templates); // rt += `final _${e.name}Controller = StreamController<$etTypeName>();\r\n`; // rt += `Stream<${etTypeName}> get ${e.name} { \r\n`; // rt += `return _${e.name}Controller.stream;\r\n`; // rt += "}"; // }); // add template var descProps = template.properties //.where((p) => !p.inherited) .map((p) => { var ptTypeName = this.getTypeName(template, p.valueType, templates, dependencies); return `new Esiur.Resource.Template.Prop('${p.name}', ${ptTypeName}, ${this._escape(p.readAnnotation)}, ${this._escape(p.writeAnnotation)})`; }); var descFuncs = template.functions .map((f) => { var ftTypeName = this.getTypeName(template, f.returnType, templates, dependencies); var args = f.args.map((a) => { var atTypeName = this.getTypeName(template, a.type, templates, dependencies); return `new Esiur.Resource.Template.Arg('${a.name}', ${atTypeName}, ${a.optional})`; }).join(', '); return `new Esiur.Resource.Template.Func('${f.name}', ${ftTypeName}, [${args}], ${this._escape(f.annotation)})`; }); var descEvents = template.events //.where((e) => !e.inherited) @REVIEW .map((e) => { var etTypeName = this.getTypeName(template, e.argumentType, templates, dependencies); return `new Esiur.Resource.Template.Evt('${e.name}', ${etTypeName}, ${e.listenable}, ${this._escape(e.annotation)})`; }); var descConsts = template.constants.map((p) => { var ctTypeName = this.getTypeName(template, p.valueType, templates, dependencies); return `new Esiur.Resource.Template.Const('${p.name}', ${ctTypeName}, ${p.value}, ${this._escape(p.annotation)})`; }); let cls = template.className.split('.'); let namespace = cls.slice(0, cls.length - 1).join('.'); rt += `\r\nstatic get template() {return new Esiur.Resource.Template.TemplateDescriber('${namespace}', [\r\n${[...descProps, ...descFuncs, ...descEvents, ...descConsts].join(',\r\n')}], \r\n${parentName}, ${template.version}, ${this.toLiteral(template.annotation)}, Esiur.Data.Guid.parse('${template.classId.toString()}'), '${className}');\r\n}`; rt += "\r\n}\r\n"; rt += `\r\nnew Esiur.Resource.Template.TypeTemplate(${className}, true);\r\n` rt = this._getDependenciesImports(dependencies) + rt; return rt; } }