#  File src/library/utils/R/str.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-2024 The R Core Team
#
#  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.
#
#  A copy of the GNU General Public License is available at
#  https://www.R-project.org/Licenses/


####------ str : show STRucture of an R object
str <- function(object, ...) UseMethod("str")

## FIXME: convert to use sQuote
str.data.frame <- function(object, ...)
{
    ## Method to 'str' for  'data.frame' objects
    if(! is.data.frame(object)) {
	warning("str.data.frame() called with non-data.frame -- coercing to one.")
	object <- data.frame(object)
    }

    ## Show further classes // Assume that they do NOT have an own Method --
    ## not quite perfect ! (.Class = 'remaining classes', starting with current)
    cl <- oldClass(object); cl <- cl[cl != "data.frame"]  #- not THIS class
    if(0 < length(cl)) cat("Classes", paste(sQuote(cl), collapse=", "), "and ")

    cat("'data.frame':	", nrow(object), " obs. of  ",
	(p <- length(object)), " variable", if(p != 1)"s", if(p > 0)":",
	"\n", sep = "")

    ## calling next method, usually  str.default:
    if(length(l <- list(...)) && any("give.length" == names(l)))
	invisible(NextMethod("str", ...))
    else invisible(NextMethod("str", give.length=structure(FALSE, from="data.frame"), ...))
}

str.Date <- str.POSIXt <- function(object, ...) {
    cl <- oldClass(object)
    ## be careful to be fast for large object:
    n <- length(object) # FIXME, could be NA
    if(n == 0L) return(str.default(object, ...))
    if(n > 1000L) object <- object[seq_len(1000L)]

    give.length <- TRUE ## default
    ## use 'give.length' when specified, else default = give.head
    if(length(larg <- list(...))) {
	nl <- names(larg)
	which.head <- which(nl == "give.head")
	if (any(Bgl <- nl == "give.length"))
	    give.length <- larg[[which(Bgl)]]
	else if(length(which.head))
	    give.length <- larg[[which.head]]
	if(length(which.head)) # eliminate it from arg.list
	    larg <- larg[ - which.head ]
	if(is.numeric(larg[["nest.lev"]]) &&
	   is.numeric(larg[["vec.len"]])) # typical call from data.frame
	    ## reduce length for typical call:
	    larg[["vec.len"]] <-
		min(larg[["vec.len"]],
		    (larg[["width"]]- nchar(larg[["indent.str"]]) -31)%/% 19)
    }

    le.str <- if(give.length) paste0("[1:",as.character(n),"]")
    cat(" ", cl[1L], le.str,", format: ", sep = "")
    ## do.call(str, c(list(format(object), give.head = FALSE), larg))
    ## ensuring 'object' is *not* copied:
    str.f.obj <- function(...) str(format(object), ...)
    do.call(str.f.obj, c(list(give.head = FALSE), larg))
}

## Called by .onLoad(), setting options(str = *)  >>>>> ./zzz.R
strOptions <- function(strict.width = "no", digits.d = 3L, vec.len = 4L,
                       list.len = 99L, deparse.lines = NULL,
                       drop.deparse.attr = TRUE,
		       formatNum = function(x, ...)
		       format(x, trim=TRUE, drop0trailing=TRUE, ...))
    list(strict.width = strict.width, digits.d = digits.d, vec.len = vec.len,
         list.len = list.len, deparse.lines = deparse.lines,
	 drop.deparse.attr = drop.deparse.attr,
	 formatNum = match.fun(formatNum))

