/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 1997--2023  The R Core Team
 *  Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, a copy is available at
 *  https://www.R-project.org/Licenses/
 *
 *
 *  IMPLEMENTATION NOTES:
 *
 *  Deparsing has 3 layers.
 *  - The user interfaces, do_deparse(), do_dput(), and do_dump() should
 *    not be called from an internal function.
 *  - unless nlines > 0, the actual deparsing via deparse2() needs
 *    to be done twice, once to count things up and a second time to put
 *    them into the string vector for return.
 *  - Printing this to a file is handled by the calling routine.
 *
 *  Current call paths:
 *
 *    do_deparse() ------------> deparse1WithCutoff()
 *    do_dput() -> deparse1() -> deparse1WithCutoff()
 *    do_dump() -> deparse1() -> deparse1WithCutoff()
 *  ---------
 *  Workhorse: deparse1WithCutoff() -> deparse2() -> deparse2buff() --> {<itself>, ...}
 *  ---------  ~~~~~~~~~~~~~~~~~~  implicit arg R_BrowseLines == getOption("deparse.max.lines")
 *
 *  ./errors.c: PrintWarnings() | warningcall_dflt() ... -> deparse1s() -> deparse1WithCutoff()
 *  ./print.c : Print[Language|Closure|Expression]()    --> deparse1w() -> deparse1WithCutoff()
 *  bind.c,match.c,..: c|rbind(), match(), switch()...-> deparse1line() -> deparse1WithCutoff()
 *
 *  INDENTATION:
 *
 *  Indentation is carried out in the routine printtab2buff at the
 *  bottom of this file.  It seems like this should be settable via
 *  options.
 *
 *
 *  LocalParseData VARIABLES  (historically GLOBALs):
 *
 *  linenumber:	 counts the number of lines that have been written,
 *		 this is used to setup storage for deparsing.
 *
 *  len:	 counts the length of the current line, it will be
 *		 used to determine when to break lines.
 *
 *  incurly:	 keeps track of whether we are inside a curly or not,
 *		 this affects the printing of if-then-else.
 *
 *  inlist:	 keeps track of whether we are inside a list or not,
 *		 this affects the printing of if-then-else.
 *
 *  startline:	 indicator TRUE=start of a line (so we can tab out to
 *		 the correct place).
 *
 *  indent:	 how many tabs should be written at the start of
 *		 a line.
 *
 *  buff:	 contains the current string, we attempt to break
 *		 lines at cutoff, but can unlimited length.
 *
 *  lbreak:	 often used to indicate whether a line has been
 *		 broken, this makes sure that that indenting behaves
 *		 itself.
 */

/* DTL ('duncan'):
* The code here used to use static variables to share values
* across the different routines. These have now been collected
* into a struct named  LocalParseData and this is explicitly
* passed between the different routines. This avoids the needs
* for the global variables and allows multiple evaluators, potentially
* in different threads, to work on their own independent copies
* that are local to their call stacks. This avoids any issues
* with interrupts, etc. not restoring values.

* The previous issue with the global "cutoff" variable is now implemented
* by creating a deparse1WithCutoff() routine which takes the cutoff from
* the caller and passes this to the different routines as a member of the
* LocalParseData struct. Access to the deparse1() routine remains unaltered.
* This is exactly as Ross had suggested ...
*
* One possible fix is to restructure the code with another function which
* takes a cutoff value as a parameter.	 Then "do_deparse" and "deparse1"
* could each call this deeper function with the appropriate argument.
* I wonder why I didn't just do this? -- it would have been quicker than
* writing this note.  I guess it needs a bit more thought ...
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define R_USE_SIGNALS 1
#include <Defn.h>
#include <Internal.h>
#include <float.h> /* for DBL_DIG */
#include <Print.h>
#include <Fileio.h>
#ifdef Win32
#include <trioremap.h>
#endif

#define BUFSIZE 512

#define MIN_Cutoff 20
#define DEFAULT_Cutoff 60
#define MAX_Cutoff (BUFSIZE - 12)
/* ----- MAX_Cutoff  <	BUFSIZE !! */

#include "RBufferUtils.h"

typedef R_StringBuffer DeparseBuffer;

typedef struct {
    int linenumber;
    int len; // FIXME: size_t
    int incurly;
    int inlist;
    Rboolean startline; /* = TRUE; */
    int indent;
    SEXP strvec;
    int left;

    DeparseBuffer buffer;

    int cutoff;
    int backtick;
    int opts;
    int sourceable;
#ifdef longstring_WARN
    int longstring;
#endif
    int maxlines;
    Rboolean active;
    int isS4;
    Rboolean fnarg; /* fn argument, so parenthesize = as assignment */
} LocalParseData;

static SEXP deparse1WithCutoff(SEXP call, Rboolean abbrev, int cutoff,
			       Rboolean backtick, int opts, int nlines);
static void args2buff(SEXP, int, int, LocalParseData *);
static void deparse2buff(SEXP, LocalParseData *);
static void print2buff(const char *, LocalParseData *);
static void printtab2buff(int, LocalParseData *);
static void writeline(LocalParseData *);
static void vec2buff   (SEXP, LocalParseData *, Rboolean do_names);
static void vector2buff(SEXP, LocalParseData *);
static void src2buff1(SEXP, LocalParseData *);
static Rboolean src2buff(SEXP, int, LocalParseData *);
static void linebreak(Rboolean *lbreak, LocalParseData *);
static void deparse2(SEXP, SEXP, LocalParseData *);

// .Internal(deparse(expr, width.cutoff, backtick, .deparseOpts(control), nlines))
attribute_hidden SEXP do_deparse(SEXP call, SEXP op, SEXP args, SEXP rho)
{
    checkArity(op, args);

    SEXP expr = CAR(args); args = CDR(args);
    int cut0 = DEFAULT_Cutoff;
    if(!isNull(CAR(args))) {
	cut0 = asInteger(CAR(args));
	if(cut0 == NA_INTEGER|| cut0 < MIN_Cutoff || cut0 > MAX_Cutoff) {
	    warning(_("invalid 'cutoff' value for 'deparse', using default"));
	    cut0 = DEFAULT_Cutoff;
	}
    }
    args = CDR(args);
    int backtick = isNull(CAR(args)) ? 0 : asLogical(CAR(args));
    args = CDR(args);
    int opts = isNull(CAR(args)) ? SHOWATTRIBUTES : asInteger(CAR(args));
    args = CDR(args);
    int nlines = asInteger(CAR(args));
    if (nlines == NA_INTEGER) nlines = -1;
    return deparse1WithCutoff(expr, FALSE, cut0, backtick, opts, nlines);
}

// deparse1() version *looking* at getOption("deparse.max.lines")
SEXP deparse1m(SEXP call, Rboolean abbrev, int opts)
{
    Rboolean backtick = TRUE;
    int old_bl = R_BrowseLines,
        blines = asInteger(GetOption1(install("deparse.max.lines")));
    if (blines != NA_INTEGER && blines > 0)
        R_BrowseLines = blines;
    SEXP result = deparse1WithCutoff(call, abbrev, DEFAULT_Cutoff, backtick,
				     opts, 0);
    R_BrowseLines = old_bl;
    return result;
}

// deparse1() version with R_BrowseLines := 0
SEXP deparse1(SEXP call, Rboolean abbrev, int opts)
{
    Rboolean backtick = TRUE;
    int old_bl = R_BrowseLines;
    R_BrowseLines = 0;
    SEXP result = deparse1WithCutoff(call, abbrev, DEFAULT_Cutoff, backtick,
				     opts, 0);
    R_BrowseLines = old_bl;
    return result;
}


/* used for language objects in print() */
attribute_hidden
SEXP deparse1w(SEXP call, Rboolean abbrev, int opts)
{
    Rboolean backtick = TRUE;
    return deparse1WithCutoff(call, abbrev, R_print.cutoff, backtick, opts, -1);
}

