@maniascript/api
Version:
Maniascript API generator
262 lines (261 loc) • 10.1 kB
JavaScript
import { DocLexer } from '../antlr/DocLexer.js';
import { generateFromParseResult, parse } from './doc-generator.js';
class APIVisitorError extends Error {
}
function isDefined(value, message) {
if (value === null || value === undefined) {
throw new APIVisitorError(message);
}
return value;
}
function getCtxDocumentation(ctx, tokens) {
if (ctx.start !== null) {
const docCommentTokens = tokens.getHiddenTokensToLeft(ctx.start.tokenIndex, DocLexer.channelNames.indexOf('DOC_COMMENT'));
if (docCommentTokens !== undefined && docCommentTokens.length > 0) {
const docToken = docCommentTokens[docCommentTokens.length - 1];
const doc = docToken.text;
// Check that the doc block is not empty
if (doc !== undefined && doc.slice(3, -2).trim() !== '') {
const result = parse(doc);
if (!result.success) {
let errorList = '';
for (const error of result.errors) {
errorList += `${error.line.toString()}:${error.column.toString()} - ${error.message}\n`;
}
throw new APIVisitorError(`Failed to parse documentation comment (${docToken.line.toString()}:${docToken.column.toString()}):\n${errorList}`);
}
let parsedDocumentation = generateFromParseResult(result);
if (Object.keys(parsedDocumentation).length === 0) {
parsedDocumentation = undefined;
}
return {
rawDocumentation: doc,
parsedDocumentation
};
}
}
}
return {};
}
class APIVisitor {
tokens;
constructor(tokens) {
this.tokens = tokens;
}
visitTypeLiteral(ctx) {
return {
category: 'literal',
name: isDefined(ctx.getChild(0)?.getText(), 'Variable name is undefined')
};
}
visitTypeClass(ctx) {
return {
category: 'class',
name: ctx.IDENTIFIER().getText()
};
}
visitTypeEnum(ctx) {
return {
category: 'enum',
namespace: isDefined(ctx.IDENTIFIER(0)?.getText(), 'Enum namespace is undefined'),
name: isDefined(ctx.IDENTIFIER(1)?.getText(), 'Enum name is undefined')
};
}
visitTypeArrayValue(ctx) {
const typeLiteral = ctx.typeLiteral();
if (typeLiteral !== null) {
return this.visitTypeLiteral(typeLiteral);
}
else {
const typeClass = ctx.typeClass();
if (typeClass !== null) {
return this.visitTypeClass(typeClass);
}
else {
const typeEnum = ctx.typeEnum();
if (typeEnum !== null) {
return this.visitTypeEnum(typeEnum);
}
else {
throw new Error('Unknown array type');
}
}
}
}
visitTypeArray(ctx) {
const type = {
category: 'array'
};
const newType = ctx.type();
if (newType !== null) {
type.type = this.visitType(newType);
}
else {
const oldType = ctx.typeArrayValue();
if (oldType !== null) {
type.type = this.visitTypeArrayValue(oldType);
}
else {
throw new APIVisitorError('Unknown array type');
}
}
return type;
}
visitType(ctx) {
let type;
const typeLiteral = ctx.typeLiteral();
if (typeLiteral !== null) {
type = this.visitTypeLiteral(typeLiteral);
}
else {
const typeClass = ctx.typeClass();
if (typeClass !== null) {
type = this.visitTypeClass(typeClass);
}
else {
const typeEnum = ctx.typeEnum();
if (typeEnum !== null) {
type = this.visitTypeEnum(typeEnum);
}
else {
const typeArray = ctx.typeArray();
if (typeArray !== null) {
type = this.visitTypeArray(typeArray);
}
else {
throw new APIVisitorError('Unknown type');
}
}
}
}
type.isValueConst = (ctx._isValueConst !== undefined) ? true : undefined;
type.isPointer = (ctx._isPointer !== undefined) ? true : undefined;
type.isPointerConst = (ctx._isPointerConst !== undefined) ? true : undefined;
return type;
}
visitVariableDeclaration(ctx) {
const documentation = getCtxDocumentation(ctx, this.tokens);
return {
rawDocumentation: documentation.rawDocumentation,
documentation: documentation.parsedDocumentation,
type: this.visitType(isDefined(ctx._variableType, 'Variable type is undefined')),
isConst: (ctx._isConst !== undefined) ? true : undefined
};
}
visitFunctionParameter(ctx) {
return {
type: this.visitType(isDefined(ctx._parameterType, 'Function parameter type is undefined')),
name: isDefined(ctx._name?.text, 'Function paramter name is undefined')
};
}
visitFunctionDeclaration(ctx) {
const documentation = getCtxDocumentation(ctx, this.tokens);
const functionDeclaration = {
rawDocumentation: documentation.rawDocumentation,
documentation: documentation.parsedDocumentation,
type: this.visitType(isDefined(ctx._functionType, 'Function type is undefined')),
parameters: []
};
const parameters = ctx.functionParameterList()?._parameters;
if (parameters !== undefined) {
for (const parameter of parameters) {
functionDeclaration.parameters.push(this.visitFunctionParameter(parameter));
}
}
return functionDeclaration;
}
visitEnumDeclaration(ctx) {
const values = [];
for (const value of ctx.enumValueList()._values) {
if (value._name?.text !== undefined) {
values.push(value._name.text);
}
}
const documentation = getCtxDocumentation(ctx, this.tokens);
return {
rawDocumentation: documentation.rawDocumentation,
documentation: documentation.parsedDocumentation,
values
};
}
visitMemberDeclarationList(ctx) {
const content = {};
for (const enumDeclaration of ctx.enumDeclaration()) {
if (enumDeclaration._name?.text !== undefined) {
if (content.enums === undefined) {
content.enums = {};
}
content.enums[enumDeclaration._name.text] = this.visitEnumDeclaration(enumDeclaration);
}
}
for (const functionDeclaration of ctx.functionDeclaration()) {
if (functionDeclaration._name?.text !== undefined) {
if (content.functions === undefined) {
content.functions = {};
}
if (functionDeclaration._name.text in content.functions) {
content.functions[functionDeclaration._name.text].push(this.visitFunctionDeclaration(functionDeclaration));
}
else {
content.functions[functionDeclaration._name.text] = [this.visitFunctionDeclaration(functionDeclaration)];
}
}
}
for (const variableDeclaration of ctx.variableDeclaration()) {
if (variableDeclaration._name?.text !== undefined) {
if (content.variables === undefined) {
content.variables = {};
}
content.variables[variableDeclaration._name.text] = this.visitVariableDeclaration(variableDeclaration);
}
}
return content;
}
visitClassDeclaration(api, ctx) {
if (ctx._name?.text !== undefined) {
api.classNames.push(ctx._name.text);
const classContent = {};
if (ctx._content !== undefined) {
Object.assign(classContent, this.visitMemberDeclarationList(ctx._content));
}
if (ctx._parent?.text !== undefined) {
Object.assign(classContent, { parent: ctx._parent.text });
}
const documentation = getCtxDocumentation(ctx, this.tokens);
classContent.rawDocumentation = documentation.rawDocumentation;
classContent.documentation = documentation.parsedDocumentation;
api.classes[ctx._name.text] = classContent;
}
return api;
}
visitNamespaceDeclaration(api, ctx) {
if (ctx._name?.text !== undefined) {
api.namespaceNames.push(ctx._name.text);
const classContent = {};
if (ctx._content !== undefined) {
Object.assign(classContent, this.visitMemberDeclarationList(ctx._content));
}
const documentation = getCtxDocumentation(ctx, this.tokens);
classContent.rawDocumentation = documentation.rawDocumentation;
classContent.documentation = documentation.parsedDocumentation;
api.namespaces[ctx._name.text] = classContent;
}
return api;
}
visit(ctx) {
let api = {
classNames: [],
namespaceNames: [],
classes: {},
namespaces: {}
};
for (const classDeclaration of ctx.classDeclaration()) {
api = this.visitClassDeclaration(api, classDeclaration);
}
for (const namespaceDeclaration of ctx.namespaceDeclaration()) {
api = this.visitNamespaceDeclaration(api, namespaceDeclaration);
}
return api;
}
}
export { APIVisitor, APIVisitorError };