UNPKG

cione-comp-lib

Version:

`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`

174 lines (173 loc) 6.3 kB
/* @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 };