ti-el
Version:
A TL (Type Language) parser
216 lines (186 loc) • 5.23 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.tldoc = void 0;
var _tl = require("./tl");
const findCommentOnLine = line => {
const commentRegexp = /^[ \t]*\/\/(.*)/;
const [, comment] = line.match(commentRegexp) || [];
return comment;
};
const findComments = str => str.split(/\r?\n/).map(findCommentOnLine).map((e, i) => e ? {
line: i + 1,
string: e.trim()
} : null).filter(Boolean);
const findDocComments = str => findComments(str).filter(({
string
}) => ['@', '-'].includes(string[0])).map(obj => ({ ...obj,
string: obj.string.replace(/^-/, '')
}));
const last = arr => arr[arr.length - 1];
const push = (el, arr) => {
arr.push(el);
return arr;
};
const updateLast = (el, arr) => {
arr[arr.length - 1] = el;
return arr;
};
const groupDocComments = comments => comments.reduce((acc, el, i) => {
if (i === 0) return push(el, acc);
const {
line,
string
} = el;
const {
line: prevLine,
string: prevString
} = last(acc);
return prevLine === line - 1 ? updateLast({
line,
string: prevString + ' ' + string
}, acc) : push(el, acc);
}, []); // mutates regexp
const regAll = (reg, str) => {
const output = [];
let result;
while (result = reg.exec(str)) output.push(result);
return output;
};
const parseDocComment = string => {
const regexp = /@(.+?) ([^@]+)/g;
return new Map(regAll(regexp, string).map(result => {
const [, key = "", value = ""] = result;
return [key, value.trim()];
}));
}; // ---
const removeHash = str => str.replace(/#.*/, '');
const combinatorsFromAST = ast => {
const constrDecls = ast.constructors.declarations;
const funcDecls = ast.functions.declarations;
const constCombs = constrDecls.filter(e => e.type === 'CombinatorDeclaration' // Also remove vector constructors
&& e.id.name !== 'vector');
const funcCombs = funcDecls.filter(e => e.type === 'CombinatorDeclaration');
const combinators = constCombs.map(c => normalizeCombinator('constructor', c)).concat(funcCombs.map(c => normalizeCombinator('function', c)));
return combinators;
};
const normalizeExpression = expr => {
if (typeof expr === 'number') throw new Error('Invalid expression');
if (expr.type === 'ETypeIdentifier') return {
type: expr.id.name,
vector: 0
};
if (expr.type === 'EExpression') {
if (expr.subexpressions.length !== 2) throw new Error('Unsupported expression length');
const typeConstr = expr.subexpressions[0];
if (typeConstr.type === 'ETypeIdentifier' && typeConstr.id.name === 'vector') {
const subterm = expr.subexpressions[1];
const nSubterm = normalizeExpression(subterm);
return { ...nSubterm,
vector: nSubterm.vector + 1
};
}
throw new Error('Unsupported type constructor');
}
throw new Error('Unsupported expression');
};
const normalizeArgument = arg => {
const {
id
} = arg;
if (!id) throw new Error('No arg.id');
const {
name
} = id;
const type = arg.argType.expression;
return {
name,
...normalizeExpression(type)
};
};
const normalizeCombinator = (kind, comb) => {
const line = comb.start.line;
const name = removeHash(comb.id.name);
if (!comb.args) throw new Error('No combinator.args');
const args = comb.args.map(normalizeArgument);
const result = comb.resultType.id.name;
return {
line,
kind,
name,
args,
result
};
}; // ---
const tldoc = input => {
const comments = groupDocComments(findDocComments(input));
const docMaps = comments.map(({
line,
string
}) => ({
line,
map: parseDocComment(string)
}));
const baseClasses = docMaps.map(e => e.map).filter(m => !!m.get('class')).map(m => {
const className = m.get('class');
const description = m.get('description');
if (!description) throw new Error('No BaseClass description');
if (!className) throw new Error('No className');
return {
name: className,
description
};
});
const ast = (0, _tl.parse)(input);
const combinators = combinatorsFromAST(ast);
const classes = docMaps.filter(({
map
}) => !map.get('class')).map(({
line,
map
}) => {
const combinator = combinators.find(c => c.line === line + 1);
if (!combinator) throw new Error(`Combinator on line ${line + 1} not found`);
const {
name,
kind,
result
} = combinator;
let description;
const parameters = [];
for (const [key, value] of map.entries()) {
if (key === 'description') {
description = value;
continue;
}
const name = key.replace(/^param_/, '');
const combArg = combinator.args.find(arg => arg.name === name);
if (!combArg) throw new Error(`Argument ${name} not found on line ${line + 1}`);
const {
type,
vector
} = combArg;
parameters.push({
name,
type,
vector,
description: value
});
}
if (!description) throw new Error(`No description of ${name}`);
return {
line,
name,
kind,
description,
parameters,
result
};
});
return {
baseClasses,
classes
};
};
exports.tldoc = tldoc;
;