static SEXP deparse1WithCutoff(SEXP call, Rboolean abbrev, int cutoff,
			       Rboolean backtick, int opts, int nlines)
{
/* Arg. abbrev:
	If abbrev is TRUE, then the returned value
	is a STRSXP of length 1 with at most 13 characters.
	This is used for plot labelling etc.
*/
    SEXP svec;
    int savedigits;
    Rboolean need_ellipses = FALSE;
    LocalParseData localData = {
	.linenumber = 0,
	.len = 0,
	.incurly = 0,
	.inlist = 0,
	.startline = TRUE,
	.indent = 0,
	.strvec = NULL,
	.left = 0,
	.buffer = { NULL, 0, BUFSIZE },
	.cutoff = DEFAULT_Cutoff,
	.backtick = FALSE,
	.opts = 0,
	.sourceable = TRUE,
#ifdef longstring_WARN
	.longstring = FALSE,
#endif
	.maxlines = INT_MAX,
	.active = TRUE,
	.isS4 = 0,
	.fnarg = FALSE
    };
    localData.cutoff = cutoff;
    localData.backtick = backtick;
    localData.opts = opts;
    localData.strvec = R_NilValue;

    PrintDefaults(); /* from global options() */
    savedigits = R_print.digits;
    R_print.digits = DBL_DIG;/* MAX precision */
    print2buff("", &localData); /* ensure allocation of buffer.data, PR#17876 */

    svec = R_NilValue;
    if (nlines > 0) {
	localData.linenumber = localData.maxlines = nlines;
    } else { // default: nlines = -1 (from R), or = 0 (from other C fn's)
	if(R_BrowseLines > 0)// not by default; e.g. from getOption("deparse.max.lines")
	    localData.maxlines = R_BrowseLines + 1; // enough to determine linenumber
	deparse2(call, svec, &localData);
	localData.active = TRUE;
	if(R_BrowseLines > 0 && localData.linenumber > R_BrowseLines) {
	    localData.linenumber = R_BrowseLines + 1;
	    need_ellipses = TRUE;
	}
    }
    PROTECT(svec = allocVector(STRSXP, localData.linenumber));
    deparse2(call, svec, &localData);
    if (abbrev) {
	char data[14];
	strncpy(data, CHAR(STRING_ELT(svec, 0)), 10);
	data[10] = '\0';
	if (strlen(CHAR(STRING_ELT(svec, 0))) > 10) strcat(data, "...");
	svec = mkString(data);
    } else if(need_ellipses) {
	SET_STRING_ELT(svec, R_BrowseLines, mkChar("  ..."));
    }
    if(nlines > 0 && localData.linenumber < nlines) {
	UNPROTECT(1); /* old svec value */
	PROTECT(svec);
	svec = lengthgets(svec, localData.linenumber);
    }
    UNPROTECT(1);
    PROTECT(svec); /* protect from warning() allocating, PR#14356 */
    R_print.digits = savedigits;
    /*: Don't warn anymore, we do deal with most (-> 'S4SXP' below)
    if ((opts & WARNINCOMPLETE) && localData.isS4)
	warning(_("deparse of an S4 object may not always be source()able"));
	else */
    if ((opts & WARNINCOMPLETE) && !localData.sourceable)
	warning(_("deparse may be incomplete"));
#ifdef longstring_WARN
    if ((opts & WARNINCOMPLETE) && localData.longstring)
	warning(_("deparse may be not be source()able in R < 2.7.0"));
#endif
    /* somewhere lower down might have allocated ... */
    R_FreeStringBuffer(&(localData.buffer));
    UNPROTECT(1);
    return svec;
}

/* deparse1line(), e.g. for non-trivial list entries in as.character(<list>).
 * --------------
 * Concatenates all lines into one long one.
 * This is needed in terms.formula, where we must be able
 * to deparse a term label into a single line of text so
 * that it can be reparsed correctly */
SEXP deparse1line_(SEXP call, Rboolean abbrev, int opts)
{
    Rboolean backtick=TRUE;
    int lines;
    SEXP temp = PROTECT(
	    deparse1WithCutoff(call, abbrev, MAX_Cutoff, backtick, opts, -1));
    if ((lines = length(temp)) > 1) {
	char *buf;
	int i;
	size_t len;
	const void *vmax;
	cetype_t enc = CE_NATIVE;
	for (len = 0, i = 0; i < length(temp); i++) {
	    SEXP s = STRING_ELT(temp, i);
	    cetype_t thisenc = getCharCE(s);
	    len += strlen(CHAR(s));  // FIXME: check for overflow?
	    if (thisenc != CE_NATIVE)
		enc = thisenc; /* assume only one non-native encoding */
	}
	vmax = vmaxget();
	buf = R_alloc((size_t) len+lines, sizeof(char));
	*buf = '\0';
	for (i = 0; i < length(temp); i++) {
	    if (i % 1000 == 999) R_CheckUserInterrupt();
	    strcat(buf, CHAR(STRING_ELT(temp, i)));
	    if (i < lines - 1)
		strcat(buf, "\n");
	}
	temp = ScalarString(mkCharCE(buf, enc));
	vmaxset(vmax);
    }
    UNPROTECT(1);
    return(temp);
}

SEXP deparse1line(SEXP call, Rboolean abbrev)
{
    return deparse1line_(call, abbrev, SIMPLEDEPARSE);
}


// called only from ./errors.c  for calls in warnings and errors :
attribute_hidden SEXP deparse1s(SEXP call)
{
   Rboolean backtick=TRUE;
   return
       deparse1WithCutoff(call, FALSE, DEFAULT_Cutoff, backtick,
			  DEFAULTDEPARSE, /* nlines = */ 1);
}

#include "Rconnections.h"

static void con_cleanup(void *data)
{
    Rconnection con = data;
    if(con->isopen) con->close(con);
}

// .Internal(dput(x, file, .deparseOpts(control)))
attribute_hidden SEXP do_dput(SEXP call, SEXP op, SEXP args, SEXP rho)
{
    checkArity(op, args);
    SEXP tval = CAR(args);
    int opts = isNull(CADDR(args)) ? SHOWATTRIBUTES : asInteger(CADDR(args));

    if (TYPEOF(tval) == CLOSXP) {
	SEXP clo = PROTECT(duplicate(tval));
	SET_CLOENV(clo, R_GlobalEnv);
	tval = deparse1(clo, 0, opts);
	UNPROTECT(1);
    } else
	tval = deparse1(tval, 0, opts);
    PROTECT(tval); /* against Rconn_printf */
    if(!inherits(CADR(args), "connection"))
	error(_("'file' must be a character string or connection"));
    int ifile = asInteger(CADR(args));
    if (ifile != 1) {
	Rconnection con = getConnection(ifile);
	RCNTXT cntxt;
	Rboolean wasopen = con->isopen;
	if(!wasopen) {
	    char mode[5];
	    strcpy(mode, con->mode);
	    strcpy(con->mode, "w");
	    if(!con->open(con)) error(_("cannot open the connection"));
	    strcpy(con->mode, mode);
	    /* Set up a context which will close the connection on error */
	    begincontext(&cntxt, CTXT_CCODE, R_NilValue, R_BaseEnv, R_BaseEnv,
			 R_NilValue, R_NilValue);
	    cntxt.cend = &con_cleanup;
	    cntxt.cenddata = con;
	}
	if(!con->canwrite) error(_("cannot write to this connection"));
	Rboolean havewarned = FALSE;
	for (int i = 0; i < LENGTH(tval); i++) {
	    int res = Rconn_printf(con, "%s\n", CHAR(STRING_ELT(tval, i)));
	    if(!havewarned &&
	       res < strlen(CHAR(STRING_ELT(tval, i))) + 1) {
		warning(_("wrote too few characters"));
		havewarned = TRUE;
	    }
	}
	if(!wasopen) {endcontext(&cntxt); con->close(con);}
    }
    else { // ifile == 1 : "Stdout"
	for (int i = 0; i < LENGTH(tval); i++)
	    Rprintf("%s\n", CHAR(STRING_ELT(tval, i)));
    }
    UNPROTECT(1); /* tval */
    return (CAR(args));
}

// .Internal(dump(list, file, envir, opts, evaluate))
attribute_hidden SEXP do_dump(SEXP call, SEXP op, SEXP args, SEXP rho)
{
    checkArity(op, args);
    SEXP names = CAR(args),
	 file = CADR(args);
    if(!inherits(file, "connection"))
	error(_("'file' must be a character string or connection"));
    if(!isString(names))
	error( _("character arguments expected"));
    int nobjs = length(names);
    if(nobjs < 1 || length(file) < 1)
	error(_("zero-length argument"));
    SEXP source = CADDR(args);
    if (source != R_NilValue && TYPEOF(source) != ENVSXP)
	error(_("invalid '%s' argument"), "envir");
    int opts = asInteger(CADDDR(args));
    /* <NOTE>: change this if extra options are added */
    if(opts == NA_INTEGER || opts < 0 || opts > 2048)
	error(_("'opts' should be small non-negative integer"));
    // evaluate :
    if (!asLogical(CAD4R(args))) opts |= DELAYPROMISES;

    SEXP objs, o = PROTECT(objs = allocList(nobjs));
    int nout = 0;
    for (int i = 0; i < nobjs; i++, o = CDR(o)) {
	SET_TAG(o, installTrChar(STRING_ELT(names, i)));
	SETCAR(o, findVar(TAG(o), source));
	if (CAR(o) == R_UnboundValue)
	    warning(_("object '%s' not found"), EncodeChar(PRINTNAME(TAG(o))));
	else nout++;
    }
    o = objs;
    SEXP outnames = PROTECT(allocVector(STRSXP, nout)); // -> result
    if(nout > 0) {
	if(INTEGER(file)[0] == 1) {
	    for (int i = 0, nout = 0; i < nobjs; i++) {
		if (CAR(o) == R_UnboundValue) continue;
		const char *obj_name = translateChar(STRING_ELT(names, i));
		SET_STRING_ELT(outnames, nout++, STRING_ELT(names, i));
		if(isValidName(obj_name)) Rprintf("%s <-\n", obj_name);
		else if(opts & S_COMPAT) Rprintf("\"%s\" <-\n", obj_name);
		else Rprintf("`%s` <-\n", obj_name);
		SEXP tval = PROTECT(deparse1(CAR(o), 0, opts));
		for (int j = 0; j < LENGTH(tval); j++)
		    Rprintf("%s\n", CHAR(STRING_ELT(tval, j)));/* translated */
		UNPROTECT(1); /* tval */
		o = CDR(o);
	    }
	}
	else {
	    Rconnection con = getConnection(INTEGER(file)[0]);
	    Rboolean wasopen = con->isopen;
	    RCNTXT cntxt;
	    if(!wasopen) {
		char mode[5];
		strcpy(mode, con->mode);
		strcpy(con->mode, "w");
		if(!con->open(con)) error(_("cannot open the connection"));
		strcpy(con->mode, mode);
		/* Set up a context which will close the connection on error */
		begincontext(&cntxt, CTXT_CCODE, R_NilValue, R_BaseEnv, R_BaseEnv,
			     R_NilValue, R_NilValue);
		cntxt.cend = &con_cleanup;
		cntxt.cenddata = con;
	    }
	    if(!con->canwrite) error(_("cannot write to this connection"));
	    Rboolean havewarned = FALSE;
	    for (int i = 0, nout = 0; i < nobjs; i++) {
		if (CAR(o) == R_UnboundValue) continue;
		SET_STRING_ELT(outnames, nout++, STRING_ELT(names, i));
		int res;
		const char *s = translateChar(STRING_ELT(names, i));
		unsigned int extra = 6;
		if(isValidName(s)) {
		    extra = 4;
		    res = Rconn_printf(con, "%s <-\n", s);
		} else if(opts & S_COMPAT)
		    res = Rconn_printf(con, "\"%s\" <-\n", s);
		else
		    res = Rconn_printf(con, "`%s` <-\n", s);
		if(!havewarned && res < strlen(s) + extra)
		    warning(_("wrote too few characters"));
		SEXP tval = PROTECT(deparse1(CAR(o), 0, opts));
		for (int j = 0; j < LENGTH(tval); j++) {
		    res = Rconn_printf(con, "%s\n", CHAR(STRING_ELT(tval, j)));
		    if(!havewarned &&
		       res < strlen(CHAR(STRING_ELT(tval, j))) + 1) {
			warning(_("wrote too few characters"));
			havewarned = TRUE;
		    }
		}
		UNPROTECT(1); /* tval */
		o = CDR(o);
	    }
	    if(!wasopen) {endcontext(&cntxt); con->close(con);}
	}
    }

    UNPROTECT(2);
    return outnames;
}

