208 lines
6.4 KiB
TypeScript
208 lines
6.4 KiB
TypeScript
/*
|
|
* Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
|
*
|
|
* openInula is licensed under Mulan PSL v2.
|
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
* You may obtain a copy of Mulan PSL v2 at:
|
|
*
|
|
* http://license.coscl.org.cn/MulanPSL2
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
* See the Mulan PSL v2 for more details.
|
|
*/
|
|
|
|
import { lexer } from './parseMappingRule';
|
|
import { RawToken, Token } from '../types/types';
|
|
import { DEFAULT_PLURAL_KEYS } from '../constants';
|
|
import { Content, FunctionArg, PlainArg, Select, TokenContext } from '../types/interfaces';
|
|
import Lexer from './Lexer';
|
|
|
|
const getContext = (lt: Record<string, any>): TokenContext => ({
|
|
offset: lt.offset,
|
|
line: lt.line,
|
|
col: lt.col,
|
|
text: lt.text,
|
|
lineNum: lt.lineBreaks,
|
|
});
|
|
|
|
export const checkSelectType = (value: string): boolean => {
|
|
return value === 'plural' || value === 'select' || value === 'selectordinal';
|
|
};
|
|
|
|
class Parser {
|
|
cardinalKeys: string[] = DEFAULT_PLURAL_KEYS;
|
|
ordinalKeys: string[] = DEFAULT_PLURAL_KEYS;
|
|
|
|
constructor(message: string) {
|
|
lexer.reset(message);
|
|
}
|
|
|
|
isSelectKeyValid(token: RawToken, type: Select['type'], value: string) {
|
|
if (value[0] === '=') {
|
|
if (type === 'select') {
|
|
throw new Error('The key value of the select type is invalid.');
|
|
}
|
|
} else if (type !== 'select') {
|
|
const values = type === 'plural' ? this.cardinalKeys : this.ordinalKeys;
|
|
if (values.length > 0 && !values.includes(value)) {
|
|
throw new Error(`${type} type key value is invalid.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
processSelect({ value: arg }: any, isPlural: boolean, context: TokenContext, type: Select['type']): Select {
|
|
const select: Select = { type, arg, cases: [], ctx: context };
|
|
|
|
if (type === 'plural' || type === 'selectordinal') {
|
|
isPlural = true;
|
|
}
|
|
|
|
for (const token of lexer) {
|
|
switch (token.type) {
|
|
case 'offset': {
|
|
if (type === 'select') {
|
|
throw new Error('The complex offset of the select type is incorrect.');
|
|
}
|
|
if (select.cases.length > 0) {
|
|
throw new Error('The complex offset must be set before cases.');
|
|
}
|
|
|
|
select.offset = Number(token.value);
|
|
context.text += token.text;
|
|
context.lineNum += token.lineBreaks;
|
|
break;
|
|
}
|
|
case 'case': {
|
|
this.isSelectKeyValid(token, type, token.value);
|
|
select.cases.push({
|
|
key: token.value.replace(/=/g, ''),
|
|
tokens: this.parse(isPlural),
|
|
ctx: getContext(token),
|
|
});
|
|
break;
|
|
}
|
|
case 'end': {
|
|
return select;
|
|
}
|
|
default: {
|
|
throw new Error(`Unrecognized analyzer token: ${token.type}`);
|
|
}
|
|
}
|
|
}
|
|
throw new Error('The message end position is invalid.');
|
|
}
|
|
|
|
parseToken(token: RawToken, isPlural: boolean): PlainArg | FunctionArg | Select {
|
|
const context = getContext(token);
|
|
const nextToken = lexer.next();
|
|
|
|
if (!nextToken) {
|
|
throw new Error('The message end position is invalid.');
|
|
}
|
|
|
|
context.text += nextToken.text;
|
|
context.lineNum += nextToken.lineBreaks;
|
|
|
|
switch (nextToken.type) {
|
|
case 'end': {
|
|
return { type: 'argument', arg: token.value, ctx: context };
|
|
}
|
|
case 'func-simple': {
|
|
const end = lexer.next();
|
|
if (!end) {
|
|
throw new Error('The message end position is invalid.');
|
|
}
|
|
if (end.type !== 'end') {
|
|
throw new Error(`Unrecognized analyzer token: ${end.type}`);
|
|
}
|
|
context.text += end.text;
|
|
if (checkSelectType(nextToken.value.toLowerCase())) {
|
|
throw new Error(`Invalid parameter type: ${nextToken.value}`);
|
|
}
|
|
return {
|
|
type: 'function',
|
|
arg: token.value,
|
|
key: nextToken.value,
|
|
ctx: context,
|
|
};
|
|
}
|
|
case 'func-args': {
|
|
if (checkSelectType(nextToken.value.toLowerCase())) {
|
|
throw new Error(`Invalid parameter type: ${nextToken.value}`);
|
|
}
|
|
const param = this.parse(isPlural);
|
|
|
|
return {
|
|
type: 'function',
|
|
arg: token.value,
|
|
key: nextToken.value,
|
|
param,
|
|
ctx: context,
|
|
};
|
|
}
|
|
case 'select':
|
|
if (checkSelectType(nextToken.value)) {
|
|
return this.processSelect(token, isPlural, context, nextToken.value);
|
|
} else {
|
|
throw new Error(`Invalid select type: ${nextToken.value}`);
|
|
}
|
|
default:
|
|
throw new Error(`Unrecognized analyzer token: ${nextToken.type}`);
|
|
}
|
|
}
|
|
|
|
// 在根级别解析时,遇到结束符号即结束解析并返回结果;而在非根级别解析时,遇到结束符号会被视为不合法的结束位置,抛出错误
|
|
parse(isPlural: boolean, isRoot?: boolean): Array<Content | PlainArg | FunctionArg | Select> {
|
|
const tokens: any[] = [];
|
|
let content: string | Content | null = null;
|
|
|
|
for (const token of lexer) {
|
|
if (token.type === 'argument') {
|
|
if (content) {
|
|
content = null;
|
|
}
|
|
tokens.push(this.parseToken(token, isPlural));
|
|
} else if (token.type === 'octothorpe' && isPlural) {
|
|
if (content) {
|
|
content = null;
|
|
}
|
|
tokens.push({ type: 'octothorpe' });
|
|
} else if (token.type === 'end' && !isRoot) {
|
|
return tokens;
|
|
} else if (token.type === 'doubleapos') {
|
|
tokens.push(token.value);
|
|
} else if (token.type === 'quoted') {
|
|
tokens.push(token.value);
|
|
} else if (token.type === 'content') {
|
|
tokens.push(token.value);
|
|
} else {
|
|
let value = token.value;
|
|
if (!isPlural && token.type === 'quoted' && value[0] === '#') {
|
|
if (value.includes('{')) {
|
|
throw new Error(`Invalid template: ${value}`);
|
|
}
|
|
value = token.text;
|
|
}
|
|
if (content) {
|
|
content = value;
|
|
} else {
|
|
content = value;
|
|
tokens.push(content);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isRoot) {
|
|
return tokens;
|
|
}
|
|
throw new Error('The message end position is invalid.');
|
|
}
|
|
}
|
|
|
|
export default function parse(message: string): Array<Content | PlainArg | FunctionArg | Select> {
|
|
const parser = new Parser(message);
|
|
return parser.parse(false, true);
|
|
}
|