lib/generator.js (2,031 lines of code) (raw):
'use strict';
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const DSL = require('@darabonba/parser');
const {
_name, _string, _subModelName, _vid,
_isBinaryOp, _modelName, _isBuiltinModel,
REQUEST, RESPONSE, CORE, _upperFirst, _avoidKeywords, _filedsAccess,
ERROR, MODEL, STREAM, UNRETRY_ERROR, RETRY_CONTEXT, SSE_EVENT,
RESP_ERROR, RETRY_OPTION, RUNTIME_OPTION, EXTEND_PARAM
} = require('./helper');
const getBuiltin = require('./builtin');
const { Tag } = require('@darabonba/parser/lib/tag');
const Annotation = require('@darabonba/annotation-parser');
class Visitor {
static get supportGenerateTest() {
return true;
}
constructor(option = {}) {
this.config = {
outputDir: '',
indent: ' ',
package: option.package,
clientName: option.clientName || 'Client',
...option
};
this.used = [];
this.output = '';
this.outputDir = option.outputDir;
if(!this.outputDir) {
throw Error('`option.outputDir` should not empty');
}
this.config.clientPath = path.join(this.outputDir, 'src', `${this.config.clientName}.php`);
this.modelDir = this.config.modelDirName || 'Models';
this.exceptionDir = this.config.exceptionDirName || 'Exceptions';
this.typedef = this.config.typedef;
this.__module = {};
this.innerModuleClassName = new Map();
this.__externModule = new Map();
this.classNamespace = new Map();
this.usedClass = new Map();
if (!this.outputDir) {
throw new Error('`option.outputDir` should not empty');
}
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, {
recursive: true
});
}
this.composer = {};
const composerPath = path.join(this.outputDir, 'composer.json');
if (fs.existsSync(composerPath)) {
try {
this.composer = JSON.parse(fs.readFileSync(composerPath));
} catch (err) {
throw new Error('invalid composer.json');
}
}
}
saveProjectFiles() {
const composerPath = path.join(this.outputDir, 'composer.json');
fs.writeFileSync(composerPath, JSON.stringify(this.composer, null, 2));
const projectFiles = ['.gitignore', '.php_cs.dist', 'autoload.php', 'bootstrap.php'];
projectFiles.map(file => {
const source = path.join(__dirname, '..', 'templates', `${file}.tmpl`);
const target = path.join(this.outputDir, file);
if(file === 'autoload.php') {
const content = fs.readFileSync(source, 'utf8');
fs.writeFileSync(target, content.replace(/\${namespace}/g, this.namespace.replace(/\\/, '\\\\')));
} else {
fs.copyFileSync(source, target);
}
});
}
initComposer() {
this.composer.name = this.composer.packageName || (this.config.packageInfo && this.config.packageInfo.name);
this.composer.type = 'library';
this.composer.description = this.composer.description || (this.config.packageInfo && this.config.packageInfo.desc);
this.composer.github = this.composer.github || (this.config.packageInfo && this.config.packageInfo.github);
this.composer.main = this.composer.main || 'src/Client.php';
this.composer.authors = this.composer.authors || [];
if(this.config.maintainers) {
let authors = [];
this.composer.authors.forEach(author => {
authors.push(`${author.name || ''}:${author.email || ''}`);
});
this.config.maintainers.forEach(maintainer => {
let name = maintainer.name ? maintainer.name : '';
let email = maintainer.email ? maintainer.email : '';
if(!authors.includes(`${name}:${email}`)) {
this.composer.authors.push({ name: name, email: email });
}
});
}
this.composer.license = this.composer.license || 'Apache-2.0';
if (!this.composer.autoload) {
this.composer.autoload = {
'psr-4': {}
};
const namsespace = this.config.package.split('.').join('\\');
this.composer.autoload['psr-4'][`${namsespace}\\`] = 'src';
}
if (!this.composer.scripts) {
this.composer.scripts = {
fixer: 'php-cs-fixer fix ./'
};
}
if (!this.composer.config) {
this.composer.config = {
'sort-packages': true,
'preferred-install': 'dist',
'optimize-autoloader': true
};
}
if (!this.composer.require) {
this.composer.require = {
'php': '>5.5',
'alibabacloud/darabonba': '^1.0.0',
};
}
Object.keys(this.requires).map(key => {
this.composer.require[key] = this.requires[key];
});
if(this.typedef) {
Object.keys(this.typedef).map(key => {
if(!this.typedef[key].package) {
return;
}
const [ name, version ] = this.typedef[key].package.split(':');
this.composer.require[name] = version;
});
}
if (!this.composer['prefer-stable']) {
this.composer['prefer-stable'] = true;
}
if (!this.composer.config) {
this.composer.config = {
'sort-packages': true,
'preferred-install': 'dist',
'optimize-autoloader': true
};
}
}
getInnerClient(aliasId, phpPath) {
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(phpPath);
return `${_upperFirst(fileInfo.name)}Client`;
}
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);
const filepath = ast.innerModule.get(aliasId);
this.visitModule(moduleAst, filepath, false, 0);
data = keys.next();
}
}
save(filepath) {
let targetPath = filepath;
if(path.resolve(filepath).startsWith(path.resolve(this.outputDir))) {
const baseDir = path.join(this.outputDir, 'src', path.sep);
filepath = filepath.replace(baseDir, '');
}
targetPath = path.join(this.outputDir, 'src', filepath);
const namespace = this.getClassNamespace(filepath);
const content = `<?php
// This file is auto-generated, don't edit it. Thanks.
namespace ${namespace};
${[...new Set(this.used)].join('\n')}
${this.output}`;
fs.mkdirSync(path.dirname(targetPath), {
recursive: true
});
fs.writeFileSync(targetPath, content);
this.output = '';
this.used = [];
this.usedClass = new Map();
}
emit(str, level) {
this.output += ' '.repeat(level * 2) + str;
}
visit(ast, level = 0) {
this.codeDir = path.dirname(this.config.clientPath);
this.classNamespace.set(this.config.clientPath, this.config.clientName);
this.visitModule(ast, this.config.clientPath, true, level);
this.initComposer();
this.saveProjectFiles();
}
getfullModelName(key, namespace = false) {
const fullModelNameArr = key.split('.');
return fullModelNameArr.map((m, i) => {
if (!namespace && i === fullModelNameArr.length - 1 && m.toLowerCase() === 'model') {
// If the model class name is 'model'
// add the '_' suffix.
return m + '_';
}
return _avoidKeywords(m);
}).join('\\');
}
getRealClientName(aliasId) {
const moduleInfo = this.moduleClass.get(aliasId);
if(!moduleInfo) {
return;
}
if(moduleInfo.aliasName) {
this.used.push(`use ${moduleInfo.namespace}\\${moduleInfo.className} as ${moduleInfo.aliasName};`);
return moduleInfo.aliasName;
}
this.used.push(`use ${moduleInfo.namespace}\\${moduleInfo.className};`);
return moduleInfo.className;
}
getRealModelName(fullModelName) {
if(fullModelName !== MODEL) {
const fullModelNameArr = fullModelName.split('\\');
fullModelName = fullModelNameArr.map((m, i) => {
if (i === fullModelNameArr.length - 1 && m.toLowerCase() === 'model') {
// If the model class name is 'model'
// add the '_' suffix.
return m + '_';
}
return _avoidKeywords(m);
}).join('\\');
}
let [ modelName ] = fullModelName.split('\\').slice(-1);
const existName = this.usedClass.get(modelName.toLowerCase());
if(existName && existName !== fullModelName) {
return `\\${fullModelName}`;
}
this.used.push(`use ${fullModelName};`);
this.usedClass.set(modelName.toLowerCase(), fullModelName);
return modelName;
}
visitExceptions(ast, filepath, level) {
const exs = ast.moduleBody.nodes.filter((item) => {
return item.type === 'exception';
});
const exDir = path.join(path.dirname(filepath), this.exceptionDir);
for (let i = 0; i < exs.length; i++) {
const exceptionName = _modelName(exs[i].exceptionName.lexeme);
const modelFilepath = path.join(exDir, `${exceptionName}Exception.php`);
const classNamespace = this.classNamespace.get(filepath);
this.modelSpace = exceptionName;
this.visitException(exs[i].exceptionBody, exceptionName, ast.extendOn, level);
this.save(modelFilepath);
if (this.predefined) {
Object.keys(this.predefined).filter((key) => {
return key.startsWith(exceptionName + '.');
}).map((key) => {
const modelDir = path.join(path.dirname(filepath), this.modelDir);
this.modelSpace = this.getfullModelName(key, true);
const realFullModelName = this.getfullModelName(key);
const [ modelName ] = realFullModelName.split('\\').slice(-1);
const realFullClassName = `${classNamespace}\\${this.modelDir}\\${realFullModelName}`;
this.usedClass.set(modelName, realFullClassName);
const subModelFilepath = path.join(modelDir, `${realFullModelName.split('\\').join(path.sep)}.php`);
this.visitModel(this.predefined[key].modelBody, modelName, this.predefined[key].extendOn, level);
this.save(subModelFilepath);
});
}
}
}
visitModels(ast, filepath, level) {
const models = ast.moduleBody.nodes.filter((item) => {
return item.type === 'model';
});
const modelDir = path.join(path.dirname(filepath), this.modelDir);
for (let i = 0; i < models.length; i++) {
const modelName = _modelName(models[i].modelName.lexeme);
const modelFilepath = path.join(modelDir, `${modelName}.php`);
const classNamespace = this.classNamespace.get(filepath);
this.modelSpace = modelName;
this.visitAnnotation(models[i].annotation, level);
let comments = DSL.comment.getFrontComments(this.comments, models[i].tokenRange[0]);
this.visitComments(comments, level);
this.visitModel(models[i].modelBody, modelName, models[i].extendOn, level);
this.save(modelFilepath);
if (this.predefined) {
Object.keys(this.predefined).filter((key) => {
return key.startsWith(modelName + '.');
}).map((key) => {
this.modelSpace = this.getfullModelName(key, true);
const realFullModelName = this.getfullModelName(key);
const [ modelName ] = realFullModelName.split('\\').slice(-1);
const realFullClassName = `${classNamespace}\\${this.modelDir}\\${realFullModelName}`;
this.usedClass.set(modelName, realFullClassName);
const subModelFilepath = path.join(modelDir, `${realFullModelName.split('\\').join(path.sep)}.php`);
this.visitModel(this.predefined[key].modelBody, modelName, this.predefined[key].extendOn, level);
this.save(subModelFilepath);
});
}
}
}
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, 'src', path.sep);
filepath = filepath.replace(baseDir, '');
}
const targetPath = path.join(this.outputDir, 'src', 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.parentModule = ast.extends;
this.comments = ast.comments;
this.notes = ast.notes;
this.usedExternException = ast.usedExternException;
this.requires = {};
this.moduleTypedef = {};
ast.innerModule = new Map();
this.moduleClass = new Map();
this.clientName = new Map();
if(this.overwrite(ast, filepath) === false) {
return;
}
if(main) {
this.clientName.set(this.config.clientName, true);
}
this.builtin = getBuiltin(this);
this.eachImport(ast.imports, ast.usedExternModel, filepath, ast.innerModule, level);
this.namespace = this.config.package.split('.').join('\\');
this.visitModels(ast, filepath, level);
this.visitExceptions(ast, filepath, level);
const apis = ast.moduleBody.nodes.filter((item) => {
return item.type === 'api';
});
if(apis.length > 0) {
this.used.push(`use ${CORE};`);
this.clientName.set('Dara', true);
}
this.visitAnnotation(ast.annotation, level);
// // models definition
this.apiBefore(main, filepath, level);
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, level);
}
let lastToken = 0;
for (let i = 0; i < apis.length; i++) {
if (i !== 0) {
this.emit('\n');
}
const apiNotes = DSL.note.getNotes(this.notes, lastToken, apis[i].tokenRange[0]);
const sse = apiNotes.find(note => note.note.lexeme === '@sse');
lastToken = apis[i].tokenRange[1];
this.eachAPI(apis[i], level + 1, sse && sse.arg.value);
}
this.functionBefore();
const functions = ast.moduleBody.nodes.filter((item) => {
return item.type === 'function';
});
for (let i = 0; i < functions.length; i++) {
if (i !== 0) {
this.emit('\n');
}
this.eachFunction(functions[i], level + 1);
}
this.moduleAfter();
if (this.config.exec) {
this.emitExec();
}
this.save(filepath);
this.saveInnerModule(ast, filepath);
}
emitExec() {
this.emit(`$path = __DIR__ . \\DIRECTORY_SEPARATOR . '..' . \\DIRECTORY_SEPARATOR . 'vendor' . \\DIRECTORY_SEPARATOR . 'autoload.php';
if (file_exists($path)) {
require_once $path;
}
${this.config.clientName}::main(array_slice($argv, 1));`);
}
visitComments(comments, level) {
comments.forEach(comment => {
this.emit(`${comment.value}`, level);
this.emit('\n');
});
}
visitAnnotation(annotation, level, bottom = true) {
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.trimEnd()
};
});
var throws = ast.items.filter((item) => {
return item.type === 'throws';
}).map((item) => {
return item.text.text.trimEnd();
});
var descriptionText = description ? description.text.text.trimEnd() : '';
var summaryText = summary ? summary.text.text.trimEnd() : '';
var returnText = _return ? _return.text.text.trimEnd() : '';
let hasNextSection = false;
this.emit('/**\n', level);
if (summaryText !== '') {
summaryText.split('\n').forEach((line) => {
this.emit(` * ${line}\n`, level);
});
hasNextSection = true;
}
if (descriptionText !== '') {
if (hasNextSection) {
this.emit(' * \n', level);
}
this.emit(' * @remarks\n', level);
descriptionText.split('\n').forEach((line) => {
this.emit(` * ${line}\n`, level);
});
hasNextSection = true;
}
if (deprecated) {
if (hasNextSection) {
this.emit(' * \n', level);
}
if (deprecated.text.text.trimEnd() === '') {
this.emit(' * @deprecated\n', level);
} else {
this.emit(` * @deprecated ${deprecated.text.text.trimEnd()}\n`, level);
}
hasNextSection = true;
}
if (params.length > 0) {
if (hasNextSection) {
this.emit(' * \n', level);
}
params.forEach((item) => {
this.emit(` * @param ${item.name} - ${item.text}\n`, level);
});
}
if (returnText !== '') {
this.emit(` * @returns ${returnText}\n`, level);
}
if (throws.length > 0) {
this.emit(' * \n', level);
throws.forEach((item) => {
this.emit(` * @throws ${item}\n`, level);
});
}
if(bottom) {
this.emit(' */', level);
this.emit('\n');
}
}
visitInit(ast, types, level) {
assert.equal(ast.type, 'init');
types.forEach((item) => {
let comments = DSL.comment.getFrontComments(this.comments, item.tokenRange[0]);
this.visitComments(comments, level + 1);
this.emit('/**\n', level + 1);
this.emit(' * @var ', level+ 1);
this.visitType(item.value);
this.emit('\n');
this.emit(' */\n', level + 1);
this.emit(`protected $${_vid(item.vid)};\n`, level + 1);
this.emit('\n');
});
this.emit('\n');
this.visitAnnotation(ast.annotation, level + 1);
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level + 1);
if (ast.initBody) {
this.emit('public function __construct', level + 1);
this.visitParams(ast.params, level);
this.emit('\n');
this.emit('{\n', level + 1);
this.visitStmts(ast.initBody, level + 2);
this.emit('}\n', level + 1);
this.emit('\n');
}
}
getClassNamespace(phpPath) {
if(path.resolve(phpPath).startsWith(path.resolve(this.outputDir))) {
const baseDir = path.join(this.outputDir, 'src', path.sep);
phpPath = phpPath.replace(baseDir, '');
}
const arr = phpPath.split(path.sep).slice(0, -1);
const namsespace = this.config.package.split('.').join('\\');
let className = namsespace;
arr.map(key => {
className += '\\' + key;
});
return className;
}
getAliasName(classNamespace, name, aliasId) {
let aliasName = '';
if(!this.clientName.has(name)) {
this.clientName.set(name, true);
return aliasName;
}
if(aliasId) {
aliasName = aliasId + 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;
}
}
}
eachImport(imports, usedModels, filepath, innerModule, level) {
this.imports = new Map();
if (imports.length > 0) {
const lockPath = path.join(this.config.pkgDir, '.libraries.json');
const lock = fs.existsSync(lockPath) ? JSON.parse(fs.readFileSync(lockPath, 'utf8')) : {};
for (let i = 0; i < imports.length; i++) {
const item = imports[i];
let comments = DSL.comment.getFrontComments(this.comments, item.tokenRange[0]);
this.visitComments(comments, level);
const aliasId = item.lexeme;
const main = item.mainModule;
const inner = item.module;
const moduleDir = main ? this.config.libraries[main] : this.config.libraries[aliasId];
const innerPath = item.innerPath;
if (!moduleDir && innerPath) {
let phpPath = innerPath.replace(/(\.tea)$|(\.spec)$|(\.dara)$/gi, '');
if (phpPath.startsWith('./') || phpPath.startsWith('../')) {
phpPath = phpPath.split('/').map(dir => _upperFirst(dir)).join(path.sep);
phpPath = path.join(path.dirname(filepath), `${phpPath}.php`);
} else if (phpPath.startsWith('/')) {
phpPath = phpPath.split('/').map(dir => _upperFirst(dir)).join(path.sep);
phpPath = `${phpPath}.php`;
}
const classNamespace = this.getClassNamespace(phpPath);
const className = this.getInnerClient(aliasId, phpPath);
innerModule.set(aliasId, path.join(path.dirname(phpPath), `${className}.php`));
this.moduleClass.set(aliasId, {
namespace: classNamespace,
className: className,
aliasName: this.getAliasName(classNamespace, className, aliasId),
modelDir: this.modelDir,
exceptionDir: this.exceptionDir,
});
continue;
}
let targetPath = '';
if (moduleDir.startsWith('./') || moduleDir.startsWith('../')) {
targetPath = path.join(this.config.pkgDir, moduleDir);
} else if (moduleDir.startsWith('/')) {
targetPath = moduleDir;
} else {
if(!lock[moduleDir]) {
throw new Error(`The '${aliasId}' is not import in Darafile.`);
}
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));
const phpRelease = pkg.releases && pkg.releases.php;
const phpPkg = pkg.php;
if (!phpRelease || !phpPkg || !phpPkg.package) {
throw new Error(`The '${aliasId}' has no PHP supported.`);
}
let classNamespace = phpPkg.package.split('.').join('\\');
let className = phpPkg.clientName || 'Client';
if (inner && pkg.exports[inner]) {
let phpPath = path.dirname(pkg.exports[inner]);
const arr = phpPath.split(path.sep).slice(1);
arr.map(key => {
classNamespace += '\\' + _upperFirst(key);
});
className = phpPkg.exports[inner];
}
const [ key ,value ] = phpRelease.split(':');
this.requires[key] = value;
this.moduleClass.set(aliasId, {
namespace: classNamespace,
className: className,
aliasName: this.getAliasName(classNamespace, className, aliasId),
modelDir: phpPkg.modelDirName || 'Models',
exceptionDir: phpPkg.exceptionDirName || 'Exceptions',
});
this.moduleTypedef[aliasId] = phpPkg.typedef;
}
this.__externModule = usedModels;
}
}
visitParams(ast, level) {
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');
const name = _name(node.paramName);
this.emit(`$${_avoidKeywords(name)}`);
}
this.emit(')');
}
visitType(ast, level) {
if (ast.type === 'array') {
this.visitType(ast.subType, level);
this.emit('[]');
} else if (ast.type === 'moduleModel') {
const [moduleId, ...rest] = ast.path;
const { namespace, modelDir, exceptionDir } = this.moduleClass.get(moduleId.lexeme);
const moduleModelName = rest.map((item) => {
return item.lexeme;
}).join('\\');
let type = modelDir;
let suffix = '';
const usedEx = this.usedExternException.get(moduleId.lexeme);
if(usedEx && usedEx.has(moduleModelName)) {
type = exceptionDir;
suffix = 'Exception';
}
const subModelName = this.getRealModelName(`${namespace}\\${type}\\${moduleModelName}${suffix}`);
this.emit(subModelName);
} else if (ast.type === 'subModel') {
const [moduleId, ...rest] = ast.path;
const subModelName = _subModelName([moduleId.lexeme, ...rest.map((item) => {
return item.lexeme;
})].join('\\'));
const realModelName = this.getRealModelName(`${this.namespace}\\${this.modelDir}\\${subModelName}`);
this.emit(realModelName);
} else if (ast.type === 'map') {
this.visitType(ast.valueType, level);
this.emit('[]');
} else if (ast.type === 'model') {
let namespace = this.namespace;
let type = this.modelDir;
let suffix = '';
if (ast.moduleName) {
namespace = this.moduleClass.get(ast.moduleName).namespace;
const { modelDir, exceptionDir } = this.moduleClass.get(ast.moduleName);
type = modelDir;
const usedEx = this.usedExternException.get(ast.moduleName);
if(usedEx && usedEx.has(ast.name)) {
suffix = 'Exception';
type = exceptionDir;
}
} else if(this.predefined[ast.name] && this.predefined[ast.name].isException) {
suffix = 'Exception';
type = this.exceptionDir;
}
const realModelName = this.getRealModelName(`${namespace}\\${type}\\${ast.name}${suffix}`);
this.emit(realModelName);
} else if (this.isIterator(ast)) {
this.visitType(ast.valueType);
} else if (ast.type === 'entry') {
this.emit('[string, ');
this.visitType(ast.valueType);
this.emit(']');
} else if (ast.idType === 'model') {
let type = this.modelDir;
let suffix = '';
if(this.predefined[ast.lexeme] && this.predefined[ast.lexeme].isException) {
type = this.exceptionDir;
suffix = 'Exception';
}
const realModelName = this.getRealModelName(`${this.namespace}\\${type}\\${ast.lexeme}${suffix}`);
this.emit(realModelName);
} else if (ast.idType === 'module') {
let moduleName = _name(ast);
if(this.builtin[moduleName]) {
moduleName = this.getType(moduleName);
} else {
moduleName = this.getRealClientName(moduleName);
}
this.emit(moduleName);
} else if (ast.idType === 'builtin_model') {
const realModelName = this.getRealModelName(this.getType(ast.lexeme));
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 {
this.emit(this.getType(_name(ast)));
}
}
visitAPIBody(ast, level) {
assert.equal(ast.type, 'apiBody');
const requestName = this.getRealModelName(REQUEST);
this.emit(`$_request = new ${requestName}();\n`, level);
this.visitStmts(ast.stmts, level);
}
visitRuntimeBefore(ast, level) {
assert.equal(ast.type, 'object');
this.emit('$_runtime = ', level);
this.visitObject(ast, level);
this.emit(';\n');
this.emit('\n');
this.emit('$_retriesAttempted = 0;\n', level);
this.emit('$_lastRequest = null;\n', level);
this.emit('$_lastResponse = null;\n', level);
const retryContextName = this.getRealModelName(RETRY_CONTEXT);
this.emit(`$_context = new ${retryContextName}([\n`, level);
this.emit('\'retriesAttempted\' => $_retriesAttempted,\n', level + 1);
this.emit(']);\n', level);
this.emit('while (Dara::shouldRetry($_runtime[\'retryOptions\'], $_context)) {\n', level);
this.emit('if ($_retriesAttempted > 0) {\n', level + 1);
this.emit('$_backoffTime = Dara::getBackoffDelay($_runtime[\'retryOptions\'], $_context);\n', level + 2);
this.emit('if ($_backoffTime > 0) {\n', level + 2);
this.emit('Dara::sleep($_backoffTime);\n', level + 3);
this.emit('}\n', level + 2);
this.emit('}\n', level + 1);
this.emit('\n');
this.emit('$_retriesAttempted++;\n', level + 1);
this.emit('try {\n', level + 1);
}
visitStmt(ast, level) {
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
if (ast.type === 'return') {
this.visitReturn(ast, level);
} else if (ast.type === 'yield') {
this.visitYield(ast, level);
} else if (ast.type === 'if') {
this.visitIf(ast, level);
} else if (ast.type === 'throw') {
this.visitThrow(ast, level);
} else if (ast.type === 'assign') {
this.visitAssign(ast, level);
} 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);
} else if (ast.type === 'while') {
this.visitWhile(ast, level);
} else if (ast.type === 'for') {
this.visitFor(ast, level);
} else if (ast.type === 'try') {
this.visitTry(ast, level);
} else {
this.emit('', level);
this.visitExpr(ast, level);
this.emit(';\n');
}
}
getType(name) {
if (name === 'integer' || name === 'number' ||
name === 'int8' || name === 'uint8' ||
name === 'int16' || name === 'uint16' ||
name === 'int32' || name === 'uint32' ||
name === 'int64' || name === 'uint64' ||
name === 'long' || name === 'ulong') {
return 'int';
}
if (name === 'float' || name === 'double') {
return 'float';
}
if (name === 'readable') {
return STREAM;
}
if (name === 'writable') {
return STREAM;
}
if (name === '$Request') {
return REQUEST;
}
if (name === '$Response') {
return RESPONSE;
}
if (name === '$Model') {
return MODEL;
}
if (name === '$Error') {
return ERROR;
}
if (name === '$ResponseError') {
return RESP_ERROR;
}
if (name === '$RetryOptions') {
return RETRY_OPTION;
}
if (name === '$RuntimeOptions') {
return RUNTIME_OPTION;
}
if (name === '$ExtendsParameters') {
return EXTEND_PARAM;
}
if (name === '$SSEEvent') {
return SSE_EVENT;
}
if (name === '$Date' || name === '$File' ||
name === '$URL' || name === '$Stream') {
return this.builtin[name].getClientName();
}
if (name === 'object') {
return 'mixed[]';
}
if (name === 'bytes') {
return 'int[]';
}
if (name === 'any') {
return 'mixed';
}
return name;
}
visitFieldType(value, level, fieldName) {
if (value.type === 'modelBody') {
const subModelName = this.getRealModelName(`${this.namespace}\\${this.modelDir}\\${this.modelSpace}\\${fieldName}`);
this.emit(subModelName);
} else if (value.type === 'array') {
this.visitType(value);
} else if (value.fieldType === 'array') {
this.visitFieldType(value.fieldItemType, level, fieldName);
this.emit('[]');
} else if (value.fieldType === 'map') {
this.visitFieldType(value.valueType);
this.emit('[]');
} else if (value.type === 'map') {
this.visitFieldType(value.valueType);
this.emit('[]');
} else if (value.tag === Tag.TYPE) {
this.emit(`${this.getType(value.lexeme)}`);
} else if (value.tag === Tag.ID && value.idType === 'model') {
const modelName = _name(value);
const realModelName = this.getRealModelName(`${this.namespace}\\${this.modelDir}\\${modelName}`);
this.emit(realModelName);
} else if (value.tag === Tag.ID && value.idType === 'module') {
let moduleName = _name(value);
if(this.builtin[moduleName]) {
moduleName = this.getType(moduleName);
} else {
moduleName = this.getRealClientName(moduleName);
}
this.emit(moduleName);
} else if (value.tag === Tag.ID && value.idType === 'builtin_model') {
const realModelName = this.getRealModelName(this.getType(value.lexeme));
this.emit(realModelName);
} else if (value.tag === Tag.ID) {
this.emit(`${value.lexeme}`);
} else if (value.type === 'moduleModel') {
const [moduleId, ...models] = value.path;
const { namespace, modelDir } = this.moduleClass.get(moduleId.lexeme);
const moduleModelName = models.map((item) => {
return item.lexeme;
}).join('\\');
const subModelName = this.getRealModelName(`${namespace}\\${modelDir}\\${moduleModelName}`);
this.emit(subModelName);
} else if (value.type === 'subModel') {
const [moduleId, ...rest] = value.path;
const subModelName = [moduleId.lexeme, ...rest.map((item) => {
return item.lexeme;
})].join('\\');
const realModelName = this.getRealModelName(`${this.namespace}\\${this.modelDir}\\${subModelName}`);
this.emit(realModelName);
} else if (value.fieldType === 'readable' || value.fieldType === 'writable') {
const realModelName = this.getRealModelName(this.getType(value.fieldType));
this.emit(realModelName);
} else if (typeof value.fieldType === 'string') {
this.emit(`${this.getType(value.fieldType)}`);
} else if (value.fieldType.type === 'moduleModel') {
const [moduleId, ...models] = value.fieldType.path;
const { namespace, modelDir } = this.moduleClass.get(moduleId.lexeme);
const subModelName = models.map((item) => {
return item.lexeme;
}).join('\\');
const realModelName = this.getRealModelName(`${namespace}\\${modelDir}\\${subModelName}`);
this.emit(realModelName);
} 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.getType(value.fieldType.lexeme)}`);
} else if (value.fieldType.idType === 'model') {
const realModelName = this.getRealModelName(`${this.namespace}\\${this.modelDir}\\${value.fieldType.lexeme}`);
this.emit(realModelName);
} else if (value.fieldType.idType === 'module') {
let moduleName = _name(value.fieldType);
if(this.builtin[moduleName]) {
moduleName = this.getType(moduleName);
} else {
moduleName = this.getRealClientName(moduleName);
}
this.emit(moduleName);
} else if (value.fieldType.idType === 'builtin_model') {
const realModelName = this.getRealModelName(this.getType(value.fieldType.lexeme));
this.emit(realModelName);
}
}
visitFromField(fieldValue, fieldName, key, value, level) {
const deep = Math.floor(level / 2);
if (fieldValue.type === 'modelBody') {
const subModelName = this.getRealModelName(`${this.namespace}\\Models\\${this.modelSpace}\\${fieldName}`);
this.emit(`${key} = ${subModelName}::fromMap(${value});\n`, level + 1);
} else if (fieldValue.type === 'array' || fieldValue.fieldType === 'array') {
this.emit(`if(!empty(${value})) {\n`, level + 1);
this.emit(`${key} = [];\n`, level + 2);
this.emit(`$n${deep} = 0;\n`, level + 2);
this.emit(`foreach(${value} as $item${deep}) {\n`, level + 2);
this.visitFromField(fieldValue.fieldItemType || fieldValue.subType, fieldName, `${key}[$n${deep}++]`, `$item${deep}`, level + 2);
this.emit('}\n', level + 2);
this.emit('}\n', level + 1);
} else if (fieldValue.fieldType === 'map' || fieldValue.type === 'map') {
this.emit(`if(!empty(${value})) {\n`, level + 1);
this.emit(`${key} = [];\n`, level + 2);
this.emit(`foreach(${value} as $key${deep} => $value${deep}) {\n`, level + 2);
this.visitFromField(fieldValue.valueType, fieldName, `${key}[$key${deep}]`, `$value${deep}`, level + 2);
this.emit('}\n', level + 2);
this.emit('}\n', level + 1);
} else if (fieldValue.type === 'moduleModel' ||
(fieldValue.fieldType && fieldValue.fieldType.type === 'moduleModel')) {
const [moduleId, ...models] = fieldValue.path || fieldValue.fieldType.path;
const { namespace, modelDir } = this.moduleClass.get(moduleId.lexeme);
const moduleModelName = models.map((item) => {
return item.lexeme;
}).join('\\');
const subModelName = this.getRealModelName(`${namespace}\\${modelDir}\\${moduleModelName}`);
this.emit(`${key} = ${subModelName}::fromMap(${value});\n`, level + 1);
} else if (fieldValue.type === 'subModel') {
const [moduleId, ...rest] = fieldValue.path;
const subModelName = _subModelName([moduleId.lexeme, ...rest.map((item) => {
return item.lexeme;
})].join('\\'));
const realModelName = this.getRealModelName(`${this.namespace}\\${this.modelDir}\\${subModelName}`);
this.emit(`${key} = ${realModelName}::fromMap(${value});\n`, level + 1);
} else if (fieldValue.idType === 'model') {
const realModelName = this.getRealModelName(`${this.namespace}\\${this.modelDir}\\${fieldValue.lexeme}`);
this.emit(`${key} = ${realModelName}::fromMap(${value});\n`, level + 1);
} else if (fieldValue.fieldType && fieldValue.fieldType.idType === 'model') {
const realModelName = this.getRealModelName(`${this.namespace}\\${this.modelDir}\\${fieldValue.fieldType.lexeme}`);
this.emit(`${key} = ${realModelName}::fromMap(${value});\n`, level + 1);
} else if (fieldValue.fieldType && fieldValue.fieldType.idType === 'builtin_model') {
const realModelName = this.getRealModelName(this.getType(fieldValue.fieldType.lexeme));
this.emit(`${key} = ${realModelName}::fromMap(${value});\n`, level + 1);
} else {
this.emit(`${key} = ${value};\n`, level + 1);
}
}
visitToArrayField(fieldValue, key, value, level) {
const deep = Math.floor(level / 2);
if (fieldValue.type === 'modelBody' || fieldValue.type === 'moduleModel' ||
fieldValue.type === 'subModel') {
this.emit(`${key} = null !== ${value} ? ${value}->toArray($noStream) : ${value};\n`, level + 1);
} else if(fieldValue.idType === 'model') {
this.emit(`${key} = null !== ${value} ? ${value}->toArray($noStream) : ${value};\n`, level + 1);
} else if(fieldValue.fieldType && (fieldValue.fieldType.type === 'moduleModel' ||
fieldValue.fieldType.idType === 'model' || fieldValue.fieldType.idType === 'builtin_model')) {
this.emit(`${key} = null !== ${value} ? ${value}->toArray($noStream) : ${value};\n`, level + 1);
} else if (fieldValue.type === 'array' || fieldValue.fieldType === 'array') {
this.emit(`if(is_array(${value})) {\n`, level + 1);
this.emit(`${key} = [];\n`, level + 2);
this.emit(`$n${deep} = 0;\n`, level + 2);
this.emit(`foreach(${value} as $item${deep}) {\n`, level + 2);
this.visitToArrayField(fieldValue.fieldItemType || fieldValue.subType, `${key}[$n${deep}++]`, `$item${deep}`, level + 2);
this.emit('}\n', level + 2);
this.emit('}\n', level + 1);
} else if (fieldValue.fieldType === 'map' || fieldValue.type === 'map') {
this.emit(`if(is_array(${value})) {\n`, level + 1);
this.emit(`${key} = [];\n`, level + 2);
this.emit(`foreach(${value} as $key${deep} => $value${deep}) {\n`, level + 2);
this.visitToArrayField(fieldValue.valueType, `${key}[$key${deep}]`, `$value${deep}`, level + 2);
this.emit('}\n', level + 2);
this.emit('}\n', level + 1);
} else {
this.emit(`${key} = ${value};\n`, level + 1);
}
}
visitModelBody(ast, level, modelName) {
assert.equal(ast.type, 'modelBody');
let node;
for (let i = 0; i < ast.nodes.length; i++) {
node = ast.nodes[i];
// TODO document gen
node = ast.nodes[i];
let comments = DSL.comment.getFrontComments(this.comments, node.tokenRange[0]);
this.visitComments(comments, level);
this.emit('/**\n', level);
this.emit(' * @var ', level);
this.visitFieldType(node.fieldValue, level, _name(node.fieldName));
this.emit('\n');
this.emit(' */\n', level);
this.emit(`public $${_name(node.fieldName)};\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);
}
this.emit('protected $_name = [\n', level);
for (let i = 0; i < ast.nodes.length; i++) {
const node = ast.nodes[i];
const nameAttr = node.attrs.find((item) => {
return item.attrName.lexeme === 'name';
});
if (nameAttr) {
this.emit(`'${_name(node.fieldName)}' => '${_string(nameAttr.attrValue)}',\n`, level + 2);
} else {
this.emit(`'${_name(node.fieldName)}' => '${_name(node.fieldName)}',\n`, level + 2);
}
}
this.emit('];\n', level);
this.emit('\n');
this.emit('public function validate()\n', level);
this.emit('{\n', level);
this.visitModelValidate(ast, level + 1);
this.emit('}\n', level);
this.emit('\n');
this.emit('public function toArray($noStream = false)\n', level);
this.emit('{\n', level);
this.emit('$res = [];\n', level + 1);
for (let i = 0; i < ast.nodes.length; i++) {
const node = ast.nodes[i];
const nameAttr = node.attrs.find((item) => {
return item.attrName.lexeme === 'name';
});
const realName = nameAttr ?_string(nameAttr.attrValue) : _name(node.fieldName);
this.emit(`if (null !== $this->${_name(node.fieldName)}) {\n`, level + 1);
this.visitToArrayField(node.fieldValue, `$res['${realName}']`, `$this->${_name(node.fieldName)}`, level + 1);
this.emit('}\n', level + 1);
this.emit('\n');
}
this.emit('return $res;\n', level + 1);
this.emit('}\n', level);
this.emit('\n');
this.emit('public function toMap($noStream = false)\n', level);
this.emit('{\n', level);
this.emit('return $this->toArray($noStream);\n', level + 1);
this.emit('}\n', level);
this.emit('\n');
this.emit('public static function fromMap($map = [])\n', level);
this.emit('{\n', level);
this.emit('$model = new self();\n', level + 1);
for (let i = 0; i < ast.nodes.length; i++) {
const node = ast.nodes[i];
const nameAttr = node.attrs.find((item) => {
return item.attrName.lexeme === 'name';
});
const realName = nameAttr ?_string(nameAttr.attrValue) : _name(node.fieldName);
const fieldName = _name(node.fieldName);
this.emit(`if (isset($map['${realName}'])) {\n`, level + 1);
this.visitFromField(node.fieldValue, fieldName, `$model->${fieldName}`, `$map['${realName}']`, level + 1);
this.emit('}\n', level + 1);
this.emit('\n');
}
this.emit('return $model;\n', level + 1);
this.emit('}\n', level);
this.emit('\n');
}
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;
}
visitFieldValidate(modelName, value, level, name) {
if (value.type === 'array' || value.fieldType === 'array') {
this.emit(`if(is_array(${name})) {\n`, level);
this.emit(`${modelName}::validateArray(${name});\n`, level + 1);
this.emit('}\n', level);
} else if (value.fieldType === 'map' || value.type === 'map') {
this.emit(`if(is_array(${name})) {\n`, level);
this.emit(`${modelName}::validateArray(${name});\n`, level + 1);
this.emit('}\n', level);
} else if (value.type === 'moduleModel' || value.type === 'modelBody'
|| value.type === 'subModel' || value.fieldType.type === 'moduleModel'
|| value.fieldType.idType === 'model' || value.fieldType.idType === 'module') {
this.emit(`if(null !== ${name}) {\n`, level);
this.emit(`${name}->validate();\n`, level + 1);
this.emit('}\n', level);
}
}
visitModelValidate(ast, level) {
const modelName = this.getRealModelName(MODEL);
for (let i = 0; i < ast.nodes.length; i++) {
const node = ast.nodes[i];
this.visitFieldValidate(modelName, node.fieldValue, level, `$this->${_name(node.fieldName)}`);
const attrName = _name(node.fieldName);
const pattern = this.getAttributes(node, 'pattern') || '';
const maxLength = this.getAttributes(node, 'maxLength') || 0;
const minLength = this.getAttributes(node, 'minLength') || 0;
const maximum = this.getAttributes(node, 'maximum') || 0;
const minimum = this.getAttributes(node, 'minimum') || 0;
const required = node.required || false;
if (required || maxLength > 0 || minLength > 0 || maximum > 0 || pattern !== '') {
if (required) {
this.emit(`${modelName}::validateRequired('${attrName}', $this->${attrName}, true);\n`, level);
}
if (pattern !== '') {
this.emit(`${modelName}::validatePattern('${attrName}', $this->${attrName}, '${pattern}');\n`, level);
}
if (maxLength > 0 && maxLength <= 2147483647) {
this.emit(`${modelName}::validateMaxLength('${attrName}', $this->${attrName}, ${maxLength});\n`, level);
}
if (minLength > 0 && minLength <= 2147483647) {
this.emit(`${modelName}::validateMinLength('${attrName}', $this->${attrName}, ${minLength});\n`, level);
}
// 不能超过JS中最大安全整数
if (maximum > 0 && maximum <= Number.MAX_SAFE_INTEGER) {
this.emit(`${modelName}::validateMaximum('${attrName}', $this->${attrName}, ${maximum});\n`, level);
}
// 不能超过JS中最大安全整数
if (minimum > 0 && minimum <= Number.MAX_SAFE_INTEGER) {
this.emit(`${modelName}::validateMinimum('${attrName}', $this->${attrName}, ${minimum});\n`, level);
}
}
}
this.emit('parent::validate();\n', level);
}
visitExtendOn(extendOn, type = 'model') {
if (!extendOn) {
const modelName = type === 'model' ? this.getRealModelName(MODEL) : this.getRealModelName(ERROR);
return this.emit(modelName);
}
let suffix = '';
let namespace = this.namespace;
let extendType = this.modelDir;
let modelName = _name(extendOn);
if(this.predefined[modelName] && this.predefined[modelName].isException) {
extendType = this.exceptionDir;
suffix = 'Exception';
}
if (extendOn.type === 'moduleModel') {
const [moduleId, ...rest] = extendOn.path;
namespace = this.moduleClass.get(moduleId.lexeme).namespace;
extendType = this.moduleClass.get(moduleId.lexeme)[`${type}Dir`];
modelName = rest.map((item) => {
return item.lexeme;
}).join('\\');
const usedEx = this.usedExternException.get(moduleId.lexeme);
if(usedEx && usedEx.has(modelName)) {
extendType = this.exceptionDir;
suffix = 'Exception';
}
} else if (extendOn.type === 'subModel') {
const [moduleId, ...rest] = extendOn.path;
modelName = [moduleId.lexeme, ...rest.map((item) => {
return item.lexeme;
})].join('\\');
}
this.emit(this.getRealModelName(`${namespace}\\${extendType}\\${modelName}${suffix}`));
}
visitModel(modelBody, modelName, extendOn, level) {
this.emit(`class ${modelName} extends `, level);
this.visitExtendOn(extendOn);
this.emit(' {\n');
this.visitModelBody(modelBody, level + 1, modelName);
this.emit('\n');
this.emit('}\n\n', level);
}
visitEcxceptionBody(ast, level) {
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);
// TODO document gen
this.emit('/**\n', level);
this.emit('* @var ', level);
this.visitFieldType(node.fieldValue, level, fieldName);
this.emit('\n');
this.emit('*/\n', level);
this.emit(`${_filedsAccess(fieldName)} $${fieldName};\n`, level);
}
this.emit('\n');
this.emit('public function __construct($map)\n', level);
this.emit('{\n', level);
this.emit('parent::__construct($map);\n', level + 1);
for (let i = 0; i < ast.nodes.length; i++) {
node = ast.nodes[i];
this.emit(`$this->${_name(node.fieldName)} = $map['${_name(node.fieldName)}'];\n`, level + 1);
}
this.emit('}\n\n', level);
for (let i = 0; i < ast.nodes.length; i++) {
node = ast.nodes[i];
// TODO document gen
this.emit('/**\n', level);
this.emit('* @return ', level);
this.visitFieldType(node.fieldValue, level, _name(node.fieldName));
this.emit('\n');
this.emit('*/\n', level);
this.emit(`public function get${_upperFirst(_name(node.fieldName))}()\n`, level);
this.emit('{\n', level);
this.emit(`return $this->${_name(node.fieldName)};\n`, level + 1);
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);
}
}
visitException(exceptionBody, exceptionName, extendOn, level) {
this.emit(`class ${exceptionName}Exception extends `, level);
this.visitExtendOn(extendOn, 'exception');
this.emit(' {\n');
this.visitEcxceptionBody(exceptionBody, level + 1, exceptionName);
this.emit('}\n\n', level);
}
eachSubModel(ast, level) {
assert.equal(ast.type, 'model');
const modelName = _subModelName(_name(ast.modelName));
this.visitModel(ast.modelBody, modelName, ast.extendOn, level);
}
visitObjectFieldValue(ast, level) {
this.visitExpr(ast, level);
}
visitObjectField(ast, level) {
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
var key = _name(ast.fieldName) || _string(ast.fieldName);
this.emit(`'${key}' => `, level);
this.visitObjectFieldValue(ast.expr, level);
this.emit(',\n');
}
visitObject(ast, level) {
assert.equal(ast.type, 'object');
if (ast.fields.length === 0) {
this.emit('[ ');
let comments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]);
if (comments.length > 0) {
this.emit('\n');
this.visitComments(comments, level + 1);
this.emit('', level);
}
this.emit(']');
} else {
const mergeFields = [];
const mapFields = [];
for (let i = 0; i < ast.fields.length; i++) {
const field = ast.fields[i];
if (field.type === 'objectField') {
mapFields.push(field);
} else if (field.type === 'expandField') {
mergeFields.push(field);
} else {
throw new Error('unimpelemented');
}
}
if(mergeFields.length > 0) {
this.emit('Dara::merge(');
}
this.emit('[\n');
for (let i = 0; i < mapFields.length; i++) {
this.visitObjectField(mapFields[i], level + 1);
}
//find the last item's back comment
let comments = DSL.comment.getBetweenComments(this.comments, ast.fields[ast.fields.length - 1].tokenRange[0], ast.tokenRange[1]);
this.visitComments(comments, level + 1);
this.emit(']', level);
if(mergeFields.length > 0) {
for (let i = 0; i < mergeFields.length; i++) {
this.emit(', ');
this.visitExpr(mergeFields[i].expr, level + 1);
}
this.emit(')');
}
}
}
visitMethodCall(ast, level) {
assert.equal(ast.left.type, 'method_call');
const name = _name(ast.left.id);
if (name.startsWith('$') && this.builtin[name]) {
const method = name.replace('$', '');
this.builtin[name][method](ast.args, level);
return;
} else if (ast.isStatic) {
this.emit(`self::${name}`);
} else {
this.emit(`$this->${name}`);
}
this.visitArgs(ast.args, level);
}
visitInstanceCall(ast, level) {
assert.equal(ast.left.type, 'instance_call');
const method = _avoidKeywords(_name(ast.left.propertyPath[0]));
if (ast.builtinModule && this.builtin[ast.builtinModule] && this.builtin[ast.builtinModule][method]) {
this.builtin[ast.builtinModule][method](ast, level);
} else {
if (ast.left.id.tag === DSL.Tag.Tag.VID) {
this.emit(`$this->${_vid(ast.left.id)}`);
} else {
this.emit(`$${_name(ast.left.id)}`);
}
this.emit(`->${(method)}`);
this.visitArgs(ast.args, level);
}
}
visitStaticCall(ast, level) {
assert.equal(ast.left.type, 'static_call');
if (ast.left.id.type === 'builtin_module') {
this.visitBuiltinStaticCall(ast);
return;
}
const aliasId = _name(ast.left.id);
const clientName= this.getRealClientName(aliasId);
this.emit(`${clientName}::${_avoidKeywords(_name(ast.left.propertyPath[0]))}`);
this.visitArgs(ast.args, level);
}
visitBuiltinStaticCall(ast) {
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.args);
}
visitCall(ast, level) {
assert.equal(ast.type, 'call');
if (ast.left.type === 'method_call') {
this.visitMethodCall(ast, level);
} else if (ast.left.type === 'instance_call') {
this.visitInstanceCall(ast, level);
} else if (ast.left.type === 'static_call') {
this.visitStaticCall(ast, level);
} else {
throw new Error('un-implemented');
}
}
visitConstruct(ast, level) {
assert.equal(ast.type, 'construct');
this.emit('new ');
let aliasId = this.getType(ast.aliasId.lexeme);
const clientName = this.getRealClientName(aliasId);
this.emit(clientName || aliasId);
this.visitArgs(ast.args, level);
}
visitSuper(ast, level) {
assert.equal(ast.type, 'super');
this.emit('parent::__construct');
this.visitArgs(ast.args, level);
}
visitArgs(args, level) {
this.emit('(');
for (let i = 0; i < args.length; i++) {
const expr = args[i];
if (expr.needCast) {
this.visitExpr(expr, level);
this.emit('->toArray()');
} else {
this.visitExpr(expr, level);
}
if (i !== args.length - 1) {
this.emit(', ');
}
}
this.emit(')');
}
visitPropertyAccess(ast) {
assert.ok(ast.type === 'property_access' || ast.type === 'property');
var id = _name(ast.id);
if (ast.id.tag === Tag.VID) {
id = `this->${_vid(ast.id)}`;
}
var expr = `$${_avoidKeywords(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 += `->${name}`;
} else {
if(!expr.startsWith('@')) {
expr = `@${expr}`;
}
expr += `['${name}']`;
}
current = ast.propertyPathTypes[i];
}
this.emit(expr);
}
visitExpr(ast, level) {
if (ast.type === 'boolean') {
this.emit(ast.value);
} else if (ast.type === 'property_access') {
this.visitPropertyAccess(ast);
} else if (ast.type === 'string') {
this.emit(`'${_string(ast.value)}'`);
} else if (ast.type === 'number') {
this.emit(ast.value.value);
} else if (ast.type === 'null') {
this.emit('null');
} else if (ast.type === 'object') {
this.visitObject(ast, level);
} else if (ast.type === 'variable') {
if(ast.inferred && ast.inferred.type === 'basic' && ast.inferred.name === 'class') {
this.emit(`${_avoidKeywords(_name(ast.id))}::class`);
} else {
this.emit(`$${_avoidKeywords(_name(ast.id))}`);
}
} else if (ast.type === 'virtualVariable') {
this.emit(`$this->${_vid(ast.vid)}`);
} else if (ast.type === 'decrement') {
if (ast.position === 'front') {
this.emit('--');
}
this.visitExpr(ast.expr, level);
if (ast.position === 'backend') {
this.emit('--');
}
} else if (ast.type === 'increment') {
if (ast.position === 'front') {
this.emit('++');
}
this.visitExpr(ast.expr, level);
if (ast.position === 'backend') {
this.emit('++');
}
} else if (ast.type === 'template_string') {
this.emit('\'');
for (var i = 0; i < ast.elements.length; i++) {
var item = ast.elements[i];
if (item.type === 'element') {
this.emit(_string(item.value));
} else if (item.type === 'expr') {
this.emit('\' . ');
if(item.expr.inferred && item.expr.inferred.name !== 'string') {
this.emit('(string)');
}
if(_isBinaryOp(item.expr.type)) {
this.emit('(');
}
this.visitExpr(item.expr, level);
if(_isBinaryOp(item.expr.type)) {
this.emit(')');
}
this.emit(' . \'');
} else {
throw new Error('unimpelemented');
}
}
this.emit('\'');
} else if (ast.type === 'call') {
this.visitCall(ast, level);
} else if (ast.type === 'construct') {
this.visitConstruct(ast, level);
} else if (ast.type === 'array') {
this.visitArray(ast, level);
} else if (ast.type === 'group') {
this.emit('(');
this.visitExpr(ast.expr, level);
this.emit(')');
} else if (_isBinaryOp(ast.type)) {
this.visitExpr(ast.left, level);
if (ast.type === 'or') {
this.emit(' || ');
} else if (ast.type === 'add') {
if(ast.inferred && ast.inferred.type === 'basic' && ast.inferred.name === 'string') {
this.emit(' . ');
} else {
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 === 'or') {
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);
} else if (ast.type === 'group') {
this.emit('(');
this.visitExpr(ast.expr, level);
this.emit('(');
} else if (ast.type === 'not') {
this.emit('!');
this.visitExpr(ast.expr, level);
} else if (ast.type === 'construct_model') {
this.visitConstructModel(ast, level);
} else if (ast.type === 'map_access') {
this.visitMapAccess(ast);
} else if (ast.type === 'array_access') {
this.visitArrayAccess(ast);
} else if (ast.type === 'super') {
this.visitSuper(ast, level);
} else {
console.log(ast);
throw new Error('unimpelemented');
}
}
visitConstructModel(ast, level) {
assert.equal(ast.type, 'construct_model');
if (ast.aliasId.isModule) {
let aliasId = ast.aliasId.lexeme;
let suffix = '';
const { namespace, modelDir, exceptionDir } = this.moduleClass.get(aliasId);
const moduleModelName = ast.propertyPath.map((item) => {
return item.lexeme;
}).join('\\');
let type = modelDir;
const usedEx = this.usedExternException.get(aliasId);
if(usedEx && usedEx.has(moduleModelName)) {
type = exceptionDir;
suffix = 'Exception';
}
const subModelName = this.getRealModelName(`${namespace}\\${type}\\${moduleModelName}${suffix}`);
this.emit(`new ${subModelName}`);
}
if (ast.aliasId.isModel) {
let mainModelName = ast.aliasId;
let type = this.modelDir;
let suffix = '';
this.emit('new ');
const modelName = [mainModelName, ...ast.propertyPath].map((item) => {
return item.lexeme;
}).join('\\');
if(_isBuiltinModel(modelName)) {
const subModelName = this.getRealModelName(this.getType(modelName));
this.emit(subModelName);
} else {
if(this.predefined[modelName] && this.predefined[modelName].isException) {
type = this.exceptionDir;
suffix = 'Exception';
}
const subModelName = this.getRealModelName(`${this.namespace}\\${type}\\${modelName}${suffix}`);
this.emit(subModelName);
}
}
this.emit('(');
if (ast.object) {
this.visitObject(ast.object, level);
} else {
this.emit('[ ]');
}
this.emit(')');
}
visitMapAccess(ast, level) {
assert.equal(ast.type, 'map_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 += `->${name}`;
} else {
expr += `['${name}']`;
}
current = ast.propertyPathTypes[i];
}
}
this.emit(`@${expr}[`, level);
this.visitExpr(ast.accessKey, level);
this.emit(']');
}
visitArrayAccess(ast, level) {
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 += `->${name}`;
} else {
expr += `['${name}']`;
}
current = ast.propertyPathTypes[i];
}
}
this.emit(`@${expr}[`, level);
this.visitExpr(ast.accessKey, level);
this.emit(']');
}
visitArray(ast, level) {
assert.equal(ast.type, 'array');
let arrayComments = DSL.comment.getBetweenComments(this.comments, ast.tokenRange[0], ast.tokenRange[1]);
if (ast.items.length === 0) {
this.emit('[ ');
if (arrayComments.length > 0) {
this.emit('\n');
this.visitComments(arrayComments, level + 1);
this.emit('', level);
}
this.emit(']');
return;
}
this.emit('[\n');
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);
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);
}
visitYield(ast, level) {
assert.equal(ast.type, 'yield');
this.emit('yield ', level);
if (!ast.expr) {
this.emit(';\n');
return;
}
if (ast.needCast) {
this.visitType(ast.expectedType);
this.emit('::fromMap(');
}
this.visitExpr(ast.expr, level);
if (ast.needCast) {
this.emit(')');
}
this.emit(';\n');
}
visitReturn(ast, level) {
assert.equal(ast.type, 'return');
this.emit('return ', level);
if (!ast.expr) {
this.emit('null;\n');
return;
}
if (ast.needCast) {
this.visitType(ast.expectedType);
this.emit('::fromMap(');
}
this.visitExpr(ast.expr, level);
if (ast.needCast) {
this.emit(')');
}
this.emit(';\n');
}
visitRetry(ast, level) {
assert.equal(ast.type, 'retry');
const errorName = this.getRealModelName(UNRETRY_ERROR);
this.emit(`throw ${errorName}($_lastRequest, $_lastException);\n`, level);
}
visitTry(ast, level) {
assert.equal(ast.type, 'try');
this.emit('try {\n', level);
this.visitStmts(ast.tryBlock, level + 1);
this.emit('}', level);
if (ast.catchBlocks && ast.catchBlocks.length > 0) {
ast.catchBlocks.forEach(catchBlock => {
if (!catchBlock.id) {
return;
}
if (!catchBlock.id.type) {
const errorName = this.getRealModelName(ERROR);
this.emit(` catch (${errorName} $${_name(catchBlock.id)}) {\n`);
} else {
this.emit(' catch (');
this.visitType(catchBlock.id.type);
this.emit(` $${_name(catchBlock.id)}) {\n`);
}
this.visitStmts(catchBlock.catchStmts, level + 1);
this.emit('}', level);
});
} else if (ast.catchBlock && ast.catchBlock.stmts.length > 0) {
const errorName = this.getRealModelName(ERROR);
this.emit(` catch (${errorName} $${_name(ast.catchId)}) {\n`);
this.visitStmts(ast.catchBlock, level + 1);
this.emit('}', level);
}
if (ast.finallyBlock && ast.finallyBlock.stmts.length > 0) {
this.emit(' finally {\n');
this.visitStmts(ast.finallyBlock, level + 1);
this.emit('}', level);
}
this.emit('\n', level);
}
visitWhile(ast, level) {
assert.equal(ast.type, 'while');
this.emit('\n');
this.emit('while (', level);
this.visitExpr(ast.condition, level + 1);
this.emit(') {\n');
this.visitStmts(ast.stmts, level + 1);
this.emit('}\n', level);
}
visitFor(ast, level) {
assert.equal(ast.type, 'for');
this.emit('\n');
this.emit('foreach(', level);
this.visitExpr(ast.list, level + 1);
this.emit(` as $${_name(ast.id)}`);
this.emit(') {\n');
this.visitStmts(ast.stmts, level + 1);
this.emit('}\n', level);
}
visitIf(ast, level) {
assert.equal(ast.type, 'if');
this.emit('if (', level);
this.visitExpr(ast.condition, level + 1);
this.emit(') {\n');
if(!ast.stmts.stmts.length) {
this.emit('', level + 1);
this.emit('\n');
}
this.visitStmts(ast.stmts, level + 1);
this.emit('}', level);
if (ast.elseIfs) {
for (let i = 0; i < ast.elseIfs.length; i++) {
const branch = ast.elseIfs[i];
this.emit(' else if (');
this.visitExpr(branch.condition, level + 1);
this.emit(') {\n');
if(!branch.stmts.stmts.length) {
this.emit('', level + 1);
this.emit('\n');
}
this.visitStmts(branch.stmts, level + 1);
this.emit('}', level);
}
}
if (ast.elseStmts) {
this.emit(' else {\n');
for (let i = 0; i < ast.elseStmts.stmts.length; i++) {
this.visitStmt(ast.elseStmts.stmts[i], level + 1);
}
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('', level + 1);
this.emit('\n');
}
this.emit('}', level);
}
this.emit('\n');
this.emit('\n');
}
visitThrow(ast, level) {
this.emit('throw ', level);
if (ast.expr.type === 'construct_model') {
this.visitConstructModel(ast.expr, level);
this.emit(';\n');
} else {
const errorName = this.getRealModelName(ERROR);
this.emit(`new ${errorName}(`);
this.visitObject(ast.expr, level);
this.emit(');\n');
}
}
visitAssign(ast, level) {
if (ast.left.type === 'property_assign' || ast.left.type === 'property') {
this.emit('', level);
this.visitPropertyAccess(ast.left);
} else if (ast.left.type === 'virtualVariable') { // vid
this.emit(`$this->${_vid(ast.left.vid)}`, level);
} else if (ast.left.type === 'variable') {
this.emit(`$${_name(ast.left.id)}`, level);
} else if (ast.left.type === 'map_access') {
this.visitMapAccess(ast.left, level);
} else if (ast.left.type === 'array_access') {
this.visitArrayAccess(ast.left, level);
} else {
throw new Error('unimpelemented');
}
this.emit(' = ');
// if (ast.expr.needToReadable) {
// const streamName = this.getRealModelName(STREAM);
// this.emit(`new ${streamName}(`);
// }
this.visitExpr(ast.expr, level);
// if (ast.expr.needToReadable) {
// this.emit(')');
// }
this.emit(';\n');
}
visitDeclare(ast, level) {
var id = _name(ast.id);
this.emit(`$${id}`, level);
this.emit(' = ');
this.visitExpr(ast.expr, level);
this.emit(';\n');
}
visitStmts(ast, level) {
assert.equal(ast.type, 'stmts');
let node;
for (var i = 0; i < ast.stmts.length; i++) {
node = ast.stmts[i];
this.visitStmt(node, level);
}
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);
}
}
visitReturnBody(ast, level) {
assert.equal(ast.type, 'returnBody');
this.emit('\n');
this.visitStmts(ast.stmts, level);
}
visitDefaultReturnBody(level) {
this.emit('\n');
this.emit('return [];\n', level);
}
visitFunctionBody(ast, level) {
assert.equal(ast.type, 'functionBody');
this.visitStmts(ast.stmts, level);
}
isIterator(returnType) {
if (returnType.type === 'iterator' || returnType.type === 'asyncIterator') {
return true;
}
return false;
}
eachFunction(ast, level) {
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
this.visitAnnotation(ast.annotation, level, false);
const functionName = _avoidKeywords(_name(ast.functionName));
if(!ast.annotation || !ast.annotation.value) {
this.emit('/**\n', level);
}
this.visitParamsType(ast.params, level);
this.visitReturnType(ast, level);
this.emit(' */', level);
this.emit('\n');
this.emit('', level);
if (ast.isStatic) {
this.emit('static ');
}
this.emit(`public function ${functionName}`);
this.visitParams(ast.params, level);
this.emit('\n');
this.emit('{\n', level);
if (ast.functionBody) {
this.visitFunctionBody(ast.functionBody, level + 1);
} else {
this.used.push('use RuntimeException;');
// interface mode
this.emit('throw new RuntimeException(\'Un-implemented!\');\n', level + 1);
}
this.emit('}\n', level);
}
visitParamsType(ast, level) {
for (var i = 0; i < ast.params.length; i++) {
const node = ast.params[i];
assert.equal(node.type, 'param');
this.emit(' * @param ', level);
this.visitType(node.paramType, level);
this.emit(' ');
const name = _name(node.paramName);
this.emit(`$${_avoidKeywords(name)}\n`);
}
}
visitReturnType(ast, level) {
this.emit(' * @return ', level);
this.visitType(ast.returnType, level);
this.emit('\n');
}
eachAPI(ast, level, sse = false) {
// if (ast.annotation) {
// this.emit(`${_anno(ast.annotation.value)}\n`, level);
// }
let comments = DSL.comment.getFrontComments(this.comments, ast.tokenRange[0]);
this.visitComments(comments, level);
this.visitAnnotation(ast.annotation, level, false);
const apiName = _avoidKeywords(_name(ast.apiName));
if(!ast.annotation || !ast.annotation.value) {
this.emit('/**\n', level);
}
this.visitParamsType(ast.params, level);
this.visitReturnType(ast, level);
this.emit(' */', level);
this.emit('\n');
this.emit(`public function ${apiName}`, level);
this.visitParams(ast.params, level);
this.emit('\n');
this.emit('{\n', level);
let baseLevel = ast.runtimeBody ? level + 2 : level;
// api level
if (ast.runtimeBody) {
this.visitRuntimeBefore(ast.runtimeBody, level + 1);
}
// temp level
this.visitAPIBody(ast.apiBody, baseLevel + 1);
if(sse && ast.runtimeBody) {
this.emit('$_runtime[\'stream\'] = true;\n', baseLevel + 1);
}
this.emit('$_response = Dara::send($_request', baseLevel + 1);
if (ast.runtimeBody) {
this.emit(', $_runtime');
}
if(sse && !ast.runtimeBody) {
this.emit(', [ \'stream\' => true, ]');
}
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);
} else {
this.visitDefaultReturnBody(baseLevel + 1);
}
if (ast.runtimeBody) {
this.visitRuntimeAfter(ast.runtimeBody, level + 1);
}
this.emit('}\n', level);
}
visitRuntimeAfter(ast, level) {
const errorName = this.getRealModelName(ERROR);
this.emit(`} catch (${errorName} $e) {\n`, level + 1);
const retryContextName = this.getRealModelName(RETRY_CONTEXT);
this.emit(`$_context = new ${retryContextName}([\n`, level + 2);
this.emit('\'retriesAttempted\' => $_retriesAttempted,\n', level + 3);
this.emit('\'lastRequest\' => $_lastRequest,\n', level + 3);
this.emit('\'lastResponse\' => $_lastResponse,\n', level + 3);
this.emit('\'exception\' => $e,\n', level + 3);
this.emit(']);\n', level + 2);
this.emit('continue;\n', level + 2);
this.emit('}\n', level + 1);
this.emit('}\n', level);
this.emit('\n');
const unretryErrorName = this.getRealModelName(UNRETRY_ERROR);
this.emit(`throw new ${unretryErrorName}($_context);\n`, level);
}
importBefore(level) {
if (this.config.editable !== true) {
this.emit('// This file is auto-generated, don\'t edit it\n', level);
}
}
apiBefore(main, filepath, level) {
let clientName = this.config.clientName;
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) {
clientName = _string(clientNote.arg.value);
} else {
const fileInfo = path.parse(filepath);
clientName = _upperFirst(fileInfo.name);
}
}
this.emit(`class ${clientName}`, level);
if (this.parentModule) {
const moduleName = this.getRealClientName(this.parentModule.lexeme);
this.emit(` extends ${moduleName}`);
}
this.emit(' {\n', level);
}
functionBefore() {
this.emit('\n');
}
moduleAfter() {
this.emit(`
}
`);
}
}
module.exports = Visitor;