static void linebreak(Rboolean *lbreak, LocalParseData *d)
{
    if (d->len > d->cutoff) {
	if (!*lbreak) {
	    *lbreak = TRUE;
	    d->indent++;
	}
	writeline(d);
    }
}

static void deparse2(SEXP what, SEXP svec, LocalParseData *d)
{
    d->strvec = svec;
    d->linenumber = 0;
    d->indent = 0;
    deparse2buff(what, d);
    writeline(d);
}


/* curlyahead looks at s to see if it is a list with
   the first op being a curly.  You need this kind of
   lookahead info to print if statements correctly.  */
static Rboolean
curlyahead(SEXP s)
{
    if (isList(s) || isLanguage(s))
	if (TYPEOF(CAR(s)) == SYMSXP && CAR(s) == R_BraceSymbol)
	    return TRUE;
    return FALSE;
}

/* needsparens looks at an arg to a unary or binary operator to
   determine if it needs to be parenthesized when deparsed
   mainop is a unary or binary operator,
   arg is an argument to it, on the left if left == 1 */

static Rboolean needsparens(PPinfo mainop, SEXP arg, unsigned int left,
			    unsigned int deepLeft)
{
    PPinfo arginfo;
    if (TYPEOF(arg) == LANGSXP) {
	if (TYPEOF(CAR(arg)) == SYMSXP) {
	    if ((TYPEOF(SYMVALUE(CAR(arg))) == BUILTINSXP) ||
		(TYPEOF(SYMVALUE(CAR(arg))) == SPECIALSXP)) {
		arginfo = PPINFO(SYMVALUE(CAR(arg)));

		/* Not all binary ops are binary! */
		switch(arginfo.kind) {
		case PP_BINARY:
		case PP_BINARY2:
		    switch(length(CDR(arg))) {
		    case 1:
			/* binary +/- precedence upgraded as unary */
			if (arginfo.precedence == PREC_SUM)
			    arginfo.precedence = PREC_SIGN;
			arginfo.kind = PP_UNARY;
			break;
		    case 2:
			break;
		    default:
			return FALSE;
		    }
		default:
		    break;
		}

		switch(arginfo.kind) {
		case PP_SUBSET:
		    switch (mainop.kind) {
		    case PP_DOLLAR:
		    case PP_SUBSET:
			if (mainop.precedence > arginfo.precedence)
			    return FALSE;
			/* else fall through */
		    default:
			break;
		    }
		case PP_BINARY:
		case PP_BINARY2:
		    if (mainop.precedence == PREC_COMPARE &&
			arginfo.precedence == PREC_COMPARE)
			return TRUE;     /*   a < b < c   is not legal syntax */
		    /* else fall through */
		case PP_ASSIGN:
		case PP_ASSIGN2:
		case PP_DOLLAR:
		    if (mainop.precedence > arginfo.precedence
			|| (mainop.precedence == arginfo.precedence && left == mainop.rightassoc)) {
			return TRUE;
		    }
		    break;
		case PP_UNARY:
		    return (left && mainop.precedence > arginfo.precedence)
			|| (deepLeft && deepLeft > arginfo.precedence);
		case PP_FOR:
		case PP_IF:
		case PP_WHILE:
		case PP_REPEAT:
		    return left || deepLeft;
		default:
		    return FALSE;
		}
	    } else if (isUserBinop(CAR(arg))) {
		if (mainop.precedence > PREC_PERCENT
		    || (mainop.precedence == PREC_PERCENT && left == mainop.rightassoc)) {
		    return TRUE;
		}
	    }
	}
    }
    else if ((TYPEOF(arg) == CPLXSXP) && (length(arg) == 1)) {
	if (mainop.precedence > PREC_SUM
	    || (mainop.precedence == PREC_SUM && left == mainop.rightassoc)) {
	    return TRUE;
	}
    }
    return FALSE;
}


/* does the character() vector x contain one `NA_character_` or is all "",
 * or if(isAtomic) does it have one "recursive" or "use.names" ?  */
static Rboolean usable_nice_names(SEXP x, Rboolean isAtomic)
{
    if(TYPEOF(x) == STRSXP) {
	R_xlen_t i, n = xlength(x);
	Rboolean all_0 = TRUE;
	if(isAtomic) // c(*, recursive=, use.names=): cannot use these as nice_names
	    for (i = 0; i < n; i++) {
		if (STRING_ELT(x, i) == NA_STRING
		    || strcmp(CHAR(STRING_ELT(x, i)), "recursive") == 0
		    || strcmp(CHAR(STRING_ELT(x, i)), "use.names") == 0)
		    return FALSE;
		else if (all_0 && *CHAR(STRING_ELT(x, i))) /* length test */
		    all_0 = FALSE;
	    }
	else
	    for (i = 0; i < n; i++) {
		if (STRING_ELT(x, i) == NA_STRING)
		    return FALSE;
		else if (all_0 && *CHAR(STRING_ELT(x, i))) /* length test */
		    all_0 = FALSE;
	    }

	return !all_0;
    }
    return TRUE;
}


typedef enum { UNKNOWN = -1,
	       SIMPLE = 0,
	       OK_NAMES,   // no structure(*); names written as  (n1 = v1, ..)
	       STRUC_ATTR, // use structure(*, <attr> = *, ..) for non-names only
	       STRUC_NMS_A // use structure(*, <attr> = *, ..)  for names, too
} attr_type;

#ifdef DEBUG_DEPARSE
static const char* attrT2char(attr_type typ) {
    switch(typ) {
    case UNKNOWN: return "UNKNOWN";
    case SIMPLE: return "SIMPLE";
    case OK_NAMES: return "OK_NAMES";
    case STRUC_ATTR: return "STRUC_ATTR";
    case STRUC_NMS_A: return "STRUC_NMS_A";
    default: return "_unknown_ attr_type -- should *NOT* happen!";
    }
}
# define ChTF(_logic_) (_logic_ ? "TRUE" : "FALSE")
#endif

/* Exact semantic of NICE_NAMES and SHOWATTRIBUTES i.e. "niceNames" and "showAttributes"

C|  depCtrl   | attr1() result
-| -----------+-----------------------------------------------------------------------------
1|  NN &&  SA | STRUCT_ATTR + NN  or  STRUC_NMS_A (if NN are not "allowed")
2| !NN &&  SA | if(has attr) STRUC_NMS_A  else "SIMPLE"
3|  NN && !SA | OK_NAMES   ||  SIMPLE  if(!has_names)
4| !NN && !SA | SIMPLE


C|  depCtrl   : what should   deparse(*, control = depCtrl)   do ?
-| -----------+-----------------------------------------------------------------------------
1|  NN &&  SA : all attributes(but srcref); names "NICE"ly (<nam> = <val>) if valid [no NA]
2| !NN &&  SA : all attributes( "    "   ) use structure(..) incl names but no _nice_ names
3|  NN && !SA : no attributes but names, names nicely even when "wrong" (i.e. NA in names(.))
4| !NN && !SA : no attributes shown, not even names

*/

