Last active
May 21, 2018 03:59
-
-
Save brantwedel/3bba625840e247438c9770aa46881718 to your computer and use it in GitHub Desktop.
Sass Expression Eval: https://www.sassmeister.com/gist/3bba625840e247438c9770aa46881718
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ---- | |
| // Sass (v3.4.25) | |
| // Compass (v1.0.3) | |
| // ---- | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Sass Expression Eval | |
| // Brant Wedel | |
| // bitbased.net | |
| // https://gist.github.com/brantwedel/3bba625840e247438c9770aa46881718 | |
| // https://www.sassmeister.com/gist/3bba625840e247438c9770aa46881718 | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // NOTE: This is a stripped down version of SassyCast with only the essentials | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // BEGIN USING SassyCast Library | |
| // HugoGiraudel | |
| // https://github.com/HugoGiraudel/SassyCast | |
| /// Toggle strict mode, in which script will throw when not able to cast a value | |
| /// into a certain type (mostly color and number). In non-strict mode, it will | |
| /// return the default value from the given type. | |
| $sc-strict-mode: false !default; | |
| /// Default return value for the `to-number(..)` function when running in | |
| $sc-non-strict-default-number: 0 !default; | |
| /// Default return value for the `to-color(..)` function when running in | |
| $sc-non-strict-default-color: transparent !default; | |
| /// Internal map for dynamically accessing default values for non-strict mode. | |
| $sc-non-strict-defaults: ( | |
| 'number': $sc-non-strict-default-number, | |
| 'color': $sc-non-strict-default-color, | |
| ); | |
| /// Internal constants map. | |
| $sc-constants: ( | |
| 'DECIMAL_SPACE': ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'), | |
| 'HEXADECIMAL_SPACE': ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'), | |
| 'COLOR_KEYWORDS': (transparent, aliceblue, antiquewhite, aqua, aquamarine, azure, beige, bisque, black, blanchedalmond, blue, blueviolet, brown, burlywood, cadetblue, chartreuse, chocolate, coral, cornflowerblue, cornsilk, crimson, cyan, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen, darkgrey, darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred, darksalmon, darkseagreen, darkslateblue, darkslategray, darkslategrey, darkturquoise, darkviolet, deeppink, deepskyblue, dimgray, dimgrey, dodgerblue, firebrick, floralwhite, forestgreen, fuchsia, gainsboro, ghostwhite, gold, goldenrod, gray, green, greenyellow, grey, honeydew, hotpink, indianred, indigo, ivory, khaki, lavender, lavenderblush, lawngreen, lemonchiffon, lightblue, lightcoral, lightcyan, lightgoldenrodyellow, lightgray, lightgreen, lightgrey, lightpink, lightsalmon, lightseagreen, lightskyblue, lightslategray, lightslategrey, lightsteelblue, lightyellow, lime, limegreen, linen, magenta, maroon, mediumaquamarine, mediumblue, mediumorchid, mediumpurple, mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, mediumvioletred, midnightblue, mintcream, mistyrose, moccasin, navajowhite, navy, oldlace, olive, olivedrab, orange, orangered, orchid, palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip, peachpuff, peru, pink, plum, powderblue, purple, rebeccapurple, red, rosybrown, royalblue, saddlebrown, salmon, sandybrown, seagreen, seashell, sienna, silver, skyblue, slateblue, slategray, slategrey, snow, springgreen, steelblue, tan, teal, thistle, tomato, turquoise, violet, wheat, white, whitesmoke, yellow, yellowgreen), | |
| 'STRINGIFIED_COLOR_KEYWORDS': ('transparent', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen'), | |
| 'UNITS': ('px', 'cm', 'mm', '%', 'ch', 'pc', 'in', 'em', 'rem', 'pt', 'ex', 'vw', 'vh', 'vmin', 'vmax', 'ms', 's', 'deg', 'rad', 'grad', 'turn', 'Hz', 'kHz', 'dpi', 'dpcm', 'dppx'), | |
| 'UNIT_VALUES': (1px, 1cm, 1mm, 1%, 1ch, 1pc, 1in, 1em, 1rem, 1pt, 1ex, 1vw, 1vh, 1vmin, 1vmax, 1ms, 1s, 1deg, 1rad, 1grad, 1turn, 1Hz, 1kHz, 1dpi, 1dpcm, 1dppx), | |
| 'FALSEY_VALUES': (false, null, '', 0), | |
| ); | |
| /// Convert to color | |
| @function to-color($value) { | |
| $type: type-of($value); | |
| @if ($type == 'color') { | |
| @return $value; | |
| } | |
| @if ($type != 'string') { | |
| @return _sc-throw($value, 'color'); | |
| } | |
| $value-lower: to-lower-case($value); | |
| $color-keyword-index: index( | |
| map-get($sc-constants, 'STRINGIFIED_COLOR_KEYWORDS'), | |
| $value-lower | |
| ); | |
| @if $color-keyword-index { | |
| @return nth(map-get($sc-constants, 'COLOR_KEYWORDS'), $color-keyword-index); | |
| } | |
| @else if (str-slice($value-lower, 1, 1) == '#') { | |
| @return _sc-from-hex($value); | |
| } | |
| // @else if (str-slice($value-lower, 1, 3) == 'rgb') { | |
| // @return _sc-from-rgb($value); | |
| // } | |
| // @else if (str-slice($value-lower, 1, 3) == 'hsl') { | |
| // @return _sc-from-hsl($value); | |
| // } | |
| @return _sc-throw($value, 'color'); | |
| } | |
| /// Cast a string into a hexadecimal color | |
| @function _sc-from-hex($string) { | |
| $string: to-lower-case($string); | |
| $r: ''; | |
| $g: ''; | |
| $b: ''; | |
| $hex: map-get($sc-constants, 'HEXADECIMAL_SPACE'); | |
| $length: str-length($string); | |
| $max: if($length == 4, 1, 2); | |
| @if ($length != 4 and $length != 7) { | |
| @return _sc-throw($string, 'color'); | |
| } | |
| @for $i from 2 through $length { | |
| $c: str-slice($string, $i, $i); | |
| @if not index($hex, $c) { | |
| @return _sc-throw($string, 'color'); | |
| } | |
| @if (str-length($r) < $max) { | |
| $r: ($r + $c); | |
| } @else if (str-length($g) < $max) { | |
| $g: ($g + $c); | |
| } @else if (str-length($b) < $max) { | |
| $b: ($b + $c); | |
| } | |
| } | |
| @if $length == 4 { | |
| $r: $r + $r; | |
| $g: $g + $g; | |
| $b: $b + $b; | |
| } | |
| @return rgb(_sc-hex-to-dec($r), _sc-hex-to-dec($g), _sc-hex-to-dec($b)); | |
| } | |
| /// Convert an hexadecimal number to a decimal number | |
| @function _sc-hex-to-dec($string) { | |
| $string: to-lower-case($string); | |
| $length: str-length($string); | |
| $hex: map-get($sc-constants, 'HEXADECIMAL_SPACE'); | |
| $decimal: 0; | |
| @for $i from 1 through $length { | |
| $factor: _sc-pow(length($hex), ($length - $i)); | |
| $index: index($hex, str-slice($string, $i, $i)) - 1; | |
| $decimal: $decimal + ($factor * $index); | |
| } | |
| @return $decimal; | |
| } | |
| /// Helper function to throw when running in strict mode, or warn and return | |
| @function _sc-throw($value, $expected-type) { | |
| $return-value: map-get($sc-non-strict-defaults, $expected-type); | |
| @if $sc-strict-mode { | |
| @error 'Could not cast `#{inspect($value)}` to #{$expected-type}.'; | |
| } @else { | |
| @warn 'Could not cast `#{inspect($value)}` to #{$expected-type}; returning `#{$return-value}`.'; | |
| } | |
| @return $return-value; | |
| } | |
| /// Cast a value to a number if possible or return 0 | |
| @function to-number($value) { | |
| $type: type-of($value); | |
| @if ($type == 'number') { | |
| @return $value; | |
| } | |
| @if ($value == true) { | |
| @return 1; | |
| } | |
| @if ($value == false) { | |
| @return 0; | |
| } | |
| @if ($type != 'string') { | |
| @return _sc-throw($value, 'number'); | |
| } | |
| $pointer: 1; | |
| $result: 0; | |
| $first-character: str-slice($value, $pointer, $pointer); | |
| $allowed-first-character: join(('-', '.'), map-get($sc-constants, 'DECIMAL_SPACE')); | |
| @if not index($allowed-first-character, $first-character) { | |
| @return _sc-throw($value, 'number'); | |
| } | |
| @if ($first-character == '.') { | |
| $value: '0' + $value; | |
| } | |
| $find-integer: _sc-find-integer($value, $pointer); | |
| $pointer: nth($find-integer, 1); | |
| $result: nth($find-integer, 2); | |
| @if (str-slice($value, $pointer, $pointer) == '.') { | |
| $find-digits: _sc-find-digits($value, $pointer); | |
| $pointer: nth($find-digits, 1); | |
| $digits: nth($find-digits, 2); | |
| $result: ($result + $digits); | |
| } | |
| @if ($first-character == '-') { | |
| $result: ($result * -1); | |
| } | |
| @if ($pointer <= str-length($value)) { | |
| $result: _sc-unit($result, str-slice($value, $pointer)); | |
| } | |
| @return $result; | |
| } | |
| /// Finding the digits part of a stringified number | |
| @function _sc-find-digits($source, $pointer) { | |
| $source: to-lower-case($source); | |
| $length: str-length($source); | |
| $numbers: map-get($sc-constants, 'DECIMAL_SPACE'); | |
| $result: 0; | |
| $runs: 1; | |
| @while ($pointer <= $length) { | |
| $token: str-slice($source, $pointer, $pointer); | |
| $index: index($numbers, $token); | |
| @if ($token == '.') { | |
| // @continue; | |
| } @else if ($index and $index > 0) { | |
| $runs: ($runs * 10); | |
| $result: ($result * 10) + ($index - 1); | |
| } @else { | |
| @return $pointer, ($result / $runs); | |
| } | |
| $pointer: ($pointer + 1); | |
| } | |
| @return $pointer, ($result / $runs); | |
| } | |
| /// Finding the integer part of a stringified number | |
| @function _sc-find-integer($source, $pointer) { | |
| $source: to-lower-case($source); | |
| $length: str-length($source); | |
| $numbers: map-get($sc-constants, 'DECIMAL_SPACE'); | |
| $result: 0; | |
| @while ($pointer <= $length) { | |
| $token: str-slice($source, $pointer, $pointer); | |
| $index: index($numbers, $token); | |
| @if ($token == '-') { | |
| // @continue; | |
| } @else if $index { | |
| $result: ($result * 10) + ($index - 1); | |
| } @else { | |
| @return $pointer, $result; | |
| } | |
| $pointer: ($pointer + 1); | |
| } | |
| @return $pointer, $result; | |
| } | |
| /// Power function | |
| @function _sc-pow($x, $n) { | |
| $ret: 1; | |
| @if $n > 0 { | |
| @for $i from 1 through $n { | |
| $ret: ($ret * $x); | |
| } | |
| } @else { | |
| @for $i from $n to 0 { | |
| $ret: ($ret / $x); | |
| } | |
| } | |
| @return $ret; | |
| } | |
| /// Tries to find a unit that would match a CSS length | |
| @function _sc-unit($number, $unit) { | |
| $units: map-get($sc-constants, 'UNIT_VALUES'); | |
| $index: index(map-get($sc-constants, 'UNITS'), $unit); | |
| @if not $index { | |
| @return _sc-throw($number, 'number'); | |
| } | |
| @return ($number * nth($units, $index)); | |
| } | |
| // END SassyCast | |
| //////////////////////////////////////////////////////////////////////////////// | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // BEGIN Sass Expression Eval | |
| // Brant Wedel | |
| // bitbased.net | |
| @function exp-cast($tkn) { | |
| @if (type-of($tkn) == string and $tkn != '' and $tkn != ' ' and $tkn != '\A') { | |
| @if ($tkn == "''" or $tkn == '""') { | |
| @return ("", string); | |
| } | |
| @if (str-index("01234567890-.", str-slice($tkn, 1, 1))) { | |
| $tkn: to-number($tkn); | |
| } | |
| @if ($tkn == 'true') { | |
| $tkn: true; | |
| } | |
| @if ($tkn == 'false') { | |
| $tkn: false; | |
| } | |
| @if ($tkn == 'null') { | |
| $tkn: null; | |
| } | |
| @if (type-of($tkn) == string and str-slice($tkn, 1, 1) == "$") { | |
| @if (str-slice($tkn, 1) == $tkn) { | |
| // does not have quotes | |
| $tkn: unquote($tkn); | |
| @return ($tkn, name); | |
| } | |
| } | |
| @if (type-of($tkn) == string and (str-slice($tkn, 1, 1) == '#' or index(map-get($sc-constants, 'STRINGIFIED_COLOR_KEYWORDS'), to-lower-case($tkn)))) { | |
| $tkn: to-color($tkn); | |
| } | |
| @if (type-of($tkn) == string) { | |
| @if (str-slice($tkn, 1) == $tkn) { | |
| // @if (str-slice($tkn, 1, 1) == '"' and str-slice($tkn, -1, -1) == '"') or | |
| // (str-slice($tkn, 1, 1) == "'" and str-slice($tkn, -1, -1) == "'") { | |
| // $tkn: str-slice($tkn, 2, -2); | |
| // } | |
| // does not have quotes | |
| $tkn: unquote($tkn); | |
| } @else { | |
| // has quotes | |
| $tkn: str-slice($tkn, 1); | |
| } | |
| } | |
| @return ($tkn, type-of($tkn)); | |
| } | |
| @return ($tkn, unknown); | |
| } | |
| @function exp-tokenize($exp, $include-names: false, $start: 1) { | |
| $sep: (' ', '\A', '(', ')', '+ ', '- ', ' %', '/', '*', ',', ':', ';', '~', '==', '!=', '<>', '>=', '<=', '=', '>', '<', " and ", " or ", "not "); | |
| $tkn: ''; | |
| $tkns: (); | |
| $names: (); | |
| $skip: 0; | |
| $args: (); | |
| $quot: null; | |
| $esc: 0; | |
| @if ($start == 1) { | |
| // FIXME: off by one error if a variable is at the end of the expression | |
| // HACK: just add a space at the end for now | |
| $exp: $exp + ' '; | |
| } | |
| @for $i from $start through str-length($exp) { | |
| @if ($skip <= 0) { | |
| $s: str-slice($exp, $i, $i); | |
| $next: str-slice($exp, $i + 1, $i + 1); | |
| $next2: str-slice($exp, $i + 1, $i + 3); | |
| $next3: str-slice($exp, $i + 1, $i + 4); | |
| $esc: $esc - 1; | |
| @if ($s == '\\' and $esc <= 0) { | |
| $s: ''; | |
| $esc: 2; | |
| } | |
| @if (not $quot and ($s == "'" or $s == '"') and $esc <= 0) { | |
| $quot: $s; | |
| } @else if ($quot and $s == $quot and $esc <= 0) { | |
| $quot: null; | |
| } @else if (not $quot and (index($sep, $s) or index($sep, $s + $next) or index($sep, $s + $next2) or index($sep, $s + $next3))) { | |
| @if(index($sep, $s + $next)) { | |
| // handle 2 character symbols | |
| @if($next != ' ') { | |
| // ignore if second character is a space TODO: trim function | |
| $s: $s + $next; | |
| } @else if($s == ' ') { | |
| // ignore if first character is a space TODO: trim function | |
| $s: $next; | |
| } | |
| $skip: length($s); | |
| } @else if(index($sep, $s + $next2)) { | |
| // handle 4 character symbols | |
| $s: $s + $next2; | |
| $skip: length($s) + 2; | |
| @if ($s == " or ") { | |
| $s: "or"; // TODO: trim function | |
| } | |
| @if ($s == "not ") { | |
| $s: "not"; // TODO: trim function | |
| } | |
| } @else if(index($sep, $s + $next3)) { | |
| // handle 5 character symbols | |
| $s: $s + $next3; | |
| $skip: length($s) + 2; | |
| @if ($s == " and ") { | |
| $s: "and"; // TODO: trim function | |
| } | |
| } | |
| @if ($s == "," and $start != 1) { | |
| $tkn: exp-cast($tkn); | |
| @if (nth($tkn, 2) == name) { | |
| $names: map-merge($names, (#{nth($tkn, 1)}: $i)); | |
| } | |
| @if (nth($tkn, 2) != unknown) { | |
| $tkns: append($tkns, nth($tkn, 1)); | |
| } | |
| $args: append($args, $tkns); | |
| $tkns: (); | |
| $s: ' '; | |
| $tkn: ''; | |
| } | |
| @if ($s == ")" and $start != 1) { | |
| $tkn: exp-cast($tkn); | |
| @if (nth($tkn, 2) == name) { | |
| $names: map-merge($names, (#{nth($tkn, 1)}: $i)); | |
| } | |
| @if (nth($tkn, 2) != unknown) { | |
| $tkns: append($tkns, nth($tkn, 1)); | |
| } | |
| $args: append($args, $tkns); | |
| $tkns: $args; | |
| @return (exp: $tkns, names: $names, end: $i); | |
| } @else if ($s == "(") { | |
| $sub: exp-tokenize($exp, $include-names, $i + 1); | |
| $tkn: (#{$tkn}: map-get($sub, exp)); | |
| $tkns: append($tkns, $tkn); | |
| $names: map-merge($names, map-get($sub, names)); | |
| $skip: map-get($sub, end) - $i; | |
| } @else { | |
| $tkn: exp-cast($tkn); | |
| @if (nth($tkn, 2) == name) { | |
| $names: map-merge($names, (#{nth($tkn, 1)}: $i)); | |
| } | |
| @if(nth($tkn, 2) != unknown) { | |
| $tkns: append($tkns, nth($tkn, 1)); | |
| } | |
| $tkn: ''; | |
| @if ($s != ' ' and $s != '\A') { | |
| $tkns: append($tkns, $s); | |
| } | |
| } | |
| } @else { | |
| @if (type-of($tkn) == string) { | |
| $tkn: $tkn + $s; | |
| } @else { | |
| $tkns: append($tkns, $tkn); | |
| $tkn: $s; | |
| } | |
| } | |
| } @else { | |
| $skip: $skip - 1; | |
| } | |
| } | |
| $tkn: exp-cast($tkn); | |
| @if (nth($tkn, 2) == name) { | |
| $names: map-merge($names, (#{nth($tkn, 1)}: $i)); | |
| } | |
| @if(nth($tkn, 2) != unknown) { | |
| $tkns: append($tkns, nth($tkn, 1)); | |
| } | |
| @if ($include-names) { | |
| @return (exp: $tkns, names: map-keys($names)); | |
| } | |
| @return $tkns; | |
| } | |
| /// Replace `$search` with `$replace` in `$string` | |
| /// @author Hugo Giraudel | |
| /// @param {String} $string - Initial string | |
| /// @param {String} $search - Substring to replace | |
| /// @param {String} $replace ('') - New value | |
| /// @return {String} - Updated string | |
| @function str-replace($string, $search, $replace: '') { | |
| $index: str-index($string, $search); | |
| @if $index { | |
| @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); | |
| } | |
| @return $string; | |
| } | |
| @function exp-eval($exp, $vars: (), $first: true) { | |
| @if($first and type-of($exp) == string) { | |
| $exp: exp-tokenize($exp, false); | |
| } | |
| $val: null; | |
| @if (type-of($exp) == list) { | |
| $op: null; | |
| $ops: ("+", "-", "*", "/", "%", "<", ">", "==", "!=", ">=", "<=", ":", "or", "and", "not"); | |
| // TODO: order of operations, and implicit lists | |
| @each $sub in $exp { | |
| @if (index($ops, $sub)) { | |
| $op: $sub; | |
| } @else { | |
| // if op and $val is list or map, operate on last item only | |
| $lst: null; | |
| $map-key: null; | |
| @if ($op) { | |
| @if (type-of($val) == list) { | |
| $lst: $val; | |
| $val: nth($lst, length($lst)); | |
| } | |
| } | |
| @if (type-of($val) == map and length(map-keys($val)) == 1) { | |
| $map-key: nth(map-keys($val), 1); | |
| $val: map-get($val, $map-key); | |
| } | |
| // recursive unwrap simple lists and maps? | |
| $lst2: null; | |
| @if (type-of($val) == list) { | |
| $lst2: $val; | |
| $val: nth($lst2, length($lst2)); | |
| } | |
| @if (not $op) { | |
| @if(type-of($val) == "null") { | |
| $val: exp-eval($sub, $vars, false); | |
| } @else if (type-of($val) == list) { | |
| $val: append($val, exp-eval($sub, $vars, false), space); | |
| } @else { | |
| $val: append(($val), exp-eval($sub, $vars, false), space); | |
| } | |
| } @else { | |
| $v: exp-eval($sub, $vars, false); | |
| @if ( | |
| (type-of($val) == number and type-of($v) == number and not comparable($v, $val)) | |
| or (type-of($val) == list and type-of($v) == number) | |
| ) { | |
| // non-comparable types, return list of op with flag | |
| $val: $val unquote($op) unquote("!incomparable") $v; | |
| } @else if(type-of($v) == list) { | |
| // TODO, support boolean logic on lists, or/and/not | |
| $val: $val unquote($op) "(#{$v})"; | |
| } @else if(type-of($val) == string and str-slice($val, -1) == ")") { | |
| $val: $val unquote($op) unquote("!incomparable") $v; | |
| } @else { | |
| @if($op == 'and') { | |
| $val: $val and $v; | |
| } @else if($op == 'or') { | |
| $val: $val or $v; | |
| } @else if($op == 'not') { | |
| $val: not $v; | |
| } @else if($op == '+') { | |
| $val: $val + $v; | |
| } @else if($op == '-') { | |
| $val: $val - $v; | |
| } @else if($op == '*') { | |
| $val: $val * $v; | |
| } @else if($op == '/') { | |
| $val: $val / $v; | |
| } @else if($op == '/') { | |
| $val: $val % $v; | |
| } @else if($op == '==') { | |
| $val: $val == $v; | |
| } @else if($op == '!=') { | |
| $val: $val != $v; | |
| } @else if($op == '>') { | |
| $val: $val > $v; | |
| } @else if($op == '<') { | |
| $val: $val < $v; | |
| } @else if($op == '>=') { | |
| $val: $val >= $v; | |
| } @else if($op == '<=') { | |
| $val: $val <= $v; | |
| } @else if($op == ':') { | |
| $val: ($val: $v); | |
| } | |
| } | |
| } | |
| $op: null; | |
| @if (type-of($lst2) == list) { | |
| $val: set-nth($lst2, length($lst2), $val); | |
| } | |
| // restore list or map if needed | |
| @if (type-of($map-key) != 'null') { | |
| $val: ($map-key: $val); | |
| } | |
| @if (type-of($lst) == list) { | |
| $val: set-nth($lst, length($lst), $val); | |
| } | |
| } | |
| } | |
| @return $val; | |
| } @else if(type-of($exp) == map) { | |
| @each $f, $args in $exp { | |
| $args2: (); | |
| $map2: (); | |
| @each $arg in $args { | |
| $arg: exp-eval($arg, $vars, false); | |
| $args2: append($args2, $arg, comma); | |
| @if (type-of($arg) == map) { | |
| $map2: map-merge($map2, $arg); | |
| } | |
| } | |
| @if ($f == '') { | |
| @if (length(map-keys($map2)) > 0) { | |
| $val: $map2; | |
| } @else if (length($args2) == 1) { | |
| // if 1 arg, it's a a grouping | |
| $val: nth($args2, 1); | |
| } @else { | |
| // if 0 or more than 1 arg, it's a list | |
| $val: $args2; | |
| } | |
| } @else if (function-exists($f)) { | |
| @if (function-exists(get-function)) { | |
| // newer sass versions require a function reference for call() | |
| $val: call(get-function($f), $args2...); | |
| } @else { | |
| $val: call($f, $args2...); | |
| } | |
| } @else if ($f == "calc") { | |
| $val: unquote(str-replace("#{$f}(#{$args2})", "!incomparable ", "")); | |
| } @else { | |
| @error("function #{$f} does not exists"); | |
| } | |
| } | |
| } @else if(type-of($exp) == string and str-slice($exp, 1, 1) == "$") { | |
| @return map-get($vars, str-slice($exp, 2)); | |
| } @else { | |
| $val: $exp; | |
| } | |
| @return $val; | |
| } | |
| // END Sass Expression Eval | |
| //////////////////////////////////////////////////////////////////////////////// | |
| /* EXAMPLES */ | |
| /* */ | |
| $expr: "1.5 + if($var < 5, $var, 8) + units"; | |
| $result: exp-eval($expr, (var:2, units: 0px)); | |
| /* (var:2, units: 0px) => #{$expr} = #{$result} */ | |
| /* */ | |
| $expr: "if(lightness($color) > 50%, darken($color, 20%), lighten($color, 20%))"; | |
| /* String: "#{$expr}" */ | |
| /* Parsed: #{inspect(exp-tokenize($expr))} */ | |
| /* */ | |
| $result: exp-eval($expr, (color:white)); | |
| /* (color:white) => #{$expr} = #{$result} */ | |
| $result: exp-eval($expr, (color:black)); | |
| /* (color:black) => #{$expr} = #{$result} */ | |
| /* */ | |
| $expr: "2 + 1 * 5"; | |
| /* #{$expr} = #{exp-eval($expr)} // TODO: No order of operations yet */ | |
| $expr: "2 + (1 * 5)"; | |
| /* #{$expr} = #{exp-eval($expr)} // use parenthesis */ | |
| /* */ | |
| $expr: "append((1, 2%, 2 + 1, 2 + 2), 5)"; | |
| /* #{$expr} = #{exp-eval($expr)} // list support */ | |
| $expr: "append((1 2% 2 + 1 2 + 2), 5)"; | |
| /* #{$expr} = #{exp-eval($expr)} */ | |
| $expr: "(1:2, 2:1 + 1, 3: 1 + 2)"; | |
| /* #{$expr} = #{inspect(exp-eval($expr))} // map support */ | |
| $expr: "(a:2, $var:1 + 1, c: 1 + 2)"; | |
| /* #{$expr} = #{inspect(exp-eval($expr, (var: b) ))} // $var as map key */ | |
| $expr: "(border: 1px solid rgba(red, 0.5))"; | |
| /* #{$expr} = #{inspect(exp-eval($expr))} // css style shorthand list */ | |
| /* */ | |
| /* STRING OPERATIONS */ | |
| $expr: "0 raw_str + raw_str + 1px 2px"; | |
| $expect: 0 raw_str + raw_str + 1px 2px; | |
| /* | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expr} = #{$expect} */ | |
| $expr: "'quoted1' + \"quoted2\""; | |
| $expect: 'quoted1' + "quoted2"; | |
| /* | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expr} = #{$expect} */ | |
| $expr: "'quoted1 with space ' + \"quoted2\""; | |
| $expect: 'quoted1 with space ' + "quoted2"; | |
| /* | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expr} = #{$expect} */ | |
| $expr: "'with,comma ' + \"quoted\""; | |
| $expect: 'with,comma ' + "quoted"; | |
| /* | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expr} = #{$expect} */ | |
| $expr: "'with\"quote ' + \"quoted\""; | |
| $expect: 'with"quote ' + "quoted"; | |
| /* | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expr} = #{$expect} */ | |
| $expr: "'escaped\\'quote ' + \"quoted\""; | |
| $expect: 'escaped\'quote ' + "quoted"; | |
| /* | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expr} = #{$expect} */ | |
| $expr: "'double\\\\escape ' + \"quoted\""; | |
| $expect: 'double\\escape ' + "quoted"; | |
| /* | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expr} = #{$expect} */ | |
| /* */ | |
| /* LOGIC OPERATIONS */ | |
| $expr: "true and (not 0 or 2)"; | |
| $expect: true and (not 0 or 2); | |
| /* | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} // TODO: no order of operations yet | |
| EXPECT: #{$expr} = #{$expect} // use parenthesis */ | |
| /* */ | |
| /* TODO: ORDER OF OPERATIONS */ | |
| $expr: "1 + 5 and 1 + 2 * 5 "; | |
| $expect: "1 + 5 and 1 + (2 * 5)"; | |
| /* | |
| PARSE: #{inspect(exp-tokenize($expr))} | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expect} = #{inspect(exp-eval($expect))} | |
| PARSE: #{inspect(exp-tokenize($expect))} | |
| */ | |
| /* */ | |
| /* calc() SUPPORT / calc() simplification */ | |
| $expr: "calc(2% + 5% + (100% + (3px * 2) + (1% + 2%)) - 4vw * 2)"; | |
| $expect: calc(2% + 5% + (100% + (3px * 2) + (1% + 2%)) - 4vw * 2); | |
| /* | |
| PARSE: #{inspect(exp-tokenize($expr))} | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} | |
| EXPECT: #{$expect} = #{$expect} | |
| */ | |
| $expr: "(border: calc(50vw - 100px) solid rgba(red, 0.5))"; | |
| $expect: (border: calc(50vw - 100px) solid rgba(red, 0.5)); | |
| /* | |
| PARSE: #{inspect(exp-tokenize($expr))} | |
| RESULT: #{$expr} = #{inspect(exp-eval($expr))} // FIXME: invalid list nesting in map? | |
| EXPECT: #{inspect($expect)} = #{inspect($expect)} | |
| */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* EXAMPLES */ | |
| /* */ | |
| /* (var:2, units: 0px) => 1.5 + if($var < 5, $var, 8) + units = 3.5units */ | |
| /* */ | |
| /* String: "if(lightness($color) > 50%, darken($color, 20%), lighten($color, 20%))" */ | |
| /* Parsed: (if: ((lightness: ($color)) ">" 50%) ((darken: ($color) (20%))) ((lighten: ($color) (20%)))) */ | |
| /* */ | |
| /* (color:white) => if(lightness($color) > 50%, darken($color, 20%), lighten($color, 20%)) = #cccccc */ | |
| /* (color:black) => if(lightness($color) > 50%, darken($color, 20%), lighten($color, 20%)) = #333333 */ | |
| /* */ | |
| /* 2 + 1 * 5 = 15 // TODO: No order of operations yet */ | |
| /* 2 + (1 * 5) = 7 // use parenthesis */ | |
| /* */ | |
| /* append((1, 2%, 2 + 1, 2 + 2), 5) = 1, 2%, 3, 4, 5 // list support */ | |
| /* append((1 2% 2 + 1 2 + 2), 5) = 1 2% 3 4 5 */ | |
| /* (1:2, 2:1 + 1, 3: 1 + 2) = (1: 2, 2: 2, 3: 3) // map support */ | |
| /* (a:2, $var:1 + 1, c: 1 + 2) = (a: 2, b: 2, c: 3) // $var as map key */ | |
| /* (border: 1px solid rgba(red, 0.5)) = (border: 1px (solid rgba(255, 0, 0, 0.5))) // css style shorthand list */ | |
| /* */ | |
| /* STRING OPERATIONS */ | |
| /* | |
| RESULT: 0 raw_str + raw_str + 1px 2px = 0 (raw_strraw_str1px 2px) | |
| EXPECT: 0 raw_str + raw_str + 1px 2px = 0 raw_strraw_str1px 2px */ | |
| /* | |
| RESULT: 'quoted1' + "quoted2" = quoted1quoted2 | |
| EXPECT: 'quoted1' + "quoted2" = quoted1quoted2 */ | |
| /* | |
| RESULT: 'quoted1 with space ' + "quoted2" = quoted1 with space quoted2 | |
| EXPECT: 'quoted1 with space ' + "quoted2" = quoted1 with space quoted2 */ | |
| /* | |
| RESULT: 'with,comma ' + "quoted" = with,comma quoted | |
| EXPECT: 'with,comma ' + "quoted" = with,comma quoted */ | |
| /* | |
| RESULT: 'with"quote ' + "quoted" = with"quote quoted | |
| EXPECT: 'with"quote ' + "quoted" = with"quote quoted */ | |
| /* | |
| RESULT: 'escaped\'quote ' + "quoted" = escaped'quote quoted | |
| EXPECT: 'escaped\'quote ' + "quoted" = escaped'quote quoted */ | |
| /* | |
| RESULT: 'double\escape ' + "quoted" = double\escape quoted | |
| EXPECT: 'double\escape ' + "quoted" = double\escape quoted */ | |
| /* */ | |
| /* LOGIC OPERATIONS */ | |
| /* | |
| RESULT: true and (not 0 or 2) = 2 // TODO: no order of operations yet | |
| EXPECT: true and (not 0 or 2) = 2 // use parenthesis */ | |
| /* */ | |
| /* TODO: ORDER OF OPERATIONS */ | |
| /* | |
| PARSE: 1 "+" 5 "and" 1 "+" 2 "*" 5 | |
| RESULT: 1 + 5 and 1 + 2 * 5 = 15 | |
| EXPECT: 1 + 5 and 1 + (2 * 5) = 11 | |
| PARSE: 1 "+" 5 "and" 1 "+" (: (2 "*" 5)) | |
| */ | |
| /* */ | |
| /* calc() SUPPORT / calc() simplification */ | |
| /* | |
| PARSE: (calc: (2% "+" 5% "+" (: (100% "+" (: (3px "*" 2)) "+" (: (1% "+" 2%)))) "-" 4vw "*" 2)) | |
| RESULT: calc(2% + 5% + (100% + (3px * 2) + (1% + 2%)) - 4vw * 2) = calc(7% + (100% + 6px + 3%) - 8vw) | |
| EXPECT: calc(2% + 5% + (100% + (3px * 2) + (1% + 2%)) - 4vw * 2) = calc(2% + 5% + (100% + (3px * 2) + (1% + 2%)) - 4vw * 2) | |
| */ | |
| /* | |
| PARSE: (: (border ":" (calc: (50vw "-" 100px)) solid (rgba: (red) (0.5)))) | |
| RESULT: (border: calc(50vw - 100px) solid rgba(red, 0.5)) = (border: calc(50vw - 100px) (solid rgba(255, 0, 0, 0.5))) // FIXME: invalid list nesting in map? | |
| EXPECT: (border: calc(50vw - 100px) solid rgba(255, 0, 0, 0.5)) = (border: calc(50vw - 100px) solid rgba(255, 0, 0, 0.5)) | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment