All files / src/parser values.js

94.18% Statements 81/86
94.38% Branches 84/89
100% Functions 8/8
94.04% Lines 79/84

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201        1x           1x         5x               53x 53x   53x 1x   52x 271x 271x 6x   265x 48x   48x 48x     4x             1x             205x 205x     205x 205x 3x   202x 123x 1x   122x   201x 201x                 79x   79x 79x 39x     40x 40x 12x   28x 18x 15x         29x                     91x   91x 91x 3x     88x 88x 8x 5x 1x   4x   80x 62x 62x 1x   61x         18x                     16x   16x 16x 1x     15x 15x 3x 3x     3x   12x 3x     9x 9x 3x   6x 3x   3x                   36x       36x         36x 26x     26x   36x     36x    
// @ts-check
 
import { makeError, parseList, skipSpaces } from './utils';
 
export const TType = Object.freeze({
  color: { canInterpolate: true },
  number: { canInterpolate: true },
  string: { canInterpolate: false }
});
 
const _strEscapeRe = new RegExp('\\\\.', 'g');
/**
 * @param {RegExpMatchArray} match
 */
function _strEscapeRepl(match) {
  return match[1];
}
 
/**
 * @param  {ParseCtx} ctx
 * @return {string}
 */
export function parseString(ctx) {
  const value = ctx.value;
  const delim = skipSpaces(ctx);
 
  if (delim !== '"' && delim !== "'") {
    throw new SyntaxError(makeError('failed to parse value: invalid string delimiter', ctx));
  }
  for (var i = ctx.index + 1; i < value.length; ++i) {
    var c = value[i];
    if (c === '\\') {
      ++i;
    }
    else if (c === delim) {
      const ret = value.slice(ctx.index + 1, i)// @ts-ignore: not sure why
      .replace(_strEscapeRe, _strEscapeRepl);
      ctx.index = ++i;
      return ret;
    }
  }
  throw new SyntaxError(makeError('failed to parse unterminated string value', ctx));
}
 
/*
  this expression does match corrupted things like "+" or the empty string,
  but this will be checked by parseScalar
 */
const _scalarRe = new RegExp('^\\s*([+-]?\\d*(?:\\.\\d*)?)\\s*([A-Za-z]*|%)?');
 
/**
 * @param  {ParseCtx} ctx
 * @return {number}
 */
export function parseScalar(ctx) {
  var match = ctx.value.slice(ctx.index).match(_scalarRe);
  Iif (!match) {
    throw new SyntaxError(makeError('failed to parse scalar value', ctx));
  }
  var value = parseFloat(match[1]);
  if (isNaN(value)) {
    throw new SyntaxError(makeError('failed to parse scalar value', ctx));
  }
  if (match[2]) {
    if (ctx.unit && (ctx.unit !== match[2])) {
      throw new SyntaxError(makeError('mixed units in scalar list', ctx));
    }
    ctx.unit = match[2];
  }
  ctx.index += match[0].length;
  return value;
}
 
/**
 * @param {string} value
 * @return {ssvg.$TRange|null} value
 */ // eslint-disable-next-line complexity
export function parseTRange(value) {
  /** @type {ParseCtx} */
  const ctx = { index: 0, value: value };
  /** @type {any} */
  var c = value ? skipSpaces(ctx) : null;
  if (c === null) {
    return null;
  }
 
  c = c.charCodeAt(0);
  if ((c === 34) || (c === 39)) { /* " | ' */
    return { type: 'string', values: parseList(ctx, parseString) };
  }
  else if ((c === 43) || (c === 45) || (c === 46) || ((c >= 48) && (c <= 57))) { /* +|-|.|[0-9] */
    var values = parseList(ctx, parseScalar);
    return (ctx.unit) ?
      { type: 'number', unit: ctx.unit, values: values } :
      { type: 'number', values: values };
  }
  else {
    return { type: 'color', values: ctx.value.split(';').map((s) => s.trim()) };
  }
}
 
/**
 * @param {string} value
 * @return {ssvg.$TValue|null}
 */
// eslint-disable-next-line complexity
export function parseTValue(value) {
  /** @type {ParseCtx} */
  const ctx = { index: 0, value: value };
  /** @type {any} */
  var c = value ? skipSpaces(ctx) : null;
  if (c === null) {
    return null;
  }
 
  c = c.charCodeAt(0);
  if ((c === 34) || (c === 39)) { /* " | ' */
    const str = parseString(ctx);
    if (skipSpaces(ctx) !== null) {
      throw new SyntaxError(makeError('invalid trailing characters in string', ctx));
    }
    return { type: 'string', value: str };
  }
  else if ((c === 43) || (c === 45) || (c === 46) || ((c >= 48) && (c <= 57))) { /* +|-|.|[0-9] */
    const scalar = parseScalar(ctx);
    if (skipSpaces(ctx) !== null) {
      throw new SyntaxError(makeError('invalid trailing characters in scalar', ctx));
    }
    return (ctx.unit) ?
      { type: 'number', unit: ctx.unit, value: scalar } :
      { type: 'number', value: scalar };
  }
  else {
    return { type: 'color', value: ctx.value };
  }
}
 
/**
 * @param {string} value
 * @return {string|number|boolean|null}
 */
// eslint-disable-next-line complexity
export function parseInitial(value) {
  /** @type {ParseCtx} */
  const ctx = { index: 0, value: value };
  /** @type {any} */
  var c = value ? skipSpaces(ctx) : null;
  if (c === null) {
    return value;
  }
 
  c = c.charCodeAt(0);
  if ((c === 34) || (c === 39)) { /* " | ' */
    const str = parseString(ctx);
    Iif (skipSpaces(ctx) !== null) {
      throw new SyntaxError(makeError('invalid trailing characters in string', ctx));
    }
    return str;
  }
  else if ((c === 43) || (c === 45) || (c === 46) || ((c >= 48) && (c <= 57))) { /* +|-|.|[0-9] */
    return parseFloat(ctx.value);
  }
  else {
    const lvalue = value.toLowerCase();
    if (lvalue === 'true') {
      return true;
    }
    else if (lvalue === 'false') {
      return false;
    }
    return value;
  }
}
 
/**
 * @param {ssvg.$TValue|null|undefined} from
 * @param {ssvg.$TValue|null|undefined} to
 * @return {ssvg.$TRange}
 */
export function makeRange(from, to) {
  Iif (!from || !to) {
    throw new SyntaxError('can create range with undefined values');
  }
 
  const ret = {
    values: [ from.value, to.value ],
    unit: from.unit,
    type: from.type
  };
  if (to.unit) {
    Iif (from.unit && (to.unit !== from.unit)) {
      throw new SyntaxError(`from/to unit differs: ${from.unit} !== ${to.unit}`);
    }
    ret.unit = to.unit;
  }
  Iif (from.type !== to.type) {
    throw new SyntaxError(`from/to type differs: ${from.type} !== ${to.type}`);
  }
  return ret;
}