docs: function parseTreeEmpty (rhs) {
    var pt = this
    return rhs.reduce (function (result, node) {
      if (result) {
        if (typeof(node) === 'string')
  	result = !isTruthy (node)
        else {
          switch (node.type) {
          case 'assign':
            result = pt.parseTreeEmpty (node.value) && (!node.local || pt.parseTreeEmpty (node.local))
            break
          case 'alt':
            result = node.opts.reduce (function (r, opt) {
  	    return r && pt.parseTreeEmpty (opt)
            }, true)
            break
          case 'cond':
            result = pt.parseTreeEmpty (node.t) && pt.parseTreeEmpty (node.f)   // this will miss some empty trees, oh well
            break
          case 'func':
            result = pt.parseTreeEmpty (node.args)
            break
          case 'lookup':
            result = false  // we aren't checking variable values, so just assume any referenced variable is nonempty (yes this will miss some empty trees)
            break
          case 'root':
          case 'alt_sampled':
  	  if (node.rhs)
  	    result = pt.parseTreeEmpty (node.rhs)
  	  break
          case 'rep_sampled':
            if (node.reps)
              return node.reps.reduce (function (all, rep) { return all && pt.parseTreeEmpty(rep) }, true)
          default:
          case 'sym':
  	  result = node.rhs && pt.parseTreeEmpty (node.rhs)
  	  break
          }
        }
      }
      return result
    }, true)
  }