lib/generator.js (2,663 lines of code) (raw):
/* eslint-disable complexity */
'use strict';
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const DSL = require('@darabonba/parser');
const xml2js = require('xml2js');
const Entities = require('html-entities').XmlEntities;
const Annotation = require('@darabonba/annotation-parser');
var UUID = require('uuid');
const getBuiltin = require('./builtin');
const { Tag } = DSL.Tag;
// Node.js 16 及以后版本才支持 Object.hasOwn,因此在不支持的环境提供 monkey patch
if (typeof Object.hasOwn !== 'function') {
Object.hasOwn = function (target, key) {
return Object.prototype.hasOwnProperty.call(target, key);
};
}
const REQUEST = 'request_';
const RESPONSE = 'response_';
const RUNTIME = 'runtime_';
const attrList = { 'maxLength': 'value', 'pattern': 'string' };
const {
_name, _upperFirst, _string, _lowerFirst,
_avoidReserveName, remove, _vid, parse, render, _format, md2Html,
_escape, _subModelName, _upperPath, _nameSpace, builtinMap, _isBuiltinModel, exceptionFields
} = require('./helper');
function md2Xml(str) {
return _format(md2Html(str));
}
function getAttr(node, attrName) {
for (let i = 0; i < node.attrs.length; i++) {
if (_name(node.attrs[i].attrName) === attrName) {
return node.attrs[i].attrValue.string || node.attrs[i].attrValue.lexeme;
}
}
}
function _isBinaryOp(type) {
const op = [
'or', 'eq', 'neq',
'gt', 'gte', 'lt',
'lte', 'add', 'subtract',
'div', 'multi', 'and'
];
return op.includes(type);
}
class Visitor {
constructor(option = {}) {
this.config = Object.assign({
outputDir: '',
indent: ' ',
clientPath: `${option.className || 'Client'}.cs`
}, option);
assert.ok(this.config.outputDir, '`option.outputDir` should not empty');
assert.ok(this.config.namespace, `Darafile -> csharp -> namespace should not empty, please add csharp option into Darafile.
example:
"csharp": {
"namespace": "NameSpace",
"className": "Client"
}`);
this.used = [];
this.conflictModelNameMap = [];
this.typedef = this.config.typedef || {};
this.namespace = option.namespace;
this.className = option.className || 'Client';
this.output = '';
this.outputDir = this.config.outputDir + '/core';
this.csprojOutputDir = option.outputDir + '/core';
this.release = option.releases && option.releases.csharp || this.namespace + ':0.0.1';
this.config.packageInfo = this.config.packageInfo || {};
this.isExec = this.config.exec;
this.asyncOnly = this.config.asyncOnly;
this.editable = option.editable;
this.classNamespace = new Map();
this.releaseVersion = this.config.releaseVersion;
this.packageManager = this.config.packageManager;
this.packageVersion = this.config.packageInfo.version;
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, {
recursive: true
});
}
remove(path.join(this.outputDir, 'Models/'));
remove(path.join(this.outputDir, `I${this.className ? this.className : 'Client'}.cs`));
}
getAliasName(classNamespace, name, aliasId) {
let aliasName = '';
if (!this.clientName.has(name)) {
this.clientName.set(name, true);
return aliasName;
}
// 别名方式
if (aliasId) {
aliasName = aliasId + name;
}
// 全写方式
// aliasName = classNamespace + '.' + name;
if (aliasName && !this.clientName.has(aliasName)) {
this.clientName.set(aliasName, true);
return aliasName;
}
const arr = classNamespace.split('.');
for (let i = arr.length - 1; i >= 0; i--) {
aliasName = arr[i] + name;
if (!this.clientName.has(aliasName)) {
this.clientName.set(aliasName, true);
return aliasName;
}
}
}
getInnerClient(aliasId, csPath) {
const moduleAst = this.ast.innerDep.get(aliasId);
const beginNotes = DSL.note.getNotes(moduleAst.notes, 0, moduleAst.moduleBody.nodes[0].tokenRange[0]);
const clientNote = beginNotes.find(note => note.note.lexeme === '@clientName');
if (clientNote) {
return _string(clientNote.arg.value);
}
const fileInfo = path.parse(csPath);
return `${_upperFirst(fileInfo.name)}Client`;
}
getAttributes(ast, name) {
const attr = ast.attrs.find((item) => {
return item.attrName.lexeme === name;
});
if (!attr) {
return;
}
return attr.attrValue.string || attr.attrValue.lexeme || attr.attrValue.value;
}
getRealClientName(aliasId) {
const moduleInfo = this.moduleClass.get(aliasId);
if (!moduleInfo) {
return;
}
if (moduleInfo.aliasName) {
const allUsed = [...this.used, `${moduleInfo.namespace}.${moduleInfo.className}`];
const updateAliasName = (alias) => {
allUsed.forEach(used => {
const namespace = used.split('.');
if (namespace.includes(alias)) {
alias = '_' + alias;
}
});
return alias;
};
moduleInfo.aliasName = updateAliasName(moduleInfo.aliasName);
this.used.push(`${moduleInfo.aliasName} = ${moduleInfo.namespace}.${moduleInfo.className}`);
return moduleInfo.aliasName;
}
// 同一个命名空间不用using,如sdk.dara中导入的api
this.used.push(`${moduleInfo.namespace}`);
return moduleInfo.className;
}
getRealModelName(namespace, subModelName, type = 'Models') {
// fullModelName example: Darabonba.Source.Models.M Darabonba.Test.Model.Models.Info Darabonba.import.Models.Request.submodel
if (namespace !== 'Darabonba.Model') {
const subModelNameArr = subModelName.split('.');
subModelName = subModelNameArr.map((m, i) => {
return _avoidReserveName(m);
}).join('.');
}
if (type === 'Exceptions') {
subModelName += 'Exception';
}
// subModel 的处理:例如fullModelName为 Darabonba.import.Models.Request.submodel的情况,index === 0时将Request作为modelName传入
// namespace为 Darabonba.import.Models, modelName为 Request.RequestSubmodel
const existName = this.usedClass.get(subModelName);
if (existName && existName !== namespace) {
if (!namespace) {
return subModelName;
}
return `${namespace}.${subModelName}`;
} else if (this.conflictModelNameMap.includes(subModelName)) {
return `${namespace}.${subModelName}`;
}
if (namespace) {
this.used.push(`${(this.getType((namespace)))}`);
this.usedClass.set(subModelName, namespace);
}
return this._type(subModelName);
}
_type(name) {
if (name === 'integer' || name === 'number' || name === 'int32') {
return 'int?';
}
if (name === 'uint16') {
return 'ushort?';
}
if (name === 'int16') {
return 'short?';
}
if (name === 'int8') {
return 'sbyte?';
}
if (name === 'uint8') {
return 'byte?';
}
if (name === 'uint32') {
return 'uint?';
}
if (name === 'int64' || name === 'long') {
return 'long?';
}
if (name === 'uint64' || name === 'ulong') {
return 'ulong?';
}
if (name === 'float') {
return 'float?';
}
if (name === 'double') {
return 'double?';
}
if (name === 'bytes') {
return 'byte[]';
}
if (name === 'class') {
return 'Type';
}
if (name === 'object') {
return 'Dictionary<string, object>';
}
if (name === 'map') {
return 'Dictionary<string, string>';
}
if (name === 'any') {
return 'object';
}
if (name === 'boolean') {
return 'bool?';
}
if (name === 'void') {
return 'void';
}
if (name === 'readable' || name === 'writable') {
return 'Stream';
}
if (name === '$Request' || name === '$Model' || name === '$Response' ||
name === '$URL' || name === '$Date' || name === '$File') {
this.used.push('Darabonba');
return `Darabonba.${name.replace('$', '')}`;
} else if (name === '$ResponseError' || name === '$Error') {
this.used.push('Darabonba.Exceptions');
} else if (name === '$SSEEvent' || name === '$RuntimeOptions' ||
name === '$ExtendsParameters' || name === '$FileField') {
this.used.push('Darabonba.Models');
return name.replace('$', '');
} else if (name === '$RetryOptions') {
this.used.push('Darabonba.RetryPolicy');
return name.replace('$', '');
} else if (name === '$Bytes' || name === '$Form' || name === '$String' ||
name === '$XML' || name === '$JSON' || name === '$Number' || name === '$Stream') {
this.used.push('Darabonba.Utils');
return `${name.replace('$', '')}Utils`;
}
if (name === '$ResponseError') {
return 'DaraResponseException';
}
if (name === '$Error') {
return 'DaraException';
}
return name;
}
getType(name) {
if (name === '$Request' || name === '$Model' || name === '$Response' ||
name === '$URL' || name === '$Date' || name === '$File') {
return 'Darabonba';
} else if (name === '$ResponseError' || name === '$Error') {
return 'Darabonba.Exceptions';
} else if (name === '$SSEEvent' || name === '$RuntimeOptions' ||
name === '$ExtendsParameters' || name === '$FileField') {
return 'Darabonba.Models';
} else if (name === '$RetryOptions') {
return 'Darabonba.RetryPolicy';
} else if (name === '$Bytes' || name === '$Form' || name === '$String' ||
name === '$XML' || name === '$JSON' || name === '$Number' || name === '$Stream') {
return 'Darabonba.Utils';
}
return name;
}
visit(ast, level = 0) {
this.conflictModels = ast.conflictModels;
for (const key of this.conflictModels.keys()) {
const conflicts = key.split(':');
this.conflictModelNameMap.push(conflicts[1] ? conflicts[1] : conflicts[0]);
}
this.classNamespace.set(this.config.clientPath, this.className);
this.visitModule(ast, this.config.clientPath, true, level);
}
saveInnerModule(ast, targetPath) {
const keys = ast.innerModule.keys();
let data = keys.next();
while (!data.done) {
const aliasId = data.value;
const moduleAst = ast.innerDep.get(aliasId);
let filepath = path.join(path.dirname(targetPath), ast.innerModule.get(aliasId));
filepath = _upperPath(filepath);
this.visitModule(moduleAst, filepath, false, 0);
data = keys.next();
}
}
save(filepath) {
const targetPath = path.join(this.outputDir, filepath);
fs.mkdirSync(path.dirname(targetPath), {
recursive: true
});
const namespace = this.getClassNamespace(filepath);
const uniqueUsed = [...new Set(this.used)];
// 当前命名空间去重
const filterUsed = uniqueUsed.filter(item => item !== namespace).map(used => `using ${used};`).join('\n');
const editDesc = this.editable !== true ? `// This file is auto-generated, don't edit it. Thanks.
` : '';
const content = `${editDesc}${filterUsed}
namespace ${namespace}
{
${this.output}
`;
fs.writeFileSync(targetPath, content);
this.output = '';
this.used = [];
this.usedClass = new Map();
if (this.isExec) {
this.visitConsoleCSProj(uniqueUsed);
} else {
this.visitCSProj(uniqueUsed);
this.visitAssemblyInfo();
}
}
getClassNamespace(filePath) {
if (filePath.startsWith(this.outputDir)) {
const baseDir = path.join(this.outputDir, 'core', path.sep);
filePath = filePath.replace(baseDir, '');
}
const arr = filePath.split(path.sep).slice(0, -1);
let className = this.namespace;
arr.map(key => {
className += '.' + key;
});
return className;
}
emit(str, level) {
this.output += ' '.repeat(level * 4) + str;
}
overwrite(ast, filepath) {
if (!ast.moduleBody.nodes || !ast.moduleBody.nodes.length) {
return;
}
const beginNotes = DSL.note.getNotes(this.notes, 0, ast.moduleBody.nodes[0].tokenRange[0]);
const overwirte = beginNotes.find(note => note.note.lexeme === '@overwrite');
if (path.resolve(filepath).startsWith(path.resolve(this.outputDir))) {
const baseDir = path.join(this.outputDir, path.sep);
filepath = filepath.replace(baseDir, '');
}
const targetPath = path.join(this.outputDir, filepath);
if (overwirte && overwirte.arg.value === false && fs.existsSync(targetPath)) {
return false;
}
return true;
}
visitModule(ast, filepath, main, level) {
assert.equal(ast.type, 'module');
this.ast = ast;
this.predefined = ast.models;
this.comments = ast.comments;
this.builtin = getBuiltin(this);
this.moduleTypedef = {};
ast.innerModule = new Map();
this.moduleClass = new Map();
this.clientName = new Map();
this.usedClass = new Map();
this.fileName;
this.notes = ast.notes;
this.usedExternException = ast.usedExternException;
this.clientName.set(this.className, true);
if (this.overwrite(ast, filepath) === false) {
return;
}
this.importBefore(level);
this.importAfter();
this.visitAnnotation(ast.annotation, level + 1);
this.eachImport(ast.imports, ast.usedExternModel, ast.innerModule, filepath, level);
this.moduleBefore();
const extendParam = { extend: [] };
const apis = ast.moduleBody.nodes.filter((item) => {
return item.type === 'api';
});
const models = ast.moduleBody.nodes.filter((item) => {
return item.type === 'model';
});
const functions = ast.moduleBody.nodes.filter((item) => {
return item.type === 'function';
});
extendParam.modelsCount = models.length;
if (ast.extends) {
var extendsName = _name(ast.extends);
let extendClass = `${this.imports[extendsName].namespace}.${this.imports[extendsName].className || 'Client'}`;
extendParam.extend = [...extendParam.extend, extendClass];
}
// Models
this.visitModels(ast, filepath, level);
// Exceptions
this.visitExceptions(ast, filepath, level);
// interface
if (this.config.packageInfo.interface) {
extendParam.extend = [...extendParam.extend, `I${this.className ? this.className : 'Client'}`];
this.used.push('System');
this.used.push('System.IO');
this.used.push('System.Collections');
this.used.push('System.Collections.Generic');
this.used.push('System.Threading.Tasks');
this.used.push('Darabonba');
this.used.push('Darabonba.Utils');
this.emit(`public interface I${this.className ? this.className : 'Client'}\n`, level + 1);
this.emit(`{\n`, level + 1);
//interface api
for (let i = 0; i < apis.length; i++) {
if (i !== 0) {
this.emit('\n');
}
this.interfaceEachAPI(apis[i], level + 2);
}
//interface function
for (let i = 0; i < functions.length; i++) {
if (functions[i].isStatic) {
continue;
}
if (i !== 0) {
this.emit('\n');
}
this.InterfaceEachFunction(functions[i], level + 2);
}
this.interfaceAfter(level + 1);
this.save(`I${this.className ? this.className : 'Client'}.cs`);
}
// Api
this.apiBefore(level, extendParam, filepath, main);
const types = ast.moduleBody.nodes.filter((item) => {
return item.type === 'type';
});
const inits = ast.moduleBody.nodes.filter((item) => {
return item.type === 'init';
});
const [init] = inits;
if (init) {
this.visitInit(init, types, main, filepath, level);
}
let lastToken = 0;
for (let i = 0; i < apis.length; i++) {
const apiNotes = DSL.note.getNotes(this.notes, lastToken, apis[i].tokenRange[0]);
const sse = apiNotes.find(note => note.note.lexeme === '@sse');
if (!sse || !sse.arg.value) {
if (i !== 0) {
this.emit('\n');
}
this.eachAPI(apis[i], level + 2, {
isAsyncMode: false
});
this.emit('\n');
this.eachAPI(apis[i], level + 2, {
isAsyncMode: true
});
}
}
this.apiAfter();
this.wrapBefore();
for (let i = 0; i < functions.length; i++) {
this.emit('\n');
// TODO 返回值为asyncIterator时不生成同步方法
if (!(this.isExec && this.asyncOnly && functions[i].isAsync)) {
this.eachFunction(functions[i], level + 2, {
isAsyncMode: false
});
}
if (functions[i].isAsync) {
this.emit('\n');
this.eachFunction(functions[i], level + 2, {
isAsyncMode: true
});
}
}
this.wrapAfter();
this.moduleAfter();
this.save(filepath);
this.saveInnerModule(ast, filepath);
}
emitAnnotation(node, attrList, level) {
const realName = getAttr(node, 'name') || _name(node.fieldName);
this.emit(`[NameInMap("${realName}")]\n`, level);
this.emit('[Validation(Required=', level);
node.required ? this.emit('true') : this.emit('false');
for (let i = 0; i < node.attrs.length; i++) {
const attrValueType = attrList[node.attrs[i].attrName.lexeme];
if (attrValueType) {
this.emit(', ');
var attrName = _upperFirst(node.attrs[i].attrName.lexeme);
attrName = attrName.split('-').join('');
if (attrValueType === 'string') {
this.emit(`${attrName}="${node.attrs[i].attrValue[attrValueType]}"`);
} else {
this.emit(`${attrName}=${node.attrs[i].attrValue[attrValueType]}`);
}
}
}
this.emit(')]\n');
const deprecated = getAttr(node, 'deprecated');
if (deprecated === 'true') {
this.emit(`[Obsolete]\n`, level);
}
}
visitModels(ast, filepath, level) {
const models = ast.moduleBody.nodes.filter((item) => {
return item.type === 'model';
});
for (let i = 0; i < models.length; i++) {
const modelName = _upperFirst(models[i].modelName.lexeme);
this.modelSpace = modelName;
this.used.push('System');
this.used.push('System.IO');
this.used.push('System.Collections');
this.used.push('System.Collections.Generic');
// for [NameInMap]
this.used.push('Darabonba');
this.visitAnnotation(models[i].annotation, level + 1);
let comments = DSL.comment.getFrontComments(this.comments, models[i].tokenRange[0]);
this.visitComments(comments, level);
this.eachModel(models[i], modelName, level + 1, ast.predefined);
this.modelAfter();
const modelDir = path.join(path.dirname(filepath), 'Models');
const modelFilepath = path.join(modelDir, `${modelName}.cs`);
this.save(modelFilepath);
}
}
visitModelBody(ast, level, env, modelName) {
assert.equal(ast.type, 'modelBody');
let node;
for (let i = 0; i < ast.nodes.length; i++) {
node = ast.nodes[i];
let comments = DSL.comment.getFrontComments(this.comments, node.tokenRange[0]);
this.visitComments(comments, level);
var paramName = _avoidReserveName(_upperFirst(_name(node.fieldName)));
if (paramName === modelName) {
paramName = paramName + '_';
}
const description = getAttr(node, 'description');
const example = getAttr(node, 'example');
const checkBlank = getAttr(node, 'checkBlank');
const nullable = getAttr(node, 'nullable');
const sensitive = getAttr(node, 'sensitive');
const deprecated = getAttr(node, 'deprecated');
let hasNextSection = false;
if (deprecated === 'true') {
this.emit(`/// <term><b>Obsolete</b></term>\n`, level);
hasNextSection = true;
}
if (description || example || typeof checkBlank !== 'undefined' || typeof nullable !== 'undefined' || typeof sensitive !== 'undefined') {
if (hasNextSection) {
this.emit(`/// \n`, level);
}
this.emit('/// <summary>\n', level);
if (description) {
const descriptions = md2Xml(description).split('\n');
for (let k = 0; k < descriptions.length; k++) {
this.emit(`/// ${descriptions[k]}\n`, level);
}
hasNextSection = true;
}
if (example) {
if (hasNextSection) {
this.emit('/// \n', level);
}
this.emit('/// <b>Example:</b>\n', level);
const examples = md2Xml(example).split('\n');
for (let k = 0; k < examples.length; k++) {
this.emit(`/// ${examples[k]}\n`, level);
}
hasNextSection = true;
}
if (typeof checkBlank !== 'undefined') {
if (hasNextSection) {
this.emit('/// \n', level);
}
this.emit('/// <b>check if is blank:</b>\n', level);
this.emit(`/// <c>${checkBlank}</c>\n`, level);
hasNextSection = true;
}
if (typeof nullable !== 'undefined') {
if (hasNextSection) {
this.emit('/// \n', level);
}
this.emit('/// <b>if can be null:</b>\n', level);
this.emit(`/// <c>${nullable}</c>\n`, level);
hasNextSection = true;
}
if (typeof sensitive !== 'undefined') {
if (hasNextSection) {
this.emit('/// \n', level);
}
this.emit('/// <b>if is sensitive:</b>\n', level);
this.emit(`/// <c>${sensitive}</c>\n`, level);
}
this.emit('/// </summary>\n', level);
}
this.emitAnnotation(node, attrList, level);
var publicDeclar = 'public ';
if (node.fieldValue.fieldType === 'array') {
let subModelName = modelName + _upperFirst(paramName);
if (node.fieldValue.fieldItemType.nodes !== undefined) {
this.emit(publicDeclar, level);
this.emit(`List<${subModelName}>`);
this.emit(` ${_upperFirst(paramName)} { get; set; }`);
this.emit('\n');
this.emit(`public class ${subModelName} : Darabonba.Model {\n`, level);
this.visitModelBody(node.fieldValue.fieldItemType, level + 1, env, subModelName);
this.emit('}\n', level);
} else {
this.emit(publicDeclar, level);
this.emit('List<');
if (node.fieldValue.fieldItemType.fieldType === 'array') {
let item = JSON.parse(JSON.stringify(node.fieldValue.fieldItemType));
while (item.fieldType === 'array' && item.nodes === undefined) {
this.emit('List<');
item = item.fieldItemType;
}
if (item.type === 'modelBody') {
this.emit(`${subModelName}`);
} else {
this.visitFieldType(item, level, modelName, _name(node.fieldName));
}
item = JSON.parse(JSON.stringify(node.fieldValue.fieldItemType));
while (item.fieldType === 'array' && item.nodes === undefined) {
this.emit('>');
item = item.fieldItemType;
}
this.emit('>');
this.emit(` ${_upperFirst(paramName)} { get; set; }`);
this.emit('\n');
if (item.type === 'modelBody') {
this.emit(`public class ${subModelName} : Darabonba.Model {\n`, level);
this.visitModelBody(item, level + 1, env, subModelName);
this.emit('}\n', level);
}
} else {
this.visitFieldType(node.fieldValue.fieldItemType, level, modelName);
this.emit('>');
this.emit(` ${paramName} { get; set; }`);
this.emit('\n');
}
}
} else if (node.fieldValue.fieldType === 'map') {
this.emit(publicDeclar, level);
this.visitFieldType(node.fieldValue, level, modelName, _name(node.fieldValue));
this.emit(` ${paramName} { get; set; }`);
this.emit('\n');
} else if (typeof node.fieldValue.fieldType === 'string') {
this.emit(publicDeclar, level);
this.emit(`${this._type(node.fieldValue.fieldType)}`);
this.emit(` ${paramName} { get; set; }`);
this.emit('\n');
} else if (node.fieldValue.fieldType) {
this.emit(publicDeclar, level);
if (node.fieldValue.fieldType.idType === 'module') {
const realClsName = this.getRealClientName(`${_name(node.fieldValue.fieldType) || 'Client'}`);
this.emit(realClsName);
} else if (node.fieldValue.fieldType.type === 'moduleModel') {
const [moduleId, ...models] = node.fieldValue.fieldType.path;
const { namespace } = this.moduleClass.get(moduleId.lexeme);
// const subModelName = models.map((item) => {
// return item.lexeme;
// }).join('.');
let subModelName = '';
let tempName = '';
models.forEach((item, index) => {
tempName += _upperFirst(item.lexeme);
if (index < models.length - 1) {
subModelName += tempName + '.';
} else {
subModelName += tempName;
}
});
const realModelName = this.getRealModelName(`${namespace}.Models`, subModelName);
this.emit(realModelName);
} else if (node.fieldValue.idType === 'builtin_model') {
const subModelName = node.fieldValue.lexeme;
const namespace = this.getType(subModelName);
const realModelName = this.getRealModelName(namespace, subModelName);
this.emit(realModelName);
} else if (node.fieldValue.fieldType.type === 'moduleTypedef') {
const [moduleId, modelName] = node.fieldValue.fieldType.path;
const moduleTypedef = this.moduleTypedef[moduleId.lexeme];
const typedef = moduleTypedef[modelName.lexeme];
if (!typedef.import) {
this.emit(typedef.type);
} else {
const typedefName = this.getRealModelName(typedef.import, typedef.type);
this.emit(typedefName);
}
} else if (node.fieldValue.fieldType.type === 'typedef' || node.fieldValue.fieldType.idType === 'typedef') {
const typedef = this.typedef[node.fieldValue.fieldType.lexeme];
if (!typedef.import) {
this.emit(typedef.type);
} else {
const typedefName = this.getRealModelName(typedef.import, typedef.type);
this.emit(typedefName);
}
} else if (node.fieldValue.fieldType.idType === 'model') {
const realModelName = this.getRealModelName(`${this.namespace}.Models`, node.fieldValue.fieldType.lexeme);
this.emit(realModelName);
} else {
this.emit(`${this._type(node.fieldValue.fieldType.lexeme)}`);
}
this.emit(` ${paramName} { get; set; }`);
this.emit('\n');
} else {
const fieldName = _avoidReserveName(_upperFirst(_name(node.fieldName)));
var subModelName = modelName + _upperFirst(fieldName);
this.emit(`public ${subModelName} ${_upperFirst(fieldName)} { get; set; }\n`, level);
this.emit(`public class ${subModelName} : Darabonba.Model {\n`, level);
this.visitModelBody(node.fieldValue, level + 1, {}, subModelName);
this.emit('}\n', level);
}
this.emit('\n');
}
if (node) {
//find the last node's back comment
let comments = DSL.comment.getBetweenComments(this.comments, node.tokenRange[0], ast.tokenRange[1]);
this.visitComments(comments, level);
}
if (ast.nodes.length === 0) {
//empty block's comment
let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]);
this.visitComments(comments, level);
}
}
visitType(ast, level, env) {
if (ast.type === 'array') {
this.emit('List<');
this.visitType(ast.subType || ast.itemType, level);
this.emit('>');
} else if (ast.type === 'map' || ast.fieldType === 'map') {
if (env && env.castToObject) {
this.emit('Dictionary<string, object>');
env.castToObject = false;
return;
}
this.emit('Dictionary<');
this.visitType(ast.keyType, level);
this.emit(', ');
this.visitType(ast.valueType, level);
this.emit('>');
} else if (this.predefined && this.predefined['module:' + _name(ast)]) {
let clientName = (this.imports[_name(ast)] && this.imports[_name(ast)]['client']) || `${_name(ast)}.Client`;
this.emit(clientName);
} else if (ast.type === 'model') {
let namespace = this.namespace;
let type = 'Models';
if (ast.moduleName) {
namespace = this.moduleClass.get(ast.moduleName).namespace;
var usedEx;
if (this.usedExternException) {
usedEx = this.usedExternException.get(ast.moduleName);
}
if (usedEx && usedEx.has(ast.name)) {
type = 'Exceptions';
}
} else if (this.predefined[ast.name] && this.predefined[ast.name].isException) {
type = 'Exceptions';
}
let tempName = '';
let models = _name(ast).split('.');
models.forEach((item, index) => {
if (index > 0) {
this.emit('.');
}
if (index === 0) {
tempName += _upperFirst(this._type(item));
const realModelName = this.getRealModelName(`${namespace}.${type}`, tempName, type);
this.emit(realModelName);
} else {
tempName += _upperFirst(this._type(item));
this.emit(tempName);
}
});
} else if (ast.type === 'moduleModel' && ast.path && ast.path.length > 0) {
const [moduleId, ...rest] = ast.path;
const { namespace } = this.moduleClass.get(moduleId.lexeme);
let moduleModelName = '';
let tempName = '';
rest.forEach((item, index) => {
tempName += _upperFirst(item.lexeme);
if (index < rest.length - 1) {
moduleModelName += tempName + '.';
} else {
moduleModelName += tempName;
}
});
let type = 'Models';
if (this.usedExternException) {
usedEx = this.usedExternException.get(moduleId.lexeme);
}
if (usedEx && usedEx.has(moduleModelName)) {
type = 'Exceptions';
}
const subModelName = this.getRealModelName(`${namespace}.${type}`, moduleModelName, type);
this.emit(subModelName);
} else if (ast.type === 'module_instance') {
if (_name(ast) in builtinMap) {
this.emit(`${builtinMap[_name(ast)]}`);
} else {
const realClsName = this.getRealClientName(_name(ast));
this.emit(realClsName);
}
} else if (ast.idType === 'module') {
let moduleName = _name(ast);
moduleName = this.getRealClientName(moduleName);
this.emit(`${moduleName || 'Client'}`);
} else if (ast.idType === 'model') {
let type = 'Models';
if (this.predefined[ast.lexeme] && this.predefined[ast.lexeme].isException) {
type = 'Exceptions';
}
const realModelName = this.getRealModelName(`${this.namespace}.${type}`, _upperFirst(ast.lexeme), type);
this.emit(realModelName);
} else if (ast.idType === 'builtin_model') {
const subModelName = ast.lexeme;
const namespace = this.getType(subModelName);
const realModelName = this.getRealModelName(namespace, subModelName);
this.emit(realModelName);
} else if (ast.type === 'moduleTypedef') {
const [moduleId, modelName] = ast.path;
const moduleTypedef = this.moduleTypedef[moduleId.lexeme];
const typedef = moduleTypedef[modelName.lexeme];
if (!typedef.import) {
this.emit(typedef.type);
} else {
const typedefName = this.getRealModelName(typedef.import, typedef.type);
this.emit(typedefName);
}
} else if (ast.type === 'typedef' || ast.idType === 'typedef') {
const typedef = this.typedef[ast.lexeme];
if (!typedef.import) {
this.emit(typedef.type);
} else {
const typedefName = this.getRealModelName(typedef.import, typedef.type);
this.emit(typedefName);
}
} else if (ast.fieldType === 'array') {
this.emit('List<');
this.visitType(ast.fieldItemType, level);
this.emit('>');
} else if (ast.type === 'entry') {
this.emit('KeyValuePair<string, ');
this.visitType(ast.valueType);
this.emit('>');
} else if (ast.type === 'subModel') {
let tempName = '';
for (let i = 0; i < ast.path.length; i++) {
if (i > 0) {
this.emit('.');
}
tempName += _upperFirst(_name(ast.path[i]));
this.emit(tempName);
}
} else if (typeof ast.fieldType === 'string') {
this.emit(`${this._type(ast.fieldType)}`);
} else if (this.isIterator(ast)) {
if (ast.type === 'iterator') {
this.emit('IEnumerable<');
} else if (ast.type === 'asyncIterator') {
this.emit('IAsyncEnumerable<');
}
this.visitType(ast.valueType);
this.emit('>');
} else if (ast.type === 'basic') {
this.emit(this._type(_name(ast)));
} else {
this.emit(this._type(_name(ast)));
}
}
visitFieldType(value, level, modelName, fieldName) {
if (value.type === 'modelBody') {
var paramName = _avoidReserveName(_upperFirst(fieldName));
const subModelName = modelName + _upperFirst(paramName);
this.emit(subModelName);
} else if (value.type === 'array') {
this.visitType(value);
} else if (value.fieldType === 'array') {
this.emit(`List<`);
this.visitFieldType(value.fieldItemType, level, modelName, fieldName);
this.emit(`>`);
} else if (value.fieldType === 'map') {
this.emit(`Dictionary<${value.keyType.lexeme}, `);
this.visitFieldType(value.valueType);
this.emit(`>`);
} else if (value.type === 'map') {
this.emit(`Dictionary<${value.keyType.lexeme}, `);
this.visitFieldType(value.valueType);
this.emit(`>`);
} else if (value.tag === Tag.TYPE) {
this.emit(`${this._type(value.lexeme)}`);
} else if (value.tag === Tag.ID && value.idType === 'model') {
const modelName = _upperFirst(_name(value));
const realModelName = this.getRealModelName(`${this.namespace}.Models`, modelName);
this.emit(realModelName);
} else if (value.tag === Tag.ID && value.idType === 'module') {
let moduleName = _upperFirst(_name(value));
moduleName = this.getRealClientName(moduleName);
this.emit(moduleName);
} else if (value.tag === Tag.ID && value.idType === 'builtin_model') {
this.emit(`${value.lexeme}`);
} else if (value.tag === Tag.ID) {
this.emit(`${value.lexeme}`);
} else if (value.type === 'moduleModel') {
const [moduleId, ...models] = value.path;
const { namespace } = this.moduleClass.get(moduleId.lexeme);
const moduleModelName = models.map((item) => {
return item.lexeme;
}).join('.');
const subModelName = this.getRealModelName(`${namespace}.Models`, moduleModelName);
this.emit(subModelName);
} else if (value.type === 'subModel') {
let tempName = '';
for (let i = 0; i < value.path.length; i++) {
if (i > 0) {
this.emit('.');
}
tempName += _upperFirst(_name(value.path[i]));
this.emit(tempName);
}
} else if (typeof value.fieldType === 'string') {
this.emit(`${this._type(value.fieldType)}`);
} else if (value.fieldType.type === 'moduleModel') {
const [moduleId, ...models] = value.fieldType.path;
this.emit(`${moduleId.lexeme}.${_subModelName(models.map((item) => item.lexeme).join('.'))}`);
} else if (value.fieldType.type === 'moduleTypedef') {
const [moduleId, modelName] = value.fieldType.path;
const moduleTypedef = this.moduleTypedef[moduleId.lexeme];
const typedef = moduleTypedef[modelName.lexeme];
if (!typedef.import) {
this.emit(typedef.type);
} else {
const typedefName = this.getRealModelName(typedef.import, typedef.type);
this.emit(typedefName);
}
} else if (value.fieldType.type === 'typedef' || value.fieldType.idType === 'typedef') {
const typedef = this.typedef[value.fieldType.lexeme];
if (!typedef.import) {
this.emit(typedef.type);
} else {
const typedefName = this.getRealModelName(typedef.import, typedef.type);
this.emit(typedefName);
}
} else if (value.fieldType.type) {
this.emit(`${this._type(value.fieldType.lexeme)}`);
} else if (value.fieldType.idType === 'model') {
const realModelName = this.getRealModelName(`${this.namespace}.Models`, value.fieldType.lexeme);
this.emit(realModelName);
} else if (value.fieldType.idType === 'module') {
let moduleName = this._type(value.fieldType.lexeme);
moduleName = this.getRealClientName(moduleName);
this.emit(moduleName);
} else if (value.fieldType.idType === 'builtin_model') {
const subModelName = this._type(value.fieldType.lexeme);
const namespace = this.getType(subModelName);
const realModelName = this.getRealModelName(namespace, subModelName);
this.emit(realModelName);
}
}
visitReturnType(ast, level, env) {
const handleIterator = (typeName) => {
if (this._type(_name(ast.returnType)) === 'void') {
this.emit(typeName);
} else {
this.emit(`${typeName}<`);
this.visitType(ast.returnType.valueType, level);
this.emit('>');
}
};
switch (ast.returnType.type) {
case 'asyncIterator':
handleIterator('IAsyncEnumerable');
break;
case 'iterator':
handleIterator('IEnumerable');
break;
default:
if (env.isAsyncMode) {
if (this._type(_name(ast.returnType)) === 'void') {
this.emit('Task');
} else {
this.emit('Task<');
this.visitType(ast.returnType, level);
this.emit('>');
}
} else {
this.visitType(ast.returnType, level);
}
break;
}
this.emit(' ');
}
visitReturnBody(ast, level, env) {
assert.equal(ast.type, 'returnBody');
this.emit('\n');
this.visitStmts(ast.stmts, level, env);
}
visitDefaultReturnBody(level) {
this.emit('\n');
this.emit('return;\n', level);
}
visitAPIBody(ast, level, env) {
assert.equal(ast.type, 'apiBody');
this.emit(`Darabonba.Request ${REQUEST} = new Darabonba.Request();\n`, level);
this.visitStmts(ast.stmts, level, env);
}
visitStmts(ast, level, env) {
assert.equal(ast.type, 'stmts');
let node;
for (var i = 0; i < ast.stmts.length; i++) {
node = ast.stmts[i];
this.visitStmt(node, level, env);
}
if (node) {
//find the last node's back comment
let comments = DSL.comment.getBackComments(this.comments, node.tokenRange[1]);
this.visitComments(comments, level);
}
if (ast.stmts.length === 0) {
//empty block's comment
let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]);
this.visitComments(comments, level);
}
}
visitStmt(ast, level, env) {
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
if (ast.type === 'return') {
this.visitReturn(ast, level, env);
} else if (ast.type === 'yield') {
this.visitYield(ast, level, env);
} else if (ast.type === 'if') {
this.visitIf(ast, level, env);
} else if (ast.type === 'throw') {
this.visitThrow(ast, level, env);
} else if (ast.type === 'assign') {
this.visitAssign(ast, level, env);
} else if (ast.type === 'retry') {
this.visitRetry(ast, level);
} else if (ast.type === 'break') {
this.emit('break;\n', level);
} else if (ast.type === 'declare') {
this.visitDeclare(ast, level, env);
} else if (ast.type === 'while') {
this.visitWhile(ast, level, env);
} else if (ast.type === 'for') {
this.visitFor(ast, level, env);
} else if (ast.type === 'call') {
this.emit('', level);
this.visitExpr(ast, level, env);
this.emit(';\n');
} else if (ast.type === 'super') {
//no need
} else if (ast.type === 'try') {
this.visitTry(ast, level, env);
} else {
this.emit('', level);
this.visitExpr(ast, level, env);
this.emit(';\n');
}
}
visitTry(ast, level, env) {
this.emit('try\n', level);
this.emit('{\n', level);
this.visitStmts(ast.tryBlock, level + 1, env);
this.emit('}\n', level);
if (ast.catchBlocks && ast.catchBlocks.length > 0) {
ast.catchBlocks.forEach(catchBlock => {
if (!catchBlock.id) {
return;
}
if (!catchBlock.id.type) {
const errorName = this.getRealModelName('Darabonba.Exceptions', 'DaraException');
this.emit(`catch (${errorName} ${_name(catchBlock.id)})\n`, level);
this.emit(`{\n`, level);
} else {
this.emit('catch (', level);
this.visitType(catchBlock.id.type);
this.emit(` ${_name(catchBlock.id)})\n`);
this.emit('{\n', level);
}
this.visitStmts(catchBlock.catchStmts, level + 1, env);
this.emit('}\n', level);
});
} else if (ast.catchBlock && ast.catchBlock.stmts.length > 0) {
const errorName = this.getRealModelName('Darabonba.Exceptions', 'DaraException');
this.emit(`catch (${errorName} ${_name(ast.catchId)})\n`, level);
this.emit('{\n', level);
this.visitStmts(ast.catchBlock, level + 1, env);
this.emit('}\n', level);
}
if (ast.finallyBlock) {
this.emit('finally\n', level);
this.emit('{\n', level);
this.visitStmts(ast.finallyBlock, level + 1, env);
this.emit('}\n', level);
}
}
visitDeclare(ast, level, env) {
var id = _name(ast.id);
this.emit(``, level);
if (ast.expr.left && ast.expr.left.id && ast.expr.left.id.type === 'builtin_module' &&
ast.expr.left.id.lexeme === '$Number' && ast.expr.left.propertyPath[0]) {
if (ast.expr.left.propertyPath[0].lexeme === 'random') {
this.emit('double');
} else if (ast.expr.left.propertyPath[0].lexeme === 'min' || ast.expr.left.propertyPath[0].lexeme === 'max') {
if (ast.expr.args[0].inferred.name !== ast.expr.args[1].inferred.name) {
this.emit('double');
} else {
this.emit(this._type(ast.expr.args[0].inferred.name));
}
}
} else {
this.visitType(ast.expr.inferred, undefined, { ...env, variable: true });
}
this.emit(` ${_avoidReserveName(id)} = `);
this.visitExpr(ast.expr, level, env);
this.emit(';\n');
}
visitParams(ast) {
assert.equal(ast.type, 'params');
this.emit('(');
for (var i = 0; i < ast.params.length; i++) {
if (i !== 0) {
this.emit(', ');
}
const node = ast.params[i];
assert.equal(node.type, 'param');
this.visitType(node.paramType);
const name = _name(node.paramName);
this.emit(` ${_avoidReserveName(name)}`);
}
this.emit(')');
}
visitRuntimeBefore(ast, level, env) {
assert.equal(ast.type, 'object');
const retryType = this.getRealModelName('Darabonba.RetryPolicy', 'RetryOptions');
this.emit(`Dictionary<string, object> ${RUNTIME} = `, level);
// this.visitConstructObject(ast, level, env);
// TODO 这里有个问题:当runtime不是全量的,例如只有一个retry: true时,左边类型为Dictionary<string, object>,右边类型为Dictionary<string, bool?>,会报错
// 现在openapi中是全量的,所以暂时没有问题
this.visitObject(ast, level, env);
this.emit(';\n');
this.emit('\n');
this.emit('RetryPolicyContext _retryPolicyContext = null;\n', level);
this.emit('Darabonba.Request _lastRequest = null;\n', level);
this.emit('Darabonba.Response _lastResponse = null;\n', level);
this.emit('Exception _lastException = null;\n', level);
this.emit('long _now = System.DateTime.Now.Millisecond;\n', level);
this.emit('int _retriesAttempted = 0;\n', level);
this.emit('_retryPolicyContext = new RetryPolicyContext\n', level);
this.emit('{\n', level);
this.emit('RetriesAttempted = _retriesAttempted\n', level + 1);
this.emit('};\n', level);
this.emit(`while (Core.ShouldRetry((${retryType})${RUNTIME}["retryOptions"], _retryPolicyContext))\n`, level);
this.emit('{\n', level);
this.emit('if (_retriesAttempted > 0)\n', level + 1);
this.emit('{\n', level + 1);
this.emit(`int backoffTime = Core.GetBackoffDelay((${retryType})${RUNTIME}["retryOptions"], _retryPolicyContext);\n`, level + 2);
this.emit('if (backoffTime > 0)\n', level + 2);
this.emit('{\n', level + 2);
this.emit('Core.Sleep(backoffTime);\n', level + 3);
this.emit('}\n', level + 2);
this.emit('}\n', level + 1);
this.emit('try\n', level + 1);
this.emit('{\n', level + 1);
}
visitRuntimeAfter(ast, level) {
this.emit('}\n', level + 1);
this.emit('catch (Exception e)\n', level + 1);
this.emit('{\n', level + 1);
this.emit('_retriesAttempted++;\n', level + 2);
this.emit('_lastException = e;\n', level + 2);
this.emit('_retryPolicyContext = new RetryPolicyContext\n', level + 2);
this.emit('{\n', level + 2);
this.emit('RetriesAttempted = _retriesAttempted,\n', level + 3);
this.emit('Request = _lastRequest,\n', level + 3);
this.emit('Response = _lastResponse,\n', level + 3);
this.emit('Exception = _lastException\n', level + 3);
this.emit('};\n', level + 2);
this.emit('}\n', level + 1);
this.emit('}\n\n', level);
this.emit('throw Core.ThrowException(_retryPolicyContext);\n', level);
}
visitExpr(ast, level, env = {}) {
if (ast.type === 'boolean') {
this.emit(ast.value);
} else if (ast.type === 'property_access') {
this.visitPropertyAccess(ast, level, env);
} else if (ast.type === 'string') {
let val = _string(ast.value);
val = val.replace(/(?<!\\)(?:\\{2})*"/g, '\\"');
this.emit(`"${val}"`);
} else if (ast.type === 'number') {
this.emit(ast.value.value);
if (ast.value.type === 'float') {
this.emit('f');
}
} else if (ast.type === 'object') {
this.visitObject(ast, level, env);
} else if (ast.type === 'variable') {
var id = _name(ast.id);
if (id === '__response') {
this.emit(RESPONSE);
} else if (id === '__request') {
this.emit(REQUEST);
} else if (ast.inferred && ast.inferred.name === 'class') {
this.emit('typeof(' + _avoidReserveName(id) + ')');
}
else {
this.emit(_avoidReserveName(id));
}
} else if (ast.type === 'virtualVariable') {
const vid = `_${_lowerFirst(_name(ast.vid).substr(1))}`;
if (_name(ast.inferred) === 'boolean') {
this.emit(`${vid}.Value`);
} else {
this.emit(`${vid}`);
}
} else if (ast.type === 'decrement') {
if (ast.position === 'front') {
this.emit('--');
}
this.visitExpr(ast.expr, level, env);
if (ast.position === 'backend') {
this.emit('--');
}
} else if (ast.type === 'increment') {
if (ast.position === 'front') {
this.emit('++');
}
this.visitExpr(ast.expr, level, env);
if (ast.position === 'backend') {
this.emit('++');
}
} else if (ast.type === 'template_string') {
var elements = ast.elements.filter((item) => {
return item.type !== 'element' || item.value.string.length > 0;
});
for (var i = 0; i < elements.length; i++) {
var item = elements[i];
if (item.type === 'element') {
this.emit('"');
let val = _string(item.value);
val = val.replace(/(?<!\\)(?:\\{2})*"/g, '\\"');
val = val.replace(/[\n]/g, '" + \n"');
this.emit(val);
this.emit('"');
} else if (item.type === 'expr') {
if (i === 0) {
this.emit('"" + ');
}
env.groupOp = true;
this.visitExpr(item.expr, level, env);
} else {
throw new Error('unimpelemented');
}
if (i < elements.length - 1) {
this.emit(' + ');
}
}
} else if (ast.type === 'call') {
this.visitCall(ast, level, env);
} else if (ast.type === 'construct') {
this.visitConstruct(ast, level, env);
} else if (ast.type === 'group') {
env.groupOp = false;
this.emit('(');
this.visitExpr(ast.expr, level, env);
this.emit(')');
} else if (_isBinaryOp(ast.type)) {
env.groupOp = true;
this.visitExpr(ast.left, level, env);
if (ast.type === 'or') {
this.emit(' || ');
} else if (ast.type === 'add') {
this.emit(' + ');
} else if (ast.type === 'subtract') {
this.emit(' - ');
} else if (ast.type === 'div') {
this.emit(' / ');
} else if (ast.type === 'multi') {
this.emit(' * ');
} else if (ast.type === 'and') {
this.emit(' && ');
} else if (ast.type === 'lte') {
this.emit(' <= ');
} else if (ast.type === 'lt') {
this.emit(' < ');
} else if (ast.type === 'gte') {
this.emit(' >= ');
} else if (ast.type === 'gt') {
this.emit(' > ');
} else if (ast.type === 'neq') {
this.emit(' != ');
} else if (ast.type === 'eq') {
this.emit(' == ');
}
this.visitExpr(ast.right, level, env);
}
else if (ast.type === 'array') {
this.visitArray(ast, level, env);
} else if (ast.type === 'null') {
this.emit('null');
} else if (ast.type === 'not') {
env.groupOp = true;
this.emit('!');
let wrapName;
if (ast.expr.left && ast.expr.left.id) {
wrapName = _upperFirst(_name(ast.expr.left.id));
}
if (ast.expr.type === 'call' && wrapName && !wrapName.startsWith('$') && !this.builtin[wrapName]) {
this.emit('(');
}
this.visitExpr(ast.expr, level, env);
if (ast.expr.type === 'call' && wrapName && !wrapName.startsWith('$') && !this.builtin[wrapName]) {
this.emit(').Value');
}
} else if (ast.type === 'construct_model') {
this.visitConstructModel(ast, level, env);
} else if (ast.type === 'map_access') {
this.visitMapAccess(ast, level, env);
} else if (ast.type === 'super') {
//no need
} else if (ast.type === 'array_access') {
this.visitArrayAccess(ast, level, env);
} else {
throw new Error('unimpelemented');
}
}
visitMapAccess(ast, level, env) {
assert.equal(ast.type, 'map_access');
var mapName = _name(ast.id);
if (ast.id.tag === Tag.VID) {
mapName = _vid(ast.id);
} else {
mapName = _name(ast.id);
}
if (ast.propertyPath && ast.propertyPath.length) {
var current = ast.id.inferred;
for (var i = 0; i < ast.propertyPath.length; i++) {
var name = _name(ast.propertyPath[i]);
if (current.type === 'model') {
mapName += `.${_upperFirst(name)}`;
} else {
mapName += `["${name}"]`;
}
current = ast.propertyPathTypes[i];
}
}
this.emit(`${mapName}.Get(`);
this.visitExpr(ast.accessKey, level, env);
this.emit(`)`);
}
visitArrayAccess(ast, level, env, isLeft) {
assert.equal(ast.type, 'array_access');
let expr;
if (ast.id.tag === DSL.Tag.Tag.VID) {
expr = `this.${_vid(ast.id)}`;
} else {
expr = `${_name(ast.id)}`;
}
if (ast.propertyPath && ast.propertyPath.length) {
var current = ast.id.inferred;
for (var i = 0; i < ast.propertyPath.length; i++) {
var name = _name(ast.propertyPath[i]);
if (current.type === 'model') {
expr += `.${_upperFirst(name)}`;
} else {
expr += `["${name}"]`;
}
current = ast.propertyPathTypes[i];
}
}
if (isLeft) {
this.emit(`${expr}[`, level);
} else {
this.emit(`${expr}[`);
}
this.visitExpr(ast.accessKey, level, env);
if (ast.accessKey.type === 'variable') {
this.emit(`.Value`);
}
this.emit(`]`);
}
visitYield(ast, level, env) {
assert.equal(ast.type, 'yield');
this.emit('yield return ', level);
if (!ast.expr) {
this.emit(';\n');
return;
}
if (ast.needCast) {
this.emit('Darabonba.Model.ToObject<');
this.visitType(ast.expectedType);
this.emit('>(');
}
if (ast.expr && ast.expr.type === 'object' && env && env.returnType && _name(env.returnType) === 'object') {
env.castToObject = true;
}
this.visitExpr(ast.expr, level, env);
if (ast.needCast) {
this.emit(')');
}
this.emit(';\n');
}
visitConstructModel(ast, level, env) {
assert.equal(ast.type, 'construct_model');
if (ast.aliasId.isModule) {
let aliasId = ast.aliasId.lexeme;
const { namespace } = this.moduleClass.get(aliasId);
this.emit('new ');
let tempName = '';
let type = 'Models';
const moduleModelName = ast.propertyPath.map((item) => {
return item.lexeme;
}).join('.');
var usedEx;
if (this.usedExternException) {
usedEx = this.usedExternException.get(aliasId);
}
if (usedEx && usedEx.has(moduleModelName)) {
type = 'Exceptions';
}
for (let i = 0; i < ast.propertyPath.length; i++) {
const item = ast.propertyPath[i];
if (i > 0) {
this.emit('.');
}
// Request.RequestSubmodel,先判断Request是否冲突
if (i === 0) {
tempName += _upperFirst(item.lexeme);
const realModelName = this.getRealModelName(`${namespace}.${type}`, tempName, type);
this.emit(realModelName);
} else {
tempName += _upperFirst(item.lexeme);
this.emit(tempName);
}
}
} else if (ast.aliasId.isModel) {
let mainModelName = ast.aliasId;
let type = 'Models';
this.emit('new ');
const modelName = [mainModelName, ...ast.propertyPath].map((item) => {
return item.lexeme;
}).join('.');
if (_isBuiltinModel(modelName)) {
const namespace = this.getType(modelName);
const subModelName = this.getRealModelName(namespace, this._type(modelName));
this.emit(subModelName);
} else {
if (this.predefined[modelName] && this.predefined[modelName].isException) {
type = 'Exceptions';
}
const subModelName = this.getRealModelName(`${this.namespace}.${type}`, modelName, type);
this.emit(subModelName);
}
} else {
this.emit(`new ${_name(ast.aliasId)}`);
}
this.visitConstructObject(ast.object, level, env);
}
visitConstructObject(ast, level, env) {
if (ast && ast.fields && ast.fields.length > 0) {
this.emit('\n');
this.emit('{\n', level);
let i = 0;
ast.fields.forEach((element) => {
let comments = DSL.comment.getFrontComments(this.comments, element.tokenRange[0]);
this.visitComments(comments, level + 1);
this.emit(_avoidReserveName(_upperFirst(_name(element.fieldName))), level + 1);
this.emit(' = ');
this.visitExpr(element.expr, level + 1, env);
this.emit(',\n');
i++;
});
// find the last item's back comment
let comments = DSL.comment.getBackComments(this.comments, ast.fields[i - 1].tokenRange[1]);
this.visitComments(comments, level + 1);
this.emit('}', level);
} else {
this.emit('()');
}
}
visitCall(ast, level, env) {
assert.equal(ast.type, 'call');
if (ast.left.type === 'method_call') {
this.visitMethodCall(ast, level, env);
} else if (ast.left.type === 'instance_call') {
this.visitInstanceCall(ast, level, env);
} else if (ast.left.type === 'static_call') {
this.visitStaticCall(ast, level, env);
} else {
throw new Error('un-implemented');
}
}
visitArray(ast, level, env) {
assert.equal(ast.type, 'array');
this.emit('new List<');
this.visitType(ast.inferred.itemType, level, env);
this.emit('>\n');
this.emit('{\n', level);
let item;
for (let i = 0; i < ast.items.length; i++) {
item = ast.items[i];
let comments = DSL.comment.getFrontComments(this.comments, item.tokenRange[0]);
this.visitComments(comments, level + 1);
this.emit('', level + 1);
this.visitExpr(item, level + 1, env);
if (i < ast.items.length - 1) {
this.emit(',');
}
this.emit('\n');
}
if (item) {
//find the last item's back comment
let comments = DSL.comment.getBetweenComments(this.comments, item.tokenRange[0], ast.tokenRange[1]);
this.visitComments(comments, level + 1);
}
this.emit('}', level);
}
visitConstruct(ast, level, env) {
assert.equal(ast.type, 'construct');
this.emit('new ');
if (_name(ast.aliasId) in builtinMap) {
this.emit(`${builtinMap[_name(ast.aliasId)]}`);
} else {
let clientName = this.getRealClientName(_name(ast.aliasId));
this.emit(clientName);
}
this.emit('(');
for (let i = 0; i < ast.args.length; i++) {
this.visitExpr(ast.args[i], level, env);
if (i !== ast.args.length - 1) {
this.emit(', ');
}
}
this.emit(')');
}
visitInstanceCall(ast, level, env = {}) {
assert.equal(ast.left.type, 'instance_call');
const method = _name(ast.left.propertyPath[0]);
let methodName = _upperFirst(method);
if (ast.builtinModule && this.builtin[ast.builtinModule] && this.builtin[ast.builtinModule][method]) {
this.builtin[ast.builtinModule][method](ast, level, env);
} else {
if (env.isAsyncMode && ast.isAsync) {
methodName += 'Async';
this.emit('await ');
}
if (ast.left.id.tag === DSL.Tag.Tag.VID) {
this.emit(`this.${_vid(ast.left.id)}`);
} else {
this.emit(`${_avoidReserveName(_name(ast.left.id))}`);
}
this.emit(`.${methodName}(`);
for (let i = 0; i < ast.args.length; i++) {
const expr = ast.args[i];
if (expr.type === 'variable' && expr.needCast) {
env.groupOp = true;
this.visitExpr(expr, level, env);
this.emit('.ToMap()');
} else {
this.visitExpr(expr, level, env);
}
if (i !== ast.args.length - 1) {
this.emit(', ');
}
}
this.emit(')');
}
}
visitStaticCall(ast, level, env) {
assert.equal(ast.left.type, 'static_call');
if (ast.left.id.type === 'builtin_module') {
this.visitBuiltinStaticCall(ast, level, env);
return;
}
const aliasId = _name(ast.left.id);
let realClsName = this.getRealClientName(aliasId);
this.emit(`${realClsName ? `${realClsName}` : ''}.${_upperFirst(_name(ast.left.propertyPath[0]))}`);
this.visitArgs(ast, level, env);
}
visitBuiltinStaticCall(ast, level, env) {
const moduleName = _name(ast.left.id);
const builtiner = this.builtin[moduleName];
if (!builtiner) {
throw new Error('un-implemented');
}
const func = _name(ast.left.propertyPath[0]);
builtiner[func](ast, level, env);
}
visitArgs(ast, level, env = {}) {
this.emit('(');
for (let i = 0; i < ast.args.length; i++) {
const expr = ast.args[i];
if (expr.needCast) {
env.groupOp = true;
this.visitExpr(expr, level, env);
this.emit('.ToMap()');
} else {
this.visitExpr(expr, level, env);
}
if (i !== ast.args.length - 1) {
this.emit(', ');
}
}
this.emit(')');
}
visitMethodCall(ast, level, env = {}) {
assert.equal(ast.left.type, 'method_call');
let wrapName = _upperFirst(_name(ast.left.id));
// builtin 方法的返回值是不为null的,所以需要排除出去
if (wrapName.startsWith('$') && this.builtin[wrapName]) {
const method = wrapName.replace('$', '');
this.builtin[wrapName][method](ast, level, env);
return;
}
if (env.isAsyncMode && ast.isAsync) {
this.emit('await ');
wrapName += 'Async';
}
if (ast.isStatic) {
this.emit(`${wrapName}(`);
} else {
this.emit(`${wrapName}(`);
}
for (let i = 0; i < ast.args.length; i++) {
const expr = ast.args[i];
if (expr.needCast) {
env.groupOp = true;
this.visitExpr(expr, level, env);
this.emit('.ToMap()');
} else {
env.groupOp = false;
this.visitExpr(expr, level, env);
}
if (i !== ast.args.length - 1) {
this.emit(', ');
}
}
this.emit(')');
}
visitIf(ast, level, env = {}) {
assert.equal(ast.type, 'if');
this.emit('if (', level);
let wrapName = '';
if (ast.condition.left && ast.condition.left.id) {
wrapName = _upperFirst(_name(ast.condition.left.id));
}
// builtin 方法的返回值是不为null的,所以需要排除出去
// Async方法需要().Value,同步方法直接.Value
if (ast.condition.type && ast.condition.type === 'call' && ast.condition.inferred.name === 'boolean' &&
ast.condition.left && ast.condition.left.type === 'method_call' && ast.condition.isAsync &&
!wrapName.startsWith('$') && !this.builtin[wrapName]) {
this.emit('(');
}
this.visitExpr(ast.condition, level + 1, env);
if (ast.condition.type && ast.condition.type === 'call' && ast.condition.inferred.name === 'boolean' &&
ast.condition.left && ast.condition.left.type === 'method_call' &&
!wrapName.startsWith('$') && !this.builtin[wrapName]) {
if (ast.condition.isAsync) {
this.emit(').Value');
} else {
this.emit('.Value');
}
}
if (ast.condition.type === 'variable') {
this.emit('.Value');
}
if (ast.condition.type === 'not' && ast.condition.expr.type === 'variable') {
this.emit('.Value');
}
this.emit(')\n');
this.emit('{\n', level);
this.visitStmts(ast.stmts, level + 1, env);
this.emit('}\n', level);
if (ast.elseIfs) {
ast.elseIfs.forEach((branch) => {
this.emit('else if (', level);
env.groupOp = false;
this.visitExpr(branch.condition, level, env);
this.emit(')\n');
this.emit(`{\n`, level);
this.visitStmts(branch.stmts, level + 1, env);
this.emit('}\n', level);
});
}
if (ast.elseStmts) {
this.emit('else\n', level);
this.emit('{\n', level);
for (let i = 0; i < ast.elseStmts.stmts.length; i++) {
this.visitStmt(ast.elseStmts.stmts[i], level + 1, env);
}
if (ast.elseStmts.stmts.length === 0) {
const comments = DSL.comment.getBetweenComments(this.comments, ast.elseStmts.tokenRange[0], ast.elseStmts.tokenRange[1]);
this.visitComments(comments, level + 1);
}
this.emit('}\n', level);
}
}
visitAssign(ast, level, env) {
let leftName;
leftName = ast.left.id && _name(ast.left.id);
if (leftName === '__response') {
leftName = RESPONSE;
} else if (leftName === '__request') {
leftName = REQUEST;
} else {
leftName = _avoidReserveName(leftName);
}
if (ast.left.type === 'id') {
this.emit(`${leftName}`, level);
} else if (ast.left.type === 'property_assign' || ast.left.type === 'property') {
this.emit(`${leftName}`, level);
let isMapNext = ast.left.id.inferred && ast.left.id.inferred.type === 'map';
for (let i = 0; i < ast.left.propertyPath.length; i++) {
if (!isMapNext) {
this.emit(`.${_upperFirst(_name(ast.left.propertyPath[i]))}`);
} else {
this.emit(`["${_name(ast.left.propertyPath[i])}"]`);
}
isMapNext = ast.left.propertyPathTypes[i].type === 'map';
}
} else if (ast.left.type === 'virtualVariable') { // vid
this.emit(`this._${_lowerFirst(_name(ast.left.vid).substr(1))}`, level);
} else if (ast.left.type === 'variable') {
this.emit(`${leftName}`, level);
} else if (ast.left.type === 'array_access') {
this.visitArrayAccess(ast.left, level, env, true);
} else if (ast.left.type === 'map_access') {
let expr;
if (ast.left.id.tag === DSL.Tag.Tag.VID) {
expr = `this.${_vid(ast.left.id)}`;
} else {
expr = `${_name(ast.left.id)}`;
}
if (ast.left.propertyPath && ast.left.propertyPath.length) {
var current = ast.left.id.inferred;
for (let i = 0; i < ast.left.propertyPath.length; i++) {
var name = _name(ast.left.propertyPath[i]);
if (current.type === 'model') {
expr += `.${_upperFirst(name)}`;
} else {
expr += `["${name}"]`;
}
current = ast.left.propertyPathTypes[i];
}
}
this.emit(`${expr}[`, level);
this.visitExpr(ast.left.accessKey, level);
this.emit(`]`);
} else {
throw new Error('unimpelemented');
}
this.emit(' = ');
if (ast.expr.needToReadable) {
this.emit('StreamUtils.BytesReadable(');
this.used.push('Darabonba.Utils');
}
if (ast.expr.type === 'object') {
// paser give error inferred
let expr = ast.expr;
expr.inferred = ast.left.inferred;
this.visitExpr(expr, level, env);
} else {
this.visitExpr(ast.expr, level, env);
}
if (ast.expr.needToReadable) {
this.emit(')');
}
this.emit(';\n');
}
visitThrow(ast, level, env) {
this.emit('throw ', level);
if (ast.expr.type === 'construct_model') {
this.visitConstructModel(ast.expr, level, env);
this.emit(';\n');
} else {
this.emit(`new DaraException(`);
this.used.push('Darabonba.Exceptions');
this.visitObject(ast.expr, level);
this.emit(');\n');
}
}
visitObject(ast, level, env) {
assert.equal(ast.type, 'object');
if (ast.fields.length === 0) {
this.emit(`new `);
this.visitType(ast.inferred, level, env);
this.emit('()');
let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]);
if (comments.length > 0) {
this.emit('\n');
this.emit('{\n', level);
this.visitComments(comments, level + 1);
this.emit('}', level);
} else {
this.emit('{}');
}
return;
}
var hasExpandField = false;
for (let i = 0; i < ast.fields.length; i++) {
const field = ast.fields[i];
if (field.type === 'expandField') {
hasExpandField = true;
break;
}
}
if (!hasExpandField) {
this.emit('new ');
this.visitType(ast.inferred, level, env);
this.emit('\n');
this.emit('{\n', level);
for (var i = 0; i < ast.fields.length; i++) {
this.visitObjectField(ast.fields[i], level + 1, env);
}
//find the last item's back comment
let comments = DSL.comment.getBetweenComments(this.comments, ast.fields[i - 1].tokenRange[0], ast.tokenRange[1]);
this.visitComments(comments, level + 1);
this.emit('}', level);
return;
}
var all = [];
// 分段
var current = [];
for (let i = 0; i < ast.fields.length; i++) {
const field = ast.fields[i];
if (field.type === 'objectField') {
current.push(field);
} else {
if (current.length > 0) {
all.push(current);
}
all.push(field);
current = [];
}
}
if (current.length > 0) {
all.push(current);
}
this.emit(`ConverterUtils.Merge<${this._type(ast.inferred.valueType.name)}>\n`);
this.emit('(\n', level);
for (let i = 0; i < all.length; i++) {
const item = all[i];
if (Array.isArray(item)) {
this.emit(`new Dictionary<${this._type(ast.inferred.keyType.name)}, ${this._type(ast.inferred.valueType.name)}>()\n`, level + 1);
this.emit('{\n', level + 1);
for (var j = 0; j < item.length; j++) {
this.visitObjectField(item[j], level + 2, env);
}
this.emit('}', level + 1);
} else {
this.emit('', level + 1);
this.visitExpr(item.expr, level + 1, env);
}
if (i < all.length - 1) {
this.emit(',');
}
this.emit('\n');
}
this.emit(')', level);
}
visitObjectField(ast, level, env) {
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
if (ast.type === 'objectField') {
var key = _name(ast.fieldName) || _string(ast.fieldName);
this.emit(`{"${key}", `, level);
this.visitObjectFieldValue(ast.expr, level, env);
} else if (ast.type === 'expandField') {
// // TODO: more cases
// this.emit(`...`, level);
// this.visitExpr(ast.expr, level);
} else {
throw new Error('unimpelemented');
}
this.emit('},\n');
}
visitObjectFieldValue(ast, level, env) {
this.visitExpr(ast, level, env);
}
visitPropertyAccess(ast, level, env) {
assert.equal(ast.type, 'property_access');
var id = _name(ast.id);
var expr = '';
if (id === '__response') {
expr += RESPONSE;
} else if (id === '__request') {
expr += REQUEST;
} else {
expr += _avoidReserveName(id);
}
var current = ast.id.inferred;
for (var i = 0; i < ast.propertyPath.length; i++) {
var name = _name(ast.propertyPath[i]);
if (current.type === 'model') {
expr += `.${_upperFirst(name)}`;
} else if (current.type === 'map') {
expr += `.Get("${name}")`;
} else {
expr += `["${name}"]`;
}
current = ast.propertyPathTypes[i];
}
this.emit(expr);
}
visitReturn(ast, level, env = {}) {
assert.equal(ast.type, 'return');
this.emit('return ', level);
if (!ast.expr) {
this.emit(';\n');
return;
}
if (ast.needCast) {
this.emit('Darabonba.Model.ToObject<');
this.visitType(ast.expectedType);
this.emit('>(');
}
if (ast.expr && ast.expr.type === 'object' && env && env.returnType && _name(env.returnType) === 'object') {
env.castToObject = true;
}
this.visitExpr(ast.expr, level, env);
if (ast.needCast) {
this.emit(')');
}
this.emit(';\n');
}
visitRetry(ast, level) {
assert.equal(ast.type, 'retry');
this.emit(`throw new DaraRetryableException(${REQUEST}, ${RESPONSE});\n`, level);
}
visitWhile(ast, level, env) {
assert.equal(ast.type, 'while');
this.emit('\n');
this.emit('while (', level);
this.visitExpr(ast.condition, level + 1, env);
this.emit(') {\n');
this.visitStmts(ast.stmts, level + 1, env);
this.emit('}\n', level);
}
visitFor(ast, level, env) {
assert.equal(ast.type, 'for');
this.emit('\n');
this.emit(`foreach (var ${_avoidReserveName(_name(ast.id))} in `, level);
this.visitExpr(ast.list, level + 1, env);
this.emit(') {\n');
this.visitStmts(ast.stmts, level + 1, env);
this.emit('}\n', level);
}
visitExtendOn(extendOn, type = 'model') {
if (!extendOn) {
if (type === 'model') {
return this.emit('Darabonba.Model');
}
this.emit('DaraException');
this.used.push('Darabonba.Exceptions');
return;
}
let namespace = this.namespace;
let modelName = _name(extendOn);
let extendType = 'Models';
if (this.predefined[modelName] && this.predefined[modelName].isException) {
extendType = 'Exceptions';
}
if (extendOn.type === 'moduleModel') {
const [moduleId, ...rest] = extendOn.path;
namespace = this.moduleClass.get(moduleId.lexeme).namespace;
modelName = rest.map((item) => {
return item.lexeme;
}).join('.');
const usedEx = this.usedExternException.get(moduleId.lexeme);
if (usedEx && usedEx.has(modelName)) {
type = 'Exceptions';
}
} else if (extendOn.type === 'subModel') {
const [moduleId, ...rest] = extendOn.path;
modelName = [moduleId.lexeme, ...rest.map((item) => {
return item.lexeme;
})].join('.');
} else if (extendOn.idType === 'builtin_model') {
if (extendOn.lexeme === '$ResponseError') {
this.emit(this.getRealModelName('Darabonba.Exceptions', 'DaraResponseException'));
}
if (extendOn.lexeme === '$Error') {
this.emit(this.getRealModelName('Darabonba.Exceptions', 'DaraException'));
}
} else {
if (extendOn.moduleName) {
this.emit(`${extendOn.moduleName}.`);
}
this.emit(this.getRealModelName(`${namespace}.${extendType}`, modelName, extendType));
}
}
visitEcxceptionBody(ast, level, exceptionName, env) {
assert.equal(ast.type, 'exceptionBody');
let node;
for (let i = 0; i < ast.nodes.length; i++) {
node = ast.nodes[i];
const fieldName = _name(node.fieldName);
// 父类里有的字段如果重复写,则表示new(隐藏父类)而不是override(覆盖父类)
if (!exceptionFields.includes(fieldName)) {
let comments = DSL.comment.getFrontComments(this.comments, node.tokenRange[0]);
this.visitComments(comments, level);
this.emit('public ', level);
this.visitFieldType(node.fieldValue, level, exceptionName, fieldName);
this.emit(` ${_escape(_upperFirst(_name(node.fieldName)))}`);
this.emit(' { get; set; }\n');
}
if (node.fieldValue.type === 'modelBody') {
this.emit(`public class ${exceptionName}${_upperFirst(node.fieldName.lexeme)} : Darabonba.Model\n`, level);
this.emit('{\n', level);
this.visitModelBody(node.fieldValue, level + 1, env, exceptionName + _upperFirst(node.fieldName.lexeme));
this.emit('}\n', level);
}
}
if (node) {
//find the last node's back comment
let comments = DSL.comment.getBetweenComments(this.comments, node.tokenRange[0], ast.tokenRange[1]);
this.visitComments(comments, level);
}
if (ast.nodes.length === 0) {
//empty block's comment
let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]);
this.visitComments(comments, level);
}
}
visitExceptions(ast, filepath, level) {
const exceptions = ast.moduleBody.nodes.filter((item) => {
return item.type === 'exception';
});
const exDir = path.join(path.dirname(filepath), 'Exceptions');
for (let i = 0; i < exceptions.length; i++) {
this.used.push('System');
this.used.push('System.IO');
this.used.push('System.Collections');
this.used.push('System.Collections.Generic');
const exceptionName = _avoidReserveName(exceptions[i].exceptionName.lexeme);
const realExceptionName = `${exceptionName}Exception`;
this.fileName = realExceptionName;
this.eachException(exceptions[i], realExceptionName, level + 1);
this.exceptionAfter();
const modelFilePath = path.join(exDir, `${this.fileName ? this.fileName : exceptionName}.cs`);
this.save(modelFilePath);
}
}
visitException(ast, exceptionName, extendOn, level, env) {
this.emit(`public class ${exceptionName} : `, level);
this.visitExtendOn(extendOn, 'exception');
this.emit('\n');
this.emit('{\n', level);
this.visitEcxceptionBody(ast.exceptionBody, level + 1, exceptionName, env);
this.emit(`\n`);
for (let i = 0; i < ast.exceptionBody.nodes.length; i++) {
const node = ast.exceptionBody.nodes[i];
if (node.fieldValue.type === 'modelBody') {
this.emit(`public class ${exceptionName}${_upperFirst(node.fieldName.lexeme)} : Darabonba.Model\n`, level + 1);
this.emit('{\n', level + 1);
this.visitModelBody(node.fieldValue, level + 2, env, exceptionName + _upperFirst(node.fieldName.lexeme));
this.emit('}\n', level + 1);
}
}
this.emit('}\n\n', level);
}
/*******************************************************/
eachException(exception, realExceptionName, level, env) {
assert.equal(exception.type, 'exception');
const exceptionName = realExceptionName ? realExceptionName : _avoidReserveName(_name(exception.exceptionName));
this.visitAnnotation(exception.annotation, level);
let comments = DSL.comment.getFrontComments(this.comments, exception.tokenRange[0]);
this.visitComments(comments, level);
this.visitException(exception, exceptionName, exception.extendOn, level, env);
}
eachModel(ast, modelName, level, predefined) {
assert.equal(ast.type, 'model');
const env = {
predefined
};
// const modelName = _upperFirst(_name(ast.modelName));
this.emit(`public class ${modelName} : `, level);
this.visitExtendOn(ast.extendOn);
this.emit(' {\n');
this.visitModelBody(ast.modelBody, level + 1, env, modelName);
this.emit('}\n\n', level);
}
eachAPI(ast, level, env = {}) {
this.visitAnnotation(ast.annotation, level);
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
this.emit('public ', level);
let apiName = _upperFirst(_name(ast.apiName));
if (env.isAsyncMode) {
this.emit('async ');
apiName += 'Async';
}
env.returnType = ast.returnType;
this.visitReturnType(ast, 0, env);
this.emit(apiName);
this.visitParams(ast.params, level, env);
this.emit('\n');
this.emit('{\n', level);
// Validator
// for (var i = 0; i < ast.params.params.length; i++) {
// const param = ast.params.params[i];
// if (_name(param.paramType) && !DSL.util.isBasicType(_name(param.paramType))) {
// this.emit(`${_avoidReserveName(param.paramName.lexeme)}.Validate();\n`, level + 1);
// }
// }
let baseLevel = ast.runtimeBody ? level + 2 : level;
// api level
if (ast.runtimeBody) {
this.visitRuntimeBefore(ast.runtimeBody, level + 1, env);
}
// temp level
this.visitAPIBody(ast.apiBody, baseLevel + 1, env);
if (env.isAsyncMode) {
this.emit(`Darabonba.Response ${RESPONSE} = await Core.DoActionAsync(${REQUEST}`, baseLevel + 1);
} else {
this.emit(`Darabonba.Response ${RESPONSE} = Core.DoAction(${REQUEST}`, baseLevel + 1);
}
if (ast.runtimeBody) {
this.emit(', runtime_');
}
this.emit(');\n');
if (ast.runtimeBody) {
this.emit(`_lastRequest = ${REQUEST};\n`, baseLevel + 1);
this.emit(`_lastResponse = ${RESPONSE};\n`, baseLevel + 1);
}
if (ast.returns) {
this.visitReturnBody(ast.returns, baseLevel + 1, env);
} else {
this.visitDefaultReturnBody(baseLevel + 1, env);
}
if (ast.runtimeBody) {
this.visitRuntimeAfter(ast.runtimeBody, level + 1);
}
this.emit('}\n', level);
}
importBefore(level) {
// Nothing
}
eachImport(imports, usedModels, innerModule, filepath, level) {
this.imports = {};
if (imports.length === 0) {
return;
}
if (!this.config.pkgDir) {
throw new Error(`Must specific pkgDir when have imports`);
}
let lock;
const lockPath = path.join(this.config.pkgDir, '.libraries.json');
if (fs.existsSync(lockPath)) {
lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
}
for (let i = 0; i < imports.length; i++) {
const item = imports[i];
const aliasId = item.lexeme;
const main = item.mainModule;
const inner = item.module;
let moduleDir;
if (this.config.libraries) {
moduleDir = main ? this.config.libraries[main] : this.config.libraries[aliasId];
}
const innerPath = item.innerPath;
const importNameSpace = _nameSpace(path.join(path.dirname(filepath), _upperPath(innerPath)));
if (!moduleDir && innerPath) {
let csPath = innerPath.replace(/(\.tea)$|(\.spec)$|(\.dara)$/gi, '');
const className = this.getInnerClient(aliasId, csPath); // Common,Util,User
// 这里设置 path.join(path.dirname(csPath),`${className}.cs`))而不是cspath.cs,因为文件名需要和类名保持一致
innerModule.set(aliasId, path.join(path.dirname(csPath), `${className}.cs`));
const aliasName = this.getAliasName(`${this.namespace}.${importNameSpace}`, className, aliasId);
this.moduleClass.set(aliasId, {
namespace: `${this.namespace}.${importNameSpace}`.replace(/\.$/, ''),
className: className,
aliasName: aliasName
});
this.imports[aliasId] = {
namespace: `${this.namespace}.${importNameSpace}`.replace(/\.$/, ''),
release: '',
className: aliasId
};
continue;
}
let targetPath = '';
if (moduleDir.startsWith('./') || moduleDir.startsWith('../')) {
targetPath = path.join(this.config.pkgDir, moduleDir);
} else if (moduleDir.startsWith('/')) {
targetPath = moduleDir;
} else {
targetPath = path.join(this.config.pkgDir, lock[moduleDir]);
}
const pkgPath = fs.existsSync(path.join(targetPath, 'Teafile')) ? path.join(targetPath, 'Teafile') : path.join(targetPath, 'Darafile');
const pkg = JSON.parse(fs.readFileSync(pkgPath));
let csharpConfig = pkg.csharp;
pkg.releases = pkg.releases || {};
if (!csharpConfig) {
throw new Error(`The '${aliasId}' has no csharp supported.`);
}
csharpConfig.release = pkg.releases.csharp;
this.imports[aliasId] = csharpConfig;
let className = csharpConfig.className || 'Client';
let namespace = csharpConfig.namespace;
if (inner && pkg.exports[inner]) {
let csPath = path.dirname(pkg.exports[inner]);
const arr = csPath.split(path.sep).slice(1);
const [filename] = pkg.exports[inner].split(path.sep).slice(-1);
arr.map(key => {
namespace += '.' + _upperFirst(key);
});
const processedFilename = filename === '.' ? '' : _upperFirst(filename.toLowerCase().replace(/(\.tea)$|(\.spec)$|(\.dara)$/gi, ''));
const exportsName = csharpConfig.exports ? csharpConfig.exports[inner] : '';
className = exportsName ? exportsName : `${processedFilename}Client`;
}
const aliasName = this.getAliasName(namespace, className, aliasId);
this.moduleClass.set(aliasId, {
namespace: namespace,
className: className,
aliasName: aliasName
});
this.moduleTypedef[aliasId] = csharpConfig.typedef;
}
}
importAfter() {
// Nothing
}
moduleBefore() {
// Nothing
}
modelAfter() {
this.emit('}\n');
}
exceptionAfter() {
this.emit('}\n');
}
interfaceEachAPI(ast, level) {
this.visitAnnotation(ast.annotation, level);
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
let apiName = _upperFirst(_name(ast.apiName));
let env = {};
env.returnType = ast.returnType;
this.emit('', level);
this.visitReturnType(ast, 0, env);
this.emit(apiName);
this.visitParams(ast.params, level, env);
this.emit(';\n');
}
InterfaceEachFunction(ast, level) {
let wrapName = _upperFirst(_name(ast.functionName));
this.visitAnnotation(ast.annotation, level);
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
this.emit('', level);
let env = {};
env.returnType = ast.returnType;
this.visitReturnType(ast, 0, env);
this.emit(`${wrapName}`);
if (this.isExec && wrapName === 'Main') {
this.emit('(string[] args)');
} else {
this.visitParams(ast.params, level, env);
}
this.emit(';\n');
}
interfaceAfter(level) {
this.emit('}\n', level);
this.emit('}\n');
}
apiBefore(level, extendParam, filepath, main) {
this.used.push('System');
this.used.push('System.IO');
this.used.push('System.Collections');
this.used.push('System.Collections.Generic');
this.used.push('System.Threading.Tasks');
this.used.push('Darabonba');
this.used.push('Darabonba.Utils');
let className = this.className;
const fileInfo = path.parse(filepath);
if (!main) {
const beginNotes = DSL.note.getNotes(this.notes, 0, this.ast.moduleBody.nodes[0].tokenRange[0]);
const clientNote = beginNotes.find(note => note.note.lexeme === '@clientName');
if (clientNote) {
className = clientNote.arg.value.string;
} else {
className = `${_upperFirst(fileInfo.name.toLowerCase())}Client`;
}
}
this.emit(`public class ${className} `, level + 1);
if (extendParam.extend && extendParam.extend.length > 0) {
const extendsModuleName = this.getRealClientName(this.ast.extends.lexeme);
const extendsInterfaces = extendParam.extend.filter(item => item.startsWith('I'));
this.emit(`: ${extendsModuleName}${extendsInterfaces.join(', ') === '' ? '' : ', '}${extendsInterfaces.join(', ')}
{
`);
} else {
this.emit(`
{
`);
}
}
// init(level) {
// // Nothing
// }
apiAfter() {
// Nothing
}
wrapBefore() {
}
eachFunction(ast, level, env = {}) {
let wrapName = _upperFirst(_name(ast.functionName));
if (wrapName === 'Main' && env.isAsyncMode && !(this.isExec && this.asyncOnly)) {
return;
}
this.visitAnnotation(ast.annotation, level);
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
this.emit('public ', level);
if (ast.isStatic) {
this.emit('static ');
}
if (env.isAsyncMode) {
this.emit('async ');
if (wrapName !== 'Main') {
wrapName += 'Async';
}
}
env.returnType = ast.returnType;
this.visitReturnType(ast, 0, env);
this.emit(`${wrapName}`);
if (this.isExec && wrapName === 'Main') {
this.emit('(string[] args)');
} else {
this.visitParams(ast.params, level, env);
}
this.emit('\n');
this.emit('{\n', level);
if (ast.functionBody) {
this.visitWrapBody(ast.functionBody, level + 1, env);
} else {
this.emit('throw new NotImplementedException();\n', level + 1);
}
this.emit('}\n', level);
}
wrapAfter() {
// Nothing
}
moduleAfter() {
this.emit(`
}
}\n`, 0);
}
isIterator(returnType) {
if (returnType.type === 'iterator' || returnType.type === 'asyncIterator') {
return true;
}
return false;
}
visitWrapBody(ast, level, env) {
assert.equal(ast.type, 'functionBody');
this.visitStmts(ast.stmts, level, env);
}
visitInit(ast, types, main, filepath, level, env) {
assert.equal(ast.type, 'init');
types.forEach((item) => {
let comments = DSL.comment.getFrontComments(this.comments, item.tokenRange[0]);
this.visitComments(comments, level + 2);
this.emit('protected ', level + 2);
if (item.value.type || item.value.idType) {
this.visitType(item.value);
this.emit(' ');
} else if (this.imports[_name(item.value)]) {
const realClsName = this.getRealClientName(_name(item.value));
this.emit(`${realClsName || 'Client'} `);
} else {
this.emit(`${this._type(_name(item.value))} `);
}
this.emit(`${_vid(item.vid)};\n`);
});
this.emit('\n');
this.visitAnnotation(ast.annotation, level + 2);
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level + 2);
let className = this.className;
if (!main) {
const fileInfo = path.parse(filepath);
const beginNotes = DSL.note.getNotes(this.notes, 0, this.ast.moduleBody.nodes[0].tokenRange[0]);
const clientNote = beginNotes.find(note => note.note.lexeme === '@clientName');
if (clientNote) {
className = clientNote.arg.value.string;
} else {
className = `${_upperFirst(fileInfo.name.toLowerCase())}Client`;
}
}
this.emit(`public ${className || 'Client'}`, level + 2);
this.visitParams(ast.params);
if (ast.initBody && ast.initBody.stmts[0] && ast.initBody.stmts[0].type === 'super') {
this.emit(`: base(`);
for (let i = 0; i < ast.initBody.stmts[0].args.length; i++) {
if (i > 0) {
this.emit(', ');
}
this.visitExpr(ast.initBody.stmts[0].args[i], level, env);
}
this.emit(`)`);
}
this.emit('\n');
this.emit('{\n', level + 2);
if (ast.initBody) {
this.visitStmts(ast.initBody, level + 3, {
isAsyncMode: false
});
}
this.emit('}\n\n', level + 2);
}
visitAnnotation(annotation, level) {
if (!annotation || !annotation.value) {
return;
}
let comments = DSL.comment.getFrontComments(this.comments, annotation.index);
this.visitComments(comments, level);
var ast = Annotation.parse(annotation.value);
var description = ast.items.find((item) => {
return item.type === 'description';
});
var summary = ast.items.find((item) => {
return item.type === 'summary';
});
var _return = ast.items.find((item) => {
return item.type === 'return';
});
var deprecated = ast.items.find((item) => {
return item.type === 'deprecated';
});
var params = ast.items.filter((item) => {
return item.type === 'param';
}).map((item) => {
return {
name: item.name.id,
text: item.text.text
};
});
var throws = ast.items.filter((item) => {
return item.type === 'throws';
}).map((item) => {
return item.text.text;
});
const deprecatedText = deprecated ? deprecated.text.text : '';
const summaryText = summary ? summary.text.text : '';
const descriptionText = description ? description.text.text.trimEnd() : '';
const returnText = _return ? _return.text.text.trimEnd() : '';
let hasNextSection = false;
if (deprecated) {
this.emit(`/// <term><b>Deprecated</b></term>\n`, level);
this.emit(`/// \n`, level);
deprecatedText.trimEnd().split('\n').forEach((line) => {
this.emit(`/// ${line}\n`, level);
});
hasNextSection = true;
}
if (summaryText !== '') {
if (hasNextSection) {
this.emit(`/// \n`, level);
}
this.emit(`/// <term><b>Summary:</b></term>\n`, level);
this.emit(`/// <summary>\n`, level);
const summaryTexts = md2Xml(summaryText);
summaryTexts.split('\n').forEach((line) => {
this.emit(`/// ${line}\n`, level);
});
this.emit(`/// </summary>\n`, level);
hasNextSection = true;
}
if (descriptionText !== '') {
if (hasNextSection) {
this.emit(`/// \n`, level);
}
this.emit(`/// <term><b>Description:</b></term>\n`, level);
this.emit(`/// <description>\n`, level);
const descriptionTexts = md2Xml(descriptionText);
descriptionTexts.split('\n').forEach((line) => {
this.emit(`/// ${line}\n`, level);
});
this.emit(`/// </description>\n`, level);
hasNextSection = true;
}
if (params.length > 0) {
if (hasNextSection) {
this.emit(`/// \n`, level);
}
params.forEach((item) => {
this.emit(`/// <param name="${item.name}">\n`, level);
item.text.trimEnd().split('\n').forEach((line) => {
this.emit(`/// ${line}\n`, level);
});
this.emit(`/// </param>\n`, level);
});
hasNextSection = true;
}
if (returnText) {
if (hasNextSection) {
this.emit(`/// \n`, level);
}
this.emit(`/// <returns>\n`, level);
returnText.split('\n').forEach((line) => {
this.emit(`/// ${line}\n`, level);
});
this.emit(`/// </returns>\n`, level);
hasNextSection = true;
}
if (throws.length > 0) {
if (hasNextSection) {
this.emit(`/// \n`, level);
}
throws.forEach((item, index) => {
this.emit(`/// <term><b>Exception:</b></term>\n`, level);
item.trimEnd().split('\n').forEach((line) => {
this.emit(`/// ${line}\n`, level);
});
if (index < throws.length - 1) {
this.emit(`/// \n`, level);
}
});
}
if (deprecated) {
this.emit(`[Obsolete("`, level);
const lines = deprecatedText.trimEnd().split('\n');
lines.forEach((line, index) => {
if (index === lines.length - 1) {
this.emit(`${line}`);
} else {
this.emit(`${line}\\n`);
}
});
this.emit(`")]\n`);
}
}
visitComments(comments, level) {
comments.forEach(comment => {
this.emit(`${comment.value}`, level);
this.emit(`\n`);
});
}
visitConsoleCSProj(uniqueUsed) {
let csprojPath = path.join(this.csprojOutputDir, (this.config.packageInfo.name || 'client') + '.csproj');
let json = {};
json = parse(fs.readFileSync(path.join(__dirname, 'files', 'consoleCsproj.tmpl')));
//填写包的基本信息
let propertyGroup = json.Project.PropertyGroup;
propertyGroup.forEach((property) => {
if (Object.hasOwn(property, 'RootNamespace')) {
property['RootNamespace'] = this.config.namespace;
}
});
let dependenciesClass = [];
if (uniqueUsed.includes('Newtonsoft.Json')) {
dependenciesClass.push('Newtonsoft.Json:13.0.1');
}
Object.keys(this.imports).forEach((key) => {
dependenciesClass.push(this.imports[key].release);
});
// 兜底依赖 Darabonba 的 1.0.0 版本
const teaVersion = '1.0.0';
dependenciesClass.push(`Darabonba:${teaVersion}`);
let currentItemGroup = {};
dependenciesClass.forEach((item, index) => {
let dependency = item.split(':');
currentItemGroup[dependency[0]] = dependency[1];
});
const newItemGroup = { ...currentItemGroup, ...this.packageManager };
//寻找用来写入的itemGroup, 避开有特殊参数的itemGroup
let itemGroup = {};
//添加或更新依赖包
Object.entries(newItemGroup || {}).forEach(([key, value]) => {
let writeReference = {};
itemGroup.PackageReference = itemGroup.PackageReference || [];
writeReference.$ = { 'Include': key, 'Version': value };
itemGroup.PackageReference.push(writeReference);
});
if (this.typedef) {
Object.keys(this.typedef).map(key => {
if (!this.typedef[key].package) {
return;
}
dependenciesClass.push(this.typedef[key].package);
});
}
json.Project.ItemGroup = itemGroup;
const builder = new xml2js.Builder();
const newCsproj = builder.buildObject(json);
fs.writeFileSync(csprojPath, Entities.decode(newCsproj));
}
visitCSProj(uniqueUsed) {
let csprojPath = path.join(this.csprojOutputDir, (this.config.packageInfo.name || 'client') + '.csproj');
let json = {};
// 保留本地已有的包
if (!fs.existsSync(csprojPath)) {
json = parse(fs.readFileSync(path.join(__dirname, 'files', 'csproj.tmpl')));
} else {
json = parse(fs.readFileSync(csprojPath));
}
//填写包的基本信息
let propertyGroup = json.Project.PropertyGroup;
let assemblyInfo = this.release.split(':');
propertyGroup.forEach((property) => {
if (Object.hasOwn(property, 'RootNamespace')) {
property['RootNamespace'] = this.config.namespace;
if (this.config.packageInfo && this.config.packageInfo.company) {
property['Authors'] = this.config.packageInfo.company;
} else if (this.config.maintainers && this.config.maintainers.name) {
property['Authors'] = this.config.maintainers.name;
}
if (this.config.packageInfo && this.config.packageInfo.description) {
property['Description'] = this.config.packageInfo.description;
}
if (this.config.packageInfo && this.config.packageInfo.property) {
Object.assign(property, this.config.packageInfo.property);
}
}
if (Object.hasOwn(property, 'AssemblyName')) {
property['AssemblyName'] = assemblyInfo[0];
}
// 支持 packageInfo 中配置当前包发布的版本(之前是<Version/>)
if (Object.hasOwn(property, 'Version') && this.packageVersion) {
property['Version'] = this.packageVersion;
}
});
let dependenciesClass = [];
if (uniqueUsed.includes('Newtonsoft.Json')) {
dependenciesClass.push('Newtonsoft.Json:13.0.1');
}
Object.keys(this.imports).forEach((key) => {
if (!this.imports[key].release) {
return;
}
dependenciesClass.push(this.imports[key].release);
});
if (this.typedef) {
Object.keys(this.typedef).map(key => {
if (!this.typedef[key].package) {
return;
}
dependenciesClass.push(this.typedef[key].package);
});
}
// 兜底依赖 Darabonba 的 1.0.0 版本
const teaVersion = '1.0.0';
dependenciesClass.push(`Darabonba:${teaVersion}`);
//寻找用来写入的itemGroup, 避开有特殊参数的itemGroup
let itemGroup = json.Project.ItemGroup;
let writeItem = {};
itemGroup.forEach((item) => {
if (!item.$) {
writeItem = item;
}
});
let newDependenciesClass = [];
if (this.packageManager) {
const needAddPackages = Object.entries(this.packageManager || {}).map(([key, value]) => `${key}:${value}`);
const needAddPackageName = new Set(needAddPackages.map(item => item.split(':')[0]));
newDependenciesClass = [...needAddPackages];
dependenciesClass.forEach(item => {
const [pkgName] = item.split(':');
if (!needAddPackageName.has(pkgName)) {
newDependenciesClass.push(item);
}
});
} else {
newDependenciesClass = dependenciesClass;
}
//添加或更新依赖包
newDependenciesClass.forEach((item, index) => {
if (item) {
let dependency = item.split(':');
//遍历所有的itemGroup,判断是否已存在该依赖
let writeReference = null;
itemGroup.forEach((group) => {
if (group.PackageReference) {
group.PackageReference.forEach((reference) => {
if (reference.$.Include === dependency[0]) {
writeReference = reference;
}
});
}
});
if (writeReference) {
writeReference.$.Version = dependency[1];
} else {
writeReference = {};
writeItem.PackageReference = writeItem.PackageReference || [];
writeReference.$ = { 'Include': dependency[0], 'Version': dependency[1] };
writeItem.PackageReference.push(writeReference);
}
}
});
const builder = new xml2js.Builder();
const newCsproj = builder.buildObject(json);
fs.writeFileSync(csprojPath, Entities.decode(newCsproj));
}
visitAssemblyInfo() {
let propertiesDir = path.join(this.csprojOutputDir, 'Properties');
if (!fs.existsSync(propertiesDir)) {
fs.mkdirSync(propertiesDir, {
recursive: true
});
}
let assemblyPath = path.join(propertiesDir, 'AssemblyInfo.cs');
let content = {};
if (!fs.existsSync(assemblyPath)) {
content = fs.readFileSync(path.join(__dirname, 'files', 'assemblyInfo.tmpl')).toString();
let params = {
title: this.config.packageInfo.title || '',
description: this.config.packageInfo.description || '',
company: this.config.packageInfo.company || '',
product: this.config.packageInfo.product || '',
guid: UUID.v1(),
version: this.release.split(':')[1],
copyRight: this.config.packageInfo.copyRight || '',
};
if (content !== '') {
content = render(content, params);
}
fs.writeFileSync(assemblyPath, content);
} else {
content = fs.readFileSync(assemblyPath).toString();
content = content.replace(/AssemblyVersion\("[\S\s].*?"\)/, `AssemblyVersion("${this.release.split(':')[1]}.0")`);
content = content.replace(/AssemblyFileVersion\("[\S\s].*?"\)/, `AssemblyFileVersion("${this.release.split(':')[1]}.0")`);
fs.writeFileSync(assemblyPath, content);
}
}
}
module.exports = Visitor;