// is *only* called  if (d->opts & SHOW_ATTR_OR_NMS) = d->opts & (SHOW_A | NICE_N)
static attr_type attr1(SEXP s, LocalParseData *d)
{
    SEXP a = ATTRIB(s), nm = getAttrib(s, R_NamesSymbol);
    attr_type attr = UNKNOWN;
    Rboolean
	nice_names = d->opts & NICE_NAMES,
	show_attr  = d->opts & SHOWATTRIBUTES,
	has_names = !isNull(nm), ok_names;
#ifdef DEBUG_DEPARSE
    REprintf("  attr1(): has_names = %s", ChTF(has_names));
#endif
    if(has_names) {
	// ok only if there's no  NA_character_,.. in names() nor all """
	ok_names = nice_names && usable_nice_names(nm, isVectorAtomic(s));
#ifdef DEBUG_DEPARSE
	REprintf(", ok_names = %s", ChTF(ok_names));
#endif
	if(!ok_names)
	    attr = show_attr ? STRUC_NMS_A :
		/* nice_names */  OK_NAMES; // even when not ok
    }

    while(attr == UNKNOWN && !isNull(a)) {
	if(has_names && TAG(a) == R_NamesSymbol) {
	    // also  ok_names = TRUE
	} else if(show_attr && TAG(a) != R_SrcrefSymbol) {
	    attr = STRUC_ATTR;
	    break;
	}
	// else
	a = CDR(a);
    }
    if(attr == UNKNOWN)
	attr = has_names ? OK_NAMES : SIMPLE;

    if(attr >= STRUC_ATTR) {
	print2buff("structure(", d);
    } else if(has_names) { // attr <= OK_NAMES
    }
#ifdef DEBUG_DEPARSE
    REprintf(", return()ing %s\n", attrT2char(attr));
#endif
    return attr;
}

static void attr2(SEXP s, LocalParseData *d, Rboolean not_names)
{
    SEXP a = ATTRIB(s);
    while(!isNull(a)) {
	if(TAG(a) != R_SrcrefSymbol &&
	   !(TAG(a) == R_NamesSymbol && not_names)) {
	    print2buff(", ", d);
	    if(TAG(a) == R_DimSymbol) {
		print2buff("dim", d); // was .Dim
	    }
	    else if(TAG(a) == R_DimNamesSymbol) {
		print2buff("dimnames", d); // was .Dimnames
	    }
	    else if(TAG(a) == R_NamesSymbol) {
		print2buff("names", d); // was .Names
	    }
	    else if(TAG(a) == R_TspSymbol) {
		print2buff("tsp", d); // was .Tsp
	    }
	    else if(TAG(a) == R_LevelsSymbol) {
		print2buff("levels", d); // was .Label
	    }
	    else {
		/* TAG(a) might contain spaces etc */
		const char *tag = CHAR(PRINTNAME(TAG(a)));
		int d_opts_in = d->opts;
		d->opts = SIMPLEDEPARSE; /* turn off quote()ing */
		if(isValidName(tag))
		    deparse2buff(TAG(a), d);
		else {
		    print2buff("\"", d);
		    deparse2buff(TAG(a), d);
		    print2buff("\"", d);
		}
		d->opts = d_opts_in;
	    }
	    print2buff(" = ", d);
	    Rboolean fnarg = d->fnarg;
	    d->fnarg = TRUE;
	    deparse2buff(CAR(a), d);
	    d->fnarg = fnarg;
	}
	a = CDR(a);
    }
    print2buff(")", d);
}

static const char *quotify(SEXP name, int quote)
{
    const char *s = CHAR(name);

    /* If a symbol is not a valid name, put it in quotes, escaping
     * any quotes in the string itself */

    if (isValidName(s) || *s == '\0') return s;

    return EncodeString(name, 0, quote, Rprt_adj_none);
}

/* check for whether we need to parenthesize a caller.  The unevaluated ones
   are tricky:
   We want
     x$f(z)
     x[n](z)
     base::mean(x)
   but
     (f+g)(z)
     (function(x) 1)(x)
     etc.
*/
static Rboolean parenthesizeCaller(SEXP s)
{
    SEXP op, sym;
    if (TYPEOF(s) == LANGSXP) { /* unevaluated */
	op = CAR(s);
	if (TYPEOF(op) == SYMSXP) {
	    if (isUserBinop(op)) return TRUE;   /* %foo% */
	    sym = SYMVALUE(op);
	    if (TYPEOF(sym) == BUILTINSXP
		|| TYPEOF(sym) == SPECIALSXP) {
		if (PPINFO(sym).precedence >= PREC_SUBSET
		    || PPINFO(sym).kind == PP_FUNCALL
		    || PPINFO(sym).kind == PP_PAREN
		    || PPINFO(sym).kind == PP_CURLY) return FALSE; /* x$f(z) or x[n](z) or f(z) or (f) or {f} */
		else return TRUE;		/* (f+g)(z) etc. */
	    }
	    return FALSE;			/* regular function call */
	 } else
	    return TRUE;			/* something strange, like (1)(x) */
    } else
	return TYPEOF(s) == CLOSXP;
}

/* This is the recursive part of deparsing. */

#define SIMPLE_OPTS (~QUOTEEXPRESSIONS & ~SHOWATTRIBUTES & ~DELAYPROMISES)
/* keep KEEPINTEGER | USESOURCE | KEEPNA | S_COMPAT, also
   WARNINCOMPLETE but that is not used below this point. */
#define SHOW_ATTR_OR_NMS (SHOWATTRIBUTES | NICE_NAMES)

