Skip to content

Instantly share code, notes, and snippets.

@brantwedel
Last active May 21, 2018 03:59
Show Gist options
  • Save brantwedel/3bba625840e247438c9770aa46881718 to your computer and use it in GitHub Desktop.
Save brantwedel/3bba625840e247438c9770aa46881718 to your computer and use it in GitHub Desktop.
// ----
// 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)}
*/
/* 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