From 466ab2814ef794959fc77865f909c4d923c1021d Mon Sep 17 00:00:00 2001 From: rofrischmann Date: Sat, 11 Feb 2017 23:44:38 +0100 Subject: [PATCH 1/2] adding support for calc expressions closes #2 --- README.md | 1 + docs/ASTNodes.md | 97 +++++++++++++++++++++++++--------- modules/generator.js | 9 ++-- modules/index.js | 6 --- modules/parser.js | 27 ++++++---- modules/traverser.js | 1 + modules/utils/CSSValueRules.js | 7 ++- 7 files changed, 101 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 13b4ee4..0e331c0 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ ast === { * [Dimension](docs/ASTNodes.md#dimension) * [Float](docs/ASTNodes.md#float) * [Function](docs/ASTNodes.md#function) + * [Expression](docs/ASTNodes.md#expression) ## Support diff --git a/docs/ASTNodes.md b/docs/ASTNodes.md index 6c92340..bd38957 100644 --- a/docs/ASTNodes.md +++ b/docs/ASTNodes.md @@ -21,14 +21,15 @@ body: [ /* child nodes */ ] * [Dimension](#dimension) * [Float](#float) * [Function](#function) +* [Expression](#expression) ## Identifier Identifiers are all kind of words such as `solid`. ```javascript // e.g. solid { -type: 'Identifier', -value: 'solid' + type: 'Identifier', + value: 'solid' } ``` @@ -37,8 +38,8 @@ Integers are simple numbers without a unit or fractional part. ```javascript // e.g. 34 { -type: 'Integer', -value: 34 + type: 'Integer', + value: 34 } ``` @@ -47,8 +48,8 @@ Keywords are special identifier that are globally valid for CSS. These are `inhe ```javascript // e.g. inherit { -type: 'Keyword', -value: 'inherit' + type: 'Keyword', + value: 'inherit' } ``` @@ -58,8 +59,8 @@ Operators are basic arithmetic expression symbols for addition `+`, subtraction ```javascript // e.g. + { -type: 'Operator', -value: '+' + type: 'Operator', + value: '+' } ``` @@ -69,8 +70,8 @@ HexColor represents color values given in hexadecimal notation. ```javascript // e.g. #FF66FF { -type: 'HexColor', -value: '#FF66FF' + type: 'HexColor', + value: '#FF66FF' } ``` @@ -80,8 +81,8 @@ URL is used for any URI-type string. *It is not validated by the parser!* ```javascript // e.g. https://github.com/ { -type: 'URL', -value: 'https://github.com/' + type: 'URL', + value: 'https://github.com/' } ``` @@ -91,8 +92,8 @@ Strings are all values that are wrapped in quotes, either single `'` or double ` ```javascript // e.g. "I'm a string!!11!1" { -type: 'String', -value: 'I\'m a string!!11!1' + type: 'String', + value: 'I\'m a string!!11!1' } ``` @@ -113,10 +114,10 @@ Dimensions are special integers or floats that are postfixed with an extra unit. ```javascript // e.g. 12px { -type: 'Dimension', -value: 12, -unit: 'px', -dimension: 'absolute-length' + type: 'Dimension', + value: 12, + unit: 'px', + dimension: 'absolute-length' } ``` @@ -132,9 +133,9 @@ Floats are floating-point numbers with a fractional part and an integer part. *( ```javascript // e.g. 587.923 { -type: 'Float', -integer: 587, -fractional: 923 + type: 'Float', + integer: 587, + fractional: 923 } ``` @@ -150,8 +151,56 @@ Functions represent CSS functions wrapped in parentheses. // e.g. rgba(10, 20, 30, 0.55) { -type: 'Function', -callee: 'rgba' -params: [ /* param nodes */ ] + type: 'Function', + callee: 'rgba' + params: [{ + type: 'Integer', + value: 10 + }, { + type: 'Integer', + value: 20 + }, { + type: 'Integer', + value: 30 + }, { + type: 'Float', + integer: 0, + fractional: 55 + }] +} +``` + +## Expression +Expressions are mathematical calculations. They may only be used inside the CSS `calc`-function. + +| Property | Description | +| ------ | ------ | +| body | An array of any AST nodes | + +```javascript + +// e.g. 100% - 30px*3 +{ + type: 'Expression', + body: [{ + type: 'Dimension', + value: 100, + unit: '%', + dimension: 'percentage' + }, { + type: 'Operator', + value: '-' + }, { + type: 'Dimension', + value: 30, + unit: 'px', + dimension: 'absolute-length' + }, { + type: 'Operator', + value: '*' + }, { + type: 'Integer', + value: 3 + }] } ``` diff --git a/modules/generator.js b/modules/generator.js index caf5663..a301e67 100644 --- a/modules/generator.js +++ b/modules/generator.js @@ -9,6 +9,9 @@ export default class Generator { case 'CSSValue': return node.body.map(generateCSSValue).join(' ') + case 'Expression': + return node.body.map(generateCSSValue).join('') + case 'Function': return `${node.callee}(${node.params.map(generateCSSValue).join(',')})` @@ -19,9 +22,9 @@ export default class Generator { return `${node.integer ? node.integer : ''}.${node.fractional}` case 'Operator': - // we use spacings left and right to ensure - // correct syntax inside calc expressions - return ` ${node.value} ` + // for addition and substraction we use spacings left and right + // to ensure correct syntax inside calc expressions + return node.value === '+' || node.value === '-' ? ` ${node.value} ` : node.value case 'String': return node.quote + node.value + node.quote diff --git a/modules/index.js b/modules/index.js index b043984..b776a7a 100644 --- a/modules/index.js +++ b/modules/index.js @@ -24,9 +24,3 @@ export default function parse(input: string): ParsedCSSValue { } } } - -const input = '1px solid "helloMyName\'is\'foo" url(https://www.youtube.com/watch?v=CSvFpBOe8eY' - -const parsed = parse(input) - -console.log(parsed.toString()) diff --git a/modules/parser.js b/modules/parser.js index 946c473..d60f92b 100644 --- a/modules/parser.js +++ b/modules/parser.js @@ -100,6 +100,18 @@ export default class Parser { return this.parseURL() } + if (this.currentToken.value.indexOf('calc') > -1) { + const node = this.parseFunction() + + const functionParams = node.params + node.params = [{ + type: 'Expression', + body: functionParams + }] + + return node + } + return this.parseFunction() } @@ -247,16 +259,10 @@ export default class Parser { } parseHexColor() { - if (this.currentToken.type === 'hash') { - const nextToken = this.getNextToken(1) - - if (nextToken.type === 'hexadecimal') { - this.updateCurrentToken(1) - - return { - type: 'HexColor', - value: `#${nextToken.value}` - } + if (this.currentToken.type === 'hexadecimal') { + return { + type: 'HexColor', + value: this.currentToken.value } } } @@ -283,6 +289,7 @@ export default class Parser { this.parseString() if (!node) { + console.log(this.currentToken) throw new SyntaxError(('Cannot parse': node)) } diff --git a/modules/traverser.js b/modules/traverser.js index 0d8eadf..a2dc269 100644 --- a/modules/traverser.js +++ b/modules/traverser.js @@ -21,6 +21,7 @@ export default class Traverser { switch (node.type) { case 'CSSValue': + case 'Expression': this.traverseNodeList(node.body, node) break diff --git a/modules/utils/CSSValueRules.js b/modules/utils/CSSValueRules.js index eb132e3..cfddfbf 100644 --- a/modules/utils/CSSValueRules.js +++ b/modules/utils/CSSValueRules.js @@ -15,9 +15,8 @@ export default { number: /^\d+$/, url_chars: /^[&:=?]$/, floating_point: /^[.]$/, - hexadecimal: /^([0-9a-f]+)$/i, + hexadecimal: /^#([0-9a-f]*)$/i, whitespace: /^\s+$/, - paren: /^[()]+$/, - comma: /^,+$/, - hash: /^#$/ + paren: /^[()]$/, + comma: /^,+$/ } From 15b8c36713a024ca58df19c25111f9b2d1f49b29 Mon Sep 17 00:00:00 2001 From: rofrischmann Date: Sat, 11 Feb 2017 23:53:57 +0100 Subject: [PATCH 2/2] fix --- modules/__tests__/tokenizer-test.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/__tests__/tokenizer-test.js b/modules/__tests__/tokenizer-test.js index 7ed7e74..4ff7eba 100644 --- a/modules/__tests__/tokenizer-test.js +++ b/modules/__tests__/tokenizer-test.js @@ -160,11 +160,8 @@ describe('Tokenizing CSS values', () => { it('should return an array of tokens', () => { expect(tokenizeCSSValue('#FF66f6')).toEqual([{ - type: 'hash', - value: '#' - }, { type: 'hexadecimal', - value: 'FF66f6' + value: '#FF66f6' }]) }) })