static void deparse2buff(SEXP s, LocalParseData *d)
{
    Rboolean lookahead = FALSE, lbreak = FALSE, fnarg = d->fnarg;
    attr_type attr = STRUC_ATTR;
    SEXP t;
    int d_opts_in = d->opts, i, n;

    d->fnarg = FALSE;

    /* This flag should only be set when recursing through the LHS
       of binary ops, so by default we reset to zero */
    int prevLeft = d->left;
    d->left = 0;

    if (!d->active) return;

    if (IS_S4_OBJECT(s)) {
	d->isS4 = TRUE;
	/* const void *vmax = vmaxget(); */
	SEXP class = getAttrib(s, R_ClassSymbol),
	    cl_def = TYPEOF(class) == STRSXP ? STRING_ELT(class, 0) : R_NilValue;
	if(TYPEOF(cl_def) == CHARSXP) { // regular S4 objects
	    print2buff("new(\"", d);
	    print2buff(translateChar(cl_def), d);
	    print2buff("\", ", d);
	    SEXP slotNms; // ---- slotNms := methods::.slotNames(s)  ---------
	    // computed alternatively, slotNms := names(getClassDef(class)@slots) :
	    static SEXP R_getClassDef = NULL, R_slots = NULL, R_asS3 = NULL;
	    if(R_getClassDef == NULL)
		R_getClassDef = findFun(install("getClassDef"), R_MethodsNamespace);
	    if(R_slots == NULL) R_slots = install("slots");
	    if(R_asS3  == NULL) R_asS3  = install("asS3");
	    SEXP e = PROTECT(lang2(R_getClassDef, class));
	    cl_def = PROTECT(eval(e, R_BaseEnv)); // correct env?
	    slotNms = // names( cl_def@slots ) :
		getAttrib(R_do_slot(cl_def, R_slots), R_NamesSymbol);
	    UNPROTECT(2); // (e, cl_def)
	    int n;
	    Rboolean has_Data = FALSE;// does it have ".Data" slot?
	    Rboolean hasS4_t = TYPEOF(s) == OBJSXP;
	    if(TYPEOF(slotNms) == STRSXP && (n = LENGTH(slotNms))) {
		PROTECT(slotNms);
		SEXP slotlist = PROTECT(allocVector(VECSXP, n));
		// := structure(lapply(slotNms, slot, object=s), names=slotNms)
		for(int i=0; i < n; i++) {
		    SEXP slot_i = STRING_ELT(slotNms, i);
		    SET_VECTOR_ELT(slotlist, i, R_do_slot(s, installTrChar(slot_i)));
		    if(!hasS4_t && !has_Data)
			has_Data = (strcmp(CHAR(slot_i), ".Data") == 0);
		}
		setAttrib(slotlist, R_NamesSymbol, slotNms);
		vec2buff(slotlist, d, TRUE);
		/*-----------------*/
		UNPROTECT(2); // (slotNms, slotlist)
	    }
	    if(!hasS4_t && !has_Data) {
		// may have *non*-slot contents, (i.e., not in .Data)
		// ==> additionally deparse asS3(s) :
		e = PROTECT(lang2(R_asS3, s)); // = asS3(s)
		SEXP S3_s = PROTECT(eval(e, R_BaseEnv)); // correct env?
		print2buff(", ", d);
		deparse2buff(S3_s, d);
		UNPROTECT(2); // (e, S3_s)
	    }
	    print2buff(")", d);
	}
	else { // exception: class is not CHARSXP
	    if(isNull(cl_def) && isNull(ATTRIB(s))) // special
		print2buff("getClass(\"S4\")@prototype", d);
	    else { // irregular S4 ((does this ever trigger ??))
		d->sourceable = FALSE;
		print2buff("<S4 object of class ", d);
		deparse2buff(class, d);
		print2buff(">", d);
	    }
	}
	/* vmaxset(vmax); */
	return;
    } // if( S4 )

    // non-S4 cases:
    switch (TYPEOF(s)) {
    case NILSXP:
	print2buff("NULL", d);
	break;
    case SYMSXP: {
	Rboolean
	    doquote = (d_opts_in & QUOTEEXPRESSIONS) && strlen(CHAR(PRINTNAME(s)));
	if (doquote) {
	    attr = (d_opts_in & SHOW_ATTR_OR_NMS) ? attr1(s, d) : SIMPLE;
	    print2buff("quote(", d);
	}
	if (d_opts_in & S_COMPAT) {
	    print2buff(quotify(PRINTNAME(s), '"'), d);
	} else if (d->backtick)
	    print2buff(quotify(PRINTNAME(s), '`'), d);
	else
	    print2buff(CHAR(PRINTNAME(s)), d);
	if (doquote) {
	    print2buff(")", d);
	    if(attr >= STRUC_ATTR) attr2(s, d, (attr == STRUC_ATTR));
	}
	break;
    }
    case CHARSXP:
    {
	const void *vmax = vmaxget();
	const char *ts = translateChar(s);
#ifdef longstring_WARN
	/* versions of R < 2.7.0 cannot parse strings longer than 8192 chars */
	if(strlen(ts) >= 8192) d->longstring = TRUE;
#endif
	print2buff(ts, d);
	vmaxset(vmax);
	break;
    }
    case SPECIALSXP:
    case BUILTINSXP:
	print2buff(".Primitive(\"", d);
	print2buff(PRIMNAME(s), d);
	print2buff("\")", d);
	break;
    case PROMSXP:
	if(d->opts & DELAYPROMISES) {
	    d->sourceable = FALSE;
	    print2buff("<promise: ", d);
	    d->opts &= ~QUOTEEXPRESSIONS; /* don't want delay(quote()) */
	    deparse2buff(PREXPR(s), d);
	    d->opts = d_opts_in;
	    print2buff(">", d);
	} else {
	    PROTECT(s = eval(s, R_EmptyEnv)); /* eval uses env of promise */
	    deparse2buff(s, d);
	    UNPROTECT(1);
	}
	break;
    case CLOSXP:
	attr = (d_opts_in & SHOW_ATTR_OR_NMS) ? attr1(s, d) : SIMPLE;
	if ((d->opts & USESOURCE)
	    && !isNull(t = getAttrib(s, R_SrcrefSymbol)))
		src2buff1(t, d);
	else {
	    /* We have established that we don't want to use the
	       source for this function */
	    d->opts &= SIMPLE_OPTS & ~USESOURCE;
	    print2buff("function (", d);
	    args2buff(FORMALS(s), 0, 1, d);
	    print2buff(") ", d);

	    writeline(d);
	    deparse2buff(BODY_EXPR(s), d);
	    d->opts = d_opts_in;
	}
	if(attr >= STRUC_ATTR) attr2(s, d, (attr == STRUC_ATTR));
	break;
    case ENVSXP:
	d->sourceable = FALSE;
	print2buff("<environment>", d);
	break;
    case VECSXP:
	attr = (d_opts_in & SHOW_ATTR_OR_NMS) ? attr1(s, d) : SIMPLE;
	print2buff("list(", d);
	d->opts = d_opts_in;// vec2buff() must use unchanged d
	vec2buff(s, d, attr == OK_NAMES || attr == STRUC_ATTR);
	d->opts |= NICE_NAMES;
	print2buff(")", d);
	if(attr >= STRUC_ATTR) attr2(s, d, (attr == STRUC_ATTR));
	d->opts = d_opts_in;
	break;
    case EXPRSXP:
	attr = (d_opts_in & SHOW_ATTR_OR_NMS) ? attr1(s, d) : SIMPLE;
	if(length(s) <= 0)
	    print2buff("expression()", d);
	else {
	    int locOpts = d->opts;
	    print2buff("expression(", d);
	    d->opts &= SIMPLE_OPTS;
	    vec2buff(s, d, attr == OK_NAMES || attr == STRUC_ATTR);
	    d->opts = locOpts;
	    print2buff(")", d);
	}
	if(attr >= STRUC_ATTR) attr2(s, d, (attr == STRUC_ATTR));
	d->opts = d_opts_in;
	break;
    case LISTSXP: {
	attr = (d_opts_in & SHOW_ATTR_OR_NMS) ? attr1(s, d) : SIMPLE;
	/* pairlist(x=) cannot be evaluated, hence with missings we use
	   as.pairlist(alist(...)) to allow evaluation of deparsed formals */
	Rboolean missing = FALSE;
	for(t=s; t != R_NilValue; t=CDR(t))
	    if (CAR(t) == R_MissingArg) {
		missing = TRUE;
		break;
	    }
	if (missing)
	    print2buff("as.pairlist(alist(", d);
	else
	    print2buff("pairlist(", d);
	d->inlist++;
	for (t=s ; CDR(t) != R_NilValue ; t=CDR(t) ) {
	    if( TAG(t) != R_NilValue ) {
		d->opts = SIMPLEDEPARSE; /* turn off quote()ing */
		deparse2buff(TAG(t), d);
		d->opts = d_opts_in;
		print2buff(" = ", d);
	    }
	    deparse2buff(CAR(t), d);
	    print2buff(", ", d);
	}
	if( TAG(t) != R_NilValue ) {
	    d->opts = SIMPLEDEPARSE; /* turn off quote()ing */
	    deparse2buff(TAG(t), d);
	    d->opts = d_opts_in;
	    print2buff(" = ", d);
	}
	deparse2buff(CAR(t), d);
	if (missing)
	    print2buff("))", d);
	else
	    print2buff(")", d);
	d->inlist--;
	if(attr >= STRUC_ATTR) attr2(s, d, (attr == STRUC_ATTR));
	break;
    }
    case LANGSXP:
	if (!isNull(ATTRIB(s)))
	    d->sourceable = FALSE;
	SEXP op = CAR(s);
	Rboolean doquote = FALSE;
	Rboolean maybe_quote = d_opts_in & QUOTEEXPRESSIONS;
	if (maybe_quote) {
	    // do *not* quote() formulas:
	    doquote = // := op is not `~` (tilde) :
		!((TYPEOF(op) == SYMSXP) &&
		  !strcmp(CHAR(PRINTNAME(op)), "~"));
	    if (doquote) {
		print2buff("quote(", d);
		d->opts &= SIMPLE_OPTS;
	    } else { // `~`
		d->opts &= ~QUOTEEXPRESSIONS;
	    }
	}

	if (TYPEOF(op) == SYMSXP) {
	    int userbinop = 0;
	    if ((TYPEOF(SYMVALUE(op)) == BUILTINSXP) ||
		(TYPEOF(SYMVALUE(op)) == SPECIALSXP) ||
		(userbinop = isUserBinop(op))) {
		PPinfo fop;
		Rboolean parens;
		s = CDR(s);
		if (userbinop) {
		    if (isNull(getAttrib(s, R_NamesSymbol))) {
			// not quite right for spacing, but can't be unary :
			fop.kind = PP_BINARY2;
			fop.precedence = PREC_PERCENT;
			fop.rightassoc = 0;
		    } else
			// if args are named, deparse as function call (PR#15350):
			fop.kind = PP_FUNCALL;
		} else
		    fop = PPINFO(SYMVALUE(op));

		switch (fop.kind) {
		case PP_BINARY:
		    switch (length(s)) {
		    case 1:
			fop.kind = PP_UNARY;
			if (fop.precedence == PREC_SUM)
			    // binary +/- precedence upgraded as unary
			    fop.precedence = PREC_SIGN;
			break;
		    case 2:
			break;
		    default:
			fop.kind = PP_FUNCALL;
			break;
		    }
		    break;
		case PP_BINARY2:
		    if (length(s) != 2)
			fop.kind = PP_FUNCALL;
		    else if (userbinop)
			fop.kind = PP_BINARY;
		    break;
		case PP_DOLLAR: {
		    if (length(s) != 2) {
			fop.kind = PP_FUNCALL;
			break;
		    }
		    SEXP rhs = CADR(s);
		    if (TYPEOF(rhs) != SYMSXP && !(isValidString(rhs)
						   && STRING_ELT(rhs, 0) != NA_STRING))
			fop.kind = PP_FUNCALL;
		    break;
		}
		default:
		    break;
		}
		switch (fop.kind) {
		case PP_IF:
		    print2buff("if (", d);
		    /* print the predicate */
		    deparse2buff(CAR(s), d);
		    print2buff(") ", d);
		    if (d->incurly && !d->inlist ) {
			lookahead = curlyahead(CADR(s));
			if (!lookahead) {
			    writeline(d);
			    d->indent++;
			}
		    }
		    /* need to find out if there is an else */
		    if (length(s) > 2) {
			deparse2buff(CADR(s), d);
			if (d->incurly && !d->inlist) {
			    writeline(d);
			    if (!lookahead)
				d->indent--;
			}
			else
			    print2buff(" ", d);
			print2buff("else ", d);
			deparse2buff(CADDR(s), d);
		    }
		    else {
			deparse2buff(CADR(s), d);
			if (d->incurly && !lookahead && !d->inlist )
			    d->indent--;
		    }
		    break;
		case PP_WHILE:
		    print2buff("while (", d);
		    deparse2buff(CAR(s), d);
		    print2buff(") ", d);
		    deparse2buff(CADR(s), d);
		    break;
		case PP_FOR:
		    print2buff("for (", d);
		    deparse2buff(CAR(s), d);
		    print2buff(" in ", d);
		    deparse2buff(CADR(s), d);
		    print2buff(") ", d);
		    deparse2buff(CADDR(s), d);
		    break;
		case PP_REPEAT:
		    print2buff("repeat ", d);
		    deparse2buff(CAR(s), d);
		    break;
		case PP_CURLY:
		    print2buff("{", d);
		    d->incurly += 1;
		    d->indent++;
		    writeline(d);
		    while (s != R_NilValue) {
			deparse2buff(CAR(s), d);
			writeline(d);
			s = CDR(s);
		    }
		    d->indent--;
		    print2buff("}", d);
		    d->incurly -= 1;
		    break;
		case PP_PAREN:
		    print2buff("(", d);
		    deparse2buff(CAR(s), d);
		    print2buff(")", d);
		    break;
		case PP_SUBSET:
		    if ((parens = needsparens(fop, CAR(s), 1, prevLeft)))
			print2buff("(", d);
		    deparse2buff(CAR(s), d);
		    if (parens)
			print2buff(")", d);
		    if (PRIMVAL(SYMVALUE(op)) == 1)
			print2buff("[", d);
		    else
			print2buff("[[", d);
		    args2buff(CDR(s), 0, 0, d);
		    if (PRIMVAL(SYMVALUE(op)) == 1)
			print2buff("]", d);
		    else
			print2buff("]]", d);
		    break;
		case PP_FUNCALL:
		case PP_RETURN:
		    if (d->backtick)
			print2buff(quotify(PRINTNAME(op), '`'), d);
		    else
			print2buff(quotify(PRINTNAME(op), '"'), d);
		    print2buff("(", d);
		    d->inlist++;
		    args2buff(s, 0, 0, d);
		    d->inlist--;
		    print2buff(")", d);
		    break;
		case PP_FOREIGN:
		    print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
		    print2buff("(", d);
		    d->inlist++;
		    args2buff(s, 1, 0, d);
		    d->inlist--;
		    print2buff(")", d);
		    break;
		case PP_FUNCTION:
		    if (!(d->opts & USESOURCE) || !isString(CADDR(s))) {
			print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
			print2buff("(", d);
			args2buff(FORMALS(s), 0, 1, d);
			print2buff(") ", d);
			deparse2buff(CADR(s), d);
		    } else {
			s = CADDR(s);
			n = length(s);
			const void *vmax = vmaxget();
			for(i = 0 ; i < n ; i++) {
			    print2buff(translateChar(STRING_ELT(s, i)), d);
			    writeline(d);
			}
			vmaxset(vmax);
		    }
		    break;
		case PP_ASSIGN:
		case PP_ASSIGN2: {
		    Rboolean outerparens = fnarg && !strcmp(CHAR(PRINTNAME(op)), "=");
		    if (outerparens)
		    	print2buff("(", d);
		    if ((parens = needsparens(fop, CAR(s), 1, prevLeft)))
			print2buff("(", d);
		    d->left = parens ? 0 : fop.precedence;
		    deparse2buff(CAR(s), d);
		    if (parens)
			print2buff(")", d);
		    print2buff(" ", d);
		    print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
		    print2buff(" ", d);
		    if ((parens = needsparens(fop, CADR(s), 0, prevLeft)))
			print2buff("(", d);
		    d->left = parens ? 0 : prevLeft;
		    deparse2buff(CADR(s), d);
		    if (parens)
			print2buff(")", d);
		    if (outerparens)
		    	print2buff(")", d);
		    d->left = 0;
		    break;
		}
		case PP_DOLLAR:
		    if ((parens = needsparens(fop, CAR(s), 1, prevLeft)))
			print2buff("(", d);
		    d->left = parens ? 0 : fop.precedence;
		    deparse2buff(CAR(s), d);
		    if (parens)
			print2buff(")", d);
		    print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
		    /*temp fix to handle printing of x$a's */
		    if( isString(CADR(s)) &&
			isValidName(CHAR(STRING_ELT(CADR(s), 0))))
			deparse2buff(STRING_ELT(CADR(s), 0), d);
		    else {
			if ((parens = needsparens(fop, CADR(s), 0, prevLeft)))
			    print2buff("(", d);
			d->left = parens ? 0 : prevLeft;
			deparse2buff(CADR(s), d);
			if (parens)
			    print2buff(")", d);
		    }
		    d->left = 0;
		    break;
		case PP_BINARY:
		    if ((parens = needsparens(fop, CAR(s), 1, prevLeft)))
			print2buff("(", d);
		    d->left = parens ? 0 : fop.precedence;
		    deparse2buff(CAR(s), d);
		    if (parens)
			print2buff(")", d);
		    print2buff(" ", d);
		    print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
		    print2buff(" ", d);
		    linebreak(&lbreak, d);

		    if ((parens = needsparens(fop, CADR(s), 0, prevLeft)))
			print2buff("(", d);
		    d->left = parens ? 0 : prevLeft;
		    deparse2buff(CADR(s), d);
		    if (parens)
			print2buff(")", d);
		    if (lbreak) {
			d->indent--;
			lbreak = FALSE;
		    }
		    d->left = 0;
		    break;
		case PP_BINARY2:	/* no space between op and args */
		    if ((parens = needsparens(fop, CAR(s), 1, prevLeft)))
			print2buff("(", d);
		    d->left = parens ? 0 : fop.precedence;
		    deparse2buff(CAR(s), d);
		    if (parens)
			print2buff(")", d);

		    print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
		    if ((parens = needsparens(fop, CADR(s), 0, prevLeft)))
			print2buff("(", d);
		    d->left = parens ? 0 : prevLeft;
		    deparse2buff(CADR(s), d);
		    if (parens)
			print2buff(")", d);
		    d->left = 0;
		    break;
		case PP_UNARY:
		    print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
		    if ((parens = needsparens(fop, CAR(s), 0, prevLeft)))
			print2buff("(", d);
		    d->left = parens ? 0 : prevLeft;
		    deparse2buff(CAR(s), d);
		    if (parens)
			print2buff(")", d);
		    d->left = 0;
		    break;
		case PP_BREAK:
		    print2buff("break", d);
		    break;
		case PP_NEXT:
		    print2buff("next", d);
		    break;
		case PP_SUBASS:
		    if(d->opts & S_COMPAT) {
			print2buff("\"", d);
			print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
			print2buff("\'(", d);
		    } else {
			print2buff("`", d);
			print2buff(CHAR(PRINTNAME(op)), d); /* ASCII */
			print2buff("`(", d);
		    }
		    args2buff(s, 0, 0, d);
		    print2buff(")", d);
		    break;
		default:
		    d->sourceable = FALSE;
		    UNIMPLEMENTED("deparse2buff");
		}
	    }
	    else {
		SEXP val = R_NilValue; /* -Wall */
		if (isSymbol(CAR(s))) {
		    val = SYMVALUE(CAR(s));
		    if (TYPEOF(val) == PROMSXP)
			val = eval(val, R_BaseEnv);
		}
		if ( isSymbol(CAR(s))
		  && TYPEOF(val) == CLOSXP
		  && streql(CHAR(PRINTNAME(CAR(s))), "::") ) { //  :: is special case
		    deparse2buff(CADR(s), d);
		    print2buff("::", d);
		    deparse2buff(CADDR(s), d);
		}
		else if ( isSymbol(CAR(s))
		  && TYPEOF(val) == CLOSXP
		  && streql(CHAR(PRINTNAME(CAR(s))), ":::") ) { // ::: is special case
		    deparse2buff(CADR(s), d);
		    print2buff(":::", d);
		    deparse2buff(CADDR(s), d);
		}
		else {
		    if ( isSymbol(CAR(s)) ){
			if(d->opts & S_COMPAT)
			    print2buff(quotify(PRINTNAME(CAR(s)), '\''), d);
			else
			    print2buff(quotify(PRINTNAME(CAR(s)), '`'), d);
		    }
		    else
			deparse2buff(CAR(s), d);
		    print2buff("(", d);
		    args2buff(CDR(s), 0, 0, d);
		    print2buff(")", d);
		}
	    }
	} // end{op : SYMSXP }
	else if (TYPEOF(op) == CLOSXP || TYPEOF(op) == SPECIALSXP
		 || TYPEOF(op) == BUILTINSXP) {
	    if (parenthesizeCaller(op)) {
		print2buff("(", d);
		deparse2buff(op, d);
		print2buff(")", d);
	    } else
		deparse2buff(op, d);
	    print2buff("(", d);
	    args2buff(CDR(s), 0, 0, d);
	    print2buff(")", d);
	}
	else { /* we have a lambda expression */
	    if (parenthesizeCaller(op)) {
		print2buff("(", d);
		deparse2buff(op, d);
		print2buff(")", d);
	    } else
		deparse2buff(op, d);
	    print2buff("(", d);
	    args2buff(CDR(s), 0, 0, d);
	    print2buff(")", d);
	}
	if (maybe_quote) {
	    d->opts = d_opts_in;
	    if(doquote)
		print2buff(")", d);
	}
	break; // end{case LANGSXP} ---------------------------------------------
    case STRSXP:
    case LGLSXP:
    case INTSXP:
    case REALSXP:
    case CPLXSXP:
    case RAWSXP:
	vector2buff(s, d);
	break;
    case EXTPTRSXP:
    {
	char tpb[32]; /* need 12+2+2*sizeof(void*) */
	d->sourceable = FALSE;
	snprintf(tpb, 32, "<pointer: %p>", R_ExternalPtrAddr(s));
	tpb[31] = '\0';
	print2buff(tpb, d);
    }
	break;
    case BCODESXP:
	d->sourceable = FALSE;
	print2buff("<bytecode>", d);
	break;
    case WEAKREFSXP:
	d->sourceable = FALSE;
	print2buff("<weak reference>", d);
	break;
    case OBJSXP: {
	/*
	print2buff("object(", d);
	if(attr >= STRUC_ATTR) attr2(s, d, (attr == STRUC_ATTR));
	 print2buff(")", d);
	*/
	d->sourceable = FALSE;
	print2buff("<object>", d);
	break;
    }
    default:
	d->sourceable = FALSE;
	UNIMPLEMENTED_TYPE("deparse2buff", s);
    }

    d->left = prevLeft;
}