str.default <-
    function(object, max.level = NA, vec.len = strO$vec.len,
	     digits.d = strO$digits.d,
	     nchar.max = 128, give.attr = TRUE,
	     drop.deparse.attr = strO$drop.deparse.attr,
	     give.head = TRUE, give.length = give.head,
	     width = getOption("width"), nest.lev = 0,
	     indent.str= paste(rep.int(" ", max(0,nest.lev+1)), collapse= ".."),
	     comp.str="$ ", no.list = FALSE, envir = baseenv(),
	     strict.width = strO$strict.width,
	     formatNum = strO$formatNum, list.len = strO$list.len,
	     deparse.lines = strO$deparse.lines,
	     ...)
{
    ## Purpose: Display STRucture of any R - object (in a compact form).
    ## --- see HELP file --
    ## ------------------------------------------------------------------------
    ## Author: Martin Maechler <maechler@stat.math.ethz.ch>	1990--1997

    ## strOptions() defaults for
    oDefs <- c("vec.len", "digits.d", "strict.width", "formatNum",
	       "drop.deparse.attr",
	       "list.len", "deparse.lines"
               )
    ## from
    strO <- getOption("str")
    if (!is.list(strO)) {
	warning('invalid options("str") -- using defaults instead')
	strO <- strOptions()
    }
    else {
        if (!all(names(strO) %in% oDefs))
            warning(gettextf("invalid components in options(\"str\"): %s",
                             paste(setdiff(names(strO), oDefs), collapse = ", ")),
                    domain = NA)
        strO <- modifyList(strOptions(), strO)
    }
    strict.width <- match.arg(strict.width, choices = c("no", "cut", "wrap"))
    if(strict.width != "no") {
	## using eval() would be cleaner, but fails inside capture.output():
	ss <- capture.output(str.default(object, max.level = max.level,
				 vec.len = vec.len, digits.d = digits.d,
				 nchar.max = nchar.max,
				 give.attr= give.attr, give.head= give.head,
				 give.length= give.length,
				 width = width, nest.lev = nest.lev,
				 indent.str = indent.str, comp.str= comp.str,
				 no.list= no.list || is.data.frame(object),
				 envir = envir, strict.width = "no",
				 formatNum = formatNum, list.len = list.len,
				 deparse.lines = deparse.lines,
					 ...) )
	if(strict.width == "wrap") {
	    nind <- nchar(indent.str) + 2
	    ss <- strwrap(ss, width = width, exdent = nind)
					# wraps at white space (only)
	}
	if(length(iLong <- which(nchar(ss) > width))) { ## cut hard
	    sL <- ss[iLong]
	    k <- as.integer(width-2L)
	    if(any(i <- grepl("\"", substr(sL, k +1L, nchar(sL))))) {
		## care *not* to cut off the closing   "  at end of
		## string that's already truncated {-> maybe_truncate()} :
		ss[iLong[ i]] <- paste0(substr(sL[ i], 1, k-1L), "\"..")
		ss[iLong[!i]] <- paste0(substr(sL[!i], 1, k), "..")
	    } else {
		ss[iLong] <- paste0(substr(sL, 1, k),"..")
	    }
	}
	cat(ss, sep = "\n")
	return(invisible())
    }

    oo <- options(digits = digits.d); on.exit(options(oo))
    le <- length(object)[1L] # [1]: protect from nonsense
    if(is.na(le)) {
        warning("'str.default': 'le' is NA, so taken as 0", immediate. = TRUE)
        le <- 0
        vec.len <- 0
    }

    nchar.w <- function(x) nchar(x, type="w", allowNA=TRUE)
    ncharN  <- function(x) {
	r <- nchar(x, type="w", allowNA=TRUE)
	if(anyNA(r)) {
	    iN <- is.na(r)
	    r[iN] <- nchar(x[iN], type="bytes")
	}
	r
    }
    ## x: character ; using "global" 'nchar.max'
    maybe_truncate <- function(x, nx = nchar.w(x), S = "\"", ch = "| __truncated__")
    {
	ok <- if(anyNA(nx)) !is.na(nx) else TRUE
	if(any(lrg <- ok & nx > nchar.max)) {
	    nc <- nchar(ch <- paste0(S, ch))
	    if(nchar.max <= nc)
		stop(gettextf("'nchar.max = %d' is too small", nchar.max), domain=NA)
	    x.lrg <- x[lrg]
	    tr.x <- strtrim(x.lrg, nchar.max - nc)
	    if(any(ii <- tr.x != x.lrg & paste0(tr.x, S) != x.lrg)) {
		x[lrg][ii] <- paste0(tr.x[ii], ch)
	    }
	}
	x
    }
    pClass <- function(cls)
	paste0("Class", if(length(cls) > 1) "es",
	       " '", paste(cls, collapse = "', '"), "' ")

    nfS <- names(fStr <- formals())# names of all formal args to str.default()
    ##' Purpose: using short strSub() calls instead of long str() ones
    ##' @title Call str() on sub-parts, with mostly the *same* arguments
    ##' @param obj R object; always a "part" of the main 'object'
    ##' @param ... further arguments to str(), [often str.default()]
    strSub <- function(obj, ...) {
	## 'give.length', ...etc are *not* automatically passed down:
	nf <- setdiff(nfS, c("object", "give.length", "comp.str", "no.list",
			     ## drop fn.name & "obj" :
			     names(match.call())[-(1:2)], "..."))
	aList <- as.list(fStr)[nf]
	aList[] <- lapply(nf, function(n) eval(as.name(n)))
	## do.call(str, c(list(object=obj),aList,list(...)), quote=TRUE), *not* copying 'obj'
	do.call(function(...) str(obj, ...), c(aList, list(...)), quote = TRUE)
    }

    ## le.str: not used for arrays:
    le.str <-
	if(is.na(le)) " __no length(.)__ "
	else if(give.length) {
	    if(le > 0) paste0("[1:", paste(le), "]") else "(0)"
	} else ""
    v.len <- vec.len # modify v.len, not vec.len!
    ## NON interesting attributes:
    std.attr <- "names"

    cl <- if((S4 <- isS4(object))) class(object) else oldClass(object)
    has.class <- S4 || !is.null(cl) # S3 or S4
    mod <- ""; char.like <- FALSE
    if(give.attr) a <- attributes(object)#-- save for later...
    dCtrl <- eval(formals(deparse)$control)
    if(drop.deparse.attr) dCtrl <- dCtrl[dCtrl != "showAttributes"]
    width.cutoff <- min(500L, max(20L, width-10L))
    nlines <- deparse.lines %||% (1L + as.integer(max(nchar.max, width.cutoff) / 8))
    deParse <- function(.) deparse(., width.cutoff = width.cutoff,
				   control = dCtrl, nlines = nlines)
    n.of. <- function(n, singl, plural) paste(n, ngettext(n, singl, plural))
    n.of <- function(n, noun) n.of.(n, noun, paste0(noun,"s"))
    l.i <- function(i) paste0("[[",i,"]]")
    arrLenstr <- function(obj) {
	rnk <- length(di. <- dim(obj))
	di <- paste0(ifelse(di. > 1, "1:",""), di.,
		     ifelse(di. > 0, "" ," "))
	pDi <- function(...) paste(c("[", ..., "]"), collapse = "")
	if(rnk == 1)
	     pDi(di[1L], "(1d)")
	else pDi(paste0(di[-rnk], ", "), di[rnk])
    }
    if(is.ts <- stats::is.ts(object))
        str1.ts <- function(o, lestr) {
            tsp.a <- stats::tsp(o)
            paste0(" Time-Series ", lestr, " from ", format(tsp.a[1L]),
                   " to ", format(tsp.a[2L]), ":")
        }
    if (is.null(object))
	cat(" NULL\n")
    else if(S4) {
	trygetSlots <- function(x, nms) {
	    r <- tryCatch(sapply(nms, methods::slot, object=x, simplify = FALSE),
			  error = conditionMessage)
	    if(is.list(r))
		r
	    else {
		warning("Not a validObject(): ", r, call.=FALSE) # instead of error
		r <- attributes(x) ## "FIXME" low-level assumption about S4 slots
		r <- r[names(r) != "class"]
		dp <- list(methods::getDataPart(x, NULL.for.none=TRUE))
		if(!is.null(dp)) names(dp) <- methods:::.dataSlot(nms)
		c(r, dp)
	    }
	}
	if(methods::is(object,"envRefClass")) {
	    cld <- tryCatch(object$getClass(), error=function(e)e)
	    if(inherits(cld, "error")) {
		cat("Prototypical reference class", " '", paste(cl, collapse = "', '"),
		    "' [package \"", attr(cl,"package"), "\"]\n", sep="")
		## add a bit more info ??
		return(invisible())
	    }
	    nFlds <- names(cld@fieldClasses)
	    a <- sapply(nFlds, function(ch) object[[ch]], simplify = FALSE)
	    cat("Reference class", " '", paste(cl, collapse = "', '"),
		"' [package \"", attr(cl,"package"), "\"] with ",
                n.of(length(a), "field"), "\n", sep = "")
	    strSub(a, no.list=TRUE, give.length=give.length,
		   nest.lev = nest.lev + 1)
	    meths <- names(cld@refMethods)
	    oMeths <- meths[is.na(match(meths, methods:::envRefMethodNames))]
	    cat(indent.str, "and ", n.of(length(meths), "method"), sep = "")
	    sNms <- names(cld@slots)
	    if(lo <- length(oMeths)) {
		cat(", of which", lo, ngettext(lo, "is", "are", domain = NA), " possibly relevant")
		if (is.na(max.level) || nest.lev < max.level)
		    cat(":",
			strwrap(paste(sort(oMeths), collapse=", "),
				indent = 2, exdent = 2,
				prefix = indent.str, width=width),# exdent = nind),
			sep = "\n")
		else cat("\n")
	    }
	    if(length(sNms <- sNms[sNms != ".xData"])) {
		cat(" and ", n.of(length(sNms), "slot"), "\n", sep="")
		sls <- trygetSlots(object, sNms)
		strSub(sls, comp.str = "@ ", no.list=TRUE, give.length=give.length,
		       indent.str = paste(indent.str,".."), nest.lev = nest.lev + 1)
	    }
	    else if(lo == 0) cat(".\n")
	}
	else { ## S4 non-envRefClass
	    sNms <- methods::.slotNames(object)
	    cat("Formal class", " '", paste(cl, collapse = "', '"),
		"' [package \"", attr(cl,"package"), "\"] with ",
		n.of(length(sNms), "slot"), "\n", sep = "")
	    s <- trygetSlots(object, sNms)
	    strSub(s, comp.str = "@ ", no.list=TRUE, give.length=give.length,
		   indent.str = paste(indent.str,".."), nest.lev = nest.lev + 1)
	    ## if there are non-slot nor "class" attributes, show them:
	    if(give.attr && length(nmsa <- setdiff(names(a), c("class", sNms))))
		strSub(a[nmsa], no.list=TRUE, give.length=give.length,
		       indent.str = paste(indent.str,".."), nest.lev = nest.lev + 1)
	}
	return(invisible())
    }
    else if(is.function(object)) {
	cat(if(is.null(ao <- args(object))) deParse(object)
	else { dp <- deParse(ao); paste(dp[-length(dp)], collapse="\n") },"\n")
    } else if(is.list(object)) {
	i.pl <- is.pairlist(object)
	is.d.f <- is.data.frame(object)
	##?if(is.d.f) std.attr <- c(std.attr, "class", if(is.d.f) "row.names")
	if(le == 0) {
	    if(is.d.f) std.attr <- c(std.attr, "class", "row.names")
	    else cat(" ", if(!is.null(names(object))) "Named ",
		     if(i.pl)"pair", "list()\n", sep = "")
	} else { # list, length >= 1 :
	    if(irregCl <- has.class &&
		   ## vapply(), lapply .. typically use length(unclass(object))
		   (length(uncObj <- unclass(object)) != le # << igraph communities
		    || identical(object[[1L]], object)
		    || inherits(tryCatch(object[[le]], error=identity), "error")# igraph
		   )
	       ) {
		le <- length(object <- uncObj)
		std.attr <- c(std.attr, "class")
	    }
	    if(no.list || (has.class &&
			   any(sapply(paste0("str.", cl),
					#use sys.function(.) ..
				      function(ob)exists(ob, mode= "function",
							 inherits= TRUE))))) {
		## str.default is a 'NextMethod' : omit the 'List of ..'
		std.attr <- c(std.attr, "class", if(is.d.f) "row.names")
	    } else { # need as.character here for double lengths.
		cat(if(i.pl) "Dotted pair list" else
		    if(irregCl) paste(pClass(cl), "hidden list") else "List",
		    " of ", as.character(le), "\n", sep = "")
	    }
	    if (is.na(max.level) || nest.lev < max.level) {
		nms <- names(object)
		if("promise" %in% (oTypes <- vapply(object, typeof, ""))) {
		    envP <- object # a list; ensure all promises are named
		    if(is.null(nms)) names(envP) <- rep.int("", le)
		    if(any(zch <- !nzchar(names(envP)["promise" == oTypes]))) ## name them
			names(envP)[zch] <- l.i(which(zch))
		}
		nam.ob <-
		    if(is.null(nms)) rep.int("", le)
		    else { ncn <- nchar.w(nms)
			   if(anyNA(ncn)) ## slower, but correct:
			      ncn <- vapply(nms, format.info, 0L)
			   format(nms, width = max(ncn), justify="left")
		       }
		for (i in seq_len(min(list.len,le) ) ) {
		    cat(indent.str, comp.str, nam.ob[i], ":", sep = "")
		    envir <- # pass envir for 'promise' components:
			if(oTypes[[i]] == "promise") {
			    structure(envP, nam = as.name(names(envP)[i]))
			} # else NULL
		    strSub(object[[i]], give.length=give.length,
                           nest.lev = nest.lev + 1,
                           indent.str = paste(indent.str,".."))
		}
		if(list.len < le)
		    cat(indent.str, "[list output truncated]\n")
	    }
	}
    } else { # not  NULL, S4, function, or list
	if (is.factor(object)) {
	    nl <- length(lev.att <- levels(object))
	    if(!is.character(lev.att)) {# should not happen..
		warning("'object' does not have valid levels()")
		nl <- 0
	    } else { ## protect against large nl:
                w <- min(max(width/2, 10), 1000)
                if(nl > w) lev.att <- lev.att[seq_len(w)]
                n.l <- length(lev.att) # possibly  n.l << nl
                lev.att <- encodeString(lev.att, na.encode = FALSE, quote = '"')
            }
	    ord <- is.ordered(object)
	    object <- unclass(object)
	    if(nl) {
		## as from 2.1.0, quotes are included ==> '-2':
		lenl <- cumsum(3 + (ncharN(lev.att) - 2))# level space
		ml <- if(n.l <= 1 || lenl[n.l] <= 13)
		    n.l else which.max(lenl > 13)
		lev.att <- maybe_truncate(lev.att[seq_len(ml)])
	    }
	    else # nl == 0
		ml <- length(lev.att <- "")

	    lsep <- if(ord) "<" else ","
	    str1 <-
		paste0(if(ord)" Ord.f" else " F", "actor",
                       if(is.array(object)) arrLenstr(object),
		       " w/ ", nl, " level", if(nl != 1) "s",
		       if(nl) " ",
		       if(nl) paste0(lev.att, collapse = lsep),
		       if(ml < nl) paste0(lsep, ".."), ":")

	    std.attr <- c("levels", "class", if(is.array(object)) "dim")
	} else if(is.ts) {
	    str1 <- str1.ts(object,
                            if(isA <- is.array(object)) arrLenstr(object) else le.str)
	    std.attr <- c("tsp", "class", if(isA) "dim")
        } else if(is.vector(object)
	       || is.atomic(object)
               ##f fails for formula:
               ##f typeof(object) in {"symbol", "language"} =: is.symbolic(.):
               ##f || (is.language(object) && !is.expression(object))
               || (is.language(object) && !is.expression(object) && !any(cl == "formula")) ) {
	    if(is.atomic(object)) {
		##-- atomic:   numeric{dbl|int} complex character logical raw
		mod <- substr(mode(object), 1, 4)
		if     (mod == "nume")
		    mod <- if(is.integer(object)) "int" else "num"
		else if(mod == "char") { mod <- "chr"; char.like <- TRUE }
		else if(mod == "comp") mod <- "cplx" #- else: keep 'logi'
		if(is.array(object)) {
		    le.str <- arrLenstr(object)
		    if(m <- match("AsIs", cl, 0L)) ## workaround bad format.AsIs()
			oldClass(object) <- cl[-m]
                    std.attr <- "dim"
		} else if(!is.null(names(object))) {
		    mod <- paste("Named", mod)
		    std.attr <- std.attr[std.attr != "names"]
		}
		if(has.class) {
		    cl <- cl[1L] # and "forget" potential other classes
		    if(cl != mod && substr(cl, 1L, nchar(mod)) != mod)
			mod <- paste0("'",cl,"' ", mod)
		    ## don't show the class *twice*
		    std.attr <- c(std.attr, "class")
		}
		str1 <-
		    if(le == 1 && !is.array(object)) paste(NULL, mod)
		    else paste0(" ", mod, if(le>0)" ", le.str)
	    } else { ##-- not atomic, but vector: #
		mod <- typeof(object)#-- typeof(.) is more precise than mode!
		str1 <- switch(mod,
			       call = " call",
			       language = " language",
			       symbol = " symbol",
			       expression = " ",# "expression(..)" by deParse(.)
			       name = " name",
			       ##not in R:argument = "",# .Argument(.) by deParse(.)
			       ## in R (once):	comment.expression

			       ## default :
			       paste("		#>#>", mod, NULL)
			       )
	    }
	} else if(typeof(object) %in%
		  c("externalptr", "weakref", "environment", "bytecode", "object")) {
	    ## Careful here, we don't want to change pointer objects
	    if(has.class)
                cat(pClass(cl))
	    le <- v.len <- 0
	    str1 <-
		if(is.environment(object)) format(object)
		else paste0("<", typeof(object), ">")
	    has.class <- TRUE # fake for later
	    std.attr <- "class"
	    ## ideally we would figure out if as.character has a
	    ## suitable method and use that.
	} else if(has.class) {
	    cat("Class", if(length(cl) > 1) "es",
		" '", paste(cl, collapse = "', '"), "' ", sep = "")
	    ## If there's a str.<method>, it should have been called before!
	    uo <- unclass(object)
	    if(!is.null(attributes(uo)$class)) {
		## another irregular case
		xtr <- c(if(identical(uo, object)) { # trap infinite loop
		    class(uo) <- NULL
		    "unclass()-immune"
		} else if(!is.object(object)) "not-object")
		if(!is.null(xtr)) cat("{",xtr,"} ", sep = "")
	    }
	    strSub(uo, indent.str = paste(indent.str,".."), nest.lev = nest.lev + 1)
	    return(invisible())
	} else if(is.atomic(object)) {
	    if((1 == length(a <- attributes(object))) && (names(a) == "names"))
		str1 <- paste(" Named vector", le.str)
	    else {
		##-- atomic / not-vector  "unclassified object" ---
		str1 <- paste(" atomic", le.str)
	    }
	} else if(typeof(object) == "promise") {
	    cat(" promise to ")
	    objExp <-
		if (is.null(envir) || is.null(nam <- attr(envir, "nam")))
		    substitute(.x., as.environment(list(.x. = object)))
		else
		    eval(bquote(substitute(.(nam), envir)))
	    strSub(objExp)
	    return(invisible())
	} else {
	    ##-- NOT-atomic / not-vector  "unclassified object" ---
	    str1 <- paste("length", le)
	}
	##-- end  if else..if else...  {still non-list case}

	##-- This needs some improvement: Not list nor atomic --
	if ((is.language(object) || !is.atomic(object)) && !has.class) {
	    ##-- has.class superfluous --
	    mod <- mode(object)
	    give.mode <- FALSE
            trimEnds <- function(ch) sub(" +$", '', sub("^ +", ' ', ch))
	    if (any(mod == c("call", "language", "(", "symbol"))
		|| is.environment(object)) {
		##give.mode <- !is.vector(object)# then it has not yet been done
		if(mod == "(") give.mode <- TRUE
		typ <- typeof(object)
		object <- deParse(object)

		le <- length(object) # is > 1 e.g. for {A;B} language
		format.fun <- function(x) x
		v.len <- round(.5 * v.len)
		if(le > 1 && typ=="language" && object[1L] == "{" && object[le]=="}") {
		    v.len <- v.len + 2
		    if(le >= 3) {
			object <- c(object[1L],
				    paste(trimEnds(object[2:(le-1)]), collapse = ";"),
				    object[le])
			le <- length(object)
		    }
		}
	    } else if (mod == "expression") {
		format.fun <- function(x) trimEnds(deParse(as.expression(x)))
		v.len <- round(.75 * v.len)
	    } else if (mod == "name"){
		object <- paste(object)#-- show `as' char
	    } else if (mod == "argument"){
		format.fun <- deParse
	    } else {
		if(mod == "...") { # DOTSXP
		    format.fun <- function(x) {
			hasNm <- nzchar(nm <- names(x) %||% rep.int("", length(x)))
			nm[hasNm] <- paste0(nm[hasNm], "=")
			paste0("(", paste(paste0(nm,"*"), collapse=", "),
			       ")")
		    }
		}
		give.mode <- TRUE
	    }
	    if(give.mode) str1 <- paste0(str1, ', mode "', mod,'":')

	} else if(is.logical(object)) {
	    v.len <- 1.5 * v.len # was '3' originally (but S prints 'T' 'F' ..)
	    format.fun <- formatNum
	} else if(is.numeric(object)) {
	    iv.len <- round(2.5 * v.len)
	    if(iSurv <- inherits(object, "Surv"))
		std.attr <- c(std.attr, "class")
	    int.surv <- iSurv || is.integer(object)
	    if(!int.surv) {
		ob <- if(le > iv.len) object[seq_len(iv.len)] else object
		ao <- abs(ob <- unclass(ob[!is.na(ob)]))
	    }
	    else if(iSurv) {
		nc <- ncol(object)
		le <- length(object <- as.character(object))
	    }
	    if(int.surv || (all(ao > 1e-10 | ao==0) && all(ao < 1e10| ao==0) &&
			    all(abs(ob - signif(ob, digits.d)) <= 9e-16*ao))) {
		if(!iSurv || nc == 2L) # "Surv" : implemented as matrix
		    ## use integer-like length
		    v.len <- iv.len
		format.fun <- formatNum
	    } else {
		v.len <- round(1.25 * v.len)
		format.fun <- formatNum
	    }
	} else if(is.complex(object)) {
	    v.len <- round(.75 * v.len)
	    format.fun <- formatNum
	}

	if(char.like) {
	    ## if object is very long, drop the rest which won't be used anyway:
	    max.len <- max(100L, width %/% 3L + 1L, if(!missing(vec.len)) vec.len)
	    if(le > max.len) le <- length(object <- object[seq_len(max.len)])
	    ## For very long strings, truncated later anyway,
	    ## both nchar(*, type="w") and encodeString() are too expensive
	    trimWidth <- as.integer(nchar.max)
	    ## FIXME: need combined  encode.and.trim.string(object, m)  with O(m) !
	    encObj <- tryCatch(strtrim(object, trimWidth), error=function(e) NULL)
	    encObj <-
		if(is.null(encObj)) { # must first encodeString() before we can trim
		    e <- encodeString(object, quote= '"', na.encode= FALSE)
		    r <- tryCatch(strtrim(e, trimWidth), error=function(.) NULL)
		    ## What else can we try?
		    if(is.null(r)) e else r
		}
		else
		    encodeString(encObj, quote= '"', na.encode= FALSE)
	    if(le > 0) ## truncate if LONG char:
		encObj <- maybe_truncate(encObj)
	    v.len <-
		if(missing(vec.len)) {
		    max(1,sum(cumsum(1 + if(le>0) ncharN(encObj) else 0) <
			      width - (4 + 5*nest.lev + nchar(str1, type="w"))))
		}	 	      # '5*ne..' above is fudge factor
		else round(v.len)
	    ile <- min(le, v.len)
	    if(ile >= 1)
		object <- encObj[seq_len(ile)]
	    formObj <- function(x) paste(as.character(x), collapse = " ")
	}
	else { # not char.like
	    if(!exists("format.fun"))
		format.fun <- switch(mod,
				     "num" =,
				     "cplx" = format,
				     "language" = deParse,
				     ## otherwise :
				     as.character)
	    ## v.len <- max(1,round(v.len))
	    ile <- min(v.len, le)
	    formObj <- function(x) maybe_truncate(paste(format.fun(x), collapse = " "),
						  S = "") # *not* string-like
	}

	cat(if(give.head) paste0(str1, " "),
	    formObj(if(ile >= 1 && mod != "...") object[seq_len(ile)]
		    else if(v.len > 0) object),
	    if(le > v.len) " ...", "\n", sep = "")

    } ## else (not function nor list)----------------------------------------

    if(give.attr) { ## possible: || has.class && any(cl == "terms")
	nam <- names(a)
	give.L <- give.length || identical(attr(give.length,"from"), "data.frame")
	for (i in seq_along(a))
	    if (all(nam[i] != std.attr)) {# only `non-standard' attributes:
		cat(indent.str, paste0('- attr(*, "', nam[i], '")='), sep = "")
		strSub(a[[i]], give.length = give.L,
		       indent.str = paste(indent.str, ".."), nest.lev = nest.lev+1)
	    }
    }
    invisible()	 ## invisible(object)#-- is SLOOOOW on large objects
}# end of `str.default()'

