/* description: Parses expressions. */ %options case-insensitive /* lexical grammar */ %lex %% \s+ /* skip whitespace */ (\-(webkit|moz)\-)?calc\b return 'CALC'; [a-z][a-z0-9-]*\s*\((?:(?:\"(?:\\.|[^\"\\])*\"|\'(?:\\.|[^\'\\])*\')|\([^)]*\)|[^\(\)]*)*\) return 'FUNCTION'; "*" return 'MUL'; "/" return 'DIV'; "+" return 'ADD'; "-" return 'SUB'; ([0-9]+("."[0-9]+)?|"."[0-9]+)em\b return 'LENGTH'; // em ([0-9]+("."[0-9]+)?|"."[0-9]+)ex\b return 'LENGTH'; // ex ([0-9]+("."[0-9]+)?|"."[0-9]+)ch\b return 'LENGTH'; // ch ([0-9]+("."[0-9]+)?|"."[0-9]+)rem\b return 'LENGTH'; // rem ([0-9]+("."[0-9]+)?|"."[0-9]+)vw\b return 'LENGTH'; // vw ([0-9]+("."[0-9]+)?|"."[0-9]+)vh\b return 'LENGTH'; // vh ([0-9]+("."[0-9]+)?|"."[0-9]+)vmin\b return 'LENGTH'; // vmin ([0-9]+("."[0-9]+)?|"."[0-9]+)vmax\b return 'LENGTH'; // vmax ([0-9]+("."[0-9]+)?|"."[0-9]+)vm\b return 'LENGTH'; // vm (non-standard name) ([0-9]+("."[0-9]+)?|"."[0-9]+)px\b return 'LENGTH'; // px ([0-9]+("."[0-9]+)?|"."[0-9]+)mm\b return 'LENGTH'; // mm ([0-9]+("."[0-9]+)?|"."[0-9]+)cm\b return 'LENGTH'; // cm ([0-9]+("."[0-9]+)?|"."[0-9]+)in\b return 'LENGTH'; // in ([0-9]+("."[0-9]+)?|"."[0-9]+)pt\b return 'LENGTH'; // pt ([0-9]+("."[0-9]+)?|"."[0-9]+)pc\b return 'LENGTH'; // pc ([0-9]+("."[0-9]+)?|"."[0-9]+)Q\b return 'LENGTH'; // Q ([0-9]+("."[0-9]+)?|"."[0-9]+)fr\b return 'LENGTH'; // fr ([0-9]+("."[0-9]+)?|"."[0-9]+)deg\b return 'ANGLE'; // deg ([0-9]+("."[0-9]+)?|"."[0-9]+)grad\b return 'ANGLE'; // grad ([0-9]+("."[0-9]+)?|"."[0-9]+)turn\b return 'ANGLE'; // turn ([0-9]+("."[0-9]+)?|"."[0-9]+)rad\b return 'ANGLE'; // rad ([0-9]+("."[0-9]+)?|"."[0-9]+)s\b return 'TIME'; // s ([0-9]+("."[0-9]+)?|"."[0-9]+)ms\b return 'TIME'; // ms ([0-9]+("."[0-9]+)?|"."[0-9]+)Hz\b return 'FREQ'; // Hz ([0-9]+("."[0-9]+)?|"."[0-9]+)kHz\b return 'FREQ'; // kHz ([0-9]+("."[0-9]+)?|"."[0-9]+)dpi\b return 'RES'; // dpi ([0-9]+("."[0-9]+)?|"."[0-9]+)dpcm\b return 'RES'; // dpcm ([0-9]+("."[0-9]+)?|"."[0-9]+)dppx\b return 'RES'; // dppm ([0-9]+("."[0-9]+)?|"."[0-9]+)\% return 'PERCENTAGE'; ([0-9]+("."[0-9]+)?|"."[0-9]+)\b return 'NUMBER'; "(" return 'LPAREN'; ")" return 'RPAREN'; #\{([\s\S]*?)\} return 'UNKNOWN'; // scss interpolation @\{([\s\S]*?)\} return 'UNKNOWN'; // less interpolation \S[^\s()*/+-]* return 'UNKNOWN'; <> return 'EOF'; /lex %left ADD SUB %left MUL DIV %left UPREC %start expression %% expression : math_expression EOF { return $1; } ; math_expression : CALC LPAREN math_expression RPAREN { $$ = $3; $$.source.start = { index: @1.range[0] }; $$.source.end = { index: @4.range[1] }; } | math_expression ADD math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3, source: { start: $1.source.start, end: $3.source.end, operator: { start: { index: @2.range[0] }, end: { index: @2.range[1] } } } }; } | math_expression SUB math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3, source: { start: $1.source.start, end: $3.source.end, operator: { start: { index: @2.range[0] }, end: { index: @2.range[1] } } } }; } | math_expression MUL math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3, source: { start: $1.source.start, end: $3.source.end, operator: { start: { index: @2.range[0] }, end: { index: @2.range[1] } } } }; } | math_expression DIV math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3, source: { start: $1.source.start, end: $3.source.end, operator: { start: { index: @2.range[0] }, end: { index: @2.range[1] } } } }; } | SUB math_expression %prec UPREC { if (@1.range[1] !== $2.source.start.index) { throw new Error('Unexpected spaces was found between sign and value'); } if (typeof $2.value !== 'number') { throw new Error('Unexpected sign'); } if ($2.sign) { throw new Error('Unexpected continuous sign'); } $$ = $2; $$.sign = '-' $$.value = -$2.value; $$.source.start.index = @1.range[0]; } | ADD math_expression %prec UPREC { if (@1.range[1] !== $2.source.start.index) { throw new Error('Unexpected spaces was found between sign and value'); } if (typeof $2.value !== 'number') { throw new Error('Unexpected sign'); } if ($2.sign) { throw new Error('Unexpected continuous sign'); } $$ = $2; $$.sign = '+' $$.source.start.index = @1.range[0]; } | LPAREN math_expression RPAREN { $$ = $2; $$.source.start = { index: @1.range[0] }; $$.source.end = { index: @3.range[1] }; } | function { $$ = $1; } | css_value { $$ = $1; } | value { $$ = $1; } ; value : NUMBER { $$ = { type: 'Value', value: parseFloat($1), source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } ; function : FUNCTION { $$ = { type: 'Function', value: $1, source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } ; css_value : LENGTH { $$ = { type: 'LengthValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } | ANGLE { $$ = { type: 'AngleValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } | TIME { $$ = { type: 'TimeValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } | FREQ { $$ = { type: 'FrequencyValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } | RES { $$ = { type: 'ResolutionValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } | PERCENTAGE { $$ = { type: 'PercentageValue', value: parseFloat($1), unit: '%', source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } | UNKNOWN { $$ = { type: 'UnknownValue', value: $1, unit: '', source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; } ;