/* If there is a string array active point to that, and */
/* otherwise we are counting lines so don't do anything. */

static void writeline(LocalParseData *d)
{
    if (d->strvec != R_NilValue && d->linenumber < d->maxlines)
	SET_STRING_ELT(d->strvec, d->linenumber, mkChar(d->buffer.data));
    d->linenumber++;
    if (d->linenumber >= d->maxlines) d->active = FALSE;
    /* reset */
    d->len = 0;
    d->buffer.data[0] = '\0';
    d->startline = TRUE;
}

static void print2buff(const char *strng, LocalParseData *d)
{
    size_t tlen, bufflen;

    if (d->startline) {
	d->startline = FALSE;
	printtab2buff(d->indent, d);	/*if at the start of a line tab over */
    }
    tlen = strlen(strng);
    R_AllocStringBuffer(0, &(d->buffer));
    bufflen = strlen(d->buffer.data);
    R_AllocStringBuffer(bufflen + tlen, &(d->buffer));
    strcat(d->buffer.data, strng);
    d->len += (int) tlen;
}

/*
 * Encodes a complex value as a syntactically correct
 * string that can be reparsed by R. This is required
 * because by default strings like '1+Infi' or '3+NaNi'
 * are produced which are not valid complex literals.
 */

#define NB 1000  /* Same as printutils.c */
#define NB2 2*NB+25
static const char *EncodeNonFiniteComplexElement(Rcomplex x, char* buff)
{
    int w, d, e, wi, di, ei;

    // format a first time to get width/decimals
    formatComplex(&x, 1, &w, &d, &e, &wi, &di, &ei, 0);

    char Re[NB];
    char Im[NB];

    strcpy(Re, EncodeReal0(x.r, w, d, e, "."));
    strcpy(Im, EncodeReal0(x.i, wi, di, ei, "."));

    snprintf(buff, NB2, "complex(real=%s, imaginary=%s)", Re, Im);
    buff[NB2-1] = '\0';
    return buff;
}

