UNPKG

@mpxjs/webpack-plugin

Version:

mpx compile core

219 lines (218 loc) 7.07 kB
export class ExpressionParser { tokens; formatter; functions; current; constructor(input, formatter = val => parseFloat(val), functions = {}) { this.tokens = this.tokenize(input); this.formatter = formatter; this.functions = functions; this.current = 0; } tokenize(input) { const tokens = []; const regex = /(\d+\.?\d*(?:px|rpx|%|vw|vh)?|[+\-*/(),]|\b[a-zA-Z_][a-zA-Z0-9_]*\b)/g; let match; while ((match = regex.exec(input))) { if (/^\d+\.?\d*(?:px|rpx|%|vw|vh)?$/.test(match[0])) { const lastToken = tokens[tokens.length - 1]; const last2Token = tokens[tokens.length - 2]; if (lastToken?.type === '-' && (!last2Token || /^[+\-*/(,]$/.test(last2Token?.type))) { tokens.pop(); tokens.push({ type: 'NUMBER', value: '-' + match[0] }); } else { tokens.push({ type: 'NUMBER', value: match[0] }); } } else { tokens.push({ type: match[0], value: match[0] }); } } return tokens; } parse() { return this.expression(); } expression() { let node = this.term(); while (this.current < this.tokens.length && (this.tokens[this.current].type === '+' || this.tokens[this.current].type === '-')) { const operator = this.tokens[this.current].type; this.current++; const right = this.term(); node = this.applyOperator(operator, node, right); } return node; } term() { let node = this.factor(); while (this.current < this.tokens.length && (this.tokens[this.current].type === '*' || this.tokens[this.current].type === '/')) { const operator = this.tokens[this.current].type; this.current++; const right = this.factor(); node = this.applyOperator(operator, node, right); } return node; } factor() { const token = this.tokens[this.current]; if (token.type === 'NUMBER') { this.current++; const numericValue = this.formatter(token.value); return { type: 'NUMBER', value: numericValue }; } else if (token.type === '(') { this.current++; const node = this.expression(); if (this.tokens[this.current].type !== ')') { throw new Error('Expected closing parenthesis'); } this.current++; return node; } else if (this.functions[token.type]) { this.current++; if (this.tokens[this.current].type !== '(') { throw new Error('Expected opening parenthesis after function'); } this.current++; const args = this.parseArguments(); if (this.tokens[this.current].type !== ')') { throw new Error('Expected closing parenthesis'); } this.current++; return this.applyFunction(token.type, args); } throw new Error(`Unexpected token: ${token.type}`); } parseArguments() { const args = []; while (this.current < this.tokens.length && this.tokens[this.current].type !== ')') { args.push(this.expression()); if (this.tokens[this.current].type === ',') { this.current++; } } return args; } applyOperator(operator, left, right) { const leftVal = left.value; const rightVal = right.value; let result; switch (operator) { case '+': result = leftVal + rightVal; break; case '-': result = leftVal - rightVal; break; case '*': result = leftVal * rightVal; break; case '/': result = leftVal / rightVal; break; default: throw new Error(`Unknown operator: ${operator}`); } return { type: 'NUMBER', value: result }; } applyFunction(func, args) { if (args.some(arg => arg.type !== 'NUMBER')) { throw new Error('Function arguments must be numbers'); } const numericArgs = args.map(arg => arg.value); if (this.functions[func]) { return { type: 'NUMBER', value: this.functions[func].apply(null, numericArgs) }; } else { throw new Error(`Unknown function: ${func}`); } } } export function parseFunc(str, funcName) { const regex = new RegExp(`${funcName}\\(`, 'g'); const result = []; let match; while ((match = regex.exec(str)) !== null) { const start = match.index; let i = start + funcName.length + 1; let depth = 1; const args = []; let arg = ''; while (depth && i < str.length) { if (depth === 1 && (str[i] === ',' || str[i] === ')')) { args.push(arg.trim()); arg = ''; } else { arg += str[i]; } switch (str[i]) { case '(': depth++; break; case ')': depth--; break; default: // Do nothing } i++; } const end = regex.lastIndex = i; result.push({ start, end, args }); } return result; } export class ReplaceSource { _source; _replacements; constructor(source) { this._source = source; this._replacements = []; } replace(start, end, content) { this._replacements.push({ start, end, content }); } source() { if (this._replacements.length === 0) { return this._source; } let current = this._source; let pos = 0; const result = []; for (const replacement of this._replacements) { const start = Math.floor(replacement.start); const end = Math.floor(replacement.end) + 1; if (pos < start) { const offset = start - pos; result.push(current.slice(0, offset)); current = current.slice(offset); pos = start; } result.push(replacement.content); if (pos < end) { const offset = end - pos; current = current.slice(offset); pos = end; } } result.push(current); return result.join(''); } }