* Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { NapiLog } = require("./NapiLog")
const { Lexer, TokenType, code } = require("./lexer")
const { AstObject, ConfigNode, ConfigTerm, ConfigArray, NodeRefType, ObjectType, Ast } = require("./ast")
function copy(obj) {
return JSON.parse(JSON.stringify(obj));
}
class Parser {
constructor() {
this.lexer_ = new Lexer();
this.current_ = {}
this.srcQueue_ = []
this.astList = {}
}
parse(fn) {
if (this.srcQueue_.indexOf(fn) == -1) this.srcQueue_.push(fn)
while (this.srcQueue_.length > 0) {
let includeList = []
let oneAst = this.parseOne(this.srcQueue_[0], includeList)
if (oneAst == null) {
return false;
}
this.astList[this.srcQueue_[0]] = {
ast: oneAst,
include: includeList,
};
this.srcQueue_.shift();
this.srcQueue_ = this.srcQueue_.concat(includeList)
}
return this.astList;
astList.push(astList.shift());
this.ast_ = astList.shift();
if (!this.ast_.merge(astList)) {
NapiLog.logError("failed to merge ast");
return false;
}
if (this.ast_.getAstRoot() == null) {
NapiLog.logError(fn + ": Empty hcs file");
return false;
}
if (!this.ast_.expand()) {
return false;
}
return astList[0]
}
convertAbsPath(includeBy, includePath) {
if (navigator.userAgent.toLowerCase().indexOf("win") > -1) {
if (includePath[1] != ':') {
let currentSrc = includeBy.substring(0, includeBy.lastIndexOf("\\") + 1)
includePath = currentSrc + includePath.replace(/\//g, "\\");
includePath = includePath.replace(/\\\.\\/g, "\\");
}
}
else if (includePath[0] != '/') {
let currentSrc = includeBy.substring(0, includeBy.lastIndexOf("/") + 1)
includePath = currentSrc + includePath
}
return includePath
}
processInclude(includeList) {
do {
if (!this.lexer_.lex(this.current_) || this.current_.type != TokenType.STRING) {
NapiLog.logError(this.lexer_ + "syntax error, expect include path after ’#include‘");
return false;
}
let includePath = this.current_.strval;
if (includePath.length <= 0) {
NapiLog.logError(this.lexer_ + "include invalid file:" + this.includePath);
return false;
}
includePath = this.convertAbsPath(this.srcQueue_[0], includePath);
includeList.push(includePath);
if (!this.lexer_.lex(this.current_)) {
return false;
}
if (this.current_.type != TokenType.INCLUDE) {
break;
}
} while (true);
return true;
}
parseOne(src, includeList) {
if (!this.lexer_.initialize(src)) {
return null;
}
if (!this.lexer_.lex(this.current_)) {
return null;
}
if (this.current_.type == TokenType.INCLUDE && !this.processInclude(includeList)) {
return null;
}
let rootNode = null;
if (this.current_.type == TokenType.ROOT) {
let preToken = copy(this.current_);
preToken.type = TokenType.LITERAL;
preToken.strval = "root";
rootNode = this.parseNode(preToken);
if (rootNode == null) {
return null;
}
} else if (this.current_.type != TokenType.EOF) {
NapiLog.logError("syntax error, expect root node of end of file")
return null;
}
if (!this.lexer_.lex(this.current_) || this.current_.type != TokenType.EOF) {
NapiLog.logError("syntax error, expect EOF")
return null;
}
let oneAst = new Ast(rootNode);
return oneAst;
}
parseNode(name, bracesStart) {
if (!bracesStart) {
if (!this.lexer_.lex(this.current_) || this.current_.type != code('{')) {
NapiLog.logError("syntax error, node miss '{'")
return null;
}
}
let node = new ConfigNode(name, NodeRefType.NODE_NOREF, "");
let child;
while (this.lexer_.lex(this.current_) && this.current_.type != 125) {
switch (this.current_.type) {
case TokenType.TEMPLATE:
child = this.parseTemplate();
break;
case TokenType.LITERAL:
child = this.parseNodeAndTerm();
break;
default:
NapiLog.logError("syntax error, except '}' or TEMPLATE or LITERAL for node '" + name.strval + '\'')
return null;
}
if (child == null) {
return null;
}
node.addChild(child);
}
if (this.current_.type != code('}')) {
NapiLog.logError(this.lexer_ + "syntax error, node miss '}'");
return null;
}
return node;
}
parseNodeAndTerm() {
let name = copy(this.current_);
if (!this.lexer_.lex(this.current_)) {
NapiLog.logError(this.lexer_ + "syntax error, broken term or node");
return null;
}
switch (this.current_.type) {
case code('='):
return this.parseTerm(name);
case code('{'):
return this.parseNode(name, true);
case code(':'):
if (this.lexer_.lex(this.current_)) {
return this.parseNodeWithRef(name);
}
NapiLog.logError("syntax error, unknown node reference type");
break;
default:
NapiLog.logError("syntax error, except '=' or '{' or ':'");
break;
}
return null;
}
parseArray() {
let array = new ConfigArray(this.current_);
let arrayType = 0;
while (this.lexer_.lex(this.current_) && this.current_.type != code(']')) {
if (this.current_.type == TokenType.STRING) {
array.addChild(new AstObject("", ObjectType.PARSEROP_STRING, this.current_.strval, this.current_));
} else if (this.current_.type == TokenType.NUMBER) {
array.addChild(new AstObject("", ObjectType.PARSEROP_UINT64,
this.current_.numval, this.current_, this.current_.baseSystem));
} else {
NapiLog.logError(this.lexer_ + "syntax error, except STRING or NUMBER in array");
return nullptr;
}
if (arrayType == 0) {
arrayType = this.current_.type;
} else if (arrayType != this.current_.type) {
NapiLog.logError(this.lexer_ + "syntax error, not allow mix type array");
return null;
}
if (this.lexer_.lex(this.current_)) {
if (this.current_.type == code(']')) {
break;
} else if (this.current_.type != code(',')) {
NapiLog.logError(this.lexer_ + "syntax error, except ',' or ']'");
return null;
}
} else return null;
}
if (this.current_.type != code(']')) {
NapiLog.logError(this.lexer_ + "syntax error, miss ']' at end of array");
return null;
}
return array;
}
parseTerm(name) {
if (!this.lexer_.lex(this.current_)) {
NapiLog.logError("syntax error, miss value of config term");
return null;
}
let term = new ConfigTerm(name, null)
switch (this.current_.type) {
case TokenType.BOOL:
term.addChild(new AstObject("", ObjectType.PARSEROP_BOOL, this.current_.numval, this.current_));
break;
case TokenType.STRING:
term.addChild(new AstObject("", ObjectType.PARSEROP_STRING, this.current_.strval, this.current_));
break;
case TokenType.NUMBER:
term.addChild(new AstObject("", ObjectType.PARSEROP_UINT64,
this.current_.numval, this.current_, this.current_.baseSystem));
break;
case code('['): {
let list = this.parseArray();
if (list == null) {
return null;
} else {
term.addChild(list);
}
break;
}
case code('&'):
if (!this.lexer_.lex(this.current_) ||
(this.current_.type != TokenType.LITERAL && this.current_.type != TokenType.REF_PATH)) {
NapiLog.logError("syntax error, invalid config term definition");
return null;
}
term.addChild(new AstObject("", ObjectType.PARSEROP_NODEREF, this.current_.strval, this.current_));
break;
case TokenType.DELETE:
term.addChild(new AstObject("", ObjectType.PARSEROP_DELETE, this.current_.strval, this.current_));
break;
default:
NapiLog.logError("syntax error, invalid config term definition");
return null;
}
if (!this.lexer_.lex(this.current_) || this.current_.type != code(';')) {
NapiLog.logError("syntax error, miss ';'");
return null;
}
return term;
}
parseTemplate() {
if (!this.lexer_.lex(this.current_) || this.current_.type != TokenType.LITERAL) {
NapiLog.logError("syntax error, template miss name");
return null;
}
let name = copy(this.current_);
let node = this.parseNode(name, false);
if (node == null) {
return node;
}
node.setNodeType(NodeRefType.NODE_TEMPLATE);
return node;
}
parseNodeRef(name) {
if (!this.lexer_.lex(this.current_) ||
(this.current_.type != TokenType.LITERAL && this.current_.type != TokenType.REF_PATH)) {
NapiLog.logError(this.lexer_ + "syntax error, miss node reference path");
return nullptr;
}
let refPath = this.current_.strval;
let node = this.parseNode(name);
if (node == null) {
return null;
}
let configNode = node;
configNode.setNodeType(NodeRefType.NODE_REF);
configNode.setRefPath(refPath);
return node;
}
parseNodeDelete(name) {
let node = this.parseNode(name);
if (node == null) {
return null;
}
node.setNodeType(NodeRefType.NODE_DELETE);
return node;
}
parseNodeCopy(name) {
let nodePath = this.current_.strval;
let node = this.parseNode(name);
if (node == null) {
return null;
}
let nodeCopy = node;
nodeCopy.setNodeType(NodeRefType.NODE_COPY);
nodeCopy.setRefPath(nodePath);
return node;
}
parseNodeWithRef(name) {
switch (this.current_.type) {
case TokenType.REF_PATH:
case TokenType.LITERAL:
return this.parseNodeCopy(name);
case code('&'):
return this.parseNodeRef(name);
case TokenType.DELETE:
return this.parseNodeDelete(name);
case code(':'):
return this.parseNodeInherit(name);
default:
NapiLog.logError("syntax error, unknown node type");
break;
}
return null;
}
parseNodeInherit(name) {
if (!this.lexer_.lex(this.current_) ||
(this.current_.type != TokenType.LITERAL && this.current_.type != TokenType.REF_PATH)) {
NapiLog.logError("syntax error, miss node inherit path");
return null;
}
let inheritPath = this.current_.strval;
let node = this.parseNode(name);
if (node == null) {
return null;
}
node.setNodeType(NodeRefType.NODE_INHERIT);
node.setRefPath(inheritPath);
return node;
}
}
module.exports = {
Parser
}