static void deparse2buf_name(SEXP nv, int i, LocalParseData *d) {
    if (!isNull(nv) && !isNull(STRING_ELT(nv, i))
	&& *CHAR(STRING_ELT(nv, i))) { /* length test */
	/* d->opts = SIMPLEDEPARSE; This seems pointless */
	if(isValidName(translateChar(STRING_ELT(nv, i))))
	    deparse2buff(STRING_ELT(nv, i), d);
	else if(d->backtick) {
	    print2buff("`", d);
	    deparse2buff(STRING_ELT(nv, i), d);
	    print2buff("`", d);
	} else {
	    print2buff("\"", d);
	    deparse2buff(STRING_ELT(nv, i), d);
	    print2buff("\"", d);
	}
	/* d->opts = d_opts_in; */
	print2buff(" = ", d);
    }
}

// deparse atomic vectors :
static void vector2buff(SEXP vector, LocalParseData *d)
{
    // Known here:  TYPEOF(vector)  is one of the 6 atomic *SXPs
    const char *strp;
    char *buff = 0, hex[64]; // 64 is more than enough
    int i, d_opts_in = d->opts,
	tlen = length(vector),
	quote = isString(vector) ? '"' : 0;
    Rboolean surround = FALSE, allNA,
	intSeq = FALSE; // := TRUE iff integer sequence 'm:n' (up *or* down)
    if(TYPEOF(vector) == INTSXP && tlen > 1) {
	int *vec = INTEGER(vector);
	// vec[1] - vec[0] could overflow, and does in package Rmpfr
	double d_i = (double) vec[1] - (double)vec[0];
	intSeq = (vec[0] != NA_INTEGER &&
		  vec[1] != NA_INTEGER &&
		  fabs(d_i) == 1);
	if(intSeq) for(i = 2; i < tlen; i++) {
	    if((vec[i] == NA_INTEGER) ||
	       ((double)vec[i] - (double)vec[i-1]) != d_i) {
		intSeq = FALSE;
		break;
	    }
	}
    }

    SEXP nv = R_NilValue;
    Rboolean do_names = d_opts_in & SHOW_ATTR_OR_NMS;// iff TRUE use '<tag_i> = <comp_i>'
    if(do_names) {
	nv = getAttrib(vector, R_NamesSymbol); // only "do names" if have names:
	if(isNull(nv))
	    do_names = FALSE;
    }
    PROTECT(nv);
    Rboolean
	STR_names, // if true, use structure(.,*) for names even if(nice_names)
	need_c = tlen > 1; // (?) only TRUE iff SHOW_ATTR_OR_NMS
    STR_names = do_names && (intSeq || tlen == 0);
#ifdef DEBUG_DEPARSE
    REprintf("vector2buff(v): length(v) = %d; initial (do|STR)_names) = (%s,%s)\n",
	     tlen, ChTF(do_names), ChTF(STR_names));
#endif
    if (STR_names) // use structure(.,*) for names even if(nice_names)
	d->opts &= ~NICE_NAMES;
    attr_type attr = (d_opts_in & SHOW_ATTR_OR_NMS) ? attr1(vector, d) : SIMPLE;
    if(do_names) do_names = (attr == OK_NAMES || attr == STRUC_ATTR);
    if(!need_c) need_c = do_names; // c(a = *) but not c(1)
#ifdef DEBUG_DEPARSE
    REprintf(" -> final (do|STR)_names) = (%s,%s), attr = %s\n",
	     ChTF(do_names), ChTF(STR_names), attrT2char(attr));
#endif
    if (tlen == 0) {
	switch(TYPEOF(vector)) {
	case LGLSXP: print2buff("logical(0)", d); break;
	case INTSXP: print2buff("integer(0)", d); break;
	case REALSXP: print2buff("numeric(0)", d); break;
	case CPLXSXP: print2buff("complex(0)", d); break;
	case STRSXP: print2buff("character(0)", d); break;
	case RAWSXP: print2buff("raw(0)", d); break;
	default: UNIMPLEMENTED_TYPE("vector2buff", vector);
	}
    }
    else if(TYPEOF(vector) == INTSXP) {
	/* We treat integer separately, as S_compatible is relevant.

	   Also, it is neat to deparse m:n in that form,
	   so we do so as from 2.5.0, and for m > n, from 3.5.0
	 */
	if(intSeq) { // m:n
		strp = EncodeElement(vector, 0, '"', '.');
		print2buff(strp, d);
		print2buff(":", d);
		strp = EncodeElement(vector, tlen - 1, '"', '.');
		print2buff(strp, d);
	} else {
	    int *vec = INTEGER(vector);
	    Rboolean addL = d->opts & KEEPINTEGER & !(d->opts & S_COMPAT);
	    allNA = (d->opts & KEEPNA) || addL;
	    for(i = 0; i < tlen; i++)
		if(vec[i] != NA_INTEGER) {
		    allNA = FALSE;
		    break;
		}
	    if((d->opts & KEEPINTEGER && (d->opts & S_COMPAT))) {
		print2buff("as.integer(", d); surround = TRUE;
	    }
	    allNA = allNA && !(d->opts & S_COMPAT);
	    if(need_c) print2buff("c(", d);
	    for (i = 0; i < tlen; i++) {
		if(do_names) // put '<tag> = '
		    deparse2buf_name(nv, i, d);
		if(allNA && vec[i] == NA_INTEGER) {
		    print2buff("NA_integer_", d);
		} else {
		    strp = EncodeElement(vector, i, quote, '.');
		    print2buff(strp, d);
		    if(addL && vec[i] != NA_INTEGER) print2buff("L", d);
		}
		if (i < (tlen - 1)) print2buff(", ", d);
		if (tlen > 1 && d->len > d->cutoff) writeline(d);
		if (!d->active) break;
	    }
	    if(need_c)   print2buff(")", d);
	    if(surround) print2buff(")", d);
	}
    } else { // tlen > 0;  _not_ INTSXP
	allNA = d->opts & KEEPNA;
	if((d->opts & KEEPNA) && TYPEOF(vector) == REALSXP) {
	    for(i = 0; i < tlen; i++)
		if(!ISNA(REAL(vector)[i])) {
		    allNA = FALSE;
		    break;
		}
	    if(allNA && (d->opts & S_COMPAT)) {
		print2buff("as.double(", d); surround = TRUE;
	    }
	} else if((d->opts & KEEPNA) && TYPEOF(vector) == CPLXSXP) {
	    Rcomplex *vec = COMPLEX(vector);
	    for(i = 0; i < tlen; i++) {
		if( !ISNA(vec[i].r) && !ISNA(vec[i].i) ) {
		    allNA = FALSE;
		    break;
		}
	    }
	    if(allNA && (d->opts & S_COMPAT)) {
		print2buff("as.complex(", d); surround = TRUE;

	    }
	} else if((d->opts & KEEPNA) && TYPEOF(vector) == STRSXP) {
	    for(i = 0; i < tlen; i++)
		if(STRING_ELT(vector, i) != NA_STRING) {
		    allNA = FALSE;
		    break;
		}
	    if(allNA && (d->opts & S_COMPAT)) {
		print2buff("as.character(", d); surround = TRUE;
	    }
	} else if(TYPEOF(vector) == RAWSXP) {
	    print2buff("as.raw(", d); surround = TRUE;
 	}
	if(need_c) print2buff("c(", d);
	allNA = allNA && !(d->opts & S_COMPAT);
	for (i = 0; i < tlen; i++) {
	    if(do_names) // put '<tag> = '
		deparse2buf_name(nv, i, d);
	    if(allNA && TYPEOF(vector) == REALSXP &&
	       ISNA(REAL(vector)[i])) {
		strp = "NA_real_";
	    } else if (TYPEOF(vector) == CPLXSXP &&
		       (ISNA(COMPLEX(vector)[i].r)
			&& ISNA(COMPLEX(vector)[i].i)) ) {
		strp = allNA ? "NA_complex_" : EncodeElement(vector, i, quote, '.');
	    } else if(TYPEOF(vector) == CPLXSXP &&
		      (ISNAN(COMPLEX(vector)[i].r) || !R_FINITE(COMPLEX(vector)[i].i)) ) {
		if (!buff)
		    buff = alloca(NB2);
		strp = EncodeNonFiniteComplexElement(COMPLEX(vector)[i], buff);
	    } else if (allNA && TYPEOF(vector) == STRSXP &&
		       STRING_ELT(vector, i) == NA_STRING) {
		strp = "NA_character_";
	    } else if (TYPEOF(vector) == REALSXP && (d->opts & S_COMPAT)) {
		int w, d, e;
		formatReal(&REAL(vector)[i], 1, &w, &d, &e, 0);
		strp = EncodeReal2(REAL(vector)[i], w, d, e);
	    } else if (TYPEOF(vector) == STRSXP) {
		const void *vmax = vmaxget();
#ifdef longstring_WARN
		const char *ts = translateChar(STRING_ELT(vector, i));
		/* versions of R < 2.7.0 cannot parse strings longer than 8192 chars */
		if(strlen(ts) >= 8192) d->longstring = TRUE;
#endif
		strp = EncodeElement(vector, i, quote, '.');
		vmaxset(vmax);
	    } else if (TYPEOF(vector) == RAWSXP) {
		strp = EncodeRaw(RAW(vector)[i], "0x");
	    } else if (TYPEOF(vector) == REALSXP && (d->opts & HEXNUMERIC)) {
		double x = REAL(vector)[i];
		// Windows warns here, but incorrectly as this is C99
		// and the snprintf used from trio is compliant.
		if (R_FINITE(x)) {
		    snprintf(hex, 32, "%a", x);
		    strp = hex;
		} else
		    strp = EncodeElement(vector, i, quote, '.');
	    } else if (TYPEOF(vector) == REALSXP && (d->opts & DIGITS17)) {
		double x = REAL(vector)[i];
		if (R_FINITE(x)) {
		    snprintf(hex, 32, "%.17g", x);
		    strp = hex;
		} else
		    strp = EncodeElement(vector, i, quote, '.');
	    } else if (TYPEOF(vector) == CPLXSXP && (d->opts & HEXNUMERIC)) {
		Rcomplex z =  COMPLEX(vector)[i];
		if (R_FINITE(z.r) && R_FINITE(z.i)) {
		    snprintf(hex, 64, "%a + %ai", z.r, z.i);
		    strp = hex;
		} else
		    strp = EncodeElement(vector, i, quote, '.');
	    } else if (TYPEOF(vector) == CPLXSXP && (d->opts & DIGITS17)) {
		Rcomplex z =  COMPLEX(vector)[i];
		if (R_FINITE(z.r) && R_FINITE(z.i)) {
		    snprintf(hex, 64, "%.17g%+.17gi", z.r, z.i);
		    strp = hex;
		} else
		    strp = EncodeElement(vector, i, quote, '.');
	    } else
		strp = EncodeElement(vector, i, quote, '.');
	    print2buff(strp, d);
	    if (i < (tlen - 1)) print2buff(", ", d);
	    if (tlen > 1 && d->len > d->cutoff) writeline(d);
	    if (!d->active) break;
	} // for(i in 1:tlen)
	if(need_c  ) print2buff(")", d);
	if(surround) print2buff(")", d);
    }
    if(attr >= STRUC_ATTR) attr2(vector, d, (attr == STRUC_ATTR));
    if (STR_names) d->opts = d_opts_in;
    UNPROTECT(1); /* nv */
} // vector2buff()


