render(fn) {
    var parser = this.parser = new Parser(this.str, this.options);

    // use plugin(s)
    for (var i = 0, len = this.options.use.length; i < len; i++) {
      this.use(this.options.use[i]);
    }

    try {
      nodes.filename = this.options.filename;
      // parse
      var ast = parser.parse();

      // evaluate
      this.evaluator = new this.options.Evaluator(ast, this.options);
      this.nodes = nodes;
      this.evaluator.renderer = this;
      ast = this.evaluator.evaluate();

      // normalize
      var normalizer = new Normalizer(ast, this.options);
      ast = normalizer.normalize();

      // compile
      var compiler = this.options.sourcemap
        ? new (require('./visitor/sourcemapper'))(ast, this.options)
        : new (require('./visitor/compiler'))(ast, this.options)
        , css = compiler.compile();

      // expose sourcemap
      if (this.options.sourcemap) this.sourcemap = compiler.map.toJSON();
    } catch (err) {
      var options = {};
      options.input = err.input || this.str;
      options.filename = err.filename || this.options.filename;
      options.lineno = err.lineno || parser.lexer.lineno;
      options.column = err.column || parser.lexer.column;
      if (!fn) throw utils.formatException(err, options);
      return fn(utils.formatException(err, options));
    }

    // fire `end` event
    var listeners = this.listeners('end');
    if (fn) listeners.push(fn);
    for (var i = 0, len = listeners.length; i < len; i++) {
      var ret = listeners[i](null, css);
      if (ret) css = ret;
    }
    if (!fn) return css;
  }
Parse and evaluate AST, then callback `fn(err, css, js)`.

@param {Function} fn
@api public