public static NodeValue strLang(NodeValue v1, NodeValue v2) {
    if (!v1.isString()) throw new ExprEvalException("Not a string (arg 1): " + v1);
    if (!v2.isString()) throw new ExprEvalException("Not a string (arg 2): " + v2);

    String lex = v1.asString();
    String lang = v2.asString();
    // Check?

    Node n = Node.createLiteral(lex, lang, null);
    return NodeValue.makeNode(n);
  }
  public static NodeValue strDatatype(NodeValue v1, NodeValue v2) {
    if (!v1.isString()) throw new ExprEvalException("Not a string (arg 1): " + v1);
    if (!v2.isIRI()) throw new ExprEvalException("Not an IRI (arg 2): " + v2);

    String lex = v1.asString();
    Node dt = v2.asNode();
    // Check?

    Node n = Node.createLiteral(lex, null, Node.getType(dt.getURI()));
    return NodeValue.makeNode(n);
  }
  public void visit(NodeValue value) {
    logger.debug("visit NodeValue " + value);

    if (!convertable) {
      expression.push(Expression.FALSE); // prevent stack empty exceptions when conversion
      return; // fails in the middle of a multi-arg operator conversion
    }

    if (value.isDecimal()
        || value.isDouble()
        || value.isFloat()
        || value.isInteger()
        || value.isNumber()) {
      expression.push(new ConstantEx(value.asString(), value.asNode()));
    } else if (value.isDateTime()) {
      // Convert xsd:dateTime: CCYY-MM-DDThh:mm:ss
      // to SQL-92 TIMESTAMP: CCYY-MM-DD hh:mm:ss[.fraction]
      // TODO support time zones (WITH TIME ZONE columns)
      expression.push(new ConstantEx(value.asString().replace("T", " "), value.asNode()));
    } else {
      expression.push(new ConstantEx(value.asString(), value.asNode()));
    }
  }