/* src2buff1: Deparse one source ref to buffer */

static void src2buff1(SEXP srcref, LocalParseData *d)
{
    int i,n;
    const void *vmax = vmaxget();
    PROTECT(srcref);

    PROTECT(srcref = lang2(R_AsCharacterSymbol, srcref));
    PROTECT(srcref = eval(srcref, R_BaseEnv));
    n = length(srcref);
    for(i = 0 ; i < n ; i++) {
	/* FIXME: does not embed UTF-8 for RGui */
	print2buff(translateChar(STRING_ELT(srcref, i)), d);
	if(i < n-1) writeline(d);
    }
    UNPROTECT(3);
    vmaxset(vmax);
}

/* src2buff : Deparse source element k to buffer, if possible; return FALSE on failure */

static Rboolean src2buff(SEXP sv, int k, LocalParseData *d)
{
    SEXP t;

    if (TYPEOF(sv) == VECSXP && length(sv) > k && !isNull(t = VECTOR_ELT(sv, k))) {
	src2buff1(t, d);
	return TRUE;
    }
    else return FALSE;
}

/* Deparse vectors of S-expressions, i.e., list() and expression() objects.
   In particular, this deparses objects of mode expression. */
static void vec2buff(SEXP v, LocalParseData *d,
		     Rboolean do_names) // iff TRUE use '<tag_i> = <comp_i>'
{
    Rboolean lbreak = FALSE;
    const void *vmax = vmaxget();
    int n = length(v);
    SEXP nv = R_NilValue;
    if(do_names) {
	nv = getAttrib(v, R_NamesSymbol); // only "do names" if have names:
	if (isNull(nv))
	    do_names = FALSE;
    }
    PROTECT(nv);
    SEXP sv; // Srcref or NULL
    if (d->opts & USESOURCE) {
	sv = getAttrib(v, R_SrcrefSymbol);
	if (TYPEOF(sv) != VECSXP)
	    sv = R_NilValue;
    } else
	sv = R_NilValue;

    for(int i = 0 ; i < n ; i++) {
	if (i > 0)
	    print2buff(", ", d);
	linebreak(&lbreak, d);
	if(do_names) // put '<tag> = '
	    deparse2buf_name(nv, i, d);
	if (!src2buff(sv, i, d))
	    deparse2buff(VECTOR_ELT(v, i), d);
    }
    if (lbreak)
	d->indent--;
    vmaxset(vmax);
    UNPROTECT(1); /* nv */
}

static void args2buff(SEXP arglist, int lineb, int formals, LocalParseData *d)
{
    Rboolean lbreak = FALSE;

    while (arglist != R_NilValue) {
	if (TYPEOF(arglist) != LISTSXP && TYPEOF(arglist) != LANGSXP)
	    error(_("badly formed function expression"));
	if (TAG(arglist) != R_NilValue) {
	    SEXP s = TAG(arglist);

	    if( s == R_DotsSymbol )
		print2buff(CHAR(PRINTNAME(s)), d);
	    else if(d->backtick)
		print2buff(quotify(PRINTNAME(s), '`'), d);
	    else
		print2buff(quotify(PRINTNAME(s), '"'), d);

	    if(formals) {
		if (CAR(arglist) != R_MissingArg) {
		    print2buff(" = ", d);
		    d->fnarg = TRUE;
		    deparse2buff(CAR(arglist), d);
		}
	    }
	    else {
		print2buff(" = ", d);
		if (CAR(arglist) != R_MissingArg) {
		    d->fnarg = TRUE;
		    deparse2buff(CAR(arglist), d);
		}
	    }
	}
	else {
	  d->fnarg = TRUE;
	  deparse2buff(CAR(arglist), d);
	}
	arglist = CDR(arglist);
	if (arglist != R_NilValue) {
	    print2buff(", ", d);
	    linebreak(&lbreak, d);
	}
    }
    if (lbreak)
	d->indent--;
}

/* This code controls indentation.  Used to follow the S style, */
/* (print 4 tabs and then start printing spaces only) but I */
/* modified it to be closer to emacs style (RI). */

static void printtab2buff(int ntab, LocalParseData *d)
{
    int i;

    for (i = 1; i <= ntab; i++)
	if (i <= 4)
	    print2buff("    ", d);
	else
	    print2buff("  ", d);
}
