function parseAttributeList() {
  var i;
  var attr = null;

  // edge styles of dot and vis
  var edgeStyles = {
    dashed: true,
    solid: false,
    dotted: [1, 5],
  };

  /**
   * Define arrow types.
   * vis currently supports types defined in 'arrowTypes'.
   * Details of arrow shapes are described in
   * http://www.graphviz.org/content/arrow-shapes
   */
  var arrowTypes = {
    dot: "circle",
    box: "box",
    crow: "crow",
    curve: "curve",
    icurve: "inv_curve",
    normal: "triangle",
    inv: "inv_triangle",
    diamond: "diamond",
    tee: "bar",
    vee: "vee",
  };

  /**
   * 'attr_list' contains attributes for checking if some of them are affected
   * later. For instance, both of 'arrowhead' and 'dir' (edge style defined
   * in DOT) make changes to 'arrows' attribute in vis.
   */
  var attr_list = new Array();
  var attr_names = new Array(); // used for checking the case.

  // parse attributes
  while (token === "[") {
    getToken();
    attr = {};
    while (token !== "" && token != "]") {
      if (tokenType != TOKENTYPE.IDENTIFIER) {
        throw newSyntaxError("Attribute name expected");
      }
      var name = token;

      getToken();
      if (token != "=") {
        throw newSyntaxError("Equal sign = expected");
      }
      getToken();

      if (tokenType != TOKENTYPE.IDENTIFIER) {
        throw newSyntaxError("Attribute value expected");
      }
      var value = token;

      // convert from dot style to vis
      if (name === "style") {
        value = edgeStyles[value];
      }

      var arrowType;
      if (name === "arrowhead") {
        arrowType = arrowTypes[value];
        name = "arrows";
        value = { to: { enabled: true, type: arrowType } };
      }

      if (name === "arrowtail") {
        arrowType = arrowTypes[value];
        name = "arrows";
        value = { from: { enabled: true, type: arrowType } };
      }

      attr_list.push({ attr: attr, name: name, value: value });
      attr_names.push(name);

      getToken();
      if (token == ",") {
        getToken();
      }
    }

    if (token != "]") {
      throw newSyntaxError("Bracket ] expected");
    }
    getToken();
  }

  /**
   * As explained in [1], graphviz has limitations for combination of
   * arrow[head|tail] and dir. If attribute list includes 'dir',
   * following cases just be supported.
   *   1. both or none + arrowhead, arrowtail
   *   2. forward + arrowhead (arrowtail is not affedted)
   *   3. back + arrowtail (arrowhead is not affected)
   * [1] https://www.graphviz.org/doc/info/attrs.html#h:undir_note
   */
  if (attr_names.includes("dir")) {
    var idx = {}; // get index of 'arrows' and 'dir'
    idx.arrows = {};
    for (i = 0; i < attr_list.length; i++) {
      if (attr_list[i].name === "arrows") {
        if (attr_list[i].value.to != null) {
          idx.arrows.to = i;
        } else if (attr_list[i].value.from != null) {
          idx.arrows.from = i;
        } else {
          throw newSyntaxError("Invalid value of arrows");
        }
      } else if (attr_list[i].name === "dir") {
        idx.dir = i;
      }
    }

    // first, add default arrow shape if it is not assigned to avoid error
    var dir_type = attr_list[idx.dir].value;
    if (!attr_names.includes("arrows")) {
      if (dir_type === "both") {
        attr_list.push({
          attr: attr_list[idx.dir].attr,
          name: "arrows",
          value: { to: { enabled: true } },
        });
        idx.arrows.to = attr_list.length - 1;
        attr_list.push({
          attr: attr_list[idx.dir].attr,
          name: "arrows",
          value: { from: { enabled: true } },
        });
        idx.arrows.from = attr_list.length - 1;
      } else if (dir_type === "forward") {
        attr_list.push({
          attr: attr_list[idx.dir].attr,
          name: "arrows",
          value: { to: { enabled: true } },
        });
        idx.arrows.to = attr_list.length - 1;
      } else if (dir_type === "back") {
        attr_list.push({
          attr: attr_list[idx.dir].attr,
          name: "arrows",
          value: { from: { enabled: true } },
        });
        idx.arrows.from = attr_list.length - 1;
      } else if (dir_type === "none") {
        attr_list.push({
          attr: attr_list[idx.dir].attr,
          name: "arrows",
          value: "",
        });
        idx.arrows.to = attr_list.length - 1;
      } else {
        throw newSyntaxError('Invalid dir type "' + dir_type + '"');
      }
    }

    var from_type;
    var to_type;
    // update 'arrows' attribute from 'dir'.
    if (dir_type === "both") {
      // both of shapes of 'from' and 'to' are given
      if (idx.arrows.to && idx.arrows.from) {
        to_type = attr_list[idx.arrows.to].value.to.type;
        from_type = attr_list[idx.arrows.from].value.from.type;
        attr_list[idx.arrows.to] = {
          attr: attr_list[idx.arrows.to].attr,
          name: attr_list[idx.arrows.to].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };
        attr_list.splice(idx.arrows.from, 1);

        // shape of 'to' is assigned and use default to 'from'
      } else if (idx.arrows.to) {
        to_type = attr_list[idx.arrows.to].value.to.type;
        from_type = "arrow";
        attr_list[idx.arrows.to] = {
          attr: attr_list[idx.arrows.to].attr,
          name: attr_list[idx.arrows.to].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };

        // only shape of 'from' is assigned and use default for 'to'
      } else if (idx.arrows.from) {
        to_type = "arrow";
        from_type = attr_list[idx.arrows.from].value.from.type;
        attr_list[idx.arrows.from] = {
          attr: attr_list[idx.arrows.from].attr,
          name: attr_list[idx.arrows.from].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };
      }
    } else if (dir_type === "back") {
      // given both of shapes, but use only 'from'
      if (idx.arrows.to && idx.arrows.from) {
        to_type = "";
        from_type = attr_list[idx.arrows.from].value.from.type;
        attr_list[idx.arrows.from] = {
          attr: attr_list[idx.arrows.from].attr,
          name: attr_list[idx.arrows.from].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };

        // given shape of 'to', but does not use it
      } else if (idx.arrows.to) {
        to_type = "";
        from_type = "arrow";
        idx.arrows.from = idx.arrows.to;
        attr_list[idx.arrows.from] = {
          attr: attr_list[idx.arrows.from].attr,
          name: attr_list[idx.arrows.from].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };

        // assign given 'from' shape
      } else if (idx.arrows.from) {
        to_type = "";
        from_type = attr_list[idx.arrows.from].value.from.type;
        attr_list[idx.arrows.to] = {
          attr: attr_list[idx.arrows.from].attr,
          name: attr_list[idx.arrows.from].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };
      }

      attr_list[idx.arrows.from] = {
        attr: attr_list[idx.arrows.from].attr,
        name: attr_list[idx.arrows.from].name,
        value: {
          from: {
            enabled: true,
            type: attr_list[idx.arrows.from].value.from.type,
          },
        },
      };
    } else if (dir_type === "none") {
      var idx_arrow;
      if (idx.arrows.to) {
        idx_arrow = idx.arrows.to;
      } else {
        idx_arrow = idx.arrows.from;
      }

      attr_list[idx_arrow] = {
        attr: attr_list[idx_arrow].attr,
        name: attr_list[idx_arrow].name,
        value: "",
      };
    } else if (dir_type === "forward") {
      // given both of shapes, but use only 'to'
      if (idx.arrows.to && idx.arrows.from) {
        to_type = attr_list[idx.arrows.to].value.to.type;
        from_type = "";
        attr_list[idx.arrows.to] = {
          attr: attr_list[idx.arrows.to].attr,
          name: attr_list[idx.arrows.to].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };

        // assign given 'to' shape
      } else if (idx.arrows.to) {
        to_type = attr_list[idx.arrows.to].value.to.type;
        from_type = "";
        attr_list[idx.arrows.to] = {
          attr: attr_list[idx.arrows.to].attr,
          name: attr_list[idx.arrows.to].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };

        // given shape of 'from', but does not use it
      } else if (idx.arrows.from) {
        to_type = "arrow";
        from_type = "";
        idx.arrows.to = idx.arrows.from;
        attr_list[idx.arrows.to] = {
          attr: attr_list[idx.arrows.to].attr,
          name: attr_list[idx.arrows.to].name,
          value: {
            to: { enabled: true, type: to_type },
            from: { enabled: true, type: from_type },
          },
        };
      }

      attr_list[idx.arrows.to] = {
        attr: attr_list[idx.arrows.to].attr,
        name: attr_list[idx.arrows.to].name,
        value: {
          to: { enabled: true, type: attr_list[idx.arrows.to].value.to.type },
        },
      };
    } else {
      throw newSyntaxError('Invalid dir type "' + dir_type + '"');
    }

    // remove 'dir' attribute no need anymore
    attr_list.splice(idx.dir, 1);
  }

  // parse 'penwidth'
  var nof_attr_list;
  if (attr_names.includes("penwidth")) {
    var tmp_attr_list = [];

    nof_attr_list = attr_list.length;
    for (i = 0; i < nof_attr_list; i++) {
      // exclude 'width' from attr_list if 'penwidth' exists
      if (attr_list[i].name !== "width") {
        if (attr_list[i].name === "penwidth") {
          attr_list[i].name = "width";
        }
        tmp_attr_list.push(attr_list[i]);
      }
    }
    attr_list = tmp_attr_list;
  }

  nof_attr_list = attr_list.length;
  for (i = 0; i < nof_attr_list; i++) {
    setValue(attr_list[i].attr, attr_list[i].name, attr_list[i].value);
  }

  return attr;
}
Parse a set with attributes,
for example [label="1.000", shape=solid]
@returns {object | null} attr