| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 | "use strict";const fs = require("fs"),  path = require("path"),  vm = require("vm");const AllWhitespaceRegexp = /^\s+$/g;/** * A simple preprocessor that is based on the Firefox preprocessor * (https://dxr.mozilla.org/mozilla-central/source/build/docs/preprocessor.rst). * The main difference is that this supports a subset of the commands and it * supports preprocessor commands in HTML-style comments. * * Currently supported commands: * - if * - elif * - else * - endif * - include * - expand * - error * * Every #if must be closed with an #endif. Nested conditions are supported. * * Within an #if or #else block, one level of comment tokens is stripped. This * allows us to write code that can run even without preprocessing. For example: * * //#if SOME_RARE_CONDITION * // // Decrement by one * // --i; * //#else * // // Increment by one. * ++i; * //#endif */function preprocess(inFilename, outFilename, defines) {  let lineNumber = 0;  function loc() {    return fs.realpathSync(inFilename) + ":" + lineNumber;  }  function expandCssImports(content, baseUrl) {    return content.replace(      /^\s*@import\s+url\(([^)]+)\);\s*$/gm,      function (all, url) {        const file = path.join(path.dirname(baseUrl), url);        const imported = fs.readFileSync(file, "utf8").toString();        return expandCssImports(imported, file);      }    );  }  // TODO make this really read line by line.  let content = fs.readFileSync(inFilename, "utf8").toString();  // Handle CSS-imports first, when necessary.  if (/\.css$/i.test(inFilename)) {    content = expandCssImports(content, inFilename);  }  const lines = content.split("\n"),    totalLines = lines.length;  const out = [];  let i = 0;  function readLine() {    if (i < totalLines) {      return lines[i++];    }    return null;  }  const writeLine =    typeof outFilename === "function"      ? outFilename      : function (line) {          if (!line || AllWhitespaceRegexp.test(line)) {            const prevLine = out[out.length - 1];            if (!prevLine || AllWhitespaceRegexp.test(prevLine)) {              return; // Avoid adding consecutive blank lines.            }          }          out.push(line);        };  function evaluateCondition(code) {    if (!code || !code.trim()) {      throw new Error("No JavaScript expression given at " + loc());    }    try {      return vm.runInNewContext(code, defines, { displayErrors: false });    } catch (e) {      throw new Error(        'Could not evaluate "' +          code +          '" at ' +          loc() +          "\n" +          e.name +          ": " +          e.message      );    }  }  function include(file) {    const realPath = fs.realpathSync(inFilename);    const dir = path.dirname(realPath);    try {      let fullpath;      if (file.indexOf("$ROOT/") === 0) {        fullpath = path.join(          __dirname,          "../..",          file.substring("$ROOT/".length)        );      } else {        fullpath = path.join(dir, file);      }      preprocess(fullpath, writeLine, defines);    } catch (e) {      if (e.code === "ENOENT") {        throw new Error('Failed to include "' + file + '" at ' + loc());      }      throw e; // Some other error    }  }  function expand(line) {    line = line.replace(/__[\w]+__/g, function (variable) {      variable = variable.substring(2, variable.length - 2);      if (variable in defines) {        return defines[variable];      }      return "";    });    writeLine(line);  }  // not inside if or else (process lines)  const STATE_NONE = 0;  // inside if, condition false (ignore until #else or #endif)  const STATE_IF_FALSE = 1;  // inside else, #if was false, so #else is true (process lines until #endif)  const STATE_ELSE_TRUE = 2;  // inside if, condition true (process lines until #else or #endif)  const STATE_IF_TRUE = 3;  // inside else or elif, #if/#elif was true, so following #else or #elif is  // false (ignore lines until #endif)  const STATE_ELSE_FALSE = 4;  let line;  let state = STATE_NONE;  const stack = [];  const control =    /^(?:\/\/|\s*\/\*|<!--)\s*#(if|elif|else|endif|expand|include|error)\b(?:\s+(.*?)(?:\*\/|-->)?$)?/;  while ((line = readLine()) !== null) {    ++lineNumber;    const m = control.exec(line);    if (m) {      switch (m[1]) {        case "if":          stack.push(state);          state = evaluateCondition(m[2]) ? STATE_IF_TRUE : STATE_IF_FALSE;          break;        case "elif":          if (state === STATE_IF_TRUE || state === STATE_ELSE_FALSE) {            state = STATE_ELSE_FALSE;          } else if (state === STATE_IF_FALSE) {            state = evaluateCondition(m[2]) ? STATE_IF_TRUE : STATE_IF_FALSE;          } else if (state === STATE_ELSE_TRUE) {            throw new Error("Found #elif after #else at " + loc());          } else {            throw new Error("Found #elif without matching #if at " + loc());          }          break;        case "else":          if (state === STATE_IF_TRUE || state === STATE_ELSE_FALSE) {            state = STATE_ELSE_FALSE;          } else if (state === STATE_IF_FALSE) {            state = STATE_ELSE_TRUE;          } else {            throw new Error("Found #else without matching #if at " + loc());          }          break;        case "endif":          if (state === STATE_NONE) {            throw new Error("Found #endif without #if at " + loc());          }          state = stack.pop();          break;        case "expand":          if (state !== STATE_IF_FALSE && state !== STATE_ELSE_FALSE) {            expand(m[2]);          }          break;        case "include":          if (state !== STATE_IF_FALSE && state !== STATE_ELSE_FALSE) {            include(m[2]);          }          break;        case "error":          if (state !== STATE_IF_FALSE && state !== STATE_ELSE_FALSE) {            throw new Error("Found #error " + m[2] + " at " + loc());          }          break;      }    } else {      if (state === STATE_NONE) {        writeLine(line);      } else if (        (state === STATE_IF_TRUE || state === STATE_ELSE_TRUE) &&        !stack.includes(STATE_IF_FALSE) &&        !stack.includes(STATE_ELSE_FALSE)      ) {        writeLine(          line            .replace(/^\/\/|^<!--/g, "  ")            .replace(/(^\s*)\/\*/g, "$1  ")            .replace(/\*\/$|-->$/g, "")        );      }    }  }  if (state !== STATE_NONE || stack.length !== 0) {    throw new Error(      "Missing #endif in preprocessor for " + fs.realpathSync(inFilename)    );  }  if (typeof outFilename !== "function") {    fs.writeFileSync(outFilename, out.join("\n"));  }}exports.preprocess = preprocess;/** * Merge two defines arrays. Values in the second param will override values in * the first. */function merge(defaults, defines) {  const ret = Object.create(null);  for (const key in defaults) {    ret[key] = defaults[key];  }  for (const key in defines) {    ret[key] = defines[key];  }  return ret;}exports.merge = merge;
 |