From e9e3f79aa41baa35a9478e45c496c099ab296659 Mon Sep 17 00:00:00 2001
From: Matt Dowle <mattjdowle@gmail.com>
Date: Fri, 13 Oct 2017 16:12:33 -0700
Subject: [PATCH 1/4] tabs -> 4 spaces, all C files

---
 CRAN_Release.cmd |   7 +-
 src/assign.c     |   6 +-
 src/fastmean.c   | 186 +++++++++++++--------------
 src/rbindlist.c  |  10 +-
 src/shift.c      | 321 ++++++++++++++++++++++++-----------------------
 src/wrappers.c   |  22 ++--
 6 files changed, 279 insertions(+), 273 deletions(-)

diff --git a/CRAN_Release.cmd b/CRAN_Release.cmd
index cf25be719f..45eac55747 100644
--- a/CRAN_Release.cmd
+++ b/CRAN_Release.cmd
@@ -21,7 +21,7 @@ grep -RI --exclude-dir=".git" --exclude="*.md" --exclude="*~" --color='auto' -n
 # Ensure no calls to omp_set_num_threads() [to avoid affecting other packages and base R]
 grep --exclude="data.table/src/openmp-utils.c" omp_set_num_threads data.table/src/*
 
-# Endure no calls to omp_get_max_threads() also since access should be via getDTthreads()
+# Ensure no calls to omp_get_max_threads() also since access should be via getDTthreads()
 grep --exclude="data.table/src/openmp-utils.c" omp_get_max_threads data.table/src/*
 
 # Ensure all #pragama omp parallel directives include a num_threads() clause
@@ -45,6 +45,11 @@ grep "&REAL" data.table/src/*.c
 # No use of long long, instead use int64_t. TODO
 # grep "long long" data.table/src/*.c
 
+# No tabs in C or R code (sorry Richard Hendricks)
+grep -P "\t" data.table/R/*.R
+grep -P "\t" data.table/src/*.c
+
+
 # seal leak potential where two unprotected API calls are passed to the same
 # function call, usually involving install() or mkChar()
 # Greppable thanks to single lines and wide screens
diff --git a/src/assign.c b/src/assign.c
index f8e87cdf0f..4ddba204fb 100644
--- a/src/assign.c
+++ b/src/assign.c
@@ -228,9 +228,9 @@ SEXP alloccolwrapper(SEXP dt, SEXP newncol, SEXP verbose) {
     for(R_len_t i = 0; i < LENGTH(ans); i++) {
         // clear the same excluded by copyMostAttrib(). Primarily for data.table and as.data.table, but added here centrally (see #4890).
 
-	setAttrib(VECTOR_ELT(ans, i), R_NamesSymbol, R_NilValue);
-	setAttrib(VECTOR_ELT(ans, i), R_DimSymbol, R_NilValue);
-	setAttrib(VECTOR_ELT(ans, i), R_DimNamesSymbol, R_NilValue);
+        setAttrib(VECTOR_ELT(ans, i), R_NamesSymbol, R_NilValue);
+        setAttrib(VECTOR_ELT(ans, i), R_DimSymbol, R_NilValue);
+        setAttrib(VECTOR_ELT(ans, i), R_DimNamesSymbol, R_NilValue);
     }
 
     UNPROTECT(1);
diff --git a/src/fastmean.c b/src/fastmean.c
index 30511dc08b..b0b488bfe8 100644
--- a/src/fastmean.c
+++ b/src/fastmean.c
@@ -28,89 +28,89 @@ if we become out of line to base R (say if base R changed its mean).
 
 SEXP fastmean(SEXP args)
 {
-  	long double s = 0., t = 0.;
-	R_len_t i, l = 0, n = 0;
-	SEXP x, ans, tmp;
-	Rboolean narm=FALSE;
-	x=CADR(args);
-	if (length(args)>2) {
-	    tmp = CADDR(args);
-	    if (!isLogical(tmp) || LENGTH(tmp)!=1 || LOGICAL(tmp)[0]==NA_LOGICAL)
+    long double s = 0., t = 0.;
+    R_len_t i, l = 0, n = 0;
+    SEXP x, ans, tmp;
+    Rboolean narm=FALSE;
+    x=CADR(args);
+    if (length(args)>2) {
+        tmp = CADDR(args);
+        if (!isLogical(tmp) || LENGTH(tmp)!=1 || LOGICAL(tmp)[0]==NA_LOGICAL)
             error("narm should be TRUE or FALSE");
-	    narm=LOGICAL(tmp)[0];
-	}
-	PROTECT(ans = allocNAVector(REALSXP, 1));
-	if (!isInteger(x) && !isReal(x) && !isLogical(x)) {
+        narm=LOGICAL(tmp)[0];
+    }
+    PROTECT(ans = allocNAVector(REALSXP, 1));
+    if (!isInteger(x) && !isReal(x) && !isLogical(x)) {
         warning("argument is not numeric or logical: returning NA");
         UNPROTECT(1);
         return(ans);
     }
     l = LENGTH(x);
-	if (narm) {
-	    switch(TYPEOF(x)) {
-	    case LGLSXP:
-	    case INTSXP:
-	        for (i = 0; i<l; i++) {
-		        if(INTEGER(x)[i] == NA_INTEGER) continue;
-		        s += INTEGER(x)[i];   // no under/overflow here, s is long double not integer
-		        n++;
-		    }
-		    if (n>0)
-		        REAL(ans)[0] = (double) (s/n);
-		    else
-		        REAL(ans)[0] = R_NaN;  // consistent with base: mean(NA,na.rm=TRUE)==NaN==mean(numeric(),na.rm=TRUE)
-	        break;
-	    case REALSXP:
-	        for (i = 0; i<l; i++) {
-	            if(ISNAN(REAL(x)[i])) continue;  // TO DO: could drop this line and let NA propogate?
-	            s += REAL(x)[i];
-	            n++;
-	        }
-	        if (n==0) {
-	            REAL(ans)[0] = R_NaN;
-	            break;
-	        }
-	        s /= n;
-	        if(R_FINITE((double)s)) {
-		        for (i = 0; i<l; i++) {
-		            if(ISNAN(REAL(x)[i])) continue;
-		            t += (REAL(x)[i] - s);
-		        }
-		        s += t/n;
-	        }
-	        REAL(ans)[0] = (double) s;
-	        break;
-	    default:
-	        error("Type '%s' not supported in fastmean", type2char(TYPEOF(x)));
-	    }
+    if (narm) {
+        switch(TYPEOF(x)) {
+        case LGLSXP:
+        case INTSXP:
+            for (i = 0; i<l; i++) {
+                if(INTEGER(x)[i] == NA_INTEGER) continue;
+                s += INTEGER(x)[i];   // no under/overflow here, s is long double not integer
+                n++;
+            }
+            if (n>0)
+                REAL(ans)[0] = (double) (s/n);
+            else
+                REAL(ans)[0] = R_NaN;  // consistent with base: mean(NA,na.rm=TRUE)==NaN==mean(numeric(),na.rm=TRUE)
+            break;
+        case REALSXP:
+            for (i = 0; i<l; i++) {
+                if(ISNAN(REAL(x)[i])) continue;  // TO DO: could drop this line and let NA propogate?
+                s += REAL(x)[i];
+                n++;
+            }
+            if (n==0) {
+                REAL(ans)[0] = R_NaN;
+                break;
+            }
+            s /= n;
+            if(R_FINITE((double)s)) {
+                for (i = 0; i<l; i++) {
+                    if(ISNAN(REAL(x)[i])) continue;
+                    t += (REAL(x)[i] - s);
+                }
+                s += t/n;
+            }
+            REAL(ans)[0] = (double) s;
+            break;
+        default:
+            error("Type '%s' not supported in fastmean", type2char(TYPEOF(x)));
+        }
     } else {  // narm==FALSE
-	    switch(TYPEOF(x)) {
-	    case LGLSXP:
-	    case INTSXP:
-	        for (i = 0; i<l; i++) {
-		        if(INTEGER(x)[i] == NA_INTEGER) {UNPROTECT(1); return(ans);}
-		        s += INTEGER(x)[i];
-		    }
-		    REAL(ans)[0] = (double) (s/l);
-	        break;
-	    case REALSXP:
-	        for (i = 0; i<l; i++) {
-	            if(ISNAN(REAL(x)[i])) {UNPROTECT(1); return(ans);}
-	            s += REAL(x)[i];
-	        }
-	        s /= l;
-	        if(R_FINITE((double)s)) {
-		        for (i = 0; i<l; i++) {
-		            // no NA if got this far
-		            t += (REAL(x)[i] - s);
-		        }
-		        s += t/LENGTH(x);
-	        }
-	        REAL(ans)[0] = (double) s;
-	        break;
-	    default:
-	        error("Type '%s' not supported in fastmean", type2char(TYPEOF(x)));
-	    }
+        switch(TYPEOF(x)) {
+        case LGLSXP:
+        case INTSXP:
+            for (i = 0; i<l; i++) {
+                if(INTEGER(x)[i] == NA_INTEGER) {UNPROTECT(1); return(ans);}
+                s += INTEGER(x)[i];
+            }
+            REAL(ans)[0] = (double) (s/l);
+            break;
+        case REALSXP:
+            for (i = 0; i<l; i++) {
+                if(ISNAN(REAL(x)[i])) {UNPROTECT(1); return(ans);}
+                s += REAL(x)[i];
+            }
+            s /= l;
+            if(R_FINITE((double)s)) {
+                for (i = 0; i<l; i++) {
+                    // no NA if got this far
+                    t += (REAL(x)[i] - s);
+                }
+                s += t/LENGTH(x);
+            }
+            REAL(ans)[0] = (double) s;
+            break;
+        default:
+            error("Type '%s' not supported in fastmean", type2char(TYPEOF(x)));
+        }
     }
     copyMostAttrib(x, ans);
     UNPROTECT(1);
@@ -118,23 +118,23 @@ SEXP fastmean(SEXP args)
 }
 
 /*
-	    case CPLXSXP:
-	        PROTECT(ans = allocVector(CPLXSXP, 1));
-	        for (i = 0; i < n; i++) {
-		    s += COMPLEX(x)[i].r;
-		    si += COMPLEX(x)[i].i;
-	        }
-	        s /= n; si /= n;
-	        if( R_FINITE((double)s) && R_FINITE((double)si) ) {
-		    for (i = 0; i < n; i++) {
-		        t += COMPLEX(x)[i].r - s;
-		        ti += COMPLEX(x)[i].i - si;
-		    }
-		    s += t/n; si += ti/n;
-	        }
-	        COMPLEX(ans)[0].r = (double) s;
-	        COMPLEX(ans)[0].i = (double) si;
-	        break;
+        case CPLXSXP:
+            PROTECT(ans = allocVector(CPLXSXP, 1));
+            for (i = 0; i < n; i++) {
+            s += COMPLEX(x)[i].r;
+            si += COMPLEX(x)[i].i;
+            }
+            s /= n; si /= n;
+            if( R_FINITE((double)s) && R_FINITE((double)si) ) {
+            for (i = 0; i < n; i++) {
+                t += COMPLEX(x)[i].r - s;
+                ti += COMPLEX(x)[i].i - si;
+            }
+            s += t/n; si += ti/n;
+            }
+            COMPLEX(ans)[0].r = (double) s;
+            COMPLEX(ans)[0].i = (double) si;
+            break;
 */
 
 
diff --git a/src/rbindlist.c b/src/rbindlist.c
index f8f5471ef8..2098426904 100644
--- a/src/rbindlist.c
+++ b/src/rbindlist.c
@@ -727,11 +727,11 @@ SEXP rbindlist(SEXP l, SEXP sexp_usenames, SEXP sexp_fill, SEXP idcol) {
                 for (r=0; r<thislen; r++)
                     SET_VECTOR_ELT(target, ansloc+r, VECTOR_ELT(thiscol,r));
                 break;
-	    case CPLXSXP : // #1659 fix
-		if (TYPEOF(thiscol) != TYPEOF(target)) error("Internal logical error in rbindlist.c, type of 'thiscol' should have already been coerced to 'target'. Please report to datatable-help.");
-		for (r=0; r<thislen; r++)
-		    COMPLEX(target)[ansloc+r] = COMPLEX(thiscol)[r];
-		break;
+            case CPLXSXP : // #1659 fix
+                if (TYPEOF(thiscol) != TYPEOF(target)) error("Internal logical error in rbindlist.c, type of 'thiscol' should have already been coerced to 'target'. Please report to datatable-help.");
+                for (r=0; r<thislen; r++)
+                    COMPLEX(target)[ansloc+r] = COMPLEX(thiscol)[r];
+                break;
             case REALSXP:
             case INTSXP:
             case LGLSXP:
diff --git a/src/shift.c b/src/shift.c
index 3c16320079..3f4368e8d3 100644
--- a/src/shift.c
+++ b/src/shift.c
@@ -5,8 +5,8 @@
 SEXP shift(SEXP obj, SEXP k, SEXP fill, SEXP type) {
 
     size_t size;
-	R_len_t i=0, j, m, nx, nk, xrows, thisk;
-	SEXP x, tmp=R_NilValue, this, ans, thisfill, class;
+    R_len_t i=0, j, m, nx, nk, xrows, thisk;
+    SEXP x, tmp=R_NilValue, this, ans, thisfill, class;
     unsigned long long *dthisfill;
     enum {LAG, LEAD} stype = LAG;
     if (!length(obj)) return(obj); // NULL, list()
@@ -15,9 +15,9 @@ SEXP shift(SEXP obj, SEXP k, SEXP fill, SEXP type) {
         SET_VECTOR_ELT(x, 0, obj);
     } else x = obj;
     if (!isNewList(x))
-    	error("x must be a list, data.frame or data.table");
+        error("x must be a list, data.frame or data.table");
     if (!isInteger(k))
-    	error("Internal error: n must be integer");
+        error("Internal error: n must be integer");
     if (length(fill) != 1)
         error("fill must be a vector of length 1");
     if (!isString(type) || length(type) != 1)
@@ -31,7 +31,7 @@ SEXP shift(SEXP obj, SEXP k, SEXP fill, SEXP type) {
     i=0;
     while(i < nk && INTEGER(k)[i] >= 0) i++;
     if (i != nk)
-    	error("n must be non-negative integer values (>= 0)");
+        error("n must be non-negative integer values (>= 0)");
     ans = PROTECT(allocVector(VECSXP, nk * nx));
     if (stype == LAG) {
         for (i=0; i<nx; i++) {
@@ -39,91 +39,91 @@ SEXP shift(SEXP obj, SEXP k, SEXP fill, SEXP type) {
             size  = SIZEOF(this);
             xrows = length(this);
             switch (TYPEOF(this)) {
-                case INTSXP :
-                    thisfill = PROTECT(coerceVector(fill, INTSXP));
-                    for (j=0; j<nk; j++) {
-                        thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                        tmp = allocVector(INTSXP, xrows);
-                        SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                        if (xrows - INTEGER(k)[j] > 0)
-                            memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
-                                   (char *)DATAPTR(this),
-                                   (xrows-INTEGER(k)[j])*size);
-                        for (m=0; m<thisk; m++)
-                            INTEGER(tmp)[m] = INTEGER(thisfill)[0];
-                        copyMostAttrib(this, tmp);
-                        if (isFactor(this))
-                            setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
-                    }
+            case INTSXP :
+                thisfill = PROTECT(coerceVector(fill, INTSXP));
+                for (j=0; j<nk; j++) {
+                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+                    tmp = allocVector(INTSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    if (xrows - INTEGER(k)[j] > 0)
+                        memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
+                               (char *)DATAPTR(this),
+                               (xrows-INTEGER(k)[j])*size);
+                    for (m=0; m<thisk; m++)
+                        INTEGER(tmp)[m] = INTEGER(thisfill)[0];
+                    copyMostAttrib(this, tmp);
+                    if (isFactor(this))
+                        setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
+                }
                 break;
 
-                case REALSXP :
-                    class = getAttrib(this, R_ClassSymbol);
-                    if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
-                        thisfill = PROTECT(allocVector(REALSXP, 1));
-                        dthisfill = (unsigned long long *)REAL(thisfill);
-                        if (INTEGER(fill)[0] == NA_INTEGER)
-                            dthisfill[0] = NA_INT64_LL;
-                        else dthisfill[0] = (unsigned long long)INTEGER(fill)[0];
-                    } else {
-                        thisfill = PROTECT(coerceVector(fill, REALSXP));
+            case REALSXP :
+                class = getAttrib(this, R_ClassSymbol);
+                if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
+                    thisfill = PROTECT(allocVector(REALSXP, 1));
+                    dthisfill = (unsigned long long *)REAL(thisfill);
+                    if (INTEGER(fill)[0] == NA_INTEGER)
+                        dthisfill[0] = NA_INT64_LL;
+                    else dthisfill[0] = (unsigned long long)INTEGER(fill)[0];
+                } else {
+                    thisfill = PROTECT(coerceVector(fill, REALSXP));
+                }
+                for (j=0; j<nk; j++) {
+                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+                    tmp = allocVector(REALSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    if (xrows - INTEGER(k)[j] > 0) {
+                        memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
+                               (char *)DATAPTR(this),
+                               (xrows-INTEGER(k)[j])*size);
                     }
-                    for (j=0; j<nk; j++) {
-                        thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                        tmp = allocVector(REALSXP, xrows);
-                        SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                        if (xrows - INTEGER(k)[j] > 0) {
-                            memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
-                                   (char *)DATAPTR(this),
-                                   (xrows-INTEGER(k)[j])*size);
-                        }
-                        for (m=0; m<thisk; m++) {
-                            REAL(tmp)[m] = REAL(thisfill)[0];
-                        }
-                        copyMostAttrib(this, tmp);
+                    for (m=0; m<thisk; m++) {
+                        REAL(tmp)[m] = REAL(thisfill)[0];
                     }
+                    copyMostAttrib(this, tmp);
+                }
                 break;
 
-                case LGLSXP :
-                    thisfill = PROTECT(coerceVector(fill, LGLSXP));
-                    for (j=0; j<nk; j++) {
-                        thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                        tmp = allocVector(LGLSXP, xrows);
-                        SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                        if (xrows - INTEGER(k)[j] > 0)
-                            memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
-                                   (char *)DATAPTR(this),
-                                   (xrows-INTEGER(k)[j])*size);
-                        for (m=0; m<thisk; m++)
-                            LOGICAL(tmp)[m] = LOGICAL(thisfill)[0];
-                        copyMostAttrib(this, tmp);
-                    }
+            case LGLSXP :
+                thisfill = PROTECT(coerceVector(fill, LGLSXP));
+                for (j=0; j<nk; j++) {
+                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+                    tmp = allocVector(LGLSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    if (xrows - INTEGER(k)[j] > 0)
+                        memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
+                               (char *)DATAPTR(this),
+                               (xrows-INTEGER(k)[j])*size);
+                    for (m=0; m<thisk; m++)
+                        LOGICAL(tmp)[m] = LOGICAL(thisfill)[0];
+                    copyMostAttrib(this, tmp);
+                }
                 break;
 
-                case STRSXP :
-                    thisfill = PROTECT(coerceVector(fill, STRSXP));
-                    for (j=0; j<nk; j++) {
-                        tmp = allocVector(STRSXP, xrows);
-                        SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                        for (m=0; m<xrows; m++)
-                            SET_STRING_ELT(tmp, m, (m < INTEGER(k)[j]) ? STRING_ELT(thisfill, 0) : STRING_ELT(this, m - INTEGER(k)[j]));
-                        copyMostAttrib(this, tmp);
-                    }
+            case STRSXP :
+                thisfill = PROTECT(coerceVector(fill, STRSXP));
+                for (j=0; j<nk; j++) {
+                    tmp = allocVector(STRSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    for (m=0; m<xrows; m++)
+                        SET_STRING_ELT(tmp, m, (m < INTEGER(k)[j]) ? STRING_ELT(thisfill, 0) : STRING_ELT(this, m - INTEGER(k)[j]));
+                    copyMostAttrib(this, tmp);
+                }
                 break;
 
-		case VECSXP :
-		    thisfill = PROTECT(coerceVector(fill, VECSXP));
-		    for (j=0; j<nk; j++) {
-			tmp = allocVector(VECSXP, xrows);
-			SET_VECTOR_ELT(ans, i*nk+j, tmp);
-			for (m=0; m<xrows; m++)
-			    SET_VECTOR_ELT(tmp, m, (m < INTEGER(k)[j]) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(this, m - INTEGER(k)[j]));
-			copyMostAttrib(this, tmp);
-		    }
-		break;
+            case VECSXP :
+                thisfill = PROTECT(coerceVector(fill, VECSXP));
+                for (j=0; j<nk; j++) {
+                    tmp = allocVector(VECSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    for (m=0; m<xrows; m++)
+                        SET_VECTOR_ELT(tmp, m, (m < INTEGER(k)[j]) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(this, m - INTEGER(k)[j]));
+                    copyMostAttrib(this, tmp);
+                }
+                break;
 
-                default :
-                    error("Unsupported type '%s'", type2char(TYPEOF(this)));
+            default :
+                error("Unsupported type '%s'", type2char(TYPEOF(this)));
             }
             copyMostAttrib(this, tmp);
             if (isFactor(this))
@@ -132,94 +132,94 @@ SEXP shift(SEXP obj, SEXP k, SEXP fill, SEXP type) {
         }
     } else if (stype == LEAD) {
         for (i=0; i<nx; i++) {
-        	this  = VECTOR_ELT(x, i);
+            this  = VECTOR_ELT(x, i);
             size  = SIZEOF(this);
-        	xrows = length(this);
-        	switch (TYPEOF(this)) {
-        		case INTSXP :
-                    thisfill = PROTECT(coerceVector(fill, INTSXP));
-        			for (j=0; j<nk; j++) {
-                        thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-    	    			tmp = allocVector(INTSXP, xrows);
-        				SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                        if (xrows - INTEGER(k)[j] > 0)
-                            memcpy((char *)DATAPTR(tmp),
-                                   (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
-                                   (xrows-INTEGER(k)[j])*size);
-                        for (m=xrows-thisk; m<xrows; m++)
-                            INTEGER(tmp)[m] = INTEGER(thisfill)[0];
-                        copyMostAttrib(this, tmp);
-                        if (isFactor(this))
-                            setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
-        			}
-        		break;
+            xrows = length(this);
+            switch (TYPEOF(this)) {
+            case INTSXP :
+                thisfill = PROTECT(coerceVector(fill, INTSXP));
+                for (j=0; j<nk; j++) {
+                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+                    tmp = allocVector(INTSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    if (xrows - INTEGER(k)[j] > 0)
+                        memcpy((char *)DATAPTR(tmp),
+                               (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
+                               (xrows-INTEGER(k)[j])*size);
+                    for (m=xrows-thisk; m<xrows; m++)
+                        INTEGER(tmp)[m] = INTEGER(thisfill)[0];
+                    copyMostAttrib(this, tmp);
+                    if (isFactor(this))
+                        setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
+                }
+                break;
 
-        		case REALSXP :
-                    class = getAttrib(this, R_ClassSymbol);
-                    if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
-                        thisfill = PROTECT(allocVector(REALSXP, 1));
-                        dthisfill = (unsigned long long *)REAL(thisfill);
-                        if (INTEGER(fill)[0] == NA_INTEGER)
-                            dthisfill[0] = NA_INT64_LL;
-                        else dthisfill[0] = (unsigned long long)INTEGER(fill)[0];
-                    } else {
-                        thisfill = PROTECT(coerceVector(fill, REALSXP));
-                    }
-                    for (j=0; j<nk; j++) {
-                        thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                        tmp = allocVector(REALSXP, xrows);
-                        SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                        if (xrows - INTEGER(k)[j] > 0)
-                            memcpy((char *)DATAPTR(tmp),
-                                   (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
-                                   (xrows-INTEGER(k)[j])*size);
-                        for (m=xrows-thisk; m<xrows; m++)
-                            REAL(tmp)[m] = REAL(thisfill)[0];
-                        copyMostAttrib(this, tmp);
-                    }
-        		break;
+            case REALSXP :
+                class = getAttrib(this, R_ClassSymbol);
+                if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
+                    thisfill = PROTECT(allocVector(REALSXP, 1));
+                    dthisfill = (unsigned long long *)REAL(thisfill);
+                    if (INTEGER(fill)[0] == NA_INTEGER)
+                        dthisfill[0] = NA_INT64_LL;
+                    else dthisfill[0] = (unsigned long long)INTEGER(fill)[0];
+                } else {
+                    thisfill = PROTECT(coerceVector(fill, REALSXP));
+                }
+                for (j=0; j<nk; j++) {
+                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+                    tmp = allocVector(REALSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    if (xrows - INTEGER(k)[j] > 0)
+                        memcpy((char *)DATAPTR(tmp),
+                               (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
+                               (xrows-INTEGER(k)[j])*size);
+                    for (m=xrows-thisk; m<xrows; m++)
+                        REAL(tmp)[m] = REAL(thisfill)[0];
+                    copyMostAttrib(this, tmp);
+                }
+                break;
 
-        		case LGLSXP :
-                    thisfill = PROTECT(coerceVector(fill, LGLSXP));
-                    for (j=0; j<nk; j++) {
-                        thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                        tmp = allocVector(LGLSXP, xrows);
-                        SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                        if (xrows - INTEGER(k)[j] > 0)
-                            memcpy((char *)DATAPTR(tmp),
-                                   (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
-                                   (xrows-INTEGER(k)[j])*size);
-                        for (m=xrows-thisk; m<xrows; m++)
-                            LOGICAL(tmp)[m] = LOGICAL(thisfill)[0];
-                        copyMostAttrib(this, tmp);
-                    }
-        		break;
+            case LGLSXP :
+                thisfill = PROTECT(coerceVector(fill, LGLSXP));
+                for (j=0; j<nk; j++) {
+                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+                    tmp = allocVector(LGLSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    if (xrows - INTEGER(k)[j] > 0)
+                        memcpy((char *)DATAPTR(tmp),
+                               (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
+                               (xrows-INTEGER(k)[j])*size);
+                    for (m=xrows-thisk; m<xrows; m++)
+                        LOGICAL(tmp)[m] = LOGICAL(thisfill)[0];
+                    copyMostAttrib(this, tmp);
+                }
+                break;
 
-                case STRSXP :
-                    thisfill = PROTECT(coerceVector(fill, STRSXP));
-                    for (j=0; j<nk; j++) {
-                        tmp = allocVector(STRSXP, xrows);
-                        SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                        for (m=0; m<xrows; m++)
-                            SET_STRING_ELT(tmp, m, (xrows-m <= INTEGER(k)[j]) ? STRING_ELT(thisfill, 0) : STRING_ELT(this, m + INTEGER(k)[j]));
-                        copyMostAttrib(this, tmp);
-                    }
+            case STRSXP :
+                thisfill = PROTECT(coerceVector(fill, STRSXP));
+                for (j=0; j<nk; j++) {
+                    tmp = allocVector(STRSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    for (m=0; m<xrows; m++)
+                        SET_STRING_ELT(tmp, m, (xrows-m <= INTEGER(k)[j]) ? STRING_ELT(thisfill, 0) : STRING_ELT(this, m + INTEGER(k)[j]));
+                    copyMostAttrib(this, tmp);
+                }
                 break;
 
-		case VECSXP :
-		    thisfill = PROTECT(coerceVector(fill, VECSXP));
-		    for (j=0; j<nk; j++) {
-			tmp = allocVector(VECSXP, xrows);
-			SET_VECTOR_ELT(ans, i*nk+j, tmp);
-			for (m=0; m<xrows; m++)
-			    SET_VECTOR_ELT(tmp, m, (xrows-m <= INTEGER(k)[j]) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(this, m + INTEGER(k)[j]));
-			copyMostAttrib(this, tmp);
-		    }
-		break;
+            case VECSXP :
+                thisfill = PROTECT(coerceVector(fill, VECSXP));
+                for (j=0; j<nk; j++) {
+                    tmp = allocVector(VECSXP, xrows);
+                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
+                    for (m=0; m<xrows; m++)
+                        SET_VECTOR_ELT(tmp, m, (xrows-m <= INTEGER(k)[j]) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(this, m + INTEGER(k)[j]));
+                        copyMostAttrib(this, tmp);
+                }
+                break;
 
-    	        default :
-    	            error("Unsupported type '%s'", type2char(TYPEOF(this)));
-        	}
+            default :
+                error("Unsupported type '%s'", type2char(TYPEOF(this)));
+            }
             UNPROTECT(1);
         }
     }
@@ -228,3 +228,4 @@ SEXP shift(SEXP obj, SEXP k, SEXP fill, SEXP type) {
         return (VECTOR_ELT(ans, 0));
     return(ans);
 }
+
diff --git a/src/wrappers.c b/src/wrappers.c
index 65f5dc3c53..46190e4910 100644
--- a/src/wrappers.c
+++ b/src/wrappers.c
@@ -96,11 +96,11 @@ SEXP copyNamedInList(SEXP x)
 
     if (TYPEOF(x) != VECSXP) error("x isn't a VECSXP");
     for (int i=0; i<LENGTH(x); i++) {
-	    if (MAYBE_REFERENCED(VECTOR_ELT(x, i))) {
-	        SET_VECTOR_ELT(x, i, duplicate(VECTOR_ELT(x,i)));
-	    }
-	}
-	return R_NilValue;
+        if (MAYBE_REFERENCED(VECTOR_ELT(x, i))) {
+            SET_VECTOR_ELT(x, i, duplicate(VECTOR_ELT(x,i)));
+        }
+    }
+    return R_NilValue;
 }
 
 SEXP dim(SEXP x)
@@ -108,18 +108,18 @@ SEXP dim(SEXP x)
     // fast implementation of dim.data.table
 
     if (TYPEOF(x) != VECSXP) {
-	error("dim.data.table expects a data.table as input (which is a list), but seems to be of type %s",
-	    type2char(TYPEOF(x)));
+        error("dim.data.table expects a data.table as input (which is a list), but seems to be of type %s",
+              type2char(TYPEOF(x)));
     }
 
     SEXP ans = allocVector(INTSXP, 2);
     if(length(x) == 0) {
-	INTEGER(ans)[0] = 0;
-	INTEGER(ans)[1] = 0;
+        INTEGER(ans)[0] = 0;
+        INTEGER(ans)[1] = 0;
     }
     else {
-	INTEGER(ans)[0] = length(VECTOR_ELT(x, 0));
-	INTEGER(ans)[1] = length(x);
+        INTEGER(ans)[0] = length(VECTOR_ELT(x, 0));
+        INTEGER(ans)[1] = length(x);
     }
 
     return ans;

From 4051aba84d197cd09891a094c020aad7f28157ab Mon Sep 17 00:00:00 2001
From: Matt Dowle <mattjdowle@gmail.com>
Date: Fri, 13 Oct 2017 18:19:05 -0700
Subject: [PATCH 2/4] 4 -> 2 space indent, all C files. Other than
 fwrite,fread,forder which were already.

---
 src/assign.c       | 1733 +++++++++++++++++-----------------
 src/between.c      |  112 +--
 src/bmerge.c       |  903 +++++++++---------
 src/chmatch.c      |  126 +--
 src/dogroups.c     |  880 ++++++++---------
 src/fastmean.c     |  202 ++--
 src/fcast.c        |  302 +++---
 src/fmelt.c        | 1212 ++++++++++++------------
 src/forder.c       | 2238 ++++++++++++++++++++++----------------------
 src/frank.c        |  340 +++----
 src/gsumm.c        | 1812 +++++++++++++++++------------------
 src/ijoin.c        | 1302 +++++++++++++-------------
 src/init.c         |  160 ++--
 src/inrange.c      |   52 +-
 src/openmp-utils.c |   66 +-
 src/quickselect.c  |  144 +--
 src/rbindlist.c    | 1466 ++++++++++++++---------------
 src/reorder.c      |  242 ++---
 src/shift.c        |  418 ++++-----
 src/subset.c       |  518 +++++-----
 src/transpose.c    |  168 ++--
 src/uniqlist.c     |  350 +++----
 src/vecseq.c       |   68 +-
 src/wrappers.c     |  156 +--
 24 files changed, 7487 insertions(+), 7483 deletions(-)

diff --git a/src/assign.c b/src/assign.c
index 4ddba204fb..9f9b8412d4 100644
--- a/src/assign.c
+++ b/src/assign.c
@@ -8,63 +8,64 @@ static R_len_t *savedtl=NULL, nalloc=0, nsaved=0;
 
 static void finalizer(SEXP p)
 {
-    SEXP x;
-    R_len_t n, l, tl;
-    if(!R_ExternalPtrAddr(p)) error("Internal error: finalizer hasn't received an ExternalPtr");
-    p = R_ExternalPtrTag(p);
-    if (!isString(p)) error("Internal error: finalizer's ExternalPtr doesn't see names in tag");
-    l = LENGTH(p);
-    tl = TRUELENGTH(p);
-    if (l<0 || tl<l) error("Internal error: finalizer sees l=%d, tl=%d",l,tl);
-    n = tl-l;
-    if (n==0) {
-        // gc's ReleaseLargeFreeVectors() will have reduced R_LargeVallocSize by the correct amount
-        // already, so nothing to do (but almost never the case).
-        return;
-    }
-    x = PROTECT(allocVector(INTSXP, 50));  // 50 so it's big enough to be on LargeVector heap. See NodeClassSize in memory.c:allocVector
-                                           // INTSXP rather than VECSXP so that GC doesn't inspect contents after LENGTH (thanks to Karl Miller, Jul 2015)
-    SETLENGTH(x,50+n*2*sizeof(void *)/4);  // 1*n for the names, 1*n for the VECSXP itself (both are over allocated).
-    UNPROTECT(1);
+  SEXP x;
+  R_len_t n, l, tl;
+  if(!R_ExternalPtrAddr(p)) error("Internal error: finalizer hasn't received an ExternalPtr");
+  p = R_ExternalPtrTag(p);
+  if (!isString(p)) error("Internal error: finalizer's ExternalPtr doesn't see names in tag");
+  l = LENGTH(p);
+  tl = TRUELENGTH(p);
+  if (l<0 || tl<l) error("Internal error: finalizer sees l=%d, tl=%d",l,tl);
+  n = tl-l;
+  if (n==0) {
+    // gc's ReleaseLargeFreeVectors() will have reduced R_LargeVallocSize by the correct amount
+    // already, so nothing to do (but almost never the case).
     return;
+  }
+  x = PROTECT(allocVector(INTSXP, 50));  // 50 so it's big enough to be on LargeVector heap. See NodeClassSize in memory.c:allocVector
+                                         // INTSXP rather than VECSXP so that GC doesn't inspect contents after LENGTH (thanks to Karl Miller, Jul 2015)
+  SETLENGTH(x,50+n*2*sizeof(void *)/4);  // 1*n for the names, 1*n for the VECSXP itself (both are over allocated).
+  UNPROTECT(1);
+  return;
 }
 
 void setselfref(SEXP x) {
-    SEXP p;
-    // Store pointer to itself so we can detect if the object has been copied. See
-    // ?copy for why copies are not just inefficient but cause a problem for over-allocated data.tables.
-    // Called from C only, not R level, so returns void.
-    setAttrib(x, SelfRefSymbol, p=R_MakeExternalPtr(
-        R_NilValue,                  // for identical() to return TRUE. identical() doesn't look at tag and prot
-        getAttrib(x, R_NamesSymbol), // to detect if names has been replaced and its tl lost, e.g. setattr(DT,"names",...)
-        PROTECT(R_MakeExternalPtr(   // to avoid an infinite loop in object.size(), if prot=x here
-            x,                       // to know if this data.table has been copied by key<-, attr<-, names<-, etc.
-            R_NilValue,              // this tag and prot currently unused
-            R_NilValue
-        ))
-    ));
-    R_RegisterCFinalizerEx(p, finalizer, FALSE);
-    UNPROTECT(1);  // The PROTECT above is needed by --enable-strict-barrier (it seems, iiuc)
-
-/*  *  base::identical doesn't check prot and tag of EXTPTR, just that the ptr itself is the
-       same in both objects. R_NilValue is always equal to R_NilValue.  R_NilValue is a memory
-       location constant within an R session, but can vary from session to session. So, it
-       looks like a pointer to a user looking at attributes(DT), but they might wonder how it
-       works if they realise the selfref of all data.tables all point to the same address (rather
-       than to the table itself which would be reasonable to expect given the attribute's name).
-    *  p=NULL rather than R_NilValue works too, other than we need something other than NULL
-       so we can detect tables loaded from disk (which set p to NULL, see 5.13 of R-exts).
-    *  x is wrapped in another EXTPTR because of object.size (called by tables(), and by users).
-       If the prot (or tag) was x directly it sends object.size into an infinite loop and then
-       "segfault from C stack overflow" (object.size does count tag and prot, unlike identical,
-       but doesn't count what's pointed to).
-    *  Could use weak reference possibly, but the fact that they can get be set to R_NilValue
-       by gc (?) didn't seem appropriate.
-    *  If the .internal.selfref attribute is removed (e.g. by user code), nothing will break, but
-       an extra copy will just be taken on next :=, with warning, with a new selfref.
-    *  object.size will count size of names twice, but that's ok as only small.
-    *  Thanks to Steve L for suggesting ExtPtr for this, rather than the previous REALSXP
-       vector which required data.table to do a show/hide dance in a masked identical.
+  SEXP p;
+  // Store pointer to itself so we can detect if the object has been copied. See
+  // ?copy for why copies are not just inefficient but cause a problem for over-allocated data.tables.
+  // Called from C only, not R level, so returns void.
+  setAttrib(x, SelfRefSymbol, p=R_MakeExternalPtr(
+    R_NilValue,                  // for identical() to return TRUE. identical() doesn't look at tag and prot
+    getAttrib(x, R_NamesSymbol), // to detect if names has been replaced and its tl lost, e.g. setattr(DT,"names",...)
+    PROTECT(R_MakeExternalPtr(   // to avoid an infinite loop in object.size(), if prot=x here
+      x,                         // to know if this data.table has been copied by key<-, attr<-, names<-, etc.
+      R_NilValue,                // this tag and prot currently unused
+      R_NilValue
+    ))
+  ));
+  R_RegisterCFinalizerEx(p, finalizer, FALSE);
+  UNPROTECT(1);  // The PROTECT above is needed by --enable-strict-barrier (it seems, iiuc)
+
+/*
+  *  base::identical doesn't check prot and tag of EXTPTR, just that the ptr itself is the
+     same in both objects. R_NilValue is always equal to R_NilValue.  R_NilValue is a memory
+     location constant within an R session, but can vary from session to session. So, it
+     looks like a pointer to a user looking at attributes(DT), but they might wonder how it
+     works if they realise the selfref of all data.tables all point to the same address (rather
+     than to the table itself which would be reasonable to expect given the attribute's name).
+  *  p=NULL rather than R_NilValue works too, other than we need something other than NULL
+     so we can detect tables loaded from disk (which set p to NULL, see 5.13 of R-exts).
+  *  x is wrapped in another EXTPTR because of object.size (called by tables(), and by users).
+     If the prot (or tag) was x directly it sends object.size into an infinite loop and then
+     "segfault from C stack overflow" (object.size does count tag and prot, unlike identical,
+     but doesn't count what's pointed to).
+  *  Could use weak reference possibly, but the fact that they can get be set to R_NilValue
+     by gc (?) didn't seem appropriate.
+  *  If the .internal.selfref attribute is removed (e.g. by user code), nothing will break, but
+     an extra copy will just be taken on next :=, with warning, with a new selfref.
+  *  object.size will count size of names twice, but that's ok as only small.
+  *  Thanks to Steve L for suggesting ExtPtr for this, rather than the previous REALSXP
+     vector which required data.table to do a show/hide dance in a masked identical.
 */
 }
 
@@ -90,8 +91,8 @@ closest I got to getting it to pass all tests :
   UNPROTECT(2);
 
 Then in finalizer:
-    SETLENGTH(names, tl)
-    SETLENGTH(dt, tl)
+  SETLENGTH(names, tl)
+  SETLENGTH(dt, tl)
 
 and that finalizer indeed now happens before the GC releases memory (thanks to the env wrapper).
 
@@ -102,173 +103,173 @@ for GC to release TRUELENGTH not LENGTH.  Would really tidy this up.
 */
 
 static int _selfrefok(SEXP x, Rboolean checkNames, Rboolean verbose) {
-    SEXP v, p, tag, prot, names;
-    v = getAttrib(x, SelfRefSymbol);
-    if (v==R_NilValue || TYPEOF(v)!=EXTPTRSXP) {
-        // .internal.selfref missing is expected and normal for i) a pre v1.7.8 data.table loaded
-        //  from disk, and ii) every time a new data.table is over-allocated for the first time.
-        //  Not being an extptr is for when users contruct a data.table via structure() using dput, post
-        //  a question, and find the extptr doesn't parse so put quotes around it (for example).
-        //  In both cases the selfref is not ok.
-        return 0;
-    }
-    p = R_ExternalPtrAddr(v);
-    if (p==NULL) {
-        if (verbose) Rprintf(".internal.selfref ptr is NULL. This is expected and normal for a data.table loaded from disk. If not, please report to datatable-help.\n");
-        return -1;
-    }
-    if (!isNull(p)) error("Internal error: .internal.selfref ptr is not NULL or R_NilValue");
-    tag = R_ExternalPtrTag(v);
-    if (!(isNull(tag) || isString(tag))) error("Internal error: .internal.selfref tag isn't NULL or a character vector");
-    names = getAttrib(x, R_NamesSymbol);
-    if (names != tag && isString(names))
-        SET_TRUELENGTH(names, LENGTH(names));
-        // R copied this vector not data.table; it's not actually over-allocated. It looks over-allocated
-        // because R copies the original vector's tl over despite allocating length.
-    prot = R_ExternalPtrProtected(v);
-    if (TYPEOF(prot) != EXTPTRSXP)   // Very rare. Was error(".internal.selfref prot is not itself an extptr").
-        return 0;                    // See http://stackoverflow.com/questions/15342227/getting-a-random-internal-selfref-error-in-data-table-for-r
-    if (x != R_ExternalPtrAddr(prot))
-        SET_TRUELENGTH(x, LENGTH(x));  // R copied this vector not data.table, it's not actually over-allocated
-    return checkNames ? names==tag : x==R_ExternalPtrAddr(prot);
+  SEXP v, p, tag, prot, names;
+  v = getAttrib(x, SelfRefSymbol);
+  if (v==R_NilValue || TYPEOF(v)!=EXTPTRSXP) {
+    // .internal.selfref missing is expected and normal for i) a pre v1.7.8 data.table loaded
+    //  from disk, and ii) every time a new data.table is over-allocated for the first time.
+    //  Not being an extptr is for when users contruct a data.table via structure() using dput, post
+    //  a question, and find the extptr doesn't parse so put quotes around it (for example).
+    //  In both cases the selfref is not ok.
+    return 0;
+  }
+  p = R_ExternalPtrAddr(v);
+  if (p==NULL) {
+    if (verbose) Rprintf(".internal.selfref ptr is NULL. This is expected and normal for a data.table loaded from disk. If not, please report to datatable-help.\n");
+    return -1;
+  }
+  if (!isNull(p)) error("Internal error: .internal.selfref ptr is not NULL or R_NilValue");
+  tag = R_ExternalPtrTag(v);
+  if (!(isNull(tag) || isString(tag))) error("Internal error: .internal.selfref tag isn't NULL or a character vector");
+  names = getAttrib(x, R_NamesSymbol);
+  if (names != tag && isString(names))
+    SET_TRUELENGTH(names, LENGTH(names));
+    // R copied this vector not data.table; it's not actually over-allocated. It looks over-allocated
+    // because R copies the original vector's tl over despite allocating length.
+  prot = R_ExternalPtrProtected(v);
+  if (TYPEOF(prot) != EXTPTRSXP)   // Very rare. Was error(".internal.selfref prot is not itself an extptr").
+    return 0;                    // See http://stackoverflow.com/questions/15342227/getting-a-random-internal-selfref-error-in-data-table-for-r
+  if (x != R_ExternalPtrAddr(prot))
+    SET_TRUELENGTH(x, LENGTH(x));  // R copied this vector not data.table, it's not actually over-allocated
+  return checkNames ? names==tag : x==R_ExternalPtrAddr(prot);
 }
 
 static Rboolean selfrefok(SEXP x, Rboolean verbose) {   // for readability
-    return(_selfrefok(x, FALSE, verbose)==1);
+  return(_selfrefok(x, FALSE, verbose)==1);
 }
 static Rboolean selfrefnamesok(SEXP x, Rboolean verbose) {
-    return(_selfrefok(x, TRUE, verbose)==1);
+  return(_selfrefok(x, TRUE, verbose)==1);
 }
 
 static SEXP shallow(SEXP dt, SEXP cols, R_len_t n)
 {
-    // NEW: cols argument to specify the columns to shallow copy on. If NULL, all columns.
-    // called from alloccol where n is checked carefully, or from shallow() at R level
-    // where n is set to truelength (i.e. a shallow copy only with no size change)
-    SEXP newdt, names, newnames;
-    R_len_t i,l;
-    int protecti=0;
-    PROTECT(newdt = allocVector(VECSXP, n));   // to do, use growVector here?
-    protecti++;
-    //copyMostAttrib(dt, newdt);   // including class
-    DUPLICATE_ATTRIB(newdt, dt);
-    // TO DO: keepattr() would be faster, but can't because shallow isn't merely a shallow copy. It
-    //        also increases truelength. Perhaps make that distinction, then, and split out, but marked
-    //        so that the next change knows to duplicate.
-    //        Does copyMostAttrib duplicate each attrib or does it point? It seems to point, hence DUPLICATE_ATTRIB
-    //        for now otherwise example(merge.data.table) fails (since attr(d4,"sorted") gets written by setnames).
-    names = getAttrib(dt, R_NamesSymbol);
-    PROTECT(newnames = allocVector(STRSXP, n));
-    protecti++;
-    if (isNull(cols)) {
-        l = LENGTH(dt);
-        for (i=0; i<l; i++) SET_VECTOR_ELT(newdt, i, VECTOR_ELT(dt,i));
-        if (length(names)) {
-            if (length(names) < l) error("Internal error: length(names)>0 but <length(dt)");
-            for (i=0; i<l; i++) SET_STRING_ELT(newnames, i, STRING_ELT(names,i));
-        }
-        // else an unnamed data.table is valid e.g. unname(DT) done by ggplot2, and .SD may have its names cleared in dogroups, but shallow will always create names for data.table(NULL) which has 100 slots all empty so you can add to an empty data.table by reference ok.
-    } else {
-        l = length(cols);
-        for (i=0; i<l; i++) SET_VECTOR_ELT(newdt, i, VECTOR_ELT(dt,INTEGER(cols)[i]-1));
-        if (length(names)) {
-            // no need to check length(names) < l here. R-level checks if all value
-            // in 'cols' are valid - in the range of 1:length(names(x))
-            for (i=0; i<l; i++) SET_STRING_ELT( newnames, i, STRING_ELT(names,INTEGER(cols)[i]-1) );
-        }
+  // NEW: cols argument to specify the columns to shallow copy on. If NULL, all columns.
+  // called from alloccol where n is checked carefully, or from shallow() at R level
+  // where n is set to truelength (i.e. a shallow copy only with no size change)
+  SEXP newdt, names, newnames;
+  R_len_t i,l;
+  int protecti=0;
+  PROTECT(newdt = allocVector(VECSXP, n));   // to do, use growVector here?
+  protecti++;
+  //copyMostAttrib(dt, newdt);   // including class
+  DUPLICATE_ATTRIB(newdt, dt);
+  // TO DO: keepattr() would be faster, but can't because shallow isn't merely a shallow copy. It
+  //        also increases truelength. Perhaps make that distinction, then, and split out, but marked
+  //        so that the next change knows to duplicate.
+  //        Does copyMostAttrib duplicate each attrib or does it point? It seems to point, hence DUPLICATE_ATTRIB
+  //        for now otherwise example(merge.data.table) fails (since attr(d4,"sorted") gets written by setnames).
+  names = getAttrib(dt, R_NamesSymbol);
+  PROTECT(newnames = allocVector(STRSXP, n));
+  protecti++;
+  if (isNull(cols)) {
+    l = LENGTH(dt);
+    for (i=0; i<l; i++) SET_VECTOR_ELT(newdt, i, VECTOR_ELT(dt,i));
+    if (length(names)) {
+      if (length(names) < l) error("Internal error: length(names)>0 but <length(dt)");
+      for (i=0; i<l; i++) SET_STRING_ELT(newnames, i, STRING_ELT(names,i));
+    }
+    // else an unnamed data.table is valid e.g. unname(DT) done by ggplot2, and .SD may have its names cleared in dogroups, but shallow will always create names for data.table(NULL) which has 100 slots all empty so you can add to an empty data.table by reference ok.
+  } else {
+    l = length(cols);
+    for (i=0; i<l; i++) SET_VECTOR_ELT(newdt, i, VECTOR_ELT(dt,INTEGER(cols)[i]-1));
+    if (length(names)) {
+      // no need to check length(names) < l here. R-level checks if all value
+      // in 'cols' are valid - in the range of 1:length(names(x))
+      for (i=0; i<l; i++) SET_STRING_ELT( newnames, i, STRING_ELT(names,INTEGER(cols)[i]-1) );
     }
-    setAttrib(newdt, R_NamesSymbol, newnames);
-    // setAttrib appears to change length and truelength, so need to do that first _then_ SET next,
-    // otherwise (if the SET were were first) the 100 tl is assigned to length.
-    SETLENGTH(newnames,l);
-    SET_TRUELENGTH(newnames,n);
-    SETLENGTH(newdt,l);
-    SET_TRUELENGTH(newdt,n);
-    setselfref(newdt);
-    UNPROTECT(protecti);
-    return(newdt);
+  }
+  setAttrib(newdt, R_NamesSymbol, newnames);
+  // setAttrib appears to change length and truelength, so need to do that first _then_ SET next,
+  // otherwise (if the SET were were first) the 100 tl is assigned to length.
+  SETLENGTH(newnames,l);
+  SET_TRUELENGTH(newnames,n);
+  SETLENGTH(newdt,l);
+  SET_TRUELENGTH(newdt,n);
+  setselfref(newdt);
+  UNPROTECT(protecti);
+  return(newdt);
 }
 
 SEXP alloccol(SEXP dt, R_len_t n, Rboolean verbose)
 {
-    SEXP names, class;
-    R_len_t l, tl;
-    if (isNull(dt)) error("alloccol has been passed a NULL dt");
-    if (TYPEOF(dt) != VECSXP) error("dt passed to alloccol isn't type VECSXP");
-    class = getAttrib(dt, R_ClassSymbol);
-    if (isNull(class)) error("dt passed to alloccol has no class attribute. Please report result of traceback() to datatable-help.");
-    l = LENGTH(dt);
-    names = getAttrib(dt,R_NamesSymbol);
-    // names may be NULL when null.data.table() passes list() to alloccol for example.
-    // So, careful to use length() on names, not LENGTH().
-    if (length(names)!=l) error("Internal error: length of names (%d) is not length of dt (%d)",length(names),l);
-    if (!selfrefok(dt,verbose))
-        return shallow(dt,R_NilValue,(n>l) ? n : l);  // e.g. test 848 and 851 in R > 3.0.2
-        // added (n>l) ? ... for #970, see test 1481.
-    // TO DO:  test realloc names if selfrefnamesok (users can setattr(x,"name") themselves for example.
-    // if (TRUELENGTH(getAttrib(dt,R_NamesSymbol))!=tl)
-    //    error("Internal error: tl of dt passes checks, but tl of names (%d) != tl of dt (%d)", tl, TRUELENGTH(getAttrib(dt,R_NamesSymbol)));
-
-    tl = TRUELENGTH(dt);
-    if (tl<0) error("Internal error, tl of class is marked but tl<0.");  // R <= 2.13.2 and we didn't catch uninitialized tl somehow
-    if (tl>0 && tl<l) error("Internal error, please report (including result of sessionInfo()) to datatable-help: tl (%d) < l (%d) but tl of class is marked.", tl, l);
-    if (tl>l+10000) warning("tl (%d) is greater than 10,000 items over-allocated (l = %d). If you didn't set the datatable.alloccol option to be very large, please report this to datatable-help including the result of sessionInfo().",tl,l);
-    if (n>tl) return(shallow(dt,R_NilValue,n)); // usual case (increasing alloc)
-    if (n<tl && verbose) Rprintf("Attempt to reduce allocation from %d to %d ignored. Can only increase allocation via shallow copy. Please do not use DT[...]<- or DT$someCol<-. Use := inside DT[...] instead.",tl,n);
-              // otherwise the finalizer can't clear up the Large Vector heap
-    return(dt);
+  SEXP names, class;
+  R_len_t l, tl;
+  if (isNull(dt)) error("alloccol has been passed a NULL dt");
+  if (TYPEOF(dt) != VECSXP) error("dt passed to alloccol isn't type VECSXP");
+  class = getAttrib(dt, R_ClassSymbol);
+  if (isNull(class)) error("dt passed to alloccol has no class attribute. Please report result of traceback() to datatable-help.");
+  l = LENGTH(dt);
+  names = getAttrib(dt,R_NamesSymbol);
+  // names may be NULL when null.data.table() passes list() to alloccol for example.
+  // So, careful to use length() on names, not LENGTH().
+  if (length(names)!=l) error("Internal error: length of names (%d) is not length of dt (%d)",length(names),l);
+  if (!selfrefok(dt,verbose))
+    return shallow(dt,R_NilValue,(n>l) ? n : l);  // e.g. test 848 and 851 in R > 3.0.2
+    // added (n>l) ? ... for #970, see test 1481.
+  // TO DO:  test realloc names if selfrefnamesok (users can setattr(x,"name") themselves for example.
+  // if (TRUELENGTH(getAttrib(dt,R_NamesSymbol))!=tl)
+  //    error("Internal error: tl of dt passes checks, but tl of names (%d) != tl of dt (%d)", tl, TRUELENGTH(getAttrib(dt,R_NamesSymbol)));
+
+  tl = TRUELENGTH(dt);
+  if (tl<0) error("Internal error, tl of class is marked but tl<0.");  // R <= 2.13.2 and we didn't catch uninitialized tl somehow
+  if (tl>0 && tl<l) error("Internal error, please report (including result of sessionInfo()) to datatable-help: tl (%d) < l (%d) but tl of class is marked.", tl, l);
+  if (tl>l+10000) warning("tl (%d) is greater than 10,000 items over-allocated (l = %d). If you didn't set the datatable.alloccol option to be very large, please report this to datatable-help including the result of sessionInfo().",tl,l);
+  if (n>tl) return(shallow(dt,R_NilValue,n)); // usual case (increasing alloc)
+  if (n<tl && verbose) Rprintf("Attempt to reduce allocation from %d to %d ignored. Can only increase allocation via shallow copy. Please do not use DT[...]<- or DT$someCol<-. Use := inside DT[...] instead.",tl,n);
+        // otherwise the finalizer can't clear up the Large Vector heap
+  return(dt);
 }
 
 SEXP alloccolwrapper(SEXP dt, SEXP newncol, SEXP verbose) {
-    if (!isInteger(newncol) || length(newncol)!=1) error("n must be integer length 1. Has getOption('datatable.alloccol') somehow become unset?");
-    if (!isLogical(verbose) || length(verbose)!=1) error("verbose must be TRUE or FALSE");
+  if (!isInteger(newncol) || length(newncol)!=1) error("n must be integer length 1. Has getOption('datatable.alloccol') somehow become unset?");
+  if (!isLogical(verbose) || length(verbose)!=1) error("verbose must be TRUE or FALSE");
 
-    SEXP ans = PROTECT(alloccol(dt, INTEGER(newncol)[0], LOGICAL(verbose)[0]));
+  SEXP ans = PROTECT(alloccol(dt, INTEGER(newncol)[0], LOGICAL(verbose)[0]));
 
-    for(R_len_t i = 0; i < LENGTH(ans); i++) {
-        // clear the same excluded by copyMostAttrib(). Primarily for data.table and as.data.table, but added here centrally (see #4890).
+  for(R_len_t i = 0; i < LENGTH(ans); i++) {
+    // clear the same excluded by copyMostAttrib(). Primarily for data.table and as.data.table, but added here centrally (see #4890).
 
-        setAttrib(VECTOR_ELT(ans, i), R_NamesSymbol, R_NilValue);
-        setAttrib(VECTOR_ELT(ans, i), R_DimSymbol, R_NilValue);
-        setAttrib(VECTOR_ELT(ans, i), R_DimNamesSymbol, R_NilValue);
-    }
+    setAttrib(VECTOR_ELT(ans, i), R_NamesSymbol, R_NilValue);
+    setAttrib(VECTOR_ELT(ans, i), R_DimSymbol, R_NilValue);
+    setAttrib(VECTOR_ELT(ans, i), R_DimNamesSymbol, R_NilValue);
+  }
 
-    UNPROTECT(1);
-    return ans;
+  UNPROTECT(1);
+  return ans;
 }
 
 SEXP shallowwrapper(SEXP dt, SEXP cols) {
-    // selfref will be FALSE on manually created data.table, e.g., via dput() or structure()
-    if (!selfrefok(dt, FALSE)) {
-        int n = isNull(cols) ? length(dt) : length(cols);
-        return(shallow(dt, cols, n));
-    } else return(shallow(dt, cols, TRUELENGTH(dt)));
+  // selfref will be FALSE on manually created data.table, e.g., via dput() or structure()
+  if (!selfrefok(dt, FALSE)) {
+    int n = isNull(cols) ? length(dt) : length(cols);
+    return(shallow(dt, cols, n));
+  } else return(shallow(dt, cols, TRUELENGTH(dt)));
 }
 
 // is.data.table() C-function, extracted from assign.c
 // Check if "data.table" class exists somewhere in class (#5115)
 Rboolean isDatatable(SEXP x) {
-    SEXP class = getAttrib(x, R_ClassSymbol);
-    for (int i=0; i<length(class); i++) {
-        if (strcmp(CHAR(STRING_ELT(class, i)), "data.table") == 0) return(TRUE);
-    }
-    return (FALSE);
+  SEXP class = getAttrib(x, R_ClassSymbol);
+  for (int i=0; i<length(class); i++) {
+    if (strcmp(CHAR(STRING_ELT(class, i)), "data.table") == 0) return(TRUE);
+  }
+  return (FALSE);
 }
 
 SEXP truelength(SEXP x) {
-    SEXP ans;
-    PROTECT(ans = allocVector(INTSXP, 1));
-    if (!isNull(x)) {
-       INTEGER(ans)[0] = TRUELENGTH(x);
-    } else {
-       INTEGER(ans)[0] = 0;
-    }
-    UNPROTECT(1);
-    return(ans);
+  SEXP ans;
+  PROTECT(ans = allocVector(INTSXP, 1));
+  if (!isNull(x)) {
+     INTEGER(ans)[0] = TRUELENGTH(x);
+  } else {
+     INTEGER(ans)[0] = 0;
+  }
+  UNPROTECT(1);
+  return(ans);
 }
 
 SEXP selfrefokwrapper(SEXP x, SEXP verbose) {
-    return ScalarInteger(_selfrefok(x,FALSE,LOGICAL(verbose)[0]));
+  return ScalarInteger(_selfrefok(x,FALSE,LOGICAL(verbose)[0]));
 }
 
 
@@ -276,516 +277,516 @@ void memrecycle(SEXP target, SEXP where, int r, int len, SEXP source);
 
 SEXP assign(SEXP dt, SEXP rows, SEXP cols, SEXP newcolnames, SEXP values, SEXP verb)
 {
-    // For internal use only by := in [.data.table, and set()
-    // newcolnames : add these columns (if any)
-    // cols : column names or numbers corresponding to the values to set
-    // rows : row numbers to assign
-    R_len_t i, j, nrow, targetlen, vlen, r, oldncol, oldtncol, coln, protecti=0, newcolnum, indexLength;
-    SEXP targetcol, RHS, names, nullint, thisvalue, thisv, targetlevels, newcol, s, colnam, class, tmp, colorder, key, index, a, assignedNames, indexNames;
-    SEXP bindingIsLocked = getAttrib(dt, install(".data.table.locked"));
-    Rboolean verbose = LOGICAL(verb)[0], anytodelete=FALSE, isDataTable=FALSE;
-    char *s1, *s2, *s3, *s4, *s5;
-    const char *c1, *tc1, *tc2;
-    int *buf, k=0, newKeyLength, indexNo;
-    size_t size; // must be size_t otherwise overflow later in memcpy
-    if (isNull(dt)) error("assign has been passed a NULL dt");
-    if (TYPEOF(dt) != VECSXP) error("dt passed to assign isn't type VECSXP");
-    if (length(bindingIsLocked) && LOGICAL(bindingIsLocked)[0])
-        error(".SD is locked. Updating .SD by reference using := or set are reserved for future use. Use := in j directly. Or use copy(.SD) as a (slow) last resort, until shallow() is exported.");
-
-    class = getAttrib(dt, R_ClassSymbol);
-    if (isNull(class)) error("Input passed to assign has no class attribute. Must be a data.table or data.frame.");
-    // Check if there is a class "data.table" somewhere (#5115).
-    // We allow set() on data.frame too; e.g. package Causata uses set() on a data.frame in tests/testTransformationReplay.R
-    // := is only allowed on a data.table. However, the ":=" = stop(...) message in data.table.R will have already
-    // detected use on a data.frame before getting to this point.
-    for (i=0; i<length(class); i++) {   // There doesn't seem to be an R API interface to inherits(), but manually here isn't too bad.
-        if (strcmp(CHAR(STRING_ELT(class, i)), "data.table") == 0) break;
+  // For internal use only by := in [.data.table, and set()
+  // newcolnames : add these columns (if any)
+  // cols : column names or numbers corresponding to the values to set
+  // rows : row numbers to assign
+  R_len_t i, j, nrow, targetlen, vlen, r, oldncol, oldtncol, coln, protecti=0, newcolnum, indexLength;
+  SEXP targetcol, RHS, names, nullint, thisvalue, thisv, targetlevels, newcol, s, colnam, class, tmp, colorder, key, index, a, assignedNames, indexNames;
+  SEXP bindingIsLocked = getAttrib(dt, install(".data.table.locked"));
+  Rboolean verbose = LOGICAL(verb)[0], anytodelete=FALSE, isDataTable=FALSE;
+  char *s1, *s2, *s3, *s4, *s5;
+  const char *c1, *tc1, *tc2;
+  int *buf, k=0, newKeyLength, indexNo;
+  size_t size; // must be size_t otherwise overflow later in memcpy
+  if (isNull(dt)) error("assign has been passed a NULL dt");
+  if (TYPEOF(dt) != VECSXP) error("dt passed to assign isn't type VECSXP");
+  if (length(bindingIsLocked) && LOGICAL(bindingIsLocked)[0])
+    error(".SD is locked. Updating .SD by reference using := or set are reserved for future use. Use := in j directly. Or use copy(.SD) as a (slow) last resort, until shallow() is exported.");
+
+  class = getAttrib(dt, R_ClassSymbol);
+  if (isNull(class)) error("Input passed to assign has no class attribute. Must be a data.table or data.frame.");
+  // Check if there is a class "data.table" somewhere (#5115).
+  // We allow set() on data.frame too; e.g. package Causata uses set() on a data.frame in tests/testTransformationReplay.R
+  // := is only allowed on a data.table. However, the ":=" = stop(...) message in data.table.R will have already
+  // detected use on a data.frame before getting to this point.
+  for (i=0; i<length(class); i++) {   // There doesn't seem to be an R API interface to inherits(), but manually here isn't too bad.
+    if (strcmp(CHAR(STRING_ELT(class, i)), "data.table") == 0) break;
+  }
+  if (i<length(class))
+    isDataTable = TRUE;
+  else {
+    for (i=0; i<length(class); i++) {
+      if (strcmp(CHAR(STRING_ELT(class, i)), "data.frame") == 0) break;
     }
-    if (i<length(class))
-        isDataTable = TRUE;
-    else {
-        for (i=0; i<length(class); i++) {
-            if (strcmp(CHAR(STRING_ELT(class, i)), "data.frame") == 0) break;
-        }
-        if (i == length(class)) error("Input is not a data.table, data.frame or an object that inherits from either.");
-        isDataTable = FALSE;   // meaning data.frame from now on. Can use set() on existing columns but not add new ones because DF aren't over-allocated.
+    if (i == length(class)) error("Input is not a data.table, data.frame or an object that inherits from either.");
+    isDataTable = FALSE;   // meaning data.frame from now on. Can use set() on existing columns but not add new ones because DF aren't over-allocated.
+  }
+  oldncol = LENGTH(dt);
+  names = getAttrib(dt,R_NamesSymbol);
+  if (isNull(names)) error("dt passed to assign has no names");
+  if (length(names)!=oldncol)
+    error("Internal error in assign: length of names (%d) is not length of dt (%d)",length(names),oldncol);
+  if (oldncol<1) error("Cannot use := to add columns to a null data.table (no columns), currently. You can use := to add (empty) columns to a 0-row data.table (1 or more empty columns), though.");
+  nrow = length(VECTOR_ELT(dt,0));
+  if (isNull(rows)) {
+    targetlen = nrow;
+    if (verbose) Rprintf("Assigning to all %d rows\n", nrow);
+    // fast way to assign to whole column, without creating 1:nrow(x) vector up in R, or here in C
+  } else {
+    if (isReal(rows)) {
+      rows = PROTECT(rows = coerceVector(rows, INTSXP));
+      protecti++;
+      warning("Coerced i from numeric to integer. Please pass integer for efficiency; e.g., 2L rather than 2");
     }
-    oldncol = LENGTH(dt);
-    names = getAttrib(dt,R_NamesSymbol);
-    if (isNull(names)) error("dt passed to assign has no names");
-    if (length(names)!=oldncol)
-        error("Internal error in assign: length of names (%d) is not length of dt (%d)",length(names),oldncol);
-    if (oldncol<1) error("Cannot use := to add columns to a null data.table (no columns), currently. You can use := to add (empty) columns to a 0-row data.table (1 or more empty columns), though.");
-    nrow = length(VECTOR_ELT(dt,0));
-    if (isNull(rows)) {
-        targetlen = nrow;
-        if (verbose) Rprintf("Assigning to all %d rows\n", nrow);
-        // fast way to assign to whole column, without creating 1:nrow(x) vector up in R, or here in C
-    } else {
-        if (isReal(rows)) {
-            rows = PROTECT(rows = coerceVector(rows, INTSXP));
-            protecti++;
-            warning("Coerced i from numeric to integer. Please pass integer for efficiency; e.g., 2L rather than 2");
-        }
-        if (!isInteger(rows))
-            error("i is type '%s'. Must be integer, or numeric is coerced with warning. If i is a logical subset, simply wrap with which(), and take the which() outside the loop if possible for efficiency.", type2char(TYPEOF(rows)));
-        targetlen = length(rows);
-        int numToDo = 0;
-        for (i=0; i<targetlen; i++) {
-            if ((INTEGER(rows)[i]<0 && INTEGER(rows)[i]!=NA_INTEGER) || INTEGER(rows)[i]>nrow)
-                error("i[%d] is %d which is out of range [1,nrow=%d].",i+1,INTEGER(rows)[i],nrow);
-            if (INTEGER(rows)[i]>=1) numToDo++;
-        }
-        if (verbose) Rprintf("Assigning to %d row subset of %d rows\n", numToDo, nrow);
-        // TODO: include in message if any rows are assigned several times (e.g. by=.EACHI with dups in i)
-        if (numToDo==0) {
-            if (!length(newcolnames)) return(dt); // all items of rows either 0 or NA. !length(newcolnames) for #759
-            if (verbose) Rprintf("Added %d new column%s initialized with all-NA\n",
-                                 length(newcolnames), (length(newcolnames)>1)?"s":"");
-        }
+    if (!isInteger(rows))
+      error("i is type '%s'. Must be integer, or numeric is coerced with warning. If i is a logical subset, simply wrap with which(), and take the which() outside the loop if possible for efficiency.", type2char(TYPEOF(rows)));
+    targetlen = length(rows);
+    int numToDo = 0;
+    for (i=0; i<targetlen; i++) {
+      if ((INTEGER(rows)[i]<0 && INTEGER(rows)[i]!=NA_INTEGER) || INTEGER(rows)[i]>nrow)
+        error("i[%d] is %d which is out of range [1,nrow=%d].",i+1,INTEGER(rows)[i],nrow);
+      if (INTEGER(rows)[i]>=1) numToDo++;
+    }
+    if (verbose) Rprintf("Assigning to %d row subset of %d rows\n", numToDo, nrow);
+    // TODO: include in message if any rows are assigned several times (e.g. by=.EACHI with dups in i)
+    if (numToDo==0) {
+      if (!length(newcolnames)) return(dt); // all items of rows either 0 or NA. !length(newcolnames) for #759
+      if (verbose) Rprintf("Added %d new column%s initialized with all-NA\n",
+                           length(newcolnames), (length(newcolnames)>1)?"s":"");
     }
+  }
+  if (!length(cols)) {
+    warning("length(LHS)==0; no columns to delete or assign RHS to.");
+    return(dt);
+  }
+  // FR #2077 - set able to add new cols by reference
+  if (isString(cols)) {
+    PROTECT(tmp = chmatch(cols, names, 0, FALSE));
+    protecti++;
+    buf = (int *) R_alloc(length(cols), sizeof(int));
+    for (i=0; i<length(cols); i++) {
+      if (INTEGER(tmp)[i] == 0) buf[k++] = i;
+    }
+    if (k>0) {
+      if (!isDataTable) error("set() on a data.frame is for changing existing columns, not adding new ones. Please use a data.table for that. data.table's are over-allocated and don't shallow copy.");
+      PROTECT(newcolnames = allocVector(STRSXP, k));
+      protecti++;
+      for (i=0; i<k; i++) {
+        SET_STRING_ELT(newcolnames, i, STRING_ELT(cols, buf[i]));
+        INTEGER(tmp)[buf[i]] = oldncol+i+1;
+      }
+    }
+    cols = tmp;
+  } else {
+    if (isReal(cols)) {
+      cols = PROTECT(cols = coerceVector(cols, INTSXP));
+      protecti++;
+      warning("Coerced j from numeric to integer. Please pass integer for efficiency; e.g., 2L rather than 2");
+    }
+    if (!isInteger(cols))
+      error("j is type '%s'. Must be integer, character, or numeric is coerced with warning.", type2char(TYPEOF(cols)));
+  }
+  if (any_duplicated(cols,FALSE)) error("Can't assign to the same column twice in the same query (duplicates detected).");
+  if (!isNull(newcolnames) && !isString(newcolnames)) error("newcolnames is supplied but isn't a character vector");
+  if (isNull(values)) {
     if (!length(cols)) {
-        warning("length(LHS)==0; no columns to delete or assign RHS to.");
-        return(dt);
+      warning("RHS is NULL, meaning delete columns(s). But, no columns in LHS to delete.");
+      return(dt);
     }
-    // FR #2077 - set able to add new cols by reference
-    if (isString(cols)) {
-        PROTECT(tmp = chmatch(cols, names, 0, FALSE));
-        protecti++;
-        buf = (int *) R_alloc(length(cols), sizeof(int));
-        for (i=0; i<length(cols); i++) {
-            if (INTEGER(tmp)[i] == 0) buf[k++] = i;
-        }
-        if (k>0) {
-            if (!isDataTable) error("set() on a data.frame is for changing existing columns, not adding new ones. Please use a data.table for that. data.table's are over-allocated and don't shallow copy.");
-            PROTECT(newcolnames = allocVector(STRSXP, k));
-            protecti++;
-            for (i=0; i<k; i++) {
-                SET_STRING_ELT(newcolnames, i, STRING_ELT(cols, buf[i]));
-                INTEGER(tmp)[buf[i]] = oldncol+i+1;
-            }
-        }
-        cols = tmp;
-    } else {
-        if (isReal(cols)) {
-            cols = PROTECT(cols = coerceVector(cols, INTSXP));
-            protecti++;
-            warning("Coerced j from numeric to integer. Please pass integer for efficiency; e.g., 2L rather than 2");
-        }
-        if (!isInteger(cols))
-            error("j is type '%s'. Must be integer, character, or numeric is coerced with warning.", type2char(TYPEOF(cols)));
+  } else {
+    if (TYPEOF(values)==VECSXP) {
+      if (length(cols)>1) {
+        if (length(values)==0) error("Supplied %d columns to be assigned an empty list (which may be an empty data.table or data.frame since they are lists too). To delete multiple columns use NULL instead. To add multiple empty list columns, use list(list()).", length(cols));
+        if (length(values)>length(cols))
+          warning("Supplied %d columns to be assigned a list (length %d) of values (%d unused)", length(cols), length(values), length(values)-length(cols));
+        else if (length(cols)%length(values) != 0)
+          warning("Supplied %d columns to be assigned a list (length %d) of values (recycled leaving remainder of %d items).",length(cols),length(values),length(cols)%length(values));
+      } // else it's a list() column being assigned to one column
     }
-    if (any_duplicated(cols,FALSE)) error("Can't assign to the same column twice in the same query (duplicates detected).");
-    if (!isNull(newcolnames) && !isString(newcolnames)) error("newcolnames is supplied but isn't a character vector");
-    if (isNull(values)) {
-        if (!length(cols)) {
-            warning("RHS is NULL, meaning delete columns(s). But, no columns in LHS to delete.");
-            return(dt);
-        }
-    } else {
-        if (TYPEOF(values)==VECSXP) {
-            if (length(cols)>1) {
-                if (length(values)==0) error("Supplied %d columns to be assigned an empty list (which may be an empty data.table or data.frame since they are lists too). To delete multiple columns use NULL instead. To add multiple empty list columns, use list(list()).", length(cols));
-                if (length(values)>length(cols))
-                    warning("Supplied %d columns to be assigned a list (length %d) of values (%d unused)", length(cols), length(values), length(values)-length(cols));
-                else if (length(cols)%length(values) != 0)
-                    warning("Supplied %d columns to be assigned a list (length %d) of values (recycled leaving remainder of %d items).",length(cols),length(values),length(cols)%length(values));
-            } // else it's a list() column being assigned to one column
-        }
+  }
+  // Check all inputs :
+  for (i=0; i<length(cols); i++) {
+    coln = INTEGER(cols)[i];
+    if (coln<1 || coln>oldncol+length(newcolnames)) {
+      if (!isDataTable) error("Item %d of column numbers in j is %d which is outside range [1,ncol=%d]. set() on a data.frame is for changing existing columns, not adding new ones. Please use a data.table for that.", i+1, coln, oldncol);
+      else error("Item %d of column numbers in j is %d which is outside range [1,ncol=%d]. Use column names instead in j to add new columns.", i+1, coln, oldncol);
     }
-    // Check all inputs :
-    for (i=0; i<length(cols); i++) {
-        coln = INTEGER(cols)[i];
-        if (coln<1 || coln>oldncol+length(newcolnames)) {
-            if (!isDataTable) error("Item %d of column numbers in j is %d which is outside range [1,ncol=%d]. set() on a data.frame is for changing existing columns, not adding new ones. Please use a data.table for that.", i+1, coln, oldncol);
-            else error("Item %d of column numbers in j is %d which is outside range [1,ncol=%d]. Use column names instead in j to add new columns.", i+1, coln, oldncol);
-        }
-        coln--;
-        if (TYPEOF(values)==VECSXP && (length(cols)>1 || length(values)==1))
-            thisvalue = VECTOR_ELT(values,i%LENGTH(values));
-        else
-            thisvalue = values;   // One vector applied to all columns, often NULL or NA for example
-        vlen = length(thisvalue);
-        if (coln+1 <= oldncol) colnam = STRING_ELT(names,coln);
-        else colnam = STRING_ELT(newcolnames,coln-length(names));
-        if (coln+1 <= oldncol && isNull(thisvalue)) continue;  // delete existing column(s) afterwards, near end of this function
-        if (vlen<1 && nrow>0) {
-            if (coln+1 <= oldncol) {
-                error("RHS of assignment to existing column '%s' is zero length but not NULL. If you intend to delete the column use NULL. Otherwise, the RHS must have length > 0; e.g., NA_integer_. If you are trying to change the column type to be an empty list column then, as with all column type changes, provide a full length RHS vector such as vector('list',nrow(DT)); i.e., 'plonk' in the new column.", CHAR(STRING_ELT(names,coln)));
-            } else if (TYPEOF(thisvalue)!=VECSXP) {  // list() is ok for new columns
-                newcolnum = coln-length(names);
-                if (newcolnum<0 || newcolnum>=length(newcolnames))
-                    error("Internal logical error. length(newcolnames)=%d, length(names)=%d, coln=%d", length(newcolnames), length(names), coln);
-                if (isNull(thisvalue)) {
-                    // fix for #1082
-                    if (!isNull(rows)) error("When deleting columns, i should not be provided");
-                    warning("Adding new column '%s' then assigning NULL (deleting it).",CHAR(STRING_ELT(newcolnames,newcolnum)));
-                    continue;
-                }
-                // RHS of assignment to new column is zero length but we'll use its type to create all-NA column of that type
-            }
-        }
-        if (!(isVectorAtomic(thisvalue) || isNewList(thisvalue)))  // NULL had a continue earlier above
-            error("RHS of assignment is not NULL, not an an atomic vector (see ?is.atomic) and not a list column.");
-        if (isMatrix(thisvalue) && (j=INTEGER(getAttrib(thisvalue, R_DimSymbol))[1]) > 1)  // matrix passes above (considered atomic vector)
-            warning("%d column matrix RHS of := will be treated as one vector", j);
-        if ((coln+1)<=oldncol && isFactor(VECTOR_ELT(dt,coln)) &&
-            !isString(thisvalue) && TYPEOF(thisvalue)!=INTSXP && TYPEOF(thisvalue)!=LGLSXP && !isReal(thisvalue) && !isNewList(thisvalue))  // !=INTSXP includes factor
-            error("Can't assign to column '%s' (type 'factor') a value of type '%s' (not character, factor, integer or numeric)", CHAR(STRING_ELT(names,coln)),type2char(TYPEOF(thisvalue)));
-        if (nrow>0 && targetlen>0) {
-            if (vlen>targetlen)
-                warning("Supplied %d items to be assigned to %d items of column '%s' (%d unused)", vlen, targetlen,CHAR(colnam),vlen-targetlen);
-            else if (vlen>0 && targetlen%vlen != 0)
-                warning("Supplied %d items to be assigned to %d items of column '%s' (recycled leaving remainder of %d items).",vlen,targetlen,CHAR(colnam),targetlen%vlen);
+    coln--;
+    if (TYPEOF(values)==VECSXP && (length(cols)>1 || length(values)==1))
+      thisvalue = VECTOR_ELT(values,i%LENGTH(values));
+    else
+      thisvalue = values;   // One vector applied to all columns, often NULL or NA for example
+    vlen = length(thisvalue);
+    if (coln+1 <= oldncol) colnam = STRING_ELT(names,coln);
+    else colnam = STRING_ELT(newcolnames,coln-length(names));
+    if (coln+1 <= oldncol && isNull(thisvalue)) continue;  // delete existing column(s) afterwards, near end of this function
+    if (vlen<1 && nrow>0) {
+      if (coln+1 <= oldncol) {
+        error("RHS of assignment to existing column '%s' is zero length but not NULL. If you intend to delete the column use NULL. Otherwise, the RHS must have length > 0; e.g., NA_integer_. If you are trying to change the column type to be an empty list column then, as with all column type changes, provide a full length RHS vector such as vector('list',nrow(DT)); i.e., 'plonk' in the new column.", CHAR(STRING_ELT(names,coln)));
+      } else if (TYPEOF(thisvalue)!=VECSXP) {  // list() is ok for new columns
+        newcolnum = coln-length(names);
+        if (newcolnum<0 || newcolnum>=length(newcolnames))
+          error("Internal logical error. length(newcolnames)=%d, length(names)=%d, coln=%d", length(newcolnames), length(names), coln);
+        if (isNull(thisvalue)) {
+          // fix for #1082
+          if (!isNull(rows)) error("When deleting columns, i should not be provided");
+          warning("Adding new column '%s' then assigning NULL (deleting it).",CHAR(STRING_ELT(newcolnames,newcolnum)));
+          continue;
         }
+        // RHS of assignment to new column is zero length but we'll use its type to create all-NA column of that type
+      }
     }
-    // having now checked the inputs, from this point there should be no errors so we can now proceed to
-    // modify DT by reference. Other than if new columns are being added and the allocVec() fails with
-    // out-of-memory. In that case the user will receive hard halt and know to rerun.
-    if (length(newcolnames)) {
-        oldtncol = TRUELENGTH(dt);   // TO DO: oldtncol can be just called tl now, as we won't realloc here any more.
-
-        if (oldtncol<oldncol) error("Internal error, please report (including result of sessionInfo()) to datatable-help: oldtncol (%d) < oldncol (%d) but tl of class is marked.", oldtncol, oldncol);
-        if (oldtncol>oldncol+10000L) warning("truelength (%d) is greater than 10,000 items over-allocated (length = %d). See ?truelength. If you didn't set the datatable.alloccol option very large, please report this to datatable-help including the result of sessionInfo().",oldtncol, oldncol);
-
-        if (oldtncol < oldncol+LENGTH(newcolnames))
-            error("Internal logical error. DT passed to assign has not been allocated enough column slots. l=%d, tl=%d, adding %d", oldncol, oldtncol, LENGTH(newcolnames));
-        if (!selfrefnamesok(dt,verbose))
-            error("It appears that at some earlier point, names of this data.table have been reassigned. Please ensure to use setnames() rather than names<- or colnames<-. Otherwise, please report to datatable-help.");
-            // Can growVector at this point easily enough, but it shouldn't happen in first place so leave it as
-            // strong error message for now.
-        else if (TRUELENGTH(names) != oldtncol)
-            error("selfrefnames is ok but tl names [%d] != tl [%d]", TRUELENGTH(names), oldtncol);
-        SETLENGTH(dt, oldncol+LENGTH(newcolnames));
-        SETLENGTH(names, oldncol+LENGTH(newcolnames));
-        for (i=0; i<LENGTH(newcolnames); i++)
-            SET_STRING_ELT(names,oldncol+i,STRING_ELT(newcolnames,i));
-        // truelengths of both already set by alloccol
+    if (!(isVectorAtomic(thisvalue) || isNewList(thisvalue)))  // NULL had a continue earlier above
+      error("RHS of assignment is not NULL, not an an atomic vector (see ?is.atomic) and not a list column.");
+    if (isMatrix(thisvalue) && (j=INTEGER(getAttrib(thisvalue, R_DimSymbol))[1]) > 1)  // matrix passes above (considered atomic vector)
+      warning("%d column matrix RHS of := will be treated as one vector", j);
+    if ((coln+1)<=oldncol && isFactor(VECTOR_ELT(dt,coln)) &&
+      !isString(thisvalue) && TYPEOF(thisvalue)!=INTSXP && TYPEOF(thisvalue)!=LGLSXP && !isReal(thisvalue) && !isNewList(thisvalue))  // !=INTSXP includes factor
+      error("Can't assign to column '%s' (type 'factor') a value of type '%s' (not character, factor, integer or numeric)", CHAR(STRING_ELT(names,coln)),type2char(TYPEOF(thisvalue)));
+    if (nrow>0 && targetlen>0) {
+      if (vlen>targetlen)
+        warning("Supplied %d items to be assigned to %d items of column '%s' (%d unused)", vlen, targetlen,CHAR(colnam),vlen-targetlen);
+      else if (vlen>0 && targetlen%vlen != 0)
+        warning("Supplied %d items to be assigned to %d items of column '%s' (recycled leaving remainder of %d items).",vlen,targetlen,CHAR(colnam),targetlen%vlen);
     }
-    for (i=0; i<length(cols); i++) {
-        coln = INTEGER(cols)[i]-1;
-        if (TYPEOF(values)==VECSXP && (LENGTH(cols)>1 || LENGTH(values)==1))
-            thisvalue = VECTOR_ELT(values,i%LENGTH(values));
-        else
-            thisvalue = values;   // One vector applied to all columns, often NULL or NA for example
-        if (TYPEOF(thisvalue)==NILSXP) {
-            if (!isNull(rows)) error("When deleting columns, i should not be provided");
-            anytodelete = TRUE;
-            continue;   // delete column(s) afterwards, below this loop
+  }
+  // having now checked the inputs, from this point there should be no errors so we can now proceed to
+  // modify DT by reference. Other than if new columns are being added and the allocVec() fails with
+  // out-of-memory. In that case the user will receive hard halt and know to rerun.
+  if (length(newcolnames)) {
+    oldtncol = TRUELENGTH(dt);   // TO DO: oldtncol can be just called tl now, as we won't realloc here any more.
+
+    if (oldtncol<oldncol) error("Internal error, please report (including result of sessionInfo()) to datatable-help: oldtncol (%d) < oldncol (%d) but tl of class is marked.", oldtncol, oldncol);
+    if (oldtncol>oldncol+10000L) warning("truelength (%d) is greater than 10,000 items over-allocated (length = %d). See ?truelength. If you didn't set the datatable.alloccol option very large, please report this to datatable-help including the result of sessionInfo().",oldtncol, oldncol);
+
+    if (oldtncol < oldncol+LENGTH(newcolnames))
+      error("Internal logical error. DT passed to assign has not been allocated enough column slots. l=%d, tl=%d, adding %d", oldncol, oldtncol, LENGTH(newcolnames));
+    if (!selfrefnamesok(dt,verbose))
+      error("It appears that at some earlier point, names of this data.table have been reassigned. Please ensure to use setnames() rather than names<- or colnames<-. Otherwise, please report to datatable-help.");
+      // Can growVector at this point easily enough, but it shouldn't happen in first place so leave it as
+      // strong error message for now.
+    else if (TRUELENGTH(names) != oldtncol)
+      error("selfrefnames is ok but tl names [%d] != tl [%d]", TRUELENGTH(names), oldtncol);
+    SETLENGTH(dt, oldncol+LENGTH(newcolnames));
+    SETLENGTH(names, oldncol+LENGTH(newcolnames));
+    for (i=0; i<LENGTH(newcolnames); i++)
+      SET_STRING_ELT(names,oldncol+i,STRING_ELT(newcolnames,i));
+    // truelengths of both already set by alloccol
+  }
+  for (i=0; i<length(cols); i++) {
+    coln = INTEGER(cols)[i]-1;
+    if (TYPEOF(values)==VECSXP && (LENGTH(cols)>1 || LENGTH(values)==1))
+      thisvalue = VECTOR_ELT(values,i%LENGTH(values));
+    else
+      thisvalue = values;   // One vector applied to all columns, often NULL or NA for example
+    if (TYPEOF(thisvalue)==NILSXP) {
+      if (!isNull(rows)) error("When deleting columns, i should not be provided");
+      anytodelete = TRUE;
+      continue;   // delete column(s) afterwards, below this loop
+    }
+    vlen = length(thisvalue);
+    if (length(rows)==0 && targetlen==vlen && (vlen>0 || nrow==0)) {
+      if (  MAYBE_SHARED(thisvalue) ||  // set() protects the NAMED of atomic vectors from .Call setting arguments to 2 by wrapping with list
+         (TYPEOF(values)==VECSXP && i>LENGTH(values)-1)) { // recycled RHS would have columns pointing to others, #185.
+        if (verbose) {
+          if (length(values)==length(cols)) {
+            // usual branch
+            Rprintf("RHS for item %d has been duplicated because NAMED is %d, but then is being plonked.\n", i+1, NAMED(thisvalue));
+          } else {
+            // rare branch where the lhs of := is longer than the items on the rhs of :=
+            Rprintf("RHS for item %d has been duplicated because the list of RHS values (length %d) is being recycled, but then is being plonked.\n", i+1, length(values));
+          }
         }
-        vlen = length(thisvalue);
-        if (length(rows)==0 && targetlen==vlen && (vlen>0 || nrow==0)) {
-            if (  MAYBE_SHARED(thisvalue) ||  // set() protects the NAMED of atomic vectors from .Call setting arguments to 2 by wrapping with list
-                 (TYPEOF(values)==VECSXP && i>LENGTH(values)-1)) { // recycled RHS would have columns pointing to others, #185.
-                if (verbose) {
-                    if (length(values)==length(cols)) {
-                      // usual branch
-                      Rprintf("RHS for item %d has been duplicated because NAMED is %d, but then is being plonked.\n", i+1, NAMED(thisvalue));
-                    } else {
-                      // rare branch where the lhs of := is longer than the items on the rhs of :=
-                      Rprintf("RHS for item %d has been duplicated because the list of RHS values (length %d) is being recycled, but then is being plonked.\n", i+1, length(values));
-                    }
-                }
-                thisvalue = duplicate(thisvalue);   // PROTECT not needed as assigned as element to protected list below.
-            } else {
-                if (verbose) Rprintf("Direct plonk of unnamed RHS, no copy.\n");  // e.g. DT[,a:=as.character(a)] as tested by 754.3
+        thisvalue = duplicate(thisvalue);   // PROTECT not needed as assigned as element to protected list below.
+      } else {
+        if (verbose) Rprintf("Direct plonk of unnamed RHS, no copy.\n");  // e.g. DT[,a:=as.character(a)] as tested by 754.3
+      }
+      SET_VECTOR_ELT(dt,coln,thisvalue);                   // plonk new column in as it's already the correct length
+      setAttrib(thisvalue, R_NamesSymbol, R_NilValue);     // clear names such as  DT[,a:=mapvector[a]]
+      setAttrib(thisvalue, R_DimSymbol, R_NilValue);       // so that matrix is treated as vector
+      setAttrib(thisvalue, R_DimNamesSymbol, R_NilValue);  // the 3rd of the 3 attribs not copied by copyMostAttrib, for consistency.
+      continue;
+    }
+    if (coln+1 > oldncol) {  // new column
+      newcol = allocNAVector(TYPEOF(thisvalue),nrow);
+      // initialize with NAs for when 'rows' is a subset and it doesn't touch
+      // do not try to save the time to NA fill (contiguous branch free assign anyway) since being
+      // sure all items will be written to (isNull(rows), length(rows), vlen<1, targetlen) is not worth the risk.
+      SET_VECTOR_ELT(dt,coln,newcol);
+      if (isVectorAtomic(thisvalue)) copyMostAttrib(thisvalue,newcol);  // class etc but not names
+      // else for lists (such as data.frame and data.table) treat them as raw lists and drop attribs
+      if (vlen<1) continue;   // e.g. DT[,newcol:=integer()] (adding new empty column)
+      targetcol = newcol;
+      RHS = thisvalue;
+    } else {                 // existing column
+      targetcol = VECTOR_ELT(dt,coln);
+      if (isFactor(targetcol)) {
+        // Coerce RHS to appropriate levels of LHS, adding new levels as necessary (unlike base)
+        // If it's the same RHS being assigned to several columns, we have to recoerce for each
+        // one because the levels of each target are likely different
+        if (isFactor(thisvalue)) {
+          PROTECT(thisvalue = asCharacterFactor(thisvalue));
+          protecti++;
+        }
+        targetlevels = getAttrib(targetcol, R_LevelsSymbol);
+        if (isNull(targetlevels)) error("somehow this factor column has no levels");
+        if (isString(thisvalue)) {
+          savetl_init();  // ** TO DO **: remove allocs that could fail between here and _end, or different way
+          for (j=0; j<length(thisvalue); j++) {
+            s = STRING_ELT(thisvalue,j);
+            if (TRUELENGTH(s)>0) {
+              savetl(s);  // pre-2.14.0 this will save all the uninitialised truelengths
+                    // so 2.14.0+ may be faster, but isn't required.
+                    // as from v1.8.0 we assume R's internal hash is positive, so don't
+                    // save the uninitialised truelengths that by chance are negative
+            }
+            SET_TRUELENGTH(s,0);
+          }
+          for (j=0; j<length(targetlevels); j++) {
+            s = STRING_ELT(targetlevels,j);
+            if (TRUELENGTH(s)>0) savetl(s);
+            SET_TRUELENGTH(s,j+1);
+          }
+          R_len_t addi = 0;
+          SEXP addlevels=NULL;
+          PROTECT(RHS = allocVector(INTSXP, length(thisvalue)));
+          protecti++;
+          for (j=0; j<length(thisvalue); j++) {
+            thisv = STRING_ELT(thisvalue,j);
+            if (TRUELENGTH(thisv)==0) {
+              if (addi==0) {
+                PROTECT(addlevels = allocVector(STRSXP, 100));
+                protecti++;
+              } else if (addi >= length(addlevels)) {
+                PROTECT(addlevels = growVector(addlevels, length(addlevels)+1000));
+                protecti++;
+              }
+              SET_STRING_ELT(addlevels,addi++,thisv);
+              // if-else for #1718 fix
+              SET_TRUELENGTH(thisv, (thisv != NA_STRING) ? (addi+length(targetlevels)) : NA_INTEGER);
             }
-            SET_VECTOR_ELT(dt,coln,thisvalue);                   // plonk new column in as it's already the correct length
-            setAttrib(thisvalue, R_NamesSymbol, R_NilValue);     // clear names such as  DT[,a:=mapvector[a]]
-            setAttrib(thisvalue, R_DimSymbol, R_NilValue);       // so that matrix is treated as vector
-            setAttrib(thisvalue, R_DimNamesSymbol, R_NilValue);  // the 3rd of the 3 attribs not copied by copyMostAttrib, for consistency.
-            continue;
+            INTEGER(RHS)[j] = TRUELENGTH(thisv);
+          }
+          if (addi > 0) {
+            R_len_t oldlen = length(targetlevels);
+            PROTECT(targetlevels = growVector(targetlevels, oldlen+addi));
+            protecti++;
+            for (j=0; j<addi; j++)
+              SET_STRING_ELT(targetlevels, oldlen+j, STRING_ELT(addlevels, j));
+            setAttrib(targetcol, R_LevelsSymbol, targetlevels);
+          }
+          for (j=0; j<length(targetlevels); j++) SET_TRUELENGTH(STRING_ELT(targetlevels,j),0);  // important to reinstate 0 for countingcharacterorder and HASHPRI (if any) as done by savetl_end().
+          savetl_end();
+        } else {
+          // value is either integer or numeric vector
+          if (TYPEOF(thisvalue)!=INTSXP && TYPEOF(thisvalue)!=LGLSXP && !isReal(thisvalue))
+            error("Internal logical error. Up front checks (before starting to modify DT) didn't catch type of RHS ('%s') assigning to factor column '%s'. Please report to datatable-help.", type2char(TYPEOF(thisvalue)), CHAR(STRING_ELT(names,coln)));
+          if (isReal(thisvalue) || TYPEOF(thisvalue)==LGLSXP) {
+            PROTECT(RHS = coerceVector(thisvalue,INTSXP));
+            protecti++;
+            // silence warning on singleton NAs
+            if (INTEGER(RHS)[0] != NA_INTEGER) warning("Coerced '%s' RHS to 'integer' to match the factor column's underlying type. Character columns are now recommended (can be in keys), or coerce RHS to integer or character first.", type2char(TYPEOF(thisvalue)));
+          } else RHS = thisvalue;
+          for (j=0; j<length(RHS); j++) {
+            if ( (INTEGER(RHS)[j]<1 || INTEGER(RHS)[j]>LENGTH(targetlevels))
+                 && INTEGER(RHS)[j] != NA_INTEGER) {
+              warning("RHS contains %d which is outside the levels range ([1,%d]) of column %d, NAs generated", INTEGER(RHS)[j], LENGTH(targetlevels), i+1);
+              INTEGER(RHS)[j] = NA_INTEGER;
+            }
+          }
         }
-        if (coln+1 > oldncol) {  // new column
-            newcol = allocNAVector(TYPEOF(thisvalue),nrow);
-            // initialize with NAs for when 'rows' is a subset and it doesn't touch
-            // do not try to save the time to NA fill (contiguous branch free assign anyway) since being
-            // sure all items will be written to (isNull(rows), length(rows), vlen<1, targetlen) is not worth the risk.
-            SET_VECTOR_ELT(dt,coln,newcol);
-            if (isVectorAtomic(thisvalue)) copyMostAttrib(thisvalue,newcol);  // class etc but not names
-            // else for lists (such as data.frame and data.table) treat them as raw lists and drop attribs
-            if (vlen<1) continue;   // e.g. DT[,newcol:=integer()] (adding new empty column)
-            targetcol = newcol;
-            RHS = thisvalue;
-        } else {                 // existing column
-            targetcol = VECTOR_ELT(dt,coln);
-            if (isFactor(targetcol)) {
-                // Coerce RHS to appropriate levels of LHS, adding new levels as necessary (unlike base)
-                // If it's the same RHS being assigned to several columns, we have to recoerce for each
-                // one because the levels of each target are likely different
-                if (isFactor(thisvalue)) {
-                    PROTECT(thisvalue = asCharacterFactor(thisvalue));
-                    protecti++;
-                }
-                targetlevels = getAttrib(targetcol, R_LevelsSymbol);
-                if (isNull(targetlevels)) error("somehow this factor column has no levels");
-                if (isString(thisvalue)) {
-                    savetl_init();  // ** TO DO **: remove allocs that could fail between here and _end, or different way
-                    for (j=0; j<length(thisvalue); j++) {
-                        s = STRING_ELT(thisvalue,j);
-                        if (TRUELENGTH(s)>0) {
-                            savetl(s);  // pre-2.14.0 this will save all the uninitialised truelengths
-                                        // so 2.14.0+ may be faster, but isn't required.
-                                        // as from v1.8.0 we assume R's internal hash is positive, so don't
-                                        // save the uninitialised truelengths that by chance are negative
-                        }
-                        SET_TRUELENGTH(s,0);
-                    }
-                    for (j=0; j<length(targetlevels); j++) {
-                        s = STRING_ELT(targetlevels,j);
-                        if (TRUELENGTH(s)>0) savetl(s);
-                        SET_TRUELENGTH(s,j+1);
-                    }
-                    R_len_t addi = 0;
-                    SEXP addlevels=NULL;
-                    PROTECT(RHS = allocVector(INTSXP, length(thisvalue)));
-                    protecti++;
-                    for (j=0; j<length(thisvalue); j++) {
-                        thisv = STRING_ELT(thisvalue,j);
-                        if (TRUELENGTH(thisv)==0) {
-                            if (addi==0) {
-                                PROTECT(addlevels = allocVector(STRSXP, 100));
-                                protecti++;
-                            } else if (addi >= length(addlevels)) {
-                                PROTECT(addlevels = growVector(addlevels, length(addlevels)+1000));
-                                protecti++;
-                            }
-                            SET_STRING_ELT(addlevels,addi++,thisv);
-                            // if-else for #1718 fix
-                            SET_TRUELENGTH(thisv, (thisv != NA_STRING) ? (addi+length(targetlevels)) : NA_INTEGER);
-                        }
-                        INTEGER(RHS)[j] = TRUELENGTH(thisv);
-                    }
-                    if (addi > 0) {
-                        R_len_t oldlen = length(targetlevels);
-                        PROTECT(targetlevels = growVector(targetlevels, oldlen+addi));
-                        protecti++;
-                        for (j=0; j<addi; j++)
-                            SET_STRING_ELT(targetlevels, oldlen+j, STRING_ELT(addlevels, j));
-                        setAttrib(targetcol, R_LevelsSymbol, targetlevels);
-                    }
-                    for (j=0; j<length(targetlevels); j++) SET_TRUELENGTH(STRING_ELT(targetlevels,j),0);  // important to reinstate 0 for countingcharacterorder and HASHPRI (if any) as done by savetl_end().
-                    savetl_end();
-                } else {
-                    // value is either integer or numeric vector
-                    if (TYPEOF(thisvalue)!=INTSXP && TYPEOF(thisvalue)!=LGLSXP && !isReal(thisvalue))
-                        error("Internal logical error. Up front checks (before starting to modify DT) didn't catch type of RHS ('%s') assigning to factor column '%s'. Please report to datatable-help.", type2char(TYPEOF(thisvalue)), CHAR(STRING_ELT(names,coln)));
-                    if (isReal(thisvalue) || TYPEOF(thisvalue)==LGLSXP) {
-                        PROTECT(RHS = coerceVector(thisvalue,INTSXP));
-                        protecti++;
-                        // silence warning on singleton NAs
-                        if (INTEGER(RHS)[0] != NA_INTEGER) warning("Coerced '%s' RHS to 'integer' to match the factor column's underlying type. Character columns are now recommended (can be in keys), or coerce RHS to integer or character first.", type2char(TYPEOF(thisvalue)));
-                    } else RHS = thisvalue;
-                    for (j=0; j<length(RHS); j++) {
-                        if ( (INTEGER(RHS)[j]<1 || INTEGER(RHS)[j]>LENGTH(targetlevels))
-                               && INTEGER(RHS)[j] != NA_INTEGER) {
-                            warning("RHS contains %d which is outside the levels range ([1,%d]) of column %d, NAs generated", INTEGER(RHS)[j], LENGTH(targetlevels), i+1);
-                            INTEGER(RHS)[j] = NA_INTEGER;
-                        }
-                    }
-                }
-            } else {
-                if (TYPEOF(targetcol) == TYPEOF(thisvalue))
-                    RHS = thisvalue;
-                else {
-                    // coerce the RHS to match the type of the column, unlike [<-.data.frame, for efficiency.
-                    if (isString(targetcol) && isFactor(thisvalue)) {
-                        PROTECT(RHS = asCharacterFactor(thisvalue));
-                        protecti++;
-                        if (verbose) Rprintf("Coerced factor to character to match the column's type (coercion is inefficient)\n");   // TO DO:  datatable.pedantic would turn this into warning
-                    } else {
-                        PROTECT(RHS = coerceVector(thisvalue,TYPEOF(targetcol)));
-                        protecti++;
-                        // FR #2551, added test for equality between RHS and thisvalue to not provide the warning when length(thisvalue) == 1
-                        if ( length(thisvalue) == 1 && TYPEOF(RHS) != VECSXP && TYPEOF(thisvalue) != VECSXP && (
-                             (isReal(thisvalue) && isInteger(targetcol) && REAL(thisvalue)[0] == INTEGER(RHS)[0]) ||
-                          (isLogical(thisvalue) && LOGICAL(thisvalue)[0] == NA_LOGICAL) ||
-                                   (isReal(RHS) && isInteger(thisvalue)) )) {
-                              ;
-                          } else {
-                            s1 = (char *)type2char(TYPEOF(targetcol));
-                            s2 = (char *)type2char(TYPEOF(thisvalue));
-                            if (isReal(thisvalue)) s3="; may have truncated precision"; else s3="";
-                            warning("Coerced '%s' RHS to '%s' to match the column's type%s. Either change the target column ['%s'] to '%s' first (by creating a new '%s' vector length %d (nrows of entire table) and assign that; i.e. 'replace' column), or coerce RHS to '%s' (e.g. 1L, NA_[real|integer]_, as.*, etc) to make your intent clear and for speed. Or, set the column type correctly up front when you create the table and stick to it, please.", s2, s1, s3, CHAR(STRING_ELT(names, coln)), s2, s2, LENGTH(VECTOR_ELT(dt,0)), s1);
-                        }
-                    }
-                }
+      } else {
+        if (TYPEOF(targetcol) == TYPEOF(thisvalue))
+          RHS = thisvalue;
+        else {
+          // coerce the RHS to match the type of the column, unlike [<-.data.frame, for efficiency.
+          if (isString(targetcol) && isFactor(thisvalue)) {
+            PROTECT(RHS = asCharacterFactor(thisvalue));
+            protecti++;
+            if (verbose) Rprintf("Coerced factor to character to match the column's type (coercion is inefficient)\n");   // TO DO:  datatable.pedantic would turn this into warning
+          } else {
+            PROTECT(RHS = coerceVector(thisvalue,TYPEOF(targetcol)));
+            protecti++;
+            // FR #2551, added test for equality between RHS and thisvalue to not provide the warning when length(thisvalue) == 1
+            if ( length(thisvalue) == 1 && TYPEOF(RHS) != VECSXP && TYPEOF(thisvalue) != VECSXP && (
+               (isReal(thisvalue) && isInteger(targetcol) && REAL(thisvalue)[0] == INTEGER(RHS)[0]) ||
+              (isLogical(thisvalue) && LOGICAL(thisvalue)[0] == NA_LOGICAL) ||
+                   (isReal(RHS) && isInteger(thisvalue)) )) {
+                ;
+              } else {
+              s1 = (char *)type2char(TYPEOF(targetcol));
+              s2 = (char *)type2char(TYPEOF(thisvalue));
+              if (isReal(thisvalue)) s3="; may have truncated precision"; else s3="";
+              warning("Coerced '%s' RHS to '%s' to match the column's type%s. Either change the target column ['%s'] to '%s' first (by creating a new '%s' vector length %d (nrows of entire table) and assign that; i.e. 'replace' column), or coerce RHS to '%s' (e.g. 1L, NA_[real|integer]_, as.*, etc) to make your intent clear and for speed. Or, set the column type correctly up front when you create the table and stick to it, please.", s2, s1, s3, CHAR(STRING_ELT(names, coln)), s2, s2, LENGTH(VECTOR_ELT(dt,0)), s1);
             }
+          }
         }
-        memrecycle(targetcol, rows, 0, targetlen, RHS);  // also called from dogroups where these arguments are used more
+      }
     }
-    PROTECT(assignedNames = allocVector(STRSXP, LENGTH(cols)));
+    memrecycle(targetcol, rows, 0, targetlen, RHS);  // also called from dogroups where these arguments are used more
+  }
+  PROTECT(assignedNames = allocVector(STRSXP, LENGTH(cols)));
+  protecti++;
+  for (i=0;i<LENGTH(cols);i++) SET_STRING_ELT(assignedNames,i,STRING_ELT(names,INTEGER(cols)[i]-1));
+  key = getAttrib(dt, sym_sorted);
+  if (length(key)) {
+    // if assigning to at least one key column, the key is truncated to one position before the first changed column.
+    //any() and subsetVector() don't seem to be exposed by R API at C level, so this is done here long hand.
+    PROTECT(tmp = chmatch(key, assignedNames, 0, TRUE));
     protecti++;
-    for (i=0;i<LENGTH(cols);i++) SET_STRING_ELT(assignedNames,i,STRING_ELT(names,INTEGER(cols)[i]-1));
-    key = getAttrib(dt, sym_sorted);
-    if (length(key)) {
-        // if assigning to at least one key column, the key is truncated to one position before the first changed column.
-        //any() and subsetVector() don't seem to be exposed by R API at C level, so this is done here long hand.
-        PROTECT(tmp = chmatch(key, assignedNames, 0, TRUE));
-        protecti++;
-        newKeyLength = xlength(key);
-        for (i=0;i<LENGTH(tmp);i++) if (LOGICAL(tmp)[i]) {
-            // If a key column is being assigned to, set newKeyLength to the key element before since everything after that may have changed in order.
-            newKeyLength = i;
-            break;
+    newKeyLength = xlength(key);
+    for (i=0;i<LENGTH(tmp);i++) if (LOGICAL(tmp)[i]) {
+      // If a key column is being assigned to, set newKeyLength to the key element before since everything after that may have changed in order.
+      newKeyLength = i;
+      break;
+    }
+    if(newKeyLength == 0){
+      // no valid key columns remain, remove the key
+      setAttrib(dt, sym_sorted, R_NilValue);
+    } else if (newKeyLength < xlength(key)){
+      // new key is shorter than original one. Reassign
+      PROTECT(tmp = allocVector(STRSXP, newKeyLength));
+      protecti++;
+      memcpy((char *)DATAPTR(tmp), (char *)DATAPTR(key), newKeyLength * sizeof(SEXP));
+      setAttrib(dt, sym_sorted, tmp);
+    }
+    //else: no key column changed, nothing to be done
+  }
+  index = getAttrib(dt, install("index"));
+  if (index != R_NilValue) {
+    s = ATTRIB(index);
+    indexNo = 0;
+    // get a vector with all index names
+    PROTECT(indexNames = allocVector(STRSXP, xlength(s)));
+    protecti++;
+    while(s != R_NilValue){
+      SET_STRING_ELT(indexNames, indexNo, PRINTNAME(TAG(s)));
+      indexNo++;
+      s = CDR(s);
+    }
+    s = ATTRIB(index); // reset to first element
+    indexNo = 0;
+    while(s != R_NilValue) { 
+      a = TAG(s);
+      indexLength = xlength(CAR(s));
+      tc1 = c1 = CHAR(PRINTNAME(a));  // the index name; e.g. "__col1__col2"
+      if (*tc1!='_' || *(tc1+1)!='_') {
+        // fix for #1396
+        if (verbose) {
+          Rprintf("Dropping index '%s' as it doesn't have '__' at the beginning of its name. It was very likely created by v1.9.4 of data.table.\n", tc1);
         }
-        if(newKeyLength == 0){
-          // no valid key columns remain, remove the key
-          setAttrib(dt, sym_sorted, R_NilValue);
-        } else if (newKeyLength < xlength(key)){
-          // new key is shorter than original one. Reassign
-          PROTECT(tmp = allocVector(STRSXP, newKeyLength));
-          protecti++;
-          memcpy((char *)DATAPTR(tmp), (char *)DATAPTR(key), newKeyLength * sizeof(SEXP));
-          setAttrib(dt, sym_sorted, tmp);
+        setAttrib(index, a, R_NilValue);
+        indexNo++;
+        s = CDR(s);
+        continue; // with next index
+      }
+      tc1 += 2; // tc1 always marks the start of a key column
+      if (!*tc1) error("Internal error: index name ends with trailing __");
+      // check the position of the first appearance of an assigned column in the index.
+      // the new index will be truncated to this position.
+      s4 = (char*) malloc(strlen(c1) + 3);
+      if(s4 == NULL){
+        error("Internal error: Couldn't allocate memory for s4.");
+      }
+      memcpy(s4, c1, strlen(c1));
+      memset(s4 + strlen(c1), '\0', 1);
+      strcat(s4, "__"); // add trailing '__' to newKey so we can search for pattern '__colName__' also at the end of the index.
+      newKeyLength = strlen(c1);
+      for(int i = 0; i < xlength(assignedNames); i++){
+        tc2 = CHAR(STRING_ELT(assignedNames, i));
+        s5 = (char*) malloc(strlen(tc2) + 5); //4 * '_' + \0
+        if(s5 == NULL){
+          free(s4);
+          error("Internal error: Couldn't allocate memory for s5.");
         }
-        //else: no key column changed, nothing to be done
-    }
-    index = getAttrib(dt, install("index"));
-    if (index != R_NilValue) {
-        s = ATTRIB(index);
-        indexNo = 0;
-        // get a vector with all index names
-        PROTECT(indexNames = allocVector(STRSXP, xlength(s)));
-        protecti++;
-        while(s != R_NilValue){
-            SET_STRING_ELT(indexNames, indexNo, PRINTNAME(TAG(s)));
-            indexNo++;
-            s = CDR(s);
+        memset(s5, '_', 2);
+        memset(s5 + 2, '\0', 1);
+        strcat(s5, tc2);
+        strcat(s5, "__");
+        tc2 = strstr(s4, s5);
+        if(tc2 == NULL){ // column is not part of key
+          free(s5);
+          continue;
         }
-        s = ATTRIB(index); // reset to first element
-        indexNo = 0;
-        while(s != R_NilValue) { 
-            a = TAG(s);
-            indexLength = xlength(CAR(s));
-            tc1 = c1 = CHAR(PRINTNAME(a));  // the index name; e.g. "__col1__col2"
-            if (*tc1!='_' || *(tc1+1)!='_') {
-                // fix for #1396
-                if (verbose) {
-                    Rprintf("Dropping index '%s' as it doesn't have '__' at the beginning of its name. It was very likely created by v1.9.4 of data.table.\n", tc1);
-                }
-                setAttrib(index, a, R_NilValue);
-                indexNo++;
-                s = CDR(s);
-                continue; // with next index
-            }
-            tc1 += 2; // tc1 always marks the start of a key column
-            if (!*tc1) error("Internal error: index name ends with trailing __");
-            // check the position of the first appearance of an assigned column in the index.
-            // the new index will be truncated to this position.
-            s4 = (char*) malloc(strlen(c1) + 3);
-            if(s4 == NULL){
-              error("Internal error: Couldn't allocate memory for s4.");
-            }
-            memcpy(s4, c1, strlen(c1));
-            memset(s4 + strlen(c1), '\0', 1);
-            strcat(s4, "__"); // add trailing '__' to newKey so we can search for pattern '__colName__' also at the end of the index.
-            newKeyLength = strlen(c1);
-            for(int i = 0; i < xlength(assignedNames); i++){
-                tc2 = CHAR(STRING_ELT(assignedNames, i));
-                s5 = (char*) malloc(strlen(tc2) + 5); //4 * '_' + \0
-                if(s5 == NULL){
-                  free(s4);
-                  error("Internal error: Couldn't allocate memory for s5.");
-                }
-                memset(s5, '_', 2);
-                memset(s5 + 2, '\0', 1);
-                strcat(s5, tc2);
-                strcat(s5, "__");
-                tc2 = strstr(s4, s5);
-                if(tc2 == NULL){ // column is not part of key
-                    free(s5);
-                    continue;
-                }
-                if(tc2 - s4 < newKeyLength){ // new column match is before last match
-                  newKeyLength = tc2 - s4;
-                }
-                free(s5);
-            }
-            memset(s4 + newKeyLength, '\0', 1); // truncate the new key to the new length
-            if(newKeyLength == 0){ // no valid key column remains. Drop the key
-                setAttrib(index, a, R_NilValue);
-                SET_STRING_ELT(indexNames, indexNo, NA_STRING);
-                if (verbose) {
-                    Rprintf("Dropping index '%s' due to an update on a key column\n", c1+2);
-                }
-            } else if(newKeyLength < strlen(c1)){
-                if(indexLength == 0 && // shortened index can be kept since it is just information on the order (see #2372)
-                   LOGICAL(chmatch(mkString(s4), indexNames, 0, TRUE))[0] == 0 ){// index with shortened name not present yet
-                    SET_TAG(s, install(s4));
-                    SET_STRING_ELT(indexNames, indexNo, mkChar(s4));
-                    if (verbose) {
-                        Rprintf("Shortening index '%s' to '%s' due to an update on a key column\n", c1+2, s4 + 2);
-                    }
-                } else{ // indexLength > 0 || shortened name present already
-                    // indexLength > 0 indicates reordering. Drop it to avoid spurious reordering in non-indexed columns (#2372)
-                    // shortened anme already present indicates that index needs to be dropped to avoid duplicate indices.
-                    setAttrib(index, a, R_NilValue);
-                    SET_STRING_ELT(indexNames, indexNo, NA_STRING);
-                    if (verbose) {
-                        Rprintf("Dropping index '%s' due to an update on a key column\n", c1+2);
-                    }
-                }
-            } //else: index is not affected by assign: nothing to be done
-            free(s4);
-            indexNo ++;
-            s = CDR(s);
+        if(tc2 - s4 < newKeyLength){ // new column match is before last match
+          newKeyLength = tc2 - s4;
+        }
+        free(s5);
+      }
+      memset(s4 + newKeyLength, '\0', 1); // truncate the new key to the new length
+      if(newKeyLength == 0){ // no valid key column remains. Drop the key
+        setAttrib(index, a, R_NilValue);
+        SET_STRING_ELT(indexNames, indexNo, NA_STRING);
+        if (verbose) {
+          Rprintf("Dropping index '%s' due to an update on a key column\n", c1+2);
+        }
+      } else if(newKeyLength < strlen(c1)){
+        if(indexLength == 0 && // shortened index can be kept since it is just information on the order (see #2372)
+           LOGICAL(chmatch(mkString(s4), indexNames, 0, TRUE))[0] == 0 ){// index with shortened name not present yet
+          SET_TAG(s, install(s4));
+          SET_STRING_ELT(indexNames, indexNo, mkChar(s4));
+          if (verbose) {
+            Rprintf("Shortening index '%s' to '%s' due to an update on a key column\n", c1+2, s4 + 2);
+          }
+        } else{ // indexLength > 0 || shortened name present already
+          // indexLength > 0 indicates reordering. Drop it to avoid spurious reordering in non-indexed columns (#2372)
+          // shortened anme already present indicates that index needs to be dropped to avoid duplicate indices.
+          setAttrib(index, a, R_NilValue);
+          SET_STRING_ELT(indexNames, indexNo, NA_STRING);
+          if (verbose) {
+            Rprintf("Dropping index '%s' due to an update on a key column\n", c1+2);
+          }
         }
+      } //else: index is not affected by assign: nothing to be done
+      free(s4);
+      indexNo ++;
+      s = CDR(s);
     }
-    if (anytodelete) {
-        // Delete any columns assigned NULL (there was a 'continue' earlier in loop above)
-        // In reverse order to make repeated memmove easy. Otherwise cols would need to be updated as well after each delete.
-        PROTECT(colorder = duplicate(cols));
-        protecti++;
-        R_isort(INTEGER(colorder),LENGTH(cols));
-        PROTECT(colorder = match(cols, colorder, 0));   // actually matches colorder to cols (oddly, arguments are that way around)
-        protecti++;
-        // Can't find a visible R entry point to return ordering of cols, above is only way I could find.
-        // Need ordering (rather than just sorting) because the RHS corresponds in order to the LHS.
-
-        for (r=LENGTH(cols)-1; r>=0; r--) {
-            i = INTEGER(colorder)[r]-1;
-            coln = INTEGER(cols)[i]-1;
-            if (TYPEOF(values)==VECSXP && LENGTH(values)>0)
-                thisvalue = VECTOR_ELT(values,i%LENGTH(values));
-            else
-                thisvalue = values;
-            if (isNull(thisvalue)) {
-                // A new column being assigned NULL would have been warned above, added above, and now deleted (just easier
-                // to code it this way e.g. so that other columns may be added or removed ok by the same query).
-                size=sizeof(SEXP *);
-                memmove((char *)DATAPTR(dt)+coln*size,
-                        (char *)DATAPTR(dt)+(coln+1)*size,
-                        (LENGTH(dt)-coln-1)*size);
-                SET_VECTOR_ELT(dt, LENGTH(dt)-1, R_NilValue);
-                SETLENGTH(dt, LENGTH(dt)-1);
-                // adding using := by group relies on NULL here to know column slot is empty.
-                // good to tidy up the vector anyway.
-                memmove((char *)DATAPTR(names)+coln*size,
-                    (char *)DATAPTR(names)+(coln+1)*size,
-                    (LENGTH(names)-coln-1)*size);
-                SET_STRING_ELT(names, LENGTH(names)-1, NA_STRING);  // no need really, just to be tidy.
-                SETLENGTH(names, LENGTH(names)-1);
-                if (LENGTH(names)==0) {
-                    // That was last column deleted, leaving NULL data.table, so we need to reset .row_names, so that it really is the NULL data.table.
-                    PROTECT(nullint=allocVector(INTSXP, 0));
-                    protecti++;
-                    setAttrib(dt, R_RowNamesSymbol, nullint);  // i.e. .set_row_names(0)
-                    //setAttrib(dt, R_NamesSymbol, R_NilValue);
-                }
-            }
+  }
+  if (anytodelete) {
+    // Delete any columns assigned NULL (there was a 'continue' earlier in loop above)
+    // In reverse order to make repeated memmove easy. Otherwise cols would need to be updated as well after each delete.
+    PROTECT(colorder = duplicate(cols));
+    protecti++;
+    R_isort(INTEGER(colorder),LENGTH(cols));
+    PROTECT(colorder = match(cols, colorder, 0));   // actually matches colorder to cols (oddly, arguments are that way around)
+    protecti++;
+    // Can't find a visible R entry point to return ordering of cols, above is only way I could find.
+    // Need ordering (rather than just sorting) because the RHS corresponds in order to the LHS.
+
+    for (r=LENGTH(cols)-1; r>=0; r--) {
+      i = INTEGER(colorder)[r]-1;
+      coln = INTEGER(cols)[i]-1;
+      if (TYPEOF(values)==VECSXP && LENGTH(values)>0)
+        thisvalue = VECTOR_ELT(values,i%LENGTH(values));
+      else
+        thisvalue = values;
+      if (isNull(thisvalue)) {
+        // A new column being assigned NULL would have been warned above, added above, and now deleted (just easier
+        // to code it this way e.g. so that other columns may be added or removed ok by the same query).
+        size=sizeof(SEXP *);
+        memmove((char *)DATAPTR(dt)+coln*size,
+            (char *)DATAPTR(dt)+(coln+1)*size,
+            (LENGTH(dt)-coln-1)*size);
+        SET_VECTOR_ELT(dt, LENGTH(dt)-1, R_NilValue);
+        SETLENGTH(dt, LENGTH(dt)-1);
+        // adding using := by group relies on NULL here to know column slot is empty.
+        // good to tidy up the vector anyway.
+        memmove((char *)DATAPTR(names)+coln*size,
+          (char *)DATAPTR(names)+(coln+1)*size,
+          (LENGTH(names)-coln-1)*size);
+        SET_STRING_ELT(names, LENGTH(names)-1, NA_STRING);  // no need really, just to be tidy.
+        SETLENGTH(names, LENGTH(names)-1);
+        if (LENGTH(names)==0) {
+          // That was last column deleted, leaving NULL data.table, so we need to reset .row_names, so that it really is the NULL data.table.
+          PROTECT(nullint=allocVector(INTSXP, 0));
+          protecti++;
+          setAttrib(dt, R_RowNamesSymbol, nullint);  // i.e. .set_row_names(0)
+          //setAttrib(dt, R_NamesSymbol, R_NilValue);
         }
+      }
     }
-    UNPROTECT(protecti);
-    return(dt);  // needed for `*tmp*` mechanism (when := isn't used), and to return the new object after a := for compound syntax.
+  }
+  UNPROTECT(protecti);
+  return(dt);  // needed for `*tmp*` mechanism (when := isn't used), and to return the new object after a := for compound syntax.
 }
 
 static Rboolean anyNamed(SEXP x) {
-    if (MAYBE_REFERENCED(x)) return TRUE;
-    if (isNewList(x)) for (int i=0; i<LENGTH(x); i++)
-        if (anyNamed(VECTOR_ELT(x,i))) return TRUE;
-    return FALSE;
+  if (MAYBE_REFERENCED(x)) return TRUE;
+  if (isNewList(x)) for (int i=0; i<LENGTH(x); i++)
+    if (anyNamed(VECTOR_ELT(x,i))) return TRUE;
+  return FALSE;
 }
 
 void memrecycle(SEXP target, SEXP where, int start, int len, SEXP source)
@@ -793,231 +794,231 @@ void memrecycle(SEXP target, SEXP where, int start, int len, SEXP source)
 // 'where' a 1-based INTEGER vector subset of target to assign to,  or NULL or integer()
 // assigns to target[start:start+len-1] or target[where[start:start+len-1]]  where start is 0-based
 {
-    int r=0, w, protecti=0;
-    if (len<1) return;
-    int slen = length(source) > len ? len : length(source); // fix for 5647. when length(source) > len, slen must be len.
-    if (slen<1) return;
-    if (TYPEOF(target) != TYPEOF(source)) error("Internal error: TYPEOF(target)['%s']!=TYPEOF(source)['%s']", type2char(TYPEOF(target)),type2char(TYPEOF(source)));
-    if (isNewList(source)) {
-        // A list() column; i.e. target is a column of pointers to SEXPs rather than the much more common case
-        // where memrecycle copies the DATAPTR data to the atomic target from the atomic source.
-        // If any item within the list is NAMED then take a fresh copy. So far this has occurred from dogroups.c when
-        // j returns .BY or similar specials as-is within a list(). Those specials are static inside
-        // dogroups so if we don't copy now the last value written to them by dogroups becomes repeated in the result;
-        // i.e. the wrong result.
-        // If source is itself recycled later (many list() column items pointing to the same object) we are ok with that
-        // since we now have a fresh copy and := will not assign with a list() column's cell value; := only changes the
-        // SEXP pointed to.
-        // If source is already not named (because j already created a fresh unnamed vector within a list()) we don't want to
-        // duplicate unnecessarily, hence checking for named rather than duplicating always.
-        // See #481 and #1270
-        if (anyNamed(source)) {
-            source = PROTECT(duplicate(source));
-            protecti++;
-        }
+  int r=0, w, protecti=0;
+  if (len<1) return;
+  int slen = length(source) > len ? len : length(source); // fix for 5647. when length(source) > len, slen must be len.
+  if (slen<1) return;
+  if (TYPEOF(target) != TYPEOF(source)) error("Internal error: TYPEOF(target)['%s']!=TYPEOF(source)['%s']", type2char(TYPEOF(target)),type2char(TYPEOF(source)));
+  if (isNewList(source)) {
+    // A list() column; i.e. target is a column of pointers to SEXPs rather than the much more common case
+    // where memrecycle copies the DATAPTR data to the atomic target from the atomic source.
+    // If any item within the list is NAMED then take a fresh copy. So far this has occurred from dogroups.c when
+    // j returns .BY or similar specials as-is within a list(). Those specials are static inside
+    // dogroups so if we don't copy now the last value written to them by dogroups becomes repeated in the result;
+    // i.e. the wrong result.
+    // If source is itself recycled later (many list() column items pointing to the same object) we are ok with that
+    // since we now have a fresh copy and := will not assign with a list() column's cell value; := only changes the
+    // SEXP pointed to.
+    // If source is already not named (because j already created a fresh unnamed vector within a list()) we don't want to
+    // duplicate unnecessarily, hence checking for named rather than duplicating always.
+    // See #481 and #1270
+    if (anyNamed(source)) {
+      source = PROTECT(duplicate(source));
+      protecti++;
     }
-    size_t size = SIZEOF(target);
-    if (!length(where)) {
-        switch (TYPEOF(target)) {
-        case INTSXP : case REALSXP : case LGLSXP :
-            break;
-        case STRSXP :
-            for (; r<slen; r++)     // only one SET_STRING_ELT per RHS item is needed to set generations (overhead)
-                SET_STRING_ELT(target, start+r, STRING_ELT(source, r));
-            break;
-        case VECSXP :
-            for (; r<slen; r++)
-                SET_VECTOR_ELT(target, start+r, VECTOR_ELT(source, r));
-                // TO DO: if := in future could ever change a list item's contents by reference, would need to duplicate at that point
-            break;
-        default :
-            error("Unsupported type '%s'", type2char(TYPEOF(target)));
-        }
-        if (slen == 1) {
-            if (size==4) for (; r<len; r++)
-                INTEGER(target)[start+r] = INTEGER(source)[0];   // copies pointer on 32bit, sizes checked in init.c
-            else for (; r<len; r++)
-                REAL(target)[start+r] = REAL(source)[0];         // copies pointer on 64bit, sizes checked in init.c
-        } else if (slen<10) {    // 10 is just a guess for when memcpy is faster. Certainly memcpy is slower when slen==1, but that's the most common case by far so not high priority to discover the optimum here. TO DO: revisit
-            if (size==4) for (; r<len; r++)
-                INTEGER(target)[start+r] = INTEGER(source)[r%slen];
-            else for (; r<len; r++)
-                REAL(target)[start+r] = REAL(source)[r%slen];
-        } else {
-            for (r=r>0?1:0; r<(len/slen); r++) {   // if the first slen were done in the switch above, convert r=slen to r=1
-                memcpy((char *)DATAPTR(target) + (start+r*slen)*size,
-                       (char *)DATAPTR(source),
-                       slen * size);
-            }
-            memcpy((char *)DATAPTR(target) + (start+r*slen)*size,
-                   (char *)DATAPTR(source),
-                   (len%slen) * size);
-        }
+  }
+  size_t size = SIZEOF(target);
+  if (!length(where)) {
+    switch (TYPEOF(target)) {
+    case INTSXP : case REALSXP : case LGLSXP :
+      break;
+    case STRSXP :
+      for (; r<slen; r++)     // only one SET_STRING_ELT per RHS item is needed to set generations (overhead)
+        SET_STRING_ELT(target, start+r, STRING_ELT(source, r));
+      break;
+    case VECSXP :
+      for (; r<slen; r++)
+        SET_VECTOR_ELT(target, start+r, VECTOR_ELT(source, r));
+        // TO DO: if := in future could ever change a list item's contents by reference, would need to duplicate at that point
+      break;
+    default :
+      error("Unsupported type '%s'", type2char(TYPEOF(target)));
+    }
+    if (slen == 1) {
+      if (size==4) for (; r<len; r++)
+        INTEGER(target)[start+r] = INTEGER(source)[0];   // copies pointer on 32bit, sizes checked in init.c
+      else for (; r<len; r++)
+        REAL(target)[start+r] = REAL(source)[0];         // copies pointer on 64bit, sizes checked in init.c
+    } else if (slen<10) {    // 10 is just a guess for when memcpy is faster. Certainly memcpy is slower when slen==1, but that's the most common case by far so not high priority to discover the optimum here. TO DO: revisit
+      if (size==4) for (; r<len; r++)
+        INTEGER(target)[start+r] = INTEGER(source)[r%slen];
+      else for (; r<len; r++)
+        REAL(target)[start+r] = REAL(source)[r%slen];
     } else {
-        switch (TYPEOF(target)) {
-        case INTSXP : case REALSXP : case LGLSXP :
-            break;
-        case STRSXP :
-            for (; r<slen; r++) {
-                w = INTEGER(where)[start+r]; if (w<1) continue;  // 0 or NA
-                SET_STRING_ELT(target, w-1, STRING_ELT(source, r));
-            }
-            break;
-        case VECSXP :
-            for (; r<slen; r++) {
-                w = INTEGER(where)[start+r]; if (w<1) continue;
-                SET_VECTOR_ELT(target, w-1, VECTOR_ELT(source, r));
-            }
-            break;
-        default :
-            error("Unsupported type '%s'", type2char(TYPEOF(target)));
-        }
-        if (slen == 1) {
-            if (size==4) for (; r<len; r++) {
-                w = INTEGER(where)[start+r]; if (w<1) continue;
-                INTEGER(target)[ w-1 ] = INTEGER(source)[0];
-            } else for (; r<len; r++) {
-                w = INTEGER(where)[start+r]; if (w<1) continue;
-                REAL(target)[ w-1 ] = REAL(source)[0];
-            }
-        } else {
-            if (size==4) for (; r<len; r++) {
-                w = INTEGER(where)[start+r]; if (w<1) continue;
-                INTEGER(target)[ w-1 ] = INTEGER(source)[r%slen];
-            } else for (; r<len; r++) {
-                w = INTEGER(where)[start+r]; if (w<1) continue;
-                REAL(target)[ w-1 ] = REAL(source)[r%slen];
-            }
-        }
-        // if slen>10 it may be worth memcpy, but we'd need to first know if 'where' was a contiguous subset
+      for (r=r>0?1:0; r<(len/slen); r++) {   // if the first slen were done in the switch above, convert r=slen to r=1
+        memcpy((char *)DATAPTR(target) + (start+r*slen)*size,
+             (char *)DATAPTR(source),
+             slen * size);
+      }
+      memcpy((char *)DATAPTR(target) + (start+r*slen)*size,
+           (char *)DATAPTR(source),
+           (len%slen) * size);
     }
-    UNPROTECT(protecti);
-}
-
-SEXP allocNAVector(SEXPTYPE type, R_len_t n)
-{
-    // an allocVector following with initialization to NA since a subassign to a new column using :=
-    // routinely leaves untouched items (rather than 0 or "" as allocVector does with its memset)
-    // We guess that author of allocVector would have liked to initialize with NA but was prevented since memset
-    // is restricted to one byte.
-    R_len_t i;
-    SEXP v;
-    PROTECT(v = allocVector(type, n));
-    switch(type) {
-    // NAs are special; no need to worry about generations.
-    case INTSXP :
-    case LGLSXP :
-        for (i=0; i<n; i++) INTEGER(v)[i] = NA_INTEGER;
-        break;
-    case REALSXP :
-        for (i=0; i<n; i++) REAL(v)[i] = NA_REAL;
-        break;
+  } else {
+    switch (TYPEOF(target)) {
+    case INTSXP : case REALSXP : case LGLSXP :
+      break;
     case STRSXP :
-        for (i=0; i<n; i++) ((SEXP *)DATAPTR(v))[i] = NA_STRING;
-        break;
+      for (; r<slen; r++) {
+        w = INTEGER(where)[start+r]; if (w<1) continue;  // 0 or NA
+        SET_STRING_ELT(target, w-1, STRING_ELT(source, r));
+      }
+      break;
     case VECSXP :
-        // list columns already have each item initialized to NULL
-        break;
+      for (; r<slen; r++) {
+        w = INTEGER(where)[start+r]; if (w<1) continue;
+        SET_VECTOR_ELT(target, w-1, VECTOR_ELT(source, r));
+      }
+      break;
     default :
-        error("Unsupported type '%s'", type2char(type));
+      error("Unsupported type '%s'", type2char(TYPEOF(target)));
+    }
+    if (slen == 1) {
+      if (size==4) for (; r<len; r++) {
+        w = INTEGER(where)[start+r]; if (w<1) continue;
+        INTEGER(target)[ w-1 ] = INTEGER(source)[0];
+      } else for (; r<len; r++) {
+        w = INTEGER(where)[start+r]; if (w<1) continue;
+        REAL(target)[ w-1 ] = REAL(source)[0];
+      }
+    } else {
+      if (size==4) for (; r<len; r++) {
+        w = INTEGER(where)[start+r]; if (w<1) continue;
+        INTEGER(target)[ w-1 ] = INTEGER(source)[r%slen];
+      } else for (; r<len; r++) {
+        w = INTEGER(where)[start+r]; if (w<1) continue;
+        REAL(target)[ w-1 ] = REAL(source)[r%slen];
+      }
     }
-    UNPROTECT(1);
-    return(v);
+    // if slen>10 it may be worth memcpy, but we'd need to first know if 'where' was a contiguous subset
+  }
+  UNPROTECT(protecti);
+}
+
+SEXP allocNAVector(SEXPTYPE type, R_len_t n)
+{
+  // an allocVector following with initialization to NA since a subassign to a new column using :=
+  // routinely leaves untouched items (rather than 0 or "" as allocVector does with its memset)
+  // We guess that author of allocVector would have liked to initialize with NA but was prevented since memset
+  // is restricted to one byte.
+  R_len_t i;
+  SEXP v;
+  PROTECT(v = allocVector(type, n));
+  switch(type) {
+  // NAs are special; no need to worry about generations.
+  case INTSXP :
+  case LGLSXP :
+    for (i=0; i<n; i++) INTEGER(v)[i] = NA_INTEGER;
+    break;
+  case REALSXP :
+    for (i=0; i<n; i++) REAL(v)[i] = NA_REAL;
+    break;
+  case STRSXP :
+    for (i=0; i<n; i++) ((SEXP *)DATAPTR(v))[i] = NA_STRING;
+    break;
+  case VECSXP :
+    // list columns already have each item initialized to NULL
+    break;
+  default :
+    error("Unsupported type '%s'", type2char(type));
+  }
+  UNPROTECT(1);
+  return(v);
 }
 
 void savetl_init() {
-    if (nsaved || nalloc || saveds || savedtl) error("Internal error: savetl_init checks failed (%d %d %p %p). Please report to datatable-help.", nsaved, nalloc, saveds, savedtl);
-    nsaved = 0;
-    nalloc = 100;
-    saveds = (SEXP *)malloc(nalloc * sizeof(SEXP));
-    if (saveds == NULL) error("Couldn't allocate saveds in savetl_init");
-    savedtl = (R_len_t *)malloc(nalloc * sizeof(R_len_t));
-    if (savedtl == NULL) {
-        free(saveds);
-        error("Couldn't allocate saveds in savetl_init");
-    }
+  if (nsaved || nalloc || saveds || savedtl) error("Internal error: savetl_init checks failed (%d %d %p %p). Please report to datatable-help.", nsaved, nalloc, saveds, savedtl);
+  nsaved = 0;
+  nalloc = 100;
+  saveds = (SEXP *)malloc(nalloc * sizeof(SEXP));
+  if (saveds == NULL) error("Couldn't allocate saveds in savetl_init");
+  savedtl = (R_len_t *)malloc(nalloc * sizeof(R_len_t));
+  if (savedtl == NULL) {
+    free(saveds);
+    error("Couldn't allocate saveds in savetl_init");
+  }
 }
 
 void savetl(SEXP s)
 {
-    if (nsaved>=nalloc) {
-        nalloc *= 2;
-        char *tmp;
-        tmp = (char *)realloc(saveds, nalloc * sizeof(SEXP));
-        if (tmp == NULL) {
-            savetl_end();
-            error("Couldn't realloc saveds in savetl");
-        }
-        saveds = (SEXP *)tmp;
-        tmp = (char *)realloc(savedtl, nalloc * sizeof(R_len_t));
-        if (tmp == NULL) {
-            savetl_end();
-            error("Couldn't realloc savedtl in savetl");
-        }
-        savedtl = (R_len_t *)tmp;
+  if (nsaved>=nalloc) {
+    nalloc *= 2;
+    char *tmp;
+    tmp = (char *)realloc(saveds, nalloc * sizeof(SEXP));
+    if (tmp == NULL) {
+      savetl_end();
+      error("Couldn't realloc saveds in savetl");
     }
-    saveds[nsaved] = s;
-    savedtl[nsaved] = TRUELENGTH(s);
-    nsaved++;
+    saveds = (SEXP *)tmp;
+    tmp = (char *)realloc(savedtl, nalloc * sizeof(R_len_t));
+    if (tmp == NULL) {
+      savetl_end();
+      error("Couldn't realloc savedtl in savetl");
+    }
+    savedtl = (R_len_t *)tmp;
+  }
+  saveds[nsaved] = s;
+  savedtl[nsaved] = TRUELENGTH(s);
+  nsaved++;
 }
 
 void savetl_end() {
-    // Can get called if nothing has been saved yet (nsaved==0), or even if _init() hasn't been called yet (pointers NULL). Such
-    // as to clear up before error. Also, it might be that nothing needed to be saved anyway.
-    for (int i=0; i<nsaved; i++) SET_TRUELENGTH(saveds[i],savedtl[i]);
-    free(saveds);  // does nothing on NULL input
-    free(savedtl);
-    nsaved = nalloc = 0;
-    saveds = NULL;
-    savedtl = NULL;
+  // Can get called if nothing has been saved yet (nsaved==0), or even if _init() hasn't been called yet (pointers NULL). Such
+  // as to clear up before error. Also, it might be that nothing needed to be saved anyway.
+  for (int i=0; i<nsaved; i++) SET_TRUELENGTH(saveds[i],savedtl[i]);
+  free(saveds);  // does nothing on NULL input
+  free(savedtl);
+  nsaved = nalloc = 0;
+  saveds = NULL;
+  savedtl = NULL;
 }
 
 SEXP setcharvec(SEXP x, SEXP which, SEXP new)
 {
-    int w;
-    if (!isString(x)) error("x must be a character vector");
-    if (!isInteger(which)) error("'which' must be an integer vector");
-    if (!isString(new)) error("'new' must be a character vector");
-    if (LENGTH(new)!=LENGTH(which)) error("'new' is length %d. Should be the same as length of 'which' (%d)",LENGTH(new),LENGTH(which));
-    for (int i=0; i<LENGTH(which); i++) {
-        w = INTEGER(which)[i];
-        if (w==NA_INTEGER || w<1 || w>LENGTH(x)) error("Item %d of 'which' is %d which is outside range of the length %d character vector", i+1,w,LENGTH(x));
-        SET_STRING_ELT(x, w-1, STRING_ELT(new, i));
-    }
-    return R_NilValue;
+  int w;
+  if (!isString(x)) error("x must be a character vector");
+  if (!isInteger(which)) error("'which' must be an integer vector");
+  if (!isString(new)) error("'new' must be a character vector");
+  if (LENGTH(new)!=LENGTH(which)) error("'new' is length %d. Should be the same as length of 'which' (%d)",LENGTH(new),LENGTH(which));
+  for (int i=0; i<LENGTH(which); i++) {
+    w = INTEGER(which)[i];
+    if (w==NA_INTEGER || w<1 || w>LENGTH(x)) error("Item %d of 'which' is %d which is outside range of the length %d character vector", i+1,w,LENGTH(x));
+    SET_STRING_ELT(x, w-1, STRING_ELT(new, i));
+  }
+  return R_NilValue;
 }
 
 SEXP setcolorder(SEXP x, SEXP o)
 {
-    // checks have already been made at R level in setcolorder()
-    // reording columns by reference makes no difference to generations
-    // so there's no need to use SET_* api.
-    SEXP *tmp = Calloc(LENGTH(x),SEXP);
-    for (int i=0; i<LENGTH(x); i++)
-        tmp[i] = VECTOR_ELT(x, INTEGER(o)[i]-1);
-    memcpy((char *)DATAPTR(x),(char *)tmp,LENGTH(x)*sizeof(char *)); // sizeof is of type size_t (unsigned) - so no overflow here
-    SEXP names = getAttrib(x,R_NamesSymbol);
-    if (isNull(names)) error("dt passed to setcolorder has no names");
-    for (int i=0; i<LENGTH(x); i++)
-        tmp[i] = STRING_ELT(names, INTEGER(o)[i]-1);
-    memcpy((char *)DATAPTR(names),(char *)tmp,LENGTH(x)*sizeof(char *));
-    // No need to change key (if any); sorted attribute is column names not positions
-    Free(tmp);
-    return(R_NilValue);
+  // checks have already been made at R level in setcolorder()
+  // reording columns by reference makes no difference to generations
+  // so there's no need to use SET_* api.
+  SEXP *tmp = Calloc(LENGTH(x),SEXP);
+  for (int i=0; i<LENGTH(x); i++)
+    tmp[i] = VECTOR_ELT(x, INTEGER(o)[i]-1);
+  memcpy((char *)DATAPTR(x),(char *)tmp,LENGTH(x)*sizeof(char *)); // sizeof is of type size_t (unsigned) - so no overflow here
+  SEXP names = getAttrib(x,R_NamesSymbol);
+  if (isNull(names)) error("dt passed to setcolorder has no names");
+  for (int i=0; i<LENGTH(x); i++)
+    tmp[i] = STRING_ELT(names, INTEGER(o)[i]-1);
+  memcpy((char *)DATAPTR(names),(char *)tmp,LENGTH(x)*sizeof(char *));
+  // No need to change key (if any); sorted attribute is column names not positions
+  Free(tmp);
+  return(R_NilValue);
 }
 
 SEXP pointWrapper(SEXP to, SEXP to_idx, SEXP from, SEXP from_idx) {
 
-    R_len_t i, fidx, tidx, l_to=length(to), l_from=length(from), l_idx=length(from_idx);
-    if (!isNewList(to) || !isNewList(from)) error("'to' and 'from' must be of type list");
-    if (length(from_idx) != length(to_idx) || l_idx == 0) error("'from_idx' and 'to_idx' must be non-empty integer vectors of same length.");
-    for (i=0; i<l_idx; i++) {
-        fidx = INTEGER(from_idx)[i]-1; // 1-based to 0-based
-        tidx = INTEGER(to_idx)[i]-1;   // 1-based to 0-based
-        if (fidx < 0 || fidx > l_from-1) error("invalid from_idx[%d]=%d, falls outside 1 and length(from)=%d.", i+1, fidx, l_from);
-        if (tidx < 0 || tidx > l_to-1) error("invalid to_idx[%d]=%d, falls outside 1 and length(to)=%d.", i+1, tidx, l_to);
-        SET_VECTOR_ELT(to, tidx, VECTOR_ELT(from, fidx));
-    }
-    return(to);
+  R_len_t i, fidx, tidx, l_to=length(to), l_from=length(from), l_idx=length(from_idx);
+  if (!isNewList(to) || !isNewList(from)) error("'to' and 'from' must be of type list");
+  if (length(from_idx) != length(to_idx) || l_idx == 0) error("'from_idx' and 'to_idx' must be non-empty integer vectors of same length.");
+  for (i=0; i<l_idx; i++) {
+    fidx = INTEGER(from_idx)[i]-1; // 1-based to 0-based
+    tidx = INTEGER(to_idx)[i]-1;   // 1-based to 0-based
+    if (fidx < 0 || fidx > l_from-1) error("invalid from_idx[%d]=%d, falls outside 1 and length(from)=%d.", i+1, fidx, l_from);
+    if (tidx < 0 || tidx > l_to-1) error("invalid to_idx[%d]=%d, falls outside 1 and length(to)=%d.", i+1, tidx, l_to);
+    SET_VECTOR_ELT(to, tidx, VECTOR_ELT(from, fidx));
+  }
+  return(to);
 }
 
diff --git a/src/between.c b/src/between.c
index 43b2455745..678016fd2e 100644
--- a/src/between.c
+++ b/src/between.c
@@ -4,101 +4,101 @@
 static double l=0.0, u=0.0;
 
 Rboolean int_upper_closed(SEXP x, R_len_t i) {
-    return (INTEGER(x)[i] == NA_INTEGER || (double)INTEGER(x)[i] <= u ? NA_LOGICAL : FALSE);
+  return (INTEGER(x)[i] == NA_INTEGER || (double)INTEGER(x)[i] <= u ? NA_LOGICAL : FALSE);
 }
 
 Rboolean int_upper_open(SEXP x, R_len_t i) {
-    return (INTEGER(x)[i] == NA_INTEGER || (double)INTEGER(x)[i] < u ? NA_LOGICAL : FALSE);
+  return (INTEGER(x)[i] == NA_INTEGER || (double)INTEGER(x)[i] < u ? NA_LOGICAL : FALSE);
 }
 
 Rboolean int_lower_closed(SEXP x, R_len_t i) {
-    return (INTEGER(x)[i] == NA_INTEGER || (double)INTEGER(x)[i] >= l ? NA_LOGICAL : FALSE);
+  return (INTEGER(x)[i] == NA_INTEGER || (double)INTEGER(x)[i] >= l ? NA_LOGICAL : FALSE);
 }
 
 Rboolean int_lower_open(SEXP x, R_len_t i) {
-    return (INTEGER(x)[i] == NA_INTEGER || (double)INTEGER(x)[i] > l ? NA_LOGICAL : FALSE);
+  return (INTEGER(x)[i] == NA_INTEGER || (double)INTEGER(x)[i] > l ? NA_LOGICAL : FALSE);
 }
 
 Rboolean int_both_closed(SEXP x, R_len_t i) {
-    return (INTEGER(x)[i] == NA_INTEGER ? NA_LOGICAL : ((double)INTEGER(x)[i] >= l && (double)INTEGER(x)[i] <= u));
+  return (INTEGER(x)[i] == NA_INTEGER ? NA_LOGICAL : ((double)INTEGER(x)[i] >= l && (double)INTEGER(x)[i] <= u));
 }
 
 Rboolean int_both_open(SEXP x, R_len_t i) {
-    return (INTEGER(x)[i] == NA_INTEGER ? NA_LOGICAL : ((double)INTEGER(x)[i] > l && (double)INTEGER(x)[i] < u));
+  return (INTEGER(x)[i] == NA_INTEGER ? NA_LOGICAL : ((double)INTEGER(x)[i] > l && (double)INTEGER(x)[i] < u));
 }
 
 Rboolean double_upper_closed(SEXP x, R_len_t i) {
-    return (ISNAN(REAL(x)[i]) || REAL(x)[i] <= u ? NA_LOGICAL : FALSE);
+  return (ISNAN(REAL(x)[i]) || REAL(x)[i] <= u ? NA_LOGICAL : FALSE);
 }
 
 Rboolean double_upper_open(SEXP x, R_len_t i) {
-    return (ISNAN(REAL(x)[i]) || REAL(x)[i] < u ? NA_LOGICAL : FALSE);
+  return (ISNAN(REAL(x)[i]) || REAL(x)[i] < u ? NA_LOGICAL : FALSE);
 }
 
 Rboolean double_lower_closed(SEXP x, R_len_t i) {
-    return (ISNAN(REAL(x)[i]) || REAL(x)[i] >= l ? NA_LOGICAL : FALSE);
+  return (ISNAN(REAL(x)[i]) || REAL(x)[i] >= l ? NA_LOGICAL : FALSE);
 }
 
 Rboolean double_lower_open(SEXP x, R_len_t i) {
-    return (ISNAN(REAL(x)[i]) || REAL(x)[i] > l ? NA_LOGICAL : FALSE);
+  return (ISNAN(REAL(x)[i]) || REAL(x)[i] > l ? NA_LOGICAL : FALSE);
 }
 
 Rboolean double_both_closed(SEXP x, R_len_t i) {
-    return (ISNAN(REAL(x)[i]) ? NA_LOGICAL : (REAL(x)[i] >= l && REAL(x)[i] <= u));
+  return (ISNAN(REAL(x)[i]) ? NA_LOGICAL : (REAL(x)[i] >= l && REAL(x)[i] <= u));
 }
 
 Rboolean double_both_open(SEXP x, R_len_t i) {
-    return (ISNAN(REAL(x)[i]) ? NA_LOGICAL : (REAL(x)[i] > l && REAL(x)[i] < u));
+  return (ISNAN(REAL(x)[i]) ? NA_LOGICAL : (REAL(x)[i] > l && REAL(x)[i] < u));
 }
 
 SEXP between(SEXP x, SEXP lower, SEXP upper, SEXP bounds) {
 
-    R_len_t i, nx = length(x), nl = length(lower), nu = length(upper);
-    l = 0.0; u = 0.0;
-    SEXP ans;
-    Rboolean (*flower)(), (*fupper)(), (*fboth)();
-    if (!nx || !nl || !nu)
-        return (allocVector(LGLSXP, 0));
-    if (nl != 1 && nl != nx)
-        error("length(lower) (%d) must be either 1 or length(x) (%d)", nl, nx);
-    if (nu != 1 && nu != nx)
-        error("length(upper) (%d) must be either 1 or length(x) (%d)", nu, nx);
-    if (!isLogical(bounds) || LOGICAL(bounds)[0] == NA_LOGICAL)
-        error("incbounds must be logical TRUE/FALSE.");
-
-    // no support for int64 yet (only handling most common cases)
-    // coerce to also get NA values properly
-    lower = PROTECT(coerceVector(lower, REALSXP)); l = REAL(lower)[0];
-    upper = PROTECT(coerceVector(upper, REALSXP)); u = REAL(upper)[0];
-    ans   = PROTECT(allocVector(LGLSXP, nx));
-
-    if (LOGICAL(bounds)[0]) {
-        fupper = isInteger(x) ? &int_upper_closed : &double_upper_closed;
-        flower = isInteger(x) ? &int_lower_closed : &double_lower_closed;
-        fboth  = isInteger(x) ? &int_both_closed  : &double_both_closed;
+  R_len_t i, nx = length(x), nl = length(lower), nu = length(upper);
+  l = 0.0; u = 0.0;
+  SEXP ans;
+  Rboolean (*flower)(), (*fupper)(), (*fboth)();
+  if (!nx || !nl || !nu)
+    return (allocVector(LGLSXP, 0));
+  if (nl != 1 && nl != nx)
+    error("length(lower) (%d) must be either 1 or length(x) (%d)", nl, nx);
+  if (nu != 1 && nu != nx)
+    error("length(upper) (%d) must be either 1 or length(x) (%d)", nu, nx);
+  if (!isLogical(bounds) || LOGICAL(bounds)[0] == NA_LOGICAL)
+    error("incbounds must be logical TRUE/FALSE.");
+
+  // no support for int64 yet (only handling most common cases)
+  // coerce to also get NA values properly
+  lower = PROTECT(coerceVector(lower, REALSXP)); l = REAL(lower)[0];
+  upper = PROTECT(coerceVector(upper, REALSXP)); u = REAL(upper)[0];
+  ans   = PROTECT(allocVector(LGLSXP, nx));
+
+  if (LOGICAL(bounds)[0]) {
+    fupper = isInteger(x) ? &int_upper_closed : &double_upper_closed;
+    flower = isInteger(x) ? &int_lower_closed : &double_lower_closed;
+    fboth  = isInteger(x) ? &int_both_closed  : &double_both_closed;
+  } else {
+    fupper = isInteger(x) ? &int_upper_open : &double_upper_open;
+    flower = isInteger(x) ? &int_lower_open : &double_lower_open;
+    fboth  = isInteger(x) ? &int_both_open  : &double_both_open;
+  }
+
+  if ( ISNAN(REAL(lower)[0]) ) {
+    if ( ISNAN(REAL(upper)[0]) ) {
+      #pragma omp parallel for num_threads(getDTthreads())
+      for (i=0; i<nx; i++) LOGICAL(ans)[i] = NA_LOGICAL;
     } else {
-        fupper = isInteger(x) ? &int_upper_open : &double_upper_open;
-        flower = isInteger(x) ? &int_lower_open : &double_lower_open;
-        fboth  = isInteger(x) ? &int_both_open  : &double_both_open;
+      #pragma omp parallel for num_threads(getDTthreads())
+      for (i=0; i<nx; i++) LOGICAL(ans)[i] = fupper(x, i);
     }
-
-    if ( ISNAN(REAL(lower)[0]) ) {
-        if ( ISNAN(REAL(upper)[0]) ) {
-            #pragma omp parallel for num_threads(getDTthreads())
-            for (i=0; i<nx; i++) LOGICAL(ans)[i] = NA_LOGICAL;
-        } else {
-            #pragma omp parallel for num_threads(getDTthreads())
-            for (i=0; i<nx; i++) LOGICAL(ans)[i] = fupper(x, i);
-        }
+  } else {
+    if ( ISNAN(REAL(upper)[0]) ) {
+      #pragma omp parallel for num_threads(getDTthreads())
+      for (i=0; i<nx; i++) LOGICAL(ans)[i] = flower(x, i);
     } else {
-        if ( ISNAN(REAL(upper)[0]) ) {
-            #pragma omp parallel for num_threads(getDTthreads())
-            for (i=0; i<nx; i++) LOGICAL(ans)[i] = flower(x, i);
-        } else {
-            #pragma omp parallel for num_threads(getDTthreads())
-            for (i=0; i<nx; i++) LOGICAL(ans)[i] = fboth(x, i);
-        }
+      #pragma omp parallel for num_threads(getDTthreads())
+      for (i=0; i<nx; i++) LOGICAL(ans)[i] = fboth(x, i);
     }
-    UNPROTECT(3);
-    return(ans);
+  }
+  UNPROTECT(3);
+  return(ans);
 }
diff --git a/src/bmerge.c b/src/bmerge.c
index 5bba53926c..c724a9e194 100644
--- a/src/bmerge.c
+++ b/src/bmerge.c
@@ -38,163 +38,163 @@ static Rboolean rollToNearest=FALSE;
 void bmerge_r(int xlow, int xupp, int ilow, int iupp, int col, int thisgrp, int lowmax, int uppmax);
 
 SEXP bmerge(SEXP iArg, SEXP xArg, SEXP icolsArg, SEXP xcolsArg, SEXP isorted, SEXP xoArg, SEXP rollarg, SEXP rollendsArg, SEXP nomatchArg, SEXP multArg, SEXP opArg, SEXP nqgrpArg, SEXP nqmaxgrpArg) {
-    int xN, iN, protecti=0;
-    ctr=0; // needed for non-equi join case
-    SEXP retFirstArg, retLengthArg, retIndexArg, allLen1Arg, allGrp1Arg;
-    retFirstArg = retLengthArg = retIndexArg = R_NilValue; // suppress gcc msg
+  int xN, iN, protecti=0;
+  ctr=0; // needed for non-equi join case
+  SEXP retFirstArg, retLengthArg, retIndexArg, allLen1Arg, allGrp1Arg;
+  retFirstArg = retLengthArg = retIndexArg = R_NilValue; // suppress gcc msg
 
-    // iArg, xArg, icolsArg and xcolsArg
-    i = iArg; x = xArg;  // set globals so bmerge_r can see them.
-    if (!isInteger(icolsArg)) error("Internal error: icols is not integer vector");
-    if (!isInteger(xcolsArg)) error("Internal error: xcols is not integer vector");
-    if (LENGTH(icolsArg) > LENGTH(xcolsArg)) error("Internal error: length(icols) [%d] > length(xcols) [%d]", LENGTH(icolsArg), LENGTH(xcolsArg));
-    icols = INTEGER(icolsArg);
-    xcols = INTEGER(xcolsArg);
-    xN = LENGTH(VECTOR_ELT(x,0));
-    iN = ilen = anslen = LENGTH(VECTOR_ELT(i,0));
-    ncol = LENGTH(icolsArg);    // there may be more sorted columns in x than involved in the join
-    for(int col=0; col<ncol; col++) {
-        if (icols[col]==NA_INTEGER) error("Internal error. icols[%d] is NA", col);
-        if (xcols[col]==NA_INTEGER) error("Internal error. xcols[%d] is NA", col);
-        if (icols[col]>LENGTH(i) || icols[col]<1) error("icols[%d]=%d outside range [1,length(i)=%d]", col, icols[col], LENGTH(i));
-        if (xcols[col]>LENGTH(x) || xcols[col]<1) error("xcols[%d]=%d outside range [1,length(x)=%d]", col, xcols[col], LENGTH(x));
-        int it = TYPEOF(VECTOR_ELT(i, icols[col]-1));
-        int xt = TYPEOF(VECTOR_ELT(x, xcols[col]-1));
-        if (it != xt) error("typeof x.%s (%s) != typeof i.%s (%s)", CHAR(STRING_ELT(getAttrib(x,R_NamesSymbol),xcols[col]-1)), type2char(xt), CHAR(STRING_ELT(getAttrib(i,R_NamesSymbol),icols[col]-1)), type2char(it));
-    }
-    // raise(SIGINT);
+  // iArg, xArg, icolsArg and xcolsArg
+  i = iArg; x = xArg;  // set globals so bmerge_r can see them.
+  if (!isInteger(icolsArg)) error("Internal error: icols is not integer vector");
+  if (!isInteger(xcolsArg)) error("Internal error: xcols is not integer vector");
+  if (LENGTH(icolsArg) > LENGTH(xcolsArg)) error("Internal error: length(icols) [%d] > length(xcols) [%d]", LENGTH(icolsArg), LENGTH(xcolsArg));
+  icols = INTEGER(icolsArg);
+  xcols = INTEGER(xcolsArg);
+  xN = LENGTH(VECTOR_ELT(x,0));
+  iN = ilen = anslen = LENGTH(VECTOR_ELT(i,0));
+  ncol = LENGTH(icolsArg);    // there may be more sorted columns in x than involved in the join
+  for(int col=0; col<ncol; col++) {
+    if (icols[col]==NA_INTEGER) error("Internal error. icols[%d] is NA", col);
+    if (xcols[col]==NA_INTEGER) error("Internal error. xcols[%d] is NA", col);
+    if (icols[col]>LENGTH(i) || icols[col]<1) error("icols[%d]=%d outside range [1,length(i)=%d]", col, icols[col], LENGTH(i));
+    if (xcols[col]>LENGTH(x) || xcols[col]<1) error("xcols[%d]=%d outside range [1,length(x)=%d]", col, xcols[col], LENGTH(x));
+    int it = TYPEOF(VECTOR_ELT(i, icols[col]-1));
+    int xt = TYPEOF(VECTOR_ELT(x, xcols[col]-1));
+    if (it != xt) error("typeof x.%s (%s) != typeof i.%s (%s)", CHAR(STRING_ELT(getAttrib(x,R_NamesSymbol),xcols[col]-1)), type2char(xt), CHAR(STRING_ELT(getAttrib(i,R_NamesSymbol),icols[col]-1)), type2char(it));
+  }
+  // raise(SIGINT);
 
-    // rollArg, rollendsArg
-    roll = 0.0; rollToNearest = FALSE;
-    if (isString(rollarg)) {
-        if (strcmp(CHAR(STRING_ELT(rollarg,0)),"nearest") != 0) error("roll is character but not 'nearest'");
-        roll=1.0; rollToNearest=TRUE;       // the 1.0 here is just any non-0.0, so roll!=0.0 can be used later
-    } else {
-        if (!isReal(rollarg)) error("Internal error: roll is not character or double");
-        roll = REAL(rollarg)[0];   // more common case (rolling forwards or backwards) or no roll when 0.0
-    }
-    rollabs = fabs(roll);
-    if (!isLogical(rollendsArg) || LENGTH(rollendsArg) != 2)
-        error("rollends must be a length 2 logical vector");
-    rollends = LOGICAL(rollendsArg);
-    if (rollToNearest && TYPEOF(VECTOR_ELT(i, icols[ncol-1]-1))==STRSXP)
-        error("roll='nearest' can't be applied to a character column, yet.");
+  // rollArg, rollendsArg
+  roll = 0.0; rollToNearest = FALSE;
+  if (isString(rollarg)) {
+    if (strcmp(CHAR(STRING_ELT(rollarg,0)),"nearest") != 0) error("roll is character but not 'nearest'");
+    roll=1.0; rollToNearest=TRUE;       // the 1.0 here is just any non-0.0, so roll!=0.0 can be used later
+  } else {
+    if (!isReal(rollarg)) error("Internal error: roll is not character or double");
+    roll = REAL(rollarg)[0];   // more common case (rolling forwards or backwards) or no roll when 0.0
+  }
+  rollabs = fabs(roll);
+  if (!isLogical(rollendsArg) || LENGTH(rollendsArg) != 2)
+    error("rollends must be a length 2 logical vector");
+  rollends = LOGICAL(rollendsArg);
+  if (rollToNearest && TYPEOF(VECTOR_ELT(i, icols[ncol-1]-1))==STRSXP)
+    error("roll='nearest' can't be applied to a character column, yet.");
 
-    // nomatch arg
-    nomatch = INTEGER(nomatchArg)[0];
+  // nomatch arg
+  nomatch = INTEGER(nomatchArg)[0];
 
-    // mult arg
-    if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "all")) mult = ALL;
-    else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "first")) mult = FIRST;
-    else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "last")) mult = LAST;
-    else error("Internal error: invalid value for 'mult'. Please report to datatable-help");
+  // mult arg
+  if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "all")) mult = ALL;
+  else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "first")) mult = FIRST;
+  else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "last")) mult = LAST;
+  else error("Internal error: invalid value for 'mult'. Please report to datatable-help");
 
-    // opArg
-    if (!isInteger(opArg) || length(opArg) != ncol)
-        error("Internal error: opArg is not an integer vector of length equal to length(on)");
-    op = INTEGER(opArg);
-    if (!isInteger(nqgrpArg))
-        error("Internal error: nqgrpArg must be an integer vector");
-    nqgrp = nqgrpArg; // set global for bmerge_r
-    scols = (!length(nqgrpArg)) ? 0 : -1; // starting col index, -1 is external group column for non-equi join case
+  // opArg
+  if (!isInteger(opArg) || length(opArg) != ncol)
+    error("Internal error: opArg is not an integer vector of length equal to length(on)");
+  op = INTEGER(opArg);
+  if (!isInteger(nqgrpArg))
+    error("Internal error: nqgrpArg must be an integer vector");
+  nqgrp = nqgrpArg; // set global for bmerge_r
+  scols = (!length(nqgrpArg)) ? 0 : -1; // starting col index, -1 is external group column for non-equi join case
 
-    // nqmaxgrpArg
-    if (!isInteger(nqmaxgrpArg) || length(nqmaxgrpArg) != 1 || INTEGER(nqmaxgrpArg)[0] <= 0)
-        error("Intrnal error: nqmaxgrpArg is not a positive length-1 integer vector");
-    nqmaxgrp = INTEGER(nqmaxgrpArg)[0];
-    if (nqmaxgrp>1 && mult == ALL) {
-        // non-equi case with mult=ALL, may need reallocation
-        anslen = 1.1 * ((iN > 1000) ? iN : 1000);
-        retFirst = Calloc(anslen, int); // anslen is set above
-        retLength = Calloc(anslen, int);
-        retIndex = Calloc(anslen, int);
-        if (retFirst==NULL || retLength==NULL || retIndex==NULL)
-            error("Internal error in allocating memory for non-equi join");
-        // initialise retIndex here directly, as next loop is meant for both equi and non-equi joins
-        for (int j=0; j<anslen; j++) retIndex[j] = j+1;
-    } else { // equi joins (or) non-equi join but no multiple matches
-        retFirstArg = PROTECT(allocVector(INTSXP, anslen));
-        retFirst = INTEGER(retFirstArg);
-        retLengthArg = PROTECT(allocVector(INTSXP, anslen)); // TODO: no need to allocate length at all when
-        retLength = INTEGER(retLengthArg);                   // mult = "first" / "last"
-        retIndexArg = PROTECT(allocVector(INTSXP, 0));
-        retIndex = INTEGER(retIndexArg);
-        protecti += 3;
-    }
-    for (int j=0; j<anslen; j++) {
-        // defaults need to populated here as bmerge_r may well not touch many locations, say if the last row of i is before the first row of x.
-        retFirst[j] = nomatch;   // default to no match for NA goto below
-        // retLength[j] = 0;   // TO DO: do this to save the branch below and later branches at R level to set .N to 0
-        retLength[j] = nomatch==0 ? 0 : 1;
-    }
+  // nqmaxgrpArg
+  if (!isInteger(nqmaxgrpArg) || length(nqmaxgrpArg) != 1 || INTEGER(nqmaxgrpArg)[0] <= 0)
+    error("Intrnal error: nqmaxgrpArg is not a positive length-1 integer vector");
+  nqmaxgrp = INTEGER(nqmaxgrpArg)[0];
+  if (nqmaxgrp>1 && mult == ALL) {
+    // non-equi case with mult=ALL, may need reallocation
+    anslen = 1.1 * ((iN > 1000) ? iN : 1000);
+    retFirst = Calloc(anslen, int); // anslen is set above
+    retLength = Calloc(anslen, int);
+    retIndex = Calloc(anslen, int);
+    if (retFirst==NULL || retLength==NULL || retIndex==NULL)
+      error("Internal error in allocating memory for non-equi join");
+    // initialise retIndex here directly, as next loop is meant for both equi and non-equi joins
+    for (int j=0; j<anslen; j++) retIndex[j] = j+1;
+  } else { // equi joins (or) non-equi join but no multiple matches
+    retFirstArg = PROTECT(allocVector(INTSXP, anslen));
+    retFirst = INTEGER(retFirstArg);
+    retLengthArg = PROTECT(allocVector(INTSXP, anslen)); // TODO: no need to allocate length at all when
+    retLength = INTEGER(retLengthArg);                   // mult = "first" / "last"
+    retIndexArg = PROTECT(allocVector(INTSXP, 0));
+    retIndex = INTEGER(retIndexArg);
+    protecti += 3;
+  }
+  for (int j=0; j<anslen; j++) {
+    // defaults need to populated here as bmerge_r may well not touch many locations, say if the last row of i is before the first row of x.
+    retFirst[j] = nomatch;   // default to no match for NA goto below
+    // retLength[j] = 0;   // TO DO: do this to save the branch below and later branches at R level to set .N to 0
+    retLength[j] = nomatch==0 ? 0 : 1;
+  }
 
-    // allLen1Arg
-    allLen1Arg = PROTECT(allocVector(LGLSXP, 1));
-    allLen1 = LOGICAL(allLen1Arg);
-    allLen1[0] = TRUE;  // All-0 and All-NA are considered all length 1 according to R code currently. Really, it means any(length>1).
+  // allLen1Arg
+  allLen1Arg = PROTECT(allocVector(LGLSXP, 1));
+  allLen1 = LOGICAL(allLen1Arg);
+  allLen1[0] = TRUE;  // All-0 and All-NA are considered all length 1 according to R code currently. Really, it means any(length>1).
 
-    // allGrp1Arg, if TRUE, out of all nested group ids, only one of them matches 'x'. Might be rare, but helps to be more efficient in that case.
-    allGrp1Arg = PROTECT(allocVector(LGLSXP, 1));
-    allGrp1 = LOGICAL(allGrp1Arg);
-    allGrp1[0] = TRUE;
-    protecti += 2;
+  // allGrp1Arg, if TRUE, out of all nested group ids, only one of them matches 'x'. Might be rare, but helps to be more efficient in that case.
+  allGrp1Arg = PROTECT(allocVector(LGLSXP, 1));
+  allGrp1 = LOGICAL(allGrp1Arg);
+  allGrp1[0] = TRUE;
+  protecti += 2;
 
-    // isorted arg
-    o = NULL;
-    if (!LOGICAL(isorted)[0]) {
-        SEXP order = PROTECT(int_vec_init(length(icolsArg), 1)); // rep(1L, length(icolsArg))
-        SEXP oSxp = PROTECT(forder(i, icolsArg, PROTECT(ScalarLogical(FALSE)),
-                            PROTECT(ScalarLogical(TRUE)), order, PROTECT(ScalarLogical(FALSE))));
-        UNPROTECT(3); // The 3 logicals in line above. TODO - split head of forder into C-level callable
-        protecti += 2;   // order and oSxp
-        if (!LENGTH(oSxp)) o = NULL; else o = INTEGER(oSxp);
-    }
+  // isorted arg
+  o = NULL;
+  if (!LOGICAL(isorted)[0]) {
+    SEXP order = PROTECT(int_vec_init(length(icolsArg), 1)); // rep(1L, length(icolsArg))
+    SEXP oSxp = PROTECT(forder(i, icolsArg, PROTECT(ScalarLogical(FALSE)),
+              PROTECT(ScalarLogical(TRUE)), order, PROTECT(ScalarLogical(FALSE))));
+    UNPROTECT(3); // The 3 logicals in line above. TODO - split head of forder into C-level callable
+    protecti += 2;   // order and oSxp
+    if (!LENGTH(oSxp)) o = NULL; else o = INTEGER(oSxp);
+  }
 
-    // xo arg
-    xo = NULL;
-    if (length(xoArg)) {
-        if (!isInteger(xoArg)) error("Internal error: xoArg is not an integer vector");
-        xo = INTEGER(xoArg);
-    }
+  // xo arg
+  xo = NULL;
+  if (length(xoArg)) {
+    if (!isInteger(xoArg)) error("Internal error: xoArg is not an integer vector");
+    xo = INTEGER(xoArg);
+  }
 
-    // start bmerge
-    if (iN) {
-        // embarassingly parallel if we've storage space for nqmaxgrp*iN
-        for (int kk=0; kk<nqmaxgrp; kk++) {
-            bmerge_r(-1,xN,-1,iN,scols,kk+1,1,1);
-        }
-    }
-    ctr += iN;
-    if (nqmaxgrp > 1 && mult == ALL) {
-        // memcpy ret* to SEXP
-        retFirstArg = PROTECT(allocVector(INTSXP, ctr));
-        retLengthArg = PROTECT(allocVector(INTSXP, ctr));
-        retIndexArg = PROTECT(allocVector(INTSXP, ctr));
-        protecti += 3;
-        memcpy(INTEGER(retFirstArg), retFirst, sizeof(int)*ctr);
-        memcpy(INTEGER(retLengthArg), retLength, sizeof(int)*ctr);
-        memcpy(INTEGER(retIndexArg), retIndex, sizeof(int)*ctr);
+  // start bmerge
+  if (iN) {
+    // embarassingly parallel if we've storage space for nqmaxgrp*iN
+    for (int kk=0; kk<nqmaxgrp; kk++) {
+      bmerge_r(-1,xN,-1,iN,scols,kk+1,1,1);
     }
-    SEXP ans = PROTECT(allocVector(VECSXP, 5)); protecti++;
-    SEXP ansnames = PROTECT(allocVector(STRSXP, 5)); protecti++;
-    SET_VECTOR_ELT(ans, 0, retFirstArg);
-    SET_VECTOR_ELT(ans, 1, retLengthArg);
-    SET_VECTOR_ELT(ans, 2, retIndexArg);
-    SET_VECTOR_ELT(ans, 3, allLen1Arg);
-    SET_VECTOR_ELT(ans, 4, allGrp1Arg);
-    SET_STRING_ELT(ansnames, 0, char_starts);  // changed from mkChar to char_ to pass the grep in CRAN_Release.cmd
-    SET_STRING_ELT(ansnames, 1, mkChar("lens"));
-    SET_STRING_ELT(ansnames, 2, mkChar("indices"));
-    SET_STRING_ELT(ansnames, 3, mkChar("allLen1"));
-    SET_STRING_ELT(ansnames, 4, mkChar("allGrp1"));
-    setAttrib(ans, R_NamesSymbol, ansnames);
-    if (nqmaxgrp > 1 && mult == ALL) {
-        Free(retFirst);
-        Free(retLength);
-        Free(retIndex);
-    }
-    UNPROTECT(protecti);
-    return (ans);
+  }
+  ctr += iN;
+  if (nqmaxgrp > 1 && mult == ALL) {
+    // memcpy ret* to SEXP
+    retFirstArg = PROTECT(allocVector(INTSXP, ctr));
+    retLengthArg = PROTECT(allocVector(INTSXP, ctr));
+    retIndexArg = PROTECT(allocVector(INTSXP, ctr));
+    protecti += 3;
+    memcpy(INTEGER(retFirstArg), retFirst, sizeof(int)*ctr);
+    memcpy(INTEGER(retLengthArg), retLength, sizeof(int)*ctr);
+    memcpy(INTEGER(retIndexArg), retIndex, sizeof(int)*ctr);
+  }
+  SEXP ans = PROTECT(allocVector(VECSXP, 5)); protecti++;
+  SEXP ansnames = PROTECT(allocVector(STRSXP, 5)); protecti++;
+  SET_VECTOR_ELT(ans, 0, retFirstArg);
+  SET_VECTOR_ELT(ans, 1, retLengthArg);
+  SET_VECTOR_ELT(ans, 2, retIndexArg);
+  SET_VECTOR_ELT(ans, 3, allLen1Arg);
+  SET_VECTOR_ELT(ans, 4, allGrp1Arg);
+  SET_STRING_ELT(ansnames, 0, char_starts);  // changed from mkChar to char_ to pass the grep in CRAN_Release.cmd
+  SET_STRING_ELT(ansnames, 1, mkChar("lens"));
+  SET_STRING_ELT(ansnames, 2, mkChar("indices"));
+  SET_STRING_ELT(ansnames, 3, mkChar("allLen1"));
+  SET_STRING_ELT(ansnames, 4, mkChar("allGrp1"));
+  setAttrib(ans, R_NamesSymbol, ansnames);
+  if (nqmaxgrp > 1 && mult == ALL) {
+    Free(retFirst);
+    Free(retLength);
+    Free(retIndex);
+  }
+  UNPROTECT(protecti);
+  return (ans);
 }
 
 static union {
@@ -213,9 +213,9 @@ static SEXP ic, xc;
 // Fix for #66, #69, #469 and #1293
 // TODO: compare 1.9.6 performance with 1.9.7 with huge number of ASCII strings.
 SEXP ENC2UTF8(SEXP s) {
-    if (!IS_ASCII(s) && s != NA_STRING && !IS_UTF8(s))
-        s = mkCharCE(translateCharUTF8(s), CE_UTF8);
-    return (s);
+  if (!IS_ASCII(s) && s != NA_STRING && !IS_UTF8(s))
+    s = mkCharCE(translateCharUTF8(s), CE_UTF8);
+  return (s);
 }
 
 void bmerge_r(int xlowIn, int xuppIn, int ilowIn, int iuppIn, int col, int thisgrp, int lowmax, int uppmax)
@@ -224,328 +224,329 @@ void bmerge_r(int xlowIn, int xuppIn, int ilowIn, int iuppIn, int col, int thisg
 // uppmax=1 if xuppIn is the upper bound of this group (needed for roll)
 // new: col starts with -1 for non-equi joins, which gathers rows from nested id group counter 'thisgrp'
 {
-    int xlow=xlowIn, xupp=xuppIn, ilow=ilowIn, iupp=iuppIn, j, k, ir, lir, tmp;
-    Rboolean isInt64=FALSE;
-    ir = lir = ilow + (iupp-ilow)/2;           // lir = logical i row.
-    if (o) ir = o[lir]-1;                      // ir = the actual i row if i were ordered
-    if (col>-1) {
-        ic = VECTOR_ELT(i,icols[col]-1);  // ic = i column
-        xc = VECTOR_ELT(x,xcols[col]-1);  // xc = x column
+  int xlow=xlowIn, xupp=xuppIn, ilow=ilowIn, iupp=iuppIn, j, k, ir, lir, tmp;
+  Rboolean isInt64=FALSE;
+  ir = lir = ilow + (iupp-ilow)/2;           // lir = logical i row.
+  if (o) ir = o[lir]-1;                      // ir = the actual i row if i were ordered
+  if (col>-1) {
+    ic = VECTOR_ELT(i,icols[col]-1);  // ic = i column
+    xc = VECTOR_ELT(x,xcols[col]-1);  // xc = x column
     // it was checked in bmerge() that the types are equal
-    } else xc = nqgrp;
-    switch (TYPEOF(xc)) {
-    case LGLSXP : case INTSXP :   // including factors
-        ival.i = (col>-1) ? INTEGER(ic)[ir] : thisgrp;
-        while(xlow < xupp-1) {
-            mid = xlow + (xupp-xlow)/2;   // Same as (xlow+xupp)/2 but without risk of overflow
-            xval.i = INTEGER(xc)[XIND(mid)];
-            if (xval.i<ival.i) {          // relies on NA_INTEGER == INT_MIN, tested in init.c
-                xlow=mid;
-            } else if (xval.i>ival.i) {   // TO DO: is *(&xlow, &xupp)[0|1]=mid more efficient than branch?
-                xupp=mid;
-            } else {
-                // xval.i == ival.i  including NA_INTEGER==NA_INTEGER
-                // branch mid to find start and end of this group in this column
-                // TO DO?: not if mult=first|last and col<ncol-1
-                tmplow = mid;
-                tmpupp = mid;
-                while(tmplow<xupp-1) {
-                    mid = tmplow + (xupp-tmplow)/2;
-                    xval.i = INTEGER(xc)[XIND(mid)];
-                    if (xval.i == ival.i) tmplow=mid; else xupp=mid;
-                }
-                while(xlow<tmpupp-1) {
-                    mid = xlow + (tmpupp-xlow)/2;
-                    xval.i = INTEGER(xc)[XIND(mid)];
-                    if (xval.i == ival.i) tmpupp=mid; else xlow=mid;
-                }
-                // xlow and xupp now surround the group in xc, we only need this range for the next column
-                break;
-            }
+  } else xc = nqgrp;
+  switch (TYPEOF(xc)) {
+  case LGLSXP : case INTSXP :   // including factors
+    ival.i = (col>-1) ? INTEGER(ic)[ir] : thisgrp;
+    while(xlow < xupp-1) {
+      mid = xlow + (xupp-xlow)/2;   // Same as (xlow+xupp)/2 but without risk of overflow
+      xval.i = INTEGER(xc)[XIND(mid)];
+      if (xval.i<ival.i) {          // relies on NA_INTEGER == INT_MIN, tested in init.c
+        xlow=mid;
+      } else if (xval.i>ival.i) {   // TO DO: is *(&xlow, &xupp)[0|1]=mid more efficient than branch?
+        xupp=mid;
+      } else {
+        // xval.i == ival.i  including NA_INTEGER==NA_INTEGER
+        // branch mid to find start and end of this group in this column
+        // TO DO?: not if mult=first|last and col<ncol-1
+        tmplow = mid;
+        tmpupp = mid;
+        while(tmplow<xupp-1) {
+          mid = tmplow + (xupp-tmplow)/2;
+          xval.i = INTEGER(xc)[XIND(mid)];
+          if (xval.i == ival.i) tmplow=mid; else xupp=mid;
         }
-        if (col>-1 && op[col] != EQ) {
-            switch (op[col]) {
-                case LE : xlow = xlowIn; break;
-                case LT : xupp = xlow + 1; xlow = xlowIn; break;
-                case GE : if (ival.i != NA_INTEGER) xupp = xuppIn; break;
-                case GT : xlow = xupp - 1; if (ival.i != NA_INTEGER) xupp = xuppIn; break;
-            }
-            // for LE/LT cases, we need to ensure xlow excludes NA indices, != EQ is checked above already
-            if (op[col] <= 3 && xlow<xupp-1 && ival.i != NA_INTEGER && INTEGER(xc)[XIND(xlow+1)] == NA_INTEGER) {
-                tmplow = xlow; tmpupp = xupp;
-                while (tmplow < tmpupp-1) {
-                    mid = tmplow + (tmpupp-tmplow)/2;
-                    xval.i = INTEGER(xc)[XIND(mid)];
-                    if (xval.i == NA_INTEGER) tmplow = mid; else tmpupp = mid;
-                }
-                xlow = tmplow; // tmplow is the index of last NA value
-            }
-        }
-        tmplow = lir;
-        tmpupp = lir;
-        if (col>-1) {
-            while(tmplow<iupp-1) {   // TO DO: could double up from lir rather than halving from iupp
-                mid = tmplow + (iupp-tmplow)/2;
-                xval.i = INTEGER(ic)[ o ? o[mid]-1 : mid ];   // reuse xval to search in i
-                if (xval.i == ival.i) tmplow=mid; else iupp=mid;
-                // if we could guarantee ivals to be *always* sorted for all columns independently (= max(nestedid) = 1), then we can speed this up by 2x by adding checks for GE,GT,LE,LT separately.
-            }
-            while(ilow<tmpupp-1) {
-                mid = ilow + (tmpupp-ilow)/2;
-                xval.i = INTEGER(ic)[ o ? o[mid]-1 : mid ];
-                if (xval.i == ival.i) tmpupp=mid; else ilow=mid;
-            }
+        while(xlow<tmpupp-1) {
+          mid = xlow + (tmpupp-xlow)/2;
+          xval.i = INTEGER(xc)[XIND(mid)];
+          if (xval.i == ival.i) tmpupp=mid; else xlow=mid;
         }
-        // ilow and iupp now surround the group in ic, too
+        // xlow and xupp now surround the group in xc, we only need this range for the next column
         break;
-    case STRSXP :
-        if (op[col] != EQ) error("Only '==' operator is supported for columns of type %s.", type2char(TYPEOF(xc)));
-        ival.s = ENC2UTF8(STRING_ELT(ic,ir));
-        while(xlow < xupp-1) {
-            mid = xlow + (xupp-xlow)/2;
-            xval.s = ENC2UTF8(STRING_ELT(xc, XIND(mid)));
-            tmp = StrCmp(xval.s, ival.s);  // uses pointer equality first, NA_STRING are allowed and joined to, then uses strcmp on CHAR().
-            if (tmp == 0) {                // TO DO: deal with mixed encodings and locale optionally
-                tmplow = mid;
-                tmpupp = mid;
-                while(tmplow<xupp-1) {
-                    mid = tmplow + (xupp-tmplow)/2;
-                    xval.s = ENC2UTF8(STRING_ELT(xc, XIND(mid)));
-                    if (ival.s == xval.s) tmplow=mid; else xupp=mid;  // the == here handles encodings as well. Marked non-utf8 encodings are converted to utf-8 using ENC2UTF8.
-                }
-                while(xlow<tmpupp-1) {
-                    mid = xlow + (tmpupp-xlow)/2;
-                    xval.s = ENC2UTF8(STRING_ELT(xc, XIND(mid)));
-                    if (ival.s == xval.s) tmpupp=mid; else xlow=mid;  // see above re ==
-                }
-                break;
-            } else if (tmp < 0) {
-                xlow=mid;
-            } else {
-                xupp=mid;
-            }
+      }
+    }
+    if (col>-1 && op[col] != EQ) {
+      switch (op[col]) {
+        case LE : xlow = xlowIn; break;
+        case LT : xupp = xlow + 1; xlow = xlowIn; break;
+        case GE : if (ival.i != NA_INTEGER) xupp = xuppIn; break;
+        case GT : xlow = xupp - 1; if (ival.i != NA_INTEGER) xupp = xuppIn; break;
+      }
+      // for LE/LT cases, we need to ensure xlow excludes NA indices, != EQ is checked above already
+      if (op[col] <= 3 && xlow<xupp-1 && ival.i != NA_INTEGER && INTEGER(xc)[XIND(xlow+1)] == NA_INTEGER) {
+        tmplow = xlow; tmpupp = xupp;
+        while (tmplow < tmpupp-1) {
+          mid = tmplow + (tmpupp-tmplow)/2;
+          xval.i = INTEGER(xc)[XIND(mid)];
+          if (xval.i == NA_INTEGER) tmplow = mid; else tmpupp = mid;
         }
-        tmplow = lir;
-        tmpupp = lir;
-        while(tmplow<iupp-1) {
-            mid = tmplow + (iupp-tmplow)/2;
-            xval.s = ENC2UTF8(STRING_ELT(ic, o ? o[mid]-1 : mid));
-            if (xval.s == ival.s) tmplow=mid; else iupp=mid;   // see above re ==
+        xlow = tmplow; // tmplow is the index of last NA value
+      }
+    }
+    tmplow = lir;
+    tmpupp = lir;
+    if (col>-1) {
+      while(tmplow<iupp-1) {   // TO DO: could double up from lir rather than halving from iupp
+        mid = tmplow + (iupp-tmplow)/2;
+        xval.i = INTEGER(ic)[ o ? o[mid]-1 : mid ];   // reuse xval to search in i
+        if (xval.i == ival.i) tmplow=mid; else iupp=mid;
+        // if we could guarantee ivals to be *always* sorted for all columns independently (= max(nestedid) = 1), then we can speed this up by 2x by adding checks for GE,GT,LE,LT separately.
+      }
+      while(ilow<tmpupp-1) {
+        mid = ilow + (tmpupp-ilow)/2;
+        xval.i = INTEGER(ic)[ o ? o[mid]-1 : mid ];
+        if (xval.i == ival.i) tmpupp=mid; else ilow=mid;
+      }
+    }
+    // ilow and iupp now surround the group in ic, too
+    break;
+  case STRSXP :
+    if (op[col] != EQ) error("Only '==' operator is supported for columns of type %s.", type2char(TYPEOF(xc)));
+    ival.s = ENC2UTF8(STRING_ELT(ic,ir));
+    while(xlow < xupp-1) {
+      mid = xlow + (xupp-xlow)/2;
+      xval.s = ENC2UTF8(STRING_ELT(xc, XIND(mid)));
+      tmp = StrCmp(xval.s, ival.s);  // uses pointer equality first, NA_STRING are allowed and joined to, then uses strcmp on CHAR().
+      if (tmp == 0) {                // TO DO: deal with mixed encodings and locale optionally
+        tmplow = mid;
+        tmpupp = mid;
+        while(tmplow<xupp-1) {
+          mid = tmplow + (xupp-tmplow)/2;
+          xval.s = ENC2UTF8(STRING_ELT(xc, XIND(mid)));
+          if (ival.s == xval.s) tmplow=mid; else xupp=mid;  // the == here handles encodings as well. Marked non-utf8 encodings are converted to utf-8 using ENC2UTF8.
         }
-        while(ilow<tmpupp-1) {
-            mid = ilow + (tmpupp-ilow)/2;
-            xval.s = ENC2UTF8(STRING_ELT(ic, o ? o[mid]-1 : mid));
-            if (xval.s == ival.s) tmpupp=mid; else ilow=mid;   // see above re ==
+        while(xlow<tmpupp-1) {
+          mid = xlow + (tmpupp-xlow)/2;
+          xval.s = ENC2UTF8(STRING_ELT(xc, XIND(mid)));
+          if (ival.s == xval.s) tmpupp=mid; else xlow=mid;  // see above re ==
         }
         break;
-    case REALSXP :
-        isInt64 = INHERITS(xc, char_integer64);
-        twiddle = isInt64 ? &i64twiddle : &dtwiddle;
-        ival.ull = twiddle(DATAPTR(ic), ir, 1);
-        while(xlow < xupp-1) {
-            mid = xlow + (xupp-xlow)/2;
-            xval.ull = twiddle(DATAPTR(xc), XIND(mid), 1);
-            if (xval.ull<ival.ull) {
-                xlow=mid;
-            } else if (xval.ull>ival.ull) {
-                xupp=mid;
-            } else { // xval.ull == ival.ull)
-                tmplow = mid;
-                tmpupp = mid;
-                while(tmplow<xupp-1) {
-                    mid = tmplow + (xupp-tmplow)/2;
-                    xval.ull = twiddle(DATAPTR(xc), XIND(mid), 1);
-                    if (xval.ull == ival.ull) tmplow=mid; else xupp=mid;
-                }
-                while(xlow<tmpupp-1) {
-                    mid = xlow + (tmpupp-xlow)/2;
-                    xval.ull = twiddle(DATAPTR(xc), XIND(mid), 1);
-                    if (xval.ull == ival.ull) tmpupp=mid; else xlow=mid;
-                }
-                break;
-            }
-        }
-        if (col>-1 && op[col] != EQ) {
-            Rboolean isivalNA = !isInt64 ? ISNAN(REAL(ic)[ir]) : (DtoLL(REAL(ic)[ir]) == NA_INT64_LL);
-            switch (op[col]) {
-            case LE : if (!isivalNA) xlow = xlowIn; break;
-            case LT : xupp = xlow + 1; if (!isivalNA) xlow = xlowIn; break;
-            case GE : if (!isivalNA) xupp = xuppIn; break;
-            case GT : xlow = xupp - 1; if (!isivalNA) xupp = xuppIn; break;
-            }
-            // for LE/LT cases, we need to ensure xlow excludes NA indices, != EQ is checked above already
-            if (op[col] <= 3 && xlow<xupp-1 && !isivalNA && (!isInt64 ? ISNAN(REAL(xc)[XIND(xlow+1)]) : (DtoLL(REAL(xc)[XIND(xlow+1)]) == NA_INT64_LL))) {
-                tmplow = xlow; tmpupp = xupp;
-                while (tmplow < tmpupp-1) {
-                    mid = tmplow + (tmpupp-tmplow)/2;
-                    xval.d = REAL(xc)[XIND(mid)];
-                    if (!isInt64 ? ISNAN(xval.d) : xval.ll == NA_INT64_LL) tmplow = mid; else tmpupp = mid;
-                }
-                xlow = tmplow; // tmplow is the index of last NA value
-            }
+      } else if (tmp < 0) {
+        xlow=mid;
+      } else {
+        xupp=mid;
+      }
+    }
+    tmplow = lir;
+    tmpupp = lir;
+    while(tmplow<iupp-1) {
+      mid = tmplow + (iupp-tmplow)/2;
+      xval.s = ENC2UTF8(STRING_ELT(ic, o ? o[mid]-1 : mid));
+      if (xval.s == ival.s) tmplow=mid; else iupp=mid;   // see above re ==
+    }
+    while(ilow<tmpupp-1) {
+      mid = ilow + (tmpupp-ilow)/2;
+      xval.s = ENC2UTF8(STRING_ELT(ic, o ? o[mid]-1 : mid));
+      if (xval.s == ival.s) tmpupp=mid; else ilow=mid;   // see above re ==
+    }
+    break;
+  case REALSXP :
+    isInt64 = INHERITS(xc, char_integer64);
+    twiddle = isInt64 ? &i64twiddle : &dtwiddle;
+    ival.ull = twiddle(DATAPTR(ic), ir, 1);
+    while(xlow < xupp-1) {
+      mid = xlow + (xupp-xlow)/2;
+      xval.ull = twiddle(DATAPTR(xc), XIND(mid), 1);
+      if (xval.ull<ival.ull) {
+        xlow=mid;
+      } else if (xval.ull>ival.ull) {
+        xupp=mid;
+      } else { // xval.ull == ival.ull)
+        tmplow = mid;
+        tmpupp = mid;
+        while(tmplow<xupp-1) {
+          mid = tmplow + (xupp-tmplow)/2;
+          xval.ull = twiddle(DATAPTR(xc), XIND(mid), 1);
+          if (xval.ull == ival.ull) tmplow=mid; else xupp=mid;
         }
-        tmplow = lir;
-        tmpupp = lir;
-        if (col>-1) {
-            while(tmplow<iupp-1) {
-                mid = tmplow + (iupp-tmplow)/2;
-                xval.ull = twiddle(DATAPTR(ic), o ? o[mid]-1 : mid, 1 );
-                if (xval.ull == ival.ull) tmplow=mid; else iupp=mid;
-            }
-            while(ilow<tmpupp-1) {
-                mid = ilow + (tmpupp-ilow)/2;
-                xval.ull = twiddle(DATAPTR(ic), o ? o[mid]-1 : mid, 1 );
-                if (xval.ull == ival.ull) tmpupp=mid; else ilow=mid;
-            }
+        while(xlow<tmpupp-1) {
+          mid = xlow + (tmpupp-xlow)/2;
+          xval.ull = twiddle(DATAPTR(xc), XIND(mid), 1);
+          if (xval.ull == ival.ull) tmpupp=mid; else xlow=mid;
         }
-        // ilow and iupp now surround the group in ic, too
         break;
-    default:
-        error("Type '%s' not supported as key column", type2char(TYPEOF(xc)));
+      }
     }
-    if (xlow<xupp-1) { // if value found, low and upp surround it, unlike standard binary search where low falls on it
-        if (col<ncol-1) {
-            bmerge_r(xlow, xupp, ilow, iupp, col+1, thisgrp, 1, 1);
-            // final two 1's are lowmax and uppmax
-        } else {
-            int len = xupp-xlow-1;
-            if (mult==ALL && len>1) allLen1[0] = FALSE;
-            if (nqmaxgrp == 1) {
-                for (j=ilow+1; j<iupp; j++) {   // usually iterates once only for j=ir
-                    k = o ? o[j]-1 : j;
-                    retFirst[k] = (mult != LAST) ? xlow+2 : xupp; // extra +1 for 1-based indexing at R level
-                    retLength[k]= (mult == ALL) ? len : 1;
-                    // retIndex initialisation is taken care of in bmerge and doesn't change for thisgrp=1
-                }
-            } else {
-                // non-equi join
-                for (j=ilow+1; j<iupp; j++) {
-                    k = o ? o[j]-1 : j;
-                    if (retFirst[k] != nomatch) {
-                        if (mult == ALL) {
-                            // for this irow, we've matches on more than one group
-                            allGrp1[0] = FALSE;
-                            retFirst[ctr+ilen] = xlow+2;
-                            retLength[ctr+ilen] = len;
-                            retIndex[ctr+ilen] = k+1;
-                            ++ctr;
-                            if (ctr+ilen >= anslen) {
-                                anslen = 1.1*anslen;
-                                tmpptr = Realloc(retFirst, anslen, int);
-                                if (tmpptr != NULL) retFirst = tmpptr;
-                                else error("Error in reallocating memory in non-equi joins.\n");
-                                tmpptr = Realloc(retLength, anslen, int);
-                                if (tmpptr != NULL) retLength = tmpptr;
-                                else error("Error in reallocating memory in non-equi joins.\n");
-                                tmpptr = Realloc(retIndex, anslen, int);
-                                if (tmpptr != NULL) retIndex = tmpptr;
-                                else error("Error in reallocating memory in non-equi joins.\n");
-                            }
-                        } else if (mult == FIRST) {
-                            retFirst[k] = (XIND(retFirst[k]-1) > XIND(xlow+1)) ? xlow+2 : retFirst[k];
-                            retLength[k] = 1;
-                        } else {
-                            retFirst[k] = (XIND(retFirst[k]-1) < XIND(xupp-1)) ? xupp : retFirst[k];
-                            retLength[k] = 1;
-                        }
-                    } else {
-                        // none of the groups so far have filled in for this index. So use it!
-                        if (mult == ALL) {
-                            retFirst[k] = xlow+2;
-                            retLength[k] = len;
-                            retIndex[k] = k+1;
-                            // no need to increment ctr of course
-                        } else {
-                            retFirst[k] = (mult == FIRST) ? xlow+2 : xupp;
-                            retLength[k] = 1;
-                        }
-                    }
-                }
-            }
+    if (col>-1 && op[col] != EQ) {
+      Rboolean isivalNA = !isInt64 ? ISNAN(REAL(ic)[ir]) : (DtoLL(REAL(ic)[ir]) == NA_INT64_LL);
+      switch (op[col]) {
+      case LE : if (!isivalNA) xlow = xlowIn; break;
+      case LT : xupp = xlow + 1; if (!isivalNA) xlow = xlowIn; break;
+      case GE : if (!isivalNA) xupp = xuppIn; break;
+      case GT : xlow = xupp - 1; if (!isivalNA) xupp = xuppIn; break;
+      }
+      // for LE/LT cases, we need to ensure xlow excludes NA indices, != EQ is checked above already
+      if (op[col] <= 3 && xlow<xupp-1 && !isivalNA && (!isInt64 ? ISNAN(REAL(xc)[XIND(xlow+1)]) : (DtoLL(REAL(xc)[XIND(xlow+1)]) == NA_INT64_LL))) {
+        tmplow = xlow; tmpupp = xupp;
+        while (tmplow < tmpupp-1) {
+          mid = tmplow + (tmpupp-tmplow)/2;
+          xval.d = REAL(xc)[XIND(mid)];
+          if (!isInt64 ? ISNAN(xval.d) : xval.ll == NA_INT64_LL) tmplow = mid; else tmpupp = mid;
         }
-    } else if (roll!=0.0 && col==ncol-1) {
-        // runs once per i row (not each search test), so not hugely time critical
-        if (xlow != xupp-1 || xlow<xlowIn || xupp>xuppIn) error("Internal error: xlow!=xupp-1 || xlow<xlowIn || xupp>xuppIn");
-        if (rollToNearest) {   // value of roll ignored currently when nearest
-            if ( (!lowmax || xlow>xlowIn) && (!uppmax || xupp<xuppIn) ) {
-                if (  ( TYPEOF(ic)==REALSXP && REAL(ic)[ir]-REAL(xc)[XIND(xlow)] <= REAL(xc)[XIND(xupp)]-REAL(ic)[ir] )
-                   || ( TYPEOF(ic)<=INTSXP && INTEGER(ic)[ir]-INTEGER(xc)[XIND(xlow)] <= INTEGER(xc)[XIND(xupp)]-INTEGER(ic)[ir] )) {
-                    retFirst[ir] = xlow+1;
-                    retLength[ir] = 1;
-                } else {
-                    retFirst[ir] = xupp+1;
-                    retLength[ir] = 1;
-                }
-            } else if (uppmax && xupp==xuppIn && rollends[1]) {
-                retFirst[ir] = xlow+1;
-                retLength[ir] = 1;
-            } else if (lowmax && xlow==xlowIn && rollends[0]) {
-                retFirst[ir] = xupp+1;
-                retLength[ir] = 1;
+        xlow = tmplow; // tmplow is the index of last NA value
+      }
+    }
+    tmplow = lir;
+    tmpupp = lir;
+    if (col>-1) {
+      while(tmplow<iupp-1) {
+        mid = tmplow + (iupp-tmplow)/2;
+        xval.ull = twiddle(DATAPTR(ic), o ? o[mid]-1 : mid, 1 );
+        if (xval.ull == ival.ull) tmplow=mid; else iupp=mid;
+      }
+      while(ilow<tmpupp-1) {
+        mid = ilow + (tmpupp-ilow)/2;
+        xval.ull = twiddle(DATAPTR(ic), o ? o[mid]-1 : mid, 1 );
+        if (xval.ull == ival.ull) tmpupp=mid; else ilow=mid;
+      }
+    }
+    // ilow and iupp now surround the group in ic, too
+    break;
+  default:
+    error("Type '%s' not supported as key column", type2char(TYPEOF(xc)));
+  }
+  if (xlow<xupp-1) { // if value found, low and upp surround it, unlike standard binary search where low falls on it
+    if (col<ncol-1) {
+      bmerge_r(xlow, xupp, ilow, iupp, col+1, thisgrp, 1, 1);
+      // final two 1's are lowmax and uppmax
+    } else {
+      int len = xupp-xlow-1;
+      if (mult==ALL && len>1) allLen1[0] = FALSE;
+      if (nqmaxgrp == 1) {
+        for (j=ilow+1; j<iupp; j++) {   // usually iterates once only for j=ir
+          k = o ? o[j]-1 : j;
+          retFirst[k] = (mult != LAST) ? xlow+2 : xupp; // extra +1 for 1-based indexing at R level
+          retLength[k]= (mult == ALL) ? len : 1;
+          // retIndex initialisation is taken care of in bmerge and doesn't change for thisgrp=1
+        }
+      } else {
+        // non-equi join
+        for (j=ilow+1; j<iupp; j++) {
+          k = o ? o[j]-1 : j;
+          if (retFirst[k] != nomatch) {
+            if (mult == ALL) {
+              // for this irow, we've matches on more than one group
+              allGrp1[0] = FALSE;
+              retFirst[ctr+ilen] = xlow+2;
+              retLength[ctr+ilen] = len;
+              retIndex[ctr+ilen] = k+1;
+              ++ctr;
+              if (ctr+ilen >= anslen) {
+                anslen = 1.1*anslen;
+                tmpptr = Realloc(retFirst, anslen, int);
+                if (tmpptr != NULL) retFirst = tmpptr;
+                else error("Error in reallocating memory in non-equi joins.\n");
+                tmpptr = Realloc(retLength, anslen, int);
+                if (tmpptr != NULL) retLength = tmpptr;
+                else error("Error in reallocating memory in non-equi joins.\n");
+                tmpptr = Realloc(retIndex, anslen, int);
+                if (tmpptr != NULL) retIndex = tmpptr;
+                else error("Error in reallocating memory in non-equi joins.\n");
+              }
+            } else if (mult == FIRST) {
+              retFirst[k] = (XIND(retFirst[k]-1) > XIND(xlow+1)) ? xlow+2 : retFirst[k];
+              retLength[k] = 1;
+            } else {
+              retFirst[k] = (XIND(retFirst[k]-1) < XIND(xupp-1)) ? xupp : retFirst[k];
+              retLength[k] = 1;
             }
-        } else {
-            // Regular roll=TRUE|+ve|-ve
-            // Fixed issues: #1405, #1650, #1007
-            // TODO: incorporate the twiddle logic for roll as well instead of tolerance?
-            if ( (   (roll>0.0 && (!lowmax || xlow>xlowIn) && (xupp<xuppIn || !uppmax || rollends[1]))
-                  || (roll<0.0 && xupp==xuppIn && uppmax && rollends[1]) )
-              && (   (TYPEOF(ic)==REALSXP &&
-                      (ival.d = REAL(ic)[ir], xval.d = REAL(xc)[XIND(xlow)], 1) &&
-                     (( !isInt64 &&
-                        (ival.d-xval.d-rollabs < 1e-6 ||
-                         ival.d-xval.d == rollabs /*#1007*/))
-                   || ( isInt64 &&
-                        (double)(ival.ll-xval.ll)-rollabs < 1e-6 ) ))  // cast to double for when rollabs==Inf
-                  || (TYPEOF(ic)<=INTSXP && (double)(INTEGER(ic)[ir]-INTEGER(xc)[XIND(xlow)])-rollabs < 1e-6 )
-                  || (TYPEOF(ic)==STRSXP)   )) {
-                retFirst[ir] = xlow+1;
-                retLength[ir] = 1;
-            } else if
-               (  (  (roll<0.0 && (!uppmax || xupp<xuppIn) && (xlow>xlowIn || !lowmax || rollends[0]))
-                  || (roll>0.0 && xlow==xlowIn && lowmax && rollends[0]) )
-              && (   (TYPEOF(ic)==REALSXP &&
-                      (ival.d = REAL(ic)[ir], xval.d = REAL(xc)[XIND(xupp)], 1) &&
-                     (( !isInt64 &&
-                        (xval.d-ival.d-rollabs < 1e-6 ||
-                         xval.d-ival.d == rollabs /*#1007*/))
-                   || ( isInt64 &&
-                        (double)(xval.ll-ival.ll)-rollabs < 1e-6 ) ))
-                  || (TYPEOF(ic)<=INTSXP && (double)(INTEGER(xc)[XIND(xupp)]-INTEGER(ic)[ir])-rollabs < 1e-6 )
-                  || (TYPEOF(ic)==STRSXP)   )) {
-                retFirst[ir] = xupp+1;   // == xlow+2
-                retLength[ir] = 1;
+          } else {
+            // none of the groups so far have filled in for this index. So use it!
+            if (mult == ALL) {
+              retFirst[k] = xlow+2;
+              retLength[k] = len;
+              retIndex[k] = k+1;
+              // no need to increment ctr of course
+            } else {
+              retFirst[k] = (mult == FIRST) ? xlow+2 : xupp;
+              retLength[k] = 1;
             }
+          }
         }
-        if (iupp-ilow > 2 && retFirst[ir]!=NA_INTEGER) {
-            // >=2 equal values in the last column being rolling to the same point.
-            for (j=ilow+1; j<iupp; j++) {
-                // will rewrite retFirst[ir] to itself, but that's ok
-                if (o) k=o[j]-1; else k=j;
-                retFirst[k] = retFirst[ir];
-                retLength[k]= retLength[ir];
-            }
+      }
+    }
+  } else if (roll!=0.0 && col==ncol-1) {
+    // runs once per i row (not each search test), so not hugely time critical
+    if (xlow != xupp-1 || xlow<xlowIn || xupp>xuppIn) error("Internal error: xlow!=xupp-1 || xlow<xlowIn || xupp>xuppIn");
+    if (rollToNearest) {   // value of roll ignored currently when nearest
+      if ( (!lowmax || xlow>xlowIn) && (!uppmax || xupp<xuppIn) ) {
+        if (  ( TYPEOF(ic)==REALSXP && REAL(ic)[ir]-REAL(xc)[XIND(xlow)] <= REAL(xc)[XIND(xupp)]-REAL(ic)[ir] )
+           || ( TYPEOF(ic)<=INTSXP && INTEGER(ic)[ir]-INTEGER(xc)[XIND(xlow)] <= INTEGER(xc)[XIND(xupp)]-INTEGER(ic)[ir] )) {
+          retFirst[ir] = xlow+1;
+          retLength[ir] = 1;
+        } else {
+          retFirst[ir] = xupp+1;
+          retLength[ir] = 1;
         }
+      } else if (uppmax && xupp==xuppIn && rollends[1]) {
+        retFirst[ir] = xlow+1;
+        retLength[ir] = 1;
+      } else if (lowmax && xlow==xlowIn && rollends[0]) {
+        retFirst[ir] = xupp+1;
+        retLength[ir] = 1;
+      }
+    } else {
+      // Regular roll=TRUE|+ve|-ve
+      // Fixed issues: #1405, #1650, #1007
+      // TODO: incorporate the twiddle logic for roll as well instead of tolerance?
+      if ( (   (roll>0.0 && (!lowmax || xlow>xlowIn) && (xupp<xuppIn || !uppmax || rollends[1]))
+            || (roll<0.0 && xupp==xuppIn && uppmax && rollends[1]) )
+        && ( (TYPEOF(ic)==REALSXP &&
+             (ival.d = REAL(ic)[ir], xval.d = REAL(xc)[XIND(xlow)], 1) &&
+             (( !isInt64 &&
+                (ival.d-xval.d-rollabs < 1e-6 ||
+                 ival.d-xval.d == rollabs /*#1007*/))
+              || ( isInt64 &&
+                  (double)(ival.ll-xval.ll)-rollabs < 1e-6 ) ))  // cast to double for when rollabs==Inf
+              || (TYPEOF(ic)<=INTSXP && (double)(INTEGER(ic)[ir]-INTEGER(xc)[XIND(xlow)])-rollabs < 1e-6 )
+              || (TYPEOF(ic)==STRSXP)   )) {
+        retFirst[ir] = xlow+1;
+        retLength[ir] = 1;
+      } else if
+         (  (  (roll<0.0 && (!uppmax || xupp<xuppIn) && (xlow>xlowIn || !lowmax || rollends[0]))
+            || (roll>0.0 && xlow==xlowIn && lowmax && rollends[0]) )
+        && (   (TYPEOF(ic)==REALSXP &&
+               (ival.d = REAL(ic)[ir], xval.d = REAL(xc)[XIND(xupp)], 1) &&
+               (( !isInt64 &&
+                  (xval.d-ival.d-rollabs < 1e-6 ||
+                   xval.d-ival.d == rollabs /*#1007*/))
+                || ( isInt64 &&
+                     (double)(xval.ll-ival.ll)-rollabs < 1e-6 ) ))
+                || (TYPEOF(ic)<=INTSXP && (double)(INTEGER(xc)[XIND(xupp)]-INTEGER(ic)[ir])-rollabs < 1e-6 )
+                || (TYPEOF(ic)==STRSXP)   )) {
+        retFirst[ir] = xupp+1;   // == xlow+2
+        retLength[ir] = 1;
+      }
     }
-    switch (op[col]) {
-    case EQ:
-        if (ilow>ilowIn && (xlow>xlowIn || ((roll!=0.0 || op[col] != EQ) && col==ncol-1)))
-            bmerge_r(xlowIn, xlow+1, ilowIn, ilow+1, col, 1, lowmax, uppmax && xlow+1==xuppIn);
-        if (iupp<iuppIn && (xupp<xuppIn || ((roll!=0.0 || op[col] != EQ) && col==ncol-1)))
-            bmerge_r(xupp-1, xuppIn, iupp-1, iuppIn, col, 1, lowmax && xupp-1==xlowIn, uppmax);
+    if (iupp-ilow > 2 && retFirst[ir]!=NA_INTEGER) {
+      // >=2 equal values in the last column being rolling to the same point.
+      for (j=ilow+1; j<iupp; j++) {
+        // will rewrite retFirst[ir] to itself, but that's ok
+        if (o) k=o[j]-1; else k=j;
+        retFirst[k] = retFirst[ir];
+        retLength[k]= retLength[ir];
+      }
+    }
+  }
+  switch (op[col]) {
+  case EQ:
+    if (ilow>ilowIn && (xlow>xlowIn || ((roll!=0.0 || op[col] != EQ) && col==ncol-1)))
+      bmerge_r(xlowIn, xlow+1, ilowIn, ilow+1, col, 1, lowmax, uppmax && xlow+1==xuppIn);
+    if (iupp<iuppIn && (xupp<xuppIn || ((roll!=0.0 || op[col] != EQ) && col==ncol-1)))
+      bmerge_r(xupp-1, xuppIn, iupp-1, iuppIn, col, 1, lowmax && xupp-1==xlowIn, uppmax);
     break;
-    case LE: case LT:
-        // roll is not yet implemented
-        if (ilow>ilowIn)
-            bmerge_r(xlowIn, xuppIn, ilowIn, ilow+1, col, 1, lowmax, uppmax && xlow+1==xuppIn);
-        if (iupp<iuppIn)
-            bmerge_r(xlowIn, xuppIn, iupp-1, iuppIn, col, 1, lowmax && xupp-1==xlowIn, uppmax);
+  case LE: case LT:
+    // roll is not yet implemented
+    if (ilow>ilowIn)
+      bmerge_r(xlowIn, xuppIn, ilowIn, ilow+1, col, 1, lowmax, uppmax && xlow+1==xuppIn);
+    if (iupp<iuppIn)
+      bmerge_r(xlowIn, xuppIn, iupp-1, iuppIn, col, 1, lowmax && xupp-1==xlowIn, uppmax);
     break;
-    case GE: case GT:
-        // roll is not yet implemented
-        if (ilow>ilowIn)
-            bmerge_r(xlowIn, xuppIn, ilowIn, ilow+1, col, 1, lowmax, uppmax && xlow+1==xuppIn);
-        if (iupp<iuppIn)
-            bmerge_r(xlowIn, xuppIn, iupp-1, iuppIn, col, 1, lowmax && xupp-1==xlowIn, uppmax);
+  case GE: case GT:
+    // roll is not yet implemented
+    if (ilow>ilowIn)
+      bmerge_r(xlowIn, xuppIn, ilowIn, ilow+1, col, 1, lowmax, uppmax && xlow+1==xuppIn);
+    if (iupp<iuppIn)
+      bmerge_r(xlowIn, xuppIn, iupp-1, iuppIn, col, 1, lowmax && xupp-1==xlowIn, uppmax);
     break;
-    }
+  }
 }
+
diff --git a/src/chmatch.c b/src/chmatch.c
index cb29e24bad..127578ba15 100644
--- a/src/chmatch.c
+++ b/src/chmatch.c
@@ -4,78 +4,78 @@
 // LATIN1_MASK (1<<2) | UTF8_MASK (1<<3) | ASCII_MASK (1<<6)
 
 SEXP match_logical(SEXP table, SEXP x) {
-    R_len_t i;
-    SEXP ans, m;
-    ans = PROTECT(allocVector(LGLSXP, length(x)));
-    m = PROTECT(match(table, x, 0)); // nomatch=0
-    for (i=0; i<length(x); i++)
-        INTEGER(ans)[i] = INTEGER(m)[i] > 0;
-    UNPROTECT(2);
-    return(ans);
+  R_len_t i;
+  SEXP ans, m;
+  ans = PROTECT(allocVector(LGLSXP, length(x)));
+  m = PROTECT(match(table, x, 0)); // nomatch=0
+  for (i=0; i<length(x); i++)
+    INTEGER(ans)[i] = INTEGER(m)[i] > 0;
+  UNPROTECT(2);
+  return(ans);
 }
 
 SEXP chmatch(SEXP x, SEXP table, R_len_t nomatch, Rboolean in) {
-    R_len_t i, m;
-    SEXP ans, s;
-    if (!isString(x) && !isNull(x)) error("x is type '%s' (must be 'character' or NULL)", type2char(TYPEOF(x)));
-    if (!isString(table) && !isNull(table)) error("table is type '%s' (must be 'character' or NULL)", type2char(TYPEOF(table)));
-    PROTECT(ans = allocVector(in ? LGLSXP : INTSXP,length(x))); // if fails, it fails before savetl
-    savetl_init();
-    for (i=0; i<length(x); i++) {
-        s = STRING_ELT(x,i);
-        if (s != NA_STRING && ENC_KNOWN(s) != 64) { // PREV: s != NA_STRING && !ENC_KNOWN(s) - changed to fix for bug #5159. The previous fix
-                                           // dealt with UNKNOWN encodings. But we could have the same string, where both are in different
-                                           // encodings than ASCII (ex: UTF8 and Latin1). To fix this, we'll to resort to 'match' if not ASCII.
-                                           // This takes care of all anomalies. It's unfortunate the 'chmatch' can't be used in these cases...
-                                           // // fix for the 2nd part of bug #5159
+  R_len_t i, m;
+  SEXP ans, s;
+  if (!isString(x) && !isNull(x)) error("x is type '%s' (must be 'character' or NULL)", type2char(TYPEOF(x)));
+  if (!isString(table) && !isNull(table)) error("table is type '%s' (must be 'character' or NULL)", type2char(TYPEOF(table)));
+  PROTECT(ans = allocVector(in ? LGLSXP : INTSXP,length(x))); // if fails, it fails before savetl
+  savetl_init();
+  for (i=0; i<length(x); i++) {
+    s = STRING_ELT(x,i);
+    if (s != NA_STRING && ENC_KNOWN(s) != 64) { // PREV: s != NA_STRING && !ENC_KNOWN(s) - changed to fix for bug #5159. The previous fix
+                       // dealt with UNKNOWN encodings. But we could have the same string, where both are in different
+                       // encodings than ASCII (ex: UTF8 and Latin1). To fix this, we'll to resort to 'match' if not ASCII.
+                       // This takes care of all anomalies. It's unfortunate the 'chmatch' can't be used in these cases...
+                       // // fix for the 2nd part of bug #5159
 
-            // explanation for PREV case: (still useful to keep it here)
-            // symbols (i.e. as.name() or as.symbol()) containing non-ascii characters (>127) seem to be 'unknown' encoding in R.
-            // The same string in different encodings (where unknown encoding is different to known, too) are different
-            // CHARSXP pointers; this chmatch relies on pointer equality only. We tried mkChar(CHAR(s)) [ what install() does ]
-            // but this impacted the fastest cases too much. Hence fall back to match() on the first unknown encoding detected
-            // since match() considers the same string in different encodings as equal (but slower). See #2538 and #4818.
-            savetl_end();
-            UNPROTECT(1);
-            return (in ? match_logical(table, x) : match(table, x, nomatch));
-        }
-        if (TRUELENGTH(s)>0) savetl(s);
-        // as from v1.8.0 we assume R's internal hash is positive. So in R < 2.14.0 we
-        // don't save the uninitialised truelengths that by chance are negative, but
-        // will save if positive. Hence R >= 2.14.0 may be faster and preferred now that R
-        // initializes truelength to 0 from R 2.14.0.
-        SET_TRUELENGTH(s,0);
+      // explanation for PREV case: (still useful to keep it here)
+      // symbols (i.e. as.name() or as.symbol()) containing non-ascii characters (>127) seem to be 'unknown' encoding in R.
+      // The same string in different encodings (where unknown encoding is different to known, too) are different
+      // CHARSXP pointers; this chmatch relies on pointer equality only. We tried mkChar(CHAR(s)) [ what install() does ]
+      // but this impacted the fastest cases too much. Hence fall back to match() on the first unknown encoding detected
+      // since match() considers the same string in different encodings as equal (but slower). See #2538 and #4818.
+      savetl_end();
+      UNPROTECT(1);
+      return (in ? match_logical(table, x) : match(table, x, nomatch));
+    }
+    if (TRUELENGTH(s)>0) savetl(s);
+    // as from v1.8.0 we assume R's internal hash is positive. So in R < 2.14.0 we
+    // don't save the uninitialised truelengths that by chance are negative, but
+    // will save if positive. Hence R >= 2.14.0 may be faster and preferred now that R
+    // initializes truelength to 0 from R 2.14.0.
+    SET_TRUELENGTH(s,0);
+  }
+  for (i=length(table)-1; i>=0; i--) {
+    s = STRING_ELT(table,i);
+    if (s != NA_STRING && ENC_KNOWN(s) != 64) { // changed !ENC_KNOWN(s) to !ASCII(s) - check above for explanation
+      for (int j=i+1; j<LENGTH(table); j++) SET_TRUELENGTH(STRING_ELT(table,j),0);  // reinstate 0 rather than leave the -i-1
+      savetl_end();
+      UNPROTECT(1);
+      return (in ? match_logical(table, x) : match(table, x, nomatch));
     }
-    for (i=length(table)-1; i>=0; i--) {
-        s = STRING_ELT(table,i);
-        if (s != NA_STRING && ENC_KNOWN(s) != 64) { // changed !ENC_KNOWN(s) to !ASCII(s) - check above for explanation
-            for (int j=i+1; j<LENGTH(table); j++) SET_TRUELENGTH(STRING_ELT(table,j),0);  // reinstate 0 rather than leave the -i-1
-            savetl_end();
-            UNPROTECT(1);
-            return (in ? match_logical(table, x) : match(table, x, nomatch));
-        }
-        if (TRUELENGTH(s)>0) savetl(s);
-        SET_TRUELENGTH(s, -i-1);
+    if (TRUELENGTH(s)>0) savetl(s);
+    SET_TRUELENGTH(s, -i-1);
+  }
+  if (in) {
+    for (i=0; i<length(x); i++) {
+      LOGICAL(ans)[i] = TRUELENGTH(STRING_ELT(x,i))<0;
+      // nomatch ignored for logical as base does I think
     }
-    if (in) {
-        for (i=0; i<length(x); i++) {
-            LOGICAL(ans)[i] = TRUELENGTH(STRING_ELT(x,i))<0;
-            // nomatch ignored for logical as base does I think
-        }
-    } else {
-        for (i=0; i<length(x); i++) {
-            m = TRUELENGTH(STRING_ELT(x,i));
-            INTEGER(ans)[i] = (m<0) ? -m : nomatch;
-        }
+  } else {
+    for (i=0; i<length(x); i++) {
+      m = TRUELENGTH(STRING_ELT(x,i));
+      INTEGER(ans)[i] = (m<0) ? -m : nomatch;
     }
-    for (i=0; i<length(table); i++)
-        SET_TRUELENGTH(STRING_ELT(table,i),0);  // reinstate 0 rather than leave the -i-1
-    savetl_end();
-    UNPROTECT(1);
-    return(ans);
+  }
+  for (i=0; i<length(table); i++)
+    SET_TRUELENGTH(STRING_ELT(table,i),0);  // reinstate 0 rather than leave the -i-1
+  savetl_end();
+  UNPROTECT(1);
+  return(ans);
 }
 
 SEXP chmatchwrapper(SEXP x, SEXP table, SEXP nomatch, SEXP in) {
-    return(chmatch(x,table,INTEGER(nomatch)[0],LOGICAL(in)[0]));
+  return(chmatch(x,table,INTEGER(nomatch)[0],LOGICAL(in)[0]));
 }
 
diff --git a/src/dogroups.c b/src/dogroups.c
index 421b022fba..0b9c586571 100644
--- a/src/dogroups.c
+++ b/src/dogroups.c
@@ -7,494 +7,494 @@
 
 
 void setSizes() {
-    // called by init.c
-    int i;
-    for (i=0;i<100;i++) sizes[i]=0;
-    // only these types are currently allowed as column types :
-    sizes[INTSXP] = sizeof(int);     // integer and factor
-    sizes[LGLSXP] = sizeof(int);     // logical
-    sizes[REALSXP] = sizeof(double); // numeric
-    sizes[STRSXP] = sizeof(SEXP *);  // character
-    sizes[VECSXP] = sizeof(SEXP *);  // a column itself can be a list()
-    for (i=0;i<100;i++) {
-        if (sizes[i]>8) error("Type %d is sizeof() greater than 8 bytes on this machine. We haven't tested on any architecture greater than 64bit, yet.", i);
-        // One place we need the largest sizeof (assumed to be 8 bytes) is the working memory malloc in reorder.c
-    }
-    SelfRefSymbol = install(".internal.selfref");
+  // called by init.c
+  int i;
+  for (i=0;i<100;i++) sizes[i]=0;
+  // only these types are currently allowed as column types :
+  sizes[INTSXP] = sizeof(int);     // integer and factor
+  sizes[LGLSXP] = sizeof(int);     // logical
+  sizes[REALSXP] = sizeof(double); // numeric
+  sizes[STRSXP] = sizeof(SEXP *);  // character
+  sizes[VECSXP] = sizeof(SEXP *);  // a column itself can be a list()
+  for (i=0;i<100;i++) {
+    if (sizes[i]>8) error("Type %d is sizeof() greater than 8 bytes on this machine. We haven't tested on any architecture greater than 64bit, yet.", i);
+    // One place we need the largest sizeof (assumed to be 8 bytes) is the working memory malloc in reorder.c
+  }
+  SelfRefSymbol = install(".internal.selfref");
 }
 
 SEXP dogroups(SEXP dt, SEXP dtcols, SEXP groups, SEXP grpcols, SEXP jiscols, SEXP xjiscols, SEXP grporder, SEXP order, SEXP starts, SEXP lens, SEXP jexp, SEXP env, SEXP lhs, SEXP newnames, SEXP on, SEXP verbose)
 {
-    R_len_t i, j, k, rownum, ngrp, njval=0, ngrpcols, ansloc=0, maxn, estn=-1, r, thisansloc, grpn, thislen, igrp, vlen, origIlen=0, origSDnrow=0;
-    int protecti=0;
-    SEXP names, names2, xknames, bynames, dtnames, ans=NULL, jval, thiscol, SDall, BY, N, I, GRP, iSD, xSD, rownames, s, RHS, listwrap, target, source, tmp;
-    Rboolean wasvector, firstalloc=FALSE, NullWarnDone=FALSE, recycleWarn=TRUE;
-    size_t size; // must be size_t, otherwise bug #5305 (integer overflow in memcpy)
-    clock_t tstart=0, tblock[10]={0}; int nblock[10]={0};
+  R_len_t i, j, k, rownum, ngrp, njval=0, ngrpcols, ansloc=0, maxn, estn=-1, r, thisansloc, grpn, thislen, igrp, vlen, origIlen=0, origSDnrow=0;
+  int protecti=0;
+  SEXP names, names2, xknames, bynames, dtnames, ans=NULL, jval, thiscol, SDall, BY, N, I, GRP, iSD, xSD, rownames, s, RHS, listwrap, target, source, tmp;
+  Rboolean wasvector, firstalloc=FALSE, NullWarnDone=FALSE, recycleWarn=TRUE;
+  size_t size; // must be size_t, otherwise bug #5305 (integer overflow in memcpy)
+  clock_t tstart=0, tblock[10]={0}; int nblock[10]={0};
 
-    if (!isInteger(order)) error("Internal error: order not integer vector");
-    //if (TYPEOF(starts) != INTSXP) error("Internal error: starts not integer");
-    //if (TYPEOF(lens) != INTSXP) error("Internal error: lens not integer");
-    // starts can now be NA (<0): if (INTEGER(starts)[0]<0 || INTEGER(lens)[0]<0) error("starts[1]<0 or lens[1]<0");
-    if (!isNull(jiscols) && LENGTH(order) && !LOGICAL(on)[0]) error("Internal error: jiscols not NULL but o__ has length");
-    if (!isNull(xjiscols) && LENGTH(order) && !LOGICAL(on)[0]) error("Internal error: xjiscols not NULL but o__ has length");
-    if(!isEnvironment(env)) error("’env’ should be an environment");
-    ngrp = length(starts);  // the number of groups  (nrow(groups) will be larger when by)
-    ngrpcols = length(grpcols);
-    // fix for longstanding FR/bug, #495. E.g., DT[, c(sum(v1), lapply(.SD, mean)), by=grp, .SDcols=v2:v3] resulted in error.. the idea is, 1) we create .SDall, which is normally == .SD. But if extra vars are detected in jexp other than .SD, then .SD becomes a shallow copy of .SDall with only .SDcols in .SD. Since internally, we don't make a copy, changing .SDall will reflect in .SD. Hopefully this'll workout :-).
-    SDall = findVar(install(".SDall"), env);
+  if (!isInteger(order)) error("Internal error: order not integer vector");
+  //if (TYPEOF(starts) != INTSXP) error("Internal error: starts not integer");
+  //if (TYPEOF(lens) != INTSXP) error("Internal error: lens not integer");
+  // starts can now be NA (<0): if (INTEGER(starts)[0]<0 || INTEGER(lens)[0]<0) error("starts[1]<0 or lens[1]<0");
+  if (!isNull(jiscols) && LENGTH(order) && !LOGICAL(on)[0]) error("Internal error: jiscols not NULL but o__ has length");
+  if (!isNull(xjiscols) && LENGTH(order) && !LOGICAL(on)[0]) error("Internal error: xjiscols not NULL but o__ has length");
+  if(!isEnvironment(env)) error("’env’ should be an environment");
+  ngrp = length(starts);  // the number of groups  (nrow(groups) will be larger when by)
+  ngrpcols = length(grpcols);
+  // fix for longstanding FR/bug, #495. E.g., DT[, c(sum(v1), lapply(.SD, mean)), by=grp, .SDcols=v2:v3] resulted in error.. the idea is, 1) we create .SDall, which is normally == .SD. But if extra vars are detected in jexp other than .SD, then .SD becomes a shallow copy of .SDall with only .SDcols in .SD. Since internally, we don't make a copy, changing .SDall will reflect in .SD. Hopefully this'll workout :-).
+  SDall = findVar(install(".SDall"), env);
 
-    defineVar(sym_BY, BY = allocVector(VECSXP, ngrpcols), env);
-    bynames = PROTECT(allocVector(STRSXP, ngrpcols));  protecti++;   // TO DO: do we really need bynames, can we assign names afterwards in one step?
-    for (i=0; i<ngrpcols; i++) {
-        j = INTEGER(grpcols)[i]-1;
-        SET_VECTOR_ELT(BY, i, allocVector(TYPEOF(VECTOR_ELT(groups, j)),
-            LENGTH(VECTOR_ELT(groups,0)) ? 1 : 0));    // This might be able to be 1 always; 0 when 'groups' are integer(0) seem sensible. #2440 was involved in the past.
-        // Fix for #5437, by cols with attributes when also used in `j` lost the attribute.
-        copyMostAttrib(VECTOR_ELT(groups, j), VECTOR_ELT(BY,i));  // not names, otherwise test 778 would fail
-        SET_STRING_ELT(bynames, i, STRING_ELT(getAttrib(groups,R_NamesSymbol), j));
-        defineVar(install(CHAR(STRING_ELT(bynames,i))), VECTOR_ELT(BY,i), env);      // by vars can be used by name in j as well as via .BY
-        if (SIZEOF(VECTOR_ELT(BY,i))==0)
-            error("Unsupported type '%s' in column %d of 'by'", type2char(TYPEOF(VECTOR_ELT(BY, i))), i+1);
-    }
-    setAttrib(BY, R_NamesSymbol, bynames); // Fix for #5415 - BY doesn't retain names anymore
-    R_LockBinding(sym_BY, env);
-    if (isNull(jiscols) && (length(bynames)!=length(groups) || length(bynames)!=length(grpcols))) error("!length(bynames)[%d]==length(groups)[%d]==length(grpcols)[%d]",length(bynames),length(groups),length(grpcols));
-    // TO DO: check this check above.
+  defineVar(sym_BY, BY = allocVector(VECSXP, ngrpcols), env);
+  bynames = PROTECT(allocVector(STRSXP, ngrpcols));  protecti++;   // TO DO: do we really need bynames, can we assign names afterwards in one step?
+  for (i=0; i<ngrpcols; i++) {
+    j = INTEGER(grpcols)[i]-1;
+    SET_VECTOR_ELT(BY, i, allocVector(TYPEOF(VECTOR_ELT(groups, j)),
+      LENGTH(VECTOR_ELT(groups,0)) ? 1 : 0));    // This might be able to be 1 always; 0 when 'groups' are integer(0) seem sensible. #2440 was involved in the past.
+    // Fix for #5437, by cols with attributes when also used in `j` lost the attribute.
+    copyMostAttrib(VECTOR_ELT(groups, j), VECTOR_ELT(BY,i));  // not names, otherwise test 778 would fail
+    SET_STRING_ELT(bynames, i, STRING_ELT(getAttrib(groups,R_NamesSymbol), j));
+    defineVar(install(CHAR(STRING_ELT(bynames,i))), VECTOR_ELT(BY,i), env);      // by vars can be used by name in j as well as via .BY
+    if (SIZEOF(VECTOR_ELT(BY,i))==0)
+      error("Unsupported type '%s' in column %d of 'by'", type2char(TYPEOF(VECTOR_ELT(BY, i))), i+1);
+  }
+  setAttrib(BY, R_NamesSymbol, bynames); // Fix for #5415 - BY doesn't retain names anymore
+  R_LockBinding(sym_BY, env);
+  if (isNull(jiscols) && (length(bynames)!=length(groups) || length(bynames)!=length(grpcols))) error("!length(bynames)[%d]==length(groups)[%d]==length(grpcols)[%d]",length(bynames),length(groups),length(grpcols));
+  // TO DO: check this check above.
 
-    N = findVar(install(".N"), env);
-    GRP = findVar(install(".GRP"), env);
-    iSD = findVar(install(".iSD"), env);  // 1-row and possibly no cols (if no i variables are used via JIS)
-    xSD = findVar(install(".xSD"), env);
-    I = findVar(install(".I"), env);
+  N = findVar(install(".N"), env);
+  GRP = findVar(install(".GRP"), env);
+  iSD = findVar(install(".iSD"), env);  // 1-row and possibly no cols (if no i variables are used via JIS)
+  xSD = findVar(install(".xSD"), env);
+  I = findVar(install(".I"), env);
 
-    dtnames = getAttrib(dt, R_NamesSymbol); // added here to fix #4990 - `:=` did not issue recycling warning during "by"
-    // fetch rownames of .SD.  rownames[1] is set to -thislen for each group, in case .SD is passed to
-    // non data.table aware package that uses rownames
-    for (s = ATTRIB(SDall); s != R_NilValue && TAG(s)!=R_RowNamesSymbol; s = CDR(s));
-    // getAttrib0 basically but that's hidden in attrib.c
-    if (s==R_NilValue) error("row.names attribute of .SD not found");
-    rownames = CAR(s);
-    if (!isInteger(rownames) || LENGTH(rownames)!=2 || INTEGER(rownames)[0]!=NA_INTEGER) error("row.names of .SD isn't integer length 2 with NA as first item; i.e., .set_row_names(). [%s %d %d]",type2char(TYPEOF(rownames)),LENGTH(rownames),INTEGER(rownames)[0]);
+  dtnames = getAttrib(dt, R_NamesSymbol); // added here to fix #4990 - `:=` did not issue recycling warning during "by"
+  // fetch rownames of .SD.  rownames[1] is set to -thislen for each group, in case .SD is passed to
+  // non data.table aware package that uses rownames
+  for (s = ATTRIB(SDall); s != R_NilValue && TAG(s)!=R_RowNamesSymbol; s = CDR(s));
+  // getAttrib0 basically but that's hidden in attrib.c
+  if (s==R_NilValue) error("row.names attribute of .SD not found");
+  rownames = CAR(s);
+  if (!isInteger(rownames) || LENGTH(rownames)!=2 || INTEGER(rownames)[0]!=NA_INTEGER) error("row.names of .SD isn't integer length 2 with NA as first item; i.e., .set_row_names(). [%s %d %d]",type2char(TYPEOF(rownames)),LENGTH(rownames),INTEGER(rownames)[0]);
 
-    // fetch names of .SD and prepare symbols. In case they are copied-on-write by user assigning to those variables
-    // using <- in j (which is valid, useful and tested), they are repointed to the .SD cols for each group.
-    names = getAttrib(SDall, R_NamesSymbol);
-    if (length(names) != length(SDall)) error("length(names)!=length(SD)");
-    SEXP *nameSyms = (SEXP *)R_alloc(length(names), sizeof(SEXP));
-    for(i = 0; i < length(SDall); i++) {
-        if (SIZEOF(VECTOR_ELT(SDall, i))==0)
-            error("Type %d in .SD column %d", TYPEOF(VECTOR_ELT(SDall, i)), i);
-        nameSyms[i] = install(CHAR(STRING_ELT(names, i)));
-        // fixes http://stackoverflow.com/questions/14753411/why-does-data-table-lose-class-definition-in-sd-after-group-by
-        copyMostAttrib(VECTOR_ELT(dt,INTEGER(dtcols)[i]-1), VECTOR_ELT(SDall,i));  // not names, otherwise test 778 would fail
-    }
+  // fetch names of .SD and prepare symbols. In case they are copied-on-write by user assigning to those variables
+  // using <- in j (which is valid, useful and tested), they are repointed to the .SD cols for each group.
+  names = getAttrib(SDall, R_NamesSymbol);
+  if (length(names) != length(SDall)) error("length(names)!=length(SD)");
+  SEXP *nameSyms = (SEXP *)R_alloc(length(names), sizeof(SEXP));
+  for(i = 0; i < length(SDall); i++) {
+    if (SIZEOF(VECTOR_ELT(SDall, i))==0)
+      error("Type %d in .SD column %d", TYPEOF(VECTOR_ELT(SDall, i)), i);
+    nameSyms[i] = install(CHAR(STRING_ELT(names, i)));
+    // fixes http://stackoverflow.com/questions/14753411/why-does-data-table-lose-class-definition-in-sd-after-group-by
+    copyMostAttrib(VECTOR_ELT(dt,INTEGER(dtcols)[i]-1), VECTOR_ELT(SDall,i));  // not names, otherwise test 778 would fail
+  }
 
-    origIlen = length(I);  // test 762 has length(I)==1 but nrow(SD)==0
-    if (length(SDall)) origSDnrow = length(VECTOR_ELT(SDall, 0));
+  origIlen = length(I);  // test 762 has length(I)==1 but nrow(SD)==0
+  if (length(SDall)) origSDnrow = length(VECTOR_ELT(SDall, 0));
 
-    xknames = getAttrib(xSD, R_NamesSymbol);
-    if (length(xknames) != length(xSD)) error("length(xknames)!=length(xSD)");
-    SEXP *xknameSyms = (SEXP *)R_alloc(length(xknames), sizeof(SEXP));
-    for(i = 0; i < length(xSD); i++) {
-        if (SIZEOF(VECTOR_ELT(xSD, i))==0)
-            error("Type %d in .xSD column %d", TYPEOF(VECTOR_ELT(xSD, i)), i);
-        xknameSyms[i] = install(CHAR(STRING_ELT(xknames, i)));
-    }
+  xknames = getAttrib(xSD, R_NamesSymbol);
+  if (length(xknames) != length(xSD)) error("length(xknames)!=length(xSD)");
+  SEXP *xknameSyms = (SEXP *)R_alloc(length(xknames), sizeof(SEXP));
+  for(i = 0; i < length(xSD); i++) {
+    if (SIZEOF(VECTOR_ELT(xSD, i))==0)
+      error("Type %d in .xSD column %d", TYPEOF(VECTOR_ELT(xSD, i)), i);
+    xknameSyms[i] = install(CHAR(STRING_ELT(xknames, i)));
+  }
 
-    if (length(iSD)!=length(jiscols)) error("length(iSD)[%d] != length(jiscols)[%d]",length(iSD),length(jiscols));
-    if (length(xSD)!=length(xjiscols)) error("length(xSD)[%d] != length(xjiscols)[%d]",length(xSD),length(xjiscols));
+  if (length(iSD)!=length(jiscols)) error("length(iSD)[%d] != length(jiscols)[%d]",length(iSD),length(jiscols));
+  if (length(xSD)!=length(xjiscols)) error("length(xSD)[%d] != length(xjiscols)[%d]",length(xSD),length(xjiscols));
 
 
-    PROTECT(listwrap = allocVector(VECSXP, 1));
-    protecti++;
-    Rboolean jexpIsSymbolOtherThanSD = (isSymbol(jexp) && strcmp(CHAR(PRINTNAME(jexp)),".SD")!=0);  // test 559
+  PROTECT(listwrap = allocVector(VECSXP, 1));
+  protecti++;
+  Rboolean jexpIsSymbolOtherThanSD = (isSymbol(jexp) && strcmp(CHAR(PRINTNAME(jexp)),".SD")!=0);  // test 559
 
-    ansloc = 0;
-    for(i=0; i<ngrp; i++) {   // even for an empty i table, ngroup is length 1 (starts is value 0), for consistency of empty cases
+  ansloc = 0;
+  for(i=0; i<ngrp; i++) {   // even for an empty i table, ngroup is length 1 (starts is value 0), for consistency of empty cases
 
-        if (INTEGER(starts)[i]==0 && (i<ngrp-1 || estn>-1)) continue;
-        // Previously had replaced (i>0 || !isNull(lhs)) with i>0 to fix #5376
-        // The above is now to fix #1993, see test 1746.
-        // In cases were no i rows match, '|| estn>-1' ensures that the last empty group creates an empty result.
-        // TODO: revisit and tidy
+    if (INTEGER(starts)[i]==0 && (i<ngrp-1 || estn>-1)) continue;
+    // Previously had replaced (i>0 || !isNull(lhs)) with i>0 to fix #5376
+    // The above is now to fix #1993, see test 1746.
+    // In cases were no i rows match, '|| estn>-1' ensures that the last empty group creates an empty result.
+    // TODO: revisit and tidy
 
-        if (!isNull(lhs) &&
-               (INTEGER(starts)[i] == NA_INTEGER ||
-                (LENGTH(order) && INTEGER(order)[ INTEGER(starts)[i]-1 ]==NA_INTEGER)))
-            continue;
-        grpn = INTEGER(lens)[i];
-        INTEGER(N)[0] = INTEGER(starts)[i] == NA_INTEGER ? 0 : grpn;
-        // .N is number of rows matched to ( 0 even when nomatch is NA)
-        INTEGER(GRP)[0] = i+1;  // group counter exposed as .GRP
+    if (!isNull(lhs) &&
+        (INTEGER(starts)[i] == NA_INTEGER ||
+         (LENGTH(order) && INTEGER(order)[ INTEGER(starts)[i]-1 ]==NA_INTEGER)))
+      continue;
+    grpn = INTEGER(lens)[i];
+    INTEGER(N)[0] = INTEGER(starts)[i] == NA_INTEGER ? 0 : grpn;
+    // .N is number of rows matched to ( 0 even when nomatch is NA)
+    INTEGER(GRP)[0] = i+1;  // group counter exposed as .GRP
 
-        for (j=0; j<length(iSD); j++) {   // either this or the next for() will run, not both
-            size = SIZEOF(VECTOR_ELT(iSD,j));
-            memcpy((char *)DATAPTR(VECTOR_ELT(iSD,j)),  // ok use of memcpy. Loop'd through columns not rows
-                   (char *)DATAPTR(VECTOR_ELT(groups,INTEGER(jiscols)[j]-1))+i*size,
-                   size);
+    for (j=0; j<length(iSD); j++) {   // either this or the next for() will run, not both
+      size = SIZEOF(VECTOR_ELT(iSD,j));
+      memcpy((char *)DATAPTR(VECTOR_ELT(iSD,j)),  // ok use of memcpy. Loop'd through columns not rows
+             (char *)DATAPTR(VECTOR_ELT(groups,INTEGER(jiscols)[j]-1))+i*size,
+             size);
+    }
+    if (LOGICAL(on)[0])
+      igrp = (length(grporder) && isNull(jiscols)) ? INTEGER(grporder)[INTEGER(starts)[i]-1]-1 : i;
+    else
+      igrp = (length(grporder)) ? INTEGER(grporder)[INTEGER(starts)[i]-1]-1 : (isNull(jiscols) ? INTEGER(starts)[i]-1 : i);
+    if (igrp >= 0) for (j=0; j<length(BY); j++) {    // igrp can be -1 so 'if' is important, otherwise memcpy crash
+      size = SIZEOF(VECTOR_ELT(BY,j));
+      memcpy((char *)DATAPTR(VECTOR_ELT(BY,j)),  // ok use of memcpy size 1. Loop'd through columns not rows
+             (char *)DATAPTR(VECTOR_ELT(groups,INTEGER(grpcols)[j]-1))+igrp*size,
+             size);
+    }
+    if (INTEGER(starts)[i] == NA_INTEGER || (LENGTH(order) && INTEGER(order)[ INTEGER(starts)[i]-1 ]==NA_INTEGER)) {
+      for (j=0; j<length(SDall); j++) {
+        switch (TYPEOF(VECTOR_ELT(SDall, j))) {
+        case LGLSXP :
+          LOGICAL(VECTOR_ELT(SDall,j))[0] = NA_LOGICAL;
+          break;
+        case INTSXP :
+          INTEGER(VECTOR_ELT(SDall,j))[0] = NA_INTEGER;
+          break;
+        case REALSXP :
+          REAL(VECTOR_ELT(SDall,j))[0] = NA_REAL;
+          break;
+        case STRSXP :
+          SET_STRING_ELT(VECTOR_ELT(SDall,j),0,NA_STRING);
+          break;
+        default:
+          error("Logical error. Type of column should have been checked by now");
         }
-        if (LOGICAL(on)[0])
-            igrp = (length(grporder) && isNull(jiscols)) ? INTEGER(grporder)[INTEGER(starts)[i]-1]-1 : i;
-        else
-            igrp = (length(grporder)) ? INTEGER(grporder)[INTEGER(starts)[i]-1]-1 : (isNull(jiscols) ? INTEGER(starts)[i]-1 : i);
-        if (igrp >= 0) for (j=0; j<length(BY); j++) {    // igrp can be -1 so 'if' is important, otherwise memcpy crash
-            size = SIZEOF(VECTOR_ELT(BY,j));
-            memcpy((char *)DATAPTR(VECTOR_ELT(BY,j)),  // ok use of memcpy size 1. Loop'd through columns not rows
-                   (char *)DATAPTR(VECTOR_ELT(groups,INTEGER(grpcols)[j]-1))+igrp*size,
+      }
+      grpn = 1;  // TO DO: it is anyway?
+      INTEGER(I)[0] = 0;
+      for (j=0; j<length(xSD); j++) {
+        switch (TYPEOF(VECTOR_ELT(xSD, j))) {
+        case LGLSXP :
+          LOGICAL(VECTOR_ELT(xSD,j))[0] = NA_LOGICAL;
+          break;
+        case INTSXP :
+          INTEGER(VECTOR_ELT(xSD,j))[0] = NA_INTEGER;
+          break;
+        case REALSXP :
+          REAL(VECTOR_ELT(xSD,j))[0] = NA_REAL;
+          break;
+        case STRSXP :
+          SET_STRING_ELT(VECTOR_ELT(xSD,j),0,NA_STRING);
+          break;
+        default:
+          error("Logical error. Type of column should have been checked by now");
+        }
+      }
+    } else {
+      if (LOGICAL(verbose)[0]) tstart = clock();
+      if (LENGTH(order)==0) {
+        rownum = INTEGER(starts)[i]-1;
+        for (j=0; j<grpn; j++) INTEGER(I)[j] = rownum+j+1;
+        if (rownum>=0) {
+          for (j=0; j<length(SDall); j++) {
+            size = SIZEOF(VECTOR_ELT(SDall,j));
+            memcpy((char *)DATAPTR(VECTOR_ELT(SDall,j)),  // direct memcpy best here, for usually large size groups. by= each row is slow and not recommended anyway, so we don't mind there's no switch here for grpn==1
+                   (char *)DATAPTR(VECTOR_ELT(dt,INTEGER(dtcols)[j]-1))+rownum*size,
+                   grpn*size);
+            // SD is our own alloc'd memory, and the source (DT) is protected throughout, so no need for SET_* overhead
+          }
+          for (j=0; j<length(xSD); j++) {
+            size = SIZEOF(VECTOR_ELT(xSD,j));
+            memcpy((char *)DATAPTR(VECTOR_ELT(xSD,j)),  // ok use of memcpy. Loop'd through columns not rows
+                   (char *)DATAPTR(VECTOR_ELT(dt,INTEGER(xjiscols)[j]-1))+rownum*size,
                    size);
+          }
         }
-        if (INTEGER(starts)[i] == NA_INTEGER || (LENGTH(order) && INTEGER(order)[ INTEGER(starts)[i]-1 ]==NA_INTEGER)) {
-            for (j=0; j<length(SDall); j++) {
-                switch (TYPEOF(VECTOR_ELT(SDall, j))) {
-                case LGLSXP :
-                    LOGICAL(VECTOR_ELT(SDall,j))[0] = NA_LOGICAL;
-                    break;
-                case INTSXP :
-                    INTEGER(VECTOR_ELT(SDall,j))[0] = NA_INTEGER;
-                    break;
-                case REALSXP :
-                    REAL(VECTOR_ELT(SDall,j))[0] = NA_REAL;
-                    break;
-                case STRSXP :
-                    SET_STRING_ELT(VECTOR_ELT(SDall,j),0,NA_STRING);
-                    break;
-                default:
-                    error("Logical error. Type of column should have been checked by now");
-                }
-            }
-            grpn = 1;  // TO DO: it is anyway?
-            INTEGER(I)[0] = 0;
-            for (j=0; j<length(xSD); j++) {
-                switch (TYPEOF(VECTOR_ELT(xSD, j))) {
-                case LGLSXP :
-                    LOGICAL(VECTOR_ELT(xSD,j))[0] = NA_LOGICAL;
-                    break;
-                case INTSXP :
-                    INTEGER(VECTOR_ELT(xSD,j))[0] = NA_INTEGER;
-                    break;
-                case REALSXP :
-                    REAL(VECTOR_ELT(xSD,j))[0] = NA_REAL;
-                    break;
-                case STRSXP :
-                    SET_STRING_ELT(VECTOR_ELT(xSD,j),0,NA_STRING);
-                    break;
-                default:
-                    error("Logical error. Type of column should have been checked by now");
-                }
+        if (LOGICAL(verbose)[0]) { tblock[0] += clock()-tstart; nblock[0]++; }
+      } else {
+        // Fairly happy with this block. No need for SET_* here. See comment above.
+        for (k=0; k<grpn; k++) INTEGER(I)[k] = INTEGER(order)[ INTEGER(starts)[i]-1 + k ];
+        for (j=0; j<length(SDall); j++) {
+          size = SIZEOF(VECTOR_ELT(SDall,j));
+          target = VECTOR_ELT(SDall,j);
+          source = VECTOR_ELT(dt,INTEGER(dtcols)[j]-1);
+          if (size==4) {
+            for (k=0; k<grpn; k++) {
+              rownum = INTEGER(I)[k]-1;
+              INTEGER(target)[k] = INTEGER(source)[rownum]; // on 32bit, copies pointers too
             }
-        } else {
-            if (LOGICAL(verbose)[0]) tstart = clock();
-            if (LENGTH(order)==0) {
-                rownum = INTEGER(starts)[i]-1;
-                for (j=0; j<grpn; j++) INTEGER(I)[j] = rownum+j+1;
-                if (rownum>=0) {
-                  for (j=0; j<length(SDall); j++) {
-                      size = SIZEOF(VECTOR_ELT(SDall,j));
-                      memcpy((char *)DATAPTR(VECTOR_ELT(SDall,j)),  // direct memcpy best here, for usually large size groups. by= each row is slow and not recommended anyway, so we don't mind there's no switch here for grpn==1
-                         (char *)DATAPTR(VECTOR_ELT(dt,INTEGER(dtcols)[j]-1))+rownum*size,
-                         grpn*size);
-                      // SD is our own alloc'd memory, and the source (DT) is protected throughout, so no need for SET_* overhead
-                  }
-                  for (j=0; j<length(xSD); j++) {
-                      size = SIZEOF(VECTOR_ELT(xSD,j));
-                      memcpy((char *)DATAPTR(VECTOR_ELT(xSD,j)),  // ok use of memcpy. Loop'd through columns not rows
-                         (char *)DATAPTR(VECTOR_ELT(dt,INTEGER(xjiscols)[j]-1))+rownum*size,
-                         size);
-                  }
-                }
-                if (LOGICAL(verbose)[0]) { tblock[0] += clock()-tstart; nblock[0]++; }
-            } else {
-                // Fairly happy with this block. No need for SET_* here. See comment above.
-                for (k=0; k<grpn; k++) INTEGER(I)[k] = INTEGER(order)[ INTEGER(starts)[i]-1 + k ];
-                for (j=0; j<length(SDall); j++) {
-                    size = SIZEOF(VECTOR_ELT(SDall,j));
-                    target = VECTOR_ELT(SDall,j);
-                    source = VECTOR_ELT(dt,INTEGER(dtcols)[j]-1);
-                    if (size==4) {
-                        for (k=0; k<grpn; k++) {
-                            rownum = INTEGER(I)[k]-1;
-                            INTEGER(target)[k] = INTEGER(source)[rownum]; // on 32bit, copies pointers too
-                        }
-                    } else {  // size 8
-                        for (k=0; k<grpn; k++) {
-                            rownum = INTEGER(I)[k]-1;
-                            REAL(target)[k] = REAL(source)[rownum];       // on 64bit, copies pointers too
-                        }
-                    }
-                }
-                if (LOGICAL(verbose)[0]) { tblock[1] += clock()-tstart; nblock[1]++; }
-                // The two blocks have separate timing statements to make sure which is running
+          } else {  // size 8
+            for (k=0; k<grpn; k++) {
+              rownum = INTEGER(I)[k]-1;
+              REAL(target)[k] = REAL(source)[rownum];       // on 64bit, copies pointers too
             }
+          }
         }
-        INTEGER(rownames)[1] = -grpn;  // the .set_row_names() of .SD. Not .N when nomatch=NA and this is a nomatch
-        for (j=0; j<length(SDall); j++) {
-            SETLENGTH(VECTOR_ELT(SDall,j), grpn);
-            defineVar(nameSyms[j], VECTOR_ELT(SDall, j), env);
-            // In case user's j assigns to the columns names (env is static) (tests 387 and 388)
-            // nameSyms pre-stored to save repeated install() for efficiency.
-        }
-        for (j=0; j<length(xSD); j++) {
-            defineVar(xknameSyms[j], VECTOR_ELT(xSD, j), env);
-        }
-        SETLENGTH(I, grpn);
+        if (LOGICAL(verbose)[0]) { tblock[1] += clock()-tstart; nblock[1]++; }
+        // The two blocks have separate timing statements to make sure which is running
+      }
+    }
+    INTEGER(rownames)[1] = -grpn;  // the .set_row_names() of .SD. Not .N when nomatch=NA and this is a nomatch
+    for (j=0; j<length(SDall); j++) {
+      SETLENGTH(VECTOR_ELT(SDall,j), grpn);
+      defineVar(nameSyms[j], VECTOR_ELT(SDall, j), env);
+      // In case user's j assigns to the columns names (env is static) (tests 387 and 388)
+      // nameSyms pre-stored to save repeated install() for efficiency.
+    }
+    for (j=0; j<length(xSD); j++) {
+      defineVar(xknameSyms[j], VECTOR_ELT(xSD, j), env);
+    }
+    SETLENGTH(I, grpn);
 
-        if (LOGICAL(verbose)[0]) tstart = clock();  // call to clock() is more expensive than an 'if'
-        PROTECT(jval = eval(jexp, env));
+    if (LOGICAL(verbose)[0]) tstart = clock();  // call to clock() is more expensive than an 'if'
+    PROTECT(jval = eval(jexp, env));
 
-        if (LOGICAL(verbose)[0]) { tblock[2] += clock()-tstart; nblock[2]++; }
+    if (LOGICAL(verbose)[0]) { tblock[2] += clock()-tstart; nblock[2]++; }
 
-        if (isNull(jval))  {
-            // j may be a plot or other side-effect only
-            UNPROTECT(1);
-            continue;
-        }
-        if ((wasvector = (isVectorAtomic(jval) || jexpIsSymbolOtherThanSD))) {  // see test 559
-            // Prior to 1.8.1 the wrap with list() was done up in R after the first group had tested.
-            // This wrap is done to make the code below easier to loop through. listwrap avoids copying jval.
-            SET_VECTOR_ELT(listwrap,0,jval);
-            jval = listwrap;
-        } else {
-            if (!isNewList(jval))    // isNewList tests for VECSXP. isList tests for (old) LISTSXP
-                error("j evaluates to type '%s'. Must evaluate to atomic vector or list.",type2char(TYPEOF(jval)));
-            if (!LENGTH(jval)) {
-                UNPROTECT(1);
-                continue;
-            }
-            for (j=0; j<LENGTH(jval); j++) {
-                thiscol = VECTOR_ELT(jval,j);
-                if (!isNull(thiscol) && (!isVector(thiscol) || isFrame(thiscol) || isArray(thiscol) ))
-                    error("All items in j=list(...) should be atomic vectors or lists. If you are trying something like j=list(.SD,newcol=mean(colA)) then use := by group instead (much quicker), or cbind or merge afterwards.");
-            }
+    if (isNull(jval))  {
+      // j may be a plot or other side-effect only
+      UNPROTECT(1);
+      continue;
+    }
+    if ((wasvector = (isVectorAtomic(jval) || jexpIsSymbolOtherThanSD))) {  // see test 559
+      // Prior to 1.8.1 the wrap with list() was done up in R after the first group had tested.
+      // This wrap is done to make the code below easier to loop through. listwrap avoids copying jval.
+      SET_VECTOR_ELT(listwrap,0,jval);
+      jval = listwrap;
+    } else {
+      if (!isNewList(jval))    // isNewList tests for VECSXP. isList tests for (old) LISTSXP
+        error("j evaluates to type '%s'. Must evaluate to atomic vector or list.",type2char(TYPEOF(jval)));
+      if (!LENGTH(jval)) {
+        UNPROTECT(1);
+        continue;
+      }
+      for (j=0; j<LENGTH(jval); j++) {
+        thiscol = VECTOR_ELT(jval,j);
+        if (!isNull(thiscol) && (!isVector(thiscol) || isFrame(thiscol) || isArray(thiscol) ))
+          error("All items in j=list(...) should be atomic vectors or lists. If you are trying something like j=list(.SD,newcol=mean(colA)) then use := by group instead (much quicker), or cbind or merge afterwards.");
+      }
+    }
+    if (!isNull(lhs)) {
+      R_len_t origncol = LENGTH(dt);
+      for (j=0; j<length(lhs); j++) {
+        target = VECTOR_ELT(dt,INTEGER(lhs)[j]-1);
+        RHS = VECTOR_ELT(jval,j%LENGTH(jval));
+        if (isNull(target)) {
+          // first time adding to new column
+          if (isNull(RHS)) error("RHS is NULL when grouping :=. Makes no sense to delete a column by group. Perhaps use an empty vector instead.");
+          if (TRUELENGTH(dt) < INTEGER(lhs)[j]) error("Internal error: Trying to add new column by reference but tl is full; alloc.col should have run first at R level before getting to this point in dogroups");
+          tmp = PROTECT(allocNAVector(TYPEOF(RHS), LENGTH(VECTOR_ELT(dt,0))));
+          // increment length only if the allocation passes, #1676
+          SETLENGTH(dtnames, LENGTH(dtnames)+1);
+          SETLENGTH(dt, LENGTH(dt)+1);
+          SET_VECTOR_ELT(dt, INTEGER(lhs)[j]-1, tmp);
+          UNPROTECT(1);
+          // Even if we could know reliably to switch from allocNAVector to allocVector for slight speedup, user code could still contain a switched halt, and in that case we'd want the groups not yet done to have NA rather than uninitialized or 0.
+          // dtnames = getAttrib(dt, R_NamesSymbol); // commented this here and added it on the beginning to fix #4990
+          SET_STRING_ELT(dtnames, INTEGER(lhs)[j]-1, STRING_ELT(newnames, INTEGER(lhs)[j]-origncol-1));
+          target = VECTOR_ELT(dt,INTEGER(lhs)[j]-1);
         }
-        if (!isNull(lhs)) {
-            R_len_t origncol = LENGTH(dt);
-            for (j=0; j<length(lhs); j++) {
-                target = VECTOR_ELT(dt,INTEGER(lhs)[j]-1);
-                RHS = VECTOR_ELT(jval,j%LENGTH(jval));
-                if (isNull(target)) {
-                    // first time adding to new column
-                    if (isNull(RHS)) error("RHS is NULL when grouping :=. Makes no sense to delete a column by group. Perhaps use an empty vector instead.");
-                    if (TRUELENGTH(dt) < INTEGER(lhs)[j]) error("Internal error: Trying to add new column by reference but tl is full; alloc.col should have run first at R level before getting to this point in dogroups");
-                    tmp = PROTECT(allocNAVector(TYPEOF(RHS), LENGTH(VECTOR_ELT(dt,0))));
-                    // increment length only if the allocation passes, #1676
-                    SETLENGTH(dtnames, LENGTH(dtnames)+1);
-                    SETLENGTH(dt, LENGTH(dt)+1);
-                    SET_VECTOR_ELT(dt, INTEGER(lhs)[j]-1, tmp);
-                    UNPROTECT(1);
-                    // Even if we could know reliably to switch from allocNAVector to allocVector for slight speedup, user code could still contain a switched halt, and in that case we'd want the groups not yet done to have NA rather than uninitialized or 0.
-                    // dtnames = getAttrib(dt, R_NamesSymbol); // commented this here and added it on the beginning to fix #4990
-                    SET_STRING_ELT(dtnames, INTEGER(lhs)[j]-1, STRING_ELT(newnames, INTEGER(lhs)[j]-origncol-1));
-                    target = VECTOR_ELT(dt,INTEGER(lhs)[j]-1);
-                }
-                if (TYPEOF(target)!=TYPEOF(RHS)) error("Type of RHS ('%s') must match LHS ('%s'). To check and coerce would impact performance too much for the fastest cases. Either change the type of the target column, or coerce the RHS of := yourself (e.g. by using 1L instead of 1)", type2char(TYPEOF(RHS)), type2char(TYPEOF(target)));
-                size = SIZEOF(target);
-                vlen = length(RHS);
-                if (vlen==0) continue;
-                if (grpn>0 && vlen>grpn && j<LENGTH(jval)) warning("RHS %d is length %d (greater than the size (%d) of group %d). The last %d element(s) will be discarded.", j+1, vlen, grpn, i+1, vlen-grpn);
-                // fix for #4990 - `:=` did not issue recycling warning during "by" operation.
-                if (vlen<grpn && vlen>0 && grpn%vlen != 0)
-                    warning("Supplied %d items to be assigned to group %d of size %d in column '%s' (recycled leaving remainder of %d items).",vlen,i+1,grpn,CHAR(STRING_ELT(dtnames,INTEGER(lhs)[j]-1)),grpn%vlen);
+        if (TYPEOF(target)!=TYPEOF(RHS)) error("Type of RHS ('%s') must match LHS ('%s'). To check and coerce would impact performance too much for the fastest cases. Either change the type of the target column, or coerce the RHS of := yourself (e.g. by using 1L instead of 1)", type2char(TYPEOF(RHS)), type2char(TYPEOF(target)));
+        size = SIZEOF(target);
+        vlen = length(RHS);
+        if (vlen==0) continue;
+        if (grpn>0 && vlen>grpn && j<LENGTH(jval)) warning("RHS %d is length %d (greater than the size (%d) of group %d). The last %d element(s) will be discarded.", j+1, vlen, grpn, i+1, vlen-grpn);
+        // fix for #4990 - `:=` did not issue recycling warning during "by" operation.
+        if (vlen<grpn && vlen>0 && grpn%vlen != 0)
+          warning("Supplied %d items to be assigned to group %d of size %d in column '%s' (recycled leaving remainder of %d items).",vlen,i+1,grpn,CHAR(STRING_ELT(dtnames,INTEGER(lhs)[j]-1)),grpn%vlen);
 
-                memrecycle(target, order, INTEGER(starts)[i]-1, grpn, RHS);
+        memrecycle(target, order, INTEGER(starts)[i]-1, grpn, RHS);
 
-                copyMostAttrib(RHS, target);  // not names, otherwise test 778 would fail.
-                /* OLD FIX: commented now. The fix below resulted in segfault on factor columns because I dint set the "levels"
-                   Instead of fixing that, I just removed setting class if it's factor. Not appropriate fix.
-                   Correct fix of copying all attributes (except names) added above. Now, everything should be alright.
-                   Test 1144 (#5104) will provide the right output now. Modified accordingly.
-                OUTDATED: if (!isFactor(RHS)) setAttrib(target, R_ClassSymbol, getAttrib(RHS, R_ClassSymbol));
-                OUTDATED: // added !isFactor(RHS) to fix #5104 (side-effect of fixing #2531)
-                   See also #155 and #36 */
+        copyMostAttrib(RHS, target);  // not names, otherwise test 778 would fail.
+        /* OLD FIX: commented now. The fix below resulted in segfault on factor columns because I dint set the "levels"
+           Instead of fixing that, I just removed setting class if it's factor. Not appropriate fix.
+           Correct fix of copying all attributes (except names) added above. Now, everything should be alright.
+           Test 1144 (#5104) will provide the right output now. Modified accordingly.
+        OUTDATED: if (!isFactor(RHS)) setAttrib(target, R_ClassSymbol, getAttrib(RHS, R_ClassSymbol));
+        OUTDATED: // added !isFactor(RHS) to fix #5104 (side-effect of fixing #2531)
+           See also #155 and #36 */
 
-            }
-            UNPROTECT(1);
-            continue;
-        }
-        maxn = 0;
-        if (njval==0) njval = LENGTH(jval);   // for first group, then the rest (when non 0) must conform to the first >0 group
-        if (njval!=LENGTH(jval)) error("j doesn't evaluate to the same number of columns for each group");  // this would be a problem even if we unlisted afterwards. This way the user finds out earlier though so he can fix and rerun sooner.
-        for (j=0; j<njval; j++) {
-            k = length(VECTOR_ELT(jval,j));  // might be NULL, so length not LENGTH
-            maxn = k>maxn ? k : maxn;
+      }
+      UNPROTECT(1);
+      continue;
+    }
+    maxn = 0;
+    if (njval==0) njval = LENGTH(jval);   // for first group, then the rest (when non 0) must conform to the first >0 group
+    if (njval!=LENGTH(jval)) error("j doesn't evaluate to the same number of columns for each group");  // this would be a problem even if we unlisted afterwards. This way the user finds out earlier though so he can fix and rerun sooner.
+    for (j=0; j<njval; j++) {
+      k = length(VECTOR_ELT(jval,j));  // might be NULL, so length not LENGTH
+      maxn = k>maxn ? k : maxn;
+    }
+    if (ansloc + maxn > estn) {
+      if (estn == -1) {
+        // Given first group and j's result on it, make a good guess for size of result required.
+        if (grpn==0)
+          estn = maxn = 0;   // empty case e.g. test 184. maxn is 1 here due to sum(integer()) == 0L
+        else if (maxn==1) // including when grpn==1 we default to assuming it's an aggregate
+          estn = LENGTH(starts);
+          // Common case 1 : j is a list of simple aggregates i.e. list of atoms only
+        else if (maxn >= grpn) {
+          estn = 0;
+          for (j=0;j<LENGTH(lens);j++) estn+=INTEGER(lens)[j];
+          // Common case 2 : j returns as many rows as there are in the group (maybe a join)
+          // TO DO: this might over allocate if first group has 1 row and j is actually a single row aggregate
+          //        in cases when we're not sure could wait for the first few groups before deciding.
+        } else  // maxn < grpn
+          estn = maxn * LENGTH(starts);
+          // Common case 3 : head or tail of .SD perhaps
+        if (estn<maxn) estn=maxn;  // if the result for the first group is larger than the table itself(!) Unusual case where a join is being done in j via .SD and the 1-row table is an edge case of bigger picture.
+        PROTECT(ans = allocVector(VECSXP, ngrpcols + njval));
+        protecti++;
+        firstalloc=TRUE;
+        for(j=0; j<ngrpcols; j++) {
+          thiscol = VECTOR_ELT(groups, INTEGER(grpcols)[j]-1);
+          SET_VECTOR_ELT(ans, j, allocVector(TYPEOF(thiscol), estn));
+          copyMostAttrib(thiscol, VECTOR_ELT(ans,j));  // not names, otherwise test 778 would fail
         }
-        if (ansloc + maxn > estn) {
-            if (estn == -1) {
-                // Given first group and j's result on it, make a good guess for size of result required.
-                if (grpn==0)
-                    estn = maxn = 0;   // empty case e.g. test 184. maxn is 1 here due to sum(integer()) == 0L
-                else if (maxn==1) // including when grpn==1 we default to assuming it's an aggregate
-                    estn = LENGTH(starts);
-                    // Common case 1 : j is a list of simple aggregates i.e. list of atoms only
-                else if (maxn >= grpn) {
-                    estn = 0;
-                    for (j=0;j<LENGTH(lens);j++) estn+=INTEGER(lens)[j];
-                    // Common case 2 : j returns as many rows as there are in the group (maybe a join)
-                    // TO DO: this might over allocate if first group has 1 row and j is actually a single row aggregate
-                    //        in cases when we're not sure could wait for the first few groups before deciding.
-                } else  // maxn < grpn
-                    estn = maxn * LENGTH(starts);
-                    // Common case 3 : head or tail of .SD perhaps
-                if (estn<maxn) estn=maxn;  // if the result for the first group is larger than the table itself(!) Unusual case where a join is being done in j via .SD and the 1-row table is an edge case of bigger picture.
-                PROTECT(ans = allocVector(VECSXP, ngrpcols + njval));
-                protecti++;
-                firstalloc=TRUE;
-                for(j=0; j<ngrpcols; j++) {
-                    thiscol = VECTOR_ELT(groups, INTEGER(grpcols)[j]-1);
-                    SET_VECTOR_ELT(ans, j, allocVector(TYPEOF(thiscol), estn));
-                    copyMostAttrib(thiscol, VECTOR_ELT(ans,j));  // not names, otherwise test 778 would fail
-                }
-                for(j=0; j<njval; j++) {
-                    thiscol = VECTOR_ELT(jval, j);
-                    if (isNull(thiscol))
-                        error("Column %d of j's result for the first group is NULL. We rely on the column types of the first result to decide the type expected for the remaining groups (and require consistency). NULL columns are acceptable for later groups (and those are replaced with NA of appropriate type and recycled) but not for the first. Please use a typed empty vector instead, such as integer() or numeric().", j+1);
-                    if (LOGICAL(verbose)[0] && !isNull(getAttrib(thiscol, R_NamesSymbol))) {
-                        if (wasvector) {
-                            Rprintf("j appears to be a named vector. The same names will likely be created over and over again for each group and slow things down. Try and pass a named list (which data.table optimizes) or an unnamed list() instead.");
-                        } else {
-                            Rprintf("Column %d of j is a named vector (each item down the rows is named, somehow). Please remove those names for efficiency (to save creating them over and over for each group). They are ignored anyway.", j+1);
-                        }
-                    }
-                    SET_VECTOR_ELT(ans, ngrpcols+j, allocVector(TYPEOF(thiscol), estn));
-                    copyMostAttrib(thiscol, VECTOR_ELT(ans,ngrpcols+j));  // not names, otherwise test 276 would fail
-                }
-                names = getAttrib(jval, R_NamesSymbol);
-                if (!isNull(names)) {
-                    if (LOGICAL(verbose)[0]) Rprintf("The result of j is a named list. It's very inefficient to create the same names over and over again for each group. When j=list(...), any names are detected, removed and put back after grouping has completed, for efficiency. Using j=transform(), for example, prevents that speedup (consider changing to :=). This message may be upgraded to warning in future.\n");  // e.g. test 104 has j=transform().
-                    // names of result come from the first group and the names of remaining groups are ignored (all that matters for them is that the number of columns (and their types) match the first group.
-                    names2 = PROTECT(allocVector(STRSXP,ngrpcols+njval));
-                    protecti++;
-                    //  for (j=0; j<ngrpcols; j++) SET_STRING_ELT(names2, j, STRING_ELT(bynames,j));  // These get set back up in R
-                    for (j=0; j<njval; j++) SET_STRING_ELT(names2, ngrpcols+j, STRING_ELT(names,j));
-                    setAttrib(ans, R_NamesSymbol, names2);
-                    // setAttrib(SD, R_NamesSymbol, R_NilValue); // so that lapply(.SD,mean) is unnamed from 2nd group on
-                }
+        for(j=0; j<njval; j++) {
+          thiscol = VECTOR_ELT(jval, j);
+          if (isNull(thiscol))
+            error("Column %d of j's result for the first group is NULL. We rely on the column types of the first result to decide the type expected for the remaining groups (and require consistency). NULL columns are acceptable for later groups (and those are replaced with NA of appropriate type and recycled) but not for the first. Please use a typed empty vector instead, such as integer() or numeric().", j+1);
+          if (LOGICAL(verbose)[0] && !isNull(getAttrib(thiscol, R_NamesSymbol))) {
+            if (wasvector) {
+              Rprintf("j appears to be a named vector. The same names will likely be created over and over again for each group and slow things down. Try and pass a named list (which data.table optimizes) or an unnamed list() instead.");
             } else {
-                estn = ((double)ngrp/i)*1.1*(ansloc+maxn);
-                if (LOGICAL(verbose)[0]) Rprintf("dogroups: growing from %d to %d rows\n", length(VECTOR_ELT(ans,0)), estn);
-                if (length(ans) != ngrpcols + njval) error("dogroups: length(ans)[%d]!=ngrpcols[%d]+njval[%d]",length(ans),ngrpcols,njval);
-                for (j=0; j<length(ans); j++) SET_VECTOR_ELT(ans, j, growVector(VECTOR_ELT(ans,j), estn));
+              Rprintf("Column %d of j is a named vector (each item down the rows is named, somehow). Please remove those names for efficiency (to save creating them over and over for each group). They are ignored anyway.", j+1);
             }
+          }
+          SET_VECTOR_ELT(ans, ngrpcols+j, allocVector(TYPEOF(thiscol), estn));
+          copyMostAttrib(thiscol, VECTOR_ELT(ans,ngrpcols+j));  // not names, otherwise test 276 would fail
         }
-        // Now copy jval into ans ...
-        for (j=0; j<ngrpcols; j++) {
-            target = VECTOR_ELT(ans,j);
-            source = VECTOR_ELT(groups, INTEGER(grpcols)[j]-1);  // target and source the same type by construction above
-            if (SIZEOF(target)==4) for (r=0; r<maxn; r++)
-                INTEGER(target)[ansloc+r] = INTEGER(source)[igrp];
-            else for (r=0; r<maxn; r++)
-                REAL(target)[ansloc+r] = REAL(source)[igrp];
-            // Shouldn't need SET_* to age objects here sice groups, TO DO revisit.
+        names = getAttrib(jval, R_NamesSymbol);
+        if (!isNull(names)) {
+          if (LOGICAL(verbose)[0]) Rprintf("The result of j is a named list. It's very inefficient to create the same names over and over again for each group. When j=list(...), any names are detected, removed and put back after grouping has completed, for efficiency. Using j=transform(), for example, prevents that speedup (consider changing to :=). This message may be upgraded to warning in future.\n");  // e.g. test 104 has j=transform().
+          // names of result come from the first group and the names of remaining groups are ignored (all that matters for them is that the number of columns (and their types) match the first group.
+          names2 = PROTECT(allocVector(STRSXP,ngrpcols+njval));
+          protecti++;
+          //  for (j=0; j<ngrpcols; j++) SET_STRING_ELT(names2, j, STRING_ELT(bynames,j));  // These get set back up in R
+          for (j=0; j<njval; j++) SET_STRING_ELT(names2, ngrpcols+j, STRING_ELT(names,j));
+          setAttrib(ans, R_NamesSymbol, names2);
+          // setAttrib(SD, R_NamesSymbol, R_NilValue); // so that lapply(.SD,mean) is unnamed from 2nd group on
         }
-        for (j=0; j<njval; j++) {
-            thisansloc = ansloc;
-            source = VECTOR_ELT(jval,j);
-            thislen = length(source);
-            target = VECTOR_ELT(ans, j+ngrpcols);
-            if (thislen == 0) {
-                // including NULL and typed empty vectors, fill with NA
-                // A NULL in the first group's jval isn't allowed; caught above after allocating ans
-                if (!NullWarnDone && maxn>1) {  // maxn==1 in tests 172,280,281,282,403,405 and 406
-                    warning("Item %d of j's result for group %d is zero length. This will be filled with %d NAs to match the longest column in this result. Later groups may have a similar problem but only the first is reported to save filling the warning buffer.", j+1, i+1, maxn);
-                    NullWarnDone = TRUE;
-                }
-                switch (TYPEOF(target)) {     // rarely called so no need to optimize this switch
-                case LGLSXP :
-                case INTSXP :
-                    for (r=0; r<maxn; r++) INTEGER(target)[thisansloc+r] = NA_INTEGER;
-                    break;
-                case REALSXP :
-                    for (r=0; r<maxn; r++) REAL(target)[thisansloc+r] = NA_REAL;
-                    break;
-                case STRSXP :
-                    for (r=0; r<maxn; r++) SET_STRING_ELT(target,thisansloc+r,NA_STRING);
-                    break;
-                case VECSXP :
-                    for (r=0; r<maxn; r++) SET_VECTOR_ELT(target,thisansloc+r,NA_STRING);
-                    break;
-                default:
-                    error("Logical error. Type of column should have been checked by now");
-                }
-                continue;
-            }
-            if (TYPEOF(source) != TYPEOF(target))
-                error("Column %d of result for group %d is type '%s' but expecting type '%s'. Column types must be consistent for each group.", j+1, i+1, type2char(TYPEOF(source)), type2char(TYPEOF(target)));
-            if (recycleWarn && maxn%thislen != 0) {
-                warning("Column %d of result for group %d is length %d but the longest column in this result is %d. Recycled leaving remainder of %d items. This warning is once only for the first group with this issue.",j+1,i+1,thislen,maxn,maxn%thislen);
-                recycleWarn = FALSE;
-            }
-            memrecycle(target, R_NilValue, thisansloc, maxn, source);
+      } else {
+        estn = ((double)ngrp/i)*1.1*(ansloc+maxn);
+        if (LOGICAL(verbose)[0]) Rprintf("dogroups: growing from %d to %d rows\n", length(VECTOR_ELT(ans,0)), estn);
+        if (length(ans) != ngrpcols + njval) error("dogroups: length(ans)[%d]!=ngrpcols[%d]+njval[%d]",length(ans),ngrpcols,njval);
+        for (j=0; j<length(ans); j++) SET_VECTOR_ELT(ans, j, growVector(VECTOR_ELT(ans,j), estn));
+      }
+    }
+    // Now copy jval into ans ...
+    for (j=0; j<ngrpcols; j++) {
+      target = VECTOR_ELT(ans,j);
+      source = VECTOR_ELT(groups, INTEGER(grpcols)[j]-1);  // target and source the same type by construction above
+      if (SIZEOF(target)==4) for (r=0; r<maxn; r++)
+        INTEGER(target)[ansloc+r] = INTEGER(source)[igrp];
+      else for (r=0; r<maxn; r++)
+        REAL(target)[ansloc+r] = REAL(source)[igrp];
+      // Shouldn't need SET_* to age objects here sice groups, TO DO revisit.
+    }
+    for (j=0; j<njval; j++) {
+      thisansloc = ansloc;
+      source = VECTOR_ELT(jval,j);
+      thislen = length(source);
+      target = VECTOR_ELT(ans, j+ngrpcols);
+      if (thislen == 0) {
+        // including NULL and typed empty vectors, fill with NA
+        // A NULL in the first group's jval isn't allowed; caught above after allocating ans
+        if (!NullWarnDone && maxn>1) {  // maxn==1 in tests 172,280,281,282,403,405 and 406
+          warning("Item %d of j's result for group %d is zero length. This will be filled with %d NAs to match the longest column in this result. Later groups may have a similar problem but only the first is reported to save filling the warning buffer.", j+1, i+1, maxn);
+          NullWarnDone = TRUE;
         }
-        ansloc += maxn;
-        if (firstalloc) {
-            protecti++;          //  remember the first jval. If we UNPROTECTed now, we'd unprotect
-            firstalloc = FALSE;  //  ans. The first jval is needed to create the right size and type of ans.
-            // TO DO: could avoid this last 'if' by adding a dummy PROTECT after first alloc for this UNPROTECT(1) to do.
+        switch (TYPEOF(target)) {     // rarely called so no need to optimize this switch
+        case LGLSXP :
+        case INTSXP :
+          for (r=0; r<maxn; r++) INTEGER(target)[thisansloc+r] = NA_INTEGER;
+          break;
+        case REALSXP :
+          for (r=0; r<maxn; r++) REAL(target)[thisansloc+r] = NA_REAL;
+          break;
+        case STRSXP :
+          for (r=0; r<maxn; r++) SET_STRING_ELT(target,thisansloc+r,NA_STRING);
+          break;
+        case VECSXP :
+          for (r=0; r<maxn; r++) SET_VECTOR_ELT(target,thisansloc+r,NA_STRING);
+          break;
+        default:
+          error("Logical error. Type of column should have been checked by now");
         }
-        else UNPROTECT(1);  // the jval. Don't want them to build up. The first jval can stay protected till the end ok.
+        continue;
+      }
+      if (TYPEOF(source) != TYPEOF(target))
+        error("Column %d of result for group %d is type '%s' but expecting type '%s'. Column types must be consistent for each group.", j+1, i+1, type2char(TYPEOF(source)), type2char(TYPEOF(target)));
+      if (recycleWarn && maxn%thislen != 0) {
+        warning("Column %d of result for group %d is length %d but the longest column in this result is %d. Recycled leaving remainder of %d items. This warning is once only for the first group with this issue.",j+1,i+1,thislen,maxn,maxn%thislen);
+        recycleWarn = FALSE;
+      }
+      memrecycle(target, R_NilValue, thisansloc, maxn, source);
     }
-    if (isNull(lhs) && ans!=NULL) {
-        if (ansloc < LENGTH(VECTOR_ELT(ans,0))) {
-            if (LOGICAL(verbose)[0]) Rprintf("Wrote less rows (%d) than allocated (%d).\n",ansloc,LENGTH(VECTOR_ELT(ans,0)));
-            for (j=0; j<length(ans); j++) SET_VECTOR_ELT(ans, j, growVector(VECTOR_ELT(ans,j), ansloc));
-            // shrinks (misuse of word 'grow') back to the rows written, otherwise leak until ...
-            // ... TO DO: set truelength to LENGTH(VECTOR_ELT(ans,0)), length to ansloc and enhance finalizer to handle over-allocated rows.
-        }
-    } else ans = R_NilValue;
-    // Now reset length of .SD columns and .I to length of largest group, otherwise leak if the last group is smaller (often is).
-    for (j=0; j<length(SDall); j++) SETLENGTH(VECTOR_ELT(SDall,j), origSDnrow);
-    SETLENGTH(I, origIlen);
-    if (LOGICAL(verbose)[0]) {
-        if (nblock[0] && nblock[1]) error("Internal error: block 0 [%d] and block 1 [%d] have both run", nblock[0], nblock[1]);
-        int w = nblock[1]>0;
-        Rprintf("\n  %s took %.3fs for %d groups\n", w ? "collecting discontiguous groups" : "memcpy contiguous groups",
-                                                    1.0*tblock[w]/CLOCKS_PER_SEC, nblock[w]);
-        Rprintf("  eval(j) took %.3fs for %d calls\n", 1.0*tblock[2]/CLOCKS_PER_SEC, nblock[2]);
+    ansloc += maxn;
+    if (firstalloc) {
+      protecti++;          //  remember the first jval. If we UNPROTECTed now, we'd unprotect
+      firstalloc = FALSE;  //  ans. The first jval is needed to create the right size and type of ans.
+      // TO DO: could avoid this last 'if' by adding a dummy PROTECT after first alloc for this UNPROTECT(1) to do.
     }
-    UNPROTECT(protecti);
-    return(ans);
+    else UNPROTECT(1);  // the jval. Don't want them to build up. The first jval can stay protected till the end ok.
+  }
+  if (isNull(lhs) && ans!=NULL) {
+    if (ansloc < LENGTH(VECTOR_ELT(ans,0))) {
+      if (LOGICAL(verbose)[0]) Rprintf("Wrote less rows (%d) than allocated (%d).\n",ansloc,LENGTH(VECTOR_ELT(ans,0)));
+      for (j=0; j<length(ans); j++) SET_VECTOR_ELT(ans, j, growVector(VECTOR_ELT(ans,j), ansloc));
+      // shrinks (misuse of word 'grow') back to the rows written, otherwise leak until ...
+      // ... TO DO: set truelength to LENGTH(VECTOR_ELT(ans,0)), length to ansloc and enhance finalizer to handle over-allocated rows.
+    }
+  } else ans = R_NilValue;
+  // Now reset length of .SD columns and .I to length of largest group, otherwise leak if the last group is smaller (often is).
+  for (j=0; j<length(SDall); j++) SETLENGTH(VECTOR_ELT(SDall,j), origSDnrow);
+  SETLENGTH(I, origIlen);
+  if (LOGICAL(verbose)[0]) {
+    if (nblock[0] && nblock[1]) error("Internal error: block 0 [%d] and block 1 [%d] have both run", nblock[0], nblock[1]);
+    int w = nblock[1]>0;
+    Rprintf("\n  %s took %.3fs for %d groups\n", w ? "collecting discontiguous groups" : "memcpy contiguous groups",
+                          1.0*tblock[w]/CLOCKS_PER_SEC, nblock[w]);
+    Rprintf("  eval(j) took %.3fs for %d calls\n", 1.0*tblock[2]/CLOCKS_PER_SEC, nblock[2]);
+  }
+  UNPROTECT(protecti);
+  return(ans);
 }
 
 SEXP keepattr(SEXP to, SEXP from)
 {
-    // Same as R_copyDFattr in src/main/attrib.c, but that seems not exposed in R's api
-    // Only difference is that we reverse from and to in the prototype, for easier calling above
-    SET_ATTRIB(to, ATTRIB(from));
-    IS_S4_OBJECT(from) ?  SET_S4_OBJECT(to) : UNSET_S4_OBJECT(to);
-    SET_OBJECT(to, OBJECT(from));
-    return to;
+  // Same as R_copyDFattr in src/main/attrib.c, but that seems not exposed in R's api
+  // Only difference is that we reverse from and to in the prototype, for easier calling above
+  SET_ATTRIB(to, ATTRIB(from));
+  IS_S4_OBJECT(from) ?  SET_S4_OBJECT(to) : UNSET_S4_OBJECT(to);
+  SET_OBJECT(to, OBJECT(from));
+  return to;
 }
 
 SEXP growVector(SEXP x, R_len_t newlen)
 {
-    // Similar to EnlargeVector in src/main/subassign.c, with the following changes :
-    // * replaced switch and loops with one memcpy for INTEGER and REAL, but need to age CHAR and VEC.
-    // * no need to cater for names
-    // * much shorter and faster
-    SEXP newx;
-    R_len_t i, len = length(x);
-    if (isNull(x)) error("growVector passed NULL");
-    PROTECT(newx = allocVector(TYPEOF(x), newlen));   // TO DO: R_realloc(?) here?
-    if (newlen < len) len=newlen;   // i.e. shrink
-    switch (TYPEOF(x)) {
-    case STRSXP :
-        for (i=0; i<len; i++)
-            SET_STRING_ELT(newx, i, STRING_ELT(x, i));
-        // TO DO. Using SET_ to ensure objects are aged, rather than memcpy. Perhaps theres a bulk/fast way to age CHECK_OLD_TO_NEW
-        break;
-    case VECSXP :
-        for (i=0; i<len; i++)
-            SET_VECTOR_ELT(newx, i, VECTOR_ELT(x, i));
-        // TO DO: Again, is there bulk op to avoid this loop, which still respects older generations
-        break;
-    default :
-        memcpy((char *)DATAPTR(newx), (char *)DATAPTR(x), len*SIZEOF(x));   // SIZEOF() returns size_t (just as sizeof()) so * shouldn't overflow
-    }
-    // if (verbose) Rprintf("Growing vector from %d to %d items of type '%s'\n", len, newlen, type2char(TYPEOF(x)));
-    // Would print for every column if here. Now just up in dogroups (one msg for each table grow).
-    keepattr(newx,x);
-    UNPROTECT(1);
-    return newx;
+  // Similar to EnlargeVector in src/main/subassign.c, with the following changes :
+  // * replaced switch and loops with one memcpy for INTEGER and REAL, but need to age CHAR and VEC.
+  // * no need to cater for names
+  // * much shorter and faster
+  SEXP newx;
+  R_len_t i, len = length(x);
+  if (isNull(x)) error("growVector passed NULL");
+  PROTECT(newx = allocVector(TYPEOF(x), newlen));   // TO DO: R_realloc(?) here?
+  if (newlen < len) len=newlen;   // i.e. shrink
+  switch (TYPEOF(x)) {
+  case STRSXP :
+    for (i=0; i<len; i++)
+      SET_STRING_ELT(newx, i, STRING_ELT(x, i));
+    // TO DO. Using SET_ to ensure objects are aged, rather than memcpy. Perhaps theres a bulk/fast way to age CHECK_OLD_TO_NEW
+    break;
+  case VECSXP :
+    for (i=0; i<len; i++)
+      SET_VECTOR_ELT(newx, i, VECTOR_ELT(x, i));
+    // TO DO: Again, is there bulk op to avoid this loop, which still respects older generations
+    break;
+  default :
+    memcpy((char *)DATAPTR(newx), (char *)DATAPTR(x), len*SIZEOF(x));   // SIZEOF() returns size_t (just as sizeof()) so * shouldn't overflow
+  }
+  // if (verbose) Rprintf("Growing vector from %d to %d items of type '%s'\n", len, newlen, type2char(TYPEOF(x)));
+  // Would print for every column if here. Now just up in dogroups (one msg for each table grow).
+  keepattr(newx,x);
+  UNPROTECT(1);
+  return newx;
 }
 
 
diff --git a/src/fastmean.c b/src/fastmean.c
index b0b488bfe8..760e5e3fcd 100644
--- a/src/fastmean.c
+++ b/src/fastmean.c
@@ -28,113 +28,113 @@ if we become out of line to base R (say if base R changed its mean).
 
 SEXP fastmean(SEXP args)
 {
-    long double s = 0., t = 0.;
-    R_len_t i, l = 0, n = 0;
-    SEXP x, ans, tmp;
-    Rboolean narm=FALSE;
-    x=CADR(args);
-    if (length(args)>2) {
-        tmp = CADDR(args);
-        if (!isLogical(tmp) || LENGTH(tmp)!=1 || LOGICAL(tmp)[0]==NA_LOGICAL)
-            error("narm should be TRUE or FALSE");
-        narm=LOGICAL(tmp)[0];
-    }
-    PROTECT(ans = allocNAVector(REALSXP, 1));
-    if (!isInteger(x) && !isReal(x) && !isLogical(x)) {
-        warning("argument is not numeric or logical: returning NA");
-        UNPROTECT(1);
-        return(ans);
-    }
-    l = LENGTH(x);
-    if (narm) {
-        switch(TYPEOF(x)) {
-        case LGLSXP:
-        case INTSXP:
-            for (i = 0; i<l; i++) {
-                if(INTEGER(x)[i] == NA_INTEGER) continue;
-                s += INTEGER(x)[i];   // no under/overflow here, s is long double not integer
-                n++;
-            }
-            if (n>0)
-                REAL(ans)[0] = (double) (s/n);
-            else
-                REAL(ans)[0] = R_NaN;  // consistent with base: mean(NA,na.rm=TRUE)==NaN==mean(numeric(),na.rm=TRUE)
-            break;
-        case REALSXP:
-            for (i = 0; i<l; i++) {
-                if(ISNAN(REAL(x)[i])) continue;  // TO DO: could drop this line and let NA propogate?
-                s += REAL(x)[i];
-                n++;
-            }
-            if (n==0) {
-                REAL(ans)[0] = R_NaN;
-                break;
-            }
-            s /= n;
-            if(R_FINITE((double)s)) {
-                for (i = 0; i<l; i++) {
-                    if(ISNAN(REAL(x)[i])) continue;
-                    t += (REAL(x)[i] - s);
-                }
-                s += t/n;
-            }
-            REAL(ans)[0] = (double) s;
-            break;
-        default:
-            error("Type '%s' not supported in fastmean", type2char(TYPEOF(x)));
+  long double s = 0., t = 0.;
+  R_len_t i, l = 0, n = 0;
+  SEXP x, ans, tmp;
+  Rboolean narm=FALSE;
+  x=CADR(args);
+  if (length(args)>2) {
+    tmp = CADDR(args);
+    if (!isLogical(tmp) || LENGTH(tmp)!=1 || LOGICAL(tmp)[0]==NA_LOGICAL)
+      error("narm should be TRUE or FALSE");
+    narm=LOGICAL(tmp)[0];
+  }
+  PROTECT(ans = allocNAVector(REALSXP, 1));
+  if (!isInteger(x) && !isReal(x) && !isLogical(x)) {
+    warning("argument is not numeric or logical: returning NA");
+    UNPROTECT(1);
+    return(ans);
+  }
+  l = LENGTH(x);
+  if (narm) {
+    switch(TYPEOF(x)) {
+    case LGLSXP:
+    case INTSXP:
+      for (i = 0; i<l; i++) {
+        if(INTEGER(x)[i] == NA_INTEGER) continue;
+        s += INTEGER(x)[i];   // no under/overflow here, s is long double not integer
+        n++;
+      }
+      if (n>0)
+        REAL(ans)[0] = (double) (s/n);
+      else
+        REAL(ans)[0] = R_NaN;  // consistent with base: mean(NA,na.rm=TRUE)==NaN==mean(numeric(),na.rm=TRUE)
+      break;
+    case REALSXP:
+      for (i = 0; i<l; i++) {
+        if(ISNAN(REAL(x)[i])) continue;  // TO DO: could drop this line and let NA propogate?
+        s += REAL(x)[i];
+        n++;
+      }
+      if (n==0) {
+        REAL(ans)[0] = R_NaN;
+        break;
+      }
+      s /= n;
+      if(R_FINITE((double)s)) {
+        for (i = 0; i<l; i++) {
+          if(ISNAN(REAL(x)[i])) continue;
+          t += (REAL(x)[i] - s);
         }
-    } else {  // narm==FALSE
-        switch(TYPEOF(x)) {
-        case LGLSXP:
-        case INTSXP:
-            for (i = 0; i<l; i++) {
-                if(INTEGER(x)[i] == NA_INTEGER) {UNPROTECT(1); return(ans);}
-                s += INTEGER(x)[i];
-            }
-            REAL(ans)[0] = (double) (s/l);
-            break;
-        case REALSXP:
-            for (i = 0; i<l; i++) {
-                if(ISNAN(REAL(x)[i])) {UNPROTECT(1); return(ans);}
-                s += REAL(x)[i];
-            }
-            s /= l;
-            if(R_FINITE((double)s)) {
-                for (i = 0; i<l; i++) {
-                    // no NA if got this far
-                    t += (REAL(x)[i] - s);
-                }
-                s += t/LENGTH(x);
-            }
-            REAL(ans)[0] = (double) s;
-            break;
-        default:
-            error("Type '%s' not supported in fastmean", type2char(TYPEOF(x)));
+        s += t/n;
+      }
+      REAL(ans)[0] = (double) s;
+      break;
+    default:
+      error("Type '%s' not supported in fastmean", type2char(TYPEOF(x)));
+    }
+  } else {  // narm==FALSE
+    switch(TYPEOF(x)) {
+    case LGLSXP:
+    case INTSXP:
+      for (i = 0; i<l; i++) {
+        if(INTEGER(x)[i] == NA_INTEGER) {UNPROTECT(1); return(ans);}
+        s += INTEGER(x)[i];
+      }
+      REAL(ans)[0] = (double) (s/l);
+      break;
+    case REALSXP:
+      for (i = 0; i<l; i++) {
+        if(ISNAN(REAL(x)[i])) {UNPROTECT(1); return(ans);}
+        s += REAL(x)[i];
+      }
+      s /= l;
+      if(R_FINITE((double)s)) {
+        for (i = 0; i<l; i++) {
+          // no NA if got this far
+          t += (REAL(x)[i] - s);
         }
+        s += t/LENGTH(x);
+      }
+      REAL(ans)[0] = (double) s;
+      break;
+    default:
+      error("Type '%s' not supported in fastmean", type2char(TYPEOF(x)));
     }
-    copyMostAttrib(x, ans);
-    UNPROTECT(1);
-    return(ans);
+  }
+  copyMostAttrib(x, ans);
+  UNPROTECT(1);
+  return(ans);
 }
 
 /*
-        case CPLXSXP:
-            PROTECT(ans = allocVector(CPLXSXP, 1));
-            for (i = 0; i < n; i++) {
-            s += COMPLEX(x)[i].r;
-            si += COMPLEX(x)[i].i;
-            }
-            s /= n; si /= n;
-            if( R_FINITE((double)s) && R_FINITE((double)si) ) {
-            for (i = 0; i < n; i++) {
-                t += COMPLEX(x)[i].r - s;
-                ti += COMPLEX(x)[i].i - si;
-            }
-            s += t/n; si += ti/n;
-            }
-            COMPLEX(ans)[0].r = (double) s;
-            COMPLEX(ans)[0].i = (double) si;
-            break;
+    case CPLXSXP:
+      PROTECT(ans = allocVector(CPLXSXP, 1));
+      for (i = 0; i < n; i++) {
+      s += COMPLEX(x)[i].r;
+      si += COMPLEX(x)[i].i;
+      }
+      s /= n; si /= n;
+      if( R_FINITE((double)s) && R_FINITE((double)si) ) {
+      for (i = 0; i < n; i++) {
+        t += COMPLEX(x)[i].r - s;
+        ti += COMPLEX(x)[i].i - si;
+      }
+      s += t/n; si += ti/n;
+      }
+      COMPLEX(ans)[0].r = (double) s;
+      COMPLEX(ans)[0].i = (double) si;
+      break;
 */
 
 
diff --git a/src/fcast.c b/src/fcast.c
index 848f050005..a22248195d 100644
--- a/src/fcast.c
+++ b/src/fcast.c
@@ -6,106 +6,106 @@
 // TO DO: margins
 SEXP fcast(SEXP lhs, SEXP val, SEXP nrowArg, SEXP ncolArg, SEXP idxArg, SEXP fill, SEXP fill_d, SEXP is_agg) {
 
-    int nrows=INTEGER(nrowArg)[0], ncols=INTEGER(ncolArg)[0];
-    int i,j,k, nlhs=length(lhs), nval=length(val), *idx = INTEGER(idxArg), thisidx;;
-    SEXP thiscol, target, ans, thisfill;
-    Rboolean isfill = TRUE, count;
+  int nrows=INTEGER(nrowArg)[0], ncols=INTEGER(ncolArg)[0];
+  int i,j,k, nlhs=length(lhs), nval=length(val), *idx = INTEGER(idxArg), thisidx;;
+  SEXP thiscol, target, ans, thisfill;
+  Rboolean isfill = TRUE, count;
 
-    ans = PROTECT(allocVector(VECSXP, nlhs + (nval * ncols)));
-    // set lhs cols
-    for (i=0; i < nlhs; i++) {
-        SET_VECTOR_ELT(ans, i, VECTOR_ELT(lhs, i));
+  ans = PROTECT(allocVector(VECSXP, nlhs + (nval * ncols)));
+  // set lhs cols
+  for (i=0; i < nlhs; i++) {
+    SET_VECTOR_ELT(ans, i, VECTOR_ELT(lhs, i));
+  }
+  // get val cols
+  for (i=0; i<nval; i++) {
+    thiscol = VECTOR_ELT(val, i);
+    thisfill = fill;
+    count = FALSE;
+    if (isNull(fill)) {
+      isfill = FALSE;
+      if (LOGICAL(is_agg)[0]) {
+        thisfill = PROTECT(allocNAVector(TYPEOF(thiscol), 1));
+        count = TRUE;
+      } else thisfill = VECTOR_ELT(fill_d, i);
     }
-    // get val cols
-    for (i=0; i<nval; i++) {
-        thiscol = VECTOR_ELT(val, i);
-        thisfill = fill;
-        count = FALSE;
-        if (isNull(fill)) {
-            isfill = FALSE;
-            if (LOGICAL(is_agg)[0]) {
-                thisfill = PROTECT(allocNAVector(TYPEOF(thiscol), 1));
-                count = TRUE;
-            } else thisfill = VECTOR_ELT(fill_d, i);
+    if (isfill && TYPEOF(fill) != TYPEOF(thiscol)) {
+      thisfill = PROTECT(coerceVector(fill, TYPEOF(thiscol)));
+      count = TRUE;
+    }
+    switch (TYPEOF(thiscol)) {
+    case INTSXP:
+      for (j=0; j<ncols; j++) {
+        target = allocVector(TYPEOF(thiscol), nrows);
+        SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
+        copyMostAttrib(thiscol, target);
+        for (k=0; k<nrows; k++) {
+          thisidx = idx[k*ncols + j];
+          INTEGER(target)[k] = (thisidx == NA_INTEGER) ? INTEGER(thisfill)[0] : INTEGER(thiscol)[thisidx-1];
+        }
+      }
+      break;
+    case REALSXP:
+      for (j=0; j<ncols; j++) {
+        target = allocVector(TYPEOF(thiscol), nrows);
+        SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
+        copyMostAttrib(thiscol, target);
+        for (k=0; k<nrows; k++) {
+          thisidx = idx[k*ncols + j];
+          REAL(target)[k] = (thisidx == NA_INTEGER) ? REAL(thisfill)[0] : REAL(thiscol)[thisidx-1];
+        }
+      }
+      break;
+    case LGLSXP:
+      for (j=0; j<ncols; j++) {
+        target = allocVector(TYPEOF(thiscol), nrows);
+        SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
+        copyMostAttrib(thiscol, target);
+        for (k=0; k<nrows; k++) {
+          thisidx = idx[k*ncols + j];
+          LOGICAL(target)[k] = (thisidx == NA_INTEGER) ? LOGICAL(thisfill)[0] : LOGICAL(thiscol)[thisidx-1];
         }
-        if (isfill && TYPEOF(fill) != TYPEOF(thiscol)) {
-            thisfill = PROTECT(coerceVector(fill, TYPEOF(thiscol)));
-            count = TRUE;
+      }
+      break;
+    case STRSXP:
+      for (j=0; j<ncols; j++) {
+        target = allocVector(TYPEOF(thiscol), nrows);
+        SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
+        copyMostAttrib(thiscol, target);
+        for (k=0; k<nrows; k++) {
+          thisidx = idx[k*ncols + j];
+          SET_STRING_ELT(target, k, (thisidx == NA_INTEGER) ? STRING_ELT(thisfill, 0) : STRING_ELT(thiscol, thisidx-1));
         }
-        switch (TYPEOF(thiscol)) {
-            case INTSXP:
-                for (j=0; j<ncols; j++) {
-                    target = allocVector(TYPEOF(thiscol), nrows);
-                    SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
-                    copyMostAttrib(thiscol, target);
-                    for (k=0; k<nrows; k++) {
-                        thisidx = idx[k*ncols + j];
-                        INTEGER(target)[k] = (thisidx == NA_INTEGER) ? INTEGER(thisfill)[0] : INTEGER(thiscol)[thisidx-1];
-                    }
-                }
-            break;
-            case REALSXP:
-                for (j=0; j<ncols; j++) {
-                    target = allocVector(TYPEOF(thiscol), nrows);
-                    SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
-                    copyMostAttrib(thiscol, target);
-                    for (k=0; k<nrows; k++) {
-                        thisidx = idx[k*ncols + j];
-                        REAL(target)[k] = (thisidx == NA_INTEGER) ? REAL(thisfill)[0] : REAL(thiscol)[thisidx-1];
-                    }
-                }
-            break;
-            case LGLSXP:
-                for (j=0; j<ncols; j++) {
-                    target = allocVector(TYPEOF(thiscol), nrows);
-                    SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
-                    copyMostAttrib(thiscol, target);
-                    for (k=0; k<nrows; k++) {
-                        thisidx = idx[k*ncols + j];
-                        LOGICAL(target)[k] = (thisidx == NA_INTEGER) ? LOGICAL(thisfill)[0] : LOGICAL(thiscol)[thisidx-1];
-                    }
-                }
-            break;
-            case STRSXP:
-                for (j=0; j<ncols; j++) {
-                    target = allocVector(TYPEOF(thiscol), nrows);
-                    SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
-                    copyMostAttrib(thiscol, target);
-                    for (k=0; k<nrows; k++) {
-                        thisidx = idx[k*ncols + j];
-                        SET_STRING_ELT(target, k, (thisidx == NA_INTEGER) ? STRING_ELT(thisfill, 0) : STRING_ELT(thiscol, thisidx-1));
-                    }
-                }
-            break;
-            case VECSXP:
-                for (j=0; j<ncols; j++) {
-                    target = allocVector(TYPEOF(thiscol), nrows);
-                    SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
-                    copyMostAttrib(thiscol, target);
-                    for (k=0; k<nrows; k++) {
-                        thisidx = idx[k*ncols + j];
-                        SET_VECTOR_ELT(target, k, (thisidx == NA_INTEGER) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(thiscol, thisidx-1));
-                    }
-                }
-            break;
+      }
+      break;
+    case VECSXP:
+      for (j=0; j<ncols; j++) {
+        target = allocVector(TYPEOF(thiscol), nrows);
+        SET_VECTOR_ELT(ans, nlhs+j+i*ncols, target);
+        copyMostAttrib(thiscol, target);
+        for (k=0; k<nrows; k++) {
+          thisidx = idx[k*ncols + j];
+          SET_VECTOR_ELT(target, k, (thisidx == NA_INTEGER) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(thiscol, thisidx-1));
         }
-        if (count) UNPROTECT(1);
+      }
+      break;
     }
-    UNPROTECT(1);
-    return(ans);
+    if (count) UNPROTECT(1);
+  }
+  UNPROTECT(1);
+  return(ans);
 }
 
 // used in bmerge.c
 SEXP int_vec_init(R_len_t n, int val) {
-    // Matt removed the SEXP val input because the ScalarInteger() input wasn't PROTECTed in caller
-    // and was potentially leaking/crashing.
-    // This function only used once and only for int, so to remove untested lines (code-coverage)
-    // decided to simplify it rather than add the PROTECT and UNPROTECT in caller.
-    if (n < 0) error("Input argument 'n' to 'int_vec_init' must be >= 0");
-    SEXP ans = PROTECT(allocVector(INTSXP, n));
-    for (R_len_t i=0; i<n; i++) INTEGER(ans)[i] = val;
-    UNPROTECT(1);
-    return(ans);
+  // Matt removed the SEXP val input because the ScalarInteger() input wasn't PROTECTed in caller
+  // and was potentially leaking/crashing.
+  // This function only used once and only for int, so to remove untested lines (code-coverage)
+  // decided to simplify it rather than add the PROTECT and UNPROTECT in caller.
+  if (n < 0) error("Input argument 'n' to 'int_vec_init' must be >= 0");
+  SEXP ans = PROTECT(allocVector(INTSXP, n));
+  for (R_len_t i=0; i<n; i++) INTEGER(ans)[i] = val;
+  UNPROTECT(1);
+  return(ans);
 }
 
 // commenting all unused functions, but not deleting it, just in case
@@ -115,88 +115,86 @@ SEXP int_vec_init(R_len_t n, int val) {
 // // # nocov start
 // // Note: all these functions below are internal functions and are designed specific to fcast.
 // SEXP zero_init(R_len_t n) {
-//     R_len_t i;
-//     SEXP ans;
-//     if (n < 0) error("Input argument 'n' to 'zero_init' must be >= 0");
-//     ans = PROTECT(allocVector(INTSXP, n));
-//     for (i=0; i<n; i++) INTEGER(ans)[i] = 0;
-//     UNPROTECT(1);
-//     return(ans);
+//   R_len_t i;
+//   SEXP ans;
+//   if (n < 0) error("Input argument 'n' to 'zero_init' must be >= 0");
+//   ans = PROTECT(allocVector(INTSXP, n));
+//   for (i=0; i<n; i++) INTEGER(ans)[i] = 0;
+//   UNPROTECT(1);
+//   return(ans);
 // }
 
 // SEXP cast_order(SEXP v, SEXP env) {
-//     R_len_t len;
-//     SEXP call, ans;
-//     if (TYPEOF(env) != ENVSXP) error("Argument 'env' to (data.table internals) 'cast_order' must be an environment");
-//     if (TYPEOF(v) == VECSXP) len = length(VECTOR_ELT(v, 0));
-//     else len = length(v);
-//     PROTECT(call = lang2(install("forder"), v)); // TODO: save the 'eval' by calling directly the C-function.
-//     ans = PROTECT(eval(call, env));
-//     if (length(ans) == 0) { // forder returns integer(0) if already sorted
-//         UNPROTECT(1); // ans
-//         ans = PROTECT(seq_int(len, 1));
-//     }
-//     UNPROTECT(2);
-//     return(ans);
+//   R_len_t len;
+//   SEXP call, ans;
+//   if (TYPEOF(env) != ENVSXP) error("Argument 'env' to (data.table internals) 'cast_order' must be an environment");
+//   if (TYPEOF(v) == VECSXP) len = length(VECTOR_ELT(v, 0));
+//   else len = length(v);
+//   PROTECT(call = lang2(install("forder"), v)); // TODO: save the 'eval' by calling directly the C-function.
+//   ans = PROTECT(eval(call, env));
+//   if (length(ans) == 0) { // forder returns integer(0) if already sorted
+//     UNPROTECT(1); // ans
+//     ans = PROTECT(seq_int(len, 1));
+//   }
+//   UNPROTECT(2);
+//   return(ans);
 // }
 
 // SEXP cross_join(SEXP s, SEXP env) {
-//     // Calling CJ is faster and don't have to worry about sorting or setting key.
-//     SEXP call, r;
-//     if (!isNewList(s) || isNull(s)) error("Argument 's' to 'cross_join' must be a list of length > 0");
-//     PROTECT(call = lang3(install("do.call"), install("CJ"), s));
-//     r = eval(call, env);
-//     UNPROTECT(1);
-//     return(r);
+//   // Calling CJ is faster and don't have to worry about sorting or setting key.
+//   SEXP call, r;
+//   if (!isNewList(s) || isNull(s)) error("Argument 's' to 'cross_join' must be a list of length > 0");
+//   PROTECT(call = lang3(install("do.call"), install("CJ"), s));
+//   r = eval(call, env);
+//   UNPROTECT(1);
+//   return(r);
 // }
 
 // SEXP diff_int(SEXP x, R_len_t n) {
-
-//     R_len_t i;
-//     SEXP ans;
-//     if (TYPEOF(x) != INTSXP) error("Argument 'x' to 'diff_int' must be an integer vector");
-//     ans = PROTECT(allocVector(INTSXP, length(x)));
-//     for (i=1; i<length(x); i++)
-//         INTEGER(ans)[i-1] = INTEGER(x)[i] - INTEGER(x)[i-1];
-//     INTEGER(ans)[length(x)-1] = n - INTEGER(x)[length(x)-1] + 1;
-//     UNPROTECT(1);
-//     return(ans);
+//   R_len_t i;
+//   SEXP ans;
+//   if (TYPEOF(x) != INTSXP) error("Argument 'x' to 'diff_int' must be an integer vector");
+//   ans = PROTECT(allocVector(INTSXP, length(x)));
+//   for (i=1; i<length(x); i++)
+//     INTEGER(ans)[i-1] = INTEGER(x)[i] - INTEGER(x)[i-1];
+//   INTEGER(ans)[length(x)-1] = n - INTEGER(x)[length(x)-1] + 1;
+//   UNPROTECT(1);
+//   return(ans);
 // }
 
 // SEXP intrep(SEXP x, SEXP len) {
-
-//     R_len_t i,j,l=0, k=0;
-//     SEXP ans;
-//     if (TYPEOF(x) != INTSXP || TYPEOF(len) != INTSXP) error("Arguments 'x' and 'len' to 'intrep' should both be integer vectors");
-//     if (length(x) != length(len)) error("'x' and 'len' must be of same length");
-//     // assuming both are of length >= 1
-//     for (i=0; i<length(len); i++)
-//         l += INTEGER(len)[i]; // assuming positive values for len. internal use - can't bother to check.
-//     ans = PROTECT(allocVector(INTSXP, l));
-//     for (i=0; i<length(len); i++) {
-//         for (j=0; j<INTEGER(len)[i]; j++) {
-//             INTEGER(ans)[k++] = INTEGER(x)[i];
-//         }
+//   R_len_t i,j,l=0, k=0;
+//   SEXP ans;
+//   if (TYPEOF(x) != INTSXP || TYPEOF(len) != INTSXP) error("Arguments 'x' and 'len' to 'intrep' should both be integer vectors");
+//   if (length(x) != length(len)) error("'x' and 'len' must be of same length");
+//   // assuming both are of length >= 1
+//   for (i=0; i<length(len); i++)
+//     l += INTEGER(len)[i]; // assuming positive values for len. internal use - can't bother to check.
+//   ans = PROTECT(allocVector(INTSXP, l));
+//   for (i=0; i<length(len); i++) {
+//     for (j=0; j<INTEGER(len)[i]; j++) {
+//       INTEGER(ans)[k++] = INTEGER(x)[i];
 //     }
-//     UNPROTECT(1); // ans
-//     return(ans);
+//   }
+//   UNPROTECT(1); // ans
+//   return(ans);
 // }
 
 // // taken match_transform() from base:::unique.c and modified
 // SEXP coerce_to_char(SEXP s, SEXP env)
 // {
-//     if(OBJECT(s)) {
-//     if(inherits(s, "factor")) return asCharacterFactor(s);
+//   if (OBJECT(s)) {
+//     if (inherits(s, "factor")) return asCharacterFactor(s);
 //     else if(getAttrib(s, R_ClassSymbol) != R_NilValue) {
-//         SEXP call, r;
-//         PROTECT(call = lang2(install("as.character"), s));
-//         r = eval(call, env);
-//         UNPROTECT(1);
-//         return r;
-//     }
+//       SEXP call, r;
+//       PROTECT(call = lang2(install("as.character"), s));
+//       r = eval(call, env);
+//       UNPROTECT(1);
+//       return r;
 //     }
-//     /* else */
-//     return coerceVector(s, STRSXP);
+//   }
+//   /* else */
+//   return coerceVector(s, STRSXP);
 // }
 
 // // # nocov end
diff --git a/src/fmelt.c b/src/fmelt.c
index e03f83e56f..427dd47efa 100644
--- a/src/fmelt.c
+++ b/src/fmelt.c
@@ -5,695 +5,695 @@
 
 // generate from 1 to n (a simple fun for melt, vecseq is convenient from R due to SEXP inputs)
 SEXP seq_int(int n, int start) {
-    SEXP ans = R_NilValue;
-    int i;
-    if (n <= 0) return(ans);
-    PROTECT(ans = allocVector(INTSXP, n));
-    for (i=0; i<n; i++) INTEGER(ans)[i] = start+i;
-    UNPROTECT(1);
-    return(ans);
+  SEXP ans = R_NilValue;
+  int i;
+  if (n <= 0) return(ans);
+  PROTECT(ans = allocVector(INTSXP, n));
+  for (i=0; i<n; i++) INTEGER(ans)[i] = start+i;
+  UNPROTECT(1);
+  return(ans);
 }
 
 // very specific "set_diff" for integers
 SEXP set_diff(SEXP x, int n) {
-    SEXP ans, xmatch;
-    int i, j = 0;
-    if (TYPEOF(x) != INTSXP) error("'x' must be an integer");
-    if (n <= 0) error("'n' must be a positive integer");
-    xmatch = match(x, seq_int(n, 1), 0); // took a while to realise: matches vec against x - thanks to comment from Matthew in assign.c!
-
-    int *buf = (int *) R_alloc(n, sizeof(int));
-    for (i=0; i<n; i++) {
-        if (INTEGER(xmatch)[i] == 0) {
-            buf[j++] = i+1;
-        }
+  SEXP ans, xmatch;
+  int i, j = 0;
+  if (TYPEOF(x) != INTSXP) error("'x' must be an integer");
+  if (n <= 0) error("'n' must be a positive integer");
+  xmatch = match(x, seq_int(n, 1), 0); // took a while to realise: matches vec against x - thanks to comment from Matthew in assign.c!
+
+  int *buf = (int *) R_alloc(n, sizeof(int));
+  for (i=0; i<n; i++) {
+    if (INTEGER(xmatch)[i] == 0) {
+      buf[j++] = i+1;
     }
-    n = j;
-    PROTECT(ans = allocVector(INTSXP, n));
-    if (n) memcpy(INTEGER(ans), buf, sizeof(int) * n); // sizeof is of type size_t - no integer overflow issues
-    UNPROTECT(1);
-    return(ans);
+  }
+  n = j;
+  PROTECT(ans = allocVector(INTSXP, n));
+  if (n) memcpy(INTEGER(ans), buf, sizeof(int) * n); // sizeof is of type size_t - no integer overflow issues
+  UNPROTECT(1);
+  return(ans);
 }
 
 // plucked and modified from base (coerce.c and summary.c).
 // for melt's `na.rm=TRUE` option
 SEXP which_notNA(SEXP x) {
-    SEXP v, ans;
-    int i, j=0, n = length(x);
+  SEXP v, ans;
+  int i, j=0, n = length(x);
 
-    PROTECT(v = allocVector(LGLSXP, n));
-    switch (TYPEOF(x)) {
-    case LGLSXP:
+  PROTECT(v = allocVector(LGLSXP, n));
+  switch (TYPEOF(x)) {
+  case LGLSXP:
     for (i = 0; i < n; i++) LOGICAL(v)[i] = (LOGICAL(x)[i] != NA_LOGICAL);
     break;
-    case INTSXP:
+  case INTSXP:
     for (i = 0; i < n; i++) LOGICAL(v)[i] = (INTEGER(x)[i] != NA_INTEGER);
     break;
-    case REALSXP:
+  case REALSXP:
     for (i = 0; i < n; i++) LOGICAL(v)[i] = !ISNAN(REAL(x)[i]);
     break;
-    case STRSXP:
+  case STRSXP:
     for (i = 0; i < n; i++) LOGICAL(v)[i] = (STRING_ELT(x, i) != NA_STRING);
     break;
-    default:
+  default:
     error("%s() applied to non-(list or vector) of type '%s'",
-            "which_notNA", type2char(TYPEOF(x)));
-    }
-
-    int *buf = (int *) R_alloc(n, sizeof(int));
-    for (i = 0; i < n; i++) {
-        if (LOGICAL(v)[i] == TRUE) {
-            buf[j] = i + 1;
-            j++;
-        }
+      "which_notNA", type2char(TYPEOF(x)));
+  }
+
+  int *buf = (int *) R_alloc(n, sizeof(int));
+  for (i = 0; i < n; i++) {
+    if (LOGICAL(v)[i] == TRUE) {
+      buf[j] = i + 1;
+      j++;
     }
-    n = j;
-    PROTECT(ans = allocVector(INTSXP, n));
-    if (n) memcpy(INTEGER(ans), buf, sizeof(int) * n);
+  }
+  n = j;
+  PROTECT(ans = allocVector(INTSXP, n));
+  if (n) memcpy(INTEGER(ans), buf, sizeof(int) * n);
 
-    UNPROTECT(2);
-    return(ans);
+  UNPROTECT(2);
+  return(ans);
 }
 
 SEXP which(SEXP x, Rboolean val) {
 
-    int i, j=0, n = length(x);
-    SEXP ans;
-    if (!isLogical(x)) error("Argument to 'which' must be logical");
-    int *buf = (int *) R_alloc(n, sizeof(int));
-    for (i = 0; i < n; i++) {
-        if (LOGICAL(x)[i] == val) {
-            buf[j] = i + 1;
-            j++;
-        }
+  int i, j=0, n = length(x);
+  SEXP ans;
+  if (!isLogical(x)) error("Argument to 'which' must be logical");
+  int *buf = (int *) R_alloc(n, sizeof(int));
+  for (i = 0; i < n; i++) {
+    if (LOGICAL(x)[i] == val) {
+      buf[j] = i + 1;
+      j++;
     }
-    n = j;
-    PROTECT(ans = allocVector(INTSXP, n));
-    if (n) memcpy(INTEGER(ans), buf, sizeof(int) * n);
+  }
+  n = j;
+  PROTECT(ans = allocVector(INTSXP, n));
+  if (n) memcpy(INTEGER(ans), buf, sizeof(int) * n);
 
-    UNPROTECT(1);
-    return(ans);
+  UNPROTECT(1);
+  return(ans);
 }
 
 // whichwrapper for R
 SEXP whichwrapper(SEXP x, SEXP val) {
-    // if (LOGICAL(val)[0] == NA_LOGICAL)
-    //     error("val should be logical TRUE/FALSE");
-    return which(x, LOGICAL(val)[0]);
+  // if (LOGICAL(val)[0] == NA_LOGICAL)
+  //     error("val should be logical TRUE/FALSE");
+  return which(x, LOGICAL(val)[0]);
 }
 
 // hack by calling paste using eval. could change this to strcat, but not sure about buffer size for large data.tables... Any ideas Matthew?
 SEXP concat(SEXP vec, SEXP idx) {
 
-    SEXP s, t, v;
-    int i, nidx=length(idx);
-
-    if (TYPEOF(vec) != STRSXP) error("concat: 'vec must be a character vector");
-    if (!isInteger(idx) || length(idx) < 0) error("concat: 'idx' must be an integer vector of length >= 0");
-    for (i=0; i<length(idx); i++) {
-        if (INTEGER(idx)[i] < 0 || INTEGER(idx)[i] > length(vec))
-            error("concat: 'idx' must take values between 0 and length(vec); 0 <= idx <= length(vec)");
-    }
-    PROTECT(v = allocVector(STRSXP, nidx > 5 ? 5 : nidx));
-    for (i=0; i<length(v); i++) {
-        SET_STRING_ELT(v, i, STRING_ELT(vec, INTEGER(idx)[i]-1));
-    }
-    if (nidx > 5) SET_STRING_ELT(v, 4, mkChar("..."));
-    PROTECT(t = s = allocList(3));
-    SET_TYPEOF(t, LANGSXP);
-    SETCAR(t, install("paste")); t = CDR(t);
-    SETCAR(t, v); t = CDR(t);
-    SETCAR(t, mkString(", "));
-    SET_TAG(t, install("collapse"));
-    UNPROTECT(2); // v, (t,s)
-    return(eval(s, R_GlobalEnv));
+  SEXP s, t, v;
+  int i, nidx=length(idx);
+
+  if (TYPEOF(vec) != STRSXP) error("concat: 'vec must be a character vector");
+  if (!isInteger(idx) || length(idx) < 0) error("concat: 'idx' must be an integer vector of length >= 0");
+  for (i=0; i<length(idx); i++) {
+    if (INTEGER(idx)[i] < 0 || INTEGER(idx)[i] > length(vec))
+      error("concat: 'idx' must take values between 0 and length(vec); 0 <= idx <= length(vec)");
+  }
+  PROTECT(v = allocVector(STRSXP, nidx > 5 ? 5 : nidx));
+  for (i=0; i<length(v); i++) {
+    SET_STRING_ELT(v, i, STRING_ELT(vec, INTEGER(idx)[i]-1));
+  }
+  if (nidx > 5) SET_STRING_ELT(v, 4, mkChar("..."));
+  PROTECT(t = s = allocList(3));
+  SET_TYPEOF(t, LANGSXP);
+  SETCAR(t, install("paste")); t = CDR(t);
+  SETCAR(t, v); t = CDR(t);
+  SETCAR(t, mkString(", "));
+  SET_TAG(t, install("collapse"));
+  UNPROTECT(2); // v, (t,s)
+  return(eval(s, R_GlobalEnv));
 }
 
 // deal with measure.vars of type VECSXP
 SEXP measurelist(SEXP measure, SEXP dtnames) {
-    int i, n=length(measure), protecti=0;
-    SEXP ans, tmp;
-    ans = PROTECT(allocVector(VECSXP, n)); protecti++;
-    for (i=0; i<n; i++) {
-        switch(TYPEOF(VECTOR_ELT(measure, i))) {
-            case STRSXP  : tmp = PROTECT(chmatch(VECTOR_ELT(measure, i), dtnames, 0, FALSE)); protecti++; break;
-            case REALSXP : tmp = PROTECT(coerceVector(VECTOR_ELT(measure, i), INTSXP)); protecti++; break;
-            case INTSXP  : tmp = VECTOR_ELT(measure, i); break;
-            default : error("Unknown 'measure.vars' type %s at index %d of list", type2char(TYPEOF(VECTOR_ELT(measure, i))), i+1);
-        }
-        SET_VECTOR_ELT(ans, i, tmp);
+  int i, n=length(measure), protecti=0;
+  SEXP ans, tmp;
+  ans = PROTECT(allocVector(VECSXP, n)); protecti++;
+  for (i=0; i<n; i++) {
+    switch(TYPEOF(VECTOR_ELT(measure, i))) {
+      case STRSXP  : tmp = PROTECT(chmatch(VECTOR_ELT(measure, i), dtnames, 0, FALSE)); protecti++; break;
+      case REALSXP : tmp = PROTECT(coerceVector(VECTOR_ELT(measure, i), INTSXP)); protecti++; break;
+      case INTSXP  : tmp = VECTOR_ELT(measure, i); break;
+      default : error("Unknown 'measure.vars' type %s at index %d of list", type2char(TYPEOF(VECTOR_ELT(measure, i))), i+1);
     }
-    UNPROTECT(protecti);
-    return(ans);
+    SET_VECTOR_ELT(ans, i, tmp);
+  }
+  UNPROTECT(protecti);
+  return(ans);
 }
 
 // internal function just to unlist integer lists
 static SEXP unlist_(SEXP xint) {
-    int i,j,k=0,totn=0,n=length(xint);
-    SEXP ans, tmp;
-    for (i=0; i<n; i++)
-        totn += length(VECTOR_ELT(xint, i));
-    ans = PROTECT(allocVector(INTSXP, totn));
-    for (i=0; i<n; i++) {
-        tmp = VECTOR_ELT(xint, i);
-        for (j=0; j<length(tmp); j++)
-            INTEGER(ans)[k++] = INTEGER(tmp)[j];
-    }
-    UNPROTECT(1);
-    return(ans);
+  int i,j,k=0,totn=0,n=length(xint);
+  SEXP ans, tmp;
+  for (i=0; i<n; i++)
+    totn += length(VECTOR_ELT(xint, i));
+  ans = PROTECT(allocVector(INTSXP, totn));
+  for (i=0; i<n; i++) {
+    tmp = VECTOR_ELT(xint, i);
+    for (j=0; j<length(tmp); j++)
+      INTEGER(ans)[k++] = INTEGER(tmp)[j];
+  }
+  UNPROTECT(1);
+  return(ans);
 }
 
 SEXP checkVars(SEXP DT, SEXP id, SEXP measure, Rboolean verbose) {
-    int i, ncol=LENGTH(DT), targetcols=0, protecti=0, u=0, v=0;
-    SEXP thiscol, idcols = R_NilValue, valuecols = R_NilValue, tmp, tmp2, booltmp, unqtmp, ans;
-    SEXP dtnames = getAttrib(DT, R_NamesSymbol);
-
-    if (isNull(id) && isNull(measure)) {
-        for (i=0; i<ncol; i++) {
-            thiscol = VECTOR_ELT(DT, i);
-            if ((isInteger(thiscol) || isNumeric(thiscol) || isLogical(thiscol)) && !isFactor(thiscol)) targetcols++;
-        }
-        idcols = PROTECT(allocVector(INTSXP, ncol-targetcols)); protecti++;
-        tmp = PROTECT(allocVector(INTSXP, targetcols)); protecti++;
-        for (i=0; i<ncol; i++) {
-            thiscol = VECTOR_ELT(DT, i);
-            if ((isInteger(thiscol) || isNumeric(thiscol) || isLogical(thiscol)) && !isFactor(thiscol)) {
-                INTEGER(tmp)[u++] = i+1;
-            } else
-                INTEGER(idcols)[v++] = i+1;
-        }
-        valuecols = PROTECT(allocVector(VECSXP, 1)); protecti++;
-        SET_VECTOR_ELT(valuecols, 0, tmp);
-        warning("To be consistent with reshape2's melt, id.vars and measure.vars are internally guessed when both are 'NULL'. All non-numeric/integer/logical type columns are conisdered id.vars, which in this case are columns [%s]. Consider providing at least one of 'id' or 'measure' vars in future.", CHAR(STRING_ELT(concat(dtnames, idcols), 0)));
-    } else if (!isNull(id) && isNull(measure)) {
-        switch(TYPEOF(id)) {
-            case STRSXP  : PROTECT(tmp = chmatch(id, dtnames, 0, FALSE)); protecti++; break;
-            case REALSXP : PROTECT(tmp = coerceVector(id, INTSXP)); protecti++; break;
-            case INTSXP  : tmp = id; break;
-            default : error("Unknown 'id.vars' type %s, must be character or integer vector", type2char(TYPEOF(id)));
-        }
-        booltmp = PROTECT(duplicated(tmp, FALSE)); protecti++;
-        for (i=0; i<length(tmp); i++) {
-            if (INTEGER(tmp)[i] <= 0 || INTEGER(tmp)[i] > ncol)
-                error("One or more values in 'id.vars' is invalid.");
-            else if (!LOGICAL(booltmp)[i]) targetcols++;
-            else continue;
-        }
-        unqtmp = PROTECT(allocVector(INTSXP, targetcols)); protecti++;
-        u = 0;
-        for (i=0; i<length(booltmp); i++) {
-            if (!LOGICAL(booltmp)[i]) {
-                INTEGER(unqtmp)[u++] = INTEGER(tmp)[i];
-            }
-        }
-        tmp2 = PROTECT(set_diff(unqtmp, ncol)); protecti++;
-        valuecols = PROTECT(allocVector(VECSXP, 1)); protecti++;
-        SET_VECTOR_ELT(valuecols, 0, tmp2);
-        idcols = tmp;
-        if (verbose) {
-            Rprintf("'measure.vars' is missing. Assigning all columns other than 'id.vars' columns as 'measure.vars'.\n");
-            if (length(tmp2)) Rprintf("Assigned 'measure.vars' are [%s].\n", CHAR(STRING_ELT(concat(dtnames, tmp2), 0)));
-        }
-    } else if (isNull(id) && !isNull(measure)) {
-        switch(TYPEOF(measure)) {
-            case STRSXP  : tmp2 = PROTECT(chmatch(measure, dtnames, 0, FALSE)); protecti++; break;
-            case REALSXP : tmp2 = PROTECT(coerceVector(measure, INTSXP)); protecti++; break;
-            case INTSXP  : tmp2 = measure; break;
-            case VECSXP  : tmp2 = PROTECT(measurelist(measure, dtnames)); protecti++; break;
-            default : error("Unknown 'measure.vars' type %s, must be character or integer vector/list", type2char(TYPEOF(measure)));
-        }
-        tmp = tmp2;
-        if (isNewList(measure)) {
-            tmp = PROTECT(unlist_(tmp2)); protecti++;
-        }
-        booltmp = PROTECT(duplicated(tmp, FALSE)); protecti++;
-        for (i=0; i<length(tmp); i++) {
-            if (INTEGER(tmp)[i] <= 0 || INTEGER(tmp)[i] > ncol)
-                error("One or more values in 'measure.vars' is invalid.");
-            else if (!LOGICAL(booltmp)[i]) targetcols++;
-            else continue;
-        }
-        unqtmp = PROTECT(allocVector(INTSXP, targetcols)); protecti++;
-        u = 0;
-        for (i=0; i<length(booltmp); i++) {
-            if (!LOGICAL(booltmp)[i]) {
-                INTEGER(unqtmp)[u++] = INTEGER(tmp)[i];
-            }
-        }
-        idcols = PROTECT(set_diff(unqtmp, ncol)); protecti++;
-        if (isNewList(measure)) valuecols = tmp2;
-        else {
-            valuecols = PROTECT(allocVector(VECSXP, 1)); protecti++;
-            SET_VECTOR_ELT(valuecols, 0, tmp2);
-        }
-        if (verbose) {
-            Rprintf("'id.vars' is missing. Assigning all columns other than 'measure.vars' columns as 'id.vars'.\n");
-            if (length(idcols)) Rprintf("Assigned 'id.vars' are [%s].\n", CHAR(STRING_ELT(concat(dtnames, idcols), 0)));
-        }
-    } else if (!isNull(id) && !isNull(measure)) {
-        switch(TYPEOF(id)) {
-            case STRSXP  : tmp = PROTECT(chmatch(id, dtnames, 0, FALSE)); protecti++; break;
-            case REALSXP : tmp = PROTECT(coerceVector(id, INTSXP)); protecti++; break;
-            case INTSXP  : tmp = id; break;
-            default : error("Unknown 'id.vars' type %s, must be character or integer vector", type2char(TYPEOF(id)));
-        }
-        for (i=0; i<length(tmp); i++) {
-            if (INTEGER(tmp)[i] <= 0 || INTEGER(tmp)[i] > ncol)
-                error("One or more values in 'id.vars' is invalid.");
-        }
-        idcols = PROTECT(tmp); protecti++;
-        switch(TYPEOF(measure)) {
-            case STRSXP  : tmp2 = PROTECT(chmatch(measure, dtnames, 0, FALSE)); protecti++; break;
-            case REALSXP : tmp2 = PROTECT(coerceVector(measure, INTSXP)); protecti++; break;
-            case INTSXP  : tmp2 = measure; break;
-            case VECSXP  : tmp2 = PROTECT(measurelist(measure, dtnames)); protecti++; break;
-            default : error("Unknown 'measure.vars' type %s, must be character or integer vector", type2char(TYPEOF(measure)));
-        }
-        tmp = tmp2;
-        if (isNewList(measure)) {
-            tmp = PROTECT(unlist_(tmp2)); protecti++;
-        }
-        for (i=0; i<length(tmp); i++) {
-            if (INTEGER(tmp)[i] <= 0 || INTEGER(tmp)[i] > ncol)
-                error("One or more values in 'measure.vars' is invalid.");
-        }
-        if (isNewList(measure)) valuecols = tmp2;
-        else {
-            valuecols = PROTECT(allocVector(VECSXP, 1)); protecti++;
-            SET_VECTOR_ELT(valuecols, 0, tmp2);
-        }
+  int i, ncol=LENGTH(DT), targetcols=0, protecti=0, u=0, v=0;
+  SEXP thiscol, idcols = R_NilValue, valuecols = R_NilValue, tmp, tmp2, booltmp, unqtmp, ans;
+  SEXP dtnames = getAttrib(DT, R_NamesSymbol);
+
+  if (isNull(id) && isNull(measure)) {
+    for (i=0; i<ncol; i++) {
+      thiscol = VECTOR_ELT(DT, i);
+      if ((isInteger(thiscol) || isNumeric(thiscol) || isLogical(thiscol)) && !isFactor(thiscol)) targetcols++;
+    }
+    idcols = PROTECT(allocVector(INTSXP, ncol-targetcols)); protecti++;
+    tmp = PROTECT(allocVector(INTSXP, targetcols)); protecti++;
+    for (i=0; i<ncol; i++) {
+      thiscol = VECTOR_ELT(DT, i);
+      if ((isInteger(thiscol) || isNumeric(thiscol) || isLogical(thiscol)) && !isFactor(thiscol)) {
+        INTEGER(tmp)[u++] = i+1;
+      } else
+        INTEGER(idcols)[v++] = i+1;
+    }
+    valuecols = PROTECT(allocVector(VECSXP, 1)); protecti++;
+    SET_VECTOR_ELT(valuecols, 0, tmp);
+    warning("To be consistent with reshape2's melt, id.vars and measure.vars are internally guessed when both are 'NULL'. All non-numeric/integer/logical type columns are conisdered id.vars, which in this case are columns [%s]. Consider providing at least one of 'id' or 'measure' vars in future.", CHAR(STRING_ELT(concat(dtnames, idcols), 0)));
+  } else if (!isNull(id) && isNull(measure)) {
+    switch(TYPEOF(id)) {
+      case STRSXP  : PROTECT(tmp = chmatch(id, dtnames, 0, FALSE)); protecti++; break;
+      case REALSXP : PROTECT(tmp = coerceVector(id, INTSXP)); protecti++; break;
+      case INTSXP  : tmp = id; break;
+      default : error("Unknown 'id.vars' type %s, must be character or integer vector", type2char(TYPEOF(id)));
+    }
+    booltmp = PROTECT(duplicated(tmp, FALSE)); protecti++;
+    for (i=0; i<length(tmp); i++) {
+      if (INTEGER(tmp)[i] <= 0 || INTEGER(tmp)[i] > ncol)
+        error("One or more values in 'id.vars' is invalid.");
+      else if (!LOGICAL(booltmp)[i]) targetcols++;
+      else continue;
+    }
+    unqtmp = PROTECT(allocVector(INTSXP, targetcols)); protecti++;
+    u = 0;
+    for (i=0; i<length(booltmp); i++) {
+      if (!LOGICAL(booltmp)[i]) {
+        INTEGER(unqtmp)[u++] = INTEGER(tmp)[i];
+      }
+    }
+    tmp2 = PROTECT(set_diff(unqtmp, ncol)); protecti++;
+    valuecols = PROTECT(allocVector(VECSXP, 1)); protecti++;
+    SET_VECTOR_ELT(valuecols, 0, tmp2);
+    idcols = tmp;
+    if (verbose) {
+      Rprintf("'measure.vars' is missing. Assigning all columns other than 'id.vars' columns as 'measure.vars'.\n");
+      if (length(tmp2)) Rprintf("Assigned 'measure.vars' are [%s].\n", CHAR(STRING_ELT(concat(dtnames, tmp2), 0)));
+    }
+  } else if (isNull(id) && !isNull(measure)) {
+    switch(TYPEOF(measure)) {
+      case STRSXP  : tmp2 = PROTECT(chmatch(measure, dtnames, 0, FALSE)); protecti++; break;
+      case REALSXP : tmp2 = PROTECT(coerceVector(measure, INTSXP)); protecti++; break;
+      case INTSXP  : tmp2 = measure; break;
+      case VECSXP  : tmp2 = PROTECT(measurelist(measure, dtnames)); protecti++; break;
+      default : error("Unknown 'measure.vars' type %s, must be character or integer vector/list", type2char(TYPEOF(measure)));
+    }
+    tmp = tmp2;
+    if (isNewList(measure)) {
+      tmp = PROTECT(unlist_(tmp2)); protecti++;
+    }
+    booltmp = PROTECT(duplicated(tmp, FALSE)); protecti++;
+    for (i=0; i<length(tmp); i++) {
+      if (INTEGER(tmp)[i] <= 0 || INTEGER(tmp)[i] > ncol)
+        error("One or more values in 'measure.vars' is invalid.");
+      else if (!LOGICAL(booltmp)[i]) targetcols++;
+      else continue;
+    }
+    unqtmp = PROTECT(allocVector(INTSXP, targetcols)); protecti++;
+    u = 0;
+    for (i=0; i<length(booltmp); i++) {
+      if (!LOGICAL(booltmp)[i]) {
+        INTEGER(unqtmp)[u++] = INTEGER(tmp)[i];
+      }
     }
-    ans = PROTECT(allocVector(VECSXP, 2)); protecti++;
-    SET_VECTOR_ELT(ans, 0, idcols);
-    SET_VECTOR_ELT(ans, 1, valuecols);
-    UNPROTECT(protecti);
-    return(ans);
+    idcols = PROTECT(set_diff(unqtmp, ncol)); protecti++;
+    if (isNewList(measure)) valuecols = tmp2;
+    else {
+      valuecols = PROTECT(allocVector(VECSXP, 1)); protecti++;
+      SET_VECTOR_ELT(valuecols, 0, tmp2);
+    }
+    if (verbose) {
+      Rprintf("'id.vars' is missing. Assigning all columns other than 'measure.vars' columns as 'id.vars'.\n");
+      if (length(idcols)) Rprintf("Assigned 'id.vars' are [%s].\n", CHAR(STRING_ELT(concat(dtnames, idcols), 0)));
+    }
+  } else if (!isNull(id) && !isNull(measure)) {
+    switch(TYPEOF(id)) {
+      case STRSXP  : tmp = PROTECT(chmatch(id, dtnames, 0, FALSE)); protecti++; break;
+      case REALSXP : tmp = PROTECT(coerceVector(id, INTSXP)); protecti++; break;
+      case INTSXP  : tmp = id; break;
+      default : error("Unknown 'id.vars' type %s, must be character or integer vector", type2char(TYPEOF(id)));
+    }
+    for (i=0; i<length(tmp); i++) {
+      if (INTEGER(tmp)[i] <= 0 || INTEGER(tmp)[i] > ncol)
+        error("One or more values in 'id.vars' is invalid.");
+    }
+    idcols = PROTECT(tmp); protecti++;
+    switch(TYPEOF(measure)) {
+      case STRSXP  : tmp2 = PROTECT(chmatch(measure, dtnames, 0, FALSE)); protecti++; break;
+      case REALSXP : tmp2 = PROTECT(coerceVector(measure, INTSXP)); protecti++; break;
+      case INTSXP  : tmp2 = measure; break;
+      case VECSXP  : tmp2 = PROTECT(measurelist(measure, dtnames)); protecti++; break;
+      default : error("Unknown 'measure.vars' type %s, must be character or integer vector", type2char(TYPEOF(measure)));
+    }
+    tmp = tmp2;
+    if (isNewList(measure)) {
+      tmp = PROTECT(unlist_(tmp2)); protecti++;
+    }
+    for (i=0; i<length(tmp); i++) {
+      if (INTEGER(tmp)[i] <= 0 || INTEGER(tmp)[i] > ncol)
+        error("One or more values in 'measure.vars' is invalid.");
+    }
+    if (isNewList(measure)) valuecols = tmp2;
+    else {
+      valuecols = PROTECT(allocVector(VECSXP, 1)); protecti++;
+      SET_VECTOR_ELT(valuecols, 0, tmp2);
+    }
+  }
+  ans = PROTECT(allocVector(VECSXP, 2)); protecti++;
+  SET_VECTOR_ELT(ans, 0, idcols);
+  SET_VECTOR_ELT(ans, 1, valuecols);
+  UNPROTECT(protecti);
+  return(ans);
 }
 
 struct processData {
-    SEXP idcols, valuecols, naidx;
-    int lids, lvalues, lmax, lmin, protecti, totlen, nrow;
-    int *isfactor, *leach, *isidentical;
-    SEXPTYPE *maxtype;
-    Rboolean narm;
+  SEXP idcols, valuecols, naidx;
+  int lids, lvalues, lmax, lmin, protecti, totlen, nrow;
+  int *isfactor, *leach, *isidentical;
+  SEXPTYPE *maxtype;
+  Rboolean narm;
 };
 
 static void preprocess(SEXP DT, SEXP id, SEXP measure, SEXP varnames, SEXP valnames, Rboolean narm, Rboolean verbose, struct processData *data) {
 
-    SEXP vars,tmp,thiscol;
-    SEXPTYPE type;
-    int i,j;
-    data->lmax = 0; data->lmin = 0; data->protecti = 0, data->totlen = 0, data->nrow = length(VECTOR_ELT(DT, 0));
-    vars = checkVars(DT, id, measure, verbose);
-    data->idcols = PROTECT(VECTOR_ELT(vars, 0)); data->protecti++;
-    data->valuecols = PROTECT(VECTOR_ELT(vars, 1)); data->protecti++;
-    data->lids = length(data->idcols);
-    data->lvalues = length(data->valuecols);
-    data->narm = narm;
-    if (length(valnames) != data->lvalues) {
-        UNPROTECT(data->protecti);
-        if (isNewList(measure)) error("When 'measure.vars' is a list, 'value.name' must be a character vector of length =1 or =length(measure.vars).");
-        else error("When 'measure.vars' is either not specified or a character/integer vector, 'value.name' must be a character vector of length =1.");
-    }
-    if (length(varnames) != 1)
-        error("'variable.name' must be a character/integer vector of length=1.");
-    data->leach = (int *)R_alloc(data->lvalues, sizeof(int));
-    data->isidentical = (int *)R_alloc(data->lvalues, sizeof(int));
-    data->isfactor = (int *)R_alloc(data->lvalues, sizeof(int));
-    data->maxtype = (SEXPTYPE *)R_alloc(data->lvalues, sizeof(SEXPTYPE));
-    for (i=0; i<data->lvalues; i++) {
-        tmp = VECTOR_ELT(data->valuecols, i);
-        data->leach[i] = length(tmp);
-        data->isidentical[i] = 1;  // TODO - why 1 and not Rboolean TRUE?
-        data->isfactor[i] = 0;  // seems to hold 2 below, so not an Rboolean FALSE here. TODO - better name for variable?
-        data->maxtype[i] = 0;   // R_alloc doesn't initialize so careful to here, relied on below
-        data->lmax = (data->lmax > data->leach[i]) ? data->lmax : data->leach[i];
-        data->lmin = (data->lmin < data->leach[i]) ? data->lmin : data->leach[i];
-        for (j=0; j<data->leach[i]; j++) {
-            thiscol = VECTOR_ELT(DT, INTEGER(tmp)[j]-1);
-            if (isFactor(thiscol)) {
-                data->isfactor[i] = (isOrdered(thiscol)) ? 2 : 1;
-                data->maxtype[i]  = STRSXP;
-            } else {
-                type = TYPEOF(thiscol);
-                if (type > data->maxtype[i]) data->maxtype[i] = type;
-            }
-        }
-        for (j=0; j<data->leach[i]; j++) {
-            thiscol = VECTOR_ELT(DT, INTEGER(tmp)[j]-1);
-            if ( (!isFactor(thiscol) && data->maxtype[i] != TYPEOF(thiscol)) || (isFactor(thiscol) && data->maxtype[i] != STRSXP) ) {
-                data->isidentical[i] = 0;
-                break;
-            }
-        }
+  SEXP vars,tmp,thiscol;
+  SEXPTYPE type;
+  int i,j;
+  data->lmax = 0; data->lmin = 0; data->protecti = 0, data->totlen = 0, data->nrow = length(VECTOR_ELT(DT, 0));
+  vars = checkVars(DT, id, measure, verbose);
+  data->idcols = PROTECT(VECTOR_ELT(vars, 0)); data->protecti++;
+  data->valuecols = PROTECT(VECTOR_ELT(vars, 1)); data->protecti++;
+  data->lids = length(data->idcols);
+  data->lvalues = length(data->valuecols);
+  data->narm = narm;
+  if (length(valnames) != data->lvalues) {
+    UNPROTECT(data->protecti);
+    if (isNewList(measure)) error("When 'measure.vars' is a list, 'value.name' must be a character vector of length =1 or =length(measure.vars).");
+    else error("When 'measure.vars' is either not specified or a character/integer vector, 'value.name' must be a character vector of length =1.");
+  }
+  if (length(varnames) != 1)
+    error("'variable.name' must be a character/integer vector of length=1.");
+  data->leach = (int *)R_alloc(data->lvalues, sizeof(int));
+  data->isidentical = (int *)R_alloc(data->lvalues, sizeof(int));
+  data->isfactor = (int *)R_alloc(data->lvalues, sizeof(int));
+  data->maxtype = (SEXPTYPE *)R_alloc(data->lvalues, sizeof(SEXPTYPE));
+  for (i=0; i<data->lvalues; i++) {
+    tmp = VECTOR_ELT(data->valuecols, i);
+    data->leach[i] = length(tmp);
+    data->isidentical[i] = 1;  // TODO - why 1 and not Rboolean TRUE?
+    data->isfactor[i] = 0;  // seems to hold 2 below, so not an Rboolean FALSE here. TODO - better name for variable?
+    data->maxtype[i] = 0;   // R_alloc doesn't initialize so careful to here, relied on below
+    data->lmax = (data->lmax > data->leach[i]) ? data->lmax : data->leach[i];
+    data->lmin = (data->lmin < data->leach[i]) ? data->lmin : data->leach[i];
+    for (j=0; j<data->leach[i]; j++) {
+      thiscol = VECTOR_ELT(DT, INTEGER(tmp)[j]-1);
+      if (isFactor(thiscol)) {
+        data->isfactor[i] = (isOrdered(thiscol)) ? 2 : 1;
+        data->maxtype[i]  = STRSXP;
+      } else {
+        type = TYPEOF(thiscol);
+        if (type > data->maxtype[i]) data->maxtype[i] = type;
+      }
     }
-    if (data->narm) {
-        data->naidx = PROTECT(allocVector(VECSXP, data->lmax));
-        data->protecti++;
+    for (j=0; j<data->leach[i]; j++) {
+      thiscol = VECTOR_ELT(DT, INTEGER(tmp)[j]-1);
+      if ( (!isFactor(thiscol) && data->maxtype[i] != TYPEOF(thiscol)) || (isFactor(thiscol) && data->maxtype[i] != STRSXP) ) {
+        data->isidentical[i] = 0;
+        break;
+      }
     }
+  }
+  if (data->narm) {
+    data->naidx = PROTECT(allocVector(VECSXP, data->lmax));
+    data->protecti++;
+  }
 }
 
 SEXP getvaluecols(SEXP DT, SEXP dtnames, Rboolean valfactor, Rboolean verbose, struct processData *data) {
 
-    int i, j, k, protecti=0, counter=0, thislen=0;
-    SEXP tmp, seqcols, thiscol, thisvaluecols, target, ansvals, thisidx=R_NilValue, flevels, clevels;
-    Rboolean coerced=FALSE, thisfac=FALSE, copyattr = FALSE, thisvalfactor;
-    size_t size;
-    for (i=0; i<data->lvalues; i++) {
-        thisvaluecols = VECTOR_ELT(data->valuecols, i);
-        if (!data->isidentical[i])
-            warning("'measure.vars' [%s] are not all of the same type. By order of hierarchy, the molten data value column will be of type '%s'. All measure variables not of type '%s' will be coerced to. Check DETAILS in ?melt.data.table for more on coercion.\n", CHAR(STRING_ELT(concat(dtnames, thisvaluecols), 0)), type2char(data->maxtype[i]), type2char(data->maxtype[i]));
-        if (data->maxtype[i] == VECSXP && data->narm) {
-            if (verbose) Rprintf("The molten data value type is a list at item %d. 'na.rm=TRUE' is ignored.\n", i+1);
-            data->narm = FALSE;
+  int i, j, k, protecti=0, counter=0, thislen=0;
+  SEXP tmp, seqcols, thiscol, thisvaluecols, target, ansvals, thisidx=R_NilValue, flevels, clevels;
+  Rboolean coerced=FALSE, thisfac=FALSE, copyattr = FALSE, thisvalfactor;
+  size_t size;
+  for (i=0; i<data->lvalues; i++) {
+    thisvaluecols = VECTOR_ELT(data->valuecols, i);
+    if (!data->isidentical[i])
+      warning("'measure.vars' [%s] are not all of the same type. By order of hierarchy, the molten data value column will be of type '%s'. All measure variables not of type '%s' will be coerced to. Check DETAILS in ?melt.data.table for more on coercion.\n", CHAR(STRING_ELT(concat(dtnames, thisvaluecols), 0)), type2char(data->maxtype[i]), type2char(data->maxtype[i]));
+    if (data->maxtype[i] == VECSXP && data->narm) {
+      if (verbose) Rprintf("The molten data value type is a list at item %d. 'na.rm=TRUE' is ignored.\n", i+1);
+      data->narm = FALSE;
+    }
+  }
+  if (data->narm) {
+    seqcols = PROTECT(seq_int(data->lvalues, 1)); protecti++;
+    for (i=0; i<data->lmax; i++) {
+      tmp = PROTECT(allocVector(VECSXP, data->lvalues));
+      for (j=0; j<data->lvalues; j++) {
+        if (i < data->leach[j]) {
+          thisvaluecols = VECTOR_ELT(data->valuecols, j);
+          SET_VECTOR_ELT(tmp, j, VECTOR_ELT(DT, INTEGER(thisvaluecols)[i]-1));
+        } else {
+          SET_VECTOR_ELT(tmp, j, allocNAVector(data->maxtype[j], data->nrow));
         }
+      }
+      tmp = PROTECT(dt_na(tmp, seqcols));
+      SET_VECTOR_ELT(data->naidx, i, which(tmp, FALSE));
+      UNPROTECT(2); // tmp
+      data->totlen += length(VECTOR_ELT(data->naidx, i));
     }
-    if (data->narm) {
-        seqcols = PROTECT(seq_int(data->lvalues, 1)); protecti++;
-        for (i=0; i<data->lmax; i++) {
-            tmp = PROTECT(allocVector(VECSXP, data->lvalues));
-            for (j=0; j<data->lvalues; j++) {
-                if (i < data->leach[j]) {
-                    thisvaluecols = VECTOR_ELT(data->valuecols, j);
-                    SET_VECTOR_ELT(tmp, j, VECTOR_ELT(DT, INTEGER(thisvaluecols)[i]-1));
-                } else {
-                    SET_VECTOR_ELT(tmp, j, allocNAVector(data->maxtype[j], data->nrow));
-                }
-            }
-            tmp = PROTECT(dt_na(tmp, seqcols));
-            SET_VECTOR_ELT(data->naidx, i, which(tmp, FALSE));
-            UNPROTECT(2); // tmp
-            data->totlen += length(VECTOR_ELT(data->naidx, i));
+  } else data->totlen = data->nrow * data->lmax;
+  flevels = PROTECT(allocVector(VECSXP, data->lmax)); protecti++;
+  Rboolean *isordered = (Rboolean *)R_alloc(data->lmax, sizeof(Rboolean));
+  ansvals = PROTECT(allocVector(VECSXP, data->lvalues)); protecti++;
+  for (i=0; i<data->lvalues; i++) {
+    thisvalfactor = (data->maxtype[i] == VECSXP) ? FALSE : valfactor;
+    target = allocVector(data->maxtype[i], data->totlen);
+    SET_VECTOR_ELT(ansvals, i, target);
+    thisvaluecols = VECTOR_ELT(data->valuecols, i);
+    counter = 0; copyattr = FALSE;
+    for (j=0; j<data->lmax; j++) {
+      thiscol = (j < data->leach[i]) ? VECTOR_ELT(DT, INTEGER(thisvaluecols)[j]-1)
+                       : allocNAVector(data->maxtype[i], data->nrow);
+      if (!copyattr && data->isidentical[i] && !data->isfactor[i]) {
+        copyMostAttrib(thiscol, target);
+        copyattr = TRUE;
+      }
+      if (TYPEOF(thiscol) != TYPEOF(target) && (data->maxtype[i] == VECSXP || !isFactor(thiscol))) {
+        thiscol = PROTECT(coerceVector(thiscol, TYPEOF(target)));
+        coerced = TRUE;
+      }
+      if (data->narm) {
+        thisidx = VECTOR_ELT(data->naidx, j);
+        thislen = length(thisidx);
+      }
+      size = SIZEOF(thiscol);
+      switch (TYPEOF(target)) {
+        case VECSXP :
+        if (data->narm) {
+          for (k=0; k<thislen; k++)
+            SET_VECTOR_ELT(target, counter + k, VECTOR_ELT(thiscol, INTEGER(thisidx)[k]-1));
+        } else {
+          for (k=0; k<data->nrow; k++) SET_VECTOR_ELT(target, j*data->nrow + k, VECTOR_ELT(thiscol, k));
+        }
+        break;
+        case STRSXP :
+        if (data->isfactor[i]) {
+          if (isFactor(thiscol)) {
+            SET_VECTOR_ELT(flevels, j, getAttrib(thiscol, R_LevelsSymbol));
+            thiscol = PROTECT(asCharacterFactor(thiscol));
+            thisfac = TRUE;
+            isordered[j] = isOrdered(thiscol);
+          } else SET_VECTOR_ELT(flevels, j, thiscol);
         }
-    } else data->totlen = data->nrow * data->lmax;
-    flevels = PROTECT(allocVector(VECSXP, data->lmax)); protecti++;
-    Rboolean *isordered = (Rboolean *)R_alloc(data->lmax, sizeof(Rboolean));
-    ansvals = PROTECT(allocVector(VECSXP, data->lvalues)); protecti++;
-    for (i=0; i<data->lvalues; i++) {
-        thisvalfactor = (data->maxtype[i] == VECSXP) ? FALSE : valfactor;
-        target = allocVector(data->maxtype[i], data->totlen);
-        SET_VECTOR_ELT(ansvals, i, target);
-        thisvaluecols = VECTOR_ELT(data->valuecols, i);
-        counter = 0; copyattr = FALSE;
-        for (j=0; j<data->lmax; j++) {
-            thiscol = (j < data->leach[i]) ? VECTOR_ELT(DT, INTEGER(thisvaluecols)[j]-1)
-                                           : allocNAVector(data->maxtype[i], data->nrow);
-            if (!copyattr && data->isidentical[i] && !data->isfactor[i]) {
-                copyMostAttrib(thiscol, target);
-                copyattr = TRUE;
-            }
-            if (TYPEOF(thiscol) != TYPEOF(target) && (data->maxtype[i] == VECSXP || !isFactor(thiscol))) {
-                thiscol = PROTECT(coerceVector(thiscol, TYPEOF(target)));
-                coerced = TRUE;
-            }
-            if (data->narm) {
-                thisidx = VECTOR_ELT(data->naidx, j);
-                thislen = length(thisidx);
-            }
-            size = SIZEOF(thiscol);
-            switch (TYPEOF(target)) {
-                case VECSXP :
-                if (data->narm) {
-                    for (k=0; k<thislen; k++)
-                        SET_VECTOR_ELT(target, counter + k, VECTOR_ELT(thiscol, INTEGER(thisidx)[k]-1));
-                } else {
-                    for (k=0; k<data->nrow; k++) SET_VECTOR_ELT(target, j*data->nrow + k, VECTOR_ELT(thiscol, k));
-                }
-                break;
-                case STRSXP :
-                if (data->isfactor[i]) {
-                    if (isFactor(thiscol)) {
-                        SET_VECTOR_ELT(flevels, j, getAttrib(thiscol, R_LevelsSymbol));
-                        thiscol = PROTECT(asCharacterFactor(thiscol));
-                        thisfac = TRUE;
-                        isordered[j] = isOrdered(thiscol);
-                    } else SET_VECTOR_ELT(flevels, j, thiscol);
-                }
-                if (data->narm) {
-                    for (k=0; k<thislen; k++)
-                        SET_STRING_ELT(target, counter + k, STRING_ELT(thiscol, INTEGER(thisidx)[k]-1));
-                } else {
-                    for (k=0; k<data->nrow; k++) SET_STRING_ELT(target, j*data->nrow + k, STRING_ELT(thiscol, k));
-                }
-                break;
-                case REALSXP :
-                if (data->narm) {
-                    for (k=0; k<thislen; k++)
-                        REAL(target)[counter + k] = REAL(thiscol)[INTEGER(thisidx)[k]-1];
-                } else {
-                    memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
-                }
-                break;
-                case INTSXP :
-                if (data->narm) {
-                    for (k=0; k<thislen; k++)
-                        INTEGER(target)[counter + k] = INTEGER(thiscol)[INTEGER(thisidx)[k]-1];
-                } else {
-                    memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
-                }
-                break;
-                case LGLSXP :
-                if (data->narm) {
-                    for (k=0; k<thislen; k++)
-                        LOGICAL(target)[counter + k] = LOGICAL(thiscol)[INTEGER(thisidx)[k]-1];
-                } else {
-                    memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
-                }
-                break;
-                default : error("Unknown column type '%s' for column '%s'.", type2char(TYPEOF(thiscol)), CHAR(STRING_ELT(dtnames, INTEGER(thisvaluecols)[i]-1)));
-            }
-            if (data->narm) counter += thislen;
-            if (coerced) {
-                UNPROTECT(1); coerced = FALSE;
-            }
-            if (thisfac) {
-                UNPROTECT(1); thisfac = FALSE;
-            }
+        if (data->narm) {
+          for (k=0; k<thislen; k++)
+            SET_STRING_ELT(target, counter + k, STRING_ELT(thiscol, INTEGER(thisidx)[k]-1));
+        } else {
+          for (k=0; k<data->nrow; k++) SET_STRING_ELT(target, j*data->nrow + k, STRING_ELT(thiscol, k));
+        }
+        break;
+        case REALSXP :
+        if (data->narm) {
+          for (k=0; k<thislen; k++)
+            REAL(target)[counter + k] = REAL(thiscol)[INTEGER(thisidx)[k]-1];
+        } else {
+          memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
         }
-        if (thisvalfactor && data->isfactor[i] && TYPEOF(target) != VECSXP) {
-            clevels = combineFactorLevels(flevels, &(data->isfactor[i]), isordered);
-            SEXP factorLangSxp = PROTECT(lang3(install(data->isfactor[i] == 1 ? "factor" : "ordered"), target, clevels));
-            SET_VECTOR_ELT(ansvals, i, eval(factorLangSxp, R_GlobalEnv));
-            UNPROTECT(2);  // clevels, factorLangSxp
+        break;
+        case INTSXP :
+        if (data->narm) {
+          for (k=0; k<thislen; k++)
+            INTEGER(target)[counter + k] = INTEGER(thiscol)[INTEGER(thisidx)[k]-1];
+        } else {
+          memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
         }
+        break;
+        case LGLSXP :
+        if (data->narm) {
+          for (k=0; k<thislen; k++)
+            LOGICAL(target)[counter + k] = LOGICAL(thiscol)[INTEGER(thisidx)[k]-1];
+        } else {
+          memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
+        }
+        break;
+        default : error("Unknown column type '%s' for column '%s'.", type2char(TYPEOF(thiscol)), CHAR(STRING_ELT(dtnames, INTEGER(thisvaluecols)[i]-1)));
+      }
+      if (data->narm) counter += thislen;
+      if (coerced) {
+        UNPROTECT(1); coerced = FALSE;
+      }
+      if (thisfac) {
+        UNPROTECT(1); thisfac = FALSE;
+      }
+    }
+    if (thisvalfactor && data->isfactor[i] && TYPEOF(target) != VECSXP) {
+      clevels = combineFactorLevels(flevels, &(data->isfactor[i]), isordered);
+      SEXP factorLangSxp = PROTECT(lang3(install(data->isfactor[i] == 1 ? "factor" : "ordered"), target, clevels));
+      SET_VECTOR_ELT(ansvals, i, eval(factorLangSxp, R_GlobalEnv));
+      UNPROTECT(2);  // clevels, factorLangSxp
     }
-    UNPROTECT(protecti);
-    return(ansvals);
+  }
+  UNPROTECT(protecti);
+  return(ansvals);
 }
 
 SEXP getvarcols(SEXP DT, SEXP dtnames, Rboolean varfactor, Rboolean verbose, struct processData *data) {
 
-    int i,j,k,cnt=0,nrows=0, nlevels=0, protecti=0, thislen, zerolen=0;
-    SEXP ansvars, thisvaluecols, levels, target, matchvals, thisnames;
-
-    ansvars = PROTECT(allocVector(VECSXP, 1)); protecti++;
-    target = allocVector(INTSXP, data->totlen);
-    SET_VECTOR_ELT(ansvars, 0, target);
-    if (data->lvalues == 1) {
-        thisvaluecols = VECTOR_ELT(data->valuecols, 0);
-        // tmp fix for #1055
-        thisnames = PROTECT(allocVector(STRSXP, length(thisvaluecols)));
-        for (i=0; i<length(thisvaluecols); i++) {
-            SET_STRING_ELT(thisnames, i, STRING_ELT(dtnames, INTEGER(thisvaluecols)[i]-1));
-        }
-        matchvals = PROTECT(match(thisnames, thisnames, 0));
-        if (data->narm) {
-            for (j=0; j<data->lmax; j++) {
-                thislen = length(VECTOR_ELT(data->naidx, j));
-                for (k=0; k<thislen; k++)
-                    INTEGER(target)[nrows + k] = INTEGER(matchvals)[j - zerolen]; // fix for #1359
-                nrows += thislen;
-                zerolen += (thislen == 0);
-            }
-            nlevels = data->lmax - zerolen;
-        } else {
-            for (j=0; j<data->lmax; j++) {
-                for (k=0; k<data->nrow; k++)
-                    INTEGER(target)[data->nrow*j + k] = INTEGER(matchvals)[j];
-            }
-            nlevels = data->lmax;
-        }
-        UNPROTECT(2); // matchvals, thisnames
+  int i,j,k,cnt=0,nrows=0, nlevels=0, protecti=0, thislen, zerolen=0;
+  SEXP ansvars, thisvaluecols, levels, target, matchvals, thisnames;
+
+  ansvars = PROTECT(allocVector(VECSXP, 1)); protecti++;
+  target = allocVector(INTSXP, data->totlen);
+  SET_VECTOR_ELT(ansvars, 0, target);
+  if (data->lvalues == 1) {
+    thisvaluecols = VECTOR_ELT(data->valuecols, 0);
+    // tmp fix for #1055
+    thisnames = PROTECT(allocVector(STRSXP, length(thisvaluecols)));
+    for (i=0; i<length(thisvaluecols); i++) {
+      SET_STRING_ELT(thisnames, i, STRING_ELT(dtnames, INTEGER(thisvaluecols)[i]-1));
+    }
+    matchvals = PROTECT(match(thisnames, thisnames, 0));
+    if (data->narm) {
+      for (j=0; j<data->lmax; j++) {
+        thislen = length(VECTOR_ELT(data->naidx, j));
+        for (k=0; k<thislen; k++)
+          INTEGER(target)[nrows + k] = INTEGER(matchvals)[j - zerolen]; // fix for #1359
+        nrows += thislen;
+        zerolen += (thislen == 0);
+      }
+      nlevels = data->lmax - zerolen;
     } else {
-        if (data->narm) {
-            for (j=0; j<data->lmax; j++) {
-                thislen = length(VECTOR_ELT(data->naidx, j));
-                for (k=0; k<thislen; k++)
-                    INTEGER(target)[nrows + k] = j+1;
-                nrows += thislen;
-                nlevels += (thislen != 0);
-            }
-        } else {
-            for (j=0; j<data->lmax; j++) {
-                for (k=0; k<data->nrow; k++)
-                    INTEGER(target)[data->nrow*j + k] = j+1;
-            }
-            nlevels = data->lmax;
-        }
+      for (j=0; j<data->lmax; j++) {
+        for (k=0; k<data->nrow; k++)
+          INTEGER(target)[data->nrow*j + k] = INTEGER(matchvals)[j];
+      }
+      nlevels = data->lmax;
     }
-    setAttrib(target, R_ClassSymbol, mkString("factor"));
-    cnt = 0;
-    if (data->lvalues == 1) {
-        levels = PROTECT(allocVector(STRSXP, nlevels));
-        thisvaluecols = VECTOR_ELT(data->valuecols, 0); // levels will be column names
-        for (i=0; i<data->lmax; i++) {
-            if (data->narm) {
-                if (length(VECTOR_ELT(data->naidx, i)) == 0) continue;
-            }
-            SET_STRING_ELT(levels, cnt++, STRING_ELT(dtnames, INTEGER(thisvaluecols)[i]-1));
-        }
-    } else levels = PROTECT(coerceVector(seq_int(nlevels, 1), STRSXP)); // generate levels = 1:nlevels
-    // base::unique is fast on vectors, and the levels on variable columns are usually small
-    SEXP uniqueLangSxp = PROTECT(lang2(install("unique"), levels));
-    setAttrib(target, R_LevelsSymbol, eval(uniqueLangSxp, R_GlobalEnv));
-    UNPROTECT(2); // levels, uniqueLangSxp
-    if (!varfactor) SET_VECTOR_ELT(ansvars, 0, asCharacterFactor(target));
-    UNPROTECT(protecti);
-    return(ansvars);
+    UNPROTECT(2); // matchvals, thisnames
+  } else {
+    if (data->narm) {
+      for (j=0; j<data->lmax; j++) {
+        thislen = length(VECTOR_ELT(data->naidx, j));
+        for (k=0; k<thislen; k++)
+          INTEGER(target)[nrows + k] = j+1;
+        nrows += thislen;
+        nlevels += (thislen != 0);
+      }
+    } else {
+      for (j=0; j<data->lmax; j++) {
+        for (k=0; k<data->nrow; k++)
+          INTEGER(target)[data->nrow*j + k] = j+1;
+      }
+      nlevels = data->lmax;
+    }
+  }
+  setAttrib(target, R_ClassSymbol, mkString("factor"));
+  cnt = 0;
+  if (data->lvalues == 1) {
+    levels = PROTECT(allocVector(STRSXP, nlevels));
+    thisvaluecols = VECTOR_ELT(data->valuecols, 0); // levels will be column names
+    for (i=0; i<data->lmax; i++) {
+      if (data->narm) {
+        if (length(VECTOR_ELT(data->naidx, i)) == 0) continue;
+      }
+      SET_STRING_ELT(levels, cnt++, STRING_ELT(dtnames, INTEGER(thisvaluecols)[i]-1));
+    }
+  } else levels = PROTECT(coerceVector(seq_int(nlevels, 1), STRSXP)); // generate levels = 1:nlevels
+  // base::unique is fast on vectors, and the levels on variable columns are usually small
+  SEXP uniqueLangSxp = PROTECT(lang2(install("unique"), levels));
+  setAttrib(target, R_LevelsSymbol, eval(uniqueLangSxp, R_GlobalEnv));
+  UNPROTECT(2); // levels, uniqueLangSxp
+  if (!varfactor) SET_VECTOR_ELT(ansvars, 0, asCharacterFactor(target));
+  UNPROTECT(protecti);
+  return(ansvars);
 }
 
 SEXP getidcols(SEXP DT, SEXP dtnames, Rboolean verbose, struct processData *data) {
 
-    int i,j,k, counter=0, thislen;
-    SEXP ansids, thiscol, target, thisidx;
-    size_t size;
-    ansids = PROTECT(allocVector(VECSXP, data->lids));
-    for (i=0; i<data->lids; i++) {
-        counter = 0;
-        thiscol = VECTOR_ELT(DT, INTEGER(data->idcols)[i]-1);
-        size = SIZEOF(thiscol);
-        target = allocVector(TYPEOF(thiscol), data->totlen);
-        SET_VECTOR_ELT(ansids, i, target);
-        copyMostAttrib(thiscol, target); // all but names,dim and dimnames. And if so, we want a copy here, not keepattr's SET_ATTRIB.
-        switch(TYPEOF(thiscol)) {
-            case REALSXP :
-            if (data->narm) {
-                for (j=0; j<data->lmax; j++) {
-                    thisidx = VECTOR_ELT(data->naidx, j);
-                    thislen = length(thisidx);
-                    for (k=0; k<thislen; k++)
-                        REAL(target)[counter + k] = REAL(thiscol)[INTEGER(thisidx)[k]-1];
-                    counter += thislen;
-                }
-            } else {
-                for (j=0; j<data->lmax; j++)
-                    memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
-            }
-            break;
-            case INTSXP :
-            if (data->narm) {
-                for (j=0; j<data->lmax; j++) {
-                    thisidx = VECTOR_ELT(data->naidx, j);
-                    thislen = length(thisidx);
-                    for (k=0; k<thislen; k++)
-                        INTEGER(target)[counter + k] = INTEGER(thiscol)[INTEGER(thisidx)[k]-1];
-                    counter += thislen;
-                }
-            } else {
-                for (j=0; j<data->lmax; j++)
-                    memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
-            }
-            break;
-            case LGLSXP :
-            if (data->narm) {
-                for (j=0; j<data->lmax; j++) {
-                    thisidx = VECTOR_ELT(data->naidx, j);
-                    thislen = length(thisidx);
-                    for (k=0; k<thislen; k++)
-                        LOGICAL(target)[counter + k] = LOGICAL(thiscol)[INTEGER(thisidx)[k]-1];
-                    counter += thislen;
-                }
-            } else {
-                for (j=0; j<data->lmax; j++)
-                    memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
-            }
-            break;
-            case STRSXP :
-            if (data->narm) {
-                for (j=0; j<data->lmax; j++) {
-                    thisidx = VECTOR_ELT(data->naidx, j);
-                    thislen = length(thisidx);
-                    for (k=0; k<thislen; k++)
-                        SET_STRING_ELT(target, counter + k, STRING_ELT(thiscol, INTEGER(thisidx)[k]-1));
-                    counter += thislen;
-                }
-            } else {
-                // SET_STRING_ELT for j=0 and memcpy for j>0, WHY?
-                // From assign.c's memcrecycle - only one SET_STRING_ELT per RHS item is needed to set generations (overhead)
-                for (k=0; k<data->nrow; k++) SET_STRING_ELT(target, k, STRING_ELT(thiscol, k));
-                for (j=1; j<data->lmax; j++)
-                    memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(target), data->nrow*size);
-            }
-            break;
-            case VECSXP :
-            for (j=0; j<data->lmax; j++) {
-                for (k=0; k<data->nrow; k++) {
-                    SET_VECTOR_ELT(target, j*data->nrow + k, VECTOR_ELT(thiscol, k));
-                }
-            }
-            break;
-            default : error("Unknown column type '%s' for column '%s' in 'data'", type2char(TYPEOF(thiscol)), CHAR(STRING_ELT(dtnames, INTEGER(data->idcols)[i]-1)));
-        }
+  int i,j,k, counter=0, thislen;
+  SEXP ansids, thiscol, target, thisidx;
+  size_t size;
+  ansids = PROTECT(allocVector(VECSXP, data->lids));
+  for (i=0; i<data->lids; i++) {
+    counter = 0;
+    thiscol = VECTOR_ELT(DT, INTEGER(data->idcols)[i]-1);
+    size = SIZEOF(thiscol);
+    target = allocVector(TYPEOF(thiscol), data->totlen);
+    SET_VECTOR_ELT(ansids, i, target);
+    copyMostAttrib(thiscol, target); // all but names,dim and dimnames. And if so, we want a copy here, not keepattr's SET_ATTRIB.
+    switch(TYPEOF(thiscol)) {
+    case REALSXP :
+      if (data->narm) {
+        for (j=0; j<data->lmax; j++) {
+          thisidx = VECTOR_ELT(data->naidx, j);
+          thislen = length(thisidx);
+          for (k=0; k<thislen; k++)
+            REAL(target)[counter + k] = REAL(thiscol)[INTEGER(thisidx)[k]-1];
+          counter += thislen;
+        }
+      } else {
+        for (j=0; j<data->lmax; j++)
+          memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
+      }
+      break;
+    case INTSXP :
+      if (data->narm) {
+        for (j=0; j<data->lmax; j++) {
+          thisidx = VECTOR_ELT(data->naidx, j);
+          thislen = length(thisidx);
+          for (k=0; k<thislen; k++)
+            INTEGER(target)[counter + k] = INTEGER(thiscol)[INTEGER(thisidx)[k]-1];
+          counter += thislen;
+        }
+      } else {
+        for (j=0; j<data->lmax; j++)
+          memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
+      }
+      break;
+    case LGLSXP :
+      if (data->narm) {
+        for (j=0; j<data->lmax; j++) {
+          thisidx = VECTOR_ELT(data->naidx, j);
+          thislen = length(thisidx);
+          for (k=0; k<thislen; k++)
+            LOGICAL(target)[counter + k] = LOGICAL(thiscol)[INTEGER(thisidx)[k]-1];
+          counter += thislen;
+        }
+      } else {
+        for (j=0; j<data->lmax; j++)
+          memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(thiscol), data->nrow*size);
+      }
+      break;
+    case STRSXP :
+      if (data->narm) {
+        for (j=0; j<data->lmax; j++) {
+          thisidx = VECTOR_ELT(data->naidx, j);
+          thislen = length(thisidx);
+          for (k=0; k<thislen; k++)
+            SET_STRING_ELT(target, counter + k, STRING_ELT(thiscol, INTEGER(thisidx)[k]-1));
+          counter += thislen;
+        }
+      } else {
+        // SET_STRING_ELT for j=0 and memcpy for j>0, WHY?
+        // From assign.c's memcrecycle - only one SET_STRING_ELT per RHS item is needed to set generations (overhead)
+        for (k=0; k<data->nrow; k++) SET_STRING_ELT(target, k, STRING_ELT(thiscol, k));
+        for (j=1; j<data->lmax; j++)
+          memcpy((char *)DATAPTR(target)+j*data->nrow*size, (char *)DATAPTR(target), data->nrow*size);
+      }
+      break;
+    case VECSXP :
+      for (j=0; j<data->lmax; j++) {
+        for (k=0; k<data->nrow; k++) {
+          SET_VECTOR_ELT(target, j*data->nrow + k, VECTOR_ELT(thiscol, k));
+        }
+      }
+      break;
+    default : error("Unknown column type '%s' for column '%s' in 'data'", type2char(TYPEOF(thiscol)), CHAR(STRING_ELT(dtnames, INTEGER(data->idcols)[i]-1)));
     }
-    UNPROTECT(1);
-    return (ansids);
+  }
+  UNPROTECT(1);
+  return (ansids);
 }
 
 SEXP fmelt(SEXP DT, SEXP id, SEXP measure, SEXP varfactor, SEXP valfactor, SEXP varnames, SEXP valnames, SEXP narmArg, SEXP verboseArg) {
 
-    int i, ncol, protecti=0;
-    SEXP dtnames, ansvals, ansvars, ansids, ansnames, ans;
-    Rboolean narm=FALSE, verbose=FALSE;
-    struct processData data;
-
-    if (!isNewList(DT)) error("Input is not of type VECSXP, expected a data.table, data.frame or list");
-    if (!isLogical(valfactor)) error("Argument 'value.factor' should be logical TRUE/FALSE");
-    if (!isLogical(varfactor)) error("Argument 'variable.factor' should be logical TRUE/FALSE");
-    if (!isLogical(narmArg)) error("Argument 'na.rm' should be logical TRUE/FALSE.");
-    if (!isString(varnames)) error("Argument 'variable.name' must be a character vector");
-    if (!isString(valnames)) error("Argument 'value.name' must be a character vector");
-    if (!isLogical(verboseArg)) error("Argument 'verbose' should be logical TRUE/FALSE");
-    ncol = LENGTH(DT);
-    if (!ncol) {
-        if (verbose) Rprintf("ncol(data) is 0. Nothing to melt. Returning original data.table.");
-        return(DT);
-    }
-    dtnames = getAttrib(DT, R_NamesSymbol);
-    if (isNull(dtnames)) error("names(data) is NULL. Please report to data.table-help");
-    if (LOGICAL(narmArg)[0] == TRUE) narm = TRUE;
-    if (LOGICAL(verboseArg)[0] == TRUE) verbose = TRUE;
-
-    preprocess(DT, id, measure, varnames, valnames, narm, verbose, &data);
-    protecti = data.protecti;
-    // edge case no measure.vars
-    if (!data.lmax) {
-        ans = shallowwrapper(DT, data.idcols);
-        ans = PROTECT(duplicate(ans)); protecti++;
-    } else {
-        ansvals = PROTECT(getvaluecols(DT, dtnames, LOGICAL(valfactor)[0], verbose, &data)); protecti++;
-        ansvars = PROTECT(getvarcols(DT, dtnames, LOGICAL(varfactor)[0], verbose, &data)); protecti++;
-        ansids  = PROTECT(getidcols(DT, dtnames, verbose, &data)); protecti++;
-
-        // populate 'ans'
-        ans = allocVector(VECSXP, data.lids+1+data.lvalues); // 1 is for variable column
-        for (i=0; i<data.lids; i++) {
-            SET_VECTOR_ELT(ans, i, VECTOR_ELT(ansids, i));
-        }
-        SET_VECTOR_ELT(ans, data.lids, VECTOR_ELT(ansvars, 0));
-        for (i=0; i<data.lvalues; i++) {
-            SET_VECTOR_ELT(ans, data.lids+1+i, VECTOR_ELT(ansvals, i));
-        }
-        // fill in 'ansnames'
-        ansnames = PROTECT(allocVector(STRSXP, data.lids+1+data.lvalues)); protecti++;
-        for (i=0; i<data.lids; i++) {
-            SET_STRING_ELT(ansnames, i, STRING_ELT(dtnames, INTEGER(data.idcols)[i]-1));
-        }
-        SET_STRING_ELT(ansnames, data.lids, STRING_ELT(varnames, 0));
-        for (i=0; i<data.lvalues; i++) {
-            SET_STRING_ELT(ansnames, data.lids+1+i, STRING_ELT(valnames, i));
-        }
-        setAttrib(ans, R_NamesSymbol, ansnames);
+  int i, ncol, protecti=0;
+  SEXP dtnames, ansvals, ansvars, ansids, ansnames, ans;
+  Rboolean narm=FALSE, verbose=FALSE;
+  struct processData data;
+
+  if (!isNewList(DT)) error("Input is not of type VECSXP, expected a data.table, data.frame or list");
+  if (!isLogical(valfactor)) error("Argument 'value.factor' should be logical TRUE/FALSE");
+  if (!isLogical(varfactor)) error("Argument 'variable.factor' should be logical TRUE/FALSE");
+  if (!isLogical(narmArg)) error("Argument 'na.rm' should be logical TRUE/FALSE.");
+  if (!isString(varnames)) error("Argument 'variable.name' must be a character vector");
+  if (!isString(valnames)) error("Argument 'value.name' must be a character vector");
+  if (!isLogical(verboseArg)) error("Argument 'verbose' should be logical TRUE/FALSE");
+  ncol = LENGTH(DT);
+  if (!ncol) {
+    if (verbose) Rprintf("ncol(data) is 0. Nothing to melt. Returning original data.table.");
+    return(DT);
+  }
+  dtnames = getAttrib(DT, R_NamesSymbol);
+  if (isNull(dtnames)) error("names(data) is NULL. Please report to data.table-help");
+  if (LOGICAL(narmArg)[0] == TRUE) narm = TRUE;
+  if (LOGICAL(verboseArg)[0] == TRUE) verbose = TRUE;
+
+  preprocess(DT, id, measure, varnames, valnames, narm, verbose, &data);
+  protecti = data.protecti;
+  // edge case no measure.vars
+  if (!data.lmax) {
+    ans = shallowwrapper(DT, data.idcols);
+    ans = PROTECT(duplicate(ans)); protecti++;
+  } else {
+    ansvals = PROTECT(getvaluecols(DT, dtnames, LOGICAL(valfactor)[0], verbose, &data)); protecti++;
+    ansvars = PROTECT(getvarcols(DT, dtnames, LOGICAL(varfactor)[0], verbose, &data)); protecti++;
+    ansids  = PROTECT(getidcols(DT, dtnames, verbose, &data)); protecti++;
+
+    // populate 'ans'
+    ans = allocVector(VECSXP, data.lids+1+data.lvalues); // 1 is for variable column
+    for (i=0; i<data.lids; i++) {
+      SET_VECTOR_ELT(ans, i, VECTOR_ELT(ansids, i));
+    }
+    SET_VECTOR_ELT(ans, data.lids, VECTOR_ELT(ansvars, 0));
+    for (i=0; i<data.lvalues; i++) {
+      SET_VECTOR_ELT(ans, data.lids+1+i, VECTOR_ELT(ansvals, i));
+    }
+    // fill in 'ansnames'
+    ansnames = PROTECT(allocVector(STRSXP, data.lids+1+data.lvalues)); protecti++;
+    for (i=0; i<data.lids; i++) {
+      SET_STRING_ELT(ansnames, i, STRING_ELT(dtnames, INTEGER(data.idcols)[i]-1));
+    }
+    SET_STRING_ELT(ansnames, data.lids, STRING_ELT(varnames, 0));
+    for (i=0; i<data.lvalues; i++) {
+      SET_STRING_ELT(ansnames, data.lids+1+i, STRING_ELT(valnames, i));
     }
-    UNPROTECT(protecti);
-    return(ans);
+    setAttrib(ans, R_NamesSymbol, ansnames);
+  }
+  UNPROTECT(protecti);
+  return(ans);
 }
diff --git a/src/forder.c b/src/forder.c
index 4a71c8e014..614f5d43cd 100644
--- a/src/forder.c
+++ b/src/forder.c
@@ -2,9 +2,9 @@
 // #define TIMING_ON
 
 /*
-    - Only forder() and *twiddle() functions are meant for use by other C code in data.table, hence all other functions here except forder and *twiddle are static.
-    - The static functions here make use of global variables, whose scope is limited to the same translation unit; therefore access from outside this file must be via forder and/or *twiddle only.
-    - The coding techniques deployed here are for efficiency; e.g. i) the static functions are recursive or called repetitively and we wish to minimise stack overhead, or ii) reach outside themselves to place results in the end result directly rather than returning small bits of memory.
+  - Only forder() and *twiddle() functions are meant for use by other C code in data.table, hence all other functions here except forder and *twiddle are static.
+  - The static functions here make use of global variables, whose scope is limited to the same translation unit; therefore access from outside this file must be via forder and/or *twiddle only.
+  - The coding techniques deployed here are for efficiency; e.g. i) the static functions are recursive or called repetitively and we wish to minimise stack overhead, or ii) reach outside themselves to place results in the end result directly rather than returning small bits of memory.
 */
 
 static int *gs[2] = {NULL};                                         // gs = groupsizes e.g. 23,12,87,2,1,34,...
@@ -31,42 +31,42 @@ static int order = 1;                                               // =1, -1 fo
 and call savetl_end() before the error(). */
 
 static void growstack(int newlen) {
-    if (newlen==0) newlen=100000;                                   // no link to icount range restriction, just 100,000 seems a good minimum at 0.4MB.
-    if (newlen>gsmaxalloc) newlen=gsmaxalloc;
-    gs[flip] = realloc(gs[flip], newlen*sizeof(int));
-    if (gs[flip] == NULL) Error("Failed to realloc working memory stack to %d*4bytes (flip=%d)", newlen, flip);
-    gsalloc[flip] = newlen;
+  if (newlen==0) newlen=100000;                                   // no link to icount range restriction, just 100,000 seems a good minimum at 0.4MB.
+  if (newlen>gsmaxalloc) newlen=gsmaxalloc;
+  gs[flip] = realloc(gs[flip], newlen*sizeof(int));
+  if (gs[flip] == NULL) Error("Failed to realloc working memory stack to %d*4bytes (flip=%d)", newlen, flip);
+  gsalloc[flip] = newlen;
 }
 
 static void push(int x) {
-    if (!stackgrps || x==0) return;
-    if (gsalloc[flip] == gsngrp[flip]) growstack(gsngrp[flip]*2);
-    gs[flip][gsngrp[flip]++] = x;
-    if (x > gsmax[flip]) gsmax[flip] = x;
+  if (!stackgrps || x==0) return;
+  if (gsalloc[flip] == gsngrp[flip]) growstack(gsngrp[flip]*2);
+  gs[flip][gsngrp[flip]++] = x;
+  if (x > gsmax[flip]) gsmax[flip] = x;
 }
 
 static void mpush(int x, int n) {
-    if (!stackgrps || x==0) return;
-    if (gsalloc[flip] < gsngrp[flip]+n) growstack((gsngrp[flip]+n)*2);
-    for (int i=0; i<n; i++) gs[flip][gsngrp[flip]++] = x;
-    if (x > gsmax[flip]) gsmax[flip] = x;
+  if (!stackgrps || x==0) return;
+  if (gsalloc[flip] < gsngrp[flip]+n) growstack((gsngrp[flip]+n)*2);
+  for (int i=0; i<n; i++) gs[flip][gsngrp[flip]++] = x;
+  if (x > gsmax[flip]) gsmax[flip] = x;
 }
 
 static void flipflop() {
-    flip = 1-flip;
-    gsngrp[flip] = 0;
-    gsmax[flip] = 0;
-    if (gsalloc[flip] < gsalloc[1-flip]) growstack(gsalloc[1-flip]*2);
+  flip = 1-flip;
+  gsngrp[flip] = 0;
+  gsmax[flip] = 0;
+  if (gsalloc[flip] < gsalloc[1-flip]) growstack(gsalloc[1-flip]*2);
 }
 
 static void gsfree() {
-    free(gs[0]); free(gs[1]);
-    gs[0] = NULL; gs[1]= NULL;
-    flip = 0;
-    gsalloc[0] = gsalloc[1] = 0;
-    gsngrp[0] = gsngrp[1] = 0;
-    gsmax[0] = gsmax[1] = 0;
-    gsmaxalloc = 0;
+  free(gs[0]); free(gs[1]);
+  gs[0] = NULL; gs[1]= NULL;
+  flip = 0;
+  gsalloc[0] = gsalloc[1] = 0;
+  gsngrp[0] = gsngrp[1] = 0;
+  gsmax[0] = gsmax[1] = 0;
+  gsmaxalloc = 0;
 }
 
 #ifdef TIMING_ON
@@ -88,160 +88,160 @@ static void gsfree() {
    base::sort.list(method="radix") turns out not to be a radix sort, but a counting sort, and we like it.
    See http://r.789695.n4.nabble.com/method-radix-in-sort-list-isn-t-actually-a-radix-sort-tp3309470p3309470.html
    Main changes :
-     1. Negatives are fine. Btw, wish raised for simple change to base R : https://bugs.r-project.org/bugzilla3/show_bug.cgi?id=15644
-     2. Doesn't cummulate through 0's for speed in repeated calls of sparse counts by saving memset back to 0 of many 0
-     3. Separated setRange so forder can redirect to iradix
+   1. Negatives are fine. Btw, wish raised for simple change to base R : https://bugs.r-project.org/bugzilla3/show_bug.cgi?id=15644
+   2. Doesn't cummulate through 0's for speed in repeated calls of sparse counts by saving memset back to 0 of many 0
+   3. Separated setRange so forder can redirect to iradix
 */
 
 static int range, xmin;                                                  // used by both icount and forder
 static void setRange(int *x, int n)
 {
-    int i, tmp;
-    xmin = NA_INTEGER;     // used by forder
-    int xmax = NA_INTEGER; // declared locally as we only need xmin outside
-    double overflow;
-
-    i = 0;
-    while(i<n && x[i]==NA_INTEGER) i++;
-    if (i<n) xmax = xmin = x[i];
-    for(; i < n; i++) {
-        tmp = x[i];
-        if(tmp == NA_INTEGER) continue;
-        if (tmp > xmax) xmax = tmp;
-        else if (tmp < xmin) xmin = tmp;
-    }
-    if(xmin == NA_INTEGER) {range = NA_INTEGER; return;}                    // all NAs, nothing to do
-
-    overflow = (double)xmax - (double)xmin + 1;                             // ex: x=c(-2147483647L, NA_integer_, 1L) results in overflowing int range.
-    if (overflow > INT_MAX) {range = INT_MAX; return;}                      // detect and force iradix here, since icount is out of the picture
-    range = xmax-xmin+1;
-
-    return;
+  int i, tmp;
+  xmin = NA_INTEGER;     // used by forder
+  int xmax = NA_INTEGER; // declared locally as we only need xmin outside
+  double overflow;
+
+  i = 0;
+  while(i<n && x[i]==NA_INTEGER) i++;
+  if (i<n) xmax = xmin = x[i];
+  for(; i < n; i++) {
+    tmp = x[i];
+    if(tmp == NA_INTEGER) continue;
+    if (tmp > xmax) xmax = tmp;
+    else if (tmp < xmin) xmin = tmp;
+  }
+  if(xmin == NA_INTEGER) {range = NA_INTEGER; return;}                    // all NAs, nothing to do
+
+  overflow = (double)xmax - (double)xmin + 1;                             // ex: x=c(-2147483647L, NA_integer_, 1L) results in overflowing int range.
+  if (overflow > INT_MAX) {range = INT_MAX; return;}                      // detect and force iradix here, since icount is out of the picture
+  range = xmax-xmin+1;
+
+  return;
 }
 
 // x*order results in integer overflow when -1*NA, so careful to avoid that here :
 static inline int icheck(int x) {
-    return ((nalast != 1) ? ((x != NA_INTEGER) ? x*order : x) : ((x != NA_INTEGER) ? (x*order)-1 : INT_MAX)); // if nalast==1, NAs must go last.
+  return ((nalast != 1) ? ((x != NA_INTEGER) ? x*order : x) : ((x != NA_INTEGER) ? (x*order)-1 : INT_MAX)); // if nalast==1, NAs must go last.
 }
 
 
 static void icount(int *x, int *o, int n)
 /* Counting sort:
-    1. Places the ordering into o directly, overwriting whatever was there
-    2. Doesn't change x
-    3. Pushes group sizes onto stack
+  1. Places the ordering into o directly, overwriting whatever was there
+  2. Doesn't change x
+  3. Pushes group sizes onto stack
 */
 {
-    int i=0, tmp;
-    int napos = range;  // always count NA in last bucket and we'll account for nalast option in due course
-    static unsigned int counts[N_RANGE+1] = {0};                            // static is IMPORTANT, counting sort is called repetitively.
-    /* counts are set back to 0 at the end efficiently. 1e5 = 0.4MB i.e
-    tiny. We'll only use the front part of it, as large as range. So it's
-    just reserving space, not using it. Have defined N_RANGE to be 100000.*/
-    if (range > N_RANGE) Error("Internal error: range = %d; isorted can't handle range > %d", range, N_RANGE);
-    for(i=0; i<n; i++) {
-        if (x[i] == NA_INTEGER) counts[napos]++;             // For nalast=NA case, we won't remove/skip NAs, rather set 'o' indices
-        else counts[x[i]-xmin]++;                            // to 0. subset will skip them. We can't know how many NAs to skip
-    }                                                        // beforehand - i.e. while allocating "ans" vector
-    // TO DO: at this point if the last count==n then it's all the same number and we can stop now.
-    // Idea from Terdiman, then improved on that by not needing to loop through counts.
-
-    tmp = 0;
-    if (nalast!=1 && counts[napos]) {
-        push(counts[napos]);
-        tmp += counts[napos];
-    }
-    int w = (order==1) ? 0 : range-1;                                   // *** BLOCK 4 ***
-    for (i=0; i<range; i++)
-    /* no point in adding tmp<n && i<=range, since range includes max,
-       need to go to max, unlike 256 loops elsewhere in forder.c */
-    {
-        if (counts[w]) {                                                    // cumulate but not through 0's. Helps resetting zeros when n<range, below.
-            push(counts[w]);
-            counts[w] = (tmp += counts[w]);
-        }
-        w += order; // order is +1 or -1
-    }
-    if (nalast==1 && counts[napos]) {
-        push(counts[napos]);
-        counts[napos] = (tmp += counts[napos]);
-    }
-    for(i=n-1; i>=0; i--) {
-        o[--counts[(x[i] == NA_INTEGER) ? napos : x[i]-xmin]] = (int)(i+1);    // This way na.last=TRUE/FALSE cases will have just a single if-check overhead.
-    }
-    if (nalast == 0)                                                        // nalast = 1, -1 are both taken care already.
-        for (i=0; i<n; i++) o[i] = (x[o[i]-1] == NA_INTEGER) ? 0 : o[i];    // nalast = 0 is dealt with separately as it just sets o to 0
-                                                                            // at those indices where x is NA. x[o[i]-1] because x is not modifed here.
-
-    /* counts were cumulated above so leaves non zero.
-    Faster to clear up now ready for next time. */
-    if (n < range) {
-        /* Many zeros in counts already. Loop through n instead,
-        doesn't matter if we set to 0 several times on any repeats */
-        counts[napos]=0;
-        for (i=0; i<n; i++) {
-            if (x[i]!=NA_INTEGER) counts[x[i]-xmin]=0;
-        }
-    } else {
-        memset(counts, 0, (range+1)*sizeof(int));                           // *** BLOCK 6 ***
+  int i=0, tmp;
+  int napos = range;  // always count NA in last bucket and we'll account for nalast option in due course
+  static unsigned int counts[N_RANGE+1] = {0};                            // static is IMPORTANT, counting sort is called repetitively.
+  /* counts are set back to 0 at the end efficiently. 1e5 = 0.4MB i.e
+  tiny. We'll only use the front part of it, as large as range. So it's
+  just reserving space, not using it. Have defined N_RANGE to be 100000.*/
+  if (range > N_RANGE) Error("Internal error: range = %d; isorted can't handle range > %d", range, N_RANGE);
+  for(i=0; i<n; i++) {
+    if (x[i] == NA_INTEGER) counts[napos]++;             // For nalast=NA case, we won't remove/skip NAs, rather set 'o' indices
+    else counts[x[i]-xmin]++;                            // to 0. subset will skip them. We can't know how many NAs to skip
+  }                                                        // beforehand - i.e. while allocating "ans" vector
+  // TO DO: at this point if the last count==n then it's all the same number and we can stop now.
+  // Idea from Terdiman, then improved on that by not needing to loop through counts.
+
+  tmp = 0;
+  if (nalast!=1 && counts[napos]) {
+    push(counts[napos]);
+    tmp += counts[napos];
+  }
+  int w = (order==1) ? 0 : range-1;                                   // *** BLOCK 4 ***
+  for (i=0; i<range; i++)
+  /* no point in adding tmp<n && i<=range, since range includes max,
+     need to go to max, unlike 256 loops elsewhere in forder.c */
+  {
+    if (counts[w]) {                                                    // cumulate but not through 0's. Helps resetting zeros when n<range, below.
+      push(counts[w]);
+      counts[w] = (tmp += counts[w]);
+    }
+    w += order; // order is +1 or -1
+  }
+  if (nalast==1 && counts[napos]) {
+    push(counts[napos]);
+    counts[napos] = (tmp += counts[napos]);
+  }
+  for(i=n-1; i>=0; i--) {
+    o[--counts[(x[i] == NA_INTEGER) ? napos : x[i]-xmin]] = (int)(i+1);    // This way na.last=TRUE/FALSE cases will have just a single if-check overhead.
+  }
+  if (nalast == 0)                                                        // nalast = 1, -1 are both taken care already.
+    for (i=0; i<n; i++) o[i] = (x[o[i]-1] == NA_INTEGER) ? 0 : o[i];    // nalast = 0 is dealt with separately as it just sets o to 0
+                                      // at those indices where x is NA. x[o[i]-1] because x is not modifed here.
+
+  /* counts were cumulated above so leaves non zero.
+  Faster to clear up now ready for next time. */
+  if (n < range) {
+    /* Many zeros in counts already. Loop through n instead,
+    doesn't matter if we set to 0 several times on any repeats */
+    counts[napos]=0;
+    for (i=0; i<n; i++) {
+      if (x[i]!=NA_INTEGER) counts[x[i]-xmin]=0;
     }
-    return;
+  } else {
+    memset(counts, 0, (range+1)*sizeof(int));                           // *** BLOCK 6 ***
+  }
+  return;
 }
 
 static void iinsert(int *x, int *o, int n)
 /*  orders both x and o by reference in-place. Fast for small vectors, low overhead.
-    don't be tempted to binsearch backwards here, have to shift anyway;
-    many memmove would have overhead and do the same thing. */
+  don't be tempted to binsearch backwards here, have to shift anyway;
+  many memmove would have overhead and do the same thing. */
 /*  when nalast == 0, iinsert will be called only from within iradix, where o[.] = 0
-    for x[.]=NA is already taken care of */
+  for x[.]=NA is already taken care of */
 {
-    int i, j, xtmp, otmp, tt;
-    for (i=1; i<n; i++) {
-        xtmp = x[i];
-        if (xtmp < x[i-1]) {
-            j = i-1;
-            otmp = o[i];
-            while (j>=0 && xtmp < x[j]) {
-                x[j+1] = x[j];
-                o[j+1] = o[j];
-                j--;
-            }
-            x[j+1] = xtmp;
-            o[j+1] = otmp;
-        }
-    }
-    tt = 0;
-    for (i=1; i<n; i++) if (x[i]==x[i-1]) tt++; else { push(tt+1); tt=0; }
-    push(tt+1);
+  int i, j, xtmp, otmp, tt;
+  for (i=1; i<n; i++) {
+    xtmp = x[i];
+    if (xtmp < x[i-1]) {
+      j = i-1;
+      otmp = o[i];
+      while (j>=0 && xtmp < x[j]) {
+        x[j+1] = x[j];
+        o[j+1] = o[j];
+        j--;
+      }
+      x[j+1] = xtmp;
+      o[j+1] = otmp;
+    }
+  }
+  tt = 0;
+  for (i=1; i<n; i++) if (x[i]==x[i-1]) tt++; else { push(tt+1); tt=0; }
+  push(tt+1);
 }
 
 /*
   iradix is a counting sort performed forwards from MSB to LSB, with some tricks
   and short circuits building on Terdiman and Herf.
-    http://codercorner.com/RadixSortRevisited.htm
-    http://stereopsis.com/radix.html
-
-    ~ Note they are LSD, but we do MSD here which is more complicated, for efficiency.
-    ~ NAs need no special treatment as NA is the most negative integer in R (checked in init.c once,
-      for efficiency) so NA naturally sort to the front.
-    ~ Using 4-pass 1-byte radix for the following reasons :
-        *  11-bit (Herf) reduces to 3-passes (3*11=33) yes, and LSD need random access
-           to o vector in each pass 1:n so reduction in passes
-        *  is good, but ...
-        *  ... Terdiman's idea to skip a radix if all values are equal occurs less the wider the
-               radix. A narrower radix benefits more from that.
-        *      That's detected here using a single 'if', an improvement on Terdiman's exposition
-               of a single loop to find if any count==n
-        *  The pass through counts bites when radix is wider, because we repetitively call this
-           iradix from fastorder forwards.
-        *  Herf's parallel histogramming is neat. In 4-pass 1-byte it needs 4*256 storage, that's
-           tiny, and can be static. 4*256 << 3*2048
-        *  4-pass 1-byte is simpler and tighter code than 3-pass 11-bit, giving modern optimizers
-           and modern CPUs a better chance. We may get
-        *  lucky anyway, if one or two of the 4-passes are skipped.
-
-     Recall: there are no comparisons at all in counting and radix, there is wide random access
-     in each LSD radix pass, though.
+  http://codercorner.com/RadixSortRevisited.htm
+  http://stereopsis.com/radix.html
+
+  ~ Note they are LSD, but we do MSD here which is more complicated, for efficiency.
+  ~ NAs need no special treatment as NA is the most negative integer in R (checked in init.c once,
+    for efficiency) so NA naturally sort to the front.
+  ~ Using 4-pass 1-byte radix for the following reasons :
+    *  11-bit (Herf) reduces to 3-passes (3*11=33) yes, and LSD need random access
+       to o vector in each pass 1:n so reduction in passes
+    *  is good, but ...
+    *  ... Terdiman's idea to skip a radix if all values are equal occurs less the wider the
+         radix. A narrower radix benefits more from that.
+    *      That's detected here using a single 'if', an improvement on Terdiman's exposition
+         of a single loop to find if any count==n
+    *  The pass through counts bites when radix is wider, because we repetitively call this
+       iradix from fastorder forwards.
+    *  Herf's parallel histogramming is neat. In 4-pass 1-byte it needs 4*256 storage, that's
+       tiny, and can be static. 4*256 << 3*2048
+    *  4-pass 1-byte is simpler and tighter code than 3-pass 11-bit, giving modern optimizers
+       and modern CPUs a better chance. We may get
+    *  lucky anyway, if one or two of the 4-passes are skipped.
+
+   Recall: there are no comparisons at all in counting and radix, there is wide random access
+   in each LSD radix pass, though.
 */
 
 static unsigned int radixcounts[8][257] = {{0}};                            // 4 are used for iradix, 8 for dradix and i64radix
@@ -253,169 +253,169 @@ static size_t radix_xsuballoc=0;
 
 static int *otmp=NULL, otmp_alloc=0;
 static void alloc_otmp(int n) {
-    if (otmp_alloc >= n) return;
-    otmp = (int *)realloc(otmp, n * sizeof(int));
-    if (otmp == NULL) Error("Failed to allocate working memory for otmp. Requested %d * %d bytes", n, sizeof(int));
-    otmp_alloc = n;
+  if (otmp_alloc >= n) return;
+  otmp = (int *)realloc(otmp, n * sizeof(int));
+  if (otmp == NULL) Error("Failed to allocate working memory for otmp. Requested %d * %d bytes", n, sizeof(int));
+  otmp_alloc = n;
 }
 
 static void *xtmp=NULL; int xtmp_alloc=0;                                   // TO DO: save xtmp if possible, see allocs in forder
 static void alloc_xtmp(int n) {                                             // TO DO: currently always the largest type (double) but
-                                                                            // could be int if that's all that's needed
-    if (xtmp_alloc >= n) return;
-    xtmp = (double *)realloc(xtmp, n * sizeof(double));
-    if (xtmp == NULL) Error("Failed to allocate working memory for xtmp. Requested %d * %d bytes", n, sizeof(double));
-    xtmp_alloc = n;
+                                      // could be int if that's all that's needed
+  if (xtmp_alloc >= n) return;
+  xtmp = (double *)realloc(xtmp, n * sizeof(double));
+  if (xtmp == NULL) Error("Failed to allocate working memory for xtmp. Requested %d * %d bytes", n, sizeof(double));
+  xtmp_alloc = n;
 }
 
 static void iradix_r(int *xsub, int *osub, int n, int radix);
 
 static void iradix(int *x, int *o, int n)
    /* As icount :
-       Places the ordering into o directly, overwriting whatever was there
-       Doesn't change x
-       Pushes group sizes onto stack */
+     Places the ordering into o directly, overwriting whatever was there
+     Doesn't change x
+     Pushes group sizes onto stack */
 {
-    int i, j, radix, nextradix, itmp, thisgrpn, maxgrpn;
-    unsigned int thisx=0, shift, *thiscounts;
-
-    for (i=0;i<n;i++) {
-        /* parallel histogramming pass; i.e. count occurrences of
-        0:255 in each byte.  Sequential so almost negligible. */
-        thisx = (unsigned int)(icheck(x[i])) - INT_MIN;                     // relies on overflow behaviour. And shouldn't -INT_MIN be up in iradix?
-        radixcounts[0][thisx & 0xFF]++;                                     // unrolled since inside n-loop
-        radixcounts[1][thisx >> 8 & 0xFF]++;
-        radixcounts[2][thisx >> 16 & 0xFF]++;
-        radixcounts[3][thisx >> 24 & 0xFF]++;
-    }
-    for (radix=0; radix<4; radix++) {
-        /* any(count == n) => all radix must have been that value =>
-        last x (still thisx) was that value */
-        i = thisx >> (radix*8) & 0xFF;
-        skip[radix] = radixcounts[radix][i] == n;
-        if (skip[radix]) radixcounts[radix][i] = 0;                         // clear it now, the other counts must be 0 already
-    }
-
-    radix = 3;  // MSD
-    while (radix>=0 && skip[radix]) radix--;
-    if (radix==-1) {                                                        // All radix are skipped; i.e. one number repeated n times.
-        if (nalast == 0 && x[0] == NA_INTEGER)                              // all values are identical. return 0 if nalast=0 & all NA
-            for (i=0; i<n; i++) o[i] = 0;                                   // because of 'return', have to take care of it here.
-        else for (i=0; i<n; i++) o[i] = (i+1);
-        push(n);
-        return;
-    }
-    for (i=radix-1; i>=0; i--) {
-        if (!skip[i]) memset(radixcounts[i], 0, 257*sizeof(unsigned int));
-        /* clear the counts as we only needed the parallel pass for skip[]
-           and we're going to use radixcounts again below. Can't use parallel
-           lower counts in MSD radix, unlike LSD. */
-    }
-    thiscounts = radixcounts[radix];
-    shift = radix * 8;
-
-    itmp = thiscounts[0];
-    maxgrpn = itmp;
-    for (i=1; itmp<n && i<256; i++) {
-        thisgrpn = thiscounts[i];
-        if (thisgrpn) {                                                     // don't cummulate through 0s, important below.
-            if (thisgrpn>maxgrpn) maxgrpn = thisgrpn;
-            thiscounts[i] = (itmp += thisgrpn);
-        }
-    }
-    for (i=n-1; i>=0; i--) {
-        thisx = ((unsigned int)(icheck(x[i])) - INT_MIN) >> shift & 0xFF;
-        o[--thiscounts[thisx]] = i+1;
-    }
-
-    if (radix_xsuballoc < maxgrpn) {
-        // The largest group according to the first non-skipped radix, so could be big (if radix is needed on first column)
-        // TO DO: could include extra bits to divide the first radix up more. Often the MSD has groups in just 0-4 out of 256.
-        // free'd at the end of forder once we're done calling iradix repetitively
-        radix_xsub = (int *)realloc(radix_xsub, maxgrpn*sizeof(double));    // realloc(NULL) == malloc
-        if (!radix_xsub) Error("Failed to realloc working memory %d*8bytes (xsub in iradix), radix=%d", maxgrpn, radix);
-        radix_xsuballoc = maxgrpn;
-    }
-
-    alloc_otmp(maxgrpn);   // TO DO: can we leave this to forder and remove these calls??
-    alloc_xtmp(maxgrpn);   // TO DO: doesn't need to be sizeof(double) always, see inside
-
-    nextradix = radix-1;
-    while (nextradix>=0 && skip[nextradix]) nextradix--;
-    if (thiscounts[0] != 0) Error("Internal error. thiscounts[0]=%d but should have been decremented to 0. dradix=%d", thiscounts[0], radix);
-    thiscounts[256] = n;
-    itmp = 0;
-    for (i=1; itmp<n && i<=256; i++) {
-        if (thiscounts[i] == 0) continue;
-        thisgrpn = thiscounts[i] - itmp;                                    // undo cumulate; i.e. diff
-        if (thisgrpn == 1 || nextradix==-1) {
-            push(thisgrpn);
-        } else {
-            for (j=0; j<thisgrpn; j++)
-                ((int *)radix_xsub)[j] = icheck(x[o[itmp+j]-1]);            // this is why this xsub here can't be the same memory as xsub in forder.
-            iradix_r(radix_xsub, o+itmp, thisgrpn, nextradix);              // changes xsub and o by reference recursively.
-        }
-        itmp = thiscounts[i];
-        thiscounts[i] = 0;
-    }
-    if (nalast == 0)                                                        // nalast = 1, -1 are both taken care already.
-        for (i=0; i<n; i++) o[i] = (x[o[i]-1] == NA_INTEGER) ? 0 : o[i];    // nalast = 0 is dealt with separately as it just sets o to 0
-                                                                            // at those indices where x is NA. x[o[i]-1] because x is not
-                                                                            // modified by reference unlike iinsert or iradix_r
+  int i, j, radix, nextradix, itmp, thisgrpn, maxgrpn;
+  unsigned int thisx=0, shift, *thiscounts;
+
+  for (i=0;i<n;i++) {
+    /* parallel histogramming pass; i.e. count occurrences of
+    0:255 in each byte.  Sequential so almost negligible. */
+    thisx = (unsigned int)(icheck(x[i])) - INT_MIN;                     // relies on overflow behaviour. And shouldn't -INT_MIN be up in iradix?
+    radixcounts[0][thisx & 0xFF]++;                                     // unrolled since inside n-loop
+    radixcounts[1][thisx >> 8 & 0xFF]++;
+    radixcounts[2][thisx >> 16 & 0xFF]++;
+    radixcounts[3][thisx >> 24 & 0xFF]++;
+  }
+  for (radix=0; radix<4; radix++) {
+    /* any(count == n) => all radix must have been that value =>
+    last x (still thisx) was that value */
+    i = thisx >> (radix*8) & 0xFF;
+    skip[radix] = radixcounts[radix][i] == n;
+    if (skip[radix]) radixcounts[radix][i] = 0;                         // clear it now, the other counts must be 0 already
+  }
+
+  radix = 3;  // MSD
+  while (radix>=0 && skip[radix]) radix--;
+  if (radix==-1) {                                                        // All radix are skipped; i.e. one number repeated n times.
+    if (nalast == 0 && x[0] == NA_INTEGER)                              // all values are identical. return 0 if nalast=0 & all NA
+      for (i=0; i<n; i++) o[i] = 0;                                   // because of 'return', have to take care of it here.
+    else for (i=0; i<n; i++) o[i] = (i+1);
+    push(n);
+    return;
+  }
+  for (i=radix-1; i>=0; i--) {
+    if (!skip[i]) memset(radixcounts[i], 0, 257*sizeof(unsigned int));
+    /* clear the counts as we only needed the parallel pass for skip[]
+       and we're going to use radixcounts again below. Can't use parallel
+       lower counts in MSD radix, unlike LSD. */
+  }
+  thiscounts = radixcounts[radix];
+  shift = radix * 8;
+
+  itmp = thiscounts[0];
+  maxgrpn = itmp;
+  for (i=1; itmp<n && i<256; i++) {
+    thisgrpn = thiscounts[i];
+    if (thisgrpn) {                                                     // don't cummulate through 0s, important below.
+      if (thisgrpn>maxgrpn) maxgrpn = thisgrpn;
+      thiscounts[i] = (itmp += thisgrpn);
+    }
+  }
+  for (i=n-1; i>=0; i--) {
+    thisx = ((unsigned int)(icheck(x[i])) - INT_MIN) >> shift & 0xFF;
+    o[--thiscounts[thisx]] = i+1;
+  }
+
+  if (radix_xsuballoc < maxgrpn) {
+    // The largest group according to the first non-skipped radix, so could be big (if radix is needed on first column)
+    // TO DO: could include extra bits to divide the first radix up more. Often the MSD has groups in just 0-4 out of 256.
+    // free'd at the end of forder once we're done calling iradix repetitively
+    radix_xsub = (int *)realloc(radix_xsub, maxgrpn*sizeof(double));    // realloc(NULL) == malloc
+    if (!radix_xsub) Error("Failed to realloc working memory %d*8bytes (xsub in iradix), radix=%d", maxgrpn, radix);
+    radix_xsuballoc = maxgrpn;
+  }
+
+  alloc_otmp(maxgrpn);   // TO DO: can we leave this to forder and remove these calls??
+  alloc_xtmp(maxgrpn);   // TO DO: doesn't need to be sizeof(double) always, see inside
+
+  nextradix = radix-1;
+  while (nextradix>=0 && skip[nextradix]) nextradix--;
+  if (thiscounts[0] != 0) Error("Internal error. thiscounts[0]=%d but should have been decremented to 0. dradix=%d", thiscounts[0], radix);
+  thiscounts[256] = n;
+  itmp = 0;
+  for (i=1; itmp<n && i<=256; i++) {
+    if (thiscounts[i] == 0) continue;
+    thisgrpn = thiscounts[i] - itmp;                                    // undo cumulate; i.e. diff
+    if (thisgrpn == 1 || nextradix==-1) {
+      push(thisgrpn);
+    } else {
+      for (j=0; j<thisgrpn; j++)
+        ((int *)radix_xsub)[j] = icheck(x[o[itmp+j]-1]);            // this is why this xsub here can't be the same memory as xsub in forder.
+      iradix_r(radix_xsub, o+itmp, thisgrpn, nextradix);              // changes xsub and o by reference recursively.
+    }
+    itmp = thiscounts[i];
+    thiscounts[i] = 0;
+  }
+  if (nalast == 0)                                                        // nalast = 1, -1 are both taken care already.
+    for (i=0; i<n; i++) o[i] = (x[o[i]-1] == NA_INTEGER) ? 0 : o[i];    // nalast = 0 is dealt with separately as it just sets o to 0
+                                      // at those indices where x is NA. x[o[i]-1] because x is not
+                                      // modified by reference unlike iinsert or iradix_r
 }
 
 static void iradix_r(int *xsub, int *osub, int n, int radix)
-    // xsub is a recursive offset into xsub working memory above in iradix, reordered by reference.
-    // osub is a an offset into the main answer o, reordered by reference.
-    // radix iterates 3,2,1,0
+  // xsub is a recursive offset into xsub working memory above in iradix, reordered by reference.
+  // osub is a an offset into the main answer o, reordered by reference.
+  // radix iterates 3,2,1,0
 {
-    int i, j, itmp, thisx, thisgrpn, nextradix, shift;
-    unsigned int *thiscounts;
-
-    if (n < N_SMALL) {              // N_SMALL=200 is guess based on limited testing. Needs calibrate().
-                                    // Was 50 based on sum(1:50)=1275 worst -vs- 256 cummulate + 256 memset + allowance since reverse order is unlikely.
-        iinsert(xsub, osub, n);     // when nalast==0, iinsert will be called only from within iradix.
-        return;
-    }
-
-    shift = radix*8;
-    thiscounts = radixcounts[radix];
+  int i, j, itmp, thisx, thisgrpn, nextradix, shift;
+  unsigned int *thiscounts;
 
-    for (i=0; i<n; i++) {
-        thisx = (unsigned int)xsub[i] - INT_MIN;                                // sequential in xsub
-        thiscounts[thisx >> shift & 0xFF]++;
-    }
-    itmp = thiscounts[0];
-    for (i=1; itmp<n && i<256; i++)
-        if (thiscounts[i]) thiscounts[i] = (itmp += thiscounts[i]);             // don't cummulate through 0s, important below
-    for (i=n-1; i>=0; i--) {
-        thisx = ((unsigned int)xsub[i] - INT_MIN) >> shift & 0xFF;
-        j = --thiscounts[thisx];
-        otmp[j] = osub[i];
-        ((int *)xtmp)[j] = xsub[i];
-    }
-    memcpy(osub, otmp, n*sizeof(int));
-    memcpy(xsub, xtmp, n*sizeof(int));
-
-    nextradix = radix-1;
-    while (nextradix>=0 && skip[nextradix]) nextradix--;
-    /* TO DO:  If nextradix==-1 AND no further columns from forder AND !retGrp, we're
-               done. We have o. Remember to memset thiscounts before returning. */
-
-    if (thiscounts[0] != 0) Error("Logical error. thiscounts[0]=%d but should have been decremented to 0. radix=%d", thiscounts[0], radix);
-    thiscounts[256] = n;
-    itmp = 0;
-    for (i=1; itmp<n && i<=256; i++) {
-        if (thiscounts[i] == 0) continue;
-        thisgrpn = thiscounts[i] - itmp;  // undo cummulate; i.e. diff
-        if (thisgrpn == 1 || nextradix==-1) {
-            push(thisgrpn);
-        } else {
-            iradix_r(xsub+itmp, osub+itmp, thisgrpn, nextradix);
-        }
-        itmp = thiscounts[i];
-        thiscounts[i] = 0;
+  if (n < N_SMALL) {          // N_SMALL=200 is guess based on limited testing. Needs calibrate().
+                              // Was 50 based on sum(1:50)=1275 worst -vs- 256 cummulate + 256 memset + allowance since reverse order is unlikely.
+    iinsert(xsub, osub, n);   // when nalast==0, iinsert will be called only from within iradix.
+    return;
+  }
+
+  shift = radix*8;
+  thiscounts = radixcounts[radix];
+
+  for (i=0; i<n; i++) {
+    thisx = (unsigned int)xsub[i] - INT_MIN;                                // sequential in xsub
+    thiscounts[thisx >> shift & 0xFF]++;
+  }
+  itmp = thiscounts[0];
+  for (i=1; itmp<n && i<256; i++)
+    if (thiscounts[i]) thiscounts[i] = (itmp += thiscounts[i]);             // don't cummulate through 0s, important below
+  for (i=n-1; i>=0; i--) {
+    thisx = ((unsigned int)xsub[i] - INT_MIN) >> shift & 0xFF;
+    j = --thiscounts[thisx];
+    otmp[j] = osub[i];
+    ((int *)xtmp)[j] = xsub[i];
+  }
+  memcpy(osub, otmp, n*sizeof(int));
+  memcpy(xsub, xtmp, n*sizeof(int));
+
+  nextradix = radix-1;
+  while (nextradix>=0 && skip[nextradix]) nextradix--;
+  /* TO DO:  If nextradix==-1 AND no further columns from forder AND !retGrp, we're
+         done. We have o. Remember to memset thiscounts before returning. */
+
+  if (thiscounts[0] != 0) Error("Logical error. thiscounts[0]=%d but should have been decremented to 0. radix=%d", thiscounts[0], radix);
+  thiscounts[256] = n;
+  itmp = 0;
+  for (i=1; itmp<n && i<=256; i++) {
+    if (thiscounts[i] == 0) continue;
+    thisgrpn = thiscounts[i] - itmp;  // undo cummulate; i.e. diff
+    if (thisgrpn == 1 || nextradix==-1) {
+      push(thisgrpn);
+    } else {
+      iradix_r(xsub+itmp, osub+itmp, thisgrpn, nextradix);
     }
+    itmp = thiscounts[i];
+    thiscounts[i] = 0;
+  }
 }
 
 // dradix from Arun's fastradixdouble.c
@@ -430,45 +430,47 @@ static unsigned long long dmask2;
 SEXP setNumericRounding(SEXP droundArg)
 // init.c has initial call with default of 2
 {
-    if (!isInteger(droundArg) || LENGTH(droundArg)!=1) error("Must an integer or numeric vector length 1");
-    if (INTEGER(droundArg)[0] < 0 || INTEGER(droundArg)[0] > 2) error("Must be 2 (default) or 1 or 0");
-    dround = INTEGER(droundArg)[0];
-    dmask1 = dround ? 1 << (8*dround-1) : 0;
-    dmask2 = 0xffffffffffffffff << dround*8;
-    return R_NilValue;
+  if (!isInteger(droundArg) || LENGTH(droundArg)!=1) error("Must an integer or numeric vector length 1");
+  if (INTEGER(droundArg)[0] < 0 || INTEGER(droundArg)[0] > 2) error("Must be 2 (default) or 1 or 0");
+  dround = INTEGER(droundArg)[0];
+  dmask1 = dround ? 1 << (8*dround-1) : 0;
+  dmask2 = 0xffffffffffffffff << dround*8;
+  return R_NilValue;
 }
 
 SEXP getNumericRounding()
 {
-    return ScalarInteger(dround);
+  return ScalarInteger(dround);
 }
 
-static union {double d;
-              unsigned long long ull;} u;
-            //  int i;
-            //  unsigned int ui;} u;
+static union {
+  double d;
+  unsigned long long ull;
+  //  int i;
+  //  unsigned int ui;
+} u;
 
 unsigned long long dtwiddle(void *p, int i, int order)
 {
-    u.d = order*((double *)p)[i];                               // take care of 'order' right at the beginning
-    if (R_FINITE(u.d)) {
-        u.ull = (u.d) ? u.ull + ((u.ull & dmask1) << 1) : 0;    // handle 0, -0 case. Fix for issues/743.
-                                                                // tested on vector length 100e6. was the fastest fix (see results at the bottom of page)
-    } else if (ISNAN(u.d)) {
-     /* 1. NA twiddled to all bits 0, sorts first.  R's value 1954 cleared.
-        2. NaN twiddled to set just bit 13, sorts immediately after NA. 13th bit to be
-           consistent with "quiet" na bit but any bit outside last 2 bytes would do.
-           (ref: http://r.789695.n4.nabble.com/Question-re-NA-NaNs-in-R-td4685014.html)
-        3. This also normalises a difference between NA on 32bit R (bit 13 set) and 64bit R (bit 13 not set)
-        4. -Inf twiddled to : 0 sign, exponent all 0, mantissa all 1, sorts after NaN
-        5. +Inf twiddled to : 1 sign, exponent all 1, mantissa all 0, sorts last since finite
-           numbers are defined by not-all-1 in exponent */
-        u.ull = (ISNA(u.d) ? 0 : (1ULL << 51));
-        return (nalast == 1 ? ~u.ull : u.ull);
-    }
-    unsigned long long mask = (u.ull & 0x8000000000000000) ?
-                     0xffffffffffffffff : 0x8000000000000000;       // always flip sign bit and if negative (sign bit was set) flip other bits too
-    return( (u.ull ^ mask) & dmask2 );
+  u.d = order*((double *)p)[i];                               // take care of 'order' right at the beginning
+  if (R_FINITE(u.d)) {
+    u.ull = (u.d) ? u.ull + ((u.ull & dmask1) << 1) : 0;    // handle 0, -0 case. Fix for issues/743.
+                                                            // tested on vector length 100e6. was the fastest fix (see results at the bottom of page)
+  } else if (ISNAN(u.d)) {
+   /* 1. NA twiddled to all bits 0, sorts first.  R's value 1954 cleared.
+      2. NaN twiddled to set just bit 13, sorts immediately after NA. 13th bit to be
+         consistent with "quiet" na bit but any bit outside last 2 bytes would do.
+         (ref: http://r.789695.n4.nabble.com/Question-re-NA-NaNs-in-R-td4685014.html)
+      3. This also normalises a difference between NA on 32bit R (bit 13 set) and 64bit R (bit 13 not set)
+      4. -Inf twiddled to : 0 sign, exponent all 0, mantissa all 1, sorts after NaN
+      5. +Inf twiddled to : 1 sign, exponent all 1, mantissa all 0, sorts last since finite
+         numbers are defined by not-all-1 in exponent */
+      u.ull = (ISNA(u.d) ? 0 : (1ULL << 51));
+      return (nalast == 1 ? ~u.ull : u.ull);
+  }
+  unsigned long long mask = (u.ull & 0x8000000000000000) ?
+           0xffffffffffffffff : 0x8000000000000000;       // always flip sign bit and if negative (sign bit was set) flip other bits too
+  return( (u.ull ^ mask) & dmask2 );
 }
 
 unsigned long long i64twiddle(void *p, int i, int order)
@@ -476,40 +478,40 @@ unsigned long long i64twiddle(void *p, int i, int order)
 // case (setkey) will not be affected much because nalast != 1 and order == 1 are
 // defaults.
 {
-    u.d = ((double *)p)[i];
-    u.ull ^= 0x8000000000000000;
-    if (nalast != 1) {
-        if (order != 1 && u.ull != 0) u.ull ^= 0xffffffffffffffff;
-    } else {
-        if ( (order == 1 && u.ull==0) || (order != 1) )
-            u.ull ^= 0xffffffffffffffff;
-    }
-    // another way - equivalent as above.
-    // if (order == 1) {// u.ull = 0 now means NA
-    //     if (nalast == 1 && u.ull == 0) u.ull ^= 0xffffffffffffffff;
-    // } else {
-    //     if (!(nalast != 1 && u.ull == 0)) u.ull ^= 0xffffffffffffffff;
-    // }
-    return u.ull;
+  u.d = ((double *)p)[i];
+  u.ull ^= 0x8000000000000000;
+  if (nalast != 1) {
+    if (order != 1 && u.ull != 0) u.ull ^= 0xffffffffffffffff;
+  } else {
+    if ( (order == 1 && u.ull==0) || (order != 1) )
+      u.ull ^= 0xffffffffffffffff;
+  }
+  // another way - equivalent as above.
+  // if (order == 1) {// u.ull = 0 now means NA
+  //     if (nalast == 1 && u.ull == 0) u.ull ^= 0xffffffffffffffff;
+  // } else {
+  //     if (!(nalast != 1 && u.ull == 0)) u.ull ^= 0xffffffffffffffff;
+  // }
+  return u.ull;
 }
 
 Rboolean dnan(void *p, int i) {
-    u.d = ((double *)p)[i];
-    return (ISNAN(u.d));
+  u.d = ((double *)p)[i];
+  return (ISNAN(u.d));
 }
 
 Rboolean i64nan(void *p, int i) {
-    u.d = ((double *)p)[i];
-    return ((u.ull ^ 0x8000000000000000) == 0);
+  u.d = ((double *)p)[i];
+  return ((u.ull ^ 0x8000000000000000) == 0);
 }
 
 /*
 unsigned long long i32twiddle(void *p, int i)
 {
-    u.i = ((int *)p)[i];  // a cast of int to long would introduce 31 redundant bits after the sign bit (better to be packed together in 32)
-    unsigned int mask = -(int)(u.ui >> 31) | 0x80000000;
-    u.ui ^= mask;
-    return( u.ull );
+  u.i = ((int *)p)[i];  // a cast of int to long would introduce 31 redundant bits after the sign bit (better to be packed together in 32)
+  unsigned int mask = -(int)(u.ui >> 31) | 0x80000000;
+  u.ui ^= mask;
+  return( u.ull );
 }
 */
 
@@ -531,86 +533,86 @@ static void dradix_r(unsigned char *xsub, int *osub, int n, int radix);
 
 static void dradix(unsigned char *x, int *o, int n)
 {
-    int i, j, radix, nextradix, itmp, thisgrpn, maxgrpn;
-    unsigned int *thiscounts;
-    unsigned long long thisx=0;
-    // see comments in iradix for structure.  This follows the same. TO DO: merge iradix in here (almost ready)
-    for (i=0;i<n;i++) {
-        thisx = twiddle(x,i,order);
-        for (radix=0; radix<colSize; radix++)
-            radixcounts[radix][((unsigned char *)&thisx)[RADIX_BYTE]]++;
-        // if dround==2 then radix 0 and 1 will be all 0 here and skipped.
-        /* on little endian, 0 is the least significant bits (the right)
-        / and 7 is the most including sign (the left); i.e. reversed. */
-    }
-    for (radix=0; radix<colSize; radix++) {
-        i = ((unsigned char *)&thisx)[RADIX_BYTE];                           // thisx is the last x after loop above
-        skip[radix] = radixcounts[radix][i] == n;
-        if (skip[radix]) radixcounts[radix][i] = 0;                     // clear it now, the other counts must be 0 already
-    }
-    radix = colSize-1;  // MSD
-    while (radix>=0 && skip[radix]) radix--;
-    if (radix==-1) {                                                    // All radix are skipped; i.e. one number repeated n times.
-        if (nalast == 0 && is_nan(x, 0))                               // all values are identical. return 0 if nalast=0 & all NA
-            for (i=0; i<n; i++) o[i] = 0;                               // because of 'return', have to take care of it here.
-        else for (i=0; i<n; i++) o[i] = (i+1);
-        push(n);
-        return;
-    }
-    for (i=radix-1; i>=0; i--) {  // clear the lower radix counts, we only did them to know skip. will be reused within each group
-        if (!skip[i]) memset(radixcounts[i], 0, 257*sizeof(unsigned int));
-    }
-    thiscounts = radixcounts[radix];
-    itmp = thiscounts[0];
-    maxgrpn = itmp;
-    for (i=1; itmp<n && i<256; i++) {
-        thisgrpn = thiscounts[i];
-        if (thisgrpn) {  // don't cummulate through 0s, important below
-            if (thisgrpn>maxgrpn) maxgrpn = thisgrpn;
-            thiscounts[i] = (itmp += thisgrpn);
-        }
-    }
-    for (i=n-1; i>=0; i--) {
-        thisx = twiddle(x,i,order);
-        o[ --thiscounts[((unsigned char *)&thisx)[RADIX_BYTE]] ] = i+1;
-    }
-
-    if (radix_xsuballoc < maxgrpn) {                                            // TO DO: centralize this alloc
-        // The largest group according to the first non-skipped radix, so could be big (if radix is needed on first column)
-        // TO DO: could include extra bits to divide the first radix up more. Often the MSD has groups in just 0-4 out of 256.
-        // free'd at the end of forder once we're done calling iradix repetitively
-        radix_xsub = (double *)realloc(radix_xsub, maxgrpn*sizeof(double));  // realloc(NULL) == malloc
-        if (!radix_xsub) Error("Failed to realloc working memory %d*8bytes (xsub in dradix), radix=%d", maxgrpn, radix);
-        radix_xsuballoc = maxgrpn;
-    }
-
-    alloc_otmp(maxgrpn);   // TO DO: leave to forder and remove these calls?
-    alloc_xtmp(maxgrpn);
-
-    nextradix = radix-1;
-    while (nextradix>=0 && skip[nextradix]) nextradix--;
-    if (thiscounts[0] != 0) Error("Logical error. thiscounts[0]=%d but should have been decremented to 0. dradix=%d", thiscounts[0], radix);
-    thiscounts[256] = n;
-    itmp = 0;
-    for (i=1; itmp<n && i<=256; i++) {
-        if (thiscounts[i] == 0) continue;
-        thisgrpn = thiscounts[i] - itmp;  // undo cummulate; i.e. diff
-        if (thisgrpn == 1 || nextradix==-1) {
-            push(thisgrpn);
-        } else {
-            if (colSize==4) { // ready for merging in iradix ...
-                error("Not yet used, still using iradix instead");
-                for (j=0; j<thisgrpn; j++) ((int *)radix_xsub)[j] = twiddle(x, o[itmp+j]-1, order);
-            } else for (j=0; j<thisgrpn; j++) ((unsigned long long *)radix_xsub)[j] = twiddle(x, o[itmp+j]-1, order); // this is why this xsub here can't be the same memory as xsub in forder
-            dradix_r(radix_xsub, o+itmp, thisgrpn, nextradix); // changes xsub and o by reference recursively.
-        }
-        itmp = thiscounts[i];
-        thiscounts[i] = 0;
-    }
-    if (nalast == 0)                                                 // nalast = 1, -1 are both taken care already.
-        for (i=0; i<n; i++) o[i] = is_nan(x, o[i]-1) ? 0 : o[i];     // nalast = 0 is dealt with separately as it just sets o to 0
-                                                                     // at those indices where x is NA. x[o[i]-1] because x is not
-                                                                     // modified by reference unlike iinsert or iradix_r
+  int i, j, radix, nextradix, itmp, thisgrpn, maxgrpn;
+  unsigned int *thiscounts;
+  unsigned long long thisx=0;
+  // see comments in iradix for structure.  This follows the same. TO DO: merge iradix in here (almost ready)
+  for (i=0;i<n;i++) {
+    thisx = twiddle(x,i,order);
+    for (radix=0; radix<colSize; radix++)
+      radixcounts[radix][((unsigned char *)&thisx)[RADIX_BYTE]]++;
+    // if dround==2 then radix 0 and 1 will be all 0 here and skipped.
+    /* on little endian, 0 is the least significant bits (the right)
+    / and 7 is the most including sign (the left); i.e. reversed. */
+  }
+  for (radix=0; radix<colSize; radix++) {
+    i = ((unsigned char *)&thisx)[RADIX_BYTE];        // thisx is the last x after loop above
+    skip[radix] = radixcounts[radix][i] == n;
+    if (skip[radix]) radixcounts[radix][i] = 0;       // clear it now, the other counts must be 0 already
+  }
+  radix = colSize-1;  // MSD
+  while (radix>=0 && skip[radix]) radix--;
+  if (radix==-1) {                                    // All radix are skipped; i.e. one number repeated n times.
+    if (nalast == 0 && is_nan(x, 0))                  // all values are identical. return 0 if nalast=0 & all NA
+      for (i=0; i<n; i++) o[i] = 0;                   // because of 'return', have to take care of it here.
+    else for (i=0; i<n; i++) o[i] = (i+1);
+    push(n);
+    return;
+  }
+  for (i=radix-1; i>=0; i--) {  // clear the lower radix counts, we only did them to know skip. will be reused within each group
+    if (!skip[i]) memset(radixcounts[i], 0, 257*sizeof(unsigned int));
+  }
+  thiscounts = radixcounts[radix];
+  itmp = thiscounts[0];
+  maxgrpn = itmp;
+  for (i=1; itmp<n && i<256; i++) {
+    thisgrpn = thiscounts[i];
+    if (thisgrpn) {  // don't cummulate through 0s, important below
+      if (thisgrpn>maxgrpn) maxgrpn = thisgrpn;
+      thiscounts[i] = (itmp += thisgrpn);
+    }
+  }
+  for (i=n-1; i>=0; i--) {
+    thisx = twiddle(x,i,order);
+    o[ --thiscounts[((unsigned char *)&thisx)[RADIX_BYTE]] ] = i+1;
+  }
+
+  if (radix_xsuballoc < maxgrpn) {                                            // TO DO: centralize this alloc
+    // The largest group according to the first non-skipped radix, so could be big (if radix is needed on first column)
+    // TO DO: could include extra bits to divide the first radix up more. Often the MSD has groups in just 0-4 out of 256.
+    // free'd at the end of forder once we're done calling iradix repetitively
+    radix_xsub = (double *)realloc(radix_xsub, maxgrpn*sizeof(double));  // realloc(NULL) == malloc
+    if (!radix_xsub) Error("Failed to realloc working memory %d*8bytes (xsub in dradix), radix=%d", maxgrpn, radix);
+    radix_xsuballoc = maxgrpn;
+  }
+
+  alloc_otmp(maxgrpn);   // TO DO: leave to forder and remove these calls?
+  alloc_xtmp(maxgrpn);
+
+  nextradix = radix-1;
+  while (nextradix>=0 && skip[nextradix]) nextradix--;
+  if (thiscounts[0] != 0) Error("Logical error. thiscounts[0]=%d but should have been decremented to 0. dradix=%d", thiscounts[0], radix);
+  thiscounts[256] = n;
+  itmp = 0;
+  for (i=1; itmp<n && i<=256; i++) {
+    if (thiscounts[i] == 0) continue;
+    thisgrpn = thiscounts[i] - itmp;  // undo cummulate; i.e. diff
+    if (thisgrpn == 1 || nextradix==-1) {
+      push(thisgrpn);
+    } else {
+      if (colSize==4) { // ready for merging in iradix ...
+        error("Not yet used, still using iradix instead");
+        for (j=0; j<thisgrpn; j++) ((int *)radix_xsub)[j] = twiddle(x, o[itmp+j]-1, order);
+      } else for (j=0; j<thisgrpn; j++) ((unsigned long long *)radix_xsub)[j] = twiddle(x, o[itmp+j]-1, order); // this is why this xsub here can't be the same memory as xsub in forder
+      dradix_r(radix_xsub, o+itmp, thisgrpn, nextradix); // changes xsub and o by reference recursively.
+    }
+    itmp = thiscounts[i];
+    thiscounts[i] = 0;
+  }
+  if (nalast == 0)                                             // nalast = 1, -1 are both taken care already.
+    for (i=0; i<n; i++) o[i] = is_nan(x, o[i]-1) ? 0 : o[i];   // nalast = 0 is dealt with separately as it just sets o to 0
+                                                               // at those indices where x is NA. x[o[i]-1] because x is not
+                                                               // modified by reference unlike iinsert or iradix_r
 
 }
 
@@ -619,85 +621,85 @@ static void dinsert(unsigned long long *x, int *o, int n)
 // don't be tempted to binsearch backwards here, have to shift anyway; many memmove would have overhead and do the same thing
 // 'dinsert' will not be called when nalast = 0 and o[0] = -1.
 {
-    int i, j, otmp, tt;
-    unsigned long long xtmp;
-    for (i=1; i<n; i++) {
-        xtmp = x[i];
-        if (xtmp < x[i-1]) {
-            j = i-1;
-            otmp = o[i];
-            while (j>=0 && xtmp < x[j]) { x[j+1] = x[j]; o[j+1] = o[j]; j--; }
-            x[j+1] = xtmp;
-            o[j+1] = otmp;
-        }
-    }
-    tt = 0;
-    for (i=1; i<n; i++) if (x[i]==x[i-1]) tt++; else { push(tt+1); tt=0; }
-    push(tt+1);
+  int i, j, otmp, tt;
+  unsigned long long xtmp;
+  for (i=1; i<n; i++) {
+    xtmp = x[i];
+    if (xtmp < x[i-1]) {
+      j = i-1;
+      otmp = o[i];
+      while (j>=0 && xtmp < x[j]) { x[j+1] = x[j]; o[j+1] = o[j]; j--; }
+      x[j+1] = xtmp;
+      o[j+1] = otmp;
+    }
+  }
+  tt = 0;
+  for (i=1; i<n; i++) if (x[i]==x[i-1]) tt++; else { push(tt+1); tt=0; }
+  push(tt+1);
 }
 
 static void dradix_r(unsigned char *xsub, int *osub, int n, int radix)
-    /* xsub is a recursive offset into xsub working memory above in dradix, reordered by reference.
-       osub is a an offset into the main answer o, reordered by reference.
-       dradix iterates 7,6,5,4,3,2,1,0 */
+  /* xsub is a recursive offset into xsub working memory above in dradix, reordered by reference.
+     osub is a an offset into the main answer o, reordered by reference.
+     dradix iterates 7,6,5,4,3,2,1,0 */
 {
-    int i, j, itmp, thisgrpn, nextradix;
-    unsigned int *thiscounts;
-    unsigned char *p;
-    if (n < 200) {
-        /* 200 is guess based on limited testing. Needs calibrate(). Was 50
-        based on sum(1:50)=1275 worst -vs- 256 cummulate + 256 memset +
-        allowance since reverse order is unlikely */
-        dinsert((void *)xsub, osub, n);                                         // order=1 here because it's already taken care of in iradix
-        return;
-    }
-    thiscounts = radixcounts[radix];
-    p = xsub + RADIX_BYTE;
-    for (i=0; i<n; i++) {
-        thiscounts[*p]++;
-        p += colSize;
+  int i, j, itmp, thisgrpn, nextradix;
+  unsigned int *thiscounts;
+  unsigned char *p;
+  if (n < 200) {
+    /* 200 is guess based on limited testing. Needs calibrate(). Was 50
+    based on sum(1:50)=1275 worst -vs- 256 cummulate + 256 memset +
+    allowance since reverse order is unlikely */
+    dinsert((void *)xsub, osub, n);                                         // order=1 here because it's already taken care of in iradix
+    return;
+  }
+  thiscounts = radixcounts[radix];
+  p = xsub + RADIX_BYTE;
+  for (i=0; i<n; i++) {
+    thiscounts[*p]++;
+    p += colSize;
+  }
+  itmp = thiscounts[0];
+  for (i=1; itmp<n && i<256; i++)
+    if (thiscounts[i]) thiscounts[i] = (itmp += thiscounts[i]);             // don't cummulate through 0s, important below
+  p = xsub + (n-1)*colSize;
+  if (colSize == 4) {
+    error("Not yet used, still using iradix instead");
+    for (i=n-1; i>=0; i--) {
+      j = --thiscounts[*(p+RADIX_BYTE)];
+      otmp[j] = osub[i];
+      ((int *)xtmp)[j] = *(int *)p;
+      p -= colSize;
     }
-    itmp = thiscounts[0];
-    for (i=1; itmp<n && i<256; i++)
-        if (thiscounts[i]) thiscounts[i] = (itmp += thiscounts[i]);             // don't cummulate through 0s, important below
-    p = xsub + (n-1)*colSize;
-    if (colSize == 4) {
-        error("Not yet used, still using iradix instead");
-        for (i=n-1; i>=0; i--) {
-            j = --thiscounts[*(p+RADIX_BYTE)];
-            otmp[j] = osub[i];
-            ((int *)xtmp)[j] = *(int *)p;
-            p -= colSize;
-        }
+  } else {
+    for (i=n-1; i>=0; i--) {
+      j = --thiscounts[*(p+RADIX_BYTE)];
+      otmp[j] = osub[i];
+      ((unsigned long long *)xtmp)[j] = *(unsigned long long *)p;
+      p -= colSize;
+    }
+  }
+  memcpy(osub, otmp, n*sizeof(int));
+  memcpy(xsub, xtmp, n*colSize);
+
+  nextradix = radix-1;
+  while (nextradix>=0 && skip[nextradix]) nextradix--;
+  // TO DO:  If nextradix==-1 and no further columns from forder,  we're done. We have o. Remember to memset thiscounts before returning.
+
+  if (thiscounts[0] != 0) Error("Logical error. thiscounts[0]=%d but should have been decremented to 0. radix=%d", thiscounts[0], radix);
+  thiscounts[256] = n;
+  itmp = 0;
+  for (i=1; itmp<n && i<=256; i++) {
+    if (thiscounts[i] == 0) continue;
+    thisgrpn = thiscounts[i] - itmp;  // undo cummulate; i.e. diff
+    if (thisgrpn == 1 || nextradix==-1) {
+      push(thisgrpn);
     } else {
-        for (i=n-1; i>=0; i--) {
-            j = --thiscounts[*(p+RADIX_BYTE)];
-            otmp[j] = osub[i];
-            ((unsigned long long *)xtmp)[j] = *(unsigned long long *)p;
-            p -= colSize;
-        }
-    }
-    memcpy(osub, otmp, n*sizeof(int));
-    memcpy(xsub, xtmp, n*colSize);
-
-    nextradix = radix-1;
-    while (nextradix>=0 && skip[nextradix]) nextradix--;
-    // TO DO:  If nextradix==-1 and no further columns from forder,  we're done. We have o. Remember to memset thiscounts before returning.
-
-    if (thiscounts[0] != 0) Error("Logical error. thiscounts[0]=%d but should have been decremented to 0. radix=%d", thiscounts[0], radix);
-    thiscounts[256] = n;
-    itmp = 0;
-    for (i=1; itmp<n && i<=256; i++) {
-        if (thiscounts[i] == 0) continue;
-        thisgrpn = thiscounts[i] - itmp;  // undo cummulate; i.e. diff
-        if (thisgrpn == 1 || nextradix==-1) {
-            push(thisgrpn);
-        } else {
-            dradix_r(xsub + itmp*colSize, osub+itmp, thisgrpn, nextradix);
-        }
-        itmp = thiscounts[i];
-        thiscounts[i] = 0;
+      dradix_r(xsub + itmp*colSize, osub+itmp, thisgrpn, nextradix);
     }
+    itmp = thiscounts[i];
+    thiscounts[i] = 0;
+  }
 }
 
 // TO DO?: dcount. Find step size, then range = (max-min)/step and proceed as icount. Many fixed precision floats (such as prices)
@@ -710,21 +712,21 @@ static SEXP *cradix_xtmp = NULL;
 static int cradix_xtmp_alloc = 0;
 
 int StrCmp2(SEXP x, SEXP y) {    // same as StrCmp but also takes into account 'na.last' argument.
-    if (x == y) return 0;                   // same cached pointer (including NA_STRING==NA_STRING)
-    if (x == NA_STRING) return nalast;      // if x=NA, nalast=1 ? then x > y else x < y (Note: nalast == 0 is already taken care of in 'csorted', won't be 0 here)
-    if (y == NA_STRING) return -nalast;     // if y=NA, nalast=1 ? then y > x
-    return order*strcmp(CHAR(ENC2UTF8(x)), CHAR(ENC2UTF8(y)));  // same as explanation in StrCmp
+  if (x == y) return 0;                   // same cached pointer (including NA_STRING==NA_STRING)
+  if (x == NA_STRING) return nalast;      // if x=NA, nalast=1 ? then x > y else x < y (Note: nalast == 0 is already taken care of in 'csorted', won't be 0 here)
+  if (y == NA_STRING) return -nalast;     // if y=NA, nalast=1 ? then y > x
+  return order*strcmp(CHAR(ENC2UTF8(x)), CHAR(ENC2UTF8(y)));  // same as explanation in StrCmp
 }
 
 int StrCmp(SEXP x, SEXP y)            // also used by bmerge and chmatch
 {
-    if (x == y) return 0;             // same cached pointer (including NA_STRING==NA_STRING)
-    if (x == NA_STRING) return -1;    // x<y
-    if (y == NA_STRING) return 1;     // x>y
-    return strcmp(CHAR(ENC2UTF8(x)), CHAR(ENC2UTF8(y))); // ENC2UTF8 handles encoding issues by converting all marked non-utf8 encodings alone to utf8 first. The function could be wrapped in the first if-statement already instead of at the last stage, but this is to ensure that all-ascii cases are handled with maximum efficiency.
-    // This seems to fix the issues as far as I've checked. Will revisit if necessary.
+  if (x == y) return 0;             // same cached pointer (including NA_STRING==NA_STRING)
+  if (x == NA_STRING) return -1;    // x<y
+  if (y == NA_STRING) return 1;     // x>y
+  return strcmp(CHAR(ENC2UTF8(x)), CHAR(ENC2UTF8(y))); // ENC2UTF8 handles encoding issues by converting all marked non-utf8 encodings alone to utf8 first. The function could be wrapped in the first if-statement already instead of at the last stage, but this is to ensure that all-ascii cases are handled with maximum efficiency.
+  // This seems to fix the issues as far as I've checked. Will revisit if necessary.
 
-    // OLD COMMENT: can return 0 here for the same string in known and unknown encodings, good if the unknown string is in that encoding but not if not ordering is ascii only (C locale). TO DO: revisit and allow user to change to strcoll, and take account of Encoding. see comments in bmerge().  10k calls of strcmp = 0.37s, 10k calls of strcoll = 4.7s. See ?Comparison, ?Encoding, Scollate in R internals.
+  // OLD COMMENT: can return 0 here for the same string in known and unknown encodings, good if the unknown string is in that encoding but not if not ordering is ascii only (C locale). TO DO: revisit and allow user to change to strcoll, and take account of Encoding. see comments in bmerge().  10k calls of strcmp = 0.37s, 10k calls of strcoll = 4.7s. See ?Comparison, ?Encoding, Scollate in R internals.
 
 }
 
@@ -745,51 +747,51 @@ static void cradix_r(SEXP *xsub, int n, int radix)
 // We may be able to improve CHARSXP derefencing by submitting patch to R to make R's string cache contiguous
 // but would likely be difficult. If we strxfrm, then it'll then be contiguous and compact then anyway.
 {
-    int i, j, itmp, *thiscounts, thisgrpn=0, thisx=0;
-    SEXP stmp;
-
-    // TO DO?: chmatch to existing sorted vector, then grow it.
-    // TO DO?: if (n<N_SMALL=200) insert sort, then loop through groups via ==
-    if (n<=1) return;
-    if (n==2) {
-        if (StrCmp(xsub[1],xsub[0])<0) { stmp=xsub[0]; xsub[0]=xsub[1]; xsub[1]=stmp; }
-        return;
-    }
-    // TO DO: if (n<50) cinsert (continuing from radix offset into CHAR) or using StrCmp. But 256 is narrow, so quick and not too much an issue.
-
-    thiscounts = cradix_counts + radix*256;
-    for (i=0; i<n; i++) {
-        thisx = xsub[i]==NA_STRING ? 0 : (radix<LENGTH(xsub[i]) ? (unsigned char)(CHAR(xsub[i])[radix]) : 1);
-        thiscounts[ thisx ]++;   // 0 for NA,  1 for ""
-    }
-    if (thiscounts[thisx] == n && radix < maxlen-1) {   // this also catches when subx has shorter strings than the rest, thiscounts[0]==n and we'll recurse very quickly through to the overall maxlen with no 256 overhead each time
-        cradix_r(xsub, n, radix+1);
-        thiscounts[thisx] = 0;  // the rest must be 0 already, save the memset
-        return;
-    }
-    itmp = thiscounts[0];
-    for (i=1; i<256; i++)
-        if (thiscounts[i]) thiscounts[i] = (itmp += thiscounts[i]);  // don't cummulate through 0s, important below
-    for (i=n-1; i>=0; i--) {
-        thisx = xsub[i]==NA_STRING ? 0 : (radix<LENGTH(xsub[i]) ? (unsigned char)(CHAR(xsub[i])[radix]) : 1);
-        j = --thiscounts[thisx];
-        cradix_xtmp[j] = xsub[i];
-    }
-    memcpy(xsub, cradix_xtmp, n*sizeof(SEXP));
-    if (radix == maxlen-1) {
-        memset(thiscounts, 0, 256*sizeof(int));
-        return;
-    }
-    if (thiscounts[0] != 0) Error("Logical error. counts[0]=%d in cradix but should have been decremented to 0. radix=%d", thiscounts[0], radix);
-    itmp = 0;
-    for (i=1;i<256;i++) {
-        if (thiscounts[i] == 0) continue;
-        thisgrpn = thiscounts[i] - itmp;  // undo cummulate; i.e. diff
-        cradix_r(xsub+itmp, thisgrpn, radix+1);
-        itmp = thiscounts[i];
-        thiscounts[i] = 0;  // set to 0 now since we're here, saves memset afterwards. Important to clear! Also more portable for machines where 0 isn't all bits 0 (?!)
-    }
-    if (itmp<n-1) cradix_r(xsub+itmp, n-itmp, radix+1);  // final group
+  int i, j, itmp, *thiscounts, thisgrpn=0, thisx=0;
+  SEXP stmp;
+
+  // TO DO?: chmatch to existing sorted vector, then grow it.
+  // TO DO?: if (n<N_SMALL=200) insert sort, then loop through groups via ==
+  if (n<=1) return;
+  if (n==2) {
+    if (StrCmp(xsub[1],xsub[0])<0) { stmp=xsub[0]; xsub[0]=xsub[1]; xsub[1]=stmp; }
+    return;
+  }
+  // TO DO: if (n<50) cinsert (continuing from radix offset into CHAR) or using StrCmp. But 256 is narrow, so quick and not too much an issue.
+
+  thiscounts = cradix_counts + radix*256;
+  for (i=0; i<n; i++) {
+    thisx = xsub[i]==NA_STRING ? 0 : (radix<LENGTH(xsub[i]) ? (unsigned char)(CHAR(xsub[i])[radix]) : 1);
+    thiscounts[ thisx ]++;   // 0 for NA,  1 for ""
+  }
+  if (thiscounts[thisx] == n && radix < maxlen-1) {   // this also catches when subx has shorter strings than the rest, thiscounts[0]==n and we'll recurse very quickly through to the overall maxlen with no 256 overhead each time
+    cradix_r(xsub, n, radix+1);
+    thiscounts[thisx] = 0;  // the rest must be 0 already, save the memset
+    return;
+  }
+  itmp = thiscounts[0];
+  for (i=1; i<256; i++)
+    if (thiscounts[i]) thiscounts[i] = (itmp += thiscounts[i]);  // don't cummulate through 0s, important below
+  for (i=n-1; i>=0; i--) {
+    thisx = xsub[i]==NA_STRING ? 0 : (radix<LENGTH(xsub[i]) ? (unsigned char)(CHAR(xsub[i])[radix]) : 1);
+    j = --thiscounts[thisx];
+    cradix_xtmp[j] = xsub[i];
+  }
+  memcpy(xsub, cradix_xtmp, n*sizeof(SEXP));
+  if (radix == maxlen-1) {
+    memset(thiscounts, 0, 256*sizeof(int));
+    return;
+  }
+  if (thiscounts[0] != 0) Error("Logical error. counts[0]=%d in cradix but should have been decremented to 0. radix=%d", thiscounts[0], radix);
+  itmp = 0;
+  for (i=1;i<256;i++) {
+    if (thiscounts[i] == 0) continue;
+    thisgrpn = thiscounts[i] - itmp;  // undo cummulate; i.e. diff
+    cradix_r(xsub+itmp, thisgrpn, radix+1);
+    itmp = thiscounts[i];
+    thiscounts[i] = 0;  // set to 0 now since we're here, saves memset afterwards. Important to clear! Also more portable for machines where 0 isn't all bits 0 (?!)
+  }
+  if (itmp<n-1) cradix_r(xsub+itmp, n-itmp, radix+1);  // final group
 }
 
 static SEXP *ustr = NULL;
@@ -804,51 +806,51 @@ static void cgroup(SEXP *x, int *o, int n)
 // Since it doesn't sort the strings, the name is cgroup.
 // there is no _pre for this.  ustr created and cleared each time.
 {
-    SEXP s;
-    int i, k, cumsum;
-    // savetl_init() is called once at the start of forder
-    if (ustr_n != 0) Error("Internal error. ustr isn't empty when starting cgroup: ustr_n=%d, ustr_alloc=%d", ustr_n, ustr_alloc);
-    for(i=0; i<n; i++) {
-        s = x[i];
-        if (TRUELENGTH(s)<0) {                   // this case first as it's the most frequent
-            SET_TRUELENGTH(s, TRUELENGTH(s)-1);  // use negative counts so as to detect R's own (positive) usage of tl on CHARSXP
-            continue;
-        }
-        if (TRUELENGTH(s)>0) {  // Save any of R's own usage of tl (assumed positive, so we can both count and save in one scan), to restore
-            savetl(s);          // afterwards. From R 2.14.0, tl is initialized to 0, prior to that it was random so this step saved too much.
-            SET_TRUELENGTH(s,0);
-        }
-        if (ustr_alloc<=ustr_n) {
-            ustr_alloc = (ustr_alloc == 0) ? 10000 : ustr_alloc*2;  // 10000 = 78k of 8byte pointers. Small initial guess, negligible time to alloc.
-            if (ustr_alloc>n) ustr_alloc = n;
-            ustr = realloc(ustr, ustr_alloc * sizeof(SEXP));
-            if (ustr == NULL) Error("Unable to realloc %d * %d bytes in cgroup", ustr_alloc, sizeof(SEXP));
-        }
-        SET_TRUELENGTH(s, -1);
-        ustr[ustr_n++] = s;
-    }
-    // TO DO: the same string in different encodings will be considered different here. Sweep through ustr and merge counts where equal (sort needed therefore, unfortunately?, only if there are any marked encodings present)
-    cumsum = 0;
-    for(i=0; i<ustr_n; i++) {                                    // 0.000
-        push(-TRUELENGTH(ustr[i]));
-        SET_TRUELENGTH(ustr[i], cumsum += -TRUELENGTH(ustr[i]));
-    }
-    int *target = (o[0] != -1) ? newo : o;
-    for(i=n-1; i>=0; i--) {
-        s = x[i];                                            // 0.400 (page fetches on string cache)
-        SET_TRUELENGTH(s, k = TRUELENGTH(s)-1);
-        target[k] = i+1;                                     // 0.800 (random access to o)
-    }
-    for(i=0; i<ustr_n; i++) SET_TRUELENGTH(ustr[i],0);     // The cummulate meant counts are left non zero, so reset for next time (0.00s).
-    ustr_n = 0;
+  SEXP s;
+  int i, k, cumsum;
+  // savetl_init() is called once at the start of forder
+  if (ustr_n != 0) Error("Internal error. ustr isn't empty when starting cgroup: ustr_n=%d, ustr_alloc=%d", ustr_n, ustr_alloc);
+  for(i=0; i<n; i++) {
+    s = x[i];
+    if (TRUELENGTH(s)<0) {                   // this case first as it's the most frequent
+      SET_TRUELENGTH(s, TRUELENGTH(s)-1);  // use negative counts so as to detect R's own (positive) usage of tl on CHARSXP
+      continue;
+    }
+    if (TRUELENGTH(s)>0) {  // Save any of R's own usage of tl (assumed positive, so we can both count and save in one scan), to restore
+      savetl(s);          // afterwards. From R 2.14.0, tl is initialized to 0, prior to that it was random so this step saved too much.
+      SET_TRUELENGTH(s,0);
+    }
+    if (ustr_alloc<=ustr_n) {
+      ustr_alloc = (ustr_alloc == 0) ? 10000 : ustr_alloc*2;  // 10000 = 78k of 8byte pointers. Small initial guess, negligible time to alloc.
+      if (ustr_alloc>n) ustr_alloc = n;
+      ustr = realloc(ustr, ustr_alloc * sizeof(SEXP));
+      if (ustr == NULL) Error("Unable to realloc %d * %d bytes in cgroup", ustr_alloc, sizeof(SEXP));
+    }
+    SET_TRUELENGTH(s, -1);
+    ustr[ustr_n++] = s;
+  }
+  // TO DO: the same string in different encodings will be considered different here. Sweep through ustr and merge counts where equal (sort needed therefore, unfortunately?, only if there are any marked encodings present)
+  cumsum = 0;
+  for(i=0; i<ustr_n; i++) {                                    // 0.000
+    push(-TRUELENGTH(ustr[i]));
+    SET_TRUELENGTH(ustr[i], cumsum += -TRUELENGTH(ustr[i]));
+  }
+  int *target = (o[0] != -1) ? newo : o;
+  for(i=n-1; i>=0; i--) {
+    s = x[i];                                            // 0.400 (page fetches on string cache)
+    SET_TRUELENGTH(s, k = TRUELENGTH(s)-1);
+    target[k] = i+1;                                     // 0.800 (random access to o)
+  }
+  for(i=0; i<ustr_n; i++) SET_TRUELENGTH(ustr[i],0);     // The cummulate meant counts are left non zero, so reset for next time (0.00s).
+  ustr_n = 0;
 }
 
 static int *csort_otmp=NULL, csort_otmp_alloc=0;
 static void alloc_csort_otmp(int n) {
-    if (csort_otmp_alloc >= n) return;
-    csort_otmp = (int *)realloc(csort_otmp, n * sizeof(int));
-    if (csort_otmp == NULL) Error("Failed to allocate working memory for csort_otmp. Requested %d * %d bytes", n, sizeof(int));
-    csort_otmp_alloc = n;
+  if (csort_otmp_alloc >= n) return;
+  csort_otmp = (int *)realloc(csort_otmp, n * sizeof(int));
+  if (csort_otmp == NULL) Error("Failed to allocate working memory for csort_otmp. Requested %d * %d bytes", n, sizeof(int));
+  csort_otmp_alloc = n;
 }
 
 static void csort(SEXP *x, int *o, int n)
@@ -860,31 +862,31 @@ static void csort(SEXP *x, int *o, int n)
    Requires csort_pre() to have created and sorted ustr already
 */
 {
-    int i;
-    /* can't use otmp, since iradix might be called here and that uses otmp (and xtmp).
-       alloc_csort_otmp(n) is called from forder for either n=nrow if 1st column,
-       or n=maxgrpn if onwards columns */
-    for(i=0; i<n; i++) csort_otmp[i] = (x[i] == NA_STRING) ? NA_INTEGER : -TRUELENGTH(x[i]);
-    if (nalast == 0 && n == 2) {                        // special case for nalast==0. n==1 is handled inside forder. at least 1 will be NA here
-        if (o[0] == -1) for (i=0; i<n; i++) o[i] = i+1;    // else use o from caller directly (not 1st column)
-        for (int i=0; i<n; i++) if (csort_otmp[i] == NA_INTEGER) o[i] = 0;
-        push(1); push(1);
-        return;
-    }
-    if (n < N_SMALL && nalast != 0) {                                    // TO DO: calibrate() N_SMALL=200
-        if (o[0] == -1) for (i=0; i<n; i++) o[i] = i+1;    // else use o from caller directly (not 1st column)
-        for (int i=0; i<n; i++) csort_otmp[i] = icheck(csort_otmp[i]);
-        iinsert(csort_otmp, o, n);
-    } else {
-        setRange(csort_otmp, n);
-        if (range == NA_INTEGER) Error("Internal error. csort's otmp contains all-NA");
-        int *target = (o[0] != -1) ? newo : o;
-        if (range <= N_RANGE) // && range<n)            // TO DO: calibrate(). radix was faster (9.2s "range<=10000" instead of 11.6s
-            icount(csort_otmp, target, n);       // "range<=N_RANGE && range<n") for run(7) where range=N_RANGE n=10000000
-        else iradix(csort_otmp, target, n);
-    }
-    // all i* push onto stack. Using their counts may be faster here than thrashing SEXP fetches over several passes as cgroup does
-    // (but cgroup needs that to keep orginal order, and cgroup saves the sort in csort_pre).
+  int i;
+  /* can't use otmp, since iradix might be called here and that uses otmp (and xtmp).
+     alloc_csort_otmp(n) is called from forder for either n=nrow if 1st column,
+     or n=maxgrpn if onwards columns */
+  for(i=0; i<n; i++) csort_otmp[i] = (x[i] == NA_STRING) ? NA_INTEGER : -TRUELENGTH(x[i]);
+  if (nalast == 0 && n == 2) {                        // special case for nalast==0. n==1 is handled inside forder. at least 1 will be NA here
+    if (o[0] == -1) for (i=0; i<n; i++) o[i] = i+1;    // else use o from caller directly (not 1st column)
+    for (int i=0; i<n; i++) if (csort_otmp[i] == NA_INTEGER) o[i] = 0;
+    push(1); push(1);
+    return;
+  }
+  if (n < N_SMALL && nalast != 0) {                                    // TO DO: calibrate() N_SMALL=200
+    if (o[0] == -1) for (i=0; i<n; i++) o[i] = i+1;    // else use o from caller directly (not 1st column)
+    for (int i=0; i<n; i++) csort_otmp[i] = icheck(csort_otmp[i]);
+    iinsert(csort_otmp, o, n);
+  } else {
+    setRange(csort_otmp, n);
+    if (range == NA_INTEGER) Error("Internal error. csort's otmp contains all-NA");
+    int *target = (o[0] != -1) ? newo : o;
+    if (range <= N_RANGE) // && range<n)            // TO DO: calibrate(). radix was faster (9.2s "range<=10000" instead of 11.6s
+      icount(csort_otmp, target, n);       // "range<=N_RANGE && range<n") for run(7) where range=N_RANGE n=10000000
+    else iradix(csort_otmp, target, n);
+  }
+  // all i* push onto stack. Using their counts may be faster here than thrashing SEXP fetches over several passes as cgroup does
+  // (but cgroup needs that to keep orginal order, and cgroup saves the sort in csort_pre).
 }
 
 static void csort_pre(SEXP *x, int n)
@@ -892,46 +894,46 @@ static void csort_pre(SEXP *x, int n)
 // Runs once for each column (if sortStr==TRUE), then ustr is used by csort within each group
 // ustr is grown on each character column, to save sorting the same strings again if several columns contain the same strings
 {
-    SEXP s;
-    int i, old_un, new_un;
-    // savetl_init() is called once at the start of forder
-    old_un = ustr_n;
-    for(i=0; i<n; i++) {
-        s = x[i];
-        if (TRUELENGTH(s)<0) continue;   // this case first as it's the most frequent. Already in ustr, this negative is its ordering.
-        if (TRUELENGTH(s)>0) {  // Save any of R's own usage of tl (assumed positive, so we can both count and save in one scan), to restore
-            savetl(s);          // afterwards. From R 2.14.0, tl is initialized to 0, prior to that it was random so this step saved too much.
-            SET_TRUELENGTH(s,0);
-        }
-        if (ustr_alloc<=ustr_n) {
-            ustr_alloc = (ustr_alloc == 0) ? 10000 : ustr_alloc*2;  // 10000 = 78k of 8byte pointers. Small initial guess, negligible time to alloc.
-            if (ustr_alloc > old_un+n) ustr_alloc = old_un + n;
-            ustr = realloc(ustr, ustr_alloc * sizeof(SEXP));
-            if (ustr==NULL) Error("Failed to realloc ustr. Requested %d * %d bytes", ustr_alloc, sizeof(SEXP));
-        }
-        SET_TRUELENGTH(s, -1);  // this -1 will become its ordering later below
-        ustr[ustr_n++] = s;
-        if (s!=NA_STRING && LENGTH(s)>maxlen) maxlen=LENGTH(s);  // length on CHARSXP is the nchar of char * (excluding \0), and treats marked encodings as if ascii.
-    }
-    new_un = ustr_n;
-    if (new_un == old_un) return;  // No new strings observed, seen them all before in previous column. ustr already sufficient.
-    // If we ever make ustr permanently held by data.table, we'll just need to make the final loop to set -i-1 before returning here.
-    // sort ustr.  TO DO: just sort new ones and merge them in.
-    // These allocs are here, to save them being in the recursive cradix_r()
-    if (cradix_counts_alloc < maxlen) {
-        cradix_counts_alloc = maxlen + 10;   // +10 to save too many reallocs
-        cradix_counts = (int *)realloc(cradix_counts, cradix_counts_alloc * 256 * sizeof(int) );  // stack of counts
-        if (!cradix_counts) Error("Failed to alloc cradix_counts");
-        memset(cradix_counts, 0, cradix_counts_alloc * 256 * sizeof(int));
-    }
-    if (cradix_xtmp_alloc < ustr_n) {
-        cradix_xtmp = (SEXP *)realloc( cradix_xtmp,  ustr_n * sizeof(SEXP) );  // TO DO: Reuse the one we have in forder. Does it need to be n length?
-        if (!cradix_xtmp) Error("Failed to alloc cradix_tmp");
-        cradix_xtmp_alloc = ustr_n;
-    }
-    cradix_r(ustr, ustr_n, 0);  // sorts ustr in-place by reference
-    for(i=0; i<ustr_n; i++)     // save ordering in the CHARSXP. negative so as to distinguish with R's own usage.
-        SET_TRUELENGTH(ustr[i], -i-1);
+  SEXP s;
+  int i, old_un, new_un;
+  // savetl_init() is called once at the start of forder
+  old_un = ustr_n;
+  for(i=0; i<n; i++) {
+    s = x[i];
+    if (TRUELENGTH(s)<0) continue;   // this case first as it's the most frequent. Already in ustr, this negative is its ordering.
+    if (TRUELENGTH(s)>0) {  // Save any of R's own usage of tl (assumed positive, so we can both count and save in one scan), to restore
+      savetl(s);          // afterwards. From R 2.14.0, tl is initialized to 0, prior to that it was random so this step saved too much.
+      SET_TRUELENGTH(s,0);
+    }
+    if (ustr_alloc<=ustr_n) {
+      ustr_alloc = (ustr_alloc == 0) ? 10000 : ustr_alloc*2;  // 10000 = 78k of 8byte pointers. Small initial guess, negligible time to alloc.
+      if (ustr_alloc > old_un+n) ustr_alloc = old_un + n;
+      ustr = realloc(ustr, ustr_alloc * sizeof(SEXP));
+      if (ustr==NULL) Error("Failed to realloc ustr. Requested %d * %d bytes", ustr_alloc, sizeof(SEXP));
+    }
+    SET_TRUELENGTH(s, -1);  // this -1 will become its ordering later below
+    ustr[ustr_n++] = s;
+    if (s!=NA_STRING && LENGTH(s)>maxlen) maxlen=LENGTH(s);  // length on CHARSXP is the nchar of char * (excluding \0), and treats marked encodings as if ascii.
+  }
+  new_un = ustr_n;
+  if (new_un == old_un) return;  // No new strings observed, seen them all before in previous column. ustr already sufficient.
+  // If we ever make ustr permanently held by data.table, we'll just need to make the final loop to set -i-1 before returning here.
+  // sort ustr.  TO DO: just sort new ones and merge them in.
+  // These allocs are here, to save them being in the recursive cradix_r()
+  if (cradix_counts_alloc < maxlen) {
+    cradix_counts_alloc = maxlen + 10;   // +10 to save too many reallocs
+    cradix_counts = (int *)realloc(cradix_counts, cradix_counts_alloc * 256 * sizeof(int) );  // stack of counts
+    if (!cradix_counts) Error("Failed to alloc cradix_counts");
+    memset(cradix_counts, 0, cradix_counts_alloc * 256 * sizeof(int));
+  }
+  if (cradix_xtmp_alloc < ustr_n) {
+    cradix_xtmp = (SEXP *)realloc( cradix_xtmp,  ustr_n * sizeof(SEXP) );  // TO DO: Reuse the one we have in forder. Does it need to be n length?
+    if (!cradix_xtmp) Error("Failed to alloc cradix_tmp");
+    cradix_xtmp_alloc = ustr_n;
+  }
+  cradix_r(ustr, ustr_n, 0);  // sorts ustr in-place by reference
+  for(i=0; i<ustr_n; i++)     // save ordering in the CHARSXP. negative so as to distinguish with R's own usage.
+    SET_TRUELENGTH(ustr[i], -i-1);
 }
 
 // functions to test vectors for sortedness: isorted, dsorted and csorted
@@ -946,540 +948,540 @@ static void csort_pre(SEXP *x, int n)
 static int isorted(int *x, int n)                           // order = 1 is ascending and order=-1 is descending
 {                                                           // also takes care of na.last argument with check through 'icheck'
                                                             // Relies on NA_INTEGER==INT_MIN, checked in init.c
-    int i=1,j=0;
-    if (nalast == 0) {                                      // when nalast = NA,
-        for (int k=0; k<n; k++) if (x[k] != NA_INTEGER) j++;
-        if (j == 0) { push(n); return(-2); }                // all NAs ? return special value to replace all o's values with '0'
-        if (j != n) return(0);                              // any NAs ? return 0 = unsorted and leave it to sort routines to replace o's with 0's
-    }                                                       // no NAs  ? continue to check the rest of isorted - the same routine as usual
-    if (n<=1) { push(n); return(1); }
-    if (icheck(x[1]) < icheck(x[0])) {
-        i = 2;
-        while (i<n && icheck(x[i]) < icheck(x[i-1])) i++;
-        if (i==n) { mpush(1,n); return(-1);}                // strictly opposite to expected 'order', no ties;
+  int i=1,j=0;
+  if (nalast == 0) {                                        // when nalast = NA,
+    for (int k=0; k<n; k++) if (x[k] != NA_INTEGER) j++;
+    if (j == 0) { push(n); return(-2); }                    // all NAs ? return special value to replace all o's values with '0'
+    if (j != n) return(0);                                  // any NAs ? return 0 = unsorted and leave it to sort routines to replace o's with 0's
+  }                                                         // no NAs  ? continue to check the rest of isorted - the same routine as usual
+  if (n<=1) { push(n); return(1); }
+  if (icheck(x[1]) < icheck(x[0])) {
+    i = 2;
+    while (i<n && icheck(x[i]) < icheck(x[i-1])) i++;
+    if (i==n) { mpush(1,n); return(-1);}                    // strictly opposite to expected 'order', no ties;
                                                             // e.g. no more than one NA at the beginning/end (for order=-1/1)
-        else return(0);
-    }
-    int old = gsngrp[flip];
-    int tt = 1;
-    for (i=1; i<n; i++) {
-        if (icheck(x[i]) < icheck(x[i-1])) { gsngrp[flip] = old; return(0); }
-        if (x[i]==x[i-1]) tt++; else { push(tt); tt=1; }
-    }
-    push(tt);
-    return(1);                                              // same as 'order', NAs at the beginning for order=1, at end for order=-1, possibly with ties
+    else return(0);
+  }
+  int old = gsngrp[flip];
+  int tt = 1;
+  for (i=1; i<n; i++) {
+    if (icheck(x[i]) < icheck(x[i-1])) { gsngrp[flip] = old; return(0); }
+    if (x[i]==x[i-1]) tt++; else { push(tt); tt=1; }
+  }
+  push(tt);
+  return(1);                                                // same as 'order', NAs at the beginning for order=1, at end for order=-1, possibly with ties
 }
 
 static int dsorted(double *x, int n)                        // order=1 is ascending and -1 is descending
 {                                                           // also accounts for nalast=0 (=NA), =1 (TRUE), -1 (FALSE) (in twiddle)
-    int i=1,j=0;
-    unsigned long long prev, this;
-    if (nalast == 0) {                                      // when nalast = NA,
-        for (int k=0; k<n; k++) if (!is_nan(x, k)) j++;
-        if (j == 0) { push(n); return(-2); }                // all NAs ? return special value to replace all o's values with '0'
-        if (j != n) return(0);                              // any NAs ? return 0 = unsorted and leave it to sort routines to replace o's with 0's
-    }                                                       // no NAs  ? continue to check the rest of isorted - the same routine as usual
-    if (n<=1) { push(n); return(1); }
-    prev = twiddle(x,0,order);
-    this = twiddle(x,1,order);
-    if (this < prev) {
-        i = 2;
-        prev=this;
-        while (i<n && (this=twiddle(x,i,order)) < prev) {i++; prev=this; }
-        if (i==n) { mpush(1,n); return(-1);}                // strictly opposite of expected 'order', no ties;
+  int i=1,j=0;
+  unsigned long long prev, this;
+  if (nalast == 0) {                                        // when nalast = NA,
+    for (int k=0; k<n; k++) if (!is_nan(x, k)) j++;
+    if (j == 0) { push(n); return(-2); }                    // all NAs ? return special value to replace all o's values with '0'
+    if (j != n) return(0);                                  // any NAs ? return 0 = unsorted and leave it to sort routines to replace o's with 0's
+  }                                                         // no NAs  ? continue to check the rest of isorted - the same routine as usual
+  if (n<=1) { push(n); return(1); }
+  prev = twiddle(x,0,order);
+  this = twiddle(x,1,order);
+  if (this < prev) {
+    i = 2;
+    prev=this;
+    while (i<n && (this=twiddle(x,i,order)) < prev) {i++; prev=this; }
+    if (i==n) { mpush(1,n); return(-1);}                    // strictly opposite of expected 'order', no ties;
                                                             // e.g. no more than one NA at the beginning/end (for order=-1/1)
-        else return(0);                                     // TO DO: improve to be stable for ties in reverse
-    }
-    int old = gsngrp[flip];
-    int tt = 1;
-    for (i=1; i<n; i++) {
-        this = twiddle(x,i,order);                          // TO DO: once we get past -Inf, NA and NaN at the bottom,  and +Inf at the top,
+    else return(0);                                         // TO DO: improve to be stable for ties in reverse
+  }
+  int old = gsngrp[flip];
+  int tt = 1;
+  for (i=1; i<n; i++) {
+    this = twiddle(x,i,order);                              // TO DO: once we get past -Inf, NA and NaN at the bottom,  and +Inf at the top,
                                                             //        the middle only need be twiddled for tolerance (worth it?)
-        if (this < prev) { gsngrp[flip] = old; return(0); }
-        if (this==prev) tt++; else { push(tt); tt=1; }
-        prev = this;
-    }
-    push(tt);
-    return(1);                                              // exactly as expected in 'order' (1=increasing, -1=decreasing), possibly with ties
+    if (this < prev) { gsngrp[flip] = old; return(0); }
+    if (this==prev) tt++; else { push(tt); tt=1; }
+    prev = this;
+  }
+  push(tt);
+  return(1);                                                // exactly as expected in 'order' (1=increasing, -1=decreasing), possibly with ties
 }
 
 static int csorted(SEXP *x, int n)                          // order=1 is ascending and -1 is descending
 {                                                           // also accounts for nalast=0 (=NA), =1 (TRUE), -1 (FALSE)
-    int i=1, j=0, tmp;
-    if (nalast == 0) {                                      // when nalast = NA,
-        for (int k=0; k<n; k++) if (x[k] != NA_STRING) j++;
-        if (j == 0) { push(n); return(-2); }                // all NAs ? return special value to replace all o's values with '0'
-        if (j != n) return(0);                              // any NAs ? return 0 = unsorted and leave it to sort routines to replace o's with 0's
-    }                                                       // no NAs  ? continue to check the rest of isorted - the same routine as usual
-    if (n<=1) { push(n); return(1); }
-    if (StrCmp2(x[1],x[0])<0) {
-        i = 2;
-        while (i<n && StrCmp2(x[i],x[i-1])<0) i++;
-        if (i==n) { mpush(1,n); return(-1);}                // strictly opposite of expected 'order', no ties;
+  int i=1, j=0, tmp;
+  if (nalast == 0) {                                        // when nalast = NA,
+    for (int k=0; k<n; k++) if (x[k] != NA_STRING) j++;
+    if (j == 0) { push(n); return(-2); }                    // all NAs ? return special value to replace all o's values with '0'
+    if (j != n) return(0);                                  // any NAs ? return 0 = unsorted and leave it to sort routines to replace o's with 0's
+  }                                                         // no NAs  ? continue to check the rest of isorted - the same routine as usual
+  if (n<=1) { push(n); return(1); }
+  if (StrCmp2(x[1],x[0])<0) {
+    i = 2;
+    while (i<n && StrCmp2(x[i],x[i-1])<0) i++;
+    if (i==n) { mpush(1,n); return(-1);}                    // strictly opposite of expected 'order', no ties;
                                                             // e.g. no more than one NA at the beginning/end (for order=-1/1)
-        else return(0);
-    }
-    int old = gsngrp[flip];
-    int tt = 1;
-    for (i=1; i<n; i++) {
-        tmp = StrCmp2(x[i],x[i-1]);
-        if (tmp < 0) { gsngrp[flip] = old; return(0); }
-        if (tmp == 0) tt++; else { push(tt); tt=1; }
-    }
-    push(tt);
-    return(1);                                              // exactly as expected in 'order', possibly with ties
+    else return(0);
+  }
+  int old = gsngrp[flip];
+  int tt = 1;
+  for (i=1; i<n; i++) {
+    tmp = StrCmp2(x[i],x[i-1]);
+    if (tmp < 0) { gsngrp[flip] = old; return(0); }
+    if (tmp == 0) tt++; else { push(tt); tt=1; }
+  }
+  push(tt);
+  return(1);                                                // exactly as expected in 'order', possibly with ties
 }
 
 static void isort(int *x, int *o, int n)
 {
-    if (n<=2) {
-        if (nalast == 0 && n == 2) {                        // nalast = 0 and n == 2 (check bottom of this file for explanation)
-            if (o[0]==-1) { o[0]=1; o[1]=2; }
-            for (int i=0; i<n; i++) if (x[i] == NA_INTEGER) o[i] = 0;
-            push(1); push(1);
-            return;
-        } else Error("Internal error: isort received n=%d. isorted should have dealt with this (e.g. as a reverse sorted vector) already",n);
-    }
-    if (n<N_SMALL && o[0] != -1 && nalast != 0) {                 // see comment above in iradix_r on N_SMALL=200.
-        /* if not o[0] then can't just populate with 1:n here, since x is changed by ref too (so would need to be copied). */
-        /* pushes inside too. Changes x and o by reference, so not suitable  in first column when o hasn't been populated yet
-           and x is the actual column in DT (hence check on o[0]). */
-        if (order != 1 || nalast != -1)                     // so that default case, i.e., order=1, nalast=FALSE will not be affected (ex: `setkey`)
-            for (int i=0; i<n; i++) x[i] = icheck(x[i]);
-        iinsert(x, o, n);
-    } else {
-        /* Tighter range (e.g. copes better with a few abormally large values in some groups), but also, when setRange was once at
-           colum level that caused an extra scan of (long) x first. 10,000 calls to setRange takes just 0.04s i.e. negligible. */
-        setRange(x, n);
-        if (range==NA_INTEGER) Error("Internal error: isort passed all-NA. isorted should have caught this before this point");
-        int *target = (o[0] != -1) ? newo : o;
-        if (range<=N_RANGE && range<=n) {                   // was range<10000 for subgroups, but 1e5 for the first column,
-            icount(x, target, n);                           // tried to generalise here.  1e4 rather than 1e5 here because iterated
-        } else {                                            // was (thisgrpn < 200 || range > 20000) then radix
-            iradix(x, target, n);                           // a short vector with large range can bite icount when iterated (BLOCK 4 and 6)
-        }
+  if (n<=2) {
+    if (nalast == 0 && n == 2) {                            // nalast = 0 and n == 2 (check bottom of this file for explanation)
+      if (o[0]==-1) { o[0]=1; o[1]=2; }
+      for (int i=0; i<n; i++) if (x[i] == NA_INTEGER) o[i] = 0;
+      push(1); push(1);
+      return;
+    } else Error("Internal error: isort received n=%d. isorted should have dealt with this (e.g. as a reverse sorted vector) already",n);
+  }
+  if (n<N_SMALL && o[0] != -1 && nalast != 0) {                 // see comment above in iradix_r on N_SMALL=200.
+    /* if not o[0] then can't just populate with 1:n here, since x is changed by ref too (so would need to be copied). */
+    /* pushes inside too. Changes x and o by reference, so not suitable  in first column when o hasn't been populated yet
+       and x is the actual column in DT (hence check on o[0]). */
+    if (order != 1 || nalast != -1)                     // so that default case, i.e., order=1, nalast=FALSE will not be affected (ex: `setkey`)
+      for (int i=0; i<n; i++) x[i] = icheck(x[i]);
+    iinsert(x, o, n);
+  } else {
+    /* Tighter range (e.g. copes better with a few abormally large values in some groups), but also, when setRange was once at
+       colum level that caused an extra scan of (long) x first. 10,000 calls to setRange takes just 0.04s i.e. negligible. */
+    setRange(x, n);
+    if (range==NA_INTEGER) Error("Internal error: isort passed all-NA. isorted should have caught this before this point");
+    int *target = (o[0] != -1) ? newo : o;
+    if (range<=N_RANGE && range<=n) {                   // was range<10000 for subgroups, but 1e5 for the first column,
+      icount(x, target, n);                             // tried to generalise here.  1e4 rather than 1e5 here because iterated
+    } else {                                            // was (thisgrpn < 200 || range > 20000) then radix
+      iradix(x, target, n);                             // a short vector with large range can bite icount when iterated (BLOCK 4 and 6)
     }
-    // TO DO: add calibrate() to init.c
+  }
+  // TO DO: add calibrate() to init.c
 }
 
 static void dsort(double *x, int *o, int n)
 {
-    if (n <= 2) {                                           // nalast = 0 and n == 2 (check bottom of this file for explanation)
-        if (nalast == 0 && n == 2) {                        // don't have to twiddle here.. at least one will be NA and 'n' WILL BE 2.
-            if (o[0]==-1) { o[0]=1; o[1]=2; }
-            for (int i=0; i<n; i++) if (is_nan(x, i)) o[i] = 0;
-            push(1); push(1);
-            return;
-        } Error("Internal error: dsort received n=%d. dsorted should have dealt with this (e.g. as a reverse sorted vector) already",n);
-    }
-    if (n<N_SMALL && o[0] != -1 && nalast != 0) {                                    // see comment above in iradix_r re N_SMALL=200,  and isort for o[0]
-        for (int i=0; i<n; i++) ((unsigned long long *)x)[i] = twiddle(x,i,order);   // have to twiddle here anyways, can't speed up default case like in isort
-        dinsert((unsigned long long *)x, o, n);
-    } else {
-        dradix((unsigned char *)x, (o[0] != -1) ? newo : o, n);
-    }
+  if (n <= 2) {                                         // nalast = 0 and n == 2 (check bottom of this file for explanation)
+    if (nalast == 0 && n == 2) {                        // don't have to twiddle here.. at least one will be NA and 'n' WILL BE 2.
+      if (o[0]==-1) { o[0]=1; o[1]=2; }
+      for (int i=0; i<n; i++) if (is_nan(x, i)) o[i] = 0;
+      push(1); push(1);
+      return;
+    } Error("Internal error: dsort received n=%d. dsorted should have dealt with this (e.g. as a reverse sorted vector) already",n);
+  }
+  if (n<N_SMALL && o[0] != -1 && nalast != 0) {                                  // see comment above in iradix_r re N_SMALL=200,  and isort for o[0]
+    for (int i=0; i<n; i++) ((unsigned long long *)x)[i] = twiddle(x,i,order);   // have to twiddle here anyways, can't speed up default case like in isort
+    dinsert((unsigned long long *)x, o, n);
+  } else {
+    dradix((unsigned char *)x, (o[0] != -1) ? newo : o, n);
+  }
 }
 
 SEXP forder(SEXP DT, SEXP by, SEXP retGrp, SEXP sortStrArg, SEXP orderArg, SEXP naArg)
 // sortStr TRUE from setkey, FALSE from by=
 {
-    int i, j, k, grp, ngrp, tmp, *osub, thisgrpn, n, col;
-    Rboolean isSorted = TRUE;
-    SEXP x, class;
-    void *xd;
+  int i, j, k, grp, ngrp, tmp, *osub, thisgrpn, n, col;
+  Rboolean isSorted = TRUE;
+  SEXP x, class;
+  void *xd;
 #ifdef TIMING_ON
-    memset(tblock, 0, NBLOCK*sizeof(clock_t));
-    memset(nblock, 0, NBLOCK*sizeof(int));
-    clock_t tstart;  // local variable to mask the global tstart for ease when timing sub funs
+  memset(tblock, 0, NBLOCK*sizeof(clock_t));
+  memset(nblock, 0, NBLOCK*sizeof(int));
+  clock_t tstart;  // local variable to mask the global tstart for ease when timing sub funs
 #endif
-    TBEG()
-
-    if (isNewList(DT)) {
-        if (!length(DT)) error("DT is an empty list() of 0 columns");
-        if (!isInteger(by) || !length(by)) error("DT has %d columns but 'by' is either not integer or length 0", length(DT));  // seq_along(x) at R level
-        n = length(VECTOR_ELT(DT,0));
-        for (i=0; i<LENGTH(by); i++) {
-            if (INTEGER(by)[i] < 1 || INTEGER(by)[i] > length(DT))
-                error("'by' value %d out of range [1,%d]", INTEGER(by)[i], length(DT));
-            if ( n != length(VECTOR_ELT(DT, INTEGER(by)[i]-1)) )
-                error("Column %d is length %d which differs from length of column 1 (%d)\n", INTEGER(by)[i], length(VECTOR_ELT(DT, INTEGER(by)[i]-1)), n);
-        }
-        x = VECTOR_ELT(DT,INTEGER(by)[0]-1);
+  TBEG()
+
+  if (isNewList(DT)) {
+    if (!length(DT)) error("DT is an empty list() of 0 columns");
+    if (!isInteger(by) || !length(by)) error("DT has %d columns but 'by' is either not integer or length 0", length(DT));  // seq_along(x) at R level
+    n = length(VECTOR_ELT(DT,0));
+    for (i=0; i<LENGTH(by); i++) {
+      if (INTEGER(by)[i] < 1 || INTEGER(by)[i] > length(DT))
+        error("'by' value %d out of range [1,%d]", INTEGER(by)[i], length(DT));
+      if ( n != length(VECTOR_ELT(DT, INTEGER(by)[i]-1)) )
+        error("Column %d is length %d which differs from length of column 1 (%d)\n", INTEGER(by)[i], length(VECTOR_ELT(DT, INTEGER(by)[i]-1)), n);
+    }
+    x = VECTOR_ELT(DT,INTEGER(by)[0]-1);
+  } else {
+    if (!isNull(by)) error("Input is a single vector but 'by' is not NULL");
+    n = length(DT);
+    x = DT;
+  }
+  if (!isLogical(retGrp) || LENGTH(retGrp)!=1 || INTEGER(retGrp)[0]==NA_LOGICAL) error("retGrp must be TRUE or FALSE");
+  if (!isLogical(sortStrArg) || LENGTH(sortStrArg)!=1 || INTEGER(sortStrArg)[0]==NA_LOGICAL ) error("sortStr must be TRUE or FALSE");
+  if (!isLogical(naArg) || LENGTH(naArg) != 1) error("na.last must be logical TRUE, FALSE or NA of length 1");
+  sortStr = LOGICAL(sortStrArg)[0];
+  // static global set on top...
+  nalast = (LOGICAL(naArg)[0] == NA_LOGICAL) ? 0 : (LOGICAL(naArg)[0] == TRUE) ? 1 : -1; // 1=TRUE, -1=FALSE, 0=NA
+  gsmaxalloc = n;  // upper limit for stack size (all size 1 groups). We'll detect and avoid that limit, but if just one non-1 group (say 2), that can't be avoided.
+
+  // TODO: check for 'orderArg'
+
+  SEXP ans = PROTECT(allocVector(INTSXP, n)); // once for the result, needs to be length n.
+  int *o = INTEGER(ans);                      // TO DO: save allocation if NULL is returned (isSorted==TRUE)
+  o[0] = -1;                                  // so [i|c|d]sort know they can populate o directly with no working memory needed to reorder existing order
+                        // had to repace this from '0' to '-1' because 'nalast = 0' replace 'o[.]' with 0 values.
+  xd = DATAPTR(x);
+  stackgrps = length(by)>1 || LOGICAL(retGrp)[0];
+  savetl_init();   // from now on use Error not error.
+
+  order = INTEGER(orderArg)[0];
+  switch(TYPEOF(x)) {
+  case INTSXP : case LGLSXP :
+    tmp = isorted(xd, n); break;
+  case REALSXP :
+    class = getAttrib(x, R_ClassSymbol);
+    if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
+      twiddle = &i64twiddle;
+      is_nan  = &i64nan; // see explanation under `is_nan` as to why we need this
     } else {
-        if (!isNull(by)) error("Input is a single vector but 'by' is not NULL");
-        n = length(DT);
-        x = DT;
-    }
-    if (!isLogical(retGrp) || LENGTH(retGrp)!=1 || INTEGER(retGrp)[0]==NA_LOGICAL) error("retGrp must be TRUE or FALSE");
-    if (!isLogical(sortStrArg) || LENGTH(sortStrArg)!=1 || INTEGER(sortStrArg)[0]==NA_LOGICAL ) error("sortStr must be TRUE or FALSE");
-    if (!isLogical(naArg) || LENGTH(naArg) != 1) error("na.last must be logical TRUE, FALSE or NA of length 1");
-    sortStr = LOGICAL(sortStrArg)[0];
-    // static global set on top...
-    nalast = (LOGICAL(naArg)[0] == NA_LOGICAL) ? 0 : (LOGICAL(naArg)[0] == TRUE) ? 1 : -1; // 1=TRUE, -1=FALSE, 0=NA
-    gsmaxalloc = n;  // upper limit for stack size (all size 1 groups). We'll detect and avoid that limit, but if just one non-1 group (say 2), that can't be avoided.
-
-    // TODO: check for 'orderArg'
-
-    SEXP ans = PROTECT(allocVector(INTSXP, n)); // once for the result, needs to be length n.
-    int *o = INTEGER(ans);                      // TO DO: save allocation if NULL is returned (isSorted==TRUE)
-    o[0] = -1;                                  // so [i|c|d]sort know they can populate o directly with no working memory needed to reorder existing order
-                                                // had to repace this from '0' to '-1' because 'nalast = 0' replace 'o[.]' with 0 values.
-    xd = DATAPTR(x);
-    stackgrps = length(by)>1 || LOGICAL(retGrp)[0];
-    savetl_init();   // from now on use Error not error.
-
-    order = INTEGER(orderArg)[0];
+      twiddle = &dtwiddle;
+      is_nan  = &dnan;
+    }
+    tmp = dsorted(xd, n); break;
+  case STRSXP :
+    tmp = csorted(xd, n); break;
+  default :
+    Error("First column being ordered is type '%s', not yet supported", type2char(TYPEOF(x)));
+  }
+  if (tmp) {                                  // -1 or 1. NEW: or -2 in case of nalast == 0 and all NAs
+    if (tmp == 1) {                         // same as expected in 'order' (1 = increasing, -1 = decreasing)
+      isSorted = TRUE;
+      for (i=0; i<n; i++) o[i] = i+1;     // TO DO: we don't need this if returning NULL? Save it? Unlikely huge gain, though.
+    } else if (tmp == -1) {                 // -1 (or -n for result of strcmp), strictly opposite to expected 'order'
+      isSorted = FALSE;
+      for (i=0; i<n; i++) o[i] = n-i;
+    } else if (nalast == 0 && tmp == -2) {  // happens only when nalast=NA/0. Means all NAs, replace with 0's therefore!
+      isSorted = FALSE;
+      for (i=0; i<n; i++) o[i] = 0;
+    }
+  } else {
+    isSorted = FALSE;
     switch(TYPEOF(x)) {
     case INTSXP : case LGLSXP :
-        tmp = isorted(xd, n); break;
+      isort(xd, o, n); break;
     case REALSXP :
-        class = getAttrib(x, R_ClassSymbol);
-        if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
-            twiddle = &i64twiddle;
-            is_nan  = &i64nan; // see explanation under `is_nan` as to why we need this
-        } else {
-            twiddle = &dtwiddle;
-            is_nan  = &dnan;
-        }
-        tmp = dsorted(xd, n); break;
+      dsort(xd, o, n); break;
     case STRSXP :
-        tmp = csorted(xd, n); break;
+      if (sortStr) { csort_pre(xd, n); alloc_csort_otmp(n); csort(xd, o, n); }
+      else cgroup(xd, o, n);
+      break;
     default :
-        Error("First column being ordered is type '%s', not yet supported", type2char(TYPEOF(x)));
+      Error("Internal error: previous default should have caught unsupported type");
     }
-    if (tmp) {                                  // -1 or 1. NEW: or -2 in case of nalast == 0 and all NAs
-        if (tmp == 1) {                         // same as expected in 'order' (1 = increasing, -1 = decreasing)
-            isSorted = TRUE;
-            for (i=0; i<n; i++) o[i] = i+1;     // TO DO: we don't need this if returning NULL? Save it? Unlikely huge gain, though.
-        } else if (tmp == -1) {                 // -1 (or -n for result of strcmp), strictly opposite to expected 'order'
-            isSorted = FALSE;
-            for (i=0; i<n; i++) o[i] = n-i;
-        } else if (nalast == 0 && tmp == -2) {  // happens only when nalast=NA/0. Means all NAs, replace with 0's therefore!
-            isSorted = FALSE;
-            for (i=0; i<n; i++) o[i] = 0;
-        }
-    } else {
-        isSorted = FALSE;
-        switch(TYPEOF(x)) {
-        case INTSXP : case LGLSXP :
-            isort(xd, o, n); break;
-        case REALSXP :
-            dsort(xd, o, n); break;
-        case STRSXP :
-            if (sortStr) { csort_pre(xd, n); alloc_csort_otmp(n); csort(xd, o, n); }
-            else cgroup(xd, o, n);
-            break;
-        default :
-            Error("Internal error: previous default should have caught unsupported type");
-        }
-    }
-    TEND(0)
+  }
+  TEND(0)
 
-    int maxgrpn = gsmax[flip];   // biggest group in the first column
-    void *xsub = NULL;           // local to forder
-    int (*f)(); void (*g)();
+  int maxgrpn = gsmax[flip];   // biggest group in the first column
+  void *xsub = NULL;           // local to forder
+  int (*f)(); void (*g)();
 
-    if (length(by)>1 && gsngrp[flip]<n) {
-        xsub = (void *)malloc(maxgrpn * sizeof(double));    // double is the largest type, 8
-        if (xsub==NULL) Error("Couldn't allocate xsub in forder, requested %d * %d bytes.", maxgrpn, sizeof(double));
-        newo = (int *)malloc(maxgrpn * sizeof(int));        // global variable, used by isort, dsort, sort and cgroup
-        if (newo==NULL) Error("Couldn't allocate newo in forder, requested %d * %d bytes.", maxgrpn, sizeof(int));
-    }
-    TEND(1)  // should be negligible time to malloc even large blocks, but time it anyway to be sure
-
-    for (col=2; col<=length(by); col++) {
-        x = VECTOR_ELT(DT,INTEGER(by)[col-1]-1);
-        xd = DATAPTR(x);
-        ngrp = gsngrp[flip];
-        if (ngrp == n && nalast != 0) break;
-        flipflop();
-        stackgrps = col!=LENGTH(by) || LOGICAL(retGrp)[0];
-        order = INTEGER(orderArg)[col-1];
-        switch(TYPEOF(x)) {
-        case INTSXP : case LGLSXP :
-            f = &isorted; g = &isort; break;
-        case REALSXP :
-            class = getAttrib(x, R_ClassSymbol);
-            if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
-                twiddle = &i64twiddle;
-                is_nan  = &i64nan;
-            } else {
-                twiddle = &dtwiddle;
-                is_nan  = &dnan;
-            }
-            f = &dsorted; g = &dsort; break;
-        case STRSXP :
-            f = &csorted;
-            if (sortStr) { csort_pre(xd, n); alloc_csort_otmp(gsmax[1-flip]); g = &csort; }
-            else g = &cgroup; // no increasing/decreasing order required if sortStr = FALSE, just a dummy argument
-            break;
-        default:
-           Error("Column %d of 'by' (%d) is type '%s', not yet supported", col, INTEGER(by)[col-1], type2char(TYPEOF(x)));
+  if (length(by)>1 && gsngrp[flip]<n) {
+    xsub = (void *)malloc(maxgrpn * sizeof(double));    // double is the largest type, 8
+    if (xsub==NULL) Error("Couldn't allocate xsub in forder, requested %d * %d bytes.", maxgrpn, sizeof(double));
+    newo = (int *)malloc(maxgrpn * sizeof(int));        // global variable, used by isort, dsort, sort and cgroup
+    if (newo==NULL) Error("Couldn't allocate newo in forder, requested %d * %d bytes.", maxgrpn, sizeof(int));
+  }
+  TEND(1)  // should be negligible time to malloc even large blocks, but time it anyway to be sure
+
+  for (col=2; col<=length(by); col++) {
+    x = VECTOR_ELT(DT,INTEGER(by)[col-1]-1);
+    xd = DATAPTR(x);
+    ngrp = gsngrp[flip];
+    if (ngrp == n && nalast != 0) break;
+    flipflop();
+    stackgrps = col!=LENGTH(by) || LOGICAL(retGrp)[0];
+    order = INTEGER(orderArg)[col-1];
+    switch(TYPEOF(x)) {
+    case INTSXP : case LGLSXP :
+      f = &isorted; g = &isort; break;
+    case REALSXP :
+      class = getAttrib(x, R_ClassSymbol);
+      if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
+        twiddle = &i64twiddle;
+        is_nan  = &i64nan;
+      } else {
+        twiddle = &dtwiddle;
+        is_nan  = &dnan;
+      }
+      f = &dsorted; g = &dsort; break;
+    case STRSXP :
+      f = &csorted;
+      if (sortStr) { csort_pre(xd, n); alloc_csort_otmp(gsmax[1-flip]); g = &csort; }
+      else g = &cgroup; // no increasing/decreasing order required if sortStr = FALSE, just a dummy argument
+      break;
+    default:
+       Error("Column %d of 'by' (%d) is type '%s', not yet supported", col, INTEGER(by)[col-1], type2char(TYPEOF(x)));
+    }
+    size_t size = SIZEOF(x);
+    if (size!=4 && size!=8) Error("Column %d of 'by' is supported type '%s' but size (%d) isn't 4 or 8\n", col, type2char(TYPEOF(x)), size);
+    // sizes of int and double are checked to be 4 and 8 respectively in init.c
+    i = 0;
+    for (grp=0; grp<ngrp; grp++) {
+      thisgrpn = gs[1-flip][grp];
+      if (thisgrpn == 1) {
+        if (nalast==0) {                    // this edge case had to be taken care of here.. (see the bottom of this file for more explanation)
+          switch(TYPEOF(x)) {
+          case INTSXP :
+            if (INTEGER(x)[o[i]-1] == NA_INTEGER) { isSorted=FALSE; o[i] = 0; } break;
+          case LGLSXP :
+            if (LOGICAL(x)[o[i]-1] == NA_LOGICAL) { isSorted=FALSE; o[i] = 0; } break;
+          case REALSXP :
+            if (ISNAN(REAL(x)[o[i]-1])) { isSorted=FALSE; o[i] = 0; } break;
+          case STRSXP :
+            if (STRING_ELT(x, o[i]-1) == NA_STRING) { isSorted=FALSE; o[i] = 0; } break;
+          default :
+            Error("Internal error: previous default should have caught unsupported type");
+          }
         }
-        size_t size = SIZEOF(x);
-        if (size!=4 && size!=8) Error("Column %d of 'by' is supported type '%s' but size (%d) isn't 4 or 8\n", col, type2char(TYPEOF(x)), size);
-        // sizes of int and double are checked to be 4 and 8 respectively in init.c
-        i = 0;
-        for (grp=0; grp<ngrp; grp++) {
-            thisgrpn = gs[1-flip][grp];
-            if (thisgrpn == 1) {
-                if (nalast==0) {                    // this edge case had to be taken care of here.. (see the bottom of this file for more explanation)
-                    switch(TYPEOF(x)) {
-                    case INTSXP :
-                        if (INTEGER(x)[o[i]-1] == NA_INTEGER) { isSorted=FALSE; o[i] = 0; } break;
-                    case LGLSXP :
-                        if (LOGICAL(x)[o[i]-1] == NA_LOGICAL) { isSorted=FALSE; o[i] = 0; } break;
-                    case REALSXP :
-                        if (ISNAN(REAL(x)[o[i]-1])) { isSorted=FALSE; o[i] = 0; } break;
-                    case STRSXP :
-                        if (STRING_ELT(x, o[i]-1) == NA_STRING) { isSorted=FALSE; o[i] = 0; } break;
-                    default :
-                        Error("Internal error: previous default should have caught unsupported type");
-                    }
-                }
-                i++; push(1); continue;
-            }
-            TBEG()
-            osub = o+i;
-            // ** TO DO **: if isSorted,  we can just point xsub into x directly. If (*f)() returns 0, though, will have to copy x at that point
-            //        When doing this,  xsub could be allocated at that point for the first time.
-            if (size==4)
-                for (j=0; j<thisgrpn; j++) ((int *)xsub)[j] = ((int *)xd)[o[i++]-1];
-            else
-                for (j=0; j<thisgrpn; j++) ((double *)xsub)[j] = ((double *)xd)[o[i++]-1];
-            TEND(2)
-
-            // continue;  // BASELINE short circuit timing point. Up to here is the cost of creating xsub.
-            tmp = (*f)(xsub, thisgrpn);     // [i|d|c]sorted(); very low cost, sequential
-            TEND(3)
-
-            if (tmp) {
-                // *sorted will have already push()'d the groups
-                if (tmp==-1) {
-                    isSorted = FALSE;
-                    for (k=0;k<thisgrpn/2;k++) {        // reverse the order in-place using no function call or working memory
-                        tmp = osub[k];                  // isorted only returns -1 for _strictly_ decreasing order, otherwise ties wouldn't be stable
-                        osub[k] = osub[thisgrpn-1-k];
-                        osub[thisgrpn-1-k] = tmp;
-                    }
-                    TEND(4)
-                } else if (nalast == 0 && tmp==-2) {    // all NAs, replace osub[.] with 0s.
-                    isSorted = FALSE;
-                    for (k=0; k<thisgrpn; k++) osub[k] = 0;
-                }
-                continue;
-            }
-            isSorted = FALSE;
-            newo[0] = -1;                               // nalast=NA will result in newo[0] = 0. So had to change to -1.
-            (*g)(xsub, osub, thisgrpn);                 // may update osub directly, or if not will put the result in global newo
-
-            TEND(5)
-
-            if (newo[0] != -1) {
-                if (nalast != 0) for (j=0; j<thisgrpn; j++) ((int *)xsub)[j] = osub[ newo[j]-1 ];           // reuse xsub to reorder osub
-                else for (j=0; j<thisgrpn; j++) ((int *)xsub)[j] = (newo[j] == 0) ? 0 : osub[ newo[j]-1 ];  // final nalast case to handle!
-                memcpy(osub, xsub, thisgrpn*sizeof(int));
-            }
-            TEND(6)
+        i++; push(1); continue;
+      }
+      TBEG()
+      osub = o+i;
+      // ** TO DO **: if isSorted,  we can just point xsub into x directly. If (*f)() returns 0, though, will have to copy x at that point
+      //        When doing this,  xsub could be allocated at that point for the first time.
+      if (size==4)
+        for (j=0; j<thisgrpn; j++) ((int *)xsub)[j] = ((int *)xd)[o[i++]-1];
+      else
+        for (j=0; j<thisgrpn; j++) ((double *)xsub)[j] = ((double *)xd)[o[i++]-1];
+      TEND(2)
+
+      // continue;  // BASELINE short circuit timing point. Up to here is the cost of creating xsub.
+      tmp = (*f)(xsub, thisgrpn);     // [i|d|c]sorted(); very low cost, sequential
+      TEND(3)
+
+      if (tmp) {
+        // *sorted will have already push()'d the groups
+        if (tmp==-1) {
+          isSorted = FALSE;
+          for (k=0;k<thisgrpn/2;k++) {        // reverse the order in-place using no function call or working memory
+            tmp = osub[k];                  // isorted only returns -1 for _strictly_ decreasing order, otherwise ties wouldn't be stable
+            osub[k] = osub[thisgrpn-1-k];
+            osub[thisgrpn-1-k] = tmp;
+          }
+          TEND(4)
+        } else if (nalast == 0 && tmp==-2) {    // all NAs, replace osub[.] with 0s.
+          isSorted = FALSE;
+          for (k=0; k<thisgrpn; k++) osub[k] = 0;
         }
-    }
+        continue;
+      }
+      isSorted = FALSE;
+      newo[0] = -1;                               // nalast=NA will result in newo[0] = 0. So had to change to -1.
+      (*g)(xsub, osub, thisgrpn);                 // may update osub directly, or if not will put the result in global newo
+
+      TEND(5)
+
+      if (newo[0] != -1) {
+        if (nalast != 0) for (j=0; j<thisgrpn; j++) ((int *)xsub)[j] = osub[ newo[j]-1 ];           // reuse xsub to reorder osub
+        else for (j=0; j<thisgrpn; j++) ((int *)xsub)[j] = (newo[j] == 0) ? 0 : osub[ newo[j]-1 ];  // final nalast case to handle!
+        memcpy(osub, xsub, thisgrpn*sizeof(int));
+      }
+      TEND(6)
+    }
+  }
 #ifdef TIMING_ON
-    for (i=0; i<NBLOCK; i++) {
-        Rprintf("Timing block %d = %8.3f   %8d\n", i, 1.0*tblock[i]/CLOCKS_PER_SEC, nblock[i]);
-        if (i==12) Rprintf("\n");
-    }
-    Rprintf("Found %d groups and maxgrpn=%d\n", gsngrp[flip], gsmax[flip]);
+  for (i=0; i<NBLOCK; i++) {
+    Rprintf("Timing block %d = %8.3f   %8d\n", i, 1.0*tblock[i]/CLOCKS_PER_SEC, nblock[i]);
+    if (i==12) Rprintf("\n");
+  }
+  Rprintf("Found %d groups and maxgrpn=%d\n", gsngrp[flip], gsmax[flip]);
 #endif
 
-    if (!sortStr && ustr_n!=0) Error("Internal error: at the end of forder sortStr==FALSE but ustr_n!=0 [%d]", ustr_n);
-    for(int i=0; i<ustr_n; i++)
-        SET_TRUELENGTH(ustr[i],0);
-    maxlen = 1;  // reset global. Minimum needed to count "" and NA
-    ustr_n = 0;
-    savetl_end();
-    free(ustr);                ustr=NULL;          ustr_alloc=0;
-
-    if (isSorted) {
-        UNPROTECT(1);  // The existing o vector, which we may save in future, if in future we only create when isSorted becomes FALSE
-        ans = PROTECT(allocVector(INTSXP, 0));  // Can't attach attributes to NULL
-    }
-    if (LOGICAL(retGrp)[0]) {
-        ngrp = gsngrp[flip];
-        setAttrib(ans, sym_starts, x = allocVector(INTSXP, ngrp));
-        //if (isSorted || LOGICAL(sort)[0])
-            for (INTEGER(x)[0]=1, i=1; i<ngrp; i++) INTEGER(x)[i] = INTEGER(x)[i-1] + gs[flip][i-1];
-        //else {
-            // it's not sorted already and we want to keep original group order
-        //    cumsum = 0;
-        //    for (i=0; i<ngrp; i++) { INTEGER(x)[i] = o[i+cumsum]; cumsum+=gs[flip][i]; }
-        //    isort(INTEGER(x), ngrp);
-        //}
-        setAttrib(ans, sym_maxgrpn, ScalarInteger(gsmax[flip]));
-    }
-
-    gsfree();
-    free(radix_xsub);          radix_xsub=NULL;    radix_xsuballoc=0;
-    free(xsub); free(newo);    xsub=newo=NULL;
-    free(xtmp);                xtmp=NULL;          xtmp_alloc=0;
-    free(otmp);                otmp=NULL;          otmp_alloc=0;
-    free(csort_otmp);          csort_otmp=NULL;    csort_otmp_alloc=0;
-
-    free(cradix_counts);       cradix_counts=NULL; cradix_counts_alloc=0;
-    free(cradix_xtmp);         cradix_xtmp=NULL;   cradix_xtmp_alloc=0;   // TO DO: use xtmp already got
-
-    UNPROTECT(1);
-    return( ans );
+  if (!sortStr && ustr_n!=0) Error("Internal error: at the end of forder sortStr==FALSE but ustr_n!=0 [%d]", ustr_n);
+  for(int i=0; i<ustr_n; i++)
+    SET_TRUELENGTH(ustr[i],0);
+  maxlen = 1;  // reset global. Minimum needed to count "" and NA
+  ustr_n = 0;
+  savetl_end();
+  free(ustr);                ustr=NULL;          ustr_alloc=0;
+
+  if (isSorted) {
+    UNPROTECT(1);  // The existing o vector, which we may save in future, if in future we only create when isSorted becomes FALSE
+    ans = PROTECT(allocVector(INTSXP, 0));  // Can't attach attributes to NULL
+  }
+  if (LOGICAL(retGrp)[0]) {
+    ngrp = gsngrp[flip];
+    setAttrib(ans, sym_starts, x = allocVector(INTSXP, ngrp));
+    //if (isSorted || LOGICAL(sort)[0])
+      for (INTEGER(x)[0]=1, i=1; i<ngrp; i++) INTEGER(x)[i] = INTEGER(x)[i-1] + gs[flip][i-1];
+    //else {
+      // it's not sorted already and we want to keep original group order
+    //    cumsum = 0;
+    //    for (i=0; i<ngrp; i++) { INTEGER(x)[i] = o[i+cumsum]; cumsum+=gs[flip][i]; }
+    //    isort(INTEGER(x), ngrp);
+    //}
+    setAttrib(ans, sym_maxgrpn, ScalarInteger(gsmax[flip]));
+  }
+
+  gsfree();
+  free(radix_xsub);          radix_xsub=NULL;    radix_xsuballoc=0;
+  free(xsub); free(newo);    xsub=newo=NULL;
+  free(xtmp);                xtmp=NULL;          xtmp_alloc=0;
+  free(otmp);                otmp=NULL;          otmp_alloc=0;
+  free(csort_otmp);          csort_otmp=NULL;    csort_otmp_alloc=0;
+
+  free(cradix_counts);       cradix_counts=NULL; cradix_counts_alloc=0;
+  free(cradix_xtmp);         cradix_xtmp=NULL;   cradix_xtmp_alloc=0;   // TO DO: use xtmp already got
+
+  UNPROTECT(1);
+  return( ans );
 }
 
 // TODO: implement 'order' argument to 'fsorted'
 // Not touching 'fsorted' for now for "decreasing order". Passing '1' as the value of 'order' argument (checks only ascending order as before).
 SEXP fsorted(SEXP x)
 {
-    // Just checks if ordered and returns FALSE early if not (and don't return ordering if so, unlike forder).
-    int tmp,n;
-    void *xd;
-    n = length(x);
-    if (n <= 1) return(ScalarLogical(TRUE));
-    if (!isVectorAtomic(x)) Error("is.sorted (R level) and fsorted (C level) only to be used on vectors. If needed on a list/data.table, you'll need the order anyway if not sorted, so use if (length(o<-forder(...))) for efficiency in one step, or equivalent at C level");
-    xd = DATAPTR(x);
-    stackgrps = FALSE;
-    order = 1;
-    switch(TYPEOF(x)) {
-    case INTSXP : case LGLSXP :
-        tmp = isorted(xd, n); break;
-    case REALSXP :
-        tmp = dsorted(xd, n); break;
-    case STRSXP :
-        tmp = csorted(xd, n); break;
-    default :
-        Error("type '%s' is not yet supported", type2char(TYPEOF(x)));
-    }
-    return(ScalarLogical( tmp==1 ? TRUE : FALSE ));
+  // Just checks if ordered and returns FALSE early if not (and don't return ordering if so, unlike forder).
+  int tmp,n;
+  void *xd;
+  n = length(x);
+  if (n <= 1) return(ScalarLogical(TRUE));
+  if (!isVectorAtomic(x)) Error("is.sorted (R level) and fsorted (C level) only to be used on vectors. If needed on a list/data.table, you'll need the order anyway if not sorted, so use if (length(o<-forder(...))) for efficiency in one step, or equivalent at C level");
+  xd = DATAPTR(x);
+  stackgrps = FALSE;
+  order = 1;
+  switch(TYPEOF(x)) {
+  case INTSXP : case LGLSXP :
+    tmp = isorted(xd, n); break;
+  case REALSXP :
+    tmp = dsorted(xd, n); break;
+  case STRSXP :
+    tmp = csorted(xd, n); break;
+  default :
+    Error("type '%s' is not yet supported", type2char(TYPEOF(x)));
+  }
+  return(ScalarLogical( tmp==1 ? TRUE : FALSE ));
 }
 
 SEXP isOrderedSubset(SEXP x, SEXP nrow)
 // specialized for use in [.data.table only
 // Ignores 0s but heeds NAs and any out-of-range (which result in NA)
 {
-    int i=0, last, this;
-    if (!length(x)) return(ScalarLogical(TRUE));
-    if (!isInteger(x)) error("x has non-0 length but isn't an integer vector");
-    if (!isInteger(nrow) || LENGTH(nrow)!=1 || INTEGER(nrow)[0]<0) error("nrow must be integer vector length 1 and >=0");
-    if (LENGTH(x)<=1) return(ScalarLogical(TRUE));
-    while (i<LENGTH(x) && INTEGER(x)[i]==0) i++;
-    if (i==LENGTH(x)) return(ScalarLogical(TRUE));
-    last = INTEGER(x)[i];  // the first non-0
-    i++;
-    for (; i<LENGTH(x); i++) {
-        this = INTEGER(x)[i];
-        if (this == 0) continue;
-        if (this < last || this < 0 || this > INTEGER(nrow)[0])
-            return(ScalarLogical(FALSE));
-        last = this;
-    }
-    return(ScalarLogical(TRUE));
+  int i=0, last, this;
+  if (!length(x)) return(ScalarLogical(TRUE));
+  if (!isInteger(x)) error("x has non-0 length but isn't an integer vector");
+  if (!isInteger(nrow) || LENGTH(nrow)!=1 || INTEGER(nrow)[0]<0) error("nrow must be integer vector length 1 and >=0");
+  if (LENGTH(x)<=1) return(ScalarLogical(TRUE));
+  while (i<LENGTH(x) && INTEGER(x)[i]==0) i++;
+  if (i==LENGTH(x)) return(ScalarLogical(TRUE));
+  last = INTEGER(x)[i];  // the first non-0
+  i++;
+  for (; i<LENGTH(x); i++) {
+    this = INTEGER(x)[i];
+    if (this == 0) continue;
+    if (this < last || this < 0 || this > INTEGER(nrow)[0])
+      return(ScalarLogical(FALSE));
+    last = this;
+  }
+  return(ScalarLogical(TRUE));
 }
 
 
 /*
-    // LSD approach ...
-
-    Rboolean firstTime = TRUE;
-    for (radix=0; radix<4; radix++) {
-        if (skip[radix]) continue;
-        tmp = ord; ord = ans; ans = tmp;     // flip-flop
-        shift = radix * 8;
-        thiscounts = counts[radix];
-        for (i=1;i<256;i++) thiscounts[i] += thiscounts[i-1];
-        tt = clock();
-        if (firstTime) {
-            for(i = n-1; i >= 0; i--) {
-                thisx = x[i] >> shift & 0xFF;
-                ans[--thiscounts[thisx]] = i+1;
-            }
-        } else {
-            for(i = n-1; i >= 0; i--) {
-                thisx = x[ord[i]-1] >> shift & 0xFF;
-                ans[--thiscounts[thisx]] = ord[i];
-            }
-        }
-        memset(thiscounts, 0, 256*sizeof(unsigned int));
-        t[6+radix] += clock()-tt;
-        firstTime = FALSE;
-    }
-    if (firstTime) Error("Internal error: x is one repeated number so isorted should have skipped iradix, but iradix has been called and skipped all 4 radix");
+  // LSD approach ...
+
+  Rboolean firstTime = TRUE;
+  for (radix=0; radix<4; radix++) {
+    if (skip[radix]) continue;
+    tmp = ord; ord = ans; ans = tmp;     // flip-flop
+    shift = radix * 8;
+    thiscounts = counts[radix];
+    for (i=1;i<256;i++) thiscounts[i] += thiscounts[i-1];
+    tt = clock();
+    if (firstTime) {
+      for(i = n-1; i >= 0; i--) {
+        thisx = x[i] >> shift & 0xFF;
+        ans[--thiscounts[thisx]] = i+1;
+      }
+    } else {
+      for(i = n-1; i >= 0; i--) {
+        thisx = x[ord[i]-1] >> shift & 0xFF;
+        ans[--thiscounts[thisx]] = ord[i];
+      }
+    }
+    memset(thiscounts, 0, 256*sizeof(unsigned int));
+    t[6+radix] += clock()-tt;
+    firstTime = FALSE;
+  }
+  if (firstTime) Error("Internal error: x is one repeated number so isorted should have skipped iradix, but iradix has been called and skipped all 4 radix");
 }
 */
 
 SEXP isReallyReal(SEXP x) {
-    int n, i=0;
-    SEXP ans;
-    if (!isReal(x))
-        error("x must be of type double.");
-    n = length(x);
-    ans = PROTECT(allocVector(LGLSXP, 1));
-    while (i<n &&
-          ( ISNA(REAL(x)[i]) ||
-          ( R_FINITE(REAL(x)[i]) && REAL(x)[i] == (int)(REAL(x)[i])))) {
-        i++;
-    }
-    LOGICAL(ans)[0] = (i<n);
-    UNPROTECT(1);
-    return(ans);
+  int n, i=0;
+  SEXP ans;
+  if (!isReal(x))
+    error("x must be of type double.");
+  n = length(x);
+  ans = PROTECT(allocVector(LGLSXP, 1));
+  while (i<n &&
+      ( ISNA(REAL(x)[i]) ||
+      ( R_FINITE(REAL(x)[i]) && REAL(x)[i] == (int)(REAL(x)[i])))) {
+    i++;
+  }
+  LOGICAL(ans)[0] = (i<n);
+  UNPROTECT(1);
+  return(ans);
 }
 
 void pbin(unsigned long long n)
 // trace utility for dev to be used in gdb, since couldn't get stdlib::atoi() to link
 {
-    int sofar = 0;
-    for(int shift=sizeof(long long)*8-1; shift>=0; shift--)
-    {
-       if (n >> shift & 1)
-         Rprintf("1");
-       else
-         Rprintf("0");
-       if (++sofar == 1 || sofar == 12) Rprintf(" ");
-    }
-    Rprintf("\n");
+  int sofar = 0;
+  for(int shift=sizeof(long long)*8-1; shift>=0; shift--)
+  {
+     if (n >> shift & 1)
+     Rprintf("1");
+     else
+     Rprintf("0");
+     if (++sofar == 1 || sofar == 12) Rprintf(" ");
+  }
+  Rprintf("\n");
 }
 
 SEXP binary(SEXP x)
 // base::intToBits is close, but why does it print the result as "00 00 00 00" (raw) rather than ("0000") bits, seems odd.
 {
-    char buffer[69];
-    int j;
-    if (!isReal(x)) error("x must be type 'double'");
-    SEXP ans = PROTECT(allocVector(STRSXP, LENGTH(x)));
-    for (int i=0; i<LENGTH(x); i++) {
-        u.d = REAL(x)[i];
-        j = 0;
-        for(int bit=64; bit>=1; bit--)
-        {
-           buffer[j++] = '0' + (u.ull >> (bit-1) & 1);
-           if (bit==64 || bit==53 || bit==17 || bit==9) buffer[j++]=' ';
-           //       ^sign      ^exponent  ^last 2 byte rounding
-        }
-        SET_STRING_ELT(ans, i, mkCharLen(buffer,68));  // 64 bits + 4 spaces
-    }
-    UNPROTECT(1);
-    return ans;
+  char buffer[69];
+  int j;
+  if (!isReal(x)) error("x must be type 'double'");
+  SEXP ans = PROTECT(allocVector(STRSXP, LENGTH(x)));
+  for (int i=0; i<LENGTH(x); i++) {
+    u.d = REAL(x)[i];
+    j = 0;
+    for(int bit=64; bit>=1; bit--)
+    {
+       buffer[j++] = '0' + (u.ull >> (bit-1) & 1);
+       if (bit==64 || bit==53 || bit==17 || bit==9) buffer[j++]=' ';
+       //       ^sign      ^exponent  ^last 2 byte rounding
+    }
+    SET_STRING_ELT(ans, i, mkCharLen(buffer,68));  // 64 bits + 4 spaces
+  }
+  UNPROTECT(1);
+  return ans;
 }
 
 
 /*
 Note on challenges implementing `nalast=NA` so that things are under the same perspective when looked at later. To go to the point, there are four challenges in implementing `nalast=NA` with the current form of forder.c (which is excellent!):
 
-    1) Handling the i|d|csorted routines for nalast=NA:
-       The current implementation of nalast=NA in *sorted routine is that if all values are NAs, then it returns -2, a different value, so that all values of o/osub can be replacedw with 0. If any values are NA, then we assume that this vector is unsorted and return 0 to be taken care of by the corresponding sort routine. Now, this may very well be questioned/improved. For ex: we could detect if the vector is sorted and also if the first value is NA, and if so, get the group size of these NAs and replace that many indices with 0. This is not yet done, but could very well be implemented later. If there are no NAs in the vector, then things are the same.
+  1) Handling the i|d|csorted routines for nalast=NA:
+     The current implementation of nalast=NA in *sorted routine is that if all values are NAs, then it returns -2, a different value, so that all values of o/osub can be replacedw with 0. If any values are NA, then we assume that this vector is unsorted and return 0 to be taken care of by the corresponding sort routine. Now, this may very well be questioned/improved. For ex: we could detect if the vector is sorted and also if the first value is NA, and if so, get the group size of these NAs and replace that many indices with 0. This is not yet done, but could very well be implemented later. If there are no NAs in the vector, then things are the same.
 
-    2) Handling cases where n==1 and n==2:
-       The current sorting routines don't take care of n<=2, and rightly so, as they can be dealt with directly. But it's not the case for nalast=NA because, when n==2 and all are NAs, point 1 will handle it. But when n==1 and it is NA and the column is not the first column to order upon, with the current implementation, "thisgrpn" is checked for "== 1" and if so, just pushes that group size and continues.. But we've to replace it with 0 in our case. So, nalast==0 is checked for inside that if statement and if so, if the value is NA is checked for all types and if so, replaced with 0. For n==2 and "any" is NA, we've to introduce a special case for this in each sort routine to take care of and not result in an error (which tells this should be already taken care of in *sorted).
+  2) Handling cases where n==1 and n==2:
+     The current sorting routines don't take care of n<=2, and rightly so, as they can be dealt with directly. But it's not the case for nalast=NA because, when n==2 and all are NAs, point 1 will handle it. But when n==1 and it is NA and the column is not the first column to order upon, with the current implementation, "thisgrpn" is checked for "== 1" and if so, just pushes that group size and continues.. But we've to replace it with 0 in our case. So, nalast==0 is checked for inside that if statement and if so, if the value is NA is checked for all types and if so, replaced with 0. For n==2 and "any" is NA, we've to introduce a special case for this in each sort routine to take care of and not result in an error (which tells this should be already taken care of in *sorted).
 
-    3) o[0] and newo[0] = -1:
-       Since nalast already populates NAs with 0 values in o, we can't use the old value of 0 to check if it's already populated or not. Therefore this has been changed to -1.
+  3) o[0] and newo[0] = -1:
+     Since nalast already populates NAs with 0 values in o, we can't use the old value of 0 to check if it's already populated or not. Therefore this has been changed to -1.
 
-    4) default cases are untouched as much as possible:
-       In order to not compromise in speed for default case (`setkey`), iinsert and dinsert are not touched at all, which means, we've to make sure that they are not run when n<200 and nalast==0, as they don't replace o[x=NA] with 0. And, 'icount', 'iradix', 'dradix' are modified to take care of nalast=NA, to my knowledge.
+  4) default cases are untouched as much as possible:
+     In order to not compromise in speed for default case (`setkey`), iinsert and dinsert are not touched at all, which means, we've to make sure that they are not run when n<200 and nalast==0, as they don't replace o[x=NA] with 0. And, 'icount', 'iradix', 'dradix' are modified to take care of nalast=NA, to my knowledge.
 
 Ends here.
 ------------------------------------------
diff --git a/src/frank.c b/src/frank.c
index 5ba651030e..c4f3488057 100644
--- a/src/frank.c
+++ b/src/frank.c
@@ -6,189 +6,189 @@
 extern SEXP char_integer64;
 
 SEXP dt_na(SEXP x, SEXP cols) {
-    int i, j, n=0, this;
-    double *dv;
-    SEXP v, ans, class;
+  int i, j, n=0, this;
+  double *dv;
+  SEXP v, ans, class;
 
-    if (!isNewList(x)) error("Internal error. Argument 'x' to Cdt_na is type '%s' not 'list'", type2char(TYPEOF(x)));
-    if (!isInteger(cols)) error("Internal error. Argument 'cols' to Cdt_na is type '%s' not 'integer'", type2char(TYPEOF(cols)));
-    for (i=0; i<LENGTH(cols); i++) {
-        this = INTEGER(cols)[i];
-        if (this<1 || this>LENGTH(x))
-            error("Item %d of 'cols' is %d which is outside 1-based range [1,ncol(x)=%d]", i+1, this, LENGTH(x));
-        if (!n) n = length(VECTOR_ELT(x, this-1));
-    }
-    ans = PROTECT(allocVector(LGLSXP, n));
-    for (i=0; i<n; i++) LOGICAL(ans)[i]=0;
-    for (i=0; i<LENGTH(cols); i++) {
-        v = VECTOR_ELT(x, INTEGER(cols)[i]-1);
-        if (!length(v) || isNewList(v) || isList(v)) continue; // like stats:::na.omit.data.frame, skip list/pairlist columns
-        if (n != length(v))
-            error("Column %d of input list x is length %d, inconsistent with first column of that item which is length %d.", i+1,length(v),n);
-        switch (TYPEOF(v)) {
-        case LGLSXP:
-            for (j=0; j<n; j++) LOGICAL(ans)[j] |= (LOGICAL(v)[j] == NA_LOGICAL);
-            break;
-        case INTSXP:
-            for (j=0; j<n; j++) LOGICAL(ans)[j] |= (INTEGER(v)[j] == NA_INTEGER);
-            break;
-        case STRSXP:
-            for (j=0; j<n; j++) LOGICAL(ans)[j] |= (STRING_ELT(v, j) == NA_STRING);
-            break;
-        case REALSXP:
-            class = getAttrib(v, R_ClassSymbol);
-            if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
-                dv = (double *)REAL(v);
-                for (j=0; j<n; j++) {
-                    LOGICAL(ans)[j] |= (DtoLL(dv[j]) == NA_INT64_LL);   // TODO: can be == NA_INT64_D directly
-                }
-            } else {
-                for (j=0; j<n; j++) LOGICAL(ans)[j] |= ISNAN(REAL(v)[j]);
-            }
-            break;
-        case RAWSXP:
-            // no such thing as a raw NA
-            // vector already initialised to all 0's
-            break;
-        case CPLXSXP:
-            // taken from https://github.com/wch/r-source/blob/d75f39d532819ccc8251f93b8ab10d5b83aac89a/src/main/coerce.c
-            for (j=0; j<n; j++) LOGICAL(ans)[j] |= (ISNAN(COMPLEX(v)[j].r) || ISNAN(COMPLEX(v)[j].i));
-            break;
-        default:
-            error("Unknown column type '%s'", type2char(TYPEOF(v)));
+  if (!isNewList(x)) error("Internal error. Argument 'x' to Cdt_na is type '%s' not 'list'", type2char(TYPEOF(x)));
+  if (!isInteger(cols)) error("Internal error. Argument 'cols' to Cdt_na is type '%s' not 'integer'", type2char(TYPEOF(cols)));
+  for (i=0; i<LENGTH(cols); i++) {
+    this = INTEGER(cols)[i];
+    if (this<1 || this>LENGTH(x))
+      error("Item %d of 'cols' is %d which is outside 1-based range [1,ncol(x)=%d]", i+1, this, LENGTH(x));
+    if (!n) n = length(VECTOR_ELT(x, this-1));
+  }
+  ans = PROTECT(allocVector(LGLSXP, n));
+  for (i=0; i<n; i++) LOGICAL(ans)[i]=0;
+  for (i=0; i<LENGTH(cols); i++) {
+    v = VECTOR_ELT(x, INTEGER(cols)[i]-1);
+    if (!length(v) || isNewList(v) || isList(v)) continue; // like stats:::na.omit.data.frame, skip list/pairlist columns
+    if (n != length(v))
+      error("Column %d of input list x is length %d, inconsistent with first column of that item which is length %d.", i+1,length(v),n);
+    switch (TYPEOF(v)) {
+    case LGLSXP:
+      for (j=0; j<n; j++) LOGICAL(ans)[j] |= (LOGICAL(v)[j] == NA_LOGICAL);
+      break;
+    case INTSXP:
+      for (j=0; j<n; j++) LOGICAL(ans)[j] |= (INTEGER(v)[j] == NA_INTEGER);
+      break;
+    case STRSXP:
+      for (j=0; j<n; j++) LOGICAL(ans)[j] |= (STRING_ELT(v, j) == NA_STRING);
+      break;
+    case REALSXP:
+      class = getAttrib(v, R_ClassSymbol);
+      if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
+        dv = (double *)REAL(v);
+        for (j=0; j<n; j++) {
+          LOGICAL(ans)[j] |= (DtoLL(dv[j]) == NA_INT64_LL);   // TODO: can be == NA_INT64_D directly
         }
+      } else {
+        for (j=0; j<n; j++) LOGICAL(ans)[j] |= ISNAN(REAL(v)[j]);
+      }
+      break;
+    case RAWSXP:
+      // no such thing as a raw NA
+      // vector already initialised to all 0's
+      break;
+    case CPLXSXP:
+      // taken from https://github.com/wch/r-source/blob/d75f39d532819ccc8251f93b8ab10d5b83aac89a/src/main/coerce.c
+      for (j=0; j<n; j++) LOGICAL(ans)[j] |= (ISNAN(COMPLEX(v)[j].r) || ISNAN(COMPLEX(v)[j].i));
+      break;
+    default:
+      error("Unknown column type '%s'", type2char(TYPEOF(v)));
     }
-    UNPROTECT(1);
-    return(ans);
+  }
+  UNPROTECT(1);
+  return(ans);
 }
 
 SEXP frank(SEXP xorderArg, SEXP xstartArg, SEXP xlenArg, SEXP ties_method) {
-    int i=0, j=0, k=0, n;
-    int *xstart = INTEGER(xstartArg), *xlen = INTEGER(xlenArg), *xorder = INTEGER(xorderArg);
-    enum {MEAN, MAX, MIN, DENSE, SEQUENCE} ties = MEAN; // RUNLENGTH
-    SEXP ans;
+  int i=0, j=0, k=0, n;
+  int *xstart = INTEGER(xstartArg), *xlen = INTEGER(xlenArg), *xorder = INTEGER(xorderArg);
+  enum {MEAN, MAX, MIN, DENSE, SEQUENCE} ties = MEAN; // RUNLENGTH
+  SEXP ans;
 
-    if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "average"))  ties = MEAN;
-    else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "max")) ties = MAX;
-    else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "min")) ties = MIN;
-    else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "dense")) ties = DENSE;
-    else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "sequence")) ties = SEQUENCE;
-    // else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "runlength")) ties = RUNLENGTH;
-    else error("Internal error: invalid ties.method for frankv(), should have been caught before. Please report to datatable-help");
-    n = length(xorderArg);
-    ans = (ties == MEAN) ? PROTECT(allocVector(REALSXP, n)) : PROTECT(allocVector(INTSXP, n));
-    if (n > 0) {
-        switch (ties) {
-            case MEAN :
-            for (i = 0; i < length(xstartArg); i++) {
-                for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
-                    REAL(ans)[xorder[j]-1] = (2*xstart[i]+xlen[i]-1)/2.0;
-            }
-            break;
-            case MAX :
-            for (i = 0; i < length(xstartArg); i++) {
-                for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
-                    INTEGER(ans)[xorder[j]-1] = xstart[i]+xlen[i]-1;
-            }
-            break;
-            case MIN :
-            for (i = 0; i < length(xstartArg); i++) {
-                for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
-                    INTEGER(ans)[xorder[j]-1] = xstart[i];
-            }
-            break;
-            case DENSE :
-            k=1;
-            for (i = 0; i < length(xstartArg); i++) {
-                for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
-                    INTEGER(ans)[xorder[j]-1] = k;
-                k++;
-            }
-            break;
-            case SEQUENCE :
-            for (i = 0; i < length(xstartArg); i++) {
-                k=1;
-                for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
-                    INTEGER(ans)[xorder[j]-1] = k++;
-            }
-            break;
-            // case RUNLENGTH :
-            // for (i = 0; i < length(xstartArg); i++) {
-            //     k=1;
-            //     for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
-            //         INTEGER(ans)[xorder[j]-1] = k++;
-            // }
-            break;
-        }
+  if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "average"))  ties = MEAN;
+  else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "max")) ties = MAX;
+  else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "min")) ties = MIN;
+  else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "dense")) ties = DENSE;
+  else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "sequence")) ties = SEQUENCE;
+  // else if (!strcmp(CHAR(STRING_ELT(ties_method, 0)), "runlength")) ties = RUNLENGTH;
+  else error("Internal error: invalid ties.method for frankv(), should have been caught before. Please report to datatable-help");
+  n = length(xorderArg);
+  ans = (ties == MEAN) ? PROTECT(allocVector(REALSXP, n)) : PROTECT(allocVector(INTSXP, n));
+  if (n > 0) {
+    switch (ties) {
+    case MEAN :
+      for (i = 0; i < length(xstartArg); i++) {
+        for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
+          REAL(ans)[xorder[j]-1] = (2*xstart[i]+xlen[i]-1)/2.0;
+      }
+      break;
+    case MAX :
+      for (i = 0; i < length(xstartArg); i++) {
+        for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
+          INTEGER(ans)[xorder[j]-1] = xstart[i]+xlen[i]-1;
+      }
+      break;
+    case MIN :
+      for (i = 0; i < length(xstartArg); i++) {
+        for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
+          INTEGER(ans)[xorder[j]-1] = xstart[i];
+      }
+      break;
+    case DENSE :
+      k=1;
+      for (i = 0; i < length(xstartArg); i++) {
+        for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
+          INTEGER(ans)[xorder[j]-1] = k;
+        k++;
+      }
+      break;
+    case SEQUENCE :
+      for (i = 0; i < length(xstartArg); i++) {
+        k=1;
+        for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
+          INTEGER(ans)[xorder[j]-1] = k++;
+      }
+      break;
+    // case RUNLENGTH :
+    //   for (i = 0; i < length(xstartArg); i++) {
+    //     k=1;
+    //     for (j = xstart[i]-1; j < xstart[i]+xlen[i]-1; j++)
+    //       INTEGER(ans)[xorder[j]-1] = k++;
+    //   }
+    //   break;
     }
-    UNPROTECT(1);
-    return(ans);
+  }
+  UNPROTECT(1);
+  return(ans);
 }
 
 // internal version of anyNA for data.tables
 SEXP anyNA(SEXP x, SEXP cols) {
-    int i, j, n=0, this;
-    double *dv;
-    SEXP v, ans, class;
+  int i, j, n=0, this;
+  double *dv;
+  SEXP v, ans, class;
 
-    if (!isNewList(x)) error("Internal error. Argument 'x' to CanyNA is type '%s' not 'list'", type2char(TYPEOF(x)));
-    if (!isInteger(cols)) error("Internal error. Argument 'cols' to CanyNA is type '%s' not 'integer'", type2char(TYPEOF(cols)));
-    for (i=0; i<LENGTH(cols); i++) {
-        this = INTEGER(cols)[i];
-        if (this<1 || this>LENGTH(x))
-            error("Item %d of 'cols' is %d which is outside 1-based range [1,ncol(x)=%d]", i+1, this, LENGTH(x));
-        if (!n) n = length(VECTOR_ELT(x, this-1));
-    }
-    ans = PROTECT(allocVector(LGLSXP, 1));
-    LOGICAL(ans)[0]=0;
-    for (i=0; i<LENGTH(cols); i++) {
-        v = VECTOR_ELT(x, INTEGER(cols)[i]-1);
-        if (!length(v) || isNewList(v) || isList(v)) continue; // like stats:::na.omit.data.frame, skip list/pairlist columns
-        if (n != length(v))
-            error("Column %d of input list x is length %d, inconsistent with first column of that item which is length %d.", i+1,length(v),n);
-        j=0;
-        switch (TYPEOF(v)) {
-        case LGLSXP:
-            while(j < n && LOGICAL(v)[j] != NA_LOGICAL) j++;
-            if (j < n) LOGICAL(ans)[0] = 1;
-            break;
-        case INTSXP:
-            while(j < n && INTEGER(v)[j] != NA_INTEGER) j++;
-            if (j < n) LOGICAL(ans)[0] = 1;
-            break;
-        case STRSXP:
-            while (j < n && STRING_ELT(v, j) != NA_STRING) j++;
-            if (j < n) LOGICAL(ans)[0] = 1;
-            break;
-        case REALSXP:
-            class = getAttrib(v, R_ClassSymbol);
-            if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
-                dv = (double *)REAL(v);
-                for (j=0; j<n; j++) {
-                    if (DtoLL(dv[j]) == NA_INT64_LL) {
-                        LOGICAL(ans)[0] = 1;
-                        break;
-                    }
-                }
-            } else {
-                while(j < n && !ISNAN(REAL(v)[j])) j++;
-                if (j < n) LOGICAL(ans)[0] = 1;
-            }
-            break;
-        case RAWSXP:
-            // no such thing as a raw NA
-            // vector already initialised to all 0's
-            break;
-        case CPLXSXP:
-            // taken from https://github.com/wch/r-source/blob/d75f39d532819ccc8251f93b8ab10d5b83aac89a/src/main/coerce.c
-            while (j < n && !ISNAN(COMPLEX(v)[j].r) && !ISNAN(COMPLEX(v)[j].i)) j++;
-            if (j < n) LOGICAL(ans)[0] = 1;
-            break;
-        default:
-            error("Unknown column type '%s'", type2char(TYPEOF(v)));
+  if (!isNewList(x)) error("Internal error. Argument 'x' to CanyNA is type '%s' not 'list'", type2char(TYPEOF(x)));
+  if (!isInteger(cols)) error("Internal error. Argument 'cols' to CanyNA is type '%s' not 'integer'", type2char(TYPEOF(cols)));
+  for (i=0; i<LENGTH(cols); i++) {
+    this = INTEGER(cols)[i];
+    if (this<1 || this>LENGTH(x))
+      error("Item %d of 'cols' is %d which is outside 1-based range [1,ncol(x)=%d]", i+1, this, LENGTH(x));
+    if (!n) n = length(VECTOR_ELT(x, this-1));
+  }
+  ans = PROTECT(allocVector(LGLSXP, 1));
+  LOGICAL(ans)[0]=0;
+  for (i=0; i<LENGTH(cols); i++) {
+    v = VECTOR_ELT(x, INTEGER(cols)[i]-1);
+    if (!length(v) || isNewList(v) || isList(v)) continue; // like stats:::na.omit.data.frame, skip list/pairlist columns
+    if (n != length(v))
+      error("Column %d of input list x is length %d, inconsistent with first column of that item which is length %d.", i+1,length(v),n);
+    j=0;
+    switch (TYPEOF(v)) {
+    case LGLSXP:
+      while(j < n && LOGICAL(v)[j] != NA_LOGICAL) j++;
+      if (j < n) LOGICAL(ans)[0] = 1;
+      break;
+    case INTSXP:
+      while(j < n && INTEGER(v)[j] != NA_INTEGER) j++;
+      if (j < n) LOGICAL(ans)[0] = 1;
+      break;
+    case STRSXP:
+      while (j < n && STRING_ELT(v, j) != NA_STRING) j++;
+      if (j < n) LOGICAL(ans)[0] = 1;
+      break;
+    case REALSXP:
+      class = getAttrib(v, R_ClassSymbol);
+      if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
+        dv = (double *)REAL(v);
+        for (j=0; j<n; j++) {
+          if (DtoLL(dv[j]) == NA_INT64_LL) {
+            LOGICAL(ans)[0] = 1;
+            break;
+          }
         }
-        if (LOGICAL(ans)[0]) break;
+      } else {
+        while(j < n && !ISNAN(REAL(v)[j])) j++;
+        if (j < n) LOGICAL(ans)[0] = 1;
+      }
+      break;
+    case RAWSXP:
+      // no such thing as a raw NA
+      // vector already initialised to all 0's
+      break;
+    case CPLXSXP:
+      // taken from https://github.com/wch/r-source/blob/d75f39d532819ccc8251f93b8ab10d5b83aac89a/src/main/coerce.c
+      while (j < n && !ISNAN(COMPLEX(v)[j].r) && !ISNAN(COMPLEX(v)[j].i)) j++;
+      if (j < n) LOGICAL(ans)[0] = 1;
+      break;
+    default:
+      error("Unknown column type '%s'", type2char(TYPEOF(v)));
     }
-    UNPROTECT(1);
-    return(ans);
+    if (LOGICAL(ans)[0]) break;
+  }
+  UNPROTECT(1);
+  return(ans);
 }
diff --git a/src/gsumm.c b/src/gsumm.c
index 051c491e64..e09ac48bdb 100644
--- a/src/gsumm.c
+++ b/src/gsumm.c
@@ -13,8 +13,10 @@ static int maxgrpn = 0;
 static int *oo = NULL;
 static int *ff = NULL;
 static int isunsorted = 0;
-static union {double d;
-              long long ll;} u;
+static union {
+  double d;
+  long long ll;
+} u;
 
 // from R's src/cov.c (for variance / sd)
 #ifdef HAVE_LONG_DOUBLE
@@ -24,1022 +26,1022 @@ static union {double d;
 #endif
 
 SEXP gforce(SEXP env, SEXP jsub, SEXP o, SEXP f, SEXP l, SEXP irowsArg) {
-    int i, j, g, *this;
-    // clock_t start = clock();
-    if (TYPEOF(env) != ENVSXP) error("env is not an environment");
-    // The type of jsub is pretty flexbile in R, so leave checking to eval() below.
-    if (!isInteger(o)) error("o is not an integer vector");
-    if (!isInteger(f)) error("f is not an integer vector");
-    if (!isInteger(l)) error("l is not an integer vector");
-    if (!isInteger(irowsArg) && !isNull(irowsArg)) error("irowsArg is not an integer vector");
-    ngrp = LENGTH(l);
-    if (LENGTH(f) != ngrp) error("length(f)=%d != length(l)=%d", LENGTH(f), ngrp);
-    grpn=0;
-    grpsize = INTEGER(l);
-    for (i=0; i<ngrp; i++) grpn+=grpsize[i];
-    if (LENGTH(o) && LENGTH(o)!=grpn) error("o has length %d but sum(l)=%d", LENGTH(o), grpn);
+  int i, j, g, *this;
+  // clock_t start = clock();
+  if (TYPEOF(env) != ENVSXP) error("env is not an environment");
+  // The type of jsub is pretty flexbile in R, so leave checking to eval() below.
+  if (!isInteger(o)) error("o is not an integer vector");
+  if (!isInteger(f)) error("f is not an integer vector");
+  if (!isInteger(l)) error("l is not an integer vector");
+  if (!isInteger(irowsArg) && !isNull(irowsArg)) error("irowsArg is not an integer vector");
+  ngrp = LENGTH(l);
+  if (LENGTH(f) != ngrp) error("length(f)=%d != length(l)=%d", LENGTH(f), ngrp);
+  grpn=0;
+  grpsize = INTEGER(l);
+  for (i=0; i<ngrp; i++) grpn+=grpsize[i];
+  if (LENGTH(o) && LENGTH(o)!=grpn) error("o has length %d but sum(l)=%d", LENGTH(o), grpn);
 
-    grp = (int *)R_alloc(grpn, sizeof(int));
-    // global grp because the g* functions (inside jsub) share this common memory
+  grp = (int *)R_alloc(grpn, sizeof(int));
+  // global grp because the g* functions (inside jsub) share this common memory
 
-    if (LENGTH(o)) {
-        isunsorted = 1; // for gmedian
-        for (g=0; g<ngrp; g++) {
-            this = INTEGER(o) + INTEGER(f)[g]-1;
-            for (j=0; j<grpsize[g]; j++)  grp[ this[j]-1 ] = g;
-        }
-    } else {
-        for (g=0; g<ngrp; g++) {
-            this = grp + INTEGER(f)[g]-1;
-            for (j=0; j<grpsize[g]; j++)  this[j] = g;
-        }
+  if (LENGTH(o)) {
+    isunsorted = 1; // for gmedian
+    for (g=0; g<ngrp; g++) {
+      this = INTEGER(o) + INTEGER(f)[g]-1;
+      for (j=0; j<grpsize[g]; j++)  grp[ this[j]-1 ] = g;
     }
-    // for gmedian
-    // initialise maxgrpn
-    maxgrpn = INTEGER(getAttrib(o, install("maxgrpn")))[0];
-    oo = INTEGER(o);
-    ff = INTEGER(f);
-
-    irows = INTEGER(irowsArg);
-    if (!isNull(irowsArg)) irowslen = length(irowsArg);
-
-    SEXP ans = PROTECT( eval(jsub, env) );
-    // if this eval() fails with R error, R will release grp for us. Which is why we use R_alloc above.
-    if (isVectorAtomic(ans)) {
-      SEXP tt = ans;
-      ans = PROTECT(allocVector(VECSXP, 1));
-      SET_VECTOR_ELT(ans, 0, tt);
-      UNPROTECT(1);
+  } else {
+    for (g=0; g<ngrp; g++) {
+      this = grp + INTEGER(f)[g]-1;
+      for (j=0; j<grpsize[g]; j++)  this[j] = g;
     }
-    ngrp = 0; maxgrpn = 0; irowslen = -1; isunsorted = 0;
+  }
+  // for gmedian
+  // initialise maxgrpn
+  maxgrpn = INTEGER(getAttrib(o, install("maxgrpn")))[0];
+  oo = INTEGER(o);
+  ff = INTEGER(f);
+
+  irows = INTEGER(irowsArg);
+  if (!isNull(irowsArg)) irowslen = length(irowsArg);
 
-    // Rprintf("gforce took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
+  SEXP ans = PROTECT( eval(jsub, env) );
+  // if this eval() fails with R error, R will release grp for us. Which is why we use R_alloc above.
+  if (isVectorAtomic(ans)) {
+    SEXP tt = ans;
+    ans = PROTECT(allocVector(VECSXP, 1));
+    SET_VECTOR_ELT(ans, 0, tt);
     UNPROTECT(1);
-    return(ans);
+  }
+  ngrp = 0; maxgrpn = 0; irowslen = -1; isunsorted = 0;
+
+  // Rprintf("gforce took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
+  UNPROTECT(1);
+  return(ans);
 }
 
 // long double usage here results in test 648 being failed when running with valgrind
 // http://valgrind.org/docs/manual/manual-core.html#manual-core.limits
 SEXP gsum(SEXP x, SEXP narm)
 {
-    if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
-    if (!isVectorAtomic(x)) error("GForce sum can only be applied to columns, not .SD or similar. To sum all items in a list such as .SD, either add the prefix base::sum(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,sum),by=,.SDcols=]'");
-    if (inherits(x, "factor")) error("sum is not meaningful for factors.");
-    int i, ix, thisgrp;
-    int n = (irowslen == -1) ? length(x) : irowslen;
-    //clock_t start = clock();
-    SEXP ans;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in gsum", grpn, n);
-    long double *s = calloc(ngrp, sizeof(long double));
-    if (!s) error("Unable to allocate %d * %d bytes for gsum", ngrp, sizeof(long double));
-    switch(TYPEOF(x)) {
-    case LGLSXP: case INTSXP:
-        for (i=0; i<n; i++) {
-            thisgrp = grp[i];
-            ix = (irowslen == -1) ? i : irows[i]-1;
-            if(INTEGER(x)[ix] == NA_INTEGER) {
-                if (!LOGICAL(narm)[0]) s[thisgrp] = NA_REAL;  // Let NA_REAL propogate from here. R_NaReal is IEEE.
-                continue;
-            }
-            s[thisgrp] += INTEGER(x)[ix];  // no under/overflow here, s is long double (like base)
-        }
-        ans = PROTECT(allocVector(INTSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            if (s[i] > INT_MAX || s[i] < INT_MIN) {
-                warning("Group %d summed to more than type 'integer' can hold so the result has been coerced to 'numeric' automatically, for convenience.", i+1);
-                UNPROTECT(1);
-                ans = PROTECT(allocVector(REALSXP, ngrp));
-                for (i=0; i<ngrp; i++) REAL(ans)[i] = (double)s[i];
-                break;
-            } else if (ISNA(s[i])) {
-                INTEGER(ans)[i] = NA_INTEGER;
-            } else {
-                INTEGER(ans)[i] = (int)s[i];
-            }
-        }
-        break;
-    case REALSXP:
+  if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
+  if (!isVectorAtomic(x)) error("GForce sum can only be applied to columns, not .SD or similar. To sum all items in a list such as .SD, either add the prefix base::sum(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,sum),by=,.SDcols=]'");
+  if (inherits(x, "factor")) error("sum is not meaningful for factors.");
+  int i, ix, thisgrp;
+  int n = (irowslen == -1) ? length(x) : irowslen;
+  //clock_t start = clock();
+  SEXP ans;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in gsum", grpn, n);
+  long double *s = calloc(ngrp, sizeof(long double));
+  if (!s) error("Unable to allocate %d * %d bytes for gsum", ngrp, sizeof(long double));
+  switch(TYPEOF(x)) {
+  case LGLSXP: case INTSXP:
+    for (i=0; i<n; i++) {
+      thisgrp = grp[i];
+      ix = (irowslen == -1) ? i : irows[i]-1;
+      if(INTEGER(x)[ix] == NA_INTEGER) {
+        if (!LOGICAL(narm)[0]) s[thisgrp] = NA_REAL;  // Let NA_REAL propogate from here. R_NaReal is IEEE.
+        continue;
+      }
+      s[thisgrp] += INTEGER(x)[ix];  // no under/overflow here, s is long double (like base)
+    }
+    ans = PROTECT(allocVector(INTSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      if (s[i] > INT_MAX || s[i] < INT_MIN) {
+        warning("Group %d summed to more than type 'integer' can hold so the result has been coerced to 'numeric' automatically, for convenience.", i+1);
+        UNPROTECT(1);
         ans = PROTECT(allocVector(REALSXP, ngrp));
-        for (i=0; i<n; i++) {
-            thisgrp = grp[i];
-            ix = (irowslen == -1) ? i : irows[i]-1;
-            if(ISNAN(REAL(x)[ix]) && LOGICAL(narm)[0]) continue;  // else let NA_REAL propogate from here
-            s[thisgrp] += REAL(x)[ix];  // done in long double, like base
-        }
-        for (i=0; i<ngrp; i++) {
-            if (s[i] > DBL_MAX) REAL(ans)[i] = R_PosInf;
-            else if (s[i] < -DBL_MAX) REAL(ans)[i] = R_NegInf;
-            else REAL(ans)[i] = (double)s[i];
-        }
+        for (i=0; i<ngrp; i++) REAL(ans)[i] = (double)s[i];
         break;
-    default:
-        free(s);
-        error("Type '%s' not supported by GForce sum (gsum). Either add the prefix base::sum(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+      } else if (ISNA(s[i])) {
+        INTEGER(ans)[i] = NA_INTEGER;
+      } else {
+        INTEGER(ans)[i] = (int)s[i];
+      }
     }
+    break;
+  case REALSXP:
+    ans = PROTECT(allocVector(REALSXP, ngrp));
+    for (i=0; i<n; i++) {
+      thisgrp = grp[i];
+      ix = (irowslen == -1) ? i : irows[i]-1;
+      if(ISNAN(REAL(x)[ix]) && LOGICAL(narm)[0]) continue;  // else let NA_REAL propogate from here
+      s[thisgrp] += REAL(x)[ix];  // done in long double, like base
+    }
+    for (i=0; i<ngrp; i++) {
+      if (s[i] > DBL_MAX) REAL(ans)[i] = R_PosInf;
+      else if (s[i] < -DBL_MAX) REAL(ans)[i] = R_NegInf;
+      else REAL(ans)[i] = (double)s[i];
+    }
+    break;
+  default:
     free(s);
-    copyMostAttrib(x, ans);
-    UNPROTECT(1);
-    // Rprintf("this gsum took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
-    return(ans);
+    error("Type '%s' not supported by GForce sum (gsum). Either add the prefix base::sum(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  free(s);
+  copyMostAttrib(x, ans);
+  UNPROTECT(1);
+  // Rprintf("this gsum took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
+  return(ans);
 }
 
 SEXP gmean(SEXP x, SEXP narm)
 {
-    SEXP ans;
-    int i, ix, protecti=0, thisgrp, n;
-    //clock_t start = clock();
-    if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
-    if (!isVectorAtomic(x)) error("GForce mean can only be applied to columns, not .SD or similar. Likely you're looking for 'DT[,lapply(.SD,mean),by=,.SDcols=]'. See ?data.table.");
-    if (inherits(x, "factor")) error("mean is not meaningful for factors.");
-    if (!LOGICAL(narm)[0]) {
-        ans = PROTECT(gsum(x,narm)); protecti++;
-        switch(TYPEOF(ans)) {
-        case LGLSXP: case INTSXP:
-            ans = PROTECT(coerceVector(ans, REALSXP)); protecti++;
-        case REALSXP:
-            for (i=0; i<ngrp; i++) REAL(ans)[i] /= grpsize[i];  // let NA propogate
-            break;
-        default :
-            error("Internal error: gsum returned type '%s'. typeof(x) is '%s'", type2char(TYPEOF(ans)), type2char(TYPEOF(x)));
-        }
-        UNPROTECT(protecti);
-        return(ans);
+  SEXP ans;
+  int i, ix, protecti=0, thisgrp, n;
+  //clock_t start = clock();
+  if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
+  if (!isVectorAtomic(x)) error("GForce mean can only be applied to columns, not .SD or similar. Likely you're looking for 'DT[,lapply(.SD,mean),by=,.SDcols=]'. See ?data.table.");
+  if (inherits(x, "factor")) error("mean is not meaningful for factors.");
+  if (!LOGICAL(narm)[0]) {
+    ans = PROTECT(gsum(x,narm)); protecti++;
+    switch(TYPEOF(ans)) {
+    case LGLSXP: case INTSXP:
+      ans = PROTECT(coerceVector(ans, REALSXP)); protecti++;
+    case REALSXP:
+      for (i=0; i<ngrp; i++) REAL(ans)[i] /= grpsize[i];  // let NA propogate
+      break;
+    default :
+      error("Internal error: gsum returned type '%s'. typeof(x) is '%s'", type2char(TYPEOF(ans)), type2char(TYPEOF(x)));
     }
-    // na.rm=TRUE.  Similar to gsum, but we need to count the non-NA as well for the divisor
-    n = (irowslen == -1) ? length(x) : irowslen;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in gsum", grpn, n);
+    UNPROTECT(protecti);
+    return(ans);
+  }
+  // na.rm=TRUE.  Similar to gsum, but we need to count the non-NA as well for the divisor
+  n = (irowslen == -1) ? length(x) : irowslen;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in gsum", grpn, n);
 
-    long double *s = calloc(ngrp, sizeof(long double));
-    if (!s) error("Unable to allocate %d * %d bytes for sum in gmean na.rm=TRUE", ngrp, sizeof(long double));
+  long double *s = calloc(ngrp, sizeof(long double));
+  if (!s) error("Unable to allocate %d * %d bytes for sum in gmean na.rm=TRUE", ngrp, sizeof(long double));
 
-    int *c = calloc(ngrp, sizeof(int));
-    if (!c) error("Unable to allocate %d * %d bytes for counts in gmean na.rm=TRUE", ngrp, sizeof(int));
+  int *c = calloc(ngrp, sizeof(int));
+  if (!c) error("Unable to allocate %d * %d bytes for counts in gmean na.rm=TRUE", ngrp, sizeof(int));
 
-    switch(TYPEOF(x)) {
-    case LGLSXP: case INTSXP:
-        for (i=0; i<n; i++) {
-            thisgrp = grp[i];
-            ix = (irowslen == -1) ? i : irows[i]-1;
-            if(INTEGER(x)[ix] == NA_INTEGER) continue;
-            s[thisgrp] += INTEGER(x)[ix];  // no under/overflow here, s is long double
-            c[thisgrp]++;
-        }
-        break;
-    case REALSXP:
-        for (i=0; i<n; i++) {
-            thisgrp = grp[i];
-            ix = (irowslen == -1) ? i : irows[i]-1;
-            if (ISNAN(REAL(x)[ix])) continue;
-            s[thisgrp] += REAL(x)[ix];
-            c[thisgrp]++;
-        }
-        break;
-    default:
-        free(s); free(c);
-        error("Type '%s' not supported by GForce mean (gmean) na.rm=TRUE. Either add the prefix base::mean(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  switch(TYPEOF(x)) {
+  case LGLSXP: case INTSXP:
+    for (i=0; i<n; i++) {
+      thisgrp = grp[i];
+      ix = (irowslen == -1) ? i : irows[i]-1;
+      if(INTEGER(x)[ix] == NA_INTEGER) continue;
+      s[thisgrp] += INTEGER(x)[ix];  // no under/overflow here, s is long double
+      c[thisgrp]++;
     }
-    ans = PROTECT(allocVector(REALSXP, ngrp));
-    for (i=0; i<ngrp; i++) {
-        if (c[i]==0) { REAL(ans)[i] = R_NaN; continue; }  // NaN to follow base::mean
-        s[i] /= c[i];
-        if (s[i] > DBL_MAX) REAL(ans)[i] = R_PosInf;
-        else if (s[i] < -DBL_MAX) REAL(ans)[i] = R_NegInf;
-        else REAL(ans)[i] = (double)s[i];
+    break;
+  case REALSXP:
+    for (i=0; i<n; i++) {
+      thisgrp = grp[i];
+      ix = (irowslen == -1) ? i : irows[i]-1;
+      if (ISNAN(REAL(x)[ix])) continue;
+      s[thisgrp] += REAL(x)[ix];
+      c[thisgrp]++;
     }
+    break;
+  default:
     free(s); free(c);
-    copyMostAttrib(x, ans);
-    UNPROTECT(1);
-    // Rprintf("this gmean na.rm=TRUE took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
-    return(ans);
+    error("Type '%s' not supported by GForce mean (gmean) na.rm=TRUE. Either add the prefix base::mean(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  ans = PROTECT(allocVector(REALSXP, ngrp));
+  for (i=0; i<ngrp; i++) {
+    if (c[i]==0) { REAL(ans)[i] = R_NaN; continue; }  // NaN to follow base::mean
+    s[i] /= c[i];
+    if (s[i] > DBL_MAX) REAL(ans)[i] = R_PosInf;
+    else if (s[i] < -DBL_MAX) REAL(ans)[i] = R_NegInf;
+    else REAL(ans)[i] = (double)s[i];
+  }
+  free(s); free(c);
+  copyMostAttrib(x, ans);
+  UNPROTECT(1);
+  // Rprintf("this gmean na.rm=TRUE took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
+  return(ans);
 }
 
 // gmin
 SEXP gmin(SEXP x, SEXP narm)
 {
-    if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
-    if (!isVectorAtomic(x)) error("GForce min can only be applied to columns, not .SD or similar. To find min of all items in a list such as .SD, either add the prefix base::min(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,min),by=,.SDcols=]'");
-    if (inherits(x, "factor")) error("min is not meaningful for factors.");
-    R_len_t i, ix, thisgrp=0;
-    int n = (irowslen == -1) ? length(x) : irowslen;
-    //clock_t start = clock();
-    SEXP ans;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in gmin", grpn, n);
-    switch(TYPEOF(x)) {
-    case LGLSXP: case INTSXP:
-        ans = PROTECT(allocVector(INTSXP, ngrp));
-        if (!LOGICAL(narm)[0]) {
-            for (i=0; i<ngrp; i++) INTEGER(ans)[i] = INT_MAX;
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (INTEGER(x)[ix] < INTEGER(ans)[thisgrp])   // NA_INTEGER==INT_MIN checked in init.c
-                    INTEGER(ans)[thisgrp] = INTEGER(x)[ix];
-            }
-        } else {
-            for (i=0; i<ngrp; i++) INTEGER(ans)[i] = NA_INTEGER;
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (INTEGER(x)[ix] == NA_INTEGER) continue;
-                if (INTEGER(ans)[thisgrp] == NA_INTEGER || INTEGER(x)[ix] < INTEGER(ans)[thisgrp])
-                    INTEGER(ans)[thisgrp] = INTEGER(x)[ix];
-            }
-            for (i=0; i<ngrp; i++) {
-                if (INTEGER(ans)[i] == NA_INTEGER) {
-                    warning("No non-missing values found in at least one group. Coercing to numeric type and returning 'Inf' for such groups to be consistent with base");
-                    UNPROTECT(1);
-                    ans = PROTECT(coerceVector(ans, REALSXP));
-                    for (i=0; i<ngrp; i++) {
-                        if (ISNA(REAL(ans)[i])) REAL(ans)[i] = R_PosInf;
-                    }
-                    break;
-                }
-            }
+  if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
+  if (!isVectorAtomic(x)) error("GForce min can only be applied to columns, not .SD or similar. To find min of all items in a list such as .SD, either add the prefix base::min(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,min),by=,.SDcols=]'");
+  if (inherits(x, "factor")) error("min is not meaningful for factors.");
+  R_len_t i, ix, thisgrp=0;
+  int n = (irowslen == -1) ? length(x) : irowslen;
+  //clock_t start = clock();
+  SEXP ans;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in gmin", grpn, n);
+  switch(TYPEOF(x)) {
+  case LGLSXP: case INTSXP:
+    ans = PROTECT(allocVector(INTSXP, ngrp));
+    if (!LOGICAL(narm)[0]) {
+      for (i=0; i<ngrp; i++) INTEGER(ans)[i] = INT_MAX;
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (INTEGER(x)[ix] < INTEGER(ans)[thisgrp])   // NA_INTEGER==INT_MIN checked in init.c
+          INTEGER(ans)[thisgrp] = INTEGER(x)[ix];
+      }
+    } else {
+      for (i=0; i<ngrp; i++) INTEGER(ans)[i] = NA_INTEGER;
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (INTEGER(x)[ix] == NA_INTEGER) continue;
+        if (INTEGER(ans)[thisgrp] == NA_INTEGER || INTEGER(x)[ix] < INTEGER(ans)[thisgrp])
+          INTEGER(ans)[thisgrp] = INTEGER(x)[ix];
+      }
+      for (i=0; i<ngrp; i++) {
+        if (INTEGER(ans)[i] == NA_INTEGER) {
+          warning("No non-missing values found in at least one group. Coercing to numeric type and returning 'Inf' for such groups to be consistent with base");
+          UNPROTECT(1);
+          ans = PROTECT(coerceVector(ans, REALSXP));
+          for (i=0; i<ngrp; i++) {
+            if (ISNA(REAL(ans)[i])) REAL(ans)[i] = R_PosInf;
+          }
+          break;
         }
-        break;
-    case STRSXP:
-        ans = PROTECT(allocVector(STRSXP, ngrp));
-        if (!LOGICAL(narm)[0]) {
-            for (i=0; i<ngrp; i++) SET_STRING_ELT(ans, i, R_BlankString);
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (STRING_ELT(x, ix) == NA_STRING) {
-                    SET_STRING_ELT(ans, thisgrp, NA_STRING);
-                } else {
-                    if (STRING_ELT(ans, thisgrp) == R_BlankString ||
-                        (STRING_ELT(ans, thisgrp) != NA_STRING && strcmp(CHAR(STRING_ELT(x, ix)), CHAR(STRING_ELT(ans, thisgrp))) < 0 )) {
-                        SET_STRING_ELT(ans, thisgrp, STRING_ELT(x, ix));
-                    }
-                }
-            }
+      }
+    }
+    break;
+  case STRSXP:
+    ans = PROTECT(allocVector(STRSXP, ngrp));
+    if (!LOGICAL(narm)[0]) {
+      for (i=0; i<ngrp; i++) SET_STRING_ELT(ans, i, R_BlankString);
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (STRING_ELT(x, ix) == NA_STRING) {
+          SET_STRING_ELT(ans, thisgrp, NA_STRING);
         } else {
-            for (i=0; i<ngrp; i++) SET_STRING_ELT(ans, i, NA_STRING);
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (STRING_ELT(x, ix) == NA_STRING) continue;
-                if (STRING_ELT(ans, thisgrp) == NA_STRING ||
-                    strcmp(CHAR(STRING_ELT(x, ix)), CHAR(STRING_ELT(ans, thisgrp))) < 0) {
-                    SET_STRING_ELT(ans, thisgrp, STRING_ELT(x, ix));
-                }
-            }
-            for (i=0; i<ngrp; i++) {
-                if (STRING_ELT(ans, i)==NA_STRING) {
-                    warning("No non-missing values found in at least one group. Returning 'NA' for such groups to be consistent with base");
-                    break;
-                }
-            }
+          if (STRING_ELT(ans, thisgrp) == R_BlankString ||
+            (STRING_ELT(ans, thisgrp) != NA_STRING && strcmp(CHAR(STRING_ELT(x, ix)), CHAR(STRING_ELT(ans, thisgrp))) < 0 )) {
+            SET_STRING_ELT(ans, thisgrp, STRING_ELT(x, ix));
+          }
         }
-        break;
-    case REALSXP:
-        ans = PROTECT(allocVector(REALSXP, ngrp));
-        if (!LOGICAL(narm)[0]) {
-            for (i=0; i<ngrp; i++) REAL(ans)[i] = R_PosInf;
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (ISNAN(REAL(x)[ix]) || REAL(x)[ix] < REAL(ans)[thisgrp])
-                    REAL(ans)[thisgrp] = REAL(x)[ix];
-            }
-        } else {
-            for (i=0; i<ngrp; i++) REAL(ans)[i] = NA_REAL;
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (ISNAN(REAL(x)[ix])) continue;
-                if (ISNAN(REAL(ans)[thisgrp]) || REAL(x)[ix] < REAL(ans)[thisgrp])
-                    REAL(ans)[thisgrp] = REAL(x)[ix];
-            }
-            for (i=0; i<ngrp; i++) {
-                if (ISNAN(REAL(ans)[i])) {
-                    warning("No non-missing values found in at least one group. Returning 'Inf' for such groups to be consistent with base");
-                    for (; i<ngrp; i++) if (ISNAN(REAL(ans)[i])) REAL(ans)[i] = R_PosInf;
-                    break;
-                }
-            }
+      }
+    } else {
+      for (i=0; i<ngrp; i++) SET_STRING_ELT(ans, i, NA_STRING);
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (STRING_ELT(x, ix) == NA_STRING) continue;
+        if (STRING_ELT(ans, thisgrp) == NA_STRING ||
+          strcmp(CHAR(STRING_ELT(x, ix)), CHAR(STRING_ELT(ans, thisgrp))) < 0) {
+          SET_STRING_ELT(ans, thisgrp, STRING_ELT(x, ix));
         }
-        break;
-    default:
-        error("Type '%s' not supported by GForce min (gmin). Either add the prefix base::min(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+      }
+      for (i=0; i<ngrp; i++) {
+        if (STRING_ELT(ans, i)==NA_STRING) {
+          warning("No non-missing values found in at least one group. Returning 'NA' for such groups to be consistent with base");
+          break;
+        }
+      }
     }
-    copyMostAttrib(x, ans); // all but names,dim and dimnames. And if so, we want a copy here, not keepattr's SET_ATTRIB.
-    UNPROTECT(1);
-    // Rprintf("this gmin took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
-    return(ans);
+    break;
+  case REALSXP:
+    ans = PROTECT(allocVector(REALSXP, ngrp));
+    if (!LOGICAL(narm)[0]) {
+      for (i=0; i<ngrp; i++) REAL(ans)[i] = R_PosInf;
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (ISNAN(REAL(x)[ix]) || REAL(x)[ix] < REAL(ans)[thisgrp])
+          REAL(ans)[thisgrp] = REAL(x)[ix];
+      }
+    } else {
+      for (i=0; i<ngrp; i++) REAL(ans)[i] = NA_REAL;
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (ISNAN(REAL(x)[ix])) continue;
+        if (ISNAN(REAL(ans)[thisgrp]) || REAL(x)[ix] < REAL(ans)[thisgrp])
+          REAL(ans)[thisgrp] = REAL(x)[ix];
+      }
+      for (i=0; i<ngrp; i++) {
+        if (ISNAN(REAL(ans)[i])) {
+          warning("No non-missing values found in at least one group. Returning 'Inf' for such groups to be consistent with base");
+          for (; i<ngrp; i++) if (ISNAN(REAL(ans)[i])) REAL(ans)[i] = R_PosInf;
+          break;
+        }
+      }
+    }
+    break;
+  default:
+    error("Type '%s' not supported by GForce min (gmin). Either add the prefix base::min(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  copyMostAttrib(x, ans); // all but names,dim and dimnames. And if so, we want a copy here, not keepattr's SET_ATTRIB.
+  UNPROTECT(1);
+  // Rprintf("this gmin took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
+  return(ans);
 }
 
 // gmax
 SEXP gmax(SEXP x, SEXP narm)
 {
-    if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
-    if (!isVectorAtomic(x)) error("GForce max can only be applied to columns, not .SD or similar. To find max of all items in a list such as .SD, either add the prefix base::max(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,max),by=,.SDcols=]'");
-    if (inherits(x, "factor")) error("max is not meaningful for factors.");
-    R_len_t i, ix, thisgrp=0;
-    int n = (irowslen == -1) ? length(x) : irowslen;
-    //clock_t start = clock();
-    SEXP ans;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in gmax", grpn, n);
+  if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
+  if (!isVectorAtomic(x)) error("GForce max can only be applied to columns, not .SD or similar. To find max of all items in a list such as .SD, either add the prefix base::max(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,max),by=,.SDcols=]'");
+  if (inherits(x, "factor")) error("max is not meaningful for factors.");
+  R_len_t i, ix, thisgrp=0;
+  int n = (irowslen == -1) ? length(x) : irowslen;
+  //clock_t start = clock();
+  SEXP ans;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in gmax", grpn, n);
 
-    // TODO rework gmax in the same way as gmin and remove this *update
-    char *update = (char *)R_alloc(ngrp, sizeof(char));
-    for (int i=0; i<ngrp; i++) update[i] = 0;
+  // TODO rework gmax in the same way as gmin and remove this *update
+  char *update = (char *)R_alloc(ngrp, sizeof(char));
+  for (int i=0; i<ngrp; i++) update[i] = 0;
 
-    switch(TYPEOF(x)) {
-    case LGLSXP: case INTSXP:
-        ans = PROTECT(allocVector(INTSXP, ngrp));
-        for (i=0; i<ngrp; i++) INTEGER(ans)[i] = 0;
-        if (!LOGICAL(narm)[0]) { // simple case - deal in a straightforward manner first
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (INTEGER(x)[ix] != NA_INTEGER && INTEGER(ans)[thisgrp] != NA_INTEGER) {
-                    if ( update[thisgrp] != 1 || INTEGER(ans)[thisgrp] < INTEGER(x)[ix] ) {
-                        INTEGER(ans)[thisgrp] = INTEGER(x)[ix];
-                        if (update[thisgrp] != 1) update[thisgrp] = 1;
-                    }
-                } else  INTEGER(ans)[thisgrp] = NA_INTEGER;
-            }
+  switch(TYPEOF(x)) {
+  case LGLSXP: case INTSXP:
+    ans = PROTECT(allocVector(INTSXP, ngrp));
+    for (i=0; i<ngrp; i++) INTEGER(ans)[i] = 0;
+    if (!LOGICAL(narm)[0]) { // simple case - deal in a straightforward manner first
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (INTEGER(x)[ix] != NA_INTEGER && INTEGER(ans)[thisgrp] != NA_INTEGER) {
+          if ( update[thisgrp] != 1 || INTEGER(ans)[thisgrp] < INTEGER(x)[ix] ) {
+            INTEGER(ans)[thisgrp] = INTEGER(x)[ix];
+            if (update[thisgrp] != 1) update[thisgrp] = 1;
+          }
+        } else  INTEGER(ans)[thisgrp] = NA_INTEGER;
+      }
+    } else {
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (INTEGER(x)[ix] != NA_INTEGER) {
+          if ( update[thisgrp] != 1 || INTEGER(ans)[thisgrp] < INTEGER(x)[ix] ) {
+            INTEGER(ans)[thisgrp] = INTEGER(x)[ix];
+            if (update[thisgrp] != 1) update[thisgrp] = 1;
+          }
         } else {
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (INTEGER(x)[ix] != NA_INTEGER) {
-                    if ( update[thisgrp] != 1 || INTEGER(ans)[thisgrp] < INTEGER(x)[ix] ) {
-                        INTEGER(ans)[thisgrp] = INTEGER(x)[ix];
-                        if (update[thisgrp] != 1) update[thisgrp] = 1;
-                    }
-                } else {
-                    if (update[thisgrp] != 1) {
-                        INTEGER(ans)[thisgrp] = NA_INTEGER;
-                    }
-                }
-            }
-            for (i=0; i<ngrp; i++) {
-                if (update[i] != 1)  {// equivalent of INTEGER(ans)[thisgrp] == NA_INTEGER
-                    warning("No non-missing values found in at least one group. Coercing to numeric type and returning 'Inf' for such groups to be consistent with base");
-                    UNPROTECT(1);
-                    ans = PROTECT(coerceVector(ans, REALSXP));
-                    for (i=0; i<ngrp; i++) {
-                        if (update[i] != 1) REAL(ans)[i] = -R_PosInf;
-                    }
-                    break;
-                }
-            }
+          if (update[thisgrp] != 1) {
+            INTEGER(ans)[thisgrp] = NA_INTEGER;
+          }
         }
-        break;
-    case STRSXP:
-        ans = PROTECT(allocVector(STRSXP, ngrp));
-        for (i=0; i<ngrp; i++) SET_STRING_ELT(ans, i, mkChar(""));
-        if (!LOGICAL(narm)[0]) { // simple case - deal in a straightforward manner first
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (STRING_ELT(x,ix) != NA_STRING && STRING_ELT(ans, thisgrp) != NA_STRING) {
-                    if ( update[thisgrp] != 1 || strcmp(CHAR(STRING_ELT(ans, thisgrp)), CHAR(STRING_ELT(x,ix))) < 0 ) {
-                        SET_STRING_ELT(ans, thisgrp, STRING_ELT(x, ix));
-                        if (update[thisgrp] != 1) update[thisgrp] = 1;
-                    }
-                } else  SET_STRING_ELT(ans, thisgrp, NA_STRING);
-            }
+      }
+      for (i=0; i<ngrp; i++) {
+        if (update[i] != 1)  {// equivalent of INTEGER(ans)[thisgrp] == NA_INTEGER
+          warning("No non-missing values found in at least one group. Coercing to numeric type and returning 'Inf' for such groups to be consistent with base");
+          UNPROTECT(1);
+          ans = PROTECT(coerceVector(ans, REALSXP));
+          for (i=0; i<ngrp; i++) {
+            if (update[i] != 1) REAL(ans)[i] = -R_PosInf;
+          }
+          break;
+        }
+      }
+    }
+    break;
+  case STRSXP:
+    ans = PROTECT(allocVector(STRSXP, ngrp));
+    for (i=0; i<ngrp; i++) SET_STRING_ELT(ans, i, mkChar(""));
+    if (!LOGICAL(narm)[0]) { // simple case - deal in a straightforward manner first
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (STRING_ELT(x,ix) != NA_STRING && STRING_ELT(ans, thisgrp) != NA_STRING) {
+          if ( update[thisgrp] != 1 || strcmp(CHAR(STRING_ELT(ans, thisgrp)), CHAR(STRING_ELT(x,ix))) < 0 ) {
+            SET_STRING_ELT(ans, thisgrp, STRING_ELT(x, ix));
+            if (update[thisgrp] != 1) update[thisgrp] = 1;
+          }
+        } else  SET_STRING_ELT(ans, thisgrp, NA_STRING);
+      }
+    } else {
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if (STRING_ELT(x, ix) != NA_STRING) {
+          if ( update[thisgrp] != 1 || strcmp(CHAR(STRING_ELT(ans, thisgrp)), CHAR(STRING_ELT(x, ix))) < 0 ) {
+            SET_STRING_ELT(ans, thisgrp, STRING_ELT(x, ix));
+            if (update[thisgrp] != 1) update[thisgrp] = 1;
+          }
         } else {
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if (STRING_ELT(x, ix) != NA_STRING) {
-                    if ( update[thisgrp] != 1 || strcmp(CHAR(STRING_ELT(ans, thisgrp)), CHAR(STRING_ELT(x, ix))) < 0 ) {
-                        SET_STRING_ELT(ans, thisgrp, STRING_ELT(x, ix));
-                        if (update[thisgrp] != 1) update[thisgrp] = 1;
-                    }
-                } else {
-                    if (update[thisgrp] != 1) {
-                        SET_STRING_ELT(ans, thisgrp, NA_STRING);
-                    }
-                }
-            }
-            for (i=0; i<ngrp; i++) {
-                if (update[i] != 1)  {// equivalent of INTEGER(ans)[thisgrp] == NA_INTEGER
-                    warning("No non-missing values found in at least one group. Returning 'NA' for such groups to be consistent with base");
-                    break;
-                }
-            }
+          if (update[thisgrp] != 1) {
+            SET_STRING_ELT(ans, thisgrp, NA_STRING);
+          }
         }
-        break;
-    case REALSXP:
-        ans = PROTECT(allocVector(REALSXP, ngrp));
-        for (i=0; i<ngrp; i++) REAL(ans)[i] = 0;
-        if (!LOGICAL(narm)[0]) {
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if ( !ISNA(REAL(x)[ix]) && !ISNA(REAL(ans)[thisgrp]) ) {
-                    if ( update[thisgrp] != 1 || REAL(ans)[thisgrp] < REAL(x)[ix] ||
-                         (ISNAN(REAL(x)[ix]) && !ISNAN(REAL(ans)[thisgrp])) ) { // #1461
-                        REAL(ans)[thisgrp] = REAL(x)[ix];
-                        if (update[thisgrp] != 1) update[thisgrp] = 1;
-                    }
-                } else REAL(ans)[thisgrp] = NA_REAL;
-            }
+      }
+      for (i=0; i<ngrp; i++) {
+        if (update[i] != 1)  {// equivalent of INTEGER(ans)[thisgrp] == NA_INTEGER
+          warning("No non-missing values found in at least one group. Returning 'NA' for such groups to be consistent with base");
+          break;
+        }
+      }
+    }
+    break;
+  case REALSXP:
+    ans = PROTECT(allocVector(REALSXP, ngrp));
+    for (i=0; i<ngrp; i++) REAL(ans)[i] = 0;
+    if (!LOGICAL(narm)[0]) {
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if ( !ISNA(REAL(x)[ix]) && !ISNA(REAL(ans)[thisgrp]) ) {
+          if ( update[thisgrp] != 1 || REAL(ans)[thisgrp] < REAL(x)[ix] ||
+             (ISNAN(REAL(x)[ix]) && !ISNAN(REAL(ans)[thisgrp])) ) { // #1461
+            REAL(ans)[thisgrp] = REAL(x)[ix];
+            if (update[thisgrp] != 1) update[thisgrp] = 1;
+          }
+        } else REAL(ans)[thisgrp] = NA_REAL;
+      }
+    } else {
+      for (i=0; i<n; i++) {
+        thisgrp = grp[i];
+        ix = (irowslen == -1) ? i : irows[i]-1;
+        if ( !ISNAN(REAL(x)[ix]) ) { // #1461
+          if ( update[thisgrp] != 1 || REAL(ans)[thisgrp] < REAL(x)[ix] ) {
+            REAL(ans)[thisgrp] = REAL(x)[ix];
+            if (update[thisgrp] != 1) update[thisgrp] = 1;
+          }
         } else {
-            for (i=0; i<n; i++) {
-                thisgrp = grp[i];
-                ix = (irowslen == -1) ? i : irows[i]-1;
-                if ( !ISNAN(REAL(x)[ix]) ) { // #1461
-                    if ( update[thisgrp] != 1 || REAL(ans)[thisgrp] < REAL(x)[ix] ) {
-                        REAL(ans)[thisgrp] = REAL(x)[ix];
-                        if (update[thisgrp] != 1) update[thisgrp] = 1;
-                    }
-                } else {
-                    if (update[thisgrp] != 1) {
-                        REAL(ans)[thisgrp] = -R_PosInf;
-                    }
-                }
-            }
-            // everything taken care of already. Just warn if all NA groups have occurred at least once
-            for (i=0; i<ngrp; i++) {
-                if (update[i] != 1)  { // equivalent of REAL(ans)[thisgrp] == -R_PosInf
-                    warning("No non-missing values found in at least one group. Returning '-Inf' for such groups to be consistent with base");
-                    break;
-                }
-            }
+          if (update[thisgrp] != 1) {
+            REAL(ans)[thisgrp] = -R_PosInf;
+          }
         }
-        break;
-    default:
-        error("Type '%s' not supported by GForce max (gmax). Either add the prefix base::max(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+      }
+      // everything taken care of already. Just warn if all NA groups have occurred at least once
+      for (i=0; i<ngrp; i++) {
+        if (update[i] != 1)  { // equivalent of REAL(ans)[thisgrp] == -R_PosInf
+          warning("No non-missing values found in at least one group. Returning '-Inf' for such groups to be consistent with base");
+          break;
+        }
+      }
     }
-    copyMostAttrib(x, ans); // all but names,dim and dimnames. And if so, we want a copy here, not keepattr's SET_ATTRIB.
-    UNPROTECT(1);
-    // Rprintf("this gmax took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
-    return(ans);
+    break;
+  default:
+    error("Type '%s' not supported by GForce max (gmax). Either add the prefix base::max(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  copyMostAttrib(x, ans); // all but names,dim and dimnames. And if so, we want a copy here, not keepattr's SET_ATTRIB.
+  UNPROTECT(1);
+  // Rprintf("this gmax took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
+  return(ans);
 }
 
 // gmedian, always returns numeric type (to avoid as.numeric() wrap..)
 SEXP gmedian(SEXP x, SEXP narm) {
 
-    if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
-    if (!isVectorAtomic(x)) error("GForce median can only be applied to columns, not .SD or similar. To find median of all items in a list such as .SD, either add the prefix stats::median(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,median),by=,.SDcols=]'");
-    if (inherits(x, "factor")) error("median is not meaningful for factors.");
-    R_len_t i=0, j=0, k=0, imed=0, thisgrpsize=0, medianindex=0, nacount=0;
-    double val = 0.0;
-    Rboolean isna = FALSE, isint64 = FALSE;
-    SEXP ans, sub, class;
-    void *ptr;
-    int n = (irowslen == -1) ? length(x) : irowslen;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in gmedian", grpn, n);
-    switch(TYPEOF(x)) {
-    case REALSXP:
-        class = getAttrib(x, R_ClassSymbol);
-        isint64 = (isString(class) && STRING_ELT(class, 0) == char_integer64);
-        ans = PROTECT(allocVector(REALSXP, ngrp));
-        sub = PROTECT(allocVector(REALSXP, maxgrpn)); // allocate once upfront
-        ptr = DATAPTR(sub);
-        if (!LOGICAL(narm)[0]) {
-            for (i=0; i<ngrp; i++) {
-                isna = FALSE;
-                thisgrpsize = grpsize[i];
-                SETLENGTH(sub, thisgrpsize);
-                for (j=0; j<thisgrpsize; j++) {
-                    k = ff[i]+j-1;
-                    if (isunsorted) k = oo[k]-1;
-                    k = (irowslen == -1) ? k : irows[k]-1;
-                    // TODO: raise this if-statement?
-                    if (!isint64) {
-                        if (!ISNAN(REAL(x)[k])) {
-                            REAL(sub)[j] = REAL(x)[k];
-                        } else {
-                            REAL(ans)[i] = NA_REAL;
-                            isna = TRUE; break;
-                        }
-                    } else {
-                        u.d = REAL(x)[k];
-                        if (u.ll != NA_INT64_LL) {
-                            REAL(sub)[j] = (double)u.ll;
-                        } else {
-                            REAL(ans)[i] = NA_REAL;
-                            isna = TRUE; break;
-                        }
-                    }
-                }
-                if (isna) continue;
-                medianindex = (R_len_t)(ceil((double)(thisgrpsize)/2));
-                REAL(ans)[i] = dquickselect(ptr, thisgrpsize, medianindex-1); // 0-indexed
-                // all elements to the left of thisgrpsize/2 is < the value at that index
-                // we just need to get min of last half
-                if (thisgrpsize % 2 == 0) {
-                    val = REAL(sub)[medianindex]; // 0-indexed
-                    for (imed=medianindex+1; imed<thisgrpsize; imed++) {
-                        val = REAL(sub)[imed] > val ? val : REAL(sub)[imed];
-                    }
-                    REAL(ans)[i] = (REAL(ans)[i] + val)/2.0;
-                }
+  if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
+  if (!isVectorAtomic(x)) error("GForce median can only be applied to columns, not .SD or similar. To find median of all items in a list such as .SD, either add the prefix stats::median(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,median),by=,.SDcols=]'");
+  if (inherits(x, "factor")) error("median is not meaningful for factors.");
+  R_len_t i=0, j=0, k=0, imed=0, thisgrpsize=0, medianindex=0, nacount=0;
+  double val = 0.0;
+  Rboolean isna = FALSE, isint64 = FALSE;
+  SEXP ans, sub, class;
+  void *ptr;
+  int n = (irowslen == -1) ? length(x) : irowslen;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in gmedian", grpn, n);
+  switch(TYPEOF(x)) {
+  case REALSXP:
+    class = getAttrib(x, R_ClassSymbol);
+    isint64 = (isString(class) && STRING_ELT(class, 0) == char_integer64);
+    ans = PROTECT(allocVector(REALSXP, ngrp));
+    sub = PROTECT(allocVector(REALSXP, maxgrpn)); // allocate once upfront
+    ptr = DATAPTR(sub);
+    if (!LOGICAL(narm)[0]) {
+      for (i=0; i<ngrp; i++) {
+        isna = FALSE;
+        thisgrpsize = grpsize[i];
+        SETLENGTH(sub, thisgrpsize);
+        for (j=0; j<thisgrpsize; j++) {
+          k = ff[i]+j-1;
+          if (isunsorted) k = oo[k]-1;
+          k = (irowslen == -1) ? k : irows[k]-1;
+          // TODO: raise this if-statement?
+          if (!isint64) {
+            if (!ISNAN(REAL(x)[k])) {
+              REAL(sub)[j] = REAL(x)[k];
+            } else {
+              REAL(ans)[i] = NA_REAL;
+              isna = TRUE; break;
             }
-        } else {
-            for (i=0; i<ngrp; i++) {
-                nacount = 0;
-                thisgrpsize = grpsize[i];
-                for (j=0; j<thisgrpsize; j++) {
-                    k = ff[i]+j-1;
-                    if (isunsorted) k = oo[k]-1;
-                    k = (irowslen == -1) ? k : irows[k]-1;
-                    // TODO: raise this if-statement?
-                    if (!isint64) {
-                        if (!ISNAN(REAL(x)[k])) {
-                            REAL(sub)[j-nacount] = REAL(x)[k];
-                        } else { nacount++; continue; }
-                    } else {
-                        u.d = REAL(x)[k];
-                        if (u.ll != NA_INT64_LL) {
-                            REAL(sub)[j-nacount] = (double)u.ll;
-                        } else { nacount++; continue; }
-                    }
-                }
-                if (nacount == thisgrpsize) {
-                    REAL(ans)[i] = NA_REAL; // all NAs
-                    continue;
-                }
-                thisgrpsize -= nacount;
-                SETLENGTH(sub, thisgrpsize);
-                medianindex = (R_len_t)(ceil((double)(thisgrpsize)/2));
-                REAL(ans)[i] = dquickselect(ptr, thisgrpsize, medianindex-1);
-                if (thisgrpsize % 2 == 0) {
-                    // all elements to the left of thisgrpsize/2 is < the value at that index
-                    // we just need to get min of last half
-                    val = REAL(sub)[medianindex]; // 0-indexed
-                    for (imed=medianindex+1; imed<thisgrpsize; imed++) {
-                        val = REAL(sub)[imed] > val ? val : REAL(sub)[imed];
-                    }
-                    REAL(ans)[i] = (REAL(ans)[i] + val)/2.0;
-                }
+          } else {
+            u.d = REAL(x)[k];
+            if (u.ll != NA_INT64_LL) {
+              REAL(sub)[j] = (double)u.ll;
+            } else {
+              REAL(ans)[i] = NA_REAL;
+              isna = TRUE; break;
             }
+          }
         }
-        SETLENGTH(sub, maxgrpn);
-        break;
-    case LGLSXP: case INTSXP:
-        ans = PROTECT(allocVector(REALSXP, ngrp));
-        sub = PROTECT(allocVector(INTSXP, maxgrpn)); // allocate once upfront
-        ptr = DATAPTR(sub);
-        if (!LOGICAL(narm)[0]) {
-            for (i=0; i<ngrp; i++) {
-                isna = FALSE;
-                thisgrpsize = grpsize[i];
-                SETLENGTH(sub, thisgrpsize);
-                for (j=0; j<thisgrpsize; j++) {
-                    k = ff[i]+j-1;
-                    if (isunsorted) k = oo[k]-1;
-                    k = (irowslen == -1) ? k : irows[k]-1;
-                    if (INTEGER(x)[k] != NA_INTEGER) {
-                        INTEGER(sub)[j] = INTEGER(x)[k];
-                    } else {
-                        REAL(ans)[i] = NA_REAL;
-                        isna = TRUE; break;
-                    }
-                }
-                if (isna) continue;
-                medianindex = (R_len_t)(ceil((double)(thisgrpsize)/2));
-                REAL(ans)[i] = iquickselect(ptr, thisgrpsize, medianindex-1); // 0-indexed
-                // all elements to the left of thisgrpsize/2 is < the value at that index
-                // we just need to get min of last half
-                if (thisgrpsize % 2 == 0) {
-                    val = INTEGER(sub)[medianindex]; // 0-indexed
-                    for (imed=medianindex+1; imed<thisgrpsize; imed++) {
-                        val = INTEGER(sub)[imed] > val ? val : INTEGER(sub)[imed];
-                    }
-                    REAL(ans)[i] = (REAL(ans)[i] + val)/2.0;
-                }
-            }
-        } else {
-            for (i=0; i<ngrp; i++) {
-                nacount = 0;
-                thisgrpsize = grpsize[i];
-                for (j=0; j<thisgrpsize; j++) {
-                    k = ff[i]+j-1;
-                    if (isunsorted) k = oo[k]-1;
-                    k = (irowslen == -1) ? k : irows[k]-1;
-                    if (INTEGER(x)[k] != NA_INTEGER) {
-                        INTEGER(sub)[j-nacount] = INTEGER(x)[k];
-                    } else { nacount++; continue; }
-                }
-                if (nacount == thisgrpsize) {
-                    REAL(ans)[i] = NA_REAL; // all NAs
-                    continue;
-                }
-                thisgrpsize -= nacount;
-                SETLENGTH(sub, thisgrpsize);
-                medianindex = (R_len_t)(ceil((double)(thisgrpsize)/2));
-                REAL(ans)[i] = iquickselect(ptr, thisgrpsize, medianindex-1);
-                if (thisgrpsize % 2 == 0) {
-                    // all elements to the left of thisgrpsize/2 is < the value at that index
-                    // we just need to get min of last half
-                    val = INTEGER(sub)[medianindex]; // 0-indexed
-                    for (imed=medianindex+1; imed<thisgrpsize; imed++) {
-                        val = INTEGER(sub)[imed] > val ? val : INTEGER(sub)[imed];
-                    }
-                    REAL(ans)[i] = (REAL(ans)[i] + val)/2.0;
-                }
-            }
+        if (isna) continue;
+        medianindex = (R_len_t)(ceil((double)(thisgrpsize)/2));
+        REAL(ans)[i] = dquickselect(ptr, thisgrpsize, medianindex-1); // 0-indexed
+        // all elements to the left of thisgrpsize/2 is < the value at that index
+        // we just need to get min of last half
+        if (thisgrpsize % 2 == 0) {
+          val = REAL(sub)[medianindex]; // 0-indexed
+          for (imed=medianindex+1; imed<thisgrpsize; imed++) {
+            val = REAL(sub)[imed] > val ? val : REAL(sub)[imed];
+          }
+          REAL(ans)[i] = (REAL(ans)[i] + val)/2.0;
         }
-        SETLENGTH(sub, maxgrpn);
-        break;
-    default:
-        error("Type '%s' not supported by GForce median (gmedian). Either add the prefix stats::median(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+      }
+    } else {
+      for (i=0; i<ngrp; i++) {
+        nacount = 0;
+        thisgrpsize = grpsize[i];
+        for (j=0; j<thisgrpsize; j++) {
+          k = ff[i]+j-1;
+          if (isunsorted) k = oo[k]-1;
+          k = (irowslen == -1) ? k : irows[k]-1;
+          // TODO: raise this if-statement?
+          if (!isint64) {
+            if (!ISNAN(REAL(x)[k])) {
+              REAL(sub)[j-nacount] = REAL(x)[k];
+            } else { nacount++; continue; }
+          } else {
+            u.d = REAL(x)[k];
+            if (u.ll != NA_INT64_LL) {
+              REAL(sub)[j-nacount] = (double)u.ll;
+            } else { nacount++; continue; }
+          }
+        }
+        if (nacount == thisgrpsize) {
+          REAL(ans)[i] = NA_REAL; // all NAs
+          continue;
+        }
+        thisgrpsize -= nacount;
+        SETLENGTH(sub, thisgrpsize);
+        medianindex = (R_len_t)(ceil((double)(thisgrpsize)/2));
+        REAL(ans)[i] = dquickselect(ptr, thisgrpsize, medianindex-1);
+        if (thisgrpsize % 2 == 0) {
+          // all elements to the left of thisgrpsize/2 is < the value at that index
+          // we just need to get min of last half
+          val = REAL(sub)[medianindex]; // 0-indexed
+          for (imed=medianindex+1; imed<thisgrpsize; imed++) {
+            val = REAL(sub)[imed] > val ? val : REAL(sub)[imed];
+          }
+          REAL(ans)[i] = (REAL(ans)[i] + val)/2.0;
+        }
+      }
     }
-    UNPROTECT(2);
-    return(ans);
+    SETLENGTH(sub, maxgrpn);
+    break;
+  case LGLSXP: case INTSXP:
+    ans = PROTECT(allocVector(REALSXP, ngrp));
+    sub = PROTECT(allocVector(INTSXP, maxgrpn)); // allocate once upfront
+    ptr = DATAPTR(sub);
+    if (!LOGICAL(narm)[0]) {
+      for (i=0; i<ngrp; i++) {
+        isna = FALSE;
+        thisgrpsize = grpsize[i];
+        SETLENGTH(sub, thisgrpsize);
+        for (j=0; j<thisgrpsize; j++) {
+          k = ff[i]+j-1;
+          if (isunsorted) k = oo[k]-1;
+          k = (irowslen == -1) ? k : irows[k]-1;
+          if (INTEGER(x)[k] != NA_INTEGER) {
+            INTEGER(sub)[j] = INTEGER(x)[k];
+          } else {
+            REAL(ans)[i] = NA_REAL;
+            isna = TRUE; break;
+          }
+        }
+        if (isna) continue;
+        medianindex = (R_len_t)(ceil((double)(thisgrpsize)/2));
+        REAL(ans)[i] = iquickselect(ptr, thisgrpsize, medianindex-1); // 0-indexed
+        // all elements to the left of thisgrpsize/2 is < the value at that index
+        // we just need to get min of last half
+        if (thisgrpsize % 2 == 0) {
+          val = INTEGER(sub)[medianindex]; // 0-indexed
+          for (imed=medianindex+1; imed<thisgrpsize; imed++) {
+            val = INTEGER(sub)[imed] > val ? val : INTEGER(sub)[imed];
+          }
+          REAL(ans)[i] = (REAL(ans)[i] + val)/2.0;
+        }
+      }
+    } else {
+      for (i=0; i<ngrp; i++) {
+        nacount = 0;
+        thisgrpsize = grpsize[i];
+        for (j=0; j<thisgrpsize; j++) {
+          k = ff[i]+j-1;
+          if (isunsorted) k = oo[k]-1;
+          k = (irowslen == -1) ? k : irows[k]-1;
+          if (INTEGER(x)[k] != NA_INTEGER) {
+            INTEGER(sub)[j-nacount] = INTEGER(x)[k];
+          } else { nacount++; continue; }
+        }
+        if (nacount == thisgrpsize) {
+          REAL(ans)[i] = NA_REAL; // all NAs
+          continue;
+        }
+        thisgrpsize -= nacount;
+        SETLENGTH(sub, thisgrpsize);
+        medianindex = (R_len_t)(ceil((double)(thisgrpsize)/2));
+        REAL(ans)[i] = iquickselect(ptr, thisgrpsize, medianindex-1);
+        if (thisgrpsize % 2 == 0) {
+          // all elements to the left of thisgrpsize/2 is < the value at that index
+          // we just need to get min of last half
+          val = INTEGER(sub)[medianindex]; // 0-indexed
+          for (imed=medianindex+1; imed<thisgrpsize; imed++) {
+            val = INTEGER(sub)[imed] > val ? val : INTEGER(sub)[imed];
+          }
+          REAL(ans)[i] = (REAL(ans)[i] + val)/2.0;
+        }
+      }
+    }
+    SETLENGTH(sub, maxgrpn);
+    break;
+  default:
+    error("Type '%s' not supported by GForce median (gmedian). Either add the prefix stats::median(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  UNPROTECT(2);
+  return(ans);
 }
 
 SEXP glast(SEXP x) {
 
-    if (!isVectorAtomic(x)) error("GForce tail can only be applied to columns, not .SD or similar. To get tail of all items in a list such as .SD, either add the prefix utils::tail(.SD) or turn off GForce optimization using options(datatable.optimize=1).");
+  if (!isVectorAtomic(x)) error("GForce tail can only be applied to columns, not .SD or similar. To get tail of all items in a list such as .SD, either add the prefix utils::tail(.SD) or turn off GForce optimization using options(datatable.optimize=1).");
 
-    R_len_t i,k;
-    int n = (irowslen == -1) ? length(x) : irowslen;
-    SEXP ans;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in gtail", grpn, n);
-    switch(TYPEOF(x)) {
-    case LGLSXP:
-        ans = PROTECT(allocVector(LGLSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]+grpsize[i]-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            LOGICAL(ans)[i] = LOGICAL(x)[k];
-        }
-    break;
-    case INTSXP:
-        ans = PROTECT(allocVector(INTSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]+grpsize[i]-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            INTEGER(ans)[i] = INTEGER(x)[k];
-        }
+  R_len_t i,k;
+  int n = (irowslen == -1) ? length(x) : irowslen;
+  SEXP ans;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in gtail", grpn, n);
+  switch(TYPEOF(x)) {
+  case LGLSXP:
+    ans = PROTECT(allocVector(LGLSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]+grpsize[i]-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      LOGICAL(ans)[i] = LOGICAL(x)[k];
+    }
     break;
-    case REALSXP:
-        ans = PROTECT(allocVector(REALSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]+grpsize[i]-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            REAL(ans)[i] = REAL(x)[k];
-        }
+  case INTSXP:
+    ans = PROTECT(allocVector(INTSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]+grpsize[i]-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      INTEGER(ans)[i] = INTEGER(x)[k];
+    }
     break;
-    case STRSXP:
-        ans = PROTECT(allocVector(STRSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]+grpsize[i]-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            SET_STRING_ELT(ans, i, STRING_ELT(x, k));
-        }
+  case REALSXP:
+    ans = PROTECT(allocVector(REALSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]+grpsize[i]-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      REAL(ans)[i] = REAL(x)[k];
+    }
     break;
-    case VECSXP:
-        ans = PROTECT(allocVector(VECSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]+grpsize[i]-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            SET_VECTOR_ELT(ans, i, VECTOR_ELT(x, k));
-        }
+  case STRSXP:
+    ans = PROTECT(allocVector(STRSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]+grpsize[i]-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      SET_STRING_ELT(ans, i, STRING_ELT(x, k));
+    }
     break;
-    default:
-        error("Type '%s' not supported by GForce tail (gtail). Either add the prefix utils::tail(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  case VECSXP:
+    ans = PROTECT(allocVector(VECSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]+grpsize[i]-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      SET_VECTOR_ELT(ans, i, VECTOR_ELT(x, k));
     }
-    copyMostAttrib(x, ans);
-    UNPROTECT(1);
-    return(ans);
+    break;
+  default:
+    error("Type '%s' not supported by GForce tail (gtail). Either add the prefix utils::tail(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  copyMostAttrib(x, ans);
+  UNPROTECT(1);
+  return(ans);
 }
 
 SEXP gfirst(SEXP x) {
 
-    if (!isVectorAtomic(x)) error("GForce head can only be applied to columns, not .SD or similar. To get head of all items in a list such as .SD, either add the prefix utils::head(.SD) or turn off GForce optimization using options(datatable.optimize=1).");
+  if (!isVectorAtomic(x)) error("GForce head can only be applied to columns, not .SD or similar. To get head of all items in a list such as .SD, either add the prefix utils::head(.SD) or turn off GForce optimization using options(datatable.optimize=1).");
 
-    R_len_t i,k;
-    int n = (irowslen == -1) ? length(x) : irowslen;
-    SEXP ans;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in ghead", grpn, n);
-    switch(TYPEOF(x)) {
-    case LGLSXP:
-        ans = PROTECT(allocVector(LGLSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]-1;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            LOGICAL(ans)[i] = LOGICAL(x)[k];
-        }
-    break;
-    case INTSXP:
-        ans = PROTECT(allocVector(INTSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]-1;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            INTEGER(ans)[i] = INTEGER(x)[k];
-        }
+  R_len_t i,k;
+  int n = (irowslen == -1) ? length(x) : irowslen;
+  SEXP ans;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in ghead", grpn, n);
+  switch(TYPEOF(x)) {
+  case LGLSXP:
+    ans = PROTECT(allocVector(LGLSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]-1;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      LOGICAL(ans)[i] = LOGICAL(x)[k];
+    }
     break;
-    case REALSXP:
-        ans = PROTECT(allocVector(REALSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]-1;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            REAL(ans)[i] = REAL(x)[k];
-        }
+  case INTSXP:
+    ans = PROTECT(allocVector(INTSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]-1;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      INTEGER(ans)[i] = INTEGER(x)[k];
+    }
     break;
-    case STRSXP:
-        ans = PROTECT(allocVector(STRSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]-1;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            SET_STRING_ELT(ans, i, STRING_ELT(x, k));
-        }
+  case REALSXP:
+    ans = PROTECT(allocVector(REALSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]-1;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      REAL(ans)[i] = REAL(x)[k];
+    }
     break;
-    case VECSXP:
-        ans = PROTECT(allocVector(VECSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            k = ff[i]-1;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            SET_VECTOR_ELT(ans, i, VECTOR_ELT(x, k));
-        }
+  case STRSXP:
+    ans = PROTECT(allocVector(STRSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]-1;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      SET_STRING_ELT(ans, i, STRING_ELT(x, k));
+    }
     break;
-    default:
-        error("Type '%s' not supported by GForce head (ghead). Either add the prefix utils::head(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  case VECSXP:
+    ans = PROTECT(allocVector(VECSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      k = ff[i]-1;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      SET_VECTOR_ELT(ans, i, VECTOR_ELT(x, k));
     }
-    copyMostAttrib(x, ans);
-    UNPROTECT(1);
-    return(ans);
+    break;
+  default:
+    error("Type '%s' not supported by GForce head (ghead). Either add the prefix utils::head(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  copyMostAttrib(x, ans);
+  UNPROTECT(1);
+  return(ans);
 }
 
 SEXP gtail(SEXP x, SEXP valArg) {
-    if (!isInteger(valArg) || LENGTH(valArg)!=1 || INTEGER(valArg)[0]!=1) error("Internal error, gtail is only implemented for n=1. This should have been caught before. Please report to datatable-help.");
-    return (glast(x));
+  if (!isInteger(valArg) || LENGTH(valArg)!=1 || INTEGER(valArg)[0]!=1) error("Internal error, gtail is only implemented for n=1. This should have been caught before. Please report to datatable-help.");
+  return (glast(x));
 }
 
 SEXP ghead(SEXP x, SEXP valArg) {
-    if (!isInteger(valArg) || LENGTH(valArg)!=1 || INTEGER(valArg)[0]!=1) error("Internal error, ghead is only implemented for n=1. This should have been caught before. Please report to datatable-help.");
-    return (gfirst(x));
+  if (!isInteger(valArg) || LENGTH(valArg)!=1 || INTEGER(valArg)[0]!=1) error("Internal error, ghead is only implemented for n=1. This should have been caught before. Please report to datatable-help.");
+  return (gfirst(x));
 }
 
 SEXP gnthvalue(SEXP x, SEXP valArg) {
 
-    if (!isInteger(valArg) || LENGTH(valArg)!=1 || INTEGER(valArg)[0]<=0) error("Internal error, `g[` (gnthvalue) is only implemented single value subsets with positive index, e.g., .SD[2]. This should have been caught before. Please report to datatable-help.");
-    R_len_t i,k, val=INTEGER(valArg)[0];
-    int n = (irowslen == -1) ? length(x) : irowslen;
-    SEXP ans;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in ghead", grpn, n);
-    switch(TYPEOF(x)) {
-    case LGLSXP:
-        ans = PROTECT(allocVector(LGLSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            if (val > grpsize[i]) { LOGICAL(ans)[i] = NA_LOGICAL; continue; }
-            k = ff[i]+val-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            LOGICAL(ans)[i] = LOGICAL(x)[k];
-        }
-    break;
-    case INTSXP:
-        ans = PROTECT(allocVector(INTSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            if (val > grpsize[i]) { INTEGER(ans)[i] = NA_INTEGER; continue; }
-            k = ff[i]+val-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            INTEGER(ans)[i] = INTEGER(x)[k];
-        }
+  if (!isInteger(valArg) || LENGTH(valArg)!=1 || INTEGER(valArg)[0]<=0) error("Internal error, `g[` (gnthvalue) is only implemented single value subsets with positive index, e.g., .SD[2]. This should have been caught before. Please report to datatable-help.");
+  R_len_t i,k, val=INTEGER(valArg)[0];
+  int n = (irowslen == -1) ? length(x) : irowslen;
+  SEXP ans;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in ghead", grpn, n);
+  switch(TYPEOF(x)) {
+  case LGLSXP:
+    ans = PROTECT(allocVector(LGLSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      if (val > grpsize[i]) { LOGICAL(ans)[i] = NA_LOGICAL; continue; }
+      k = ff[i]+val-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      LOGICAL(ans)[i] = LOGICAL(x)[k];
+    }
     break;
-    case REALSXP:
-        ans = PROTECT(allocVector(REALSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            if (val > grpsize[i]) { REAL(ans)[i] = NA_REAL; continue; }
-            k = ff[i]+val-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            REAL(ans)[i] = REAL(x)[k];
-        }
+  case INTSXP:
+    ans = PROTECT(allocVector(INTSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      if (val > grpsize[i]) { INTEGER(ans)[i] = NA_INTEGER; continue; }
+      k = ff[i]+val-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      INTEGER(ans)[i] = INTEGER(x)[k];
+    }
     break;
-    case STRSXP:
-        ans = PROTECT(allocVector(STRSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            if (val > grpsize[i]) { SET_STRING_ELT(ans, i, NA_STRING); continue; }
-            k = ff[i]+val-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            SET_STRING_ELT(ans, i, STRING_ELT(x, k));
-        }
+  case REALSXP:
+    ans = PROTECT(allocVector(REALSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      if (val > grpsize[i]) { REAL(ans)[i] = NA_REAL; continue; }
+      k = ff[i]+val-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      REAL(ans)[i] = REAL(x)[k];
+    }
     break;
-    case VECSXP:
-        ans = PROTECT(allocVector(VECSXP, ngrp));
-        for (i=0; i<ngrp; i++) {
-            if (val > grpsize[i]) { SET_VECTOR_ELT(ans, i, R_NilValue); continue; }
-            k = ff[i]+val-2;
-            if (isunsorted) k = oo[k]-1;
-            k = (irowslen == -1) ? k : irows[k]-1;
-            SET_VECTOR_ELT(ans, i, VECTOR_ELT(x, k));
-        }
+  case STRSXP:
+    ans = PROTECT(allocVector(STRSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      if (val > grpsize[i]) { SET_STRING_ELT(ans, i, NA_STRING); continue; }
+      k = ff[i]+val-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      SET_STRING_ELT(ans, i, STRING_ELT(x, k));
+    }
     break;
-    default:
-        error("Type '%s' not supported by GForce subset `[` (gnthvalue). Either add the prefix utils::head(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  case VECSXP:
+    ans = PROTECT(allocVector(VECSXP, ngrp));
+    for (i=0; i<ngrp; i++) {
+      if (val > grpsize[i]) { SET_VECTOR_ELT(ans, i, R_NilValue); continue; }
+      k = ff[i]+val-2;
+      if (isunsorted) k = oo[k]-1;
+      k = (irowslen == -1) ? k : irows[k]-1;
+      SET_VECTOR_ELT(ans, i, VECTOR_ELT(x, k));
     }
-    copyMostAttrib(x, ans);
-    UNPROTECT(1);
-    return(ans);
+    break;
+  default:
+    error("Type '%s' not supported by GForce subset `[` (gnthvalue). Either add the prefix utils::head(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  copyMostAttrib(x, ans);
+  UNPROTECT(1);
+  return(ans);
 }
 
 // TODO: gwhich.min, gwhich.max
 // implemented this similar to gmedian to balance well between speed and memory usage. There's one extra allocation on maximum groups and that's it.. and that helps speed things up extremely since we don't have to collect x's values for each group for each step (mean, residuals, mean again and then variance).
 SEXP gvarsd1(SEXP x, SEXP narm, Rboolean isSD)
 {
-    if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
-    if (!isVectorAtomic(x)) error("GForce var/sd can only be applied to columns, not .SD or similar. To find var/sd of all items in a list such as .SD, either add the prefix stats::var(.SD) (or stats::sd(.SD)) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,var),by=,.SDcols=]'");
-    if (inherits(x, "factor")) error("var/sd is not meaningful for factors.");
-    long double m, s, v;
-    R_len_t i, j, ix, thisgrpsize = 0, n = (irowslen == -1) ? length(x) : irowslen;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in gvar", grpn, n);
-    SEXP sub, ans = PROTECT(allocVector(REALSXP, ngrp));
-    Rboolean ans_na;
-    switch(TYPEOF(x)) {
-        case LGLSXP: case INTSXP:
-        sub = PROTECT(allocVector(INTSXP, maxgrpn)); // allocate once upfront
-        if (!LOGICAL(narm)[0]) {
-            for (i=0; i<ngrp; i++) {
-                m=0.; s=0.; v=0.; ans_na = FALSE;
-                if (grpsize[i] != 1) {
-                    thisgrpsize = grpsize[i];
-                    SETLENGTH(sub, thisgrpsize); // to gather this group's data
-                    for (j=0; j<thisgrpsize; j++) {
-                        ix = ff[i]+j-1;
-                        if (isunsorted) ix = oo[ix]-1;
-                        ix = (irowslen == -1) ? ix : irows[ix]-1;
-                        if (INTEGER(x)[ix] == NA_INTEGER) { ans_na = TRUE; break; }
-                        INTEGER(sub)[j] = INTEGER(x)[ix];
-                        m += INTEGER(sub)[j]; // sum
-                    }
-                    if (ans_na) { REAL(ans)[i] = NA_REAL; continue; }
-                    m = m/thisgrpsize; // mean, first pass
-                    for (j=0; j<thisgrpsize; j++) s += (INTEGER(sub)[j]-m); // residuals
-                    m += (s/thisgrpsize); // mean, second pass
-                    for (j=0; j<thisgrpsize; j++) { // variance
-                        v += (INTEGER(sub)[j]-(double)m) * (INTEGER(sub)[j]-(double)m);
-                    }
-                    REAL(ans)[i] = (double)v/(thisgrpsize-1);
-                    if (isSD) REAL(ans)[i] = SQRTL(REAL(ans)[i]);
-                } else REAL(ans)[i] = NA_REAL;
-            }
-        } else {
-            for (i=0; i<ngrp; i++) {
-                m=0.; s=0.; v=0.; thisgrpsize = 0;
-                if (grpsize[i] != 1) {
-                    SETLENGTH(sub, grpsize[i]); // to gather this group's data
-                    for (j=0; j<grpsize[i]; j++) {
-                        ix = ff[i]+j-1;
-                        if (isunsorted) ix = oo[ix]-1;
-                        ix = (irowslen == -1) ? ix : irows[ix]-1;
-                        if (INTEGER(x)[ix] == NA_INTEGER) continue;
-                        INTEGER(sub)[thisgrpsize] = INTEGER(x)[ix];
-                        m += INTEGER(sub)[thisgrpsize]; // sum
-                        thisgrpsize++;
-                    }
-                    if (thisgrpsize <= 1) { REAL(ans)[i] = NA_REAL; continue; }
-                    m = m/thisgrpsize; // mean, first pass
-                    for (j=0; j<thisgrpsize; j++) s += (INTEGER(sub)[j]-m); // residuals
-                    m += (s/thisgrpsize); // mean, second pass
-                    for (j=0; j<thisgrpsize; j++) { // variance
-                        v += (INTEGER(sub)[j]-(double)m) * (INTEGER(sub)[j]-(double)m);
-                    }
-                    REAL(ans)[i] = (double)v/(thisgrpsize-1);
-                    if (isSD) REAL(ans)[i] = SQRTL(REAL(ans)[i]);
-                } else REAL(ans)[i] = NA_REAL;
-            }
-        }
-        SETLENGTH(sub, maxgrpn);
-        break;
-        case REALSXP:
-        sub = PROTECT(allocVector(REALSXP, maxgrpn)); // allocate once upfront
-        if (!LOGICAL(narm)[0]) {
-            for (i=0; i<ngrp; i++) {
-                m=0.; s=0.; v=0.; ans_na = FALSE;
-                if (grpsize[i] != 1) {
-                    thisgrpsize = grpsize[i];
-                    SETLENGTH(sub, thisgrpsize); // to gather this group's data
-                    for (j=0; j<thisgrpsize; j++) {
-                        ix = ff[i]+j-1;
-                        if (isunsorted) ix = oo[ix]-1;
-                        ix = (irowslen == -1) ? ix : irows[ix]-1;
-                        if (ISNAN(REAL(x)[ix])) { ans_na = TRUE; break; }
-                        REAL(sub)[j] = REAL(x)[ix];
-                        m += REAL(sub)[j]; // sum
-                    }
-                    if (ans_na) { REAL(ans)[i] = NA_REAL; continue; }
-                    m = m/thisgrpsize; // mean, first pass
-                    for (j=0; j<thisgrpsize; j++) s += (REAL(sub)[j]-m); // residuals
-                    m += (s/thisgrpsize); // mean, second pass
-                    for (j=0; j<thisgrpsize; j++) { // variance
-                        v += (REAL(sub)[j]-(double)m) * (REAL(sub)[j]-(double)m);
-                    }
-                    REAL(ans)[i] = (double)v/(thisgrpsize-1);
-                    if (isSD) REAL(ans)[i] = SQRTL(REAL(ans)[i]);
-                } else REAL(ans)[i] = NA_REAL;
-            }
-        } else {
-            for (i=0; i<ngrp; i++) {
-                m=0.; s=0.; v=0.; thisgrpsize = 0;
-                if (grpsize[i] != 1) {
-                    SETLENGTH(sub, grpsize[i]); // to gather this group's data
-                    for (j=0; j<grpsize[i]; j++) {
-                        ix = ff[i]+j-1;
-                        if (isunsorted) ix = oo[ix]-1;
-                        ix = (irowslen == -1) ? ix : irows[ix]-1;
-                        if (ISNAN(REAL(x)[ix])) continue;
-                        REAL(sub)[thisgrpsize] = REAL(x)[ix];
-                        m += REAL(sub)[thisgrpsize]; // sum
-                        thisgrpsize++;
-                    }
-                    if (thisgrpsize <= 1) { REAL(ans)[i] = NA_REAL; continue; }
-                    m = m/thisgrpsize; // mean, first pass
-                    for (j=0; j<thisgrpsize; j++) s += (REAL(sub)[j]-m); // residuals
-                    m += (s/thisgrpsize); // mean, second pass
-                    for (j=0; j<thisgrpsize; j++) { // variance
-                        v += (REAL(sub)[j]-(double)m) * (REAL(sub)[j]-(double)m);
-                    }
-                    REAL(ans)[i] = (double)v/(thisgrpsize-1);
-                    if (isSD) REAL(ans)[i] = SQRTL(REAL(ans)[i]);
-                } else REAL(ans)[i] = NA_REAL;
-            }
-        }
-        SETLENGTH(sub, maxgrpn);
-        break;
-        default:
-            if (isSD) {
-                error("Type '%s' not supported by GForce var (gvar). Either add the prefix stats::var(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
-            } else {
-                error("Type '%s' not supported by GForce sd (gsd). Either add the prefix stats::sd(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
-            }
+  if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
+  if (!isVectorAtomic(x)) error("GForce var/sd can only be applied to columns, not .SD or similar. To find var/sd of all items in a list such as .SD, either add the prefix stats::var(.SD) (or stats::sd(.SD)) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,var),by=,.SDcols=]'");
+  if (inherits(x, "factor")) error("var/sd is not meaningful for factors.");
+  long double m, s, v;
+  R_len_t i, j, ix, thisgrpsize = 0, n = (irowslen == -1) ? length(x) : irowslen;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in gvar", grpn, n);
+  SEXP sub, ans = PROTECT(allocVector(REALSXP, ngrp));
+  Rboolean ans_na;
+  switch(TYPEOF(x)) {
+  case LGLSXP: case INTSXP:
+    sub = PROTECT(allocVector(INTSXP, maxgrpn)); // allocate once upfront
+    if (!LOGICAL(narm)[0]) {
+      for (i=0; i<ngrp; i++) {
+        m=0.; s=0.; v=0.; ans_na = FALSE;
+        if (grpsize[i] != 1) {
+          thisgrpsize = grpsize[i];
+          SETLENGTH(sub, thisgrpsize); // to gather this group's data
+          for (j=0; j<thisgrpsize; j++) {
+            ix = ff[i]+j-1;
+            if (isunsorted) ix = oo[ix]-1;
+            ix = (irowslen == -1) ? ix : irows[ix]-1;
+            if (INTEGER(x)[ix] == NA_INTEGER) { ans_na = TRUE; break; }
+            INTEGER(sub)[j] = INTEGER(x)[ix];
+            m += INTEGER(sub)[j]; // sum
+          }
+          if (ans_na) { REAL(ans)[i] = NA_REAL; continue; }
+          m = m/thisgrpsize; // mean, first pass
+          for (j=0; j<thisgrpsize; j++) s += (INTEGER(sub)[j]-m); // residuals
+          m += (s/thisgrpsize); // mean, second pass
+          for (j=0; j<thisgrpsize; j++) { // variance
+            v += (INTEGER(sub)[j]-(double)m) * (INTEGER(sub)[j]-(double)m);
+          }
+          REAL(ans)[i] = (double)v/(thisgrpsize-1);
+          if (isSD) REAL(ans)[i] = SQRTL(REAL(ans)[i]);
+        } else REAL(ans)[i] = NA_REAL;
+      }
+    } else {
+      for (i=0; i<ngrp; i++) {
+        m=0.; s=0.; v=0.; thisgrpsize = 0;
+        if (grpsize[i] != 1) {
+          SETLENGTH(sub, grpsize[i]); // to gather this group's data
+          for (j=0; j<grpsize[i]; j++) {
+            ix = ff[i]+j-1;
+            if (isunsorted) ix = oo[ix]-1;
+            ix = (irowslen == -1) ? ix : irows[ix]-1;
+            if (INTEGER(x)[ix] == NA_INTEGER) continue;
+            INTEGER(sub)[thisgrpsize] = INTEGER(x)[ix];
+            m += INTEGER(sub)[thisgrpsize]; // sum
+            thisgrpsize++;
+          }
+          if (thisgrpsize <= 1) { REAL(ans)[i] = NA_REAL; continue; }
+          m = m/thisgrpsize; // mean, first pass
+          for (j=0; j<thisgrpsize; j++) s += (INTEGER(sub)[j]-m); // residuals
+          m += (s/thisgrpsize); // mean, second pass
+          for (j=0; j<thisgrpsize; j++) { // variance
+            v += (INTEGER(sub)[j]-(double)m) * (INTEGER(sub)[j]-(double)m);
+          }
+          REAL(ans)[i] = (double)v/(thisgrpsize-1);
+          if (isSD) REAL(ans)[i] = SQRTL(REAL(ans)[i]);
+        } else REAL(ans)[i] = NA_REAL;
+      }
     }
-    UNPROTECT(2);
-    return (ans);
+    SETLENGTH(sub, maxgrpn);
+    break;
+  case REALSXP:
+    sub = PROTECT(allocVector(REALSXP, maxgrpn)); // allocate once upfront
+    if (!LOGICAL(narm)[0]) {
+      for (i=0; i<ngrp; i++) {
+        m=0.; s=0.; v=0.; ans_na = FALSE;
+        if (grpsize[i] != 1) {
+          thisgrpsize = grpsize[i];
+          SETLENGTH(sub, thisgrpsize); // to gather this group's data
+          for (j=0; j<thisgrpsize; j++) {
+            ix = ff[i]+j-1;
+            if (isunsorted) ix = oo[ix]-1;
+            ix = (irowslen == -1) ? ix : irows[ix]-1;
+            if (ISNAN(REAL(x)[ix])) { ans_na = TRUE; break; }
+            REAL(sub)[j] = REAL(x)[ix];
+            m += REAL(sub)[j]; // sum
+          }
+          if (ans_na) { REAL(ans)[i] = NA_REAL; continue; }
+          m = m/thisgrpsize; // mean, first pass
+          for (j=0; j<thisgrpsize; j++) s += (REAL(sub)[j]-m); // residuals
+          m += (s/thisgrpsize); // mean, second pass
+          for (j=0; j<thisgrpsize; j++) { // variance
+            v += (REAL(sub)[j]-(double)m) * (REAL(sub)[j]-(double)m);
+          }
+          REAL(ans)[i] = (double)v/(thisgrpsize-1);
+          if (isSD) REAL(ans)[i] = SQRTL(REAL(ans)[i]);
+        } else REAL(ans)[i] = NA_REAL;
+      }
+    } else {
+      for (i=0; i<ngrp; i++) {
+        m=0.; s=0.; v=0.; thisgrpsize = 0;
+        if (grpsize[i] != 1) {
+          SETLENGTH(sub, grpsize[i]); // to gather this group's data
+          for (j=0; j<grpsize[i]; j++) {
+            ix = ff[i]+j-1;
+            if (isunsorted) ix = oo[ix]-1;
+            ix = (irowslen == -1) ? ix : irows[ix]-1;
+            if (ISNAN(REAL(x)[ix])) continue;
+            REAL(sub)[thisgrpsize] = REAL(x)[ix];
+            m += REAL(sub)[thisgrpsize]; // sum
+            thisgrpsize++;
+          }
+          if (thisgrpsize <= 1) { REAL(ans)[i] = NA_REAL; continue; }
+          m = m/thisgrpsize; // mean, first pass
+          for (j=0; j<thisgrpsize; j++) s += (REAL(sub)[j]-m); // residuals
+          m += (s/thisgrpsize); // mean, second pass
+          for (j=0; j<thisgrpsize; j++) { // variance
+            v += (REAL(sub)[j]-(double)m) * (REAL(sub)[j]-(double)m);
+          }
+          REAL(ans)[i] = (double)v/(thisgrpsize-1);
+          if (isSD) REAL(ans)[i] = SQRTL(REAL(ans)[i]);
+        } else REAL(ans)[i] = NA_REAL;
+      }
+    }
+    SETLENGTH(sub, maxgrpn);
+    break;
+  default:
+      if (isSD) {
+        error("Type '%s' not supported by GForce var (gvar). Either add the prefix stats::var(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+      } else {
+        error("Type '%s' not supported by GForce sd (gsd). Either add the prefix stats::sd(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+      }
+  }
+  UNPROTECT(2);
+  return (ans);
 }
 
 SEXP gvar(SEXP x, SEXP narm) {
-    return (gvarsd1(x, narm, FALSE));
+  return (gvarsd1(x, narm, FALSE));
 }
 
 SEXP gsd(SEXP x, SEXP narm) {
-    return (gvarsd1(x, narm, TRUE));
+  return (gvarsd1(x, narm, TRUE));
 }
 
 SEXP gprod(SEXP x, SEXP narm)
 {
-    if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
-    if (!isVectorAtomic(x)) error("GForce prod can only be applied to columns, not .SD or similar. To multiply all items in a list such as .SD, either add the prefix base::prod(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,prod),by=,.SDcols=]'");
-    if (inherits(x, "factor")) error("prod is not meaningful for factors.");
-    int i, ix, thisgrp;
-    int n = (irowslen == -1) ? length(x) : irowslen;
-    //clock_t start = clock();
-    SEXP ans;
-    if (grpn != n) error("grpn [%d] != length(x) [%d] in gprod", grpn, n);
-    long double *s = malloc(ngrp * sizeof(long double));
-    if (!s) error("Unable to allocate %d * %d bytes for gprod", ngrp, sizeof(long double));
-    for (i=0; i<ngrp; i++) s[i] = 1.0;
-    ans = PROTECT(allocVector(REALSXP, ngrp));
-    switch(TYPEOF(x)) {
-    case LGLSXP: case INTSXP:
-        for (i=0; i<n; i++) {
-            thisgrp = grp[i];
-            ix = (irowslen == -1) ? i : irows[i]-1;
-            if(INTEGER(x)[ix] == NA_INTEGER) {
-                if (!LOGICAL(narm)[0]) s[thisgrp] = NA_REAL;  // Let NA_REAL propogate from here. R_NaReal is IEEE.
-                continue;
-            }
-            s[thisgrp] *= INTEGER(x)[ix];  // no under/overflow here, s is long double (like base)
-        }
-        for (i=0; i<ngrp; i++) {
-            if (s[i] > DBL_MAX) REAL(ans)[i] = R_PosInf;
-            else if (s[i] < -DBL_MAX) REAL(ans)[i] = R_NegInf;
-            else REAL(ans)[i] = (double)s[i];
-        }
-        break;
-    case REALSXP:
-        for (i=0; i<n; i++) {
-            thisgrp = grp[i];
-            ix = (irowslen == -1) ? i : irows[i]-1;
-            if(ISNAN(REAL(x)[ix]) && LOGICAL(narm)[0]) continue;  // else let NA_REAL propogate from here
-            s[thisgrp] *= REAL(x)[ix];  // done in long double, like base
-        }
-        for (i=0; i<ngrp; i++) {
-            if (s[i] > DBL_MAX) REAL(ans)[i] = R_PosInf;
-            else if (s[i] < -DBL_MAX) REAL(ans)[i] = R_NegInf;
-            else REAL(ans)[i] = (double)s[i];
-        }
-        break;
-    default:
-        free(s);
-        error("Type '%s' not supported by GForce prod (gprod). Either add the prefix base::prod(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  if (!isLogical(narm) || LENGTH(narm)!=1 || LOGICAL(narm)[0]==NA_LOGICAL) error("na.rm must be TRUE or FALSE");
+  if (!isVectorAtomic(x)) error("GForce prod can only be applied to columns, not .SD or similar. To multiply all items in a list such as .SD, either add the prefix base::prod(.SD) or turn off GForce optimization using options(datatable.optimize=1). More likely, you may be looking for 'DT[,lapply(.SD,prod),by=,.SDcols=]'");
+  if (inherits(x, "factor")) error("prod is not meaningful for factors.");
+  int i, ix, thisgrp;
+  int n = (irowslen == -1) ? length(x) : irowslen;
+  //clock_t start = clock();
+  SEXP ans;
+  if (grpn != n) error("grpn [%d] != length(x) [%d] in gprod", grpn, n);
+  long double *s = malloc(ngrp * sizeof(long double));
+  if (!s) error("Unable to allocate %d * %d bytes for gprod", ngrp, sizeof(long double));
+  for (i=0; i<ngrp; i++) s[i] = 1.0;
+  ans = PROTECT(allocVector(REALSXP, ngrp));
+  switch(TYPEOF(x)) {
+  case LGLSXP: case INTSXP:
+    for (i=0; i<n; i++) {
+      thisgrp = grp[i];
+      ix = (irowslen == -1) ? i : irows[i]-1;
+      if(INTEGER(x)[ix] == NA_INTEGER) {
+        if (!LOGICAL(narm)[0]) s[thisgrp] = NA_REAL;  // Let NA_REAL propogate from here. R_NaReal is IEEE.
+        continue;
+      }
+      s[thisgrp] *= INTEGER(x)[ix];  // no under/overflow here, s is long double (like base)
+    }
+    for (i=0; i<ngrp; i++) {
+      if (s[i] > DBL_MAX) REAL(ans)[i] = R_PosInf;
+      else if (s[i] < -DBL_MAX) REAL(ans)[i] = R_NegInf;
+      else REAL(ans)[i] = (double)s[i];
     }
+    break;
+  case REALSXP:
+    for (i=0; i<n; i++) {
+      thisgrp = grp[i];
+      ix = (irowslen == -1) ? i : irows[i]-1;
+      if(ISNAN(REAL(x)[ix]) && LOGICAL(narm)[0]) continue;  // else let NA_REAL propogate from here
+      s[thisgrp] *= REAL(x)[ix];  // done in long double, like base
+    }
+    for (i=0; i<ngrp; i++) {
+      if (s[i] > DBL_MAX) REAL(ans)[i] = R_PosInf;
+      else if (s[i] < -DBL_MAX) REAL(ans)[i] = R_NegInf;
+      else REAL(ans)[i] = (double)s[i];
+    }
+    break;
+  default:
     free(s);
-    copyMostAttrib(x, ans);
-    UNPROTECT(1);
-    // Rprintf("this gprod took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
-    return(ans);
+    error("Type '%s' not supported by GForce prod (gprod). Either add the prefix base::prod(.) or turn off GForce optimization using options(datatable.optimize=1)", type2char(TYPEOF(x)));
+  }
+  free(s);
+  copyMostAttrib(x, ans);
+  UNPROTECT(1);
+  // Rprintf("this gprod took %8.3f\n", 1.0*(clock()-start)/CLOCKS_PER_SEC);
+  return(ans);
 }
diff --git a/src/ijoin.c b/src/ijoin.c
index 26974d5ddb..02d2c8afef 100644
--- a/src/ijoin.c
+++ b/src/ijoin.c
@@ -5,687 +5,687 @@
 // TODO: implement 'lookup' for 'gaps' and 'overlaps' arguments
 SEXP lookup(SEXP ux, SEXP xlen, SEXP indices, SEXP gaps, SEXP overlaps, SEXP multArg, SEXP typeArg, SEXP verbose) {
 
-    SEXP vv, tt, lookup, type_lookup;
-    R_len_t i,j,k,*idx,*len1,*len2,xrows=INTEGER(xlen)[0],uxrows=LENGTH(VECTOR_ELT(ux, 0)),uxcols=LENGTH(ux);
-    int *from = (int *)INTEGER(VECTOR_ELT(indices, 0));
-    int *to   = (int *)INTEGER(VECTOR_ELT(indices, 1));
-    clock_t pass1, pass2, pass3, start;
-    enum {ALL, FIRST, LAST} mult = ALL;
-    enum {ANY, WITHIN, START, END, EQUAL} type = ANY;
-
-    if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "all"))  mult = ALL;
-    else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "first")) mult = FIRST;
-    else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "last")) mult = LAST;
-    else error("Internal error: invalid value for 'mult'; this should have been caught before. Please report to datatable-help");
-
-    if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "any"))  type = ANY;
-    else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "within")) type = WITHIN;
-    else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "start")) type = START;
-    else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "end")) type = END;
-    else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "equal")) type = EQUAL;
-    else error("Internal error: invalid value for 'type'; this should have been caught before. Please report to datatable-help");
-
-    // For reference: uxcols-1 = type_count, uxcols-2 = count, uxcols-3 = type_lookup, uxcols-4 = lookup
-    // first pass: calculate lengths first
-    start = clock();
-    len1 = (int *)INTEGER(VECTOR_ELT(ux, uxcols-2));
-    len2 = (int *)INTEGER(VECTOR_ELT(ux, uxcols-1));
-    switch (mult) {
-        case FIRST:
-        for (i=0; i<xrows; i++) {
-            for (j=from[i]; j<=to[i]; j++) {
-                len1[j-1]++;
-            }
-        }
-        if (type != WITHIN) {
-            for (i=0; i<uxrows; i++)                      // TODO: this allocation can be avoided if we take care of FIRST/LAST accordingly in 'overlaps'
-                if (len1[i]) len2[i] = 1;
-        }
-        break;
+  SEXP vv, tt, lookup, type_lookup;
+  R_len_t i,j,k,*idx,*len1,*len2,xrows=INTEGER(xlen)[0],uxrows=LENGTH(VECTOR_ELT(ux, 0)),uxcols=LENGTH(ux);
+  int *from = (int *)INTEGER(VECTOR_ELT(indices, 0));
+  int *to   = (int *)INTEGER(VECTOR_ELT(indices, 1));
+  clock_t pass1, pass2, pass3, start;
+  enum {ALL, FIRST, LAST} mult = ALL;
+  enum {ANY, WITHIN, START, END, EQUAL} type = ANY;
+
+  if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "all"))  mult = ALL;
+  else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "first")) mult = FIRST;
+  else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "last")) mult = LAST;
+  else error("Internal error: invalid value for 'mult'; this should have been caught before. Please report to datatable-help");
+
+  if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "any"))  type = ANY;
+  else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "within")) type = WITHIN;
+  else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "start")) type = START;
+  else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "end")) type = END;
+  else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "equal")) type = EQUAL;
+  else error("Internal error: invalid value for 'type'; this should have been caught before. Please report to datatable-help");
+
+  // For reference: uxcols-1 = type_count, uxcols-2 = count, uxcols-3 = type_lookup, uxcols-4 = lookup
+  // first pass: calculate lengths first
+  start = clock();
+  len1 = (int *)INTEGER(VECTOR_ELT(ux, uxcols-2));
+  len2 = (int *)INTEGER(VECTOR_ELT(ux, uxcols-1));
+  switch (mult) {
+  case FIRST:
+    for (i=0; i<xrows; i++) {
+      for (j=from[i]; j<=to[i]; j++) {
+        len1[j-1]++;
+      }
+    }
+    if (type != WITHIN) {
+      for (i=0; i<uxrows; i++)                      // TODO: this allocation can be avoided if we take care of FIRST/LAST accordingly in 'overlaps'
+        if (len1[i]) len2[i] = 1;
+    }
+    break;
 
-        case LAST :
-        switch (type) {
-            case ANY:
-            for (i=0; i<xrows; i++) {
-                for (j=from[i]; j<=to[i]; j++) {
-                    len1[j-1]++;
-                    if (from[i]==j && !len2[j-1]) len2[j-1]++;
-                }
-            }
-            break;
-            case START: case END: case EQUAL: case WITHIN:
-            for (i=0; i<xrows; i++) {
-                for (j=from[i]; j<=to[i]; j++) {
-                    len1[j-1]++;
-                }
-            }
-            if (type != WITHIN) {
-                for (i=0; i<uxrows; i++)              // TODO: this allocation can be avoided if we take care of FIRST/LAST accordingly in 'overlaps'
-                    if (len1[i]) len2[i] = 1;
-            }
-            break;
+  case LAST :
+    switch (type) {
+    case ANY:
+      for (i=0; i<xrows; i++) {
+        for (j=from[i]; j<=to[i]; j++) {
+          len1[j-1]++;
+          if (from[i]==j && !len2[j-1]) len2[j-1]++;
         }
-        break;
-
-        case ALL :
-            switch (type) {
-                case START: case END:
-                for (i=0; i<xrows; i++) {
-                    for (j=from[i]; j<=to[i]; j++) {
-                        len1[j-1]++; len2[j-1]++;       // alternatively, we could simply do with len2=len1 ?
-                    }
-                }
-                break;
-                case EQUAL:
-                for (i=0; i<xrows; i++) {
-                    len1[from[i]-1]++; len1[to[i]-1]++;
-                    len2[from[i]-1]++; len2[to[i]-1]++;
-                }
-                break;
-                case ANY :
-                for (i=0; i<xrows; i++) {
-                    k = from[i];
-                    for (j=from[i]; j<=to[i]; j++) {
-                        len1[j-1]++;
-                        if (k==j) len2[j-1]++;
-                    }
-                }
-                break;
-                case WITHIN :
-                for (i=0; i<xrows; i++) {
-                    for (j=from[i]; j<=to[i]; j++) {
-                        len1[j-1]++;
-                    }
-                }
-                break;
-            }
-        break;
-    }
-    pass1 = clock() - start;
-    if (LOGICAL(verbose)[0])
-        Rprintf("First pass on calculating lengths in lookup ... done in %8.3f seconds\n", 1.0*(pass1)/CLOCKS_PER_SEC);
-    // second pass: allocate vectors
-    start = clock();
-    lookup = VECTOR_ELT(ux, uxcols-4);
-    type_lookup = VECTOR_ELT(ux, uxcols-3);
-    for (i=0; i<uxrows; i++) {
-        vv = allocVector(INTSXP, len1[i]);
-        SET_VECTOR_ELT(lookup, i, vv);
-        if (type != WITHIN) {
-            vv = allocVector(INTSXP, len2[i]);
-            SET_VECTOR_ELT(type_lookup, i, vv);
+      }
+      break;
+    case START: case END: case EQUAL: case WITHIN:
+      for (i=0; i<xrows; i++) {
+        for (j=from[i]; j<=to[i]; j++) {
+          len1[j-1]++;
         }
+      }
+      if (type != WITHIN) {
+        for (i=0; i<uxrows; i++)              // TODO: this allocation can be avoided if we take care of FIRST/LAST accordingly in 'overlaps'
+          if (len1[i]) len2[i] = 1;
+      }
+      break;
     }
-    pass2 = clock() - start;
-    if (LOGICAL(verbose)[0])
-        Rprintf("Second pass on allocation in lookup ... done in %8.3f seconds\n", 1.0*(pass2)/CLOCKS_PER_SEC);
-    // generate lookup
-    start = clock();
-    idx = Calloc(uxrows, R_len_t); // resets bits, =0
+    break;
+
+  case ALL :
     switch (type) {
-        case ANY: case START: case END: case WITHIN:
-        for (i=0; i<xrows; i++) {
-            for (j=from[i]; j<=to[i]; j++) {
-                vv = VECTOR_ELT(lookup, j-1);  // cache misses - memory efficiency? but 'lookups' are tiny - takes 0.036s on A.thaliana GFF for entire process)
-                INTEGER(vv)[idx[j-1]++] = i+1;
-            }
+    case START: case END:
+      for (i=0; i<xrows; i++) {
+        for (j=from[i]; j<=to[i]; j++) {
+          len1[j-1]++; len2[j-1]++;       // alternatively, we could simply do with len2=len1 ?
         }
-        break;
-        case EQUAL:
-        for (i=0; i<xrows; i++) {
-            INTEGER(VECTOR_ELT(lookup, from[i]-1))[idx[from[i]-1]++] = i+1;
-            INTEGER(VECTOR_ELT(lookup, to[i]-1))[idx[to[i]-1]++] = i+1;
+      }
+      break;
+    case EQUAL:
+      for (i=0; i<xrows; i++) {
+        len1[from[i]-1]++; len1[to[i]-1]++;
+        len2[from[i]-1]++; len2[to[i]-1]++;
+      }
+      break;
+    case ANY :
+      for (i=0; i<xrows; i++) {
+        k = from[i];
+        for (j=from[i]; j<=to[i]; j++) {
+          len1[j-1]++;
+          if (k==j) len2[j-1]++;
         }
-        break;
+      }
+      break;
+    case WITHIN :
+      for (i=0; i<xrows; i++) {
+        for (j=from[i]; j<=to[i]; j++) {
+          len1[j-1]++;
+        }
+      }
+      break;
     }
-    Free(idx);
-    // generate type_lookup
+    break;
+  }
+  pass1 = clock() - start;
+  if (LOGICAL(verbose)[0])
+    Rprintf("First pass on calculating lengths in lookup ... done in %8.3f seconds\n", 1.0*(pass1)/CLOCKS_PER_SEC);
+  // second pass: allocate vectors
+  start = clock();
+  lookup = VECTOR_ELT(ux, uxcols-4);
+  type_lookup = VECTOR_ELT(ux, uxcols-3);
+  for (i=0; i<uxrows; i++) {
+    vv = allocVector(INTSXP, len1[i]);
+    SET_VECTOR_ELT(lookup, i, vv);
     if (type != WITHIN) {
-        switch (mult) {
-            case FIRST :
-            for (i=0; i<uxrows; i++) {
-                if (!len1[i]) continue;
-                vv = VECTOR_ELT(lookup, i);
-                tt = VECTOR_ELT(type_lookup, i);
-                INTEGER(tt)[0] = INTEGER(vv)[0];
-            }
-            break;
+      vv = allocVector(INTSXP, len2[i]);
+      SET_VECTOR_ELT(type_lookup, i, vv);
+    }
+  }
+  pass2 = clock() - start;
+  if (LOGICAL(verbose)[0])
+    Rprintf("Second pass on allocation in lookup ... done in %8.3f seconds\n", 1.0*(pass2)/CLOCKS_PER_SEC);
+  // generate lookup
+  start = clock();
+  idx = Calloc(uxrows, R_len_t); // resets bits, =0
+  switch (type) {
+  case ANY: case START: case END: case WITHIN:
+    for (i=0; i<xrows; i++) {
+      for (j=from[i]; j<=to[i]; j++) {
+        vv = VECTOR_ELT(lookup, j-1);  // cache misses - memory efficiency? but 'lookups' are tiny - takes 0.036s on A.thaliana GFF for entire process)
+        INTEGER(vv)[idx[j-1]++] = i+1;
+      }
+    }
+    break;
+  case EQUAL:
+    for (i=0; i<xrows; i++) {
+      INTEGER(VECTOR_ELT(lookup, from[i]-1))[idx[from[i]-1]++] = i+1;
+      INTEGER(VECTOR_ELT(lookup, to[i]-1))[idx[to[i]-1]++] = i+1;
+    }
+    break;
+  }
+  Free(idx);
+  // generate type_lookup
+  if (type != WITHIN) {
+    switch (mult) {
+    case FIRST :
+      for (i=0; i<uxrows; i++) {
+        if (!len1[i]) continue;
+        vv = VECTOR_ELT(lookup, i);
+        tt = VECTOR_ELT(type_lookup, i);
+        INTEGER(tt)[0] = INTEGER(vv)[0];
+      }
+      break;
+
+    case LAST :
+      for (i=0; i<uxrows; i++) {
+        if (!len1[i]) continue;
+        vv = VECTOR_ELT(lookup, i);
+        tt = VECTOR_ELT(type_lookup, i);
+        INTEGER(tt)[0] = INTEGER(vv)[len1[i]-1];
+      }
+
+    case ALL :
+      switch (type) {
+      case START: case END: case EQUAL:
+        for (i=0; i<uxrows; i++)
+          SET_VECTOR_ELT(type_lookup, i, VECTOR_ELT(lookup, i));
+        break;
 
-            case LAST :
-            for (i=0; i<uxrows; i++) {
-                if (!len1[i]) continue;
-                vv = VECTOR_ELT(lookup, i);
-                tt = VECTOR_ELT(type_lookup, i);
-                INTEGER(tt)[0] = INTEGER(vv)[len1[i]-1];
-            }
+      case ANY :
+        for (i=0; i<uxrows; i++) {
+          vv = VECTOR_ELT(lookup, i);
+          tt = VECTOR_ELT(type_lookup, i);
+          k=0;
+          for (j=len1[i]-len2[i]; j<len1[i]; j++)
+            INTEGER(tt)[k++] = INTEGER(vv)[j];
+        }
+        break;
 
-            case ALL :
-            switch (type) {
-                case START: case END: case EQUAL:
-                for (i=0; i<uxrows; i++)
-                    SET_VECTOR_ELT(type_lookup, i, VECTOR_ELT(lookup, i));
-                break;
-
-                case ANY :
-                for (i=0; i<uxrows; i++) {
-                    vv = VECTOR_ELT(lookup, i);
-                    tt = VECTOR_ELT(type_lookup, i);
-                    k=0;
-                    for (j=len1[i]-len2[i]; j<len1[i]; j++)
-                        INTEGER(tt)[k++] = INTEGER(vv)[j];
-                }
-                break;
-
-                case WITHIN :
-                // for (i=0; i<uxrows; i++) {
-                //     vv = VECTOR_ELT(lookup, i);
-                //     tt = VECTOR_ELT(type_lookup, i);
-                //     for (j=0; j<len2[i]; j++)
-                //         INTEGER(tt)[j] = INTEGER(vv)[j];
-                // }
-                break;
-            }
+      case WITHIN :
+        // for (i=0; i<uxrows; i++) {
+        //     vv = VECTOR_ELT(lookup, i);
+        //     tt = VECTOR_ELT(type_lookup, i);
+        //     for (j=0; j<len2[i]; j++)
+        //         INTEGER(tt)[j] = INTEGER(vv)[j];
+        // }
         break;
-        }
+      }
+      break;
     }
-    pass3 = clock() - start;
-    if (LOGICAL(verbose)[0])
-        Rprintf("Final step in generating lookup ... done in %8.3f seconds\n", 1.0*(pass3)/CLOCKS_PER_SEC);
-    return(R_NilValue);
+  }
+  pass3 = clock() - start;
+  if (LOGICAL(verbose)[0])
+    Rprintf("Final step in generating lookup ... done in %8.3f seconds\n", 1.0*(pass3)/CLOCKS_PER_SEC);
+  return(R_NilValue);
 }
 
 SEXP overlaps(SEXP ux, SEXP imatches, SEXP multArg, SEXP typeArg, SEXP nomatchArg, SEXP verbose) {
 
-    R_len_t i,j,k,m,uxcols=LENGTH(ux),rows=length(VECTOR_ELT(imatches,0));
-    int nomatch = INTEGER(nomatchArg)[0], totlen=0, len, thislen, wlen=0;
-    int *from   = (int *)INTEGER(VECTOR_ELT(imatches, 0));
-    int *to     = (int *)INTEGER(VECTOR_ELT(imatches, 1));
-    int *len1   = (int *)INTEGER(VECTOR_ELT(ux, uxcols-2));
-    int *len2   = (int *)INTEGER(VECTOR_ELT(ux, uxcols-1));
-    SEXP lookup = VECTOR_ELT(ux, uxcols-4);
-    SEXP type_lookup = VECTOR_ELT(ux, uxcols-3);
-    SEXP ans, f1__, f2__, tmp1, tmp2;
-    clock_t end1, end2, start;
-    enum {ALL, FIRST, LAST} mult = ALL;
-    enum {ANY, WITHIN, START, END, EQUAL} type = ANY;
-
-    if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "all"))  mult = ALL;
-    else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "first")) mult = FIRST;
-    else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "last")) mult = LAST;
-    else error("Internal error: invalid value for 'mult'; this should have been caught before. Please report to datatable-help");
-
-    if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "any"))  type = ANY;
-    else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "within")) type = WITHIN;
-    else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "start")) type = START;
-    else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "end")) type = END;
-    else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "equal")) type = EQUAL;
-    else error("Internal error: invalid value for 'type'; this should have been caught before. Please report to datatable-help");
-
-    // As a first pass get the final length, so that we can allocate up-front and not deal with Calloc + Realloc + size calculation hassle
-    // Checked the time for this loop on realisitc data (81m reads) and took 0.27 seconds! No excuses ;).
-    start = clock();
-    if (mult == ALL) {
-        totlen=0;
-        switch (type) {
-            case START: case END:
-            for (i=0; i<rows; i++)
-                totlen += (from[i] > 0 && len2[from[i]-1]) ? len2[from[i]-1] : 1;
-            break;
-
-            case EQUAL:
-            for (i=0; i<rows; i++) {
-                len = totlen; wlen=0, j=0, m=0;
-                k = (from[i]>0) ? from[i] : 1;
-                if (k == to[i]) {
-                    wlen = len1[k-1];
-                } else if (k < to[i]) {
-                    tmp1 = VECTOR_ELT(lookup, k-1);
-                    tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
-                    while (j<len1[k-1] && m<len2[to[i]-1]) {
-                        if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
-                            ++wlen; ++j; ++m;
-                        } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
-                            break;
-                        } else ++j;
-                    }
-                }
-                totlen += wlen;
-                if (len == totlen)
-                    ++totlen;
-            }
-            break;
-
-            case ANY:
-            for (i=0; i<rows; i++) {
-                len = totlen;
-                // k = (from[i] > 0) ? from[i] : 1;
-                k = from[i];
-                if (k<=to[i])
-                    totlen += len1[k-1];
-                for (j=k+1; j<=to[i]; j++)
-                    totlen += len2[j-1];
-                if (len == totlen)
-                    ++totlen;
-            }
-            break;
-
-            case WITHIN:
-            for (i=0; i<rows; i++) {
-                len = totlen; j=0; m=0;
-                k = from[i];
-                if (k > 0) {
-                    if (k == to[i]) {
-                        totlen += len1[k-1];
-                    } else if (k < to[i]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        tmp2 = VECTOR_ELT(lookup, to[i]-1);
-                        while (j<len1[k-1] && m<len1[to[i]-1]) {
-                            if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
-                                ++totlen; ++j; ++m;
-                            } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
-                                ++m;
-                            } else ++j;
-                        }
-                    }
-                }
-                if (len == totlen)
-                    ++totlen;
-            }
-            break;
+  R_len_t i,j,k,m,uxcols=LENGTH(ux),rows=length(VECTOR_ELT(imatches,0));
+  int nomatch = INTEGER(nomatchArg)[0], totlen=0, len, thislen, wlen=0;
+  int *from   = (int *)INTEGER(VECTOR_ELT(imatches, 0));
+  int *to     = (int *)INTEGER(VECTOR_ELT(imatches, 1));
+  int *len1   = (int *)INTEGER(VECTOR_ELT(ux, uxcols-2));
+  int *len2   = (int *)INTEGER(VECTOR_ELT(ux, uxcols-1));
+  SEXP lookup = VECTOR_ELT(ux, uxcols-4);
+  SEXP type_lookup = VECTOR_ELT(ux, uxcols-3);
+  SEXP ans, f1__, f2__, tmp1, tmp2;
+  clock_t end1, end2, start;
+  enum {ALL, FIRST, LAST} mult = ALL;
+  enum {ANY, WITHIN, START, END, EQUAL} type = ANY;
+
+  if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "all"))  mult = ALL;
+  else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "first")) mult = FIRST;
+  else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "last")) mult = LAST;
+  else error("Internal error: invalid value for 'mult'; this should have been caught before. Please report to datatable-help");
+
+  if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "any"))  type = ANY;
+  else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "within")) type = WITHIN;
+  else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "start")) type = START;
+  else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "end")) type = END;
+  else if (!strcmp(CHAR(STRING_ELT(typeArg, 0)), "equal")) type = EQUAL;
+  else error("Internal error: invalid value for 'type'; this should have been caught before. Please report to datatable-help");
+
+  // As a first pass get the final length, so that we can allocate up-front and not deal with Calloc + Realloc + size calculation hassle
+  // Checked the time for this loop on realisitc data (81m reads) and took 0.27 seconds! No excuses ;).
+  start = clock();
+  if (mult == ALL) {
+    totlen=0;
+    switch (type) {
+    case START: case END:
+      for (i=0; i<rows; i++)
+        totlen += (from[i] > 0 && len2[from[i]-1]) ? len2[from[i]-1] : 1;
+      break;
+
+    case EQUAL:
+      for (i=0; i<rows; i++) {
+        len = totlen; wlen=0, j=0, m=0;
+        k = (from[i]>0) ? from[i] : 1;
+        if (k == to[i]) {
+          wlen = len1[k-1];
+        } else if (k < to[i]) {
+          tmp1 = VECTOR_ELT(lookup, k-1);
+          tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
+          while (j<len1[k-1] && m<len2[to[i]-1]) {
+            if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
+              ++wlen; ++j; ++m;
+            } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
+              break;
+            } else ++j;
+          }
         }
-    } else totlen = rows;
-    end1 = clock() - start;
-    if (LOGICAL(verbose)[0])
-        Rprintf("First pass on calculating lengths in overlaps ... done in %8.3f seconds\n", 1.0*(end1)/CLOCKS_PER_SEC);
-
-    // ans[0] is the the position of 'query' and ans[1] is that of 'subject'
-    // allocate f1__ and f2__ and assign 'nomatch' to f2__
-    ans = PROTECT(allocVector(VECSXP, 2));
-    f1__ = allocVector(INTSXP, totlen);
-    SET_VECTOR_ELT(ans, 0, f1__);
-    f2__ = allocVector(INTSXP, totlen);
-    SET_VECTOR_ELT(ans, 1, f2__);
-    thislen=0;
-    start = clock();
-
-    // switching mult=ALL,FIRST,LAST separately to
-    //   - enhance performance for special cases, and
-    //   - easy to fix any bugs in the future
-    switch (mult) {
-        case ALL:
-        switch (type) {
-            case START : case END :
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                if (from[i] > 0) {
-                    k = from[i];
-                    tmp2 = VECTOR_ELT(type_lookup, k-1);
-                    for (j=0; j<len2[k-1]; j++) {
-                        INTEGER(f1__)[thislen] = i+1;
-                        INTEGER(f2__)[thislen] = INTEGER(tmp2)[j];
-                        ++thislen;
-                    }
-                }
-                if (len == thislen) {
-                    INTEGER(f1__)[thislen] = i+1;
-                    INTEGER(f2__)[thislen] = nomatch;
-                    ++thislen;
-                }
-            }
-            break;
-
-            case EQUAL :
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                if (from[i] > 0 && to[i] > 0) {
-                    k = from[i];
-                    if (k == to[i]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
-                        for (j=0; j<len1[k-1]; j++) {
-                            INTEGER(f1__)[thislen] = i+1;
-                            INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
-                            ++thislen;
-                        }
-                    } else if (k < to[i]) {
-                        j=0; m=0;
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
-                        while (j<len1[k-1] && m<len2[to[i]-1]) {
-                            if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
-                                INTEGER(f1__)[thislen] = i+1;
-                                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
-                                 ++thislen; ++j; ++m;
-                             } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
-                                 ++m;
-                             } else ++j;
-                         }
-                     }
-                 }
-                 if (len == thislen) {
-                     INTEGER(f1__)[thislen] = i+1;
-                     INTEGER(f2__)[thislen] = nomatch;
-                     ++thislen;
-                 }
+        totlen += wlen;
+        if (len == totlen)
+          ++totlen;
+      }
+      break;
+
+    case ANY:
+      for (i=0; i<rows; i++) {
+        len = totlen;
+        // k = (from[i] > 0) ? from[i] : 1;
+        k = from[i];
+        if (k<=to[i])
+          totlen += len1[k-1];
+        for (j=k+1; j<=to[i]; j++)
+          totlen += len2[j-1];
+        if (len == totlen)
+          ++totlen;
+      }
+      break;
+
+    case WITHIN:
+      for (i=0; i<rows; i++) {
+        len = totlen; j=0; m=0;
+        k = from[i];
+        if (k > 0) {
+          if (k == to[i]) {
+            totlen += len1[k-1];
+          } else if (k < to[i]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            tmp2 = VECTOR_ELT(lookup, to[i]-1);
+            while (j<len1[k-1] && m<len1[to[i]-1]) {
+              if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
+                ++totlen; ++j; ++m;
+              } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
+                ++m;
+              } else ++j;
             }
-            break;
-
-            case ANY :
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                // k = (from[i]>0) ? from[i] : 1;
-                k = from[i];
-                if (k<=to[i]) {
-                    tmp1 = VECTOR_ELT(lookup, k-1);
-                    for (m=0; m<len1[k-1]; m++) {
-                        INTEGER(f1__)[thislen] = i+1;
-                        INTEGER(f2__)[thislen] = INTEGER(tmp1)[m];
-                        ++thislen;
-                    }
-                }
-                for (j=k+1; j<=to[i]; j++) {
-                    tmp2 = VECTOR_ELT(type_lookup, j-1);
-                    for (m=0; m<len2[j-1]; m++) {
-                        INTEGER(f1__)[thislen] = i+1;
-                        INTEGER(f2__)[thislen] = INTEGER(tmp2)[m];
-                        ++thislen;
-                    }
-                }
-                // dint go through any loops above
-                if (len == thislen) {
-                    INTEGER(f1__)[thislen] = i+1;
-                    INTEGER(f2__)[thislen] = nomatch;
-                    ++thislen;
-                }
-            }
-            break;
-
-            case WITHIN :
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                k=from[i];
-                if (k > 0) {
-                    if (k == to[i]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        for (j=0; j<len1[k-1]; j++) {
-                            INTEGER(f1__)[thislen] = i+1;
-                            INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
-                            ++thislen;
-                        }
-                    } else if (k < to[i]) {
-                        j=0; m=0;
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        tmp2 = VECTOR_ELT(lookup, to[i]-1);
-                        while (j<len1[k-1] && m<len1[to[i]-1]) {
-                            if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
-                                INTEGER(f1__)[thislen] = i+1;
-                                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
-                                 ++thislen; ++j; ++m;
-                             } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
-                                 ++m;
-                             } else ++j;
-                         }
-                     }
-                 }
-                 if (len == thislen) {
-                     INTEGER(f1__)[thislen] = i+1;
-                     INTEGER(f2__)[thislen] = nomatch;
-                     ++thislen;
-                 }
-            }
-            break;
+          }
         }
-        break;
-
-        case FIRST:
-        switch (type) {
-            case START: case END:
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                INTEGER(f1__)[thislen] = i+1;
-                k = (from[i]>0) ? from[i] : 1;
-                if (k <= to[i]) { // len1[k-1] is equal to len2[k-1] and will always be >0, so no length check necessary.
-                    tmp1 = VECTOR_ELT(lookup, k-1);
-                    INTEGER(f2__)[thislen] = INTEGER(tmp1)[0];
-                    ++thislen;
-                }
-                if (len == thislen) {
-                    INTEGER(f2__)[thislen] = nomatch;
-                    ++thislen;
-                }
+        if (len == totlen)
+          ++totlen;
+      }
+      break;
+    }
+  } else totlen = rows;
+  end1 = clock() - start;
+  if (LOGICAL(verbose)[0])
+    Rprintf("First pass on calculating lengths in overlaps ... done in %8.3f seconds\n", 1.0*(end1)/CLOCKS_PER_SEC);
+
+  // ans[0] is the the position of 'query' and ans[1] is that of 'subject'
+  // allocate f1__ and f2__ and assign 'nomatch' to f2__
+  ans = PROTECT(allocVector(VECSXP, 2));
+  f1__ = allocVector(INTSXP, totlen);
+  SET_VECTOR_ELT(ans, 0, f1__);
+  f2__ = allocVector(INTSXP, totlen);
+  SET_VECTOR_ELT(ans, 1, f2__);
+  thislen=0;
+  start = clock();
+
+  // switching mult=ALL,FIRST,LAST separately to
+  //   - enhance performance for special cases, and
+  //   - easy to fix any bugs in the future
+  switch (mult) {
+  case ALL:
+    switch (type) {
+    case START : case END :
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        if (from[i] > 0) {
+          k = from[i];
+          tmp2 = VECTOR_ELT(type_lookup, k-1);
+          for (j=0; j<len2[k-1]; j++) {
+            INTEGER(f1__)[thislen] = i+1;
+            INTEGER(f2__)[thislen] = INTEGER(tmp2)[j];
+            ++thislen;
+          }
+        }
+        if (len == thislen) {
+          INTEGER(f1__)[thislen] = i+1;
+          INTEGER(f2__)[thislen] = nomatch;
+          ++thislen;
+        }
+      }
+      break;
+
+    case EQUAL :
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        if (from[i] > 0 && to[i] > 0) {
+          k = from[i];
+          if (k == to[i]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
+            for (j=0; j<len1[k-1]; j++) {
+              INTEGER(f1__)[thislen] = i+1;
+              INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
+              ++thislen;
             }
-            break;
-
-            case EQUAL :
-            for (i=0; i<rows; i++) {
-                len = thislen;
+          } else if (k < to[i]) {
+            j=0; m=0;
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
+            while (j<len1[k-1] && m<len2[to[i]-1]) {
+              if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
                 INTEGER(f1__)[thislen] = i+1;
-                if (from[i] > 0 && to[i] > 0) {
-                    k = from[i];
-                    if (k == to[i]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        INTEGER(f2__)[thislen] = INTEGER(tmp1)[0];
-                        ++thislen;
-                    } else if (k < to[i]) {
-                        j=0; m=0;
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
-                        while (j<len1[k-1] && m<len2[to[i]-1]) {
-                            if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
-                                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
-                                 ++thislen; ++j; ++m;
-                                 break;
-                             } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
-                                 ++m;
-                             } else ++j;
-                         }
-                     }
-                 }
-                 if (len == thislen) {
-                     INTEGER(f2__)[thislen] = nomatch;
-                     ++thislen;
-                 }
+                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
+                 ++thislen; ++j; ++m;
+               } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
+                 ++m;
+               } else ++j;
+             }
+           }
+         }
+         if (len == thislen) {
+           INTEGER(f1__)[thislen] = i+1;
+           INTEGER(f2__)[thislen] = nomatch;
+           ++thislen;
+         }
+      }
+      break;
+
+    case ANY :
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        // k = (from[i]>0) ? from[i] : 1;
+        k = from[i];
+        if (k<=to[i]) {
+          tmp1 = VECTOR_ELT(lookup, k-1);
+          for (m=0; m<len1[k-1]; m++) {
+            INTEGER(f1__)[thislen] = i+1;
+            INTEGER(f2__)[thislen] = INTEGER(tmp1)[m];
+            ++thislen;
+          }
+        }
+        for (j=k+1; j<=to[i]; j++) {
+          tmp2 = VECTOR_ELT(type_lookup, j-1);
+          for (m=0; m<len2[j-1]; m++) {
+            INTEGER(f1__)[thislen] = i+1;
+            INTEGER(f2__)[thislen] = INTEGER(tmp2)[m];
+            ++thislen;
+          }
+        }
+        // dint go through any loops above
+        if (len == thislen) {
+          INTEGER(f1__)[thislen] = i+1;
+          INTEGER(f2__)[thislen] = nomatch;
+          ++thislen;
+        }
+      }
+      break;
+
+    case WITHIN :
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        k=from[i];
+        if (k > 0) {
+          if (k == to[i]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            for (j=0; j<len1[k-1]; j++) {
+              INTEGER(f1__)[thislen] = i+1;
+              INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
+              ++thislen;
             }
-            break;
-
-            case ANY:
-            for (i=0; i<rows; i++) {
-                len = thislen;
+          } else if (k < to[i]) {
+            j=0; m=0;
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            tmp2 = VECTOR_ELT(lookup, to[i]-1);
+            while (j<len1[k-1] && m<len1[to[i]-1]) {
+              if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
                 INTEGER(f1__)[thislen] = i+1;
-                // k = (from[i]>0) ? from[i] : 1;
-                k = from[i];
-                for (j=k; j<=to[i]; j++) {
-                    if (len2[j-1]) {
-                        tmp2 = VECTOR_ELT(type_lookup, j-1);
-                        INTEGER(f2__)[thislen] = INTEGER(tmp2)[0];
-                        ++thislen;
-                        break;
-                    }
-                }
-                if (len == thislen) {
-                    INTEGER(f2__)[thislen] = nomatch;
-                    ++thislen;
-                }
-            }
-            break;
+                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
+                 ++thislen; ++j; ++m;
+               } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
+                 ++m;
+               } else ++j;
+             }
+           }
+         }
+         if (len == thislen) {
+           INTEGER(f1__)[thislen] = i+1;
+           INTEGER(f2__)[thislen] = nomatch;
+           ++thislen;
+         }
+      }
+      break;
+    }
+    break;
 
-            case WITHIN:
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                INTEGER(f1__)[thislen] = i+1;
-                k = from[i];
-                if (k > 0) {
-                    if (k == to[i] && len1[k-1]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        INTEGER(f2__)[thislen] = INTEGER(tmp1)[0];
-                        ++thislen;
-                    } else if (k < to[i]) {
-                        j=0; m=0;
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        tmp2 = VECTOR_ELT(lookup, to[i]-1);
-                        while (j<len1[k-1] && m<len1[to[i]-1]) {
-                            if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
-                                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
-                                 ++thislen; ++j; ++m;
-                                 break;
-                             } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
-                                 ++m;;
-                             } else ++j;
-                         }
-                     }
-                }
-                if (len == thislen) {
-                    INTEGER(f2__)[thislen] = nomatch;
-                    ++thislen;
-                }
-            }
-            break;
+  case FIRST:
+    switch (type) {
+      case START: case END:
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        INTEGER(f1__)[thislen] = i+1;
+        k = (from[i]>0) ? from[i] : 1;
+        if (k <= to[i]) { // len1[k-1] is equal to len2[k-1] and will always be >0, so no length check necessary.
+          tmp1 = VECTOR_ELT(lookup, k-1);
+          INTEGER(f2__)[thislen] = INTEGER(tmp1)[0];
+          ++thislen;
         }
-        break;
-
-        case LAST:
-        switch (type) {
-            case START: case END:
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                INTEGER(f1__)[thislen] = i+1;
-                k = (from[i]>0) ? from[i] : 1;
-                if (k <= to[i]) { // len1[k-1] is equal to len2[k-1] and will always be >0, so no length check necessary.
-                    tmp1 = VECTOR_ELT(lookup, k-1);
-                    INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
-                    ++thislen;
-                }
-                if (len == thislen) {
-                    INTEGER(f2__)[thislen] = nomatch;
-                    ++thislen;
-                }
-            }
-            break;
-
-            case EQUAL :
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                INTEGER(f1__)[thislen] = i+1;
-                if (from[i] > 0 && to[i] > 0) {
-                    k = from[i];
-                    if (k == to[i]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
-                        ++thislen;
-                    } else if (k < to[i]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
-                        j=len1[k-1]-1; m=len2[k-1]-1;
-                        while (j>=0 && m>=0) {
-                            if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
-                                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
-                                 ++thislen; --j; --m;
-                                 break;
-                             } else if ( INTEGER(tmp1)[j] < INTEGER(tmp2)[m] ) {
-                                 --m;
-                             } else --j;
-                         }
-                     }
-                 }
-                 if (len == thislen) {
-                     INTEGER(f2__)[thislen] = nomatch;
-                     ++thislen;
-                 }
-            }
+        if (len == thislen) {
+          INTEGER(f2__)[thislen] = nomatch;
+          ++thislen;
+        }
+      }
+      break;
+
+    case EQUAL :
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        INTEGER(f1__)[thislen] = i+1;
+        if (from[i] > 0 && to[i] > 0) {
+          k = from[i];
+          if (k == to[i]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            INTEGER(f2__)[thislen] = INTEGER(tmp1)[0];
+            ++thislen;
+          } else if (k < to[i]) {
+            j=0; m=0;
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
+            while (j<len1[k-1] && m<len2[to[i]-1]) {
+              if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
+                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
+                 ++thislen; ++j; ++m;
+                 break;
+               } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
+                 ++m;
+               } else ++j;
+             }
+           }
+         }
+         if (len == thislen) {
+           INTEGER(f2__)[thislen] = nomatch;
+           ++thislen;
+         }
+      }
+      break;
+
+    case ANY:
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        INTEGER(f1__)[thislen] = i+1;
+        // k = (from[i]>0) ? from[i] : 1;
+        k = from[i];
+        for (j=k; j<=to[i]; j++) {
+          if (len2[j-1]) {
+            tmp2 = VECTOR_ELT(type_lookup, j-1);
+            INTEGER(f2__)[thislen] = INTEGER(tmp2)[0];
+            ++thislen;
             break;
+          }
+        }
+        if (len == thislen) {
+          INTEGER(f2__)[thislen] = nomatch;
+          ++thislen;
+        }
+      }
+      break;
+
+    case WITHIN:
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        INTEGER(f1__)[thislen] = i+1;
+        k = from[i];
+        if (k > 0) {
+          if (k == to[i] && len1[k-1]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            INTEGER(f2__)[thislen] = INTEGER(tmp1)[0];
+            ++thislen;
+          } else if (k < to[i]) {
+            j=0; m=0;
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            tmp2 = VECTOR_ELT(lookup, to[i]-1);
+            while (j<len1[k-1] && m<len1[to[i]-1]) {
+              if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
+                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
+                 ++thislen; ++j; ++m;
+                 break;
+               } else if ( INTEGER(tmp1)[j] > INTEGER(tmp2)[m] ) {
+                 ++m;;
+               } else ++j;
+             }
+           }
+        }
+        if (len == thislen) {
+          INTEGER(f2__)[thislen] = nomatch;
+          ++thislen;
+        }
+      }
+      break;
+    }
+    break;
 
-            // OLD logic for 'any,last' which had to check for maximum for each 'i'. Better logic below.
-            // for 'first' we need to just get the minimum of first non-zero-length element, but not the same case for 'last'.
-            // We've to loop over from[i]:to[i] and get maximum of all tmp2 values (each is of length 1 already conveniently set uo) in that range
-            // case ANY:
-            // for (i=0; i<rows; i++) {
-            //     len = thislen;
-            //     INTEGER(f1__)[thislen] = i+1;
-            //     INTEGER(f2__)[thislen] = 0;
-            //     // k = (from[i]>0) ? from[i] : 1;
-            //     k = from[i];
-            //     for (j=k; j<=to[i]; j++) {
-            //         if (len2[j-1]) {
-            //             tmp2 = VECTOR_ELT(type_lookup, j-1);
-            //             INTEGER(f2__)[thislen] = (INTEGER(f2__)[thislen] < INTEGER(tmp2)[len2[j-1]-1]) ? INTEGER(tmp2)[len2[j-1]-1] : INTEGER(f2__)[thislen];
-            //         }
-            //     }
-            //     if (INTEGER(f2__)[thislen] == 0)
-            //         INTEGER(f2__)[thislen] = nomatch;
-            //     ++thislen;
-            // }
-            // break;
-
-            case ANY:
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                INTEGER(f1__)[thislen] = i+1;
-                // k = (from[i]>0) ? from[i] : 1;
-                k = from[i];
-                if (k <= to[i]) {
-                    if (k==to[i] && len1[k-1]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
-                        ++thislen;
-                    } else {
-                        for (j=to[i]; j>k; j--) {
-                            if (len2[j-1]) {
-                                tmp2 = VECTOR_ELT(type_lookup, j-1);
-                                INTEGER(f2__)[thislen] = INTEGER(tmp2)[0]; // tmp2 will be length 1
-                                ++thislen; break;
-                            }
-                        }
-                        if (len == thislen && len1[k-1]) {
-                            tmp1 = VECTOR_ELT(lookup, k-1);
-                            INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
-                            ++thislen;
-                        }
-                    }
-                }
-                if (len == thislen) {
-                    INTEGER(f2__)[thislen] = nomatch;
-                    ++thislen;
-                }
+  case LAST:
+    switch (type) {
+    case START: case END:
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        INTEGER(f1__)[thislen] = i+1;
+        k = (from[i]>0) ? from[i] : 1;
+        if (k <= to[i]) { // len1[k-1] is equal to len2[k-1] and will always be >0, so no length check necessary.
+          tmp1 = VECTOR_ELT(lookup, k-1);
+          INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
+          ++thislen;
+        }
+        if (len == thislen) {
+          INTEGER(f2__)[thislen] = nomatch;
+          ++thislen;
+        }
+      }
+      break;
+
+    case EQUAL :
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        INTEGER(f1__)[thislen] = i+1;
+        if (from[i] > 0 && to[i] > 0) {
+          k = from[i];
+          if (k == to[i]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
+            ++thislen;
+          } else if (k < to[i]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            tmp2 = VECTOR_ELT(type_lookup, to[i]-1);
+            j=len1[k-1]-1; m=len2[k-1]-1;
+            while (j>=0 && m>=0) {
+              if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
+                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
+                 ++thislen; --j; --m;
+                 break;
+               } else if ( INTEGER(tmp1)[j] < INTEGER(tmp2)[m] ) {
+                 --m;
+               } else --j;
+             }
+           }
+         }
+         if (len == thislen) {
+           INTEGER(f2__)[thislen] = nomatch;
+           ++thislen;
+         }
+      }
+      break;
+
+      // OLD logic for 'any,last' which had to check for maximum for each 'i'. Better logic below.
+      // for 'first' we need to just get the minimum of first non-zero-length element, but not the same case for 'last'.
+      // We've to loop over from[i]:to[i] and get maximum of all tmp2 values (each is of length 1 already conveniently set uo) in that range
+      // case ANY:
+      // for (i=0; i<rows; i++) {
+      //     len = thislen;
+      //     INTEGER(f1__)[thislen] = i+1;
+      //     INTEGER(f2__)[thislen] = 0;
+      //     // k = (from[i]>0) ? from[i] : 1;
+      //     k = from[i];
+      //     for (j=k; j<=to[i]; j++) {
+      //         if (len2[j-1]) {
+      //             tmp2 = VECTOR_ELT(type_lookup, j-1);
+      //             INTEGER(f2__)[thislen] = (INTEGER(f2__)[thislen] < INTEGER(tmp2)[len2[j-1]-1]) ? INTEGER(tmp2)[len2[j-1]-1] : INTEGER(f2__)[thislen];
+      //         }
+      //     }
+      //     if (INTEGER(f2__)[thislen] == 0)
+      //         INTEGER(f2__)[thislen] = nomatch;
+      //     ++thislen;
+      // }
+      // break;
+
+    case ANY:
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        INTEGER(f1__)[thislen] = i+1;
+        // k = (from[i]>0) ? from[i] : 1;
+        k = from[i];
+        if (k <= to[i]) {
+          if (k==to[i] && len1[k-1]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
+            ++thislen;
+          } else {
+            for (j=to[i]; j>k; j--) {
+              if (len2[j-1]) {
+                tmp2 = VECTOR_ELT(type_lookup, j-1);
+                INTEGER(f2__)[thislen] = INTEGER(tmp2)[0]; // tmp2 will be length 1
+                ++thislen; break;
+              }
             }
-            break;
-
-            case WITHIN:
-            for (i=0; i<rows; i++) {
-                len = thislen;
-                INTEGER(f1__)[thislen] = i+1;
-                k = from[i];
-                if (k > 0) {
-                    if (k == to[i] && len1[k-1]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
-                        ++thislen;
-                    } else if (k < to[i]) {
-                        tmp1 = VECTOR_ELT(lookup, k-1);
-                        tmp2 = VECTOR_ELT(lookup, to[i]-1);
-                        j=len1[k-1]-1; m=len1[to[i]-1]-1;
-                        while (j>=0 && m>=0) {
-                            if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
-                                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
-                                 ++thislen; --j; --m;
-                                 break;
-                             } else if ( INTEGER(tmp1)[j] < INTEGER(tmp2)[m] ) {
-                                 --m;
-                             } else --j;
-                         }
-                     }
-                 }
-                 if (len == thislen) {
-                     INTEGER(f2__)[thislen] = nomatch;
-                     ++thislen;
-                 }
+            if (len == thislen && len1[k-1]) {
+              tmp1 = VECTOR_ELT(lookup, k-1);
+              INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
+              ++thislen;
             }
-            break;
+          }
         }
-        break;
+        if (len == thislen) {
+          INTEGER(f2__)[thislen] = nomatch;
+          ++thislen;
+        }
+      }
+      break;
+
+    case WITHIN:
+      for (i=0; i<rows; i++) {
+        len = thislen;
+        INTEGER(f1__)[thislen] = i+1;
+        k = from[i];
+        if (k > 0) {
+          if (k == to[i] && len1[k-1]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            INTEGER(f2__)[thislen] = INTEGER(tmp1)[len1[k-1]-1];
+            ++thislen;
+          } else if (k < to[i]) {
+            tmp1 = VECTOR_ELT(lookup, k-1);
+            tmp2 = VECTOR_ELT(lookup, to[i]-1);
+            j=len1[k-1]-1; m=len1[to[i]-1]-1;
+            while (j>=0 && m>=0) {
+              if ( INTEGER(tmp1)[j] == INTEGER(tmp2)[m] ) {
+                INTEGER(f2__)[thislen] = INTEGER(tmp1)[j];
+                 ++thislen; --j; --m;
+                 break;
+               } else if ( INTEGER(tmp1)[j] < INTEGER(tmp2)[m] ) {
+                 --m;
+               } else --j;
+             }
+           }
+         }
+         if (len == thislen) {
+           INTEGER(f2__)[thislen] = nomatch;
+           ++thislen;
+         }
+      }
+      break;
     }
-    end2 = clock() - start;
-    if (LOGICAL(verbose)[0])
-        Rprintf("Final step, fetching indices in overlaps ... done in %8.3f seconds\n", 1.0*(end2)/CLOCKS_PER_SEC);
-    UNPROTECT(1);
-    return(ans);
+    break;
+  }
+  end2 = clock() - start;
+  if (LOGICAL(verbose)[0])
+    Rprintf("Final step, fetching indices in overlaps ... done in %8.3f seconds\n", 1.0*(end2)/CLOCKS_PER_SEC);
+  UNPROTECT(1);
+  return(ans);
 }
diff --git a/src/init.c b/src/init.c
index 7f9a511932..1b62c9d15d 100644
--- a/src/init.c
+++ b/src/init.c
@@ -167,91 +167,91 @@ R_ExternalMethodDef externalMethods[] = {
 void attribute_visible R_init_datatable(DllInfo *info)
 // relies on pkg/src/Makevars to mv data.table.so to datatable.so
 {
-    R_registerRoutines(info, NULL, callMethods, NULL, externalMethods);
-    R_useDynamicSymbols(info, FALSE);
-    setSizes();
-    const char *msg = "... failed. Please forward this message to maintainer('data.table').";
-    if ((int)NA_INTEGER != (int)INT_MIN) error("Checking NA_INTEGER [%d] == INT_MIN [%d] %s", NA_INTEGER, INT_MIN, msg);
-    if ((int)NA_INTEGER != (int)NA_LOGICAL) error("Checking NA_INTEGER [%d] == NA_LOGICAL [%d] %s", NA_INTEGER, NA_LOGICAL, msg);
-    if (sizeof(int) != 4) error("Checking sizeof(int) [%d] is 4 %s", sizeof(int), msg);
-    if (sizeof(double) != 8) error("Checking sizeof(double) [%d] is 8 %s", sizeof(double), msg);     // 8 on both 32bit and 64bit
-    // alignof not available in C99: if (alignof(double) != 8) error("Checking alignof(double) [%d] is 8 %s", alignof(double), msg);  // 8 on both 32bit and 64bit
-    if (sizeof(long long) != 8) error("Checking sizeof(long long) [%d] is 8 %s", sizeof(long long), msg);
-    if (sizeof(char *) != 4 && sizeof(char *) != 8) error("Checking sizeof(pointer) [%d] is 4 or 8 %s", sizeof(char *), msg);
-    if (sizeof(SEXP) != sizeof(char *)) error("Checking sizeof(SEXP) [%d] == sizeof(pointer) [%d] %s", sizeof(SEXP), sizeof(char *), msg);
-    if (sizeof(uint64_t) != 8) error("Checking sizeof(uint64_t) [%d] is 8 %s", sizeof(uint64_t), msg);
-    if (sizeof(int64_t) != 8) error("Checking sizeof(int64_t) [%d] is 8 %s", sizeof(int64_t), msg);
-    if (sizeof(signed char) != 1) error("Checking sizeof(signed char) [%d] is 1 %s", sizeof(signed char), msg);
-    if (sizeof(int8_t) != 1) error("Checking sizeof(int8_t) [%d] is 1 %s", sizeof(int8_t), msg);
+  R_registerRoutines(info, NULL, callMethods, NULL, externalMethods);
+  R_useDynamicSymbols(info, FALSE);
+  setSizes();
+  const char *msg = "... failed. Please forward this message to maintainer('data.table').";
+  if ((int)NA_INTEGER != (int)INT_MIN) error("Checking NA_INTEGER [%d] == INT_MIN [%d] %s", NA_INTEGER, INT_MIN, msg);
+  if ((int)NA_INTEGER != (int)NA_LOGICAL) error("Checking NA_INTEGER [%d] == NA_LOGICAL [%d] %s", NA_INTEGER, NA_LOGICAL, msg);
+  if (sizeof(int) != 4) error("Checking sizeof(int) [%d] is 4 %s", sizeof(int), msg);
+  if (sizeof(double) != 8) error("Checking sizeof(double) [%d] is 8 %s", sizeof(double), msg);     // 8 on both 32bit and 64bit
+  // alignof not available in C99: if (alignof(double) != 8) error("Checking alignof(double) [%d] is 8 %s", alignof(double), msg);  // 8 on both 32bit and 64bit
+  if (sizeof(long long) != 8) error("Checking sizeof(long long) [%d] is 8 %s", sizeof(long long), msg);
+  if (sizeof(char *) != 4 && sizeof(char *) != 8) error("Checking sizeof(pointer) [%d] is 4 or 8 %s", sizeof(char *), msg);
+  if (sizeof(SEXP) != sizeof(char *)) error("Checking sizeof(SEXP) [%d] == sizeof(pointer) [%d] %s", sizeof(SEXP), sizeof(char *), msg);
+  if (sizeof(uint64_t) != 8) error("Checking sizeof(uint64_t) [%d] is 8 %s", sizeof(uint64_t), msg);
+  if (sizeof(int64_t) != 8) error("Checking sizeof(int64_t) [%d] is 8 %s", sizeof(int64_t), msg);
+  if (sizeof(signed char) != 1) error("Checking sizeof(signed char) [%d] is 1 %s", sizeof(signed char), msg);
+  if (sizeof(int8_t) != 1) error("Checking sizeof(int8_t) [%d] is 1 %s", sizeof(int8_t), msg);
 
-    SEXP tmp = PROTECT(allocVector(INTSXP,2));
-    if (LENGTH(tmp)!=2) error("Checking LENGTH(allocVector(INTSXP,2)) [%d] is 2 %s", LENGTH(tmp), msg);
-    if (TRUELENGTH(tmp)!=0) error("Checking TRUELENGTH(allocVector(INTSXP,2)) [%d] is 0 %s", TRUELENGTH(tmp), msg);
-    UNPROTECT(1);
+  SEXP tmp = PROTECT(allocVector(INTSXP,2));
+  if (LENGTH(tmp)!=2) error("Checking LENGTH(allocVector(INTSXP,2)) [%d] is 2 %s", LENGTH(tmp), msg);
+  if (TRUELENGTH(tmp)!=0) error("Checking TRUELENGTH(allocVector(INTSXP,2)) [%d] is 0 %s", TRUELENGTH(tmp), msg);
+  UNPROTECT(1);
 
-    // According to IEEE (http://en.wikipedia.org/wiki/IEEE_754-1985#Zero) we can rely on 0.0 being all 0 bits.
-    // But check here anyway just to be sure, just in case this answer is right (http://stackoverflow.com/a/2952680/403310).
-    int i = 314;
-    memset(&i, 0, sizeof(int));
-    if (i != 0) error("Checking memset(&i,0,sizeof(int)); i == (int)0 %s", msg);
-    unsigned int ui = 314;
-    memset(&ui, 0, sizeof(unsigned int));
-    if (ui != 0) error("Checking memset(&ui, 0, sizeof(unsigned int)); ui == (unsigned int)0 %s", msg);
-    double d = 3.14;
-    memset(&d, 0, sizeof(double));
-    if (d != 0.0) error("Checking memset(&d, 0, sizeof(double)); d == (double)0.0 %s", msg);
-    long double ld = 3.14;
-    memset(&ld, 0, sizeof(long double));
-    if (ld != 0.0) error("Checking memset(&ld, 0, sizeof(long double)); ld == (long double)0.0 %s", msg);
+  // According to IEEE (http://en.wikipedia.org/wiki/IEEE_754-1985#Zero) we can rely on 0.0 being all 0 bits.
+  // But check here anyway just to be sure, just in case this answer is right (http://stackoverflow.com/a/2952680/403310).
+  int i = 314;
+  memset(&i, 0, sizeof(int));
+  if (i != 0) error("Checking memset(&i,0,sizeof(int)); i == (int)0 %s", msg);
+  unsigned int ui = 314;
+  memset(&ui, 0, sizeof(unsigned int));
+  if (ui != 0) error("Checking memset(&ui, 0, sizeof(unsigned int)); ui == (unsigned int)0 %s", msg);
+  double d = 3.14;
+  memset(&d, 0, sizeof(double));
+  if (d != 0.0) error("Checking memset(&d, 0, sizeof(double)); d == (double)0.0 %s", msg);
+  long double ld = 3.14;
+  memset(&ld, 0, sizeof(long double));
+  if (ld != 0.0) error("Checking memset(&ld, 0, sizeof(long double)); ld == (long double)0.0 %s", msg);
 
-    // Check unsigned cast used in fread.c. This isn't overflow/underflow, just cast.
-    if ((uint_fast8_t)('0'-'/') != 1) error("The ascii character '/' is not just before '0'");
-    if ((uint_fast8_t)('/'-'0') < 10) error("The C expression (uint_fast8_t)('/'-'0')<10 is true. Should be false.");
-    if ((uint_fast8_t)(':'-'9') != 1) error("The ascii character ':' is not just after '9'");
-    if ((uint_fast8_t)('9'-':') < 10) error("The C expression (uint_fast8_t)('9'-':')<10 is true. Should be false.");
+  // Check unsigned cast used in fread.c. This isn't overflow/underflow, just cast.
+  if ((uint_fast8_t)('0'-'/') != 1) error("The ascii character '/' is not just before '0'");
+  if ((uint_fast8_t)('/'-'0') < 10) error("The C expression (uint_fast8_t)('/'-'0')<10 is true. Should be false.");
+  if ((uint_fast8_t)(':'-'9') != 1) error("The ascii character ':' is not just after '9'");
+  if ((uint_fast8_t)('9'-':') < 10) error("The C expression (uint_fast8_t)('9'-':')<10 is true. Should be false.");
 
-    // Variables rather than #define for NA_INT64 to ensure correct usage; i.e. not casted
-    NA_INT64_LL = LLONG_MIN;
-    NA_INT64_D = LLtoD(NA_INT64_LL);
-    if (NA_INT64_LL != DtoLL(NA_INT64_D)) error("Conversion of NA_INT64 via double failed %lld!=%lld", NA_INT64_LL, DtoLL(NA_INT64_D));
-    // LLONG_MIN when punned to double is the sign bit set and then all zeros in exponent and significand i.e. -0.0
-    //   That's why we must never test for NA_INT64_D using == in double type. Must always DtoLL and compare long long types.
-    //   Assigning NA_INT64_D to a REAL is ok however.
-    if (NA_INT64_D != 0.0)  error("NA_INT64_D (negative -0.0) is not == 0.0.");
-    if (NA_INT64_D != -0.0) error("NA_INT64_D (negative -0.0) is not ==-0.0.");
-    if (ISNAN(NA_INT64_D)) error("ISNAN(NA_INT64_D) is TRUE but should not be");
-    if (isnan(NA_INT64_D)) error("isnan(NA_INT64_D) is TRUE but should not be");
+  // Variables rather than #define for NA_INT64 to ensure correct usage; i.e. not casted
+  NA_INT64_LL = LLONG_MIN;
+  NA_INT64_D = LLtoD(NA_INT64_LL);
+  if (NA_INT64_LL != DtoLL(NA_INT64_D)) error("Conversion of NA_INT64 via double failed %lld!=%lld", NA_INT64_LL, DtoLL(NA_INT64_D));
+  // LLONG_MIN when punned to double is the sign bit set and then all zeros in exponent and significand i.e. -0.0
+  //   That's why we must never test for NA_INT64_D using == in double type. Must always DtoLL and compare long long types.
+  //   Assigning NA_INT64_D to a REAL is ok however.
+  if (NA_INT64_D != 0.0)  error("NA_INT64_D (negative -0.0) is not == 0.0.");
+  if (NA_INT64_D != -0.0) error("NA_INT64_D (negative -0.0) is not ==-0.0.");
+  if (ISNAN(NA_INT64_D)) error("ISNAN(NA_INT64_D) is TRUE but should not be");
+  if (isnan(NA_INT64_D)) error("isnan(NA_INT64_D) is TRUE but should not be");
 
-    setNumericRounding(PROTECT(ScalarInteger(0))); // #1642, #1728, #1463, #485
-    UNPROTECT(1);
+  setNumericRounding(PROTECT(ScalarInteger(0))); // #1642, #1728, #1463, #485
+  UNPROTECT(1);
 
-    // create needed strings in advance for speed, same techique as R_*Symbol
-    // Following R-exts 5.9.4; paragraph and example starting "Using install ..."
-    // either use PRINTNAME(install()) or R_PreserveObject(mkChar()) here.
-    char_integer64 = PRINTNAME(install("integer64"));
-    char_ITime =     PRINTNAME(install("ITime"));
-    char_Date =      PRINTNAME(install("Date"));   // used for IDate too since IDate inherits from Date
-    char_POSIXct =   PRINTNAME(install("POSIXct"));
-    char_nanotime =  PRINTNAME(install("nanotime"));
-    char_starts =    PRINTNAME(sym_starts = install("starts"));
-    if (TYPEOF(char_integer64) != CHARSXP) {
-      // checking one is enough in case of any R-devel changes
-      error("PRINTNAME(install(\"integer64\")) has returned %s not %s",
-            type2char(TYPEOF(char_integer64)), type2char(CHARSXP));
-    }
+  // create needed strings in advance for speed, same techique as R_*Symbol
+  // Following R-exts 5.9.4; paragraph and example starting "Using install ..."
+  // either use PRINTNAME(install()) or R_PreserveObject(mkChar()) here.
+  char_integer64 = PRINTNAME(install("integer64"));
+  char_ITime =     PRINTNAME(install("ITime"));
+  char_Date =      PRINTNAME(install("Date"));   // used for IDate too since IDate inherits from Date
+  char_POSIXct =   PRINTNAME(install("POSIXct"));
+  char_nanotime =  PRINTNAME(install("nanotime"));
+  char_starts =    PRINTNAME(sym_starts = install("starts"));
+  if (TYPEOF(char_integer64) != CHARSXP) {
+    // checking one is enough in case of any R-devel changes
+    error("PRINTNAME(install(\"integer64\")) has returned %s not %s",
+      type2char(TYPEOF(char_integer64)), type2char(CHARSXP));
+  }
 
-    // create commonly used symbols, same as R_*Symbol but internal to DT
-    // Not really for speed but to avoid leak in situations like setAttrib(DT, install(), allocVector()) where
-    // the allocVector() can happen first and then the install() could gc and free it before it is protected
-    // within setAttrib. Thanks to Bill Dunlap finding and reporting. Using these symbols instead of install()
-    // avoids the gc without needing an extra PROTECT and immediate UNPROTECT after the setAttrib which would
-    // look odd (and devs in future might be tempted to remove them). Avoiding passing install() to API calls
-    // keeps the code neat and readable. Also see grep's added to CRAN_Release.cmd to find such calls.
-    sym_sorted  = install("sorted");
-    sym_BY      = install(".BY");
-    sym_maxgrpn = install("maxgrpn");
+  // create commonly used symbols, same as R_*Symbol but internal to DT
+  // Not really for speed but to avoid leak in situations like setAttrib(DT, install(), allocVector()) where
+  // the allocVector() can happen first and then the install() could gc and free it before it is protected
+  // within setAttrib. Thanks to Bill Dunlap finding and reporting. Using these symbols instead of install()
+  // avoids the gc without needing an extra PROTECT and immediate UNPROTECT after the setAttrib which would
+  // look odd (and devs in future might be tempted to remove them). Avoiding passing install() to API calls
+  // keeps the code neat and readable. Also see grep's added to CRAN_Release.cmd to find such calls.
+  sym_sorted  = install("sorted");
+  sym_BY      = install(".BY");
+  sym_maxgrpn = install("maxgrpn");
 
-    avoid_openmp_hang_within_fork();
+  avoid_openmp_hang_within_fork();
 }
 
 inline Rboolean INHERITS(SEXP x, SEXP char_) {
@@ -265,9 +265,9 @@ inline Rboolean INHERITS(SEXP x, SEXP char_) {
   // ii) no attrib writes must be possible in other threads.
   SEXP class;
   if (isString(class = getAttrib(x, R_ClassSymbol))) {
-    for (int i=0; i<LENGTH(class); i++) {
-      if (STRING_ELT(class, i) == char_) return TRUE;
-    }
+  for (int i=0; i<LENGTH(class); i++) {
+    if (STRING_ELT(class, i) == char_) return TRUE;
+  }
   }
   return FALSE;
 }
diff --git a/src/inrange.c b/src/inrange.c
index 4261011ea3..7bc6571577 100644
--- a/src/inrange.c
+++ b/src/inrange.c
@@ -4,32 +4,32 @@
 
 SEXP inrange(SEXP ansArg, SEXP xoArg, SEXP startsArg, SEXP lenArg) {
 
-    int *ans = INTEGER(ansArg), *xo = INTEGER(xoArg);
-    int *starts = INTEGER(startsArg), *len = INTEGER(lenArg);
-    R_len_t i, j, n = length(startsArg), nxo = length(xoArg);
-    for (i = 0; i < n; i++) {
-        for (j = starts[i]-1; j < starts[i]-1+len[i]; j++) {
-            ans[nxo ? xo[j]-1 : j] = 1;
-        }
+  int *ans = INTEGER(ansArg), *xo = INTEGER(xoArg);
+  int *starts = INTEGER(startsArg), *len = INTEGER(lenArg);
+  R_len_t i, j, n = length(startsArg), nxo = length(xoArg);
+  for (i = 0; i < n; i++) {
+    for (j = starts[i]-1; j < starts[i]-1+len[i]; j++) {
+      ans[nxo ? xo[j]-1 : j] = 1;
     }
-    // old complicated logic which is only really useful when matches
-    // contains A LOT of overlapping indices.. rare in real examples.
-    // so switched to simpler logic above.. retaining it commented for now.
+  }
+  // old complicated logic which is only really useful when matches
+  // contains A LOT of overlapping indices.. rare in real examples.
+  // so switched to simpler logic above.. retaining it commented for now.
 
-    // R_len_t i =0,j, ss,ee,new_ss,new_ee;
-    // while(i < n && starts[i] == 0) i++;
-    // while (i < n) {
-    //     ss = starts[i]-1;
-    //     ee = ss + len[i]-1;
-    //     // Rprintf("Starting at %d, start=%d, end=%d\n", i, ss, ee);
-    //     // ss[i+1] >= ss[i] due to ordering from R-side
-    //     // if ee[i] >= ss[i+1], then there's overlap, pick largest of ee[i], ee[i+1]
-    //     while(++i < n && ee >= (new_ss = starts[i]-1)) {
-    //         new_ee = new_ss + len[i]-1;
-    //         ee = ee > new_ee ? ee : new_ee;
-    //     }
-    //     // Rprintf("Moved to %d, start=%d, end=%d\n", i, ss, ee);
-    //     for (j=ss; j<=ee; j++) ans[nxo ? xo[j]-1 : j] = 1;
-    // }
-    return (R_NilValue);
+  // R_len_t i =0,j, ss,ee,new_ss,new_ee;
+  // while(i < n && starts[i] == 0) i++;
+  // while (i < n) {
+  //     ss = starts[i]-1;
+  //     ee = ss + len[i]-1;
+  //     // Rprintf("Starting at %d, start=%d, end=%d\n", i, ss, ee);
+  //     // ss[i+1] >= ss[i] due to ordering from R-side
+  //     // if ee[i] >= ss[i+1], then there's overlap, pick largest of ee[i], ee[i+1]
+  //     while(++i < n && ee >= (new_ss = starts[i]-1)) {
+  //         new_ee = new_ss + len[i]-1;
+  //         ee = ee > new_ee ? ee : new_ee;
+  //     }
+  //     // Rprintf("Moved to %d, start=%d, end=%d\n", i, ss, ee);
+  //     for (j=ss; j<=ee; j++) ans[nxo ? xo[j]-1 : j] = 1;
+  // }
+  return (R_NilValue);
 }
diff --git a/src/openmp-utils.c b/src/openmp-utils.c
index 730b6c0c25..d6e3b70316 100644
--- a/src/openmp-utils.c
+++ b/src/openmp-utils.c
@@ -22,63 +22,63 @@ static int DTthreads = 0;
 
 int getDTthreads() {
 #ifdef _OPENMP
-    int ans = DTthreads == 0 ? omp_get_max_threads() : MIN(DTthreads, omp_get_max_threads());
-    return MAX(1, ans);
+  int ans = DTthreads == 0 ? omp_get_max_threads() : MIN(DTthreads, omp_get_max_threads());
+  return MAX(1, ans);
 #else
-    return 1;
+  return 1;
 #endif
 }
 
 SEXP getDTthreads_R() {
-    return ScalarInteger(getDTthreads());
+  return ScalarInteger(getDTthreads());
 }
 
 SEXP setDTthreads(SEXP threads) {
-    if (!isInteger(threads) || length(threads) != 1 || INTEGER(threads)[0] < 0) {
-        // catches NA too since NA is -ve
-        error("Argument to setDTthreads must be a single integer >= 0. \
-            Default 0 is recommended to use all CPU.");
-    }
-    int old = DTthreads;
-    DTthreads = INTEGER(threads)[0];
+  if (!isInteger(threads) || length(threads) != 1 || INTEGER(threads)[0] < 0) {
+    // catches NA too since NA is -ve
+    error("Argument to setDTthreads must be a single integer >= 0. \
+           Default 0 is recommended to use all CPU.");
+  }
+  int old = DTthreads;
+  DTthreads = INTEGER(threads)[0];
 #ifdef _OPENMP
-    if (omp_get_max_threads() < omp_get_thread_limit()) {
-      if (DTthreads==0) {
-        // for example after test 1705 has auto switched to single-threaded for parallel's fork,
-        // we want to return to multi-threaded.
-        // omp_set_num_threads() sets the value returned by omp_get_max_threads()
-        omp_set_num_threads(omp_get_thread_limit());
-      } else if (DTthreads > omp_get_max_threads()) {
-        omp_set_num_threads( MIN(DTthreads, omp_get_thread_limit()) );
-      }
+  if (omp_get_max_threads() < omp_get_thread_limit()) {
+    if (DTthreads==0) {
+      // for example after test 1705 has auto switched to single-threaded for parallel's fork,
+      // we want to return to multi-threaded.
+      // omp_set_num_threads() sets the value returned by omp_get_max_threads()
+      omp_set_num_threads(omp_get_thread_limit());
+    } else if (DTthreads > omp_get_max_threads()) {
+      omp_set_num_threads( MIN(DTthreads, omp_get_thread_limit()) );
     }
+  }
 #endif
-    return ScalarInteger(old);
+  return ScalarInteger(old);
 }
 
 // auto avoid deadlock when data.table called from parallel::mclapply
 void when_fork() {
 
-    // attempted workaround for Intel's OpenMP implementation which leaves threads running after
-    // parallel region; these crash when forked.
+  // attempted workaround for Intel's OpenMP implementation which leaves threads running after
+  // parallel region; these crash when forked.
 #ifdef _OPENMP
-    omp_set_num_threads(1);
+  omp_set_num_threads(1);
 #endif
 
-    // GNU OpenMP seems ok with just setting DTthreads to 1 which limits the next parallel region
-    // if data.table is used within the fork'd proceess.
-    DTthreads = 1;
+  // GNU OpenMP seems ok with just setting DTthreads to 1 which limits the next parallel region
+  // if data.table is used within the fork'd proceess.
+  DTthreads = 1;
 
-    // We used to have an after_fork() callback too, to return to multi-threaded mode after parallel's
-    // fork completes. But now in an attempt to alleviate problems propogating (likely Intel's OpenMP only)
-    // we now leave data.table in single-threaded mode after parallel's fork. User can call setDTthreads(0)
-    // to return to multi-threaded as we do in tests on Linux.
+  // We used to have an after_fork() callback too, to return to multi-threaded mode after parallel's
+  // fork completes. But now in an attempt to alleviate problems propogating (likely Intel's OpenMP only)
+  // we now leave data.table in single-threaded mode after parallel's fork. User can call setDTthreads(0)
+  // to return to multi-threaded as we do in tests on Linux.
 }
 
 void avoid_openmp_hang_within_fork() {
-    // Called once on loading data.table from init.c
+  // Called once on loading data.table from init.c
 #ifdef _OPENMP
-    pthread_atfork(&when_fork, NULL, NULL);
+  pthread_atfork(&when_fork, NULL, NULL);
 #endif
 }
 
diff --git a/src/quickselect.c b/src/quickselect.c
index 898ed4a366..ea79e79192 100644
--- a/src/quickselect.c
+++ b/src/quickselect.c
@@ -9,85 +9,85 @@
 #define SWAP(a,b) temp=(a);(a)=(b);(b)=temp;
 
 double dquickselect(double *x, int n, int k) {
-    unsigned long i,ir,j,l,mid;
-    double a,temp;
+  unsigned long i,ir,j,l,mid;
+  double a,temp;
 
-    l=0;
-    ir=n-1;
-    for(;;) {
-        if (ir <= l+1) {
-            if (ir == l+1 && x[ir] < x[l]) {
-                SWAP(x[l],x[ir]);
-            }
-        return x[k];
-        } else {
-            mid=(l+ir) >> 1;
-            SWAP(x[mid],x[l+1]);
-            if (x[l] > x[ir]) {
-                SWAP(x[l],x[ir]);
-            }
-            if (x[l+1] > x[ir]) {
-                SWAP(x[l+1],x[ir]);
-            }
-            if (x[l] > x[l+1]) {
-                SWAP(x[l],x[l+1]);
-            }
-            i=l+1;
-            j=ir;
-            a=x[l+1];
-            for (;;) {
-                do i++; while (x[i] < a);
-                do j--; while (x[j] > a);
-                if (j < i) break;
-                    SWAP(x[i],x[j]);
-            }
-            x[l+1]=x[j];
-            x[j]=a;
-            if (j >= k) ir=j-1;
-            if (j <= k) l=i;
-        }
+  l=0;
+  ir=n-1;
+  for(;;) {
+    if (ir <= l+1) {
+      if (ir == l+1 && x[ir] < x[l]) {
+        SWAP(x[l],x[ir]);
+      }
+    return x[k];
+    } else {
+      mid=(l+ir) >> 1;
+      SWAP(x[mid],x[l+1]);
+      if (x[l] > x[ir]) {
+        SWAP(x[l],x[ir]);
+      }
+      if (x[l+1] > x[ir]) {
+        SWAP(x[l+1],x[ir]);
+      }
+      if (x[l] > x[l+1]) {
+        SWAP(x[l],x[l+1]);
+      }
+      i=l+1;
+      j=ir;
+      a=x[l+1];
+      for (;;) {
+        do i++; while (x[i] < a);
+        do j--; while (x[j] > a);
+        if (j < i) break;
+          SWAP(x[i],x[j]);
+      }
+      x[l+1]=x[j];
+      x[j]=a;
+      if (j >= k) ir=j-1;
+      if (j <= k) l=i;
     }
+  }
 }
 
 double iquickselect(int *x, int n, int k) {
-    unsigned long i,ir,j,l,mid;
-    int a,temp;
+  unsigned long i,ir,j,l,mid;
+  int a,temp;
 
-    l=0;
-    ir=n-1;
-    for(;;) {
-        if (ir <= l+1) {
-            if (ir == l+1 && x[ir] < x[l]) {
-                SWAP(x[l],x[ir]);
-            }
-        return (double)(x[k]);
-        } else {
-            mid=(l+ir) >> 1;
-            SWAP(x[mid],x[l+1]);
-            if (x[l] > x[ir]) {
-                SWAP(x[l],x[ir]);
-            }
-            if (x[l+1] > x[ir]) {
-                SWAP(x[l+1],x[ir]);
-            }
-            if (x[l] > x[l+1]) {
-                SWAP(x[l],x[l+1]);
-            }
-            i=l+1;
-            j=ir;
-            a=x[l+1];
-            for (;;) {
-                do i++; while (x[i] < a);
-                do j--; while (x[j] > a);
-                if (j < i) break;
-                    SWAP(x[i],x[j]);
-            }
-            x[l+1]=x[j];
-            x[j]=a;
-            if (j >= k) ir=j-1;
-            if (j <= k) l=i;
-        }
+  l=0;
+  ir=n-1;
+  for(;;) {
+    if (ir <= l+1) {
+      if (ir == l+1 && x[ir] < x[l]) {
+        SWAP(x[l],x[ir]);
+      }
+    return (double)(x[k]);
+    } else {
+      mid=(l+ir) >> 1;
+      SWAP(x[mid],x[l+1]);
+      if (x[l] > x[ir]) {
+        SWAP(x[l],x[ir]);
+      }
+      if (x[l+1] > x[ir]) {
+        SWAP(x[l+1],x[ir]);
+      }
+      if (x[l] > x[l+1]) {
+        SWAP(x[l],x[l+1]);
+      }
+      i=l+1;
+      j=ir;
+      a=x[l+1];
+      for (;;) {
+        do i++; while (x[i] < a);
+        do j--; while (x[j] > a);
+        if (j < i) break;
+          SWAP(x[i],x[j]);
+      }
+      x[l+1]=x[j];
+      x[j]=a;
+      if (j >= k) ir=j-1;
+      if (j <= k) l=i;
     }
+  }
 }
 
 
diff --git a/src/rbindlist.c b/src/rbindlist.c
index 2098426904..a36dd30d75 100644
--- a/src/rbindlist.c
+++ b/src/rbindlist.c
@@ -9,8 +9,8 @@
 // a simple linked list, will use this when finding global order for ordered factors
 // will keep two ints
 struct llist {
-    struct llist * next;
-    R_len_t i, j;
+  struct llist * next;
+  R_len_t i, j;
 };
 
 // hash table code copied from main/unique.c, specialized for our particular needs
@@ -25,16 +25,16 @@ typedef size_t hlen;
 typedef struct _HashData HashData;
 
 struct _HashData {
-    int K;
-    hlen M;
-    RLEN nmax;
-    hlen (*hash)(SEXP, RLEN, HashData *);
-    int (*equal)(SEXP, RLEN, SEXP, RLEN);
-    struct llist ** HashTable;
-
-    int nomatch;
-    Rboolean useUTF8;
-    Rboolean useCache;
+  int K;
+  hlen M;
+  RLEN nmax;
+  hlen (*hash)(SEXP, RLEN, HashData *);
+  int (*equal)(SEXP, RLEN, SEXP, RLEN);
+  struct llist ** HashTable;
+
+  int nomatch;
+  Rboolean useUTF8;
+  Rboolean useCache;
 };
 
 /*
@@ -59,40 +59,40 @@ to NA_INTEGER.
 
 static hlen scatter(unsigned int key, HashData *d)
 {
-    return 3141592653U * key >> (32 - d->K);
+  return 3141592653U * key >> (32 - d->K);
 }
 
 /* Hash CHARSXP by address. Hash values are int, For 64bit pointers,
  * we do (upper ^ lower) */
 static hlen cshash(SEXP x, RLEN indx, HashData *d)
 {
-    intptr_t z = (intptr_t) STRING_ELT(x, indx);
-    unsigned int z1 = (unsigned int)(z & 0xffffffff), z2 = 0;
+  intptr_t z = (intptr_t) STRING_ELT(x, indx);
+  unsigned int z1 = (unsigned int)(z & 0xffffffff), z2 = 0;
 #if SIZEOF_LONG == 8
-    z2 = (unsigned int)(z/0x100000000L);
+  z2 = (unsigned int)(z/0x100000000L);
 #endif
-    return scatter(z1 ^ z2, d);
+  return scatter(z1 ^ z2, d);
 }
 
 static hlen shash(SEXP x, RLEN indx, HashData *d)
 {
-    unsigned int k;
-    const char *p;
-    const void *vmax = vmaxget();
-    if(!d->useUTF8 && d->useCache) return cshash(x, indx, d);
-    /* Not having d->useCache really should not happen anymore. */
-    p = translateCharUTF8(STRING_ELT(x, indx));
-    k = 0;
-    while (*p++)
-        k = 11 * k + (unsigned int) *p; /* was 8 but 11 isn't a power of 2 */
-    vmaxset(vmax); /* discard any memory used by translateChar */
-    return scatter(k, d);
+  unsigned int k;
+  const char *p;
+  const void *vmax = vmaxget();
+  if(!d->useUTF8 && d->useCache) return cshash(x, indx, d);
+  /* Not having d->useCache really should not happen anymore. */
+  p = translateCharUTF8(STRING_ELT(x, indx));
+  k = 0;
+  while (*p++)
+    k = 11 * k + (unsigned int) *p; /* was 8 but 11 isn't a power of 2 */
+  vmaxset(vmax); /* discard any memory used by translateChar */
+  return scatter(k, d);
 }
 
 static int sequal(SEXP x, RLEN i, SEXP y, RLEN j)
 {
-    // using our function instead of copying a lot more code from base
-    return !StrCmp(STRING_ELT(x, i), STRING_ELT(y, j));
+  // using our function instead of copying a lot more code from base
+  return !StrCmp(STRING_ELT(x, i), STRING_ELT(y, j));
 }
 
 /*
@@ -106,276 +106,276 @@ R. Sedgewick (1998) Algorithms in C++ 3rd edition p.606.
 */
 static void MKsetup(HashData *d, RLEN n)
 {
-    if(n < 0 || n >= 1073741824) /* protect against overflow to -ve */
-        error("length %d is too large for hashing", n);
-
-    size_t n2 = 2U * (size_t) n;
-    d->M = 2;
-    d->K = 1;
-    while (d->M < n2) {
-        d->M *= 2;
-        d->K++;
-    }
-    d->nmax = n;
+  if(n < 0 || n >= 1073741824) /* protect against overflow to -ve */
+    error("length %d is too large for hashing", n);
+
+  size_t n2 = 2U * (size_t) n;
+  d->M = 2;
+  d->K = 1;
+  while (d->M < n2) {
+    d->M *= 2;
+    d->K++;
+  }
+  d->nmax = n;
 }
 
 #define IMAX 4294967296L
 static void HashTableSetup(HashData *d, RLEN n)
 {
-    d->hash = shash;
-    d->equal = sequal;
-    MKsetup(d, n);
-    //d->HashTable = malloc(sizeof(struct llist *) * (d->M));
-    //if (d->HashTable == NULL) error("malloc failed in rbindlist.c. This part of the code will be reworked.");
-    d->HashTable = (struct llist **)R_alloc(d->M, sizeof(struct llist *));
-    for (RLEN i = 0; i < d->M; i++) d->HashTable[i] = NULL;
+  d->hash = shash;
+  d->equal = sequal;
+  MKsetup(d, n);
+  //d->HashTable = malloc(sizeof(struct llist *) * (d->M));
+  //if (d->HashTable == NULL) error("malloc failed in rbindlist.c. This part of the code will be reworked.");
+  d->HashTable = (struct llist **)R_alloc(d->M, sizeof(struct llist *));
+  for (RLEN i = 0; i < d->M; i++) d->HashTable[i] = NULL;
 }
 /*
 static void CleanHashTable(HashData *d)
 {
-    struct llist * root, * tmp;
-
-    for (RLEN i = 0; i < d->M; ++i) {
-        root = d->HashTable[i];
-        while (root != NULL) {
-            tmp = root->next;
-            free(root);
-            root = tmp;
-        }
+  struct llist * root, * tmp;
+
+  for (RLEN i = 0; i < d->M; ++i) {
+    root = d->HashTable[i];
+    while (root != NULL) {
+      tmp = root->next;
+      free(root);
+      root = tmp;
     }
-    free(d->HashTable);
+  }
+  free(d->HashTable);
 }
 */
 
 // factorType is 1 for factor and 2 for ordered
 // will simply unique normal factors and attempt to find global order for ordered ones
 SEXP combineFactorLevels(SEXP factorLevels, int * factorType, Rboolean * isRowOrdered) {
-    // find total length
-    RLEN size = 0;
-    R_len_t len = LENGTH(factorLevels), n, i, j;
-    SEXP elem;
-    for (i = 0; i < len; ++i) {
-        elem = VECTOR_ELT(factorLevels, i);
-        n = LENGTH(elem);
-        size += n;
-        /* for (j = 0; j < n; ++j) { */
-        /*     if(IS_BYTES(STRING_ELT(elem, j))) { */
-        /*         data.useUTF8 = FALSE; break; */
-        /*     } */
-        /*     if(ENC_KNOWN(STRING_ELT(elem, j))) { */
-        /*         data.useUTF8 = TRUE; */
-        /*     } */
-        /*     if(!IS_CACHED(STRING_ELT(elem, j))) { */
-        /*         data.useCache = FALSE; break; */
-        /*     } */
-        /* } */
+  // find total length
+  RLEN size = 0;
+  R_len_t len = LENGTH(factorLevels), n, i, j;
+  SEXP elem;
+  for (i = 0; i < len; ++i) {
+    elem = VECTOR_ELT(factorLevels, i);
+    n = LENGTH(elem);
+    size += n;
+    /* for (j = 0; j < n; ++j) { */
+    /*     if(IS_BYTES(STRING_ELT(elem, j))) { */
+    /*         data.useUTF8 = FALSE; break; */
+    /*     } */
+    /*     if(ENC_KNOWN(STRING_ELT(elem, j))) { */
+    /*         data.useUTF8 = TRUE; */
+    /*     } */
+    /*     if(!IS_CACHED(STRING_ELT(elem, j))) { */
+    /*         data.useCache = FALSE; break; */
+    /*     } */
+    /* } */
+  }
+
+  // set up hash to put duplicates in
+  HashData data;
+  data.useUTF8 = FALSE;
+  data.useCache = TRUE;
+  HashTableSetup(&data, size);
+
+  struct llist **h = data.HashTable;
+  hlen idx;
+  struct llist * pl;
+  R_len_t uniqlen = 0;
+  // we insert in opposite order because it's more convenient later to choose first of the duplicates
+  for (i = len-1; i >= 0; --i) {
+    elem = VECTOR_ELT(factorLevels, i);
+    n = LENGTH(elem);
+    for (j = n-1; j >= 0; --j) {
+      idx = data.hash(elem, j, &data);
+      while (h[idx] != NULL) {
+        pl = h[idx];
+        if (data.equal(VECTOR_ELT(factorLevels, pl->i), pl->j, elem, j))
+          break;
+        // it's a collision, not a match, so iterate to a new spot
+        idx = (idx + 1) % data.M;
+      }
+      if (data.nmax-- < 0) error("hash table is full");
+
+      pl = (struct llist *)R_alloc(1, sizeof(struct llist));
+      pl->next = NULL;
+      pl->i = i;
+      pl->j = j;
+      if (h[idx] != NULL) {
+        pl->next = h[idx];
+      } else {
+        ++uniqlen;
+      }
+      h[idx] = pl;
     }
-
-    // set up hash to put duplicates in
-    HashData data;
-    data.useUTF8 = FALSE;
-    data.useCache = TRUE;
-    HashTableSetup(&data, size);
-
-    struct llist **h = data.HashTable;
-    hlen idx;
-    struct llist * pl;
-    R_len_t uniqlen = 0;
-    // we insert in opposite order because it's more convenient later to choose first of the duplicates
-    for (i = len-1; i >= 0; --i) {
-        elem = VECTOR_ELT(factorLevels, i);
-        n = LENGTH(elem);
-        for (j = n-1; j >= 0; --j) {
-            idx = data.hash(elem, j, &data);
-            while (h[idx] != NULL) {
-                pl = h[idx];
-                if (data.equal(VECTOR_ELT(factorLevels, pl->i), pl->j, elem, j))
-                    break;
-                // it's a collision, not a match, so iterate to a new spot
-                idx = (idx + 1) % data.M;
-            }
-            if (data.nmax-- < 0) error("hash table is full");
-
-            pl = (struct llist *)R_alloc(1, sizeof(struct llist));
-            pl->next = NULL;
-            pl->i = i;
-            pl->j = j;
-            if (h[idx] != NULL) {
-                pl->next = h[idx];
-            } else {
-                ++uniqlen;
-            }
-            h[idx] = pl;
+  }
+
+  SEXP finalLevels = PROTECT(allocVector(STRSXP, uniqlen));
+  R_len_t counter = 0;
+  if (*factorType == 2) {
+    int *locs = (int *)R_alloc(len, sizeof(int));
+    for (int i=0; i<len; i++) locs[i] = 0;
+    // note there's a goto (!!) normalFactor below. When locs was allocated with malloc, the goto jumped over the
+    // old free() and caused leak. Now uses the safer R_alloc.  TODO - review all this logic.
+
+    R_len_t k;
+    SEXP tmp;
+    for (i = 0; i < len; ++i) {
+      if (!isRowOrdered[i]) continue;
+
+      elem = VECTOR_ELT(factorLevels, i);
+      n = LENGTH(elem);
+      for (j = locs[i]; j < n; ++j) {
+        idx = data.hash(elem, j, &data);
+        while (h[idx] != NULL) {
+          pl = h[idx];
+          if (data.equal(VECTOR_ELT(factorLevels, pl->i), pl->j, elem, j)) {
+            do {
+              if (!isRowOrdered[pl->i]) continue;
+
+              tmp = VECTOR_ELT(factorLevels, pl->i);
+              if (locs[pl->i] > pl->j) {
+                // failed to construct global order, need to break out of too many loops
+                // so will use goto :o
+                warning("ordered factor levels cannot be combined, going to convert to simple factor instead");
+                counter = 0;
+                *factorType = 1;
+                goto normalFactor;
+              }
+
+              for (k = locs[pl->i]; k < pl->j; ++k) {
+                SET_STRING_ELT(finalLevels, counter++, STRING_ELT(tmp, k));
+              }
+              locs[pl->i] = pl->j + 1;
+            } while ( (pl = pl->next) ); // added parenthesis to remove compiler warning 'suggest parentheses around assignment used as truth value'
+            SET_STRING_ELT(finalLevels, counter++, STRING_ELT(elem, j));
+            break;
+          }
+          // it's a collision, not a match, so iterate to a new spot
+          idx = (idx + 1) % data.M;
         }
+        if (h[idx] == NULL) error("internal hash error, please report to datatable-help");
+      }
     }
 
-    SEXP finalLevels = PROTECT(allocVector(STRSXP, uniqlen));
-    R_len_t counter = 0;
-    if (*factorType == 2) {
-        int *locs = (int *)R_alloc(len, sizeof(int));
-        for (int i=0; i<len; i++) locs[i] = 0;
-        // note there's a goto (!!) normalFactor below. When locs was allocated with malloc, the goto jumped over the
-        // old free() and caused leak. Now uses the safer R_alloc.  TODO - review all this logic.
-
-        R_len_t k;
-        SEXP tmp;
-        for (i = 0; i < len; ++i) {
-            if (!isRowOrdered[i]) continue;
-
-            elem = VECTOR_ELT(factorLevels, i);
-            n = LENGTH(elem);
-            for (j = locs[i]; j < n; ++j) {
-                idx = data.hash(elem, j, &data);
-                while (h[idx] != NULL) {
-                    pl = h[idx];
-                    if (data.equal(VECTOR_ELT(factorLevels, pl->i), pl->j, elem, j)) {
-                        do {
-                            if (!isRowOrdered[pl->i]) continue;
-
-                            tmp = VECTOR_ELT(factorLevels, pl->i);
-                            if (locs[pl->i] > pl->j) {
-                                // failed to construct global order, need to break out of too many loops
-                                // so will use goto :o
-                                warning("ordered factor levels cannot be combined, going to convert to simple factor instead");
-                                counter = 0;
-                                *factorType = 1;
-                                goto normalFactor;
-                            }
-
-                            for (k = locs[pl->i]; k < pl->j; ++k) {
-                                SET_STRING_ELT(finalLevels, counter++, STRING_ELT(tmp, k));
-                            }
-                            locs[pl->i] = pl->j + 1;
-                        } while ( (pl = pl->next) ); // added parenthesis to remove compiler warning 'suggest parentheses around assignment used as truth value'
-                        SET_STRING_ELT(finalLevels, counter++, STRING_ELT(elem, j));
-                        break;
-                    }
-                    // it's a collision, not a match, so iterate to a new spot
-                    idx = (idx + 1) % data.M;
-                }
-                if (h[idx] == NULL) error("internal hash error, please report to datatable-help");
-            }
-        }
-
-        // fill in the rest of the unordered elements
-        Rboolean record;
-        for (i = 0; i < len; ++i) {
-            if (isRowOrdered[i]) continue;
-
-            elem = VECTOR_ELT(factorLevels, i);
-            n = LENGTH(elem);
-            for (j = 0; j < n; ++j) {
-                idx = data.hash(elem, j, &data);
-                while (h[idx] != NULL) {
-                    pl = h[idx];
-                    if (data.equal(VECTOR_ELT(factorLevels, pl->i), pl->j, elem, j)) {
-                        // Fixes #899. "rest" can have identical levels in
-                        // more than 1 data.table.
-                        if (!(pl->i == i && pl->j == j)) break;
-                        record = TRUE;
-                        do {
-                            // if this element was in an ordered list, it's been recorded already
-                            if (isRowOrdered[pl->i]) {
-                                record = FALSE;
-                                break;
-                            }
-                        } while ( (pl = pl->next) ); // added parenthesis to remove compiler warning 'suggest parentheses around assignment used as truth value'
-                        if (record)
-                            SET_STRING_ELT(finalLevels, counter++, STRING_ELT(elem, j));
-
-                        break;
-                    }
-                    // it's a collision, not a match, so iterate to a new spot
-                    idx = (idx + 1) % data.M;
-                }
-                if (h[idx] == NULL) error("internal hash error, please report to datatable-help");
-            }
+    // fill in the rest of the unordered elements
+    Rboolean record;
+    for (i = 0; i < len; ++i) {
+      if (isRowOrdered[i]) continue;
+
+      elem = VECTOR_ELT(factorLevels, i);
+      n = LENGTH(elem);
+      for (j = 0; j < n; ++j) {
+        idx = data.hash(elem, j, &data);
+        while (h[idx] != NULL) {
+          pl = h[idx];
+          if (data.equal(VECTOR_ELT(factorLevels, pl->i), pl->j, elem, j)) {
+            // Fixes #899. "rest" can have identical levels in
+            // more than 1 data.table.
+            if (!(pl->i == i && pl->j == j)) break;
+            record = TRUE;
+            do {
+              // if this element was in an ordered list, it's been recorded already
+              if (isRowOrdered[pl->i]) {
+                record = FALSE;
+                break;
+              }
+            } while ( (pl = pl->next) ); // added parenthesis to remove compiler warning 'suggest parentheses around assignment used as truth value'
+            if (record)
+              SET_STRING_ELT(finalLevels, counter++, STRING_ELT(elem, j));
+
+            break;
+          }
+          // it's a collision, not a match, so iterate to a new spot
+          idx = (idx + 1) % data.M;
         }
+        if (h[idx] == NULL) error("internal hash error, please report to datatable-help");
+      }
     }
+  }
 
  normalFactor:
-    if (*factorType == 1) {
-        for (i = 0; i < len; ++i) {
-            elem = VECTOR_ELT(factorLevels, i);
-            n = LENGTH(elem);
-            for (j = 0; j < n; ++j) {
-                idx = data.hash(elem, j, &data);
-                while (h[idx] != NULL) {
-                    pl = h[idx];
-                    if (data.equal(VECTOR_ELT(factorLevels, pl->i), pl->j, elem, j)) {
-                        if (pl->i == i && pl->j == j) {
-                            SET_STRING_ELT(finalLevels, counter++, STRING_ELT(elem, j));
-                        }
-                        break;
-                    }
-                    // it's a collision, not a match, so iterate to a new spot
-                    idx = (idx + 1) % data.M;
-                }
-                if (h[idx] == NULL) error("internal hash error, please report to datatable-help");
+  if (*factorType == 1) {
+    for (i = 0; i < len; ++i) {
+      elem = VECTOR_ELT(factorLevels, i);
+      n = LENGTH(elem);
+      for (j = 0; j < n; ++j) {
+        idx = data.hash(elem, j, &data);
+        while (h[idx] != NULL) {
+          pl = h[idx];
+          if (data.equal(VECTOR_ELT(factorLevels, pl->i), pl->j, elem, j)) {
+            if (pl->i == i && pl->j == j) {
+              SET_STRING_ELT(finalLevels, counter++, STRING_ELT(elem, j));
             }
+            break;
+          }
+          // it's a collision, not a match, so iterate to a new spot
+          idx = (idx + 1) % data.M;
         }
+        if (h[idx] == NULL) error("internal hash error, please report to datatable-help");
+      }
     }
+  }
 
-    // CleanHashTable(&data);   No longer needed now we use R_alloc(). But the hash table approach
-    // will be removed completely at some point.
+  // CleanHashTable(&data);   No longer needed now we use R_alloc(). But the hash table approach
+  // will be removed completely at some point.
 
-    return finalLevels;
+  return finalLevels;
 }
 
 
 /* Arun's addition and changes to incorporate 'usenames=T/F' and 'fill=T/F' arguments to rbindlist */
 
 /*
-    l               = input list of data.tables/lists/data.frames
-    n               = length(l)
-    ans             = rbind'd result
-    i               = an index over length of l
-    use.names       = whether binding should check for names and bind them accordingly
-    fill            = whether missing columns should be filled with NAs
-
-    ans_ptr         = final_names - column names for 'ans' (list item 1)
-    ans_ptr         = match_indices - when use.names=TRUE, for each element in l, what's the destination col index (in 'ans') for each of the cols (list item 2)
-    n_rows          = total number of rows in 'ans'
-    n_cols          = total number of cols in 'ans'
-    max_type        = for each col in 'ans', what's the final SEXPTYPE? (for coercion if necessary)
-    is_factor       = for each col in 'ans' mark which one's a factor (to convert to factor at the end)
-    is_ofactor      = for each col in 'ans' mark which one's an ordered factor (to convert to ordered factor at the end)
-    fn_rows         = the length of first column (rows) for each item in l.
-    mincol          = get the minimum number of columns in an item from l. Used to check if 'fill=TRUE' is really necessary, even if set.
+  l               = input list of data.tables/lists/data.frames
+  n               = length(l)
+  ans             = rbind'd result
+  i               = an index over length of l
+  use.names       = whether binding should check for names and bind them accordingly
+  fill            = whether missing columns should be filled with NAs
+
+  ans_ptr         = final_names - column names for 'ans' (list item 1)
+  ans_ptr         = match_indices - when use.names=TRUE, for each element in l, what's the destination col index (in 'ans') for each of the cols (list item 2)
+  n_rows          = total number of rows in 'ans'
+  n_cols          = total number of cols in 'ans'
+  max_type        = for each col in 'ans', what's the final SEXPTYPE? (for coercion if necessary)
+  is_factor       = for each col in 'ans' mark which one's a factor (to convert to factor at the end)
+  is_ofactor      = for each col in 'ans' mark which one's an ordered factor (to convert to ordered factor at the end)
+  fn_rows         = the length of first column (rows) for each item in l.
+  mincol          = get the minimum number of columns in an item from l. Used to check if 'fill=TRUE' is really necessary, even if set.
 */
 
 struct preprocessData {
-    SEXP ans_ptr, colname;
-    size_t n_rows, n_cols;
-    int *fn_rows, *is_factor, first, lcount, mincol, protecti;
-    SEXPTYPE *max_type;
+  SEXP ans_ptr, colname;
+  size_t n_rows, n_cols;
+  int *fn_rows, *is_factor, first, lcount, mincol, protecti;
+  SEXPTYPE *max_type;
 };
 
 static SEXP unlist2(SEXP v) {
 
-    RLEN i, j, k=0, ni, n=0;
-    SEXP ans, vi, lnames, groups, runids;
-
-    for (i=0; i<length(v); i++) n += length(VECTOR_ELT(v, i));
-    ans    = PROTECT(allocVector(VECSXP, 3));
-    lnames = PROTECT(allocVector(STRSXP, n));
-    groups = PROTECT(allocVector(INTSXP, n));
-    runids = PROTECT(allocVector(INTSXP, n));
-    for (i=0; i<length(v); i++) {
-        vi = VECTOR_ELT(v, i);
-        ni = length(vi);
-        for (j=0; j<ni; j++) {
-            SET_STRING_ELT(lnames, k + j, STRING_ELT(vi, j));
-            INTEGER(groups)[k + j] = i+1;
-            INTEGER(runids)[k + j] = j;
-        }
-        k+=j;
+  RLEN i, j, k=0, ni, n=0;
+  SEXP ans, vi, lnames, groups, runids;
+
+  for (i=0; i<length(v); i++) n += length(VECTOR_ELT(v, i));
+  ans    = PROTECT(allocVector(VECSXP, 3));
+  lnames = PROTECT(allocVector(STRSXP, n));
+  groups = PROTECT(allocVector(INTSXP, n));
+  runids = PROTECT(allocVector(INTSXP, n));
+  for (i=0; i<length(v); i++) {
+    vi = VECTOR_ELT(v, i);
+    ni = length(vi);
+    for (j=0; j<ni; j++) {
+      SET_STRING_ELT(lnames, k + j, STRING_ELT(vi, j));
+      INTEGER(groups)[k + j] = i+1;
+      INTEGER(runids)[k + j] = j;
     }
-    SET_VECTOR_ELT(ans, 0, lnames);
-    SET_VECTOR_ELT(ans, 1, groups);
-    SET_VECTOR_ELT(ans, 2, runids);
-    UNPROTECT(4);
-    return(ans);
+    k+=j;
+  }
+  SET_VECTOR_ELT(ans, 0, lnames);
+  SET_VECTOR_ELT(ans, 1, groups);
+  SET_VECTOR_ELT(ans, 2, runids);
+  UNPROTECT(4);
+  return(ans);
 }
 
 // Don't use elsewhere. No checks are made on byArg and handleSorted
@@ -383,406 +383,406 @@ static SEXP unlist2(SEXP v) {
 // input is already sorted, like forder. if not, seq_len(nrow(dt)).
 static SEXP fast_order(SEXP dt, R_len_t byArg, R_len_t handleSorted) {
 
-    R_len_t i, protecti=0;
-    SEXP ans, by=R_NilValue, retGrp, sortStr, order, na, starts;
+  R_len_t i, protecti=0;
+  SEXP ans, by=R_NilValue, retGrp, sortStr, order, na, starts;
 
-    retGrp  = PROTECT(allocVector(LGLSXP, 1)); LOGICAL(retGrp)[0]  = TRUE;
-    sortStr = PROTECT(allocVector(LGLSXP, 1)); LOGICAL(sortStr)[0] = FALSE;
-    na      = PROTECT(allocVector(LGLSXP, 1)); LOGICAL(na)[0] = FALSE;
+  retGrp  = PROTECT(allocVector(LGLSXP, 1)); LOGICAL(retGrp)[0]  = TRUE;
+  sortStr = PROTECT(allocVector(LGLSXP, 1)); LOGICAL(sortStr)[0] = FALSE;
+  na      = PROTECT(allocVector(LGLSXP, 1)); LOGICAL(na)[0] = FALSE;
 
-    if (byArg) {
-        by    = PROTECT(allocVector(INTSXP, byArg));
-        order = PROTECT(allocVector(INTSXP, byArg));
-        for (i=0; i<byArg; i++) {
-            INTEGER(by)[i] = i+1;
-            INTEGER(order)[i] = 1;
-        }
-        UNPROTECT(5);
-    } else {
-        order = PROTECT(allocVector(INTSXP, 1)); INTEGER(order)[0] = 1;
-        UNPROTECT(4);
-    }
-    ans = PROTECT(forder(dt, by, retGrp, sortStr, order, na)); protecti++;
-    if (!length(ans) && handleSorted != 0) {
-        starts = getAttrib(ans, sym_starts);
-        // if cols are already sorted, 'forder' gives integer(0), got to replace it with 1:.N
-        ans = PROTECT(allocVector(INTSXP, length(VECTOR_ELT(dt, 0)))); protecti++;
-        for (i=0; i<length(ans); i++) INTEGER(ans)[i] = i+1;
-        // TODO: for loop appears redundant because length(ans)==0 due to if (!length(ans)) above
-        setAttrib(ans, sym_starts, starts);
+  if (byArg) {
+    by    = PROTECT(allocVector(INTSXP, byArg));
+    order = PROTECT(allocVector(INTSXP, byArg));
+    for (i=0; i<byArg; i++) {
+      INTEGER(by)[i] = i+1;
+      INTEGER(order)[i] = 1;
     }
-    UNPROTECT(protecti); // ans
-    return(ans);
+    UNPROTECT(5);
+  } else {
+    order = PROTECT(allocVector(INTSXP, 1)); INTEGER(order)[0] = 1;
+    UNPROTECT(4);
+  }
+  ans = PROTECT(forder(dt, by, retGrp, sortStr, order, na)); protecti++;
+  if (!length(ans) && handleSorted != 0) {
+    starts = getAttrib(ans, sym_starts);
+    // if cols are already sorted, 'forder' gives integer(0), got to replace it with 1:.N
+    ans = PROTECT(allocVector(INTSXP, length(VECTOR_ELT(dt, 0)))); protecti++;
+    for (i=0; i<length(ans); i++) INTEGER(ans)[i] = i+1;
+    // TODO: for loop appears redundant because length(ans)==0 due to if (!length(ans)) above
+    setAttrib(ans, sym_starts, starts);
+  }
+  UNPROTECT(protecti); // ans
+  return(ans);
 }
 
 static SEXP uniq_lengths(SEXP v, R_len_t n) {
 
-    R_len_t i, nv=length(v);
-    SEXP ans = PROTECT(allocVector(INTSXP, nv));
-    for (i=1; i<nv; i++) {
-        INTEGER(ans)[i-1] = INTEGER(v)[i] - INTEGER(v)[i-1];
-    }
-    // last value
-    INTEGER(ans)[nv-1] = n - INTEGER(v)[nv-1] + 1;
-    UNPROTECT(1);
-    return(ans);
+  R_len_t i, nv=length(v);
+  SEXP ans = PROTECT(allocVector(INTSXP, nv));
+  for (i=1; i<nv; i++) {
+    INTEGER(ans)[i-1] = INTEGER(v)[i] - INTEGER(v)[i-1];
+  }
+  // last value
+  INTEGER(ans)[nv-1] = n - INTEGER(v)[nv-1] + 1;
+  UNPROTECT(1);
+  return(ans);
 }
 
 static SEXP match_names(SEXP v) {
 
-    R_len_t i, j, idx, ncols, protecti=0;
-    SEXP ans, dt, lnames, ti;
-    SEXP uorder, starts, ulens, index, firstofeachgroup, origorder;
-    SEXP fnames, findices, runid, grpid;
-
-    ans    = PROTECT(allocVector(VECSXP, 2));
-    dt     = PROTECT(unlist2(v)); protecti++;
-    lnames = VECTOR_ELT(dt, 0);
-    grpid  = PROTECT(duplicate(VECTOR_ELT(dt, 1))); protecti++; // dt[1] will be reused, so backup
-    runid  = VECTOR_ELT(dt, 2);
-
-    uorder = PROTECT(fast_order(dt, 2, 1));  protecti++; // byArg alone is set, everything else is set inside fast_order
-    starts = getAttrib(uorder, sym_starts);
-    ulens  = PROTECT(uniq_lengths(starts, length(lnames))); protecti++;
-
-    // seq_len(.N) for each group
-    index = PROTECT(VECTOR_ELT(dt, 1)); protecti++; // reuse dt[1] (in 0-index coordinate), value already backed up above.
-    for (i=0; i<length(ulens); i++) {
-        for (j=0; j<INTEGER(ulens)[i]; j++)
-            INTEGER(index)[INTEGER(uorder)[INTEGER(starts)[i]-1+j]-1] = j;
-    }
-    // order again
-    uorder = PROTECT(fast_order(dt, 2, 1));  protecti++; // byArg alone is set, everything else is set inside fast_order
-    starts = getAttrib(uorder, sym_starts);
-    ulens  = PROTECT(uniq_lengths(starts, length(lnames))); protecti++;
-    ncols  = length(starts);
-    // check if order has to be changed (bysameorder = FALSE here by default - in `[.data.table` parlance)
-    firstofeachgroup = PROTECT(allocVector(INTSXP, length(starts)));
-    for (i=0; i<ncols; i++) INTEGER(firstofeachgroup)[i] = INTEGER(uorder)[INTEGER(starts)[i]-1];
-    origorder = PROTECT(fast_order(firstofeachgroup, 0, 0));
-    if (length(origorder)) {
-        reorder(starts, origorder);
-        reorder(ulens, origorder);
-    }
-    UNPROTECT(2);
-    // get fnames and findices
-    fnames   = PROTECT(allocVector(STRSXP, ncols)); protecti++;
-    findices = PROTECT(allocVector(VECSXP, ncols)); protecti++;
-    for (i=0; i<ncols; i++) {
-        idx = INTEGER(uorder)[INTEGER(starts)[i]-1]-1;
-        SET_STRING_ELT(fnames, i, STRING_ELT(lnames, idx));
-        ti = PROTECT(allocVector(INTSXP, length(v)));
-        for (j=0;j<length(v);j++) INTEGER(ti)[j]=-1; // TODO: can we eliminate this?
-        for (j=0; j<INTEGER(ulens)[i]; j++) {
-            idx = INTEGER(uorder)[INTEGER(starts)[i]-1+j]-1;
-            INTEGER(ti)[INTEGER(grpid)[idx]-1] = INTEGER(runid)[idx];
-        }
-        UNPROTECT(1);
-        SET_VECTOR_ELT(findices, i, ti);
+  R_len_t i, j, idx, ncols, protecti=0;
+  SEXP ans, dt, lnames, ti;
+  SEXP uorder, starts, ulens, index, firstofeachgroup, origorder;
+  SEXP fnames, findices, runid, grpid;
+
+  ans    = PROTECT(allocVector(VECSXP, 2));
+  dt     = PROTECT(unlist2(v)); protecti++;
+  lnames = VECTOR_ELT(dt, 0);
+  grpid  = PROTECT(duplicate(VECTOR_ELT(dt, 1))); protecti++; // dt[1] will be reused, so backup
+  runid  = VECTOR_ELT(dt, 2);
+
+  uorder = PROTECT(fast_order(dt, 2, 1));  protecti++; // byArg alone is set, everything else is set inside fast_order
+  starts = getAttrib(uorder, sym_starts);
+  ulens  = PROTECT(uniq_lengths(starts, length(lnames))); protecti++;
+
+  // seq_len(.N) for each group
+  index = PROTECT(VECTOR_ELT(dt, 1)); protecti++; // reuse dt[1] (in 0-index coordinate), value already backed up above.
+  for (i=0; i<length(ulens); i++) {
+    for (j=0; j<INTEGER(ulens)[i]; j++)
+      INTEGER(index)[INTEGER(uorder)[INTEGER(starts)[i]-1+j]-1] = j;
+  }
+  // order again
+  uorder = PROTECT(fast_order(dt, 2, 1));  protecti++; // byArg alone is set, everything else is set inside fast_order
+  starts = getAttrib(uorder, sym_starts);
+  ulens  = PROTECT(uniq_lengths(starts, length(lnames))); protecti++;
+  ncols  = length(starts);
+  // check if order has to be changed (bysameorder = FALSE here by default - in `[.data.table` parlance)
+  firstofeachgroup = PROTECT(allocVector(INTSXP, length(starts)));
+  for (i=0; i<ncols; i++) INTEGER(firstofeachgroup)[i] = INTEGER(uorder)[INTEGER(starts)[i]-1];
+  origorder = PROTECT(fast_order(firstofeachgroup, 0, 0));
+  if (length(origorder)) {
+    reorder(starts, origorder);
+    reorder(ulens, origorder);
+  }
+  UNPROTECT(2);
+  // get fnames and findices
+  fnames   = PROTECT(allocVector(STRSXP, ncols)); protecti++;
+  findices = PROTECT(allocVector(VECSXP, ncols)); protecti++;
+  for (i=0; i<ncols; i++) {
+    idx = INTEGER(uorder)[INTEGER(starts)[i]-1]-1;
+    SET_STRING_ELT(fnames, i, STRING_ELT(lnames, idx));
+    ti = PROTECT(allocVector(INTSXP, length(v)));
+    for (j=0;j<length(v);j++) INTEGER(ti)[j]=-1; // TODO: can we eliminate this?
+    for (j=0; j<INTEGER(ulens)[i]; j++) {
+      idx = INTEGER(uorder)[INTEGER(starts)[i]-1+j]-1;
+      INTEGER(ti)[INTEGER(grpid)[idx]-1] = INTEGER(runid)[idx];
     }
-    UNPROTECT(protecti);
-    SET_VECTOR_ELT(ans, 0, fnames);
-    SET_VECTOR_ELT(ans, 1, findices);
-    UNPROTECT(1); // ans
-    return(ans);
+    UNPROTECT(1);
+    SET_VECTOR_ELT(findices, i, ti);
+  }
+  UNPROTECT(protecti);
+  SET_VECTOR_ELT(ans, 0, fnames);
+  SET_VECTOR_ELT(ans, 1, findices);
+  UNPROTECT(1); // ans
+  return(ans);
 }
 
 static void preprocess(SEXP l, Rboolean usenames, Rboolean fill, struct preprocessData *data) {
 
-    R_len_t i, j, idx;
-    SEXP li, lnames=R_NilValue, fnames, findices=R_NilValue, f_ind=R_NilValue, thiscol, col_name=R_NilValue, thisClass = R_NilValue;
-    SEXPTYPE type;
-
-    data->first = -1; data->lcount = 0; data->n_rows = 0; data->n_cols = 0; data->protecti = 0;
-    data->max_type = NULL; data->is_factor = NULL; data->ans_ptr = R_NilValue; data->mincol=0;
-    data->fn_rows = (int *)R_alloc(LENGTH(l), sizeof(int));
-    data->colname = R_NilValue;
-
-    // get first non null name, 'rbind' was doing a 'match.names' for each item.. which is a bit more time consuming.
-    // And warning that it'll be matched by names is not necessary, I think, as that's the default for 'rbind'. We
-    // should instead document it.
-    for (i=0; i<LENGTH(l); i++) { // isNull is checked already in rbindlist
-        li = VECTOR_ELT(l, i);
-        if (isNull(li)) continue;
-        if (TYPEOF(li) != VECSXP) error("Item %d of list input is not a data.frame, data.table or list",i+1);
-        if (!LENGTH(li)) continue;
-        col_name = getAttrib(li, R_NamesSymbol);
-        if (!isNull(col_name)) break;
-    }
-    if (!isNull(col_name)) { data->colname = PROTECT(col_name); data->protecti++; }
-    if (usenames) { lnames = PROTECT(allocVector(VECSXP, LENGTH(l))); data->protecti++;}
-    for (i=0; i<LENGTH(l); i++) {
-        data->fn_rows[i] = 0;  // careful to initialize before continues as R_alloc above doesn't initialize
-        li = VECTOR_ELT(l, i);
-        if (isNull(li)) continue;
-        if (TYPEOF(li) != VECSXP) error("Item %d of list input is not a data.frame, data.table or list",i+1);
-        if (!LENGTH(li)) continue;
-        col_name = getAttrib(li, R_NamesSymbol);
-        if (fill && isNull(col_name))
-            error("fill=TRUE, but names of input list at position %d is NULL. All items of input list must have names set when fill=TRUE.", i+1);
-        data->lcount++;
-        data->fn_rows[i] = length(VECTOR_ELT(li, 0));
-        if (data->first == -1) {
-            data->first = i;
-            data->n_cols = LENGTH(li);
-            data->mincol = LENGTH(li);
-            if (!usenames) {
-                data->ans_ptr = PROTECT(allocVector(VECSXP, 2)); data->protecti++;
-                if (isNull(col_name)) SET_VECTOR_ELT(data->ans_ptr, 0, data->colname);
-                else SET_VECTOR_ELT(data->ans_ptr, 0, col_name);
-            } else {
-                if (isNull(col_name)) SET_VECTOR_ELT(lnames, i, data->colname);
-                else SET_VECTOR_ELT(lnames, i, col_name);
-            }
-            data->n_rows += data->fn_rows[i];
-            continue;
-        } else {
-            if (!fill && LENGTH(li) != data->n_cols)
-                if (LENGTH(li) != data->n_cols) error("Item %d has %d columns, inconsistent with item %d which has %d columns. If instead you need to fill missing columns, use set argument 'fill' to TRUE.",i+1, LENGTH(li), data->first+1, data->n_cols);
-        }
-        if (data->mincol > LENGTH(li)) data->mincol = LENGTH(li);
-        data->n_rows += data->fn_rows[i];
-        if (usenames) {
-            if (isNull(col_name)) SET_VECTOR_ELT(lnames, i, data->colname);
-            else SET_VECTOR_ELT(lnames, i, col_name);
-        }
+  R_len_t i, j, idx;
+  SEXP li, lnames=R_NilValue, fnames, findices=R_NilValue, f_ind=R_NilValue, thiscol, col_name=R_NilValue, thisClass = R_NilValue;
+  SEXPTYPE type;
+
+  data->first = -1; data->lcount = 0; data->n_rows = 0; data->n_cols = 0; data->protecti = 0;
+  data->max_type = NULL; data->is_factor = NULL; data->ans_ptr = R_NilValue; data->mincol=0;
+  data->fn_rows = (int *)R_alloc(LENGTH(l), sizeof(int));
+  data->colname = R_NilValue;
+
+  // get first non null name, 'rbind' was doing a 'match.names' for each item.. which is a bit more time consuming.
+  // And warning that it'll be matched by names is not necessary, I think, as that's the default for 'rbind'. We
+  // should instead document it.
+  for (i=0; i<LENGTH(l); i++) { // isNull is checked already in rbindlist
+    li = VECTOR_ELT(l, i);
+    if (isNull(li)) continue;
+    if (TYPEOF(li) != VECSXP) error("Item %d of list input is not a data.frame, data.table or list",i+1);
+    if (!LENGTH(li)) continue;
+    col_name = getAttrib(li, R_NamesSymbol);
+    if (!isNull(col_name)) break;
+  }
+  if (!isNull(col_name)) { data->colname = PROTECT(col_name); data->protecti++; }
+  if (usenames) { lnames = PROTECT(allocVector(VECSXP, LENGTH(l))); data->protecti++;}
+  for (i=0; i<LENGTH(l); i++) {
+    data->fn_rows[i] = 0;  // careful to initialize before continues as R_alloc above doesn't initialize
+    li = VECTOR_ELT(l, i);
+    if (isNull(li)) continue;
+    if (TYPEOF(li) != VECSXP) error("Item %d of list input is not a data.frame, data.table or list",i+1);
+    if (!LENGTH(li)) continue;
+    col_name = getAttrib(li, R_NamesSymbol);
+    if (fill && isNull(col_name))
+      error("fill=TRUE, but names of input list at position %d is NULL. All items of input list must have names set when fill=TRUE.", i+1);
+    data->lcount++;
+    data->fn_rows[i] = length(VECTOR_ELT(li, 0));
+    if (data->first == -1) {
+      data->first = i;
+      data->n_cols = LENGTH(li);
+      data->mincol = LENGTH(li);
+      if (!usenames) {
+        data->ans_ptr = PROTECT(allocVector(VECSXP, 2)); data->protecti++;
+        if (isNull(col_name)) SET_VECTOR_ELT(data->ans_ptr, 0, data->colname);
+        else SET_VECTOR_ELT(data->ans_ptr, 0, col_name);
+      } else {
+        if (isNull(col_name)) SET_VECTOR_ELT(lnames, i, data->colname);
+        else SET_VECTOR_ELT(lnames, i, col_name);
+      }
+      data->n_rows += data->fn_rows[i];
+      continue;
+    } else {
+      if (!fill && LENGTH(li) != data->n_cols)
+        if (LENGTH(li) != data->n_cols) error("Item %d has %d columns, inconsistent with item %d which has %d columns. If instead you need to fill missing columns, use set argument 'fill' to TRUE.",i+1, LENGTH(li), data->first+1, data->n_cols);
     }
+    if (data->mincol > LENGTH(li)) data->mincol = LENGTH(li);
+    data->n_rows += data->fn_rows[i];
     if (usenames) {
-        data->ans_ptr = PROTECT(match_names(lnames)); data->protecti++;
-        fnames = VECTOR_ELT(data->ans_ptr, 0);
-        findices = VECTOR_ELT(data->ans_ptr, 1);
-        if (isNull(data->colname) && data->n_cols > 0)
-            error("use.names=TRUE but no item of input list has any names.\n");
-        if (!fill && length(fnames) != data->mincol) {
-            error("Answer requires %d columns whereas one or more item(s) in the input list has only %d columns. This could be because the items in the list may not all have identical column names or some of the items may have duplicate names. In either case, if you're aware of this and would like to fill those missing columns, set the argument 'fill=TRUE'.", length(fnames), data->mincol);
-        } else data->n_cols = length(fnames);
+      if (isNull(col_name)) SET_VECTOR_ELT(lnames, i, data->colname);
+      else SET_VECTOR_ELT(lnames, i, col_name);
     }
-
-    // decide type of each column
-    // initialize the max types - will possibly increment later
-    data->max_type  = (SEXPTYPE *)R_alloc(data->n_cols, sizeof(SEXPTYPE));
-    data->is_factor = (int *)R_alloc(data->n_cols, sizeof(int));
-    for (i = 0; i< data->n_cols; i++) {
-        thisClass = R_NilValue;
-        data->max_type[i] = 0;
-        data->is_factor[i] = 0;
-        if (usenames) f_ind = VECTOR_ELT(findices, i);
-        for (j=data->first; j<LENGTH(l); j++) {
-            if (data->is_factor[i] == 2) break;
-            idx = (usenames) ? INTEGER(f_ind)[j] : i;
-            li = VECTOR_ELT(l, j);
-            if (isNull(li) || !LENGTH(li) || idx < 0) continue;
-            thiscol = VECTOR_ELT(li, idx);
-            // Fix for #705, check attributes
-            if (j == data->first)
-                thisClass = getAttrib(thiscol, R_ClassSymbol);
-            if (isFactor(thiscol)) {
-                data->is_factor[i] = (isOrdered(thiscol)) ? 2 : 1;
-                data->max_type[i]  = STRSXP;
-            } else {
-                // Fix for #705, check attributes and error if non-factor class and not identical
-                if (!data->is_factor[i] &&
-                    !R_compute_identical(thisClass, getAttrib(thiscol, R_ClassSymbol), 0) && !fill) {
-                    error("Class attributes at column %d of input list at position %d does not match with column %d of input list at position %d. Coercion of objects of class 'factor' alone is handled internally by rbind/rbindlist at the moment.", i+1, j+1, i+1, data->first+1);
-                }
-                type = TYPEOF(thiscol);
-                if (type > data->max_type[i]) data->max_type[i] = type;
-            }
+  }
+  if (usenames) {
+    data->ans_ptr = PROTECT(match_names(lnames)); data->protecti++;
+    fnames = VECTOR_ELT(data->ans_ptr, 0);
+    findices = VECTOR_ELT(data->ans_ptr, 1);
+    if (isNull(data->colname) && data->n_cols > 0)
+      error("use.names=TRUE but no item of input list has any names.\n");
+    if (!fill && length(fnames) != data->mincol) {
+      error("Answer requires %d columns whereas one or more item(s) in the input list has only %d columns. This could be because the items in the list may not all have identical column names or some of the items may have duplicate names. In either case, if you're aware of this and would like to fill those missing columns, set the argument 'fill=TRUE'.", length(fnames), data->mincol);
+    } else data->n_cols = length(fnames);
+  }
+
+  // decide type of each column
+  // initialize the max types - will possibly increment later
+  data->max_type  = (SEXPTYPE *)R_alloc(data->n_cols, sizeof(SEXPTYPE));
+  data->is_factor = (int *)R_alloc(data->n_cols, sizeof(int));
+  for (i = 0; i< data->n_cols; i++) {
+    thisClass = R_NilValue;
+    data->max_type[i] = 0;
+    data->is_factor[i] = 0;
+    if (usenames) f_ind = VECTOR_ELT(findices, i);
+    for (j=data->first; j<LENGTH(l); j++) {
+      if (data->is_factor[i] == 2) break;
+      idx = (usenames) ? INTEGER(f_ind)[j] : i;
+      li = VECTOR_ELT(l, j);
+      if (isNull(li) || !LENGTH(li) || idx < 0) continue;
+      thiscol = VECTOR_ELT(li, idx);
+      // Fix for #705, check attributes
+      if (j == data->first)
+        thisClass = getAttrib(thiscol, R_ClassSymbol);
+      if (isFactor(thiscol)) {
+        data->is_factor[i] = (isOrdered(thiscol)) ? 2 : 1;
+        data->max_type[i]  = STRSXP;
+      } else {
+        // Fix for #705, check attributes and error if non-factor class and not identical
+        if (!data->is_factor[i] &&
+          !R_compute_identical(thisClass, getAttrib(thiscol, R_ClassSymbol), 0) && !fill) {
+          error("Class attributes at column %d of input list at position %d does not match with column %d of input list at position %d. Coercion of objects of class 'factor' alone is handled internally by rbind/rbindlist at the moment.", i+1, j+1, i+1, data->first+1);
         }
+        type = TYPEOF(thiscol);
+        if (type > data->max_type[i]) data->max_type[i] = type;
+      }
     }
+  }
 }
 
 // function does c(idcol, nm), where length(idcol)=1
 // fix for #1432, + more efficient to move the logic to C
 SEXP add_idcol(SEXP nm, SEXP idcol, int cols) {
-    SEXP ans = PROTECT(allocVector(STRSXP, cols+1));
-    SET_STRING_ELT(ans, 0, STRING_ELT(idcol, 0));
-    for (int i=0; i<cols; i++) {
-        SET_STRING_ELT(ans, i+1, STRING_ELT(nm, i));
-    }
-    UNPROTECT(1);
-    return (ans);
+  SEXP ans = PROTECT(allocVector(STRSXP, cols+1));
+  SET_STRING_ELT(ans, 0, STRING_ELT(idcol, 0));
+  for (int i=0; i<cols; i++) {
+    SET_STRING_ELT(ans, i+1, STRING_ELT(nm, i));
+  }
+  UNPROTECT(1);
+  return (ans);
 }
 
 SEXP rbindlist(SEXP l, SEXP sexp_usenames, SEXP sexp_fill, SEXP idcol) {
 
-    R_len_t jj, ansloc, resi, i,j,r, idx, thislen;
-    struct preprocessData data;
-    Rboolean usenames, fill, to_copy = FALSE, coerced=FALSE, isidcol = !isNull(idcol);
-    SEXP fnames = R_NilValue, findices = R_NilValue, f_ind = R_NilValue, ans, lf, li, target, thiscol, levels;
-    SEXP factorLevels = R_NilValue, finalFactorLevels;
-    R_len_t protecti=0;
-
-    // first level of error checks
-    if (!isLogical(sexp_usenames) || LENGTH(sexp_usenames)!=1 || LOGICAL(sexp_usenames)[0]==NA_LOGICAL)
-        error("use.names should be TRUE or FALSE");
-    if (!isLogical(sexp_fill) || LENGTH(sexp_fill) != 1 || LOGICAL(sexp_fill)[0] == NA_LOGICAL)
-        error("fill should be TRUE or FALSE");
-    if (!length(l)) return(l);
-    if (TYPEOF(l) != VECSXP) error("Input to rbindlist must be a list of data.tables");
-
-    usenames = LOGICAL(sexp_usenames)[0];
-    fill = LOGICAL(sexp_fill)[0];
-    if (fill && !usenames) {
-        // override default
-        warning("Resetting 'use.names' to TRUE. 'use.names' can not be FALSE when 'fill=TRUE'.\n");
-        usenames=TRUE;
-    }
+  R_len_t jj, ansloc, resi, i,j,r, idx, thislen;
+  struct preprocessData data;
+  Rboolean usenames, fill, to_copy = FALSE, coerced=FALSE, isidcol = !isNull(idcol);
+  SEXP fnames = R_NilValue, findices = R_NilValue, f_ind = R_NilValue, ans, lf, li, target, thiscol, levels;
+  SEXP factorLevels = R_NilValue, finalFactorLevels;
+  R_len_t protecti=0;
+
+  // first level of error checks
+  if (!isLogical(sexp_usenames) || LENGTH(sexp_usenames)!=1 || LOGICAL(sexp_usenames)[0]==NA_LOGICAL)
+    error("use.names should be TRUE or FALSE");
+  if (!isLogical(sexp_fill) || LENGTH(sexp_fill) != 1 || LOGICAL(sexp_fill)[0] == NA_LOGICAL)
+    error("fill should be TRUE or FALSE");
+  if (!length(l)) return(l);
+  if (TYPEOF(l) != VECSXP) error("Input to rbindlist must be a list of data.tables");
+
+  usenames = LOGICAL(sexp_usenames)[0];
+  fill = LOGICAL(sexp_fill)[0];
+  if (fill && !usenames) {
+    // override default
+    warning("Resetting 'use.names' to TRUE. 'use.names' can not be FALSE when 'fill=TRUE'.\n");
+    usenames=TRUE;
+  }
+
+  // check for factor, get max types, and when usenames=TRUE get the answer 'names' and column indices for proper reordering.
+  preprocess(l, usenames, fill, &data);
+  fnames   = VECTOR_ELT(data.ans_ptr, 0);
+  if (usenames) findices = VECTOR_ELT(data.ans_ptr, 1);
+  protecti = data.protecti;   // TODO very ugly and doesn't seem right. Assign items to list instead, perhaps.
+  if (data.n_rows == 0 && data.n_cols == 0) {
+    UNPROTECT(protecti);
+    return(R_NilValue);
+  }
+  if (isidcol) {
+    fnames = PROTECT(add_idcol(fnames, idcol, data.n_cols));
+    protecti++;
+  }
+  factorLevels = PROTECT(allocVector(VECSXP, data.lcount));
+  Rboolean *isRowOrdered = (Rboolean *)R_alloc(data.lcount, sizeof(Rboolean));
+  for (int i=0; i<data.lcount; i++) isRowOrdered[i] = FALSE;
+
+  ans = PROTECT(allocVector(VECSXP, data.n_cols+isidcol)); protecti++;
+  setAttrib(ans, R_NamesSymbol, fnames);
+  lf = VECTOR_ELT(l, data.first);
+  for(j=0; j<data.n_cols; j++) {
+    if (fill) target = allocNAVector(data.max_type[j], data.n_rows);
+    else target = allocVector(data.max_type[j], data.n_rows);
+    SET_VECTOR_ELT(ans, j+isidcol, target);
 
-    // check for factor, get max types, and when usenames=TRUE get the answer 'names' and column indices for proper reordering.
-    preprocess(l, usenames, fill, &data);
-    fnames   = VECTOR_ELT(data.ans_ptr, 0);
-    if (usenames) findices = VECTOR_ELT(data.ans_ptr, 1);
-    protecti = data.protecti;   // TODO very ugly and doesn't seem right. Assign items to list instead, perhaps.
-    if (data.n_rows == 0 && data.n_cols == 0) {
-        UNPROTECT(protecti);
-        return(R_NilValue);
-    }
-    if (isidcol) {
-        fnames = PROTECT(add_idcol(fnames, idcol, data.n_cols));
-        protecti++;
+    if (usenames) {
+      to_copy = TRUE;
+      f_ind   = VECTOR_ELT(findices, j);
+    } else {
+      thiscol = VECTOR_ELT(lf, j);
+      if (!isFactor(thiscol)) copyMostAttrib(thiscol, target); // all but names,dim and dimnames. And if so, we want a copy here, not keepattr's SET_ATTRIB.
     }
-    factorLevels = PROTECT(allocVector(VECSXP, data.lcount));
-    Rboolean *isRowOrdered = (Rboolean *)R_alloc(data.lcount, sizeof(Rboolean));
-    for (int i=0; i<data.lcount; i++) isRowOrdered[i] = FALSE;
-
-    ans = PROTECT(allocVector(VECSXP, data.n_cols+isidcol)); protecti++;
-    setAttrib(ans, R_NamesSymbol, fnames);
-    lf = VECTOR_ELT(l, data.first);
-    for(j=0; j<data.n_cols; j++) {
-        if (fill) target = allocNAVector(data.max_type[j], data.n_rows);
-        else target = allocVector(data.max_type[j], data.n_rows);
-        SET_VECTOR_ELT(ans, j+isidcol, target);
-
-        if (usenames) {
-            to_copy = TRUE;
-            f_ind   = VECTOR_ELT(findices, j);
-        } else {
-            thiscol = VECTOR_ELT(lf, j);
-            if (!isFactor(thiscol)) copyMostAttrib(thiscol, target); // all but names,dim and dimnames. And if so, we want a copy here, not keepattr's SET_ATTRIB.
-        }
-        ansloc = 0;
-        jj = 0; // to increment factorLevels
-        resi = -1;
-        for (i=data.first; i<LENGTH(l); i++) {
-            li = VECTOR_ELT(l,i);
-            if (!length(li)) continue;  // majority of time though, each item of l is populated
-            thislen = data.fn_rows[i];
-            idx = (usenames) ? INTEGER(f_ind)[i] : j;
-            if (idx < 0) {
-                ansloc += thislen;
-                resi++;
-                if (data.is_factor[j]) {
-                    isRowOrdered[resi] = FALSE;
-                    SET_VECTOR_ELT(factorLevels, jj, allocNAVector(data.max_type[j], 1)); // the only level here is NA.
-                    jj++;
-                }
-                continue;
-            }
-            thiscol = VECTOR_ELT(li, idx);
-            if (thislen != length(thiscol)) error("Column %d of item %d is length %d, inconsistent with first column of that item which is length %d. rbind/rbindlist doesn't recycle as it already expects each item to be a uniform list, data.frame or data.table", j+1, i+1, length(thiscol), thislen);
-            // couldn't figure out a way to this outside this loop when fill = TRUE.
-            if (to_copy && !isFactor(thiscol)) {
-                copyMostAttrib(thiscol, target);
-                to_copy = FALSE;
-            }
-            resi++;  // after the first, there might be NULL or empty which are skipped, resi increments up until lcount
-            if (TYPEOF(thiscol) != TYPEOF(target) && !isFactor(thiscol)) {
-                thiscol = PROTECT(coerceVector(thiscol, TYPEOF(target)));
-                coerced = TRUE;
-                // TO DO: options(datatable.pedantic=TRUE) to issue this warning :
-                // warning("Column %d of item %d is type '%s', inconsistent with column %d of item %d's type ('%s')",j+1,i+1,type2char(TYPEOF(thiscol)),j+1,first+1,type2char(TYPEOF(target)));
-            }
-            switch(TYPEOF(target)) {
-            case STRSXP :
-                isRowOrdered[resi] = FALSE;
-                if (isFactor(thiscol)) {
-                    levels = getAttrib(thiscol, R_LevelsSymbol);
-                    for (r=0; r<thislen; r++)
-                        if (INTEGER(thiscol)[r]==NA_INTEGER)
-                            SET_STRING_ELT(target, ansloc+r, NA_STRING);
-                        else
-                            SET_STRING_ELT(target, ansloc+r, STRING_ELT(levels,INTEGER(thiscol)[r]-1));
-
-                    // add levels to factorLevels
-                    // changed "i" to "jj" and increment 'jj' after so as to fill only non-empty tables with levels
-                    SET_VECTOR_ELT(factorLevels, jj, levels); jj++;
-                    if (isOrdered(thiscol)) isRowOrdered[resi] = TRUE;
-                } else {
-                    if (TYPEOF(thiscol) != STRSXP) error("Internal logical error in rbindlist.c (not STRSXP), please report to datatable-help.");
-                    for (r=0; r<thislen; r++) SET_STRING_ELT(target, ansloc+r, STRING_ELT(thiscol,r));
-
-                    // if this column is going to be a factor, add column to factorLevels
-                    // changed "i" to "jj" and increment 'jj' after so as to fill only non-empty tables with levels
-                    if (data.is_factor[j]) {
-                        SET_VECTOR_ELT(factorLevels, jj, thiscol);
-                        jj++;
-                    }
-                    // removed 'coerced=FALSE; UNPROTECT(1)' as it resulted in a stack imbalance.
-                    // anyways it's taken care of after the switch. So no need here.
-                }
-                break;
-            case VECSXP :
-                if (TYPEOF(thiscol) != VECSXP) error("Internal logical error in rbindlist.c (not VECSXP), please report to datatable-help.");
-                for (r=0; r<thislen; r++)
-                    SET_VECTOR_ELT(target, ansloc+r, VECTOR_ELT(thiscol,r));
-                break;
-            case CPLXSXP : // #1659 fix
-                if (TYPEOF(thiscol) != TYPEOF(target)) error("Internal logical error in rbindlist.c, type of 'thiscol' should have already been coerced to 'target'. Please report to datatable-help.");
-                for (r=0; r<thislen; r++)
-                    COMPLEX(target)[ansloc+r] = COMPLEX(thiscol)[r];
-                break;
-            case REALSXP:
-            case INTSXP:
-            case LGLSXP:
-                if (TYPEOF(thiscol) != TYPEOF(target)) error("Internal logical error in rbindlist.c, type of 'thiscol' should have already been coerced to 'target'. Please report to datatable-help.");
-                memcpy((char *)DATAPTR(target) + ansloc * SIZEOF(thiscol),
-                       (char *)DATAPTR(thiscol),
-                       thislen * SIZEOF(thiscol));
-                break;
-            default :
-                error("Unsupported column type '%s'", type2char(TYPEOF(target)));
-            }
-            ansloc += thislen;
-            if (coerced) {
-                UNPROTECT(1);
-                coerced = FALSE;
-            }
-        }
+    ansloc = 0;
+    jj = 0; // to increment factorLevels
+    resi = -1;
+    for (i=data.first; i<LENGTH(l); i++) {
+      li = VECTOR_ELT(l,i);
+      if (!length(li)) continue;  // majority of time though, each item of l is populated
+      thislen = data.fn_rows[i];
+      idx = (usenames) ? INTEGER(f_ind)[i] : j;
+      if (idx < 0) {
+        ansloc += thislen;
+        resi++;
         if (data.is_factor[j]) {
-            finalFactorLevels = combineFactorLevels(factorLevels, &(data.is_factor[j]), isRowOrdered);
-            SEXP factorLangSxp = PROTECT(lang3(install(data.is_factor[j] == 1 ? "factor" : "ordered"),
-                                               target, finalFactorLevels));
-            SET_VECTOR_ELT(ans, j+isidcol, eval(factorLangSxp, R_GlobalEnv));
-            UNPROTECT(2);  // finalFactorLevels, factorLangSxp
+          isRowOrdered[resi] = FALSE;
+          SET_VECTOR_ELT(factorLevels, jj, allocNAVector(data.max_type[j], 1)); // the only level here is NA.
+          jj++;
         }
-    }
-    if (factorLevels != R_NilValue) UNPROTECT_PTR(factorLevels);
-
-    // fix for #1432, + more efficient to move the logic to C
-    if (isidcol) {
-        R_len_t runidx = 1, cntridx = 0;
-        SEXP lnames = getAttrib(l, R_NamesSymbol);
-        if (isNull(lnames)) {
-            target = allocVector(INTSXP, data.n_rows);
-            SET_VECTOR_ELT(ans, 0, target);
-            for (i=0; i<LENGTH(l); i++) {
-                for (j=0; j<data.fn_rows[i]; j++)
-                    INTEGER(target)[cntridx++] = runidx;
-                runidx++;
-            }
+        continue;
+      }
+      thiscol = VECTOR_ELT(li, idx);
+      if (thislen != length(thiscol)) error("Column %d of item %d is length %d, inconsistent with first column of that item which is length %d. rbind/rbindlist doesn't recycle as it already expects each item to be a uniform list, data.frame or data.table", j+1, i+1, length(thiscol), thislen);
+      // couldn't figure out a way to this outside this loop when fill = TRUE.
+      if (to_copy && !isFactor(thiscol)) {
+        copyMostAttrib(thiscol, target);
+        to_copy = FALSE;
+      }
+      resi++;  // after the first, there might be NULL or empty which are skipped, resi increments up until lcount
+      if (TYPEOF(thiscol) != TYPEOF(target) && !isFactor(thiscol)) {
+        thiscol = PROTECT(coerceVector(thiscol, TYPEOF(target)));
+        coerced = TRUE;
+        // TO DO: options(datatable.pedantic=TRUE) to issue this warning :
+        // warning("Column %d of item %d is type '%s', inconsistent with column %d of item %d's type ('%s')",j+1,i+1,type2char(TYPEOF(thiscol)),j+1,first+1,type2char(TYPEOF(target)));
+      }
+      switch(TYPEOF(target)) {
+      case STRSXP :
+        isRowOrdered[resi] = FALSE;
+        if (isFactor(thiscol)) {
+          levels = getAttrib(thiscol, R_LevelsSymbol);
+          for (r=0; r<thislen; r++)
+            if (INTEGER(thiscol)[r]==NA_INTEGER)
+              SET_STRING_ELT(target, ansloc+r, NA_STRING);
+            else
+              SET_STRING_ELT(target, ansloc+r, STRING_ELT(levels,INTEGER(thiscol)[r]-1));
+
+          // add levels to factorLevels
+          // changed "i" to "jj" and increment 'jj' after so as to fill only non-empty tables with levels
+          SET_VECTOR_ELT(factorLevels, jj, levels); jj++;
+          if (isOrdered(thiscol)) isRowOrdered[resi] = TRUE;
         } else {
-            target = allocVector(STRSXP, data.n_rows);
-            SET_VECTOR_ELT(ans, 0, target);
-            for (i=0; i<LENGTH(l); i++) {
-                for (j=0; j<data.fn_rows[i]; j++)
-                    SET_STRING_ELT(target, cntridx++, STRING_ELT(lnames, i));
-            }
+          if (TYPEOF(thiscol) != STRSXP) error("Internal logical error in rbindlist.c (not STRSXP), please report to datatable-help.");
+          for (r=0; r<thislen; r++) SET_STRING_ELT(target, ansloc+r, STRING_ELT(thiscol,r));
+
+          // if this column is going to be a factor, add column to factorLevels
+          // changed "i" to "jj" and increment 'jj' after so as to fill only non-empty tables with levels
+          if (data.is_factor[j]) {
+            SET_VECTOR_ELT(factorLevels, jj, thiscol);
+            jj++;
+          }
+          // removed 'coerced=FALSE; UNPROTECT(1)' as it resulted in a stack imbalance.
+          // anyways it's taken care of after the switch. So no need here.
         }
+        break;
+      case VECSXP :
+        if (TYPEOF(thiscol) != VECSXP) error("Internal logical error in rbindlist.c (not VECSXP), please report to datatable-help.");
+        for (r=0; r<thislen; r++)
+          SET_VECTOR_ELT(target, ansloc+r, VECTOR_ELT(thiscol,r));
+        break;
+      case CPLXSXP : // #1659 fix
+        if (TYPEOF(thiscol) != TYPEOF(target)) error("Internal logical error in rbindlist.c, type of 'thiscol' should have already been coerced to 'target'. Please report to datatable-help.");
+        for (r=0; r<thislen; r++)
+          COMPLEX(target)[ansloc+r] = COMPLEX(thiscol)[r];
+        break;
+      case REALSXP:
+      case INTSXP:
+      case LGLSXP:
+        if (TYPEOF(thiscol) != TYPEOF(target)) error("Internal logical error in rbindlist.c, type of 'thiscol' should have already been coerced to 'target'. Please report to datatable-help.");
+        memcpy((char *)DATAPTR(target) + ansloc * SIZEOF(thiscol),
+             (char *)DATAPTR(thiscol),
+             thislen * SIZEOF(thiscol));
+        break;
+      default :
+        error("Unsupported column type '%s'", type2char(TYPEOF(target)));
+      }
+      ansloc += thislen;
+      if (coerced) {
+        UNPROTECT(1);
+        coerced = FALSE;
+      }
+    }
+    if (data.is_factor[j]) {
+      finalFactorLevels = combineFactorLevels(factorLevels, &(data.is_factor[j]), isRowOrdered);
+      SEXP factorLangSxp = PROTECT(lang3(install(data.is_factor[j] == 1 ? "factor" : "ordered"),
+                         target, finalFactorLevels));
+      SET_VECTOR_ELT(ans, j+isidcol, eval(factorLangSxp, R_GlobalEnv));
+      UNPROTECT(2);  // finalFactorLevels, factorLangSxp
     }
+  }
+  if (factorLevels != R_NilValue) UNPROTECT_PTR(factorLevels);
+
+  // fix for #1432, + more efficient to move the logic to C
+  if (isidcol) {
+    R_len_t runidx = 1, cntridx = 0;
+    SEXP lnames = getAttrib(l, R_NamesSymbol);
+    if (isNull(lnames)) {
+      target = allocVector(INTSXP, data.n_rows);
+      SET_VECTOR_ELT(ans, 0, target);
+      for (i=0; i<LENGTH(l); i++) {
+        for (j=0; j<data.fn_rows[i]; j++)
+          INTEGER(target)[cntridx++] = runidx;
+        runidx++;
+      }
+    } else {
+      target = allocVector(STRSXP, data.n_rows);
+      SET_VECTOR_ELT(ans, 0, target);
+      for (i=0; i<LENGTH(l); i++) {
+        for (j=0; j<data.fn_rows[i]; j++)
+          SET_STRING_ELT(target, cntridx++, STRING_ELT(lnames, i));
+      }
+    }
+  }
 
-    UNPROTECT(protecti);
-    return(ans);
+  UNPROTECT(protecti);
+  return(ans);
 }
 
 /*
@@ -805,91 +805,91 @@ SEXP rbindlist(SEXP l, SEXP sexp_usenames, SEXP sexp_fill, SEXP idcol) {
 */
 SEXP chmatch2_old(SEXP x, SEXP table, SEXP nomatch) {
 
-    R_len_t i, j, k, nx, li, si, oi;
-    SEXP dt, l, ans, order, start, lens, grpid, index;
-    if (TYPEOF(nomatch) != INTSXP || length(nomatch) != 1) error("'nomatch' must be an integer of length 1");
-    if (!length(x) || isNull(x)) return(allocVector(INTSXP, 0));
-    if (TYPEOF(x) != STRSXP) error("'x' must be a character vector");
-    nx=length(x);
-    if (!length(table) || isNull(table)) {
-        ans = PROTECT(allocVector(INTSXP, nx));
-        for (i=0; i<nx; i++) INTEGER(ans)[i] = INTEGER(nomatch)[0];
-        UNPROTECT(1);
-        return(ans);
-    }
-    if (TYPEOF(table) != STRSXP) error("'table' must be a character vector");
-    // Done with special cases. On to the real deal.
-    l = PROTECT(allocVector(VECSXP, 2));
-    SET_VECTOR_ELT(l, 0, x);
-    SET_VECTOR_ELT(l, 1, table);
-
-    UNPROTECT(1); // l
-    dt = PROTECT(unlist2(l));
-
-    // order - first time
-    order = PROTECT(fast_order(dt, 2, 1));
-    start = getAttrib(order, sym_starts);
-    lens  = PROTECT(uniq_lengths(start, length(order))); // length(order) = nrow(dt)
-    grpid = VECTOR_ELT(dt, 1);
-    index = VECTOR_ELT(dt, 2);
-
-    // replace dt[1], we don't need it anymore
-    k=0;
-    for (i=0; i<length(lens); i++) {
-        for (j=0; j<INTEGER(lens)[i]; j++) {
-            INTEGER(grpid)[INTEGER(order)[k+j]-1] = j;
-        }
-        k += j;
-    }
-    // order - again
-    UNPROTECT(2); // order, lens
-    order = PROTECT(fast_order(dt, 2, 1));
-    start = getAttrib(order, sym_starts);
-    lens  = PROTECT(uniq_lengths(start, length(order)));
-
+  R_len_t i, j, k, nx, li, si, oi;
+  SEXP dt, l, ans, order, start, lens, grpid, index;
+  if (TYPEOF(nomatch) != INTSXP || length(nomatch) != 1) error("'nomatch' must be an integer of length 1");
+  if (!length(x) || isNull(x)) return(allocVector(INTSXP, 0));
+  if (TYPEOF(x) != STRSXP) error("'x' must be a character vector");
+  nx=length(x);
+  if (!length(table) || isNull(table)) {
     ans = PROTECT(allocVector(INTSXP, nx));
-    k = 0;
-    for (i=0; i<length(lens); i++) {
-        li = INTEGER(lens)[i];
-        si = INTEGER(start)[i]-1;
-        oi = INTEGER(order)[si]-1;
-        if (oi > nx-1) continue;
-        INTEGER(ans)[oi] = (li == 2) ? INTEGER(index)[INTEGER(order)[si+1]-1]+1 : INTEGER(nomatch)[0];
-    }
-    UNPROTECT(4); // order, lens, ans
+    for (i=0; i<nx; i++) INTEGER(ans)[i] = INTEGER(nomatch)[0];
+    UNPROTECT(1);
     return(ans);
+  }
+  if (TYPEOF(table) != STRSXP) error("'table' must be a character vector");
+  // Done with special cases. On to the real deal.
+  l = PROTECT(allocVector(VECSXP, 2));
+  SET_VECTOR_ELT(l, 0, x);
+  SET_VECTOR_ELT(l, 1, table);
+
+  UNPROTECT(1); // l
+  dt = PROTECT(unlist2(l));
+
+  // order - first time
+  order = PROTECT(fast_order(dt, 2, 1));
+  start = getAttrib(order, sym_starts);
+  lens  = PROTECT(uniq_lengths(start, length(order))); // length(order) = nrow(dt)
+  grpid = VECTOR_ELT(dt, 1);
+  index = VECTOR_ELT(dt, 2);
+
+  // replace dt[1], we don't need it anymore
+  k=0;
+  for (i=0; i<length(lens); i++) {
+    for (j=0; j<INTEGER(lens)[i]; j++) {
+      INTEGER(grpid)[INTEGER(order)[k+j]-1] = j;
+    }
+    k += j;
+  }
+  // order - again
+  UNPROTECT(2); // order, lens
+  order = PROTECT(fast_order(dt, 2, 1));
+  start = getAttrib(order, sym_starts);
+  lens  = PROTECT(uniq_lengths(start, length(order)));
+
+  ans = PROTECT(allocVector(INTSXP, nx));
+  k = 0;
+  for (i=0; i<length(lens); i++) {
+    li = INTEGER(lens)[i];
+    si = INTEGER(start)[i]-1;
+    oi = INTEGER(order)[si]-1;
+    if (oi > nx-1) continue;
+    INTEGER(ans)[oi] = (li == 2) ? INTEGER(index)[INTEGER(order)[si+1]-1]+1 : INTEGER(nomatch)[0];
+  }
+  UNPROTECT(4); // order, lens, ans
+  return(ans);
 }
 
 // utility function used from within chmatch2
 static SEXP listlist(SEXP x) {
 
-    R_len_t i,j,k, nl;
-    SEXP lx, xo, xs, xl, tmp, ans, ans0, ans1;
-
-    lx = PROTECT(allocVector(VECSXP, 1));
-    SET_VECTOR_ELT(lx, 0, x);
-    xo = PROTECT(fast_order(lx, 1, 1));
-    xs = getAttrib(xo, sym_starts);
-    xl = PROTECT(uniq_lengths(xs, length(x)));
-
-    ans0 = PROTECT(allocVector(STRSXP, length(xs)));
-    ans1 = PROTECT(allocVector(VECSXP, length(xs)));
-    k=0;
-    for (i=0; i<length(xs); i++) {
-        SET_STRING_ELT(ans0, i, STRING_ELT(x, INTEGER(xo)[INTEGER(xs)[i]-1]-1));
-        nl = INTEGER(xl)[i];
-        tmp = allocVector(INTSXP, nl);
-        SET_VECTOR_ELT(ans1, i, tmp);
-        for (j=0; j<nl; j++) {
-            INTEGER(tmp)[j] = INTEGER(xo)[k+j];
-        }
-        k += j;
+  R_len_t i,j,k, nl;
+  SEXP lx, xo, xs, xl, tmp, ans, ans0, ans1;
+
+  lx = PROTECT(allocVector(VECSXP, 1));
+  SET_VECTOR_ELT(lx, 0, x);
+  xo = PROTECT(fast_order(lx, 1, 1));
+  xs = getAttrib(xo, sym_starts);
+  xl = PROTECT(uniq_lengths(xs, length(x)));
+
+  ans0 = PROTECT(allocVector(STRSXP, length(xs)));
+  ans1 = PROTECT(allocVector(VECSXP, length(xs)));
+  k=0;
+  for (i=0; i<length(xs); i++) {
+    SET_STRING_ELT(ans0, i, STRING_ELT(x, INTEGER(xo)[INTEGER(xs)[i]-1]-1));
+    nl = INTEGER(xl)[i];
+    tmp = allocVector(INTSXP, nl);
+    SET_VECTOR_ELT(ans1, i, tmp);
+    for (j=0; j<nl; j++) {
+      INTEGER(tmp)[j] = INTEGER(xo)[k+j];
     }
-    ans = PROTECT(allocVector(VECSXP, 2));
-    SET_VECTOR_ELT(ans, 0, ans0);
-    SET_VECTOR_ELT(ans, 1, ans1);
-    UNPROTECT(6);
-    return(ans);
+    k += j;
+  }
+  ans = PROTECT(allocVector(VECSXP, 2));
+  SET_VECTOR_ELT(ans, 0, ans0);
+  SET_VECTOR_ELT(ans, 1, ans1);
+  UNPROTECT(6);
+  return(ans);
 }
 
 /*
@@ -907,45 +907,45 @@ static SEXP listlist(SEXP x) {
 */
 SEXP chmatch2(SEXP x, SEXP y, SEXP nomatch) {
 
-    R_len_t i, j, k, nx, ix, iy;
-    SEXP xll, yll, xu, yu, ans, xl, yl, mx;
-    if (TYPEOF(nomatch) != INTSXP || length(nomatch) != 1) error("'nomatch' must be an integer of length 1");
-    if (!length(x) || isNull(x)) return(allocVector(INTSXP, 0));
-    if (TYPEOF(x) != STRSXP) error("'x' must be a character vector");
-    nx = length(x);
-    if (!length(y) || isNull(y)) {
-        ans = PROTECT(allocVector(INTSXP, nx));
-        for (i=0; i<nx; i++) INTEGER(ans)[i] = INTEGER(nomatch)[0];
-        UNPROTECT(1);
-        return(ans);
-    }
-    if (TYPEOF(y) != STRSXP) error("'table' must be a character vector");
-    // Done with special cases. On to the real deal.
-    xll = PROTECT(listlist(x));
-    yll = PROTECT(listlist(y));
-
-    xu = VECTOR_ELT(xll, 0);
-    yu = VECTOR_ELT(yll, 0);
-
-    mx  = PROTECT(chmatch(xu, yu, 0, FALSE));
+  R_len_t i, j, k, nx, ix, iy;
+  SEXP xll, yll, xu, yu, ans, xl, yl, mx;
+  if (TYPEOF(nomatch) != INTSXP || length(nomatch) != 1) error("'nomatch' must be an integer of length 1");
+  if (!length(x) || isNull(x)) return(allocVector(INTSXP, 0));
+  if (TYPEOF(x) != STRSXP) error("'x' must be a character vector");
+  nx = length(x);
+  if (!length(y) || isNull(y)) {
     ans = PROTECT(allocVector(INTSXP, nx));
-    k=0;
-    for (i=0; i<length(mx); i++) {
-        xl = VECTOR_ELT(VECTOR_ELT(xll, 1), i);
-        ix = length(xl);
-        if (INTEGER(mx)[i] == 0) {
-            for (j=0; j<ix; j++)
-                INTEGER(ans)[INTEGER(xl)[j]-1] = INTEGER(nomatch)[0];
-        } else {
-            yl = VECTOR_ELT(VECTOR_ELT(yll, 1), INTEGER(mx)[i]-1);
-            iy = length(yl);
-            for (j=0; j < ix; j++)
-                INTEGER(ans)[INTEGER(xl)[j]-1] = (j < iy) ? INTEGER(yl)[j] : INTEGER(nomatch)[0];
-            k += ix;
-        }
-    }
-    UNPROTECT(4);
+    for (i=0; i<nx; i++) INTEGER(ans)[i] = INTEGER(nomatch)[0];
+    UNPROTECT(1);
     return(ans);
+  }
+  if (TYPEOF(y) != STRSXP) error("'table' must be a character vector");
+  // Done with special cases. On to the real deal.
+  xll = PROTECT(listlist(x));
+  yll = PROTECT(listlist(y));
+
+  xu = VECTOR_ELT(xll, 0);
+  yu = VECTOR_ELT(yll, 0);
+
+  mx  = PROTECT(chmatch(xu, yu, 0, FALSE));
+  ans = PROTECT(allocVector(INTSXP, nx));
+  k=0;
+  for (i=0; i<length(mx); i++) {
+    xl = VECTOR_ELT(VECTOR_ELT(xll, 1), i);
+    ix = length(xl);
+    if (INTEGER(mx)[i] == 0) {
+      for (j=0; j<ix; j++)
+        INTEGER(ans)[INTEGER(xl)[j]-1] = INTEGER(nomatch)[0];
+    } else {
+      yl = VECTOR_ELT(VECTOR_ELT(yll, 1), INTEGER(mx)[i]-1);
+      iy = length(yl);
+      for (j=0; j < ix; j++)
+        INTEGER(ans)[INTEGER(xl)[j]-1] = (j < iy) ? INTEGER(yl)[j] : INTEGER(nomatch)[0];
+      k += ix;
+    }
+  }
+  UNPROTECT(4);
+  return(ans);
 
 }
 
diff --git a/src/reorder.c b/src/reorder.c
index a0677967cb..6257d67e7b 100644
--- a/src/reorder.c
+++ b/src/reorder.c
@@ -2,139 +2,139 @@
 
 SEXP reorder(SEXP x, SEXP order)
 {
-    // For internal use only by setkey().
-    // 'order' must strictly be a permutation of 1:n (i.e. no repeats, zeros or NAs)
-    // If only a small subset in the middle is reordered the ends are moved in: [start,end].
-    // x may be a vector, or a list of same-length vectors such as data.table
+  // For internal use only by setkey().
+  // 'order' must strictly be a permutation of 1:n (i.e. no repeats, zeros or NAs)
+  // If only a small subset in the middle is reordered the ends are moved in: [start,end].
+  // x may be a vector, or a list of same-length vectors such as data.table
 
-    R_len_t nrow, ncol;
-    int maxSize = 0;
-    if (isNewList(x)) {
-      nrow = length(VECTOR_ELT(x,0));
-      ncol = length(x);
-      for (int i=0; i<ncol; i++) {
-        SEXP v = VECTOR_ELT(x,i);
-        if (SIZEOF(v)!=4 && SIZEOF(v)!=8)
-          error("Item %d of list is type '%s' which isn't yet supported", i+1, type2char(TYPEOF(v)));
-        if (length(v)!=nrow)
-          error("Column %d is length %d which differs from length of column 1 (%d). Invalid data.table.", i+1, length(v), nrow);
-        if (SIZEOF(v) > maxSize)
-          maxSize=SIZEOF(v);
-      }
-    } else {
-      if (SIZEOF(x)!=4 && SIZEOF(x)!=8)
-        error("reorder accepts vectors but this non-VECSXP is type '%s' which isn't yet supported", type2char(TYPEOF(x)));
-      maxSize = SIZEOF(x);
-      nrow = length(x);
-      ncol = 1;
+  R_len_t nrow, ncol;
+  int maxSize = 0;
+  if (isNewList(x)) {
+    nrow = length(VECTOR_ELT(x,0));
+    ncol = length(x);
+    for (int i=0; i<ncol; i++) {
+      SEXP v = VECTOR_ELT(x,i);
+      if (SIZEOF(v)!=4 && SIZEOF(v)!=8)
+        error("Item %d of list is type '%s' which isn't yet supported", i+1, type2char(TYPEOF(v)));
+      if (length(v)!=nrow)
+        error("Column %d is length %d which differs from length of column 1 (%d). Invalid data.table.", i+1, length(v), nrow);
+      if (SIZEOF(v) > maxSize)
+        maxSize=SIZEOF(v);
     }
-    if (!isInteger(order)) error("order must be an integer vector");
-    if (length(order) != nrow) error("nrow(x)[%d]!=length(order)[%d]",nrow,length(order));
+  } else {
+    if (SIZEOF(x)!=4 && SIZEOF(x)!=8)
+      error("reorder accepts vectors but this non-VECSXP is type '%s' which isn't yet supported", type2char(TYPEOF(x)));
+    maxSize = SIZEOF(x);
+    nrow = length(x);
+    ncol = 1;
+  }
+  if (!isInteger(order)) error("order must be an integer vector");
+  if (length(order) != nrow) error("nrow(x)[%d]!=length(order)[%d]",nrow,length(order));
 
-    R_len_t start = 0;
-    while (start<nrow && INTEGER(order)[start] == start+1) start++;
-    if (start==nrow) return(R_NilValue);  // input is 1:n, nothing to do
-    R_len_t end = nrow-1;
-    while (INTEGER(order)[end] == end+1) end--;
-    for (R_len_t i=start; i<=end; i++) {
-      int itmp = INTEGER(order)[i]-1;
-      if (itmp<start || itmp>end) error("order is not a permutation of 1:nrow[%d]", nrow);
-    }
-    // Creorder is for internal use (so we should get the input right!), but the check above seems sensible, otherwise
-    // would be segfault below. The for loop above should run in neglible time (sequential) and will also catch NAs.
-    // It won't catch duplicates in order, but that's ok. Checking that would be going too far given this is for internal use only.
+  R_len_t start = 0;
+  while (start<nrow && INTEGER(order)[start] == start+1) start++;
+  if (start==nrow) return(R_NilValue);  // input is 1:n, nothing to do
+  R_len_t end = nrow-1;
+  while (INTEGER(order)[end] == end+1) end--;
+  for (R_len_t i=start; i<=end; i++) {
+    int itmp = INTEGER(order)[i]-1;
+    if (itmp<start || itmp>end) error("order is not a permutation of 1:nrow[%d]", nrow);
+  }
+  // Creorder is for internal use (so we should get the input right!), but the check above seems sensible, otherwise
+  // would be segfault below. The for loop above should run in neglible time (sequential) and will also catch NAs.
+  // It won't catch duplicates in order, but that's ok. Checking that would be going too far given this is for internal use only.
 
-    // Enough working ram for one column of the largest type, for every thread.
-    // Up to a limit of 1GB total. It's system dependent how to find out the truly free RAM - TODO.
-    // Without a limit it could easily start swapping and not work at all.
-    int nth = MIN(getDTthreads(), ncol);
-    size_t oneTmpSize = (end-start+1)*(size_t)maxSize;
-    size_t totalLimit = 1024*1024*(size_t)1024;  // 1GB
-    nth = MIN(totalLimit/oneTmpSize, nth);
-    if (nth==0) nth=1;  // if one column's worth is very big, we'll just have to try
-    char *tmp[nth];  // VLA ok because small; limited to max getDTthreads() not ncol which could be > 1e6
-    int ok=0; for (; ok<nth; ok++) {
-      tmp[ok] = malloc(oneTmpSize);
-      if (tmp[ok] == NULL) break;
-    }
-    if (ok==0) error("unable to allocate %d * %d bytes of working memory for reordering data.table", end-start+1, maxSize);
-    nth = ok;  // as many threads for which we have a successful malloc
-    // So we can still reorder a 10GB table in 16GB of RAM, as long as we have at least one column's worth of tmp
+  // Enough working ram for one column of the largest type, for every thread.
+  // Up to a limit of 1GB total. It's system dependent how to find out the truly free RAM - TODO.
+  // Without a limit it could easily start swapping and not work at all.
+  int nth = MIN(getDTthreads(), ncol);
+  size_t oneTmpSize = (end-start+1)*(size_t)maxSize;
+  size_t totalLimit = 1024*1024*(size_t)1024;  // 1GB
+  nth = MIN(totalLimit/oneTmpSize, nth);
+  if (nth==0) nth=1;  // if one column's worth is very big, we'll just have to try
+  char *tmp[nth];  // VLA ok because small; limited to max getDTthreads() not ncol which could be > 1e6
+  int ok=0; for (; ok<nth; ok++) {
+    tmp[ok] = malloc(oneTmpSize);
+    if (tmp[ok] == NULL) break;
+  }
+  if (ok==0) error("unable to allocate %d * %d bytes of working memory for reordering data.table", end-start+1, maxSize);
+  nth = ok;  // as many threads for which we have a successful malloc
+  // So we can still reorder a 10GB table in 16GB of RAM, as long as we have at least one column's worth of tmp
 
-    #pragma omp parallel for schedule(dynamic) num_threads(nth)
-    for (int i=0; i<ncol; i++) {
-      const SEXP v = isNewList(x) ? VECTOR_ELT(x,i) : x;
-      const int size = SIZEOF(v);
-      const int me = omp_get_thread_num();
-      const int *vi = INTEGER(order)+start;
-      if (size==4) {
-        const int *vd = (const int *)DATAPTR(v);
-        int *tmpp = (int *)tmp[me];
-        for (int j=start; j<=end; j++) {
-          *tmpp++ = vd[*vi++ -1];  // just copies 4 bytes, including pointers on 32bit
-        }
-      } else {
-        const double *vd = (const double *)DATAPTR(v);
-        double *tmpp = (double *)tmp[me];
-        for (int j=start; j<=end; j++) {
-          *tmpp++ = vd[*vi++ -1];  // just copies 8 bytes, pointers too including STRSXP and VECSXP
-        }
+  #pragma omp parallel for schedule(dynamic) num_threads(nth)
+  for (int i=0; i<ncol; i++) {
+    const SEXP v = isNewList(x) ? VECTOR_ELT(x,i) : x;
+    const int size = SIZEOF(v);
+    const int me = omp_get_thread_num();
+    const int *vi = INTEGER(order)+start;
+    if (size==4) {
+      const int *vd = (const int *)DATAPTR(v);
+      int *tmpp = (int *)tmp[me];
+      for (int j=start; j<=end; j++) {
+        *tmpp++ = vd[*vi++ -1];  // just copies 4 bytes, including pointers on 32bit
+      }
+    } else {
+      const double *vd = (const double *)DATAPTR(v);
+      double *tmpp = (double *)tmp[me];
+      for (int j=start; j<=end; j++) {
+        *tmpp++ = vd[*vi++ -1];  // just copies 8 bytes, pointers too including STRSXP and VECSXP
       }
-      // How is this possible to not only ignore the write barrier but in parallel too?
-      // Only because this reorder() function accepts and checks a unique permutation of 1:nrow. It
-      // performs an in-place shuffle. This operation in the end does not change gcgen, mark or
-      // named/refcnt. They all stay the same even for STRSXP and VECSXP because it's just a data shuffle.
-      //
-      // Theory:
-      // The write to tmp is contiguous and io efficient (so less threads should not help that)
-      // The read from vd is as io efficient as order is ordered (the more threads the better when close
-      // to ordered but less threads may help when not very ordered).
-      // TODO large data benchmark to confirm theory and auto tune.
-      // io probably limits too much but at least this is our best shot (e.g. branchless) in finding out
-      // on other platforms with faster bus, perhaps
-
-      // copy the reordered data back into the original vector
-      memcpy((char *)DATAPTR(v) + start*(size_t)size,
-             tmp[me],
-             (end-start+1)*(size_t)size);
-      // size_t, otherwise #5305 (integer overflow in memcpy)
     }
-    for (int i=0; i<nth; i++) free(tmp[i]);
-    return(R_NilValue);
+    // How is this possible to not only ignore the write barrier but in parallel too?
+    // Only because this reorder() function accepts and checks a unique permutation of 1:nrow. It
+    // performs an in-place shuffle. This operation in the end does not change gcgen, mark or
+    // named/refcnt. They all stay the same even for STRSXP and VECSXP because it's just a data shuffle.
+    //
+    // Theory:
+    // The write to tmp is contiguous and io efficient (so less threads should not help that)
+    // The read from vd is as io efficient as order is ordered (the more threads the better when close
+    // to ordered but less threads may help when not very ordered).
+    // TODO large data benchmark to confirm theory and auto tune.
+    // io probably limits too much but at least this is our best shot (e.g. branchless) in finding out
+    // on other platforms with faster bus, perhaps
+
+    // copy the reordered data back into the original vector
+    memcpy((char *)DATAPTR(v) + start*(size_t)size,
+           tmp[me],
+           (end-start+1)*(size_t)size);
+    // size_t, otherwise #5305 (integer overflow in memcpy)
+  }
+  for (int i=0; i<nth; i++) free(tmp[i]);
+  return(R_NilValue);
 }
 
 // reverse a vector - equivalent of rev(x) in base, but implemented in C and about 12x faster (on 1e8)
 SEXP setrev(SEXP x) {
-    R_len_t j, n, len;
-    size_t size;
-    char *tmp, *xt;
-    if (TYPEOF(x) == VECSXP || isMatrix(x)) error("Input 'x' must be a vector");
-    len = length(x);
-    if (len <= 1) return(x);
-    size = SIZEOF(x);
-    if (!size) error("don't know how to reverse type '%s' of input 'x'.",type2char(TYPEOF(x)));
-    n = (int)(len/2);
-    xt = (char *)DATAPTR(x);
-    if (size==4) {
-        tmp = (char *)Calloc(1, int);
-        if (!tmp) error("unable to allocate temporary working memory for reordering x");
-        for (j=0;j<n;j++) {
-            *(int *)tmp = ((int *)xt)[j];  // just copies 4 bytes (pointers on 32bit too)
-            ((int *)xt)[j] = ((int *)xt)[len-1-j];
-            ((int *)xt)[len-1-j] = *(int *)tmp;
-        }
-    } else {
-        if (size!=8) error("Size of x isn't 4 or 8");
-        tmp = (char *)Calloc(1, double);
-        if (!tmp) error("unable to allocate temporary working memory for reordering x");
-        for (j=0;j<n;j++) {
-            *(double *)tmp = ((double *)xt)[j];  // just copies 8 bytes (pointers on 64bit too)
-            ((double *)xt)[j] = ((double *)xt)[len-1-j];
-            ((double *)xt)[len-1-j] = *(double *)tmp;
-        }
+  R_len_t j, n, len;
+  size_t size;
+  char *tmp, *xt;
+  if (TYPEOF(x) == VECSXP || isMatrix(x)) error("Input 'x' must be a vector");
+  len = length(x);
+  if (len <= 1) return(x);
+  size = SIZEOF(x);
+  if (!size) error("don't know how to reverse type '%s' of input 'x'.",type2char(TYPEOF(x)));
+  n = (int)(len/2);
+  xt = (char *)DATAPTR(x);
+  if (size==4) {
+    tmp = (char *)Calloc(1, int);
+    if (!tmp) error("unable to allocate temporary working memory for reordering x");
+    for (j=0;j<n;j++) {
+      *(int *)tmp = ((int *)xt)[j];  // just copies 4 bytes (pointers on 32bit too)
+      ((int *)xt)[j] = ((int *)xt)[len-1-j];
+      ((int *)xt)[len-1-j] = *(int *)tmp;
+    }
+  } else {
+    if (size!=8) error("Size of x isn't 4 or 8");
+    tmp = (char *)Calloc(1, double);
+    if (!tmp) error("unable to allocate temporary working memory for reordering x");
+    for (j=0;j<n;j++) {
+      *(double *)tmp = ((double *)xt)[j];  // just copies 8 bytes (pointers on 64bit too)
+      ((double *)xt)[j] = ((double *)xt)[len-1-j];
+      ((double *)xt)[len-1-j] = *(double *)tmp;
     }
-    Free(tmp);
-    return(R_NilValue);
+  }
+  Free(tmp);
+  return(R_NilValue);
 }
 
 
diff --git a/src/shift.c b/src/shift.c
index 3f4368e8d3..b309ca4fae 100644
--- a/src/shift.c
+++ b/src/shift.c
@@ -4,228 +4,228 @@
 
 SEXP shift(SEXP obj, SEXP k, SEXP fill, SEXP type) {
 
-    size_t size;
-    R_len_t i=0, j, m, nx, nk, xrows, thisk;
-    SEXP x, tmp=R_NilValue, this, ans, thisfill, class;
-    unsigned long long *dthisfill;
-    enum {LAG, LEAD} stype = LAG;
-    if (!length(obj)) return(obj); // NULL, list()
-    if (isVectorAtomic(obj)) {
-        x = allocVector(VECSXP, 1);
-        SET_VECTOR_ELT(x, 0, obj);
-    } else x = obj;
-    if (!isNewList(x))
-        error("x must be a list, data.frame or data.table");
-    if (!isInteger(k))
-        error("Internal error: n must be integer");
-    if (length(fill) != 1)
-        error("fill must be a vector of length 1");
-    if (!isString(type) || length(type) != 1)
-        error("type must be a character vector of length 1");
+  size_t size;
+  R_len_t i=0, j, m, nx, nk, xrows, thisk;
+  SEXP x, tmp=R_NilValue, this, ans, thisfill, class;
+  unsigned long long *dthisfill;
+  enum {LAG, LEAD} stype = LAG;
+  if (!length(obj)) return(obj); // NULL, list()
+  if (isVectorAtomic(obj)) {
+    x = allocVector(VECSXP, 1);
+    SET_VECTOR_ELT(x, 0, obj);
+  } else x = obj;
+  if (!isNewList(x))
+    error("x must be a list, data.frame or data.table");
+  if (!isInteger(k))
+    error("Internal error: n must be integer");
+  if (length(fill) != 1)
+    error("fill must be a vector of length 1");
+  if (!isString(type) || length(type) != 1)
+    error("type must be a character vector of length 1");
 
-    if (!strcmp(CHAR(STRING_ELT(type, 0)), "lag"))  stype = LAG;
-    else if (!strcmp(CHAR(STRING_ELT(type, 0)), "lead")) stype = LEAD;
-    else error("Internal error: invalid type for shift(), should have been caught before. Please report to datatable-help");
+  if (!strcmp(CHAR(STRING_ELT(type, 0)), "lag"))  stype = LAG;
+  else if (!strcmp(CHAR(STRING_ELT(type, 0)), "lead")) stype = LEAD;
+  else error("Internal error: invalid type for shift(), should have been caught before. Please report to datatable-help");
 
-    nx = length(x); nk = length(k);
-    i=0;
-    while(i < nk && INTEGER(k)[i] >= 0) i++;
-    if (i != nk)
-        error("n must be non-negative integer values (>= 0)");
-    ans = PROTECT(allocVector(VECSXP, nk * nx));
-    if (stype == LAG) {
-        for (i=0; i<nx; i++) {
-            this  = VECTOR_ELT(x, i);
-            size  = SIZEOF(this);
-            xrows = length(this);
-            switch (TYPEOF(this)) {
-            case INTSXP :
-                thisfill = PROTECT(coerceVector(fill, INTSXP));
-                for (j=0; j<nk; j++) {
-                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                    tmp = allocVector(INTSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    if (xrows - INTEGER(k)[j] > 0)
-                        memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
-                               (char *)DATAPTR(this),
-                               (xrows-INTEGER(k)[j])*size);
-                    for (m=0; m<thisk; m++)
-                        INTEGER(tmp)[m] = INTEGER(thisfill)[0];
-                    copyMostAttrib(this, tmp);
-                    if (isFactor(this))
-                        setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
-                }
-                break;
-
-            case REALSXP :
-                class = getAttrib(this, R_ClassSymbol);
-                if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
-                    thisfill = PROTECT(allocVector(REALSXP, 1));
-                    dthisfill = (unsigned long long *)REAL(thisfill);
-                    if (INTEGER(fill)[0] == NA_INTEGER)
-                        dthisfill[0] = NA_INT64_LL;
-                    else dthisfill[0] = (unsigned long long)INTEGER(fill)[0];
-                } else {
-                    thisfill = PROTECT(coerceVector(fill, REALSXP));
-                }
-                for (j=0; j<nk; j++) {
-                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                    tmp = allocVector(REALSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    if (xrows - INTEGER(k)[j] > 0) {
-                        memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
-                               (char *)DATAPTR(this),
-                               (xrows-INTEGER(k)[j])*size);
-                    }
-                    for (m=0; m<thisk; m++) {
-                        REAL(tmp)[m] = REAL(thisfill)[0];
-                    }
-                    copyMostAttrib(this, tmp);
-                }
-                break;
+  nx = length(x); nk = length(k);
+  i=0;
+  while(i < nk && INTEGER(k)[i] >= 0) i++;
+  if (i != nk)
+    error("n must be non-negative integer values (>= 0)");
+  ans = PROTECT(allocVector(VECSXP, nk * nx));
+  if (stype == LAG) {
+    for (i=0; i<nx; i++) {
+      this  = VECTOR_ELT(x, i);
+      size  = SIZEOF(this);
+      xrows = length(this);
+      switch (TYPEOF(this)) {
+      case INTSXP :
+        thisfill = PROTECT(coerceVector(fill, INTSXP));
+        for (j=0; j<nk; j++) {
+          thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+          tmp = allocVector(INTSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          if (xrows - INTEGER(k)[j] > 0)
+            memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
+                 (char *)DATAPTR(this),
+                 (xrows-INTEGER(k)[j])*size);
+          for (m=0; m<thisk; m++)
+            INTEGER(tmp)[m] = INTEGER(thisfill)[0];
+          copyMostAttrib(this, tmp);
+          if (isFactor(this))
+            setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
+        }
+        break;
 
-            case LGLSXP :
-                thisfill = PROTECT(coerceVector(fill, LGLSXP));
-                for (j=0; j<nk; j++) {
-                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                    tmp = allocVector(LGLSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    if (xrows - INTEGER(k)[j] > 0)
-                        memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
-                               (char *)DATAPTR(this),
-                               (xrows-INTEGER(k)[j])*size);
-                    for (m=0; m<thisk; m++)
-                        LOGICAL(tmp)[m] = LOGICAL(thisfill)[0];
-                    copyMostAttrib(this, tmp);
-                }
-                break;
+      case REALSXP :
+        class = getAttrib(this, R_ClassSymbol);
+        if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
+          thisfill = PROTECT(allocVector(REALSXP, 1));
+          dthisfill = (unsigned long long *)REAL(thisfill);
+          if (INTEGER(fill)[0] == NA_INTEGER)
+            dthisfill[0] = NA_INT64_LL;
+          else dthisfill[0] = (unsigned long long)INTEGER(fill)[0];
+        } else {
+          thisfill = PROTECT(coerceVector(fill, REALSXP));
+        }
+        for (j=0; j<nk; j++) {
+          thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+          tmp = allocVector(REALSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          if (xrows - INTEGER(k)[j] > 0) {
+            memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
+                 (char *)DATAPTR(this),
+                 (xrows-INTEGER(k)[j])*size);
+          }
+          for (m=0; m<thisk; m++) {
+            REAL(tmp)[m] = REAL(thisfill)[0];
+          }
+          copyMostAttrib(this, tmp);
+        }
+        break;
 
-            case STRSXP :
-                thisfill = PROTECT(coerceVector(fill, STRSXP));
-                for (j=0; j<nk; j++) {
-                    tmp = allocVector(STRSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    for (m=0; m<xrows; m++)
-                        SET_STRING_ELT(tmp, m, (m < INTEGER(k)[j]) ? STRING_ELT(thisfill, 0) : STRING_ELT(this, m - INTEGER(k)[j]));
-                    copyMostAttrib(this, tmp);
-                }
-                break;
+      case LGLSXP :
+        thisfill = PROTECT(coerceVector(fill, LGLSXP));
+        for (j=0; j<nk; j++) {
+          thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+          tmp = allocVector(LGLSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          if (xrows - INTEGER(k)[j] > 0)
+            memcpy((char *)DATAPTR(tmp)+(INTEGER(k)[j]*size),
+                 (char *)DATAPTR(this),
+                 (xrows-INTEGER(k)[j])*size);
+          for (m=0; m<thisk; m++)
+            LOGICAL(tmp)[m] = LOGICAL(thisfill)[0];
+          copyMostAttrib(this, tmp);
+        }
+        break;
 
-            case VECSXP :
-                thisfill = PROTECT(coerceVector(fill, VECSXP));
-                for (j=0; j<nk; j++) {
-                    tmp = allocVector(VECSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    for (m=0; m<xrows; m++)
-                        SET_VECTOR_ELT(tmp, m, (m < INTEGER(k)[j]) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(this, m - INTEGER(k)[j]));
-                    copyMostAttrib(this, tmp);
-                }
-                break;
+      case STRSXP :
+        thisfill = PROTECT(coerceVector(fill, STRSXP));
+        for (j=0; j<nk; j++) {
+          tmp = allocVector(STRSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          for (m=0; m<xrows; m++)
+            SET_STRING_ELT(tmp, m, (m < INTEGER(k)[j]) ? STRING_ELT(thisfill, 0) : STRING_ELT(this, m - INTEGER(k)[j]));
+          copyMostAttrib(this, tmp);
+        }
+        break;
 
-            default :
-                error("Unsupported type '%s'", type2char(TYPEOF(this)));
-            }
-            copyMostAttrib(this, tmp);
-            if (isFactor(this))
-                setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
-            UNPROTECT(1);
+      case VECSXP :
+        thisfill = PROTECT(coerceVector(fill, VECSXP));
+        for (j=0; j<nk; j++) {
+          tmp = allocVector(VECSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          for (m=0; m<xrows; m++)
+            SET_VECTOR_ELT(tmp, m, (m < INTEGER(k)[j]) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(this, m - INTEGER(k)[j]));
+          copyMostAttrib(this, tmp);
         }
-    } else if (stype == LEAD) {
-        for (i=0; i<nx; i++) {
-            this  = VECTOR_ELT(x, i);
-            size  = SIZEOF(this);
-            xrows = length(this);
-            switch (TYPEOF(this)) {
-            case INTSXP :
-                thisfill = PROTECT(coerceVector(fill, INTSXP));
-                for (j=0; j<nk; j++) {
-                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                    tmp = allocVector(INTSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    if (xrows - INTEGER(k)[j] > 0)
-                        memcpy((char *)DATAPTR(tmp),
-                               (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
-                               (xrows-INTEGER(k)[j])*size);
-                    for (m=xrows-thisk; m<xrows; m++)
-                        INTEGER(tmp)[m] = INTEGER(thisfill)[0];
-                    copyMostAttrib(this, tmp);
-                    if (isFactor(this))
-                        setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
-                }
-                break;
+        break;
 
-            case REALSXP :
-                class = getAttrib(this, R_ClassSymbol);
-                if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
-                    thisfill = PROTECT(allocVector(REALSXP, 1));
-                    dthisfill = (unsigned long long *)REAL(thisfill);
-                    if (INTEGER(fill)[0] == NA_INTEGER)
-                        dthisfill[0] = NA_INT64_LL;
-                    else dthisfill[0] = (unsigned long long)INTEGER(fill)[0];
-                } else {
-                    thisfill = PROTECT(coerceVector(fill, REALSXP));
-                }
-                for (j=0; j<nk; j++) {
-                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                    tmp = allocVector(REALSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    if (xrows - INTEGER(k)[j] > 0)
-                        memcpy((char *)DATAPTR(tmp),
-                               (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
-                               (xrows-INTEGER(k)[j])*size);
-                    for (m=xrows-thisk; m<xrows; m++)
-                        REAL(tmp)[m] = REAL(thisfill)[0];
-                    copyMostAttrib(this, tmp);
-                }
-                break;
+      default :
+        error("Unsupported type '%s'", type2char(TYPEOF(this)));
+      }
+      copyMostAttrib(this, tmp);
+      if (isFactor(this))
+        setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
+      UNPROTECT(1);
+    }
+  } else if (stype == LEAD) {
+    for (i=0; i<nx; i++) {
+      this  = VECTOR_ELT(x, i);
+      size  = SIZEOF(this);
+      xrows = length(this);
+      switch (TYPEOF(this)) {
+      case INTSXP :
+        thisfill = PROTECT(coerceVector(fill, INTSXP));
+        for (j=0; j<nk; j++) {
+          thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+          tmp = allocVector(INTSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          if (xrows - INTEGER(k)[j] > 0)
+            memcpy((char *)DATAPTR(tmp),
+                 (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
+                 (xrows-INTEGER(k)[j])*size);
+          for (m=xrows-thisk; m<xrows; m++)
+            INTEGER(tmp)[m] = INTEGER(thisfill)[0];
+          copyMostAttrib(this, tmp);
+          if (isFactor(this))
+            setAttrib(tmp, R_LevelsSymbol, getAttrib(this, R_LevelsSymbol));
+        }
+        break;
 
-            case LGLSXP :
-                thisfill = PROTECT(coerceVector(fill, LGLSXP));
-                for (j=0; j<nk; j++) {
-                    thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
-                    tmp = allocVector(LGLSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    if (xrows - INTEGER(k)[j] > 0)
-                        memcpy((char *)DATAPTR(tmp),
-                               (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
-                               (xrows-INTEGER(k)[j])*size);
-                    for (m=xrows-thisk; m<xrows; m++)
-                        LOGICAL(tmp)[m] = LOGICAL(thisfill)[0];
-                    copyMostAttrib(this, tmp);
-                }
-                break;
+      case REALSXP :
+        class = getAttrib(this, R_ClassSymbol);
+        if (isString(class) && STRING_ELT(class, 0) == char_integer64) {
+          thisfill = PROTECT(allocVector(REALSXP, 1));
+          dthisfill = (unsigned long long *)REAL(thisfill);
+          if (INTEGER(fill)[0] == NA_INTEGER)
+            dthisfill[0] = NA_INT64_LL;
+          else dthisfill[0] = (unsigned long long)INTEGER(fill)[0];
+        } else {
+          thisfill = PROTECT(coerceVector(fill, REALSXP));
+        }
+        for (j=0; j<nk; j++) {
+          thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+          tmp = allocVector(REALSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          if (xrows - INTEGER(k)[j] > 0)
+            memcpy((char *)DATAPTR(tmp),
+                 (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
+                 (xrows-INTEGER(k)[j])*size);
+          for (m=xrows-thisk; m<xrows; m++)
+            REAL(tmp)[m] = REAL(thisfill)[0];
+          copyMostAttrib(this, tmp);
+        }
+        break;
 
-            case STRSXP :
-                thisfill = PROTECT(coerceVector(fill, STRSXP));
-                for (j=0; j<nk; j++) {
-                    tmp = allocVector(STRSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    for (m=0; m<xrows; m++)
-                        SET_STRING_ELT(tmp, m, (xrows-m <= INTEGER(k)[j]) ? STRING_ELT(thisfill, 0) : STRING_ELT(this, m + INTEGER(k)[j]));
-                    copyMostAttrib(this, tmp);
-                }
-                break;
+      case LGLSXP :
+        thisfill = PROTECT(coerceVector(fill, LGLSXP));
+        for (j=0; j<nk; j++) {
+          thisk = (xrows >= INTEGER(k)[j]) ? INTEGER(k)[j] : xrows;
+          tmp = allocVector(LGLSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          if (xrows - INTEGER(k)[j] > 0)
+            memcpy((char *)DATAPTR(tmp),
+                 (char *)DATAPTR(this)+(INTEGER(k)[j]*size),
+                 (xrows-INTEGER(k)[j])*size);
+          for (m=xrows-thisk; m<xrows; m++)
+            LOGICAL(tmp)[m] = LOGICAL(thisfill)[0];
+          copyMostAttrib(this, tmp);
+        }
+        break;
 
-            case VECSXP :
-                thisfill = PROTECT(coerceVector(fill, VECSXP));
-                for (j=0; j<nk; j++) {
-                    tmp = allocVector(VECSXP, xrows);
-                    SET_VECTOR_ELT(ans, i*nk+j, tmp);
-                    for (m=0; m<xrows; m++)
-                        SET_VECTOR_ELT(tmp, m, (xrows-m <= INTEGER(k)[j]) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(this, m + INTEGER(k)[j]));
-                        copyMostAttrib(this, tmp);
-                }
-                break;
+      case STRSXP :
+        thisfill = PROTECT(coerceVector(fill, STRSXP));
+        for (j=0; j<nk; j++) {
+          tmp = allocVector(STRSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          for (m=0; m<xrows; m++)
+            SET_STRING_ELT(tmp, m, (xrows-m <= INTEGER(k)[j]) ? STRING_ELT(thisfill, 0) : STRING_ELT(this, m + INTEGER(k)[j]));
+          copyMostAttrib(this, tmp);
+        }
+        break;
 
-            default :
-                error("Unsupported type '%s'", type2char(TYPEOF(this)));
-            }
-            UNPROTECT(1);
+      case VECSXP :
+        thisfill = PROTECT(coerceVector(fill, VECSXP));
+        for (j=0; j<nk; j++) {
+          tmp = allocVector(VECSXP, xrows);
+          SET_VECTOR_ELT(ans, i*nk+j, tmp);
+          for (m=0; m<xrows; m++)
+            SET_VECTOR_ELT(tmp, m, (xrows-m <= INTEGER(k)[j]) ? VECTOR_ELT(thisfill, 0) : VECTOR_ELT(this, m + INTEGER(k)[j]));
+            copyMostAttrib(this, tmp);
         }
+        break;
+
+      default :
+        error("Unsupported type '%s'", type2char(TYPEOF(this)));
+      }
+      UNPROTECT(1);
     }
-    UNPROTECT(1);
-    if (isVectorAtomic(obj) && length(ans) == 1)
-        return (VECTOR_ELT(ans, 0));
-    return(ans);
+  }
+  UNPROTECT(1);
+  if (isVectorAtomic(obj) && length(ans) == 1)
+    return (VECTOR_ELT(ans, 0));
+  return(ans);
 }
 
diff --git a/src/subset.c b/src/subset.c
index f35f9bd429..72a8b30566 100644
--- a/src/subset.c
+++ b/src/subset.c
@@ -3,126 +3,126 @@
 static SEXP subsetVectorRaw(SEXP target, SEXP source, SEXP idx, Rboolean any0orNA)
 // Only for use by subsetDT() or subsetVector() below, hence static
 {
-    if (!length(target)) return target;
+  if (!length(target)) return target;
 
-    const int max=length(source);
-    switch(TYPEOF(source)) {
-    case INTSXP : case LGLSXP :
-        if (any0orNA) {
-          // any 0 or NA *in idx*; if there's 0 or NA in the data that's just regular data to be copied
-          for (int i=0, ansi=0; i<LENGTH(idx); i++) {
-              int this = INTEGER(idx)[i];
-              if (this==0) continue;
-              INTEGER(target)[ansi++] = (this==NA_INTEGER || this>max) ? NA_INTEGER : INTEGER(source)[this-1];
-              // negatives are checked before (in check_idx()) not to have reached here
-              // NA_INTEGER == NA_LOGICAL is checked in init.c
-          }
-        } else {
-          // totally branch free to give optimizer/hardware best chance on all platforms
-          // We keep the branchless version together here inside the same switch to keep
-          // the code together by type
-          // INTEGER and LENGTH are up front to isolate in preparation to stop using USE_RINTERNALS
-          int *vd = INTEGER(source);
-          int *vi = INTEGER(idx);
-          int *p =  INTEGER(target);
-          const int upp = LENGTH(idx);
-          for (int i=0; i<upp; i++) *p++ = vd[vi[i]-1];
-        }
-        break;
-    case REALSXP :
-        if (any0orNA) {
-          // define needed vars just when we need them. To registerize and to limit scope related bugs
-          union { double d; long long ll; } naval;
-          if (INHERITS(source, char_integer64)) naval.ll = NA_INT64_LL;
-          else naval.d = NA_REAL;
-          for (int i=0, ansi=0; i<LENGTH(idx); i++) {
-              int this = INTEGER(idx)[i];
-              if (this==0) continue;
-              REAL(target)[ansi++] = (this==NA_INTEGER || this>max) ? naval.d : REAL(source)[this-1];
-          }
-        } else {
-          double *vd = REAL(source);
-          int *vi =    INTEGER(idx);
-          double *p =  REAL(target);
-          const int upp = LENGTH(idx);
-          for (int i=0; i<upp; i++) *p++ = vd[vi[i]-1];
-        }
-        break;
-    case STRSXP : {
-        #pragma omp critical
-        // write barrier is not thread safe. We can and do do non-STRSXP at the same time, though.
-        // we don't strictly need the critical since subsetDT has been written to dispatch one-thread only to
-        // do all the STRSXP columns, but keep the critical here anyway for safety. So long as it's once at high
-        // level as it is here and not deep.
-        // We could go parallel here but would need access to NODE_IS_OLDER, at least. Given gcgen, mark and named
-        // are upper bounded and max 3, REFCNT==REFCNTMAX could be checked first and then critical SET_ if not.
-        // Inside that critical just before SET_ it could check REFCNT<REFCNTMAX still held. Similarly for gcgen.
-        // TODO - discuss with Luke Tierney. Produce benchmarks on integer/double to see if it's worth making a safe
-        // API interface for package use for STRSXP.
-        {
-          if (any0orNA) {
-            for (int i=0, ansi=0; i<LENGTH(idx); i++) {
-                int this = INTEGER(idx)[i];
-                if (this==0) continue;
-                SET_STRING_ELT(target, ansi++, (this==NA_INTEGER || this>max) ? NA_STRING : STRING_ELT(source, this-1));
-            }
-          } else {
-            SEXP *vd = (SEXP *)DATAPTR(source);
-            int *vi =    INTEGER(idx);
-            const int upp = LENGTH(idx);
-            for (int i=0; i<upp; i++) SET_STRING_ELT(target, i, vd[vi[i]-1]);
-            // Aside: setkey() knows it always receives a permutation (it does a shuffle in-place) and so doesn't
-            // need to use SET_*. setkey() can do its own parallelism therefore, including STRSXP and VECSXP.
-          }
-        }}
-        break;
-    case VECSXP : {
-        #pragma omp critical
-        {
-          if (any0orNA) {
-            for (int i=0, ansi=0; i<LENGTH(idx); i++) {
-                int this = INTEGER(idx)[i];
-                if (this==0) continue;
-                SET_VECTOR_ELT(target, ansi++, (this==NA_INTEGER || this>max) ? R_NilValue : VECTOR_ELT(source, this-1));
-            }
-          } else {
-            for (int i=0; i<LENGTH(idx); i++) {
-                SET_VECTOR_ELT(target, i, VECTOR_ELT(source, INTEGER(idx)[i]-1));
-            }
-          }
-        }}
-        break;
-    case CPLXSXP :
-        if (any0orNA) {
-          for (int i=0, ansi=0; i<LENGTH(idx); i++) {
-              int this = INTEGER(idx)[i];
-              if (this==0) continue;
-              if (this==NA_INTEGER || this>max) {
-                  COMPLEX(target)[ansi].r = NA_REAL;
-                  COMPLEX(target)[ansi++].i = NA_REAL;
-              } else COMPLEX(target)[ansi++] = COMPLEX(source)[this-1];
-          }
-        } else {
-          for (int i=0; i<LENGTH(idx); i++)
-              COMPLEX(target)[i] = COMPLEX(source)[INTEGER(idx)[i]-1];
-        }
-        break;
-    case RAWSXP :
-        if (any0orNA) {
-          for (int i=0, ansi=0; i<LENGTH(idx); i++) {
-              int this = INTEGER(idx)[i];
-              if (this==0) continue;
-              RAW(target)[ansi++] = (this==NA_INTEGER || this>max) ? (Rbyte) 0 : RAW(source)[this-1];
-          }
-        } else {
-          for (int i=0; i<LENGTH(idx); i++)
-              RAW(target)[i] = RAW(source)[INTEGER(idx)[i]-1];
-        }
-        break;
-    // default :
-    // no error() needed here as caught earlier when single threaded; error() here not thread-safe.
+  const int max=length(source);
+  switch(TYPEOF(source)) {
+  case INTSXP : case LGLSXP :
+    if (any0orNA) {
+      // any 0 or NA *in idx*; if there's 0 or NA in the data that's just regular data to be copied
+      for (int i=0, ansi=0; i<LENGTH(idx); i++) {
+        int this = INTEGER(idx)[i];
+        if (this==0) continue;
+        INTEGER(target)[ansi++] = (this==NA_INTEGER || this>max) ? NA_INTEGER : INTEGER(source)[this-1];
+        // negatives are checked before (in check_idx()) not to have reached here
+        // NA_INTEGER == NA_LOGICAL is checked in init.c
+      }
+    } else {
+      // totally branch free to give optimizer/hardware best chance on all platforms
+      // We keep the branchless version together here inside the same switch to keep
+      // the code together by type
+      // INTEGER and LENGTH are up front to isolate in preparation to stop using USE_RINTERNALS
+      int *vd = INTEGER(source);
+      int *vi = INTEGER(idx);
+      int *p =  INTEGER(target);
+      const int upp = LENGTH(idx);
+      for (int i=0; i<upp; i++) *p++ = vd[vi[i]-1];
+    }
+    break;
+  case REALSXP :
+    if (any0orNA) {
+      // define needed vars just when we need them. To registerize and to limit scope related bugs
+      union { double d; long long ll; } naval;
+      if (INHERITS(source, char_integer64)) naval.ll = NA_INT64_LL;
+      else naval.d = NA_REAL;
+      for (int i=0, ansi=0; i<LENGTH(idx); i++) {
+        int this = INTEGER(idx)[i];
+        if (this==0) continue;
+        REAL(target)[ansi++] = (this==NA_INTEGER || this>max) ? naval.d : REAL(source)[this-1];
+      }
+    } else {
+      double *vd = REAL(source);
+      int *vi =    INTEGER(idx);
+      double *p =  REAL(target);
+      const int upp = LENGTH(idx);
+      for (int i=0; i<upp; i++) *p++ = vd[vi[i]-1];
+    }
+    break;
+  case STRSXP : {
+    #pragma omp critical
+    // write barrier is not thread safe. We can and do do non-STRSXP at the same time, though.
+    // we don't strictly need the critical since subsetDT has been written to dispatch one-thread only to
+    // do all the STRSXP columns, but keep the critical here anyway for safety. So long as it's once at high
+    // level as it is here and not deep.
+    // We could go parallel here but would need access to NODE_IS_OLDER, at least. Given gcgen, mark and named
+    // are upper bounded and max 3, REFCNT==REFCNTMAX could be checked first and then critical SET_ if not.
+    // Inside that critical just before SET_ it could check REFCNT<REFCNTMAX still held. Similarly for gcgen.
+    // TODO - discuss with Luke Tierney. Produce benchmarks on integer/double to see if it's worth making a safe
+    // API interface for package use for STRSXP.
+    {
+      if (any0orNA) {
+      for (int i=0, ansi=0; i<LENGTH(idx); i++) {
+        int this = INTEGER(idx)[i];
+        if (this==0) continue;
+        SET_STRING_ELT(target, ansi++, (this==NA_INTEGER || this>max) ? NA_STRING : STRING_ELT(source, this-1));
+      }
+      } else {
+      SEXP *vd = (SEXP *)DATAPTR(source);
+      int *vi =    INTEGER(idx);
+      const int upp = LENGTH(idx);
+      for (int i=0; i<upp; i++) SET_STRING_ELT(target, i, vd[vi[i]-1]);
+      // Aside: setkey() knows it always receives a permutation (it does a shuffle in-place) and so doesn't
+      // need to use SET_*. setkey() can do its own parallelism therefore, including STRSXP and VECSXP.
+      }
+    }}
+    break;
+  case VECSXP : {
+    #pragma omp critical
+    {
+      if (any0orNA) {
+      for (int i=0, ansi=0; i<LENGTH(idx); i++) {
+        int this = INTEGER(idx)[i];
+        if (this==0) continue;
+        SET_VECTOR_ELT(target, ansi++, (this==NA_INTEGER || this>max) ? R_NilValue : VECTOR_ELT(source, this-1));
+      }
+      } else {
+      for (int i=0; i<LENGTH(idx); i++) {
+        SET_VECTOR_ELT(target, i, VECTOR_ELT(source, INTEGER(idx)[i]-1));
+      }
+      }
+    }}
+    break;
+  case CPLXSXP :
+    if (any0orNA) {
+      for (int i=0, ansi=0; i<LENGTH(idx); i++) {
+        int this = INTEGER(idx)[i];
+        if (this==0) continue;
+        if (this==NA_INTEGER || this>max) {
+          COMPLEX(target)[ansi].r = NA_REAL;
+          COMPLEX(target)[ansi++].i = NA_REAL;
+        } else COMPLEX(target)[ansi++] = COMPLEX(source)[this-1];
+      }
+    } else {
+      for (int i=0; i<LENGTH(idx); i++)
+        COMPLEX(target)[i] = COMPLEX(source)[INTEGER(idx)[i]-1];
     }
-    return target;
+    break;
+  case RAWSXP :
+    if (any0orNA) {
+      for (int i=0, ansi=0; i<LENGTH(idx); i++) {
+        int this = INTEGER(idx)[i];
+        if (this==0) continue;
+        RAW(target)[ansi++] = (this==NA_INTEGER || this>max) ? (Rbyte) 0 : RAW(source)[this-1];
+      }
+    } else {
+      for (int i=0; i<LENGTH(idx); i++)
+        RAW(target)[i] = RAW(source)[INTEGER(idx)[i]-1];
+    }
+    break;
+  // default :
+  // no error() needed here as caught earlier when single threaded; error() here not thread-safe.
+  }
+  return target;
 }
 
 static void check_idx(SEXP idx, int max, /*outputs...*/int *ansLen, Rboolean *any0orNA)
@@ -134,75 +134,75 @@ static void check_idx(SEXP idx, int max, /*outputs...*/int *ansLen, Rboolean *an
 // do this once up-front and reuse the result for each column
 // single cache efficient sweep so no need to go parallel (well, very low priority to go parallel)
 {
-    if (!isInteger(idx)) error("Internal error. 'idx' is type '%s' not 'integer'", type2char(TYPEOF(idx)));
-    Rboolean anyNeg=FALSE, anyNA=FALSE;
-    int ans=0;
-    for (int i=0; i<LENGTH(idx); i++) {
-        int this = INTEGER(idx)[i];
-        ans += (this!=0);
-        anyNeg |= this<0 && this!=NA_INTEGER;
-        anyNA |= this==NA_INTEGER || this>max;
-    }
-    if (anyNeg) error("Internal error: idx contains negatives. Should have been dealt with earlier.");
-    *ansLen = ans;
-    *any0orNA = ans<LENGTH(idx) || anyNA;
+  if (!isInteger(idx)) error("Internal error. 'idx' is type '%s' not 'integer'", type2char(TYPEOF(idx)));
+  Rboolean anyNeg=FALSE, anyNA=FALSE;
+  int ans=0;
+  for (int i=0; i<LENGTH(idx); i++) {
+    int this = INTEGER(idx)[i];
+    ans += (this!=0);
+    anyNeg |= this<0 && this!=NA_INTEGER;
+    anyNA |= this==NA_INTEGER || this>max;
+  }
+  if (anyNeg) error("Internal error: idx contains negatives. Should have been dealt with earlier.");
+  *ansLen = ans;
+  *any0orNA = ans<LENGTH(idx) || anyNA;
 }
 
 // TODO - currently called from R level first. Can it be called from check_idx instead?
 SEXP convertNegativeIdx(SEXP idx, SEXP maxArg)
 {
-    // + more precise and helpful error messages telling user exactly where the problem is (saving user debugging time)
-    // + a little more efficient than negativeSubscript in src/main/subscript.c (it's private to R so we can't call it anyway)
+  // + more precise and helpful error messages telling user exactly where the problem is (saving user debugging time)
+  // + a little more efficient than negativeSubscript in src/main/subscript.c (it's private to R so we can't call it anyway)
 
-    if (!isInteger(idx)) error("Internal error. 'idx' is type '%s' not 'integer'", type2char(TYPEOF(idx)));
-    if (!isInteger(maxArg) || length(maxArg)!=1) error("Internal error. 'maxArg' is type '%s' and length %d, should be an integer singleton", type2char(TYPEOF(maxArg)), length(maxArg));
-    int max = INTEGER(maxArg)[0];
-    if (max<0) error("Internal error. max is %d, must be >= 0.", max);  // NA also an error which'll print as INT_MIN
-    int firstNegative = 0, firstPositive = 0, firstNA = 0, num0 = 0;
-    for (int i=0; i<LENGTH(idx); i++) {
-        int this = INTEGER(idx)[i];
-        if (this==NA_INTEGER) { if (firstNA==0) firstNA = i+1;  continue; }
-        if (this==0)          { num0++;  continue; }
-        if (this>0)           { if (firstPositive==0) firstPositive=i+1; continue; }
-        if (firstNegative==0) firstNegative=i+1;
-    }
-    if (firstNegative==0) return(idx);  // 0's and NA can be mixed with positives, there are no negatives present, so we're done
-    if (firstPositive) error("Item %d of i is %d and item %d is %d. Cannot mix positives and negatives.",
-                 firstNegative, INTEGER(idx)[firstNegative-1], firstPositive, INTEGER(idx)[firstPositive-1]);
-    if (firstNA)       error("Item %d of i is %d and item %d is NA. Cannot mix negatives and NA.",
-                 firstNegative, INTEGER(idx)[firstNegative-1], firstNA);
+  if (!isInteger(idx)) error("Internal error. 'idx' is type '%s' not 'integer'", type2char(TYPEOF(idx)));
+  if (!isInteger(maxArg) || length(maxArg)!=1) error("Internal error. 'maxArg' is type '%s' and length %d, should be an integer singleton", type2char(TYPEOF(maxArg)), length(maxArg));
+  int max = INTEGER(maxArg)[0];
+  if (max<0) error("Internal error. max is %d, must be >= 0.", max);  // NA also an error which'll print as INT_MIN
+  int firstNegative = 0, firstPositive = 0, firstNA = 0, num0 = 0;
+  for (int i=0; i<LENGTH(idx); i++) {
+    int this = INTEGER(idx)[i];
+    if (this==NA_INTEGER) { if (firstNA==0) firstNA = i+1;  continue; }
+    if (this==0)          { num0++;  continue; }
+    if (this>0)           { if (firstPositive==0) firstPositive=i+1; continue; }
+    if (firstNegative==0) firstNegative=i+1;
+  }
+  if (firstNegative==0) return(idx);  // 0's and NA can be mixed with positives, there are no negatives present, so we're done
+  if (firstPositive) error("Item %d of i is %d and item %d is %d. Cannot mix positives and negatives.",
+         firstNegative, INTEGER(idx)[firstNegative-1], firstPositive, INTEGER(idx)[firstPositive-1]);
+  if (firstNA)       error("Item %d of i is %d and item %d is NA. Cannot mix negatives and NA.",
+         firstNegative, INTEGER(idx)[firstNegative-1], firstNA);
 
-    // idx is all negative without any NA but perhaps 0 present (num0) ...
+  // idx is all negative without any NA but perhaps 0 present (num0) ...
 
-    char *tmp = (char *)R_alloc(max, sizeof(char));    // 4 times less memory that INTSXP in src/main/subscript.c
-    for (int i=0; i<max; i++) tmp[i] = 0;
-    // Not using Calloc as valgrind shows it leaking (I don't see why) - just changed to R_alloc to be done with it.
-    // Maybe R needs to be rebuilt with valgrind before Calloc's Free can be matched up by valgrind?
-    int firstDup = 0, numDup = 0, firstBeyond = 0, numBeyond = 0;
-    for (int i=0; i<LENGTH(idx); i++) {
-        int this = -INTEGER(idx)[i];
-        if (this==0) continue;
-        if (this>max) {
-            numBeyond++;
-            if (firstBeyond==0) firstBeyond=i+1;
-            continue;
-        }
-        if (tmp[this-1]==1) {
-            numDup++;
-            if (firstDup==0) firstDup=i+1;
-        } else tmp[this-1] = 1;
+  char *tmp = (char *)R_alloc(max, sizeof(char));    // 4 times less memory that INTSXP in src/main/subscript.c
+  for (int i=0; i<max; i++) tmp[i] = 0;
+  // Not using Calloc as valgrind shows it leaking (I don't see why) - just changed to R_alloc to be done with it.
+  // Maybe R needs to be rebuilt with valgrind before Calloc's Free can be matched up by valgrind?
+  int firstDup = 0, numDup = 0, firstBeyond = 0, numBeyond = 0;
+  for (int i=0; i<LENGTH(idx); i++) {
+    int this = -INTEGER(idx)[i];
+    if (this==0) continue;
+    if (this>max) {
+      numBeyond++;
+      if (firstBeyond==0) firstBeyond=i+1;
+      continue;
     }
-    if (numBeyond)
-        warning("Item %d of i is %d but there are only %d rows. Ignoring this and %d more like it out of %d.", firstBeyond, INTEGER(idx)[firstBeyond-1], max, numBeyond-1, LENGTH(idx));
-    if (numDup)
-        warning("Item %d of i is %d which has occurred before. Ignoring this and %d other duplicates out of %d.", firstDup, INTEGER(idx)[firstDup-1], numDup-1, LENGTH(idx));
+    if (tmp[this-1]==1) {
+      numDup++;
+      if (firstDup==0) firstDup=i+1;
+    } else tmp[this-1] = 1;
+  }
+  if (numBeyond)
+    warning("Item %d of i is %d but there are only %d rows. Ignoring this and %d more like it out of %d.", firstBeyond, INTEGER(idx)[firstBeyond-1], max, numBeyond-1, LENGTH(idx));
+  if (numDup)
+    warning("Item %d of i is %d which has occurred before. Ignoring this and %d other duplicates out of %d.", firstDup, INTEGER(idx)[firstDup-1], numDup-1, LENGTH(idx));
 
-    SEXP ans = PROTECT(allocVector(INTSXP, max-LENGTH(idx)+num0+numDup+numBeyond));
-    int ansi = 0;
-    for (int i=0; i<max; i++) if (tmp[i]==0) INTEGER(ans)[ansi++] = i+1;
-    UNPROTECT(1);
-    if (ansi != max-LENGTH(idx)+num0+numDup+numBeyond) error("Internal error: ansi[%d] != max[%d]-LENGTH(idx)[%d]+num0[%d]+numDup[%d]+numBeyond[%d] in convertNegativeIdx",ansi,max,LENGTH(idx),num0,numDup,numBeyond);
-    return(ans);
+  SEXP ans = PROTECT(allocVector(INTSXP, max-LENGTH(idx)+num0+numDup+numBeyond));
+  int ansi = 0;
+  for (int i=0; i<max; i++) if (tmp[i]==0) INTEGER(ans)[ansi++] = i+1;
+  UNPROTECT(1);
+  if (ansi != max-LENGTH(idx)+num0+numDup+numBeyond) error("Internal error: ansi[%d] != max[%d]-LENGTH(idx)[%d]+num0[%d]+numDup[%d]+numBeyond[%d] in convertNegativeIdx",ansi,max,LENGTH(idx),num0,numDup,numBeyond);
+  return(ans);
 }
 
 /*
@@ -217,100 +217,100 @@ SEXP convertNegativeIdx(SEXP idx, SEXP maxArg)
 *       do that anyway
 */
 SEXP subsetDT(SEXP x, SEXP rows, SEXP cols) {
-    if (!isNewList(x)) error("Internal error. Argument 'x' to CsubsetDT is type '%s' not 'list'", type2char(TYPEOF(rows)));
-    if (!length(x)) return(x);  // return empty list
+  if (!isNewList(x)) error("Internal error. Argument 'x' to CsubsetDT is type '%s' not 'list'", type2char(TYPEOF(rows)));
+  if (!length(x)) return(x);  // return empty list
 
-    // check index once up front for 0 or NA, for branchless subsetVectorRaw
-    R_len_t ansn=0;
-    Rboolean any0orNA=FALSE;
-    check_idx(rows, length(VECTOR_ELT(x,0)), &ansn, &any0orNA);
+  // check index once up front for 0 or NA, for branchless subsetVectorRaw
+  R_len_t ansn=0;
+  Rboolean any0orNA=FALSE;
+  check_idx(rows, length(VECTOR_ELT(x,0)), &ansn, &any0orNA);
 
-    if (!isInteger(cols)) error("Internal error. Argument 'cols' to Csubset is type '%s' not 'integer'", type2char(TYPEOF(cols)));
+  if (!isInteger(cols)) error("Internal error. Argument 'cols' to Csubset is type '%s' not 'integer'", type2char(TYPEOF(cols)));
+  for (int i=0; i<LENGTH(cols); i++) {
+    int this = INTEGER(cols)[i];
+    if (this<1 || this>LENGTH(x)) error("Item %d of 'cols' is %d which is outside 1-based range [1,ncol(x)=%d]", i+1, this, LENGTH(x));
+  }
+  SEXP ans = PROTECT(allocVector(VECSXP, LENGTH(cols)+64));  // just do alloc.col directly, eventually alloc.col can be deprecated.
+  copyMostAttrib(x, ans);  // other than R_NamesSymbol, R_DimSymbol and R_DimNamesSymbol
+               // so includes row.names (oddly, given other dims aren't) and "sorted", dealt with below
+  SET_TRUELENGTH(ans, LENGTH(ans));
+  SETLENGTH(ans, LENGTH(cols));
+  for (int i=0; i<LENGTH(cols); i++) {
+    SEXP source, target;
+    target = PROTECT(allocVector(TYPEOF(source=VECTOR_ELT(x, INTEGER(cols)[i]-1)), ansn));
+    SETLENGTH(target, ansn);
+    SET_TRUELENGTH(target, ansn);
+    copyMostAttrib(source, target);
+    SET_VECTOR_ELT(ans, i, target);
+    UNPROTECT(1);
+  }
+  #pragma omp parallel num_threads(MIN(getDTthreads(),LENGTH(cols)))
+  {
+    #pragma omp master
+    // this thread and this thread only handles all the STRSXP and VECSXP columns, one by one
+    // it doesn't have to be master; the directive is just convenient.
     for (int i=0; i<LENGTH(cols); i++) {
-        int this = INTEGER(cols)[i];
-        if (this<1 || this>LENGTH(x)) error("Item %d of 'cols' is %d which is outside 1-based range [1,ncol(x)=%d]", i+1, this, LENGTH(x));
+    SEXP target = VECTOR_ELT(ans, i);
+    if (isString(target) || isNewList(target))
+      subsetVectorRaw(target, VECTOR_ELT(x, INTEGER(cols)[i]-1), rows, any0orNA);
     }
-    SEXP ans = PROTECT(allocVector(VECSXP, LENGTH(cols)+64));  // just do alloc.col directly, eventually alloc.col can be deprecated.
-    copyMostAttrib(x, ans);  // other than R_NamesSymbol, R_DimSymbol and R_DimNamesSymbol
-                             // so includes row.names (oddly, given other dims aren't) and "sorted", dealt with below
-    SET_TRUELENGTH(ans, LENGTH(ans));
-    SETLENGTH(ans, LENGTH(cols));
+    #pragma omp for schedule(dynamic)
+    // slaves get on with the other non-STRSXP non-VECSXP columns at the same time.
+    // master may join in when it's finished, straight away if there are no STRSXP or VECSXP columns
     for (int i=0; i<LENGTH(cols); i++) {
-        SEXP source, target;
-        target = PROTECT(allocVector(TYPEOF(source=VECTOR_ELT(x, INTEGER(cols)[i]-1)), ansn));
-        SETLENGTH(target, ansn);
-        SET_TRUELENGTH(target, ansn);
-        copyMostAttrib(source, target);
-        SET_VECTOR_ELT(ans, i, target);
-        UNPROTECT(1);
+      SEXP target = VECTOR_ELT(ans, i);
+      if (!isString(target) && !isNewList(target))
+        subsetVectorRaw(target, VECTOR_ELT(x, INTEGER(cols)[i]-1), rows, any0orNA);
     }
-    #pragma omp parallel num_threads(MIN(getDTthreads(),LENGTH(cols)))
-    {
-      #pragma omp master
-      // this thread and this thread only handles all the STRSXP and VECSXP columns, one by one
-      // it doesn't have to be master; the directive is just convenient.
-      for (int i=0; i<LENGTH(cols); i++) {
-        SEXP target = VECTOR_ELT(ans, i);
-        if (isString(target) || isNewList(target))
-          subsetVectorRaw(target, VECTOR_ELT(x, INTEGER(cols)[i]-1), rows, any0orNA);
-      }
-      #pragma omp for schedule(dynamic)
-      // slaves get on with the other non-STRSXP non-VECSXP columns at the same time.
-      // master may join in when it's finished, straight away if there are no STRSXP or VECSXP columns
-      for (int i=0; i<LENGTH(cols); i++) {
-        SEXP target = VECTOR_ELT(ans, i);
-        if (!isString(target) && !isNewList(target))
-          subsetVectorRaw(target, VECTOR_ELT(x, INTEGER(cols)[i]-1), rows, any0orNA);
-      }
-    }
-    SEXP tmp = PROTECT(allocVector(STRSXP, LENGTH(cols)+64));
-    SET_TRUELENGTH(tmp, LENGTH(tmp));
-    SETLENGTH(tmp, LENGTH(cols));
-    setAttrib(ans, R_NamesSymbol, tmp);
-    subsetVectorRaw(tmp, getAttrib(x, R_NamesSymbol), cols, /*any0orNA=*/FALSE);
-    UNPROTECT(1);
+  }
+  SEXP tmp = PROTECT(allocVector(STRSXP, LENGTH(cols)+64));
+  SET_TRUELENGTH(tmp, LENGTH(tmp));
+  SETLENGTH(tmp, LENGTH(cols));
+  setAttrib(ans, R_NamesSymbol, tmp);
+  subsetVectorRaw(tmp, getAttrib(x, R_NamesSymbol), cols, /*any0orNA=*/FALSE);
+  UNPROTECT(1);
 
-    tmp = PROTECT(allocVector(INTSXP, 2));
-    INTEGER(tmp)[0] = NA_INTEGER;
-    INTEGER(tmp)[1] = -ansn;
-    setAttrib(ans, R_RowNamesSymbol, tmp);  // The contents of tmp must be set before being passed to setAttrib(). setAttrib looks at tmp value and copies it in the case of R_RowNamesSymbol. Caused hard to track bug around 28 Sep 2014.
-    UNPROTECT(1);
+  tmp = PROTECT(allocVector(INTSXP, 2));
+  INTEGER(tmp)[0] = NA_INTEGER;
+  INTEGER(tmp)[1] = -ansn;
+  setAttrib(ans, R_RowNamesSymbol, tmp);  // The contents of tmp must be set before being passed to setAttrib(). setAttrib looks at tmp value and copies it in the case of R_RowNamesSymbol. Caused hard to track bug around 28 Sep 2014.
+  UNPROTECT(1);
 
-    // maintain key if ordered subset ...
-    SEXP key = getAttrib(x, sym_sorted);
-    if (length(key)) {
-        SEXP in = PROTECT(chmatch(key,getAttrib(ans,R_NamesSymbol), 0, TRUE)); // (nomatch ignored when in=TRUE)
-        int i = 0;  while(i<LENGTH(key) && LOGICAL(in)[i]) i++;
-        UNPROTECT(1);
-        // i is now the keylen that can be kept. 2 lines above much easier in C than R
-        if (i==0) {
-            setAttrib(ans, sym_sorted, R_NilValue);
-            // clear key that was copied over by copyMostAttrib() above
-        } else {
-            if (isOrderedSubset(rows, PROTECT(ScalarInteger(length(VECTOR_ELT(x,0)))))) {
-              setAttrib(ans, sym_sorted, tmp=allocVector(STRSXP, i));
-              for (int j=0; j<i; j++) SET_STRING_ELT(tmp, j, STRING_ELT(key, j));
-            }
-            UNPROTECT(1);  // the ScalarInteger above. isOrderedSubset() is exposed at R level hence needs SEXP
-        }
-    }
-    setAttrib(ans, install(".data.table.locked"), R_NilValue);
-    setselfref(ans);
+  // maintain key if ordered subset ...
+  SEXP key = getAttrib(x, sym_sorted);
+  if (length(key)) {
+    SEXP in = PROTECT(chmatch(key,getAttrib(ans,R_NamesSymbol), 0, TRUE)); // (nomatch ignored when in=TRUE)
+    int i = 0;  while(i<LENGTH(key) && LOGICAL(in)[i]) i++;
     UNPROTECT(1);
-    return ans;
+    // i is now the keylen that can be kept. 2 lines above much easier in C than R
+    if (i==0) {
+      setAttrib(ans, sym_sorted, R_NilValue);
+      // clear key that was copied over by copyMostAttrib() above
+    } else {
+      if (isOrderedSubset(rows, PROTECT(ScalarInteger(length(VECTOR_ELT(x,0)))))) {
+        setAttrib(ans, sym_sorted, tmp=allocVector(STRSXP, i));
+        for (int j=0; j<i; j++) SET_STRING_ELT(tmp, j, STRING_ELT(key, j));
+      }
+      UNPROTECT(1);  // the ScalarInteger above. isOrderedSubset() is exposed at R level hence needs SEXP
+    }
+  }
+  setAttrib(ans, install(".data.table.locked"), R_NilValue);
+  setselfref(ans);
+  UNPROTECT(1);
+  return ans;
 }
 
 SEXP subsetVector(SEXP x, SEXP idx) { // idx is 1-based passed from R level
-    int ansn;
-    Rboolean any0orNA;
-    check_idx(idx, length(x), &ansn, &any0orNA);
-    SEXP ans = PROTECT(allocVector(TYPEOF(x), ansn));
-    SETLENGTH(ans, ansn);
-    SET_TRUELENGTH(ans, ansn);
-    copyMostAttrib(x, ans);
-    subsetVectorRaw(ans, x, idx, any0orNA);
-    UNPROTECT(1);
-    return ans;
+  int ansn;
+  Rboolean any0orNA;
+  check_idx(idx, length(x), &ansn, &any0orNA);
+  SEXP ans = PROTECT(allocVector(TYPEOF(x), ansn));
+  SETLENGTH(ans, ansn);
+  SET_TRUELENGTH(ans, ansn);
+  copyMostAttrib(x, ans);
+  subsetVectorRaw(ans, x, idx, any0orNA);
+  UNPROTECT(1);
+  return ans;
 }
 
 
diff --git a/src/transpose.c b/src/transpose.c
index 34050bcc4c..7a195252fa 100644
--- a/src/transpose.c
+++ b/src/transpose.c
@@ -4,94 +4,94 @@
 
 SEXP transpose(SEXP l, SEXP fill, SEXP ignoreArg) {
 
-    R_len_t i, j, k=0, maxlen=0, zerolen=0, anslen;
-    SEXP li, thisi, ans;
-    SEXPTYPE type, maxtype=0;
-    Rboolean coerce = FALSE;
+  R_len_t i, j, k=0, maxlen=0, zerolen=0, anslen;
+  SEXP li, thisi, ans;
+  SEXPTYPE type, maxtype=0;
+  Rboolean coerce = FALSE;
 
-    if (!isNewList(l))
-        error("l must be a list.");
-    if (!length(l))
-        return(duplicate(l));
-    if (!isLogical(ignoreArg) || LOGICAL(ignoreArg)[0] == NA_LOGICAL)
-        error("ignore.empty should be logical TRUE/FALSE.");
-    if (length(fill) != 1)
-        error("fill must be NULL or length=1 vector.");
-    R_len_t ln = LENGTH(l);
-    Rboolean ignore = LOGICAL(ignoreArg)[0];
+  if (!isNewList(l))
+    error("l must be a list.");
+  if (!length(l))
+    return(duplicate(l));
+  if (!isLogical(ignoreArg) || LOGICAL(ignoreArg)[0] == NA_LOGICAL)
+    error("ignore.empty should be logical TRUE/FALSE.");
+  if (length(fill) != 1)
+    error("fill must be NULL or length=1 vector.");
+  R_len_t ln = LENGTH(l);
+  Rboolean ignore = LOGICAL(ignoreArg)[0];
 
-    // preprocessing
-    R_len_t *len  = (R_len_t *)R_alloc(ln, sizeof(R_len_t));
-    for (i=0; i<ln; i++) {
-        li = VECTOR_ELT(l, i);
-        if (!isVectorAtomic(li) && !isNull(li))
-            error("Item %d of list input is not an atomic vector", i+1);
-        len[i] = length(li);
-        if (len[i] > maxlen)
-            maxlen = len[i];
-        zerolen += (len[i] == 0);
-        if (isFactor(li)) {
-            maxtype = STRSXP;
-        } else {
-            type = TYPEOF(li);
-            if (type > maxtype)
-                maxtype = type;
-        }
+  // preprocessing
+  R_len_t *len  = (R_len_t *)R_alloc(ln, sizeof(R_len_t));
+  for (i=0; i<ln; i++) {
+    li = VECTOR_ELT(l, i);
+    if (!isVectorAtomic(li) && !isNull(li))
+      error("Item %d of list input is not an atomic vector", i+1);
+    len[i] = length(li);
+    if (len[i] > maxlen)
+      maxlen = len[i];
+    zerolen += (len[i] == 0);
+    if (isFactor(li)) {
+      maxtype = STRSXP;
+    } else {
+      type = TYPEOF(li);
+      if (type > maxtype)
+        maxtype = type;
     }
-    // coerce fill to maxtype
-    fill = PROTECT(coerceVector(fill, maxtype));
+  }
+  // coerce fill to maxtype
+  fill = PROTECT(coerceVector(fill, maxtype));
 
-    // allocate 'ans'
-    ans = PROTECT(allocVector(VECSXP, maxlen));
-    anslen = (!ignore) ? ln : (ln - zerolen);
-    for (i=0; i<maxlen; i++) {
-        thisi = allocVector(maxtype, anslen);
-        SET_VECTOR_ELT(ans, i, thisi);
-    }
+  // allocate 'ans'
+  ans = PROTECT(allocVector(VECSXP, maxlen));
+  anslen = (!ignore) ? ln : (ln - zerolen);
+  for (i=0; i<maxlen; i++) {
+    thisi = allocVector(maxtype, anslen);
+    SET_VECTOR_ELT(ans, i, thisi);
+  }
 
-    // transpose
-    for (i=0; i<ln; i++) {
-        if (ignore && !len[i]) continue;
-        li = VECTOR_ELT(l, i);
-        if (TYPEOF(li) != maxtype) {
-            coerce = TRUE;
-            if (!isFactor(li)) li = PROTECT(coerceVector(li, maxtype));
-            else li = PROTECT(asCharacterFactor(li));
-        }
-        switch (maxtype) {
-            case INTSXP :
-                for (j=0; j<maxlen; j++) {
-                    thisi = VECTOR_ELT(ans, j);
-                    INTEGER(thisi)[k] = (j < len[i]) ? INTEGER(li)[j] : INTEGER(fill)[0];
-                }
-            break;
-            case LGLSXP :
-                for (j=0; j<maxlen; j++) {
-                    thisi = VECTOR_ELT(ans, j);
-                    LOGICAL(thisi)[k] = (j < len[i]) ? LOGICAL(li)[j] : LOGICAL(fill)[0];
-                }
-            break;
-            case REALSXP :
-                for (j=0; j<maxlen; j++) {
-                    thisi = VECTOR_ELT(ans, j);
-                    REAL(thisi)[k] = (j < len[i]) ? REAL(li)[j] : REAL(fill)[0];
-                }
-            break;
-            case STRSXP :
-                for (j=0; j<maxlen; j++) {
-                    thisi = VECTOR_ELT(ans, j);
-                    SET_STRING_ELT(thisi, k, (j < len[i]) ? STRING_ELT(li, j) : STRING_ELT(fill, 0));
-                }
-            break;
-            default :
-                error("Unsupported column type '%s'", type2char(maxtype));
-        }
-        if (coerce) {
-            coerce = FALSE;
-            UNPROTECT(1);
-        }
-        k++;
+  // transpose
+  for (i=0; i<ln; i++) {
+    if (ignore && !len[i]) continue;
+    li = VECTOR_ELT(l, i);
+    if (TYPEOF(li) != maxtype) {
+      coerce = TRUE;
+      if (!isFactor(li)) li = PROTECT(coerceVector(li, maxtype));
+      else li = PROTECT(asCharacterFactor(li));
+    }
+    switch (maxtype) {
+    case INTSXP :
+      for (j=0; j<maxlen; j++) {
+        thisi = VECTOR_ELT(ans, j);
+        INTEGER(thisi)[k] = (j < len[i]) ? INTEGER(li)[j] : INTEGER(fill)[0];
+      }
+      break;
+    case LGLSXP :
+      for (j=0; j<maxlen; j++) {
+        thisi = VECTOR_ELT(ans, j);
+        LOGICAL(thisi)[k] = (j < len[i]) ? LOGICAL(li)[j] : LOGICAL(fill)[0];
+      }
+      break;
+    case REALSXP :
+      for (j=0; j<maxlen; j++) {
+        thisi = VECTOR_ELT(ans, j);
+        REAL(thisi)[k] = (j < len[i]) ? REAL(li)[j] : REAL(fill)[0];
+      }
+      break;
+    case STRSXP :
+      for (j=0; j<maxlen; j++) {
+        thisi = VECTOR_ELT(ans, j);
+        SET_STRING_ELT(thisi, k, (j < len[i]) ? STRING_ELT(li, j) : STRING_ELT(fill, 0));
+      }
+      break;
+    default :
+        error("Unsupported column type '%s'", type2char(maxtype));
+    }
+    if (coerce) {
+      coerce = FALSE;
+      UNPROTECT(1);
     }
-    UNPROTECT(2);
-    return(ans);
+    k++;
+  }
+  UNPROTECT(2);
+  return(ans);
 }
diff --git a/src/uniqlist.c b/src/uniqlist.c
index e2bf80fba0..ecb75b0e39 100644
--- a/src/uniqlist.c
+++ b/src/uniqlist.c
@@ -4,81 +4,81 @@
 // Also improvements for numeric type with a hack of checking unsigned int (to overcome NA/NaN/Inf/-Inf comparisons) (> 2x speed-up)
 SEXP uniqlist(SEXP l, SEXP order)
 {
-    // This works like UNIX uniq as referred to by ?base::unique; i.e., it
-    // drops immediately repeated rows but doesn't drop duplicates of any
-    // previous row. Unless, order is provided, then it also drops any previous
-    // row. l must be a list of same length vectors ans is allocated first
-    // (maximum length the number of rows) and the length returned in anslen.
-    // DONE: ans is now grown
-    Rboolean b, byorder;
-    unsigned long long *ulv; // for numeric check speed-up
-    SEXP v, ans, class;
-    R_len_t i, j, nrow, ncol, len, thisi, previ, isize=1000;
+  // This works like UNIX uniq as referred to by ?base::unique; i.e., it
+  // drops immediately repeated rows but doesn't drop duplicates of any
+  // previous row. Unless, order is provided, then it also drops any previous
+  // row. l must be a list of same length vectors ans is allocated first
+  // (maximum length the number of rows) and the length returned in anslen.
+  // DONE: ans is now grown
+  Rboolean b, byorder;
+  unsigned long long *ulv; // for numeric check speed-up
+  SEXP v, ans, class;
+  R_len_t i, j, nrow, ncol, len, thisi, previ, isize=1000;
 
-    int *iidx = Calloc(isize, int); // for 'idx'
-    int *n_iidx; // to catch allocation errors using Realloc!
-    ncol = length(l);
-    nrow = length(VECTOR_ELT(l,0));
-    len = 1;
-    iidx[0] = 1; // first row is always the first of the first group
-    byorder = INTEGER(order)[0] != -1;
-    // Using MISSING() does not seem stable under windows. Always having arguments passed in seems a good idea anyway.
-    thisi = byorder ? INTEGER(order)[0]-1 : 0;
-    for (i=1; i<nrow; i++) {
-        previ = thisi;
-        thisi = byorder ? INTEGER(order)[i]-1 : i;
-        j = ncol;  // the last column varies the most frequently so check that first and work backwards
-        b = TRUE;
-        while (--j>=0 && b) {
-            v=VECTOR_ELT(l,j);
-            switch (TYPEOF(v)) {
-            case INTSXP : case LGLSXP :  // NA_INTEGER==NA_LOGICAL checked in init.c
-                b=INTEGER(v)[thisi]==INTEGER(v)[previ]; break;
-            case STRSXP :
-                // fix for #469, when key is set, duplicated calls uniqlist, where encoding
-                // needs to be taken care of.
-                b=ENC2UTF8(STRING_ELT(v,thisi))==ENC2UTF8(STRING_ELT(v,previ)); break;  // marked non-utf8 encodings are converted to utf8 so as to match properly when inputs are of different encodings.
-            case REALSXP :
-                ulv = (unsigned long long *)REAL(v);
-                b = ulv[thisi] == ulv[previ]; // (gives >=2x speedup)
-                if (!b) {
-                    class = getAttrib(v, R_ClassSymbol);
-                    twiddle = (isString(class) && STRING_ELT(class, 0)==char_integer64) ? &i64twiddle : &dtwiddle;
-                    b = twiddle(ulv, thisi, 1) == twiddle(ulv, previ, 1);
-                }
-                break;
-                // TO DO: store previ twiddle call, but it'll need to be vector since this is in a loop through columns. Hopefully the first == will short circuit most often
-            default :
-                error("Type '%s' not supported", type2char(TYPEOF(v)));
-            }
-        }
-        if (!b) iidx[len++] = i+1;
-        if (len >= isize) {
-            isize = 1.1*isize*nrow/i;
-            n_iidx = Realloc(iidx, isize, int);
-            if (n_iidx != NULL) iidx = n_iidx; else error("Error in reallocating memory in 'uniqlist'\n");
+  int *iidx = Calloc(isize, int); // for 'idx'
+  int *n_iidx; // to catch allocation errors using Realloc!
+  ncol = length(l);
+  nrow = length(VECTOR_ELT(l,0));
+  len = 1;
+  iidx[0] = 1; // first row is always the first of the first group
+  byorder = INTEGER(order)[0] != -1;
+  // Using MISSING() does not seem stable under windows. Always having arguments passed in seems a good idea anyway.
+  thisi = byorder ? INTEGER(order)[0]-1 : 0;
+  for (i=1; i<nrow; i++) {
+    previ = thisi;
+    thisi = byorder ? INTEGER(order)[i]-1 : i;
+    j = ncol;  // the last column varies the most frequently so check that first and work backwards
+    b = TRUE;
+    while (--j>=0 && b) {
+      v=VECTOR_ELT(l,j);
+      switch (TYPEOF(v)) {
+      case INTSXP : case LGLSXP :  // NA_INTEGER==NA_LOGICAL checked in init.c
+        b=INTEGER(v)[thisi]==INTEGER(v)[previ]; break;
+      case STRSXP :
+        // fix for #469, when key is set, duplicated calls uniqlist, where encoding
+        // needs to be taken care of.
+        b=ENC2UTF8(STRING_ELT(v,thisi))==ENC2UTF8(STRING_ELT(v,previ)); break;  // marked non-utf8 encodings are converted to utf8 so as to match properly when inputs are of different encodings.
+      case REALSXP :
+        ulv = (unsigned long long *)REAL(v);
+        b = ulv[thisi] == ulv[previ]; // (gives >=2x speedup)
+        if (!b) {
+          class = getAttrib(v, R_ClassSymbol);
+          twiddle = (isString(class) && STRING_ELT(class, 0)==char_integer64) ? &i64twiddle : &dtwiddle;
+          b = twiddle(ulv, thisi, 1) == twiddle(ulv, previ, 1);
         }
+        break;
+        // TO DO: store previ twiddle call, but it'll need to be vector since this is in a loop through columns. Hopefully the first == will short circuit most often
+      default :
+        error("Type '%s' not supported", type2char(TYPEOF(v)));
+      }
+    }
+    if (!b) iidx[len++] = i+1;
+    if (len >= isize) {
+      isize = 1.1*isize*nrow/i;
+      n_iidx = Realloc(iidx, isize, int);
+      if (n_iidx != NULL) iidx = n_iidx; else error("Error in reallocating memory in 'uniqlist'\n");
     }
-    PROTECT(ans = allocVector(INTSXP, len));
-    memcpy(INTEGER(ans), iidx, sizeof(int)*len); // sizeof is of type size_t - no integer overflow issues
-    Free(iidx);
-    UNPROTECT(1);
-    return(ans);
+  }
+  PROTECT(ans = allocVector(INTSXP, len));
+  memcpy(INTEGER(ans), iidx, sizeof(int)*len); // sizeof is of type size_t - no integer overflow issues
+  Free(iidx);
+  UNPROTECT(1);
+  return(ans);
 }
 
 SEXP uniqlengths(SEXP x, SEXP n) {
-    SEXP ans;
-    R_len_t i, len;
-    if (TYPEOF(x) != INTSXP || length(x) < 0) error("Input argument 'x' to 'uniqlengths' must be an integer vector of length >= 0");
-    if (TYPEOF(n) != INTSXP || length(n) != 1) error("Input argument 'n' to 'uniqlengths' must be an integer vector of length 1");
-    PROTECT(ans = allocVector(INTSXP, length(x)));
-    len = length(x);
-    for (i=1; i<len; i++) {
-        INTEGER(ans)[i-1] = INTEGER(x)[i] - INTEGER(x)[i-1];
-    }
-    INTEGER(ans)[len-1] = INTEGER(n)[0] - INTEGER(x)[len-1] + 1;
-    UNPROTECT(1);
-    return(ans);
+  SEXP ans;
+  R_len_t i, len;
+  if (TYPEOF(x) != INTSXP || length(x) < 0) error("Input argument 'x' to 'uniqlengths' must be an integer vector of length >= 0");
+  if (TYPEOF(n) != INTSXP || length(n) != 1) error("Input argument 'n' to 'uniqlengths' must be an integer vector of length 1");
+  PROTECT(ans = allocVector(INTSXP, length(x)));
+  len = length(x);
+  for (i=1; i<len; i++) {
+    INTEGER(ans)[i-1] = INTEGER(x)[i] - INTEGER(x)[i-1];
+  }
+  INTEGER(ans)[len-1] = INTEGER(n)[0] - INTEGER(x)[len-1] + 1;
+  UNPROTECT(1);
+  return(ans);
 }
 
 // we could compute `uniqlist` and `uniqlengths` and then construct the result
@@ -132,119 +132,119 @@ SEXP rleid(SEXP l, SEXP cols)
 }
 
 SEXP nestedid(SEXP l, SEXP cols, SEXP order, SEXP grps, SEXP resetvals, SEXP multArg) {
-    Rboolean b, byorder = length(order);
-    SEXP v, ans, class;
-    R_len_t nrows = length(VECTOR_ELT(l,0)), ncols = length(cols);
-    R_len_t i, j, k, thisi, previ, ansgrpsize=1000, nansgrp=0;
-    R_len_t *ptr, *ansgrp = Calloc(ansgrpsize, R_len_t), tmp, starts, grplen;
-    R_len_t ngrps = length(grps), *i64 = Calloc(ncols, R_len_t);
-    R_len_t resetctr=0, rlen = length(resetvals) ? INTEGER(resetvals)[0] : 0;
-    if (!isInteger(cols) || ncols == 0)
-        error("cols must be an integer vector of positive length");
-    // mult arg
-    enum {ALL, FIRST, LAST} mult = ALL;
-    if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "all")) mult = ALL;
-    else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "first")) mult = FIRST;
-    else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "last")) mult = LAST;
-    else error("Internal error: invalid value for 'mult'. Please report to datatable-help");
-    // integer64
-    for (j=0; j<ncols; j++) {
-        class = getAttrib(VECTOR_ELT(l, INTEGER(cols)[j]-1), R_ClassSymbol);
-        i64[j] = isString(class) && STRING_ELT(class, 0) == char_integer64;
+  Rboolean b, byorder = length(order);
+  SEXP v, ans, class;
+  R_len_t nrows = length(VECTOR_ELT(l,0)), ncols = length(cols);
+  R_len_t i, j, k, thisi, previ, ansgrpsize=1000, nansgrp=0;
+  R_len_t *ptr, *ansgrp = Calloc(ansgrpsize, R_len_t), tmp, starts, grplen;
+  R_len_t ngrps = length(grps), *i64 = Calloc(ncols, R_len_t);
+  R_len_t resetctr=0, rlen = length(resetvals) ? INTEGER(resetvals)[0] : 0;
+  if (!isInteger(cols) || ncols == 0)
+    error("cols must be an integer vector of positive length");
+  // mult arg
+  enum {ALL, FIRST, LAST} mult = ALL;
+  if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "all")) mult = ALL;
+  else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "first")) mult = FIRST;
+  else if (!strcmp(CHAR(STRING_ELT(multArg, 0)), "last")) mult = LAST;
+  else error("Internal error: invalid value for 'mult'. Please report to datatable-help");
+  // integer64
+  for (j=0; j<ncols; j++) {
+    class = getAttrib(VECTOR_ELT(l, INTEGER(cols)[j]-1), R_ClassSymbol);
+    i64[j] = isString(class) && STRING_ELT(class, 0) == char_integer64;
+  }
+  ans  = PROTECT(allocVector(INTSXP, nrows));
+  int *ians = INTEGER(ans), *igrps = INTEGER(grps);
+  grplen = (ngrps == 1) ? nrows : igrps[1]-igrps[0];
+  starts = igrps[0]-1 + (mult != LAST ? 0 : grplen-1);
+  ansgrp[0] = byorder ? INTEGER(order)[starts]-1 : starts;
+  for (j=0; j<grplen; j++) {
+    ians[byorder ? INTEGER(order)[igrps[0]-1+j]-1 : igrps[0]-1+j] = 1;
+  }
+  nansgrp = 1;
+  for (i=1; i<ngrps; i++) {
+    // "first"=add next grp to current grp iff min(next) >= min(current)
+    // "last"=add next grp to current grp iff max(next) >= max(current)
+    // in addition to this thisi >= previ should be satisfied
+    // could result in more groups.. so done only for first/last cases
+    // as it allows to extract indices directly in bmerge.
+    grplen = (i+1 < ngrps) ? igrps[i+1]-igrps[i] : nrows-igrps[i]+1;
+    starts = igrps[i]-1 + (mult != LAST ? 0 : grplen-1);
+    thisi = byorder ? INTEGER(order)[starts]-1 : starts;
+    for (k=0; k<nansgrp; k++) {
+      j = ncols;
+      previ = ansgrp[k];
+      // b=TRUE is ideal for mult=ALL, results in lesser groups
+      b = mult == ALL || (thisi >= previ);
+      // >= 0 is not necessary as first col will always be in
+      // increasing order. NOTE: all "==" cols are already skipped for
+      // computing nestedid during R-side call, for efficiency.
+      while(b && --j>0) {
+        v = VECTOR_ELT(l,INTEGER(cols)[j]-1);
+        switch(TYPEOF(v)) {
+        case INTSXP: case LGLSXP:
+          b = INTEGER(v)[thisi] >= INTEGER(v)[previ];
+          break;
+        case STRSXP :
+          b = ENC2UTF8(STRING_ELT(v,thisi)) == ENC2UTF8(STRING_ELT(v,previ));
+          break;
+        case REALSXP:
+          twiddle = i64[j] ? &i64twiddle : &dtwiddle;
+          b = twiddle(DATAPTR(v), thisi, 1) >= twiddle(DATAPTR(v), previ, 1);
+          break;
+        default:
+          error("Type '%s' not supported", type2char(TYPEOF(v)));
+        }
+      }
+      if (b) break;
     }
-    ans  = PROTECT(allocVector(INTSXP, nrows));
-    int *ians = INTEGER(ans), *igrps = INTEGER(grps);
-    grplen = (ngrps == 1) ? nrows : igrps[1]-igrps[0];
-    starts = igrps[0]-1 + (mult != LAST ? 0 : grplen-1);
-    ansgrp[0] = byorder ? INTEGER(order)[starts]-1 : starts;
-    for (j=0; j<grplen; j++) {
-        ians[byorder ? INTEGER(order)[igrps[0]-1+j]-1 : igrps[0]-1+j] = 1;
+    // TODO: move this as the outer for-loop and parallelise..
+    // but preferably wait to see if problems with that big non-equi
+    // group sizes do occur that commonly before to invest time here.
+    if (rlen != starts) {
+      tmp = b ? k : nansgrp++;
+    } else { // we're wrapping up this group, reset nansgrp
+      tmp = 0; nansgrp = 1;
+      rlen += INTEGER(resetvals)[++resetctr];
     }
-    nansgrp = 1;
-    for (i=1; i<ngrps; i++) {
-        // "first"=add next grp to current grp iff min(next) >= min(current)
-        // "last"=add next grp to current grp iff max(next) >= max(current)
-        // in addition to this thisi >= previ should be satisfied
-        // could result in more groups.. so done only for first/last cases
-        // as it allows to extract indices directly in bmerge.
-        grplen = (i+1 < ngrps) ? igrps[i+1]-igrps[i] : nrows-igrps[i]+1;
-        starts = igrps[i]-1 + (mult != LAST ? 0 : grplen-1);
-        thisi = byorder ? INTEGER(order)[starts]-1 : starts;
-        for (k=0; k<nansgrp; k++) {
-            j = ncols;
-            previ = ansgrp[k];
-            // b=TRUE is ideal for mult=ALL, results in lesser groups
-            b = mult == ALL || (thisi >= previ);
-            // >= 0 is not necessary as first col will always be in
-            // increasing order. NOTE: all "==" cols are already skipped for
-            // computing nestedid during R-side call, for efficiency.
-            while(b && --j>0) {
-                v = VECTOR_ELT(l,INTEGER(cols)[j]-1);
-                switch(TYPEOF(v)) {
-                    case INTSXP: case LGLSXP:
-                    b = INTEGER(v)[thisi] >= INTEGER(v)[previ];
-                    break;
-                    case STRSXP :
-                    b = ENC2UTF8(STRING_ELT(v,thisi)) == ENC2UTF8(STRING_ELT(v,previ));
-                    break;
-                    case REALSXP:
-                    twiddle = i64[j] ? &i64twiddle : &dtwiddle;
-                    b = twiddle(DATAPTR(v), thisi, 1) >= twiddle(DATAPTR(v), previ, 1);
-                    break;
-                    default:
-                    error("Type '%s' not supported", type2char(TYPEOF(v)));
-                }
-            }
-            if (b) break;
-        }
-        // TODO: move this as the outer for-loop and parallelise..
-        // but preferably wait to see if problems with that big non-equi
-        // group sizes do occur that commonly before to invest time here.
-        if (rlen != starts) {
-            tmp = b ? k : nansgrp++;
-        } else { // we're wrapping up this group, reset nansgrp
-            tmp = 0; nansgrp = 1;
-            rlen += INTEGER(resetvals)[++resetctr];
-        }
-        if (nansgrp >= ansgrpsize) {
-            ansgrpsize = 1.1*ansgrpsize*nrows/i;
-            ptr = Realloc(ansgrp, ansgrpsize, int);
-            if (ptr != NULL) ansgrp = ptr;
-            else error("Error in reallocating memory in 'nestedid'\n");
-        }
-        for (j=0; j<grplen; j++) {
-            ians[byorder ? INTEGER(order)[igrps[i]-1+j]-1 : igrps[i]-1+j] = tmp+1;
-        }
-        ansgrp[tmp] = thisi;
+    if (nansgrp >= ansgrpsize) {
+      ansgrpsize = 1.1*ansgrpsize*nrows/i;
+      ptr = Realloc(ansgrp, ansgrpsize, int);
+      if (ptr != NULL) ansgrp = ptr;
+      else error("Error in reallocating memory in 'nestedid'\n");
+    }
+    for (j=0; j<grplen; j++) {
+      ians[byorder ? INTEGER(order)[igrps[i]-1+j]-1 : igrps[i]-1+j] = tmp+1;
     }
-    Free(ansgrp);
-    Free(i64);
-    UNPROTECT(1);
-    return(ans);
+    ansgrp[tmp] = thisi;
+  }
+  Free(ansgrp);
+  Free(i64);
+  UNPROTECT(1);
+  return(ans);
 }
 
 SEXP nqnewindices(SEXP xo, SEXP len, SEXP indices, SEXP nArg) {
 
-    R_len_t i, n = INTEGER(nArg)[0], nas=0, tmp=0;
-    SEXP ans, newstarts, newlen;
-    ans = PROTECT(allocVector(VECSXP, 2));
-    SET_VECTOR_ELT(ans, 0, (newstarts = allocVector(INTSXP, n)));
-    SET_VECTOR_ELT(ans, 1, (newlen = allocVector(INTSXP, n)));
+  R_len_t i, n = INTEGER(nArg)[0], nas=0, tmp=0;
+  SEXP ans, newstarts, newlen;
+  ans = PROTECT(allocVector(VECSXP, 2));
+  SET_VECTOR_ELT(ans, 0, (newstarts = allocVector(INTSXP, n)));
+  SET_VECTOR_ELT(ans, 1, (newlen = allocVector(INTSXP, n)));
 
-    for (i=0; i<n; i++) INTEGER(newlen)[i] = 0;
-    for (i=0; i<length(indices); i++) {
-        if (INTEGER(xo)[i] != NA_INTEGER)
-            INTEGER(newlen)[INTEGER(indices)[i]-1] += INTEGER(len)[i];
-        else INTEGER(newlen)[INTEGER(indices)[i]-1] = INTEGER(len)[i] != 0;
-    }
-    for (i=0; i<n; i++) {
-        if (INTEGER(xo)[nas++] == NA_INTEGER) {
-            INTEGER(newstarts)[i] = NA_INTEGER;
-        } else {
-            INTEGER(newstarts)[i] = INTEGER(newlen)[i] ? tmp+1 : 0;
-            tmp += INTEGER(newlen)[i];
-        }
+  for (i=0; i<n; i++) INTEGER(newlen)[i] = 0;
+  for (i=0; i<length(indices); i++) {
+    if (INTEGER(xo)[i] != NA_INTEGER)
+      INTEGER(newlen)[INTEGER(indices)[i]-1] += INTEGER(len)[i];
+    else INTEGER(newlen)[INTEGER(indices)[i]-1] = INTEGER(len)[i] != 0;
+  }
+  for (i=0; i<n; i++) {
+    if (INTEGER(xo)[nas++] == NA_INTEGER) {
+      INTEGER(newstarts)[i] = NA_INTEGER;
+    } else {
+      INTEGER(newstarts)[i] = INTEGER(newlen)[i] ? tmp+1 : 0;
+      tmp += INTEGER(newlen)[i];
     }
-    UNPROTECT(1);
-    return (ans);
+  }
+  UNPROTECT(1);
+  return (ans);
 }
diff --git a/src/vecseq.c b/src/vecseq.c
index 58f83f09c9..0d80032c01 100644
--- a/src/vecseq.c
+++ b/src/vecseq.c
@@ -2,41 +2,41 @@
 
 SEXP vecseq(SEXP x, SEXP len, SEXP clamp)
 {
-    // Name taken from bit::vecseq, but,
-    //   * implemented in C rather than R
-    //   * takes len rather than y endpoints
-    //   * no concat=FALSE or eval arguments as not needed.
-    //   * Adds clamp to prevent very large vectors being created (unless allow.cartesian=TRUE)
-    // Specially for use by [.data.table after binary search. Now so specialized that for general use
-    // bit::vecseq is recommended (Jens has coded it in C now).
-    R_len_t reslen,i,j,k, thisx;
-    double limit;
-    SEXP ans;
+  // Name taken from bit::vecseq, but,
+  //   * implemented in C rather than R
+  //   * takes len rather than y endpoints
+  //   * no concat=FALSE or eval arguments as not needed.
+  //   * Adds clamp to prevent very large vectors being created (unless allow.cartesian=TRUE)
+  // Specially for use by [.data.table after binary search. Now so specialized that for general use
+  // bit::vecseq is recommended (Jens has coded it in C now).
+  R_len_t reslen,i,j,k, thisx;
+  double limit;
+  SEXP ans;
 
-    if (!isInteger(x)) error("x must be an integer vector");
-    if (!isInteger(len)) error("len must be an integer vector");
-    if (LENGTH(x) != LENGTH(len)) error("x and len must be the same length");
-    reslen = 0;
-    for (i=0; i<LENGTH(len); i++) {
-        if (INT_MAX-reslen < INTEGER(len)[i])
-            error("Join results in more than 2^31 rows (internal vecseq reached physical limit). Very likely misspecified join. Check for duplicate key values in i each of which join to the same group in x over and over again. If that's ok, try by=.EACHI to run j for each group to avoid the large allocation. Otherwise, please search for this error message in the FAQ, Wiki, Stack Overflow and datatable-help for advice.");
-        reslen += INTEGER(len)[i];
+  if (!isInteger(x)) error("x must be an integer vector");
+  if (!isInteger(len)) error("len must be an integer vector");
+  if (LENGTH(x) != LENGTH(len)) error("x and len must be the same length");
+  reslen = 0;
+  for (i=0; i<LENGTH(len); i++) {
+    if (INT_MAX-reslen < INTEGER(len)[i])
+      error("Join results in more than 2^31 rows (internal vecseq reached physical limit). Very likely misspecified join. Check for duplicate key values in i each of which join to the same group in x over and over again. If that's ok, try by=.EACHI to run j for each group to avoid the large allocation. Otherwise, please search for this error message in the FAQ, Wiki, Stack Overflow and datatable-help for advice.");
+    reslen += INTEGER(len)[i];
+  }
+  if (!isNull(clamp)) {
+    if (!isNumeric(clamp) || LENGTH(clamp)!=1) error("clamp must be a double vector length 1");
+    limit = REAL(clamp)[0];
+    if (limit<0) error("clamp must be positive");
+    if (reslen>limit) error("Join results in %d rows; more than %d = nrow(x)+nrow(i). Check for duplicate key values in i each of which join to the same group in x over and over again. If that's ok, try by=.EACHI to run j for each group to avoid the large allocation. If you are sure you wish to proceed, rerun with allow.cartesian=TRUE. Otherwise, please search for this error message in the FAQ, Wiki, Stack Overflow and datatable-help for advice.", reslen, (int)limit);
+  }
+  ans = PROTECT(allocVector(INTSXP, reslen));
+  k = 0;
+  for (i=0; i<LENGTH(len); i++) {
+    thisx = INTEGER(x)[i];
+    for (j=0; j<INTEGER(len)[i]; j++) {
+      INTEGER(ans)[k++] = thisx++;
     }
-    if (!isNull(clamp)) {
-        if (!isNumeric(clamp) || LENGTH(clamp)!=1) error("clamp must be a double vector length 1");
-        limit = REAL(clamp)[0];
-        if (limit<0) error("clamp must be positive");
-        if (reslen>limit) error("Join results in %d rows; more than %d = nrow(x)+nrow(i). Check for duplicate key values in i each of which join to the same group in x over and over again. If that's ok, try by=.EACHI to run j for each group to avoid the large allocation. If you are sure you wish to proceed, rerun with allow.cartesian=TRUE. Otherwise, please search for this error message in the FAQ, Wiki, Stack Overflow and datatable-help for advice.", reslen, (int)limit);
-    }
-    ans = PROTECT(allocVector(INTSXP, reslen));
-    k = 0;
-    for (i=0; i<LENGTH(len); i++) {
-        thisx = INTEGER(x)[i];
-        for (j=0; j<INTEGER(len)[i]; j++) {
-            INTEGER(ans)[k++] = thisx++;
-        }
-    }
-    UNPROTECT(1);
-    return(ans);
+  }
+  UNPROTECT(1);
+  return(ans);
 }
 
diff --git a/src/wrappers.c b/src/wrappers.c
index 46190e4910..479e5b455d 100644
--- a/src/wrappers.c
+++ b/src/wrappers.c
@@ -8,121 +8,121 @@
 
 SEXP setattrib(SEXP x, SEXP name, SEXP value)
 {
-    if (TYPEOF(name) != STRSXP) error("Attribute name must be of type character");
-    if ( !isNewList(x) &&
-         strcmp(CHAR(STRING_ELT(name, 0)), "class") == 0 &&
-         isString(value) && (strcmp(CHAR(STRING_ELT(value, 0)), "data.table") == 0 ||
-         strcmp(CHAR(STRING_ELT(value, 0)), "data.frame") == 0) )
-        error("Internal structure doesn't seem to be a list. Can't set class to be 'data.table' or 'data.frame'. Use 'as.data.table()' or 'as.data.frame()' methods instead.");
-    if (isLogical(x) && x == ScalarLogical(TRUE)) {  // ok not to protect this ScalarLogical() as not assigned or passed
-        x = PROTECT(duplicate(x));
-        setAttrib(x, name, MAYBE_REFERENCED(value) ? duplicate(value) : value);
-        UNPROTECT(1);
-        return(x);
-    }
-    setAttrib(x, name,
-        MAYBE_REFERENCED(value) ? duplicate(value) : value);
-        // duplicate is temp fix to restore R behaviour prior to R-devel change on 10 Jan 2014 (r64724).
-        // TO DO: revisit. Enough to reproduce is: DT=data.table(a=1:3); DT[2]; DT[,b:=2]
-        // ... Error: selfrefnames is ok but tl names [1] != tl [100]
-    return(R_NilValue);
+  if (TYPEOF(name) != STRSXP) error("Attribute name must be of type character");
+  if ( !isNewList(x) &&
+       strcmp(CHAR(STRING_ELT(name, 0)), "class") == 0 &&
+       isString(value) && (strcmp(CHAR(STRING_ELT(value, 0)), "data.table") == 0 ||
+       strcmp(CHAR(STRING_ELT(value, 0)), "data.frame") == 0) )
+    error("Internal structure doesn't seem to be a list. Can't set class to be 'data.table' or 'data.frame'. Use 'as.data.table()' or 'as.data.frame()' methods instead.");
+  if (isLogical(x) && x == ScalarLogical(TRUE)) {  // ok not to protect this ScalarLogical() as not assigned or passed
+    x = PROTECT(duplicate(x));
+    setAttrib(x, name, MAYBE_REFERENCED(value) ? duplicate(value) : value);
+    UNPROTECT(1);
+    return(x);
+  }
+  setAttrib(x, name,
+    MAYBE_REFERENCED(value) ? duplicate(value) : value);
+    // duplicate is temp fix to restore R behaviour prior to R-devel change on 10 Jan 2014 (r64724).
+    // TO DO: revisit. Enough to reproduce is: DT=data.table(a=1:3); DT[2]; DT[,b:=2]
+    // ... Error: selfrefnames is ok but tl names [1] != tl [100]
+  return(R_NilValue);
 }
 
 // fix for #1142 - duplicated levels for factors
 SEXP setlevels(SEXP x, SEXP levels, SEXP ulevels) {
 
-    R_len_t nx = length(x), i;
-    SEXP xchar, newx;
-    xchar = PROTECT(allocVector(STRSXP, nx));
-    for (i=0; i<nx; i++)
-        SET_STRING_ELT(xchar, i, STRING_ELT(levels, INTEGER(x)[i]-1));
-    newx = PROTECT(chmatch(xchar, ulevels, NA_INTEGER, FALSE));
-    for (i=0; i<nx; i++) INTEGER(x)[i] = INTEGER(newx)[i];
-    setAttrib(x, R_LevelsSymbol, ulevels);
-    UNPROTECT(2);
-    return(x);
+  R_len_t nx = length(x), i;
+  SEXP xchar, newx;
+  xchar = PROTECT(allocVector(STRSXP, nx));
+  for (i=0; i<nx; i++)
+    SET_STRING_ELT(xchar, i, STRING_ELT(levels, INTEGER(x)[i]-1));
+  newx = PROTECT(chmatch(xchar, ulevels, NA_INTEGER, FALSE));
+  for (i=0; i<nx; i++) INTEGER(x)[i] = INTEGER(newx)[i];
+  setAttrib(x, R_LevelsSymbol, ulevels);
+  UNPROTECT(2);
+  return(x);
 }
 
 SEXP copy(SEXP x)
 {
-    return(duplicate(x));
+  return(duplicate(x));
 }
 
 SEXP copyattr(SEXP from, SEXP to)
 {
-    // for use by [.data.table to retain attribs such as "comments" when subsetting and j is missing
-    copyMostAttrib(from, to);
-    return(R_NilValue);
+  // for use by [.data.table to retain attribs such as "comments" when subsetting and j is missing
+  copyMostAttrib(from, to);
+  return(R_NilValue);
 }
 
 SEXP setlistelt(SEXP l, SEXP i, SEXP value)
 {
-    R_len_t i2;
-    // Internal use only. So that := can update elements of a list of data.table, #2204. Just needed to overallocate/grow the VECSXP.
-    if (!isNewList(l)) error("First argument to setlistelt must be a list()");
-    if (!isInteger(i) || LENGTH(i)!=1) error("Second argument to setlistelt must a length 1 integer vector");
-    i2 = INTEGER(i)[0];
-    if (LENGTH(l) < i2 || i2<1) error("i (%d) is outside the range of items [1,%d]",i2,LENGTH(l));
-    SET_VECTOR_ELT(l, i2-1, value);
-    return(R_NilValue);
+  R_len_t i2;
+  // Internal use only. So that := can update elements of a list of data.table, #2204. Just needed to overallocate/grow the VECSXP.
+  if (!isNewList(l)) error("First argument to setlistelt must be a list()");
+  if (!isInteger(i) || LENGTH(i)!=1) error("Second argument to setlistelt must a length 1 integer vector");
+  i2 = INTEGER(i)[0];
+  if (LENGTH(l) < i2 || i2<1) error("i (%d) is outside the range of items [1,%d]",i2,LENGTH(l));
+  SET_VECTOR_ELT(l, i2-1, value);
+  return(R_NilValue);
 }
 
 SEXP setmutable(SEXP x)
 {
-    // called from one single place at R level. TODO: avoid somehow, but fails tests without
-    // At least the SET_NAMED() direct call is passed 0 and makes no assumptions about >0.  Good enough for now as patch for CRAN is needed.
-    SET_NAMED(x,0);
-    return(x);
+  // called from one single place at R level. TODO: avoid somehow, but fails tests without
+  // At least the SET_NAMED() direct call is passed 0 and makes no assumptions about >0.  Good enough for now as patch for CRAN is needed.
+  SET_NAMED(x,0);
+  return(x);
 }
 
 SEXP address(SEXP x)
 {
-    // A better way than : http://stackoverflow.com/a/10913296/403310
-    char buffer[32];
-    snprintf(buffer, 32, "%p", (void *)x);
-    return(mkString(buffer));
+  // A better way than : http://stackoverflow.com/a/10913296/403310
+  char buffer[32];
+  snprintf(buffer, 32, "%p", (void *)x);
+  return(mkString(buffer));
 }
 
 SEXP copyNamedInList(SEXP x)
 {
-    // As from R 3.1.0 list() no longer copies NAMED inputs
-    // Since data.table allows subassignment by reference, we need a way to copy NAMED inputs, still.
-    // But for many other applications (such as in j and elsewhere internally) the new non-copying list() in R 3.1.0 is very welcome.
+  // As from R 3.1.0 list() no longer copies NAMED inputs
+  // Since data.table allows subassignment by reference, we need a way to copy NAMED inputs, still.
+  // But for many other applications (such as in j and elsewhere internally) the new non-copying list() in R 3.1.0 is very welcome.
 
-    // This is intended to be called just after list(...) in data.table().  It isn't for use on a single data.table, as
-    // member columns of a list aren't marked as NAMED when the VECSXP is.
+  // This is intended to be called just after list(...) in data.table().  It isn't for use on a single data.table, as
+  // member columns of a list aren't marked as NAMED when the VECSXP is.
 
-    // For now, this makes the old behaviour of list() in R<3.1.0 available for use, where we need it.
+  // For now, this makes the old behaviour of list() in R<3.1.0 available for use, where we need it.
 
-    if (TYPEOF(x) != VECSXP) error("x isn't a VECSXP");
-    for (int i=0; i<LENGTH(x); i++) {
-        if (MAYBE_REFERENCED(VECTOR_ELT(x, i))) {
-            SET_VECTOR_ELT(x, i, duplicate(VECTOR_ELT(x,i)));
-        }
+  if (TYPEOF(x) != VECSXP) error("x isn't a VECSXP");
+  for (int i=0; i<LENGTH(x); i++) {
+    if (MAYBE_REFERENCED(VECTOR_ELT(x, i))) {
+      SET_VECTOR_ELT(x, i, duplicate(VECTOR_ELT(x,i)));
     }
-    return R_NilValue;
+  }
+  return R_NilValue;
 }
 
 SEXP dim(SEXP x)
 {
-    // fast implementation of dim.data.table
-
-    if (TYPEOF(x) != VECSXP) {
-        error("dim.data.table expects a data.table as input (which is a list), but seems to be of type %s",
-              type2char(TYPEOF(x)));
-    }
-
-    SEXP ans = allocVector(INTSXP, 2);
-    if(length(x) == 0) {
-        INTEGER(ans)[0] = 0;
-        INTEGER(ans)[1] = 0;
-    }
-    else {
-        INTEGER(ans)[0] = length(VECTOR_ELT(x, 0));
-        INTEGER(ans)[1] = length(x);
-    }
-
-    return ans;
+  // fast implementation of dim.data.table
+
+  if (TYPEOF(x) != VECSXP) {
+    error("dim.data.table expects a data.table as input (which is a list), but seems to be of type %s",
+        type2char(TYPEOF(x)));
+  }
+
+  SEXP ans = allocVector(INTSXP, 2);
+  if(length(x) == 0) {
+    INTEGER(ans)[0] = 0;
+    INTEGER(ans)[1] = 0;
+  }
+  else {
+    INTEGER(ans)[0] = length(VECTOR_ELT(x, 0));
+    INTEGER(ans)[1] = length(x);
+  }
+
+  return ans;
 }
 
 

From 5c1e0090e92abf2369303cb0b4d1cf9be5c40563 Mon Sep 17 00:00:00 2001
From: Matt Dowle <mattjdowle@gmail.com>
Date: Fri, 13 Oct 2017 18:31:24 -0700
Subject: [PATCH 3/4] tabs -> 4 spaces, 3 R files that contained tabs

---
 R/data.table.R | 46 +++++++++++++++++++++++-----------------------
 R/last.R       |  2 +-
 R/shift.R      |  6 +++---
 3 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/R/data.table.R b/R/data.table.R
index bd89964202..49c67402b7 100644
--- a/R/data.table.R
+++ b/R/data.table.R
@@ -708,7 +708,7 @@ chmatch2 <- function(x, table, nomatch=NA_integer_) {
             if (!is.logical(i) && !is.numeric(i)) stop("i has not evaluated to logical, integer or double")
             if (is.logical(i)) {
                 if (length(i)==1L  # to avoid unname copy when length(i)==nrow (normal case we don't want to slow down)
-		    && isTRUE(unname(i))) irows=i=NULL  # unname() for #2152 - length 1 named logical vector.
+                    && isTRUE(unname(i))) irows=i=NULL  # unname() for #2152 - length 1 named logical vector.
                 # NULL is efficient signal to avoid creating 1:nrow(x) but still return all rows, fixes #1249
 
                 else if (length(i)<=1L) irows=i=integer(0)
@@ -2327,17 +2327,17 @@ copy <- function(x) {
                            # TO DO: inside Ccopy it could reset tl to 0 or length, but no matter as selfrefok detects it
                            # TO DO: revisit duplicate.c in R 3.0.3 and see where it's at
     if (!is.data.table(x)) {
-	# fix for #1476. TODO: find if a cleaner fix is possible..
-	if (is.list(x)) {
-	    anydt = vapply(x, is.data.table, TRUE, USE.NAMES=FALSE)
-	    if (sum(anydt)) {
-		newx[anydt] = lapply(newx[anydt], function(x) {
-				    setattr(x, ".data.table.locked", NULL)
-				    alloc.col(x)
-				})
-	    }
-	}
-	return(newx)   # e.g. in as.data.table.list() the list is copied before changing to data.table
+        # fix for #1476. TODO: find if a cleaner fix is possible..
+        if (is.list(x)) {
+            anydt = vapply(x, is.data.table, TRUE, USE.NAMES=FALSE)
+            if (sum(anydt)) {
+                newx[anydt] = lapply(newx[anydt], function(x) {
+                    setattr(x, ".data.table.locked", NULL)
+                    alloc.col(x)
+                })
+            }
+        }
+        return(newx)   # e.g. in as.data.table.list() the list is copied before changing to data.table
     }
     setattr(newx,".data.table.locked",NULL)
     alloc.col(newx)
@@ -2437,13 +2437,13 @@ setattr <- function(x,name,value) {
         # creating names longer than the number of columns of x, and to change the key, too
         # For convenience so that setattr(DT,"names",allnames) works as expected without requiring a switch to setnames.
     else {
-	# fix for R's global TRUE value input, #1281
-	ans = .Call(Csetattrib, x, name, value)
-	# If name=="names" and this is the first time names are assigned (e.g. in data.table()), this will be grown by alloc.col very shortly afterwards in the caller.
-	if (!is.null(ans)) {
-	    warning("Input is a length=1 logical that points to the same address as R's global TRUE value. Therefore the attribute has not been set by reference, rather on a copy. You will need to assign the result back to a variable. See https://github.com/Rdatatable/data.table/issues/1281 for more.")
-	    x = ans
-	}
+        # fix for R's global TRUE value input, #1281
+        ans = .Call(Csetattrib, x, name, value)
+        # If name=="names" and this is the first time names are assigned (e.g. in data.table()), this will be grown by alloc.col very shortly afterwards in the caller.
+        if (!is.null(ans)) {
+            warning("Input is a length=1 logical that points to the same address as R's global TRUE value. Therefore the attribute has not been set by reference, rather on a copy. You will need to assign the result back to a variable. See https://github.com/Rdatatable/data.table/issues/1281 for more.")
+            x = ans
+        }
     }
     # fix for #1142 - duplicated levels for factors
     if (name == "levels" && is.factor(x) && anyDuplicated(value))
@@ -2686,10 +2686,10 @@ setDT <- function(x, keep.rownames=FALSE, key=NULL, check.names=FALSE) {
         x = null.data.table()
     } else if (is.list(x)) {
         # copied from as.data.table.list - except removed the copy
-	for (i in seq_along(x)) {
-	    if (inherits(x[[i]], "POSIXlt"))
-		stop("Column ", i, " is of POSIXlt type. Please convert it to POSIXct using as.POSIXct and run setDT again. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
-	}
+        for (i in seq_along(x)) {
+            if (inherits(x[[i]], "POSIXlt"))
+            stop("Column ", i, " is of POSIXlt type. Please convert it to POSIXct using as.POSIXct and run setDT again. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
+        }
         n = vapply(x, length, 0L)
         mn = max(n)
         if (any(n<mn))
diff --git a/R/last.R b/R/last.R
index be287602ca..03da38d455 100644
--- a/R/last.R
+++ b/R/last.R
@@ -6,7 +6,7 @@
 last <- function(x, ...) {
     if (nargs()==1L) {
         if (is.vector(x)) {
-        	if (!length(x)) return(x) else return(x[[length(x)]])  # for vectors, [[ works like [
+            if (!length(x)) return(x) else return(x[[length(x)]])  # for vectors, [[ works like [
         } else if (is.data.frame(x)) return(x[NROW(x),])
     }
     if(!requireNamespace("xts", quietly = TRUE)) {
diff --git a/R/shift.R b/R/shift.R
index ffea5639b2..9ee9198df7 100644
--- a/R/shift.R
+++ b/R/shift.R
@@ -3,9 +3,9 @@ shift <- function(x, n=1L, fill=NA, type=c("lag", "lead"), give.names=FALSE) {
     ans = .Call(Cshift, x, as.integer(n), fill, type)
     if (give.names) {
         if (is.null(names(x))) {
-        		xsub = substitute(x)
-        		if (is.atomic(x) && is.name(xsub)) nx = deparse(xsub, 500L)
-        		else nx = paste("V", if (is.atomic(x)) 1L else seq_along(x), sep="")
+            xsub = substitute(x)
+            if (is.atomic(x) && is.name(xsub)) nx = deparse(xsub, 500L)
+            else nx = paste("V", if (is.atomic(x)) 1L else seq_along(x), sep="")
         }
         else nx = names(x)
         setattr(ans, 'names', do.call("paste", c(CJ(nx, type, n, sorted=FALSE), sep="_")))

From 11644eb7c79815c26e66f82491a67ea6bd3e1abf Mon Sep 17 00:00:00 2001
From: Matt Dowle <mattjdowle@gmail.com>
Date: Fri, 13 Oct 2017 19:22:48 -0700
Subject: [PATCH 4/4] 4 -> 2 space indent, all R files other than foverlaps
 which was already.

---
 R/AllS4.R            |    4 +-
 R/IDateTime.R        |  272 +--
 R/as.data.table.R    |  294 +--
 R/between.R          |   52 +-
 R/bmerge.R           |  194 +-
 R/c.factor.R         |   24 +-
 R/cedta.R            |   42 +-
 R/data.table.R       | 4795 +++++++++++++++++++++---------------------
 R/duplicated.R       |  164 +-
 R/fcast.R            |  412 ++--
 R/fmelt.R            |   74 +-
 R/frank.R            |  170 +-
 R/fread.R            |  269 ++-
 R/fwrite.R           |  112 +-
 R/getdots.R          |    8 +-
 R/groupingsets.R     |  204 +-
 R/last.R             |   52 +-
 R/like.R             |   18 +-
 R/merge.R            |  158 +-
 R/onAttach.R         |   36 +-
 R/onLoad.R           |  172 +-
 R/openmp-utils.R     |    4 +-
 R/print.data.table.R |  242 +--
 R/setkey.R           |  538 ++---
 R/setops.R           |  478 ++---
 R/shift.R            |   22 +-
 R/tables.R           |   88 +-
 R/test.data.table.R  |  330 +--
 R/timetaken.R        |   14 +-
 R/transpose.R        |   60 +-
 R/uniqlist.R         |   30 +-
 R/utils.R            |   44 +-
 R/xts.R              |   24 +-
 33 files changed, 4697 insertions(+), 4703 deletions(-)

diff --git a/R/AllS4.R b/R/AllS4.R
index 0ba5e31d32..1e6244fbcb 100644
--- a/R/AllS4.R
+++ b/R/AllS4.R
@@ -8,12 +8,12 @@ setOldClass(c('data.table', 'data.frame'))
 
 ## as(some.data.frame, "data.table")
 setAs("data.frame", "data.table", function(from) {
-    as.data.table(from)
+  as.data.table(from)
 })
 
 ## as(some.data.table, "data.frame")
 setAs("data.table", "data.frame", function(from) {
-    as.data.frame(from)
+  as.data.frame(from)
 })
 
 setOldClass("IDate")
diff --git a/R/IDateTime.R b/R/IDateTime.R
index 7680866174..f94874d7d8 100644
--- a/R/IDateTime.R
+++ b/R/IDateTime.R
@@ -6,16 +6,16 @@
 as.IDate <- function(x, ...) UseMethod("as.IDate")
 
 as.IDate.default <- function(x, tz = attr(x, "tzone"), ...) {
-    if (is.null(tz)) tz = "UTC"
-    as.IDate(as.Date(x, tz = tz, ...))
+  if (is.null(tz)) tz = "UTC"
+  as.IDate(as.Date(x, tz = tz, ...))
 }
 
 as.IDate.numeric <- function(x, ...) {
-    structure(as.integer(x) %/% 86400L, class=c("IDate","Date"))
+  structure(as.integer(x) %/% 86400L, class=c("IDate","Date"))
 }
 
 as.IDate.Date <- function(x, ...) {
-    structure(as.integer(x), class=c("IDate","Date"))
+  structure(as.integer(x), class=c("IDate","Date"))
 }
 
 as.IDate.POSIXct <- function(x, tz = attr(x, "tzone"), ...) {
@@ -26,7 +26,7 @@ as.IDate.POSIXct <- function(x, tz = attr(x, "tzone"), ...) {
 as.IDate.IDate <- function(x, ...) x
 
 as.Date.IDate <- function(x, ...) {
-    structure(as.numeric(x), class="Date")
+  structure(as.numeric(x), class="Date")
 }
 
 mean.IDate <-
@@ -36,9 +36,9 @@ c.IDate <-
 rep.IDate <-
 split.IDate <-
 unique.IDate <-
-    function(x, ...) {
-        as.IDate(NextMethod())
-    }
+  function(x, ...) {
+    as.IDate(NextMethod())
+  }
 
 # fix for #1315
 as.list.IDate <- function(x, ...) NextMethod()
@@ -48,46 +48,46 @@ as.list.IDate <- function(x, ...) NextMethod()
 ##     if (missing(digits)) digits <- units # workaround to provide a units argument to match the round generic and round.POSIXt
 ##     units <- match.arg(digits, c("weeks", "months", "quarters", "years"))
 round.IDate <- function (x, digits=c("weeks", "months", "quarters", "years"), ...) {
-    units <- match.arg(digits)
-    as.IDate(switch(units,
-                    weeks  = round(x, "year") + 7 * (yday(x) %/% 7),
-                    months = ISOdate(year(x), month(x), 1),
-                    quarters = ISOdate(year(x), 3 * (quarter(x)-1) + 1, 1),
-                    years = ISOdate(year(x), 1, 1)))
+  units <- match.arg(digits)
+  as.IDate(switch(units,
+          weeks  = round(x, "year") + 7 * (yday(x) %/% 7),
+          months = ISOdate(year(x), month(x), 1),
+          quarters = ISOdate(year(x), 3 * (quarter(x)-1) + 1, 1),
+          years = ISOdate(year(x), 1, 1)))
 }
 
 #Adapted from `+.Date`
 `+.IDate` <- function (e1, e2) {
-    if (nargs() == 1L)
-        return(e1)
-    if (inherits(e1, "difftime") || inherits(e2, "difftime"))
-        stop("difftime objects may not be added to IDate. Use plain integer instead of difftime.")
-    if ( (storage.mode(e1)=="double" && isReallyReal(e1)) ||
-         (storage.mode(e2)=="double" && isReallyReal(e2)) ) {
-        return(`+.Date`(e1,e2))
-        # IDate doesn't support fractional days; revert to base Date
-    }
-    if (inherits(e1, "Date") && inherits(e2, "Date"))
-        stop("binary + is not defined for \"IDate\" objects")
-    structure(as.integer(unclass(e1) + unclass(e2)), class = c("IDate", "Date"))
+  if (nargs() == 1L)
+    return(e1)
+  if (inherits(e1, "difftime") || inherits(e2, "difftime"))
+    stop("difftime objects may not be added to IDate. Use plain integer instead of difftime.")
+  if ( (storage.mode(e1)=="double" && isReallyReal(e1)) ||
+       (storage.mode(e2)=="double" && isReallyReal(e2)) ) {
+    return(`+.Date`(e1,e2))
+    # IDate doesn't support fractional days; revert to base Date
+  }
+  if (inherits(e1, "Date") && inherits(e2, "Date"))
+    stop("binary + is not defined for \"IDate\" objects")
+  structure(as.integer(unclass(e1) + unclass(e2)), class = c("IDate", "Date"))
 }
 
 `-.IDate` <- function (e1, e2) {
-    if (!inherits(e1, "IDate"))
-        stop("can only subtract from \"IDate\" objects")
-    if (storage.mode(e1) != "integer")
-        stop("Internal error: storage mode of IDate is somehow no longer integer")
-    if (nargs() == 1)
-        stop("unary - is not defined for \"IDate\" objects")
-    if (inherits(e2, "difftime"))
-        stop("difftime objects may not be subtracted from IDate. Use plain integer instead of difftime.")
-    if ( storage.mode(e2)=="double" && isReallyReal(e2) ) {
-        return(`-.Date`(as.Date(e1),as.Date(e2)))
-        # IDate doesn't support fractional days so revert to base Date
-    }
-    ans = as.integer(unclass(e1) - unclass(e2))
-    if (!inherits(e2, "Date")) class(ans) = c("IDate","Date")
-    return(ans)
+  if (!inherits(e1, "IDate"))
+    stop("can only subtract from \"IDate\" objects")
+  if (storage.mode(e1) != "integer")
+    stop("Internal error: storage mode of IDate is somehow no longer integer")
+  if (nargs() == 1)
+    stop("unary - is not defined for \"IDate\" objects")
+  if (inherits(e2, "difftime"))
+    stop("difftime objects may not be subtracted from IDate. Use plain integer instead of difftime.")
+  if ( storage.mode(e2)=="double" && isReallyReal(e2) ) {
+    return(`-.Date`(as.Date(e1),as.Date(e2)))
+    # IDate doesn't support fractional days so revert to base Date
+  }
+  ans = as.integer(unclass(e1) - unclass(e2))
+  if (!inherits(e2, "Date")) class(ans) = c("IDate","Date")
+  return(ans)
 }
 
 
@@ -100,104 +100,104 @@ round.IDate <- function (x, digits=c("weeks", "months", "quarters", "years"), ..
 as.ITime <- function(x, ...) UseMethod("as.ITime")
 
 as.ITime.default <- function(x, ...) {
-    as.ITime(as.POSIXlt(x, ...))
+  as.ITime(as.POSIXlt(x, ...))
 }
 
 as.ITime.POSIXct <- function(x, tz = attr(x, "tzone"), ...) {
-    if (is.null(tz)) tz = "UTC"
-    if (tz %in% c("UTC", "GMT")) as.ITime(unclass(x), ...) else as.ITime(as.POSIXlt(x, tz = tz, ...))
+  if (is.null(tz)) tz = "UTC"
+  if (tz %in% c("UTC", "GMT")) as.ITime(unclass(x), ...) else as.ITime(as.POSIXlt(x, tz = tz, ...))
 }
 
 as.ITime.numeric <- function(x, ...) {
-    structure(as.integer(x) %% 86400L, class = "ITime")
+  structure(as.integer(x) %% 86400L, class = "ITime")
 }
 
 as.ITime.character <- function(x, format, ...) {
-    x <- unclass(x)
-    if (!missing(format)) return(as.ITime(strptime(x, format = format, ...)))
-    # else allow for mixed formats, such as test 1189 where seconds are caught despite varying format
-    y <- strptime(x, format = "%H:%M:%OS", ...)
-    w <- which(is.na(y))
-    formats = c("%H:%M",
-                "%Y-%m-%d %H:%M:%OS",
-                "%Y/%m/%d %H:%M:%OS",
-                "%Y-%m-%d %H:%M",
-                "%Y/%m/%d %H:%M",
-                "%Y-%m-%d",
-                "%Y/%m/%d")
-    for (f in formats) {
-      if (!length(w)) break
-      new <- strptime(x[w], format = f, ...)
-      nna <- !is.na(new)
-      if (any(nna)) {
-        y[ w[nna] ] <- new
-        w <- w[!nna]
-      }
+  x <- unclass(x)
+  if (!missing(format)) return(as.ITime(strptime(x, format = format, ...)))
+  # else allow for mixed formats, such as test 1189 where seconds are caught despite varying format
+  y <- strptime(x, format = "%H:%M:%OS", ...)
+  w <- which(is.na(y))
+  formats = c("%H:%M",
+        "%Y-%m-%d %H:%M:%OS",
+        "%Y/%m/%d %H:%M:%OS",
+        "%Y-%m-%d %H:%M",
+        "%Y/%m/%d %H:%M",
+        "%Y-%m-%d",
+        "%Y/%m/%d")
+  for (f in formats) {
+    if (!length(w)) break
+    new <- strptime(x[w], format = f, ...)
+    nna <- !is.na(new)
+    if (any(nna)) {
+      y[ w[nna] ] <- new
+      w <- w[!nna]
     }
-    return(as.ITime(y))
+  }
+  return(as.ITime(y))
 }
 
 as.ITime.POSIXlt <- function(x, ...) {
-    structure(with(x, as.integer(sec) + min * 60L + hour * 3600L),
-              class = "ITime")
+  structure(with(x, as.integer(sec) + min * 60L + hour * 3600L),
+        class = "ITime")
 }
 
 as.character.ITime <- format.ITime <- function(x, ...) {
-    # adapted from chron's format.times
-    # Fix for #811. Thanks to @StefanFritsch for the code snippet
-    neg <- x < 0L
-    x  <- abs(unclass(x))
-    hh <- x %/% 3600L
-    mm <- (x - hh * 3600L) %/% 60L
-    # #2171 -- trunc gives numeric but %02d requires integer;
-    #   as.integer is also faster (but doesn't handle integer overflow)
-    #   http://stackoverflow.com/questions/43894077
-    ss <- as.integer(x - hh * 3600L - 60L * mm)
-    res = sprintf('%02d:%02d:%02d', hh, mm, ss)
-    # Fix for #1354, so that "NA" input is handled correctly.
-    if (is.na(any(neg))) res[is.na(x)] = NA
-    neg = which(neg)
-    if (length(neg)) res[neg] = paste("-", res[neg], sep="")
-    res
+  # adapted from chron's format.times
+  # Fix for #811. Thanks to @StefanFritsch for the code snippet
+  neg <- x < 0L
+  x  <- abs(unclass(x))
+  hh <- x %/% 3600L
+  mm <- (x - hh * 3600L) %/% 60L
+  # #2171 -- trunc gives numeric but %02d requires integer;
+  #   as.integer is also faster (but doesn't handle integer overflow)
+  #   http://stackoverflow.com/questions/43894077
+  ss <- as.integer(x - hh * 3600L - 60L * mm)
+  res = sprintf('%02d:%02d:%02d', hh, mm, ss)
+  # Fix for #1354, so that "NA" input is handled correctly.
+  if (is.na(any(neg))) res[is.na(x)] = NA
+  neg = which(neg)
+  if (length(neg)) res[neg] = paste("-", res[neg], sep="")
+  res
 }
 
 as.data.frame.ITime <- function(x, ...) {
-    # This method is just for ggplot2, #1713
-    # Avoids the error "cannot coerce class '"ITime"' into a data.frame", but for some reason
-    # ggplot2 doesn't seem to call the print method to get axis labels, so still prints integers.
-    # Tried converting to POSIXct but that gives the error below.
-    # If user converts to POSIXct themselves, then it works for some reason.
-    ans = list(x)
-    # ans = list(as.POSIXct(x,tzone=""))  # ggplot2 gives "Error: Discrete value supplied to continuous scale"
-    setattr(ans,"class","data.frame")
-    setattr(ans,"row.names", .set_row_names(length(x)))
-    setattr(ans,"names",NULL)
-    ans
+  # This method is just for ggplot2, #1713
+  # Avoids the error "cannot coerce class '"ITime"' into a data.frame", but for some reason
+  # ggplot2 doesn't seem to call the print method to get axis labels, so still prints integers.
+  # Tried converting to POSIXct but that gives the error below.
+  # If user converts to POSIXct themselves, then it works for some reason.
+  ans = list(x)
+  # ans = list(as.POSIXct(x,tzone=""))  # ggplot2 gives "Error: Discrete value supplied to continuous scale"
+  setattr(ans,"class","data.frame")
+  setattr(ans,"row.names", .set_row_names(length(x)))
+  setattr(ans,"names",NULL)
+  ans
 }
 
 print.ITime <- function(x, ...) {
-    print(format(x))
+  print(format(x))
 }
 
 rep.ITime <- function (x, ...)
 {
-    y <- rep(unclass(x), ...)
-    structure(y, class = "ITime")
+  y <- rep(unclass(x), ...)
+  structure(y, class = "ITime")
 }
 
 "[.ITime" <- function(x, ..., drop = TRUE)
 {
-    cl <- oldClass(x)
-    class(x) <- NULL
-    val <- NextMethod("[")
-    class(val) <- cl
-    val
+  cl <- oldClass(x)
+  class(x) <- NULL
+  val <- NextMethod("[")
+  class(val) <- cl
+  val
 }
 
 unique.ITime <- function(x, ...) {
-    ans = NextMethod()
-    setattr(ans,"class","ITime")
-    ans
+  ans = NextMethod()
+  setattr(ans,"class","ITime")
+  ans
 }
 
 # create a data.table with IDate and ITime columns
@@ -205,60 +205,60 @@ unique.ITime <- function(x, ...) {
 
 IDateTime <- function(x, ...) UseMethod("IDateTime")
 IDateTime.default <- function(x, ...) {
-    data.table(idate = as.IDate(x), itime = as.ITime(x))
+  data.table(idate = as.IDate(x), itime = as.ITime(x))
 }
 
 # POSIXt support
 
 as.POSIXct.IDate <- function(x, tz = "UTC", time = 0, ...) {
-    if (missing(time) && inherits(tz, "ITime")) {
-        time <- tz # allows you to use time as the 2nd argument
-        tz <- "UTC"
-    }
-    if (tz == "") tz <- "UTC"
-    as.POSIXct(as.POSIXlt(x, ...), tz, ...) + time
+  if (missing(time) && inherits(tz, "ITime")) {
+    time <- tz # allows you to use time as the 2nd argument
+    tz <- "UTC"
+  }
+  if (tz == "") tz <- "UTC"
+  as.POSIXct(as.POSIXlt(x, ...), tz, ...) + time
 }
 
 as.POSIXct.ITime <- function(x, tz = "UTC", date = as.Date(Sys.time()), ...) {
-    if (missing(date) && any(class(tz) %in% c("Date", "IDate", "POSIXt", "dates"))) {
-        date <- tz # allows you to use date as the 2nd argument
-        tz <- "UTC"
-    }
-    as.POSIXct(as.POSIXlt(date), tz = tz) + x
+  if (missing(date) && any(class(tz) %in% c("Date", "IDate", "POSIXt", "dates"))) {
+    date <- tz # allows you to use date as the 2nd argument
+    tz <- "UTC"
+  }
+  as.POSIXct(as.POSIXlt(date), tz = tz) + x
 }
 
 as.POSIXlt.ITime <- function(x, ...) {
-    as.POSIXlt(as.POSIXct(x, ...))
+  as.POSIXlt(as.POSIXct(x, ...))
 }
 
 # chron support
 
 as.chron.IDate <- function(x, time = NULL, ...) {
-    if(!requireNamespace("chron", quietly = TRUE)) stop("Install suggested `chron` package to use `as.chron.IDate` function.") else {
-        if (!is.null(time)) {
-            chron::chron(dates. = chron::as.chron(as.Date(x)), times. = chron::as.chron(time))
-        } else {
-            chron::chron(dates. = chron::as.chron(as.Date(x)))
-        }
+  if(!requireNamespace("chron", quietly = TRUE)) stop("Install suggested `chron` package to use `as.chron.IDate` function.") else {
+    if (!is.null(time)) {
+      chron::chron(dates. = chron::as.chron(as.Date(x)), times. = chron::as.chron(time))
+    } else {
+      chron::chron(dates. = chron::as.chron(as.Date(x)))
     }
+  }
 }
 
 as.chron.ITime <- function(x, date = NULL, ...) {
-    if(!requireNamespace("chron", quietly = TRUE)) stop("Install suggested `chron` package to use `as.chron.ITime` function.") else {
-        if (!is.null(date)) {
-            chron::chron(dates. = chron::as.chron(as.Date(date)), times. = chron::as.chron(x))
-        } else {
-            chron::chron(times. = as.character(x))
-        }
+  if(!requireNamespace("chron", quietly = TRUE)) stop("Install suggested `chron` package to use `as.chron.ITime` function.") else {
+    if (!is.null(date)) {
+      chron::chron(dates. = chron::as.chron(as.Date(date)), times. = chron::as.chron(x))
+    } else {
+      chron::chron(times. = as.character(x))
     }
+  }
 }
 
 as.ITime.times <- function(x, ...) {
-    x <- unclass(x)
-    daypart <- x - floor(x)
-    secs <- as.integer(round(daypart * 86400))
-    structure(secs,
-              class = "ITime")
+  x <- unclass(x)
+  daypart <- x - floor(x)
+  secs <- as.integer(round(daypart * 86400))
+  structure(secs,
+        class = "ITime")
 }
 
 ###################################################################
diff --git a/R/as.data.table.R b/R/as.data.table.R
index 59e6cdc9c2..73dac4e894 100644
--- a/R/as.data.table.R
+++ b/R/as.data.table.R
@@ -1,8 +1,8 @@
 as.data.table <-function(x, keep.rownames=FALSE, ...)
 {
-    if (is.null(x))
-        return(null.data.table())
-    UseMethod("as.data.table")
+  if (is.null(x))
+    return(null.data.table())
+  UseMethod("as.data.table")
 }
 
 as.data.table.default <- function(x, ...){
@@ -13,144 +13,144 @@ as.data.table.factor <- as.data.table.ordered <-
 as.data.table.integer <- as.data.table.numeric <-
 as.data.table.logical <- as.data.table.character <-
 as.data.table.Date <- as.data.table.ITime <- function(x, keep.rownames=FALSE, ...) {
-    if (is.matrix(x)) {
-        return(as.data.table.matrix(x, ...))
-    }
-    tt = deparse(substitute(x))[1]
-    nm = names(x)
-    # FR #2356 - transfer names of named vector as "rn" column if required
-    if (!identical(keep.rownames, FALSE) & !is.null(nm))
-        x <- list(nm, unname(x))
-    else x <- list(x)
-    if (tt == make.names(tt)) {
-        # can specify col name to keep.rownames, #575
-        nm = if (length(x) == 2L) if (is.character(keep.rownames)) keep.rownames[1L] else "rn"
-        setattr(x, 'names', c(nm, tt))
-    }
-    as.data.table.list(x, FALSE)
+  if (is.matrix(x)) {
+    return(as.data.table.matrix(x, ...))
+  }
+  tt = deparse(substitute(x))[1]
+  nm = names(x)
+  # FR #2356 - transfer names of named vector as "rn" column if required
+  if (!identical(keep.rownames, FALSE) & !is.null(nm))
+    x <- list(nm, unname(x))
+  else x <- list(x)
+  if (tt == make.names(tt)) {
+    # can specify col name to keep.rownames, #575
+    nm = if (length(x) == 2L) if (is.character(keep.rownames)) keep.rownames[1L] else "rn"
+    setattr(x, 'names', c(nm, tt))
+  }
+  as.data.table.list(x, FALSE)
 }
 
 # as.data.table.table - FR #4848
 as.data.table.table <- function(x, keep.rownames=FALSE, ...) {
-    # Fix for bug #5408 - order of columns are different when doing as.data.table(with(DT, table(x, y)))
-    val = rev(dimnames(provideDimnames(x)))
-    if (is.null(names(val)) || !any(nzchar(names(val))))
-        setattr(val, 'names', paste("V", rev(seq_along(val)), sep=""))
-    ans <- data.table(do.call(CJ, c(val, sorted=FALSE)), N = as.vector(x))
-    setcolorder(ans, c(rev(head(names(ans), -1)), "N"))
-    ans
+  # Fix for bug #5408 - order of columns are different when doing as.data.table(with(DT, table(x, y)))
+  val = rev(dimnames(provideDimnames(x)))
+  if (is.null(names(val)) || !any(nzchar(names(val))))
+    setattr(val, 'names', paste("V", rev(seq_along(val)), sep=""))
+  ans <- data.table(do.call(CJ, c(val, sorted=FALSE)), N = as.vector(x))
+  setcolorder(ans, c(rev(head(names(ans), -1)), "N"))
+  ans
 }
 
 as.data.table.matrix <- function(x, keep.rownames=FALSE, ...) {
-    if (!identical(keep.rownames, FALSE)) {
-        # can specify col name to keep.rownames, #575
-        ans = data.table(rn=rownames(x), x, keep.rownames=FALSE)
-        if (is.character(keep.rownames))
-            setnames(ans, 'rn', keep.rownames[1L])
-        return(ans)
-    }
-    d <- dim(x)
-    nrows <- d[1L]
-    ir <- seq_len(nrows)
-    ncols <- d[2L]
-    ic <- seq_len(ncols)
-    dn <- dimnames(x)
-    collabs <- dn[[2L]]
-    empty <- !nzchar(collabs)
-    if (any(empty))
-        collabs[empty] <- paste("V", ic, sep = "")[empty]
-    value <- vector("list", ncols)
-    if (mode(x) == "character") {
-        # fix for #745 - A long overdue SO post: http://stackoverflow.com/questions/17691050/data-table-still-converts-strings-to-factors
-        for (i in ic) value[[i]] <- x[, i]                  # <strike>for efficiency.</strike> For consistency - data.table likes and prefers "character"
-    }
-    else {
-        for (i in ic) value[[i]] <- as.vector(x[, i])       # to drop any row.names that would otherwise be retained inside every column of the data.table
-    }
-    if (length(collabs) == ncols)
-        setattr(value, "names", collabs)
-    else
-        setattr(value, "names", paste("V", ic, sep = ""))
-    setattr(value,"row.names",.set_row_names(nrows))
-    setattr(value,"class",c("data.table","data.frame"))
-    alloc.col(value)
+  if (!identical(keep.rownames, FALSE)) {
+    # can specify col name to keep.rownames, #575
+    ans = data.table(rn=rownames(x), x, keep.rownames=FALSE)
+    if (is.character(keep.rownames))
+      setnames(ans, 'rn', keep.rownames[1L])
+    return(ans)
+  }
+  d <- dim(x)
+  nrows <- d[1L]
+  ir <- seq_len(nrows)
+  ncols <- d[2L]
+  ic <- seq_len(ncols)
+  dn <- dimnames(x)
+  collabs <- dn[[2L]]
+  empty <- !nzchar(collabs)
+  if (any(empty))
+    collabs[empty] <- paste("V", ic, sep = "")[empty]
+  value <- vector("list", ncols)
+  if (mode(x) == "character") {
+    # fix for #745 - A long overdue SO post: http://stackoverflow.com/questions/17691050/data-table-still-converts-strings-to-factors
+    for (i in ic) value[[i]] <- x[, i]                  # <strike>for efficiency.</strike> For consistency - data.table likes and prefers "character"
+  }
+  else {
+    for (i in ic) value[[i]] <- as.vector(x[, i])       # to drop any row.names that would otherwise be retained inside every column of the data.table
+  }
+  if (length(collabs) == ncols)
+    setattr(value, "names", collabs)
+  else
+    setattr(value, "names", paste("V", ic, sep = ""))
+  setattr(value,"row.names",.set_row_names(nrows))
+  setattr(value,"class",c("data.table","data.frame"))
+  alloc.col(value)
 }
 
 # as.data.table.array - #1418
 as.data.table.array <- function(x, keep.rownames=FALSE, sorted=TRUE, value.name="value", na.rm=TRUE, ...) {
-    dx = dim(x)
-    if (length(dx) <= 2L)
-        stop("as.data.table.array method should be only called for arrays with 3+ dimensions, for 2 dimensions matrix method should be used")
-    if (!is.character(value.name) || length(value.name)!=1L || is.na(value.name) || !nzchar(value.name))
-        stop("Argument 'value.name' must be scalar character, non-NA and non zero char")
-    if (!is.logical(sorted) || length(sorted)!=1L || is.na(sorted))
-        stop("Argument 'sorted' must be scalar logical and non-NA")
-    if (!is.logical(na.rm) || length(na.rm)!=1L || is.na(na.rm))
-        stop("Argument 'na.rm' must be scalar logical and non-NA")
+  dx = dim(x)
+  if (length(dx) <= 2L)
+    stop("as.data.table.array method should be only called for arrays with 3+ dimensions, for 2 dimensions matrix method should be used")
+  if (!is.character(value.name) || length(value.name)!=1L || is.na(value.name) || !nzchar(value.name))
+    stop("Argument 'value.name' must be scalar character, non-NA and non zero char")
+  if (!is.logical(sorted) || length(sorted)!=1L || is.na(sorted))
+    stop("Argument 'sorted' must be scalar logical and non-NA")
+  if (!is.logical(na.rm) || length(na.rm)!=1L || is.na(na.rm))
+    stop("Argument 'na.rm' must be scalar logical and non-NA")
 
-    dnx = dimnames(x)
-    # NULL dimnames will create integer keys, not character as in table method
-    val = rev(if (is.null(dnx)) lapply(dim(x), seq.int) else dnx)
-    if (is.null(names(val)) || all(!nzchar(names(val))))
-        setattr(val, 'names', paste("V", rev(seq_along(val)), sep=""))
-    if (value.name %in% names(val))
-        stop(sprintf("Argument 'value.name' should not overlap with column names in result: %s.", paste(rev(names(val)), collapse=", ")))
-    N = NULL
-    ans = data.table(do.call(CJ, c(val, sorted=FALSE)), N=as.vector(x))
-    if (isTRUE(na.rm))
-        ans = ans[!is.na(N)]
-    setnames(ans, "N", value.name)
-    dims = rev(head(names(ans), -1))
-    setcolorder(ans, c(dims, value.name))
-    if (isTRUE(sorted))
-        setkeyv(ans, dims)
-    ans[]
+  dnx = dimnames(x)
+  # NULL dimnames will create integer keys, not character as in table method
+  val = rev(if (is.null(dnx)) lapply(dim(x), seq.int) else dnx)
+  if (is.null(names(val)) || all(!nzchar(names(val))))
+    setattr(val, 'names', paste("V", rev(seq_along(val)), sep=""))
+  if (value.name %in% names(val))
+    stop(sprintf("Argument 'value.name' should not overlap with column names in result: %s.", paste(rev(names(val)), collapse=", ")))
+  N = NULL
+  ans = data.table(do.call(CJ, c(val, sorted=FALSE)), N=as.vector(x))
+  if (isTRUE(na.rm))
+    ans = ans[!is.na(N)]
+  setnames(ans, "N", value.name)
+  dims = rev(head(names(ans), -1))
+  setcolorder(ans, c(dims, value.name))
+  if (isTRUE(sorted))
+    setkeyv(ans, dims)
+  ans[]
 }
 
 as.data.table.list <- function(x, keep.rownames=FALSE, ...) {
-    if (!length(x)) return( null.data.table() )
-    # fix for #833, as.data.table.list with matrix/data.frame/data.table as a list element..
-    # TODO: move this entire logic (along with data.table() to C
-    for (i in seq_along(x)) {
-        dims = dim(x[[i]])
-        if (!is.null(dims)) {
-            ans = do.call("data.table", x)
-            setnames(ans, make.unique(names(ans)))
-            return(ans)
-        }
+  if (!length(x)) return( null.data.table() )
+  # fix for #833, as.data.table.list with matrix/data.frame/data.table as a list element..
+  # TODO: move this entire logic (along with data.table() to C
+  for (i in seq_along(x)) {
+    dims = dim(x[[i]])
+    if (!is.null(dims)) {
+      ans = do.call("data.table", x)
+      setnames(ans, make.unique(names(ans)))
+      return(ans)
     }
-    n = vapply(x, length, 0L)
-    mn = max(n)
-    x = copy(x)
-    idx = which(n < mn)
-    if (length(idx)) {
-        for (i in idx) {
-            if (!is.null(x[[i]])) {# avoids warning when a list element is NULL
-                if (inherits(x[[i]], "POSIXlt")) {
-                    warning("POSIXlt column type detected and converted to POSIXct. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
-                    x[[i]] = as.POSIXct(x[[i]])
-                }
-                # Implementing FR #4813 - recycle with warning when nr %% nrows[i] != 0L
-                if (!n[i] && mn)
-                    warning("Item ", i, " is of size 0 but maximum size is ", mn, ", therefore recycled with 'NA'")
-                else if (n[i] && mn %% n[i] != 0)
-                    warning("Item ", i, " is of size ", n[i], " but maximum size is ", mn, " (recycled leaving a remainder of ", mn%%n[i], " items)")
-                x[[i]] = rep(x[[i]], length.out=mn)
-            }
+  }
+  n = vapply(x, length, 0L)
+  mn = max(n)
+  x = copy(x)
+  idx = which(n < mn)
+  if (length(idx)) {
+    for (i in idx) {
+      if (!is.null(x[[i]])) {# avoids warning when a list element is NULL
+        if (inherits(x[[i]], "POSIXlt")) {
+          warning("POSIXlt column type detected and converted to POSIXct. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
+          x[[i]] = as.POSIXct(x[[i]])
         }
+        # Implementing FR #4813 - recycle with warning when nr %% nrows[i] != 0L
+        if (!n[i] && mn)
+          warning("Item ", i, " is of size 0 but maximum size is ", mn, ", therefore recycled with 'NA'")
+        else if (n[i] && mn %% n[i] != 0)
+          warning("Item ", i, " is of size ", n[i], " but maximum size is ", mn, " (recycled leaving a remainder of ", mn%%n[i], " items)")
+        x[[i]] = rep(x[[i]], length.out=mn)
+      }
     }
-    # fix for #842
-    if (mn > 0L) {
-        nz = which(n > 0L)
-        xx = point(vector("list", length(nz)), seq_along(nz), x, nz)
-        if (!is.null(names(x)))
-            setattr(xx, 'names', names(x)[nz])
-        x = xx
-    }
-    if (is.null(names(x))) setattr(x,"names",paste("V",seq_len(length(x)),sep=""))
-    setattr(x,"row.names",.set_row_names(max(n)))
-    setattr(x,"class",c("data.table","data.frame"))
-    alloc.col(x)
+  }
+  # fix for #842
+  if (mn > 0L) {
+    nz = which(n > 0L)
+    xx = point(vector("list", length(nz)), seq_along(nz), x, nz)
+    if (!is.null(names(x)))
+      setattr(xx, 'names', names(x)[nz])
+    x = xx
+  }
+  if (is.null(names(x))) setattr(x,"names",paste("V",seq_len(length(x)),sep=""))
+  setattr(x,"row.names",.set_row_names(max(n)))
+  setattr(x,"class",c("data.table","data.frame"))
+  alloc.col(x)
 }
 
 # don't retain classes before "data.frame" while converting
@@ -158,36 +158,36 @@ as.data.table.list <- function(x, keep.rownames=FALSE, ...) {
 # tests and as.data.table.data.frame) I've commented #527
 # for now. This addresses #1078 and #1128
 .resetclass <- function(x, class) {
-    cx = class(x)
-    n  = chmatch(class, cx)
-    cx = unique( c("data.table", "data.frame", tail(cx, length(cx)-n)) )
+  cx = class(x)
+  n  = chmatch(class, cx)
+  cx = unique( c("data.table", "data.frame", tail(cx, length(cx)-n)) )
 }
 
 as.data.table.data.frame <- function(x, keep.rownames=FALSE, ...) {
-    if (!identical(keep.rownames, FALSE)) {
-        # can specify col name to keep.rownames, #575
-        ans = data.table(rn=rownames(x), x, keep.rownames=FALSE)
-        if (is.character(keep.rownames))
-            setnames(ans, 'rn', keep.rownames[1L])
-        return(ans)
-    }
-    ans = copy(x)  # TO DO: change this deep copy to be shallow.
-    setattr(ans,"row.names",.set_row_names(nrow(x)))
+  if (!identical(keep.rownames, FALSE)) {
+    # can specify col name to keep.rownames, #575
+    ans = data.table(rn=rownames(x), x, keep.rownames=FALSE)
+    if (is.character(keep.rownames))
+      setnames(ans, 'rn', keep.rownames[1L])
+    return(ans)
+  }
+  ans = copy(x)  # TO DO: change this deep copy to be shallow.
+  setattr(ans,"row.names",.set_row_names(nrow(x)))
 
-    ## NOTE: This test (#527) is no longer in effect ##
-    # for nlme::groupedData which has class c("nfnGroupedData","nfGroupedData","groupedData","data.frame")
-    # See test 527.
-    ##
+  ## NOTE: This test (#527) is no longer in effect ##
+  # for nlme::groupedData which has class c("nfnGroupedData","nfGroupedData","groupedData","data.frame")
+  # See test 527.
+  ##
 
-    # fix for #1078 and #1128, see .resetclass() for explanation.
-    setattr(ans, "class", .resetclass(x, "data.frame"))
-    alloc.col(ans)
+  # fix for #1078 and #1128, see .resetclass() for explanation.
+  setattr(ans, "class", .resetclass(x, "data.frame"))
+  alloc.col(ans)
 }
 
 as.data.table.data.table <- function(x, ...) {
-    # as.data.table always returns a copy, automatically takes care of #473
-    x = copy(x) # #1681
-    # fix for #1078 and #1128, see .resetclass() for explanation.
-    setattr(x, 'class', .resetclass(x, "data.table"))
-    return(x)
+  # as.data.table always returns a copy, automatically takes care of #473
+  x = copy(x) # #1681
+  # fix for #1078 and #1128, see .resetclass() for explanation.
+  setattr(x, 'class', .resetclass(x, "data.table"))
+  return(x)
 }
diff --git a/R/between.R b/R/between.R
index 5f142e32e2..01bb9ce8a8 100644
--- a/R/between.R
+++ b/R/between.R
@@ -1,14 +1,14 @@
 # is x[i] in between lower[i] and upper[i] ?
 between <- function(x,lower,upper,incbounds=TRUE) {
-    is_strictly_numeric <- function(x) is.numeric(x) && !"integer64" %in% class(x)
-    if (is_strictly_numeric(x) && is_strictly_numeric(lower) &&
-        is_strictly_numeric(upper) && length(lower) == 1L && length(upper) == 1L) {
-        # faster parallelised version for int/double for most common scenario
-        .Call(Cbetween, x, lower, upper, incbounds)
-    } else {
-        if(incbounds) x>=lower & x<=upper
-        else x>lower & x<upper
-    }
+  is_strictly_numeric <- function(x) is.numeric(x) && !"integer64" %in% class(x)
+  if (is_strictly_numeric(x) && is_strictly_numeric(lower) &&
+    is_strictly_numeric(upper) && length(lower) == 1L && length(upper) == 1L) {
+    # faster parallelised version for int/double for most common scenario
+    .Call(Cbetween, x, lower, upper, incbounds)
+  } else {
+    if(incbounds) x>=lower & x<=upper
+    else x>lower & x<upper
+  }
 }
 
 # %between% is vectorised, #534.
@@ -18,23 +18,23 @@ between <- function(x,lower,upper,incbounds=TRUE) {
 # issue FR #707
 # is x[i] found anywhere within [lower, upper] range?
 inrange <- function(x,lower,upper,incbounds=TRUE) {
-    query = setDT(list(x=x))
-    subject = setDT(list(l=lower, u=upper))
-    ops = if (incbounds) c(4L, 2L) else c(5L, 3L) # >=,<= and >,<
-    verbose = getOption("datatable.verbose")
-    if (verbose) {last.started.at=proc.time()[3];cat("forderv(query) took ... ");flush.console()}
-    xo = forderv(query)
-    if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
-    ans = bmerge(shallow(subject), query, 1:2, c(1L,1L), FALSE, xo,
-            0, c(FALSE, TRUE), 0L, "all", ops, integer(0),
-            1L, verbose) # fix for #1819, turn on verbose messages
-    options(datatable.verbose=FALSE)
-    setDT(ans[c("starts", "lens")], key=c("starts", "lens"))
-    options(datatable.verbose=verbose)
-    if (verbose) {last.started.at=proc.time()[3];cat("Generating final logical vector ... ");flush.console()}
-    .Call(Cinrange, idx <- vector("logical", length(x)), xo, ans[["starts"]], ans[["lens"]])
-    if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
-    idx
+  query = setDT(list(x=x))
+  subject = setDT(list(l=lower, u=upper))
+  ops = if (incbounds) c(4L, 2L) else c(5L, 3L) # >=,<= and >,<
+  verbose = getOption("datatable.verbose")
+  if (verbose) {last.started.at=proc.time()[3];cat("forderv(query) took ... ");flush.console()}
+  xo = forderv(query)
+  if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
+  ans = bmerge(shallow(subject), query, 1:2, c(1L,1L), FALSE, xo,
+      0, c(FALSE, TRUE), 0L, "all", ops, integer(0),
+      1L, verbose) # fix for #1819, turn on verbose messages
+  options(datatable.verbose=FALSE)
+  setDT(ans[c("starts", "lens")], key=c("starts", "lens"))
+  options(datatable.verbose=verbose)
+  if (verbose) {last.started.at=proc.time()[3];cat("Generating final logical vector ... ");flush.console()}
+  .Call(Cinrange, idx <- vector("logical", length(x)), xo, ans[["starts"]], ans[["lens"]])
+  if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
+  idx
 }
 
 "%inrange%" <- function(x,y) inrange(x,y[[1L]],y[[2L]],incbounds=TRUE)
diff --git a/R/bmerge.R b/R/bmerge.R
index 23772d9d2e..79be6c8e5c 100644
--- a/R/bmerge.R
+++ b/R/bmerge.R
@@ -1,108 +1,108 @@
 
 bmerge <- function(i, x, leftcols, rightcols, io, xo, roll, rollends, nomatch, mult, ops, nqgrp, nqmaxgrp, verbose)
 {
-    # TO DO: rename leftcols to icols, rightcols to xcols
-    # NB: io is currently just TRUE or FALSE for whether i is keyed
-    # TO DO: io and xo could be moved inside Cbmerge
-    # bmerge moved to be separate function now that list() doesn't copy in R
-    # types of i join columns are promoted to match x's types (with warning or verbose)
+  # TO DO: rename leftcols to icols, rightcols to xcols
+  # NB: io is currently just TRUE or FALSE for whether i is keyed
+  # TO DO: io and xo could be moved inside Cbmerge
+  # bmerge moved to be separate function now that list() doesn't copy in R
+  # types of i join columns are promoted to match x's types (with warning or verbose)
 
-    # Important that i is already passed in as a shallow copy, due to these coercions for factors.
-    # i.e. bmerge(i<-shallow(i),...)
-    # The caller ([.data.table) then uses the coerced columns to build the output
+  # Important that i is already passed in as a shallow copy, due to these coercions for factors.
+  # i.e. bmerge(i<-shallow(i),...)
+  # The caller ([.data.table) then uses the coerced columns to build the output
 
-    # careful to only plonk syntax (full column) on i from now on (otherwise i would change)
-    # TO DO: enforce via .internal.shallow attribute and expose shallow() to users
-    # This is why shallow() is very importantly internal only, currently.
+  # careful to only plonk syntax (full column) on i from now on (otherwise i would change)
+  # TO DO: enforce via .internal.shallow attribute and expose shallow() to users
+  # This is why shallow() is very importantly internal only, currently.
 
-    origi = shallow(i)      # Needed for factor to factor/character joins, to recover the original levels
-                            # Otherwise, types of i join columns are anyways promoted to match x's
-                            # types (with warning or verbose)
-    resetifactor = NULL     # Keep track of any factor to factor/character join cols (only time we keep orig)
-    for (a in seq_along(leftcols)) {
-        # This loop is simply to support joining factor columns
-        # Note that if i is keyed, if this coerces, i's key gets dropped and the key may not be retained
-        lc = leftcols[a]   # i   # TO DO: rename left and right to i and x
-        rc = rightcols[a]  # x
-        icnam = names(i)[lc]
-        xcnam = names(x)[rc]
-        if (is.character(x[[rc]])) {
-            if (is.character(i[[lc]])) next
-            if (!is.factor(i[[lc]]))
-                stop("x.'",xcnam,"' is a character column being joined to i.'",icnam,"' which is type '",typeof(i[[lc]]),"'. Character columns must join to factor or character columns.")
-            if (verbose) cat("Coercing factor column i.'",icnam,"' to character to match type of x.'",xcnam,"'.\n",sep="")
-            set(i,j=lc,value=as.character(i[[lc]]))
-            # no longer copies all of i, thanks to shallow() and :=/set
-            next
-        }
-        if (is.factor(x[[rc]])) {
-            if (is.character(i[[lc]])) {
-                if (verbose) cat("Coercing character column i.'",icnam,"' to factor to match type of x.'",xcnam,"'. If possible please change x.'",xcnam,"' to character. Character columns are now preferred in joins.\n",sep="")
-                set(origi, j=lc, value=factor(origi[[lc]])) # note the use of 'origi' here - see #499 and #945
-                # TO DO: we need a way to avoid copying 'value' for internal purposes
-                # that would allow setting: set(i, j=lc, value=origi[[lc]]) without resulting in a copy.
-                # until then using 'val <- origi[[lc]]' below to avoid another copy.
-            } else {
-                if (!is.factor(i[[lc]]))
-                    stop("x.'",xcnam,"' is a factor column being joined to i.'",icnam,"' which is type '",typeof(i[[lc]]),"'. Factor columns must join to factor or character columns.")
-            }
-            # Retain original levels of i's factor columns in factor to factor joins (important when NAs,
-            # see tests 687 and 688).
-            # Moved it outside of 'else' to fix #499 and #945.
-            resetifactor = c(resetifactor,lc)
-            if (roll!=0.0 && a==length(leftcols)) stop("Attempting roll join on factor column x.",names(x)[rc],". Only integer, double or character colums may be roll joined.")   # because the chmatch on next line returns <strike>NA</strike> <new>0</new> for missing chars in x (rather than some integer greater than existing). Note roll!=0.0 is ok in this 0 special floating point case e.g. as.double(FALSE)==0.0 is ok, and "nearest"!=0.0 is also true.
-            val = origi[[lc]] # note: using 'origi' here because set(..., value = .) always copies '.', we need a way to avoid it in internal cases.
-            lx = levels(x[[rc]])
-            li = levels(val)
-            newfactor = chmatch(li, lx, nomatch=0L)[val] # fix for #945, a hacky solution for now.
-            levels(newfactor) = lx
-            class(newfactor) = "factor"
-            set(i, j=lc, value=newfactor)
-            # COMMENT BELOW IS NOT TRUE ANYMORE... had to change nomatch to 0L to take care of case where 'NA' occurs as a separate value... See #945.
-            # <OUTDATED> NAs can be produced by this level match, in which case the C code (it knows integer value NA)
-            # can skip over the lookup. It's therefore important we pass NA rather than 0 to the C code.
-        }
-        # Fix for #1108.
-        # TODO: clean this code up...
-        # NOTE: bit64::is.double(int64) returns FALSE.. but base::is.double returns TRUE
-        is.int64 <- function(x) inherits(x, 'integer64')
-        is.strictlydouble <- function(x) !is.int64(x) && is.double(x)
-        if (is.integer(x[[rc]]) && (base::is.double(i[[lc]]) || is.logical(i[[lc]]))) {
-            # TO DO: add warning if reallyreal about loss of precision
-            # or could coerce in binary search on the fly, at cost
-            if (verbose) cat("Coercing ", typeof(i[[lc]])," column i.'",icnam,"' to integer to match type of x.'",xcnam,"'. Please avoid coercion for efficiency.\n",sep="")
-            newval = i[[lc]]
-            if (is.int64(newval))
-                newval = as.integer(newval)
-            else mode(newval) = "integer"  # retains column attributes (such as IDateTime class)
-            set(i, j=lc, value=newval)
-        } else if (is.int64(x[[rc]]) && (is.integer(i[[lc]]) || is.logical(i[[lc]]) || is.strictlydouble(i[[lc]]) )) {
-            if (verbose) cat("Coercing ",typeof(i[[lc]])," column i.'",icnam,"' to double to match type of x.'",xcnam,"'. Please avoid coercion for efficiency.\n",sep="")
-            newval = bit64::as.integer64(i[[lc]])
-            set(i, j=lc, value=newval)
-        } else if (is.strictlydouble(x[[rc]]) && (is.integer(i[[lc]]) || is.logical(i[[lc]]) || is.int64(i[[lc]]) )) {
-            if (verbose) cat("Coercing ",typeof(i[[lc]])," column i.'",icnam,"' to double to match type of x.'",xcnam,"'. Please avoid coercion for efficiency.\n",sep="")
-            newval = i[[lc]]
-            if (is.int64(newval))
-                newval = as.numeric(newval)
-            else mode(newval) = "double"
-            set(i, j=lc, value=newval)
-        }
+  origi = shallow(i)      # Needed for factor to factor/character joins, to recover the original levels
+                          # Otherwise, types of i join columns are anyways promoted to match x's
+                          # types (with warning or verbose)
+  resetifactor = NULL     # Keep track of any factor to factor/character join cols (only time we keep orig)
+  for (a in seq_along(leftcols)) {
+    # This loop is simply to support joining factor columns
+    # Note that if i is keyed, if this coerces, i's key gets dropped and the key may not be retained
+    lc = leftcols[a]   # i   # TO DO: rename left and right to i and x
+    rc = rightcols[a]  # x
+    icnam = names(i)[lc]
+    xcnam = names(x)[rc]
+    if (is.character(x[[rc]])) {
+      if (is.character(i[[lc]])) next
+      if (!is.factor(i[[lc]]))
+        stop("x.'",xcnam,"' is a character column being joined to i.'",icnam,"' which is type '",typeof(i[[lc]]),"'. Character columns must join to factor or character columns.")
+      if (verbose) cat("Coercing factor column i.'",icnam,"' to character to match type of x.'",xcnam,"'.\n",sep="")
+      set(i,j=lc,value=as.character(i[[lc]]))
+      # no longer copies all of i, thanks to shallow() and :=/set
+      next
     }
-    if (verbose) {last.started.at=proc.time()[3];cat("Starting bmerge ...");flush.console()}
-    ans = .Call(Cbmerge, i, x, as.integer(leftcols), as.integer(rightcols), io<-haskey(i), xo, roll, rollends, nomatch, mult, ops, nqgrp, nqmaxgrp)
-    # NB: io<-haskey(i) necessary for test 579 where the := above change the factor to character and remove i's key
-    if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console()}
-
-    # in the caller's shallow copy,  see comment at the top of this function for usage
-    # We want to leave the coercions to i in place otherwise, since the caller depends on that to build the result
-    if (length(resetifactor)) {
-        for (ii in resetifactor)
-            set(i,j=ii,value=origi[[ii]])
-        if (haskey(origi))
-            setattr(i, 'sorted', key(origi))
+    if (is.factor(x[[rc]])) {
+      if (is.character(i[[lc]])) {
+        if (verbose) cat("Coercing character column i.'",icnam,"' to factor to match type of x.'",xcnam,"'. If possible please change x.'",xcnam,"' to character. Character columns are now preferred in joins.\n",sep="")
+        set(origi, j=lc, value=factor(origi[[lc]])) # note the use of 'origi' here - see #499 and #945
+        # TO DO: we need a way to avoid copying 'value' for internal purposes
+        # that would allow setting: set(i, j=lc, value=origi[[lc]]) without resulting in a copy.
+        # until then using 'val <- origi[[lc]]' below to avoid another copy.
+      } else {
+        if (!is.factor(i[[lc]]))
+          stop("x.'",xcnam,"' is a factor column being joined to i.'",icnam,"' which is type '",typeof(i[[lc]]),"'. Factor columns must join to factor or character columns.")
+      }
+      # Retain original levels of i's factor columns in factor to factor joins (important when NAs,
+      # see tests 687 and 688).
+      # Moved it outside of 'else' to fix #499 and #945.
+      resetifactor = c(resetifactor,lc)
+      if (roll!=0.0 && a==length(leftcols)) stop("Attempting roll join on factor column x.",names(x)[rc],". Only integer, double or character colums may be roll joined.")   # because the chmatch on next line returns <strike>NA</strike> <new>0</new> for missing chars in x (rather than some integer greater than existing). Note roll!=0.0 is ok in this 0 special floating point case e.g. as.double(FALSE)==0.0 is ok, and "nearest"!=0.0 is also true.
+      val = origi[[lc]] # note: using 'origi' here because set(..., value = .) always copies '.', we need a way to avoid it in internal cases.
+      lx = levels(x[[rc]])
+      li = levels(val)
+      newfactor = chmatch(li, lx, nomatch=0L)[val] # fix for #945, a hacky solution for now.
+      levels(newfactor) = lx
+      class(newfactor) = "factor"
+      set(i, j=lc, value=newfactor)
+      # COMMENT BELOW IS NOT TRUE ANYMORE... had to change nomatch to 0L to take care of case where 'NA' occurs as a separate value... See #945.
+      # <OUTDATED> NAs can be produced by this level match, in which case the C code (it knows integer value NA)
+      # can skip over the lookup. It's therefore important we pass NA rather than 0 to the C code.
+    }
+    # Fix for #1108.
+    # TODO: clean this code up...
+    # NOTE: bit64::is.double(int64) returns FALSE.. but base::is.double returns TRUE
+    is.int64 <- function(x) inherits(x, 'integer64')
+    is.strictlydouble <- function(x) !is.int64(x) && is.double(x)
+    if (is.integer(x[[rc]]) && (base::is.double(i[[lc]]) || is.logical(i[[lc]]))) {
+      # TO DO: add warning if reallyreal about loss of precision
+      # or could coerce in binary search on the fly, at cost
+      if (verbose) cat("Coercing ", typeof(i[[lc]])," column i.'",icnam,"' to integer to match type of x.'",xcnam,"'. Please avoid coercion for efficiency.\n",sep="")
+      newval = i[[lc]]
+      if (is.int64(newval))
+        newval = as.integer(newval)
+      else mode(newval) = "integer"  # retains column attributes (such as IDateTime class)
+      set(i, j=lc, value=newval)
+    } else if (is.int64(x[[rc]]) && (is.integer(i[[lc]]) || is.logical(i[[lc]]) || is.strictlydouble(i[[lc]]) )) {
+      if (verbose) cat("Coercing ",typeof(i[[lc]])," column i.'",icnam,"' to double to match type of x.'",xcnam,"'. Please avoid coercion for efficiency.\n",sep="")
+      newval = bit64::as.integer64(i[[lc]])
+      set(i, j=lc, value=newval)
+    } else if (is.strictlydouble(x[[rc]]) && (is.integer(i[[lc]]) || is.logical(i[[lc]]) || is.int64(i[[lc]]) )) {
+      if (verbose) cat("Coercing ",typeof(i[[lc]])," column i.'",icnam,"' to double to match type of x.'",xcnam,"'. Please avoid coercion for efficiency.\n",sep="")
+      newval = i[[lc]]
+      if (is.int64(newval))
+        newval = as.numeric(newval)
+      else mode(newval) = "double"
+      set(i, j=lc, value=newval)
     }
-    return(ans)
+  }
+  if (verbose) {last.started.at=proc.time()[3];cat("Starting bmerge ...");flush.console()}
+  ans = .Call(Cbmerge, i, x, as.integer(leftcols), as.integer(rightcols), io<-haskey(i), xo, roll, rollends, nomatch, mult, ops, nqgrp, nqmaxgrp)
+  # NB: io<-haskey(i) necessary for test 579 where the := above change the factor to character and remove i's key
+  if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console()}
+
+  # in the caller's shallow copy,  see comment at the top of this function for usage
+  # We want to leave the coercions to i in place otherwise, since the caller depends on that to build the result
+  if (length(resetifactor)) {
+    for (ii in resetifactor)
+      set(i,j=ii,value=origi[[ii]])
+    if (haskey(origi))
+      setattr(i, 'sorted', key(origi))
+  }
+  return(ans)
 }
 
 
diff --git a/R/c.factor.R b/R/c.factor.R
index eadd4ef47c..65a25736d3 100644
--- a/R/c.factor.R
+++ b/R/c.factor.R
@@ -6,18 +6,18 @@
 
 c.factor <- function(...)
 {
-    args <- list(...)
-    for (i in seq_along(args)) if (!is.factor(args[[i]])) args[[i]] = as.factor(args[[i]])
-    # The first must be factor otherwise we wouldn't be inside c.factor, its checked anyway in the line above.
-    newlevels = unique(unlist(lapply(args,levels),recursive=TRUE,use.names=TRUE))
-    if (length(o <- forderv(newlevels)))
-        newlevels <- newlevels[o]
-    nm <- names(unlist(args,recursive=TRUE,use.names=TRUE))
-    ans = unlist(lapply(args, function(x) {   # Its faster this way when there are many references to the same level, which is normally the case
-        m = match(levels(x), newlevels)
-        m[as.integer(x)]
-    }))
-    structure(ans, levels = newlevels, names = nm, class = "factor")
+  args <- list(...)
+  for (i in seq_along(args)) if (!is.factor(args[[i]])) args[[i]] = as.factor(args[[i]])
+  # The first must be factor otherwise we wouldn't be inside c.factor, its checked anyway in the line above.
+  newlevels = unique(unlist(lapply(args,levels),recursive=TRUE,use.names=TRUE))
+  if (length(o <- forderv(newlevels)))
+    newlevels <- newlevels[o]
+  nm <- names(unlist(args,recursive=TRUE,use.names=TRUE))
+  ans = unlist(lapply(args, function(x) {   # Its faster this way when there are many references to the same level, which is normally the case
+    m = match(levels(x), newlevels)
+    m[as.integer(x)]
+  }))
+  structure(ans, levels = newlevels, names = nm, class = "factor")
 }
 
 #"[.factor" <- function(x, ...)
diff --git a/R/cedta.R b/R/cedta.R
index 017ae36b07..6b0bd5ace9 100644
--- a/R/cedta.R
+++ b/R/cedta.R
@@ -20,27 +20,27 @@ cedta.pkgEvalsUserCode = c("gWidgetsWWW","statET","FastRWeb","slidify","rmarkdow
 # http://stackoverflow.com/a/13131555/403310
 
 cedta <- function(n=2L) {
-    # Calling Environment Data Table Aware
-    te = topenv(parent.frame(n))
-    if (!isNamespace(te)) {
-        # e.g. DT queries at the prompt (.GlobalEnv) and knitr's eval(,envir=globalenv()) but not DF[...] inside knitr::kable v1.6
-        return(TRUE)
-    }
-    nsname = getNamespaceName(te)
-    ans = nsname == "data.table" ||
-        "data.table" %chin% names(getNamespaceImports(te)) ||
-        "data.table" %chin% tryCatch(get(".Depends",paste("package",nsname,sep=":"),inherits=FALSE),error=function(e)NULL) ||
-        (nsname == "utils" && exists("debugger.look",parent.frame(n+1L))) ||
-        (nsname == "base"  && all(c("FUN", "X") %in% ls(parent.frame(n)))  ) || # lapply
-        (nsname %chin% cedta.pkgEvalsUserCode && any(sapply(sys.calls(), function(x) is.name(x[[1L]]) && (x[[1L]]=="eval" || x[[1L]]=="evalq")))) ||
-        nsname %chin% cedta.override ||
-        identical(TRUE, tryCatch(get(".datatable.aware",asNamespace(nsname),inherits=FALSE),error=function(e)NULL))
-    if (!ans && getOption("datatable.verbose")) {
-        cat("cedta decided '",nsname,"' wasn't data.table aware. Call stack with [[1L]] applied:\n",sep="")
-        print(sapply(sys.calls(), "[[", 1L))
-        # so we can trace the namespace name that may need to be added (very unusually)
-    }
-    ans
+  # Calling Environment Data Table Aware
+  te = topenv(parent.frame(n))
+  if (!isNamespace(te)) {
+    # e.g. DT queries at the prompt (.GlobalEnv) and knitr's eval(,envir=globalenv()) but not DF[...] inside knitr::kable v1.6
+    return(TRUE)
+  }
+  nsname = getNamespaceName(te)
+  ans = nsname == "data.table" ||
+    "data.table" %chin% names(getNamespaceImports(te)) ||
+    "data.table" %chin% tryCatch(get(".Depends",paste("package",nsname,sep=":"),inherits=FALSE),error=function(e)NULL) ||
+    (nsname == "utils" && exists("debugger.look",parent.frame(n+1L))) ||
+    (nsname == "base"  && all(c("FUN", "X") %in% ls(parent.frame(n)))  ) || # lapply
+    (nsname %chin% cedta.pkgEvalsUserCode && any(sapply(sys.calls(), function(x) is.name(x[[1L]]) && (x[[1L]]=="eval" || x[[1L]]=="evalq")))) ||
+    nsname %chin% cedta.override ||
+    identical(TRUE, tryCatch(get(".datatable.aware",asNamespace(nsname),inherits=FALSE),error=function(e)NULL))
+  if (!ans && getOption("datatable.verbose")) {
+    cat("cedta decided '",nsname,"' wasn't data.table aware. Call stack with [[1L]] applied:\n",sep="")
+    print(sapply(sys.calls(), "[[", 1L))
+    # so we can trace the namespace name that may need to be added (very unusually)
+  }
+  ans
 }
 
 
diff --git a/R/data.table.R b/R/data.table.R
index 49c67402b7..78bb41b62c 100644
--- a/R/data.table.R
+++ b/R/data.table.R
@@ -1,7 +1,7 @@
 
 dim.data.table <- function(x)
 {
-    .Call(Cdim, x)
+  .Call(Cdim, x)
 }
 
 .global <- new.env()  # thanks to: http://stackoverflow.com/a/12605694/403310
@@ -35,174 +35,174 @@ is.ff <- function(x) inherits(x, "ff")  # define this in data.table so that we d
 #}
 
 null.data.table <-function() {
-    ans = list()
-    setattr(ans,"class",c("data.table","data.frame"))
-    setattr(ans,"row.names",.set_row_names(0L))
-    alloc.col(ans)
+  ans = list()
+  setattr(ans,"class",c("data.table","data.frame"))
+  setattr(ans,"row.names",.set_row_names(0L))
+  alloc.col(ans)
 }
 
 data.table <-function(..., keep.rownames=FALSE, check.names=FALSE, key=NULL, stringsAsFactors=FALSE)
 {
-    # NOTE: It may be faster in some circumstances to create a data.table by creating a list l first, and then setattr(l,"class",c("data.table","data.frame")) at the expense of checking.
-    # TO DO: rewrite data.table(), one of the oldest functions here. Many people use data.table() to convert data.frame rather than
-    # as.data.table which is faster; speed could be better.  Revisit how many copies are taken in for example data.table(DT1,DT2) which
-    # cbind directs to.  And the nested loops for recycling lend themselves to being C level.
-
-    x <- list(...)   # doesn't copy named inputs as from R >= 3.1.0 (a very welcome change)
-    if (!.R.listCopiesNamed) .Call(CcopyNamedInList,x)   # to maintain the old behaviour going forwards, for now. See test 548.2.
-    # **TO DO** Something strange with NAMED on components of `...`. To investigate. Or just port data.table() to C. This is why
-    # it's switched, because extra copies would be introduced in R <= 3.1.0, iiuc.
-
-    # fix for #5377 - data.table(null list, data.frame and data.table) should return null data.table. Simple fix: check all scenarios here at the top.
-    if (identical(x, list(NULL)) || identical(x, list(list())) ||
-           identical(x, list(data.frame(NULL))) || identical(x, list(data.table(NULL)))) return( null.data.table() )
-    tt <- as.list(substitute(list(...)))[-1L]  # Intention here is that data.table(X,Y) will automatically put X and Y as the column names.  For longer expressions, name the arguments to data.table(). But in a call to [.data.table, wrap in list() e.g. DT[,list(a=mean(v),b=foobarzoo(zang))] will get the col names
-    vnames = names(tt)
-    if (is.null(vnames)) vnames = rep.int("",length(x))
-    vnames[is.na(vnames)] = ""
-    novname = vnames==""
-    if (any(!novname)) {
-        if (any(vnames[!novname] == ".SD")) stop("A column may not be called .SD. That has special meaning.")
-    }
-    for (i in which(novname)) {
-        # if (ncol(as.data.table(x[[i]])) <= 1) { # cbind call in test 230 fails if I write ncol(as.data.table(eval(tt[[i]], parent.frame()))) <= 1, no idea why... (keep this for later even though all tests pass with ncol(.).. because base uses as.data.frame(.))
-        if (is.null(ncol(x[[i]]))) {
-            if ((tmp <- deparse(tt[[i]])[1]) == make.names(tmp))
-                vnames[i] <- tmp
-        }
+  # NOTE: It may be faster in some circumstances to create a data.table by creating a list l first, and then setattr(l,"class",c("data.table","data.frame")) at the expense of checking.
+  # TO DO: rewrite data.table(), one of the oldest functions here. Many people use data.table() to convert data.frame rather than
+  # as.data.table which is faster; speed could be better.  Revisit how many copies are taken in for example data.table(DT1,DT2) which
+  # cbind directs to.  And the nested loops for recycling lend themselves to being C level.
+
+  x <- list(...)   # doesn't copy named inputs as from R >= 3.1.0 (a very welcome change)
+  if (!.R.listCopiesNamed) .Call(CcopyNamedInList,x)   # to maintain the old behaviour going forwards, for now. See test 548.2.
+  # **TO DO** Something strange with NAMED on components of `...`. To investigate. Or just port data.table() to C. This is why
+  # it's switched, because extra copies would be introduced in R <= 3.1.0, iiuc.
+
+  # fix for #5377 - data.table(null list, data.frame and data.table) should return null data.table. Simple fix: check all scenarios here at the top.
+  if (identical(x, list(NULL)) || identical(x, list(list())) ||
+      identical(x, list(data.frame(NULL))) || identical(x, list(data.table(NULL)))) return( null.data.table() )
+  tt <- as.list(substitute(list(...)))[-1L]  # Intention here is that data.table(X,Y) will automatically put X and Y as the column names.  For longer expressions, name the arguments to data.table(). But in a call to [.data.table, wrap in list() e.g. DT[,list(a=mean(v),b=foobarzoo(zang))] will get the col names
+  vnames = names(tt)
+  if (is.null(vnames)) vnames = rep.int("",length(x))
+  vnames[is.na(vnames)] = ""
+  novname = vnames==""
+  if (any(!novname)) {
+    if (any(vnames[!novname] == ".SD")) stop("A column may not be called .SD. That has special meaning.")
+  }
+  for (i in which(novname)) {
+    # if (ncol(as.data.table(x[[i]])) <= 1) { # cbind call in test 230 fails if I write ncol(as.data.table(eval(tt[[i]], parent.frame()))) <= 1, no idea why... (keep this for later even though all tests pass with ncol(.).. because base uses as.data.frame(.))
+    if (is.null(ncol(x[[i]]))) {
+      if ((tmp <- deparse(tt[[i]])[1]) == make.names(tmp))
+        vnames[i] <- tmp
     }
-    tt = vnames==""
-    if (any(tt)) vnames[tt] = paste("V", which(tt), sep = "")
-    # so now finally we have good column names. We also will use novname later to know which were explicitly supplied in the call.
-    n <- length(x)
-    if (n < 1L)
-        return( null.data.table() )
-    if (length(vnames) != n) stop("logical error in vnames")
-    vnames <- as.list.default(vnames)
-    nrows = integer(n)          # vector of lengths of each column. may not be equal if silent repetition is required.
-    numcols = integer(n)         # the ncols of each of the inputs (e.g. if inputs contain matrix or data.table)
-    for (i in seq_len(n)) {
-        xi = x[[i]]
-        if (is.null(xi)) stop("column or argument ",i," is NULL")
-        if ("POSIXlt" %chin% class(xi)) {
-            warning("POSIXlt column type detected and converted to POSIXct. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
-            x[[i]] = as.POSIXct(xi)
-        } else if (is.matrix(xi) || is.data.frame(xi)) {  # including data.table (a data.frame, too)
-            xi = as.data.table(xi, keep.rownames=keep.rownames)       # TO DO: allow a matrix to be a column of a data.table. This could allow a key'd lookup to a matrix, not just by a single rowname vector, but by a combination of several columns. A matrix column could be stored either by row or by column contiguous in memory.
-            x[[i]] = xi
-            numcols[i] = length(xi)
-        } else if (is.table(xi)) {
-            x[[i]] = xi = as.data.table.table(xi, keep.rownames=keep.rownames)
-            numcols[i] = length(xi)
-        } else if (is.function(xi)) {
-            x[[i]] = xi = list(xi)
-        }
-        nrows[i] <- NROW(xi)    # for a vector (including list() columns) returns the length
-        if (numcols[i]>0L) {
-            namesi <- names(xi)  # works for both data.frame's, matrices and data.tables's
-            if (length(namesi)==0L) namesi = rep.int("",ncol(xi))
-            namesi[is.na(namesi)] = ""
-            tt = namesi==""
-            if (any(tt)) namesi[tt] = paste("V", which(tt), sep = "")
-            if (novname[i]) vnames[[i]] = namesi
-            else vnames[[i]] = paste(vnames[[i]], namesi, sep=".")
-        }
+  }
+  tt = vnames==""
+  if (any(tt)) vnames[tt] = paste("V", which(tt), sep = "")
+  # so now finally we have good column names. We also will use novname later to know which were explicitly supplied in the call.
+  n <- length(x)
+  if (n < 1L)
+    return( null.data.table() )
+  if (length(vnames) != n) stop("logical error in vnames")
+  vnames <- as.list.default(vnames)
+  nrows = integer(n)          # vector of lengths of each column. may not be equal if silent repetition is required.
+  numcols = integer(n)         # the ncols of each of the inputs (e.g. if inputs contain matrix or data.table)
+  for (i in seq_len(n)) {
+    xi = x[[i]]
+    if (is.null(xi)) stop("column or argument ",i," is NULL")
+    if ("POSIXlt" %chin% class(xi)) {
+      warning("POSIXlt column type detected and converted to POSIXct. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
+      x[[i]] = as.POSIXct(xi)
+    } else if (is.matrix(xi) || is.data.frame(xi)) {  # including data.table (a data.frame, too)
+      xi = as.data.table(xi, keep.rownames=keep.rownames)       # TO DO: allow a matrix to be a column of a data.table. This could allow a key'd lookup to a matrix, not just by a single rowname vector, but by a combination of several columns. A matrix column could be stored either by row or by column contiguous in memory.
+      x[[i]] = xi
+      numcols[i] = length(xi)
+    } else if (is.table(xi)) {
+      x[[i]] = xi = as.data.table.table(xi, keep.rownames=keep.rownames)
+      numcols[i] = length(xi)
+    } else if (is.function(xi)) {
+      x[[i]] = xi = list(xi)
+    }
+    nrows[i] <- NROW(xi)    # for a vector (including list() columns) returns the length
+    if (numcols[i]>0L) {
+      namesi <- names(xi)  # works for both data.frame's, matrices and data.tables's
+      if (length(namesi)==0L) namesi = rep.int("",ncol(xi))
+      namesi[is.na(namesi)] = ""
+      tt = namesi==""
+      if (any(tt)) namesi[tt] = paste("V", which(tt), sep = "")
+      if (novname[i]) vnames[[i]] = namesi
+      else vnames[[i]] = paste(vnames[[i]], namesi, sep=".")
     }
-    nr <- max(nrows)
-    ckey = NULL
-    recycledkey = FALSE
-    for (i in seq_len(n)) {
-        xi = x[[i]]
-        if (is.data.table(xi) && haskey(xi)) {
-            if (nrows[i]<nr) recycledkey = TRUE
-            else ckey = c(ckey, key(xi))
-        }
+  }
+  nr <- max(nrows)
+  ckey = NULL
+  recycledkey = FALSE
+  for (i in seq_len(n)) {
+    xi = x[[i]]
+    if (is.data.table(xi) && haskey(xi)) {
+      if (nrows[i]<nr) recycledkey = TRUE
+      else ckey = c(ckey, key(xi))
     }
-    for (i in which(nrows < nr)) {
-        # TO DO ... recycle in C, but not high priority as large data already regular from database or file
-        xi <- x[[i]]
-        if (identical(xi,list())) {
-            x[[i]] = vector("list", nr)
-            next
-        }
-        if (nrows[i]==0L) stop("Item ",i," has no length. Provide at least one item (such as NA, NA_integer_ etc) to be repeated to match the ",nr," rows in the longest column. Or, all columns can be 0 length, for insert()ing rows into.")
-        # Implementing FR #4813 - recycle with warning when nr %% nrows[i] != 0L
-        if (nr%%nrows[i] != 0L) warning("Item ", i, " is of size ", nrows[i], " but maximum size is ", nr, " (recycled leaving remainder of ", nr%%nrows[i], " items)")
-        # if (nr%%nrows[i] == 0L) {
-            if (is.data.frame(xi)) {   # including data.table
-                ..i = rep(seq_len(nrow(xi)), length.out = nr)
-                x[[i]] = xi[..i,,drop=FALSE]
-                next
-            }
-            if (is.atomic(xi) || is.list(xi)) {
-                # TO DO: surely use set() here, or avoid the coercion
-                x[[i]] = rep(xi, length.out = nr)
-                next
-            }
-            stop("problem recycling column ",i,", try a simpler type")
-        # }
-        stop("argument ",i," (nrow ",nrows[i],") cannot be recycled without remainder to match longest nrow (",nr,")")
-    }
-    if (any(numcols>0L)) {
-        value = vector("list",sum(pmax(numcols,1L)))
-        k = 1L
-        for(i in seq_len(n)) {
-            if (is.list(x[[i]]) && !is.ff(x[[i]])) {
-                for(j in seq_len(length(x[[i]]))) {
-                    value[[k]] = x[[i]][[j]]
-                    k=k+1L
-                }
-            } else {
-                value[[k]] = x[[i]]
-                k=k+1L
-            }
+  }
+  for (i in which(nrows < nr)) {
+    # TO DO ... recycle in C, but not high priority as large data already regular from database or file
+    xi <- x[[i]]
+    if (identical(xi,list())) {
+      x[[i]] = vector("list", nr)
+      next
+    }
+    if (nrows[i]==0L) stop("Item ",i," has no length. Provide at least one item (such as NA, NA_integer_ etc) to be repeated to match the ",nr," rows in the longest column. Or, all columns can be 0 length, for insert()ing rows into.")
+    # Implementing FR #4813 - recycle with warning when nr %% nrows[i] != 0L
+    if (nr%%nrows[i] != 0L) warning("Item ", i, " is of size ", nrows[i], " but maximum size is ", nr, " (recycled leaving remainder of ", nr%%nrows[i], " items)")
+    # if (nr%%nrows[i] == 0L) {
+      if (is.data.frame(xi)) {   # including data.table
+        ..i = rep(seq_len(nrow(xi)), length.out = nr)
+        x[[i]] = xi[..i,,drop=FALSE]
+        next
+      }
+      if (is.atomic(xi) || is.list(xi)) {
+        # TO DO: surely use set() here, or avoid the coercion
+        x[[i]] = rep(xi, length.out = nr)
+        next
+      }
+      stop("problem recycling column ",i,", try a simpler type")
+    # }
+    stop("argument ",i," (nrow ",nrows[i],") cannot be recycled without remainder to match longest nrow (",nr,")")
+  }
+  if (any(numcols>0L)) {
+    value = vector("list",sum(pmax(numcols,1L)))
+    k = 1L
+    for(i in seq_len(n)) {
+      if (is.list(x[[i]]) && !is.ff(x[[i]])) {
+        for(j in seq_len(length(x[[i]]))) {
+          value[[k]] = x[[i]][[j]]
+          k=k+1L
         }
-    } else {
-        value = x
-    }
-    vnames <- unlist(vnames)
-    if (check.names)   # default FALSE
-        vnames <- make.names(vnames, unique = TRUE)
-    setattr(value,"names",vnames)
-    setattr(value,"row.names",.set_row_names(nr))
-    setattr(value,"class",c("data.table","data.frame"))
-    if (!is.null(key)) {
-      if (!is.character(key)) stop("key argument of data.table() must be character")
-      if (length(key)==1L) {
-          key = strsplit(key,split=",")[[1L]]
-          # eg key="A,B"; a syntax only useful in key argument to data.table(), really.
+      } else {
+        value[[k]] = x[[i]]
+        k=k+1L
       }
-      setkeyv(value,key)
-    } else {
-       # retain key of cbind(DT1, DT2, DT3) where DT2 is keyed but not DT1. cbind calls data.table().
-       # If DT inputs with keys have been recycled then can't retain key
-       if (length(ckey)
-           && !recycledkey
-           && !any(duplicated(ckey))
-           && all(ckey %in% names(value))
-           && !any(duplicated(names(value)[names(value) %in% ckey])))
-           setattr(value, "sorted", ckey)
-    }
-    # FR #643, setfactor is an internal function in fread.R
-    if (isTRUE(stringsAsFactors)) setfactor(value, which(vapply(value, is.character, TRUE)), FALSE)
-    alloc.col(value)  # returns a NAMED==0 object, unlike data.frame()
+    }
+  } else {
+    value = x
+  }
+  vnames <- unlist(vnames)
+  if (check.names)   # default FALSE
+    vnames <- make.names(vnames, unique = TRUE)
+  setattr(value,"names",vnames)
+  setattr(value,"row.names",.set_row_names(nr))
+  setattr(value,"class",c("data.table","data.frame"))
+  if (!is.null(key)) {
+    if (!is.character(key)) stop("key argument of data.table() must be character")
+    if (length(key)==1L) {
+      key = strsplit(key,split=",")[[1L]]
+      # eg key="A,B"; a syntax only useful in key argument to data.table(), really.
+    }
+    setkeyv(value,key)
+  } else {
+     # retain key of cbind(DT1, DT2, DT3) where DT2 is keyed but not DT1. cbind calls data.table().
+     # If DT inputs with keys have been recycled then can't retain key
+     if (length(ckey)
+       && !recycledkey
+       && !any(duplicated(ckey))
+       && all(ckey %in% names(value))
+       && !any(duplicated(names(value)[names(value) %in% ckey])))
+       setattr(value, "sorted", ckey)
+  }
+  # FR #643, setfactor is an internal function in fread.R
+  if (isTRUE(stringsAsFactors)) setfactor(value, which(vapply(value, is.character, TRUE)), FALSE)
+  alloc.col(value)  # returns a NAMED==0 object, unlike data.frame()
 }
 
 replace_dot_alias <- function(e) {
-    # we don't just simply alias .=list because i) list is a primitive (faster to iterate) and ii) we test for use
-    # of "list" in several places so it saves having to remember to write "." || "list" in those places
-    if (is.call(e)) {
-        if (e[[1L]] == ".") e[[1L]] = quote(list)
-        for (i in seq_along(e)[-1]) if (!is.null(e[[i]])) e[[i]] = replace_dot_alias(e[[i]])
-    }
-    e
+  # we don't just simply alias .=list because i) list is a primitive (faster to iterate) and ii) we test for use
+  # of "list" in several places so it saves having to remember to write "." || "list" in those places
+  if (is.call(e)) {
+    if (e[[1L]] == ".") e[[1L]] = quote(list)
+    for (i in seq_along(e)[-1]) if (!is.null(e[[i]])) e[[i]] = replace_dot_alias(e[[i]])
+  }
+  e
 }
 
 .massagei <- function(x) {
-    # J alias for list as well in i, just if the first symbol
-    if (is.call(x) && as.character(x[[1L]]) %chin% c("J","."))
-        x[[1L]] = quote(list)
-    x
+  # J alias for list as well in i, just if the first symbol
+  if (is.call(x) && as.character(x[[1L]]) %chin% c("J","."))
+    x[[1L]] = quote(list)
+  x
 }
 
 # A (relatively) fast (uses DT grouping) wrapper for matching two vectors, BUT:
@@ -213,1650 +213,1650 @@ replace_dot_alias <- function(e) {
 # chmatch2(c("a", "a"), c("a", "b")) # 1,NA - the second one doesn't 'see' the first 'a'
 # chmatch2(c("a", "a"), c("a", "a.1")) # 1,NA - this is where it differs from pmatch - we don't need the partial match.
 chmatch2 <- function(x, table, nomatch=NA_integer_) {
-    .Call(Cchmatch2, x, table, as.integer(nomatch)) # this is in 'rbindlist.c' for now.
+  .Call(Cchmatch2, x, table, as.integer(nomatch)) # this is in 'rbindlist.c' for now.
 }
 
 "[.data.table" <- function (x, i, j, by, keyby, with=TRUE, nomatch=getOption("datatable.nomatch"), mult="all", roll=FALSE, rollends=if (roll=="nearest") c(TRUE,TRUE) else if (roll>=0) c(FALSE,TRUE) else c(TRUE,FALSE), which=FALSE, .SDcols, verbose=getOption("datatable.verbose"), allow.cartesian=getOption("datatable.allow.cartesian"), drop=NULL, on=NULL)
 {
-    # ..selfcount <<- ..selfcount+1  # in dev, we check no self calls, each of which doubles overhead, or could
-    # test explicitly if the caller is [.data.table (even stronger test. TO DO.)
-    # the drop=NULL is to sink drop argument when dispatching to [.data.frame; using '...' stops test 147
-    if (!cedta()) {
-        # Fix for #5070 (to do)
-        Nargs = nargs() - (!missing(drop))
-        ans = if (Nargs<3L) `[.data.frame`(x,i)  # drop ignored anyway by DF[i]
-              else if (missing(drop)) `[.data.frame`(x,i,j)
-              else `[.data.frame`(x,i,j,drop)
-        # added is.data.table(ans) check to fix bug #5069
-        if (!missing(i) & is.data.table(ans)) setkey(ans,NULL)  # See test 304
-        return(ans)
-    }
-    if (!mult %chin% c("first","last","all")) stop("mult argument can only be 'first','last' or 'all'")
-    missingroll = missing(roll)
-    missingwith = missing(with)
-    if (length(roll)!=1L || is.na(roll)) stop("roll must be a single TRUE, FALSE, positive/negative integer/double including +Inf and -Inf or 'nearest'")
-    if (is.character(roll)) {
-        if (roll!="nearest") stop("roll is '",roll,"' (type character). Only valid character value is 'nearest'.")
-    } else {
-        roll = if (isTRUE(roll)) +Inf else as.double(roll)
-    }
-    force(rollends)
-    if (!is.logical(rollends)) stop("rollends must be a logical vector")
-    if (length(rollends)>2) stop("rollends must be length 1 or 2")
-    if (length(rollends)==1) rollends=rep.int(rollends,2L)
-    # TO DO (document/faq/example). Removed for now ... if ((roll || rolltolast) && missing(mult)) mult="last" # for when there is exact match to mult. This does not control cases where the roll is mult, that is always the last one.
-    missingnomatch = missing(nomatch)
-    if (!is.na(nomatch) && nomatch!=0L) stop("nomatch must either be NA or 0, or (ideally) NA_integer_ or 0L")
-    nomatch = as.integer(nomatch)
-    if (!is.logical(which) || length(which)>1) stop("'which' must be a logical vector length 1. Either FALSE, TRUE or NA.")
-    if ((isTRUE(which)||is.na(which)) && !missing(j)) stop("'which' is ",which," (meaning return row numbers) but 'j' is also supplied. Either you need row numbers or the result of j, but only one type of result can be returned.")
-    if (!is.na(nomatch) && is.na(which)) stop("which=NA with nomatch=0 would always return an empty vector. Please change or remove either which or nomatch.")
-    .global$print=""
-    if (missing(i) && missing(j)) {
-        # ...[] == oops at console, forgot print(...)
-        # or some kind of dynamic construction that has edge case of no contents inside [...]
-        return(x)
+  # ..selfcount <<- ..selfcount+1  # in dev, we check no self calls, each of which doubles overhead, or could
+  # test explicitly if the caller is [.data.table (even stronger test. TO DO.)
+  # the drop=NULL is to sink drop argument when dispatching to [.data.frame; using '...' stops test 147
+  if (!cedta()) {
+    # Fix for #5070 (to do)
+    Nargs = nargs() - (!missing(drop))
+    ans = if (Nargs<3L) `[.data.frame`(x,i)  # drop ignored anyway by DF[i]
+        else if (missing(drop)) `[.data.frame`(x,i,j)
+        else `[.data.frame`(x,i,j,drop)
+    # added is.data.table(ans) check to fix bug #5069
+    if (!missing(i) & is.data.table(ans)) setkey(ans,NULL)  # See test 304
+    return(ans)
+  }
+  if (!mult %chin% c("first","last","all")) stop("mult argument can only be 'first','last' or 'all'")
+  missingroll = missing(roll)
+  missingwith = missing(with)
+  if (length(roll)!=1L || is.na(roll)) stop("roll must be a single TRUE, FALSE, positive/negative integer/double including +Inf and -Inf or 'nearest'")
+  if (is.character(roll)) {
+    if (roll!="nearest") stop("roll is '",roll,"' (type character). Only valid character value is 'nearest'.")
+  } else {
+    roll = if (isTRUE(roll)) +Inf else as.double(roll)
+  }
+  force(rollends)
+  if (!is.logical(rollends)) stop("rollends must be a logical vector")
+  if (length(rollends)>2) stop("rollends must be length 1 or 2")
+  if (length(rollends)==1) rollends=rep.int(rollends,2L)
+  # TO DO (document/faq/example). Removed for now ... if ((roll || rolltolast) && missing(mult)) mult="last" # for when there is exact match to mult. This does not control cases where the roll is mult, that is always the last one.
+  missingnomatch = missing(nomatch)
+  if (!is.na(nomatch) && nomatch!=0L) stop("nomatch must either be NA or 0, or (ideally) NA_integer_ or 0L")
+  nomatch = as.integer(nomatch)
+  if (!is.logical(which) || length(which)>1) stop("'which' must be a logical vector length 1. Either FALSE, TRUE or NA.")
+  if ((isTRUE(which)||is.na(which)) && !missing(j)) stop("'which' is ",which," (meaning return row numbers) but 'j' is also supplied. Either you need row numbers or the result of j, but only one type of result can be returned.")
+  if (!is.na(nomatch) && is.na(which)) stop("which=NA with nomatch=0 would always return an empty vector. Please change or remove either which or nomatch.")
+  .global$print=""
+  if (missing(i) && missing(j)) {
+    # ...[] == oops at console, forgot print(...)
+    # or some kind of dynamic construction that has edge case of no contents inside [...]
+    return(x)
+  }
+  if (!missing(keyby)) {
+    if (!missing(by)) stop("Provide either 'by' or 'keyby' but not both")
+    by=bysub=substitute(keyby)
+    # Assign to 'by' so that by is no longer missing and we can proceed as if there were one by
+  } else {
+    bysub = if (missing(by)) NULL # and leave missing(by)==TRUE
+        else substitute(by)
+  }
+  byjoin = FALSE
+  if (!missing(by)) {
+    if (missing(j)) stop("'by' or 'keyby' is supplied but not j")
+    byjoin = is.symbol(bysub) && bysub==".EACHI"
+  }
+  irows = NULL  # Meaning all rows. We avoid creating 1:nrow(x) for efficiency.
+  notjoin = FALSE
+  rightcols = leftcols = integer()
+  if (!with && missing(j)) stop("j must be provided when with=FALSE")
+  if (!missing(j)) {
+    jsub = replace_dot_alias(substitute(j))
+    root = if (is.call(jsub)) as.character(jsub[[1L]])[1L] else ""
+    if (root == ":" ||
+        (root %chin% c("-","!") && is.call(jsub[[2L]]) && jsub[[2L]][[1L]]=="(" && is.call(jsub[[2L]][[2L]]) && jsub[[2L]][[2L]][[1L]]==":") ||
+        ( !length(all.vars(jsub)) &&
+          root %chin% c("","c","paste","paste0","-","!") &&
+          missing(by) )) {   # test 763. TODO: likely that !missing(by) iff with==TRUE (so, with can be removed)
+      # When no variable names (i.e. symbols) occur in j, scope doesn't matter because there are no symbols to find.
+      # Automatically set with=FALSE in this case so that DT[,1], DT[,2:3], DT[,"someCol"] and DT[,c("colB","colD")]
+      # work as expected.  As before, a vector will never be returned, but a single column data.table
+      # for type consistency with >1 cases. To return a single vector use DT[["someCol"]] or DT[[3]].
+      # The root==":" is to allow DT[,colC:colH] even though that contains two variable names.
+      # root == "-" or "!" is for tests 1504.11 and 1504.13 (a : with a ! or - modifier root)
+      # This change won't break anything because it didn't do anything anyway; i.e. used to return the
+      # j value straight back: DT[,1] == 1 which isn't possibly useful.  It was that for consistency
+      # of learning since it was simpler to state that j always gets eval'd within the scope of DT.
+      # We don't want to evaluate j at all in making this decision because i) evaluating itself could
+      # increment some variable and not intended to be evaluated a 2nd time later on and ii) we don't
+      # want decisions like this to depend on the data or vector lengths since that can introduce
+      # inconistency reminiscent of drop in [.data.table that we seek to avoid.
+      with=FALSE
+    } else if (is.name(jsub)) {
+      jsubChar = as.character(jsub)
+      if (substring(jsubChar,1,2) == "..") {
+        if (nchar(jsubChar)==2) stop("The symbol .. is invalid. The .. prefix must be followed by at least one character.")
+        if (!exists(jsubChar, where=parent.frame())) {
+          # We have recommended manual ".." prefix in the past so that needs to keep working and take precedence
+          jsub = as.name(jsubChar<-substring(jsubChar,3))
+        }
+        with = FALSE
+      }
+      if (!with && !exists(jsubChar, where=parent.frame())) {
+        # Would fail anyway with 'object 'a' not found' but give a more helpful error. Thanks to Jan's suggestion.
+        stop("Variable '",jsubChar,"' is not found in calling scope. Looking in calling scope because either you used the .. prefix or set with=FALSE")
+      }
     }
-    if (!missing(keyby)) {
-        if (!missing(by)) stop("Provide either 'by' or 'keyby' but not both")
-        by=bysub=substitute(keyby)
-        # Assign to 'by' so that by is no longer missing and we can proceed as if there were one by
-    } else {
-        bysub = if (missing(by)) NULL # and leave missing(by)==TRUE
-                else substitute(by)
-    }
-    byjoin = FALSE
-    if (!missing(by)) {
-        if (missing(j)) stop("'by' or 'keyby' is supplied but not j")
-        byjoin = is.symbol(bysub) && bysub==".EACHI"
-    }
-    irows = NULL  # Meaning all rows. We avoid creating 1:nrow(x) for efficiency.
-    notjoin = FALSE
-    rightcols = leftcols = integer()
-    if (!with && missing(j)) stop("j must be provided when with=FALSE")
-    if (!missing(j)) {
-        jsub = replace_dot_alias(substitute(j))
+    if (root=="{") {
+      if (length(jsub) == 2L) {
+        jsub = jsub[[2L]]  # to allow {} wrapping of := e.g. [,{`:=`(...)},] [#376]
         root = if (is.call(jsub)) as.character(jsub[[1L]])[1L] else ""
-        if (root == ":" ||
-            (root %chin% c("-","!") && is.call(jsub[[2L]]) && jsub[[2L]][[1L]]=="(" && is.call(jsub[[2L]][[2L]]) && jsub[[2L]][[2L]][[1L]]==":") ||
-            ( !length(all.vars(jsub)) &&
-              root %chin% c("","c","paste","paste0","-","!") &&
-              missing(by) )) {   # test 763. TODO: likely that !missing(by) iff with==TRUE (so, with can be removed)
-            # When no variable names (i.e. symbols) occur in j, scope doesn't matter because there are no symbols to find.
-            # Automatically set with=FALSE in this case so that DT[,1], DT[,2:3], DT[,"someCol"] and DT[,c("colB","colD")]
-            # work as expected.  As before, a vector will never be returned, but a single column data.table
-            # for type consistency with >1 cases. To return a single vector use DT[["someCol"]] or DT[[3]].
-            # The root==":" is to allow DT[,colC:colH] even though that contains two variable names.
-            # root == "-" or "!" is for tests 1504.11 and 1504.13 (a : with a ! or - modifier root)
-            # This change won't break anything because it didn't do anything anyway; i.e. used to return the
-            # j value straight back: DT[,1] == 1 which isn't possibly useful.  It was that for consistency
-            # of learning since it was simpler to state that j always gets eval'd within the scope of DT.
-            # We don't want to evaluate j at all in making this decision because i) evaluating itself could
-            # increment some variable and not intended to be evaluated a 2nd time later on and ii) we don't
-            # want decisions like this to depend on the data or vector lengths since that can introduce
-            # inconistency reminiscent of drop in [.data.table that we seek to avoid.
-            with=FALSE
-        } else if (is.name(jsub)) {
-            jsubChar = as.character(jsub)
-            if (substring(jsubChar,1,2) == "..") {
-              if (nchar(jsubChar)==2) stop("The symbol .. is invalid. The .. prefix must be followed by at least one character.")
-              if (!exists(jsubChar, where=parent.frame())) {
-                # We have recommended manual ".." prefix in the past so that needs to keep working and take precedence
-                jsub = as.name(jsubChar<-substring(jsubChar,3))
-              }
-              with = FALSE
+      } else if (length(jsub) > 2L && is.call(jsub[[2L]]) && jsub[[2L]][[1L]] == ":=") {
+        #2142 -- j can be {} and have length 1
+        stop("You have wrapped := with {} which is ok but then := must be the only thing inside {}. You have something else inside {} as well. Consider placing the {} on the RHS of := instead; e.g. DT[,someCol:={tmpVar1<-...;tmpVar2<-...;tmpVar1*tmpVar2}")
+      }
+    }
+    if (root=="eval" && !any(all.vars(jsub[[2]]) %chin% names(x))) {
+      # TODO: this && !any depends on data. Can we remove it?
+      # Grab the dynamic expression from calling scope now to give the optimizer a chance to optimize it
+      # Only when top level is eval call.  Not nested like x:=eval(...) or `:=`(x=eval(...), y=eval(...))
+      jsub = eval(jsub[[2L]], parent.frame(), parent.frame())  # this evals the symbol to return the dynamic expression
+      if (is.expression(jsub)) jsub = jsub[[1L]]    # if expression, convert it to call
+      # Note that the dynamic expression could now be := (new in v1.9.7)
+      root = if (is.call(jsub)) as.character(jsub[[1L]])[1L] else ""
+    }
+    if (root == ":=") {
+      allow.cartesian=TRUE   # (see #800)
+      if (!missing(i) && !missing(keyby))
+        stop(":= with keyby is only possible when i is not supplied since you can't setkey on a subset of rows. Either change keyby to by or remove i")
+      if (!missingnomatch) {
+        warning("nomatch isn't relevant together with :=, ignoring nomatch")
+        nomatch=0L
+      }
+    }
+  }
+
+  # To take care of duplicate column names properly (see chmatch2 function above `[data.table`) for description
+  dupmatch <- function(x, y, ...) {
+    if (anyDuplicated(x))
+      pmax(chmatch(x,y, ...), chmatch2(x,y,0L))
+    else chmatch(x,y)
+  }
+
+  # setdiff removes duplicate entries, which'll create issues with duplicated names. Use '%chin% instead.
+  dupdiff <- function(x, y) x[!x %chin% y]
+
+  if (!missing(i)) {
+    xo = NULL
+    isub = substitute(i)
+    if (identical(isub, NA)) {
+      # only possibility *isub* can be NA (logical) is the symbol NA itself; i.e. DT[NA]
+      # replace NA in this case with NA_integer_ as that's almost surely what user intended to
+      # return a single row with NA in all columns. (DT[0] returns an empty table, with correct types.)
+      # Any expression (including length 1 vectors) that evaluates to a single NA logical will
+      # however be left as NA logical since that's important for consistency to return empty in that
+      # case; e.g. DT[Col==3] where DT is 1 row and Col contains NA.
+      # Replacing the NA symbol makes DT[NA] and DT[c(1,NA)] consistent and provides
+      # an easy way to achieve a single row of NA as users expect rather than requiring them
+      # to know and change to DT[NA_integer_].
+      isub=NA_integer_
+    }
+    isnull_inames = FALSE
+    nqgrp = integer(0)  # for non-equi join
+    nqmaxgrp = 1L       # for non-equi join
+    # Fixes 4994: a case where quoted expression with a "!", ex: expr = quote(!dt1); dt[eval(expr)] requires
+    # the "eval" to be checked before `as.name("!")`. Therefore interchanged.
+    restore.N = remove.N = FALSE
+    if (exists(".N", envir=parent.frame(), inherits=FALSE)) {
+      old.N = get(".N", envir=parent.frame(), inherits=FALSE)
+      locked.N = bindingIsLocked(".N", parent.frame())
+      if (locked.N) eval(call("unlockBinding", ".N", parent.frame()))  # eval call to pass R CMD check NOTE until we find cleaner way
+      assign(".N", nrow(x), envir=parent.frame(), inherits=FALSE)
+      restore.N = TRUE
+      # the comment below is invalid hereafter (due to fix for #1145)
+      # binding locked when .SD[.N] but that's ok as that's the .N we want anyway
+
+      # TO DO: change isub at C level s/.N/nrow(x); changing a symbol to a constant should be ok
+    } else {
+       assign(".N", nrow(x), envir=parent.frame(), inherits=FALSE)
+       remove.N = TRUE
+    }
+    if (is.call(isub) && isub[[1L]]=="eval") {  # TO DO: or ..()
+      isub = eval(.massagei(isub[[2L]]), parent.frame(), parent.frame())
+      if (is.expression(isub)) isub=isub[[1L]]
+    }
+    if (is.call(isub) && isub[[1L]] == as.name("!")) {
+      notjoin = TRUE
+      if (!missingnomatch) stop("not-join '!' prefix is present on i but nomatch is provided. Please remove nomatch.");
+      nomatch = 0L
+      isub = isub[[2L]]
+      # #932 related so that !(v1 == 1) becomes v1 == 1 instead of (v1 == 1) after removing "!"
+      if (is.call(isub) && isub[[1L]] == "(" && !is.name(isub[[2L]]))
+        isub = isub[[2L]]
+    }
+    if (is.call(isub) && isub[[1L]] == as.name("order") && getOption("datatable.optimize") >= 1) { # optimize here so that we can switch it off if needed
+      if (verbose) cat("order optimisation is on, i changed from 'order(...)' to 'forder(DT, ...)'.\n")
+      isub = as.list(isub)
+      isub = as.call(c(list(quote(forder), quote(x)), isub[-1L]))
+    }
+    if (is.null(isub)) return( null.data.table() )
+    if (is.call(isub) && isub[[1L]] == quote(forder)) {
+      order_env = new.env(parent=parent.frame())            # until 'forder' is exported
+      assign("forder", forder, order_env)
+      assign("x", x, order_env)
+      i = eval(isub, order_env, parent.frame())             # for optimisation of 'order' to 'forder'
+      # that forder returns integer(0) is taken care of internally within forder
+    } else if (is.call(isub) &&
+           getOption("datatable.use.index") && # #1422
+           as.character(isub[[1L]]) %chin% c("==","%in%") &&
+           is.name(isub[[2L]]) &&
+           (isub2<-as.character(isub[[2L]])) %chin% names(x) &&
+           (getOption("datatable.auto.index") || (isub2 %chin% indices(x))) && # `||` used to either auto.index or already have index #1422
+           is.null(attr(x, '.data.table.locked'))) {  # fix for #958, don't create auto index on '.SD'.
+      # LHS is a column name symbol
+      # simplest case for now (single ==).  Later, top level may be &,|,< or >
+      # TO DO: print method could print physical and secondary keys at end.
+      # TO DO: move down to if (is.data.table) clause below, later ...
+      RHS = eval(isub[[3L]], x, parent.frame())
+      # fix for #961
+      if (is.list(RHS)) RHS = as.character(RHS)
+      if (isub[[1L]] == "==" && length(RHS)>1) {
+        if (length(RHS)!=nrow(x)) stop("RHS of == is length ",length(RHS)," which is not 1 or nrow (",nrow(x),"). For robustness, no recycling is allowed (other than of length 1 RHS). Consider %in% instead.")
+        i = x[[isub2]] == RHS    # DT[colA == colB] regular element-wise vector scan
+      } else if ( (is.integer(x[[isub2]]) && is.double(RHS) && isReallyReal(RHS)) || (mode(x[[isub2]]) != mode(RHS) && !(class(x[[isub2]]) %in% c("character", "factor") &&
+             class(RHS) %in% c("character", "factor"))) ||
+             (is.factor(x[[isub2]]) && !is.factor(RHS) && mode(RHS)=="numeric") ) { # fringe case, #1361. TODO: cleaner way of doing these checks.
+          # re-direct all non-matching mode cases to base R, as data.table's binary
+          # search based join is strict in types. #957 and #961.
+          i = if (isub[[1L]] == "==") x[[isub2]] == RHS else x[[isub2]] %in% RHS
+      } else {
+        # fix for #932 (notjoin) and also when RHS is NA (and notjoin is also TRUE)
+        if (isub[[1L]] == "==") {
+          # RHS is of length=1 or n
+          if (any_na(as_list(RHS))) {
+            notjoin = FALSE
+            RHS = RHS[0L]
+          } else if (notjoin) {
+            RHS = c(RHS, if (is.double(RHS)) c(NA, NaN) else NA)
+          }
+        }
+        if (haskey(x) && isub2 == key(x)[1L]) {
+          # join to key(x)[1L]
+          xo <- integer()
+          rightcols = chmatch(key(x)[1],names(x))
+        } else {
+          xo = get2key(x,isub2)  # Can't be any index with that col as the first one because those indexes will reorder within each group
+          if (is.null(xo)) {   # integer() would be valid and signifies o=1:.N
+            if (verbose) {cat("Creating new index '",isub2,"'\n",sep="");flush.console()}
+            if (identical(getOption("datatable.auto.index"), FALSE)) warning("Index is being created on '",isub2,"' besides the fact that option 'datatable.auto.index' is FALSE. Please report to data.table#1422.") # why not double check that, even if won't happen now may be a good check for future changes
+            setindexv(x,isub2)
+            xo = get2key(x,isub2)
+          } else {
+            if (verbose) {cat("Using existing index '",isub2,"'\n",sep="");flush.console()}
+          }
+          rightcols = chmatch(isub2, names(x))
+        }
+        # convert RHS to list to join to key (either physical or secondary)
+        i = as.data.table( unique(RHS) )
+        # To do: wrap isub[[3L]] with as.data.table() first before eval to save copy
+        leftcols = 1L
+        ans = bmerge(i, x, leftcols, rightcols, io<-FALSE, xo, roll=0.0, rollends=c(FALSE,FALSE), nomatch=0L, mult="all", 1L, nqgrp, nqmaxgrp, verbose=verbose)
+        # No need to shallow copy i before passing to bmerge; we just created i above ourselves
+        i = if (ans$allLen1 && !identical(suppressWarnings(min(ans$starts)), 0L)) ans$starts else vecseq(ans$starts, ans$lens, NULL)
+        if (length(xo)) i = fsort(xo[i], internal=TRUE) else i = fsort(i, internal=TRUE) # fix for #1495
+        leftcols = rightcols = NULL  # these are used later to know whether a join was done, affects column order of result. So reset.
+      }
+    }
+    else if (!is.name(isub)) i = eval(.massagei(isub), x, parent.frame())
+    else {
+      # isub is a single symbol name such as B in DT[B]
+      i = try(eval(isub, parent.frame(), parent.frame()), silent=TRUE)
+      if (inherits(i,"try-error")) {
+        # must be "not found" since isub is a mere symbol
+        col = try(eval(isub, x), silent=TRUE)  # is it a column name?
+        if (identical(typeof(col),"logical"))
+          stop(as.character(isub)," is not found in calling scope but it is a column of type logical. If you wish to select rows where that column is TRUE, either wrap the symbol with '()' or use ==TRUE to be clearest to readers of your code.")
+        else
+          stop(as.character(isub)," is not found in calling scope and it is not a column of type logical. When the first argument inside DT[...] is a single symbol, it is looked for in calling scope.")
+      }
+    }
+    if (restore.N) {
+      assign(".N", old.N, envir=parent.frame())
+      if (locked.N) lockBinding(".N", parent.frame())
+    }
+    if (remove.N) rm(list=".N", envir=parent.frame())
+    if (is.matrix(i)) {
+      if (is.numeric(i) && ncol(i)==1L) { # #826 - subset DT on single integer vector stored as matrix
+        i = as.integer(i)
+      } else {
+        stop("i is invalid type (matrix). Perhaps in future a 2 column matrix could return a list of elements of DT (in the spirit of A[B] in FAQ 2.14). Please let datatable-help know if you'd like this, or add your comments to FR #657.")
+      }
+    }
+    if (is.logical(i)) {
+      if (notjoin) {
+        notjoin = FALSE
+        i = !i
+      }
+    }
+    if (is.null(i)) return( null.data.table() )
+    if (is.character(i)) {
+      isnull_inames = TRUE
+      i = data.table(V1=i)   # for user convenience; e.g. DT["foo"] without needing DT[.("foo")]
+    } else if (identical(class(i),"list") && length(i)==1L && is.data.frame(i[[1L]])) i = as.data.table(i[[1L]])
+    else if (identical(class(i),"data.frame")) i = as.data.table(i)   # TO DO: avoid these as.data.table() and use a flag instead
+    else if (identical(class(i),"list")) {
+      isnull_inames = is.null(names(i))
+      i = as.data.table(i)
+    }
+    if (is.data.table(i)) {
+      if (!haskey(x) && missing(on) && is.null(xo)) {
+        stop("When i is a data.table (or character vector), the columns to join by must be specified either using 'on=' argument (see ?data.table) or by keying x (i.e. sorted, and, marked as sorted, see ?setkey). Keyed joins might have further speed benefits on very large data due to x being sorted in RAM.")
+      }
+      if (!missing(on)) {
+        # on = .() is now possible, #1257
+        parse_on <- function(onsub) {
+          ops = c("==", "<=", "<", ">=", ">", "!=")
+          pat = paste("(", ops, ")", sep = "", collapse = "|")
+          if (is.call(onsub) && onsub[[1L]] == "eval") {
+            onsub = eval(onsub[[2L]], parent.frame(2L), parent.frame(2L))
+            if (is.call(onsub) && onsub[[1L]] == "eval") onsub = onsub[[2L]]
+          }
+          if (is.call(onsub) && as.character(onsub[[1L]]) %in% c("list", ".")) {
+            spat = paste("[ ]+(", pat, ")[ ]+", sep="")
+            onsub = lapply(as.list(onsub)[-1L], function(x) gsub(spat, "\\1", deparse(x, width.cutoff=500L)))
+            onsub = as.call(c(quote(c), onsub))
+          }
+          on = eval(onsub, parent.frame(2L), parent.frame(2L))
+          if (!is.character(on))
+            stop("'on' argument should be a named atomic vector of column names indicating which columns in 'i' should be joined with which columns in 'x'.")
+          this_op = regmatches(on, gregexpr(pat, on))
+          idx = (vapply(this_op, length, 0L) == 0L)
+          this_op[idx] = "=="
+          this_op = unlist(this_op, use.names=FALSE)
+          idx_op = match(this_op, ops, nomatch=0L)
+          if (any(idx_op %in% c(0L, 6L)))
+            stop("Invalid operators ", paste(this_op[idx_op==0L], collapse=","), ". Only allowed operators are ", paste(ops[1:5], collapse=""), ".")
+          if (is.null(names(on))) {
+            on[idx] = if (isnull_inames) paste(on[idx], paste("V", seq_len(sum(idx)), sep=""), sep="==") else paste(on[idx], on[idx], sep="==")
+          } else {
+            on[idx] = paste(names(on)[idx], on[idx], sep="==")
+          }
+          split = tstrsplit(on, paste("[ ]*", pat, "[ ]*", sep=""))
+          on = setattr(split[[2L]], 'names', split[[1L]])
+          if (length(empty_idx <- which(names(on) == "")))
+            names(on)[empty_idx] = on[empty_idx]
+          list(on = on, ops = idx_op)
+        }
+        on_ops = parse_on(substitute(on))
+        on = on_ops[[1L]]
+        ops = on_ops[[2L]]
+        # TODO: collect all '==' ops first to speeden up Cnestedid
+        rightcols = chmatch(names(on), names(x))
+        if (length(nacols <- which(is.na(rightcols))))
+          stop("Column(s) [", paste(names(on)[nacols], collapse=","), "] not found in x")
+        leftcols  = chmatch(unname(on), names(i))
+        if (length(nacols <- which(is.na(leftcols))))
+          stop("Column(s) [", paste(unname(on)[nacols], collapse=","), "] not found in i")
+        # figure out the columns on which to compute groups on
+        non_equi = which.first(ops != 1L) # 1 is "==" operator
+        if (!is.na(non_equi)) {
+          # non-equi operators present.. investigate groups..
+          if (verbose) cat("Non-equi join operators detected ... \n")
+          if (!missingroll) stop("roll is not implemented for non-equi joins yet.")
+          if (verbose) {last.started.at=proc.time()[3];cat("  forder took ... ");flush.console()}
+          # TODO: could check/reuse secondary indices, but we need 'starts' attribute as well!
+          xo = forderv(x, rightcols, retGrp=TRUE)
+          if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
+          xg = attr(xo, 'starts')
+          resetcols = head(rightcols, non_equi-1L)
+          if (length(resetcols)) {
+            # TODO: can we get around having to reorder twice here?
+            # or at least reuse previous order?
+            if (verbose) {last.started.at=proc.time()[3];cat("  Generating group lengths ... ");flush.console()}
+            resetlen = attr(forderv(x, resetcols, retGrp=TRUE), 'starts')
+            resetlen = .Call(Cuniqlengths, resetlen, nrow(x))
+            if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
+          } else resetlen = integer(0)
+          if (verbose) {last.started.at=proc.time()[3];cat("  Generating non-equi group ids ... ");flush.console()}
+          nqgrp = .Call(Cnestedid, x, rightcols[non_equi:length(rightcols)], xo, xg, resetlen, mult)
+          if (verbose) {cat("done in", round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
+          if ( (nqmaxgrp <- max(nqgrp)) > 1L) { # got some non-equi join work to do
+            if ("_nqgrp_" %in% names(x)) stop("Column name '_nqgrp_' is reserved for non-equi joins.")
+            if (verbose) {last.started.at=proc.time()[3];cat("  Recomputing forder with non-equi ids ... ");flush.console()}
+            set(nqx<-shallow(x), j="_nqgrp_", value=nqgrp)
+            xo = forderv(nqx, c(ncol(nqx), rightcols))
+            if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
+          } else nqgrp = integer(0)
+          if (verbose) cat("  Found", nqmaxgrp, "non-equi group(s) ...\n")
+        }
+        if (is.na(non_equi)) {
+          # equi join. use existing key (#1825) or existing secondary index (#1439)
+          if ( identical(head(key(x), length(on)), names(on)) ) {
+            xo = integer(0)
+            if (verbose) cat("on= matches existing key, using key\n")
+          } else {
+            if (isTRUE(getOption("datatable.use.index"))) {
+              idxName = paste0("__", names(on), sep="", collapse="")  # TODO: wrong, no sep!
+              xo = attr(attr(x, 'index'), idxName)
+              if (verbose && !is.null(xo)) cat("on= matches existing index, using index\n")
             }
-            if (!with && !exists(jsubChar, where=parent.frame())) {
-              # Would fail anyway with 'object 'a' not found' but give a more helpful error. Thanks to Jan's suggestion.
-              stop("Variable '",jsubChar,"' is not found in calling scope. Looking in calling scope because either you used the .. prefix or set with=FALSE")
+            if (is.null(xo)) {
+              last.started.at=proc.time()[3]
+              xo = forderv(x, by = rightcols)
+              if (verbose) cat("Calculated ad hoc index in", round(proc.time()[3]-last.started.at,3), "secs\n")
+              # TODO: use setindex() instead, so it's cached for future reuse
             }
+          }
         }
-        if (root=="{") {
-            if (length(jsub) == 2L) {
-                jsub = jsub[[2L]]  # to allow {} wrapping of := e.g. [,{`:=`(...)},] [#376]
-                root = if (is.call(jsub)) as.character(jsub[[1L]])[1L] else ""
-            } else if (length(jsub) > 2L && is.call(jsub[[2L]]) && jsub[[2L]][[1L]] == ":=") {
-                #2142 -- j can be {} and have length 1
-                stop("You have wrapped := with {} which is ok but then := must be the only thing inside {}. You have something else inside {} as well. Consider placing the {} on the RHS of := instead; e.g. DT[,someCol:={tmpVar1<-...;tmpVar2<-...;tmpVar1*tmpVar2}")
-            }
+      } else if (is.null(xo)) {
+        rightcols = chmatch(key(x),names(x))   # NAs here (i.e. invalid data.table) checked in bmerge()
+        leftcols = if (haskey(i))
+          chmatch(head(key(i),length(rightcols)),names(i))
+        else
+          seq_len(min(length(i),length(rightcols)))
+        rightcols = head(rightcols,length(leftcols))
+        xo = integer()  ## signifies 1:.N
+        ops = rep(1L, length(leftcols))
+      }
+      # Implementation for not-join along with by=.EACHI, #604
+      if (notjoin && (byjoin || mult != "all")) { # mult != "all" needed for #1571 fix
+        notjoin = FALSE
+        if (verbose) {last.started.at=proc.time()[3];cat("not-join called with 'by=.EACHI'; Replacing !i with i=setdiff(x,i) ...");flush.console()}
+        orignames = copy(names(i))
+        i = setdiff_(x, i, rightcols, leftcols) # part of #547
+        if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
+        setnames(i, orignames[leftcols])
+        setattr(i, 'sorted', names(i)) # since 'x' has key set, this'll always be sorted
+      }
+      io = if (missing(on)) haskey(i) else identical(unname(on), head(key(i), length(on)))
+      i = .shallow(i, retain.key = io)
+      ans = bmerge(i, x, leftcols, rightcols, io, xo, roll, rollends, nomatch, mult, ops, nqgrp, nqmaxgrp, verbose=verbose)
+      # temp fix for issue spotted by Jan, test #1653.1. TODO: avoid this
+      # 'setorder', as there's another 'setorder' in generating 'irows' below...
+      if (length(ans$indices)) setorder(setDT(ans[1:3]), indices)
+      allLen1 = ans$allLen1
+      allGrp1 = ans$allGrp1
+      f__ = ans$starts
+      len__ = ans$lens
+      indices__ = ans$indices
+      # length of input nomatch (single 0 or NA) is 1 in both cases.
+      # When no match, len__ is 0 for nomatch=0 and 1 for nomatch=NA, so len__ isn't .N
+      # If using secondary key of x, f__ will refer to xo
+      if (is.na(which)) {
+        w = if (notjoin) f__!=0L else is.na(f__)
+        return( if (length(xo)) fsort(xo[w], internal=TRUE) else which(w) )
+      }
+      if (mult=="all") {
+        # is by=.EACHI along with non-equi join?
+        nqbyjoin = byjoin && length(ans$indices) && !allGrp1
+        if (!byjoin || nqbyjoin) {
+          # Really, `anyDuplicated` in base is AWESOME!
+          # allow.cartesian shouldn't error if a) not-join, b) 'i' has no duplicates
+          irows = if (allLen1) f__ else vecseq(f__,len__,
+            if( allow.cartesian ||
+              notjoin || # #698. When notjoin=TRUE, ignore allow.cartesian. Rows in answer will never be > nrow(x).
+              !anyDuplicated(f__, incomparables = c(0L, NA_integer_)))  # #742. If 'i' has no duplicates, ignore
+              NULL
+            else as.double(nrow(x)+nrow(i))) # rows in i might not match to x so old max(nrow(x),nrow(i)) wasn't enough. But this limit now only applies when there are duplicates present so the reason now for nrow(x)+nrow(i) is just to nail it down and be bigger than max(nrow(x),nrow(i)).
+          # Fix for #1092 and #1074
+          # TODO: implement better version of "any"/"all"/"which" to avoid
+          # unnecessary construction of logical vectors
+          if (identical(nomatch, 0L) && allLen1) irows = irows[irows != 0L]
+        } else {
+          if (length(xo) && missing(on))
+            stop("Cannot by=.EACHI when joining to a secondary key, yet")
+          # since f__ refers to xo later in grouping, so xo needs to be passed through to dogroups too.
+          if (length(irows))
+            stop("Internal error. irows has length in by=.EACHI")
         }
-        if (root=="eval" && !any(all.vars(jsub[[2]]) %chin% names(x))) {
-            # TODO: this && !any depends on data. Can we remove it?
-            # Grab the dynamic expression from calling scope now to give the optimizer a chance to optimize it
-            # Only when top level is eval call.  Not nested like x:=eval(...) or `:=`(x=eval(...), y=eval(...))
-            jsub = eval(jsub[[2L]], parent.frame(), parent.frame())  # this evals the symbol to return the dynamic expression
-            if (is.expression(jsub)) jsub = jsub[[1L]]    # if expression, convert it to call
-            # Note that the dynamic expression could now be := (new in v1.9.7)
-            root = if (is.call(jsub)) as.character(jsub[[1L]])[1L] else ""
+        if (nqbyjoin) {
+          irows = if (length(xo)) xo[irows] else irows
+          xo = setorder(setDT(list(indices=rep.int(indices__, len__), irows=irows)))[["irows"]]
+          ans = .Call(Cnqnewindices, xo, len__, indices__, max(indices__))
+          f__ = ans[[1L]]; len__ = ans[[2L]]
+          allLen1 = FALSE # TODO; should this always be FALSE?
+          irows = NULL # important to reset
+          if (any_na(as_list(xo))) xo = xo[!is.na(xo)]
         }
-        if (root == ":=") {
-            allow.cartesian=TRUE   # (see #800)
-            if (!missing(i) && !missing(keyby))
-                stop(":= with keyby is only possible when i is not supplied since you can't setkey on a subset of rows. Either change keyby to by or remove i")
-            if (!missingnomatch) {
-                warning("nomatch isn't relevant together with :=, ignoring nomatch")
-                nomatch=0L
-            }
+      } else {
+        # turning on mult = "first"/"last" for non-equi joins again to test..
+        # if (nqmaxgrp>1L) stop("Non-equi joins aren't yet functional with mult='first' and mult='last'.")
+        # mult="first"/"last" logic moved to bmerge.c, also handles non-equi cases, #1452
+        if (!byjoin) { #1287 and #1271
+          irows = f__ # len__ is set to 1 as well, no need for 'pmin' logic
+          if (identical(nomatch,0L)) irows = irows[len__>0L]  # 0s are len 0, so this removes -1 irows
+        }
+        # TODO: when nomatch=NA, len__ need not be allocated / set at all for mult="first"/"last"?
+        # TODO: how about when nomatch=0L, can we avoid allocating then as well?
+      }
+      if (length(xo) && length(irows)) {
+        irows = xo[irows]   # TO DO: fsort here?
+        if (mult=="all" && !allGrp1) {
+          irows = setorder(setDT(list(indices=rep.int(indices__, len__), irows=irows)))[["irows"]]
         }
+      }
+    } else {
+      if (!missing(on)) {
+        stop("logical error. i is not a data.table, but 'on' argument is provided.")
+      }
+      # TO DO: TODO: Incorporate which_ here on DT[!i] where i is logical. Should avoid i = !i (above) - inefficient.
+      # i is not a data.table
+      if (!is.logical(i) && !is.numeric(i)) stop("i has not evaluated to logical, integer or double")
+      if (is.logical(i)) {
+        if (length(i)==1L  # to avoid unname copy when length(i)==nrow (normal case we don't want to slow down)
+          && isTRUE(unname(i))) irows=i=NULL  # unname() for #2152 - length 1 named logical vector.
+        # NULL is efficient signal to avoid creating 1:nrow(x) but still return all rows, fixes #1249
+
+        else if (length(i)<=1L) irows=i=integer(0)
+        # FALSE, NA and empty. All should return empty data.table. The NA here will be result of expression,
+        # where for consistency of edge case #1252 all NA to be removed. If NA is a single NA symbol, it
+        # was auto converted to NA_integer_ higher up for ease of use and convenience. We definitely
+        # don't want base R behaviour where DF[NA,] returns an entire copy filled with NA everywhere.
+
+        else if (length(i)==nrow(x)) irows=i=which(i)
+        # The which() here auto removes NA for convenience so user doesn't need to remember "!is.na() & ..."
+        # Also this which() is for consistenty of DT[colA>3,which=TRUE] and which(DT[,colA>3])
+        # Assigning to 'i' here as well to save memory, #926.
+
+        else stop("i evaluates to a logical vector length ", length(i), " but there are ", nrow(x), " rows. Recycling of logical i is no longer allowed as it hides more bugs than is worth the rare convenience. Explicitly use rep(...,length=.N) if you really need to recycle.")
+      } else {
+        irows = as.integer(i)  # e.g. DT[c(1,3)] and DT[c(-1,-3)] ok but not DT[c(1,-3)] (caught as error)
+        irows = .Call(CconvertNegativeIdx, irows, nrow(x)) # simplifies logic from here on (can assume positive subscripts)
+                                   # maintains Arun's fix for #2697 (test 1042)
+                                   # efficient in C with more detailed messages
+                                   # falls through quickly (no R level allocs) if no negatives
+                                   # minor TO DO: can we merge this with check_idx in fcast.c/subset ?
+      }
     }
+    if (notjoin) {
+      if (byjoin || !is.integer(irows) || is.na(nomatch)) stop("Internal error: notjoin but byjoin or !integer or nomatch==NA")
+      irows = irows[irows!=0L]
+      i = irows = if (length(irows)) seq_len(nrow(x))[-irows] else NULL  # NULL meaning all rows i.e. seq_len(nrow(x))
+      leftcols = integer()  # proceed as if row subset from now on, length(leftcols) is switched on later
+      rightcols = integer()
+      # Doing this once here, helps speed later when repeatedly subsetting each column. R's [irows] would do this for each
+      # column when irows contains negatives.
+    }
+    if (which) return( if (is.null(irows)) seq_len(nrow(x)) else irows )
+  } else {  # missing(i)
+    i = NULL
+  }
 
-    # To take care of duplicate column names properly (see chmatch2 function above `[data.table`) for description
-    dupmatch <- function(x, y, ...) {
-        if (anyDuplicated(x))
-            pmax(chmatch(x,y, ...), chmatch2(x,y,0L))
-        else chmatch(x,y)
-    }
-
-    # setdiff removes duplicate entries, which'll create issues with duplicated names. Use '%chin% instead.
-    dupdiff <- function(x, y) x[!x %chin% y]
-
-    if (!missing(i)) {
-        xo = NULL
-        isub = substitute(i)
-        if (identical(isub, NA)) {
-            # only possibility *isub* can be NA (logical) is the symbol NA itself; i.e. DT[NA]
-            # replace NA in this case with NA_integer_ as that's almost surely what user intended to
-            # return a single row with NA in all columns. (DT[0] returns an empty table, with correct types.)
-            # Any expression (including length 1 vectors) that evaluates to a single NA logical will
-            # however be left as NA logical since that's important for consistency to return empty in that
-            # case; e.g. DT[Col==3] where DT is 1 row and Col contains NA.
-            # Replacing the NA symbol makes DT[NA] and DT[c(1,NA)] consistent and provides
-            # an easy way to achieve a single row of NA as users expect rather than requiring them
-            # to know and change to DT[NA_integer_].
-            isub=NA_integer_
-        }
-        isnull_inames = FALSE
-        nqgrp = integer(0)  # for non-equi join
-        nqmaxgrp = 1L       # for non-equi join
-        # Fixes 4994: a case where quoted expression with a "!", ex: expr = quote(!dt1); dt[eval(expr)] requires
-        # the "eval" to be checked before `as.name("!")`. Therefore interchanged.
-        restore.N = remove.N = FALSE
-        if (exists(".N", envir=parent.frame(), inherits=FALSE)) {
-            old.N = get(".N", envir=parent.frame(), inherits=FALSE)
-            locked.N = bindingIsLocked(".N", parent.frame())
-            if (locked.N) eval(call("unlockBinding", ".N", parent.frame()))  # eval call to pass R CMD check NOTE until we find cleaner way
-            assign(".N", nrow(x), envir=parent.frame(), inherits=FALSE)
-            restore.N = TRUE
-            # the comment below is invalid hereafter (due to fix for #1145)
-            # binding locked when .SD[.N] but that's ok as that's the .N we want anyway
-
-            # TO DO: change isub at C level s/.N/nrow(x); changing a symbol to a constant should be ok
+  byval = NULL
+  xnrow = nrow(x)
+  xcols = xcolsAns = icols = icolsAns = integer()
+  xdotcols = FALSE
+  othervars = character(0)
+  if (missing(j)) {
+    # missing(by)==TRUE was already checked above before dealing with i
+    if (!length(x)) return(null.data.table())
+    if (!length(leftcols)) {
+      ansvars = nx = names(x)
+      jisvars = character()
+      xcols = xcolsAns = seq_along(x)
+    } else {
+      jisvars = names(i)[-leftcols]
+      tt = jisvars %chin% names(x)
+      if (length(tt)) jisvars[tt] = paste("i.",jisvars[tt],sep="")
+      if (length(duprightcols <- rightcols[duplicated(rightcols)])) {
+        nx = c(names(x), names(x)[duprightcols])
+        rightcols = chmatch2(names(x)[rightcols], nx)
+        nx = make.unique(nx)
+      } else nx = names(x)
+      ansvars = make.unique(c(nx, jisvars))
+      icols = c(leftcols, seq_along(i)[-leftcols])
+      icolsAns = c(rightcols, seq.int(length(nx)+1L, length.out=ncol(i)-length(unique(leftcols))))
+      xcols = xcolsAns = seq_along(x)[-rightcols]
+    }
+    ansvals = chmatch(ansvars, nx)
+  } else {
+    if (is.data.table(i)) {
+      idotprefix = paste0("i.", names(i))
+      xdotprefix = paste0("x.", names(x))
+    } else idotprefix = xdotprefix = character(0)
+
+    # j was substituted before dealing with i so that := can set allow.cartesian=FALSE (#800) (used above in i logic)
+    if (is.null(jsub)) return(NULL)
+
+    if (!with && is.call(jsub) && jsub[[1L]]==":=") {
+      # TODO: make these both errors (or single long error in both cases) in next release.
+      # i.e. using with=FALSE together with := at all will become an error. Eventually with will be removed.
+      if (is.null(names(jsub)) && is.name(jsub[[2L]])) {
+        warning("with=FALSE together with := was deprecated in v1.9.4 released Oct 2014. Please wrap the LHS of := with parentheses; e.g., DT[,(myVar):=sum(b),by=a] to assign to column name(s) held in variable myVar. See ?':=' for other examples. As warned in 2014, this is now a warning.")
+        jsub[[2L]] = eval(jsub[[2L]], parent.frame(), parent.frame())
+      } else {
+        warning("with=FALSE ignored, it isn't needed when using :=. See ?':=' for examples.")
+      }
+      with = TRUE
+    }
+
+    if (!with) {
+      # missing(by)==TRUE was already checked above before dealing with i
+      if (is.call(jsub) && deparse(jsub[[1]], 500L, backtick=FALSE) %in% c("!", "-")) {  # TODO is deparse avoidable here?
+        notj = TRUE
+        jsub = jsub[[2L]]
+      } else notj = FALSE
+      # fix for #1216, make sure the paranthesis are peeled from expr of the form (((1:4)))
+      while (is.call(jsub) && jsub[[1L]] == "(") jsub = as.list(jsub)[[-1L]]
+      if (is.call(jsub) && length(jsub) == 3L && jsub[[1L]] == ":") {
+        j = eval(jsub, setattr(as.list(seq_along(x)), 'names', names(x)), parent.frame()) # else j will be evaluated for the first time on next line
+      } else {
+        j = eval(jsub, parent.frame(), parent.frame())
+      }
+      if (is.logical(j)) j <- which(j)
+      if (!length(j)) return( null.data.table() )
+      if (is.factor(j)) j = as.character(j)  # fix for FR: #4867
+      if (is.character(j)) {
+        if (notj) {
+          w = chmatch(j, names(x))
+          if (any(is.na(w))) {
+            warning("column(s) not removed because not found: ",paste(j[is.na(w)],collapse=","))
+            w = w[!is.na(w)]
+          }
+          # changed names(x)[-w] to use 'setdiff'. Here, all instances of the column must be removed.
+          # Ex: DT <- data.table(x=1, y=2, x=3); DT[, !"x", with=FALSE] should just output 'y'.
+          # But keep 'dup cols' beause it's basically DT[, !names(DT) %chin% "x", with=FALSE] which'll subset all cols not 'x'.
+          ansvars = if (length(w)) dupdiff(names(x), names(x)[w]) else names(x)
+          ansvals = dupmatch(ansvars, names(x))
         } else {
-           assign(".N", nrow(x), envir=parent.frame(), inherits=FALSE)
-           remove.N = TRUE
+          # once again, use 'setdiff'. Basically, unless indices are specified in `j`, we shouldn't care about duplicated columns.
+          ansvars = j   # x. and i. prefixes may be in here, and they'll be dealt with below
+          # dups = FALSE here.. even if DT[, c("x", "x"), with=FALSE], we subset only the first.. No way to tell which one the OP wants without index.
+          ansvals = chmatch(ansvars, names(x))
         }
-        if (is.call(isub) && isub[[1L]]=="eval") {  # TO DO: or ..()
-            isub = eval(.massagei(isub[[2L]]), parent.frame(), parent.frame())
-            if (is.expression(isub)) isub=isub[[1L]]
+      } else if (is.numeric(j)) {
+        j = as.integer(j)
+        if (any(w<-(j>ncol(x)))) stop("Item ",which.first(w)," of j is ",j[which.first(w)]," which is outside the column number range [1,ncol=", ncol(x),"]")
+        j = j[j!=0L]
+        if (!length(j)) return(null.data.table())
+        if (any(j<0L) && any(j>0L)) stop("j mixes positives and negatives")
+        if (any(j<0L)) j = seq_len(ncol(x))[j]
+        ansvars = names(x)[ if (notj) -j else j ]  # DT[,!"columntoexclude",with=FALSE] if a copy is needed, rather than :=NULL
+        ansvals = if (notj) setdiff(seq_along(x), j) else j
+      } else stop("When with=FALSE, j-argument should be of type logical/character/integer indicating the columns to select.") # fix for #1440.
+      if (!length(ansvals)) return(null.data.table())
+    } else {   # with=TRUE and byjoin could be TRUE
+      bynames = NULL
+      allbyvars = NULL
+      if (byjoin) {
+        bynames = names(x)[rightcols]
+      } else if (!missing(by)) {
+        # deal with by before j because we need byvars when j contains .SD
+        # may evaluate to NULL | character() | "" | list(), likely a result of a user expression where no-grouping is one case being loop'd through
+        bysubl = as.list.default(bysub)
+        bysuborig = bysub
+        if (is.name(bysub) && !(as.character(bysub) %chin% names(x))) {  # TO DO: names(x),names(i),and i. and x. prefixes
+          bysub = eval(bysub, parent.frame(), parent.frame())
+          # fix for # 5106 - http://stackoverflow.com/questions/19983423/why-by-on-a-vector-not-from-a-data-table-column-is-very-slow
+          # case where by=y where y is not a column name, and not a call/symbol/expression, but an atomic vector outside of DT.
+          # note that if y is a list, this'll return an error (not sure if it should).
+          if (is.atomic(bysub)) bysubl = list(bysuborig) else bysubl = as.list.default(bysub)
         }
-        if (is.call(isub) && isub[[1L]] == as.name("!")) {
-            notjoin = TRUE
-            if (!missingnomatch) stop("not-join '!' prefix is present on i but nomatch is provided. Please remove nomatch.");
-            nomatch = 0L
-            isub = isub[[2L]]
-            # #932 related so that !(v1 == 1) becomes v1 == 1 instead of (v1 == 1) after removing "!"
-            if (is.call(isub) && isub[[1L]] == "(" && !is.name(isub[[2L]]))
-                isub = isub[[2L]]
-        }
-        if (is.call(isub) && isub[[1L]] == as.name("order") && getOption("datatable.optimize") >= 1) { # optimize here so that we can switch it off if needed
-            if (verbose) cat("order optimisation is on, i changed from 'order(...)' to 'forder(DT, ...)'.\n")
-            isub = as.list(isub)
-            isub = as.call(c(list(quote(forder), quote(x)), isub[-1L]))
-        }
-        if (is.null(isub)) return( null.data.table() )
-        if (is.call(isub) && isub[[1L]] == quote(forder)) {
-            order_env = new.env(parent=parent.frame())            # until 'forder' is exported
-            assign("forder", forder, order_env)
-            assign("x", x, order_env)
-            i = eval(isub, order_env, parent.frame())             # for optimisation of 'order' to 'forder'
-            # that forder returns integer(0) is taken care of internally within forder
-        } else if (is.call(isub) &&
-                     getOption("datatable.use.index") && # #1422
-                     as.character(isub[[1L]]) %chin% c("==","%in%") &&
-                     is.name(isub[[2L]]) &&
-                     (isub2<-as.character(isub[[2L]])) %chin% names(x) &&
-                     (getOption("datatable.auto.index") || (isub2 %chin% indices(x))) && # `||` used to either auto.index or already have index #1422
-                     is.null(attr(x, '.data.table.locked'))) {  # fix for #958, don't create auto index on '.SD'.
-            # LHS is a column name symbol
-            # simplest case for now (single ==).  Later, top level may be &,|,< or >
-            # TO DO: print method could print physical and secondary keys at end.
-            # TO DO: move down to if (is.data.table) clause below, later ...
-            RHS = eval(isub[[3L]], x, parent.frame())
-            # fix for #961
-            if (is.list(RHS)) RHS = as.character(RHS)
-            if (isub[[1L]] == "==" && length(RHS)>1) {
-                if (length(RHS)!=nrow(x)) stop("RHS of == is length ",length(RHS)," which is not 1 or nrow (",nrow(x),"). For robustness, no recycling is allowed (other than of length 1 RHS). Consider %in% instead.")
-                i = x[[isub2]] == RHS    # DT[colA == colB] regular element-wise vector scan
-            } else if ( (is.integer(x[[isub2]]) && is.double(RHS) && isReallyReal(RHS)) || (mode(x[[isub2]]) != mode(RHS) && !(class(x[[isub2]]) %in% c("character", "factor") &&
-                         class(RHS) %in% c("character", "factor"))) ||
-                         (is.factor(x[[isub2]]) && !is.factor(RHS) && mode(RHS)=="numeric") ) { # fringe case, #1361. TODO: cleaner way of doing these checks.
-                    # re-direct all non-matching mode cases to base R, as data.table's binary
-                    # search based join is strict in types. #957 and #961.
-                    i = if (isub[[1L]] == "==") x[[isub2]] == RHS else x[[isub2]] %in% RHS
-            } else {
-                # fix for #932 (notjoin) and also when RHS is NA (and notjoin is also TRUE)
-                if (isub[[1L]] == "==") {
-                    # RHS is of length=1 or n
-                    if (any_na(as_list(RHS))) {
-                        notjoin = FALSE
-                        RHS = RHS[0L]
-                    } else if (notjoin) {
-                        RHS = c(RHS, if (is.double(RHS)) c(NA, NaN) else NA)
-                    }
-                }
-                if (haskey(x) && isub2 == key(x)[1L]) {
-                    # join to key(x)[1L]
-                    xo <- integer()
-                    rightcols = chmatch(key(x)[1],names(x))
-                } else {
-                    xo = get2key(x,isub2)  # Can't be any index with that col as the first one because those indexes will reorder within each group
-                    if (is.null(xo)) {   # integer() would be valid and signifies o=1:.N
-                        if (verbose) {cat("Creating new index '",isub2,"'\n",sep="");flush.console()}
-                        if (identical(getOption("datatable.auto.index"), FALSE)) warning("Index is being created on '",isub2,"' besides the fact that option 'datatable.auto.index' is FALSE. Please report to data.table#1422.") # why not double check that, even if won't happen now may be a good check for future changes
-                        setindexv(x,isub2)
-                        xo = get2key(x,isub2)
-                    } else {
-                        if (verbose) {cat("Using existing index '",isub2,"'\n",sep="");flush.console()}
-                    }
-                    rightcols = chmatch(isub2, names(x))
-                }
-                # convert RHS to list to join to key (either physical or secondary)
-                i = as.data.table( unique(RHS) )
-                # To do: wrap isub[[3L]] with as.data.table() first before eval to save copy
-                leftcols = 1L
-                ans = bmerge(i, x, leftcols, rightcols, io<-FALSE, xo, roll=0.0, rollends=c(FALSE,FALSE), nomatch=0L, mult="all", 1L, nqgrp, nqmaxgrp, verbose=verbose)
-                # No need to shallow copy i before passing to bmerge; we just created i above ourselves
-                i = if (ans$allLen1 && !identical(suppressWarnings(min(ans$starts)), 0L)) ans$starts else vecseq(ans$starts, ans$lens, NULL)
-                if (length(xo)) i = fsort(xo[i], internal=TRUE) else i = fsort(i, internal=TRUE) # fix for #1495
-                leftcols = rightcols = NULL  # these are used later to know whether a join was done, affects column order of result. So reset.
-            }
+        if (length(bysubl) && identical(bysubl[[1L]],quote(eval))) {    # TO DO: or by=..()
+          bysub = eval(bysubl[[2]], parent.frame(), parent.frame())
+          bysub = replace_dot_alias(bysub) # fix for #1298
+          if (is.expression(bysub)) bysub=bysub[[1L]]
+          bysubl = as.list.default(bysub)
+        } else if (is.call(bysub) && as.character(bysub[[1L]]) %chin% c("c","key","names", "intersect", "setdiff")) {
+          # catch common cases, so we don't have to copy x[irows] for all columns
+          # *** TO DO ***: try() this eval first (as long as not list() or .()) and see if it evaluates to column names
+          # to avoid the explicit c,key,names which already misses paste("V",1:10) for example
+          #        tried before but since not wrapped in try() it failed on some tests
+          # or look for column names used in this by (since if none it wouldn't find column names anyway
+          # when evaled within full x[irows]).  Trouble is that colA%%2L is a call and should be within frame.
+          tt = eval(bysub, parent.frame(), parent.frame())
+          if (!is.character(tt)) stop("by=c(...), key(...) or names(...) must evaluate to 'character'")
+          bysub=tt
+        } else if (is.call(bysub) && !as.character(bysub[[1L]]) %chin% c("list", "as.list", "{", ".", ":")) {
+          # potential use of function, ex: by=month(date). catch it and wrap with "(", because we need to set "bysameorder" to FALSE as we don't know if the function will return ordered results just because "date" is ordered. Fixes #2670.
+          bysub = as.call(c(as.name('('), list(bysub)))
+          bysubl = as.list.default(bysub)
+        } else if (is.call(bysub) && bysub[[1L]] == ".") bysub[[1L]] = quote(list)
+
+        if (mode(bysub) == "character") {
+          if (length(grep(",",bysub))) {
+            if (length(bysub)>1L) stop("'by' is a character vector length ",length(bysub)," but one or more items include a comma. Either pass a vector of column names (which can contain spaces, but no commas), or pass a vector length 1 containing comma separated column names. See ?data.table for other possibilities.")
+            bysub = strsplit(bysub,split=",")[[1L]]
+          }
+          tt = grep("^[^`]+$",bysub)
+          if (length(tt)) bysub[tt] = paste("`",bysub[tt],"`",sep="")
+          bysub = parse(text=paste("list(",paste(bysub,collapse=","),")",sep=""))[[1L]]
+          bysubl = as.list.default(bysub)
         }
-        else if (!is.name(isub)) i = eval(.massagei(isub), x, parent.frame())
+        allbyvars = intersect(all.vars(bysub),names(x))
+
+        orderedirows = .Call(CisOrderedSubset, irows, nrow(x))  # TRUE when irows is NULL (i.e. no i clause)
+        # orderedirows = is.sorted(f__)
+        bysameorder = orderedirows && haskey(x) && all(vapply_1b(bysubl,is.name)) && length(allbyvars) && identical(allbyvars,head(key(x),length(allbyvars)))
+        if (is.null(irows))
+          if (is.call(bysub) && length(bysub) == 3L && bysub[[1L]] == ":" && is.name(bysub[[2L]]) && is.name(bysub[[3L]])) {
+            byval = eval(bysub, setattr(as.list(seq_along(x)), 'names', names(x)), parent.frame())
+            byval = as.list(x)[byval]
+          } else byval = eval(bysub, x, parent.frame())
         else {
-            # isub is a single symbol name such as B in DT[B]
-            i = try(eval(isub, parent.frame(), parent.frame()), silent=TRUE)
-            if (inherits(i,"try-error")) {
-                # must be "not found" since isub is a mere symbol
-                col = try(eval(isub, x), silent=TRUE)  # is it a column name?
-                if (identical(typeof(col),"logical"))
-                    stop(as.character(isub)," is not found in calling scope but it is a column of type logical. If you wish to select rows where that column is TRUE, either wrap the symbol with '()' or use ==TRUE to be clearest to readers of your code.")
-                else
-                    stop(as.character(isub)," is not found in calling scope and it is not a column of type logical. When the first argument inside DT[...] is a single symbol, it is looked for in calling scope.")
-            }
+          if (!is.integer(irows)) stop("Internal error: irows isn't integer")  # length 0 when i returns no rows
+          # Passing irows as i to x[] below has been troublesome in a rare edge case.
+          # irows may contain NA, 0, negatives and >nrow(x) here. That's all ok.
+          # But we may need i join column values to be retained (where those rows have no match), hence we tried eval(isub)
+          # in 1.8.3, but this failed test 876.
+          # TO DO: Add a test like X[i,sum(v),by=i.x2], or where by includes a join column (both where some i don't match).
+          # TO DO: Make xss directly, rather than recursive call.
+          if (!is.na(nomatch)) irows = irows[irows!=0L]   # TO DO: can be removed now we have CisSortedSubset
+          if (length(allbyvars)) {    ###############  TO DO  TO DO  TO DO  ###############
+            if (verbose) cat("i clause present and columns used in by detected, only these subset:",paste(allbyvars,collapse=","),"\n")
+            xss = x[irows,allbyvars,with=FALSE,nomatch=nomatch,mult=mult,roll=roll,rollends=rollends]
+          } else {
+            if (verbose) cat("i clause present but columns used in by not detected. Having to subset all columns before evaluating 'by': '",deparse(by),"'\n",sep="")
+            xss = x[irows,nomatch=nomatch,mult=mult,roll=roll,rollends=rollends]
+          }
+          if (is.call(bysub) && length(bysub) == 3L && bysub[[1L]] == ":") {
+            byval = eval(bysub, setattr(as.list(seq_along(xss)), 'names', names(xss)), parent.frame())
+            byval = as.list(xss)[byval]
+          } else byval = eval(bysub, xss, parent.frame())
+          xnrow = nrow(xss)
+          # TO DO: pass xss (x subset) through into dogroups. Still need irows there (for :=), but more condense
+          # and contiguous to use xss to form .SD in dogroups than going via irows
         }
-        if (restore.N) {
-            assign(".N", old.N, envir=parent.frame())
-            if (locked.N) lockBinding(".N", parent.frame())
+        if (!length(byval) && xnrow>0L) {
+          # see missing(by) up above for comments
+          # by could be NULL or character(0) for example (e.g. passed in as argument in a loop of different bys)
+          bysameorder = FALSE  # 1st and only group is the entire table, so could be TRUE, but FALSE to avoid
+                     # a key of empty character()
+          byval = list()
+          bynames = allbyvars = NULL
+          # the rest now fall through
+        } else bynames = names(byval)
+        if (is.atomic(byval)) {
+          if (is.character(byval) && length(byval)<=ncol(x) && !(is.name(bysub) && as.character(bysub)%chin%names(x)) ) {
+            stop("'by' appears to evaluate to column names but isn't c() or key(). Use by=list(...) if you can. Otherwise, by=eval",deparse(bysub)," should work. This is for efficiency so data.table can detect which columns are needed.")
+          } else {
+            # by may be a single unquoted column name but it must evaluate to list so this is a convenience to users. Could also be a single expression here such as DT[,sum(v),by=colA%%2]
+            byval = list(byval)
+            bysubl = c(as.name("list"),bysuborig)  # for guessing the column name below
+            if (is.name(bysuborig))
+              bynames = as.character(bysuborig)
+            else
+              bynames = names(byval)
+          }
         }
-        if (remove.N) rm(list=".N", envir=parent.frame())
-        if (is.matrix(i)) {
-            if (is.numeric(i) && ncol(i)==1L) { # #826 - subset DT on single integer vector stored as matrix
-                i = as.integer(i)
-            } else {
-                stop("i is invalid type (matrix). Perhaps in future a 2 column matrix could return a list of elements of DT (in the spirit of A[B] in FAQ 2.14). Please let datatable-help know if you'd like this, or add your comments to FR #657.")
-            }
+        if (!is.list(byval)) stop("'by' or 'keyby' must evaluate to vector or list of vectors (where 'list' includes data.table and data.frame which are lists, too)")
+        for (jj in seq_len(length(byval))) {
+          if (!typeof(byval[[jj]]) %chin% c("integer","logical","character","double")) stop("column or expression ",jj," of 'by' or 'keyby' is type ",typeof(byval[[jj]]),". Do not quote column names. Usage: DT[,sum(colC),by=list(colA,month(colB))]")
         }
-        if (is.logical(i)) {
-            if (notjoin) {
-                notjoin = FALSE
-                i = !i
+        tt = vapply_1i(byval,length)
+        if (any(tt!=xnrow)) stop("The items in the 'by' or 'keyby' list are length (",paste(tt,collapse=","),"). Each must be same length as rows in x or number of rows returned by i (",xnrow,").")
+        if (is.null(bynames)) bynames = rep.int("",length(byval))
+        if (any(bynames=="")) {
+          if (length(bysubl)<2) stop("When 'by' or 'keyby' is list() we expect something inside the brackets")
+          for (jj in seq_along(bynames)) {
+            if (bynames[jj]=="") {
+              # Best guess. Use "month" in the case of by=month(date), use "a" in the case of by=a%%2
+              byvars = all.vars(bysubl[[jj+1L]], functions = TRUE)
+              if (length(byvars) == 1) tt = byvars
+              else {
+                tt = grep("^eval|^[^[:alpha:]. ]",byvars,invert=TRUE,value=TRUE)[1L]
+                if (!length(tt)) tt = all.vars(bysubl[[jj+1L]])[1L]
+              }
+              # fix for #497
+              if (length(byvars) > 1L && tt %in% all.vars(jsub, FALSE)) {
+                bynames[jj] = deparse(bysubl[[jj+1L]])
+                if (verbose)
+                  cat("by-expression '", bynames[jj], "' is not named, and the auto-generated name '", tt, "' clashed with variable(s) in j. Therefore assigning the entire by-expression as name.\n", sep="")
+
+              }
+              else bynames[jj] = tt
+              # if user doesn't like this inferred name, user has to use by=list() to name the column
             }
+          }
+          # Fix for #1334
+          if (any(duplicated(bynames))) {
+            bynames = make.unique(bynames)
+          }
         }
-        if (is.null(i)) return( null.data.table() )
-        if (is.character(i)) {
-            isnull_inames = TRUE
-            i = data.table(V1=i)   # for user convenience; e.g. DT["foo"] without needing DT[.("foo")]
-        } else if (identical(class(i),"list") && length(i)==1L && is.data.frame(i[[1L]])) i = as.data.table(i[[1L]])
-        else if (identical(class(i),"data.frame")) i = as.data.table(i)   # TO DO: avoid these as.data.table() and use a flag instead
-        else if (identical(class(i),"list")) {
-            isnull_inames = is.null(names(i))
-            i = as.data.table(i)
+        setattr(byval, "names", bynames)  # byval is just a list not a data.table hence setattr not setnames
+      }
+
+      jvnames = NULL
+      if (is.name(jsub)) {
+        # j is a single unquoted column name
+        if (jsub!=".SD") {
+          jvnames = gsub("^[.](N|I|GRP|BY)$","\\1",as.character(jsub))
+          # jsub is list()ed after it's eval'd inside dogroups.
         }
-        if (is.data.table(i)) {
-            if (!haskey(x) && missing(on) && is.null(xo)) {
-                stop("When i is a data.table (or character vector), the columns to join by must be specified either using 'on=' argument (see ?data.table) or by keying x (i.e. sorted, and, marked as sorted, see ?setkey). Keyed joins might have further speed benefits on very large data due to x being sorted in RAM.")
-            }
-            if (!missing(on)) {
-                # on = .() is now possible, #1257
-                parse_on <- function(onsub) {
-                    ops = c("==", "<=", "<", ">=", ">", "!=")
-                    pat = paste("(", ops, ")", sep = "", collapse = "|")
-                    if (is.call(onsub) && onsub[[1L]] == "eval") {
-                        onsub = eval(onsub[[2L]], parent.frame(2L), parent.frame(2L))
-                        if (is.call(onsub) && onsub[[1L]] == "eval") onsub = onsub[[2L]]
-                    }
-                    if (is.call(onsub) && as.character(onsub[[1L]]) %in% c("list", ".")) {
-                        spat = paste("[ ]+(", pat, ")[ ]+", sep="")
-                        onsub = lapply(as.list(onsub)[-1L], function(x) gsub(spat, "\\1", deparse(x, width.cutoff=500L)))
-                        onsub = as.call(c(quote(c), onsub))
-                    }
-                    on = eval(onsub, parent.frame(2L), parent.frame(2L))
-                    if (!is.character(on))
-                        stop("'on' argument should be a named atomic vector of column names indicating which columns in 'i' should be joined with which columns in 'x'.")
-                    this_op = regmatches(on, gregexpr(pat, on))
-                    idx = (vapply(this_op, length, 0L) == 0L)
-                    this_op[idx] = "=="
-                    this_op = unlist(this_op, use.names=FALSE)
-                    idx_op = match(this_op, ops, nomatch=0L)
-                    if (any(idx_op %in% c(0L, 6L)))
-                        stop("Invalid operators ", paste(this_op[idx_op==0L], collapse=","), ". Only allowed operators are ", paste(ops[1:5], collapse=""), ".")
-                    if (is.null(names(on))) {
-                        on[idx] = if (isnull_inames) paste(on[idx], paste("V", seq_len(sum(idx)), sep=""), sep="==") else paste(on[idx], on[idx], sep="==")
-                    } else {
-                        on[idx] = paste(names(on)[idx], on[idx], sep="==")
-                    }
-                    split = tstrsplit(on, paste("[ ]*", pat, "[ ]*", sep=""))
-                    on = setattr(split[[2L]], 'names', split[[1L]])
-                    if (length(empty_idx <- which(names(on) == "")))
-                        names(on)[empty_idx] = on[empty_idx]
-                    list(on = on, ops = idx_op)
-                }
-                on_ops = parse_on(substitute(on))
-                on = on_ops[[1L]]
-                ops = on_ops[[2L]]
-                # TODO: collect all '==' ops first to speeden up Cnestedid
-                rightcols = chmatch(names(on), names(x))
-                if (length(nacols <- which(is.na(rightcols))))
-                    stop("Column(s) [", paste(names(on)[nacols], collapse=","), "] not found in x")
-                leftcols  = chmatch(unname(on), names(i))
-                if (length(nacols <- which(is.na(leftcols))))
-                    stop("Column(s) [", paste(unname(on)[nacols], collapse=","), "] not found in i")
-                # figure out the columns on which to compute groups on
-                non_equi = which.first(ops != 1L) # 1 is "==" operator
-                if (!is.na(non_equi)) {
-                    # non-equi operators present.. investigate groups..
-                    if (verbose) cat("Non-equi join operators detected ... \n")
-                    if (!missingroll) stop("roll is not implemented for non-equi joins yet.")
-                    if (verbose) {last.started.at=proc.time()[3];cat("  forder took ... ");flush.console()}
-                    # TODO: could check/reuse secondary indices, but we need 'starts' attribute as well!
-                    xo = forderv(x, rightcols, retGrp=TRUE)
-                    if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
-                    xg = attr(xo, 'starts')
-                    resetcols = head(rightcols, non_equi-1L)
-                    if (length(resetcols)) {
-                        # TODO: can we get around having to reorder twice here?
-                        # or at least reuse previous order?
-                        if (verbose) {last.started.at=proc.time()[3];cat("  Generating group lengths ... ");flush.console()}
-                        resetlen = attr(forderv(x, resetcols, retGrp=TRUE), 'starts')
-                        resetlen = .Call(Cuniqlengths, resetlen, nrow(x))
-                        if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
-                    } else resetlen = integer(0)
-                    if (verbose) {last.started.at=proc.time()[3];cat("  Generating non-equi group ids ... ");flush.console()}
-                    nqgrp = .Call(Cnestedid, x, rightcols[non_equi:length(rightcols)], xo, xg, resetlen, mult)
-                    if (verbose) {cat("done in", round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
-                    if ( (nqmaxgrp <- max(nqgrp)) > 1L) { # got some non-equi join work to do
-                        if ("_nqgrp_" %in% names(x)) stop("Column name '_nqgrp_' is reserved for non-equi joins.")
-                        if (verbose) {last.started.at=proc.time()[3];cat("  Recomputing forder with non-equi ids ... ");flush.console()}
-                        set(nqx<-shallow(x), j="_nqgrp_", value=nqgrp)
-                        xo = forderv(nqx, c(ncol(nqx), rightcols))
-                        if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
-                    } else nqgrp = integer(0)
-                    if (verbose) cat("  Found", nqmaxgrp, "non-equi group(s) ...\n")
-                }
-                if (is.na(non_equi)) {
-                    # equi join. use existing key (#1825) or existing secondary index (#1439)
-                    if ( identical(head(key(x), length(on)), names(on)) ) {
-                        xo = integer(0)
-                        if (verbose) cat("on= matches existing key, using key\n")
-                    } else {
-                        if (isTRUE(getOption("datatable.use.index"))) {
-                            idxName = paste0("__", names(on), sep="", collapse="")  # TODO: wrong, no sep!
-                            xo = attr(attr(x, 'index'), idxName)
-                            if (verbose && !is.null(xo)) cat("on= matches existing index, using index\n")
-                        }
-                        if (is.null(xo)) {
-                            last.started.at=proc.time()[3]
-                            xo = forderv(x, by = rightcols)
-                            if (verbose) cat("Calculated ad hoc index in", round(proc.time()[3]-last.started.at,3), "secs\n")
-                            # TODO: use setindex() instead, so it's cached for future reuse
-                        }
-                    }
-                }
-            } else if (is.null(xo)) {
-                rightcols = chmatch(key(x),names(x))   # NAs here (i.e. invalid data.table) checked in bmerge()
-                leftcols = if (haskey(i))
-                    chmatch(head(key(i),length(rightcols)),names(i))
-                else
-                    seq_len(min(length(i),length(rightcols)))
-                rightcols = head(rightcols,length(leftcols))
-                xo = integer()  ## signifies 1:.N
-                ops = rep(1L, length(leftcols))
-            }
-            # Implementation for not-join along with by=.EACHI, #604
-            if (notjoin && (byjoin || mult != "all")) { # mult != "all" needed for #1571 fix
-                notjoin = FALSE
-                if (verbose) {last.started.at=proc.time()[3];cat("not-join called with 'by=.EACHI'; Replacing !i with i=setdiff(x,i) ...");flush.console()}
-                orignames = copy(names(i))
-                i = setdiff_(x, i, rightcols, leftcols) # part of #547
-                if (verbose) {cat("done in",round(proc.time()[3]-last.started.at,3),"secs\n");flush.console}
-                setnames(i, orignames[leftcols])
-                setattr(i, 'sorted', names(i)) # since 'x' has key set, this'll always be sorted
-            }
-            io = if (missing(on)) haskey(i) else identical(unname(on), head(key(i), length(on)))
-            i = .shallow(i, retain.key = io)
-            ans = bmerge(i, x, leftcols, rightcols, io, xo, roll, rollends, nomatch, mult, ops, nqgrp, nqmaxgrp, verbose=verbose)
-            # temp fix for issue spotted by Jan, test #1653.1. TODO: avoid this
-            # 'setorder', as there's another 'setorder' in generating 'irows' below...
-            if (length(ans$indices)) setorder(setDT(ans[1:3]), indices)
-            allLen1 = ans$allLen1
-            allGrp1 = ans$allGrp1
-            f__ = ans$starts
-            len__ = ans$lens
-            indices__ = ans$indices
-            # length of input nomatch (single 0 or NA) is 1 in both cases.
-            # When no match, len__ is 0 for nomatch=0 and 1 for nomatch=NA, so len__ isn't .N
-            # If using secondary key of x, f__ will refer to xo
-            if (is.na(which)) {
-                w = if (notjoin) f__!=0L else is.na(f__)
-                return( if (length(xo)) fsort(xo[w], internal=TRUE) else which(w) )
-            }
-            if (mult=="all") {
-                # is by=.EACHI along with non-equi join?
-                nqbyjoin = byjoin && length(ans$indices) && !allGrp1
-                if (!byjoin || nqbyjoin) {
-                    # Really, `anyDuplicated` in base is AWESOME!
-                    # allow.cartesian shouldn't error if a) not-join, b) 'i' has no duplicates
-                    irows = if (allLen1) f__ else vecseq(f__,len__,
-                        if( allow.cartesian ||
-                            notjoin || # #698. When notjoin=TRUE, ignore allow.cartesian. Rows in answer will never be > nrow(x).
-                            !anyDuplicated(f__, incomparables = c(0L, NA_integer_)))  # #742. If 'i' has no duplicates, ignore
-                            NULL
-                        else as.double(nrow(x)+nrow(i))) # rows in i might not match to x so old max(nrow(x),nrow(i)) wasn't enough. But this limit now only applies when there are duplicates present so the reason now for nrow(x)+nrow(i) is just to nail it down and be bigger than max(nrow(x),nrow(i)).
-                    # Fix for #1092 and #1074
-                    # TODO: implement better version of "any"/"all"/"which" to avoid
-                    # unnecessary construction of logical vectors
-                    if (identical(nomatch, 0L) && allLen1) irows = irows[irows != 0L]
-                } else {
-                    if (length(xo) && missing(on))
-                        stop("Cannot by=.EACHI when joining to a secondary key, yet")
-                    # since f__ refers to xo later in grouping, so xo needs to be passed through to dogroups too.
-                    if (length(irows))
-                        stop("Internal error. irows has length in by=.EACHI")
-                }
-                if (nqbyjoin) {
-                    irows = if (length(xo)) xo[irows] else irows
-                    xo = setorder(setDT(list(indices=rep.int(indices__, len__), irows=irows)))[["irows"]]
-                    ans = .Call(Cnqnewindices, xo, len__, indices__, max(indices__))
-                    f__ = ans[[1L]]; len__ = ans[[2L]]
-                    allLen1 = FALSE # TODO; should this always be FALSE?
-                    irows = NULL # important to reset
-                    if (any_na(as_list(xo))) xo = xo[!is.na(xo)]
-                }
-            } else {
-                # turning on mult = "first"/"last" for non-equi joins again to test..
-                # if (nqmaxgrp>1L) stop("Non-equi joins aren't yet functional with mult='first' and mult='last'.")
-                # mult="first"/"last" logic moved to bmerge.c, also handles non-equi cases, #1452
-                if (!byjoin) { #1287 and #1271
-                    irows = f__ # len__ is set to 1 as well, no need for 'pmin' logic
-                    if (identical(nomatch,0L)) irows = irows[len__>0L]  # 0s are len 0, so this removes -1 irows
-                }
-                # TODO: when nomatch=NA, len__ need not be allocated / set at all for mult="first"/"last"?
-                # TODO: how about when nomatch=0L, can we avoid allocating then as well?
-            }
-            if (length(xo) && length(irows)) {
-                irows = xo[irows]   # TO DO: fsort here?
-                if (mult=="all" && !allGrp1) {
-                    irows = setorder(setDT(list(indices=rep.int(indices__, len__), irows=irows)))[["irows"]]
-                }
-            }
+      } else if (is.call(jsub) && as.character(jsub[[1L]]) %chin% c("list",".")) {
+        jsub[[1L]] = quote(list)
+        jsubl = as.list.default(jsub)  # TO DO: names(jsub) and names(jsub)="" seem to work so make use of that
+        if (length(jsubl)>1) {
+          jvnames = names(jsubl)[-1L]   # check list(a=sum(v),v)
+          if (is.null(jvnames)) jvnames = rep.int("", length(jsubl)-1L)
+          for (jj in seq.int(2L,length(jsubl))) {
+            if (jvnames[jj-1L] == "" && mode(jsubl[[jj]])=="name")
+              jvnames[jj-1L] = gsub("^[.](N|I|GRP|BY)$","\\1",deparse(jsubl[[jj]]))
+            # TO DO: if call to a[1] for example, then call it 'a' too
+          }
+          setattr(jsubl, "names", NULL)  # drops the names from the list so it's faster to eval the j for each group. We'll put them back aftwards on the result.
+          jsub = as.call(jsubl)
+        } # else empty list is needed for test 468: adding an empty list column
+      } # else maybe a call to transform or something which returns a list.
+      av = all.vars(jsub,TRUE)  # TRUE fixes bug #1294 which didn't see b in j=fns[[b]](c)
+      use.I = ".I" %chin% av
+      # browser()
+      if (any(c(".SD","eval","get","mget") %chin% av)) {
+        if (missing(.SDcols)) {
+          # here we need to use 'dupdiff' instead of 'setdiff'. Ex: setdiff(c("x", "x"), NULL) will give 'x'.
+          ansvars = dupdiff(names(x),union(bynames,allbyvars))   # TO DO: allbyvars here for vars used by 'by'. Document.
+          # just using .SD in j triggers all non-by columns in the subset even if some of
+          # those columns are not used. It would be tricky to detect whether the j expression
+          # really does use all of the .SD columns or not, hence .SDcols for grouping
+          # over a subset of columns
+
+          # all duplicate columns must be matched, because nothing is provided
+          ansvals = dupmatch(ansvars, names(x))
         } else {
-            if (!missing(on)) {
-                stop("logical error. i is not a data.table, but 'on' argument is provided.")
-            }
-            # TO DO: TODO: Incorporate which_ here on DT[!i] where i is logical. Should avoid i = !i (above) - inefficient.
-            # i is not a data.table
-            if (!is.logical(i) && !is.numeric(i)) stop("i has not evaluated to logical, integer or double")
-            if (is.logical(i)) {
-                if (length(i)==1L  # to avoid unname copy when length(i)==nrow (normal case we don't want to slow down)
-                    && isTRUE(unname(i))) irows=i=NULL  # unname() for #2152 - length 1 named logical vector.
-                # NULL is efficient signal to avoid creating 1:nrow(x) but still return all rows, fixes #1249
-
-                else if (length(i)<=1L) irows=i=integer(0)
-                # FALSE, NA and empty. All should return empty data.table. The NA here will be result of expression,
-                # where for consistency of edge case #1252 all NA to be removed. If NA is a single NA symbol, it
-                # was auto converted to NA_integer_ higher up for ease of use and convenience. We definitely
-                # don't want base R behaviour where DF[NA,] returns an entire copy filled with NA everywhere.
-
-                else if (length(i)==nrow(x)) irows=i=which(i)
-                # The which() here auto removes NA for convenience so user doesn't need to remember "!is.na() & ..."
-                # Also this which() is for consistenty of DT[colA>3,which=TRUE] and which(DT[,colA>3])
-                # Assigning to 'i' here as well to save memory, #926.
-
-                else stop("i evaluates to a logical vector length ", length(i), " but there are ", nrow(x), " rows. Recycling of logical i is no longer allowed as it hides more bugs than is worth the rare convenience. Explicitly use rep(...,length=.N) if you really need to recycle.")
-            } else {
-                irows = as.integer(i)  # e.g. DT[c(1,3)] and DT[c(-1,-3)] ok but not DT[c(1,-3)] (caught as error)
-                irows = .Call(CconvertNegativeIdx, irows, nrow(x)) # simplifies logic from here on (can assume positive subscripts)
-                                                                   # maintains Arun's fix for #2697 (test 1042)
-                                                                   # efficient in C with more detailed messages
-                                                                   # falls through quickly (no R level allocs) if no negatives
-                                                                   # minor TO DO: can we merge this with check_idx in fcast.c/subset ?
-            }
-        }
-        if (notjoin) {
-            if (byjoin || !is.integer(irows) || is.na(nomatch)) stop("Internal error: notjoin but byjoin or !integer or nomatch==NA")
-            irows = irows[irows!=0L]
-            i = irows = if (length(irows)) seq_len(nrow(x))[-irows] else NULL  # NULL meaning all rows i.e. seq_len(nrow(x))
-            leftcols = integer()  # proceed as if row subset from now on, length(leftcols) is switched on later
-            rightcols = integer()
-            # Doing this once here, helps speed later when repeatedly subsetting each column. R's [irows] would do this for each
-            # column when irows contains negatives.
+          # FR #4979 - negative numeric and character indices for SDcols
+          colsub = substitute(.SDcols)
+          # fix for #5190. colsub[[1L]] gave error when it's a symbol.
+          if (is.call(colsub) && deparse(colsub[[1L]], 500L, backtick=FALSE) %in% c("!", "-")) {
+            colm = TRUE
+            colsub = colsub[[2L]]
+          } else colm = FALSE
+          # fix for #1216, make sure the paranthesis are peeled from expr of the form (((1:4)))
+          while(is.call(colsub) && colsub[[1L]] == "(") colsub = as.list(colsub)[[-1L]]
+          if (is.call(colsub) && length(colsub) == 3L && colsub[[1L]] == ":") {
+            # .SDcols is of the format a:b
+            .SDcols = eval(colsub, setattr(as.list(seq_along(x)), 'names', names(x)), parent.frame())
+          } else {
+            .SDcols = eval(colsub, parent.frame(), parent.frame())
+          }
+          if (is.logical(.SDcols)) {
+            ansvals = which_(rep(.SDcols, length.out=length(x)), !colm)
+            ansvars = names(x)[ansvals]
+          } else if (is.numeric(.SDcols)) {
+            # if .SDcols is numeric, use 'dupdiff' instead of 'setdiff'
+            if (length(unique(sign(.SDcols))) != 1L) stop(".SDcols is numeric but has both +ve and -ve indices")
+            if (any(is.na(.SDcols)) || any(abs(.SDcols)>ncol(x)) || any(abs(.SDcols)<1L)) stop(".SDcols is numeric but out of bounds (or NA)")
+            if (colm) ansvars = dupdiff(names(x)[-.SDcols], bynames) else ansvars = names(x)[.SDcols]
+            ansvals = if (colm) setdiff(seq_along(names(x)), c(as.integer(.SDcols), which(names(x) %chin% bynames))) else as.integer(.SDcols)
+          } else {
+            if (!is.character(.SDcols)) stop(".SDcols should be column numbers or names")
+            if (any(is.na(.SDcols)) || any(!.SDcols %chin% names(x))) stop("Some items of .SDcols are not column names (or are NA)")
+            if (colm) ansvars = setdiff(setdiff(names(x), .SDcols), bynames) else ansvars = .SDcols
+            # dups = FALSE here. DT[, .SD, .SDcols=c("x", "x")] again doesn't really help with which 'x' to keep (and if '-' which x to remove)
+            ansvals = chmatch(ansvars, names(x))
+          }
         }
-        if (which) return( if (is.null(irows)) seq_len(nrow(x)) else irows )
-    } else {  # missing(i)
-        i = NULL
-    }
-
-    byval = NULL
-    xnrow = nrow(x)
-    xcols = xcolsAns = icols = icolsAns = integer()
-    xdotcols = FALSE
-    othervars = character(0)
-    if (missing(j)) {
-        # missing(by)==TRUE was already checked above before dealing with i
-        if (!length(x)) return(null.data.table())
-        if (!length(leftcols)) {
-            ansvars = nx = names(x)
-            jisvars = character()
-            xcols = xcolsAns = seq_along(x)
-        } else {
-            jisvars = names(i)[-leftcols]
-            tt = jisvars %chin% names(x)
-            if (length(tt)) jisvars[tt] = paste("i.",jisvars[tt],sep="")
-            if (length(duprightcols <- rightcols[duplicated(rightcols)])) {
-                nx = c(names(x), names(x)[duprightcols])
-                rightcols = chmatch2(names(x)[rightcols], nx)
-                nx = make.unique(nx)
-            } else nx = names(x)
-            ansvars = make.unique(c(nx, jisvars))
-            icols = c(leftcols, seq_along(i)[-leftcols])
-            icolsAns = c(rightcols, seq.int(length(nx)+1L, length.out=ncol(i)-length(unique(leftcols))))
-            xcols = xcolsAns = seq_along(x)[-rightcols]
+        # fix for long standing FR/bug, #495 and #484
+        allcols = c(names(x), xdotprefix, names(i), idotprefix)
+        if ( length(othervars <- setdiff(intersect(av, allcols), c(bynames, ansvars))) ) {
+          # we've a situation like DT[, c(sum(V1), lapply(.SD, mean)), by=., .SDcols=...] or
+          # DT[, lapply(.SD, function(x) x *v1), by=, .SDcols=...] etc.,
+          ansvars = union(ansvars, othervars)
+          ansvals = chmatch(ansvars, names(x))
         }
-        ansvals = chmatch(ansvars, nx)
-    } else {
-        if (is.data.table(i)) {
-            idotprefix = paste0("i.", names(i))
-            xdotprefix = paste0("x.", names(x))
-        } else idotprefix = xdotprefix = character(0)
-
-        # j was substituted before dealing with i so that := can set allow.cartesian=FALSE (#800) (used above in i logic)
-        if (is.null(jsub)) return(NULL)
-
-        if (!with && is.call(jsub) && jsub[[1L]]==":=") {
-            # TODO: make these both errors (or single long error in both cases) in next release.
-            # i.e. using with=FALSE together with := at all will become an error. Eventually with will be removed.
-            if (is.null(names(jsub)) && is.name(jsub[[2L]])) {
-                warning("with=FALSE together with := was deprecated in v1.9.4 released Oct 2014. Please wrap the LHS of := with parentheses; e.g., DT[,(myVar):=sum(b),by=a] to assign to column name(s) held in variable myVar. See ?':=' for other examples. As warned in 2014, this is now a warning.")
-                jsub[[2L]] = eval(jsub[[2L]], parent.frame(), parent.frame())
-            } else {
-                warning("with=FALSE ignored, it isn't needed when using :=. See ?':=' for examples.")
-            }
-            with = TRUE
+        # .SDcols might include grouping columns if users wants that, but normally we expect user not to include them in .SDcols
+      } else {
+        if (!missing(.SDcols)) warning("This j doesn't use .SD but .SDcols has been supplied. Ignoring .SDcols. See ?data.table.")
+        allcols = c(names(x), xdotprefix, names(i), idotprefix)
+        ansvars = setdiff(intersect(av,allcols), bynames)
+        if (verbose) cat("Detected that j uses these columns:",if (!length(ansvars)) "<none>" else paste(ansvars,collapse=","),"\n")
+        # using a few named columns will be faster
+        # Consider:   DT[,max(diff(date)),by=list(month=month(date))]
+        # and:        DT[,lapply(.SD,sum),by=month(date)]
+        # We don't want date in .SD in the latter, but we do in the former; hence the union() above.
+        ansvals = chmatch(ansvars, names(x))
+      }
+      # if (!length(ansvars)) Leave ansvars empty. Important for test 607.
+
+      # TODO remove as (m)get is now folded in above.
+      # added 'mget' - fix for #994
+      if (any(c("get", "mget") %chin% av)) {
+        if (verbose) {
+          cat("'(m)get' found in j. ansvars being set to all columns. Use .SDcols or a single j=eval(macro) instead. Both will detect the columns used which is important for efficiency.\nOld:", paste(ansvars,collapse=","),"\n")
+          # get('varname') is too difficult to detect which columns are used in general
+          # eval(macro) column names are detected via the  if jsub[[1]]==eval switch earlier above.
         }
 
-        if (!with) {
-            # missing(by)==TRUE was already checked above before dealing with i
-            if (is.call(jsub) && deparse(jsub[[1]], 500L, backtick=FALSE) %in% c("!", "-")) {  # TODO is deparse avoidable here?
-                notj = TRUE
-                jsub = jsub[[2L]]
-            } else notj = FALSE
-            # fix for #1216, make sure the paranthesis are peeled from expr of the form (((1:4)))
-            while (is.call(jsub) && jsub[[1L]] == "(") jsub = as.list(jsub)[[-1L]]
-            if (is.call(jsub) && length(jsub) == 3L && jsub[[1L]] == ":") {
-                j = eval(jsub, setattr(as.list(seq_along(x)), 'names', names(x)), parent.frame()) # else j will be evaluated for the first time on next line
-            } else {
-                j = eval(jsub, parent.frame(), parent.frame())
-            }
-            if (is.logical(j)) j <- which(j)
-            if (!length(j)) return( null.data.table() )
-            if (is.factor(j)) j = as.character(j)  # fix for FR: #4867
-            if (is.character(j)) {
-                if (notj) {
-                    w = chmatch(j, names(x))
-                    if (any(is.na(w))) {
-                        warning("column(s) not removed because not found: ",paste(j[is.na(w)],collapse=","))
-                        w = w[!is.na(w)]
-                    }
-                    # changed names(x)[-w] to use 'setdiff'. Here, all instances of the column must be removed.
-                    # Ex: DT <- data.table(x=1, y=2, x=3); DT[, !"x", with=FALSE] should just output 'y'.
-                    # But keep 'dup cols' beause it's basically DT[, !names(DT) %chin% "x", with=FALSE] which'll subset all cols not 'x'.
-                    ansvars = if (length(w)) dupdiff(names(x), names(x)[w]) else names(x)
-                    ansvals = dupmatch(ansvars, names(x))
-                } else {
-                    # once again, use 'setdiff'. Basically, unless indices are specified in `j`, we shouldn't care about duplicated columns.
-                    ansvars = j   # x. and i. prefixes may be in here, and they'll be dealt with below
-                    # dups = FALSE here.. even if DT[, c("x", "x"), with=FALSE], we subset only the first.. No way to tell which one the OP wants without index.
-                    ansvals = chmatch(ansvars, names(x))
-                }
-            } else if (is.numeric(j)) {
-                j = as.integer(j)
-                if (any(w<-(j>ncol(x)))) stop("Item ",which.first(w)," of j is ",j[which.first(w)]," which is outside the column number range [1,ncol=", ncol(x),"]")
-                j = j[j!=0L]
-                if (!length(j)) return(null.data.table())
-                if (any(j<0L) && any(j>0L)) stop("j mixes positives and negatives")
-                if (any(j<0L)) j = seq_len(ncol(x))[j]
-                ansvars = names(x)[ if (notj) -j else j ]  # DT[,!"columntoexclude",with=FALSE] if a copy is needed, rather than :=NULL
-                ansvals = if (notj) setdiff(seq_along(x), j) else j
-            } else stop("When with=FALSE, j-argument should be of type logical/character/integer indicating the columns to select.") # fix for #1440.
-            if (!length(ansvals)) return(null.data.table())
-        } else {   # with=TRUE and byjoin could be TRUE
-            bynames = NULL
-            allbyvars = NULL
-            if (byjoin) {
-                bynames = names(x)[rightcols]
-            } else if (!missing(by)) {
-                # deal with by before j because we need byvars when j contains .SD
-                # may evaluate to NULL | character() | "" | list(), likely a result of a user expression where no-grouping is one case being loop'd through
-                bysubl = as.list.default(bysub)
-                bysuborig = bysub
-                if (is.name(bysub) && !(as.character(bysub) %chin% names(x))) {  # TO DO: names(x),names(i),and i. and x. prefixes
-                    bysub = eval(bysub, parent.frame(), parent.frame())
-                    # fix for # 5106 - http://stackoverflow.com/questions/19983423/why-by-on-a-vector-not-from-a-data-table-column-is-very-slow
-                    # case where by=y where y is not a column name, and not a call/symbol/expression, but an atomic vector outside of DT.
-                    # note that if y is a list, this'll return an error (not sure if it should).
-                    if (is.atomic(bysub)) bysubl = list(bysuborig) else bysubl = as.list.default(bysub)
-                }
-                if (length(bysubl) && identical(bysubl[[1L]],quote(eval))) {    # TO DO: or by=..()
-                    bysub = eval(bysubl[[2]], parent.frame(), parent.frame())
-                    bysub = replace_dot_alias(bysub) # fix for #1298
-                    if (is.expression(bysub)) bysub=bysub[[1L]]
-                    bysubl = as.list.default(bysub)
-                } else if (is.call(bysub) && as.character(bysub[[1L]]) %chin% c("c","key","names", "intersect", "setdiff")) {
-                    # catch common cases, so we don't have to copy x[irows] for all columns
-                    # *** TO DO ***: try() this eval first (as long as not list() or .()) and see if it evaluates to column names
-                    # to avoid the explicit c,key,names which already misses paste("V",1:10) for example
-                    #        tried before but since not wrapped in try() it failed on some tests
-                    # or look for column names used in this by (since if none it wouldn't find column names anyway
-                    # when evaled within full x[irows]).  Trouble is that colA%%2L is a call and should be within frame.
-                    tt = eval(bysub, parent.frame(), parent.frame())
-                    if (!is.character(tt)) stop("by=c(...), key(...) or names(...) must evaluate to 'character'")
-                    bysub=tt
-                } else if (is.call(bysub) && !as.character(bysub[[1L]]) %chin% c("list", "as.list", "{", ".", ":")) {
-                    # potential use of function, ex: by=month(date). catch it and wrap with "(", because we need to set "bysameorder" to FALSE as we don't know if the function will return ordered results just because "date" is ordered. Fixes #2670.
-                    bysub = as.call(c(as.name('('), list(bysub)))
-                    bysubl = as.list.default(bysub)
-                } else if (is.call(bysub) && bysub[[1L]] == ".") bysub[[1L]] = quote(list)
-
-                if (mode(bysub) == "character") {
-                    if (length(grep(",",bysub))) {
-                        if (length(bysub)>1L) stop("'by' is a character vector length ",length(bysub)," but one or more items include a comma. Either pass a vector of column names (which can contain spaces, but no commas), or pass a vector length 1 containing comma separated column names. See ?data.table for other possibilities.")
-                        bysub = strsplit(bysub,split=",")[[1L]]
-                    }
-                    tt = grep("^[^`]+$",bysub)
-                    if (length(tt)) bysub[tt] = paste("`",bysub[tt],"`",sep="")
-                    bysub = parse(text=paste("list(",paste(bysub,collapse=","),")",sep=""))[[1L]]
-                    bysubl = as.list.default(bysub)
-                }
-                allbyvars = intersect(all.vars(bysub),names(x))
-
-                orderedirows = .Call(CisOrderedSubset, irows, nrow(x))  # TRUE when irows is NULL (i.e. no i clause)
-                # orderedirows = is.sorted(f__)
-                bysameorder = orderedirows && haskey(x) && all(vapply_1b(bysubl,is.name)) && length(allbyvars) && identical(allbyvars,head(key(x),length(allbyvars)))
-                if (is.null(irows))
-                    if (is.call(bysub) && length(bysub) == 3L && bysub[[1L]] == ":" && is.name(bysub[[2L]]) && is.name(bysub[[3L]])) {
-                        byval = eval(bysub, setattr(as.list(seq_along(x)), 'names', names(x)), parent.frame())
-                        byval = as.list(x)[byval]
-                    } else byval = eval(bysub, x, parent.frame())
-                else {
-                    if (!is.integer(irows)) stop("Internal error: irows isn't integer")  # length 0 when i returns no rows
-                    # Passing irows as i to x[] below has been troublesome in a rare edge case.
-                    # irows may contain NA, 0, negatives and >nrow(x) here. That's all ok.
-                    # But we may need i join column values to be retained (where those rows have no match), hence we tried eval(isub)
-                    # in 1.8.3, but this failed test 876.
-                    # TO DO: Add a test like X[i,sum(v),by=i.x2], or where by includes a join column (both where some i don't match).
-                    # TO DO: Make xss directly, rather than recursive call.
-                    if (!is.na(nomatch)) irows = irows[irows!=0L]   # TO DO: can be removed now we have CisSortedSubset
-                    if (length(allbyvars)) {    ###############  TO DO  TO DO  TO DO  ###############
-                        if (verbose) cat("i clause present and columns used in by detected, only these subset:",paste(allbyvars,collapse=","),"\n")
-                        xss = x[irows,allbyvars,with=FALSE,nomatch=nomatch,mult=mult,roll=roll,rollends=rollends]
-                    } else {
-                        if (verbose) cat("i clause present but columns used in by not detected. Having to subset all columns before evaluating 'by': '",deparse(by),"'\n",sep="")
-                        xss = x[irows,nomatch=nomatch,mult=mult,roll=roll,rollends=rollends]
-                    }
-                    if (is.call(bysub) && length(bysub) == 3L && bysub[[1L]] == ":") {
-                        byval = eval(bysub, setattr(as.list(seq_along(xss)), 'names', names(xss)), parent.frame())
-                        byval = as.list(xss)[byval]
-                    } else byval = eval(bysub, xss, parent.frame())
-                    xnrow = nrow(xss)
-                    # TO DO: pass xss (x subset) through into dogroups. Still need irows there (for :=), but more condense
-                    # and contiguous to use xss to form .SD in dogroups than going via irows
-                }
-                if (!length(byval) && xnrow>0L) {
-                    # see missing(by) up above for comments
-                    # by could be NULL or character(0) for example (e.g. passed in as argument in a loop of different bys)
-                    bysameorder = FALSE  # 1st and only group is the entire table, so could be TRUE, but FALSE to avoid
-                                         # a key of empty character()
-                    byval = list()
-                    bynames = allbyvars = NULL
-                    # the rest now fall through
-                } else bynames = names(byval)
-                if (is.atomic(byval)) {
-                    if (is.character(byval) && length(byval)<=ncol(x) && !(is.name(bysub) && as.character(bysub)%chin%names(x)) ) {
-                        stop("'by' appears to evaluate to column names but isn't c() or key(). Use by=list(...) if you can. Otherwise, by=eval",deparse(bysub)," should work. This is for efficiency so data.table can detect which columns are needed.")
-                    } else {
-                        # by may be a single unquoted column name but it must evaluate to list so this is a convenience to users. Could also be a single expression here such as DT[,sum(v),by=colA%%2]
-                        byval = list(byval)
-                        bysubl = c(as.name("list"),bysuborig)  # for guessing the column name below
-                        if (is.name(bysuborig))
-                            bynames = as.character(bysuborig)
-                        else
-                            bynames = names(byval)
-                    }
-                }
-                if (!is.list(byval)) stop("'by' or 'keyby' must evaluate to vector or list of vectors (where 'list' includes data.table and data.frame which are lists, too)")
-                for (jj in seq_len(length(byval))) {
-                    if (!typeof(byval[[jj]]) %chin% c("integer","logical","character","double")) stop("column or expression ",jj," of 'by' or 'keyby' is type ",typeof(byval[[jj]]),". Do not quote column names. Usage: DT[,sum(colC),by=list(colA,month(colB))]")
-                }
-                tt = vapply_1i(byval,length)
-                if (any(tt!=xnrow)) stop("The items in the 'by' or 'keyby' list are length (",paste(tt,collapse=","),"). Each must be same length as rows in x or number of rows returned by i (",xnrow,").")
-                if (is.null(bynames)) bynames = rep.int("",length(byval))
-                if (any(bynames=="")) {
-                    if (length(bysubl)<2) stop("When 'by' or 'keyby' is list() we expect something inside the brackets")
-                    for (jj in seq_along(bynames)) {
-                        if (bynames[jj]=="") {
-                            # Best guess. Use "month" in the case of by=month(date), use "a" in the case of by=a%%2
-                            byvars = all.vars(bysubl[[jj+1L]], functions = TRUE)
-                            if (length(byvars) == 1) tt = byvars
-                            else {
-                                tt = grep("^eval|^[^[:alpha:]. ]",byvars,invert=TRUE,value=TRUE)[1L]
-                                if (!length(tt)) tt = all.vars(bysubl[[jj+1L]])[1L]
-                            }
-                            # fix for #497
-                            if (length(byvars) > 1L && tt %in% all.vars(jsub, FALSE)) {
-                                bynames[jj] = deparse(bysubl[[jj+1L]])
-                                if (verbose)
-                                    cat("by-expression '", bynames[jj], "' is not named, and the auto-generated name '", tt, "' clashed with variable(s) in j. Therefore assigning the entire by-expression as name.\n", sep="")
-
-                            }
-                            else bynames[jj] = tt
-                            # if user doesn't like this inferred name, user has to use by=list() to name the column
-                        }
-                    }
-                    # Fix for #1334
-                    if (any(duplicated(bynames))) {
-                        bynames = make.unique(bynames)
-                    }
-                }
-                setattr(byval, "names", bynames)  # byval is just a list not a data.table hence setattr not setnames
-            }
+        # Do not include z in .SD when dt[, z := {.SD; get("x")}, .SDcols = "y"] (#2326, #2338)
+        if (is.call(jsub) && length(jsub[[1L]]) == 1L && jsub[[1L]] == ":=" && is.symbol(jsub[[2L]])) {
+          jsub_lhs_symbol <- as.character(jsub[[2L]])
+            if (jsub_lhs_symbol %chin% othervars) {
+            ansvars <- setdiff(ansvars, jsub_lhs_symbol)
+          }
+        }
+        if (length(ansvars)) othervars = ansvars # #1744 fix
+        allcols = c(names(x), xdotprefix, names(i), idotprefix)
+        ansvars = setdiff(allcols,bynames) # fix for bug #5443
+        ansvals = chmatch(ansvars, names(x))
+        if (length(othervars)) othervars = setdiff(ansvars, othervars) # #1744 fix
+        if (verbose) cat("New:",paste(ansvars,collapse=","),"\n")
+      }
 
-            jvnames = NULL
-            if (is.name(jsub)) {
-                # j is a single unquoted column name
-                if (jsub!=".SD") {
-                    jvnames = gsub("^[.](N|I|GRP|BY)$","\\1",as.character(jsub))
-                    # jsub is list()ed after it's eval'd inside dogroups.
-                }
-            } else if (is.call(jsub) && as.character(jsub[[1L]]) %chin% c("list",".")) {
-                jsub[[1L]] = quote(list)
-                jsubl = as.list.default(jsub)  # TO DO: names(jsub) and names(jsub)="" seem to work so make use of that
-                if (length(jsubl)>1) {
-                    jvnames = names(jsubl)[-1L]   # check list(a=sum(v),v)
-                    if (is.null(jvnames)) jvnames = rep.int("", length(jsubl)-1L)
-                    for (jj in seq.int(2L,length(jsubl))) {
-                        if (jvnames[jj-1L] == "" && mode(jsubl[[jj]])=="name")
-                            jvnames[jj-1L] = gsub("^[.](N|I|GRP|BY)$","\\1",deparse(jsubl[[jj]]))
-                        # TO DO: if call to a[1] for example, then call it 'a' too
-                    }
-                    setattr(jsubl, "names", NULL)  # drops the names from the list so it's faster to eval the j for each group. We'll put them back aftwards on the result.
-                    jsub = as.call(jsubl)
-                } # else empty list is needed for test 468: adding an empty list column
-            } # else maybe a call to transform or something which returns a list.
-            av = all.vars(jsub,TRUE)  # TRUE fixes bug #1294 which didn't see b in j=fns[[b]](c)
-            use.I = ".I" %chin% av
-            # browser()
-            if (any(c(".SD","eval","get","mget") %chin% av)) {
-                if (missing(.SDcols)) {
-                    # here we need to use 'dupdiff' instead of 'setdiff'. Ex: setdiff(c("x", "x"), NULL) will give 'x'.
-                    ansvars = dupdiff(names(x),union(bynames,allbyvars))   # TO DO: allbyvars here for vars used by 'by'. Document.
-                    # just using .SD in j triggers all non-by columns in the subset even if some of
-                    # those columns are not used. It would be tricky to detect whether the j expression
-                    # really does use all of the .SD columns or not, hence .SDcols for grouping
-                    # over a subset of columns
-
-                    # all duplicate columns must be matched, because nothing is provided
-                    ansvals = dupmatch(ansvars, names(x))
-                } else {
-                    # FR #4979 - negative numeric and character indices for SDcols
-                    colsub = substitute(.SDcols)
-                    # fix for #5190. colsub[[1L]] gave error when it's a symbol.
-                    if (is.call(colsub) && deparse(colsub[[1L]], 500L, backtick=FALSE) %in% c("!", "-")) {
-                        colm = TRUE
-                        colsub = colsub[[2L]]
-                    } else colm = FALSE
-                    # fix for #1216, make sure the paranthesis are peeled from expr of the form (((1:4)))
-                    while(is.call(colsub) && colsub[[1L]] == "(") colsub = as.list(colsub)[[-1L]]
-                    if (is.call(colsub) && length(colsub) == 3L && colsub[[1L]] == ":") {
-                        # .SDcols is of the format a:b
-                        .SDcols = eval(colsub, setattr(as.list(seq_along(x)), 'names', names(x)), parent.frame())
-                    } else {
-                        .SDcols = eval(colsub, parent.frame(), parent.frame())
-                    }
-                    if (is.logical(.SDcols)) {
-                        ansvals = which_(rep(.SDcols, length.out=length(x)), !colm)
-                        ansvars = names(x)[ansvals]
-                    } else if (is.numeric(.SDcols)) {
-                        # if .SDcols is numeric, use 'dupdiff' instead of 'setdiff'
-                        if (length(unique(sign(.SDcols))) != 1L) stop(".SDcols is numeric but has both +ve and -ve indices")
-                        if (any(is.na(.SDcols)) || any(abs(.SDcols)>ncol(x)) || any(abs(.SDcols)<1L)) stop(".SDcols is numeric but out of bounds (or NA)")
-                        if (colm) ansvars = dupdiff(names(x)[-.SDcols], bynames) else ansvars = names(x)[.SDcols]
-                        ansvals = if (colm) setdiff(seq_along(names(x)), c(as.integer(.SDcols), which(names(x) %chin% bynames))) else as.integer(.SDcols)
-                    } else {
-                        if (!is.character(.SDcols)) stop(".SDcols should be column numbers or names")
-                        if (any(is.na(.SDcols)) || any(!.SDcols %chin% names(x))) stop("Some items of .SDcols are not column names (or are NA)")
-                        if (colm) ansvars = setdiff(setdiff(names(x), .SDcols), bynames) else ansvars = .SDcols
-                        # dups = FALSE here. DT[, .SD, .SDcols=c("x", "x")] again doesn't really help with which 'x' to keep (and if '-' which x to remove)
-                        ansvals = chmatch(ansvars, names(x))
-                    }
-                }
-                # fix for long standing FR/bug, #495 and #484
-                allcols = c(names(x), xdotprefix, names(i), idotprefix)
-                if ( length(othervars <- setdiff(intersect(av, allcols), c(bynames, ansvars))) ) {
-                    # we've a situation like DT[, c(sum(V1), lapply(.SD, mean)), by=., .SDcols=...] or
-                    # DT[, lapply(.SD, function(x) x *v1), by=, .SDcols=...] etc.,
-                    ansvars = union(ansvars, othervars)
-                    ansvals = chmatch(ansvars, names(x))
-                }
-                # .SDcols might include grouping columns if users wants that, but normally we expect user not to include them in .SDcols
-            } else {
-                if (!missing(.SDcols)) warning("This j doesn't use .SD but .SDcols has been supplied. Ignoring .SDcols. See ?data.table.")
-                allcols = c(names(x), xdotprefix, names(i), idotprefix)
-                ansvars = setdiff(intersect(av,allcols), bynames)
-                if (verbose) cat("Detected that j uses these columns:",if (!length(ansvars)) "<none>" else paste(ansvars,collapse=","),"\n")
-                # using a few named columns will be faster
-                # Consider:   DT[,max(diff(date)),by=list(month=month(date))]
-                # and:        DT[,lapply(.SD,sum),by=month(date)]
-                # We don't want date in .SD in the latter, but we do in the former; hence the union() above.
-                ansvals = chmatch(ansvars, names(x))
+      lhs = NULL
+      newnames = NULL
+      suppPrint = identity
+      if (length(av) && av[1L] == ":=") {
+        if (identical(attr(x,".data.table.locked"),TRUE)) stop(".SD is locked. Using := in .SD's j is reserved for possible future use; a tortuously flexible way to modify by group. Use := in j directly to modify by group by reference.")
+        suppPrint <- function(x) { .global$print=address(x); x }
+        # Suppress print when returns ok not on error, bug #2376. Thanks to: http://stackoverflow.com/a/13606880/403310
+        # All appropriate returns following this point are wrapped; i.e. return(suppPrint(x)).
+
+        if (is.null(names(jsub))) {
+          # regular LHS:=RHS usage, or `:=`(...) with no named arguments (an error)
+          # `:=`(LHS,RHS) is valid though, but more because can't see how to detect that, than desire
+          if (length(jsub)!=3L) stop("In `:=`(col1=val1, col2=val2, ...) form, all arguments must be named.")
+          lhs = jsub[[2]]
+          jsub = jsub[[3]]
+          if (is.name(lhs)) {
+            lhs = as.character(lhs)
+          } else {
+            # e.g. (MyVar):= or get("MyVar"):=
+            lhs = eval(lhs, parent.frame(), parent.frame())
+          }
+        } else {
+          # `:=`(c2=1L,c3=2L,...)
+          lhs = names(jsub)[-1]
+          if (any(lhs=="")) stop("In `:=`(col1=val1, col2=val2, ...) form, all arguments must be named.")
+          names(jsub)=""
+          jsub[[1]]=as.name("list")
+        }
+        av = all.vars(jsub,TRUE)
+        if (!is.atomic(lhs)) stop("LHS of := must be a symbol, or an atomic vector (column names or positions).")
+        if (is.character(lhs))
+          m = chmatch(lhs,names(x))
+        else if (is.numeric(lhs)) {
+          m = as.integer(lhs)
+          if (any(m<1L | ncol(x)<m)) stop("LHS of := appears to be column positions but are outside [1,ncol] range. New columns can only be added by name.")
+          lhs = names(x)[m]
+        } else
+          stop("LHS of := isn't column names ('character') or positions ('integer' or 'numeric')")
+        if (all(!is.na(m))) {
+          # updates by reference to existing columns
+          cols = as.integer(m)
+          newnames=NULL
+          if (identical(irows, integer())) {
+            # Empty integer() means no rows e.g. logical i with only FALSE and NA
+            # got converted to empty integer() by the which() above
+            # Short circuit and do-nothing since columns already exist. If some don't
+            # exist then for consistency with cases where irows is non-empty, we need to create
+            # them of the right type and populate with NA.  Which will happen via the regular
+            # alternative branches below, to cover #759.
+            # We need this short circuit at all just for convenience. Otherwise users may need to
+            # fix errors in their RHS when called on empty edge cases, even when the result won't be
+            # used anyway (so it would be annoying to have to fix it.)
+            if (verbose) {
+              cat("No rows match i. No new columns to add so not evaluating RHS of :=\n")
+              cat("Assigning to 0 row subset of",nrow(x),"rows\n")
             }
-            # if (!length(ansvars)) Leave ansvars empty. Important for test 607.
-
-            # TODO remove as (m)get is now folded in above.
-            # added 'mget' - fix for #994
-            if (any(c("get", "mget") %chin% av)) {
-                if (verbose) {
-                    cat("'(m)get' found in j. ansvars being set to all columns. Use .SDcols or a single j=eval(macro) instead. Both will detect the columns used which is important for efficiency.\nOld:", paste(ansvars,collapse=","),"\n")
-                    # get('varname') is too difficult to detect which columns are used in general
-                    # eval(macro) column names are detected via the  if jsub[[1]]==eval switch earlier above.
-                }
-
-                # Do not include z in .SD when dt[, z := {.SD; get("x")}, .SDcols = "y"] (#2326, #2338)
-                if (is.call(jsub) && length(jsub[[1L]]) == 1L && jsub[[1L]] == ":=" && is.symbol(jsub[[2L]])) {
-                  jsub_lhs_symbol <- as.character(jsub[[2L]])
-                  if (jsub_lhs_symbol %chin% othervars) {
-                    ansvars <- setdiff(ansvars, jsub_lhs_symbol)
-                  }
-                }
-                if (length(ansvars)) othervars = ansvars # #1744 fix
-                allcols = c(names(x), xdotprefix, names(i), idotprefix)
-                ansvars = setdiff(allcols,bynames) # fix for bug #5443
-                ansvals = chmatch(ansvars, names(x))
-                if (length(othervars)) othervars = setdiff(ansvars, othervars) # #1744 fix
-                if (verbose) cat("New:",paste(ansvars,collapse=","),"\n")
+            .global$print = address(x)
+            return(invisible(x))
+          }
+        } else {
+          # Adding new column(s). TO DO: move after the first eval in case the jsub has an error.
+          newnames=setdiff(lhs,names(x))
+          m[is.na(m)] = ncol(x)+seq_len(length(newnames))
+          cols = as.integer(m)
+          if ((ok<-selfrefok(x,verbose))==0L)   # ok==0 so no warning when loaded from disk (-1) [-1 considered TRUE by R]
+            warning("Invalid .internal.selfref detected and fixed by taking a (shallow) copy of the data.table so that := can add this new column by reference. At an earlier point, this data.table has been copied by R (or been created manually using structure() or similar). Avoid key<-, names<- and attr<- which in R currently (and oddly) may copy the whole data.table. Use set* syntax instead to avoid copying: ?set, ?setnames and ?setattr. Also, in R<=v3.0.2, list(DT1,DT2) copied the entire DT1 and DT2 (R's list() used to copy named objects); please upgrade to R>v3.0.2 if that is biting. If this message doesn't help, please report to datatable-help so the root cause can be fixed.")
+          if ((ok<1L) || (truelength(x) < ncol(x)+length(newnames))) {
+            DT = x  # in case getOption contains "ncol(DT)" as it used to.  TODO: warn and then remove
+            n = length(newnames) + eval(getOption("datatable.alloccol"))  # TODO: warn about expressions and then drop the eval()
+            # i.e. reallocate at the size as if the new columns were added followed by alloc.col().
+            name = substitute(x)
+            if (is.name(name) && ok && verbose) { # && NAMED(x)>0 (TO DO)    # ok here includes -1 (loaded from disk)
+              cat("Growing vector of column pointers from truelength ",truelength(x)," to ",n,". A shallow copy has been taken, see ?alloc.col. Only a potential issue if two variables point to the same data (we can't yet detect that well) and if not you can safely ignore this. To avoid this message you could alloc.col() first, deep copy first using copy(), wrap with suppressWarnings() or increase the 'datatable.alloccol' option.\n")
+              # Verbosity should not issue warnings, so cat rather than warning.
+              # TO DO: Add option 'datatable.pedantic' to turn on warnings like this.
+
+              # TO DO ... comments moved up from C ...
+              # Note that the NAMED(dt)>1 doesn't work because .Call
+              # always sets to 2 (see R-ints), it seems. Work around
+              # may be possible but not yet working. When the NAMED test works, we can drop allocwarn argument too
+              # because that's just passed in as FALSE from [<- where we know `*tmp*` isn't really NAMED=2.
+              # Note also that this growing will happen for missing columns assigned NULL, too. But so rare, we
+              # don't mind.
             }
-
-            lhs = NULL
-            newnames = NULL
-            suppPrint = identity
-            if (length(av) && av[1L] == ":=") {
-                if (identical(attr(x,".data.table.locked"),TRUE)) stop(".SD is locked. Using := in .SD's j is reserved for possible future use; a tortuously flexible way to modify by group. Use := in j directly to modify by group by reference.")
-                suppPrint <- function(x) { .global$print=address(x); x }
-                # Suppress print when returns ok not on error, bug #2376. Thanks to: http://stackoverflow.com/a/13606880/403310
-                # All appropriate returns following this point are wrapped; i.e. return(suppPrint(x)).
-
-                if (is.null(names(jsub))) {
-                    # regular LHS:=RHS usage, or `:=`(...) with no named arguments (an error)
-                    # `:=`(LHS,RHS) is valid though, but more because can't see how to detect that, than desire
-                    if (length(jsub)!=3L) stop("In `:=`(col1=val1, col2=val2, ...) form, all arguments must be named.")
-                    lhs = jsub[[2]]
-                    jsub = jsub[[3]]
-                    if (is.name(lhs)) {
-                        lhs = as.character(lhs)
-                    } else {
-                        # e.g. (MyVar):= or get("MyVar"):=
-                        lhs = eval(lhs, parent.frame(), parent.frame())
-                    }
-                } else {
-                    # `:=`(c2=1L,c3=2L,...)
-                    lhs = names(jsub)[-1]
-                    if (any(lhs=="")) stop("In `:=`(col1=val1, col2=val2, ...) form, all arguments must be named.")
-                    names(jsub)=""
-                    jsub[[1]]=as.name("list")
-                }
-                av = all.vars(jsub,TRUE)
-                if (!is.atomic(lhs)) stop("LHS of := must be a symbol, or an atomic vector (column names or positions).")
-                if (is.character(lhs))
-                    m = chmatch(lhs,names(x))
-                else if (is.numeric(lhs)) {
-                    m = as.integer(lhs)
-                    if (any(m<1L | ncol(x)<m)) stop("LHS of := appears to be column positions but are outside [1,ncol] range. New columns can only be added by name.")
-                    lhs = names(x)[m]
-                } else
-                    stop("LHS of := isn't column names ('character') or positions ('integer' or 'numeric')")
-                if (all(!is.na(m))) {
-                    # updates by reference to existing columns
-                    cols = as.integer(m)
-                    newnames=NULL
-                    if (identical(irows, integer())) {
-                        # Empty integer() means no rows e.g. logical i with only FALSE and NA
-                        # got converted to empty integer() by the which() above
-                        # Short circuit and do-nothing since columns already exist. If some don't
-                        # exist then for consistency with cases where irows is non-empty, we need to create
-                        # them of the right type and populate with NA.  Which will happen via the regular
-                        # alternative branches below, to cover #759.
-                        # We need this short circuit at all just for convenience. Otherwise users may need to
-                        # fix errors in their RHS when called on empty edge cases, even when the result won't be
-                        # used anyway (so it would be annoying to have to fix it.)
-                        if (verbose) {
-                            cat("No rows match i. No new columns to add so not evaluating RHS of :=\n")
-                            cat("Assigning to 0 row subset of",nrow(x),"rows\n")
-                        }
-                        .global$print = address(x)
-                        return(invisible(x))
-                    }
-                } else {
-                    # Adding new column(s). TO DO: move after the first eval in case the jsub has an error.
-                    newnames=setdiff(lhs,names(x))
-                    m[is.na(m)] = ncol(x)+seq_len(length(newnames))
-                    cols = as.integer(m)
-                    if ((ok<-selfrefok(x,verbose))==0L)   # ok==0 so no warning when loaded from disk (-1) [-1 considered TRUE by R]
-                        warning("Invalid .internal.selfref detected and fixed by taking a (shallow) copy of the data.table so that := can add this new column by reference. At an earlier point, this data.table has been copied by R (or been created manually using structure() or similar). Avoid key<-, names<- and attr<- which in R currently (and oddly) may copy the whole data.table. Use set* syntax instead to avoid copying: ?set, ?setnames and ?setattr. Also, in R<=v3.0.2, list(DT1,DT2) copied the entire DT1 and DT2 (R's list() used to copy named objects); please upgrade to R>v3.0.2 if that is biting. If this message doesn't help, please report to datatable-help so the root cause can be fixed.")
-                    if ((ok<1L) || (truelength(x) < ncol(x)+length(newnames))) {
-                        DT = x  # in case getOption contains "ncol(DT)" as it used to.  TODO: warn and then remove
-                        n = length(newnames) + eval(getOption("datatable.alloccol"))  # TODO: warn about expressions and then drop the eval()
-                        # i.e. reallocate at the size as if the new columns were added followed by alloc.col().
-                        name = substitute(x)
-                        if (is.name(name) && ok && verbose) { # && NAMED(x)>0 (TO DO)    # ok here includes -1 (loaded from disk)
-                            cat("Growing vector of column pointers from truelength ",truelength(x)," to ",n,". A shallow copy has been taken, see ?alloc.col. Only a potential issue if two variables point to the same data (we can't yet detect that well) and if not you can safely ignore this. To avoid this message you could alloc.col() first, deep copy first using copy(), wrap with suppressWarnings() or increase the 'datatable.alloccol' option.\n")
-                            # Verbosity should not issue warnings, so cat rather than warning.
-                            # TO DO: Add option 'datatable.pedantic' to turn on warnings like this.
-
-                            # TO DO ... comments moved up from C ...
-                            # Note that the NAMED(dt)>1 doesn't work because .Call
-                            # always sets to 2 (see R-ints), it seems. Work around
-                            # may be possible but not yet working. When the NAMED test works, we can drop allocwarn argument too
-                            # because that's just passed in as FALSE from [<- where we know `*tmp*` isn't really NAMED=2.
-                            # Note also that this growing will happen for missing columns assigned NULL, too. But so rare, we
-                            # don't mind.
-                        }
-                        alloc.col(x, n, verbose=verbose)   # always assigns to calling scope; i.e. this scope
-                        if (is.name(name)) {
-                            assign(as.character(name),x,parent.frame(),inherits=TRUE)
-                        } else if (is.call(name) && (name[[1L]] == "$" || name[[1L]] == "[[") && is.name(name[[2L]])) {
-                            k = eval(name[[2L]], parent.frame(), parent.frame())
-                            if (is.list(k)) {
-                                origj = j = if (name[[1L]] == "$") as.character(name[[3L]]) else eval(name[[3L]], parent.frame(), parent.frame())
-                                if (is.character(j)) {
-                                    if (length(j)!=1L) stop("L[[i]][,:=] syntax only valid when i is length 1, but it's length %d",length(j))
-                                    j = match(j, names(k))
-                                    if (is.na(j)) stop("Item '",origj,"' not found in names of list")
-                                }
-                                .Call(Csetlistelt,k,as.integer(j), x)
-                            } else if (is.environment(k) && exists(as.character(name[[3L]]), k)) {
-                                assign(as.character(name[[3L]]), x, k, inherits=FALSE)
-                            }
-                        } # TO DO: else if env$<- or list$<-
-                    }
+            alloc.col(x, n, verbose=verbose)   # always assigns to calling scope; i.e. this scope
+            if (is.name(name)) {
+              assign(as.character(name),x,parent.frame(),inherits=TRUE)
+            } else if (is.call(name) && (name[[1L]] == "$" || name[[1L]] == "[[") && is.name(name[[2L]])) {
+              k = eval(name[[2L]], parent.frame(), parent.frame())
+              if (is.list(k)) {
+                origj = j = if (name[[1L]] == "$") as.character(name[[3L]]) else eval(name[[3L]], parent.frame(), parent.frame())
+                if (is.character(j)) {
+                  if (length(j)!=1L) stop("L[[i]][,:=] syntax only valid when i is length 1, but it's length %d",length(j))
+                  j = match(j, names(k))
+                  if (is.na(j)) stop("Item '",origj,"' not found in names of list")
                 }
-            }
+                .Call(Csetlistelt,k,as.integer(j), x)
+              } else if (is.environment(k) && exists(as.character(name[[3L]]), k)) {
+                assign(as.character(name[[3L]]), x, k, inherits=FALSE)
+              }
+            } # TO DO: else if env$<- or list$<-
+          }
         }
+      }
+    }
 
-        if (length(ansvars)) {
-            w = ansvals
-            if (length(rightcols) && missing(by)) {
-                w[ w %in% rightcols ] = NA
-            }
-            # patch for #1615. Allow 'x.' syntax. Only useful during join op when x's join col needs to be used.
-            # Note that I specifically have not implemented x[y, aa, on=c(aa="bb")] to refer to x's join column
-            # as well because x[i, col] == x[i][, col] will not be TRUE anymore..
-            if ( any(xdotprefixvals <- ansvars %in% xdotprefix)) {
-                w[xdotprefixvals] = chmatch(ansvars[xdotprefixvals], xdotprefix)
-                xdotcols = TRUE
-            }
-            if (!any(wna <- is.na(w))) {
-                xcols = w
-                xcolsAns = seq_along(ansvars)
-                icols = icolsAns = integer()
-            } else {
-                if (!length(leftcols)) stop("column(s) not found: ", paste(ansvars[wna],collapse=", "))
-                xcols = w[!wna]
-                xcolsAns = which(!wna)
-                ivars = names(i)
-                ivars[leftcols] = names(x)[rightcols]
-                w2 = chmatch(ansvars[wna], ivars)
-                if (any(w2na <- is.na(w2))) {
-                    ivars = paste("i.",ivars,sep="")
-                    ivars[leftcols] = names(i)[leftcols]
-                    w2[w2na] = chmatch(ansvars[wna][w2na], ivars)
-                    if (any(w2na <- is.na(w2))) {
-                        ivars[leftcols] = paste("i.",ivars[leftcols],sep="")
-                        w2[w2na] = chmatch(ansvars[wna][w2na], ivars)
-                        if (any(w2na <- is.na(w2))) stop("column(s) not found: ", paste(ansvars[wna][w2na],sep=", "))
-                    }
-                }
-                icols = w2
-                icolsAns = which(wna)
-            }
+    if (length(ansvars)) {
+      w = ansvals
+      if (length(rightcols) && missing(by)) {
+        w[ w %in% rightcols ] = NA
+      }
+      # patch for #1615. Allow 'x.' syntax. Only useful during join op when x's join col needs to be used.
+      # Note that I specifically have not implemented x[y, aa, on=c(aa="bb")] to refer to x's join column
+      # as well because x[i, col] == x[i][, col] will not be TRUE anymore..
+      if ( any(xdotprefixvals <- ansvars %in% xdotprefix)) {
+        w[xdotprefixvals] = chmatch(ansvars[xdotprefixvals], xdotprefix)
+        xdotcols = TRUE
+      }
+      if (!any(wna <- is.na(w))) {
+        xcols = w
+        xcolsAns = seq_along(ansvars)
+        icols = icolsAns = integer()
+      } else {
+        if (!length(leftcols)) stop("column(s) not found: ", paste(ansvars[wna],collapse=", "))
+        xcols = w[!wna]
+        xcolsAns = which(!wna)
+        ivars = names(i)
+        ivars[leftcols] = names(x)[rightcols]
+        w2 = chmatch(ansvars[wna], ivars)
+        if (any(w2na <- is.na(w2))) {
+          ivars = paste("i.",ivars,sep="")
+          ivars[leftcols] = names(i)[leftcols]
+          w2[w2na] = chmatch(ansvars[wna][w2na], ivars)
+          if (any(w2na <- is.na(w2))) {
+            ivars[leftcols] = paste("i.",ivars[leftcols],sep="")
+            w2[w2na] = chmatch(ansvars[wna][w2na], ivars)
+            if (any(w2na <- is.na(w2))) stop("column(s) not found: ", paste(ansvars[wna][w2na],sep=", "))
+          }
         }
-    }  # end of  if !missing(j)
+        icols = w2
+        icolsAns = which(wna)
+      }
+    }
+  }  # end of  if !missing(j)
 
-    SDenv = new.env(parent=parent.frame())
-    # taking care of warnings for posixlt type, #646
-    SDenv$strptime <- function(x, ...) {
+  SDenv = new.env(parent=parent.frame())
+  # taking care of warnings for posixlt type, #646
+  SDenv$strptime <- function(x, ...) {
     warning("POSIXlt column type detected and converted to POSIXct. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
     as.POSIXct(base::strptime(x, ...))
-    }
-
-    # hash=TRUE (the default) does seem better as expected using e.g. test 645.  TO DO experiment with 'size' argument
-    if (missing(by) || (!byjoin && !length(byval))) {
-        # No grouping: 'by' = missing | NULL | character() | "" | list()
-        # Considered passing a one-group to dogroups but it doesn't do the recycling of i within group, that's done here
-        if (length(ansvars)) {
-            # TO DO: port more of this to C
-            ans = vector("list", length(ansvars))
-            if (length(i) && length(icols)) {
-                if (allLen1 && allGrp1 && (is.na(nomatch) || !any(f__==0L))) {   # nomatch=0 should drop rows in i that have no match
-                    for (s in seq_along(icols)) {
-                        target = icolsAns[s]
-                        source = icols[s]
-                        ans[[target]] = i[[source]]
-                        if (address(ans[[target]]) == address(i[[source]])) ans[[target]] = copy(ans[[target]])
-                    }
-                } else {
-            ii = rep.int(if(allGrp1) seq_len(nrow(i)) else indices__, len__)
-                    for (s in seq_along(icols)) {
-                        target = icolsAns[s]
-                        source = icols[s]
-                        ans[[target]] = .Call(CsubsetVector,i[[source]],ii)  # i.e. i[[source]][ii]
-                    }
-                }
-            }
-            if (is.null(irows)) {
-                for (s in seq_along(xcols)) {  # xcols means non-join x columns, since join columns come from i
-                    target = xcolsAns[s]
-                    source = xcols[s]
-                    ans[[target]] = x[[source]]
-                    # Temp fix for #921 - skip COPY until after evaluating 'jval' (scroll down).
-                    # Unless 'with=FALSE' - can not be expressions but just column names.
-                    if (!with && address(ans[[target]]) == address(x[[source]]))
-                        ans[[target]] = copy(ans[[target]])
-                    else ans[[target]] = ans[[target]]
-                }
-            } else {
-                for (s in seq_along(xcols)) {
-                    target = xcolsAns[s]
-                    source = xcols[s]
-                    ans[[target]] = .Call(CsubsetVector,x[[source]],irows)   # i.e. x[[source]][irows], but guaranteed new memory even for singleton logicals from R 3.1.0
-                }
-            }
-            # the address==address is a temp fix for R >= 3.1.0. TO DO: allow shallow copy here, then copy only when user uses :=
-            # or set* on the result by using NAMED/REFCNT on columns, with warning if they copy. Since then, even foo = DT$b
-            # would cause the next set or := to copy that column (so the warning is needed). To tackle that, we could have our
-            # own DT.NAMED attribute, perhaps.
-            # Or keep the rule that [.data.table always returns new memory, and create view() or view= as well, maybe cleaner.
-
-            setattr(ans, "names", ansvars)
-            if (haskey(x)) {
-                keylen = which.first(!key(x) %chin% ansvars)-1L
-                if (is.na(keylen)) keylen = length(key(x))
-                len = length(rightcols)
-                # fix for #1268, #1704, #1766 and #1823
-                chk = if (len && !missing(on)) !identical(head(key(x), len), names(on)) else FALSE
-                if ( (keylen>len || chk) && !.Call(CisOrderedSubset, irows, nrow(x))) {
-                    keylen = if (!chk) len else 0L # fix for #1268
-                }
-                if (keylen && ((is.data.table(i) && haskey(i)) || is.logical(i) || (.Call(CisOrderedSubset, irows, nrow(x)) && ((roll == FALSE) || length(irows) == 1L)))) # see #1010. don't set key when i has no key, but irows is ordered and roll != FALSE
-                    setattr(ans,"sorted",head(key(x),keylen))
-            }
-            setattr(ans, "class", class(x)) # fix for #5296
-            setattr(ans, "row.names", .set_row_names(nrow(ans)))
-
-            if (!with || missing(j)) return(alloc.col(ans))
-
-            SDenv$.SDall = ans
-            SDenv$.SD = if (!length(othervars)) SDenv$.SDall else shallow(SDenv$.SDall, setdiff(ansvars, othervars))
-            SDenv$.N = nrow(SDenv$.SD)
+  }
 
+  # hash=TRUE (the default) does seem better as expected using e.g. test 645.  TO DO experiment with 'size' argument
+  if (missing(by) || (!byjoin && !length(byval))) {
+    # No grouping: 'by' = missing | NULL | character() | "" | list()
+    # Considered passing a one-group to dogroups but it doesn't do the recycling of i within group, that's done here
+    if (length(ansvars)) {
+      # TO DO: port more of this to C
+      ans = vector("list", length(ansvars))
+      if (length(i) && length(icols)) {
+        if (allLen1 && allGrp1 && (is.na(nomatch) || !any(f__==0L))) {   # nomatch=0 should drop rows in i that have no match
+          for (s in seq_along(icols)) {
+            target = icolsAns[s]
+            source = icols[s]
+            ans[[target]] = i[[source]]
+            if (address(ans[[target]]) == address(i[[source]])) ans[[target]] = copy(ans[[target]])
+          }
         } else {
-            SDenv$.SDall = SDenv$.SD = null.data.table()   # no columns used by j so .SD can be empty. Only needs to exist so that we can rely on it being there when locking it below for example. If .SD were used by j, of course then xvars would be the columns and we wouldn't be in this leaf.
-            SDenv$.N = if (is.null(irows)) nrow(x) else length(irows) * !identical(suppressWarnings(max(irows)), 0L)
-            # Fix for #963.
-            # When irows is integer(0), length(irows) = 0 will result in 0 (as expected).
-            # Binary search can return all 0 irows when none of the input matches. Instead of doing all(irows==0L) (previous method), which has to allocate a logical vector the size of irows, we can make use of 'max'. If max is 0, we return 0. The condition where only some irows > 0 won't occur.
-        }
-        # Temp fix for #921. Allocate `.I` only if j-expression uses it.
-        SDenv$.I = if (!missing(j) && use.I) seq_len(SDenv$.N) else 0L
-        SDenv$.GRP = 1L
-        setattr(SDenv$.SD,".data.table.locked",TRUE)   # used to stop := modifying .SD via j=f(.SD), bug#1727. The more common case of j=.SD[,subcol:=1] was already caught when jsub is inspected for :=.
-        setattr(SDenv$.SDall,".data.table.locked",TRUE)
-        lockBinding(".SD",SDenv)
-        lockBinding(".SDall",SDenv)
-        lockBinding(".N",SDenv)
-        lockBinding(".I",SDenv)
-        lockBinding(".GRP",SDenv)
-        for (ii in ansvars) assign(ii, SDenv$.SDall[[ii]], SDenv)
-        # Since .SD is inside SDenv, alongside its columns as variables, R finds .SD symbol more quickly, if used.
-        # There isn't a copy of the columns here, the xvar symbols point to the SD columns (copy-on-write).
-
-        if (is.name(jsub) && is.null(lhs) && !exists(jsubChar<-as.character(jsub), SDenv, inherits=FALSE)) {
-            stop("j (the 2nd argument inside [...]) is a single symbol but column name '",jsubChar,"' is not found. Perhaps you intended DT[,..",jsubChar,"] or DT[,",jsubChar,",with=FALSE]. This difference to data.frame is deliberate and explained in FAQ 1.1.")
+      ii = rep.int(if(allGrp1) seq_len(nrow(i)) else indices__, len__)
+          for (s in seq_along(icols)) {
+            target = icolsAns[s]
+            source = icols[s]
+            ans[[target]] = .Call(CsubsetVector,i[[source]],ii)  # i.e. i[[source]][ii]
+          }
         }
-
-        jval = eval(jsub, SDenv, parent.frame())
-        # copy 'jval' when required
-        # More speedup - only check + copy if irows is NULL
-        # Temp fix for #921 - check address and copy *after* evaluating 'jval'
-        if (is.null(irows)) {
-            if (!is.list(jval)) { # performance improvement when i-arg is S4, but not list, #1438, Thanks @DCEmilberg.
-                jcpy = address(jval) %in% vapply_1c(SDenv$.SD, address) # %chin% errors when RHS is list()
-                if (jcpy) jval = copy(jval)
-            } else if (address(jval) == address(SDenv$.SD)) {
-                jval = copy(jval)
-            } else if ( length(jcpy <- which(vapply_1c(jval, address) %in% vapply_1c(SDenv, address))) ) {
-                for (jidx in jcpy) jval[[jidx]] = copy(jval[[jidx]])
-            } else if (is.call(jsub) && jsub[[1L]] == "get" && is.list(jval)) {
-                jval = copy(jval) # fix for #1212
-            }
-        } else {
-            if (is.data.table(jval)) {
-                setattr(jval, '.data.table.locked', NULL) # fix for #1341
-                if (!truelength(jval)) alloc.col(jval)
-            }
+      }
+      if (is.null(irows)) {
+        for (s in seq_along(xcols)) {  # xcols means non-join x columns, since join columns come from i
+          target = xcolsAns[s]
+          source = xcols[s]
+          ans[[target]] = x[[source]]
+          # Temp fix for #921 - skip COPY until after evaluating 'jval' (scroll down).
+          # Unless 'with=FALSE' - can not be expressions but just column names.
+          if (!with && address(ans[[target]]) == address(x[[source]]))
+            ans[[target]] = copy(ans[[target]])
+          else ans[[target]] = ans[[target]]
         }
-        if (!is.null(lhs)) {
-            # TODO?: use set() here now that it can add new columns. Then remove newnames and alloc logic above.
-            .Call(Cassign,x,irows,cols,newnames,jval,verbose)
-            return(suppPrint(x))
+      } else {
+        for (s in seq_along(xcols)) {
+          target = xcolsAns[s]
+          source = xcols[s]
+          ans[[target]] = .Call(CsubsetVector,x[[source]],irows)   # i.e. x[[source]][irows], but guaranteed new memory even for singleton logicals from R 3.1.0
         }
-        if ((is.call(jsub) && is.list(jval) && jsub[[1L]] != "get" && !is.object(jval)) || !missing(by)) {
-            # is.call: selecting from a list column should return list
-            # is.object: for test 168 and 168.1 (S4 object result from ggplot2::qplot). Just plain list results should result in data.table
-
-            # Fix for #813 and #758. Ex: DT[c(FALSE, FALSE), list(integer(0), y)]
-            # where DT = data.table(x=1:2, y=3:4) should return an empty data.table!!
-            if (!is.null(irows) && (identical(irows, integer(0)) || all(irows %in% 0L))) ## TODO: any way to not check all 'irows' values?
-                if (is.atomic(jval)) jval = jval[0L] else jval = lapply(jval, `[`, 0L)
-            if (is.atomic(jval)) {
-                setattr(jval,"names",NULL)
-                jval = data.table(jval) # TO DO: should this be setDT(list(jval)) instead?
-            } else {
-                if (is.null(jvnames)) jvnames=names(jval)
-                # avoid copy if all vectors are already of same lengths, use setDT
-                lenjval = vapply(jval, length, 0L)
-                if (any(lenjval != lenjval[1L])) {
-                    jval = as.data.table.list(jval)   # does the vector expansion to create equal length vectors
-                    jvnames = jvnames[lenjval != 0L]  # fix for #1477
-                } else setDT(jval)
-            }
-            if (is.null(jvnames)) jvnames = character(length(jval)-length(bynames))
-            ww = which(jvnames=="")
-            if (any(ww)) jvnames[ww] = paste("V",ww,sep="")
-            setnames(jval, jvnames)
+      }
+      # the address==address is a temp fix for R >= 3.1.0. TO DO: allow shallow copy here, then copy only when user uses :=
+      # or set* on the result by using NAMED/REFCNT on columns, with warning if they copy. Since then, even foo = DT$b
+      # would cause the next set or := to copy that column (so the warning is needed). To tackle that, we could have our
+      # own DT.NAMED attribute, perhaps.
+      # Or keep the rule that [.data.table always returns new memory, and create view() or view= as well, maybe cleaner.
+
+      setattr(ans, "names", ansvars)
+      if (haskey(x)) {
+        keylen = which.first(!key(x) %chin% ansvars)-1L
+        if (is.na(keylen)) keylen = length(key(x))
+        len = length(rightcols)
+        # fix for #1268, #1704, #1766 and #1823
+        chk = if (len && !missing(on)) !identical(head(key(x), len), names(on)) else FALSE
+        if ( (keylen>len || chk) && !.Call(CisOrderedSubset, irows, nrow(x))) {
+          keylen = if (!chk) len else 0L # fix for #1268
         }
+        if (keylen && ((is.data.table(i) && haskey(i)) || is.logical(i) || (.Call(CisOrderedSubset, irows, nrow(x)) && ((roll == FALSE) || length(irows) == 1L)))) # see #1010. don't set key when i has no key, but irows is ordered and roll != FALSE
+          setattr(ans,"sorted",head(key(x),keylen))
+      }
+      setattr(ans, "class", class(x)) # fix for #5296
+      setattr(ans, "row.names", .set_row_names(nrow(ans)))
 
-        # fix for bug #5114 from GSee's - .data.table.locked=TRUE.   # TO DO: more efficient way e.g. address==address (identical will do that but then proceed to deep compare if !=, wheras we want just to stop?)
-        # Commented as it's taken care of above, along with #921 fix. Kept here for the bug fix info and TO DO.
-        # if (identical(jval, SDenv$.SD)) return(copy(jval))
+      if (!with || missing(j)) return(alloc.col(ans))
 
-        if (is.data.table(jval)) {
-            setattr(jval, 'class', class(x)) # fix for #5296
-            if (haskey(x) && all(key(x) %chin% names(jval)) && suppressWarnings(is.sorted(jval, by=key(x))))  # TO DO: perhaps this usage of is.sorted should be allowed internally then (tidy up and make efficient)
-                setattr(jval, 'sorted', key(x))
-        }
-        return(jval)
-    }
-
-    ###########################################################################
-    # Grouping ...
-    ###########################################################################
-
-    o__ = integer()
-    if (".N" %chin% ansvars) stop("The column '.N' can't be grouped because it conflicts with the special .N variable. Try setnames(DT,'.N','N') first.")
-    if (".I" %chin% ansvars) stop("The column '.I' can't be grouped because it conflicts with the special .I variable. Try setnames(DT,'.I','I') first.")
-    SDenv$.iSD = NULL  # null.data.table()
-    SDenv$.xSD = NULL  # null.data.table() - introducing for FR #2693 and Gabor's post on fixing for FAQ 2.8
-
-    assign("print", function(x,...){base::print(x,...);NULL}, SDenv)
-    # Now ggplot2 returns data from print, we need a way to throw it away otherwise j accumulates the result
-
-    SDenv$.SDall = SDenv$.SD = null.data.table()  # e.g. test 607. Grouping still proceeds even though no .SD e.g. grouping key only tables, or where j consists of .N only
-    SDenv$.N = as.integer(0)     # not 0L for the reson on next line :
-    SDenv$.GRP = as.integer(1)   # oddly using 1L doesn't work reliably here! Possible R bug? TO DO: create reproducible example and report. To reproduce change to 1L and run test.data.table, test 780 fails. The assign seems ineffective and a previous value for .GRP from a previous test is retained, despite just creating a new SDenv.
-
-    if (byjoin) {
-        # The groupings come instead from each row of the i data.table.
-        # Much faster for a few known groups vs a 'by' for all followed by a subset
-        if (!is.data.table(i)) stop("logicial error. i is not data.table, but mult='all' and 'by'=.EACHI")
-        byval = i
-        bynames = if (missing(on)) head(key(x),length(leftcols)) else names(on)
-        allbyvars = NULL
-        bysameorder = haskey(i) || (is.sorted(f__) && ((roll == FALSE) || length(f__) == 1L)) # Fix for #1010
-        ##  'av' correct here ??  *** TO DO ***
-        xjisvars = intersect(av, names(x)[rightcols])  # no "x." for xvars.
-        # if 'get' is in 'av' use all cols in 'i', fix for bug #5443
-        # added 'mget' - fix for #994
-        jisvars = if (any(c("get", "mget") %chin% av)) names(i) else intersect(gsub("^i[.]","", setdiff(av, xjisvars)), names(i))
-        # JIS (non join cols) but includes join columns too (as there are named in i)
-        if (length(jisvars)) {
-            tt = min(nrow(i),1L)
-            SDenv$.iSD = i[tt,jisvars,with=FALSE]
-            for (ii in jisvars) {
-                assign(ii, SDenv$.iSD[[ii]], SDenv)
-                assign(paste("i.",ii,sep=""), SDenv$.iSD[[ii]], SDenv)
-            }
-        }
+      SDenv$.SDall = ans
+      SDenv$.SD = if (!length(othervars)) SDenv$.SDall else shallow(SDenv$.SDall, setdiff(ansvars, othervars))
+      SDenv$.N = nrow(SDenv$.SD)
 
     } else {
-        # Find the groups, using 'byval' ...
-        if (missing(by)) stop("Internal error, by is missing")
-
-        if (length(byval) && length(byval[[1]])) {
-            if (!bysameorder) {
-                if (verbose) {last.started.at=proc.time()[3];cat("Finding groups using forderv ... ");flush.console()}
-                o__ = forderv(byval, sort=!missing(keyby), retGrp=TRUE)
-                # The sort= argument is called sortStr at C level. It's just about saving the sort of unique strings at
-                # C level for efficiency (cgroup vs csort) when by= not keyby=. All other types are always sorted. Getting
-                # orginal order below is the part that retains original order. Passing sort=TRUE here always won't change any
-                # result at all (tested and confirmed to double check), it'll just make by= slower when there's a large
-                # number of unique strings. It must be TRUE when keyby= though, since the key is just marked afterwards.
-                # forderv() returns empty integer() if already ordered to save allocating 1:xnrow
-                bysameorder = orderedirows && !length(o__)
-                if (verbose) {
-                    cat(round(proc.time()[3]-last.started.at, 3), "sec\n")
-                    last.started.at=proc.time()[3]
-                    cat("Finding group sizes from the positions (can be avoided to save RAM) ... ")
-                    flush.console()  # for windows
-                }
-                f__ = attr(o__, "starts")
-                len__ = uniqlengths(f__, xnrow)
-                if (verbose) { cat(round(proc.time()[3]-last.started.at, 3), "sec\n");flush.console()}
-                if (!bysameorder && missing(keyby)) {
-                    # TO DO: lower this into forder.c
-                    if (verbose) {last.started.at=proc.time()[3];cat("Getting back original order ... ");flush.console()}
-                    firstofeachgroup = o__[f__]
-                    if (length(origorder <- forderv(firstofeachgroup))) {
-                        f__ = f__[origorder]
-                        len__ = len__[origorder]
-                    }
-                    if (verbose) {cat(round(proc.time()[3]-last.started.at, 3), "sec\n")}
-                }
-                if (!orderedirows && !length(o__)) o__ = 1:xnrow  # temp fix.  TODO: revist orderedirows
-            } else {
-                if (verbose) {last.started.at=proc.time()[3];cat("Finding groups using uniqlist ... ");flush.console()}
-                f__ = uniqlist(byval)
-                if (verbose) {
-                    cat(round(proc.time()[3]-last.started.at, 3), "sec\n")
-                    last.started.at=proc.time()[3]
-                    cat("Finding group sizes from the positions (can be avoided to save RAM) ... ")
-                    flush.console()  # for windows
-                }
-                len__ = uniqlengths(f__, xnrow)
-                # TO DO: combine uniqlist and uniquelengths into one call.  Or, just set len__ to NULL when dogroups infers that.
-                if (verbose) { cat(round(proc.time()[3]-last.started.at, 3), "sec\n");flush.console() }
-            }
-        } else {
-            f__=NULL
-            len__=0L
-            bysameorder=TRUE   # for test 724
-        }
-        # TO DO: allow secondary keys to be stored, then we see if our by matches one, if so use it, and no need to sort again. TO DO: document multiple keys.
-    }
-    alloc = if (length(len__)) seq_len(max(len__)) else 0L
-    SDenv$.I = alloc
-    if (length(xcols)) {
-        #  TODO add: if (length(alloc)==nrow(x)) stop("There is no need to deep copy x in this case")
-        SDenv$.SDall = .Call(CsubsetDT,x,alloc,xcols)    # must be deep copy when largest group is a subset
-        if (xdotcols) setattr(SDenv$.SDall, 'names', ansvars[seq_along(xcols)]) # now that we allow 'x.' prefix in 'j'
-        SDenv$.SD = if (!length(othervars)) SDenv$.SDall else shallow(SDenv$.SDall, setdiff(ansvars, othervars))
-    }
-    if (nrow(SDenv$.SDall)==0L) {
-        setattr(SDenv$.SDall,"row.names",c(NA_integer_,0L))
-        setattr(SDenv$.SD,"row.names",c(NA_integer_,0L))
-    }
-    # .set_row_names() basically other than not integer() for 0 length, otherwise dogroups has no [1] to modify to -.N
+      SDenv$.SDall = SDenv$.SD = null.data.table()   # no columns used by j so .SD can be empty. Only needs to exist so that we can rely on it being there when locking it below for example. If .SD were used by j, of course then xvars would be the columns and we wouldn't be in this leaf.
+      SDenv$.N = if (is.null(irows)) nrow(x) else length(irows) * !identical(suppressWarnings(max(irows)), 0L)
+      # Fix for #963.
+      # When irows is integer(0), length(irows) = 0 will result in 0 (as expected).
+      # Binary search can return all 0 irows when none of the input matches. Instead of doing all(irows==0L) (previous method), which has to allocate a logical vector the size of irows, we can make use of 'max'. If max is 0, we return 0. The condition where only some irows > 0 won't occur.
+    }
+    # Temp fix for #921. Allocate `.I` only if j-expression uses it.
+    SDenv$.I = if (!missing(j) && use.I) seq_len(SDenv$.N) else 0L
+    SDenv$.GRP = 1L
     setattr(SDenv$.SD,".data.table.locked",TRUE)   # used to stop := modifying .SD via j=f(.SD), bug#1727. The more common case of j=.SD[,subcol:=1] was already caught when jsub is inspected for :=.
     setattr(SDenv$.SDall,".data.table.locked",TRUE)
     lockBinding(".SD",SDenv)
     lockBinding(".SDall",SDenv)
     lockBinding(".N",SDenv)
-    lockBinding(".GRP",SDenv)
     lockBinding(".I",SDenv)
-    lockBinding(".iSD",SDenv)
-
-    GForce = FALSE
-    if ( getOption("datatable.optimize")>=1 && (is.call(jsub) || (is.name(jsub) && as.character(jsub) %chin% c(".SD",".N"))) ) {  # Ability to turn off if problems or to benchmark the benefit
-        # Optimization to reduce overhead of calling lapply over and over for each group
-        ansvarsnew = setdiff(ansvars, othervars)
-        oldjsub = jsub
-        funi = 1L # Fix for #985
-        # convereted the lapply(.SD, ...) to a function and used below, easier to implement FR #2722 then.
-        .massageSD <- function(jsub) {
-            txt = as.list(jsub)[-1L]
-            if (length(names(txt))>1L) .Call(Csetcharvec, names(txt), 2L, "")  # fixes bug #4839
-            fun = txt[[2L]]
-            if (is.call(fun) && fun[[1L]]=="function") {
-                # Fix for #2381: added SDenv$.SD to 'eval' to take care of cases like: lapply(.SD, function(x) weighted.mean(x, bla)) where "bla" is a column in DT
-                # http://stackoverflow.com/questions/13441868/data-table-and-stratified-means
-                # adding this does not compromise in speed (that is, not any lesser than without SDenv$.SD)
-                # replaced SDenv$.SD to SDenv to deal with Bug #5007 reported by Ricardo (Nice catch!)
-                thisfun = paste("..FUN", funi, sep="") # Fix for #985
-                assign(thisfun,eval(fun, SDenv, SDenv), SDenv)  # to avoid creating function() for each column of .SD
-                lockBinding(thisfun,SDenv)
-                txt[[1L]] = as.name(thisfun)
-            } else {
-                if (is.character(fun)) fun = as.name(fun)
-                txt[[1L]] = fun
-            }
-            ans = vector("list",length(ansvarsnew)+1L)
-            ans[[1L]] = as.name("list")
-            for (ii in seq_along(ansvarsnew)) {
-                txt[[2L]] = as.name(ansvarsnew[ii])
-                ans[[ii+1L]] = as.call(txt)
-            }
-            jsub = as.call(ans)  # important no names here
-            jvnames = ansvarsnew      # but here instead
-            list(jsub, jvnames)
-            # It may seem inefficient to constuct a potentially long expression. But, consider calling
-            # lapply 100000 times. The C code inside lapply does the LCONS stuff anyway, every time it
-            # is called, involving small memory allocations.
-            # The R level lapply calls as.list which needs a shallow copy.
-            # lapply also does a setAttib of names (duplicating the same names over and over again
-            # for each group) which is terrible for our needs. We replace all that with a
-            # (ok, long, but not huge in memory terms) list() which is primitive (so avoids symbol
-            # lookup), and the eval() inside dogroups hardly has to do anything. All this results in
-            # overhead minimised. We don't need to worry about the env passed to the eval in a possible
-            # lapply replacement, or how to pass ... efficiently to it.
-            # Plus we optimize lapply first, so that mean() can be optimized too as well, next.
+    lockBinding(".GRP",SDenv)
+    for (ii in ansvars) assign(ii, SDenv$.SDall[[ii]], SDenv)
+    # Since .SD is inside SDenv, alongside its columns as variables, R finds .SD symbol more quickly, if used.
+    # There isn't a copy of the columns here, the xvar symbols point to the SD columns (copy-on-write).
+
+    if (is.name(jsub) && is.null(lhs) && !exists(jsubChar<-as.character(jsub), SDenv, inherits=FALSE)) {
+      stop("j (the 2nd argument inside [...]) is a single symbol but column name '",jsubChar,"' is not found. Perhaps you intended DT[,..",jsubChar,"] or DT[,",jsubChar,",with=FALSE]. This difference to data.frame is deliberate and explained in FAQ 1.1.")
+    }
+
+    jval = eval(jsub, SDenv, parent.frame())
+    # copy 'jval' when required
+    # More speedup - only check + copy if irows is NULL
+    # Temp fix for #921 - check address and copy *after* evaluating 'jval'
+    if (is.null(irows)) {
+      if (!is.list(jval)) { # performance improvement when i-arg is S4, but not list, #1438, Thanks @DCEmilberg.
+        jcpy = address(jval) %in% vapply_1c(SDenv$.SD, address) # %chin% errors when RHS is list()
+        if (jcpy) jval = copy(jval)
+      } else if (address(jval) == address(SDenv$.SD)) {
+        jval = copy(jval)
+      } else if ( length(jcpy <- which(vapply_1c(jval, address) %in% vapply_1c(SDenv, address))) ) {
+        for (jidx in jcpy) jval[[jidx]] = copy(jval[[jidx]])
+      } else if (is.call(jsub) && jsub[[1L]] == "get" && is.list(jval)) {
+        jval = copy(jval) # fix for #1212
+      }
+    } else {
+      if (is.data.table(jval)) {
+        setattr(jval, '.data.table.locked', NULL) # fix for #1341
+        if (!truelength(jval)) alloc.col(jval)
+      }
+    }
+    if (!is.null(lhs)) {
+      # TODO?: use set() here now that it can add new columns. Then remove newnames and alloc logic above.
+      .Call(Cassign,x,irows,cols,newnames,jval,verbose)
+      return(suppPrint(x))
+    }
+    if ((is.call(jsub) && is.list(jval) && jsub[[1L]] != "get" && !is.object(jval)) || !missing(by)) {
+      # is.call: selecting from a list column should return list
+      # is.object: for test 168 and 168.1 (S4 object result from ggplot2::qplot). Just plain list results should result in data.table
+
+      # Fix for #813 and #758. Ex: DT[c(FALSE, FALSE), list(integer(0), y)]
+      # where DT = data.table(x=1:2, y=3:4) should return an empty data.table!!
+      if (!is.null(irows) && (identical(irows, integer(0)) || all(irows %in% 0L))) ## TODO: any way to not check all 'irows' values?
+        if (is.atomic(jval)) jval = jval[0L] else jval = lapply(jval, `[`, 0L)
+      if (is.atomic(jval)) {
+        setattr(jval,"names",NULL)
+        jval = data.table(jval) # TO DO: should this be setDT(list(jval)) instead?
+      } else {
+        if (is.null(jvnames)) jvnames=names(jval)
+        # avoid copy if all vectors are already of same lengths, use setDT
+        lenjval = vapply(jval, length, 0L)
+        if (any(lenjval != lenjval[1L])) {
+          jval = as.data.table.list(jval)   # does the vector expansion to create equal length vectors
+          jvnames = jvnames[lenjval != 0L]  # fix for #1477
+        } else setDT(jval)
+      }
+      if (is.null(jvnames)) jvnames = character(length(jval)-length(bynames))
+      ww = which(jvnames=="")
+      if (any(ww)) jvnames[ww] = paste("V",ww,sep="")
+      setnames(jval, jvnames)
+    }
+
+    # fix for bug #5114 from GSee's - .data.table.locked=TRUE.   # TO DO: more efficient way e.g. address==address (identical will do that but then proceed to deep compare if !=, wheras we want just to stop?)
+    # Commented as it's taken care of above, along with #921 fix. Kept here for the bug fix info and TO DO.
+    # if (identical(jval, SDenv$.SD)) return(copy(jval))
+
+    if (is.data.table(jval)) {
+      setattr(jval, 'class', class(x)) # fix for #5296
+      if (haskey(x) && all(key(x) %chin% names(jval)) && suppressWarnings(is.sorted(jval, by=key(x))))  # TO DO: perhaps this usage of is.sorted should be allowed internally then (tidy up and make efficient)
+        setattr(jval, 'sorted', key(x))
+    }
+    return(jval)
+  }
+
+  ###########################################################################
+  # Grouping ...
+  ###########################################################################
+
+  o__ = integer()
+  if (".N" %chin% ansvars) stop("The column '.N' can't be grouped because it conflicts with the special .N variable. Try setnames(DT,'.N','N') first.")
+  if (".I" %chin% ansvars) stop("The column '.I' can't be grouped because it conflicts with the special .I variable. Try setnames(DT,'.I','I') first.")
+  SDenv$.iSD = NULL  # null.data.table()
+  SDenv$.xSD = NULL  # null.data.table() - introducing for FR #2693 and Gabor's post on fixing for FAQ 2.8
+
+  assign("print", function(x,...){base::print(x,...);NULL}, SDenv)
+  # Now ggplot2 returns data from print, we need a way to throw it away otherwise j accumulates the result
+
+  SDenv$.SDall = SDenv$.SD = null.data.table()  # e.g. test 607. Grouping still proceeds even though no .SD e.g. grouping key only tables, or where j consists of .N only
+  SDenv$.N = as.integer(0)     # not 0L for the reson on next line :
+  SDenv$.GRP = as.integer(1)   # oddly using 1L doesn't work reliably here! Possible R bug? TO DO: create reproducible example and report. To reproduce change to 1L and run test.data.table, test 780 fails. The assign seems ineffective and a previous value for .GRP from a previous test is retained, despite just creating a new SDenv.
+
+  if (byjoin) {
+    # The groupings come instead from each row of the i data.table.
+    # Much faster for a few known groups vs a 'by' for all followed by a subset
+    if (!is.data.table(i)) stop("logicial error. i is not data.table, but mult='all' and 'by'=.EACHI")
+    byval = i
+    bynames = if (missing(on)) head(key(x),length(leftcols)) else names(on)
+    allbyvars = NULL
+    bysameorder = haskey(i) || (is.sorted(f__) && ((roll == FALSE) || length(f__) == 1L)) # Fix for #1010
+    ##  'av' correct here ??  *** TO DO ***
+    xjisvars = intersect(av, names(x)[rightcols])  # no "x." for xvars.
+    # if 'get' is in 'av' use all cols in 'i', fix for bug #5443
+    # added 'mget' - fix for #994
+    jisvars = if (any(c("get", "mget") %chin% av)) names(i) else intersect(gsub("^i[.]","", setdiff(av, xjisvars)), names(i))
+    # JIS (non join cols) but includes join columns too (as there are named in i)
+    if (length(jisvars)) {
+      tt = min(nrow(i),1L)
+      SDenv$.iSD = i[tt,jisvars,with=FALSE]
+      for (ii in jisvars) {
+        assign(ii, SDenv$.iSD[[ii]], SDenv)
+        assign(paste("i.",ii,sep=""), SDenv$.iSD[[ii]], SDenv)
+      }
+    }
+
+  } else {
+    # Find the groups, using 'byval' ...
+    if (missing(by)) stop("Internal error, by is missing")
+
+    if (length(byval) && length(byval[[1]])) {
+      if (!bysameorder) {
+        if (verbose) {last.started.at=proc.time()[3];cat("Finding groups using forderv ... ");flush.console()}
+        o__ = forderv(byval, sort=!missing(keyby), retGrp=TRUE)
+        # The sort= argument is called sortStr at C level. It's just about saving the sort of unique strings at
+        # C level for efficiency (cgroup vs csort) when by= not keyby=. All other types are always sorted. Getting
+        # orginal order below is the part that retains original order. Passing sort=TRUE here always won't change any
+        # result at all (tested and confirmed to double check), it'll just make by= slower when there's a large
+        # number of unique strings. It must be TRUE when keyby= though, since the key is just marked afterwards.
+        # forderv() returns empty integer() if already ordered to save allocating 1:xnrow
+        bysameorder = orderedirows && !length(o__)
+        if (verbose) {
+          cat(round(proc.time()[3]-last.started.at, 3), "sec\n")
+          last.started.at=proc.time()[3]
+          cat("Finding group sizes from the positions (can be avoided to save RAM) ... ")
+          flush.console()  # for windows
         }
-        if (is.name(jsub)) {
-            if (jsub == ".SD") {
-                jsub = as.call(c(quote(list), lapply(ansvarsnew, as.name)))
-                jvnames = ansvarsnew
-            }
-        } else {
-            if ( length(jsub) == 3L && (jsub[[1L]] == "[" || jsub[[1L]] == "head" || jsub[[1L]] == "tail") && jsub[[2L]] == ".SD" && (is.numeric(jsub[[3L]]) || jsub[[3L]] == ".N") ) {
-                # optimise .SD[1] or .SD[2L]. Not sure how to test .SD[a] as to whether a is numeric/integer or a data.table, yet.
-                jsub = as.call(c(quote(list), lapply(ansvarsnew, function(x) { jsub[[2L]] = as.name(x); jsub })))
-                jvnames = ansvarsnew
-            } else if (jsub[[1L]]=="lapply" && jsub[[2L]]==".SD" && length(xcols)) {
-                deparse_ans = .massageSD(jsub)
-                jsub = deparse_ans[[1L]]
-                jvnames = deparse_ans[[2L]]
-            } else if (jsub[[1L]] == "c" && length(jsub) > 1L) {
-                # TODO, TO DO: raise the checks for 'jvnames' earlier (where jvnames is set by checking 'jsub') and set 'jvnames' already.
-                # FR #2722 is just about optimisation of j=c(.N, lapply(.SD, .)) that is taken care of here.
-                # FR #735 tries to optimise j-expressions of the form c(...) as long as ... contains
-                # 1) lapply(.SD, ...), 2) simply .SD or .SD[..], 3) .N, 4) list(...) and 5) functions that normally return a single value*
-                # On 5)* the IMPORTANT point to note is that things that are not wrapped within "list(...)" should *always*
-                # return length 1 output for us to optimise. Else, there's no equivalent to optimising c(...) to list(...) AFAICT.
-                # One issue could be that these functions (e.g., mean) can be "re-defined" by the OP to produce a length > 1 output
-                # Of course this is worrying too much though. If the issue comes up, we'll just remove the relevant optimisations.
-                # For now, we optimise all functions mentioned in 'optfuns' below.
-                optfuns = c("max", "min", "mean", "length", "sum", "median", "sd", "var")
-                is_valid = TRUE
-                any_SD = FALSE
-                jsubl = as.list.default(jsub)
-                oldjvnames = jvnames
-                jvnames = NULL           # TODO: not let jvnames grow, maybe use (number of lapply(.SD, .))*lenght(ansvarsnew) + other jvars ?? not straightforward.
-                # Fix for #744. Don't use 'i' in for-loops. It masks the 'i' from the input!!
-                for (i_ in 2:length(jsubl)) {
-                    this = jsub[[i_]]
-                    if (is.name(this)) {
-                        if (this == ".SD") { # optimise '.SD' alone
-                            any_SD = TRUE
-                            jsubl[[i_]] = lapply(ansvarsnew, as.name)
-                            jvnames = c(jvnames, ansvarsnew)
-                        } else if (this == ".N") {
-                            # don't optimise .I in c(.SD, .I), it's length can be > 1
-                            # only c(.SD, list(.I)) should be optimised!! .N is always length 1.
-                            jvnames = c(jvnames, gsub("^[.]([N])$", "\\1", this))
-                        } else {
-                            # jvnames = c(jvnames, if (is.null(names(jsubl))) "" else names(jsubl)[i_])
-                            is_valid=FALSE
-                            break
-                        }
-                    } else if (is.call(this)) {
-                        if (this[[1L]] == "lapply" && this[[2L]] == ".SD" && length(xcols)) {
-                            any_SD = TRUE
-                            deparse_ans = .massageSD(this)
-                            funi = funi + 1L # Fix for #985
-                            jsubl[[i_]] = as.list(deparse_ans[[1L]][-1L]) # just keep the '.' from list(.)
-                            jvnames = c(jvnames, deparse_ans[[2L]])
-                        } else if (this[[1]] == "list") {
-                            # also handle c(lapply(.SD, sum), list()) - silly, yes, but can happen
-                            if (length(this) > 1L) {
-                                jl__ = as.list(jsubl[[i_]])[-1L] # just keep the '.' from list(.)
-                                jn__ = if (is.null(names(jl__))) rep("", length(jl__)) else names(jl__)
-                                idx  = unlist(lapply(jl__, function(x) is.name(x) && x == ".I"))
-                                if (any(idx)) jn__[idx & (jn__ == "")] = "I"
-                                jvnames = c(jvnames, jn__)
-                                jsubl[[i_]] = jl__
-                            }
-                        } else if (is.call(this) && length(this) > 1L && as.character(this[[1L]]) %in% optfuns) {
-                            jvnames = c(jvnames, if (is.null(names(jsubl))) "" else names(jsubl)[i_])
-                        } else if ( length(this) == 3L && (this[[1L]] == "[" || this[[1L]] == "head") &&
-                                        this[[2L]] == ".SD" && (is.numeric(this[[3L]]) || this[[3L]] == ".N") ) {
-                            # optimise .SD[1] or .SD[2L]. Not sure how to test .SD[a] as to whether a is numeric/integer or a data.table, yet.
-                            any_SD = TRUE
-                            jsubl[[i_]] = lapply(ansvarsnew, function(x) { this[[2L]] = as.name(x); this })
-                            jvnames = c(jvnames, ansvarsnew)
-                        } else if (any(all.vars(this) == ".SD")) {
-                            # TODO, TO DO: revisit complex cases (as illustrated below)
-                            # complex cases like DT[, c(.SD[x>1], .SD[J(.)], c(.SD), a + .SD, lapply(.SD, sum)), by=grp]
-                            # hard to optimise such cases (+ difficulty in counting exact columns and therefore names). revert back to no optimisation.
-                            is_valid=FALSE
-                            break
-                        } else { # just to be sure that any other case (I've overlooked) runs smoothly, without optimisation
-                            # TO DO, TODO: maybe a message/warning here so that we can catch the overlooked cases, if any?
-                            is_valid=FALSE
-                            break
-                        }
-                    } else {
-                        is_valid = FALSE
-                        break
-                    }
-                }
-                if (!is_valid || !any_SD) { # restore if c(...) doesn't contain lapply(.SD, ..) or if it's just invalid
-                    jvnames = oldjvnames           # reset jvnames
-                    jsub = oldjsub                 # reset jsub
-                    jsubl = as.list.default(jsubl) # reset jsubl
-                } else {
-                    setattr(jsubl, 'names', NULL)
-                    jsub = as.call(unlist(jsubl, use.names=FALSE))
-                    jsub[[1L]] = quote(list)
-                }
-            }
+        f__ = attr(o__, "starts")
+        len__ = uniqlengths(f__, xnrow)
+        if (verbose) { cat(round(proc.time()[3]-last.started.at, 3), "sec\n");flush.console()}
+        if (!bysameorder && missing(keyby)) {
+          # TO DO: lower this into forder.c
+          if (verbose) {last.started.at=proc.time()[3];cat("Getting back original order ... ");flush.console()}
+          firstofeachgroup = o__[f__]
+          if (length(origorder <- forderv(firstofeachgroup))) {
+            f__ = f__[origorder]
+            len__ = len__[origorder]
+          }
+          if (verbose) {cat(round(proc.time()[3]-last.started.at, 3), "sec\n")}
         }
+        if (!orderedirows && !length(o__)) o__ = 1:xnrow  # temp fix.  TODO: revist orderedirows
+      } else {
+        if (verbose) {last.started.at=proc.time()[3];cat("Finding groups using uniqlist ... ");flush.console()}
+        f__ = uniqlist(byval)
         if (verbose) {
-            if (!identical(oldjsub, jsub))
-                cat("lapply optimization changed j from '",deparse(oldjsub),"' to '",deparse(jsub,width.cutoff=200L),"'\n",sep="")
-            else
-                cat("lapply optimization is on, j unchanged as '",deparse(jsub,width.cutoff=200L),"'\n",sep="")
+          cat(round(proc.time()[3]-last.started.at, 3), "sec\n")
+          last.started.at=proc.time()[3]
+          cat("Finding group sizes from the positions (can be avoided to save RAM) ... ")
+          flush.console()  # for windows
         }
-        dotN <- function(x) if (is.name(x) && x == ".N") TRUE else FALSE # For #5760
-        # FR #971, GForce kicks in on all subsets, no joins yet. Although joins could work with
-        # nomatch=0L even now.. but not switching it on yet, will deal it separately.
-        if (getOption("datatable.optimize")>=2 && !is.data.table(i) && !byjoin && length(f__) && !length(lhs)) {
-            if (!length(ansvars) && !use.I) {
-                GForce = FALSE
-                if ( (is.name(jsub) && jsub == ".N") || (is.call(jsub) && length(jsub)==2L && jsub[[1L]] == "list" && jsub[[2L]] == ".N") ) {
-                    GForce = TRUE
-                    if (verbose) cat("GForce optimized j to '",deparse(jsub,width.cutoff=200L),"'\n",sep="")
-                }
+        len__ = uniqlengths(f__, xnrow)
+        # TO DO: combine uniqlist and uniquelengths into one call.  Or, just set len__ to NULL when dogroups infers that.
+        if (verbose) { cat(round(proc.time()[3]-last.started.at, 3), "sec\n");flush.console() }
+      }
+    } else {
+      f__=NULL
+      len__=0L
+      bysameorder=TRUE   # for test 724
+    }
+    # TO DO: allow secondary keys to be stored, then we see if our by matches one, if so use it, and no need to sort again. TO DO: document multiple keys.
+  }
+  alloc = if (length(len__)) seq_len(max(len__)) else 0L
+  SDenv$.I = alloc
+  if (length(xcols)) {
+    #  TODO add: if (length(alloc)==nrow(x)) stop("There is no need to deep copy x in this case")
+    SDenv$.SDall = .Call(CsubsetDT,x,alloc,xcols)    # must be deep copy when largest group is a subset
+    if (xdotcols) setattr(SDenv$.SDall, 'names', ansvars[seq_along(xcols)]) # now that we allow 'x.' prefix in 'j'
+    SDenv$.SD = if (!length(othervars)) SDenv$.SDall else shallow(SDenv$.SDall, setdiff(ansvars, othervars))
+  }
+  if (nrow(SDenv$.SDall)==0L) {
+    setattr(SDenv$.SDall,"row.names",c(NA_integer_,0L))
+    setattr(SDenv$.SD,"row.names",c(NA_integer_,0L))
+  }
+  # .set_row_names() basically other than not integer() for 0 length, otherwise dogroups has no [1] to modify to -.N
+  setattr(SDenv$.SD,".data.table.locked",TRUE)   # used to stop := modifying .SD via j=f(.SD), bug#1727. The more common case of j=.SD[,subcol:=1] was already caught when jsub is inspected for :=.
+  setattr(SDenv$.SDall,".data.table.locked",TRUE)
+  lockBinding(".SD",SDenv)
+  lockBinding(".SDall",SDenv)
+  lockBinding(".N",SDenv)
+  lockBinding(".GRP",SDenv)
+  lockBinding(".I",SDenv)
+  lockBinding(".iSD",SDenv)
+
+  GForce = FALSE
+  if ( getOption("datatable.optimize")>=1 && (is.call(jsub) || (is.name(jsub) && as.character(jsub) %chin% c(".SD",".N"))) ) {  # Ability to turn off if problems or to benchmark the benefit
+    # Optimization to reduce overhead of calling lapply over and over for each group
+    ansvarsnew = setdiff(ansvars, othervars)
+    oldjsub = jsub
+    funi = 1L # Fix for #985
+    # convereted the lapply(.SD, ...) to a function and used below, easier to implement FR #2722 then.
+    .massageSD <- function(jsub) {
+      txt = as.list(jsub)[-1L]
+      if (length(names(txt))>1L) .Call(Csetcharvec, names(txt), 2L, "")  # fixes bug #4839
+      fun = txt[[2L]]
+      if (is.call(fun) && fun[[1L]]=="function") {
+        # Fix for #2381: added SDenv$.SD to 'eval' to take care of cases like: lapply(.SD, function(x) weighted.mean(x, bla)) where "bla" is a column in DT
+        # http://stackoverflow.com/questions/13441868/data-table-and-stratified-means
+        # adding this does not compromise in speed (that is, not any lesser than without SDenv$.SD)
+        # replaced SDenv$.SD to SDenv to deal with Bug #5007 reported by Ricardo (Nice catch!)
+        thisfun = paste("..FUN", funi, sep="") # Fix for #985
+        assign(thisfun,eval(fun, SDenv, SDenv), SDenv)  # to avoid creating function() for each column of .SD
+        lockBinding(thisfun,SDenv)
+        txt[[1L]] = as.name(thisfun)
+      } else {
+        if (is.character(fun)) fun = as.name(fun)
+        txt[[1L]] = fun
+      }
+      ans = vector("list",length(ansvarsnew)+1L)
+      ans[[1L]] = as.name("list")
+      for (ii in seq_along(ansvarsnew)) {
+        txt[[2L]] = as.name(ansvarsnew[ii])
+        ans[[ii+1L]] = as.call(txt)
+      }
+      jsub = as.call(ans)  # important no names here
+      jvnames = ansvarsnew      # but here instead
+      list(jsub, jvnames)
+      # It may seem inefficient to constuct a potentially long expression. But, consider calling
+      # lapply 100000 times. The C code inside lapply does the LCONS stuff anyway, every time it
+      # is called, involving small memory allocations.
+      # The R level lapply calls as.list which needs a shallow copy.
+      # lapply also does a setAttib of names (duplicating the same names over and over again
+      # for each group) which is terrible for our needs. We replace all that with a
+      # (ok, long, but not huge in memory terms) list() which is primitive (so avoids symbol
+      # lookup), and the eval() inside dogroups hardly has to do anything. All this results in
+      # overhead minimised. We don't need to worry about the env passed to the eval in a possible
+      # lapply replacement, or how to pass ... efficiently to it.
+      # Plus we optimize lapply first, so that mean() can be optimized too as well, next.
+    }
+    if (is.name(jsub)) {
+      if (jsub == ".SD") {
+        jsub = as.call(c(quote(list), lapply(ansvarsnew, as.name)))
+        jvnames = ansvarsnew
+      }
+    } else {
+      if ( length(jsub) == 3L && (jsub[[1L]] == "[" || jsub[[1L]] == "head" || jsub[[1L]] == "tail") && jsub[[2L]] == ".SD" && (is.numeric(jsub[[3L]]) || jsub[[3L]] == ".N") ) {
+        # optimise .SD[1] or .SD[2L]. Not sure how to test .SD[a] as to whether a is numeric/integer or a data.table, yet.
+        jsub = as.call(c(quote(list), lapply(ansvarsnew, function(x) { jsub[[2L]] = as.name(x); jsub })))
+        jvnames = ansvarsnew
+      } else if (jsub[[1L]]=="lapply" && jsub[[2L]]==".SD" && length(xcols)) {
+        deparse_ans = .massageSD(jsub)
+        jsub = deparse_ans[[1L]]
+        jvnames = deparse_ans[[2L]]
+      } else if (jsub[[1L]] == "c" && length(jsub) > 1L) {
+        # TODO, TO DO: raise the checks for 'jvnames' earlier (where jvnames is set by checking 'jsub') and set 'jvnames' already.
+        # FR #2722 is just about optimisation of j=c(.N, lapply(.SD, .)) that is taken care of here.
+        # FR #735 tries to optimise j-expressions of the form c(...) as long as ... contains
+        # 1) lapply(.SD, ...), 2) simply .SD or .SD[..], 3) .N, 4) list(...) and 5) functions that normally return a single value*
+        # On 5)* the IMPORTANT point to note is that things that are not wrapped within "list(...)" should *always*
+        # return length 1 output for us to optimise. Else, there's no equivalent to optimising c(...) to list(...) AFAICT.
+        # One issue could be that these functions (e.g., mean) can be "re-defined" by the OP to produce a length > 1 output
+        # Of course this is worrying too much though. If the issue comes up, we'll just remove the relevant optimisations.
+        # For now, we optimise all functions mentioned in 'optfuns' below.
+        optfuns = c("max", "min", "mean", "length", "sum", "median", "sd", "var")
+        is_valid = TRUE
+        any_SD = FALSE
+        jsubl = as.list.default(jsub)
+        oldjvnames = jvnames
+        jvnames = NULL           # TODO: not let jvnames grow, maybe use (number of lapply(.SD, .))*lenght(ansvarsnew) + other jvars ?? not straightforward.
+        # Fix for #744. Don't use 'i' in for-loops. It masks the 'i' from the input!!
+        for (i_ in 2:length(jsubl)) {
+          this = jsub[[i_]]
+          if (is.name(this)) {
+            if (this == ".SD") { # optimise '.SD' alone
+              any_SD = TRUE
+              jsubl[[i_]] = lapply(ansvarsnew, as.name)
+              jvnames = c(jvnames, ansvarsnew)
+            } else if (this == ".N") {
+              # don't optimise .I in c(.SD, .I), it's length can be > 1
+              # only c(.SD, list(.I)) should be optimised!! .N is always length 1.
+              jvnames = c(jvnames, gsub("^[.]([N])$", "\\1", this))
             } else {
-                # Apply GForce
-                gfuns = c("sum", "prod", "mean", "median", "var", "sd", ".N", "min", "max", "head", "last", "first", "tail", "[") # added .N for #5760
-                .ok <- function(q) {
-                    if (dotN(q)) return(TRUE) # For #5760
-                    cond = is.call(q) && as.character(q[[1L]]) %chin% gfuns && !is.call(q[[2L]])
-                    ans  = cond && (length(q)==2 || identical("na",substring(names(q)[3L],1,2)))
-                    if (identical(ans, TRUE)) return(ans)
-                    ans = cond && length(q)==3 && ( as.character(q[[1]]) %chin% c("head", "tail") &&
-                                                         (identical(q[[3]], 1) || identical(q[[3]], 1L)) ||
-                                                    as.character(q[[1]]) %chin% "[" && is.numeric(q[[3]]) &&
-                                                        length(q[[3]])==1 && q[[3]]>0 )
-                    if (is.na(ans)) ans=FALSE
-                    ans
-                }
-                if (jsub[[1L]]=="list") {
-                    GForce = TRUE
-                    for (ii in seq_along(jsub)[-1L]) if (!.ok(jsub[[ii]])) GForce = FALSE
-                } else GForce = .ok(jsub)
-                if (GForce) {
-                    if (jsub[[1L]]=="list")
-                        for (ii in seq_along(jsub)[-1L]) {
-                            if (dotN(jsub[[ii]])) next; # For #5760
-                            jsub[[ii]][[1L]] = as.name(paste("g", jsub[[ii]][[1L]], sep=""))
-                            if (length(jsub[[ii]])==3) jsub[[ii]][[3]] = eval(jsub[[ii]][[3]], parent.frame())  # tests 1187.2 & 1187.4
-                        }
-                    else {
-                        jsub[[1L]] = as.name(paste("g", jsub[[1L]], sep=""))
-                        if (length(jsub)==3) jsub[[3]] = eval(jsub[[3]], parent.frame())   # tests 1187.3 & 1187.5
-                    }
-                    if (verbose) cat("GForce optimized j to '",deparse(jsub,width.cutoff=200),"'\n",sep="")
-                } else if (verbose) cat("GForce is on, left j unchanged\n");
-            }
-        }
-        if (!GForce && !is.name(jsub)) {
-            # Still do the old speedup for mean, for now
-            nomeanopt=FALSE  # to be set by .optmean() using <<- inside it
-            oldjsub = jsub
-            if (jsub[[1L]]=="list") {
-                for (ii in seq_along(jsub)[-1L]) {
-                    if (dotN(jsub[[ii]])) next; # For #5760
-                    if (is.call(jsub[[ii]]) && jsub[[ii]][[1L]]=="mean")
-                        jsub[[ii]] = .optmean(jsub[[ii]])
-                }
-            } else if (jsub[[1L]]=="mean") {
-                jsub = .optmean(jsub)
-            }
-            if (nomeanopt) {
-                warning("Unable to optimize call to mean() and could be very slow. You must name 'na.rm' like that otherwise if you do mean(x,TRUE) the TRUE is taken to mean 'trim' which is the 2nd argument of mean. 'trim' is not yet optimized.",immediate.=TRUE)
+              # jvnames = c(jvnames, if (is.null(names(jsubl))) "" else names(jsubl)[i_])
+              is_valid=FALSE
+              break
             }
-            if (verbose) {
-                if (!identical(oldjsub, jsub))
-                    cat("Old mean optimization changed j from '",deparse(oldjsub),"' to '",deparse(jsub,width.cutoff=200),"'\n",sep="")
-                else
-                    cat("Old mean optimization is on, left j unchanged.\n")
+          } else if (is.call(this)) {
+            if (this[[1L]] == "lapply" && this[[2L]] == ".SD" && length(xcols)) {
+              any_SD = TRUE
+              deparse_ans = .massageSD(this)
+              funi = funi + 1L # Fix for #985
+              jsubl[[i_]] = as.list(deparse_ans[[1L]][-1L]) # just keep the '.' from list(.)
+              jvnames = c(jvnames, deparse_ans[[2L]])
+            } else if (this[[1]] == "list") {
+              # also handle c(lapply(.SD, sum), list()) - silly, yes, but can happen
+              if (length(this) > 1L) {
+                jl__ = as.list(jsubl[[i_]])[-1L] # just keep the '.' from list(.)
+                jn__ = if (is.null(names(jl__))) rep("", length(jl__)) else names(jl__)
+                idx  = unlist(lapply(jl__, function(x) is.name(x) && x == ".I"))
+                if (any(idx)) jn__[idx & (jn__ == "")] = "I"
+                jvnames = c(jvnames, jn__)
+                jsubl[[i_]] = jl__
+              }
+            } else if (is.call(this) && length(this) > 1L && as.character(this[[1L]]) %in% optfuns) {
+              jvnames = c(jvnames, if (is.null(names(jsubl))) "" else names(jsubl)[i_])
+            } else if ( length(this) == 3L && (this[[1L]] == "[" || this[[1L]] == "head") &&
+                    this[[2L]] == ".SD" && (is.numeric(this[[3L]]) || this[[3L]] == ".N") ) {
+              # optimise .SD[1] or .SD[2L]. Not sure how to test .SD[a] as to whether a is numeric/integer or a data.table, yet.
+              any_SD = TRUE
+              jsubl[[i_]] = lapply(ansvarsnew, function(x) { this[[2L]] = as.name(x); this })
+              jvnames = c(jvnames, ansvarsnew)
+            } else if (any(all.vars(this) == ".SD")) {
+              # TODO, TO DO: revisit complex cases (as illustrated below)
+              # complex cases like DT[, c(.SD[x>1], .SD[J(.)], c(.SD), a + .SD, lapply(.SD, sum)), by=grp]
+              # hard to optimise such cases (+ difficulty in counting exact columns and therefore names). revert back to no optimisation.
+              is_valid=FALSE
+              break
+            } else { # just to be sure that any other case (I've overlooked) runs smoothly, without optimisation
+              # TO DO, TODO: maybe a message/warning here so that we can catch the overlooked cases, if any?
+              is_valid=FALSE
+              break
             }
-            assign("Cfastmean", Cfastmean, SDenv)
-            assign("mean", base::mean.default, SDenv)
-            # Old comments still here for now ...
-            # Here in case nomeanopt=TRUE or some calls to mean weren't detected somehow. Better but still slow.
-            # Maybe change to :
-            #     assign("mean", fastmean, SDenv)  # neater than the hard work above, but slower
-            # when fastmean can do trim.
+          } else {
+            is_valid = FALSE
+            break
+          }
         }
-    } else if (verbose) {
-        if (getOption("datatable.optimize")<1) cat("All optimizations are turned off\n")
-        else cat("Optimization is on but left j unchanged (single plain symbol): '",deparse(jsub,width.cutoff=200),"'\n",sep="")
-    }
-    if (byjoin) {
-        groups = i
-        grpcols = leftcols # 'leftcols' are the columns in i involved in the join (either head of key(i) or head along i)
-        jiscols = chmatch(jisvars,names(i))  # integer() if there are no jisvars (usually there aren't, advanced feature)
-        xjiscols = chmatch(xjisvars, names(x))
-        SDenv$.xSD = x[min(nrow(i), 1L), xjisvars, with=FALSE]
-        if (!missing(on)) o__ = xo else o__ = integer(0)
-    } else {
-        groups = byval
-        grpcols = seq_along(byval)
-        jiscols = NULL   # NULL rather than integer() is used in C to know when using by
-        xjiscols = NULL
-    }
-    lockBinding(".xSD", SDenv)
-    grporder = o__
-    # for #971, added !GForce. if (GForce) we do it much more (memory) efficiently than subset of order vector below.
-    if (length(irows) && !isTRUE(irows) && !GForce) {
-        # fix for bug #2758. TO DO: provide a better error message
-        if (length(irows) > 1 && length(zo__ <- which(irows == 0)) > 0) stop("i[", zo__[1], "] is 0. While grouping, i=0 is allowed when it's the only value. When length(i) > 1, all i should be > 0.")
-        if (length(o__) && length(irows)!=length(o__)) stop("Internal error: length(irows)!=length(o__)")
-        o__ = if (length(o__)) irows[o__]  # better do this once up front (even though another alloc) than deep repeated branch in dogroups.c
-              else irows
-    } # else grporder is left bound to same o__ memory (no cost of copy)
-    if (is.null(lhs)) cols=NULL
-    if (!length(f__)) {
-        # for consistency of empty case in test 184
-        f__=len__=0L
-    }
-    if (verbose) {last.started.at=proc.time()[3];cat("Making each group and running j (GForce ",GForce,") ... ",sep="");flush.console()}
-    if (GForce) {
-        thisEnv = new.env()  # not parent=parent.frame() so that gsum is found
-        for (ii in ansvars) assign(ii, x[[ii]], thisEnv)
-        assign(".N", len__, thisEnv) # For #5760
-        #fix for #1683
-        if (use.I) assign(".I", seq_len(nrow(x)), thisEnv)
-        ans = gforce(thisEnv, jsub, o__, f__, len__, irows) # irows needed for #971.
-        gi = if (length(o__)) o__[f__] else f__
-        g = lapply(grpcols, function(i) groups[[i]][gi])
-        ans = c(g, ans)
-    } else {
-        ans = .Call(Cdogroups, x, xcols, groups, grpcols, jiscols, xjiscols, grporder, o__, f__, len__, jsub, SDenv, cols, newnames, !missing(on), verbose)
+        if (!is_valid || !any_SD) { # restore if c(...) doesn't contain lapply(.SD, ..) or if it's just invalid
+          jvnames = oldjvnames           # reset jvnames
+          jsub = oldjsub                 # reset jsub
+          jsubl = as.list.default(jsubl) # reset jsubl
+        } else {
+          setattr(jsubl, 'names', NULL)
+          jsub = as.call(unlist(jsubl, use.names=FALSE))
+          jsub[[1L]] = quote(list)
+        }
+      }
     }
-    if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console()}
-    # TO DO: xrows would be a better name for irows: irows means the rows of x that i joins to
-    # Grouping by i: icols the joins columns (might not need), isdcols (the non join i and used by j), all __ are length x
-    # Grouping by by: i is by val, icols NULL, o__ may be subset of x, f__ points to o__ (or x if !length o__)
-    # TO DO: setkey could mark the key whether it is unique or not.
-
-    if (!is.null(lhs)) {
-        if (any(names(x)[cols] %chin% key(x)))
-            setkey(x,NULL)
-        # fixes #1479. Take care of secondary indices, TODO: cleaner way of doing this
-        attrs = attr(x, 'index')
-        skeys = names(attributes(attrs))
-        if (!is.null(skeys)) {
-            hits  = unlist(lapply(paste("__", names(x)[cols], sep=""), function(x) grep(x, skeys)))
-            hits  = skeys[unique(hits)]
-            for (i in seq_along(hits)) setattr(attrs, hits[i], NULL) # does by reference
+    if (verbose) {
+      if (!identical(oldjsub, jsub))
+        cat("lapply optimization changed j from '",deparse(oldjsub),"' to '",deparse(jsub,width.cutoff=200L),"'\n",sep="")
+      else
+        cat("lapply optimization is on, j unchanged as '",deparse(jsub,width.cutoff=200L),"'\n",sep="")
+    }
+    dotN <- function(x) if (is.name(x) && x == ".N") TRUE else FALSE # For #5760
+    # FR #971, GForce kicks in on all subsets, no joins yet. Although joins could work with
+    # nomatch=0L even now.. but not switching it on yet, will deal it separately.
+    if (getOption("datatable.optimize")>=2 && !is.data.table(i) && !byjoin && length(f__) && !length(lhs)) {
+      if (!length(ansvars) && !use.I) {
+        GForce = FALSE
+        if ( (is.name(jsub) && jsub == ".N") || (is.call(jsub) && length(jsub)==2L && jsub[[1L]] == "list" && jsub[[2L]] == ".N") ) {
+          GForce = TRUE
+          if (verbose) cat("GForce optimized j to '",deparse(jsub,width.cutoff=200L),"'\n",sep="")
         }
-        if (!missing(keyby)) {
-            cnames = as.character(bysubl)[-1]
-            if (all(cnames %chin% names(x))) {
-                if (verbose) {last.started.at=proc.time()[3];cat("setkey() after the := with keyby= ... ");flush.console()}
-                setkeyv(x,cnames)  # TO DO: setkey before grouping to get memcpy benefit.
-                if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console()}
+      } else {
+        # Apply GForce
+        gfuns = c("sum", "prod", "mean", "median", "var", "sd", ".N", "min", "max", "head", "last", "first", "tail", "[") # added .N for #5760
+        .ok <- function(q) {
+          if (dotN(q)) return(TRUE) # For #5760
+          cond = is.call(q) && as.character(q[[1L]]) %chin% gfuns && !is.call(q[[2L]])
+          ans  = cond && (length(q)==2 || identical("na",substring(names(q)[3L],1,2)))
+          if (identical(ans, TRUE)) return(ans)
+          ans = cond && length(q)==3 && ( as.character(q[[1]]) %chin% c("head", "tail") &&
+                             (identical(q[[3]], 1) || identical(q[[3]], 1L)) ||
+                          as.character(q[[1]]) %chin% "[" && is.numeric(q[[3]]) &&
+                            length(q[[3]])==1 && q[[3]]>0 )
+          if (is.na(ans)) ans=FALSE
+          ans
+        }
+        if (jsub[[1L]]=="list") {
+          GForce = TRUE
+          for (ii in seq_along(jsub)[-1L]) if (!.ok(jsub[[ii]])) GForce = FALSE
+        } else GForce = .ok(jsub)
+        if (GForce) {
+          if (jsub[[1L]]=="list")
+            for (ii in seq_along(jsub)[-1L]) {
+              if (dotN(jsub[[ii]])) next; # For #5760
+              jsub[[ii]][[1L]] = as.name(paste("g", jsub[[ii]][[1L]], sep=""))
+              if (length(jsub[[ii]])==3) jsub[[ii]][[3]] = eval(jsub[[ii]][[3]], parent.frame())  # tests 1187.2 & 1187.4
             }
-            else warning(":= keyby not straightforward character column names or list() of column names, treating as a by:",paste(cnames,collapse=","),"\n")
+          else {
+            jsub[[1L]] = as.name(paste("g", jsub[[1L]], sep=""))
+            if (length(jsub)==3) jsub[[3]] = eval(jsub[[3]], parent.frame())   # tests 1187.3 & 1187.5
+          }
+          if (verbose) cat("GForce optimized j to '",deparse(jsub,width.cutoff=200),"'\n",sep="")
+        } else if (verbose) cat("GForce is on, left j unchanged\n");
+      }
+    }
+    if (!GForce && !is.name(jsub)) {
+      # Still do the old speedup for mean, for now
+      nomeanopt=FALSE  # to be set by .optmean() using <<- inside it
+      oldjsub = jsub
+      if (jsub[[1L]]=="list") {
+        for (ii in seq_along(jsub)[-1L]) {
+          if (dotN(jsub[[ii]])) next; # For #5760
+          if (is.call(jsub[[ii]]) && jsub[[ii]][[1L]]=="mean")
+            jsub[[ii]] = .optmean(jsub[[ii]])
         }
-        return(suppPrint(x))
-    }
-    if (is.null(ans)) {
-        ans = as.data.table.list(lapply(groups,"[",0L))  # side-effects only such as test 168
-        setnames(ans,seq_along(bynames),bynames)   # TO DO: why doesn't groups have bynames in the first place?
-        return(ans)
-    }
-    setattr(ans,"row.names",.set_row_names(length(ans[[1L]])))
-    setattr(ans,"class",class(x)) # fix for #5296
-    if (is.null(names(ans))) {
-        # Efficiency gain of dropping names has been successful. Ordinarily this will run.
-        if (is.null(jvnames)) jvnames = character(length(ans)-length(bynames))
-        if (length(bynames)+length(jvnames)!=length(ans))
-            stop("Internal error: jvnames is length ",length(jvnames), " but ans is ",length(ans)," and bynames is ", length(bynames))
-        ww = which(jvnames=="")
-        if (any(ww)) jvnames[ww] = paste("V",ww,sep="")
-        setattr(ans, "names", c(bynames, jvnames))
-    } else {
-        setnames(ans,seq_along(bynames),bynames)   # TO DO: reinvestigate bynames flowing from dogroups here and simplify
+      } else if (jsub[[1L]]=="mean") {
+        jsub = .optmean(jsub)
+      }
+      if (nomeanopt) {
+        warning("Unable to optimize call to mean() and could be very slow. You must name 'na.rm' like that otherwise if you do mean(x,TRUE) the TRUE is taken to mean 'trim' which is the 2nd argument of mean. 'trim' is not yet optimized.",immediate.=TRUE)
+      }
+      if (verbose) {
+        if (!identical(oldjsub, jsub))
+          cat("Old mean optimization changed j from '",deparse(oldjsub),"' to '",deparse(jsub,width.cutoff=200),"'\n",sep="")
+        else
+          cat("Old mean optimization is on, left j unchanged.\n")
+      }
+      assign("Cfastmean", Cfastmean, SDenv)
+      assign("mean", base::mean.default, SDenv)
+      # Old comments still here for now ...
+      # Here in case nomeanopt=TRUE or some calls to mean weren't detected somehow. Better but still slow.
+      # Maybe change to :
+      #     assign("mean", fastmean, SDenv)  # neater than the hard work above, but slower
+      # when fastmean can do trim.
+    }
+  } else if (verbose) {
+    if (getOption("datatable.optimize")<1) cat("All optimizations are turned off\n")
+    else cat("Optimization is on but left j unchanged (single plain symbol): '",deparse(jsub,width.cutoff=200),"'\n",sep="")
+  }
+  if (byjoin) {
+    groups = i
+    grpcols = leftcols # 'leftcols' are the columns in i involved in the join (either head of key(i) or head along i)
+    jiscols = chmatch(jisvars,names(i))  # integer() if there are no jisvars (usually there aren't, advanced feature)
+    xjiscols = chmatch(xjisvars, names(x))
+    SDenv$.xSD = x[min(nrow(i), 1L), xjisvars, with=FALSE]
+    if (!missing(on)) o__ = xo else o__ = integer(0)
+  } else {
+    groups = byval
+    grpcols = seq_along(byval)
+    jiscols = NULL   # NULL rather than integer() is used in C to know when using by
+    xjiscols = NULL
+  }
+  lockBinding(".xSD", SDenv)
+  grporder = o__
+  # for #971, added !GForce. if (GForce) we do it much more (memory) efficiently than subset of order vector below.
+  if (length(irows) && !isTRUE(irows) && !GForce) {
+    # fix for bug #2758. TO DO: provide a better error message
+    if (length(irows) > 1 && length(zo__ <- which(irows == 0)) > 0) stop("i[", zo__[1], "] is 0. While grouping, i=0 is allowed when it's the only value. When length(i) > 1, all i should be > 0.")
+    if (length(o__) && length(irows)!=length(o__)) stop("Internal error: length(irows)!=length(o__)")
+    o__ = if (length(o__)) irows[o__]  # better do this once up front (even though another alloc) than deep repeated branch in dogroups.c
+          else irows
+  } # else grporder is left bound to same o__ memory (no cost of copy)
+  if (is.null(lhs)) cols=NULL
+  if (!length(f__)) {
+    # for consistency of empty case in test 184
+    f__=len__=0L
+  }
+  if (verbose) {last.started.at=proc.time()[3];cat("Making each group and running j (GForce ",GForce,") ... ",sep="");flush.console()}
+  if (GForce) {
+    thisEnv = new.env()  # not parent=parent.frame() so that gsum is found
+    for (ii in ansvars) assign(ii, x[[ii]], thisEnv)
+    assign(".N", len__, thisEnv) # For #5760
+    #fix for #1683
+    if (use.I) assign(".I", seq_len(nrow(x)), thisEnv)
+    ans = gforce(thisEnv, jsub, o__, f__, len__, irows) # irows needed for #971.
+    gi = if (length(o__)) o__[f__] else f__
+    g = lapply(grpcols, function(i) groups[[i]][gi])
+    ans = c(g, ans)
+  } else {
+    ans = .Call(Cdogroups, x, xcols, groups, grpcols, jiscols, xjiscols, grporder, o__, f__, len__, jsub, SDenv, cols, newnames, !missing(on), verbose)
+  }
+  if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console()}
+  # TO DO: xrows would be a better name for irows: irows means the rows of x that i joins to
+  # Grouping by i: icols the joins columns (might not need), isdcols (the non join i and used by j), all __ are length x
+  # Grouping by by: i is by val, icols NULL, o__ may be subset of x, f__ points to o__ (or x if !length o__)
+  # TO DO: setkey could mark the key whether it is unique or not.
+
+  if (!is.null(lhs)) {
+    if (any(names(x)[cols] %chin% key(x)))
+      setkey(x,NULL)
+    # fixes #1479. Take care of secondary indices, TODO: cleaner way of doing this
+    attrs = attr(x, 'index')
+    skeys = names(attributes(attrs))
+    if (!is.null(skeys)) {
+      hits  = unlist(lapply(paste("__", names(x)[cols], sep=""), function(x) grep(x, skeys)))
+      hits  = skeys[unique(hits)]
+      for (i in seq_along(hits)) setattr(attrs, hits[i], NULL) # does by reference
     }
-    if (byjoin && !missing(keyby) && !bysameorder) {
-        if (verbose) {last.started.at=proc.time()[3];cat("setkey() afterwards for keyby=.EACHI ... ");flush.console()}
-        setkeyv(ans,names(ans)[seq_along(byval)])
+    if (!missing(keyby)) {
+      cnames = as.character(bysubl)[-1]
+      if (all(cnames %chin% names(x))) {
+        if (verbose) {last.started.at=proc.time()[3];cat("setkey() after the := with keyby= ... ");flush.console()}
+        setkeyv(x,cnames)  # TO DO: setkey before grouping to get memcpy benefit.
         if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console()}
-    } else if (!missing(keyby) || (haskey(x) && bysameorder)) {
-        setattr(ans,"sorted",names(ans)[seq_along(grpcols)])
+      }
+      else warning(":= keyby not straightforward character column names or list() of column names, treating as a by:",paste(cnames,collapse=","),"\n")
     }
-    alloc.col(ans)   # TO DO: overallocate in dogroups in the first place and remove this line
+    return(suppPrint(x))
+  }
+  if (is.null(ans)) {
+    ans = as.data.table.list(lapply(groups,"[",0L))  # side-effects only such as test 168
+    setnames(ans,seq_along(bynames),bynames)   # TO DO: why doesn't groups have bynames in the first place?
+    return(ans)
+  }
+  setattr(ans,"row.names",.set_row_names(length(ans[[1L]])))
+  setattr(ans,"class",class(x)) # fix for #5296
+  if (is.null(names(ans))) {
+    # Efficiency gain of dropping names has been successful. Ordinarily this will run.
+    if (is.null(jvnames)) jvnames = character(length(ans)-length(bynames))
+    if (length(bynames)+length(jvnames)!=length(ans))
+      stop("Internal error: jvnames is length ",length(jvnames), " but ans is ",length(ans)," and bynames is ", length(bynames))
+    ww = which(jvnames=="")
+    if (any(ww)) jvnames[ww] = paste("V",ww,sep="")
+    setattr(ans, "names", c(bynames, jvnames))
+  } else {
+    setnames(ans,seq_along(bynames),bynames)   # TO DO: reinvestigate bynames flowing from dogroups here and simplify
+  }
+  if (byjoin && !missing(keyby) && !bysameorder) {
+    if (verbose) {last.started.at=proc.time()[3];cat("setkey() afterwards for keyby=.EACHI ... ");flush.console()}
+    setkeyv(ans,names(ans)[seq_along(byval)])
+    if (verbose) {cat(round(proc.time()[3]-last.started.at,3),"secs\n");flush.console()}
+  } else if (!missing(keyby) || (haskey(x) && bysameorder)) {
+    setattr(ans,"sorted",names(ans)[seq_along(grpcols)])
+  }
+  alloc.col(ans)   # TO DO: overallocate in dogroups in the first place and remove this line
 }
 
 .optmean <- function(expr) {   # called by optimization of j inside [.data.table only. Outside for a small speed advantage.
-    if (length(expr)==2L)  # no parameters passed to mean, so defaults of trim=0 and na.rm=FALSE
-        return(call(".External",quote(Cfastmean),expr[[2L]], FALSE))
-        # return(call(".Internal",expr))  # slightly faster than .External, but R now blocks .Internal in coerce.c from apx Sep 2012
-    if (length(expr)==3L && identical("na",substring(names(expr)[3L],1,2)))   # one parameter passed to mean()
-        return(call(".External",quote(Cfastmean),expr[[2L]], expr[[3L]]))  # faster than .Call
-    assign("nomeanopt",TRUE,parent.frame())
-    expr  # e.g. trim is not optimized, just na.rm
+  if (length(expr)==2L)  # no parameters passed to mean, so defaults of trim=0 and na.rm=FALSE
+    return(call(".External",quote(Cfastmean),expr[[2L]], FALSE))
+    # return(call(".Internal",expr))  # slightly faster than .External, but R now blocks .Internal in coerce.c from apx Sep 2012
+  if (length(expr)==3L && identical("na",substring(names(expr)[3L],1,2)))   # one parameter passed to mean()
+    return(call(".External",quote(Cfastmean),expr[[2L]], expr[[3L]]))  # faster than .Call
+  assign("nomeanopt",TRUE,parent.frame())
+  expr  # e.g. trim is not optimized, just na.rm
 }
 
 #  [[.data.frame is now dispatched due to inheritance.
@@ -1886,314 +1886,309 @@ chmatch2 <- function(x, table, nomatch=NA_integer_) {
 
 as.matrix.data.table <- function(x,...)
 {
-    dm <- dim(x)
-    cn <- names(x)
-    if (any(dm == 0L))
-        return(array(NA, dim = dm, dimnames = list(NULL, cn)))
-    p <- dm[2L]
-    n <- dm[1L]
-    collabs <- as.list(cn)
-    X <- x
-    class(X) <- NULL
-    non.numeric <- non.atomic <- FALSE
-    all.logical <- TRUE
+  dm <- dim(x)
+  cn <- names(x)
+  if (any(dm == 0L))
+    return(array(NA, dim = dm, dimnames = list(NULL, cn)))
+  p <- dm[2L]
+  n <- dm[1L]
+  collabs <- as.list(cn)
+  X <- x
+  class(X) <- NULL
+  non.numeric <- non.atomic <- FALSE
+  all.logical <- TRUE
+  for (j in seq_len(p)) {
+    if (is.ff(X[[j]])) X[[j]] <- X[[j]][]   # to bring the ff into memory, since we need to create a matrix in memory
+    xj <- X[[j]]
+    if (length(dj <- dim(xj)) == 2L && dj[2L] > 1L) {
+      if (inherits(xj, "data.table"))
+        xj <- X[[j]] <- as.matrix(X[[j]])
+      dnj <- dimnames(xj)[[2]]
+      collabs[[j]] <- paste(collabs[[j]], if (length(dnj) >
+        0L)
+        dnj
+      else seq_len(dj[2L]), sep = ".")
+    }
+    if (!is.logical(xj))
+      all.logical <- FALSE
+    if (length(levels(xj)) > 0 || !(is.numeric(xj) || is.complex(xj) || is.logical(xj)) ||
+        (!is.null(cl <- attr(xj, "class")) && any(cl %chin%
+        c("Date", "POSIXct", "POSIXlt"))))
+      non.numeric <- TRUE
+    if (!is.atomic(xj))
+      non.atomic <- TRUE
+  }
+  if (non.atomic) {
     for (j in seq_len(p)) {
-        if (is.ff(X[[j]])) X[[j]] <- X[[j]][]   # to bring the ff into memory, since we need to create a matrix in memory
-        xj <- X[[j]]
-        if (length(dj <- dim(xj)) == 2L && dj[2L] > 1L) {
-            if (inherits(xj, "data.table"))
-                xj <- X[[j]] <- as.matrix(X[[j]])
-            dnj <- dimnames(xj)[[2]]
-            collabs[[j]] <- paste(collabs[[j]], if (length(dnj) >
-                0L)
-                dnj
-            else seq_len(dj[2L]), sep = ".")
-        }
-        if (!is.logical(xj))
-            all.logical <- FALSE
-        if (length(levels(xj)) > 0 || !(is.numeric(xj) || is.complex(xj) || is.logical(xj)) ||
-            (!is.null(cl <- attr(xj, "class")) && any(cl %chin%
-                c("Date", "POSIXct", "POSIXlt"))))
-            non.numeric <- TRUE
-        if (!is.atomic(xj))
-            non.atomic <- TRUE
-    }
-    if (non.atomic) {
-        for (j in seq_len(p)) {
-            xj <- X[[j]]
-            if (is.recursive(xj)) {
-            }
-            else X[[j]] <- as.list(as.vector(xj))
-        }
+      xj <- X[[j]]
+      if (is.recursive(xj)) { }
+      else X[[j]] <- as.list(as.vector(xj))
     }
-    else if (all.logical) {
-    }
-    else if (non.numeric) {
-        for (j in seq_len(p)) {
-            if (is.character(X[[j]]))
-                next
-            xj <- X[[j]]
-            miss <- is.na(xj)
-            xj <- if (length(levels(xj)))
-                as.vector(xj)
-            else format(xj)
-            is.na(xj) <- miss
-            X[[j]] <- xj
-        }
+  }
+  else if (all.logical) { }
+  else if (non.numeric) {
+    for (j in seq_len(p)) {
+      if (is.character(X[[j]])) next
+      xj <- X[[j]]
+      miss <- is.na(xj)
+      xj <- if (length(levels(xj))) as.vector(xj) else format(xj)
+      is.na(xj) <- miss
+      X[[j]] <- xj
     }
-    X <- unlist(X, recursive = FALSE, use.names = FALSE)
-    dim(X) <- c(n, length(X)/n)
-    dimnames(X) <- list(NULL, unlist(collabs, use.names = FALSE))
-    X
+  }
+  X <- unlist(X, recursive = FALSE, use.names = FALSE)
+  dim(X) <- c(n, length(X)/n)
+  dimnames(X) <- list(NULL, unlist(collabs, use.names = FALSE))
+  X
 }
 
 # bug #2375. fixed. same as head.data.frame and tail.data.frame to deal with negative indices
 head.data.table <- function(x, n=6, ...) {
-    if (!cedta()) return(NextMethod())
-    stopifnot(length(n) == 1L)
-    i = seq_len(if (n<0L) max(nrow(x)+n, 0L) else min(n,nrow(x)))
-    x[i, , ]
+  if (!cedta()) return(NextMethod())
+  stopifnot(length(n) == 1L)
+  i = seq_len(if (n<0L) max(nrow(x)+n, 0L) else min(n,nrow(x)))
+  x[i, , ]
 }
 tail.data.table <- function(x, n=6, ...) {
-    if (!cedta()) return(NextMethod())
-    stopifnot(length(n) == 1L)
-    n <- if (n<0L) max(nrow(x) + n, 0L) else min(n, nrow(x))
-    i = seq.int(to=nrow(x), length.out=n)
-    x[i]
+  if (!cedta()) return(NextMethod())
+  stopifnot(length(n) == 1L)
+  n <- if (n<0L) max(nrow(x) + n, 0L) else min(n, nrow(x))
+  i = seq.int(to=nrow(x), length.out=n)
+  x[i]
 }
 
 "[<-.data.table" <- function (x, i, j, value) {
-    # [<- is provided for consistency, but := is preferred as it allows by group and by reference to subsets of columns
-    # with no copy of the (very large, say 10GB) columns at all. := is like an UPDATE in SQL and we like and want two symbols to change.
-    if (!cedta()) {
-        x = if (nargs()<4) `[<-.data.frame`(x, i, value=value)
-            else `[<-.data.frame`(x, i, j, value)
-        return(alloc.col(x))    # over-allocate (again).   Avoid all this by using :=.
-    }
-    # TO DO: warning("Please use DT[i,j:=value] syntax instead of DT[i,j]<-value, for efficiency. See ?':='")
-    if (!missing(i)) {
-        isub=substitute(i)
-        i = eval(.massagei(isub), x, parent.frame())
-        if (is.matrix(i)) {
-            if (!missing(j)) stop("When i is matrix in DT[i]<-value syntax, it doesn't make sense to provide j")
-            x = `[<-.data.frame`(x, i, value=value)
-            return(alloc.col(x))
-        }
-        i = x[i, which=TRUE]
-        # Tried adding ... after value above, and passing ... in here (e.g. for mult="first") but R CMD check
-        # then gives "The argument of a replacement function which corresponds to the right hand side must be
-        # named 'value'".  So, users have to use := for that.
-    } else i = NULL          # meaning (to C code) all rows, without allocating 1L:nrow(x) vector
-    if (missing(j)) j=names(x)
-    if (!is.atomic(j)) stop("j must be atomic vector, see ?is.atomic")
-    if (any(is.na(j))) stop("NA in j")
-    if (is.character(j)) {
-        newnames = setdiff(j,names(x))
-        cols = as.integer(chmatch(j, c(names(x),newnames)))
-        # We can now mix existing columns and new columns
-    } else {
-        if (!is.numeric(j)) stop("j must be vector of column name or positions")
-        if (any(j>ncol(x))) stop("Attempt to assign to column position greater than ncol(x). Create the column by name, instead. This logic intends to catch (most likely) user errors.")
-        cols = as.integer(j)  # for convenience e.g. to convert 1 to 1L
-        newnames = NULL
-    }
-    reinstatekey=NULL
-    if (haskey(x) && identical(key(x),key(value)) &&
-        identical(names(x),names(value)) &&
-        is.sorted(i) &&
-        identical(substitute(x),quote(`*tmp*`))) {
-        # DT["a",]$y <- 1.1  winds up creating `*tmp*` subset of rows and assigning _all_ the columns into x and
-        # over-writing the key columns with the same value (not just the single 'y' column).
-        # That isn't good for speed; it's an R thing. Solution is to use := instead to avoid all this, but user
-        # expects key to be retained in this case because _he_ didn't assign to a key column (the internal base R
-        # code did).
-        reinstatekey=key(x)
-    }
-    if (!selfrefok(x) || truelength(x) < ncol(x)+length(newnames)) {
-        x = alloc.col(x,length(x)+length(newnames)) # because [<- copies via *tmp* and main/duplicate.c copies at length but copies truelength over too
-        # search for one other .Call to assign in [.data.table to see how it differs
-    }
-    verbose=getOption("datatable.verbose")
-    if (!.R.subassignCopiesOthers) {   # From 3.1.0, DF[2,"b"] = 7 no longer copies DF$a, but the VECSXP is copied (i.e. a shallow copy).
-        x = .Call(Cassign,copy(x),i,cols,newnames,value,verbose)
-    } else {
-        .Call(Cassign,x,i,cols,newnames,value,verbose)
-    }
-    alloc.col(x)  #  can maybe avoid this realloc, but this is (slow) [<- anyway, so just be safe.
-    if (length(reinstatekey)) setkeyv(x,reinstatekey)
-    invisible(x)
-    # no copy at all if user calls directly; i.e. `[<-.data.table`(x,i,j,value)
-    # or uses data.table := syntax; i.e. DT[i,j:=value]
-    # but, there is one copy by R in [<- dispatch to `*tmp*`; i.e. DT[i,j]<-value. *Update: not from R > 3.0.2, yay*
-    # That copy is via main/duplicate.c which preserves truelength but copies length amount. Hence alloc.col(x,length(x)).
-    # No warn passed to assign here because we know it'll be copied via *tmp*.
-    # := allows subassign to a column with no copy of the column at all,  and by group, etc.
+  # [<- is provided for consistency, but := is preferred as it allows by group and by reference to subsets of columns
+  # with no copy of the (very large, say 10GB) columns at all. := is like an UPDATE in SQL and we like and want two symbols to change.
+  if (!cedta()) {
+    x = if (nargs()<4) `[<-.data.frame`(x, i, value=value)
+        else `[<-.data.frame`(x, i, j, value)
+    return(alloc.col(x))    # over-allocate (again).   Avoid all this by using :=.
+  }
+  # TO DO: warning("Please use DT[i,j:=value] syntax instead of DT[i,j]<-value, for efficiency. See ?':='")
+  if (!missing(i)) {
+    isub=substitute(i)
+    i = eval(.massagei(isub), x, parent.frame())
+    if (is.matrix(i)) {
+      if (!missing(j)) stop("When i is matrix in DT[i]<-value syntax, it doesn't make sense to provide j")
+      x = `[<-.data.frame`(x, i, value=value)
+      return(alloc.col(x))
+    }
+    i = x[i, which=TRUE]
+    # Tried adding ... after value above, and passing ... in here (e.g. for mult="first") but R CMD check
+    # then gives "The argument of a replacement function which corresponds to the right hand side must be
+    # named 'value'".  So, users have to use := for that.
+  } else i = NULL          # meaning (to C code) all rows, without allocating 1L:nrow(x) vector
+  if (missing(j)) j=names(x)
+  if (!is.atomic(j)) stop("j must be atomic vector, see ?is.atomic")
+  if (any(is.na(j))) stop("NA in j")
+  if (is.character(j)) {
+    newnames = setdiff(j,names(x))
+    cols = as.integer(chmatch(j, c(names(x),newnames)))
+    # We can now mix existing columns and new columns
+  } else {
+    if (!is.numeric(j)) stop("j must be vector of column name or positions")
+    if (any(j>ncol(x))) stop("Attempt to assign to column position greater than ncol(x). Create the column by name, instead. This logic intends to catch (most likely) user errors.")
+    cols = as.integer(j)  # for convenience e.g. to convert 1 to 1L
+    newnames = NULL
+  }
+  reinstatekey=NULL
+  if (haskey(x) && identical(key(x),key(value)) &&
+    identical(names(x),names(value)) &&
+    is.sorted(i) &&
+    identical(substitute(x),quote(`*tmp*`))) {
+    # DT["a",]$y <- 1.1  winds up creating `*tmp*` subset of rows and assigning _all_ the columns into x and
+    # over-writing the key columns with the same value (not just the single 'y' column).
+    # That isn't good for speed; it's an R thing. Solution is to use := instead to avoid all this, but user
+    # expects key to be retained in this case because _he_ didn't assign to a key column (the internal base R
+    # code did).
+    reinstatekey=key(x)
+  }
+  if (!selfrefok(x) || truelength(x) < ncol(x)+length(newnames)) {
+    x = alloc.col(x,length(x)+length(newnames)) # because [<- copies via *tmp* and main/duplicate.c copies at length but copies truelength over too
+    # search for one other .Call to assign in [.data.table to see how it differs
+  }
+  verbose=getOption("datatable.verbose")
+  if (!.R.subassignCopiesOthers) {   # From 3.1.0, DF[2,"b"] = 7 no longer copies DF$a, but the VECSXP is copied (i.e. a shallow copy).
+    x = .Call(Cassign,copy(x),i,cols,newnames,value,verbose)
+  } else {
+    .Call(Cassign,x,i,cols,newnames,value,verbose)
+  }
+  alloc.col(x)  #  can maybe avoid this realloc, but this is (slow) [<- anyway, so just be safe.
+  if (length(reinstatekey)) setkeyv(x,reinstatekey)
+  invisible(x)
+  # no copy at all if user calls directly; i.e. `[<-.data.table`(x,i,j,value)
+  # or uses data.table := syntax; i.e. DT[i,j:=value]
+  # but, there is one copy by R in [<- dispatch to `*tmp*`; i.e. DT[i,j]<-value. *Update: not from R > 3.0.2, yay*
+  # That copy is via main/duplicate.c which preserves truelength but copies length amount. Hence alloc.col(x,length(x)).
+  # No warn passed to assign here because we know it'll be copied via *tmp*.
+  # := allows subassign to a column with no copy of the column at all,  and by group, etc.
 }
 
 "$<-.data.table" <- function(x, name, value) {
-    if (!cedta()) {
-        ans = `$<-.data.frame`(x, name, value)
-        return(alloc.col(ans))           # over-allocate (again)
-    }
-    x = copy(x)
-    `[<-.data.table`(x,j=name,value=value)  # important i is missing here
+  if (!cedta()) {
+    ans = `$<-.data.frame`(x, name, value)
+    return(alloc.col(ans))           # over-allocate (again)
+  }
+  x = copy(x)
+  `[<-.data.table`(x,j=name,value=value)  # important i is missing here
 }
 
 as.data.frame.data.table <- function(x, ...)
 {
-    ans = copy(x)
-    setattr(ans,"row.names",.set_row_names(nrow(x)))   # since R 2.4.0, data.frames can have non-character row names
-    setattr(ans,"class","data.frame")
-    setattr(ans,"sorted",NULL)  # remove so if you convert to df, do something, and convert back, it is not sorted
-    setattr(ans,".internal.selfref",NULL)
-    # leave tl intact, no harm,
-    ans
+  ans = copy(x)
+  setattr(ans,"row.names",.set_row_names(nrow(x)))   # since R 2.4.0, data.frames can have non-character row names
+  setattr(ans,"class","data.frame")
+  setattr(ans,"sorted",NULL)  # remove so if you convert to df, do something, and convert back, it is not sorted
+  setattr(ans,".internal.selfref",NULL)
+  # leave tl intact, no harm,
+  ans
 }
 
 as.list.data.table <- function(x, ...) {
-    # Similar to as.list.data.frame in base. Although a data.table/frame is a list, too, it may be
-    # being coerced to raw list type (by calling code) so that "[" and "[[" work in their raw list form,
-    # such as lapply does for data.frame. So we do have to remove the class attributes (and thus shallow
-    # copy is almost instant way to achieve that, without risking compatibility).
-    #if (sys.call(-2L)[[1L]]=="lapply")
-    #    return(x)
-    ans = shallow(x)
-    setattr(ans, "class", NULL)
-    setattr(ans, "row.names", NULL)
-    setattr(ans, "sorted", NULL)
-    setattr(ans,".internal.selfref", NULL)   # needed to pass S4 tests for example
-    ans
+  # Similar to as.list.data.frame in base. Although a data.table/frame is a list, too, it may be
+  # being coerced to raw list type (by calling code) so that "[" and "[[" work in their raw list form,
+  # such as lapply does for data.frame. So we do have to remove the class attributes (and thus shallow
+  # copy is almost instant way to achieve that, without risking compatibility).
+  #if (sys.call(-2L)[[1L]]=="lapply")
+  #    return(x)
+  ans = shallow(x)
+  setattr(ans, "class", NULL)
+  setattr(ans, "row.names", NULL)
+  setattr(ans, "sorted", NULL)
+  setattr(ans,".internal.selfref", NULL)   # needed to pass S4 tests for example
+  ans
 }
 
 
 dimnames.data.table <- function(x) {
-    if (!cedta()) {
-        if (!inherits(x, "data.frame"))
-          stop("data.table inherits from data.frame (from v1.5), but this data.table does not. Has it been created manually (e.g. by using 'structure' rather than 'data.table') or saved to disk using a prior version of data.table?")
-        return(`dimnames.data.frame`(x))
-    }
-    list(NULL, names(x))
+  if (!cedta()) {
+    if (!inherits(x, "data.frame"))
+      stop("data.table inherits from data.frame (from v1.5), but this data.table does not. Has it been created manually (e.g. by using 'structure' rather than 'data.table') or saved to disk using a prior version of data.table?")
+    return(`dimnames.data.frame`(x))
+  }
+  list(NULL, names(x))
 }
 
 "dimnames<-.data.table" = function (x, value)   # so that can do  colnames(dt)=<..>  as well as names(dt)=<..>
 {
-    if (!cedta()) return(`dimnames<-.data.frame`(x,value))  # won't maintain key column (if any). Revisit if ever causes a compatibility problem but don't think it's likely that packages change column names using dimnames<-. See names<-.data.table below.
-    if (.R.assignNamesCopiesAll) warning("This is R<3.1.0 where dimnames(x)<-value syntax deep copies the entire table. Please upgrade to R>=3.1.0 and see ?setnames which allows you to change names by name with built-in checks and warnings.")
-    if (!is.list(value) || length(value) != 2) stop("attempting to assign invalid object to dimnames of a data.table")
-    if (!is.null(value[[1L]])) stop("data.tables do not have rownames")
-    if (ncol(x) != length(value[[2]])) stop("can't assign",length(value[[2]]),"colnames to a",ncol(x),"column data.table")
-    setnames(x,as.character(value[[2]]))
-    x  # this returned value is now shallow copied by R 3.1.0 via *tmp*. A very welcome change.
+  if (!cedta()) return(`dimnames<-.data.frame`(x,value))  # won't maintain key column (if any). Revisit if ever causes a compatibility problem but don't think it's likely that packages change column names using dimnames<-. See names<-.data.table below.
+  if (.R.assignNamesCopiesAll) warning("This is R<3.1.0 where dimnames(x)<-value syntax deep copies the entire table. Please upgrade to R>=3.1.0 and see ?setnames which allows you to change names by name with built-in checks and warnings.")
+  if (!is.list(value) || length(value) != 2) stop("attempting to assign invalid object to dimnames of a data.table")
+  if (!is.null(value[[1L]])) stop("data.tables do not have rownames")
+  if (ncol(x) != length(value[[2]])) stop("can't assign",length(value[[2]]),"colnames to a",ncol(x),"column data.table")
+  setnames(x,as.character(value[[2]]))
+  x  # this returned value is now shallow copied by R 3.1.0 via *tmp*. A very welcome change.
 }
 
 "names<-.data.table" <- function(x,value)
 {
-    # When non data.table aware packages change names, we'd like to maintain the key, too.
-    # If call is names(DT)[2]="newname", R will call this names<-.data.table function (notice no i) with 'value' already prepared to be same length as ncol
-    caller = as.character(sys.call(-2L))[1L]
-    if ( ((tt<-identical(caller,"colnames<-")) && cedta(3)) || cedta() ) {
-        if (.R.assignNamesCopiesAll)
-            warning("This is R<3.1.0 where ",if(tt)"col","names(x)<-value deep copies the entire table (several times). Please upgrade to R>=3.1.0 and see ?setnames which allows you to change names by name with built-in checks and warnings.")
-    }
-    x = shallow(x) # `names<-` should NOT modify by reference. Related to #1015, #476 and #825. Needed for R v3.1.0+.  TO DO: revisit
-    if (is.null(value))
-        setattr(x,"names",NULL)   # e.g. plyr::melt() calls base::unname()
-    else
-        setnames(x,value)
-    x   # this returned value is now shallow copied by R 3.1.0 via *tmp*. A very welcome change.
+  # When non data.table aware packages change names, we'd like to maintain the key, too.
+  # If call is names(DT)[2]="newname", R will call this names<-.data.table function (notice no i) with 'value' already prepared to be same length as ncol
+  caller = as.character(sys.call(-2L))[1L]
+  if ( ((tt<-identical(caller,"colnames<-")) && cedta(3)) || cedta() ) {
+    if (.R.assignNamesCopiesAll)
+      warning("This is R<3.1.0 where ",if(tt)"col","names(x)<-value deep copies the entire table (several times). Please upgrade to R>=3.1.0 and see ?setnames which allows you to change names by name with built-in checks and warnings.")
+  }
+  x = shallow(x) # `names<-` should NOT modify by reference. Related to #1015, #476 and #825. Needed for R v3.1.0+.  TO DO: revisit
+  if (is.null(value))
+    setattr(x,"names",NULL)   # e.g. plyr::melt() calls base::unname()
+  else
+    setnames(x,value)
+  x   # this returned value is now shallow copied by R 3.1.0 via *tmp*. A very welcome change.
 }
 
 within.data.table <- function (data, expr, ...)
 # basically within.list but retains key (if any)
 # will be slower than using := or a regular query (see ?within for further info).
 {
-    if (!cedta()) return(NextMethod())
-    parent <- parent.frame()
-    e <- evalq(environment(), data, parent)
-    eval(substitute(expr), e)  # might (and it's known that some user code does) contain rm()
-    l <- as.list(e)
-    l <- l[!vapply_1b(l, is.null)]
-    nD <- length(del <- setdiff(names(data), (nl <- names(l))))
-    ans = copy(data)
-    if (length(nl)) ans[,nl] <- l
-    if (nD) ans[,del] <- NULL
-    if (haskey(data) && all(key(data) %chin% names(ans))) {
-        x = TRUE
-        for (i in key(data)) {
-            x = identical(data[[i]],ans[[i]])
-            if (!x) break
-        }
-        if (x) setattr(ans,"sorted",key(data))
-    }
-    ans
+  if (!cedta()) return(NextMethod())
+  parent <- parent.frame()
+  e <- evalq(environment(), data, parent)
+  eval(substitute(expr), e)  # might (and it's known that some user code does) contain rm()
+  l <- as.list(e)
+  l <- l[!vapply_1b(l, is.null)]
+  nD <- length(del <- setdiff(names(data), (nl <- names(l))))
+  ans = copy(data)
+  if (length(nl)) ans[,nl] <- l
+  if (nD) ans[,del] <- NULL
+  if (haskey(data) && all(key(data) %chin% names(ans))) {
+    x = TRUE
+    for (i in key(data)) {
+      x = identical(data[[i]],ans[[i]])
+      if (!x) break
+    }
+    if (x) setattr(ans,"sorted",key(data))
+  }
+  ans
 }
 
 
 transform.data.table <- function (`_data`, ...)
 # basically transform.data.frame with data.table instead of data.frame, and retains key
 {
-    if (!cedta()) return(NextMethod())
-    e <- eval(substitute(list(...)), `_data`, parent.frame())
-    tags <- names(e)
-    inx <- chmatch(tags, names(`_data`))
-    matched <- !is.na(inx)
-    if (any(matched)) {
-        if (isTRUE(attr(`_data`, ".data.table.locked", TRUE))) setattr(`_data`, ".data.table.locked", NULL) # fix for #1641
-        `_data`[,inx[matched]] <- e[matched]
-        `_data` <- data.table(`_data`)
-    }
-    if (!all(matched)) {
-        ans <- do.call("data.table", c(list(`_data`), e[!matched]))
-    } else {
-        ans <- `_data`
-    }
-    key.cols <- key(`_data`)
-    if (!any(tags %chin% key.cols)) {
-        setattr(ans, "sorted", key.cols)
-    }
-    ans
+  if (!cedta()) return(NextMethod())
+  e <- eval(substitute(list(...)), `_data`, parent.frame())
+  tags <- names(e)
+  inx <- chmatch(tags, names(`_data`))
+  matched <- !is.na(inx)
+  if (any(matched)) {
+    if (isTRUE(attr(`_data`, ".data.table.locked", TRUE))) setattr(`_data`, ".data.table.locked", NULL) # fix for #1641
+    `_data`[,inx[matched]] <- e[matched]
+    `_data` <- data.table(`_data`)
+  }
+  if (!all(matched)) {
+    ans <- do.call("data.table", c(list(`_data`), e[!matched]))
+  } else {
+    ans <- `_data`
+  }
+  key.cols <- key(`_data`)
+  if (!any(tags %chin% key.cols)) {
+    setattr(ans, "sorted", key.cols)
+  }
+  ans
 }
 
 subset.data.table <- function (x, subset, select, ...)
 {
-    key.cols <- key(x)
+  key.cols <- key(x)
 
-    if (missing(subset)) {
-        r <- TRUE
-    } else {
-        e <- substitute(subset)
-        r <- eval(e, x, parent.frame())
-        if (!is.logical(r))
-            stop("'subset' must evaluate to logical")
-        r <- r & !is.na(r)
-    }
+  if (missing(subset)) {
+    r <- TRUE
+  } else {
+    e <- substitute(subset)
+    r <- eval(e, x, parent.frame())
+    if (!is.logical(r))
+      stop("'subset' must evaluate to logical")
+    r <- r & !is.na(r)
+  }
 
-    if (missing(select)) {
-        vars <- seq_len(ncol(x))
-    } else {
-        nl <- as.list(seq_len(ncol(x)))
-        setattr(nl,"names",names(x))
-        vars <- eval(substitute(select), nl, parent.frame())  # e.g.  select=colF:colP
-        # #891 fix - don't convert numeric vars to column names - will break when there are duplicate columns
-        key.cols <- intersect(key.cols, names(x)[vars]) ## Only keep key.columns found in the select clause
-    }
-
-    ans <- x[r, vars, with = FALSE]
-
-    if (nrow(ans) > 0L) {
-        if (!missing(select) && length(key.cols)) {
-            ## Set the key on the returned data.table as long as the key
-            ## columns that "remain" are the same as the original, or a
-            ## prefix of it.
-            is.prefix <- all(key(x)[seq_len(length(key.cols))] == key.cols)
-            if (is.prefix) {
-                setattr(ans, "sorted", key.cols)
-            }
-        }
-    } else {
-        setkey(ans,NULL)
+  if (missing(select)) {
+    vars <- seq_len(ncol(x))
+  } else {
+    nl <- as.list(seq_len(ncol(x)))
+    setattr(nl,"names",names(x))
+    vars <- eval(substitute(select), nl, parent.frame())  # e.g.  select=colF:colP
+    # #891 fix - don't convert numeric vars to column names - will break when there are duplicate columns
+    key.cols <- intersect(key.cols, names(x)[vars]) ## Only keep key.columns found in the select clause
+  }
+
+  ans <- x[r, vars, with = FALSE]
+
+  if (nrow(ans) > 0L) {
+    if (!missing(select) && length(key.cols)) {
+      ## Set the key on the returned data.table as long as the key
+      ## columns that "remain" are the same as the original, or a
+      ## prefix of it.
+      is.prefix <- all(key(x)[seq_len(length(key.cols))] == key.cols)
+      if (is.prefix) {
+        setattr(ans, "sorted", key.cols)
+      }
     }
-    ans
+  } else {
+    setkey(ans,NULL)
+  }
+  ans
 }
 
 # Equivalent of 'rowSums(is.na(dt) > 0L)' but much faster and memory efficient.
@@ -2204,31 +2199,31 @@ is_na <- function(x, by=seq_along(x)) .Call(Cdt_na, x, by)
 any_na <- function(x, by=seq_along(x)) .Call(CanyNA, x, by)
 
 na.omit.data.table <- function (object, cols = seq_along(object), invert = FALSE, ...) {
-    if (!cedta()) return(NextMethod())
-    if ( !missing(invert) && is.na(as.logical(invert)) )
-        stop("Argument 'invert' must be logical TRUE/FALSE")
-    if (is.character(cols)) {
-        old = cols
-        cols = chmatch(cols, names(object), nomatch=0L)
-        if (any(cols==0L))
-            stop("Columns ", paste(old[cols==0L], collapse=","),
-              " doesn't exist in the input data.table")
-    }
-    cols = as.integer(cols)
-    ix = .Call(Cdt_na, object, cols)
-    ans = .Call(CsubsetDT, object, which_(ix, bool = invert), seq_along(object))
-    if (any(ix)) setindexv(ans, NULL)[] else ans #1734
-    # compare the above to stats:::na.omit.data.frame
+  if (!cedta()) return(NextMethod())
+  if ( !missing(invert) && is.na(as.logical(invert)) )
+    stop("Argument 'invert' must be logical TRUE/FALSE")
+  if (is.character(cols)) {
+    old = cols
+    cols = chmatch(cols, names(object), nomatch=0L)
+    if (any(cols==0L))
+      stop("Columns ", paste(old[cols==0L], collapse=","),
+        " doesn't exist in the input data.table")
+  }
+  cols = as.integer(cols)
+  ix = .Call(Cdt_na, object, cols)
+  ans = .Call(CsubsetDT, object, which_(ix, bool = invert), seq_along(object))
+  if (any(ix)) setindexv(ans, NULL)[] else ans #1734
+  # compare the above to stats:::na.omit.data.frame
 }
 
 which_ <- function(x, bool = TRUE) {
-    # fix for #1467, quotes result in "not resolved in current namespace" error
-    .Call(Cwhichwrapper, x, bool)
+  # fix for #1467, quotes result in "not resolved in current namespace" error
+  .Call(Cwhichwrapper, x, bool)
 }
 
 is.na.data.table <- function (x) {
-    if (!cedta()) return(`is.na.data.frame`(x))
-    do.call("cbind", lapply(x, "is.na"))
+  if (!cedta()) return(`is.na.data.frame`(x))
+  do.call("cbind", lapply(x, "is.na"))
 }
 
 # not longer needed as inherits ...
@@ -2238,184 +2233,184 @@ is.na.data.table <- function (x) {
 
 Ops.data.table <- function(e1, e2 = NULL)
 {
-    ans = NextMethod()
-    if (cedta() && is.data.frame(ans))
-        ans = as.data.table(ans)
-    ans
+  ans = NextMethod()
+  if (cedta() && is.data.frame(ans))
+    ans = as.data.table(ans)
+  ans
 }
 
 split.data.table <- function(x, f, drop = FALSE, by, sorted = FALSE, keep.by = TRUE, flatten = TRUE, ..., verbose = getOption("datatable.verbose")) {
-    if (!is.data.table(x)) stop("x argument must be a data.table")
-    stopifnot(is.logical(drop), is.logical(sorted), is.logical(keep.by),  is.logical(flatten))
-    # split data.frame way, using `f` and not `by` argument
-    if (!missing(f)) {
-        if (!length(f) && nrow(x))
-            stop("group length is 0 but data nrow > 0")
-        if (!missing(by))
-            stop("passing 'f' argument together with 'by' is not allowed, use 'by' when split by column in data.table and 'f' when split by external factor")
-        # same as split.data.frame - handling all exceptions, factor orders etc, in a single stream of processing was a nightmare in factor and drop consistency
-        return(lapply(split(x = seq_len(nrow(x)), f = f, drop = drop, ...), function(ind) x[ind]))
-    }
-    if (missing(by)) stop("you must provide 'by' or 'f' arguments")
-    # check reserved column names during processing
-    if (".ll.tech.split" %in% names(x)) stop("column '.ll.tech.split' is reserved for split.data.table processing")
-    if (".nm.tech.split" %in% by) stop("column '.nm.tech.split' is reserved for split.data.table processing")
-    if (!all(by %in% names(x))) stop("argument 'by' must refer to data.table column names")
-    if (!all(by.atomic <- vapply_1b(by, function(.by) is.atomic(x[[.by]])))) stop(sprintf("argument 'by' must refer only to atomic type columns, classes of '%s' columns are not atomic type", paste(by[!by.atomic], collapse=", ")))
-    # list of data.tables (flatten) or list of lists of ... data.tables
-    make.levels = function(x, cols, sorted) {
-        by.order = if (!sorted) x[, funique(.SD), .SDcols=cols] # remember order of data, only when not sorted=FALSE
-        ul = lapply(setNames(cols, nm=cols), function(col) {
-          if (!is.factor(x[[col]])) unique(x[[col]]) else {
-            .x_lev = levels(x[[col]])
-            #need to keep as a factor or order will be lost, #2082
-            factor(.x_lev, levels = .x_lev)
-          }
-        })
-        r = do.call("CJ", c(ul, sorted=sorted, unique=TRUE))
-        if (!sorted && nrow(by.order)) {
-            ii = r[by.order, on=cols, which=TRUE]
-            r = rbindlist(list(
-                r[ii], # original order from data
-                r[-ii] # empty levels at the end
-            ))
-        }
-        r
-    }
-    .by = by[1L]
-    # this builds data.table call - is much more cleaner than handling each case one by one
-    dtq = as.list(call("[", as.name("x")))
-    join = FALSE
-    flatten_any = flatten && any(vapply_1b(by, function(col) is.factor(x[[col]])))
-    nested_current = !flatten && is.factor(x[[.by]])
-    if (!drop && (flatten_any || nested_current)) {
-        dtq[["i"]] = substitute(make.levels(x, cols=.cols, sorted=.sorted), list(.cols=if (flatten) by else .by, .sorted=sorted))
-        join = TRUE
-    }
-    dtq[["j"]] = substitute(
-        list(.ll.tech.split=list(.expr)),
-        list(.expr = if (join) quote(if(.N == 0L) .SD[0L] else .SD) else as.name(".SD")) # simplify when `nomatch` accept NULL #857 ?
-    )
-    by.or.keyby = if (join) "by" else c("by"[!sorted], "keyby"[sorted])[1L]
-    dtq[[by.or.keyby]] = substitute( # retain order, for `join` and `sorted` it will use order of `i` data.table instead of `keyby`.
-        .expr,
-        list(.expr = if(join) as.name(".EACHI") else if (flatten) by else .by)
-    )
-    dtq[[".SDcols"]] = if (keep.by) names(x) else setdiff(names(x), if (flatten) by else .by)
-    if (join) dtq[["on"]] = if (flatten) by else .by
-    dtq = as.call(dtq)
-    if (isTRUE(verbose)) cat("Processing split.data.table with: ", deparse(dtq, width.cutoff=500L), "\n", sep="")
-    tmp = eval(dtq)
-    # add names on list
-    setattr(ll <- tmp$.ll.tech.split,
-            "names",
-            as.character(
-                if (!flatten) tmp[[.by]] else tmp[, list(.nm.tech.split=paste(unlist(lapply(.SD, as.character)), collapse = ".")), by=by, .SDcols=by]$.nm.tech.split
-            ))
-    # handle nested split
-    if (flatten || length(by) == 1L) return(
-        lapply(lapply(ll, setattr, '.data.table.locked', NULL), setDT) # alloc.col could handle DT in list as done in: c9c4ff80bdd4c600b0c4eff23b207d53677176bd
-    ) else if (length(by) > 1L) return(
-        lapply(ll, split.data.table, drop=drop, by=by[-1L], sorted=sorted, keep.by=keep.by, flatten=flatten)
-    )
+  if (!is.data.table(x)) stop("x argument must be a data.table")
+  stopifnot(is.logical(drop), is.logical(sorted), is.logical(keep.by),  is.logical(flatten))
+  # split data.frame way, using `f` and not `by` argument
+  if (!missing(f)) {
+    if (!length(f) && nrow(x))
+      stop("group length is 0 but data nrow > 0")
+    if (!missing(by))
+      stop("passing 'f' argument together with 'by' is not allowed, use 'by' when split by column in data.table and 'f' when split by external factor")
+    # same as split.data.frame - handling all exceptions, factor orders etc, in a single stream of processing was a nightmare in factor and drop consistency
+    return(lapply(split(x = seq_len(nrow(x)), f = f, drop = drop, ...), function(ind) x[ind]))
+  }
+  if (missing(by)) stop("you must provide 'by' or 'f' arguments")
+  # check reserved column names during processing
+  if (".ll.tech.split" %in% names(x)) stop("column '.ll.tech.split' is reserved for split.data.table processing")
+  if (".nm.tech.split" %in% by) stop("column '.nm.tech.split' is reserved for split.data.table processing")
+  if (!all(by %in% names(x))) stop("argument 'by' must refer to data.table column names")
+  if (!all(by.atomic <- vapply_1b(by, function(.by) is.atomic(x[[.by]])))) stop(sprintf("argument 'by' must refer only to atomic type columns, classes of '%s' columns are not atomic type", paste(by[!by.atomic], collapse=", ")))
+  # list of data.tables (flatten) or list of lists of ... data.tables
+  make.levels = function(x, cols, sorted) {
+    by.order = if (!sorted) x[, funique(.SD), .SDcols=cols] # remember order of data, only when not sorted=FALSE
+    ul = lapply(setNames(cols, nm=cols), function(col) {
+      if (!is.factor(x[[col]])) unique(x[[col]]) else {
+      .x_lev = levels(x[[col]])
+      #need to keep as a factor or order will be lost, #2082
+      factor(.x_lev, levels = .x_lev)
+      }
+    })
+    r = do.call("CJ", c(ul, sorted=sorted, unique=TRUE))
+    if (!sorted && nrow(by.order)) {
+      ii = r[by.order, on=cols, which=TRUE]
+      r = rbindlist(list(
+        r[ii], # original order from data
+        r[-ii] # empty levels at the end
+      ))
+    }
+    r
+  }
+  .by = by[1L]
+  # this builds data.table call - is much more cleaner than handling each case one by one
+  dtq = as.list(call("[", as.name("x")))
+  join = FALSE
+  flatten_any = flatten && any(vapply_1b(by, function(col) is.factor(x[[col]])))
+  nested_current = !flatten && is.factor(x[[.by]])
+  if (!drop && (flatten_any || nested_current)) {
+    dtq[["i"]] = substitute(make.levels(x, cols=.cols, sorted=.sorted), list(.cols=if (flatten) by else .by, .sorted=sorted))
+    join = TRUE
+  }
+  dtq[["j"]] = substitute(
+    list(.ll.tech.split=list(.expr)),
+    list(.expr = if (join) quote(if(.N == 0L) .SD[0L] else .SD) else as.name(".SD")) # simplify when `nomatch` accept NULL #857 ?
+  )
+  by.or.keyby = if (join) "by" else c("by"[!sorted], "keyby"[sorted])[1L]
+  dtq[[by.or.keyby]] = substitute( # retain order, for `join` and `sorted` it will use order of `i` data.table instead of `keyby`.
+    .expr,
+    list(.expr = if(join) as.name(".EACHI") else if (flatten) by else .by)
+  )
+  dtq[[".SDcols"]] = if (keep.by) names(x) else setdiff(names(x), if (flatten) by else .by)
+  if (join) dtq[["on"]] = if (flatten) by else .by
+  dtq = as.call(dtq)
+  if (isTRUE(verbose)) cat("Processing split.data.table with: ", deparse(dtq, width.cutoff=500L), "\n", sep="")
+  tmp = eval(dtq)
+  # add names on list
+  setattr(ll <- tmp$.ll.tech.split,
+      "names",
+      as.character(
+        if (!flatten) tmp[[.by]] else tmp[, list(.nm.tech.split=paste(unlist(lapply(.SD, as.character)), collapse = ".")), by=by, .SDcols=by]$.nm.tech.split
+      ))
+  # handle nested split
+  if (flatten || length(by) == 1L) return(
+    lapply(lapply(ll, setattr, '.data.table.locked', NULL), setDT) # alloc.col could handle DT in list as done in: c9c4ff80bdd4c600b0c4eff23b207d53677176bd
+  ) else if (length(by) > 1L) return(
+    lapply(ll, split.data.table, drop=drop, by=by[-1L], sorted=sorted, keep.by=keep.by, flatten=flatten)
+  )
 }
 
 # TO DO, add more warnings e.g. for by.data.table(), telling user what the data.table syntax is but letting them dispatch to data.frame if they want
 
 copy <- function(x) {
-    newx = .Call(Ccopy,x)  # copies at length but R's duplicate() also copies truelength over.
-                           # TO DO: inside Ccopy it could reset tl to 0 or length, but no matter as selfrefok detects it
-                           # TO DO: revisit duplicate.c in R 3.0.3 and see where it's at
-    if (!is.data.table(x)) {
-        # fix for #1476. TODO: find if a cleaner fix is possible..
-        if (is.list(x)) {
-            anydt = vapply(x, is.data.table, TRUE, USE.NAMES=FALSE)
-            if (sum(anydt)) {
-                newx[anydt] = lapply(newx[anydt], function(x) {
-                    setattr(x, ".data.table.locked", NULL)
-                    alloc.col(x)
-                })
-            }
-        }
-        return(newx)   # e.g. in as.data.table.list() the list is copied before changing to data.table
+  newx = .Call(Ccopy,x)  # copies at length but R's duplicate() also copies truelength over.
+                         # TO DO: inside Ccopy it could reset tl to 0 or length, but no matter as selfrefok detects it
+                         # TO DO: revisit duplicate.c in R 3.0.3 and see where it's at
+  if (!is.data.table(x)) {
+    # fix for #1476. TODO: find if a cleaner fix is possible..
+    if (is.list(x)) {
+      anydt = vapply(x, is.data.table, TRUE, USE.NAMES=FALSE)
+      if (sum(anydt)) {
+        newx[anydt] = lapply(newx[anydt], function(x) {
+          setattr(x, ".data.table.locked", NULL)
+          alloc.col(x)
+        })
+      }
     }
-    setattr(newx,".data.table.locked",NULL)
-    alloc.col(newx)
+    return(newx)   # e.g. in as.data.table.list() the list is copied before changing to data.table
+  }
+  setattr(newx,".data.table.locked",NULL)
+  alloc.col(newx)
 }
 
 copyattr <- function(from, to) {
-    .Call(Ccopyattr, from, to)
+  .Call(Ccopyattr, from, to)
 }
 
 point <- function(to, to_idx, from, from_idx) {
-    .Call(CpointWrapper, to, to_idx, from, from_idx)
+  .Call(CpointWrapper, to, to_idx, from, from_idx)
 }
 
 .shallow <- function(x, cols = NULL, retain.key = FALSE, unlock = FALSE) {
-    isnull = is.null(cols)
-    if (!isnull) cols = validate(cols, x)  # NULL is default = all columns
-    ans = .Call(Cshallowwrapper, x, cols)  # copies VECSXP only
-
-    if(retain.key){
-      if(isnull) return(ans) # handle most frequent case first
-      ## get correct key if cols are present
-      cols = names(x)[cols]
-      keylength <- which.first(!key(ans) %chin% cols) - 1L
-      if(is.na(keylength)) keylength <- length(key(ans))
-      if(!keylength){
-        setattr(ans, "sorted", NULL) ## no key remaining
+  isnull = is.null(cols)
+  if (!isnull) cols = validate(cols, x)  # NULL is default = all columns
+  ans = .Call(Cshallowwrapper, x, cols)  # copies VECSXP only
+
+  if(retain.key){
+    if (isnull) return(ans) # handle most frequent case first
+    ## get correct key if cols are present
+    cols = names(x)[cols]
+    keylength <- which.first(!key(ans) %chin% cols) - 1L
+    if (is.na(keylength)) keylength <- length(key(ans))
+    if (!keylength) {
+      setattr(ans, "sorted", NULL) ## no key remaining
+    } else {
+      setattr(ans, "sorted", head(key(ans), keylength)) ## keep what can be kept
+    }
+    ## take care of attributes.
+    indices <- names(attributes(attr(ans, "index")))
+    for(index in indices) {
+      indexcols <- strsplit(index, split = "__")[[1]][-1L]
+      indexlength <- which.first(!indexcols %chin% cols) - 1L
+      if (is.na(indexlength)) next ## all columns are present, nothing to be done
+      reducedindex <- paste0(c("", indexcols[seq_len(indexlength)]), collapse = "__") ## the columns until the first missing form the new index
+      if (reducedindex %chin% indices || !indexlength) {
+        ## Either reduced index already present or no columns of the original index remain.
+        ## Drop the original index completely
+        setattr(attr(ans, "index", exact = TRUE), index, NULL)
+      } else if(length(attr(attr(ans, "index"), index))) {
+        ## index is not length 0. Drop it since shortening could lead to spurious reordering in discarded columns (#2336)
+        setattr(attr(ans, "index", exact = TRUE), index, NULL)
       } else {
-        setattr(ans, "sorted", head(key(ans), keylength)) ## keep what can be kept
-      }
-      ## take care of attributes.
-      indices <- names(attributes(attr(ans, "index")))
-      for(index in indices){
-        indexcols <- strsplit(index, split = "__")[[1]][-1L]
-        indexlength <- which.first(!indexcols %chin% cols) - 1L
-        if(is.na(indexlength)) next ## all columns are present, nothing to be done
-        reducedindex <- paste0(c("", indexcols[seq_len(indexlength)]), collapse = "__") ## the columns until the first missing form the new index
-        if(reducedindex %chin% indices || !indexlength){
-          ## Either reduced index already present or no columns of the original index remain.
-          ## Drop the original index completely
-          setattr(attr(ans, "index", exact = TRUE), index, NULL)
-        } else if(length(attr(attr(ans, "index"), index))){
-          ## index is not length 0. Drop it since shortening could lead to spurious reordering in discarded columns (#2336)
-          setattr(attr(ans, "index", exact = TRUE), index, NULL)
-        } else {
-          ## rename index to reducedindex
-          names(attributes(attr(ans, "index")))[names(attributes(attr(ans, "index"))) == index] <- reducedindex
-        }
+        ## rename index to reducedindex
+        names(attributes(attr(ans, "index")))[names(attributes(attr(ans, "index"))) == index] <- reducedindex
       }
-    } else { # retain.key == FALSE
-      setattr(ans, "sorted", NULL)
-      setattr(ans, "index", NULL)
     }
-    if (unlock) setattr(ans, '.data.table.locked', NULL)
-    ans
+  } else { # retain.key == FALSE
+    setattr(ans, "sorted", NULL)
+    setattr(ans, "index", NULL)
+  }
+  if (unlock) setattr(ans, '.data.table.locked', NULL)
+  ans
 
 }
 
 shallow <- function(x, cols=NULL) {
-    if (!is.data.table(x))
-        stop("x is not a data.table. Shallow copy is a copy of the vector of column pointers (only), so is only meaningful for data.table")
-    ans = .shallow(x, cols=cols, retain.key = TRUE)
-    ans
+  if (!is.data.table(x))
+    stop("x is not a data.table. Shallow copy is a copy of the vector of column pointers (only), so is only meaningful for data.table")
+  ans = .shallow(x, cols=cols, retain.key = TRUE)
+  ans
 }
 
 alloc.col <- function(DT, n=getOption("datatable.alloccol"), verbose=getOption("datatable.verbose"))
 {
-    name = substitute(DT)
-    if (identical(name,quote(`*tmp*`))) stop("alloc.col attempting to modify `*tmp*`")
-    ans = .Call(Calloccolwrapper, DT, length(DT)+as.integer(eval(n)), verbose)
-    if (is.name(name)) {
-        name = as.character(name)
-        assign(name,ans,parent.frame(),inherits=TRUE)
-    }
-    .Call(Csetmutable,ans)
+  name = substitute(DT)
+  if (identical(name,quote(`*tmp*`))) stop("alloc.col attempting to modify `*tmp*`")
+  ans = .Call(Calloccolwrapper, DT, length(DT)+as.integer(eval(n)), verbose)
+  if (is.name(name)) {
+    name = as.character(name)
+    assign(name,ans,parent.frame(),inherits=TRUE)
+  }
+  .Call(Csetmutable,ans)
 }
 
 selfrefok <- function(DT,verbose=getOption("datatable.verbose")) {
-    .Call(Cselfrefokwrapper,DT,verbose)
+  .Call(Cselfrefokwrapper,DT,verbose)
 }
 
 truelength <- function(x) .Call(Ctruelength,x)
@@ -2424,171 +2419,171 @@ truelength <- function(x) .Call(Ctruelength,x)
 # which initializes tl to zero rather than leaving uninitialized.
 
 setattr <- function(x,name,value) {
-    # Wrapper for setAttrib internal R function
-    # Sets attribute by reference (no copy)
-    # Named setattr (rather than setattrib) at R level to more closely resemble attr<-
-    # And as from 1.7.8 is made exported in NAMESPACE for use in user attributes.
-    # User can also call `attr<-` function directly, but that copies (maybe just when NAMED>0, which is always for data.frame, I think).  See "Confused by NAMED" thread on r-devel 24 Nov 2011.
-    # We tend to use setattr() internally in data.table.R because often we construct a data.table and it hasn't
-    # got names yet. setnames() is the user interface which checks integrity and doesn't let you drop names for example.
-    if (name=="names" && is.data.table(x) && length(attr(x,"names")) && !is.null(value))
-        setnames(x,value)
-        # Using setnames here so that truelength of names can be retained, to carry out integrity checks such as not
-        # creating names longer than the number of columns of x, and to change the key, too
-        # For convenience so that setattr(DT,"names",allnames) works as expected without requiring a switch to setnames.
-    else {
-        # fix for R's global TRUE value input, #1281
-        ans = .Call(Csetattrib, x, name, value)
-        # If name=="names" and this is the first time names are assigned (e.g. in data.table()), this will be grown by alloc.col very shortly afterwards in the caller.
-        if (!is.null(ans)) {
-            warning("Input is a length=1 logical that points to the same address as R's global TRUE value. Therefore the attribute has not been set by reference, rather on a copy. You will need to assign the result back to a variable. See https://github.com/Rdatatable/data.table/issues/1281 for more.")
-            x = ans
-        }
+  # Wrapper for setAttrib internal R function
+  # Sets attribute by reference (no copy)
+  # Named setattr (rather than setattrib) at R level to more closely resemble attr<-
+  # And as from 1.7.8 is made exported in NAMESPACE for use in user attributes.
+  # User can also call `attr<-` function directly, but that copies (maybe just when NAMED>0, which is always for data.frame, I think).  See "Confused by NAMED" thread on r-devel 24 Nov 2011.
+  # We tend to use setattr() internally in data.table.R because often we construct a data.table and it hasn't
+  # got names yet. setnames() is the user interface which checks integrity and doesn't let you drop names for example.
+  if (name=="names" && is.data.table(x) && length(attr(x,"names")) && !is.null(value))
+    setnames(x,value)
+    # Using setnames here so that truelength of names can be retained, to carry out integrity checks such as not
+    # creating names longer than the number of columns of x, and to change the key, too
+    # For convenience so that setattr(DT,"names",allnames) works as expected without requiring a switch to setnames.
+  else {
+    # fix for R's global TRUE value input, #1281
+    ans = .Call(Csetattrib, x, name, value)
+    # If name=="names" and this is the first time names are assigned (e.g. in data.table()), this will be grown by alloc.col very shortly afterwards in the caller.
+    if (!is.null(ans)) {
+      warning("Input is a length=1 logical that points to the same address as R's global TRUE value. Therefore the attribute has not been set by reference, rather on a copy. You will need to assign the result back to a variable. See https://github.com/Rdatatable/data.table/issues/1281 for more.")
+      x = ans
     }
-    # fix for #1142 - duplicated levels for factors
-    if (name == "levels" && is.factor(x) && anyDuplicated(value))
-        .Call(Csetlevels, x, (value <- as.character(value)), unique(value))
-    invisible(x)
+  }
+  # fix for #1142 - duplicated levels for factors
+  if (name == "levels" && is.factor(x) && anyDuplicated(value))
+    .Call(Csetlevels, x, (value <- as.character(value)), unique(value))
+  invisible(x)
 }
 
 setnames <- function(x,old,new) {
-    # Sets by reference, maintains truelength, no copy of table at all.
-    # But also more convenient than names(DT)[i]="newname"  because we can also do setnames(DT,"oldname","newname")
-    # without an onerous match() ourselves. old can be positions, too, but we encourage by name for robustness.
-    if (!is.data.frame(x)) stop("x is not a data.table or data.frame")
-    if (!length(attr(x,"names"))) stop("x has no column names")  # because setnames is for user user. Internally, use setattr(x,"names",...)
-    if (length(names(x)) != length(x)) stop("dt is length ",length(dt)," but its names are length ",length(names(x)))
-    if (missing(new)) {
-        # for setnames(DT,new); e.g., setnames(DT,c("A","B")) where ncol(DT)==2
-        if (!is.character(old)) stop("Passed a vector of type '",typeof(old),"'. Needs to be type 'character'.")
-        if (length(old) != ncol(x)) stop("Can't assign ",length(old)," names to a ",ncol(x)," column data.table")
-        # note that duplicate names are permitted to be created in this usage only
-        w = which(names(x) != old)
-        if (!length(w)) return(invisible(x))  # no changes
-        new = old[w]
-        i = w
+  # Sets by reference, maintains truelength, no copy of table at all.
+  # But also more convenient than names(DT)[i]="newname"  because we can also do setnames(DT,"oldname","newname")
+  # without an onerous match() ourselves. old can be positions, too, but we encourage by name for robustness.
+  if (!is.data.frame(x)) stop("x is not a data.table or data.frame")
+  if (!length(attr(x,"names"))) stop("x has no column names")  # because setnames is for user user. Internally, use setattr(x,"names",...)
+  if (length(names(x)) != length(x)) stop("dt is length ",length(dt)," but its names are length ",length(names(x)))
+  if (missing(new)) {
+    # for setnames(DT,new); e.g., setnames(DT,c("A","B")) where ncol(DT)==2
+    if (!is.character(old)) stop("Passed a vector of type '",typeof(old),"'. Needs to be type 'character'.")
+    if (length(old) != ncol(x)) stop("Can't assign ",length(old)," names to a ",ncol(x)," column data.table")
+    # note that duplicate names are permitted to be created in this usage only
+    w = which(names(x) != old)
+    if (!length(w)) return(invisible(x))  # no changes
+    new = old[w]
+    i = w
+  } else {
+    if (missing(old)) stop("When 'new' is provided, 'old' must be provided too")
+    if (!is.character(new)) stop("'new' is not a character vector")
+    if (is.numeric(old)) {
+      if (length(sgn <- unique(sign(old))) != 1L)
+        stop("Items of 'old' is numeric but has both +ve and -ve indices.")
+      tt = abs(old)<1L | abs(old)>length(x) | is.na(old)
+      if (any(tt)) stop("Items of 'old' either NA or outside range [1,",length(x),"]: ",paste(old[tt],collapse=","))
+      i = if (sgn == 1L) as.integer(old) else seq_along(x)[as.integer(old)]
+      if (any(duplicated(i))) stop("Some duplicates exist in 'old': ",paste(i[duplicated(i)],collapse=","))
     } else {
-        if (missing(old)) stop("When 'new' is provided, 'old' must be provided too")
-        if (!is.character(new)) stop("'new' is not a character vector")
-        if (is.numeric(old)) {
-            if (length(sgn <- unique(sign(old))) != 1L)
-                stop("Items of 'old' is numeric but has both +ve and -ve indices.")
-            tt = abs(old)<1L | abs(old)>length(x) | is.na(old)
-            if (any(tt)) stop("Items of 'old' either NA or outside range [1,",length(x),"]: ",paste(old[tt],collapse=","))
-            i = if (sgn == 1L) as.integer(old) else seq_along(x)[as.integer(old)]
-            if (any(duplicated(i))) stop("Some duplicates exist in 'old': ",paste(i[duplicated(i)],collapse=","))
-        } else {
-            if (!is.character(old)) stop("'old' is type ",typeof(old)," but should be integer, double or character")
-            if (any(duplicated(old))) stop("Some duplicates exist in 'old': ", paste(old[duplicated(old)],collapse=","))
-            i = chmatch(old,names(x))
-            if (any(is.na(i))) stop("Items of 'old' not found in column names: ",paste(old[is.na(i)],collapse=","))
-            if (any(tt<-!is.na(chmatch(old,names(x)[-i])))) stop("Some items of 'old' are duplicated (ambiguous) in column names: ",paste(old[tt],collapse=","))
-        }
-        if (length(new)!=length(i)) stop("'old' is length ",length(i)," but 'new' is length ",length(new))
+      if (!is.character(old)) stop("'old' is type ",typeof(old)," but should be integer, double or character")
+      if (any(duplicated(old))) stop("Some duplicates exist in 'old': ", paste(old[duplicated(old)],collapse=","))
+      i = chmatch(old,names(x))
+      if (any(is.na(i))) stop("Items of 'old' not found in column names: ",paste(old[is.na(i)],collapse=","))
+      if (any(tt<-!is.na(chmatch(old,names(x)[-i])))) stop("Some items of 'old' are duplicated (ambiguous) in column names: ",paste(old[tt],collapse=","))
     }
-    # update the key if the column name being change is in the key
-    m = chmatch(names(x)[i], key(x))
+    if (length(new)!=length(i)) stop("'old' is length ",length(i)," but 'new' is length ",length(new))
+  }
+  # update the key if the column name being change is in the key
+  m = chmatch(names(x)[i], key(x))
+  w = which(!is.na(m))
+  if (length(w))
+    .Call(Csetcharvec, attr(x,"sorted"), m[w], new[w])
+
+  # update secondary keys
+  idx = attr(x,"index")
+  for (k in names(attributes(idx))) {
+    tt = strsplit(k,split="__")[[1]][-1]
+    m = chmatch(names(x)[i], tt)
     w = which(!is.na(m))
-    if (length(w))
-        .Call(Csetcharvec, attr(x,"sorted"), m[w], new[w])
-
-    # update secondary keys
-    idx = attr(x,"index")
-    for (k in names(attributes(idx))) {
-        tt = strsplit(k,split="__")[[1]][-1]
-        m = chmatch(names(x)[i], tt)
-        w = which(!is.na(m))
-        if (length(w)) {
-            tt[m[w]] = new[w]
-            newk = paste("__",paste(tt,collapse="__"),sep="")
-            setattr(idx, newk, attr(idx, k))
-            setattr(idx, k, NULL)
-        }
+    if (length(w)) {
+      tt[m[w]] = new[w]
+      newk = paste("__",paste(tt,collapse="__"),sep="")
+      setattr(idx, newk, attr(idx, k))
+      setattr(idx, k, NULL)
     }
+  }
 
-    .Call(Csetcharvec, attr(x,"names"), as.integer(i), new)
-    invisible(x)
+  .Call(Csetcharvec, attr(x,"names"), as.integer(i), new)
+  invisible(x)
 }
 
 setcolorder <- function(x, neworder)
 {
-    if (any(duplicated(neworder))) stop("neworder contains duplicates")
-    # if (!is.data.table(x)) stop("x is not a data.table")
-    if (length(neworder) != length(x)) {
-      if (length(neworder) > length(x))
-        stop("neworder is length ", length(neworder),
-             " but x has only ", length(x), " columns.")
-      #if shorter than length(x), pad by the missing
-      #  elements (checks below will catch other mistakes)
-      neworder = c(neworder, setdiff(if (is.character(neworder)) names(x)
-                                     else seq_along(x), neworder))
-    }
-    if (is.character(neworder)) {
-        if (any(duplicated(names(x)))) stop("x has some duplicated column name(s): ", paste(names(x)[duplicated(names(x))], collapse=","), ". Please remove or rename the duplicate(s) and try again.")
-        o = as.integer(chmatch(neworder, names(x)))
-        if (any(is.na(o))) stop("Names in neworder not found in x: ", paste(neworder[is.na(o)], collapse=","))
-    } else {
-        if (!is.numeric(neworder)) stop("neworder is not a character or numeric vector")
-        o = as.integer(neworder)
-        m = !(o %in% seq_len(length(x)))
-        if (any(m)) stop("Column numbers in neworder out of bounds: ", paste(o[m], collapse=","))
-    }
-    .Call(Csetcolorder, x, o)
-    invisible(x)
+  if (any(duplicated(neworder))) stop("neworder contains duplicates")
+  # if (!is.data.table(x)) stop("x is not a data.table")
+  if (length(neworder) != length(x)) {
+    if (length(neworder) > length(x))
+    stop("neworder is length ", length(neworder),
+       " but x has only ", length(x), " columns.")
+    #if shorter than length(x), pad by the missing
+    #  elements (checks below will catch other mistakes)
+    neworder = c(neworder, setdiff(if (is.character(neworder)) names(x)
+                                   else seq_along(x), neworder))
+  }
+  if (is.character(neworder)) {
+    if (any(duplicated(names(x)))) stop("x has some duplicated column name(s): ", paste(names(x)[duplicated(names(x))], collapse=","), ". Please remove or rename the duplicate(s) and try again.")
+    o = as.integer(chmatch(neworder, names(x)))
+    if (any(is.na(o))) stop("Names in neworder not found in x: ", paste(neworder[is.na(o)], collapse=","))
+  } else {
+    if (!is.numeric(neworder)) stop("neworder is not a character or numeric vector")
+    o = as.integer(neworder)
+    m = !(o %in% seq_len(length(x)))
+    if (any(m)) stop("Column numbers in neworder out of bounds: ", paste(o[m], collapse=","))
+  }
+  .Call(Csetcolorder, x, o)
+  invisible(x)
 }
 
 set <- function(x,i=NULL,j,value)  # low overhead, loopable
 {
-    if (is.atomic(value)) {
-        # protect NAMED of atomic value from .Call's NAMED=2 by wrapping with list()
-        l = vector("list",1)
-        .Call(Csetlistelt,l,1L,value)  # to avoid the copy by list() in R < 3.1.0
-        value = l
-    }
-    .Call(Cassign,x,i,j,NULL,value,FALSE)   #  verbose=FALSE for speed to avoid getOption()  TO DO: somehow read getOption("datatable.verbose") from C level
-    invisible(x)
+  if (is.atomic(value)) {
+    # protect NAMED of atomic value from .Call's NAMED=2 by wrapping with list()
+    l = vector("list",1)
+    .Call(Csetlistelt,l,1L,value)  # to avoid the copy by list() in R < 3.1.0
+    value = l
+  }
+  .Call(Cassign,x,i,j,NULL,value,FALSE)   #  verbose=FALSE for speed to avoid getOption()  TO DO: somehow read getOption("datatable.verbose") from C level
+  invisible(x)
 }
 
 chmatch <- function(x,table,nomatch=NA_integer_)
-    .Call(Cchmatchwrapper,x,table,as.integer(nomatch[1L]),FALSE) # [1L] to fix #1672
+  .Call(Cchmatchwrapper,x,table,as.integer(nomatch[1L]),FALSE) # [1L] to fix #1672
 
 "%chin%" <- function(x,table) {
-    # TO DO  if table has 'ul' then match to that
-    .Call(Cchmatchwrapper,x,table,NA_integer_,TRUE)
+  # TO DO  if table has 'ul' then match to that
+  .Call(Cchmatchwrapper,x,table,NA_integer_,TRUE)
 }
 
 chorder <- function(x) {
-    o = forderv(x, sort=TRUE, retGrp=FALSE)
-    if (length(o)) o else seq_along(x)
+  o = forderv(x, sort=TRUE, retGrp=FALSE)
+  if (length(o)) o else seq_along(x)
 }
 
 chgroup <- function(x) {
-    # TO DO: deprecate and remove this. It's exported but doubt anyone uses it. Think the plan was to use it internally, but forderv superceded.
-    o = forderv(x, sort=FALSE, retGrp=TRUE)
-    if (length(o)) as.vector(o) else seq_along(x)  # as.vector removes the attributes
+  # TO DO: deprecate and remove this. It's exported but doubt anyone uses it. Think the plan was to use it internally, but forderv superceded.
+  o = forderv(x, sort=FALSE, retGrp=TRUE)
+  if (length(o)) as.vector(o) else seq_along(x)  # as.vector removes the attributes
 }
 
 
 .rbind.data.table <- function(..., use.names=TRUE, fill=FALSE, idcol=NULL) {
-    # See FAQ 2.23
-    # Called from base::rbind.data.frame
-    # fix for #1626.. because some packages (like psych) bind an input
-    # data.frame/data.table with a matrix..
-    l = lapply(list(...), function(x) if (is.list(x)) x else as.data.table(x))
-    rbindlist(l, use.names, fill, idcol)
+  # See FAQ 2.23
+  # Called from base::rbind.data.frame
+  # fix for #1626.. because some packages (like psych) bind an input
+  # data.frame/data.table with a matrix..
+  l = lapply(list(...), function(x) if (is.list(x)) x else as.data.table(x))
+  rbindlist(l, use.names, fill, idcol)
 }
 
 rbindlist <- function(l, use.names=fill, fill=FALSE, idcol=NULL) {
-    if (identical(idcol, FALSE)) idcol = NULL
-    else if (!is.null(idcol)) {
-        if (isTRUE(idcol)) idcol = ".id"
-        if (!is.character(idcol)) stop("idcol must be a logical or character vector of length 1. If logical TRUE the id column will named '.id'.")
-        idcol = idcol[1L]
-    }
-    # fix for #1467, quotes result in "not resolved in current namespace" error
-    ans = .Call(Crbindlist, l, use.names, fill, idcol)
-    if (!length(ans)) return(null.data.table())
-    setDT(ans)[]
+  if (identical(idcol, FALSE)) idcol = NULL
+  else if (!is.null(idcol)) {
+    if (isTRUE(idcol)) idcol = ".id"
+    if (!is.character(idcol)) stop("idcol must be a logical or character vector of length 1. If logical TRUE the id column will named '.id'.")
+    idcol = idcol[1L]
+  }
+  # fix for #1467, quotes result in "not resolved in current namespace" error
+  ans = .Call(Crbindlist, l, use.names, fill, idcol)
+  if (!length(ans)) return(null.data.table())
+  setDT(ans)[]
 }
 
 vecseq <- function(x,y,clamp) .Call(Cvecseq,x,y,clamp)
@@ -2605,7 +2600,7 @@ setDF <- function(x, rownames=NULL) {
     # copied from as.data.frame.data.table
     if (is.null(rownames)) {
       rn <- .set_row_names(nrow(x))
-    }   else {
+    } else {
       if (length(rownames) != nrow(x))
         stop("rownames incorrect length; expected ", nrow(x), " names, got ", length(rownames))
       rn <- rownames
@@ -2615,7 +2610,7 @@ setDF <- function(x, rownames=NULL) {
     setattr(x, "sorted", NULL)
     setattr(x, ".internal.selfref", NULL)
   } else if (is.data.frame(x)) {
-    if (!is.null(rownames)){
+    if (!is.null(rownames)) {
       if (length(rownames) != nrow(x))
         stop("rownames incorrect length; expected ", nrow(x), " names, got ", length(rownames))
       setattr(x, "row.names", rownames)
@@ -2640,7 +2635,7 @@ setDF <- function(x, rownames=NULL) {
       rn <- .set_row_names(mn)
     } else {
       if (length(rownames) != mn)
-        stop("rownames incorrect length; expected ", mn, " names, got ", length(rownames))
+      stop("rownames incorrect length; expected ", mn, " names, got ", length(rownames))
       rn <- rownames
     }
     setattr(x,"row.names", rn)
@@ -2650,149 +2645,149 @@ setDF <- function(x, rownames=NULL) {
 }
 
 setDT <- function(x, keep.rownames=FALSE, key=NULL, check.names=FALSE) {
-    name = substitute(x)
-    if (is.name(name)) {
-        home <- function(x, env) {
-            if (identical(env, emptyenv()))
-                stop("Can not find symbol ", cname, call. = FALSE)
-            else if (exists(x, env, inherits=FALSE)) env
-            else home(x, parent.env(env))
-        }
-        cname = as.character(name)
-        envir = home(cname, parent.frame())
-        if (bindingIsLocked(cname, envir)) {
-            stop("Can not convert '", cname, "' to data.table by reference because binding is locked. It is very likely that '", cname, "' resides within a package (or an environment) that is locked to prevent modifying its variable bindings. Try copying the object to your current environment, ex: var <- copy(var) and then using setDT again.")
-        }
+  name = substitute(x)
+  if (is.name(name)) {
+    home <- function(x, env) {
+      if (identical(env, emptyenv()))
+        stop("Can not find symbol ", cname, call. = FALSE)
+      else if (exists(x, env, inherits=FALSE)) env
+      else home(x, parent.env(env))
+    }
+    cname = as.character(name)
+    envir = home(cname, parent.frame())
+    if (bindingIsLocked(cname, envir)) {
+      stop("Can not convert '", cname, "' to data.table by reference because binding is locked. It is very likely that '", cname, "' resides within a package (or an environment) that is locked to prevent modifying its variable bindings. Try copying the object to your current environment, ex: var <- copy(var) and then using setDT again.")
     }
-    if (is.data.table(x)) {
-        # fix for #1078 and #1128, see .resetclass() for explanation.
-        setattr(x, 'class', .resetclass(x, 'data.table'))
-        if (!missing(key)) setkeyv(x, key) # fix for #1169
-        if (check.names) setattr(x, "names", make.names(names(x), unique=TRUE))
-        if (selfrefok(x) > 0) return(invisible(x)) else alloc.col(x)
-    } else if (is.data.frame(x)) {
-        rn = if (!identical(keep.rownames, FALSE)) rownames(x) else NULL
-        setattr(x, "row.names", .set_row_names(nrow(x)))
-        if (check.names) setattr(x, "names", make.names(names(x), unique=TRUE))
-        # fix for #1078 and #1128, see .resetclass() for explanation.
-        setattr(x, "class", .resetclass(x, 'data.frame'))
-        alloc.col(x)
-        if (!is.null(rn)) {
-            nm = c(if (is.character(keep.rownames)) keep.rownames[1L] else "rn", names(x))
-            x[, (nm[1L]) := rn]
-            setcolorder(x, nm)
-        }
-    } else if (is.null(x) || (is.list(x) && !length(x))) {
-        x = null.data.table()
-    } else if (is.list(x)) {
-        # copied from as.data.table.list - except removed the copy
-        for (i in seq_along(x)) {
-            if (inherits(x[[i]], "POSIXlt"))
-            stop("Column ", i, " is of POSIXlt type. Please convert it to POSIXct using as.POSIXct and run setDT again. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
-        }
-        n = vapply(x, length, 0L)
-        mn = max(n)
-        if (any(n<mn))
-            stop("All elements in argument 'x' to 'setDT' must be of same length")
-        xn = names(x)
-        if (is.null(xn)) {
-            setattr(x, "names", paste("V",seq_len(length(x)),sep=""))
-        } else {
-            idx = xn %chin% "" # names can be NA - test 1006 caught that!
-            if (any(idx)) {
-                xn[idx] = paste("V", seq_along(which(idx)), sep="")
-                setattr(x, "names", xn)
-            }
-            if (check.names) setattr(x, "names", make.names(xn, unique=TRUE))
-        }
-        setattr(x,"row.names",.set_row_names(max(n)))
-        setattr(x,"class",c("data.table","data.frame"))
-        alloc.col(x)
+  }
+  if (is.data.table(x)) {
+    # fix for #1078 and #1128, see .resetclass() for explanation.
+    setattr(x, 'class', .resetclass(x, 'data.table'))
+    if (!missing(key)) setkeyv(x, key) # fix for #1169
+    if (check.names) setattr(x, "names", make.names(names(x), unique=TRUE))
+    if (selfrefok(x) > 0) return(invisible(x)) else alloc.col(x)
+  } else if (is.data.frame(x)) {
+    rn = if (!identical(keep.rownames, FALSE)) rownames(x) else NULL
+    setattr(x, "row.names", .set_row_names(nrow(x)))
+    if (check.names) setattr(x, "names", make.names(names(x), unique=TRUE))
+    # fix for #1078 and #1128, see .resetclass() for explanation.
+    setattr(x, "class", .resetclass(x, 'data.frame'))
+    alloc.col(x)
+    if (!is.null(rn)) {
+      nm = c(if (is.character(keep.rownames)) keep.rownames[1L] else "rn", names(x))
+      x[, (nm[1L]) := rn]
+      setcolorder(x, nm)
+    }
+  } else if (is.null(x) || (is.list(x) && !length(x))) {
+    x = null.data.table()
+  } else if (is.list(x)) {
+    # copied from as.data.table.list - except removed the copy
+    for (i in seq_along(x)) {
+      if (inherits(x[[i]], "POSIXlt"))
+      stop("Column ", i, " is of POSIXlt type. Please convert it to POSIXct using as.POSIXct and run setDT again. We do not recommend use of POSIXlt at all because it uses 40 bytes to store one date.")
+    }
+    n = vapply(x, length, 0L)
+    mn = max(n)
+    if (any(n<mn))
+      stop("All elements in argument 'x' to 'setDT' must be of same length")
+    xn = names(x)
+    if (is.null(xn)) {
+      setattr(x, "names", paste("V",seq_len(length(x)),sep=""))
     } else {
-        stop("Argument 'x' to 'setDT' should be a 'list', 'data.frame' or 'data.table'")
-    }
-    if (!is.null(key)) setkeyv(x, key)
-    if (is.name(name)) {
-        name = as.character(name)
-        assign(name, x, parent.frame(), inherits=TRUE)
-    } else if (is.call(name) && (name[[1L]] == "$" || name[[1L]] == "[[") && is.name(name[[2L]])) {
-        # common case is call from 'lapply()'
-        k = eval(name[[2L]], parent.frame(), parent.frame())
-        if (is.list(k)) {
-            origj = j = if (name[[1L]] == "$") as.character(name[[3L]]) else eval(name[[3L]], parent.frame(), parent.frame())
-            if (length(j) == 1L) {
-                if (is.character(j)) {
-                    j = match(j, names(k))
-                    if (is.na(j))
-                        stop("Item '", origj, "' not found in names of input list")
-                }
-            }
-            .Call(Csetlistelt,k,as.integer(j), x)
-        } else if (is.environment(k) && exists(as.character(name[[3L]]), k)) {
-            assign(as.character(name[[3L]]), x, k, inherits=FALSE)
+      idx = xn %chin% "" # names can be NA - test 1006 caught that!
+      if (any(idx)) {
+        xn[idx] = paste("V", seq_along(which(idx)), sep="")
+        setattr(x, "names", xn)
+      }
+      if (check.names) setattr(x, "names", make.names(xn, unique=TRUE))
+    }
+    setattr(x,"row.names",.set_row_names(max(n)))
+    setattr(x,"class",c("data.table","data.frame"))
+    alloc.col(x)
+  } else {
+    stop("Argument 'x' to 'setDT' should be a 'list', 'data.frame' or 'data.table'")
+  }
+  if (!is.null(key)) setkeyv(x, key)
+  if (is.name(name)) {
+    name = as.character(name)
+    assign(name, x, parent.frame(), inherits=TRUE)
+  } else if (is.call(name) && (name[[1L]] == "$" || name[[1L]] == "[[") && is.name(name[[2L]])) {
+    # common case is call from 'lapply()'
+    k = eval(name[[2L]], parent.frame(), parent.frame())
+    if (is.list(k)) {
+      origj = j = if (name[[1L]] == "$") as.character(name[[3L]]) else eval(name[[3L]], parent.frame(), parent.frame())
+      if (length(j) == 1L) {
+        if (is.character(j)) {
+          j = match(j, names(k))
+          if (is.na(j))
+            stop("Item '", origj, "' not found in names of input list")
         }
+      }
+      .Call(Csetlistelt,k,as.integer(j), x)
+    } else if (is.environment(k) && exists(as.character(name[[3L]]), k)) {
+      assign(as.character(name[[3L]]), x, k, inherits=FALSE)
     }
-    invisible(x)
+  }
+  invisible(x)
 }
 
 as_list <- function(x) {
-    lx = vector("list", 1L)
-    .Call(Csetlistelt, lx, 1L, x)
-    lx
+  lx = vector("list", 1L)
+  .Call(Csetlistelt, lx, 1L, x)
+  lx
 }
 
 # FR #1353
 rowid <- function(..., prefix=NULL) {
-    rowidv(list(...), prefix=prefix)
+  rowidv(list(...), prefix=prefix)
 }
 
 rowidv <- function(x, cols=seq_along(x), prefix=NULL) {
-    if (!is.null(prefix) && (!is.character(prefix) || length(prefix) != 1L))
-        stop("prefix must be NULL or a character vector of length=1.")
-    if (is.atomic(x)) {
-        if (!missing(cols) && !is.null(cols))
-            stop("x is a single vector, non-NULL 'cols' doesn't make sense.")
-        cols = 1L
-        x = as_list(x)
-    } else {
-        if (!length(cols))
-            stop("x is a list, 'cols' can not be on 0-length.")
-        if (is.character(cols))
-            cols = chmatch(cols, names(x))
-        cols = as.integer(cols)
-    }
-    xorder = forderv(x, by=cols, sort=FALSE, retGrp=TRUE) # speedup on char with sort=FALSE
-    xstart = attr(xorder, 'start')
-    if (!length(xorder)) xorder = seq_along(x[[1L]])
-    ids = .Call(Cfrank, xorder, xstart, uniqlengths(xstart, length(xorder)), "sequence")
-    if (!is.null(prefix))
-        ids = paste0(prefix, ids)
-    ids
+  if (!is.null(prefix) && (!is.character(prefix) || length(prefix) != 1L))
+    stop("prefix must be NULL or a character vector of length=1.")
+  if (is.atomic(x)) {
+    if (!missing(cols) && !is.null(cols))
+      stop("x is a single vector, non-NULL 'cols' doesn't make sense.")
+    cols = 1L
+    x = as_list(x)
+  } else {
+    if (!length(cols))
+      stop("x is a list, 'cols' can not be on 0-length.")
+    if (is.character(cols))
+      cols = chmatch(cols, names(x))
+    cols = as.integer(cols)
+  }
+  xorder = forderv(x, by=cols, sort=FALSE, retGrp=TRUE) # speedup on char with sort=FALSE
+  xstart = attr(xorder, 'start')
+  if (!length(xorder)) xorder = seq_along(x[[1L]])
+  ids = .Call(Cfrank, xorder, xstart, uniqlengths(xstart, length(xorder)), "sequence")
+  if (!is.null(prefix))
+    ids = paste0(prefix, ids)
+  ids
 }
 
 # FR #686
 rleid <- function(..., prefix=NULL) {
-    rleidv(list(...), prefix=prefix)
+  rleidv(list(...), prefix=prefix)
 }
 
 rleidv <- function(x, cols=seq_along(x), prefix=NULL) {
-    if (!is.null(prefix) && (!is.character(prefix) || length(prefix) != 1L))
-        stop("prefix must be NULL or a character vector of length=1.")
-    if (is.atomic(x)) {
-        if (!missing(cols) && !is.null(cols))
-            stop("x is a single vector, non-NULL 'cols' doesn't make sense.")
-        cols = 1L
-        x = as_list(x)
-    } else {
-        if (!length(cols))
-            stop("x is a list, 'cols' can not be 0-length.")
-        if (is.character(cols))
-            cols = chmatch(cols, names(x))
-        cols = as.integer(cols)
-    }
-    ids = .Call(Crleid, x, cols)
-    if (!is.null(prefix)) ids = paste0(prefix, ids)
-    ids
+  if (!is.null(prefix) && (!is.character(prefix) || length(prefix) != 1L))
+    stop("prefix must be NULL or a character vector of length=1.")
+  if (is.atomic(x)) {
+    if (!missing(cols) && !is.null(cols))
+      stop("x is a single vector, non-NULL 'cols' doesn't make sense.")
+    cols = 1L
+    x = as_list(x)
+  } else {
+    if (!length(cols))
+      stop("x is a list, 'cols' can not be 0-length.")
+    if (is.character(cols))
+      cols = chmatch(cols, names(x))
+    cols = as.integer(cols)
+  }
+  ids = .Call(Crleid, x, cols)
+  if (!is.null(prefix)) ids = paste0(prefix, ids)
+  ids
 }
 
 # GForce functions
@@ -2812,5 +2807,5 @@ gsd <- function(x, na.rm=FALSE) .Call(Cgsd, x, na.rm)
 gforce <- function(env, jsub, o, f, l, rows) .Call(Cgforce, env, jsub, o, f, l, rows)
 
 isReallyReal <- function(x) {
-    .Call(CisReallyReal, x)
+  .Call(CisReallyReal, x)
 }
diff --git a/R/duplicated.R b/R/duplicated.R
index 37f74c148b..fdf6cae095 100644
--- a/R/duplicated.R
+++ b/R/duplicated.R
@@ -1,42 +1,42 @@
 
 duplicated.data.table <- function(x, incomparables=FALSE, fromLast=FALSE, by=seq_along(x), ...) {
-    if (!cedta()) return(NextMethod("duplicated"))
-    if (!identical(incomparables, FALSE)) {
-        .NotYetUsed("incomparables != FALSE")
-    }
-    if (missing(by) && isTRUE(getOption("datatable.old.unique.by.key")))  #1284
-        by = key(x)
-    if (nrow(x) == 0L || ncol(x) == 0L) return(logical(0)) # fix for bug #5582
-    if (is.na(fromLast) || !is.logical(fromLast)) stop("'fromLast' must be TRUE or FALSE")
-    query <- .duplicated.helper(x, by)
-    # fix for bug #5405 - unique on null data table returns error (because of 'forderv')
-    # however, in this case we can bypass having to go to forderv at all.
-    if (!length(query$by)) return(logical(0))
-    res <- rep.int(TRUE, nrow(x))
+  if (!cedta()) return(NextMethod("duplicated"))
+  if (!identical(incomparables, FALSE)) {
+    .NotYetUsed("incomparables != FALSE")
+  }
+  if (missing(by) && isTRUE(getOption("datatable.old.unique.by.key")))  #1284
+    by = key(x)
+  if (nrow(x) == 0L || ncol(x) == 0L) return(logical(0)) # fix for bug #5582
+  if (is.na(fromLast) || !is.logical(fromLast)) stop("'fromLast' must be TRUE or FALSE")
+  query <- .duplicated.helper(x, by)
+  # fix for bug #5405 - unique on null data table returns error (because of 'forderv')
+  # however, in this case we can bypass having to go to forderv at all.
+  if (!length(query$by)) return(logical(0))
+  res <- rep.int(TRUE, nrow(x))
 
-    if (query$use.keyprefix) {
-        f = uniqlist(shallow(x, query$by))
-        if (fromLast) f = cumsum(uniqlengths(f, nrow(x)))
-    } else {
-        o = forderv(x, by=query$by, sort=FALSE, retGrp=TRUE)
-        f = attr(o,"starts")
-        if (fromLast) f = cumsum(uniqlengths(f, nrow(x)))
-        if (length(o)) f=o[f]
-    }
-    res[f] = FALSE
-    res
+  if (query$use.keyprefix) {
+    f = uniqlist(shallow(x, query$by))
+    if (fromLast) f = cumsum(uniqlengths(f, nrow(x)))
+  } else {
+    o = forderv(x, by=query$by, sort=FALSE, retGrp=TRUE)
+    f = attr(o,"starts")
+    if (fromLast) f = cumsum(uniqlengths(f, nrow(x)))
+    if (length(o)) f=o[f]
+  }
+  res[f] = FALSE
+  res
 }
 
 unique.data.table <- function(x, incomparables=FALSE, fromLast=FALSE, by=seq_along(x), ...) {
-    if (!cedta()) return(NextMethod("unique"))
-    if (missing(by) && isTRUE(getOption("datatable.old.unique.by.key")))  #1284
-        by = key(x)
-    dups <- duplicated.data.table(x, incomparables, fromLast, by, ...)
-    ans <- .Call(CsubsetDT, x, which_(dups, FALSE), seq_len(ncol(x))) # more memory efficient version of which(!dups)
-    if (nrow(x) != nrow(ans)) setindexv(ans, NULL)[] else ans #1760
-    # i.e. x[!dups] but avoids [.data.table overhead when unique() is loop'd
-    # TO DO: allow logical to be passed through to C level, and allow cols=NULL to mean all, for further speed gain.
-    #        See news for v1.9.3 for link to benchmark use-case on datatable-help.
+  if (!cedta()) return(NextMethod("unique"))
+  if (missing(by) && isTRUE(getOption("datatable.old.unique.by.key")))  #1284
+    by = key(x)
+  dups <- duplicated.data.table(x, incomparables, fromLast, by, ...)
+  ans <- .Call(CsubsetDT, x, which_(dups, FALSE), seq_len(ncol(x))) # more memory efficient version of which(!dups)
+  if (nrow(x) != nrow(ans)) setindexv(ans, NULL)[] else ans #1760
+  # i.e. x[!dups] but avoids [.data.table overhead when unique() is loop'd
+  # TO DO: allow logical to be passed through to C level, and allow cols=NULL to mean all, for further speed gain.
+  #        See news for v1.9.3 for link to benchmark use-case on datatable-help.
 }
 
 ## Specify the column names to be used in the uniqueness query, and if this
@@ -48,35 +48,35 @@ unique.data.table <- function(x, incomparables=FALSE, fromLast=FALSE, by=seq_alo
 ## unique.data.table has been refactored to simply call duplicated.data.table
 ## making the refactor unnecessary, but let's leave it here just in case
 .duplicated.helper <- function(x, by) {
-    use.sub.cols <- !is.null(by) # && !isTRUE(by) # Fixing bug #5424
+  use.sub.cols <- !is.null(by) # && !isTRUE(by) # Fixing bug #5424
 
-    if (use.sub.cols) {
-        ## Did the user specify (integer) indexes for the columns?
-        if (is.numeric(by)) {
-            if (any(as.integer(by) != by) || any(by<1) || any(by>ncol(x))) {
-                stop("Integer values between 1 and ncol are required for 'by' when ",
-                     "column indices. It's often better to use column names.")
-            }
-            by <- names(x)[by]
-        }
-        if (!is.character(by)) {
-            stop("Only NULL, column indices or column names are allowed in by")
-        }
-        bad.cols <- setdiff(by, names(x))
-        if (length(bad.cols)) {
-            stop("by specifies column names that do not exist. First 5: ",paste(head(bad.cols,5),collapse=","))
-        }
-
-        use.keyprefix = haskey(x) &&
-            length(by) <= length(key(x)) &&
-            all(head(key(x), length(by)) == by)
-    } else {
-        ## by is not was explicitly set to
-        use.keyprefix = FALSE
-        by = names(x)
+  if (use.sub.cols) {
+    ## Did the user specify (integer) indexes for the columns?
+    if (is.numeric(by)) {
+      if (any(as.integer(by) != by) || any(by<1) || any(by>ncol(x))) {
+        stop("Integer values between 1 and ncol are required for 'by' when ",
+             "column indices. It's often better to use column names.")
+      }
+      by <- names(x)[by]
+    }
+    if (!is.character(by)) {
+      stop("Only NULL, column indices or column names are allowed in by")
     }
+    bad.cols <- setdiff(by, names(x))
+    if (length(bad.cols)) {
+      stop("by specifies column names that do not exist. First 5: ",paste(head(bad.cols,5),collapse=","))
+    }
+
+    use.keyprefix = haskey(x) &&
+      length(by) <= length(key(x)) &&
+      all(head(key(x), length(by)) == by)
+  } else {
+    ## by is not was explicitly set to
+    use.keyprefix = FALSE
+    by = names(x)
+  }
 
-    list(use.keyprefix=use.keyprefix, by=by)
+  list(use.keyprefix=use.keyprefix, by=by)
 }
 
 # FR #5172 anyDuplicated.data.table
@@ -84,13 +84,13 @@ unique.data.table <- function(x, incomparables=FALSE, fromLast=FALSE, by=seq_alo
 # In that sense, this anyDuplicated is *not* the same as base's - meaning it's not a different implementation
 # This is just a wrapper. That being said, it should be incredibly fast on data.tables (due to data.table's fast forder)
 anyDuplicated.data.table <- function(x, incomparables=FALSE, fromLast=FALSE, by=seq_along(x), ...) {
-    if (!cedta()) return(NextMethod("anyDuplicated"))
-    if (missing(by) && isTRUE(getOption("datatable.old.unique.by.key")))  #1284
-        by = key(x)
-    dups <- duplicated(x, incomparables, fromLast, by, ...)
-    if (fromLast) idx = tail(which(dups), 1L) else idx = head(which(dups), 1L)
-    if (!length(idx)) idx=0L
-    idx
+  if (!cedta()) return(NextMethod("anyDuplicated"))
+  if (missing(by) && isTRUE(getOption("datatable.old.unique.by.key")))  #1284
+    by = key(x)
+  dups <- duplicated(x, incomparables, fromLast, by, ...)
+  if (fromLast) idx = tail(which(dups), 1L) else idx = head(which(dups), 1L)
+  if (!length(idx)) idx=0L
+  idx
 }
 
 # simple straightforward helper function to get the number
@@ -98,21 +98,21 @@ anyDuplicated.data.table <- function(x, incomparables=FALSE, fromLast=FALSE, by=
 # we really mean `.SD` - used in a grouping operation
 # TODO: optimise uniqueN further with GForce.
 uniqueN <- function(x, by = if (is.list(x)) seq_along(x) else NULL, na.rm=FALSE) { # na.rm, #1455
-    if (missing(by) && is.data.table(x) && isTRUE(getOption("datatable.old.unique.by.key")))  #1284
-        by = key(x)
-    if (is.null(x)) return(0L)
-    if (!is.atomic(x) && !is.data.frame(x))
-        stop("x must be an atomic vector or data.frames/data.tables")
-    if (is.atomic(x)) x = as_list(x)
-    if (is.null(by)) by = seq_along(x)
-    o = forderv(x, by=by, retGrp=TRUE, na.last=if (!na.rm) FALSE else NA)
-    starts = attr(o, 'starts')
-    if (!na.rm) {
-        length(starts)
-    } else {
-        # TODO: internal efficient sum
-        # fix for #1771, account for already sorted input
-        sum( (if (length(o)) o[starts] else starts) != 0L)
-    }
+  if (missing(by) && is.data.table(x) && isTRUE(getOption("datatable.old.unique.by.key")))  #1284
+    by = key(x)
+  if (is.null(x)) return(0L)
+  if (!is.atomic(x) && !is.data.frame(x))
+    stop("x must be an atomic vector or data.frames/data.tables")
+  if (is.atomic(x)) x = as_list(x)
+  if (is.null(by)) by = seq_along(x)
+  o = forderv(x, by=by, retGrp=TRUE, na.last=if (!na.rm) FALSE else NA)
+  starts = attr(o, 'starts')
+  if (!na.rm) {
+    length(starts)
+  } else {
+    # TODO: internal efficient sum
+    # fix for #1771, account for already sorted input
+    sum( (if (length(o)) o[starts] else starts) != 0L)
+  }
 }
 
diff --git a/R/fcast.R b/R/fcast.R
index de563911bd..a6927e82e4 100644
--- a/R/fcast.R
+++ b/R/fcast.R
@@ -1,233 +1,233 @@
 guess <- function(x) {
-    if ("value" %chin% names(x))
-        return("value")
-    if ("(all)" %chin% names(x))
-        return("(all)")
-    var <- names(x)[ncol(x)]
-    message("Using '", var, "' as value column. Use 'value.var' to override")
-    return(var)
+  if ("value" %chin% names(x))
+    return("value")
+  if ("(all)" %chin% names(x))
+    return("(all)")
+  var <- names(x)[ncol(x)]
+  message("Using '", var, "' as value column. Use 'value.var' to override")
+  return(var)
 }
 
 dcast <- function(data, formula, fun.aggregate = NULL, ..., margins = NULL,
-            subset = NULL, fill = NULL, value.var = guess(data)) {
-    if (is.data.table(data))
-        UseMethod("dcast", data)
-    else
-        reshape2::dcast(data, formula, fun.aggregate = fun.aggregate, ..., margins = margins,
-            subset = subset, fill = fill, value.var = value.var)
+      subset = NULL, fill = NULL, value.var = guess(data)) {
+  if (is.data.table(data))
+    UseMethod("dcast", data)
+  else
+    reshape2::dcast(data, formula, fun.aggregate = fun.aggregate, ..., margins = margins,
+      subset = subset, fill = fill, value.var = value.var)
 }
 
 check_formula <- function(formula, varnames, valnames) {
-    if (is.character(formula)) formula = as.formula(formula)
-    if (class(formula) != "formula" || length(formula) != 3L)
-        stop("Invalid formula. Cast formula should be of the form LHS ~ RHS, for e.g., a + b ~ c.")
-    vars = all.vars(formula)
-    vars = vars[!vars %chin% c(".", "...")]
-    allvars = c(vars, valnames)
-    if (any(allvars %in% varnames[duplicated(varnames)]))
-      stop('data.table to cast must have unique column names')
-    ans = deparse_formula(as.list(formula)[-1L], varnames, allvars)
+  if (is.character(formula)) formula = as.formula(formula)
+  if (class(formula) != "formula" || length(formula) != 3L)
+    stop("Invalid formula. Cast formula should be of the form LHS ~ RHS, for e.g., a + b ~ c.")
+  vars = all.vars(formula)
+  vars = vars[!vars %chin% c(".", "...")]
+  allvars = c(vars, valnames)
+  if (any(allvars %in% varnames[duplicated(varnames)]))
+    stop('data.table to cast must have unique column names')
+  ans = deparse_formula(as.list(formula)[-1L], varnames, allvars)
 }
 
 deparse_formula <- function(expr, varnames, allvars) {
-    lvars = lapply(expr, function(this) {
-        if (is.call(this)) {
-            if (this[[1L]] == quote(`+`))
-                unlist(deparse_formula(as.list(this)[-1L], varnames, allvars))
-            else this
-        } else if (is.name(this)) {
-            if (this == quote(`...`)) {
-                subvars = setdiff(varnames, allvars)
-                lapply(subvars, as.name)
-            } else this
-        }
-    })
-    lvars = lapply(lvars, function(x) if (length(x) && !is.list(x)) list(x) else x)
+  lvars = lapply(expr, function(this) {
+    if (is.call(this)) {
+      if (this[[1L]] == quote(`+`))
+        unlist(deparse_formula(as.list(this)[-1L], varnames, allvars))
+      else this
+    } else if (is.name(this)) {
+      if (this == quote(`...`)) {
+        subvars = setdiff(varnames, allvars)
+        lapply(subvars, as.name)
+      } else this
+    }
+  })
+  lvars = lapply(lvars, function(x) if (length(x) && !is.list(x)) list(x) else x)
 }
 
 value_vars <- function(value.var, varnames) {
-    if (is.character(value.var))
-        value.var = list(value.var)
-    value.var = lapply(value.var, unique)
-    valnames = unique(unlist(value.var))
-    iswrong = which(!valnames %in% varnames)
-    if (length(iswrong))
-        stop("value.var values [", paste(value.var[iswrong], collapse=", "), "] are not found in 'data'.")
-    value.var
+  if (is.character(value.var))
+    value.var = list(value.var)
+  value.var = lapply(value.var, unique)
+  valnames = unique(unlist(value.var))
+  iswrong = which(!valnames %in% varnames)
+  if (length(iswrong))
+    stop("value.var values [", paste(value.var[iswrong], collapse=", "), "] are not found in 'data'.")
+  value.var
 }
 
 aggregate_funs <- function(funs, vals, sep="_", ...) {
-    if (is.call(funs) && funs[[1L]] == "eval")
-        funs = eval(funs[[2L]], parent.frame(2L), parent.frame(2L))
-    if (is.call(funs) && as.character(funs[[1L]]) %in% c("c", "list"))
-        funs = lapply(as.list(funs)[-1L], function(x) {
-            if (is.call(x) && as.character(x[[1L]]) %in% c("c", "list")) as.list(x)[-1L] else x
-        })
-    else funs = list(funs)
-    if (length(funs) != length(vals)) {
-        if (length(vals) == 1L)
-            vals = replicate(length(funs), vals)
-        else stop("When 'fun.aggregate' and 'value.var' are both lists, 'value.var' must be either of length =1 or =length(fun.aggregate).")
-    }
-    only_one_fun = length(unlist(funs)) == 1L
-    dots = list(...)
-    construct_funs <- function(fun, val) {
-        if (!is.list(fun)) fun = list(fun)
-        ans = vector("list", length(fun)*length(val))
-        nms = vector("character", length(ans))
-        k = 1L
-        for (i in fun) {
-            for (j in val) {
-                expr = list(i, as.name(j))
-                if (length(dots))
-                    expr = c(expr, dots)
-                ans[[k]] = as.call(expr)
-                # changed order of arguments here, #1153
-                nms[k] = if (only_one_fun) j else
-                            paste(j, all.names(i, max.names=1L, functions=TRUE), sep=sep)
-                k = k+1L;
-            }
-        }
-        setattr(ans, 'names', nms)
+  if (is.call(funs) && funs[[1L]] == "eval")
+    funs = eval(funs[[2L]], parent.frame(2L), parent.frame(2L))
+  if (is.call(funs) && as.character(funs[[1L]]) %in% c("c", "list"))
+    funs = lapply(as.list(funs)[-1L], function(x) {
+      if (is.call(x) && as.character(x[[1L]]) %in% c("c", "list")) as.list(x)[-1L] else x
+    })
+  else funs = list(funs)
+  if (length(funs) != length(vals)) {
+    if (length(vals) == 1L)
+      vals = replicate(length(funs), vals)
+    else stop("When 'fun.aggregate' and 'value.var' are both lists, 'value.var' must be either of length =1 or =length(fun.aggregate).")
+  }
+  only_one_fun = length(unlist(funs)) == 1L
+  dots = list(...)
+  construct_funs <- function(fun, val) {
+    if (!is.list(fun)) fun = list(fun)
+    ans = vector("list", length(fun)*length(val))
+    nms = vector("character", length(ans))
+    k = 1L
+    for (i in fun) {
+      for (j in val) {
+        expr = list(i, as.name(j))
+        if (length(dots))
+          expr = c(expr, dots)
+        ans[[k]] = as.call(expr)
+        # changed order of arguments here, #1153
+        nms[k] = if (only_one_fun) j else
+              paste(j, all.names(i, max.names=1L, functions=TRUE), sep=sep)
+        k = k+1L;
+      }
     }
-    ans = mapply(construct_funs, funs, vals, SIMPLIFY=FALSE)
-    as.call(c(quote(list), unlist(ans)))
+    setattr(ans, 'names', nms)
+  }
+  ans = mapply(construct_funs, funs, vals, SIMPLIFY=FALSE)
+  as.call(c(quote(list), unlist(ans)))
 }
 
 dcast.data.table <- function(data, formula, fun.aggregate = NULL, sep = "_", ..., margins = NULL, subset = NULL, fill = NULL, drop = TRUE, value.var = guess(data), verbose = getOption("datatable.verbose")) {
-    if (!is.data.table(data)) stop("'data' must be a data.table.")
-    drop = as.logical(rep(drop, length.out=2L))
-    if (any(is.na(drop))) stop("'drop' must be logical TRUE/FALSE")
-    lvals = value_vars(value.var, names(data))
-    valnames = unique(unlist(lvals))
-    lvars = check_formula(formula, names(data), valnames)
-    lvars = lapply(lvars, function(x) if (!length(x)) quote(`.`) else x)
-    # tired of lapply and the way it handles environments!
-    allcols = c(unlist(lvars), lapply(valnames, as.name))
-    dat = vector("list", length(allcols))
-    for (i in seq_along(allcols)) {
-        x = allcols[[i]]
-        dat[[i]] = if (identical(x, quote(`.`))) rep(".", nrow(data))
-                      else eval(x, data, parent.frame())
-        if (is.function(dat[[i]]))
-            stop("Column [", deparse(x), "] not found or of unknown type.")
-    }
-    setattr(lvars, 'names', c("lhs", "rhs"))
-    # Have to take care of duplicate names, and provide names for expression columns properly.
-    varnames = make.unique(vapply_1c(unlist(lvars), all.vars, max.names=1L), sep=sep)
-    dupidx = which(valnames %in% varnames)
-    if (length(dupidx)) {
-        dups = valnames[dupidx]
-        valnames = tail(make.unique(c(varnames, valnames)), -length(varnames))
-        lvals = lapply(lvals, function(x) { x[x %in% dups] = valnames[dupidx]; x })
+  if (!is.data.table(data)) stop("'data' must be a data.table.")
+  drop = as.logical(rep(drop, length.out=2L))
+  if (any(is.na(drop))) stop("'drop' must be logical TRUE/FALSE")
+  lvals = value_vars(value.var, names(data))
+  valnames = unique(unlist(lvals))
+  lvars = check_formula(formula, names(data), valnames)
+  lvars = lapply(lvars, function(x) if (!length(x)) quote(`.`) else x)
+  # tired of lapply and the way it handles environments!
+  allcols = c(unlist(lvars), lapply(valnames, as.name))
+  dat = vector("list", length(allcols))
+  for (i in seq_along(allcols)) {
+    x = allcols[[i]]
+    dat[[i]] = if (identical(x, quote(`.`))) rep(".", nrow(data))
+            else eval(x, data, parent.frame())
+    if (is.function(dat[[i]]))
+      stop("Column [", deparse(x), "] not found or of unknown type.")
+  }
+  setattr(lvars, 'names', c("lhs", "rhs"))
+  # Have to take care of duplicate names, and provide names for expression columns properly.
+  varnames = make.unique(vapply_1c(unlist(lvars), all.vars, max.names=1L), sep=sep)
+  dupidx = which(valnames %in% varnames)
+  if (length(dupidx)) {
+    dups = valnames[dupidx]
+    valnames = tail(make.unique(c(varnames, valnames)), -length(varnames))
+    lvals = lapply(lvals, function(x) { x[x %in% dups] = valnames[dupidx]; x })
+  }
+  lhsnames = head(varnames, length(lvars$lhs))
+  rhsnames = tail(varnames, -length(lvars$lhs))
+  setattr(dat, 'names', c(varnames, valnames))
+  setDT(dat)
+  if (any(vapply_1b(as.list(dat)[varnames], is.list))) {
+    stop("Columns specified in formula can not be of type list")
+  }
+  m <- as.list(match.call()[-1L])
+  subset <- m[["subset"]][[2L]]
+  if (!is.null(subset)) {
+    if (is.name(subset)) subset = as.call(list(quote(`(`), subset))
+    idx = which(eval(subset, data, parent.frame())) # any advantage thro' secondary keys?
+    dat = .Call(CsubsetDT, dat, idx, seq_along(dat))
+  }
+  if (!nrow(dat) || !ncol(dat)) stop("Can not cast an empty data.table")
+  fun.call = m[["fun.aggregate"]]
+  fill.default = NULL
+  if (is.null(fun.call)) {
+    oo = forderv(dat, by=varnames, retGrp=TRUE)
+    if (attr(oo, 'maxgrpn') > 1L) {
+      message("Aggregate function missing, defaulting to 'length'")
+      fun.call = quote(length)
     }
-    lhsnames = head(varnames, length(lvars$lhs))
-    rhsnames = tail(varnames, -length(lvars$lhs))
-    setattr(dat, 'names', c(varnames, valnames))
-    setDT(dat)
-    if (any(vapply_1b(as.list(dat)[varnames], is.list))) {
-        stop("Columns specified in formula can not be of type list")
+  }
+  if (!is.null(fun.call)) {
+    fun.call = aggregate_funs(fun.call, lvals, sep, ...)
+    errmsg = "Aggregating function(s) should take vector inputs and return a single value (length=1). However, function(s) returns length!=1. This value will have to be used to fill any missing combinations, and therefore must be length=1. Either override by setting the 'fill' argument explicitly or modify your function to handle this case appropriately."
+    if (is.null(fill)) {
+      fill.default <- suppressWarnings(dat[0][, eval(fun.call)])
+      # tryCatch(fill.default <- dat[0][, eval(fun.call)], error = function(x) stop(errmsg, call.=FALSE))
+      if (nrow(fill.default) != 1L) stop(errmsg, call.=FALSE)
     }
-    m <- as.list(match.call()[-1L])
-    subset <- m[["subset"]][[2L]]
-    if (!is.null(subset)) {
-        if (is.name(subset)) subset = as.call(list(quote(`(`), subset))
-        idx = which(eval(subset, data, parent.frame())) # any advantage thro' secondary keys?
-        dat = .Call(CsubsetDT, dat, idx, seq_along(dat))
-    }
-    if (!nrow(dat) || !ncol(dat)) stop("Can not cast an empty data.table")
-    fun.call = m[["fun.aggregate"]]
-    fill.default = NULL
-    if (is.null(fun.call)) {
-        oo = forderv(dat, by=varnames, retGrp=TRUE)
-        if (attr(oo, 'maxgrpn') > 1L) {
-            message("Aggregate function missing, defaulting to 'length'")
-            fun.call = quote(length)
-        }
-    }
-    if (!is.null(fun.call)) {
-        fun.call = aggregate_funs(fun.call, lvals, sep, ...)
-        errmsg = "Aggregating function(s) should take vector inputs and return a single value (length=1). However, function(s) returns length!=1. This value will have to be used to fill any missing combinations, and therefore must be length=1. Either override by setting the 'fill' argument explicitly or modify your function to handle this case appropriately."
-        if (is.null(fill)) {
-            fill.default <- suppressWarnings(dat[0][, eval(fun.call)])
-            # tryCatch(fill.default <- dat[0][, eval(fun.call)], error = function(x) stop(errmsg, call.=FALSE))
-            if (nrow(fill.default) != 1L) stop(errmsg, call.=FALSE)
-        }
-        if (!any(valnames %chin% varnames)) {
-            dat = dat[, eval(fun.call), by=c(varnames)]
-        } else {
-            dat = dat[, { .SD; eval(fun.call) }, by=c(varnames), .SDcols = valnames]
-        }
+    if (!any(valnames %chin% varnames)) {
+      dat = dat[, eval(fun.call), by=c(varnames)]
+    } else {
+      dat = dat[, { .SD; eval(fun.call) }, by=c(varnames), .SDcols = valnames]
     }
-    order_ <- function(x) {
-        o = forderv(x, retGrp=TRUE, sort=TRUE)
-        idx = attr(o, 'starts')
-        if (!length(o)) o = seq_along(x)
-        o[idx] # subsetVector retains attributes, using R's subset for now
+  }
+  order_ <- function(x) {
+    o = forderv(x, retGrp=TRUE, sort=TRUE)
+    idx = attr(o, 'starts')
+    if (!length(o)) o = seq_along(x)
+    o[idx] # subsetVector retains attributes, using R's subset for now
+  }
+  cj_uniq <- function(DT) {
+    do.call("CJ", lapply(DT, function(x)
+      if (is.factor(x)) {
+        xint = seq_along(levels(x))
+        setattr(xint, 'levels', levels(x))
+        setattr(xint, 'class', class(x))
+      } else .Call(CsubsetVector, x, order_(x))
+  ))}
+  valnames = setdiff(names(dat), varnames)
+  # 'dat' != 'data'? then setkey to speed things up (slightly), else ad-hoc (for now). Still very fast!
+  if (!is.null(fun.call) || !is.null(subset))
+    setkeyv(dat, varnames)
+  if (length(rhsnames)) {
+    lhs = shallow(dat, lhsnames); rhs = shallow(dat, rhsnames); val = shallow(dat, valnames)
+    # handle drop=TRUE/FALSE - Update: Logic moved to R, AND faster than previous version. Take that... old me :-).
+    if (all(drop)) {
+      map = setDT(lapply(list(lhsnames, rhsnames), function(cols) frankv(dat, cols=cols, ties.method="dense")))
+      maporder = lapply(map, order_)
+      mapunique = lapply(seq_along(map), function(i) .Call(CsubsetVector, map[[i]], maporder[[i]]))
+      lhs = .Call(CsubsetDT, lhs, maporder[[1L]], seq_along(lhs))
+      rhs = .Call(CsubsetDT, rhs, maporder[[2L]], seq_along(rhs))
+    } else {
+      lhs_ = if (!drop[1L]) cj_uniq(lhs) else setkey(unique(lhs, by=names(lhs)))
+      rhs_ = if (!drop[2L]) cj_uniq(rhs) else setkey(unique(rhs, by=names(rhs)))
+      map = vector("list", 2L)
+      .Call(Csetlistelt, map, 1L, lhs_[lhs, which=TRUE])
+      .Call(Csetlistelt, map, 2L, rhs_[rhs, which=TRUE])
+      setDT(map)
+      mapunique = vector("list", 2L)
+      .Call(Csetlistelt, mapunique, 1L, seq_len(nrow(lhs_)))
+      .Call(Csetlistelt, mapunique, 2L, seq_len(nrow(rhs_)))
+      lhs = lhs_; rhs = rhs_
     }
-    cj_uniq <- function(DT) {
-        do.call("CJ", lapply(DT, function(x)
-            if (is.factor(x)) {
-                xint = seq_along(levels(x))
-                setattr(xint, 'levels', levels(x))
-                setattr(xint, 'class', class(x))
-            } else .Call(CsubsetVector, x, order_(x))
-    ))}
-    valnames = setdiff(names(dat), varnames)
-    # 'dat' != 'data'? then setkey to speed things up (slightly), else ad-hoc (for now). Still very fast!
-    if (!is.null(fun.call) || !is.null(subset))
-        setkeyv(dat, varnames)
-    if (length(rhsnames)) {
-        lhs = shallow(dat, lhsnames); rhs = shallow(dat, rhsnames); val = shallow(dat, valnames)
-        # handle drop=TRUE/FALSE - Update: Logic moved to R, AND faster than previous version. Take that... old me :-).
-        if (all(drop)) {
-            map = setDT(lapply(list(lhsnames, rhsnames), function(cols) frankv(dat, cols=cols, ties.method="dense")))
-            maporder = lapply(map, order_)
-            mapunique = lapply(seq_along(map), function(i) .Call(CsubsetVector, map[[i]], maporder[[i]]))
-            lhs = .Call(CsubsetDT, lhs, maporder[[1L]], seq_along(lhs))
-            rhs = .Call(CsubsetDT, rhs, maporder[[2L]], seq_along(rhs))
-        } else {
-            lhs_ = if (!drop[1L]) cj_uniq(lhs) else setkey(unique(lhs, by=names(lhs)))
-            rhs_ = if (!drop[2L]) cj_uniq(rhs) else setkey(unique(rhs, by=names(rhs)))
-            map = vector("list", 2L)
-            .Call(Csetlistelt, map, 1L, lhs_[lhs, which=TRUE])
-            .Call(Csetlistelt, map, 2L, rhs_[rhs, which=TRUE])
-            setDT(map)
-            mapunique = vector("list", 2L)
-            .Call(Csetlistelt, mapunique, 1L, seq_len(nrow(lhs_)))
-            .Call(Csetlistelt, mapunique, 2L, seq_len(nrow(rhs_)))
-            lhs = lhs_; rhs = rhs_
-        }
-        maplen = vapply_1i(mapunique, length)
-        idx = do.call("CJ", mapunique)[map, I := .I][["I"]] # TO DO: move this to C and avoid materialising the Cross Join.
-        ans = .Call(Cfcast, lhs, val, maplen[[1L]], maplen[[2L]], idx, fill, fill.default, is.null(fun.call))
-        allcols = do.call("paste", c(rhs, sep=sep))
-        if (length(valnames) > 1L)
-            allcols = do.call("paste", if (identical(".", allcols)) list(valnames, sep=sep)
-                        else c(CJ(valnames, allcols, sorted=FALSE), sep=sep))
-            # removed 'setcolorder()' here, #1153
-        setattr(ans, 'names', c(lhsnames, allcols))
-        setDT(ans); setattr(ans, 'sorted', lhsnames)
+    maplen = vapply_1i(mapunique, length)
+    idx = do.call("CJ", mapunique)[map, I := .I][["I"]] # TO DO: move this to C and avoid materialising the Cross Join.
+    ans = .Call(Cfcast, lhs, val, maplen[[1L]], maplen[[2L]], idx, fill, fill.default, is.null(fun.call))
+    allcols = do.call("paste", c(rhs, sep=sep))
+    if (length(valnames) > 1L)
+      allcols = do.call("paste", if (identical(".", allcols)) list(valnames, sep=sep)
+            else c(CJ(valnames, allcols, sorted=FALSE), sep=sep))
+      # removed 'setcolorder()' here, #1153
+    setattr(ans, 'names', c(lhsnames, allcols))
+    setDT(ans); setattr(ans, 'sorted', lhsnames)
+  } else {
+    # formula is of the form x + y ~ . (rare case)
+    if (drop) {
+      if (is.null(subset) && is.null(fun.call)) {
+        dat = copy(dat) # can't be avoided
+        setkeyv(dat, lhsnames)
+      }
+      ans = dat
     } else {
-        # formula is of the form x + y ~ . (rare case)
-        if (drop) {
-            if (is.null(subset) && is.null(fun.call)) {
-                dat = copy(dat) # can't be avoided
-                setkeyv(dat, lhsnames)
-            }
-            ans = dat
-        } else {
-            lhs = shallow(dat, lhsnames)
-            val = shallow(dat, valnames)
-            lhs_ = cj_uniq(lhs)
-            idx = lhs_[lhs, I := .I][["I"]]
-            lhs_[, I := NULL]
-            ans = .Call(Cfcast, lhs_, val, nrow(lhs_), 1L, idx, fill, fill.default, is.null(fun.call))
-            setDT(ans); setattr(ans, 'sorted', lhsnames)
-            setnames(ans, c(lhsnames, valnames))
-        }
-        if (length(valnames) == 1L)
-            setnames(ans, valnames, value.var)
+      lhs = shallow(dat, lhsnames)
+      val = shallow(dat, valnames)
+      lhs_ = cj_uniq(lhs)
+      idx = lhs_[lhs, I := .I][["I"]]
+      lhs_[, I := NULL]
+      ans = .Call(Cfcast, lhs_, val, nrow(lhs_), 1L, idx, fill, fill.default, is.null(fun.call))
+      setDT(ans); setattr(ans, 'sorted', lhsnames)
+      setnames(ans, c(lhsnames, valnames))
     }
-    return (ans)
+    if (length(valnames) == 1L)
+      setnames(ans, valnames, value.var)
+  }
+  return (ans)
 }
diff --git a/R/fmelt.R b/R/fmelt.R
index 8743dc8446..813a6992ee 100644
--- a/R/fmelt.R
+++ b/R/fmelt.R
@@ -1,48 +1,48 @@
 # Add melt generic, don't import reshape2 as it requires R >= 3.0.0.
 melt <- function(data, ..., na.rm = FALSE, value.name = "value") {
   if (is.data.table(data))
-      UseMethod("melt", data)
+    UseMethod("melt", data)
   else
-      reshape2::melt(data, ..., na.rm=na.rm, value.name=value.name)
+    reshape2::melt(data, ..., na.rm=na.rm, value.name=value.name)
 }
 
 patterns <- function(..., cols=character(0)) {
-    p = unlist(list(...), use.names=FALSE)
-    if (!is.character(p))
-        stop("Input patterns must be of type character.")
-    lapply(p, grep, cols)
+  p = unlist(list(...), use.names=FALSE)
+  if (!is.character(p))
+    stop("Input patterns must be of type character.")
+  lapply(p, grep, cols)
 }
 
 melt.data.table <- function(data, id.vars, measure.vars, variable.name = "variable",
-           value.name = "value", ..., na.rm = FALSE, variable.factor = TRUE, value.factor = FALSE,
-           verbose = getOption("datatable.verbose")) {
-    if (!is.data.table(data)) stop("'data' must be a data.table")
-    if (missing(id.vars)) id.vars=NULL
-    if (missing(measure.vars)) measure.vars = NULL
-    measure.sub = substitute(measure.vars)
-    if (is.call(measure.sub) && measure.sub[[1L]] == "patterns") {
-        measure.sub = as.list(measure.sub)[-1L]
-        idx = which(names(measure.sub) %in% "cols")
-        if (length(idx)) {
-            cols = eval(measure.sub[["cols"]], parent.frame())
-            measure.sub = measure.sub[-idx]
-        } else cols = names(data)
-        pats = lapply(measure.sub, eval, parent.frame())
-        measure.vars = patterns(pats, cols=cols)
-    }
-    if (is.list(measure.vars) && length(measure.vars) > 1L) {
-        if (length(value.name) == 1L)
-          value.name = paste(value.name, seq_along(measure.vars), sep="")
-    }
-    ans <- .Call(Cfmelt, data, id.vars, measure.vars,
-            as.logical(variable.factor), as.logical(value.factor),
-            variable.name, value.name, as.logical(na.rm),
-            as.logical(verbose))
-    setDT(ans)
-    if (any(duplicated(names(ans)))) {
-        cat("Duplicate column names found in molten data.table. Setting unique names using 'make.names'\n")
-        setnames(ans, make.unique(names(ans)))
-    }
-    setattr(ans, 'sorted', NULL)
-    ans
+       value.name = "value", ..., na.rm = FALSE, variable.factor = TRUE, value.factor = FALSE,
+       verbose = getOption("datatable.verbose")) {
+  if (!is.data.table(data)) stop("'data' must be a data.table")
+  if (missing(id.vars)) id.vars=NULL
+  if (missing(measure.vars)) measure.vars = NULL
+  measure.sub = substitute(measure.vars)
+  if (is.call(measure.sub) && measure.sub[[1L]] == "patterns") {
+    measure.sub = as.list(measure.sub)[-1L]
+    idx = which(names(measure.sub) %in% "cols")
+    if (length(idx)) {
+      cols = eval(measure.sub[["cols"]], parent.frame())
+      measure.sub = measure.sub[-idx]
+    } else cols = names(data)
+    pats = lapply(measure.sub, eval, parent.frame())
+    measure.vars = patterns(pats, cols=cols)
+  }
+  if (is.list(measure.vars) && length(measure.vars) > 1L) {
+    if (length(value.name) == 1L)
+      value.name = paste(value.name, seq_along(measure.vars), sep="")
+  }
+  ans <- .Call(Cfmelt, data, id.vars, measure.vars,
+      as.logical(variable.factor), as.logical(value.factor),
+      variable.name, value.name, as.logical(na.rm),
+      as.logical(verbose))
+  setDT(ans)
+  if (any(duplicated(names(ans)))) {
+    cat("Duplicate column names found in molten data.table. Setting unique names using 'make.names'\n")
+    setnames(ans, make.unique(names(ans)))
+  }
+  setattr(ans, 'sorted', NULL)
+  ans
 }
diff --git a/R/frank.R b/R/frank.R
index 2de42a4606..b1f3421a3b 100644
--- a/R/frank.R
+++ b/R/frank.R
@@ -1,93 +1,93 @@
 frankv <- function(x, cols=seq_along(x), order=1L, na.last=TRUE, ties.method=c("average", "first", "random", "max", "min", "dense")) {
-    ties.method = match.arg(ties.method)
-    if (!length(na.last)) stop('length(na.last) = 0')
-    if (length(na.last) != 1L) {
-        warning("length(na.last) > 1, only the first element will be used")
-        na.last = na.last[1L]
+  ties.method = match.arg(ties.method)
+  if (!length(na.last)) stop('length(na.last) = 0')
+  if (length(na.last) != 1L) {
+    warning("length(na.last) > 1, only the first element will be used")
+    na.last = na.last[1L]
+  }
+  keep = (na.last == "keep")
+  na.last = as.logical(na.last)
+  as_list <- function(x) {
+    xx = vector("list", 1L)
+    .Call(Csetlistelt, xx, 1L, x)
+    xx
+  }
+  if (is.atomic(x)) {
+    if (!missing(cols) && !is.null(cols))
+      stop("x is a single vector, non-NULL 'cols' doesn't make sense")
+    cols = 1L
+    x = as_list(x)
+  } else {
+    if (!length(cols))
+      stop("x is a list, 'cols' can not be 0-length")
+    if (is.character(cols))
+      cols = chmatch(cols, names(x))
+    cols = as.integer(cols)
+  }
+  x = .shallow(x, cols) # shallow copy even if list..
+  setDT(x)
+  cols = seq_along(cols)
+  if (is.na(na.last)) {
+    set(x, j = "..na_prefix..", value = is_na(x, cols))
+    order = if (length(order) == 1L) c(1L, rep(order, length(cols))) else c(1L, order)
+    cols = c(ncol(x), cols)
+    nas  = x[[ncol(x)]]
+  }
+  if (ties.method == "random") {
+    set(x, i = if (is.na(na.last)) which_(nas, FALSE) else NULL,
+         j = "..stats_runif..",
+         value = stats::runif(nrow(x)))
+    order = if (length(order) == 1L) c(rep(order, length(cols)), 1L) else c(order, 1L)
+    cols = c(cols, ncol(x))
+  }
+  xorder  = forderv(x, by=cols, order=order, sort=TRUE, retGrp=TRUE,
+        na.last=if (identical(na.last, FALSE)) na.last else TRUE)
+  xstart  = attr(xorder, 'starts')
+  xsorted = FALSE
+  if (!length(xorder)) {
+    xsorted = TRUE
+    xorder  = seq_along(x[[1L]])
+  }
+  ans = switch(ties.method,
+    average = , min = , max =, dense = {
+      rank = .Call(Cfrank, xorder, xstart, uniqlengths(xstart, length(xorder)), ties.method)
+    },
+    first = , random = {
+      if (xsorted) xorder else forderv(xorder)
     }
-    keep = (na.last == "keep")
-    na.last = as.logical(na.last)
-    as_list <- function(x) {
-        xx = vector("list", 1L)
-        .Call(Csetlistelt, xx, 1L, x)
-        xx
-    }
-    if (is.atomic(x)) {
-        if (!missing(cols) && !is.null(cols))
-            stop("x is a single vector, non-NULL 'cols' doesn't make sense")
-        cols = 1L
-        x = as_list(x)
-    } else {
-        if (!length(cols))
-            stop("x is a list, 'cols' can not be 0-length")
-        if (is.character(cols))
-            cols = chmatch(cols, names(x))
-        cols = as.integer(cols)
-    }
-    x = .shallow(x, cols) # shallow copy even if list..
-    setDT(x)
-    cols = seq_along(cols)
-    if (is.na(na.last)) {
-        set(x, j = "..na_prefix..", value = is_na(x, cols))
-        order = if (length(order) == 1L) c(1L, rep(order, length(cols))) else c(1L, order)
-        cols = c(ncol(x), cols)
-        nas  = x[[ncol(x)]]
-    }
-    if (ties.method == "random") {
-        set(x, i = if (is.na(na.last)) which_(nas, FALSE) else NULL,
-               j = "..stats_runif..",
-               value = stats::runif(nrow(x)))
-        order = if (length(order) == 1L) c(rep(order, length(cols)), 1L) else c(order, 1L)
-        cols = c(cols, ncol(x))
-    }
-    xorder  = forderv(x, by=cols, order=order, sort=TRUE, retGrp=TRUE,
-                na.last=if (identical(na.last, FALSE)) na.last else TRUE)
-    xstart  = attr(xorder, 'starts')
-    xsorted = FALSE
-    if (!length(xorder)) {
-        xsorted = TRUE
-        xorder  = seq_along(x[[1L]])
-    }
-    ans = switch(ties.method,
-           average = , min = , max =, dense = {
-               rank = .Call(Cfrank, xorder, xstart, uniqlengths(xstart, length(xorder)), ties.method)
-           },
-           first = , random = {
-               if (xsorted) xorder else forderv(xorder)
-           }
-         )
-    # take care of na.last="keep"
-    V1 = NULL # for R CMD CHECK warning
-    if (isTRUE(keep)) {
-        ans = (setDT(as_list(ans))[which_(nas, TRUE), V1 := NA])[[1L]]
-    } else if (is.na(na.last)) {
-        ans = ans[which_(nas, FALSE)]
-    }
-    ans
+  )
+  # take care of na.last="keep"
+  V1 = NULL # for R CMD CHECK warning
+  if (isTRUE(keep)) {
+    ans = (setDT(as_list(ans))[which_(nas, TRUE), V1 := NA])[[1L]]
+  } else if (is.na(na.last)) {
+    ans = ans[which_(nas, FALSE)]
+  }
+  ans
 }
 
 frank <- function(x, ..., na.last=TRUE, ties.method=c("average", "first", "random", "max", "min", "dense")) {
-    cols = substitute(list(...))[-1]
-    if (identical(as.character(cols), "NULL")) {
-        cols  = NULL
-        order = 1L
-    } else if (length(cols)) {
-        cols=as.list(cols)
-        order=rep(1L, length(cols))
-        for (i in seq_along(cols)) {
-            v=as.list(cols[[i]])
-            if (length(v) > 1 && v[[1L]] == "+") v=v[[-1L]]
-            else if (length(v) > 1 && v[[1L]] == "-") {
-                v=v[[-1L]]
-                order[i] = -1L
-            }
-            cols[[i]]=as.character(v)
-        }
-        cols=unlist(cols, use.names=FALSE)
-    } else {
-        cols=colnames(x)
-        order=if (is.null(cols)) 1L else rep(1L, length(cols))
+  cols = substitute(list(...))[-1]
+  if (identical(as.character(cols), "NULL")) {
+    cols  = NULL
+    order = 1L
+  } else if (length(cols)) {
+    cols=as.list(cols)
+    order=rep(1L, length(cols))
+    for (i in seq_along(cols)) {
+      v=as.list(cols[[i]])
+      if (length(v) > 1 && v[[1L]] == "+") v=v[[-1L]]
+      else if (length(v) > 1 && v[[1L]] == "-") {
+        v=v[[-1L]]
+        order[i] = -1L
+      }
+      cols[[i]]=as.character(v)
     }
-    frankv(x, cols=cols, order=order, na.last=na.last, ties.method=ties.method)
+    cols=unlist(cols, use.names=FALSE)
+  } else {
+    cols=colnames(x)
+    order=if (is.null(cols)) 1L else rep(1L, length(cols))
+  }
+  frankv(x, cols=cols, order=order, na.last=na.last, ties.method=ties.method)
 
 }
diff --git a/R/fread.R b/R/fread.R
index 8cfc2768ad..dff64b0d4d 100644
--- a/R/fread.R
+++ b/R/fread.R
@@ -1,151 +1,150 @@
 
 fread <- function(input="",file,sep="auto",sep2="auto",dec=".",quote="\"",nrows=Inf,header="auto",na.strings="NA",stringsAsFactors=FALSE,verbose=getOption("datatable.verbose"),autostart=NA,skip=0,select=NULL,drop=NULL,colClasses=NULL,integer64=getOption("datatable.integer64"), col.names, check.names=FALSE, encoding="unknown", strip.white=TRUE, fill=FALSE, blank.lines.skip=FALSE, key=NULL, showProgress=interactive(),data.table=getOption("datatable.fread.datatable"),nThread=getDTthreads(),logical01=TRUE)
 {
-    stopifnot( is.character(sep), length(sep)==1, sep=="auto" || nchar(sep)==1 )
-    if (sep == "auto") sep=""
-    stopifnot( is.character(dec), length(dec)==1, nchar(dec)==1 )
-    # handle encoding, #563
-    if (length(encoding) != 1L || !encoding %in% c("unknown", "UTF-8", "Latin-1")) {
-        stop("Argument 'encoding' must be 'unknown', 'UTF-8' or 'Latin-1'.")
-    }
-    isTrueFalse = function(x) isTRUE(x) || identical(FALSE, x)
-    isTrueFalseNA = function(x) isTRUE(x) || identical(FALSE, x) || identical(NA, x)
-    stopifnot( isTrueFalse(strip.white), isTrueFalse(blank.lines.skip), isTrueFalse(fill), isTrueFalse(showProgress),
-               isTrueFalse(stringsAsFactors), isTrueFalse(verbose), isTrueFalse(check.names), isTrueFalse(logical01) )
-    stopifnot( is.numeric(nrows), length(nrows)==1 )
-    if (is.na(nrows) || nrows<0) nrows=Inf   # accept -1 to mean Inf, as read.table does
-    if (identical(header,"auto")) header=NA
-    stopifnot(isTrueFalseNA(header))
-    stopifnot(length(skip)==1)
-    stopifnot(is.numeric(nThread) && length(nThread)==1)
-    nThread=as.integer(nThread)
-    stopifnot(nThread>=1)
-    if (!missing(file)) {
-        if (!identical(input, "")) stop("You can provide 'input' or 'file', not both.")
-        if (!file.exists(file)) stop(sprintf("Provided file '%s' does not exists.", file))
-        input = file
+  stopifnot( is.character(sep), length(sep)==1, sep=="auto" || nchar(sep)==1 )
+  if (sep == "auto") sep=""
+  stopifnot( is.character(dec), length(dec)==1, nchar(dec)==1 )
+  # handle encoding, #563
+  if (length(encoding) != 1L || !encoding %in% c("unknown", "UTF-8", "Latin-1")) {
+    stop("Argument 'encoding' must be 'unknown', 'UTF-8' or 'Latin-1'.")
+  }
+  isTrueFalse = function(x) isTRUE(x) || identical(FALSE, x)
+  isTrueFalseNA = function(x) isTRUE(x) || identical(FALSE, x) || identical(NA, x)
+  stopifnot( isTrueFalse(strip.white), isTrueFalse(blank.lines.skip), isTrueFalse(fill), isTrueFalse(showProgress),
+             isTrueFalse(stringsAsFactors), isTrueFalse(verbose), isTrueFalse(check.names), isTrueFalse(logical01) )
+  stopifnot( is.numeric(nrows), length(nrows)==1 )
+  if (is.na(nrows) || nrows<0) nrows=Inf   # accept -1 to mean Inf, as read.table does
+  if (identical(header,"auto")) header=NA
+  stopifnot(isTrueFalseNA(header))
+  stopifnot(length(skip)==1)
+  stopifnot(is.numeric(nThread) && length(nThread)==1)
+  nThread=as.integer(nThread)
+  stopifnot(nThread>=1)
+  if (!missing(file)) {
+    if (!identical(input, "")) stop("You can provide 'input' or 'file', not both.")
+    if (!file.exists(file)) stop(sprintf("Provided file '%s' does not exists.", file))
+    input = file
+  }
+  if (!missing(autostart)) warning("'autostart' is now deprecated and ignored. Consider skip='string' or skip=n");
+  is_url <- function(x) grepl("^(http|ftp)s?://", x)
+  is_secureurl <- function(x) grepl("^(http|ftp)s://", x)
+  is_file <- function(x) grepl("^file://", x)
+  if (!is.character(input) || length(input)!=1L) {
+    stop("'input' must be a single character string containing a file name, a command, full path to a file, a URL starting 'http[s]://', 'ftp[s]://' or 'file://', or the input data itself")
+  } else if (is_url(input) || is_file(input)) {
+    tt = tempfile()
+    on.exit(unlink(tt), add = TRUE)
+    # In text mode on Windows-only, R doubles up \r to make \r\r\n line endings. mode="wb" avoids that. See ?connections:"CRLF"
+    if (!is_secureurl(input)) {
+      #1668 - force "auto" when is_file to
+      #  ensure we don't use an invalid option, e.g. wget
+      method <- if (is_file(input)) "auto" else
+        getOption("download.file.method", default = "auto")
+      download.file(input, tt, method = method, mode = "wb", quiet = !showProgress)
+    } else {
+      if (!requireNamespace("curl", quietly = TRUE))
+        stop("Input URL requires https:// connection for which fread() requires 'curl' package, but cannot be found. Please install the package using 'install.packages()'.")
+      curl::curl_download(input, tt, mode = "wb", quiet = !showProgress)
     }
-    if (!missing(autostart)) warning("'autostart' is now deprecated and ignored. Consider skip='string' or skip=n");
-    is_url <- function(x) grepl("^(http|ftp)s?://", x)
-    is_secureurl <- function(x) grepl("^(http|ftp)s://", x)
-    is_file <- function(x) grepl("^file://", x)
-    if (!is.character(input) || length(input)!=1L) {
-        stop("'input' must be a single character string containing a file name, a command, full path to a file, a URL starting 'http[s]://', 'ftp[s]://' or 'file://', or the input data itself")
-    } else if (is_url(input) || is_file(input)) {
-        tt = tempfile()
-        on.exit(unlink(tt), add = TRUE)
-        # In text mode on Windows-only, R doubles up \r to make \r\r\n line endings. mode="wb" avoids that. See ?connections:"CRLF"
-        if (!is_secureurl(input)) {
-            #1668 - force "auto" when is_file to
-            #  ensure we don't use an invalid option, e.g. wget
-            method <- if (is_file(input)) "auto" else
-                getOption("download.file.method", default = "auto")
-            download.file(input, tt, method = method,
-                          mode = "wb", quiet = !showProgress)
-        } else {
-            if (!requireNamespace("curl", quietly = TRUE))
-                stop("Input URL requires https:// connection for which fread() requires 'curl' package, but cannot be found. Please install the package using 'install.packages()'.")
-            curl::curl_download(input, tt, mode = "wb", quiet = !showProgress)
-        }
-        input = tt
-    } else if (input == "" || length(grep('\\n|\\r', input)) > 0) {
-        # text input
-    } else if (isTRUE(file.info(input)$isdir)) { # fix for #989, dir.exists() requires v3.2+
-        stop("'input' can not be a directory name, but must be a single character string containing a file name, a command, full path to a file, a URL starting 'http[s]://', 'ftp[s]://' or 'file://', or the input data itself.")
-    } else if (!file.exists(input)) {
-        if (!length(grep(' ', input))) {
-            stop("File '",input,"' does not exist; getwd()=='", getwd(), "'",
-              ". Include correct full path, or one or more spaces to consider the input a system command.")
-        }
-        tt = tempfile()
-        on.exit(unlink(tt), add = TRUE)
-        if (.Platform$OS.type == "unix") {
-            if (file.exists('/dev/shm') && file.info('/dev/shm')$isdir) {
-                tt = tempfile(tmpdir = '/dev/shm')
-            }
-            system(paste('(', input, ') > ', tt, sep=""))
-        } else {
-            shell(paste('(', input, ') > ', tt, sep=""))
-        }
-        input = tt
+    input = tt
+  } else if (input == "" || length(grep('\\n|\\r', input)) > 0) {
+    # text input
+  } else if (isTRUE(file.info(input)$isdir)) { # fix for #989, dir.exists() requires v3.2+
+    stop("'input' can not be a directory name, but must be a single character string containing a file name, a command, full path to a file, a URL starting 'http[s]://', 'ftp[s]://' or 'file://', or the input data itself.")
+  } else if (!file.exists(input)) {
+    if (!length(grep(' ', input))) {
+      stop("File '",input,"' does not exist; getwd()=='", getwd(), "'",
+        ". Include correct full path, or one or more spaces to consider the input a system command.")
     }
-    if (is.logical(colClasses)) {
-        if (!all(is.na(colClasses))) stop("colClasses is type 'logical' which is ok if all NA but it has some TRUE or FALSE values in it which is not allowed. Please consider the drop= or select= argument instead. See ?fread.")
-        colClasses = NULL
+    tt = tempfile()
+    on.exit(unlink(tt), add = TRUE)
+    if (.Platform$OS.type == "unix") {
+      if (file.exists('/dev/shm') && file.info('/dev/shm')$isdir) {
+        tt = tempfile(tmpdir = '/dev/shm')
+      }
+      system(paste('(', input, ') > ', tt, sep=""))
+    } else {
+      shell(paste('(', input, ') > ', tt, sep=""))
     }
-    if (!is.null(colClasses) && is.atomic(colClasses)) {
-        if (!is.character(colClasses)) stop("colClasses is not type list or character vector")
-        if (!length(colClasses)) stop("colClasses is character vector ok but has 0 length")
-        if (!is.null(names(colClasses))) {   # names are column names; convert to list approach
-            colClasses = tapply(names(colClasses), colClasses, c, simplify=FALSE)
-        }
+    input = tt
+  }
+  if (is.logical(colClasses)) {
+    if (!all(is.na(colClasses))) stop("colClasses is type 'logical' which is ok if all NA but it has some TRUE or FALSE values in it which is not allowed. Please consider the drop= or select= argument instead. See ?fread.")
+    colClasses = NULL
+  }
+  if (!is.null(colClasses) && is.atomic(colClasses)) {
+    if (!is.character(colClasses)) stop("colClasses is not type list or character vector")
+    if (!length(colClasses)) stop("colClasses is character vector ok but has 0 length")
+    if (!is.null(names(colClasses))) {   # names are column names; convert to list approach
+      colClasses = tapply(names(colClasses), colClasses, c, simplify=FALSE)
     }
-    if (is.numeric(skip)) skip = as.integer(skip)
-    warnings2errors = getOption("warn") >= 2
-    ans = .Call(CfreadR,input,sep,dec,quote,header,nrows,skip,na.strings,strip.white,blank.lines.skip,
-                        fill,showProgress,nThread,verbose,warnings2errors,logical01,select,drop,colClasses,integer64,encoding)
-    nr = length(ans[[1]])
-    if ((!"bit64" %chin% loadedNamespaces()) && any(sapply(ans,inherits,"integer64"))) require_bit64()
-    setattr(ans,"row.names",.set_row_names(nr))
+  }
+  if (is.numeric(skip)) skip = as.integer(skip)
+  warnings2errors = getOption("warn") >= 2
+  ans = .Call(CfreadR,input,sep,dec,quote,header,nrows,skip,na.strings,strip.white,blank.lines.skip,
+              fill,showProgress,nThread,verbose,warnings2errors,logical01,select,drop,colClasses,integer64,encoding)
+  nr = length(ans[[1]])
+  if ((!"bit64" %chin% loadedNamespaces()) && any(sapply(ans,inherits,"integer64"))) require_bit64()
+  setattr(ans,"row.names",.set_row_names(nr))
 
-    if (isTRUE(data.table)) {
-        setattr(ans, "class", c("data.table", "data.frame"))
-        alloc.col(ans)
+  if (isTRUE(data.table)) {
+    setattr(ans, "class", c("data.table", "data.frame"))
+    alloc.col(ans)
+  } else {
+    setattr(ans, "class", "data.frame")
+  }
+  # #1027, make.unique -> make.names as spotted by @DavidArenberg
+  if (check.names) {
+    setattr(ans, 'names', make.names(names(ans), unique=TRUE))
+  }
+  cols = NULL
+  if (stringsAsFactors)
+    cols = which(vapply(ans, is.character, TRUE))
+  else if (length(colClasses)) {
+    if (is.list(colClasses) && "factor" %in% names(colClasses))
+      cols = colClasses[["factor"]]
+    else if (is.character(colClasses) && "factor" %chin% colClasses)
+      cols = which(colClasses=="factor")
+  }
+  setfactor(ans, cols, verbose)
+  if (!missing(select)) {
+    # fix for #1445
+    if (is.numeric(select)) {
+      reorder = if (length(o <- forderv(select))) o else seq_along(select)
     } else {
-        setattr(ans, "class", "data.frame")
-    }
-    # #1027, make.unique -> make.names as spotted by @DavidArenberg
-    if (check.names) {
-        setattr(ans, 'names', make.names(names(ans), unique=TRUE))
+      reorder = select[select %chin% names(ans)]
+      # any missing columns are warning about in fread.c and skipped
     }
-    cols = NULL
-    if (stringsAsFactors)
-        cols = which(vapply(ans, is.character, TRUE))
-    else if (length(colClasses)) {
-        if (is.list(colClasses) && "factor" %in% names(colClasses))
-            cols = colClasses[["factor"]]
-        else if (is.character(colClasses) && "factor" %chin% colClasses)
-            cols = which(colClasses=="factor")
+    setcolorder(ans, reorder)
+  }
+  # FR #768
+  if (!missing(col.names))
+    setnames(ans, col.names) # setnames checks and errors automatically
+  if (!is.null(key) && data.table) {
+    if (!is.character(key))
+      stop("key argument of data.table() must be character")
+    if (length(key) == 1L) {
+      key = strsplit(key, split = ",")[[1L]]
     }
-    setfactor(ans, cols, verbose)
-    if (!missing(select)) {
-        # fix for #1445
-        if (is.numeric(select)) {
-            reorder = if (length(o <- forderv(select))) o else seq_along(select)
-        } else {
-            reorder = select[select %chin% names(ans)]
-            # any missing columns are warning about in fread.c and skipped
-        }
-        setcolorder(ans, reorder)
-    }
-    # FR #768
-    if (!missing(col.names))
-        setnames(ans, col.names) # setnames checks and errors automatically
-    if (!is.null(key) && data.table) {
-        if (!is.character(key))
-            stop("key argument of data.table() must be character")
-        if (length(key) == 1L) {
-            key = strsplit(key, split = ",")[[1L]]
-        }
-        setkeyv(ans, key)
-    }
-    ans
+    setkeyv(ans, key)
+  }
+  ans
 }
 
 # for internal use only. Used in `fread` and `data.table` for 'stringsAsFactors' argument
 setfactor <- function(x, cols, verbose) {
-    # simplified but faster version of `factor()` for internal use.
-    as_factor <- function(x) {
-        lev = forderv(x, retGrp = TRUE, na.last = NA)
-        # get levels, also take care of all sorted condition
-        lev = if (length(lev)) x[lev[attributes(lev)$starts]] else x[attributes(lev)$starts]
-        ans = chmatch(x, lev)
-        setattr(ans, 'levels', lev)
-        setattr(ans, 'class', 'factor')
-    }
-    if (length(cols)) {
-        if (verbose) cat("Converting column(s) [", paste(names(x)[cols], collapse = ", "), "] from 'char' to 'factor'\n", sep = "")
-        for (j in cols) set(x, j = j, value = as_factor(.subset2(x, j)))
-    }
-    invisible(x)
+  # simplified but faster version of `factor()` for internal use.
+  as_factor <- function(x) {
+    lev = forderv(x, retGrp = TRUE, na.last = NA)
+    # get levels, also take care of all sorted condition
+    lev = if (length(lev)) x[lev[attributes(lev)$starts]] else x[attributes(lev)$starts]
+    ans = chmatch(x, lev)
+    setattr(ans, 'levels', lev)
+    setattr(ans, 'class', 'factor')
+  }
+  if (length(cols)) {
+    if (verbose) cat("Converting column(s) [", paste(names(x)[cols], collapse = ", "), "] from 'char' to 'factor'\n", sep = "")
+    for (j in cols) set(x, j = j, value = as_factor(.subset2(x, j)))
+  }
+  invisible(x)
 }
diff --git a/R/fwrite.R b/R/fwrite.R
index f1bc34cf2f..66136c9da3 100644
--- a/R/fwrite.R
+++ b/R/fwrite.R
@@ -1,59 +1,59 @@
 fwrite <- function(x, file="", append=FALSE, quote="auto",
-                   sep=",", sep2=c("","|",""), eol=if (.Platform$OS.type=="windows") "\r\n" else "\n",
-                   na="", dec=".", row.names=FALSE, col.names=TRUE,
-                   qmethod=c("double","escape"),
-                   logical01=getOption("datatable.logical01", TRUE),
-                   logicalAsInt=logical01,
-                   dateTimeAs = c("ISO","squash","epoch","write.csv"),
-                   buffMB=8, nThread=getDTthreads(),
-                   showProgress=interactive(),
-                   verbose=getOption("datatable.verbose", FALSE)) {
-    isLOGICAL = function(x) isTRUE(x) || identical(FALSE, x)  # it seems there is no isFALSE in R?
-    na = as.character(na[1L]) # fix for #1725
-    if (missing(qmethod)) qmethod = qmethod[1L]
-    if (missing(dateTimeAs)) dateTimeAs = dateTimeAs[1L]
-    else if (length(dateTimeAs)>1) stop("dateTimeAs must be a single string")
-    dateTimeAs = chmatch(dateTimeAs, c("ISO","squash","epoch","write.csv"))-1L
-    if (is.na(dateTimeAs)) stop("dateTimeAs must be 'ISO','squash','epoch' or 'write.csv'")
-    if (!missing(logical01) && !missing(logicalAsInt))
-      stop("logicalAsInt has been renamed logical01. Use logical01 only, not both.")
-    if (!missing(logicalAsInt)) {
-      # TODO: warning("logicalAsInt has been renamed logical01 for consistency with fread. It will work fine but please change to logical01 at your convenience so we can remove logicalAsInt in future.")
-      logical01 = logicalAsInt
-      logicalAsInt=NULL
-    }
-    buffMB = as.integer(buffMB)
-    nThread = as.integer(nThread)
-    # write.csv default is 'double' so fwrite follows suit. write.table's default is 'escape'
-    # validate arguments
-    stopifnot(is.list(x), ncol(x) > 0L,
-        identical(quote,"auto") || identical(quote,FALSE) || identical(quote,TRUE),
-        is.character(sep) && length(sep)==1L && nchar(sep) == 1L,
-        is.character(sep2) && length(sep2)==3L && nchar(sep2[2L])==1L,
-        is.character(dec) && length(dec)==1L && nchar(dec) == 1L,
-        dec != sep,  # sep2!=dec and sep2!=sep checked at C level when we know if list columns are present
-        is.character(eol) && length(eol)==1L,
-        length(qmethod) == 1L && qmethod %in% c("double", "escape"),
-        isLOGICAL(col.names), isLOGICAL(append), isLOGICAL(row.names),
-        isLOGICAL(verbose), isLOGICAL(showProgress), isLOGICAL(logical01),
-        length(na) == 1L, #1725, handles NULL or character(0) input
-        is.character(file) && length(file)==1 && !is.na(file),
-        length(buffMB)==1 && !is.na(buffMB) && 1<=buffMB && buffMB<=1024,
-        length(nThread)==1 && !is.na(nThread) && nThread>=1
-        )
-    file <- path.expand(file)  # "~/foo/bar"
-    if (append && missing(col.names) && (file=="" || file.exists(file)))
-        col.names = FALSE  # test 1658.16 checks this
-    if (identical(quote,"auto")) quote=NA  # logical NA
-    if (file=="") {
-        # console output which it seems isn't thread safe on Windows even when one-batch-at-a-time
-        nThread = 1L
-        showProgress = FALSE
-        eol = "\n"  # Rprintf() is used at C level which knows inside it to output \r\n on Windows. Otherwise extra \r is output.
-    }
-    .Call(CfwriteR, x, file, sep, sep2, eol, na, dec, quote, qmethod=="escape", append,
-                    row.names, col.names, logical01, dateTimeAs, buffMB, nThread,
-                    showProgress, verbose)
-    invisible()
+           sep=",", sep2=c("","|",""), eol=if (.Platform$OS.type=="windows") "\r\n" else "\n",
+           na="", dec=".", row.names=FALSE, col.names=TRUE,
+           qmethod=c("double","escape"),
+           logical01=getOption("datatable.logical01", TRUE),
+           logicalAsInt=logical01,
+           dateTimeAs = c("ISO","squash","epoch","write.csv"),
+           buffMB=8, nThread=getDTthreads(),
+           showProgress=interactive(),
+           verbose=getOption("datatable.verbose", FALSE)) {
+  isLOGICAL = function(x) isTRUE(x) || identical(FALSE, x)  # it seems there is no isFALSE in R?
+  na = as.character(na[1L]) # fix for #1725
+  if (missing(qmethod)) qmethod = qmethod[1L]
+  if (missing(dateTimeAs)) dateTimeAs = dateTimeAs[1L]
+  else if (length(dateTimeAs)>1) stop("dateTimeAs must be a single string")
+  dateTimeAs = chmatch(dateTimeAs, c("ISO","squash","epoch","write.csv"))-1L
+  if (is.na(dateTimeAs)) stop("dateTimeAs must be 'ISO','squash','epoch' or 'write.csv'")
+  if (!missing(logical01) && !missing(logicalAsInt))
+    stop("logicalAsInt has been renamed logical01. Use logical01 only, not both.")
+  if (!missing(logicalAsInt)) {
+    # TODO: warning("logicalAsInt has been renamed logical01 for consistency with fread. It will work fine but please change to logical01 at your convenience so we can remove logicalAsInt in future.")
+    logical01 = logicalAsInt
+    logicalAsInt=NULL
+  }
+  buffMB = as.integer(buffMB)
+  nThread = as.integer(nThread)
+  # write.csv default is 'double' so fwrite follows suit. write.table's default is 'escape'
+  # validate arguments
+  stopifnot(is.list(x), ncol(x) > 0L,
+    identical(quote,"auto") || identical(quote,FALSE) || identical(quote,TRUE),
+    is.character(sep) && length(sep)==1L && nchar(sep) == 1L,
+    is.character(sep2) && length(sep2)==3L && nchar(sep2[2L])==1L,
+    is.character(dec) && length(dec)==1L && nchar(dec) == 1L,
+    dec != sep,  # sep2!=dec and sep2!=sep checked at C level when we know if list columns are present
+    is.character(eol) && length(eol)==1L,
+    length(qmethod) == 1L && qmethod %in% c("double", "escape"),
+    isLOGICAL(col.names), isLOGICAL(append), isLOGICAL(row.names),
+    isLOGICAL(verbose), isLOGICAL(showProgress), isLOGICAL(logical01),
+    length(na) == 1L, #1725, handles NULL or character(0) input
+    is.character(file) && length(file)==1 && !is.na(file),
+    length(buffMB)==1 && !is.na(buffMB) && 1<=buffMB && buffMB<=1024,
+    length(nThread)==1 && !is.na(nThread) && nThread>=1
+    )
+  file <- path.expand(file)  # "~/foo/bar"
+  if (append && missing(col.names) && (file=="" || file.exists(file)))
+    col.names = FALSE  # test 1658.16 checks this
+  if (identical(quote,"auto")) quote=NA  # logical NA
+  if (file=="") {
+    # console output which it seems isn't thread safe on Windows even when one-batch-at-a-time
+    nThread = 1L
+    showProgress = FALSE
+    eol = "\n"  # Rprintf() is used at C level which knows inside it to output \r\n on Windows. Otherwise extra \r is output.
+  }
+  .Call(CfwriteR, x, file, sep, sep2, eol, na, dec, quote, qmethod=="escape", append,
+          row.names, col.names, logical01, dateTimeAs, buffMB, nThread,
+          showProgress, verbose)
+  invisible()
 }
 
diff --git a/R/getdots.R b/R/getdots.R
index 2f4c34272c..f96ad2c901 100644
--- a/R/getdots.R
+++ b/R/getdots.R
@@ -3,8 +3,8 @@
 
 getdots <- function()
 {
-    # return a string vector of the arguments in '...'
-    # My long winded way: gsub(" ","",unlist(strsplit(deparse(substitute(list(...))),"[(,)]")))[-1]
-    # Peter Dalgaard's & Brian Ripley helped out and ended up with :
-    as.character(match.call(sys.function(-1), call=sys.call(-1), expand.dots=FALSE)$...)
+  # return a string vector of the arguments in '...'
+  # My long winded way: gsub(" ","",unlist(strsplit(deparse(substitute(list(...))),"[(,)]")))[-1]
+  # Peter Dalgaard's & Brian Ripley helped out and ended up with :
+  as.character(match.call(sys.function(-1), call=sys.call(-1), expand.dots=FALSE)$...)
 }
diff --git a/R/groupingsets.R b/R/groupingsets.R
index 2e8fb6eba9..038f45b1f7 100644
--- a/R/groupingsets.R
+++ b/R/groupingsets.R
@@ -1,121 +1,121 @@
 rollup <- function(x, ...) {
-    UseMethod("rollup")
+  UseMethod("rollup")
 }
 rollup.data.table <- function(x, j, by, .SDcols, id = FALSE, ...) {
-    # input data type basic validation
-    if (!is.data.table(x))
-        stop("Argument 'x' must be data.table object")
-    if (!is.character(by))
-        stop("Argument 'by' must be character vector of column names used in grouping.")
-    if (!is.logical(id))
-        stop("Argument 'id' must be logical scalar.")
-    # generate grouping sets for rollup
-    sets = lapply(length(by):0, function(i) by[0:i])
-    # redirect to workhorse function
-    jj = substitute(j)
-    groupingsets.data.table(x, by=by, sets=sets, .SDcols=.SDcols, id=id, jj=jj)
+  # input data type basic validation
+  if (!is.data.table(x))
+    stop("Argument 'x' must be data.table object")
+  if (!is.character(by))
+    stop("Argument 'by' must be character vector of column names used in grouping.")
+  if (!is.logical(id))
+    stop("Argument 'id' must be logical scalar.")
+  # generate grouping sets for rollup
+  sets = lapply(length(by):0, function(i) by[0:i])
+  # redirect to workhorse function
+  jj = substitute(j)
+  groupingsets.data.table(x, by=by, sets=sets, .SDcols=.SDcols, id=id, jj=jj)
 }
 
 cube <- function(x, ...) {
-    UseMethod("cube")
+  UseMethod("cube")
 }
 cube.data.table <- function(x, j, by, .SDcols, id = FALSE, ...) {
-    # input data type basic validation
-    if (!is.data.table(x))
-        stop("Argument 'x' must be data.table object")
-    if (!is.character(by))
-        stop("Argument 'by' must be character vector of column names used in grouping.")
-    if (!is.logical(id))
-        stop("Argument 'id' must be logical scalar.")
-    # generate grouping sets for cube - power set: http://stackoverflow.com/a/32187892/2490497
-    n = length(by)
-    keepBool = sapply(2L^(1:n - 1L), function(k) rep(c(FALSE, TRUE), each=k, times=(2L^n / (2L*k))))
-    sets = lapply((2L^n):1, function(j) by[keepBool[j, ]])
-    # redirect to workhorse function
-    jj = substitute(j)
-    groupingsets.data.table(x, by=by, sets=sets, .SDcols=.SDcols, id=id, jj=jj)
+  # input data type basic validation
+  if (!is.data.table(x))
+    stop("Argument 'x' must be data.table object")
+  if (!is.character(by))
+    stop("Argument 'by' must be character vector of column names used in grouping.")
+  if (!is.logical(id))
+    stop("Argument 'id' must be logical scalar.")
+  # generate grouping sets for cube - power set: http://stackoverflow.com/a/32187892/2490497
+  n = length(by)
+  keepBool = sapply(2L^(1:n - 1L), function(k) rep(c(FALSE, TRUE), each=k, times=(2L^n / (2L*k))))
+  sets = lapply((2L^n):1, function(j) by[keepBool[j, ]])
+  # redirect to workhorse function
+  jj = substitute(j)
+  groupingsets.data.table(x, by=by, sets=sets, .SDcols=.SDcols, id=id, jj=jj)
 }
 
 groupingsets <- function(x, ...) {
-    UseMethod("groupingsets")
+  UseMethod("groupingsets")
 }
 groupingsets.data.table <- function(x, j, by, sets, .SDcols, id = FALSE, jj, ...) {
-    # input data type basic validation
-    if (!is.data.table(x))
-        stop("Argument 'x' must be data.table object")
-    if (ncol(x) < 1L)
-        stop("Argument 'x' is 0 column data.table, no measure to apply grouping over.")
-    if (length(names(x)) != uniqueN(names(x)))
-        stop("data.table must not contains duplicate column names.")
-    if (!is.character(by))
-        stop("Argument 'by' must be character vector of column names used in grouping.")
-    if (length(by) != uniqueN(by))
-        stop("Argument 'by' must have unique column names for grouping.")
-    if (!is.list(sets) || !all(sapply(sets, is.character)))
-        stop("Argument 'sets' must be a list of character vectors.")
-    if (!is.logical(id))
-        stop("Argument 'id' must be logical scalar.")
-    # logic constraints validation
-    if (!all((sets.all.by <- unique(unlist(sets))) %chin% by))
-        stop(sprintf("All columns used in 'sets' argument must be in 'by' too. Columns used in 'sets' but not present in 'by': %s.", paste(setdiff(sets.all.by, by), collapse=", ")))
-    if (id && "grouping" %chin% names(x))
-        stop("When using `id=TRUE` the 'x' data.table must not have column named 'grouping'.")
-    if (!all(sapply(sets, function(x) length(x)==uniqueN(x))))
-        stop("Character vectors in 'sets' list must not have duplicated column names within single grouping set.")
-    if (!identical(lapply(sets, sort), unique(lapply(sets, sort))))
-        warning("Double counting is going to happen. Argument 'sets' should be unique without taking order into account, unless you really want double counting, then get used to that warning. Otherwise `sets=unique(lapply(sets, sort))` will do the trick.")
-    # input arguments handling
-    jj = if (!missing(jj)) jj else substitute(j)
-    av = all.vars(jj, TRUE)
-    if (":=" %chin% av)
-        stop("Expression passed to grouping sets function must not update by reference. Use ':=' on results of your grouping function.")
-    if (missing(.SDcols))
-        .SDcols = if (".SD" %chin% av) setdiff(names(x), by) else NULL
-    # 0 rows template data.table to keep colorder and type
-    if (length(by)) {
-        empty = if (length(.SDcols)) x[0L, eval(jj), by, .SDcols=.SDcols] else x[0L, eval(jj), by]
+  # input data type basic validation
+  if (!is.data.table(x))
+    stop("Argument 'x' must be data.table object")
+  if (ncol(x) < 1L)
+    stop("Argument 'x' is 0 column data.table, no measure to apply grouping over.")
+  if (length(names(x)) != uniqueN(names(x)))
+    stop("data.table must not contains duplicate column names.")
+  if (!is.character(by))
+    stop("Argument 'by' must be character vector of column names used in grouping.")
+  if (length(by) != uniqueN(by))
+    stop("Argument 'by' must have unique column names for grouping.")
+  if (!is.list(sets) || !all(sapply(sets, is.character)))
+    stop("Argument 'sets' must be a list of character vectors.")
+  if (!is.logical(id))
+    stop("Argument 'id' must be logical scalar.")
+  # logic constraints validation
+  if (!all((sets.all.by <- unique(unlist(sets))) %chin% by))
+    stop(sprintf("All columns used in 'sets' argument must be in 'by' too. Columns used in 'sets' but not present in 'by': %s.", paste(setdiff(sets.all.by, by), collapse=", ")))
+  if (id && "grouping" %chin% names(x))
+    stop("When using `id=TRUE` the 'x' data.table must not have column named 'grouping'.")
+  if (!all(sapply(sets, function(x) length(x)==uniqueN(x))))
+    stop("Character vectors in 'sets' list must not have duplicated column names within single grouping set.")
+  if (!identical(lapply(sets, sort), unique(lapply(sets, sort))))
+    warning("Double counting is going to happen. Argument 'sets' should be unique without taking order into account, unless you really want double counting, then get used to that warning. Otherwise `sets=unique(lapply(sets, sort))` will do the trick.")
+  # input arguments handling
+  jj = if (!missing(jj)) jj else substitute(j)
+  av = all.vars(jj, TRUE)
+  if (":=" %chin% av)
+    stop("Expression passed to grouping sets function must not update by reference. Use ':=' on results of your grouping function.")
+  if (missing(.SDcols))
+    .SDcols = if (".SD" %chin% av) setdiff(names(x), by) else NULL
+  # 0 rows template data.table to keep colorder and type
+  if (length(by)) {
+    empty = if (length(.SDcols)) x[0L, eval(jj), by, .SDcols=.SDcols] else x[0L, eval(jj), by]
+  } else {
+    empty = if (length(.SDcols)) x[0L, eval(jj), .SDcols=.SDcols] else x[0L, eval(jj)]
+    if (!is.data.table(empty)) empty = setDT(list(empty)) # improve after #648, see comment in aggr.set
+  }
+  if (id && "grouping" %chin% names(empty)) # `j` could have been evaluated to `grouping` field
+    stop("When using `id=TRUE` the 'j' expression must not evaluate to column named 'grouping'.")
+  if (length(names(empty)) != uniqueN(names(empty)))
+    stop("There exists duplicated column names in the results, ensure the column passed/evaluated in `j` and those in `by` are not overlapping.")
+  # adding grouping column to template - aggregation level identifier
+  if (id) {
+    set(empty, j = "grouping", value = integer())
+    setcolorder(empty, c("grouping", by, setdiff(names(empty), c("grouping", by))))
+  }
+  # workaround for rbindlist fill=TRUE on integer64 #1459
+  int64.cols = vapply(empty, inherits, logical(1), "integer64")
+  int64.cols = names(int64.cols)[int64.cols]
+  if (length(int64.cols) && !requireNamespace("bit64", quietly=TRUE))
+    stop("Using integer64 class columns require to have 'bit64' package installed.")
+  int64.by.cols = intersect(int64.cols, by)
+  # aggregate function called for each grouping set
+  aggregate.set <- function(by.set) {
+    if (length(by.set)) {
+      r = if (length(.SDcols)) x[, eval(jj), by.set, .SDcols=.SDcols] else x[, eval(jj), by.set]
     } else {
-        empty = if (length(.SDcols)) x[0L, eval(jj), .SDcols=.SDcols] else x[0L, eval(jj)]
-        if (!is.data.table(empty)) empty = setDT(list(empty)) # improve after #648, see comment in aggr.set
+      r = if (length(.SDcols)) x[, eval(jj), .SDcols=.SDcols] else x[, eval(jj)]
+      # workaround for grand total single var as data.table too, change to drop=FALSE after #648 solved
+      if (!is.data.table(r)) r = setDT(list(r))
     }
-    if (id && "grouping" %chin% names(empty)) # `j` could have been evaluated to `grouping` field
-        stop("When using `id=TRUE` the 'j' expression must not evaluate to column named 'grouping'.")
-    if (length(names(empty)) != uniqueN(names(empty)))
-        stop("There exists duplicated column names in the results, ensure the column passed/evaluated in `j` and those in `by` are not overlapping.")
-    # adding grouping column to template - aggregation level identifier
     if (id) {
-        set(empty, j = "grouping", value = integer())
-        setcolorder(empty, c("grouping", by, setdiff(names(empty), c("grouping", by))))
+      # integer bit mask of aggregation levels: http://www.postgresql.org/docs/9.5/static/functions-aggregate.html#FUNCTIONS-GROUPING-TABLE
+      set(r, j = "grouping", value = strtoi(paste(c("1", "0")[by %chin% by.set + 1L], collapse=""), base=2L))
     }
-    # workaround for rbindlist fill=TRUE on integer64 #1459
-    int64.cols = vapply(empty, inherits, logical(1), "integer64")
-    int64.cols = names(int64.cols)[int64.cols]
-    if (length(int64.cols) && !requireNamespace("bit64", quietly=TRUE))
-        stop("Using integer64 class columns require to have 'bit64' package installed.")
-    int64.by.cols = intersect(int64.cols, by)
-    # aggregate function called for each grouping set
-    aggregate.set <- function(by.set) {
-        if (length(by.set)) {
-            r = if (length(.SDcols)) x[, eval(jj), by.set, .SDcols=.SDcols] else x[, eval(jj), by.set]
-        } else {
-            r = if (length(.SDcols)) x[, eval(jj), .SDcols=.SDcols] else x[, eval(jj)]
-            # workaround for grand total single var as data.table too, change to drop=FALSE after #648 solved
-            if (!is.data.table(r)) r = setDT(list(r))
-        }
-        if (id) {
-            # integer bit mask of aggregation levels: http://www.postgresql.org/docs/9.5/static/functions-aggregate.html#FUNCTIONS-GROUPING-TABLE
-            set(r, j = "grouping", value = strtoi(paste(c("1", "0")[by %chin% by.set + 1L], collapse=""), base=2L))
-        }
-        if (length(int64.by.cols)) {
-            # workaround for rbindlist fill=TRUE on integer64 #1459
-            missing.int64.by.cols = setdiff(int64.by.cols, by.set)
-            if (length(missing.int64.by.cols)) r[, (missing.int64.by.cols) := bit64::as.integer64(NA)]
-        }
-        r
+    if (length(int64.by.cols)) {
+      # workaround for rbindlist fill=TRUE on integer64 #1459
+      missing.int64.by.cols = setdiff(int64.by.cols, by.set)
+      if (length(missing.int64.by.cols)) r[, (missing.int64.by.cols) := bit64::as.integer64(NA)]
     }
-    # actually processing everything here
-    rbindlist(c(
-        list(empty), # 0 rows template for colorder and type
-        lapply(sets, aggregate.set) # all aggregations
-    ), use.names=TRUE, fill=TRUE)
+    r
+  }
+  # actually processing everything here
+  rbindlist(c(
+    list(empty), # 0 rows template for colorder and type
+    lapply(sets, aggregate.set) # all aggregations
+  ), use.names=TRUE, fill=TRUE)
 }
diff --git a/R/last.R b/R/last.R
index 03da38d455..d0c27a8c30 100644
--- a/R/last.R
+++ b/R/last.R
@@ -4,34 +4,34 @@
 # We'd like last() on vectors to be fast, so that's a direct x[NROW(x)] as it was in data.table, otherwise use xts's.
 # If xts is loaded higher than data.table, xts::last will work but slower.
 last <- function(x, ...) {
-    if (nargs()==1L) {
-        if (is.vector(x)) {
-            if (!length(x)) return(x) else return(x[[length(x)]])  # for vectors, [[ works like [
-        } else if (is.data.frame(x)) return(x[NROW(x),])
-    }
-    if(!requireNamespace("xts", quietly = TRUE)) {
-        tail(x, n = 1L, ...)
-    } else {
-        # fix with suggestion from Joshua, #1347
-        if (!"package:xts" %in% search()) {
-            tail(x, n = 1L, ...)
-        } else xts::last(x, ...) # UseMethod("last") doesn't find xts's methods, not sure what I did wrong.
-    }
+  if (nargs()==1L) {
+    if (is.vector(x)) {
+      if (!length(x)) return(x) else return(x[[length(x)]])  # for vectors, [[ works like [
+    } else if (is.data.frame(x)) return(x[NROW(x),])
+  }
+  if(!requireNamespace("xts", quietly = TRUE)) {
+    tail(x, n = 1L, ...)
+  } else {
+    # fix with suggestion from Joshua, #1347
+    if (!"package:xts" %in% search()) {
+      tail(x, n = 1L, ...)
+    } else xts::last(x, ...) # UseMethod("last") doesn't find xts's methods, not sure what I did wrong.
+  }
 }
 
 # first(), similar to last(), not sure why this wasn't exported in the first place...
 first <- function(x, ...) {
-    if (nargs()==1L) {
-        if (is.vector(x)) {
-            if (!length(x)) return(x) else return(x[[1L]])
-        } else if (is.data.frame(x)) return(x[1L,])
-    }
-    if(!requireNamespace("xts", quietly = TRUE)) {
-        head(x, n = 1L, ...)
-    } else {
-        # fix with suggestion from Joshua, #1347
-        if (!"package:xts" %in% search()) {
-            head(x, n = 1L, ...)
-        } else xts::first(x, ...)
-    }
+  if (nargs()==1L) {
+    if (is.vector(x)) {
+      if (!length(x)) return(x) else return(x[[1L]])
+    } else if (is.data.frame(x)) return(x[1L,])
+  }
+  if(!requireNamespace("xts", quietly = TRUE)) {
+    head(x, n = 1L, ...)
+  } else {
+    # fix with suggestion from Joshua, #1347
+    if (!"package:xts" %in% search()) {
+      head(x, n = 1L, ...)
+    } else xts::first(x, ...)
+  }
 }
diff --git a/R/like.R b/R/like.R
index 7de38bc73a..c19c8791e7 100644
--- a/R/like.R
+++ b/R/like.R
@@ -1,14 +1,14 @@
 like <- function(vector, pattern)
 {
-    # Intended for use with a data.table 'where'
-    # Don't use * or % like SQL's like.  Uses regexpr syntax - more powerful.
-    if (is.factor(vector)) {
-        as.integer(vector) %in% grep(pattern,levels(vector))
-    } else {
-        # most usually character, but integer and numerics will be silently coerced by grepl
-        grepl(pattern,vector)
-    }
-    # returns 'logical' so can be combined with other where clauses.
+  # Intended for use with a data.table 'where'
+  # Don't use * or % like SQL's like.  Uses regexpr syntax - more powerful.
+  if (is.factor(vector)) {
+    as.integer(vector) %in% grep(pattern,levels(vector))
+  } else {
+    # most usually character, but integer and numerics will be silently coerced by grepl
+    grepl(pattern,vector)
+  }
+  # returns 'logical' so can be combined with other where clauses.
 }
 
 "%like%" = like
diff --git a/R/merge.R b/R/merge.R
index a663f0f283..b36f74fcb0 100644
--- a/R/merge.R
+++ b/R/merge.R
@@ -1,86 +1,86 @@
 merge.data.table <- function(x, y, by = NULL, by.x = NULL, by.y = NULL, all = FALSE, all.x = all,
-                             all.y = all, sort = TRUE, suffixes = c(".x", ".y"), allow.cartesian=getOption("datatable.allow.cartesian"), ...) {
-    if (!sort %in% c(TRUE, FALSE))
-      stop("Argument 'sort' should be logical TRUE/FALSE")
-    if (!is.data.table(y)) {
-        y = as.data.table(y)
-        if (missing(by) && missing(by.x)) {
-            by = key(x)
-        }
+               all.y = all, sort = TRUE, suffixes = c(".x", ".y"), allow.cartesian=getOption("datatable.allow.cartesian"), ...) {
+  if (!sort %in% c(TRUE, FALSE))
+    stop("Argument 'sort' should be logical TRUE/FALSE")
+  if (!is.data.table(y)) {
+    y = as.data.table(y)
+    if (missing(by) && missing(by.x)) {
+      by = key(x)
     }
-    if ((x0 <- length(x)==0L) | (y0 <- length(y)==0L)) warning(sprintf("You are trying to join data.tables where %s 0 columns data.table.", if(x0 & y0) "'x' and 'y' arguments are" else if(x0 & !y0) "'x' argument is" else if(!x0 & y0) "'y' argument is"))
-    if (any(duplicated(names(x)))) stop("x has some duplicated column name(s): ",paste(names(x)[duplicated(names(x))],collapse=","),". Please remove or rename the duplicate(s) and try again.")
-    if (any(duplicated(names(y)))) stop("y has some duplicated column name(s): ",paste(names(y)[duplicated(names(y))],collapse=","),". Please remove or rename the duplicate(s) and try again.")
+  }
+  if ((x0 <- length(x)==0L) | (y0 <- length(y)==0L)) warning(sprintf("You are trying to join data.tables where %s 0 columns data.table.", if(x0 & y0) "'x' and 'y' arguments are" else if(x0 & !y0) "'x' argument is" else if(!x0 & y0) "'y' argument is"))
+  if (any(duplicated(names(x)))) stop("x has some duplicated column name(s): ",paste(names(x)[duplicated(names(x))],collapse=","),". Please remove or rename the duplicate(s) and try again.")
+  if (any(duplicated(names(y)))) stop("y has some duplicated column name(s): ",paste(names(y)[duplicated(names(y))],collapse=","),". Please remove or rename the duplicate(s) and try again.")
 
-    ## set up 'by'/'by.x'/'by.y'
-    if ( (!is.null(by.x) || !is.null(by.y)) && length(by.x)!=length(by.y) )
-        stop("`by.x` and `by.y` must be of same length.")
-    if (!missing(by) && !missing(by.x))
-        warning("Supplied both `by` and `by.x/by.y`. `by` argument will be ignored.")
-    if (!is.null(by.x)) {
-        if ( !is.character(by.x) || !is.character(by.y))
-            stop("A non-empty vector of column names are required for `by.x` and `by.y`.")
-        if (!all(by.x %in% names(x)))
-            stop("Elements listed in `by.x` must be valid column names in x.")
-        if (!all(by.y %in% names(y)))
-            stop("Elements listed in `by.y` must be valid column names in y.")
-        by = by.x
-        names(by) = by.y
-    } else {
-        if (is.null(by))
-            by = intersect(key(x), key(y))
-        if (is.null(by))
-            by = key(x)
-        if (is.null(by))
-            by = intersect(names(x), names(y))
-        if (length(by) == 0L || !is.character(by))
-            stop("A non-empty vector of column names for `by` is required.")
-        if (!all(by %in% intersect(colnames(x), colnames(y))))
-            stop("Elements listed in `by` must be valid column names in x and y")
-        by = unname(by)
-        by.x = by.y = by
-    }
-    # with i. prefix in v1.9.3, this goes away. Left here for now ...
-    ## sidestep the auto-increment column number feature-leading-to-bug by
-    ## ensuring no names end in ".1", see unit test
-    ## "merge and auto-increment columns in y[x]" in test-data.frame.like.R
-    start = setdiff(names(x), by.x)
-    end = setdiff(names(y), by.y)
-    dupnames = intersect(start, end)
-    if (length(dupnames)) {
-        start[chmatch(dupnames, start, 0L)] = paste(dupnames, suffixes[1L], sep="")
-        end[chmatch(dupnames, end, 0L)] = paste(dupnames, suffixes[2L], sep="")
-    }
+  ## set up 'by'/'by.x'/'by.y'
+  if ( (!is.null(by.x) || !is.null(by.y)) && length(by.x)!=length(by.y) )
+    stop("`by.x` and `by.y` must be of same length.")
+  if (!missing(by) && !missing(by.x))
+    warning("Supplied both `by` and `by.x/by.y`. `by` argument will be ignored.")
+  if (!is.null(by.x)) {
+    if ( !is.character(by.x) || !is.character(by.y))
+      stop("A non-empty vector of column names are required for `by.x` and `by.y`.")
+    if (!all(by.x %in% names(x)))
+      stop("Elements listed in `by.x` must be valid column names in x.")
+    if (!all(by.y %in% names(y)))
+      stop("Elements listed in `by.y` must be valid column names in y.")
+    by = by.x
+    names(by) = by.y
+  } else {
+    if (is.null(by))
+      by = intersect(key(x), key(y))
+    if (is.null(by))
+      by = key(x)
+    if (is.null(by))
+      by = intersect(names(x), names(y))
+    if (length(by) == 0L || !is.character(by))
+      stop("A non-empty vector of column names for `by` is required.")
+    if (!all(by %in% intersect(colnames(x), colnames(y))))
+      stop("Elements listed in `by` must be valid column names in x and y")
+    by = unname(by)
+    by.x = by.y = by
+  }
+  # with i. prefix in v1.9.3, this goes away. Left here for now ...
+  ## sidestep the auto-increment column number feature-leading-to-bug by
+  ## ensuring no names end in ".1", see unit test
+  ## "merge and auto-increment columns in y[x]" in test-data.frame.like.R
+  start = setdiff(names(x), by.x)
+  end = setdiff(names(y), by.y)
+  dupnames = intersect(start, end)
+  if (length(dupnames)) {
+    start[chmatch(dupnames, start, 0L)] = paste(dupnames, suffixes[1L], sep="")
+    end[chmatch(dupnames, end, 0L)] = paste(dupnames, suffixes[2L], sep="")
+  }
 
-    dt = y[x,nomatch = if (all.x) NA else 0,on=by,allow.cartesian=allow.cartesian]   # includes JIS columns (with a i. prefix if conflict with x names)
+  dt = y[x,nomatch = if (all.x) NA else 0,on=by,allow.cartesian=allow.cartesian]   # includes JIS columns (with a i. prefix if conflict with x names)
 
-    if (all.y && nrow(y)) {  # If y does not have any rows, no need to proceed
-        # Perhaps not very commonly used, so not a huge deal that the join is redone here.
-        missingyidx = y[!x,which=TRUE,on=by,allow.cartesian=allow.cartesian]
-        if (length(missingyidx)) {
-            yy = y[missingyidx]
-            othercolsx = setdiff(names(x), by)
-            if (length(othercolsx)) {
-                tmp = rep.int(NA_integer_, length(missingyidx))
-                # TO DO: use set() here instead..
-                yy = cbind(yy, x[tmp, othercolsx, with = FALSE])
-            }
-            # empty data.tables (nrow =0, ncol>0) doesn't skip names anymore in new rbindlist
-            # takes care of #5672 without having to save names. This is how it should be, IMHO.
-            dt = rbind(dt, yy, use.names=FALSE)
-        }
-    }
-    # X[Y] sytax puts JIS i columns at the end, merge likes them alongside i.
-    newend = setdiff(names(y), by.y)
-    # fix for #1290, make sure by.y order is set properly before naming
-    setcolorder(dt, c(by.y, setdiff(names(dt), c(by.y, newend)), newend))
-    setnames(dt, c(by.x, start, end))
-    if (nrow(dt) > 0L) {
-      setkeyv(dt, if (sort) by.x else NULL)
+  if (all.y && nrow(y)) {  # If y does not have any rows, no need to proceed
+    # Perhaps not very commonly used, so not a huge deal that the join is redone here.
+    missingyidx = y[!x,which=TRUE,on=by,allow.cartesian=allow.cartesian]
+    if (length(missingyidx)) {
+      yy = y[missingyidx]
+      othercolsx = setdiff(names(x), by)
+      if (length(othercolsx)) {
+        tmp = rep.int(NA_integer_, length(missingyidx))
+        # TO DO: use set() here instead..
+        yy = cbind(yy, x[tmp, othercolsx, with = FALSE])
+      }
+      # empty data.tables (nrow =0, ncol>0) doesn't skip names anymore in new rbindlist
+      # takes care of #5672 without having to save names. This is how it should be, IMHO.
+      dt = rbind(dt, yy, use.names=FALSE)
     }
-    # merge resets class, #1378. X[Y] is quite clear that X is being *subset* by Y,
-    # makes sense to therefore retain X's class, unlike `merge`. Hard to tell what
-    # class to retain for *full join* for example.
-    setattr(dt, 'class', c("data.table", "data.frame"))
-    dt
+  }
+  # X[Y] sytax puts JIS i columns at the end, merge likes them alongside i.
+  newend = setdiff(names(y), by.y)
+  # fix for #1290, make sure by.y order is set properly before naming
+  setcolorder(dt, c(by.y, setdiff(names(dt), c(by.y, newend)), newend))
+  setnames(dt, c(by.x, start, end))
+  if (nrow(dt) > 0L) {
+    setkeyv(dt, if (sort) by.x else NULL)
+  }
+  # merge resets class, #1378. X[Y] is quite clear that X is being *subset* by Y,
+  # makes sense to therefore retain X's class, unlike `merge`. Hard to tell what
+  # class to retain for *full join* for example.
+  setattr(dt, 'class', c("data.table", "data.frame"))
+  dt
 }
diff --git a/R/onAttach.R b/R/onAttach.R
index ba247258c4..e00b81105e 100644
--- a/R/onAttach.R
+++ b/R/onAttach.R
@@ -1,26 +1,26 @@
 .onAttach <- function(libname, pkgname) {
   # Runs when attached to search() path such as by library() or require()
   if (interactive()) {
-    v = packageVersion("data.table")
-    d = read.dcf(system.file("DESCRIPTION", package="data.table"), fields = c("Packaged", "Built"))
-    if(is.na(d[1])){
-      if(is.na(d[2])){
-        return() #neither field exists
-      } else{
-        d = unlist(strsplit(d[2], split="; "))[3]
-      }
+  v = packageVersion("data.table")
+  d = read.dcf(system.file("DESCRIPTION", package="data.table"), fields = c("Packaged", "Built"))
+  if (is.na(d[1])) {
+    if (is.na(d[2])) {
+      return() #neither field exists
     } else {
-      d = d[1]
+      d = unlist(strsplit(d[2], split="; "))[3]
     }
-    dev = as.integer(v[1,3])%%2 == 1  # version number odd => dev
-    packageStartupMessage("data.table ", v, if(dev) paste0(" IN DEVELOPMENT built ", d))
-    if (dev && (Sys.Date() - as.Date(d))>28)
-        packageStartupMessage("**********\nThis development version of data.table was built more than 4 weeks ago. Please update.\n**********")
-    if (!.Call(ChasOpenMP))
-        packageStartupMessage("**********\nThis installation of data.table has not detected OpenMP support. It should still work but in single-threaded mode. If this is a Mac, please ensure you are using R>=3.4.0 and have installed the MacOS binary package from CRAN: see ?install.packages, the 'type=' argument and the 'Binary packages' section. If you compiled from source, please reinstall and precisely follow the installation instructions on the data.table homepage. This warning message should not occur on Windows or Linux. If it does and you've followed the installation instructions on the data.table homepage, please file a GitHub issue.\n**********")
-    packageStartupMessage('  The fastest way to learn (by data.table authors): https://www.datacamp.com/courses/data-analysis-the-data-table-way')
-    packageStartupMessage('  Documentation: ?data.table, example(data.table) and browseVignettes("data.table")')
-    packageStartupMessage('  Release notes, videos and slides: http://r-datatable.com')
+  } else {
+    d = d[1]
+  }
+  dev = as.integer(v[1,3])%%2 == 1  # version number odd => dev
+  packageStartupMessage("data.table ", v, if(dev) paste0(" IN DEVELOPMENT built ", d))
+  if (dev && (Sys.Date() - as.Date(d))>28)
+    packageStartupMessage("**********\nThis development version of data.table was built more than 4 weeks ago. Please update.\n**********")
+  if (!.Call(ChasOpenMP))
+    packageStartupMessage("**********\nThis installation of data.table has not detected OpenMP support. It should still work but in single-threaded mode. If this is a Mac, please ensure you are using R>=3.4.0 and have installed the MacOS binary package from CRAN: see ?install.packages, the 'type=' argument and the 'Binary packages' section. If you compiled from source, please reinstall and precisely follow the installation instructions on the data.table homepage. This warning message should not occur on Windows or Linux. If it does and you've followed the installation instructions on the data.table homepage, please file a GitHub issue.\n**********")
+  packageStartupMessage('  The fastest way to learn (by data.table authors): https://www.datacamp.com/courses/data-analysis-the-data-table-way')
+  packageStartupMessage('  Documentation: ?data.table, example(data.table) and browseVignettes("data.table")')
+  packageStartupMessage('  Release notes, videos and slides: http://r-datatable.com')
   }
 }
 
diff --git a/R/onLoad.R b/R/onLoad.R
index e49df3b1e3..47e2fa44c0 100644
--- a/R/onLoad.R
+++ b/R/onLoad.R
@@ -1,95 +1,95 @@
 .onLoad <- function(libname, pkgname) {
-    # Runs when loaded but not attached to search() path; e.g., when a package just Imports (not Depends on) data.table
+  # Runs when loaded but not attached to search() path; e.g., when a package just Imports (not Depends on) data.table
 
-    "Please read FAQ 2.23 (vignette('datatable-faq')) which explains in detail why data.table adds one for loop to the start of base::cbind.data.frame and base::rbind.data.frame. If there is a better solution we will gladly change it."
-    # Commented as a character string so this message is retained and seen by anyone who types data.table:::.onLoad
-    tt = base::cbind.data.frame
-    ss = body(tt)
-    if (class(ss)!="{") ss = as.call(c(as.name("{"), ss))
-    prefix = if (!missing(pkgname)) "data.table::" else ""  # R provides the arguments when it calls .onLoad, I don't in dev/test
-    if (!length(grep("data.table",ss[[2]]))) {
-        ss = ss[c(1,NA,2:length(ss))]
-        ss[[2]] = parse(text=paste("if (!identical(class(..1),'data.frame')) for (x in list(...)) { if (inherits(x,'data.table')) return(",prefix,"data.table(...)) }",sep=""))[[1]]
-        body(tt)=ss
-        (unlockBinding)("cbind.data.frame",baseenv())
-        assign("cbind.data.frame",tt,envir=asNamespace("base"),inherits=FALSE)
-        lockBinding("cbind.data.frame",baseenv())
-    }
-    tt = base::rbind.data.frame
-    ss = body(tt)
-    if (class(ss)!="{") ss = as.call(c(as.name("{"), ss))
-    if (!length(grep("data.table",ss[[2]]))) {
-        ss = ss[c(1,NA,2:length(ss))]
-        ss[[2]] = parse(text=paste("for (x in list(...)) { if (inherits(x,'data.table')) return(",prefix,".rbind.data.table(...)) }",sep=""))[[1]] # fix for #4995
-        body(tt)=ss
-        (unlockBinding)("rbind.data.frame",baseenv())
-        assign("rbind.data.frame",tt,envir=asNamespace("base"),inherits=FALSE)
-        lockBinding("rbind.data.frame",baseenv())
-    }
-    # Set options for the speed boost in v1.8.0 by avoiding 'default' arg of getOption(,default=)
-    opts = c("datatable.verbose"="FALSE",            # datatable.<argument name>
-             "datatable.nomatch"="NA_integer_",      # datatable.<argument name>
-             "datatable.optimize"="Inf",             # datatable.<argument name>
-             "datatable.print.nrows"="100L",         # datatable.<argument name>
-             "datatable.print.topn"="5L",            # datatable.<argument name>
-             "datatable.print.class"="FALSE",        # for print.data.table
-             "datatable.print.rownames"="TRUE",      # for print.data.table
-             "datatable.print.colnames"="'auto'",      # for print.data.table
-             "datatable.print.keys"="FALSE",         # for print.data.table
-             "datatable.allow.cartesian"="FALSE",    # datatable.<argument name>
-             "datatable.dfdispatchwarn"="TRUE",                   # not a function argument
-             "datatable.warnredundantby"="TRUE",                  # not a function argument
-             "datatable.alloccol"="1024L",           # argument 'n' of alloc.col. Over-allocate 1024 spare column slots
-             "datatable.integer64"="'integer64'",    # datatable.<argument name>    integer64|double|character
-             "datatable.auto.index"="TRUE",          # DT[col=="val"] to auto add index so 2nd time faster
-             "datatable.use.index"="TRUE",           # global switch to address #1422
-             "datatable.fread.datatable"="TRUE",
-             "datatable.prettyprint.char" = NULL,     # FR #1091
-             "datatable.old.unique.by.key" = "FALSE", # TODO: warn 1 year, remove after 2 years
-             "datatable.logical01" = "TRUE"           # fwrite/fread to revert to FALSE. TODO: warn in next release and remove after 1 year
-             )
-    for (i in setdiff(names(opts),names(options()))) {
-        eval(parse(text=paste("options(",i,"=",opts[i],")",sep="")))
-    }
+  "Please read FAQ 2.23 (vignette('datatable-faq')) which explains in detail why data.table adds one for loop to the start of base::cbind.data.frame and base::rbind.data.frame. If there is a better solution we will gladly change it."
+  # Commented as a character string so this message is retained and seen by anyone who types data.table:::.onLoad
+  tt = base::cbind.data.frame
+  ss = body(tt)
+  if (class(ss)!="{") ss = as.call(c(as.name("{"), ss))
+  prefix = if (!missing(pkgname)) "data.table::" else ""  # R provides the arguments when it calls .onLoad, I don't in dev/test
+  if (!length(grep("data.table",ss[[2]]))) {
+    ss = ss[c(1,NA,2:length(ss))]
+    ss[[2]] = parse(text=paste("if (!identical(class(..1),'data.frame')) for (x in list(...)) { if (inherits(x,'data.table')) return(",prefix,"data.table(...)) }",sep=""))[[1]]
+    body(tt)=ss
+    (unlockBinding)("cbind.data.frame",baseenv())
+    assign("cbind.data.frame",tt,envir=asNamespace("base"),inherits=FALSE)
+    lockBinding("cbind.data.frame",baseenv())
+  }
+  tt = base::rbind.data.frame
+  ss = body(tt)
+  if (class(ss)!="{") ss = as.call(c(as.name("{"), ss))
+  if (!length(grep("data.table",ss[[2]]))) {
+    ss = ss[c(1,NA,2:length(ss))]
+    ss[[2]] = parse(text=paste("for (x in list(...)) { if (inherits(x,'data.table')) return(",prefix,".rbind.data.table(...)) }",sep=""))[[1]] # fix for #4995
+    body(tt)=ss
+    (unlockBinding)("rbind.data.frame",baseenv())
+    assign("rbind.data.frame",tt,envir=asNamespace("base"),inherits=FALSE)
+    lockBinding("rbind.data.frame",baseenv())
+  }
+  # Set options for the speed boost in v1.8.0 by avoiding 'default' arg of getOption(,default=)
+  opts = c("datatable.verbose"="FALSE",            # datatable.<argument name>
+       "datatable.nomatch"="NA_integer_",      # datatable.<argument name>
+       "datatable.optimize"="Inf",             # datatable.<argument name>
+       "datatable.print.nrows"="100L",         # datatable.<argument name>
+       "datatable.print.topn"="5L",            # datatable.<argument name>
+       "datatable.print.class"="FALSE",        # for print.data.table
+       "datatable.print.rownames"="TRUE",      # for print.data.table
+       "datatable.print.colnames"="'auto'",      # for print.data.table
+       "datatable.print.keys"="FALSE",         # for print.data.table
+       "datatable.allow.cartesian"="FALSE",    # datatable.<argument name>
+       "datatable.dfdispatchwarn"="TRUE",                   # not a function argument
+       "datatable.warnredundantby"="TRUE",                  # not a function argument
+       "datatable.alloccol"="1024L",           # argument 'n' of alloc.col. Over-allocate 1024 spare column slots
+       "datatable.integer64"="'integer64'",    # datatable.<argument name>    integer64|double|character
+       "datatable.auto.index"="TRUE",          # DT[col=="val"] to auto add index so 2nd time faster
+       "datatable.use.index"="TRUE",           # global switch to address #1422
+       "datatable.fread.datatable"="TRUE",
+       "datatable.prettyprint.char" = NULL,     # FR #1091
+       "datatable.old.unique.by.key" = "FALSE", # TODO: warn 1 year, remove after 2 years
+       "datatable.logical01" = "TRUE"           # fwrite/fread to revert to FALSE. TODO: warn in next release and remove after 1 year
+       )
+  for (i in setdiff(names(opts),names(options()))) {
+    eval(parse(text=paste("options(",i,"=",opts[i],")",sep="")))
+  }
 
-    if (!is.null(getOption("datatable.old.bywithoutby")))
-        warning("Option 'datatable.old.bywithoutby' has been removed as warned for 2 years. It is now ignored. Please use by=.EACHI instead and stop using this option.")
+  if (!is.null(getOption("datatable.old.bywithoutby")))
+    warning("Option 'datatable.old.bywithoutby' has been removed as warned for 2 years. It is now ignored. Please use by=.EACHI instead and stop using this option.")
 
-    # reshape2
-    # Tried this :
-    # if (!"package:reshape2" %in% search()) {
-    #   # temporary until reshape2 pull request to make generic is on CRAN ...
-    #   try(library(reshape2, pos="package:base", quietly=TRUE, warn.conflicts=FALSE), silent=TRUE)
-    # }
-    # which works. But then when melt in data.table is loaded, _that's_ what generates the mask message.
-    # There's also a NOTE: Package startup functions should not change the search path.
-    # Therefore, removed. Users will need to make sure reshape2 isn't loaded, or loaded behind data.table on search()
+  # reshape2
+  # Tried this :
+  # if (!"package:reshape2" %in% search()) {
+  #   # temporary until reshape2 pull request to make generic is on CRAN ...
+  #   try(library(reshape2, pos="package:base", quietly=TRUE, warn.conflicts=FALSE), silent=TRUE)
+  # }
+  # which works. But then when melt in data.table is loaded, _that's_ what generates the mask message.
+  # There's also a NOTE: Package startup functions should not change the search path.
+  # Therefore, removed. Users will need to make sure reshape2 isn't loaded, or loaded behind data.table on search()
 
-    # Test R behaviour ...
+  # Test R behaviour ...
 
-    x = 1:3
-    y = list(x)
-    .R.listCopiesNamed <<- (address(x) != address(y[[1]]))   # FALSE from R 3.1
+  x = 1:3
+  y = list(x)
+  .R.listCopiesNamed <<- (address(x) != address(y[[1]]))   # FALSE from R 3.1
 
-    DF = data.frame(a=1:3, b=4:6)
-    add1 = address(DF$a)
-    add2 = address(DF$b)
-    names(DF) = c("A","B")
-    add3 = address(DF$A)
-    add4 = address(DF$B)
-    .R.assignNamesCopiesAll <<- add1 != add3                 # FALSE from R 3.1
-    if ((add1 == add3) != (add2 == add4)) stop("If one column is copied surely the other should be as well, when checking .R.assignNamesCopiesAll")
+  DF = data.frame(a=1:3, b=4:6)
+  add1 = address(DF$a)
+  add2 = address(DF$b)
+  names(DF) = c("A","B")
+  add3 = address(DF$A)
+  add4 = address(DF$B)
+  .R.assignNamesCopiesAll <<- add1 != add3                 # FALSE from R 3.1
+  if ((add1 == add3) != (add2 == add4)) stop("If one column is copied surely the other should be as well, when checking .R.assignNamesCopiesAll")
 
-    DF = data.frame(a=1:3, b=4:6)
-    add1 = address(DF$a)
-    add2 = address(DF)
-    DF[2,"b"] = 7  # changed b but not a
-    add3 = address(DF$a)
-    add4 = address(DF)
-    .R.subassignCopiesOthers <<- add1 != add3                # FALSE from R 3.1
-    .R.subassignCopiesVecsxp <<- add2 != add4                # currently TRUE in R 3.1, but could feasibly change
+  DF = data.frame(a=1:3, b=4:6)
+  add1 = address(DF$a)
+  add2 = address(DF)
+  DF[2,"b"] = 7  # changed b but not a
+  add3 = address(DF$a)
+  add4 = address(DF)
+  .R.subassignCopiesOthers <<- add1 != add3                # FALSE from R 3.1
+  .R.subassignCopiesVecsxp <<- add2 != add4                # currently TRUE in R 3.1, but could feasibly change
 
-    invisible()
+  invisible()
 }
 
 # Switch on these variables instead of getRversion(). Set to TRUE just to create them as a single logical. They are set by .onLoad() above.
@@ -105,7 +105,7 @@ getRversion <- function(...) stop("Reminder to data.table developers: don't use
 # 4) Defining getRversion with a stop() here helps prevent new switches on getRversion() being added in future. Easily circumvented but the point is to issue the message above.
 
 .onUnload <- function(libpath) {
-    # fix for #474. the shared object name is different from package name
-    # So 'detach' doesn't find datatable.so, as it looks by default for data.table.so
-    library.dynam.unload("datatable", libpath)
+  # fix for #474. the shared object name is different from package name
+  # So 'detach' doesn't find datatable.so, as it looks by default for data.table.so
+  library.dynam.unload("datatable", libpath)
 }
diff --git a/R/openmp-utils.R b/R/openmp-utils.R
index ebc7066889..15d3acc57a 100644
--- a/R/openmp-utils.R
+++ b/R/openmp-utils.R
@@ -1,8 +1,8 @@
 setDTthreads <- function(threads) {
-    invisible(.Call(CsetDTthreads, as.integer(threads)))
+  invisible(.Call(CsetDTthreads, as.integer(threads)))
 }
 
 getDTthreads <- function() {
-    .Call(CgetDTthreads)
+  .Call(CgetDTthreads)
 }
 
diff --git a/R/print.data.table.R b/R/print.data.table.R
index c03b9bb585..998301f6ce 100644
--- a/R/print.data.table.R
+++ b/R/print.data.table.R
@@ -1,135 +1,135 @@
 # Moved here out from data.table.R on 10 Aug 2017. See data.table.R for history prior to that.
 
 print.data.table <- function(x, topn=getOption("datatable.print.topn"),
-                             nrows=getOption("datatable.print.nrows"),
-                             class=getOption("datatable.print.class"),
-                             row.names=getOption("datatable.print.rownames"),
-                             col.names=getOption("datatable.print.colnames"),
-                             print.keys=getOption("datatable.print.keys"),
-                             quote=FALSE, ...) {    # topn  - print the top topn and bottom topn rows with '---' inbetween (5)
-    # nrows - under this the whole (small) table is printed, unless topn is provided (100)
-    # class - should column class be printed underneath column name? (FALSE)
-    if (!col.names %in% c("auto", "top", "none"))
-      stop("Valid options for col.names are 'auto', 'top', and 'none'")
-    if (col.names == "none" && class)
-      warning("Column classes will be suppressed when col.names is 'none'")
-    if (!shouldPrint(x)) {
-        #  := in [.data.table sets .global$print=address(x) to suppress the next print i.e., like <- does. See FAQ 2.22 and README item in v1.9.5
-        # The issue is distinguishing "> DT" (after a previous := in a function) from "> DT[,foo:=1]". To print.data.table(), there
-        # is no difference. Now from R 3.2.0 a side effect of the very welcome and requested change to avoid silent deep copy is that
-        # there is now no longer a difference between > DT and > print(DT). So decided that DT[] is now needed to guarantee print; simpler.
-        # This applies just at the prompt. Inside functions, print(DT) will of course print.
-        # Other options investigated (could revisit): Cstack_info(), .Last.value gets set first before autoprint, history(), sys.status(),
-        #   topenv(), inspecting next statement in caller, using clock() at C level to timeout suppression after some number of cycles
-        SYS <- sys.calls()
-        if (length(SYS) <= 2 ||  # "> DT" auto-print or "> print(DT)" explicit print (cannot distinguish from R 3.2.0 but that's ok)
-            ( length(SYS) > 3L && is.symbol(thisSYS <- SYS[[length(SYS)-3L]][[1L]]) &&
-              as.character(thisSYS) %chin% mimicsAutoPrint ) )  {
-            return(invisible())
-            # is.symbol() temp fix for #1758.
-        }
+               nrows=getOption("datatable.print.nrows"),
+               class=getOption("datatable.print.class"),
+               row.names=getOption("datatable.print.rownames"),
+               col.names=getOption("datatable.print.colnames"),
+               print.keys=getOption("datatable.print.keys"),
+               quote=FALSE, ...) {    # topn  - print the top topn and bottom topn rows with '---' inbetween (5)
+  # nrows - under this the whole (small) table is printed, unless topn is provided (100)
+  # class - should column class be printed underneath column name? (FALSE)
+  if (!col.names %in% c("auto", "top", "none"))
+    stop("Valid options for col.names are 'auto', 'top', and 'none'")
+  if (col.names == "none" && class)
+    warning("Column classes will be suppressed when col.names is 'none'")
+  if (!shouldPrint(x)) {
+    #  := in [.data.table sets .global$print=address(x) to suppress the next print i.e., like <- does. See FAQ 2.22 and README item in v1.9.5
+    # The issue is distinguishing "> DT" (after a previous := in a function) from "> DT[,foo:=1]". To print.data.table(), there
+    # is no difference. Now from R 3.2.0 a side effect of the very welcome and requested change to avoid silent deep copy is that
+    # there is now no longer a difference between > DT and > print(DT). So decided that DT[] is now needed to guarantee print; simpler.
+    # This applies just at the prompt. Inside functions, print(DT) will of course print.
+    # Other options investigated (could revisit): Cstack_info(), .Last.value gets set first before autoprint, history(), sys.status(),
+    #   topenv(), inspecting next statement in caller, using clock() at C level to timeout suppression after some number of cycles
+    SYS <- sys.calls()
+    if (length(SYS) <= 2 ||  # "> DT" auto-print or "> print(DT)" explicit print (cannot distinguish from R 3.2.0 but that's ok)
+        ( length(SYS) > 3L && is.symbol(thisSYS <- SYS[[length(SYS)-3L]][[1L]]) &&
+          as.character(thisSYS) %chin% mimicsAutoPrint ) )  {
+      return(invisible())
+      # is.symbol() temp fix for #1758.
     }
-    if (!is.numeric(nrows)) nrows = 100L
-    if (!is.infinite(nrows)) nrows = as.integer(nrows)
-    if (nrows <= 0L) return(invisible())   # ability to turn off printing
-    if (!is.numeric(topn)) topn = 5L
-    topnmiss = missing(topn)
-    topn = max(as.integer(topn),1L)
-    if (print.keys){
-      if (!is.null(ky <- key(x)))
-        cat("Key: <", paste(ky, collapse=", "), ">\n", sep="")
-      if (!is.null(ixs <- indices(x)))
-        cat("Ind", if (length(ixs) > 1) "ices" else "ex", ": <",
-            paste(ixs, collapse=">, <"), ">\n", sep="")
-    }
-    if (nrow(x) == 0L) {
-        if (length(x)==0L)
-           cat("Null data.table (0 rows and 0 cols)\n")  # See FAQ 2.5 and NEWS item in v1.8.9
-        else
-           cat("Empty data.table (0 rows) of ",length(x)," col",if(length(x)>1L)"s",": ",paste(head(names(x),6),collapse=","),if(ncol(x)>6)"...","\n",sep="")
-        return(invisible())
-    }
-    if (topn*2<nrow(x) && (nrow(x)>nrows || !topnmiss)) {
-        toprint = rbind(head(x, topn), tail(x, topn))
-        rn = c(seq_len(topn), seq.int(to=nrow(x), length.out=topn))
-        printdots = TRUE
-    } else {
-        toprint = x
-        rn = seq_len(nrow(x))
-        printdots = FALSE
-    }
-    toprint=format.data.table(toprint, ...)
+  }
+  if (!is.numeric(nrows)) nrows = 100L
+  if (!is.infinite(nrows)) nrows = as.integer(nrows)
+  if (nrows <= 0L) return(invisible())   # ability to turn off printing
+  if (!is.numeric(topn)) topn = 5L
+  topnmiss = missing(topn)
+  topn = max(as.integer(topn),1L)
+  if (print.keys){
+    if (!is.null(ky <- key(x)))
+    cat("Key: <", paste(ky, collapse=", "), ">\n", sep="")
+    if (!is.null(ixs <- indices(x)))
+    cat("Ind", if (length(ixs) > 1) "ices" else "ex", ": <",
+      paste(ixs, collapse=">, <"), ">\n", sep="")
+  }
+  if (nrow(x) == 0L) {
+    if (length(x)==0L)
+       cat("Null data.table (0 rows and 0 cols)\n")  # See FAQ 2.5 and NEWS item in v1.8.9
+    else
+       cat("Empty data.table (0 rows) of ",length(x)," col",if(length(x)>1L)"s",": ",paste(head(names(x),6),collapse=","),if(ncol(x)>6)"...","\n",sep="")
+    return(invisible())
+  }
+  if (topn*2<nrow(x) && (nrow(x)>nrows || !topnmiss)) {
+    toprint = rbind(head(x, topn), tail(x, topn))
+    rn = c(seq_len(topn), seq.int(to=nrow(x), length.out=topn))
+    printdots = TRUE
+  } else {
+    toprint = x
+    rn = seq_len(nrow(x))
+    printdots = FALSE
+  }
+  toprint=format.data.table(toprint, ...)
 
-    if ((!"bit64" %chin% loadedNamespaces()) && any(sapply(x,inherits,"integer64"))) require_bit64()
-    # When we depend on R 3.2.0 (Apr 2015) we can use isNamespaceLoaded() added then, instead of %chin% above
+  if ((!"bit64" %chin% loadedNamespaces()) && any(sapply(x,inherits,"integer64"))) require_bit64()
+  # When we depend on R 3.2.0 (Apr 2015) we can use isNamespaceLoaded() added then, instead of %chin% above
 
-    # FR #5020 - add row.names = logical argument to print.data.table
-    if (isTRUE(row.names)) rownames(toprint)=paste(format(rn,right=TRUE,scientific=FALSE),":",sep="") else rownames(toprint)=rep.int("", nrow(toprint))
-    if (is.null(names(x)) || all(names(x) == ""))
-      # fixes bug #97 (RF#4934) and #545 (RF#5253)
-      colnames(toprint)=rep("", ncol(toprint))
-    if (isTRUE(class) && col.names != "none") {
-      #Matching table for most common types & their abbreviations
-      class_abb = c(list = "<list>", integer = "<int>", numeric = "<num>",
-            character = "<char>", Date = "<Date>", complex = "<cplx>",
-            factor = "<fctr>", POSIXct = "<POSc>", logical = "<lgcl>",
-            IDate = "<IDat>", integer64 = "<i64>", raw = "<raw>",
-            expression = "<expr>", ordered = "<ord>")
-      classes = vapply(x, function(col) class(col)[1L], "", USE.NAMES=FALSE)
-      abbs = unname(class_abb[classes])
-      if ( length(idx <- which(is.na(abbs))) )
-        abbs[idx] = paste("<", classes[idx], ">", sep="")
-      toprint = rbind(abbs, toprint)
-      rownames(toprint)[1L] = ""
-    }
-    if (quote) colnames(toprint) <- paste0('"', old <- colnames(toprint), '"')
-    if (printdots) {
-        toprint = rbind(head(toprint, topn), "---"="", tail(toprint, topn))
-        rownames(toprint) = format(rownames(toprint), justify="right")
-        if (col.names == "none") {
-            cut_top(print(toprint, right=TRUE, quote=quote))
-        } else {
-            print(toprint, right=TRUE, quote=quote)
-        }
-        return(invisible())
-    }
-    if (nrow(toprint)>20L && col.names == "auto")
-        # repeat colnames at the bottom if over 20 rows so you don't have to scroll up to see them
-        #   option to shut this off per request of Oleg Bondar on SO, #1482
-        toprint=rbind(toprint, matrix(if (quote) old else colnames(toprint), nrow=1L)) # fixes bug #4934
+  # FR #5020 - add row.names = logical argument to print.data.table
+  if (isTRUE(row.names)) rownames(toprint)=paste(format(rn,right=TRUE,scientific=FALSE),":",sep="") else rownames(toprint)=rep.int("", nrow(toprint))
+  if (is.null(names(x)) || all(names(x) == ""))
+    # fixes bug #97 (RF#4934) and #545 (RF#5253)
+    colnames(toprint)=rep("", ncol(toprint))
+  if (isTRUE(class) && col.names != "none") {
+    #Matching table for most common types & their abbreviations
+    class_abb = c(list = "<list>", integer = "<int>", numeric = "<num>",
+      character = "<char>", Date = "<Date>", complex = "<cplx>",
+      factor = "<fctr>", POSIXct = "<POSc>", logical = "<lgcl>",
+      IDate = "<IDat>", integer64 = "<i64>", raw = "<raw>",
+      expression = "<expr>", ordered = "<ord>")
+    classes = vapply(x, function(col) class(col)[1L], "", USE.NAMES=FALSE)
+    abbs = unname(class_abb[classes])
+    if ( length(idx <- which(is.na(abbs))) )
+    abbs[idx] = paste("<", classes[idx], ">", sep="")
+    toprint = rbind(abbs, toprint)
+    rownames(toprint)[1L] = ""
+  }
+  if (quote) colnames(toprint) <- paste0('"', old <- colnames(toprint), '"')
+  if (printdots) {
+    toprint = rbind(head(toprint, topn), "---"="", tail(toprint, topn))
+    rownames(toprint) = format(rownames(toprint), justify="right")
     if (col.names == "none") {
-        cut_top(print(toprint, right=TRUE, quote=quote))
+      cut_top(print(toprint, right=TRUE, quote=quote))
     } else {
-        print(toprint, right=TRUE, quote=quote)
+      print(toprint, right=TRUE, quote=quote)
     }
-    invisible()
+    return(invisible())
+  }
+  if (nrow(toprint)>20L && col.names == "auto")
+    # repeat colnames at the bottom if over 20 rows so you don't have to scroll up to see them
+    #   option to shut this off per request of Oleg Bondar on SO, #1482
+    toprint=rbind(toprint, matrix(if (quote) old else colnames(toprint), nrow=1L)) # fixes bug #4934
+  if (col.names == "none") {
+    cut_top(print(toprint, right=TRUE, quote=quote))
+  } else {
+    print(toprint, right=TRUE, quote=quote)
+  }
+  invisible()
 }
 
 format.data.table <- function (x, ..., justify="none") {
-    if (is.atomic(x) && !is.null(x)) {
-        stop("Internal structure doesn't seem to be a list. Possibly corrupt data.table.")
-    }
-    format.item <- function(x) {
-        if (is.atomic(x) || is.formula(x)) # FR #2591 - format.data.table issue with columns of class "formula"
-            paste(c(format(head(x,6), justify=justify, ...), if(length(x)>6)""),collapse=",")  # fix for #5435 - format has to be added here...
-        else
-            paste("<",class(x)[1L],">",sep="")
-    }
-    # FR #1091 for pretty printing of character
-    # TODO: maybe instead of doing "this is...", we could do "this ... test"?
-    char.trunc <- function(x, trunc.char = getOption("datatable.prettyprint.char")) {
-        trunc.char = max(0L, suppressWarnings(as.integer(trunc.char[1L])), na.rm=TRUE)
-        if (!is.character(x) || trunc.char <= 0L) return(x)
-        idx = which(nchar(x) > trunc.char)
-        x[idx] = paste(substr(x[idx], 1L, as.integer(trunc.char)), "...", sep="")
-        x
-    }
-    do.call("cbind",lapply(x,function(col,...){
-        if (!is.null(dim(col))) stop("Invalid column: it has dimensions. Can't format it. If it's the result of data.table(table()), use as.data.table(table()) instead.")
-        if (is.list(col)) col = vapply_1c(col, format.item)
-        else col = format(char.trunc(col), justify=justify, ...) # added an else here to fix #5435
-        col
-    },...))
+  if (is.atomic(x) && !is.null(x)) {
+    stop("Internal structure doesn't seem to be a list. Possibly corrupt data.table.")
+  }
+  format.item <- function(x) {
+    if (is.atomic(x) || is.formula(x)) # FR #2591 - format.data.table issue with columns of class "formula"
+      paste(c(format(head(x,6), justify=justify, ...), if(length(x)>6)""),collapse=",")  # fix for #5435 - format has to be added here...
+    else
+      paste("<",class(x)[1L],">",sep="")
+  }
+  # FR #1091 for pretty printing of character
+  # TODO: maybe instead of doing "this is...", we could do "this ... test"?
+  char.trunc <- function(x, trunc.char = getOption("datatable.prettyprint.char")) {
+    trunc.char = max(0L, suppressWarnings(as.integer(trunc.char[1L])), na.rm=TRUE)
+    if (!is.character(x) || trunc.char <= 0L) return(x)
+    idx = which(nchar(x) > trunc.char)
+    x[idx] = paste(substr(x[idx], 1L, as.integer(trunc.char)), "...", sep="")
+    x
+  }
+  do.call("cbind",lapply(x,function(col,...){
+    if (!is.null(dim(col))) stop("Invalid column: it has dimensions. Can't format it. If it's the result of data.table(table()), use as.data.table(table()) instead.")
+    if (is.list(col)) col = vapply_1c(col, format.item)
+    else col = format(char.trunc(col), justify=justify, ...) # added an else here to fix #5435
+    col
+  },...))
 }
 
 mimicsAutoPrint = c("knit_print.default")
@@ -137,7 +137,7 @@ mimicsAutoPrint = c("knit_print.default")
 
 shouldPrint = function(x) {
   ret = (.global$print=="" ||   # to save address() calls and adding lots of address strings to R's global cache
-         address(x)!=.global$print)
+     address(x)!=.global$print)
   .global$print = ""
   ret
 }
diff --git a/R/setkey.R b/R/setkey.R
index ee64dc39ab..afa62ad4c2 100644
--- a/R/setkey.R
+++ b/R/setkey.R
@@ -1,10 +1,10 @@
 setkey <- function(x, ..., verbose=getOption("datatable.verbose"), physical=TRUE)
 {
-    if (is.character(x)) stop("x may no longer be the character name of the data.table. The possibility was undocumented and has been removed.")
-    cols = as.character(substitute(list(...))[-1])
-    if (!length(cols)) cols=colnames(x)
-    else if (identical(cols,"NULL")) cols=NULL
-    setkeyv(x, cols, verbose=verbose, physical=physical)
+  if (is.character(x)) stop("x may no longer be the character name of the data.table. The possibility was undocumented and has been removed.")
+  cols = as.character(substitute(list(...))[-1])
+  if (!length(cols)) cols=colnames(x)
+  else if (identical(cols,"NULL")) cols=NULL
+  setkeyv(x, cols, verbose=verbose, physical=physical)
 }
 
 # FR #1442
@@ -12,42 +12,42 @@ setindex <- function(...) setkey(..., physical=FALSE)
 setindexv <- function(...) setkeyv(..., physical=FALSE)
 
 set2key <- function(...) {
-    warning("set2key() will be deprecated in the next relase. Please use setindex() instead.", call.=FALSE)
-    setkey(..., physical=FALSE)
+  warning("set2key() will be deprecated in the next relase. Please use setindex() instead.", call.=FALSE)
+  setkey(..., physical=FALSE)
 }
 set2keyv <- function(...) {
-    warning("set2key() will be deprecated in the next relase. Please use setindex() instead.", call.=FALSE)
-    setkeyv(..., physical=FALSE)
+  warning("set2key() will be deprecated in the next relase. Please use setindex() instead.", call.=FALSE)
+  setkeyv(..., physical=FALSE)
 }
 
 setkeyv <- function(x, cols, verbose=getOption("datatable.verbose"), physical=TRUE)
 {
-    if (is.null(cols)) {   # this is done on a data.frame when !cedta at top of [.data.table
-        if (physical) setattr(x,"sorted",NULL)
-        setattr(x,"index",NULL)  # setkey(DT,NULL) also clears secondary keys. set2key(DT,NULL) just clears secondary keys.
-        return(invisible(x))
-    }
-    if (!is.data.table(x)) stop("x is not a data.table")
-    if (!is.character(cols)) stop("cols is not a character vector. Please see further information in ?setkey.")
-    if (physical && identical(attr(x,".data.table.locked"),TRUE)) stop("Setting a physical key on .SD is reserved for possible future use; to modify the original data's order by group. Try set2key instead. Or, set*(copy(.SD)) as a (slow) last resort.")
-    if (!length(cols)) {
-        warning("cols is a character vector of zero length. Removed the key, but use NULL instead, or wrap with suppressWarnings() to avoid this warning.")
-        setattr(x,"sorted",NULL)
-        return(invisible(x))
-    }
-    if (identical(cols,"")) stop("cols is the empty string. Use NULL to remove the key.")
-    if (!all(nzchar(cols))) stop("cols contains some blanks.")
-    if (!length(cols)) {
-        cols = colnames(x)   # All columns in the data.table, usually a few when used in this form
-    } else {
-        # remove backticks from cols
-        cols <- gsub("`", "", cols)
-        miss = !(cols %in% colnames(x))
-        if (any(miss)) stop("some columns are not in the data.table: " %+% cols[miss])
-    }
+  if (is.null(cols)) {   # this is done on a data.frame when !cedta at top of [.data.table
+    if (physical) setattr(x,"sorted",NULL)
+    setattr(x,"index",NULL)  # setkey(DT,NULL) also clears secondary keys. set2key(DT,NULL) just clears secondary keys.
+    return(invisible(x))
+  }
+  if (!is.data.table(x)) stop("x is not a data.table")
+  if (!is.character(cols)) stop("cols is not a character vector. Please see further information in ?setkey.")
+  if (physical && identical(attr(x,".data.table.locked"),TRUE)) stop("Setting a physical key on .SD is reserved for possible future use; to modify the original data's order by group. Try set2key instead. Or, set*(copy(.SD)) as a (slow) last resort.")
+  if (!length(cols)) {
+    warning("cols is a character vector of zero length. Removed the key, but use NULL instead, or wrap with suppressWarnings() to avoid this warning.")
+    setattr(x,"sorted",NULL)
+    return(invisible(x))
+  }
+  if (identical(cols,"")) stop("cols is the empty string. Use NULL to remove the key.")
+  if (!all(nzchar(cols))) stop("cols contains some blanks.")
+  if (!length(cols)) {
+    cols = colnames(x)   # All columns in the data.table, usually a few when used in this form
+  } else {
+    # remove backticks from cols
+    cols <- gsub("`", "", cols)
+    miss = !(cols %in% colnames(x))
+    if (any(miss)) stop("some columns are not in the data.table: " %+% cols[miss])
+  }
 
   ## determine, whether key is already present:
-  if(identical(key(x),cols)){
+  if (identical(key(x),cols)) {
     ## key is present, nothing needs to be done
     return(invisible(x))
   } else if(identical(head(key(x), length(cols)), cols)){
@@ -57,64 +57,64 @@ setkeyv <- function(x, cols, verbose=getOption("datatable.verbose"), physical=TR
     ## maybe additional speedup can be achieved if part of the key is already present?
   }
 
-    if (".xi" %chin% names(x)) stop("x contains a column called '.xi'. Conflicts with internal use by data.table.")
-    for (i in cols) {
-        .xi = x[[i]]  # [[ is copy on write, otherwise checking type would be copying each column
-        if (!typeof(.xi) %chin% c("integer","logical","character","double")) stop("Column '",i,"' is type '",typeof(.xi),"' which is not supported as a key column type, currently.")
-    }
-    if (!is.character(cols) || length(cols)<1) stop("'cols' should be character at this point in setkey")
+  if (".xi" %chin% names(x)) stop("x contains a column called '.xi'. Conflicts with internal use by data.table.")
+  for (i in cols) {
+    .xi = x[[i]]  # [[ is copy on write, otherwise checking type would be copying each column
+    if (!typeof(.xi) %chin% c("integer","logical","character","double")) stop("Column '",i,"' is type '",typeof(.xi),"' which is not supported as a key column type, currently.")
+  }
+  if (!is.character(cols) || length(cols)<1) stop("'cols' should be character at this point in setkey")
+  if (verbose) {
+    tt = system.time(o <- forderv(x, cols, sort=TRUE, retGrp=FALSE))  # system.time does a gc, so we don't want this always on, until refcnt is on by default in R
+    cat("forder took", tt["user.self"]+tt["sys.self"], "sec\n")
+  } else {
+    o <- forderv(x, cols, sort=TRUE, retGrp=FALSE)
+  }
+  if (!physical) {
+    if (is.null(attr(x,"index",exact=TRUE))) setattr(x, "index", integer())
+    setattr(attr(x,"index",exact=TRUE), paste("__",paste(cols,collapse="__"),sep=""), o)
+    return(invisible(x))
+  }
+  setattr(x,"index",NULL)   # TO DO: reorder existing indexes likely faster than rebuilding again. Allow optionally. Simpler for now to clear.
+  if (length(o)) {
     if (verbose) {
-        tt = system.time(o <- forderv(x, cols, sort=TRUE, retGrp=FALSE))  # system.time does a gc, so we don't want this always on, until refcnt is on by default in R
-        cat("forder took", tt["user.self"]+tt["sys.self"], "sec\n")
+      tt = system.time(.Call(Creorder,x,o))
+      cat("reorder took", tt["user.self"]+tt["sys.self"], "sec\n")
     } else {
-        o <- forderv(x, cols, sort=TRUE, retGrp=FALSE)
-    }
-    if (!physical) {
-        if (is.null(attr(x,"index",exact=TRUE))) setattr(x, "index", integer())
-        setattr(attr(x,"index",exact=TRUE), paste("__",paste(cols,collapse="__"),sep=""), o)
-        return(invisible(x))
+      .Call(Creorder,x,o)
     }
-    setattr(x,"index",NULL)   # TO DO: reorder existing indexes likely faster than rebuilding again. Allow optionally. Simpler for now to clear.
-    if (length(o)) {
-        if (verbose) {
-            tt = system.time(.Call(Creorder,x,o))
-            cat("reorder took", tt["user.self"]+tt["sys.self"], "sec\n")
-        } else {
-            .Call(Creorder,x,o)
-        }
-    } else {
-        if (verbose) cat("x is already ordered by these columns, no need to call reorder\n")
-    } # else empty integer() from forderv means x is already ordered by those cols, nothing to do.
-    setattr(x,"sorted",cols)
-    invisible(x)
+  } else {
+    if (verbose) cat("x is already ordered by these columns, no need to call reorder\n")
+  } # else empty integer() from forderv means x is already ordered by those cols, nothing to do.
+  setattr(x,"sorted",cols)
+  invisible(x)
 }
 
 key <- function(x) attr(x,"sorted",exact=TRUE)
 key2 <- function(x) {
-    warning("key2() will be deprecated in the next relase. Please use indices() instead.", call.=FALSE)
-    ans = names(attributes(attr(x,"index",exact=TRUE)))
-    if (is.null(ans)) return(ans) # otherwise character() gets returned by next line
-    gsub("^__","",ans)
+  warning("key2() will be deprecated in the next relase. Please use indices() instead.", call.=FALSE)
+  ans = names(attributes(attr(x,"index",exact=TRUE)))
+  if (is.null(ans)) return(ans) # otherwise character() gets returned by next line
+  gsub("^__","",ans)
 }
 indices <- function(x, vectors = FALSE) {
-    ans = names(attributes(attr(x,"index",exact=TRUE)))
-    if (is.null(ans)) return(ans) # otherwise character() gets returned by next line
-    ans <- gsub("^__","",ans)
-    if (isTRUE(vectors))
-        ans <- strsplit(ans, "__", fixed = TRUE)
-    ans
+  ans = names(attributes(attr(x,"index",exact=TRUE)))
+  if (is.null(ans)) return(ans) # otherwise character() gets returned by next line
+  ans <- gsub("^__","",ans)
+  if (isTRUE(vectors))
+    ans <- strsplit(ans, "__", fixed = TRUE)
+  ans
 }
 
 get2key <- function(x, col) attr(attr(x,"index",exact=TRUE),paste("__",col,sep=""),exact=TRUE)   # work in progress, not yet exported
 
 "key<-" <- function(x,value) {
-    warning("The key(x)<-value form of setkey can copy the whole table. This is due to <- in R itself. Please change to setkeyv(x,value) or setkey(x,...) which do not copy and are faster. See help('setkey'). You can safely ignore this warning if it is inconvenient to change right now. Setting options(warn=2) turns this warning into an error, so you can then use traceback() to find and change your key<- calls.")
-    setkeyv(x,value)
-    # The returned value here from key<- is then copied by R before assigning to x, it seems. That's
-    # why we can't do anything about it without a change in R itself. If we return NULL (or invisible()) from this key<-
-    # method, the table gets set to NULL. So, although we call setkeyv(x,cols) here, and that doesn't copy, the
-    # returned value (x) then gets copied by R.
-    # So, solution is that caller has to call setkey or setkeyv directly themselves, to avoid <- dispatch and its copy.
+  warning("The key(x)<-value form of setkey can copy the whole table. This is due to <- in R itself. Please change to setkeyv(x,value) or setkey(x,...) which do not copy and are faster. See help('setkey'). You can safely ignore this warning if it is inconvenient to change right now. Setting options(warn=2) turns this warning into an error, so you can then use traceback() to find and change your key<- calls.")
+  setkeyv(x,value)
+  # The returned value here from key<- is then copied by R before assigning to x, it seems. That's
+  # why we can't do anything about it without a change in R itself. If we return NULL (or invisible()) from this key<-
+  # method, the table gets set to NULL. So, although we call setkeyv(x,cols) here, and that doesn't copy, the
+  # returned value (x) then gets copied by R.
+  # So, solution is that caller has to call setkey or setkeyv directly themselves, to avoid <- dispatch and its copy.
 }
 
 haskey <- function(x) !is.null(key(x))
@@ -144,178 +144,178 @@ setreordervec <- function(x, order) .Call(Creorder, x, order)
 # consistency; e.g., consistent twiddling of numeric/integer64, NA at the beginning of integer, locale ordering of character vectors.
 
 is.sorted <- function(x, by=seq_along(x)) {
-    if (is.list(x)) {
-        warning("Use 'if (length(o<-forderv(DT,by))) ...' for efficiency in one step, so you have o as well if not sorted.")
-        # could pass through a flag for forderv to return early on first FALSE. But we don't need that internally
-        # since internally we always then need ordering, an it's better in one step. Don't want inefficiency to creep in.
-        # This is only here for user/debugging use to check/test valid keys; e.g. data.table:::is.sorted(DT,by)
-        0 == length(forderv(x,by,retGrp=FALSE,sort=TRUE))
-    } else {
-        if (!missing(by)) stop("x is vector but 'by' is supplied")
-        .Call(Cfsorted, x)
-    }
-    # Cfsorted could be named CfIsSorted, but since "sorted" is an adjective not verb, it's clear; e.g., Cfsort would sort it ("sort" is verb).
-    # Return value of TRUE/FALSE is relied on in [.data.table quite a bit on vectors. Simple. Stick with that (rather than -1/0/+1)
-    # Important to call forder.c::fsorted here, for consistent character ordering and numeric/integer64 twiddling.
+  if (is.list(x)) {
+    warning("Use 'if (length(o<-forderv(DT,by))) ...' for efficiency in one step, so you have o as well if not sorted.")
+    # could pass through a flag for forderv to return early on first FALSE. But we don't need that internally
+    # since internally we always then need ordering, an it's better in one step. Don't want inefficiency to creep in.
+    # This is only here for user/debugging use to check/test valid keys; e.g. data.table:::is.sorted(DT,by)
+    0 == length(forderv(x,by,retGrp=FALSE,sort=TRUE))
+  } else {
+    if (!missing(by)) stop("x is vector but 'by' is supplied")
+    .Call(Cfsorted, x)
+  }
+  # Cfsorted could be named CfIsSorted, but since "sorted" is an adjective not verb, it's clear; e.g., Cfsort would sort it ("sort" is verb).
+  # Return value of TRUE/FALSE is relied on in [.data.table quite a bit on vectors. Simple. Stick with that (rather than -1/0/+1)
+  # Important to call forder.c::fsorted here, for consistent character ordering and numeric/integer64 twiddling.
 }
 
 forderv <- function(x, by=seq_along(x), retGrp=FALSE, sort=TRUE, order=1L, na.last=FALSE)
 {
-    if (!(sort || retGrp)) stop("At least one of retGrp or sort must be TRUE")
-    na.last = as.logical(na.last)
-    if (!length(na.last)) stop('length(na.last) = 0')
-    if (length(na.last) != 1L) {
-        warning("length(na.last) > 1, only the first element will be used")
-        na.last = na.last[1L]
-    }
-    # TO DO: export and document forder
-    if (is.atomic(x)) {
-        if (!missing(by) && !is.null(by)) stop("x is a single vector, non-NULL 'by' doesn't make sense")
-        by = NULL
-        if ( !missing(order) && (length(order) != 1L || !(order %in% c(1L, -1L))) )
-            stop("x is a single vector, length(order) must be =1 and it's value should be 1 (ascending) or -1 (descending).")
-    } else {
-        if (!length(x)) return(integer(0)) # to be consistent with base::order. this'll make sure forderv(NULL) will result in error
-                                           # (as base does) but forderv(data.table(NULL)) and forderv(list()) will return integer(0))
-        if (is.character(by)) by=chmatch(by, names(x))
-        by = as.integer(by)
-        if ( (length(order) != 1L && length(order) != length(by)) || any(!order %in% c(1L, -1L)) )
-            stop("x is a list, length(order) must be either =1 or =length(by) and each value should be 1 or -1 for each column in 'by', corresponding to ascending or descending order, respectively. If length(order) == 1, it will be recycled to length(by).")
-        if (length(order) == 1L) order = rep(order, length(by))
-    }
-    order = as.integer(order)
-    .Call(Cforder, x, by, retGrp, sort, order, na.last)  # returns integer() if already sorted, regardless of sort=TRUE|FALSE
+  if (!(sort || retGrp)) stop("At least one of retGrp or sort must be TRUE")
+  na.last = as.logical(na.last)
+  if (!length(na.last)) stop('length(na.last) = 0')
+  if (length(na.last) != 1L) {
+    warning("length(na.last) > 1, only the first element will be used")
+    na.last = na.last[1L]
+  }
+  # TO DO: export and document forder
+  if (is.atomic(x)) {
+    if (!missing(by) && !is.null(by)) stop("x is a single vector, non-NULL 'by' doesn't make sense")
+    by = NULL
+    if ( !missing(order) && (length(order) != 1L || !(order %in% c(1L, -1L))) )
+      stop("x is a single vector, length(order) must be =1 and it's value should be 1 (ascending) or -1 (descending).")
+  } else {
+    if (!length(x)) return(integer(0)) # to be consistent with base::order. this'll make sure forderv(NULL) will result in error
+                       # (as base does) but forderv(data.table(NULL)) and forderv(list()) will return integer(0))
+    if (is.character(by)) by=chmatch(by, names(x))
+    by = as.integer(by)
+    if ( (length(order) != 1L && length(order) != length(by)) || any(!order %in% c(1L, -1L)) )
+      stop("x is a list, length(order) must be either =1 or =length(by) and each value should be 1 or -1 for each column in 'by', corresponding to ascending or descending order, respectively. If length(order) == 1, it will be recycled to length(by).")
+    if (length(order) == 1L) order = rep(order, length(by))
+  }
+  order = as.integer(order)
+  .Call(Cforder, x, by, retGrp, sort, order, na.last)  # returns integer() if already sorted, regardless of sort=TRUE|FALSE
 }
 
 forder <- function(x, ..., na.last=TRUE, decreasing=FALSE)
 {
-    if (!is.data.table(x)) stop("x must be a data.table.")
-    if (ncol(x) == 0) stop("Attempting to order a 0-column data.table.")
-    if (is.na(decreasing) || !is.logical(decreasing)) stop("'decreasing' must be logical TRUE or FALSE")
-    cols = substitute(list(...))[-1]
-    if (identical(as.character(cols),"NULL") || !length(cols)) return(NULL) # to provide the same output as base::order
-    ans = x
-    order = rep(1L, length(cols))
-    if (length(cols)) {
-        ans = vector("list", length(cols))
-        cols = as.list(cols)
-        xcols = names(x)
-        for (i in seq_along(cols)) {
-            v=cols[[i]]
-            if (i == 1L && is.call(v) && length(v) == 2L && v[[1L]] == "list") return(1L) # to be consistent with base, see comment below under while loop
-            while (is.call(v) && length(v) == 2L && v[[1L]] != "list") {
-                # take care of "--x", "{-x}", "(---+x)" etc., cases and also "list(y)". 'list(y)' is ambiguous though. In base, with(DT, order(x, list(y))) will error
-                # that 'arguments are not of same lengths'. But with(DT, order(list(x), list(y))) will return 1L, which is very strange. On top of that, with(DT,
-                # order(x, as.list(10:1)) would return 'unimplemented type list'. It's all very inconsistent. But we HAVE to be consistent with base HERE.
-                if (!as.character(v[[1L]]) %chin% c("+", "-")) break   # FIX for bug #5583
-                if (v[[1L]] == "-") order[i] = -order[i]
-                v = v[[-1L]]
-            }
-            if (is.name(v)) {
-                ix <- chmatch(as.character(v), xcols, nomatch=0L)
-                if (ix != 0L) ans <- point(ans, i, x, ix) # see 'point' in data.table.R and C-version pointWrapper in assign.c - avoid copies
-                else {
-                    v = as.call(list(as.name("list"), v))
-                    ans <- point(ans, i, eval(v, x, parent.frame()), 1L)
-                }
-            } else {
-                if (!is.object(eval(v, x, parent.frame()))) {
-                    v   = as.call(list(as.name("list"), v))
-                    ans = point(ans, i, eval(v, x, parent.frame()), 1L) # eval has to make a copy here (not due to list(.), but due to ex: "4-5*y"), unavoidable.
-                } else ans = point(ans, i, list(unlist(eval(v, x, parent.frame()))), 1L)
-            } # else stop("Column arguments to order by in 'forder' should be of type name/symbol (ex: quote(x)) or call (ex: quote(-x), quote(x+5*y))")
+  if (!is.data.table(x)) stop("x must be a data.table.")
+  if (ncol(x) == 0) stop("Attempting to order a 0-column data.table.")
+  if (is.na(decreasing) || !is.logical(decreasing)) stop("'decreasing' must be logical TRUE or FALSE")
+  cols = substitute(list(...))[-1]
+  if (identical(as.character(cols),"NULL") || !length(cols)) return(NULL) # to provide the same output as base::order
+  ans = x
+  order = rep(1L, length(cols))
+  if (length(cols)) {
+    ans = vector("list", length(cols))
+    cols = as.list(cols)
+    xcols = names(x)
+    for (i in seq_along(cols)) {
+      v=cols[[i]]
+      if (i == 1L && is.call(v) && length(v) == 2L && v[[1L]] == "list") return(1L) # to be consistent with base, see comment below under while loop
+      while (is.call(v) && length(v) == 2L && v[[1L]] != "list") {
+        # take care of "--x", "{-x}", "(---+x)" etc., cases and also "list(y)". 'list(y)' is ambiguous though. In base, with(DT, order(x, list(y))) will error
+        # that 'arguments are not of same lengths'. But with(DT, order(list(x), list(y))) will return 1L, which is very strange. On top of that, with(DT,
+        # order(x, as.list(10:1)) would return 'unimplemented type list'. It's all very inconsistent. But we HAVE to be consistent with base HERE.
+        if (!as.character(v[[1L]]) %chin% c("+", "-")) break   # FIX for bug #5583
+        if (v[[1L]] == "-") order[i] = -order[i]
+        v = v[[-1L]]
+      }
+      if (is.name(v)) {
+        ix <- chmatch(as.character(v), xcols, nomatch=0L)
+        if (ix != 0L) ans <- point(ans, i, x, ix) # see 'point' in data.table.R and C-version pointWrapper in assign.c - avoid copies
+        else {
+          v = as.call(list(as.name("list"), v))
+          ans <- point(ans, i, eval(v, x, parent.frame()), 1L)
         }
+      } else {
+        if (!is.object(eval(v, x, parent.frame()))) {
+          v   = as.call(list(as.name("list"), v))
+          ans = point(ans, i, eval(v, x, parent.frame()), 1L) # eval has to make a copy here (not due to list(.), but due to ex: "4-5*y"), unavoidable.
+        } else ans = point(ans, i, list(unlist(eval(v, x, parent.frame()))), 1L)
+      } # else stop("Column arguments to order by in 'forder' should be of type name/symbol (ex: quote(x)) or call (ex: quote(-x), quote(x+5*y))")
     }
-    cols = seq_along(ans)
-    for (i in cols) {
-        if (!typeof(ans[[i]]) %chin% c("integer","logical","character","double"))
-            stop("Column '",i,"' is type '",typeof(ans[[i]]),"' which is not supported for ordering currently.")
-    }
-    o = forderv(ans, cols, sort=TRUE, retGrp=FALSE, order= if (decreasing) -order else order, na.last)
-    if (!length(o)) o = seq_along(ans[[1L]]) else o
-    o
+  }
+  cols = seq_along(ans)
+  for (i in cols) {
+    if (!typeof(ans[[i]]) %chin% c("integer","logical","character","double"))
+      stop("Column '",i,"' is type '",typeof(ans[[i]]),"' which is not supported for ordering currently.")
+  }
+  o = forderv(ans, cols, sort=TRUE, retGrp=FALSE, order= if (decreasing) -order else order, na.last)
+  if (!length(o)) o = seq_along(ans[[1L]]) else o
+  o
 }
 
 fsort <- function(x, decreasing = FALSE, na.last = FALSE, internal=FALSE, verbose=FALSE, ...)
 {
-    if (typeof(x)=="double" && !decreasing && !na.last) {
-      if (internal) stop("Internal code should not be being called on type double")
-      return(.Call(Cfsort, x, verbose))
-    } else {
-      # fsort is now exported for testing. Trying to head off complaints "it's slow on integer"
-      # The only places internally we use fsort internally (3 calls, all on integer) have had internal=TRUE added for now.
-      # TODO: implement integer and character in Cfsort and remove this branch and warning
-      if (!internal) warning("Input is not a vector of type double. New parallel sort has only been done for double vectors so far. Invoking relatively inefficient sort using order first.")
-      o = forderv(x, order=!decreasing, na.last=na.last)
-      return( if (length(o)) x[o] else x )   # TO DO: document this shortcut for already-sorted
-    }
+  if (typeof(x)=="double" && !decreasing && !na.last) {
+    if (internal) stop("Internal code should not be being called on type double")
+    return(.Call(Cfsort, x, verbose))
+  } else {
+    # fsort is now exported for testing. Trying to head off complaints "it's slow on integer"
+    # The only places internally we use fsort internally (3 calls, all on integer) have had internal=TRUE added for now.
+    # TODO: implement integer and character in Cfsort and remove this branch and warning
+    if (!internal) warning("Input is not a vector of type double. New parallel sort has only been done for double vectors so far. Invoking relatively inefficient sort using order first.")
+    o = forderv(x, order=!decreasing, na.last=na.last)
+    return( if (length(o)) x[o] else x )   # TO DO: document this shortcut for already-sorted
+  }
 }
 
 setorder <- function(x, ..., na.last=FALSE)
 # na.last=FALSE here, to be consistent with data.table's default
 # as opposed to DT[order(.)] where na.last=TRUE, to be consistent with base
 {
-    if (!is.data.frame(x)) stop("x must be a data.frame or data.table.")
-    cols = substitute(list(...))[-1]
-    if (identical(as.character(cols),"NULL")) return(x)
-    if (length(cols)) {
-        cols=as.list(cols)
-        order=rep(1L, length(cols))
-        for (i in seq_along(cols)) {
-            v=as.list(cols[[i]])
-            if (length(v) > 1 && v[[1L]] == "+") v=v[[-1L]]
-            else if (length(v) > 1 && v[[1L]] == "-") {
-                v=v[[-1L]]
-                order[i] = -1L
-            }
-            cols[[i]]=as.character(v)
-        }
-        cols=unlist(cols, use.names=FALSE)
-    } else {
-        cols=colnames(x)
-        order=rep(1L, length(cols))
+  if (!is.data.frame(x)) stop("x must be a data.frame or data.table.")
+  cols = substitute(list(...))[-1]
+  if (identical(as.character(cols),"NULL")) return(x)
+  if (length(cols)) {
+    cols=as.list(cols)
+    order=rep(1L, length(cols))
+    for (i in seq_along(cols)) {
+      v=as.list(cols[[i]])
+      if (length(v) > 1 && v[[1L]] == "+") v=v[[-1L]]
+      else if (length(v) > 1 && v[[1L]] == "-") {
+        v=v[[-1L]]
+        order[i] = -1L
+      }
+      cols[[i]]=as.character(v)
     }
-    setorderv(x, cols, order, na.last)
+    cols=unlist(cols, use.names=FALSE)
+  } else {
+    cols=colnames(x)
+    order=rep(1L, length(cols))
+  }
+  setorderv(x, cols, order, na.last)
 }
 
 setorderv <- function(x, cols, order=1L, na.last=FALSE)
 {
-    if (is.null(cols)) return(x)
-    if (!is.data.frame(x)) stop("x must be a data.frame or data.table")
-    na.last = as.logical(na.last)
-    if (is.na(na.last) || !length(na.last)) stop('na.last must be logical TRUE/FALSE')
-    if (!is.character(cols)) stop("cols is not a character vector. Please see further information in ?setorder.")
-    if (!length(cols)) {
-        warning("cols is a character vector of zero length. Use NULL instead, or wrap with suppressWarnings() to avoid this warning.")
-        return(x)
-    }
-    if (!all(nzchar(cols))) stop("cols contains some blanks.")     # TODO: probably I'm checking more than necessary here.. there are checks in 'forderv' as well
-    if (!length(cols)) {
-        cols = colnames(x)   # All columns in the data.table, usually a few when used in this form
-    } else {
-        # remove backticks from cols
-        cols <- gsub("`", "", cols)
-        miss = !(cols %in% colnames(x))
-        if (any(miss)) stop("some columns are not in the data.table: " %+% cols[miss])
-    }
-    if (".xi" %in% colnames(x)) stop("x contains a column called '.xi'. Conflicts with internal use by data.table.")
-    for (i in cols) {
-        .xi = x[[i]]  # [[ is copy on write, otherwise checking type would be copying each column
-        if (!typeof(.xi) %chin% c("integer","logical","character","double")) stop("Column '",i,"' is type '",typeof(.xi),"' which is not supported for ordering currently.")
-    }
-    if (!is.character(cols) || length(cols)<1) stop("'cols' should be character at this point in setkey.")
+  if (is.null(cols)) return(x)
+  if (!is.data.frame(x)) stop("x must be a data.frame or data.table")
+  na.last = as.logical(na.last)
+  if (is.na(na.last) || !length(na.last)) stop('na.last must be logical TRUE/FALSE')
+  if (!is.character(cols)) stop("cols is not a character vector. Please see further information in ?setorder.")
+  if (!length(cols)) {
+    warning("cols is a character vector of zero length. Use NULL instead, or wrap with suppressWarnings() to avoid this warning.")
+    return(x)
+  }
+  if (!all(nzchar(cols))) stop("cols contains some blanks.")     # TODO: probably I'm checking more than necessary here.. there are checks in 'forderv' as well
+  if (!length(cols)) {
+    cols = colnames(x)   # All columns in the data.table, usually a few when used in this form
+  } else {
+    # remove backticks from cols
+    cols <- gsub("`", "", cols)
+    miss = !(cols %in% colnames(x))
+    if (any(miss)) stop("some columns are not in the data.table: " %+% cols[miss])
+  }
+  if (".xi" %in% colnames(x)) stop("x contains a column called '.xi'. Conflicts with internal use by data.table.")
+  for (i in cols) {
+    .xi = x[[i]]  # [[ is copy on write, otherwise checking type would be copying each column
+    if (!typeof(.xi) %chin% c("integer","logical","character","double")) stop("Column '",i,"' is type '",typeof(.xi),"' which is not supported for ordering currently.")
+  }
+  if (!is.character(cols) || length(cols)<1) stop("'cols' should be character at this point in setkey.")
 
-    o = forderv(x, cols, sort=TRUE, retGrp=FALSE, order=order, na.last=na.last)
-    if (length(o)) {
-        .Call(Creorder, x, o)
-        if (is.data.frame(x) & !is.data.table(x)) {
-            .Call(Creorder, rn <- rownames(x), o)
-            setattr(x, 'row.names', rn)
-        }
-        setattr(x, 'sorted', NULL) # if 'forderv' is not 0-length, it means order has changed. So, set key to NULL, else retain key.
-        setattr(x, 'index', NULL)  # remove secondary keys too. These could be reordered and retained, but simpler and faster to remove
+  o = forderv(x, cols, sort=TRUE, retGrp=FALSE, order=order, na.last=na.last)
+  if (length(o)) {
+    .Call(Creorder, x, o)
+    if (is.data.frame(x) & !is.data.table(x)) {
+      .Call(Creorder, rn <- rownames(x), o)
+      setattr(x, 'row.names', rn)
     }
-    invisible(x)
+    setattr(x, 'sorted', NULL) # if 'forderv' is not 0-length, it means order has changed. So, set key to NULL, else retain key.
+    setattr(x, 'index', NULL)  # remove secondary keys too. These could be reordered and retained, but simpler and faster to remove
+  }
+  invisible(x)
 }
 
 binary <- function(x) .Call(Cbinary, x)
@@ -324,8 +324,8 @@ setNumericRounding <- function(x) {.Call(CsetNumericRounding, as.integer(x)); in
 getNumericRounding <- function() .Call(CgetNumericRounding)
 
 SJ <- function(...) {
-    JDT = as.data.table(list(...))
-    setkey(JDT)
+  JDT = as.data.table(list(...))
+  setkey(JDT)
 }
 # S for Sorted, usually used in i to sort the i table
 
@@ -333,52 +333,52 @@ SJ <- function(...) {
 
 CJ <- function(..., sorted = TRUE, unique = FALSE)
 {
-    # Pass in a list of unique values, e.g. ids and dates
-    # Cross Join will then produce a join table with the combination of all values (cross product).
-    # The last vector is varied the quickest in the table, so dates should be last for roll for example
-    l = list(...)
-    if (unique) l = lapply(l, unique)
+  # Pass in a list of unique values, e.g. ids and dates
+  # Cross Join will then produce a join table with the combination of all values (cross product).
+  # The last vector is varied the quickest in the table, so dates should be last for roll for example
+  l = list(...)
+  if (unique) l = lapply(l, unique)
 
-    dups = FALSE # fix for #1513
-    if (length(l)==1L && sorted && length(o <- forderv(l[[1L]])))
-        l[[1L]] = l[[1L]][o]
-    else if (length(l) > 1L) {
-        # using rep.int instead of rep speeds things up considerably (but attributes are dropped).
-        attribs = lapply(l, attributes)  # remember attributes for resetting after rep.int
-        n = vapply(l, length, 0L)
-        nrow = prod(n)
-        x = c(rev(take(cumprod(rev(n)))), 1L)
-        for (i in seq_along(x)) {
-            y = l[[i]]
-            # fix for #1513
-            if (sorted) {
-                if (length(o <- forderv(y, retGrp=TRUE))) y = y[o]
-                if (!dups) dups = attr(o, 'maxgrpn') > 1L
-            }
-            if (i == 1L)
-                l[[i]] = rep.int(y, times = rep.int(x[i], n[i]))   # i.e. rep(y, each=x[i])
-            else if (i == length(n))
-                l[[i]] = rep.int(y, times = nrow/(x[i]*n[i]))
-            else
-                l[[i]] = rep.int(rep.int(y, times = rep.int(x[i], n[i])), times = nrow/(x[i]*n[i]))
-            if (!is.null(attribs[[i]])){
-                attributes(l[[i]]) <- attribs[[i]] # reset all attributes that were destroyed by rep.int
-            }
-        }
+  dups = FALSE # fix for #1513
+  if (length(l)==1L && sorted && length(o <- forderv(l[[1L]])))
+    l[[1L]] = l[[1L]][o]
+  else if (length(l) > 1L) {
+    # using rep.int instead of rep speeds things up considerably (but attributes are dropped).
+    attribs = lapply(l, attributes)  # remember attributes for resetting after rep.int
+    n = vapply(l, length, 0L)
+    nrow = prod(n)
+    x = c(rev(take(cumprod(rev(n)))), 1L)
+    for (i in seq_along(x)) {
+      y = l[[i]]
+      # fix for #1513
+      if (sorted) {
+        if (length(o <- forderv(y, retGrp=TRUE))) y = y[o]
+        if (!dups) dups = attr(o, 'maxgrpn') > 1L
+      }
+      if (i == 1L)
+        l[[i]] = rep.int(y, times = rep.int(x[i], n[i]))   # i.e. rep(y, each=x[i])
+      else if (i == length(n))
+        l[[i]] = rep.int(y, times = nrow/(x[i]*n[i]))
+      else
+        l[[i]] = rep.int(rep.int(y, times = rep.int(x[i], n[i])), times = nrow/(x[i]*n[i]))
+      if (!is.null(attribs[[i]])){
+        attributes(l[[i]]) <- attribs[[i]] # reset all attributes that were destroyed by rep.int
+      }
     }
-    setattr(l, "row.names", .set_row_names(length(l[[1L]])))
-    setattr(l, "class", c("data.table", "data.frame"))
+  }
+  setattr(l, "row.names", .set_row_names(length(l[[1L]])))
+  setattr(l, "class", c("data.table", "data.frame"))
 
-    if (is.null(vnames <- names(l)))
-        vnames = vector("character", length(l))
-    if (any(tt <- vnames == "")) {
-        vnames[tt] = paste("V", which(tt), sep="")
-        setattr(l, "names", vnames)
-    }
-    l <- alloc.col(l)  # a tiny bit wasteful to over-allocate a fixed join table (column slots only), doing it anyway for consistency, and it's possible a user may wish to use SJ directly outside a join and would expect consistent over-allocation.
-    if (sorted) {
-        if (!dups) setattr(l, 'sorted', names(l))
-        else setkey(l) # fix #1513
-    }
-    l
+  if (is.null(vnames <- names(l)))
+    vnames = vector("character", length(l))
+  if (any(tt <- vnames == "")) {
+    vnames[tt] = paste("V", which(tt), sep="")
+    setattr(l, "names", vnames)
+  }
+  l <- alloc.col(l)  # a tiny bit wasteful to over-allocate a fixed join table (column slots only), doing it anyway for consistency, and it's possible a user may wish to use SJ directly outside a join and would expect consistent over-allocation.
+  if (sorted) {
+    if (!dups) setattr(l, 'sorted', names(l))
+    else setkey(l) # fix #1513
+  }
+  l
 }
diff --git a/R/setops.R b/R/setops.R
index 8364632769..39c03fda7e 100644
--- a/R/setops.R
+++ b/R/setops.R
@@ -4,284 +4,284 @@
 # Iff all of 'cols' is present in 'x' return col indices
 # is.data.table(dt) check should be performed in the calling function
 validate <- function(cols, dt) {
-    argcols = deparse(substitute(cols))
-    argdt = deparse(substitute(dt))
-    origcols = cols
-    if (is.character(cols)) cols = chmatch(cols, names(dt))
-    cols = as.integer(cols)
-    isna = which(!cols %in% seq_along(dt))
-    if (length(isna))
-        stop(argcols, " value(s) [", paste(origcols[isna], collapse=", "), "] not present (or out of range) in ", argdt)
-    cols
+  argcols = deparse(substitute(cols))
+  argdt = deparse(substitute(dt))
+  origcols = cols
+  if (is.character(cols)) cols = chmatch(cols, names(dt))
+  cols = as.integer(cols)
+  isna = which(!cols %in% seq_along(dt))
+  if (length(isna))
+    stop(argcols, " value(s) [", paste(origcols[isna], collapse=", "), "] not present (or out of range) in ", argdt)
+  cols
 }
 
 # setdiff for data.tables, internal at the moment #547, used in not-join
 setdiff_ <- function(x, y, by.x=seq_along(x), by.y=seq_along(y), use.names=FALSE) {
-    if (!is.data.table(x) || !is.data.table(y)) stop("x and y must both be data.tables")
-    if (is.null(x) || !length(x)) return(x)
-    by.x = validate(by.x, x)
-    if (is.null(y) || !length(y)) return(unique(x, by=by.x))
-    by.y = validate(by.y, y)
-    if (length(by.x) != length(by.y)) stop("length(by.x) != length(by.y)")
-    # factor in x should've factor/character in y, and viceversa
-    for (a in seq_along(by.x)) {
-        lc = by.y[a]
-        rc = by.x[a]
-        icnam = names(y)[lc]
-        xcnam = names(x)[rc]
-        if ( is.character(x[[rc]]) && !(is.character(y[[lc]]) || is.factor(y[[lc]])) ) {
-            stop("When x's column ('",xcnam,"') is character, the corresponding column in y ('",icnam,"') should be factor or character, but found incompatible type '",typeof(y[[lc]]),"'.")
-        } else if ( is.factor(x[[rc]]) && !(is.character(y[[lc]]) || is.factor(y[[lc]])) ) {
-            stop("When x's column ('",xcnam,"') is factor, the corresponding column in y ('",icnam,"') should be character or factor, but found incompatible type '",typeof(y[[lc]]),"'.")
-        } else if ( (is.integer(x[[rc]]) || is.double(x[[rc]])) && (is.logical(y[[lc]]) || is.character(y[[lc]])) ) {
-            stop("When x's column ('",xcnam,"') is integer or numeric, the corresponding column in y ('",icnam,"') can not be character or logical types, but found incompatible type '",typeof(y[[lc]]),"'.")
-        }
+  if (!is.data.table(x) || !is.data.table(y)) stop("x and y must both be data.tables")
+  if (is.null(x) || !length(x)) return(x)
+  by.x = validate(by.x, x)
+  if (is.null(y) || !length(y)) return(unique(x, by=by.x))
+  by.y = validate(by.y, y)
+  if (length(by.x) != length(by.y)) stop("length(by.x) != length(by.y)")
+  # factor in x should've factor/character in y, and viceversa
+  for (a in seq_along(by.x)) {
+    lc = by.y[a]
+    rc = by.x[a]
+    icnam = names(y)[lc]
+    xcnam = names(x)[rc]
+    if ( is.character(x[[rc]]) && !(is.character(y[[lc]]) || is.factor(y[[lc]])) ) {
+      stop("When x's column ('",xcnam,"') is character, the corresponding column in y ('",icnam,"') should be factor or character, but found incompatible type '",typeof(y[[lc]]),"'.")
+    } else if ( is.factor(x[[rc]]) && !(is.character(y[[lc]]) || is.factor(y[[lc]])) ) {
+      stop("When x's column ('",xcnam,"') is factor, the corresponding column in y ('",icnam,"') should be character or factor, but found incompatible type '",typeof(y[[lc]]),"'.")
+    } else if ( (is.integer(x[[rc]]) || is.double(x[[rc]])) && (is.logical(y[[lc]]) || is.character(y[[lc]])) ) {
+      stop("When x's column ('",xcnam,"') is integer or numeric, the corresponding column in y ('",icnam,"') can not be character or logical types, but found incompatible type '",typeof(y[[lc]]),"'.")
     }
-    ux = unique(shallow(x, by.x))
-    uy = unique(shallow(y, by.y))
-    ix = duplicated(rbind(uy, ux, use.names=use.names, fill=FALSE))[-seq_len(nrow(uy))]
-    .Call(CsubsetDT, ux, which_(ix, FALSE), seq_along(ux)) # more memory efficient version of which(!ix)
+  }
+  ux = unique(shallow(x, by.x))
+  uy = unique(shallow(y, by.y))
+  ix = duplicated(rbind(uy, ux, use.names=use.names, fill=FALSE))[-seq_len(nrow(uy))]
+  .Call(CsubsetDT, ux, which_(ix, FALSE), seq_along(ux)) # more memory efficient version of which(!ix)
 }
 
 # set operators ----
 
 funique <- function(x) {
-    stopifnot(is.data.table(x))
-    dup = duplicated(x)
-    if (any(dup)) .Call(CsubsetDT, x, which_(dup, FALSE), seq_along(x)) else x
+  stopifnot(is.data.table(x))
+  dup = duplicated(x)
+  if (any(dup)) .Call(CsubsetDT, x, which_(dup, FALSE), seq_along(x)) else x
 }
 
 fintersect <- function(x, y, all=FALSE) {
-    if (!is.logical(all) || length(all) != 1L) stop("argument 'all' should be logical of length one")
-    if (!is.data.table(x) || !is.data.table(y)) stop("x and y must be both data.tables")
-    if (!identical(sort(names(x)), sort(names(y)))) stop("x and y must have same column names")
-    if (!identical(names(x), names(y))) stop("x and y must have same column order")
-    bad.type = setNames(c("raw","complex","list") %chin% c(vapply(x, typeof, FUN.VALUE = ""), vapply(y, typeof, FUN.VALUE = "")), c("raw","complex","list"))
-    if (any(bad.type)) stop(sprintf("x and y must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
-    if (!identical(lapply(x, class), lapply(y, class))) stop("x and y must have same column classes")
-    if (".seqn" %in% names(x)) stop("None of the datasets to intersect should contain a column named '.seqn'")
-    if (!nrow(x) || !nrow(y)) return(x[0L])
-    if (all) {
-        x = shallow(x)[, ".seqn" := rowidv(x)]
-        y = shallow(y)[, ".seqn" := rowidv(y)]
-        jn.on = c(".seqn",setdiff(names(x),".seqn"))
-        x[y, .SD, .SDcols=setdiff(names(x),".seqn"), nomatch=0L, on=jn.on]
-    } else {
-        x[funique(y), nomatch=0L, on=names(x), mult="first"]
-    }
+  if (!is.logical(all) || length(all) != 1L) stop("argument 'all' should be logical of length one")
+  if (!is.data.table(x) || !is.data.table(y)) stop("x and y must be both data.tables")
+  if (!identical(sort(names(x)), sort(names(y)))) stop("x and y must have same column names")
+  if (!identical(names(x), names(y))) stop("x and y must have same column order")
+  bad.type = setNames(c("raw","complex","list") %chin% c(vapply(x, typeof, FUN.VALUE = ""), vapply(y, typeof, FUN.VALUE = "")), c("raw","complex","list"))
+  if (any(bad.type)) stop(sprintf("x and y must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
+  if (!identical(lapply(x, class), lapply(y, class))) stop("x and y must have same column classes")
+  if (".seqn" %in% names(x)) stop("None of the datasets to intersect should contain a column named '.seqn'")
+  if (!nrow(x) || !nrow(y)) return(x[0L])
+  if (all) {
+    x = shallow(x)[, ".seqn" := rowidv(x)]
+    y = shallow(y)[, ".seqn" := rowidv(y)]
+    jn.on = c(".seqn",setdiff(names(x),".seqn"))
+    x[y, .SD, .SDcols=setdiff(names(x),".seqn"), nomatch=0L, on=jn.on]
+  } else {
+    x[funique(y), nomatch=0L, on=names(x), mult="first"]
+  }
 }
 
 fsetdiff <- function(x, y, all=FALSE) {
-    if (!is.logical(all) || length(all) != 1L) stop("argument 'all' should be logical of length one")
-    if (!is.data.table(x) || !is.data.table(y)) stop("x and y must be both data.tables")
-    if (!identical(sort(names(x)), sort(names(y)))) stop("x and y must have same column names")
-    if (!identical(names(x), names(y))) stop("x and y must have same column order")
-    bad.type = setNames(c("raw","complex","list") %chin% c(vapply(x, typeof, FUN.VALUE = ""), vapply(y, typeof, FUN.VALUE = "")), c("raw","complex","list"))
-    if (any(bad.type)) stop(sprintf("x and y must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
-    if (!identical(lapply(x, class), lapply(y, class))) stop("x and y must have same column classes")
-    if (".seqn" %in% names(x)) stop("None of the datasets to setdiff should contain a column named '.seqn'")
-    if (!nrow(x)) return(x)
-    if (!nrow(y)) return(if (!all) funique(x) else x)
-    if (all) {
-        x = shallow(x)[, ".seqn" := rowidv(x)]
-        y = shallow(y)[, ".seqn" := rowidv(y)]
-        jn.on = c(".seqn",setdiff(names(x),".seqn"))
-        x[!y, .SD, .SDcols=setdiff(names(x),".seqn"), on=jn.on]
-    } else {
-        funique(x[!y, on=names(x)])
-    }
+  if (!is.logical(all) || length(all) != 1L) stop("argument 'all' should be logical of length one")
+  if (!is.data.table(x) || !is.data.table(y)) stop("x and y must be both data.tables")
+  if (!identical(sort(names(x)), sort(names(y)))) stop("x and y must have same column names")
+  if (!identical(names(x), names(y))) stop("x and y must have same column order")
+  bad.type = setNames(c("raw","complex","list") %chin% c(vapply(x, typeof, FUN.VALUE = ""), vapply(y, typeof, FUN.VALUE = "")), c("raw","complex","list"))
+  if (any(bad.type)) stop(sprintf("x and y must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
+  if (!identical(lapply(x, class), lapply(y, class))) stop("x and y must have same column classes")
+  if (".seqn" %in% names(x)) stop("None of the datasets to setdiff should contain a column named '.seqn'")
+  if (!nrow(x)) return(x)
+  if (!nrow(y)) return(if (!all) funique(x) else x)
+  if (all) {
+    x = shallow(x)[, ".seqn" := rowidv(x)]
+    y = shallow(y)[, ".seqn" := rowidv(y)]
+    jn.on = c(".seqn",setdiff(names(x),".seqn"))
+    x[!y, .SD, .SDcols=setdiff(names(x),".seqn"), on=jn.on]
+  } else {
+    funique(x[!y, on=names(x)])
+  }
 }
 
 funion <- function(x, y, all=FALSE) {
-    if (!is.logical(all) || length(all) != 1L) stop("argument 'all' should be logical of length one")
-    if (!is.data.table(x) || !is.data.table(y)) stop("x and y must be both data.tables")
-    if (!identical(sort(names(x)), sort(names(y)))) stop("x and y must have same column names")
-    if (!identical(names(x), names(y))) stop("x and y must have same column order")
-    bad.type = setNames(c("raw","complex", if(!all) "list") %chin% c(vapply(x, typeof, FUN.VALUE = ""), vapply(y, typeof, FUN.VALUE = "")), c("raw","complex", if(!all) "list"))
-    if (any(bad.type)) stop(sprintf("x and y must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
-    if (!identical(lapply(x, class), lapply(y, class))) stop("x and y must have same column classes")
-    ans = rbindlist(list(x, y))
-    if (!all) ans = funique(ans)
-    ans
+  if (!is.logical(all) || length(all) != 1L) stop("argument 'all' should be logical of length one")
+  if (!is.data.table(x) || !is.data.table(y)) stop("x and y must be both data.tables")
+  if (!identical(sort(names(x)), sort(names(y)))) stop("x and y must have same column names")
+  if (!identical(names(x), names(y))) stop("x and y must have same column order")
+  bad.type = setNames(c("raw","complex", if(!all) "list") %chin% c(vapply(x, typeof, FUN.VALUE = ""), vapply(y, typeof, FUN.VALUE = "")), c("raw","complex", if(!all) "list"))
+  if (any(bad.type)) stop(sprintf("x and y must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
+  if (!identical(lapply(x, class), lapply(y, class))) stop("x and y must have same column classes")
+  ans = rbindlist(list(x, y))
+  if (!all) ans = funique(ans)
+  ans
 }
 
 fsetequal <- function(x, y) {
-    if (!is.data.table(x) || !is.data.table(y)) stop("x and y must be both data.tables")
-    if (!identical(sort(names(x)), sort(names(y)))) stop("x and y must have same column names")
-    if (!identical(names(x), names(y))) stop("x and y must have same column order")
-    bad.type = setNames(c("raw","complex","list") %chin% c(vapply(x, typeof, FUN.VALUE = ""), vapply(y, typeof, FUN.VALUE = "")), c("raw","complex","list"))
-    if (any(bad.type)) stop(sprintf("x and y must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
-    if (!identical(lapply(x, class), lapply(y, class))) stop("x and y must have same column classes")
-    isTRUE(all.equal.data.table(x, y, check.attributes = FALSE, ignore.row.order = TRUE))
+  if (!is.data.table(x) || !is.data.table(y)) stop("x and y must be both data.tables")
+  if (!identical(sort(names(x)), sort(names(y)))) stop("x and y must have same column names")
+  if (!identical(names(x), names(y))) stop("x and y must have same column order")
+  bad.type = setNames(c("raw","complex","list") %chin% c(vapply(x, typeof, FUN.VALUE = ""), vapply(y, typeof, FUN.VALUE = "")), c("raw","complex","list"))
+  if (any(bad.type)) stop(sprintf("x and y must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
+  if (!identical(lapply(x, class), lapply(y, class))) stop("x and y must have same column classes")
+  isTRUE(all.equal.data.table(x, y, check.attributes = FALSE, ignore.row.order = TRUE))
 }
 
 # all.equal ----
 
 all.equal.data.table <- function(target, current, trim.levels=TRUE, check.attributes=TRUE, ignore.col.order=FALSE, ignore.row.order=FALSE, tolerance=sqrt(.Machine$double.eps), ...) {
-    stopifnot(is.logical(trim.levels), is.logical(check.attributes), is.logical(ignore.col.order), is.logical(ignore.row.order), is.numeric(tolerance))
-    if (!is.data.table(target) || !is.data.table(current)) stop("'target' and 'current' must be both data.tables")
+  stopifnot(is.logical(trim.levels), is.logical(check.attributes), is.logical(ignore.col.order), is.logical(ignore.row.order), is.numeric(tolerance))
+  if (!is.data.table(target) || !is.data.table(current)) stop("'target' and 'current' must be both data.tables")
 
-    msg = character(0)
-    # init checks that detect high level all.equal
-    if (nrow(current) != nrow(target)) msg = "Different number of rows"
-    if (ncol(current) != ncol(target)) msg = c(msg, "Different number of columns")
-    diff.colnames = !identical(sort(names(target)), sort(names(current)))
-    diff.colorder = !identical(names(target), names(current))
-    if (check.attributes && diff.colnames) msg = c(msg, "Different column names")
-    if (!diff.colnames && !ignore.col.order && diff.colorder) msg = c(msg, "Different column order")
+  msg = character(0)
+  # init checks that detect high level all.equal
+  if (nrow(current) != nrow(target)) msg = "Different number of rows"
+  if (ncol(current) != ncol(target)) msg = c(msg, "Different number of columns")
+  diff.colnames = !identical(sort(names(target)), sort(names(current)))
+  diff.colorder = !identical(names(target), names(current))
+  if (check.attributes && diff.colnames) msg = c(msg, "Different column names")
+  if (!diff.colnames && !ignore.col.order && diff.colorder) msg = c(msg, "Different column order")
 
-    if (length(msg)) return(msg) # skip check.attributes and further heavy processing
+  if (length(msg)) return(msg) # skip check.attributes and further heavy processing
 
-    # ignore.col.order
-    if (ignore.col.order && diff.colorder) current = setcolorder(shallow(current), names(target))
+  # ignore.col.order
+  if (ignore.col.order && diff.colorder) current = setcolorder(shallow(current), names(target))
 
-    # Always check modes equal, like base::all.equal
-    targetModes = vapply_1c(target, mode)
-    currentModes = vapply_1c(current,  mode)
-    if (any( d<-(targetModes!=currentModes) )) {
-        w = head(which(d),3)
-        return(paste0("Datasets have different column modes. First 3: ",paste(
-         paste(names(targetModes)[w],"(",paste(targetModes[w],currentModes[w],sep="!="),")",sep="")
-                      ,collapse=" ")))
+  # Always check modes equal, like base::all.equal
+  targetModes = vapply_1c(target, mode)
+  currentModes = vapply_1c(current,  mode)
+  if (any( d<-(targetModes!=currentModes) )) {
+    w = head(which(d),3)
+    return(paste0("Datasets have different column modes. First 3: ",paste(
+     paste(names(targetModes)[w],"(",paste(targetModes[w],currentModes[w],sep="!="),")",sep="")
+            ,collapse=" ")))
+  }
+
+  if (check.attributes) {
+    squashClass = function(x) if (is.object(x)) paste(class(x),collapse=";") else mode(x)
+    # else mode() is so that integer==numeric, like base all.equal does.
+    targetTypes = vapply_1c(target, squashClass)
+    currentTypes = vapply_1c(current, squashClass)
+    if (length(targetTypes) != length(currentTypes))
+      stop("Internal error: ncol(current)==ncol(target) was checked above")
+    if (any( d<-(targetTypes != currentTypes))) {
+      w = head(which(d),3)
+      return(paste0("Datasets have different column classes. First 3: ",paste(
+     paste(names(targetTypes)[w],"(",paste(targetTypes[w],currentTypes[w],sep="!="),")",sep="")
+            ,collapse=" ")))
     }
+  }
 
-    if (check.attributes) {
-        squashClass = function(x) if (is.object(x)) paste(class(x),collapse=";") else mode(x)
-        # else mode() is so that integer==numeric, like base all.equal does.
-        targetTypes = vapply_1c(target, squashClass)
-        currentTypes = vapply_1c(current, squashClass)
-        if (length(targetTypes) != length(currentTypes))
-            stop("Internal error: ncol(current)==ncol(target) was checked above")
-        if (any( d<-(targetTypes != currentTypes))) {
-            w = head(which(d),3)
-            return(paste0("Datasets have different column classes. First 3: ",paste(
-         paste(names(targetTypes)[w],"(",paste(targetTypes[w],currentTypes[w],sep="!="),")",sep="")
-                      ,collapse=" ")))
-        }
+  if (check.attributes) {
+    # check key
+    k1 = key(target)
+    k2 = key(current)
+    if (!identical(k1, k2)) {
+      return(sprintf("Datasets has different keys. 'target'%s. 'current'%s.",
+               if(length(k1)) paste0(": ", paste(k1, collapse=", ")) else " has no key",
+               if(length(k2)) paste0(": ", paste(k2, collapse=", ")) else " has no key"))
+    }
+    # check index
+    i1 = indices(target)
+    i2 = indices(current)
+    if (!identical(i1, i2)) {
+      return(sprintf("Datasets has different indexes. 'target'%s. 'current'%s.",
+               if(length(i1)) paste0(": ", paste(i1, collapse=", ")) else " has no index",
+               if(length(i2)) paste0(": ", paste(i2, collapse=", ")) else " has no index"))
     }
 
-    if (check.attributes) {
-        # check key
-        k1 = key(target)
-        k2 = key(current)
-        if (!identical(k1, k2)) {
-            return(sprintf("Datasets has different keys. 'target'%s. 'current'%s.",
-                           if(length(k1)) paste0(": ", paste(k1, collapse=", ")) else " has no key",
-                           if(length(k2)) paste0(": ", paste(k2, collapse=", ")) else " has no key"))
-        }
-        # check index
-        i1 = indices(target)
-        i2 = indices(current)
-        if (!identical(i1, i2)) {
-            return(sprintf("Datasets has different indexes. 'target'%s. 'current'%s.",
-                           if(length(i1)) paste0(": ", paste(i1, collapse=", ")) else " has no index",
-                           if(length(i2)) paste0(": ", paste(i2, collapse=", ")) else " has no index"))
-        }
+    # Trim any extra row.names attributes that came from some inheritence
+    # Trim ".internal.selfref" as long as there is no `all.equal.externalptr` method
+    exclude.attrs = function(x, attrs = c("row.names",".internal.selfref")) x[!names(x) %in% attrs]
+    a1 = exclude.attrs(attributes(target))
+    a2 = exclude.attrs(attributes(current))
+    if (length(a1) != length(a2)) return(sprintf("Datasets has different number of (non-excluded) attributes: target %s, current %s", length(a1), length(a2)))
+    if (!identical(nm1 <- sort(names(a1)), nm2 <- sort(names(a2)))) return(sprintf("Datasets has attributes with different names: %s", paste(setdiff(union(names(a1), names(a2)), intersect(names(a1), names(a2))), collapse=", ")))
+    attrs.r = all.equal(a1[nm1], a2[nm2], ..., check.attributes = check.attributes)
+    if (is.character(attrs.r)) return(paste("Attributes: <", attrs.r, ">")) # skip further heavy processing
+  }
 
-        # Trim any extra row.names attributes that came from some inheritence
-        # Trim ".internal.selfref" as long as there is no `all.equal.externalptr` method
-        exclude.attrs = function(x, attrs = c("row.names",".internal.selfref")) x[!names(x) %in% attrs]
-        a1 = exclude.attrs(attributes(target))
-        a2 = exclude.attrs(attributes(current))
-        if (length(a1) != length(a2)) return(sprintf("Datasets has different number of (non-excluded) attributes: target %s, current %s", length(a1), length(a2)))
-        if (!identical(nm1 <- sort(names(a1)), nm2 <- sort(names(a2)))) return(sprintf("Datasets has attributes with different names: %s", paste(setdiff(union(names(a1), names(a2)), intersect(names(a1), names(a2))), collapse=", ")))
-        attrs.r = all.equal(a1[nm1], a2[nm2], ..., check.attributes = check.attributes)
-        if (is.character(attrs.r)) return(paste("Attributes: <", attrs.r, ">")) # skip further heavy processing
+  if (ignore.row.order) {
+    if (".seqn" %in% names(target))
+      stop("None of the datasets to compare should contain a column named '.seqn'")
+    bad.type = setNames(c("raw","complex","list") %chin% c(vapply(current, typeof, FUN.VALUE = ""), vapply(target, typeof, FUN.VALUE = "")), c("raw","complex","list"))
+    if (any(bad.type))
+      stop(sprintf("Datasets to compare with 'ignore.row.order' must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
+    if (between(tolerance, 0, sqrt(.Machine$double.eps), incbounds=FALSE)) {
+      warning(sprintf("Argument 'tolerance' was forced to lowest accepted value `sqrt(.Machine$double.eps)` from provided %s", format(tolerance, scientific=FALSE)))
+      tolerance = sqrt(.Machine$double.eps)
     }
-
-    if (ignore.row.order) {
-        if (".seqn" %in% names(target))
-            stop("None of the datasets to compare should contain a column named '.seqn'")
-        bad.type = setNames(c("raw","complex","list") %chin% c(vapply(current, typeof, FUN.VALUE = ""), vapply(target, typeof, FUN.VALUE = "")), c("raw","complex","list"))
-        if (any(bad.type))
-            stop(sprintf("Datasets to compare with 'ignore.row.order' must not have unsupported column types: %s", paste(names(bad.type)[bad.type], collapse=", ")))
-        if (between(tolerance, 0, sqrt(.Machine$double.eps), incbounds=FALSE)) {
-            warning(sprintf("Argument 'tolerance' was forced to lowest accepted value `sqrt(.Machine$double.eps)` from provided %s", format(tolerance, scientific=FALSE)))
-            tolerance = sqrt(.Machine$double.eps)
-        }
-        target_dup = as.logical(anyDuplicated(target))
-        current_dup = as.logical(anyDuplicated(current))
-        tolerance.msg = if (identical(tolerance, 0)) ", be aware you are using `tolerance=0` which may result into visually equal data" else ""
-        if (target_dup || current_dup) {
-            # handling 'tolerance' for duplicate rows - those `msg` will be returned only when equality with tolerance will fail
-            if (any(vapply_1c(target,typeof)=="double") && !identical(tolerance, 0)) {
-                if (target_dup && !current_dup) msg = c(msg, "Dataset 'target' has duplicate rows while 'current' doesn't")
-                else if (!target_dup && current_dup) msg = c(msg, "Dataset 'current' has duplicate rows while 'target' doesn't")
-                else { # both
-                    if (!identical(tolerance, sqrt(.Machine$double.eps))) # non-default will raise error
-                        stop("Duplicate rows in datasets, numeric columns and ignore.row.order cannot be used with non 0 tolerance argument")
-                    msg = c(msg, "Both datasets have duplicate rows, they also have numeric columns, together with ignore.row.order this force 'tolerance' argument to 0")
-                    tolerance = 0
-                }
-            } else { # no numeric columns or tolerance==0L
-                if (target_dup && !current_dup)
-                    return(sprintf("Dataset 'target' has duplicate rows while 'current' doesn't%s", tolerance.msg))
-                if (!target_dup && current_dup)
-                    return(sprintf("Dataset 'current' has duplicate rows while 'target' doesn't%s", tolerance.msg))
-            }
-        }
-        jn.on = if (target_dup && current_dup) {
-            target = shallow(target)[, ".seqn" := rowidv(target)]
-            current = shallow(current)[, ".seqn" := rowidv(current)]
-            c(".seqn", setdiff(names(target), ".seqn"))
-        } else names(target)
-        # handling 'tolerance' for factor cols - those `msg` will be returned only when equality with tolerance will fail
-        if (any(vapply_1b(target,is.factor)) && !identical(tolerance, 0)) {
-            if (!identical(tolerance, sqrt(.Machine$double.eps))) # non-default will raise error
-                stop("Factor columns and ignore.row.order cannot be used with non 0 tolerance argument")
-            msg = c(msg, "Using factor columns together together with ignore.row.order, this force 'tolerance' argument to 0")
-            tolerance = 0
+    target_dup = as.logical(anyDuplicated(target))
+    current_dup = as.logical(anyDuplicated(current))
+    tolerance.msg = if (identical(tolerance, 0)) ", be aware you are using `tolerance=0` which may result into visually equal data" else ""
+    if (target_dup || current_dup) {
+      # handling 'tolerance' for duplicate rows - those `msg` will be returned only when equality with tolerance will fail
+      if (any(vapply_1c(target,typeof)=="double") && !identical(tolerance, 0)) {
+        if (target_dup && !current_dup) msg = c(msg, "Dataset 'target' has duplicate rows while 'current' doesn't")
+        else if (!target_dup && current_dup) msg = c(msg, "Dataset 'current' has duplicate rows while 'target' doesn't")
+        else { # both
+          if (!identical(tolerance, sqrt(.Machine$double.eps))) # non-default will raise error
+            stop("Duplicate rows in datasets, numeric columns and ignore.row.order cannot be used with non 0 tolerance argument")
+          msg = c(msg, "Both datasets have duplicate rows, they also have numeric columns, together with ignore.row.order this force 'tolerance' argument to 0")
+          tolerance = 0
         }
-        # roll join to support 'tolerance' argument, conditional to retain support for factor when tolerance=0
-        ans = if (identical(tolerance, 0)) target[current, nomatch=NA, which=TRUE, on=jn.on] else {
-            ans1 = target[current, roll=tolerance, rollends=TRUE, which=TRUE, on=jn.on]
-            ans2 = target[current, roll=-tolerance, rollends=TRUE, which=TRUE, on=jn.on]
-            pmin(ans1, ans2, na.rm=TRUE)
-        }
-        if (any_na(as_list(ans))) {
-            msg = c(msg, sprintf("Dataset 'current' has rows not present in 'target'%s%s", if (target_dup || current_dup) " or present in different quantity" else "", tolerance.msg))
-            return(msg)
-        }
-        ans = if (identical(tolerance, 0)) current[target, nomatch=NA, which=TRUE, on=jn.on] else {
-            ans1 = current[target, roll=tolerance, rollends=TRUE, which=TRUE, on=jn.on]
-            ans2 = current[target, roll=-tolerance, rollends=TRUE, which=TRUE, on=jn.on]
-            pmin(ans1, ans2, na.rm=TRUE)
-        }
-        if (any_na(as_list(ans))) {
-            msg = c(msg, sprintf("Dataset 'target' has rows not present in 'current'%s%s", if (target_dup || current_dup) " or present in different quantity" else "", tolerance.msg))
-            return(msg)
-        }
-    } else {
-        for (i in seq_along(target)) {
-            # trim.levels moved here
-            x = target[[i]]
-            y = current[[i]]
-            if (xor(is.factor(x),is.factor(y)))
-                return("Internal error: factor type mismatch should have been caught earlier")
-            cols.r = TRUE
-            if (is.factor(x)) {
-                if (!identical(levels(x),levels(y))) {
-                    if (trim.levels) {
-                      # do this regardless of check.attributes (that's more about classes, checked above)
-                      x = factor(x)
-                      y = factor(y)
-                      if (!identical(levels(x),levels(y)))
-                        cols.r = "Levels not identical even after refactoring since trim.levels is TRUE"
-                    } else {
-                        cols.r = "Levels not identical. No attempt to refactor because trim.levels is FALSE"
-                    }
-                } else {
-                    cols.r = all.equal(x, y, check.attributes=check.attributes)
-                    # the check.attributes here refers to everything other than the levels, which are always
-                    # dealt with according to trim.levels
-                }
-            } else {
-                cols.r = all.equal(unclass(x), unclass(y), tolerance=tolerance, ..., check.attributes=check.attributes)
-                # classes were explicitly checked earlier above, so ignore classes here.
-            }
-            if (!isTRUE(cols.r)) return(paste0("Column '", names(target)[i], "': ", paste(cols.r,collapse=" ")))
+      } else { # no numeric columns or tolerance==0L
+        if (target_dup && !current_dup)
+          return(sprintf("Dataset 'target' has duplicate rows while 'current' doesn't%s", tolerance.msg))
+        if (!target_dup && current_dup)
+          return(sprintf("Dataset 'current' has duplicate rows while 'target' doesn't%s", tolerance.msg))
+      }
+    }
+    jn.on = if (target_dup && current_dup) {
+      target = shallow(target)[, ".seqn" := rowidv(target)]
+      current = shallow(current)[, ".seqn" := rowidv(current)]
+      c(".seqn", setdiff(names(target), ".seqn"))
+    } else names(target)
+    # handling 'tolerance' for factor cols - those `msg` will be returned only when equality with tolerance will fail
+    if (any(vapply_1b(target,is.factor)) && !identical(tolerance, 0)) {
+      if (!identical(tolerance, sqrt(.Machine$double.eps))) # non-default will raise error
+        stop("Factor columns and ignore.row.order cannot be used with non 0 tolerance argument")
+      msg = c(msg, "Using factor columns together together with ignore.row.order, this force 'tolerance' argument to 0")
+      tolerance = 0
+    }
+    # roll join to support 'tolerance' argument, conditional to retain support for factor when tolerance=0
+    ans = if (identical(tolerance, 0)) target[current, nomatch=NA, which=TRUE, on=jn.on] else {
+      ans1 = target[current, roll=tolerance, rollends=TRUE, which=TRUE, on=jn.on]
+      ans2 = target[current, roll=-tolerance, rollends=TRUE, which=TRUE, on=jn.on]
+      pmin(ans1, ans2, na.rm=TRUE)
+    }
+    if (any_na(as_list(ans))) {
+      msg = c(msg, sprintf("Dataset 'current' has rows not present in 'target'%s%s", if (target_dup || current_dup) " or present in different quantity" else "", tolerance.msg))
+      return(msg)
+    }
+    ans = if (identical(tolerance, 0)) current[target, nomatch=NA, which=TRUE, on=jn.on] else {
+      ans1 = current[target, roll=tolerance, rollends=TRUE, which=TRUE, on=jn.on]
+      ans2 = current[target, roll=-tolerance, rollends=TRUE, which=TRUE, on=jn.on]
+      pmin(ans1, ans2, na.rm=TRUE)
+    }
+    if (any_na(as_list(ans))) {
+      msg = c(msg, sprintf("Dataset 'target' has rows not present in 'current'%s%s", if (target_dup || current_dup) " or present in different quantity" else "", tolerance.msg))
+      return(msg)
+    }
+  } else {
+    for (i in seq_along(target)) {
+      # trim.levels moved here
+      x = target[[i]]
+      y = current[[i]]
+      if (xor(is.factor(x),is.factor(y)))
+        return("Internal error: factor type mismatch should have been caught earlier")
+      cols.r = TRUE
+      if (is.factor(x)) {
+        if (!identical(levels(x),levels(y))) {
+          if (trim.levels) {
+            # do this regardless of check.attributes (that's more about classes, checked above)
+            x = factor(x)
+            y = factor(y)
+            if (!identical(levels(x),levels(y)))
+            cols.r = "Levels not identical even after refactoring since trim.levels is TRUE"
+          } else {
+            cols.r = "Levels not identical. No attempt to refactor because trim.levels is FALSE"
+          }
+        } else {
+          cols.r = all.equal(x, y, check.attributes=check.attributes)
+          # the check.attributes here refers to everything other than the levels, which are always
+          # dealt with according to trim.levels
         }
+      } else {
+        cols.r = all.equal(unclass(x), unclass(y), tolerance=tolerance, ..., check.attributes=check.attributes)
+        # classes were explicitly checked earlier above, so ignore classes here.
+      }
+      if (!isTRUE(cols.r)) return(paste0("Column '", names(target)[i], "': ", paste(cols.r,collapse=" ")))
     }
-    TRUE
+  }
+  TRUE
 }
 
diff --git a/R/shift.R b/R/shift.R
index 9ee9198df7..62d85bce24 100644
--- a/R/shift.R
+++ b/R/shift.R
@@ -1,14 +1,14 @@
 shift <- function(x, n=1L, fill=NA, type=c("lag", "lead"), give.names=FALSE) {
-    type = match.arg(type)
-    ans = .Call(Cshift, x, as.integer(n), fill, type)
-    if (give.names) {
-        if (is.null(names(x))) {
-            xsub = substitute(x)
-            if (is.atomic(x) && is.name(xsub)) nx = deparse(xsub, 500L)
-            else nx = paste("V", if (is.atomic(x)) 1L else seq_along(x), sep="")
-        }
-        else nx = names(x)
-        setattr(ans, 'names', do.call("paste", c(CJ(nx, type, n, sorted=FALSE), sep="_")))
+  type = match.arg(type)
+  ans = .Call(Cshift, x, as.integer(n), fill, type)
+  if (give.names) {
+    if (is.null(names(x))) {
+      xsub = substitute(x)
+      if (is.atomic(x) && is.name(xsub)) nx = deparse(xsub, 500L)
+      else nx = paste("V", if (is.atomic(x)) 1L else seq_along(x), sep="")
     }
-    ans
+    else nx = names(x)
+    setattr(ans, 'names', do.call("paste", c(CJ(nx, type, n, sorted=FALSE), sep="_")))
+  }
+  ans
 }
diff --git a/R/tables.R b/R/tables.R
index f10271101e..37da8dca71 100644
--- a/R/tables.R
+++ b/R/tables.R
@@ -4,50 +4,50 @@ MB = NCOL = NROW = NULL
 tables <- function(mb=TRUE, order.col="NAME", width=80,
                    env=parent.frame(), silent=FALSE, index=FALSE)
 {
-    # Prints name, size and colnames of all data.tables in the calling environment by default
-    all_obj = objects(envir=env, all.names=TRUE)
-    is_DT = which(vapply_1b(all_obj, function(x) is.data.table(get(x, envir=env))))
-    if (!length(is_DT)) {
-        if (!silent) cat("No objects of class data.table exist in .GlobalEnv\n")
-        return(invisible(data.table(NULL)))
-    }
-    DT_names = all_obj[is_DT]
-    info = rbindlist(lapply(DT_names, function(dt_n){
-        DT = get(dt_n, envir=env)   # doesn't copy
-        info_i =
-            data.table(NAME = dt_n,
-                       NROW = nrow(DT),
-                       NCOL = ncol(DT))
-        if (mb)
-          # mb is an option because object.size() appears to be slow.
-          # **TO DO: revisit**
-          set(info_i, , "MB",
-                #1048576 = 1024^2
-                round(as.numeric(object.size(DT))/1048576))
-        set(info_i, , "COLS", list(list(names(DT))))
-        set(info_i, , "KEY", list(list(key(DT))))
-        if (index) set(info_i, , "INDICES", list(list(indices(DT))))
-        info_i
-    }))
-    info[ , NROW := format(sprintf("%4s", prettyNum(NROW, big.mark=",")), justify="right")]   # %4s is for minimum width
-    info[ , NCOL := format(sprintf("%4s", prettyNum(NCOL, big.mark=",")), justify="right")]
-    if (mb) {
-        total = sum(info$MB)
-        info[ , MB := format(sprintf("%2s", prettyNum(MB, big.mark=",")), justify="right")]
-    }
-    if (!order.col %in% names(info)) stop("order.col='",order.col,"' not a column name of info")
-    info = info[base::order(info[[order.col]])]  # base::order to maintain locale ordering of table names
-    m = as.matrix(info)
-    colnames(m)[2] = sprintf(paste("%",nchar(m[1,"NROW"]), "s", sep=""), "NROW")
-    colnames(m)[3] = sprintf(paste("%",nchar(m[1,"NCOL"]), "s", sep=""), "NCOL")
-    if (mb) colnames(m)[4] = sprintf(paste("%", nchar(m[1,"MB"]), "s", sep=""), "MB")
-    m[ , "COLS"] = substring(m[,"COLS"], 1L, width)
-    m[ , "KEY"] = substring(m[,"KEY"], 1L, width)
-    if (!silent) {
-        print(m, quote=FALSE, right=FALSE)
-        if (mb) cat("Total: ", prettyNum(as.character(total), big.mark=","), "MB\n", sep="")
-    }
-    invisible(info)
+  # Prints name, size and colnames of all data.tables in the calling environment by default
+  all_obj = objects(envir=env, all.names=TRUE)
+  is_DT = which(vapply_1b(all_obj, function(x) is.data.table(get(x, envir=env))))
+  if (!length(is_DT)) {
+    if (!silent) cat("No objects of class data.table exist in .GlobalEnv\n")
+    return(invisible(data.table(NULL)))
+  }
+  DT_names = all_obj[is_DT]
+  info = rbindlist(lapply(DT_names, function(dt_n){
+    DT = get(dt_n, envir=env)   # doesn't copy
+    info_i =
+      data.table(NAME = dt_n,
+                 NROW = nrow(DT),
+                 NCOL = ncol(DT))
+    if (mb)
+      # mb is an option because object.size() appears to be slow.
+      # **TO DO: revisit**
+      set(info_i, , "MB",
+        #1048576 = 1024^2
+        round(as.numeric(object.size(DT))/1048576))
+    set(info_i, , "COLS", list(list(names(DT))))
+    set(info_i, , "KEY", list(list(key(DT))))
+    if (index) set(info_i, , "INDICES", list(list(indices(DT))))
+    info_i
+  }))
+  info[ , NROW := format(sprintf("%4s", prettyNum(NROW, big.mark=",")), justify="right")]   # %4s is for minimum width
+  info[ , NCOL := format(sprintf("%4s", prettyNum(NCOL, big.mark=",")), justify="right")]
+  if (mb) {
+    total = sum(info$MB)
+    info[ , MB := format(sprintf("%2s", prettyNum(MB, big.mark=",")), justify="right")]
+  }
+  if (!order.col %in% names(info)) stop("order.col='",order.col,"' not a column name of info")
+  info = info[base::order(info[[order.col]])]  # base::order to maintain locale ordering of table names
+  m = as.matrix(info)
+  colnames(m)[2] = sprintf(paste("%",nchar(m[1,"NROW"]), "s", sep=""), "NROW")
+  colnames(m)[3] = sprintf(paste("%",nchar(m[1,"NCOL"]), "s", sep=""), "NCOL")
+  if (mb) colnames(m)[4] = sprintf(paste("%", nchar(m[1,"MB"]), "s", sep=""), "MB")
+  m[ , "COLS"] = substring(m[,"COLS"], 1L, width)
+  m[ , "KEY"] = substring(m[,"KEY"], 1L, width)
+  if (!silent) {
+    print(m, quote=FALSE, right=FALSE)
+    if (mb) cat("Total: ", prettyNum(as.character(total), big.mark=","), "MB\n", sep="")
+  }
+  invisible(info)
 }
 
 
diff --git a/R/test.data.table.R b/R/test.data.table.R
index cb2d0fdb72..2fb4f55afb 100644
--- a/R/test.data.table.R
+++ b/R/test.data.table.R
@@ -1,36 +1,36 @@
 
 test.data.table <- function(verbose=FALSE, pkg="pkg", silent=FALSE) {
-    if (exists("test.data.table",.GlobalEnv,inherits=FALSE)) {
-        # package developer
-        if ("package:data.table" %in% search()) stop("data.table package is loaded. Unload or start a fresh R session.")
-        d = if (pkg %in% dir()) paste0(getwd(),"/",pkg) else Sys.getenv("CC_DIR")
-        d = paste0(d, "/inst/tests")
+  if (exists("test.data.table",.GlobalEnv,inherits=FALSE)) {
+    # package developer
+    if ("package:data.table" %in% search()) stop("data.table package is loaded. Unload or start a fresh R session.")
+    d = if (pkg %in% dir()) paste0(getwd(),"/",pkg) else Sys.getenv("CC_DIR")
+    d = paste0(d, "/inst/tests")
+  } else {
+    # R CMD check and user running test.data.table()
+    d = paste0(getNamespaceInfo("data.table","path"),"/tests")
+  }
+  # for (fn in dir(d,"*.[rR]$",full=TRUE)) {  # testthat runs those
+  oldenc = options(encoding="UTF-8")[[1L]]  # just for tests 708-712 on Windows
+  # TO DO: reinstate solution for C locale of CRAN's Mac (R-Forge's Mac is ok)
+  # oldlocale = Sys.getlocale("LC_CTYPE")
+  # Sys.setlocale("LC_CTYPE", "")   # just for CRAN's Mac to get it off C locale (post to r-devel on 16 Jul 2012)
+  olddir = setwd(d)
+  on.exit(setwd(olddir))
+  envirs <- list()
+  for (fn in file.path(d, 'tests.Rraw')) {    # not testthat
+    cat("Running",fn,"\n")
+    oldverbose = options(datatable.verbose=verbose)
+    envirs[[fn]] = new.env(parent=.GlobalEnv)
+    if(isTRUE(silent)){
+      try(sys.source(fn,envir=envirs[[fn]]), silent=silent)
     } else {
-        # R CMD check and user running test.data.table()
-        d = paste0(getNamespaceInfo("data.table","path"),"/tests")
+      sys.source(fn,envir=envirs[[fn]])
     }
-    # for (fn in dir(d,"*.[rR]$",full=TRUE)) {  # testthat runs those
-    oldenc = options(encoding="UTF-8")[[1L]]  # just for tests 708-712 on Windows
-    # TO DO: reinstate solution for C locale of CRAN's Mac (R-Forge's Mac is ok)
-    # oldlocale = Sys.getlocale("LC_CTYPE")
-    # Sys.setlocale("LC_CTYPE", "")   # just for CRAN's Mac to get it off C locale (post to r-devel on 16 Jul 2012)
-    olddir = setwd(d)
-    on.exit(setwd(olddir))
-    envirs <- list()
-    for (fn in file.path(d, 'tests.Rraw')) {    # not testthat
-        cat("Running",fn,"\n")
-        oldverbose = options(datatable.verbose=verbose)
-        envirs[[fn]] = new.env(parent=.GlobalEnv)
-        if(isTRUE(silent)){
-            try(sys.source(fn,envir=envirs[[fn]]), silent=silent)
-        } else {
-            sys.source(fn,envir=envirs[[fn]])
-        }
-        options(oldverbose)
-    }
-    options(encoding=oldenc)
-    # Sys.setlocale("LC_CTYPE", oldlocale)
-    invisible(sum(sapply(envirs, `[[`, "nfail"))==0)
+    options(oldverbose)
+  }
+  options(encoding=oldenc)
+  # Sys.setlocale("LC_CTYPE", oldlocale)
+  invisible(sum(sapply(envirs, `[[`, "nfail"))==0)
 }
 
 # Define test() and its globals here, for use in dev
@@ -41,151 +41,151 @@ test.data.table <- function(verbose=FALSE, pkg="pkg", silent=FALSE) {
 # .devtesting = TRUE
 
 compactprint <- function(DT, topn=2) {
-    tt = vapply_1c(DT,function(x)class(x)[1L])
-    tt[tt=="integer64"] = "i64"
-    cn = paste(" [Key=",paste(key(DT),collapse=","),
-                " Types=",paste(substring(sapply(DT,typeof),1,3),collapse=","),
-                " Classes=",paste(substring(tt,1,3),collapse=","),
-                "]",sep="")
-    if (nrow(DT)) {
-      print(copy(DT)[,(cn):=""], topn=topn)
-    } else {
-      print(DT)  # "Empty data.table (0 rows) of <ncol> columns ...
-      if (ncol(DT)) cat(cn,"\n")
-    }
-    invisible()
+  tt = vapply_1c(DT,function(x)class(x)[1L])
+  tt[tt=="integer64"] = "i64"
+  cn = paste(" [Key=",paste(key(DT),collapse=","),
+             " Types=",paste(substring(sapply(DT,typeof),1,3),collapse=","),
+             " Classes=",paste(substring(tt,1,3),collapse=","),
+             "]",sep="")
+  if (nrow(DT)) {
+    print(copy(DT)[,(cn):=""], topn=topn)
+  } else {
+    print(DT)  # "Empty data.table (0 rows) of <ncol> columns ...
+    if (ncol(DT)) cat(cn,"\n")
+  }
+  invisible()
 }
 
 INT = function(...) { as.integer(c(...)) }   # utility used in tests.Rraw
 
 test <- function(num,x,y,error=NULL,warning=NULL,output=NULL) {
-    # Usage:
-    # i) tests that x equals y when both x and y are supplied, the most common usage
-    # ii) tests that x is TRUE when y isn't supplied
-    # iii) if error is supplied, y should be missing and x is tested to result in an error message matching the pattern
-    # iv) if warning is supplied, y (if supplied) is checked to equal x, and x should result in a warning message matching the pattern
-    # v) if output is supplied, x is evaluated and printed and the output is checked to match the pattern
-    # At most one of error, warning or output may be supplied, all single character strings (passed to grep)
-    # num just needs to be numeric and unique. We normally increment integers at the end, but inserts can be made using decimals e.g. 10,11,11.1,11.2,12,13,...
-    # Motivations:
-    # 1) we'd like to know all tests that fail not just stop at the first. This often helps by revealing a common feature across a set of
-    #    failing tests
-    # 2) test() tests more deeply than a diff on console output and uses a data.table appropriate definition of "equals" different
-    #    from all.equal and different to identical related to row.names and unused factor levels
-    # 3) each test has a unique id which we refer to in commit messages, emails etc.
-    nfail = get("nfail", parent.frame())   # to cater for both test.data.table() and stepping through tests in dev
-    whichfail = get("whichfail", parent.frame())
-    all.equal.result = TRUE
-    assign("ntest", get("ntest", parent.frame()) + 1, parent.frame(), inherits=TRUE)   # bump number of tests run
-    assign("lastnum", num, parent.frame(), inherits=TRUE)
+  # Usage:
+  # i) tests that x equals y when both x and y are supplied, the most common usage
+  # ii) tests that x is TRUE when y isn't supplied
+  # iii) if error is supplied, y should be missing and x is tested to result in an error message matching the pattern
+  # iv) if warning is supplied, y (if supplied) is checked to equal x, and x should result in a warning message matching the pattern
+  # v) if output is supplied, x is evaluated and printed and the output is checked to match the pattern
+  # At most one of error, warning or output may be supplied, all single character strings (passed to grep)
+  # num just needs to be numeric and unique. We normally increment integers at the end, but inserts can be made using decimals e.g. 10,11,11.1,11.2,12,13,...
+  # Motivations:
+  # 1) we'd like to know all tests that fail not just stop at the first. This often helps by revealing a common feature across a set of
+  #    failing tests
+  # 2) test() tests more deeply than a diff on console output and uses a data.table appropriate definition of "equals" different
+  #    from all.equal and different to identical related to row.names and unused factor levels
+  # 3) each test has a unique id which we refer to in commit messages, emails etc.
+  nfail = get("nfail", parent.frame())   # to cater for both test.data.table() and stepping through tests in dev
+  whichfail = get("whichfail", parent.frame())
+  all.equal.result = TRUE
+  assign("ntest", get("ntest", parent.frame()) + 1, parent.frame(), inherits=TRUE)   # bump number of tests run
+  assign("lastnum", num, parent.frame(), inherits=TRUE)
 
-    cat("\rRunning test id", num, "     ")
-    flush.console()
-    # This flush is for Windows to make sure last test number is written to file in CRAN and win-builder output where
-    # console output is captured. \r seems especially prone to not being auto flushed. The downside is that the last 13
-    # lines output are filled with the last 13 "running test num" lines rather than the last error output, but that's
-    # better than the dev-time-lost when it crashes and it actually crashed much later than the last test number visible.
+  cat("\rRunning test id", num, "     ")
+  flush.console()
+  # This flush is for Windows to make sure last test number is written to file in CRAN and win-builder output where
+  # console output is captured. \r seems especially prone to not being auto flushed. The downside is that the last 13
+  # lines output are filled with the last 13 "running test num" lines rather than the last error output, but that's
+  # better than the dev-time-lost when it crashes and it actually crashed much later than the last test number visible.
 
-    xsub = substitute(x)
-    ysub = substitute(y)
-    if (is.null(output)) err <<- try(x,TRUE)
-    else {
-        out = gsub("NULL$","",paste(capture.output(print(err<<-try(x,TRUE))),collapse=""))
-        out = gsub("\n","",gsub("\r","",out))  # ensure no \r or \n pollution on windows
-        # We use .* to shorten what we test for (so the grep below needs fixed=FALSE)
-        # but other characters should be matched literally
-        output = gsub("\\","\\\\",output,fixed=TRUE)  # e.g numbers like 9.9e+10 should match the + literally
-        output = gsub("[","\\[",output,fixed=TRUE)
-        output = gsub("]","\\]",output,fixed=TRUE)
-        output = gsub("(","\\(",output,fixed=TRUE)
-        output = gsub(")","\\)",output,fixed=TRUE)
-        output = gsub("+","\\+",output,fixed=TRUE)  # e.g numbers like 9.9e+10 should match the + literally
-        output = gsub("\n","",output,fixed=TRUE)  # e.g numbers like 9.9e+10 should match the + literally
-        if (!length(grep(output,out))) {
-            cat("Test",num,"didn't produce correct output:\n")
-            cat(">",deparse(xsub),"\n")
-            cat("Expected: '",output,"'\n",sep="")
-            cat("Observed: '",out,"'\n",sep="")
-            assign("nfail", nfail+1, parent.frame(), inherits=TRUE)
-            assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
-            return()
-        }
-    }
-    if (!is.null(error) || !is.null(warning)) {
-        type = ifelse(!is.null(error),"error","warning")
-        if (type=="error" && !missing(y)) stop("Test ",num," is invalid: when error= is provided it doesn't make sense to pass y as well")
-        patt = txt = ifelse(!is.null(error),error,warning)
-        patt = gsub("[(]","[(]",patt)
-        patt = gsub("[)]","[)]",patt)
-        patt = gsub("\\^","\\\\^", patt)  # for test 923 containing 2^31 in error message
-        observedtype = ifelse(length(grep("converted from warning",err)), "warning", "error")
-        if (! (inherits(err,"try-error") &&
-               length(grep(patt,err)) &&
-               type==observedtype)) {
-            cat("Test",num,"didn't produce correct",type,":\n")
-            cat(">",deparse(xsub),"\n")
-            cat("Expected ",type,": '",txt,"'\n",sep="")
-            if (!inherits(err,"try-error"))
-                cat("Observed: no error or warning\n")
-            else
-                cat("Observed ",observedtype,": '",gsub("^[(]converted from warning[)] ","",gsub("\n$","",gsub("^Error.* : \n  ","",as.character(err)))),"'\n",sep="")
-            assign("nfail", nfail+1, parent.frame(), inherits=TRUE)   # Not the same as nfail <<- nfail + 1, it seems (when run via R CMD check)
-            assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
-            return()
-        }
-        if (type=="warning")
-            err <- if (is.null(output)) x<-try(suppressWarnings(x),TRUE) else out<-paste(capture.output(x<-try(suppressWarnings(x),TRUE)),collapse="")
-        else return()
-    }
-    if (inherits(err,"try-error") || (!missing(y) && inherits(err<-try(y,TRUE),"try-error"))) {
-        cat("Test",num,err)
-        assign("nfail", nfail+1, parent.frame(), inherits=TRUE)
-        assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
-        return()
+  xsub = substitute(x)
+  ysub = substitute(y)
+  if (is.null(output)) err <<- try(x,TRUE)
+  else {
+    out = gsub("NULL$","",paste(capture.output(print(err<<-try(x,TRUE))),collapse=""))
+    out = gsub("\n","",gsub("\r","",out))  # ensure no \r or \n pollution on windows
+    # We use .* to shorten what we test for (so the grep below needs fixed=FALSE)
+    # but other characters should be matched literally
+    output = gsub("\\","\\\\",output,fixed=TRUE)  # e.g numbers like 9.9e+10 should match the + literally
+    output = gsub("[","\\[",output,fixed=TRUE)
+    output = gsub("]","\\]",output,fixed=TRUE)
+    output = gsub("(","\\(",output,fixed=TRUE)
+    output = gsub(")","\\)",output,fixed=TRUE)
+    output = gsub("+","\\+",output,fixed=TRUE)  # e.g numbers like 9.9e+10 should match the + literally
+    output = gsub("\n","",output,fixed=TRUE)  # e.g numbers like 9.9e+10 should match the + literally
+    if (!length(grep(output,out))) {
+      cat("Test",num,"didn't produce correct output:\n")
+      cat(">",deparse(xsub),"\n")
+      cat("Expected: '",output,"'\n",sep="")
+      cat("Observed: '",out,"'\n",sep="")
+      assign("nfail", nfail+1, parent.frame(), inherits=TRUE)
+      assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
+      return()
     }
-    if (missing(y)) {
-        if (!is.null(output)) return()
-        if (isTRUE(as.vector(x))) return()  # as.vector to drop names of a named vector such as returned by system.time
-        cat("Test",num,"expected TRUE but observed:\n")
-        cat(">",deparse(xsub),"\n")
-        if (is.data.table(x)) compactprint(x) else print(x)
-        assign("nfail", nfail+1, parent.frame(), inherits=TRUE)
-        assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
-        return()
-    } else {
-        if (identical(x,y)) return()
-        if (is.data.table(x) && is.data.table(y)) {
-            # TO DO:  test 166 doesn't pass with these :
-            # if (!selfrefok(x)) stop("x selfref not ok")
-            # if (!selfrefok(y)) stop("y selfref not ok")
-            xc=copy(x)
-            yc=copy(y)  # so we don't affect the original data which may be used in the next test
-            # drop unused levels in factors
-            if (length(x)) for (i in which(vapply_1b(x,is.factor))) {.xi=x[[i]];xc[,(i):=factor(.xi)]}
-            if (length(y)) for (i in which(vapply_1b(y,is.factor))) {.yi=y[[i]];yc[,(i):=factor(.yi)]}
-            setattr(xc,"row.names",NULL)  # for test 165+, i.e. x may have row names set from inheritance but y won't, consider these equal
-            setattr(yc,"row.names",NULL)
-            setattr(xc,"index",NULL)   # too onerous to create test RHS with the correct index as well, just check result
-            setattr(yc,"index",NULL)
-            if (identical(xc,yc) && identical(key(x),key(y))) return()  # check key on original x and y because := above might have cleared it on xc or yc
-            if (isTRUE(all.equal.result<-all.equal(xc,yc)) && identical(key(x),key(y)) &&
-                identical(vapply_1c(xc,typeof), vapply_1c(yc,typeof))) return()
-        }
-        if (is.factor(x) && is.factor(y)) {
-            x = factor(x)
-            y = factor(y)
-            if (identical(x,y)) return()
-        }
-        if (is.atomic(x) && is.atomic(y) && isTRUE(all.equal.result<-all.equal(x,y))) return()
-        # For test 617 on r-prerel-solaris-sparc on 7 Mar 2013
+  }
+  if (!is.null(error) || !is.null(warning)) {
+    type = ifelse(!is.null(error),"error","warning")
+    if (type=="error" && !missing(y)) stop("Test ",num," is invalid: when error= is provided it doesn't make sense to pass y as well")
+    patt = txt = ifelse(!is.null(error),error,warning)
+    patt = gsub("[(]","[(]",patt)
+    patt = gsub("[)]","[)]",patt)
+    patt = gsub("\\^","\\\\^", patt)  # for test 923 containing 2^31 in error message
+    observedtype = ifelse(length(grep("converted from warning",err)), "warning", "error")
+    if (! (inherits(err,"try-error") &&
+           length(grep(patt,err)) &&
+           type==observedtype)) {
+      cat("Test",num,"didn't produce correct",type,":\n")
+      cat(">",deparse(xsub),"\n")
+      cat("Expected ",type,": '",txt,"'\n",sep="")
+      if (!inherits(err,"try-error"))
+        cat("Observed: no error or warning\n")
+      else
+        cat("Observed ",observedtype,": '",gsub("^[(]converted from warning[)] ","",gsub("\n$","",gsub("^Error.* : \n  ","",as.character(err)))),"'\n",sep="")
+      assign("nfail", nfail+1, parent.frame(), inherits=TRUE)   # Not the same as nfail <<- nfail + 1, it seems (when run via R CMD check)
+      assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
+      return()
     }
-    cat("Test",num,"ran without errors but failed check that x equals y:\n")
-    cat("> x =",deparse(xsub),"\n")
-    if (is.data.table(x)) compactprint(x) else if (length(x)>6) {cat("First 6 of", length(x),":");print(head(x))} else print(x)
-    cat("> y =",deparse(ysub),"\n")
-    if (is.data.table(y)) compactprint(y) else if (length(y)>6) {cat("First 6 of", length(y),":");print(head(y))} else print(y)
-    if (!isTRUE(all.equal.result)) cat(all.equal.result,sep="\n")
+    if (type=="warning")
+      err <- if (is.null(output)) x<-try(suppressWarnings(x),TRUE) else out<-paste(capture.output(x<-try(suppressWarnings(x),TRUE)),collapse="")
+    else return()
+  }
+  if (inherits(err,"try-error") || (!missing(y) && inherits(err<-try(y,TRUE),"try-error"))) {
+    cat("Test",num,err)
+    assign("nfail", nfail+1, parent.frame(), inherits=TRUE)
+    assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
+    return()
+  }
+  if (missing(y)) {
+    if (!is.null(output)) return()
+    if (isTRUE(as.vector(x))) return()  # as.vector to drop names of a named vector such as returned by system.time
+    cat("Test",num,"expected TRUE but observed:\n")
+    cat(">",deparse(xsub),"\n")
+    if (is.data.table(x)) compactprint(x) else print(x)
     assign("nfail", nfail+1, parent.frame(), inherits=TRUE)
     assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
-    invisible()
+    return()
+  } else {
+    if (identical(x,y)) return()
+    if (is.data.table(x) && is.data.table(y)) {
+      # TO DO:  test 166 doesn't pass with these :
+      # if (!selfrefok(x)) stop("x selfref not ok")
+      # if (!selfrefok(y)) stop("y selfref not ok")
+      xc=copy(x)
+      yc=copy(y)  # so we don't affect the original data which may be used in the next test
+      # drop unused levels in factors
+      if (length(x)) for (i in which(vapply_1b(x,is.factor))) {.xi=x[[i]];xc[,(i):=factor(.xi)]}
+      if (length(y)) for (i in which(vapply_1b(y,is.factor))) {.yi=y[[i]];yc[,(i):=factor(.yi)]}
+      setattr(xc,"row.names",NULL)  # for test 165+, i.e. x may have row names set from inheritance but y won't, consider these equal
+      setattr(yc,"row.names",NULL)
+      setattr(xc,"index",NULL)   # too onerous to create test RHS with the correct index as well, just check result
+      setattr(yc,"index",NULL)
+      if (identical(xc,yc) && identical(key(x),key(y))) return()  # check key on original x and y because := above might have cleared it on xc or yc
+      if (isTRUE(all.equal.result<-all.equal(xc,yc)) && identical(key(x),key(y)) &&
+        identical(vapply_1c(xc,typeof), vapply_1c(yc,typeof))) return()
+    }
+    if (is.factor(x) && is.factor(y)) {
+      x = factor(x)
+      y = factor(y)
+      if (identical(x,y)) return()
+    }
+    if (is.atomic(x) && is.atomic(y) && isTRUE(all.equal.result<-all.equal(x,y))) return()
+    # For test 617 on r-prerel-solaris-sparc on 7 Mar 2013
+  }
+  cat("Test",num,"ran without errors but failed check that x equals y:\n")
+  cat("> x =",deparse(xsub),"\n")
+  if (is.data.table(x)) compactprint(x) else if (length(x)>6) {cat("First 6 of", length(x),":");print(head(x))} else print(x)
+  cat("> y =",deparse(ysub),"\n")
+  if (is.data.table(y)) compactprint(y) else if (length(y)>6) {cat("First 6 of", length(y),":");print(head(y))} else print(y)
+  if (!isTRUE(all.equal.result)) cat(all.equal.result,sep="\n")
+  assign("nfail", nfail+1, parent.frame(), inherits=TRUE)
+  assign("whichfail", c(whichfail, num), parent.frame(), inherits=TRUE)
+  invisible()
 }
diff --git a/R/timetaken.R b/R/timetaken.R
index 493520a55f..72db043df8 100644
--- a/R/timetaken.R
+++ b/R/timetaken.R
@@ -1,11 +1,11 @@
 timetaken <- function(started.at)
 {
    if (inherits(started.at,"POSIXct")) {
-      # started.at was Sys.time(). Slower method due to POSIXt.
-      secs <- as.double(difftime(Sys.time(), started.at, units="secs"))
+    # started.at was Sys.time(). Slower method due to POSIXt.
+    secs <- as.double(difftime(Sys.time(), started.at, units="secs"))
    } else {
-      # new faster method using started.at = proc.time()
-      secs = proc.time()[3] - started.at[3]
+    # new faster method using started.at = proc.time()
+    secs = proc.time()[3] - started.at[3]
    }
    mins <- secs %/% 60
    hrs <- mins %/% 60
@@ -13,9 +13,9 @@ timetaken <- function(started.at)
    mins = mins - hrs * 60
    hrs = hrs - 24*days
    if (secs >= 60) {
-       if (days >= 1) res = sprintf("%d days ", as.integer(days)) else res=""
-       paste(res,sprintf("%02.0f:%02.0f:%02.0f", hrs, mins, secs %% 60),sep="")
+     if (days >= 1) res = sprintf("%d days ", as.integer(days)) else res=""
+     paste(res,sprintf("%02.0f:%02.0f:%02.0f", hrs, mins, secs %% 60),sep="")
    } else {
-       sprintf(if (secs>=10) "%.1fsec" else "%.3fsec", secs)
+     sprintf(if (secs>=10) "%.1fsec" else "%.3fsec", secs)
    }
 }
diff --git a/R/transpose.R b/R/transpose.R
index d1e709ba43..2e106b904e 100644
--- a/R/transpose.R
+++ b/R/transpose.R
@@ -1,36 +1,36 @@
 transpose <- function(l, fill=NA, ignore.empty=FALSE) {
-    ans = .Call(Ctranspose, l, fill, ignore.empty)
-    if (is.data.table(l)) setDT(ans)
-    else if (is.data.frame(l)) {
-        if (is.null(names(ans)))
-            setattr(ans, "names", paste("V", seq_along(ans), sep = ""))
-        setattr(ans, "row.names", .set_row_names(length(ans[[1L]])))
-        setattr(ans, "class", "data.frame")
-    }
-    ans[]
+  ans = .Call(Ctranspose, l, fill, ignore.empty)
+  if (is.data.table(l)) setDT(ans)
+  else if (is.data.frame(l)) {
+    if (is.null(names(ans)))
+      setattr(ans, "names", paste("V", seq_along(ans), sep = ""))
+    setattr(ans, "row.names", .set_row_names(length(ans[[1L]])))
+    setattr(ans, "class", "data.frame")
+  }
+  ans[]
 }
 
 tstrsplit <- function(x, ..., fill=NA, type.convert=FALSE, keep, names=FALSE) {
-    ans = transpose(strsplit(as.character(x), ...), fill=fill, ignore.empty=FALSE)
-    if (!missing(keep)) {
-        keep = suppressWarnings(as.integer(keep))
-        chk = min(keep) >= min(1L, length(ans)) & max(keep) <= length(ans)
-        if (!isTRUE(chk)) # handles NA case too
-            stop("'keep' should contain integer values between ",
-                min(1L, length(ans)), " and ", length(ans), ".")
-        ans = ans[keep]
-    }
-    # Implementing #1094, but default FALSE
-    if(type.convert) ans = lapply(ans, type.convert, as.is = TRUE)
-    if (identical(names, FALSE)) return(ans)
-    else if (isTRUE(names)) names = paste0("V", seq_along(ans))
-    if (!is.character(names))
-        stop("'names' must be TRUE/FALSE or a character vector.")
-    if (length(names) != length(ans)) {
-        str = if (missing(keep)) "ans" else "keep"
-        stop("length(names) (= ", length(names),
-            ") is not equal to length(", str, ") (= ", length(ans), ").")
-    }
-    setattr(ans, 'names', names)
+  ans = transpose(strsplit(as.character(x), ...), fill=fill, ignore.empty=FALSE)
+  if (!missing(keep)) {
+    keep = suppressWarnings(as.integer(keep))
+    chk = min(keep) >= min(1L, length(ans)) & max(keep) <= length(ans)
+    if (!isTRUE(chk)) # handles NA case too
+      stop("'keep' should contain integer values between ",
+        min(1L, length(ans)), " and ", length(ans), ".")
+    ans = ans[keep]
+  }
+  # Implementing #1094, but default FALSE
+  if(type.convert) ans = lapply(ans, type.convert, as.is = TRUE)
+  if (identical(names, FALSE)) return(ans)
+  else if (isTRUE(names)) names = paste0("V", seq_along(ans))
+  if (!is.character(names))
+    stop("'names' must be TRUE/FALSE or a character vector.")
+  if (length(names) != length(ans)) {
+    str = if (missing(keep)) "ans" else "keep"
+    stop("length(names) (= ", length(names),
+      ") is not equal to length(", str, ") (= ", length(ans), ").")
+  }
+  setattr(ans, 'names', names)
   ans
 }
diff --git a/R/uniqlist.R b/R/uniqlist.R
index 5a3f74abd2..06831ce12a 100644
--- a/R/uniqlist.R
+++ b/R/uniqlist.R
@@ -1,24 +1,24 @@
 
 uniqlist <- function (l, order = -1L)
 {
-    # Assumes input list is ordered by each list item (or by 'order' if supplied), and that all list elements are the same length
-    # Finds the non-duplicate rows. Was called duplist but now grows vector - doesn't over-allocate result vector and
-    # is >2x times faster on numeric types
-    # TO DO: Possibly reinstate reverse argument :
-    #    FALSE works in the usual duplicated() way,  the first in a sequence of dups, will be FALSE
-    #    TRUE has the last in a sequence of dups FALSE (so you can keep the last if thats required)
-    # l = list(...)
-    if (!is.list(l))
-        stop("l not type list")
-    if (!length(l))  return(list(0L))
-    ans <- .Call(Cuniqlist, l, as.integer(order))
-    ans
+  # Assumes input list is ordered by each list item (or by 'order' if supplied), and that all list elements are the same length
+  # Finds the non-duplicate rows. Was called duplist but now grows vector - doesn't over-allocate result vector and
+  # is >2x times faster on numeric types
+  # TO DO: Possibly reinstate reverse argument :
+  #    FALSE works in the usual duplicated() way,  the first in a sequence of dups, will be FALSE
+  #    TRUE has the last in a sequence of dups FALSE (so you can keep the last if thats required)
+  # l = list(...)
+  if (!is.list(l))
+    stop("l not type list")
+  if (!length(l))  return(list(0L))
+  ans <- .Call(Cuniqlist, l, as.integer(order))
+  ans
 }
 
 # implemented for returning the lengths of groups obtained from uniqlist (for internal use only)
 uniqlengths <- function(x, len) {
-    # check for type happens in C, but still converting to integer here to be sure.
-    ans <- .Call(Cuniqlengths, as.integer(x), as.integer(len))
-    ans
+  # check for type happens in C, but still converting to integer here to be sure.
+  ans <- .Call(Cuniqlengths, as.integer(x), as.integer(len))
+  ans
 }
 
diff --git a/R/utils.R b/R/utils.R
index 6b6109de23..057a1e4e26 100644
--- a/R/utils.R
+++ b/R/utils.R
@@ -3,41 +3,41 @@
 # which.first
 which.first <- function(x)
 {
-    if (!is.logical(x)) {
-        stop("x not boolean")
-    }
-    match(TRUE, x)
+  if (!is.logical(x)) {
+    stop("x not boolean")
+  }
+  match(TRUE, x)
 }
 
 # which.last
 which.last <- function(x)
 {
-    if (!is.logical(x)) {
-        stop("x not boolean")
-    }
-    length(x) - match(TRUE, rev(x)) +1
+  if (!is.logical(x)) {
+    stop("x not boolean")
+  }
+  length(x) - match(TRUE, rev(x)) +1
 }
 
 # trim
 trim <- function(x) {
-    # Removes whitespace at the beginning and end of strings
-    # Assigning to x[] to retain the original dimensions, rownames and colnames
-    x[] = gsub(" +$", "", x)
-    x[] = gsub("^ +", "", x)
-    x
+  # Removes whitespace at the beginning and end of strings
+  # Assigning to x[] to retain the original dimensions, rownames and colnames
+  x[] = gsub(" +$", "", x)
+  x[] = gsub("^ +", "", x)
+  x
 }
 
 # take (I don't see it being used anywhere)
 take <- function(x, n=1)
 {
-    # returns the head of head, without the last n observations
-    # convenient when inlining expressions
-    # NROW, for vectors, returns the vector length, but we cater for non-table like lists also here
-    # TO DO: allow taking along any dimension e.g. all but last column, rather than all but last row.
-    if (is.list(x) && !is.data.frame(x)  && !is.data.table(x)) l = length(x)
-    else l = NROW(x)
-    if (l < n) stop("Cannot take ",n," from ",l)
-    head(x, l-n)
+  # returns the head of head, without the last n observations
+  # convenient when inlining expressions
+  # NROW, for vectors, returns the vector length, but we cater for non-table like lists also here
+  # TO DO: allow taking along any dimension e.g. all but last column, rather than all but last row.
+  if (is.list(x) && !is.data.frame(x)  && !is.data.table(x)) l = length(x)
+  else l = NROW(x)
+  if (l < n) stop("Cannot take ",n," from ",l)
+  head(x, l-n)
 }
 # TODO: Implement take as UseMethod. Specific methods for each type.
 
@@ -51,7 +51,7 @@ UseMethod("%+%")
 require_bit64 = function() {
   # called in fread and print when they see integer64 columns are present
   if (!requireNamespace("bit64",quietly=TRUE))
-    warning("Some columns are type 'integer64' but package bit64 is not installed. Those columns will print as strange looking floating point data. There is no need to reload the data. Simply install.packages('bit64') to obtain the integer64 print method and print the data again.")
+  warning("Some columns are type 'integer64' but package bit64 is not installed. Those columns will print as strange looking floating point data. There is no need to reload the data. Simply install.packages('bit64') to obtain the integer64 print method and print the data again.")
 }
 
 # vapply for return value character(1)
diff --git a/R/xts.R b/R/xts.R
index 877df2473e..d60840ebb4 100644
--- a/R/xts.R
+++ b/R/xts.R
@@ -1,17 +1,17 @@
 as.data.table.xts <- function(x, keep.rownames = TRUE, ...) {
-    stopifnot(requireNamespace("xts"), !missing(x), xts::is.xts(x))
-    r = setDT(as.data.frame(x, row.names=NULL))
-    if (!keep.rownames) return(r[])
-    if ("index" %in% names(x)) stop("Input xts object should not have 'index' column because it would result in duplicate column names. Rename 'index' column in xts or use `keep.rownames=FALSE` and add index manually as another column.")
-    r[, "index" := zoo::index(x)]
-    setcolorder(r, c("index", setdiff(names(r), "index")))[]
+  stopifnot(requireNamespace("xts"), !missing(x), xts::is.xts(x))
+  r = setDT(as.data.frame(x, row.names=NULL))
+  if (!keep.rownames) return(r[])
+  if ("index" %in% names(x)) stop("Input xts object should not have 'index' column because it would result in duplicate column names. Rename 'index' column in xts or use `keep.rownames=FALSE` and add index manually as another column.")
+  r[, "index" := zoo::index(x)]
+  setcolorder(r, c("index", setdiff(names(r), "index")))[]
 }
 
 as.xts.data.table <- function(x, ...) {
-    stopifnot(requireNamespace("xts"), !missing(x), is.data.table(x))
-    if (!any((index_class <- class(x[[1L]])) %in% c("POSIXct","Date"))) stop("data.table must have a POSIXct, Date or IDate column on first position, use `setcolorder` function.")
-    colsNumeric = vapply_1b(x, is.numeric)[-1L] # exclude first col, xts index
-    if (any(!colsNumeric)) warning(paste("Following columns are not numeric and will be omitted:", paste(names(colsNumeric)[!colsNumeric], collapse=", ")))
-    r = setDF(x[, .SD, .SDcols=names(colsNumeric)[colsNumeric]])
-    xts::as.xts(r, order.by=if ("IDate" %in% index_class) as.Date(x[[1L]]) else x[[1L]])
+  stopifnot(requireNamespace("xts"), !missing(x), is.data.table(x))
+  if (!any((index_class <- class(x[[1L]])) %in% c("POSIXct","Date"))) stop("data.table must have a POSIXct, Date or IDate column on first position, use `setcolorder` function.")
+  colsNumeric = vapply_1b(x, is.numeric)[-1L] # exclude first col, xts index
+  if (any(!colsNumeric)) warning(paste("Following columns are not numeric and will be omitted:", paste(names(colsNumeric)[!colsNumeric], collapse=", ")))
+  r = setDF(x[, .SD, .SDcols=names(colsNumeric)[colsNumeric]])
+  xts::as.xts(r, order.by=if ("IDate" %in% index_class) as.Date(x[[1L]]) else x[[1L]])
 }
