Skip to content

Commit 153d0f2

Browse files
fix: handle uppercase units and functions (#71)
1 parent 185f0fb commit 153d0f2

File tree

3 files changed

+153
-129
lines changed

3 files changed

+153
-129
lines changed

src/__tests__/index.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,3 +604,24 @@ test(
604604
'calc(var(--foo) + 10px)',
605605
'calc(var(--foo) + 10px)',
606606
);
607+
608+
test(
609+
'should reduce calc (uppercase)',
610+
testValue,
611+
'CALC(1PX + 1PX)',
612+
'2PX',
613+
);
614+
615+
test(
616+
'should reduce calc (uppercase) (#1)',
617+
testValue,
618+
'CALC(VAR(--foo) + VAR(--bar))',
619+
'CALC(VAR(--foo) + VAR(--bar))',
620+
);
621+
622+
test(
623+
'should reduce calc (uppercase) (#2)',
624+
testValue,
625+
'CALC( (1EM - CALC( 10PX + 1EM)) / 2)',
626+
'-5PX',
627+
);

src/lib/convert.js

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
import convertUnits from 'css-unit-converter';
2-
3-
function convertNodes(left, right, precision) {
4-
switch (left.type) {
5-
case 'LengthValue':
6-
case 'AngleValue':
7-
case 'TimeValue':
8-
case 'FrequencyValue':
9-
case 'ResolutionValue':
10-
return convertAbsoluteLength(left, right, precision);
11-
default:
12-
return { left, right };
13-
}
14-
}
15-
16-
function convertAbsoluteLength(left, right, precision) {
17-
if (right.type === left.type) {
18-
right = {
19-
type: left.type,
20-
value: convertUnits(right.value, right.unit, left.unit, precision),
21-
unit: left.unit,
22-
};
23-
}
24-
return { left, right };
25-
}
26-
27-
export default convertNodes;
1+
import convertUnits from 'css-unit-converter';
2+
3+
function convertNodes(left, right, precision) {
4+
switch (left.type) {
5+
case 'LengthValue':
6+
case 'AngleValue':
7+
case 'TimeValue':
8+
case 'FrequencyValue':
9+
case 'ResolutionValue':
10+
return convertAbsoluteLength(left, right, precision);
11+
default:
12+
return { left, right };
13+
}
14+
}
15+
16+
function convertAbsoluteLength(left, right, precision) {
17+
if (right.type === left.type) {
18+
right = {
19+
type: left.type,
20+
value: convertUnits(right.value, right.unit.toLowerCase(), left.unit.toLowerCase(), precision),
21+
unit: left.unit,
22+
};
23+
}
24+
return { left, right };
25+
}
26+
27+
export default convertNodes;

src/parser.jison

Lines changed: 105 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,105 @@
1-
/* description: Parses expressions. */
2-
3-
/* lexical grammar */
4-
%lex
5-
%%
6-
\s+ /* skip whitespace */
7-
8-
(\-(webkit|moz)\-)?calc\b return 'CALC';
9-
10-
[a-z][a-z0-9-]*\s*\((?:(?:\"(?:\\.|[^\"\\])*\"|\'(?:\\.|[^\'\\])*\')|\([^)]*\)|[^\(\)]*)*\) return 'FUNCTION';
11-
12-
"*" return 'MUL';
13-
"/" return 'DIV';
14-
"+" return 'ADD';
15-
"-" return 'SUB';
16-
17-
([0-9]+("."[0-9]+)?|"."[0-9]+)px\b return 'LENGTH';
18-
([0-9]+("."[0-9]+)?|"."[0-9]+)cm\b return 'LENGTH';
19-
([0-9]+("."[0-9]+)?|"."[0-9]+)mm\b return 'LENGTH';
20-
([0-9]+("."[0-9]+)?|"."[0-9]+)in\b return 'LENGTH';
21-
([0-9]+("."[0-9]+)?|"."[0-9]+)pt\b return 'LENGTH';
22-
([0-9]+("."[0-9]+)?|"."[0-9]+)pc\b return 'LENGTH';
23-
([0-9]+("."[0-9]+)?|"."[0-9]+)deg\b return 'ANGLE';
24-
([0-9]+("."[0-9]+)?|"."[0-9]+)grad\b return 'ANGLE';
25-
([0-9]+("."[0-9]+)?|"."[0-9]+)rad\b return 'ANGLE';
26-
([0-9]+("."[0-9]+)?|"."[0-9]+)turn\b return 'ANGLE';
27-
([0-9]+("."[0-9]+)?|"."[0-9]+)s\b return 'TIME';
28-
([0-9]+("."[0-9]+)?|"."[0-9]+)ms\b return 'TIME';
29-
([0-9]+("."[0-9]+)?|"."[0-9]+)Hz\b return 'FREQ';
30-
([0-9]+("."[0-9]+)?|"."[0-9]+)kHz\b return 'FREQ';
31-
([0-9]+("."[0-9]+)?|"."[0-9]+)dpi\b return 'RES';
32-
([0-9]+("."[0-9]+)?|"."[0-9]+)dpcm\b return 'RES';
33-
([0-9]+("."[0-9]+)?|"."[0-9]+)dppx\b return 'RES';
34-
([0-9]+("."[0-9]+)?|"."[0-9]+)em\b return 'EMS';
35-
([0-9]+("."[0-9]+)?|"."[0-9]+)ex\b return 'EXS';
36-
([0-9]+("."[0-9]+)?|"."[0-9]+)ch\b return 'CHS';
37-
([0-9]+("."[0-9]+)?|"."[0-9]+)rem\b return 'REMS';
38-
([0-9]+("."[0-9]+)?|"."[0-9]+)vw\b return 'VWS';
39-
([0-9]+("."[0-9]+)?|"."[0-9]+)vh\b return 'VHS';
40-
([0-9]+("."[0-9]+)?|"."[0-9]+)vmin\b return 'VMINS';
41-
([0-9]+("."[0-9]+)?|"."[0-9]+)vmax\b return 'VMAXS';
42-
([0-9]+("."[0-9]+)?|"."[0-9]+)\% return 'PERCENTAGE';
43-
([0-9]+("."[0-9]+)?|"."[0-9]+)\b return 'NUMBER';
44-
45-
"(" return 'LPAREN';
46-
")" return 'RPAREN';
47-
48-
<<EOF>> return 'EOF';
49-
50-
/lex
51-
52-
%left ADD SUB
53-
%left MUL DIV
54-
%left UPREC
55-
56-
57-
%start expression
58-
59-
%%
60-
61-
expression
62-
: math_expression EOF { return $1; }
63-
;
64-
65-
math_expression
66-
: CALC LPAREN math_expression RPAREN { $$ = $3; }
67-
| math_expression ADD math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3 }; }
68-
| math_expression SUB math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3 }; }
69-
| math_expression MUL math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3 }; }
70-
| math_expression DIV math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3 }; }
71-
| LPAREN math_expression RPAREN { $$ = $2; }
72-
| function { $$ = $1; }
73-
| css_value { $$ = $1; }
74-
| value { $$ = $1; }
75-
;
76-
77-
value
78-
: NUMBER { $$ = { type: 'Value', value: parseFloat($1) }; }
79-
| SUB NUMBER { $$ = { type: 'Value', value: parseFloat($2) * -1 }; }
80-
;
81-
82-
function
83-
: FUNCTION { $$ = { type: 'Function', value: $1 }; }
84-
;
85-
86-
css_value
87-
: LENGTH { $$ = { type: 'LengthValue', value: parseFloat($1), unit: /[a-z]+/.exec($1)[0] }; }
88-
| ANGLE { $$ = { type: 'AngleValue', value: parseFloat($1), unit: /[a-z]+/.exec($1)[0] }; }
89-
| TIME { $$ = { type: 'TimeValue', value: parseFloat($1), unit: /[a-z]+/.exec($1)[0] }; }
90-
| FREQ { $$ = { type: 'FrequencyValue', value: parseFloat($1), unit: /[a-z]+/.exec($1)[0] }; }
91-
| RES { $$ = { type: 'ResolutionValue', value: parseFloat($1), unit: /[a-z]+/.exec($1)[0] }; }
92-
| EMS { $$ = { type: 'EmValue', value: parseFloat($1), unit: 'em' }; }
93-
| EXS { $$ = { type: 'ExValue', value: parseFloat($1), unit: 'ex' }; }
94-
| CHS { $$ = { type: 'ChValue', value: parseFloat($1), unit: 'ch' }; }
95-
| REMS { $$ = { type: 'RemValue', value: parseFloat($1), unit: 'rem' }; }
96-
| VHS { $$ = { type: 'VhValue', value: parseFloat($1), unit: 'vh' }; }
97-
| VWS { $$ = { type: 'VwValue', value: parseFloat($1), unit: 'vw' }; }
98-
| VMINS { $$ = { type: 'VminValue', value: parseFloat($1), unit: 'vmin' }; }
99-
| VMAXS { $$ = { type: 'VmaxValue', value: parseFloat($1), unit: 'vmax' }; }
100-
| PERCENTAGE { $$ = { type: 'PercentageValue', value: parseFloat($1), unit: '%' }; }
101-
| SUB css_value { var prev = $2; prev.value *= -1; $$ = prev; }
102-
;
1+
/* description: Parses expressions. */
2+
3+
/* lexical grammar */
4+
%lex
5+
6+
%options case-insensitive
7+
8+
%%
9+
\s+ /* skip whitespace */
10+
11+
(\-(webkit|moz)\-)?calc\b return 'CALC';
12+
13+
[a-z][a-z0-9-]*\s*\((?:(?:\"(?:\\.|[^\"\\])*\"|\'(?:\\.|[^\'\\])*\')|\([^)]*\)|[^\(\)]*)*\) return 'FUNCTION';
14+
15+
"*" return 'MUL';
16+
"/" return 'DIV';
17+
"+" return 'ADD';
18+
"-" return 'SUB';
19+
20+
([0-9]+("."[0-9]+)?|"."[0-9]+)px\b return 'LENGTH';
21+
([0-9]+("."[0-9]+)?|"."[0-9]+)cm\b return 'LENGTH';
22+
([0-9]+("."[0-9]+)?|"."[0-9]+)mm\b return 'LENGTH';
23+
([0-9]+("."[0-9]+)?|"."[0-9]+)in\b return 'LENGTH';
24+
([0-9]+("."[0-9]+)?|"."[0-9]+)pt\b return 'LENGTH';
25+
([0-9]+("."[0-9]+)?|"."[0-9]+)pc\b return 'LENGTH';
26+
([0-9]+("."[0-9]+)?|"."[0-9]+)deg\b return 'ANGLE';
27+
([0-9]+("."[0-9]+)?|"."[0-9]+)grad\b return 'ANGLE';
28+
([0-9]+("."[0-9]+)?|"."[0-9]+)rad\b return 'ANGLE';
29+
([0-9]+("."[0-9]+)?|"."[0-9]+)turn\b return 'ANGLE';
30+
([0-9]+("."[0-9]+)?|"."[0-9]+)s\b return 'TIME';
31+
([0-9]+("."[0-9]+)?|"."[0-9]+)ms\b return 'TIME';
32+
([0-9]+("."[0-9]+)?|"."[0-9]+)Hz\b return 'FREQ';
33+
([0-9]+("."[0-9]+)?|"."[0-9]+)kHz\b return 'FREQ';
34+
([0-9]+("."[0-9]+)?|"."[0-9]+)dpi\b return 'RES';
35+
([0-9]+("."[0-9]+)?|"."[0-9]+)dpcm\b return 'RES';
36+
([0-9]+("."[0-9]+)?|"."[0-9]+)dppx\b return 'RES';
37+
([0-9]+("."[0-9]+)?|"."[0-9]+)em\b return 'EMS';
38+
([0-9]+("."[0-9]+)?|"."[0-9]+)ex\b return 'EXS';
39+
([0-9]+("."[0-9]+)?|"."[0-9]+)ch\b return 'CHS';
40+
([0-9]+("."[0-9]+)?|"."[0-9]+)rem\b return 'REMS';
41+
([0-9]+("."[0-9]+)?|"."[0-9]+)vw\b return 'VWS';
42+
([0-9]+("."[0-9]+)?|"."[0-9]+)vh\b return 'VHS';
43+
([0-9]+("."[0-9]+)?|"."[0-9]+)vmin\b return 'VMINS';
44+
([0-9]+("."[0-9]+)?|"."[0-9]+)vmax\b return 'VMAXS';
45+
([0-9]+("."[0-9]+)?|"."[0-9]+)\% return 'PERCENTAGE';
46+
([0-9]+("."[0-9]+)?|"."[0-9]+)\b return 'NUMBER';
47+
48+
"(" return 'LPAREN';
49+
")" return 'RPAREN';
50+
51+
<<EOF>> return 'EOF';
52+
53+
/lex
54+
55+
%left ADD SUB
56+
%left MUL DIV
57+
%left UPREC
58+
59+
60+
%start expression
61+
62+
%%
63+
64+
expression
65+
: math_expression EOF { return $1; }
66+
;
67+
68+
math_expression
69+
: CALC LPAREN math_expression RPAREN { $$ = $3; }
70+
| math_expression ADD math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3 }; }
71+
| math_expression SUB math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3 }; }
72+
| math_expression MUL math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3 }; }
73+
| math_expression DIV math_expression { $$ = { type: 'MathExpression', operator: $2, left: $1, right: $3 }; }
74+
| LPAREN math_expression RPAREN { $$ = $2; }
75+
| function { $$ = $1; }
76+
| css_value { $$ = $1; }
77+
| value { $$ = $1; }
78+
;
79+
80+
value
81+
: NUMBER { $$ = { type: 'Value', value: parseFloat($1) }; }
82+
| SUB NUMBER { $$ = { type: 'Value', value: parseFloat($2) * -1 }; }
83+
;
84+
85+
function
86+
: FUNCTION { $$ = { type: 'Function', value: $1 }; }
87+
;
88+
89+
css_value
90+
: LENGTH { $$ = { type: 'LengthValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0] }; }
91+
| ANGLE { $$ = { type: 'AngleValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0] }; }
92+
| TIME { $$ = { type: 'TimeValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0] }; }
93+
| FREQ { $$ = { type: 'FrequencyValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0] }; }
94+
| RES { $$ = { type: 'ResolutionValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0] }; }
95+
| EMS { $$ = { type: 'EmValue', value: parseFloat($1), unit: 'em' }; }
96+
| EXS { $$ = { type: 'ExValue', value: parseFloat($1), unit: 'ex' }; }
97+
| CHS { $$ = { type: 'ChValue', value: parseFloat($1), unit: 'ch' }; }
98+
| REMS { $$ = { type: 'RemValue', value: parseFloat($1), unit: 'rem' }; }
99+
| VHS { $$ = { type: 'VhValue', value: parseFloat($1), unit: 'vh' }; }
100+
| VWS { $$ = { type: 'VwValue', value: parseFloat($1), unit: 'vw' }; }
101+
| VMINS { $$ = { type: 'VminValue', value: parseFloat($1), unit: 'vmin' }; }
102+
| VMAXS { $$ = { type: 'VmaxValue', value: parseFloat($1), unit: 'vmax' }; }
103+
| PERCENTAGE { $$ = { type: 'PercentageValue', value: parseFloat($1), unit: '%' }; }
104+
| SUB css_value { var prev = $2; prev.value *= -1; $$ = prev; }
105+
;

0 commit comments

Comments
 (0)