anchor.diff:      (labelurl . "labelURL")
       (tailurl . "tailURL")))
   
  -(defun symbol->attr (symbol)
  +(defun symbol->id (symbol)
     "Given a symbol for an attribute, return its ID."
  -  (let ((special-case (assoc symbol special-ids)))
  -    (if special-case
  -        (cdr special-case)
  -        (string-downcase (symbol-name symbol)))))
  +  (etypecase symbol
  +    (symbol (let ((special-case (assoc symbol special-ids)))
  +              (if special-case
  +                  (cdr special-case)
  +                  (string-downcase (symbol-name symbol)))))
  +    (string symbol)))
   
   (defun id->symbol (id)
     "Given an attribute ID, return a symbol."
  -  (let ((special-case (rassoc id special-ids)))
  -    (if special-case
  -        (car special-case)
  -        (intern (string-upcase id) :cl-dot))))
  +  (etypecase id
  +    (string (let ((special-case (rassoc id special-ids)))
  +              (if special-case
  +                  (car special-case)
  +                  (intern (string-upcase id) :cl-dot))))
  +    (symbol id)))
   
   (defclass identified ()
     ((id :reader id :initarg :id :initform nil :type (or nil id-type))))
   
   (defclass attributed ()
  -  ((attributes :accessor attributes :initarg :attributes)))
  +  ((attributes :accessor attributes :initarg :attributes :initform nil)))
  +
  +(defun print-alist (alist stream &optional (bracket t))
  +  (when alist
  +    (format stream "~:[~;[~]~{~A~^, ~}~:[~;]~]"
  +            bracket
  +            (loop for (attr . value) in alist
  +               collect (format nil "~{~A=\"~A\"~}"
  +                               (mapcar #'symbol->id (list attr value))))
  +            bracket)))
   
   (defun normalize-statement (statement)
     (etypecase statement
  
positive.code:                                               (subseq statement 0 arrow))
                                               (normalize-statement
                                                (subseq statement (+ 2 arrow)))))
             (make-instance 'node :id statement))))
      (symbol
       (normalize-statement (symbol-name statement)))))
  
  (defclass subgraph (identified)
    ((node-attrs :accessor node-attrs :initarg :node-attrs)
     (edge-attrs :accessor node-attrs :initarg :node-attrs)
     (statements :accessor statements :initarg :statements)))
  (defmethod :after initialize-instance ((object subgraph) &key &allow-other-keys)
    (mapc #'normalize-statement statements))
  
  (defmethod print-object ((object subgraph) stream)
    (format stream
            "~:[~;strict ~]~:[~:[subgraph~;graph~]~;~*digraph~] ~:[~;~:*~A ~]~%~
             {~%~{  ~A;~%~}}~%"
            (graph-strict object)
            (typep object 'digraph)
            (typep object 'graph)
            (when (id object) (string-downcase (id object)))
            (statements object)))
  
  ;;; A graph uses --
  (defclass graph (subgraph)
    ((strict :reader graph-strict :initarg :strict :initform nil :type (or nil t))
     (identifiers :accessor identifiers :initform (make-hash-table))))
  
  ;;; A digraph uses ->
  (defclass digraph (graph)
  
negative.code: 
  (defmethod print-object ((object edge) stream)
    (format stream "~A -> ~A"
            (string-downcase (id (car (nodes object))))
            (string-downcase (id (cadr (nodes object)))))) ;TODO: add attributes
  
  (defun dot-read (str)
    (string-trim-left " \n" str))
  