165 lines
4.4 KiB
TypeScript
165 lines
4.4 KiB
TypeScript
import { Spec, Line, Tokens } from '../../primitives.js';
|
|
import { splitSpace, isSpace } from '../../util.js';
|
|
import { Tokenizer } from './index.js';
|
|
|
|
const isQuoted = (s: string) => s && s.startsWith('"') && s.endsWith('"');
|
|
|
|
/**
|
|
* Splits remaining `spec.lines[].tokens.description` into `name` and `descriptions` tokens,
|
|
* and populates the `spec.name`
|
|
*/
|
|
export default function nameTokenizer(): Tokenizer {
|
|
const typeEnd = (num: number, { tokens }: Line, i: number) =>
|
|
tokens.type === '' ? num : i;
|
|
|
|
return (spec: Spec): Spec => {
|
|
// look for the name starting in the line where {type} ends
|
|
let finalTypeLine = spec.source.reduce(typeEnd, 0);
|
|
|
|
let tokens: Tokens;
|
|
if (spec.type) {
|
|
do {
|
|
({ tokens } = spec.source[finalTypeLine]);
|
|
if (tokens.description.trim()) {
|
|
break;
|
|
}
|
|
finalTypeLine++;
|
|
} while (spec.source[finalTypeLine]);
|
|
} else {
|
|
({ tokens } = spec.source[finalTypeLine]);
|
|
}
|
|
|
|
const source = tokens.description.trimStart();
|
|
|
|
const quotedGroups = source.split('"');
|
|
|
|
// if it starts with quoted group, assume it is a literal
|
|
if (
|
|
quotedGroups.length > 1 &&
|
|
quotedGroups[0] === '' &&
|
|
quotedGroups.length % 2 === 1
|
|
) {
|
|
spec.name = quotedGroups[1];
|
|
tokens.name = `"${quotedGroups[1]}"`;
|
|
[tokens.postName, tokens.description] = splitSpace(
|
|
source.slice(tokens.name.length)
|
|
);
|
|
return spec;
|
|
}
|
|
|
|
let brackets = 0;
|
|
let name = '';
|
|
let optional = false;
|
|
let defaultValue: string;
|
|
|
|
// assume name is non-space string or anything wrapped into brackets
|
|
for (const ch of source) {
|
|
if (brackets === 0 && isSpace(ch)) break;
|
|
if (ch === '[') brackets++;
|
|
if (ch === ']') brackets--;
|
|
name += ch;
|
|
}
|
|
|
|
if (brackets !== 0) {
|
|
spec.problems.push({
|
|
code: 'spec:name:unpaired-brackets',
|
|
message: 'unpaired brackets',
|
|
line: spec.source[0].number,
|
|
critical: true,
|
|
});
|
|
return spec;
|
|
}
|
|
|
|
const nameToken = name;
|
|
|
|
if (name[0] === '[' && name[name.length - 1] === ']') {
|
|
optional = true;
|
|
name = name.slice(1, -1);
|
|
|
|
const parts = name.split('=');
|
|
name = parts[0].trim();
|
|
if (parts[1] !== undefined)
|
|
defaultValue = parts.slice(1).join('=').trim();
|
|
|
|
if (name === '') {
|
|
spec.problems.push({
|
|
code: 'spec:name:empty-name',
|
|
message: 'empty name',
|
|
line: spec.source[0].number,
|
|
critical: true,
|
|
});
|
|
return spec;
|
|
}
|
|
|
|
if (defaultValue === '') {
|
|
spec.problems.push({
|
|
code: 'spec:name:empty-default',
|
|
message: 'empty default value',
|
|
line: spec.source[0].number,
|
|
critical: true,
|
|
});
|
|
return spec;
|
|
}
|
|
|
|
// has "=" and is not a string, except for "=>"
|
|
if (!isQuoted(defaultValue) && /=(?!>)/.test(defaultValue)) {
|
|
spec.problems.push({
|
|
code: 'spec:name:invalid-default',
|
|
message: 'invalid default value syntax',
|
|
line: spec.source[0].number,
|
|
critical: true,
|
|
});
|
|
return spec;
|
|
}
|
|
}
|
|
|
|
if (!optional) {
|
|
const eqIndex = name.search(/=(?!>)/);
|
|
if (eqIndex !== -1) {
|
|
defaultValue = name.slice(eqIndex + 1).trim();
|
|
name = name.slice(0, eqIndex).trim();
|
|
|
|
if (name === '') {
|
|
spec.problems.push({
|
|
code: 'spec:name:empty-name',
|
|
message: 'empty name',
|
|
line: spec.source[0].number,
|
|
critical: true,
|
|
});
|
|
return spec;
|
|
}
|
|
|
|
if (defaultValue === '') {
|
|
spec.problems.push({
|
|
code: 'spec:name:empty-default',
|
|
message: 'empty default value',
|
|
line: spec.source[0].number,
|
|
critical: true,
|
|
});
|
|
return spec;
|
|
}
|
|
|
|
if (!isQuoted(defaultValue) && /=(?!>)/.test(defaultValue)) {
|
|
spec.problems.push({
|
|
code: 'spec:name:invalid-default',
|
|
message: 'invalid default value syntax',
|
|
line: spec.source[0].number,
|
|
critical: true,
|
|
});
|
|
return spec;
|
|
}
|
|
}
|
|
}
|
|
|
|
spec.optional = optional;
|
|
spec.name = name;
|
|
tokens.name = nameToken;
|
|
|
|
if (defaultValue !== undefined) spec.default = defaultValue;
|
|
[tokens.postName, tokens.description] = splitSpace(
|
|
source.slice(tokens.name.length)
|
|
);
|
|
return spec;
|
|
};
|
|
}
|