docs: function emitLine(line) {
    var c = line[0];
    ++linenum;
    if (c === '=') {
      // ignore
    } else if (c === '@') {
      var match = line.match(line_regex);
      if (!match) {
        return console.log('regex fail on line ' + linenum + ': "' + line + '"');
      }
      var module = match[1];
      var symbol = match[2]; // opt
      var symbol_offs = h2i(match[3]); // opt, hex
      var site_addr = h2i(match[4]); // hex
      var op = match[5]; // + or -, < or >
      var ptr_addr = h2i(match[6]); // hex
      var size = h2i(match[7]); // hex
  
      if (op === '-' || op === '<') {
        var alloc_data = mem[ptr_addr];
        if (alloc_data) {
          var site_data = addr[alloc_data.site_addr]
          --site_data.count;
          ++site_data.traffic_count;
          site_data.total_size -= alloc_data.size;
          site_data.traffic_size += alloc_data.size;
          delete mem[ptr_addr];
        } else {
          // warning: freeing unmatched alloc
        }
      } else if (op === '+' || op === '>') {
        mem[ptr_addr] = { size: size, site_addr: site_addr };
        var site_data = addr[site_addr];
        if (!site_data) {
          addr[site_addr] = site_data = { module: module, symbol: symbol, symbol_offs: symbol_offs, count: 0, total_size: 0, traffic_count: 0, traffic_size: 0 };
        }
        ++site_data.count;
        ++site_data.traffic_count;
        site_data.total_size += size;
        site_data.traffic_size += size;
      }
    } else {
      console.log('unrecognized type on line ' + linenum + ': "' + line + '"');
    }
  }