## An extended `ls()' whose print method will use str() :
ls.str <-
    function (pos = -1, name, envir, all.names = FALSE, pattern, mode = "any")
{
    if(missing(envir)) ## [for "lazy" reasons, this fails as default]
        envir <- as.environment(pos)
    nms <- ls(name, envir = envir, all.names=all.names, pattern=pattern)
    r <- vapply(nms, exists, NA, envir=envir, mode=mode, inherits=FALSE)
    structure(nms[r], envir = envir, mode = mode, class = "ls_str")
}

lsf.str <- function(pos = -1, envir, ...) {
    if(missing(envir)) ## [for "lazy" reasons, this fails as default]
        envir <- as.environment(pos)
    ls.str(pos=pos, envir=envir, mode = "function", ...)
}

print.ls_str <- function(x, max.level = 1, give.attr = FALSE,
                         ..., digits = max(1, getOption("str")$digits.d))
{
    E <- attr(x, "envir")
    stopifnot(is.environment(E))
    M <- attr(x, "mode")
    args <- list(...)
    if(length(args) && "digits.d" %in% names(args)) {
        if(missing(digits))
            digits <- args$digits.d
        else
            warning("'digits' and 'digits.d' are both specified and the latter is not used")
        args$digits.d <- NULL
    }
    strargs <- c(list(max.level = max.level, give.attr = give.attr,
                      digits.d = digits), args)
    n. <- substr(tempfile("ls_str_", tmpdir=""), 2L, 20L)
    for(nam in x) {
	cat(nam, ": ")
	## check missingness, e.g. inside debug(.) :
	eA <- sprintf("%s:%s", nam, n.) # need a 'mark' in case nam *is* an error object
	o <- tryCatch(get(nam, envir = E, mode = M),
		      error = function(e){ attr(e, eA) <- TRUE; e })
	if(inherits(o, "error") &&  isTRUE(attr(o, eA))) {
            cat(if(inherits(o, "getMissingError")) "<missing>" else o$message,
                "\n", sep = "")
	}
	else {
	    ## do.call(str, c(list(o), strargs),
	    ##	  quote = is.call(o) || is.symbol(o)) # protect calls from eval.
	    ## ensuring 'obj' is *not* copied:
	    strO <- function(...) str(o, ...)
	    do.call(strO, strargs, quote = is.call(o) || is.symbol(o))
					# protect calls from eval.
	}
    }
    invisible(x)
}
