cione-comp-lib
Version:
`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`
174 lines (173 loc) • 6.3 kB
JavaScript
/* @license
* Copyright 2019 Google LLC. All Rights Reserved.
* 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 numberNode = (value, unit) => ({ type: "number", number: value, unit });
const parseExpressions = (() => {
const cache = {};
const MAX_PARSE_ITERATIONS = 1e3;
return (inputString) => {
const cacheKey = inputString;
if (cacheKey in cache) {
return cache[cacheKey];
}
const expressions = [];
let parseIterations = 0;
while (inputString) {
if (++parseIterations > MAX_PARSE_ITERATIONS) {
inputString = "";
break;
}
const expressionParseResult = parseExpression(inputString);
const expression = expressionParseResult.nodes[0];
if (expression == null || expression.terms.length === 0) {
break;
}
expressions.push(expression);
inputString = expressionParseResult.remainingInput;
}
return cache[cacheKey] = expressions;
};
})();
const parseExpression = (() => {
const IS_IDENT_RE = /^(\-\-|[a-z\u0240-\uffff])/i;
const IS_OPERATOR_RE = /^([\*\+\/]|[\-]\s)/i;
const IS_EXPRESSION_END_RE = /^[\),]/;
const FUNCTION_ARGUMENTS_FIRST_TOKEN = "(";
const HEX_FIRST_TOKEN = "#";
return (inputString) => {
const terms = [];
while (inputString.length) {
inputString = inputString.trim();
if (IS_EXPRESSION_END_RE.test(inputString)) {
break;
} else if (inputString[0] === FUNCTION_ARGUMENTS_FIRST_TOKEN) {
const { nodes, remainingInput } = parseFunctionArguments(inputString);
inputString = remainingInput;
terms.push({
type: "function",
name: { type: "ident", value: "calc" },
arguments: nodes
});
} else if (IS_IDENT_RE.test(inputString)) {
const identParseResult = parseIdent(inputString);
const identNode = identParseResult.nodes[0];
inputString = identParseResult.remainingInput;
if (inputString[0] === FUNCTION_ARGUMENTS_FIRST_TOKEN) {
const { nodes, remainingInput } = parseFunctionArguments(inputString);
terms.push({ type: "function", name: identNode, arguments: nodes });
inputString = remainingInput;
} else {
terms.push(identNode);
}
} else if (IS_OPERATOR_RE.test(inputString)) {
terms.push({ type: "operator", value: inputString[0] });
inputString = inputString.slice(1);
} else {
const { nodes, remainingInput } = inputString[0] === HEX_FIRST_TOKEN ? parseHex(inputString) : parseNumber(inputString);
if (nodes.length === 0) {
break;
}
terms.push(nodes[0]);
inputString = remainingInput;
}
}
return { nodes: [{ type: "expression", terms }], remainingInput: inputString };
};
})();
const parseIdent = (() => {
const NOT_IDENT_RE = /[^a-z0-9_\-\u0240-\uffff]/i;
return (inputString) => {
const match = inputString.match(NOT_IDENT_RE);
const ident = match == null ? inputString : inputString.substr(0, match.index);
const remainingInput = match == null ? "" : inputString.substr(match.index);
return { nodes: [{ type: "ident", value: ident }], remainingInput };
};
})();
const parseNumber = (() => {
const VALUE_RE = /[\+\-]?(\d+[\.]\d+|\d+|[\.]\d+)([eE][\+\-]?\d+)?/;
const UNIT_RE = /^[a-z%]+/i;
const ALLOWED_UNITS = /^(m|mm|cm|rad|deg|[%])$/;
return (inputString) => {
const valueMatch = inputString.match(VALUE_RE);
const value = valueMatch == null ? "0" : valueMatch[0];
inputString = value == null ? inputString : inputString.slice(value.length);
const unitMatch = inputString.match(UNIT_RE);
let unit = unitMatch != null && unitMatch[0] !== "" ? unitMatch[0] : null;
const remainingInput = unitMatch == null ? inputString : inputString.slice(unit.length);
if (unit != null && !ALLOWED_UNITS.test(unit)) {
unit = null;
}
return {
nodes: [{
type: "number",
number: parseFloat(value) || 0,
unit
}],
remainingInput
};
};
})();
const parseHex = (() => {
const HEX_RE = /^[a-f0-9]*/i;
return (inputString) => {
inputString = inputString.slice(1).trim();
const hexMatch = inputString.match(HEX_RE);
const nodes = hexMatch == null ? [] : [{ type: "hex", value: hexMatch[0] }];
return {
nodes,
remainingInput: hexMatch == null ? inputString : inputString.slice(hexMatch[0].length)
};
};
})();
const parseFunctionArguments = (inputString) => {
const expressionNodes = [];
inputString = inputString.slice(1).trim();
while (inputString.length) {
const expressionParseResult = parseExpression(inputString);
expressionNodes.push(expressionParseResult.nodes[0]);
inputString = expressionParseResult.remainingInput.trim();
if (inputString[0] === ",") {
inputString = inputString.slice(1).trim();
} else if (inputString[0] === ")") {
inputString = inputString.slice(1);
break;
}
}
return { nodes: expressionNodes, remainingInput: inputString };
};
const $visitedTypes = Symbol("visitedTypes");
class ASTWalker {
constructor(visitedTypes) {
this[$visitedTypes] = visitedTypes;
}
walk(ast, callback) {
const remaining = ast.slice();
while (remaining.length) {
const next = remaining.shift();
if (this[$visitedTypes].indexOf(next.type) > -1) {
callback(next);
}
switch (next.type) {
case "expression":
remaining.unshift(...next.terms);
break;
case "function":
remaining.unshift(next.name, ...next.arguments);
break;
}
}
}
}
const ZERO = Object.freeze({ type: "number", number: 0, unit: null });
export { ASTWalker, ZERO, numberNode, parseExpressions };