public boolean match(String command) {
    if (cleft == null) {
      // The compilation error happened during the signature declaration, so
      // we can't match it, nor can we even tell if it's what they intended for us to run.
      return false;
    }
    boolean case_sensitive = Prefs.CaseSensitive();
    String[] cmds = command.split(" ");
    List<String> args = new ArrayList(Arrays.asList(cmds));
    boolean isAMatch = true;
    StringBuilder lastVar = new StringBuilder();
    int lastJ = 0;
    try {
      for (int j = 0; j < cleft.size(); j++) {
        if (!isAMatch) {
          break;
        }
        lastJ = j;
        Construct c = cleft.get(j);
        String arg = args.get(j);
        if (c.getCType() != ConstructType.VARIABLE) {
          if (case_sensitive && !c.val().equals(arg)
              || !case_sensitive && !c.val().equalsIgnoreCase(arg)) {
            isAMatch = false;
            continue;
          }
        } else {
          // It's a variable. If it's optional, the rest of them are optional too, so as long as the
          // size of
          // args isn't greater than the size of cleft, it's a match
          if (((Variable) c).isOptional()) {
            if (args.size() <= cleft.size()) {
              return true;
            } else {
              Construct fin = cleft.get(cleft.size() - 1);
              if (fin instanceof Variable) {
                if (((Variable) fin).isFinal()) {
                  return true;
                }
              }
              return false;
            }
          }
        }
        if (j == cleft.size() - 1) {
          if (cleft.get(j).getCType() == ConstructType.VARIABLE) {
            Variable lv = (Variable) cleft.get(j);
            if (lv.isFinal()) {
              for (int a = j; a < args.size(); a++) {
                if (lastVar.length() == 0) {
                  lastVar.append(args.get(a));
                } else {
                  lastVar.append(" ").append(args.get(a));
                }
              }
            }
          }
        }
      }
    } catch (IndexOutOfBoundsException e) {
      if (cleft.get(lastJ).getCType() != ConstructType.VARIABLE
          || cleft.get(lastJ).getCType() == ConstructType.VARIABLE
              && !((Variable) cleft.get(lastJ)).isOptional()) {
        isAMatch = false;
      }
    }
    boolean lastIsFinal = false;
    if (cleft.get(cleft.size() - 1) instanceof Variable) {
      Variable v = (Variable) cleft.get(cleft.size() - 1);
      if (v.isFinal()) {
        lastIsFinal = true;
      }
    }
    if ((cleft.get(lastJ) instanceof Variable && ((Variable) cleft.get(lastJ)).isOptional())) {
      return true;
    }

    if (cleft.size() != cmds.length && !lastIsFinal) {
      isAMatch = false;
    }
    //        ArrayList<Variable> vars = new ArrayList<Variable>();
    //        Variable v = null;
    //        for (int j = 0; j < cleft.size(); j++) {
    //            try {
    //                if (cleft.get(j).getCType() == ConstructType.VARIABLE) {
    //                    if (((Variable) cleft.get(j)).getName().equals("$")) {
    //                        v = new Variable(((Variable) cleft.get(j)).getName(),
    //                                lastVar.toString(), Target.UNKNOWN);
    //                    } else {
    //                        v = new Variable(((Variable) cleft.get(j)).getName(),
    //                                args.get(j), Target.UNKNOWN);
    //                    }
    //                }
    //            } catch (IndexOutOfBoundsException e) {
    //                v = new Variable(((Variable) cleft.get(j)).getName(),
    //                        ((Variable) cleft.get(j)).getDefault(), Target.UNKNOWN);
    //            }
    //            if (v != null) {
    //                vars.add(v);
    //            }
    //        }
    return isAMatch;
  }
  public void checkAmbiguous(List<Script> scripts) throws ConfigCompileException {
    // for (int i = 0; i < scripts.size(); i++) {
    List<Construct> thisCommand = this.cleft;
    for (int j = 0; j < scripts.size(); j++) {
      List<Construct> thatCommand = scripts.get(j).cleft;
      if (thatCommand == null) {
        // it hasn't been compiled yet.
        return;
      }
      if (this.cleft == scripts.get(j).cleft) {
        // Of course this command is going to match it's own signature
        continue;
      }
      boolean soFarAMatch = true;
      for (int k = 0; k < thisCommand.size(); k++) {
        try {
          Construct c1 = thisCommand.get(k);
          Construct c2 = thatCommand.get(k);
          if (c1.getCType() != c2.getCType()
              || ((c1 instanceof Variable) && !((Variable) c1).isOptional())) {
            soFarAMatch = false;
          } else {
            // It's a literal, check to see if it's the same literal
            if (c1.nval() == null || !c1.val().equals(c2.val())) {
              soFarAMatch = false;
            }
          }
        } catch (IndexOutOfBoundsException e) {
          /**
           * The two commands: /cmd $var1 [$var2] /cmd $var1 would cause this exception to be
           * thrown, but the signatures are the same, so the fact that they've matched this far
           * means they are ambiguous. However, /cmd $var1 $var2 /cmd $var1 is not ambiguous
           */
          // thatCommand is the short one
          if (!(thisCommand.get(k) instanceof Variable)
              || (thisCommand.get(k) instanceof Variable
                  && !((Variable) thisCommand.get(k)).isOptional())) {
            soFarAMatch = false;
          }
        }
      }
      if (thatCommand.size() > thisCommand.size()) {
        int k = thisCommand.size();
        // thisCommand is the short one
        if (!(thatCommand.get(k) instanceof Variable)
            || (thatCommand.get(k) instanceof Variable
                && !((Variable) thatCommand.get(k)).isOptional())) {
          soFarAMatch = false;
        }
      }

      if (soFarAMatch) {
        String commandThis = "";
        for (Construct c : thisCommand) {
          commandThis += c.val() + " ";
        }
        String commandThat = "";
        for (Construct c : thatCommand) {
          commandThat += c.val() + " ";
        }
        scripts.get(j).compilerError = true;
        this.compilerError = true;
        throw new ConfigCompileException(
            "The command "
                + commandThis.trim()
                + " is ambiguous because it "
                + "matches the signature of "
                + commandThat.trim(),
            thisCommand.get(0).getTarget());
      }
    }

    //        //Also, check for undefined variables on the right, and unused variables on the left
    //        ArrayList<String> left_copy = new ArrayList<String>();
    //        for (Map.Entry<String, Variable> v : left_vars.entrySet()) {
    //            left_copy.add(v.getValue().getName());
    //        }
    //        Arrays.asList(new String[]{}).toArray(new String[]{});
    //        for (ParseTree gtn : cright) {
    //            GenericTree<Construct> tree = new GenericTree<Construct>();
    //            tree.setRoot(gtn);
    //            List<ParseTree> builtTree = tree.build(GenericTreeTraversalOrderEnum.PRE_ORDER);
    //            for (ParseTree c : builtTree) {
    //                if (c.getData() instanceof Variable) {
    //                    for (Map.Entry<String, Variable> v : left_vars.entrySet()) {
    //                        if (v.getValue().getName().equals(((Variable) c.getData()).getName()))
    // {
    //                            //Found it, remove this from the left_copy, and break
    //                            left_copy.remove(v.getValue().getName());
    //                            break;
    //                            //TODO: Layton!
    //                        }
    //                    }
    //                }
    //            }
    //        }
    // }
  }
  public Construct eval(ParseTree c, final Environment env) throws CancelCommandException {
    final Construct m = c.getData();
    CurrentEnv = env;
    // TODO: Reevaluate if this line is needed. The script doesn't know the label inherently, the
    // environment does, and setting it this way taints the environment.
    CurrentEnv.getEnv(GlobalEnv.class).SetLabel(this.label);
    if (m.getCType() == ConstructType.FUNCTION) {
      env.getEnv(GlobalEnv.class).SetScript(this);
      if (m.val().matches("^_[^_].*")) {
        // Not really a function, so we can't put it in Function.
        Procedure p = getProc(m.val());
        if (p == null) {
          throw new ConfigRuntimeException(
              "Unknown procedure \"" + m.val() + "\"",
              ExceptionType.InvalidProcedureException,
              m.getTarget());
        }
        Environment newEnv = env;
        try {
          newEnv = env.clone();
        } catch (Exception e) {
        }
        ProfilePoint pp =
            env.getEnv(GlobalEnv.class).GetProfiler().start(m.val() + " execution", LogLevel.INFO);
        Construct ret = p.cexecute(c.getChildren(), newEnv, m.getTarget());
        pp.stop();
        return ret;
      }
      final Function f;
      try {
        f = (Function) FunctionList.getFunction(m);
      } catch (ConfigCompileException e) {
        // Turn it into a config runtime exception. This shouldn't ever happen though.
        throw new ConfigRuntimeException("Unable to find function " + m.val(), m.getTarget());
      }
      // We have special handling for loop and other control flow functions
      if (f instanceof assign) {
        if (c.getChildAt(0).getData() instanceof CFunction) {
          CFunction test = (CFunction) c.getChildAt(0).getData();
          if (test.val().equals("array_get")) {
            env.getEnv(GlobalEnv.class).SetFlag("array_get_alt_mode", true);
            Construct arrayAndIndex = eval(c.getChildAt(0), env);
            env.getEnv(GlobalEnv.class).ClearFlag("array_get_alt_mode");
            return ((assign) f)
                .array_assign(m.getTarget(), env, arrayAndIndex, eval(c.getChildAt(1), env));
          }
        }
      }

      if (f.useSpecialExec()) {
        ProfilePoint p = null;
        if (f.shouldProfile()
            && env.getEnv(GlobalEnv.class).GetProfiler() != null
            && env.getEnv(GlobalEnv.class).GetProfiler().isLoggable(f.profileAt())) {
          p =
              env.getEnv(GlobalEnv.class)
                  .GetProfiler()
                  .start(f.profileMessageS(c.getChildren()), f.profileAt());
        }
        Construct ret =
            f.execs(m.getTarget(), env, this, c.getChildren().toArray(new ParseTree[] {}));
        if (p != null) {
          p.stop();
        }
        return ret;
      }

      ArrayList<Construct> args = new ArrayList<Construct>();
      for (ParseTree c2 : c.getChildren()) {
        args.add(eval(c2, env));
      }
      if (f.isRestricted()) {
        boolean perm = Static.hasCHPermission(f.getName(), env);
        if (!perm) {
          throw new ConfigRuntimeException(
              "You do not have permission to use the " + f.getName() + " function.",
              ExceptionType.InsufficientPermissionException,
              m.getTarget());
        }
      }
      Object[] a = args.toArray();
      Construct[] ca = new Construct[a.length];
      for (int i = 0; i < a.length; i++) {
        ca[i] = (Construct) a[i];
        // CArray, CBoolean, CDouble, CInt, CNull, CString, CVoid, CEntry, CLabel (only to sconcat).
        if (!(ca[i] instanceof CArray
                || ca[i] instanceof CBoolean
                || ca[i] instanceof CDouble
                || ca[i] instanceof CInt
                || ca[i] instanceof CNull
                || ca[i] instanceof CString
                || ca[i] instanceof CVoid
                || ca[i] instanceof IVariable
                || ca[i] instanceof CEntry
                || ca[i] instanceof CLabel)
            && (!f.getName().equals("__autoconcat__") && (ca[i] instanceof CLabel))) {
          throw new ConfigRuntimeException(
              "Invalid Construct ("
                  + ca[i].getClass()
                  + ") being passed as an argument to a function ("
                  + f.getName()
                  + ")",
              null,
              m.getTarget());
        }
        if (env.getEnv(GlobalEnv.class).GetFlag("array_get_alt_mode") == Boolean.TRUE && i == 0) {
          continue;
        }
        while (f.preResolveVariables() && ca[i] instanceof IVariable) {
          IVariable cur = (IVariable) ca[i];
          ca[i] =
              env.getEnv(GlobalEnv.class).GetVarList().get(cur.getName(), cur.getTarget()).ival();
        }
      }

      {
        // It takes a moment to generate the toString of some things, so lets not do it
        // if we actually aren't going to profile
        ProfilePoint p = null;
        if (f.shouldProfile()
            && env.getEnv(GlobalEnv.class).GetProfiler() != null
            && env.getEnv(GlobalEnv.class).GetProfiler().isLoggable(f.profileAt())) {
          p = env.getEnv(GlobalEnv.class).GetProfiler().start(f.profileMessage(ca), f.profileAt());
        }
        Construct ret = f.exec(m.getTarget(), env, ca);
        if (p != null) {
          p.stop();
        }
        return ret;
      }

    } else if (m.getCType() == ConstructType.VARIABLE) {
      return new CString(m.val(), m.getTarget());
    } else {
      return m;
    }
  }