Upgrade to mksh R56b.

R56b is a bugfix-only release everyone should upgrade to:

[tg] Reference the FAQ webpage
[panpo, Riviera] Fix documentation bug wrt. Esc+Ctrl-L
[tg, Larry Hynes] Fix “0” movement in vi mode
[tg] Replace broken libcs’ offsetof macro with MirBSD’s

R56 is a bugfix release with some experimental fixes:

[tg, Seb] Do not apply alias name restrictions to hash/tilde tracking
[tg] Restore ‘.’, ‘:’ and ‘[’ in alias names (“[[” is still forbidden)
[tg] Fix accidentally defanged $PATHSEP test
[tg] On ^C (INTR and QUIT edchars), shove edit line into history
[iSKUNK, tg] Begin porting to z/OS using EBCDIC encoding, incomplete
[tg] Redo fast character classes code, adding POSIX and other helpers
[tg] bind parses backslash-escaped ‘^’ (and ‘\’) as escaped
[tg] Building with -DMKSH_ASSUME_UTF8=0 no longer causes a known failure in the testsuite
[tg] New test.sh option -U to pass a UTF-8 locale to use in the tests
[tg] re_format(7) BSD: [[ $x = *[[:\<:]]foo[[:\>:]]* ]]
[tg, iSKUNK] Use Config in check.pl only if it exists
[tg] New matching code for bracket expressions, full POSIX (8bit)
[komh] Exclude FAT/HPFS/NTFS-unsafe tests on OS/2 (and Cygwin/MSYS)
[tg] Update to Unicode 10.0.0
[tg, selk] Make readonly idempotent
[tg, multiplexd] When truncating the persistent history, do not change the underlying file, do all operations on the locked one; do not stop using the history at all if it has been truncated
[tg, Jörg] Turn off UTF-8 mode upon turning on POSIX mode
[Martijn Dekker, Geoff Clare, many on the Austin list, tg] In POSIX mode, make the exec builtin force a $PATH search plus execve
[tg] Fix GCC 7, Coverity Scan warnings
[tg, Michal Hlavinka] Track background process PIDs even interactive
[tg] Always expose mksh’s hexdump shell function; speed it up by working on the input in chunks; use character classes to make it EBCDIC safe
[tg] Revamp dot.mkshrc default editor selection mechanism

Bug: N/A
Test: builds and boots
Change-Id: I35d624c4352c1b7c1ee499f9680712a30c64323b
diff --git a/Android.mk b/Android.mk
index 829fc7b..5a53d9f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -32,48 +32,89 @@
 
 MKSH_INCLUDES := $(LOCAL_PATH)/src
 
+# Compiler flags...
 MKSH_CFLAGS += \
     -Wno-deprecated-declarations \
     -fno-asynchronous-unwind-tables \
     -fno-strict-aliasing \
     -fstack-protector -fwrapv \
 
-# ...and CPPFLAGS.
+# ...various options we choose...
 MKSH_CFLAGS += \
-    -DDEBUG_LEAKS -DMKSH_ASSUME_UTF8 \
+    -DDEBUG_LEAKS \
+    -DMKSH_ASSUME_UTF8 \
     -DMKSH_DONT_EMIT_IDSTRING \
+    -DKSH_VERSIONNAME_VENDOR_EXT=\"\ Android\" \
+
+# ...and the defines from Build.sh.
+MKSH_CFLAGS += \
     -DMKSH_BUILDSH \
-    -D_GNU_SOURCE -DSETUID_CAN_FAIL_WITH_EAGAIN \
-    -DHAVE_ATTRIBUTE_BOUNDED=0 -DHAVE_ATTRIBUTE_FORMAT=1 \
+    -D_GNU_SOURCE \
+    -DSETUID_CAN_FAIL_WITH_EAGAIN \
+    -DHAVE_STRING_POOLING=1 \
+    -DHAVE_ATTRIBUTE_BOUNDED=1 \
+    -DHAVE_ATTRIBUTE_FORMAT=1 \
     -DHAVE_ATTRIBUTE_NORETURN=1 \
     -DHAVE_ATTRIBUTE_PURE=1 \
     -DHAVE_ATTRIBUTE_UNUSED=1 \
-    -DHAVE_ATTRIBUTE_USED=1 -DHAVE_SYS_TIME_H=1 -DHAVE_TIME_H=1 \
-    -DHAVE_BOTH_TIME_H=1 -DHAVE_SYS_BSDTYPES_H=0 \
-    -DHAVE_SYS_FILE_H=1 -DHAVE_SYS_MKDEV_H=0 -DHAVE_SYS_MMAN_H=1 \
-    -DHAVE_SYS_PARAM_H=1 -DHAVE_SYS_RESOURCE_H=1 \
-    -DHAVE_SYS_SELECT_H=1 -DHAVE_SYS_SYSMACROS_H=1 \
-    -DHAVE_BSTRING_H=0 -DHAVE_GRP_H=1 -DHAVE_IO_H=0 -DHAVE_LIBGEN_H=1 \
-    -DHAVE_LIBUTIL_H=0 -DHAVE_PATHS_H=1 -DHAVE_STDINT_H=1 \
-    -DHAVE_STRINGS_H=1 -DHAVE_TERMIOS_H=1 -DHAVE_ULIMIT_H=0 \
-    -DHAVE_VALUES_H=0 -DHAVE_CAN_INTTYPES=1 -DHAVE_CAN_UCBINTS=1 \
-    -DHAVE_CAN_INT8TYPE=1 -DHAVE_CAN_UCBINT8=1 -DHAVE_RLIM_T=1 \
+    -DHAVE_ATTRIBUTE_USED=1 \
+    -DHAVE_SYS_TIME_H=1 \
+    -DHAVE_TIME_H=1 \
+    -DHAVE_BOTH_TIME_H=1 \
+    -DHAVE_SYS_BSDTYPES_H=0 \
+    -DHAVE_SYS_FILE_H=1 \
+    -DHAVE_SYS_MKDEV_H=0 \
+    -DHAVE_SYS_MMAN_H=1 \
+    -DHAVE_SYS_PARAM_H=1 \
+    -DHAVE_SYS_RESOURCE_H=1 \
+    -DHAVE_SYS_SELECT_H=1 \
+    -DHAVE_SYS_SYSMACROS_H=1 \
+    -DHAVE_BSTRING_H=0 \
+    -DHAVE_GRP_H=1 \
+    -DHAVE_IO_H=0 \
+    -DHAVE_LIBGEN_H=1 \
+    -DHAVE_LIBUTIL_H=0 \
+    -DHAVE_PATHS_H=1 \
+    -DHAVE_STDINT_H=1 \
+    -DHAVE_STRINGS_H=1 \
+    -DHAVE_TERMIOS_H=1 \
+    -DHAVE_ULIMIT_H=0 \
+    -DHAVE_VALUES_H=0 \
+    -DHAVE_CAN_INTTYPES=1 \
+    -DHAVE_CAN_UCBINTS=1 \
+    -DHAVE_CAN_INT8TYPE=1 \
+    -DHAVE_CAN_UCBINT8=1 \
+    -DHAVE_RLIM_T=1 \
     -DHAVE_SIG_T=1 \
-    -DHAVE_STRING_POOLING=1 \
-    -DHAVE_SYS_ERRLIST=0 -DHAVE_SYS_SIGNAME=1 \
-    -DHAVE_SYS_SIGLIST=1 -DHAVE_FLOCK=1 -DHAVE_LOCK_FCNTL=1 \
+    -DHAVE_SYS_ERRLIST=0 \
+    -DHAVE_SYS_SIGNAME=1 \
+    -DHAVE_SYS_SIGLIST=1 \
+    -DHAVE_FLOCK=1 \
+    -DHAVE_LOCK_FCNTL=1 \
     -DHAVE_GETRUSAGE=1 \
     -DHAVE_GETSID=1 \
     -DHAVE_GETTIMEOFDAY=1 \
-    -DHAVE_ISSETUGID=0 \
     -DHAVE_KILLPG=1 \
-    -DHAVE_MEMMOVE=1 -DHAVE_MKNOD=0 -DHAVE_MMAP=1 -DHAVE_NICE=1 \
-    -DHAVE_REVOKE=0 -DHAVE_SETLOCALE_CTYPE=0 \
-    -DHAVE_LANGINFO_CODESET=0 -DHAVE_SELECT=1 -DHAVE_SETRESUGID=1 \
-    -DHAVE_SETGROUPS=1 -DHAVE_STRERROR=1 -DHAVE_STRSIGNAL=0 \
-    -DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 -DHAVE_REVOKE_DECL=1 \
-    -DHAVE_SYS_ERRLIST_DECL=0 -DHAVE_SYS_SIGLIST_DECL=1 \
-    -DHAVE_PERSISTENT_HISTORY=0 -DMKSH_BUILD_R=551
+    -DHAVE_MEMMOVE=1 \
+    -DHAVE_MKNOD=0 \
+    -DHAVE_MMAP=1 \
+    -DHAVE_FTRUNCATE=1 \
+    -DHAVE_NICE=1 \
+    -DHAVE_REVOKE=0 \
+    -DHAVE_SETLOCALE_CTYPE=1 \
+    -DHAVE_LANGINFO_CODESET=1 \
+    -DHAVE_SELECT=1 \
+    -DHAVE_SETRESUGID=1 \
+    -DHAVE_SETGROUPS=1 \
+    -DHAVE_STRERROR=1 \
+    -DHAVE_STRSIGNAL=0 \
+    -DHAVE_STRLCPY=1 \
+    -DHAVE_FLOCK_DECL=1 \
+    -DHAVE_REVOKE_DECL=1 \
+    -DHAVE_SYS_ERRLIST_DECL=0 \
+    -DHAVE_SYS_SIGLIST_DECL=1 \
+    -DHAVE_PERSISTENT_HISTORY=0 \
+    -DMKSH_BUILD_R=562 \
 
 LOCAL_SRC_FILES := $(MKSH_SRC_FILES)
 
diff --git a/Android.patch.txt b/Android.patch.txt
new file mode 100644
index 0000000..c3cf892
--- /dev/null
+++ b/Android.patch.txt
@@ -0,0 +1,48 @@
+--- mksh-R56b/funcs.c	2017-05-05 15:53:55.000000000 -0700
++++ src/funcs.c	2017-09-22 16:19:44.327000462 -0700
+@@ -103,7 +103,9 @@
+ 	{Tsgbreak, c_brkcont},
+ 	{T__builtin, c_builtin},
+ 	{Tbuiltin, c_builtin},
++#if !defined(__ANDROID__)
+ 	{Tbcat, c_cat},
++#endif
+ 	{Tcd, c_cd},
+ 	/* dash compatibility hack */
+ 	{"chdir", c_cd},
+@@ -126,7 +128,9 @@
+ 	{"pwd", c_pwd},
+ 	{Tread, c_read},
+ 	{Tdsgreadonly, c_typeset},
++#if !defined(__ANDROID__)
+ 	{"!realpath", c_realpath},
++#endif
+ 	{"~rename", c_rename},
+ 	{"*=return", c_exitreturn},
+ 	{Tsgset, c_set},
+@@ -160,8 +164,10 @@
+ 	{"~printf", c_printf},
+ #endif
+ #if HAVE_SELECT
++#if !defined(__ANDROID__)
+ 	{"sleep", c_sleep},
+ #endif
++#endif
+ #ifdef __MirBSD__
+ 	/* alias to "true" for historical reasons */
+ 	{"domainname", c_true},
+--- mksh-R56b/main.c	2017-04-28 04:14:14.000000000 -0700
++++ src/main.c	2017-09-22 15:58:14.134149037 -0700
+@@ -410,6 +410,12 @@
+ 		}
+ 	}
+ 
++	/* override default PATH regardless of environment */
++#ifdef MKSH_DEFPATH_OVERRIDE
++	vp = global(TPATH);
++	setstr(vp, MKSH_DEFPATH_OVERRIDE, KSH_RETURN_ERROR);
++#endif
++
+ 	/* for security */
+ 	typeset(TinitIFS, 0, 0, 0, 0);
+ 
diff --git a/src/Build.sh b/src/Build.sh
index ca88a06..78fe347 100644
--- a/src/Build.sh
+++ b/src/Build.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.716 2017/04/12 18:33:22 tg Exp $'
+srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.727 2017/08/29 13:38:28 tg Exp $'
 #-
 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
 #		2011, 2012, 2013, 2014, 2015, 2016, 2017
@@ -53,6 +53,16 @@
 alln=0123456789
 alls=______________________________________________________________
 
+case `echo a | tr '\201' X` in
+X)
+	# EBCDIC build system
+	lfcr='\n\r'
+	;;
+*)
+	lfcr='\012\015'
+	;;
+esac
+
 genopt_die() {
 	if test -n "$1"; then
 		echo >&2 "E: $*"
@@ -425,7 +435,7 @@
 		na=0
 	fi
 	hf=$1; shift
-	hv=`echo "$hf" | tr -d '\012\015' | tr -c $alll$allu$alln $alls`
+	hv=`echo "$hf" | tr -d "$lfcr" | tr -c $alll$allu$alln $alls`
 	echo "/* NeXTstep bug workaround */" >x
 	for i
 	do
@@ -496,6 +506,7 @@
 tfn=
 legacy=0
 textmode=0
+ebcdic=false
 
 for i
 do
@@ -519,6 +530,9 @@
 	:-c)
 		last=c
 		;;
+	:-E)
+		ebcdic=true
+		;;
 	:-G)
 		echo "$me: Do not call me with '-G'!" >&2
 		exit 1
@@ -603,6 +617,10 @@
 	add_cppflags -DMKSH_LEGACY_MODE
 fi
 
+if $ebcdic; then
+	add_cppflags -DMKSH_EBCDIC
+fi
+
 if test $textmode = 0; then
 	check_categories="$check_categories shell:textmode-no shell:binmode-yes"
 else
@@ -765,7 +783,9 @@
 	add_cppflags -DSETUID_CAN_FAIL_WITH_EAGAIN
 	;;
 Haiku)
-	add_cppflags -DMKSH_ASSUME_UTF8; HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	add_cppflags -DMKSH_ASSUME_UTF8
+	HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	HAVE_ISOFF_MKSH_ASSUME_UTF8=0
 	;;
 Harvey)
 	add_cppflags -D_POSIX_SOURCE
@@ -773,11 +793,14 @@
 	add_cppflags -D_BSD_EXTENSION
 	add_cppflags -D_SUSV2_SOURCE
 	add_cppflags -D_GNU_SOURCE
-	add_cppflags -DMKSH_ASSUME_UTF8; HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	add_cppflags -DMKSH_ASSUME_UTF8
+	HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	HAVE_ISOFF_MKSH_ASSUME_UTF8=0
 	add_cppflags -DMKSH_NO_CMDLINE_EDITING
 	add_cppflags -DMKSH__NO_SETEUGID
 	oswarn=' and will currently not work'
 	add_cppflags -DMKSH_UNEMPLOYED
+	add_cppflags -DMKSH_NOPROSPECTOFWORK
 	# these taken from Harvey-OS github and need re-checking
 	add_cppflags -D_setjmp=setjmp -D_longjmp=longjmp
 	: "${HAVE_CAN_NO_EH_FRAME=0}"
@@ -826,7 +849,9 @@
 MirBSD)
 	;;
 MSYS_*)
-	add_cppflags -DMKSH_ASSUME_UTF8=0; HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	add_cppflags -DMKSH_ASSUME_UTF8=0
+	HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	HAVE_ISOFF_MKSH_ASSUME_UTF8=1
 	# almost same as CYGWIN* (from RT|Chatzilla)
 	: "${HAVE_SETLOCALE_CTYPE=0}"
 	# broken on this OE (from ir0nh34d)
@@ -860,7 +885,9 @@
 	: "${HAVE_SETLOCALE_CTYPE=0}"
 	;;
 OS/2)
-	add_cppflags -DMKSH_ASSUME_UTF8=0; HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	add_cppflags -DMKSH_ASSUME_UTF8=0
+	HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	HAVE_ISOFF_MKSH_ASSUME_UTF8=1
 	HAVE_TERMIOS_H=0
 	HAVE_MKNOD=0	# setmode() incompatible
 	oswarn="; it is being ported"
@@ -894,6 +921,16 @@
 ] incompatibilities with $y.
 "
 	;;
+OS/390)
+	add_cppflags -DMKSH_ASSUME_UTF8=0
+	HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	HAVE_ISOFF_MKSH_ASSUME_UTF8=1
+	: "${CC=xlc}"
+	: "${SIZE=: size}"
+	add_cppflags -DMKSH_FOR_Z_OS
+	add_cppflags -D_ALL_SOURCE
+	oswarn='; EBCDIC support is incomplete'
+	;;
 OSF1)
 	HAVE_SIG_T=0	# incompatible
 	add_cppflags -D_OSF_SOURCE
@@ -907,7 +944,9 @@
 	add_cppflags -D_LIMITS_EXTENSION
 	add_cppflags -D_BSD_EXTENSION
 	add_cppflags -D_SUSV2_SOURCE
-	add_cppflags -DMKSH_ASSUME_UTF8; HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	add_cppflags -DMKSH_ASSUME_UTF8
+	HAVE_ISSET_MKSH_ASSUME_UTF8=1
+	HAVE_ISOFF_MKSH_ASSUME_UTF8=0
 	add_cppflags -DMKSH_NO_CMDLINE_EDITING
 	add_cppflags -DMKSH__NO_SETEUGID
 	oswarn=' and will currently not work'
@@ -1047,7 +1086,7 @@
 # - LLVM+clang defines __GNUC__ too
 # - nwcc defines __GNUC__ too
 CPP="$CC -E"
-$e ... which compiler seems to be used
+$e ... which compiler type seems to be used
 cat >conftest.c <<'EOF'
 const char *
 #if defined(__ICC) || defined(__INTEL_COMPILER)
@@ -1297,7 +1336,7 @@
 	# huh?
 	;;
 esac
-$e "$bi==> which compiler seems to be used...$ao $ui$ct$etd$ao"
+$e "$bi==> which compiler type seems to be used...$ao $ui$ct$etd$ao"
 rmf conftest.c conftest.o conftest a.out* a.exe* conftest.exe* vv.out
 
 #
@@ -1392,8 +1431,16 @@
 	DOWARN=-Wc,-we
 	;;
 xlc)
-	save_NOWARN=-qflag=i:e
-	DOWARN=-qflag=i:i
+	case $TARGET_OS in
+	OS/390)
+		save_NOWARN=-qflag=e
+		DOWARN=-qflag=i
+		;;
+	*)
+		save_NOWARN=-qflag=i:e
+		DOWARN=-qflag=i:i
+		;;
+	esac
 	;;
 *)
 	test x"$save_NOWARN" = x"" && save_NOWARN=-Wno-error
@@ -1563,10 +1610,24 @@
 	ac_flags 1 extansi -Xa
 	;;
 xlc)
-	ac_flags 1 rodata "-qro -qroconst -qroptr"
-	ac_flags 1 rtcheck -qcheck=all
-	#ac_flags 1 rtchkc -qextchk	# reported broken
-	ac_flags 1 wformat "-qformat=all -qformat=nozln"
+	case $TARGET_OS in
+	OS/390)
+		# On IBM z/OS, the following are warnings by default:
+		# CCN3296: #include file <foo.h> not found.
+		# CCN3944: Attribute "__foo__" is not supported and is ignored.
+		# CCN3963: The attribute "foo" is not a valid variable attribute and is ignored.
+		ac_flags 1 halton '-qhaltonmsg=CCN3296 -qhaltonmsg=CCN3944 -qhaltonmsg=CCN3963'
+		# CCN3290: Unknown macro name FOO on #undef directive.
+		# CCN4108: The use of keyword '__attribute__' is non-portable.
+		ac_flags 1 supprss '-qsuppress=CCN3290 -qsuppress=CCN4108'
+		;;
+	*)
+		ac_flags 1 rodata '-qro -qroconst -qroptr'
+		ac_flags 1 rtcheck -qcheck=all
+		#ac_flags 1 rtchkc -qextchk	# reported broken
+		ac_flags 1 wformat '-qformat=all -qformat=nozln'
+		;;
+	esac
 	#ac_flags 1 wp64 -qwarn64	# too verbose for now
 	;;
 esac
@@ -1705,6 +1766,10 @@
     check_categories="$check_categories arge nojsig"
 ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \
     'if the default UTF-8 mode is specified' && : "${HAVE_SETLOCALE_CTYPE=0}"
+ac_ifcpp 'if !MKSH_ASSUME_UTF8' isoff_MKSH_ASSUME_UTF8 \
+    isset_MKSH_ASSUME_UTF8 0 \
+    'if the default UTF-8 mode is disabled' && \
+    check_categories="$check_categories noutf8"
 #ac_ifcpp 'ifdef MKSH_DISABLE_DEPRECATED' isset_MKSH_DISABLE_DEPRECATED '' \
 #    "if deprecated features are to be omitted" && \
 #    check_categories="$check_categories nodeprecated"
@@ -2025,6 +2090,11 @@
 	    munmap(NULL, 0)); }
 EOF
 
+ac_test ftruncate mmap 0 'for ftruncate' <<-'EOF'
+	#include <unistd.h>
+	int main(void) { return (ftruncate(0, 0)); }
+EOF
+
 ac_test nice <<-'EOF'
 	#include <unistd.h>
 	int main(void) { return (nice(4)); }
@@ -2179,8 +2249,8 @@
 # other checks
 #
 fd='if to use persistent history'
-ac_cache PERSISTENT_HISTORY || case $HAVE_MMAP$HAVE_FLOCK$HAVE_LOCK_FCNTL in
-11*|101) fv=1 ;;
+ac_cache PERSISTENT_HISTORY || case $HAVE_FTRUNCATE$HAVE_MMAP$HAVE_FLOCK$HAVE_LOCK_FCNTL in
+111*|1101) fv=1 ;;
 esac
 test 1 = $fv || check_categories="$check_categories no-histfile"
 ac_testdone
@@ -2339,7 +2409,7 @@
 addsrcs USE_PRINTF_BUILTIN printf.c
 test 1 = "$USE_PRINTF_BUILTIN" && add_cppflags -DMKSH_PRINTF_BUILTIN
 test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose"
-add_cppflags -DMKSH_BUILD_R=551
+add_cppflags -DMKSH_BUILD_R=562
 
 $e $bi$me: Finished configuration testing, now producing output.$ao
 
@@ -2366,8 +2436,8 @@
 	set -A check_categories -- $check_categories
 	pflag='$curdir/$mkshexe'
 	sflag='$srcdir/check.t'
-	usee=0 Pflag=0 Sflag=0 uset=0 vflag=1 xflag=0
-	while getopts "C:e:fPp:QSs:t:v" ch; do case \$ch {
+	usee=0 useU=0 Pflag=0 Sflag=0 uset=0 vflag=1 xflag=0
+	while getopts "C:e:fPp:QSs:t:U:v" ch; do case \$ch {
 	(C)	check_categories[\${#check_categories[*]}]=\$OPTARG ;;
 	(e)	usee=1; eflag=\$OPTARG ;;
 	(f)	check_categories[\${#check_categories[*]}]=fastbox ;;
@@ -2380,6 +2450,7 @@
 	(+S)	Sflag=0 ;;
 	(s)	sflag=\$OPTARG ;;
 	(t)	uset=1; tflag=\$OPTARG ;;
+	(U)	useU=1; Uflag=\$OPTARG ;;
 	(v)	vflag=1 ;;
 	(+v)	vflag=0 ;;
 	(*)	xflag=1 ;;
@@ -2387,6 +2458,9 @@
 	done
 	shift \$((OPTIND - 1))
 	set -A args -- '$srcdir/check.pl' -p "\$pflag"
+	if $ebcdic; then
+		args[\${#args[*]}]=-E
+	fi
 	x=
 	for y in "\${check_categories[@]}"; do
 		x=\$x,\$y
@@ -2404,6 +2478,10 @@
 		args[\${#args[*]}]=-t
 		args[\${#args[*]}]=\$tflag
 	fi
+	if (( useU )); then
+		args[\${#args[*]}]=-U
+		args[\${#args[*]}]=\$Uflag
+	fi
 	(( vflag )) && args[\${#args[*]}]=-v
 	(( xflag )) && args[\${#args[*]}]=-x	# force usage by synerr
 	if [[ -n \$TMPDIR && -d \$TMPDIR/. ]]; then
@@ -2647,7 +2725,7 @@
 MKSH_ASSUME_UTF8		(0=disabled, 1=enabled; default: unset)
 MKSH_BINSHPOSIX			if */sh or */-sh, enable set -o posix
 MKSH_BINSHREDUCED		if */sh or */-sh, enable set -o sh
-MKSH_CLS_STRING			"\033[;H\033[J"
+MKSH_CLS_STRING			KSH_ESC_STRING "[;H" KSH_ESC_STRING "[J"
 MKSH_DEFAULT_EXECSHELL		"/bin/sh" (do not change)
 MKSH_DEFAULT_PROFILEDIR		"/etc" (do not change)
 MKSH_DEFAULT_TMPDIR		"/tmp" (do not change)
diff --git a/src/check.pl b/src/check.pl
index a80d4e1..e9c2437 100644
--- a/src/check.pl
+++ b/src/check.pl
@@ -1,8 +1,8 @@
-# $MirOS: src/bin/mksh/check.pl,v 1.42 2015/11/29 17:05:00 tg Exp $
+# $MirOS: src/bin/mksh/check.pl,v 1.49 2017/05/05 21:17:31 tg Exp $
 # $OpenBSD: th,v 1.1 2013/12/02 20:39:44 millert Exp $
 #-
 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
-#		2012, 2013, 2014, 2015
+#		2012, 2013, 2014, 2015, 2017
 #	mirabilos <m@mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -78,9 +78,9 @@
 #					the test harness).
 #					CYGWIN is set to nodosfilewarning.
 #					ENV is set to /nonexistant.
-#					PATHSEP is set to either : or ;.
 #					__progname is set to the -p argument.
 #					__perlname is set to $^X (perlexe).
+#					@utflocale@ is substituted from -U.
 #	file-setup		mps	Used to create files, directories
 #					and symlinks. First word is either
 #					file, dir or symlink; second word is
@@ -153,9 +153,15 @@
 #	p	tag takes parameters (used with m).
 #	s	tag can be used several times.
 
+# require Config only if it exists
 # pull EINTR from POSIX.pm or Errno.pm if they exist
 # otherwise just skip it
 BEGIN {
+	eval {
+		require Config;
+		import Config;
+		1;
+	};
 	$EINTR = 0;
 	eval {
 		require POSIX;
@@ -172,7 +178,6 @@
 };
 
 use Getopt::Std;
-use Config;
 
 $os = defined $^O ? $^O : 'unknown';
 
@@ -180,7 +185,7 @@
 
 $Usage = <<EOF ;
 Usage: $prog [-Pv] [-C cat] [-e e=v] [-p prog] [-s fn] [-T dir] \
-       [-t tmo] name ...
+       [-t tmo] [-U lcl] name ...
 	-C c	Specify the comma separated list of categories the program
 		belongs to (see category field).
 	-e e=v	Set the environment variable e to v for all tests
@@ -193,6 +198,7 @@
 		scaned for test files (which end in .t).
 	-T dir	Use dir instead of /tmp to hold temporary files
 	-t t	Use t as default time limit for tests (default is unlimited)
+	-U lcl	Use lcl as UTF-8 locale (e.g. C.UTF-8) instead of the default
 	-v	Verbose mode: print reason test failed.
 	name	specifies the name of the test(s) to run; if none are
 		specified, all tests are run.
@@ -241,7 +247,7 @@
 
 %known_tests = ();
 
-if (!getopts('C:e:Pp:s:T:t:v')) {
+if (!getopts('C:Ee:Pp:s:T:t:U:v')) {
     print STDERR $Usage;
     exit 1;
 }
@@ -250,8 +256,10 @@
 die "$prog: no test set specified (use -s)\n" if !defined $opt_s;
 $test_prog = $opt_p;
 $verbose = defined $opt_v && $opt_v;
+$is_ebcdic = defined $opt_E && $opt_E;
 $test_set = $opt_s;
 $temp_base = $opt_T || "/tmp";
+$utflocale = $opt_U || (($os eq "hpux") ? "en_US.utf8" : "en_US.UTF-8");
 if (defined $opt_t) {
     die "$prog: bad -t argument (should be number > 0): $opt_t\n"
 	if $opt_t !~ /^\d+$/ || $opt_t <= 0;
@@ -259,6 +267,14 @@
 }
 $program_kludge = defined $opt_P ? $opt_P : 0;
 
+if ($is_ebcdic) {
+	$categories{'shell:ebcdic-yes'} = 1;
+	$categories{'shell:ascii-no'} = 1;
+} else {
+	$categories{'shell:ebcdic-no'} = 1;
+	$categories{'shell:ascii-yes'} = 1;
+}
+
 if (defined $opt_C) {
     foreach $c (split(',', $opt_C)) {
 	$c =~ s/\s+//;
@@ -281,12 +297,24 @@
 }
 $new_env{'CYGWIN'} = 'nodosfilewarning';
 $new_env{'ENV'} = '/nonexistant';
-$new_env{'PATHSEP'} = $os eq 'os2' ? ';' : ':';
+
 if (($os eq 'VMS') || ($Config{perlpath} =~ m/$Config{_exe}$/i)) {
 	$new_env{'__perlname'} = $Config{perlpath};
 } else {
 	$new_env{'__perlname'} = $Config{perlpath} . $Config{_exe};
 }
+$new_env{'__perlname'} = $^X if ($new_env{'__perlname'} eq '') and -f $^X and -x $^X;
+if ($new_env{'__perlname'} eq '') {
+	foreach $pathelt (split /:/,$ENV{'PATH'}) {
+		chomp($pathelt = `pwd`) if $pathelt eq '';
+		my $x = $pathelt . '/' . $^X;
+		next unless -f $x and -x $x;
+		$new_env{'__perlname'} = $x;
+		last;
+	}
+}
+$new_env{'__perlname'} = $^X if ($new_env{'__perlname'} eq '');
+
 if (defined $opt_e) {
     # XXX need a way to allow many -e arguments...
     if ($opt_e =~ /^([a-zA-Z_]\w*)(|=(.*))$/) {
@@ -866,38 +894,50 @@
 	    $char = 1;
 	}
     }
-    return "first difference: line $lineno, char $char (wanted '"
-	. &format_char($ce) . "', got '"
-	. &format_char($cg) . "'";
+    return "first difference: line $lineno, char $char (wanted " .
+	&format_char($ce) . ", got " . &format_char($cg);
 }
 
 sub
 format_char
 {
-    local($ch, $s);
+    local($ch, $s, $q);
 
     $ch = ord($_[0]);
+    $q = "'";
+
+    if ($is_ebcdic) {
+	if ($ch == 0x15) {
+		return $q . '\n' . $q;
+	} elsif ($ch == 0x16) {
+		return $q . '\b' . $q;
+	} elsif ($ch == 0x05) {
+		return $q . '\t' . $q;
+	} elsif ($ch < 64 || $ch == 255) {
+		return sprintf("X'%02X'", $ch);
+	}
+	return sprintf("'%c' (X'%02X')", $ch, $ch);
+    }
+
+    $s = sprintf("0x%02X (", $ch);
     if ($ch == 10) {
-	return '\n';
+	return $s . $q . '\n' . $q . ')';
     } elsif ($ch == 13) {
-	return '\r';
+	return $s . $q . '\r' . $q . ')';
     } elsif ($ch == 8) {
-	return '\b';
+	return $s . $q . '\b' . $q . ')';
     } elsif ($ch == 9) {
-	return '\t';
+	return $s . $q . '\t' . $q . ')';
     } elsif ($ch > 127) {
-	$ch -= 127;
-	$s = "M-";
-    } else {
-	$s = '';
+	$ch -= 128;
+	$s .= "M-";
     }
     if ($ch < 32) {
-	$s .= '^';
-	$ch += ord('@');
+	return sprintf("%s^%c)", $s, $ch + ord('@'));
     } elsif ($ch == 127) {
-	return $s . "^?";
+	return $s . "^?)";
     }
-    return $s . sprintf("%c", $ch);
+    return sprintf("%s'%c')", $s, $ch);
 }
 
 sub
@@ -1159,6 +1199,8 @@
 	    print STDERR "$prog:$test{':long-name'}: env-setup field doesn't start and end with the same character\n";
 	    return undef;
 	}
+
+	$test{'env-setup'} =~ s/\@utflocale\@/$utflocale/g;
     }
     if (defined $test{'expected-exit'}) {
 	local($val) = $test{'expected-exit'};
diff --git a/src/check.t b/src/check.t
index 93c614f..4473c08 100644
--- a/src/check.t
+++ b/src/check.t
Binary files differ
diff --git a/src/dot.mkshrc b/src/dot.mkshrc
index af55d7d..4a3dfea 100644
--- a/src/dot.mkshrc
+++ b/src/dot.mkshrc
@@ -1,5 +1,5 @@
 # $Id$
-# $MirOS: src/bin/mksh/dot.mkshrc,v 1.114 2017/03/19 22:31:26 tg Exp $
+# $MirOS: src/bin/mksh/dot.mkshrc,v 1.121 2017/08/08 21:10:21 tg Exp $
 #-
 # Copyright (c) 2002, 2003, 2004, 2006, 2007, 2008, 2009, 2010,
 #		2011, 2012, 2013, 2014, 2015, 2016, 2017
@@ -56,6 +56,13 @@
 	done
 )
 
+# customise your favourite editor here; the first one found is used
+for EDITOR in "${EDITOR:-}" jupp jstar mcedit ed vi; do
+	EDITOR=$(\\builtin whence -p "$EDITOR") || EDITOR=
+	[[ -n $EDITOR && -x $EDITOR ]] && break
+	EDITOR=
+done
+
 \\builtin alias ls=ls l='ls -F' la='l -a' ll='l -l' lo='l -alo'
 \: "${HOSTNAME:=$(\\builtin ulimit -c 0; \\builtin print -r -- $(hostname \
     2>/dev/null))}${EDITOR:=/bin/ed}${TERM:=vt100}${USER:=$(\\builtin ulimit \
@@ -73,6 +80,7 @@
 \: "${MKSH:=$(\\builtin whence -p mksh)}${MKSH:=/bin/mksh}"
 \\builtin export MKSH
 
+# prompts
 PS4='[$EPOCHREALTIME] '; PS1='#'; (( USER_ID )) && PS1='$'; PS1=$'\001\r''${|
 	\\builtin typeset e=$?
 
@@ -86,6 +94,8 @@
 
 	\\builtin return $e
 } '"$PS1 "
+
+# utilities
 \\builtin alias doch='sudo mksh -c "$(\\builtin fc -ln -1)"'
 \\builtin command -v rot13 >/dev/null || \\builtin alias rot13='tr \
     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \
@@ -99,42 +109,47 @@
 	}
 else
 	function hd {
-		\\builtin typeset -Uui16 -Z11 pos=0
-		\\builtin typeset -Uui16 -Z5 hv=2147483647
-		\\builtin typeset dasc line i
-		\\builtin set +U
-
-		\\builtin cat "$@" | if \\builtin read -arN -1 line; then
-			\\builtin typeset -i1 'line[*]'
-			i=0
-			while (( i < ${#line[*]} )); do
-				hv=${line[i++]}
-				if (( (pos & 15) == 0 )); then
-					(( pos )) && \
-					    \\builtin print -r -- "$dasc|"
-					\\builtin print -nr "${pos#16#}  "
-					dasc=' |'
-				fi
-				\\builtin print -nr "${hv#16#} "
-				#XXX EBCDIC, but we need [[:print:]] to fix this
-				if (( (hv < 32) || (hv > 126) )); then
-					dasc+=.
-				else
-					dasc+=${line[i-1]#1#}
-				fi
-				(( (pos++ & 15) == 7 )) && \
-				    \\builtin print -nr -- '- '
-			done
-			while (( pos & 15 )); do
-				\\builtin print -nr '   '
-				(( (pos++ & 15) == 7 )) && \
-				    \\builtin print -nr -- '- '
-			done
-			(( hv == 2147483647 )) || \\builtin print -r -- "$dasc|"
-		fi
+		\\builtin cat "$@" | hd_mksh "$@"
 	}
 fi
 
+# NUL-safe and EBCDIC-safe hexdump (from stdin)
+function hd_mksh {
+	\\builtin typeset -Uui16 -Z11 pos=0
+	\\builtin typeset -Uui16 -Z5 hv=2147483647
+	\\builtin typeset dasc dn line i
+	\\builtin set +U
+
+	while \\builtin read -arn 512 line; do
+		\\builtin typeset -i1 'line[*]'
+		i=0
+		while (( i < ${#line[*]} )); do
+			dn=
+			(( (hv = line[i++]) != 0 )) && dn=${line[i-1]#1#}
+			if (( (pos & 15) == 0 )); then
+				(( pos )) && \
+				    \\builtin print -r -- "$dasc|"
+				\\builtin print -nr "${pos#16#}  "
+				dasc=' |'
+			fi
+			\\builtin print -nr "${hv#16#} "
+			if [[ $dn = [[:print:]] ]]; then
+				dasc+=$dn
+			else
+				dasc+=.
+			fi
+			(( (pos++ & 15) == 7 )) && \
+			    \\builtin print -nr -- '- '
+		done
+	done
+	while (( pos & 15 )); do
+		\\builtin print -nr '   '
+		(( (pos++ & 15) == 7 )) && \
+		    \\builtin print -nr -- '- '
+	done
+	(( hv == 2147483647 )) || \\builtin print -r -- "$dasc|"
+}
+
 # Berkeley C shell compatible dirs, popd, and pushd functions
 # Z shell compatible chpwd() hook, used to update DIRSTACK[0]
 DIRSTACKBASE=$(\\builtin realpath ~/. 2>/dev/null || \
@@ -483,6 +498,7 @@
 	i_func[nfunc++]=setenv
 	i_func[nfunc++]=smores
 	i_func[nfunc++]=hd
+	i_func[nfunc++]=hd_mksh
 	i_func[nfunc++]=chpwd
 	i_func[nfunc++]=cd
 	i_func[nfunc++]=cd_csh
@@ -588,6 +604,11 @@
 
 \: place customisations below this line
 
+# some defaults follow — you are supposed to adjust these to your
+# liking; by default we add ~/.etc/bin and ~/bin (whichever exist)
+# to $PATH, set $SHELL to mksh, set some defaults for man and less
+# and show a few more possible things for users to begin moving in
+
 for p in ~/.etc/bin ~/bin; do
 	[[ -d $p/. ]] || \\builtin continue
 	[[ $PATHSEP$PATH$PATHSEP = *"$PATHSEP$p$PATHSEP"* ]] || \
diff --git a/src/edit.c b/src/edit.c
index 58eaf7f..8bccf13 100644
--- a/src/edit.c
+++ b/src/edit.c
@@ -28,16 +28,16 @@
 
 #ifndef MKSH_NO_CMDLINE_EDITING
 
-__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.321 2017/04/12 16:46:20 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.340 2017/08/27 23:33:50 tg Exp $");
 
 /*
  * in later versions we might use libtermcap for this, but since external
  * dependencies are problematic, this has not yet been decided on; another
- * good string is "\033c" except on hardware terminals like the DEC VT420
- * which do a full power cycle then...
+ * good string is KSH_ESC_STRING "c" except on hardware terminals like the
+ * DEC VT420 which do a full power cycle then...
  */
 #ifndef MKSH_CLS_STRING
-#define MKSH_CLS_STRING		"\033[;H\033[J"
+#define MKSH_CLS_STRING		KSH_ESC_STRING "[;H" KSH_ESC_STRING "[J"
 #endif
 
 /* tty driver characters we are interested in */
@@ -76,7 +76,7 @@
 static char *holdbufp;			/* place to hold last edit buffer */
 
 /* 0=dumb 1=tmux (for now) */
-static bool x_term_mode;
+static uint8_t x_term_mode;
 
 static void x_adjust(void);
 static int x_getc(void);
@@ -97,6 +97,7 @@
 #if !MKSH_S_NOVI
 static int x_vi(char *);
 #endif
+static void x_intr(int, int) MKSH_A_NORETURN;
 
 #define x_flush()	shf_flush(shl_out)
 #if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
@@ -309,14 +310,14 @@
 			 * empirically made list of chars to escape
 			 * for globbing as well as QCHAR itself
 			 */
-			switch (ch) {
+			switch (ord(ch)) {
 			case QCHAR:
-			case '$':
-			case '*':
-			case '?':
-			case '[':
-			case '\\':
-			case '`':
+			case ord('$'):
+			case ord('*'):
+			case ord('?'):
+			case ord('['):
+			case ord('\\'):
+			case ord('`'):
 				*dp++ = QCHAR;
 				break;
 			}
@@ -467,7 +468,7 @@
 	const struct path_order_info *b = (const struct path_order_info *)bb;
 	int t;
 
-	if ((t = strcmp(a->word + a->base, b->word + b->base)))
+	if ((t = ascstrcmp(a->word + a->base, b->word + b->base)))
 		return (t);
 	if (a->path_order > b->path_order)
 		return (1);
@@ -535,7 +536,7 @@
 		char **words = (char **)XPptrv(w);
 		size_t i, j;
 
-		qsort(words, nwords, sizeof(void *), xstrcmp);
+		qsort(words, nwords, sizeof(void *), ascpstrcmp);
 		for (i = j = 0; i < nwords - 1; i++) {
 			if (strcmp(words[i], words[i + 1]))
 				words[j++] = words[i];
@@ -552,8 +553,7 @@
 	return (nwords);
 }
 
-#define IS_WORDC(c)	(!ctype(c, C_LEX1) && (c) != '\'' && (c) != '"' && \
-			    (c) != '`' && (c) != '=' && (c) != ':')
+#define IS_WORDC(c)	(!ctype(c, C_EDNWC))
 
 static int
 x_locate_word(const char *buf, int buflen, int pos, int *startp,
@@ -588,9 +588,9 @@
 		int p = start - 1;
 
 		/* Figure out if this is a command */
-		while (p >= 0 && ksh_isspace(buf[p]))
+		while (p >= 0 && ctype(buf[p], C_SPACE))
 			p--;
-		iscmd = p < 0 || vstrchr(";|&()`", buf[p]);
+		iscmd = p < 0 || ctype(buf[p], C_EDCMD);
 		if (iscmd) {
 			/*
 			 * If command has a /, path, etc. is not searched;
@@ -649,11 +649,12 @@
 		for (s = toglob; *s; s++) {
 			if (*s == '\\' && s[1])
 				s++;
-			else if (*s == '?' || *s == '*' || *s == '[' ||
-			    *s == '$' ||
+			else if (ctype(*s, C_QUEST | C_DOLAR) ||
+			    ord(*s) == ord('*') || ord(*s) == ord('[') ||
 			    /* ?() *() +() @() !() but two already checked */
-			    (s[1] == '(' /*)*/ &&
-			    (*s == '+' || *s == '@' || *s == '!'))) {
+			    (ord(s[1]) == ord('(' /*)*/) &&
+			    (ord(*s) == ord('+') || ord(*s) == ord('@') ||
+			    ord(*s) == ord('!')))) {
 				/*
 				 * just expand based on the extglob
 				 * or parameter
@@ -714,8 +715,8 @@
 				break;
 			}
 	/* false for nwords==1 as 0 = words[0][prefix_len] then */
-	if (UTFMODE && prefix_len && (words[0][prefix_len] & 0xC0) == 0x80)
-		while (prefix_len && (words[0][prefix_len] & 0xC0) != 0xC0)
+	if (UTFMODE && prefix_len && (rtt2asc(words[0][prefix_len]) & 0xC0) == 0x80)
+		while (prefix_len && (rtt2asc(words[0][prefix_len]) & 0xC0) != 0xC0)
 			--prefix_len;
 	return (prefix_len);
 }
@@ -747,7 +748,7 @@
 	const char *p;
 
 	if (se == NULL)
-		se = s + strlen(s);
+		se = strnul(s);
 	if (s == se)
 		return (0);
 
@@ -799,7 +800,7 @@
 	while (sp) {
 		xp = Xstring(xs, xp);
 		if (!(p = cstrchr(sp, MKSH_PATHSEPC)))
-			p = sp + strlen(sp);
+			p = strnul(sp);
 		pathlen = p - sp;
 		if (pathlen) {
 			/*
@@ -858,8 +859,7 @@
 	int rval = 0;
 
 	while (wlen - add > 0)
-		if (vstrchr("\"#$&'()*:;<=>?[\\`{|}", s[add]) ||
-		    ctype(s[add], C_IFS)) {
+		if (ctype(s[add], C_IFS | C_EDQ)) {
 			if (putbuf_func(s, add) != 0) {
 				rval = -1;
 				break;
@@ -908,11 +908,6 @@
 #define	XF_NOBIND	2	/* not allowed to bind to function */
 #define	XF_PREFIX	4	/* function sets prefix */
 
-/* Separator for completion */
-#define	is_cfs(c)	((c) == ' ' || (c) == '\t' || (c) == '"' || (c) == '\'')
-/* Separator for motion */
-#define	is_mfs(c)	(!(ksh_isalnux(c) || (c) == '$' || ((c) & 0x80)))
-
 #define X_NTABS		4			/* normal, meta1, meta2, pc */
 #define X_TABSZ		256			/* size of keydef tables etc */
 
@@ -991,6 +986,7 @@
 static int x_size2(char *, char **);
 static void x_zots(char *);
 static void x_zotc3(char **);
+static void x_vi_zotc(int);
 static void x_load_hist(char **);
 static int x_search(char *, int, int);
 #ifndef MKSH_SMALL
@@ -1036,56 +1032,56 @@
 };
 
 static struct x_defbindings const x_defbindings[] = {
-	{ XFUNC_del_back,		0, CTRL('?')	},
-	{ XFUNC_del_bword,		1, CTRL('?')	},
-	{ XFUNC_eot_del,		0, CTRL('D')	},
-	{ XFUNC_del_back,		0, CTRL('H')	},
-	{ XFUNC_del_bword,		1, CTRL('H')	},
+	{ XFUNC_del_back,		0,  CTRL_QM	},
+	{ XFUNC_del_bword,		1,  CTRL_QM	},
+	{ XFUNC_eot_del,		0,  CTRL_D	},
+	{ XFUNC_del_back,		0,  CTRL_H	},
+	{ XFUNC_del_bword,		1,  CTRL_H	},
 	{ XFUNC_del_bword,		1,	'h'	},
 	{ XFUNC_mv_bword,		1,	'b'	},
 	{ XFUNC_mv_fword,		1,	'f'	},
 	{ XFUNC_del_fword,		1,	'd'	},
-	{ XFUNC_mv_back,		0, CTRL('B')	},
-	{ XFUNC_mv_forw,		0, CTRL('F')	},
-	{ XFUNC_search_char_forw,	0, CTRL(']')	},
-	{ XFUNC_search_char_back,	1, CTRL(']')	},
-	{ XFUNC_newline,		0, CTRL('M')	},
-	{ XFUNC_newline,		0, CTRL('J')	},
-	{ XFUNC_end_of_text,		0, CTRL('_')	},
-	{ XFUNC_abort,			0, CTRL('G')	},
-	{ XFUNC_prev_com,		0, CTRL('P')	},
-	{ XFUNC_next_com,		0, CTRL('N')	},
-	{ XFUNC_nl_next_com,		0, CTRL('O')	},
-	{ XFUNC_search_hist,		0, CTRL('R')	},
+	{ XFUNC_mv_back,		0,  CTRL_B	},
+	{ XFUNC_mv_forw,		0,  CTRL_F	},
+	{ XFUNC_search_char_forw,	0,  CTRL_BC	},
+	{ XFUNC_search_char_back,	1,  CTRL_BC	},
+	{ XFUNC_newline,		0,  CTRL_M	},
+	{ XFUNC_newline,		0,  CTRL_J	},
+	{ XFUNC_end_of_text,		0,  CTRL_US	},
+	{ XFUNC_abort,			0,  CTRL_G	},
+	{ XFUNC_prev_com,		0,  CTRL_P	},
+	{ XFUNC_next_com,		0,  CTRL_N	},
+	{ XFUNC_nl_next_com,		0,  CTRL_O	},
+	{ XFUNC_search_hist,		0,  CTRL_R	},
 	{ XFUNC_beg_hist,		1,	'<'	},
 	{ XFUNC_end_hist,		1,	'>'	},
 	{ XFUNC_goto_hist,		1,	'g'	},
-	{ XFUNC_mv_end,			0, CTRL('E')	},
-	{ XFUNC_mv_beg,			0, CTRL('A')	},
-	{ XFUNC_draw_line,		0, CTRL('L')	},
-	{ XFUNC_cls,			1, CTRL('L')	},
-	{ XFUNC_meta1,			0, CTRL('[')	},
-	{ XFUNC_meta2,			0, CTRL('X')	},
-	{ XFUNC_kill,			0, CTRL('K')	},
-	{ XFUNC_yank,			0, CTRL('Y')	},
+	{ XFUNC_mv_end,			0,  CTRL_E	},
+	{ XFUNC_mv_beg,			0,  CTRL_A	},
+	{ XFUNC_draw_line,		0,  CTRL_L	},
+	{ XFUNC_cls,			1,  CTRL_L	},
+	{ XFUNC_meta1,			0,  CTRL_BO	},
+	{ XFUNC_meta2,			0,  CTRL_X	},
+	{ XFUNC_kill,			0,  CTRL_K	},
+	{ XFUNC_yank,			0,  CTRL_Y	},
 	{ XFUNC_meta_yank,		1,	'y'	},
-	{ XFUNC_literal,		0, CTRL('^')	},
+	{ XFUNC_literal,		0,  CTRL_CA	},
 	{ XFUNC_comment,		1,	'#'	},
-	{ XFUNC_transpose,		0, CTRL('T')	},
-	{ XFUNC_complete,		1, CTRL('[')	},
-	{ XFUNC_comp_list,		0, CTRL('I')	},
+	{ XFUNC_transpose,		0,  CTRL_T	},
+	{ XFUNC_complete,		1,  CTRL_BO	},
+	{ XFUNC_comp_list,		0,  CTRL_I	},
 	{ XFUNC_comp_list,		1,	'='	},
 	{ XFUNC_enumerate,		1,	'?'	},
 	{ XFUNC_expand,			1,	'*'	},
-	{ XFUNC_comp_file,		1, CTRL('X')	},
-	{ XFUNC_comp_comm,		2, CTRL('[')	},
+	{ XFUNC_comp_file,		1,  CTRL_X	},
+	{ XFUNC_comp_comm,		2,  CTRL_BO	},
 	{ XFUNC_list_comm,		2,	'?'	},
-	{ XFUNC_list_file,		2, CTRL('Y')	},
+	{ XFUNC_list_file,		2,  CTRL_Y	},
 	{ XFUNC_set_mark,		1,	' '	},
-	{ XFUNC_kill_region,		0, CTRL('W')	},
-	{ XFUNC_xchg_point_mark,	2, CTRL('X')	},
-	{ XFUNC_literal,		0, CTRL('V')	},
-	{ XFUNC_version,		1, CTRL('V')	},
+	{ XFUNC_kill_region,		0,  CTRL_W	},
+	{ XFUNC_xchg_point_mark,	2,  CTRL_X	},
+	{ XFUNC_literal,		0,  CTRL_V	},
+	{ XFUNC_version,		1,  CTRL_V	},
 	{ XFUNC_prev_histword,		1,	'.'	},
 	{ XFUNC_prev_histword,		1,	'_'	},
 	{ XFUNC_set_arg,		1,	'0'	},
@@ -1148,7 +1144,7 @@
 #endif
 #ifndef MKSH_SMALL
 	/* more non-standard ones */
-	{ XFUNC_eval_region,		1, CTRL('E')	},
+	{ XFUNC_eval_region,		1,  CTRL_E	},
 	{ XFUNC_edit_line,		2,	'e'	}
 #endif
 };
@@ -1191,17 +1187,19 @@
 	if (c == -1)
 		return (-1);
 	if (UTFMODE) {
-		if ((buf[0] >= 0xC2) && (buf[0] < 0xF0)) {
+		if ((rtt2asc(buf[0]) >= (unsigned char)0xC2) &&
+		    (rtt2asc(buf[0]) < (unsigned char)0xF0)) {
 			c = x_e_getc();
 			if (c == -1)
 				return (-1);
-			if ((c & 0xC0) != 0x80) {
+			if ((rtt2asc(c) & 0xC0) != 0x80) {
 				x_e_ungetc(c);
 				return (1);
 			}
 			buf[pos++] = c;
 		}
-		if ((buf[0] >= 0xE0) && (buf[0] < 0xF0)) {
+		if ((rtt2asc(buf[0]) >= (unsigned char)0xE0) &&
+		    (rtt2asc(buf[0]) < (unsigned char)0xF0)) {
 			/* XXX x_e_ungetc is one-octet only */
 			buf[pos++] = c = x_e_getc();
 			if (c == -1)
@@ -1299,9 +1297,7 @@
 			return (i);
 		case KINTR:
 			/* special case for interrupt */
-			trapsig(SIGINT);
-			x_mode(false);
-			unwind(LSHELL);
+			x_intr(SIGINT, c);
 		}
 		/* ad-hoc hack for fixing the cursor position */
 		x_goto(xcp);
@@ -1320,11 +1316,11 @@
 	if (c == 0) {
  invmbs:
 		left = 0;
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	if (UTFMODE) {
-		if (((c & 0xC0) == 0x80) && left) {
+		if (((rtt2asc(c) & 0xC0) == 0x80) && left) {
 			str[pos++] = c;
 			if (!--left) {
 				str[pos] = '\0';
@@ -1382,7 +1378,7 @@
 x_do_ins(const char *cp, size_t len)
 {
 	if (xep + len >= xend) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (-1);
 	}
 	memmove(xcp + len, xcp, xep - xcp + 1);
@@ -1422,7 +1418,7 @@
 	ssize_t i = 0;
 
 	if (xcp == xbuf) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	do {
@@ -1448,7 +1444,7 @@
 	}
 
 	if (!i) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	x_delete(i, false);
@@ -1558,15 +1554,15 @@
 	char *cp = xcp;
 
 	if (cp == xbuf) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (0);
 	}
 	while (x_arg--) {
-		while (cp != xbuf && is_mfs(cp[-1])) {
+		while (cp != xbuf && ctype(cp[-1], C_MFS)) {
 			cp--;
 			nb++;
 		}
-		while (cp != xbuf && !is_mfs(cp[-1])) {
+		while (cp != xbuf && !ctype(cp[-1], C_MFS)) {
 			cp--;
 			nb++;
 		}
@@ -1582,13 +1578,13 @@
 	char *cp = xcp;
 
 	if (cp == xep) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (0);
 	}
 	while (x_arg--) {
-		while (cp != xep && is_mfs(*cp))
+		while (cp != xep && ctype(*cp, C_MFS))
 			cp++;
-		while (cp != xep && !is_mfs(*cp))
+		while (cp != xep && !ctype(*cp, C_MFS))
 			cp++;
 	}
 	nc = x_nb2nc(cp - xcp);
@@ -1621,7 +1617,7 @@
 {
 	if (UTFMODE)
 		while ((!lower_bound || (cp > lower_bound)) &&
-		    ((*(unsigned char *)cp & 0xC0) == 0x80))
+		    ((rtt2asc(*cp) & 0xC0) == 0x80))
 			--cp;
 	return (cp);
 }
@@ -1642,14 +1638,14 @@
 {
 	uint8_t c = *(unsigned char *)cp;
 
-	if (UTFMODE && (c > 0x7F))
+	if (UTFMODE && (rtt2asc(c) > 0x7F))
 		return (utf_widthadj(cp, (const char **)dcp));
 	if (dcp)
 		*dcp = cp + 1;
 	if (c == '\t')
 		/* Kludge, tabs are always four spaces. */
 		return (4);
-	if (ISCTRL(c) && /* but not C1 */ c < 0x80)
+	if (ksh_isctrl(c))
 		/* control unsigned char */
 		return (2);
 	return (1);
@@ -1674,9 +1670,9 @@
 		/* Kludge, tabs are always four spaces. */
 		x_e_puts(T4spaces);
 		(*cp)++;
-	} else if (ISCTRL(c) && /* but not C1 */ c < 0x80) {
+	} else if (ksh_isctrl(c)) {
 		x_e_putc2('^');
-		x_e_putc2(UNCTRL(c));
+		x_e_putc2(ksh_unctrl(c));
 		(*cp)++;
 	} else
 		x_e_putc3((const char **)cp);
@@ -1686,7 +1682,7 @@
 x_mv_back(int c MKSH_A_UNUSED)
 {
 	if (xcp == xbuf) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	while (x_arg--) {
@@ -1703,7 +1699,7 @@
 	char *cp = xcp, *cp2;
 
 	if (xcp == xep) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	while (x_arg--) {
@@ -1724,13 +1720,13 @@
 
 	*xep = '\0';
 	if (x_e_getmbc(tmp) < 0) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	while (x_arg--) {
 		if ((cp = (cp == xep) ? NULL : strstr(cp + 1, tmp)) == NULL &&
 		    (cp = strstr(xbuf, tmp)) == NULL) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 	}
@@ -1745,7 +1741,7 @@
 	bool b;
 
 	if (x_e_getmbc(tmp) < 0) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	for (; x_arg--; cp = p)
@@ -1753,7 +1749,7 @@
 			if (p-- == xbuf)
 				p = xep;
 			if (p == cp) {
-				x_e_putc2(7);
+				x_e_putc2(KSH_BEL);
 				return (KSTD);
 			}
 			if ((tmp[1] && ((p+1) > xep)) ||
@@ -1789,7 +1785,7 @@
 	unsigned char tmp[1], *cp = tmp;
 
 	*tmp = isedchar(edchars.eof) ? (unsigned char)edchars.eof :
-	    (unsigned char)CTRL('D');
+	    (unsigned char)CTRL_D;
 	x_zotc3((char **)&cp);
 	x_putc('\r');
 	x_putc('\n');
@@ -1849,7 +1845,7 @@
 		sp = holdbufp;
 		modified = 0;
 	} else if (hp < history || hp > histptr) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return;
 	}
 	if (sp == NULL)
@@ -1859,7 +1855,7 @@
 		strlcpy(holdbufp, xbuf, LINE);
 	strlcpy(xbuf, sp, xend - xbuf);
 	xbp = xbuf;
-	xep = xcp = xbuf + strlen(xbuf);
+	xep = xcp = strnul(xbuf);
 	x_adjust();
 	modified = 0;
 }
@@ -1904,13 +1900,13 @@
 		if ((c = x_e_getc()) < 0)
 			return (KSTD);
 		f = x_tab[0][c];
-		if (c == CTRL('[')) {
+		if (c == CTRL_BO) {
 			if ((f & 0x7F) == XFUNC_meta1) {
 				if ((c = x_e_getc()) < 0)
 					return (KSTD);
 				f = x_tab[1][c] & 0x7F;
 				if (f == XFUNC_meta1 || f == XFUNC_meta2)
-					x_meta1(CTRL('['));
+					x_meta1(CTRL_BO);
 				x_e_ungetc(c);
 			}
 			break;
@@ -1942,7 +1938,7 @@
 			/* add char to pattern */
 			/* overflow check... */
 			if ((size_t)(p - pat) >= sizeof(pat) - 1) {
-				x_e_putc2(7);
+				x_e_putc2(KSH_BEL);
 				continue;
 			}
 			*p++ = c, *p = '\0';
@@ -1988,7 +1984,7 @@
 			return (i);
 		}
 	}
-	x_e_putc2(7);
+	x_e_putc2(KSH_BEL);
 	x_histp = histptr;
 	return (-1);
 }
@@ -2094,7 +2090,7 @@
 	int col;
 
 	if (lastch == ' ' && !line_was_cleared && x_term_mode == 1) {
-		shf_puts("\033[K", shl_out);
+		shf_puts(KSH_ESC_STRING "[K", shl_out);
 		line_was_cleared = true;
 	}
 	if (lastch == ' ' && line_was_cleared)
@@ -2168,11 +2164,11 @@
 	 * to the one they want.
 	 */
 	if (xcp == xbuf) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	} else if (xcp == xep || Flag(FGMACS)) {
 		if (xcp - xbuf == 1) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 		/*
@@ -2181,12 +2177,12 @@
 		 */
 		x_bs3(&xcp);
 		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 		x_bs3(&xcp);
 		if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 		utf_wctomb(xcp, tmpa);
@@ -2199,12 +2195,12 @@
 		 * cursor, move cursor position along one.
 		 */
 		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 		x_bs3(&xcp);
 		if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 		utf_wctomb(xcp, tmpa);
@@ -2313,21 +2309,35 @@
 	return (KSTD);
 }
 
-static int
-x_abort(int c MKSH_A_UNUSED)
+/* fake receiving an interrupt */
+static void
+x_intr(int signo, int c)
 {
-	/* x_zotc(c); */
+	x_vi_zotc(c);
+	*xep = '\0';
+	strip_nuls(xbuf, xep - xbuf);
+	if (*xbuf)
+		histsave(&source->line, xbuf, HIST_STORE, true);
 	xlp = xep = xcp = xbp = xbuf;
 	xlp_valid = true;
 	*xcp = 0;
 	x_modified();
+	x_flush();
+	trapsig(signo);
+	x_mode(false);
+	unwind(LSHELL);
+}
+
+static int
+x_abort(int c MKSH_A_UNUSED)
+{
 	return (KINTR);
 }
 
 static int
 x_error(int c MKSH_A_UNUSED)
 {
-	x_e_putc2(7);
+	x_e_putc2(KSH_BEL);
 	return (KSTD);
 }
 
@@ -2387,19 +2397,18 @@
 	strdupx(news, cp, ap);
 	op = news;
 	while (*cp) {
-		/* XXX -- should handle \^ escape? */
-		if (*cp == '^') {
+		switch (*cp) {
+		case '^':
 			cp++;
-			/*XXX or ^^ escape? this is ugly. */
-			if (*cp >= '?')
-				/* includes '?'; ASCII */
-				*op++ = CTRL(*cp);
-			else {
-				*op++ = '^';
-				cp--;
-			}
-		} else
+			*op++ = ksh_toctrl(*cp);
+			break;
+		case '\\':
+			if (cp[1] == '\\' || cp[1] == '^')
+				++cp;
+			/* FALLTHROUGH */
+		default:
 			*op++ = *cp;
+		}
 		cp++;
 	}
 	*op = '\0';
@@ -2412,9 +2421,9 @@
 {
 	char *p = *buf;
 
-	if (ISCTRL(c)) {
+	if (ksh_isctrl(c)) {
 		*p++ = '^';
-		*p++ = UNCTRL(c);
+		*p++ = ksh_unctrl(c);
 	} else
 		*p++ = c;
 	*p = 0;
@@ -2437,9 +2446,9 @@
 	int f = x_tab[prefix][key];
 
 	if (prefix)
-		/* prefix == 1 || prefix == 2 */
-		shf_puts(x_mapout(prefix == 1 ? CTRL('[') :
-		    prefix == 2 ? CTRL('X') : 0), shl_stdout);
+		/* prefix == 1 || prefix == 2 || prefix == 3 */
+		shf_puts(x_mapout(prefix == 1 ? CTRL_BO :
+		    prefix == 2 ? CTRL_X : 0), shl_stdout);
 #ifdef MKSH_SMALL
 	shprintf("%s = ", x_mapout(key));
 #else
@@ -2603,7 +2612,7 @@
 	char *xr;
 
 	if (xmp == NULL) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	if (xmp > xcp) {
@@ -2625,7 +2634,7 @@
 	char *tmp;
 
 	if (xmp == NULL) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	tmp = xmp;
@@ -2703,7 +2712,7 @@
 	    &start, &end, &words);
 
 	if (nwords == 0) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	x_goto(xbuf + start);
@@ -2713,7 +2722,7 @@
 	while (i < nwords) {
 		if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 ||
 		    (++i < nwords && x_ins(T1space) < 0)) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 	}
@@ -2737,7 +2746,7 @@
 	    &start, &end, &words);
 	/* no match */
 	if (nwords == 0) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return;
 	}
 	if (type == CT_LIST) {
@@ -2894,9 +2903,10 @@
 {
 	int width = 1;
 
-	if (c == '\r' || c == '\n')
+	if (ctype(c, C_CR | C_LF))
 		x_col = 0;
 	if (x_col < xx_cols) {
+#ifndef MKSH_EBCDIC
 		if (UTFMODE && (c > 0x7F)) {
 			char utf_tmp[3];
 			size_t x;
@@ -2911,9 +2921,10 @@
 				x_putc(utf_tmp[2]);
 			width = utf_wcwidth(c);
 		} else
+#endif
 			x_putc(c);
 		switch (c) {
-		case 7:
+		case KSH_BEL:
 			break;
 		case '\r':
 		case '\n':
@@ -2935,7 +2946,7 @@
 {
 	int width = 1, c = **(const unsigned char **)cp;
 
-	if (c == '\r' || c == '\n')
+	if (ctype(c, C_CR | C_LF))
 		x_col = 0;
 	if (x_col < xx_cols) {
 		if (UTFMODE && (c > 0x7F)) {
@@ -2944,7 +2955,13 @@
 			width = utf_widthadj(*cp, (const char **)&cp2);
 			if (cp2 == *cp + 1) {
 				(*cp)++;
+#ifdef MKSH_EBCDIC
+				x_putc(asc2rtt(0xEF));
+				x_putc(asc2rtt(0xBF));
+				x_putc(asc2rtt(0xBD));
+#else
 				shf_puts("\xEF\xBF\xBD", shl_out);
+#endif
 			} else
 				while (*cp < cp2)
 					x_putcf(*(*cp)++);
@@ -2953,7 +2970,7 @@
 			x_putc(c);
 		}
 		switch (c) {
-		case 7:
+		case KSH_BEL:
 			break;
 		case '\r':
 		case '\n':
@@ -2997,7 +3014,7 @@
 
 	/* strip command prefix */
 	c &= 255;
-	while (c >= 0 && ksh_isdigit(c)) {
+	while (c >= 0 && ctype(c, C_DIGIT)) {
 		n = n * 10 + ksh_numdig(c);
 		if (n > LINE)
 			/* upper bound for repeat */
@@ -3007,7 +3024,7 @@
 	}
 	if (c < 0 || first) {
  x_set_arg_too_big:
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		x_arg = 1;
 		x_arg_defaulted = true;
 	} else {
@@ -3026,7 +3043,7 @@
 	int ret = x_do_comment(xbuf, xend - xbuf, &len);
 
 	if (ret < 0)
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 	else {
 		x_modified();
 		xep = xbuf + len;
@@ -3049,7 +3066,7 @@
 	strdupx(v, KSH_VERSION, ATEMP);
 
 	xbuf = xbp = xcp = v;
-	xend = xep = v + strlen(v);
+	xend = xep = strnul(v);
 	x_redraw('\r');
 	x_flush();
 
@@ -3077,7 +3094,7 @@
 {
 	if (x_arg_defaulted) {
 		if (xep == xbuf) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 		if (modified) {
@@ -3092,7 +3109,7 @@
 		    "fc -e ${VISUAL:-${EDITOR:-vi}} --", x_arg);
 	else
 		strlcpy(xbuf, "fc -e ${VISUAL:-${EDITOR:-vi}} --", xend - xbuf);
-	xep = xbuf + strlen(xbuf);
+	xep = strnul(xbuf);
 	return (x_newline('\n'));
 }
 #endif
@@ -3132,7 +3149,7 @@
 		last_arg = x_arg_defaulted ? -1 : x_arg;
 	xhp = histptr - (m - 1);
 	if ((xhp < history) || !(cp = *xhp)) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		x_modified();
 		return (KSTD);
 	}
@@ -3144,11 +3161,11 @@
 		/*
 		 * ignore white-space after the last word
 		 */
-		while (rcp > cp && is_cfs(*rcp))
+		while (rcp > cp && ctype(*rcp, C_CFS))
 			rcp--;
-		while (rcp > cp && !is_cfs(*rcp))
+		while (rcp > cp && !ctype(*rcp, C_CFS))
 			rcp--;
-		if (is_cfs(*rcp))
+		if (ctype(*rcp, C_CFS))
 			rcp++;
 		x_ins(rcp);
 	} else {
@@ -3159,16 +3176,16 @@
 		/*
 		 * ignore white-space at start of line
 		 */
-		while (*rcp && is_cfs(*rcp))
+		while (*rcp && ctype(*rcp, C_CFS))
 			rcp++;
 		while (x_arg-- > 0) {
-			while (*rcp && !is_cfs(*rcp))
+			while (*rcp && !ctype(*rcp, C_CFS))
 				rcp++;
-			while (*rcp && is_cfs(*rcp))
+			while (*rcp && ctype(*rcp, C_CFS))
 				rcp++;
 		}
 		cp = rcp;
-		while (*rcp && !is_cfs(*rcp))
+		while (*rcp && !ctype(*rcp, C_CFS))
 			rcp++;
 		ch = *rcp;
 		*rcp = '\0';
@@ -3220,14 +3237,14 @@
 	char *cp = xcp;
 
 	if (cp == xep) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	while (x_arg--) {
 		/*
 		 * first skip over any white-space
 		 */
-		while (cp != xep && is_mfs(*cp))
+		while (cp != xep && ctype(*cp, C_MFS))
 			cp++;
 		/*
 		 * do the first char on its own since it may be
@@ -3245,7 +3262,7 @@
 		/*
 		 * now for the rest of the word
 		 */
-		while (cp != xep && !is_mfs(*cp)) {
+		while (cp != xep && !ctype(*cp, C_MFS)) {
 			if (c == 'U')
 				/* uppercase */
 				*cp = ksh_toupper(*cp);
@@ -3324,17 +3341,17 @@
 #endif
 
 		if (!edchars.erase)
-			edchars.erase = CTRL('H');
+			edchars.erase = CTRL_H;
 		if (!edchars.kill)
-			edchars.kill = CTRL('U');
+			edchars.kill = CTRL_U;
 		if (!edchars.intr)
-			edchars.intr = CTRL('C');
+			edchars.intr = CTRL_C;
 		if (!edchars.quit)
-			edchars.quit = CTRL('\\');
+			edchars.quit = CTRL_BK;
 		if (!edchars.eof)
-			edchars.eof = CTRL('D');
+			edchars.eof = CTRL_D;
 		if (!edchars.werase)
-			edchars.werase = CTRL('W');
+			edchars.werase = CTRL_W;
 
 		if (isedchar(edchars.erase)) {
 			bind_if_not_bound(0, edchars.erase, XFUNC_del_back);
@@ -3368,6 +3385,7 @@
 static int vi_insert(int);
 static int vi_cmd(int, const char *);
 static int domove(int, const char *, int);
+static int domovebeg(void);
 static int redo_insert(int);
 static void yank_range(int, int);
 static int bracktype(int);
@@ -3394,12 +3412,10 @@
 static int expand_word(int);
 static int complete_word(int, int);
 static int print_expansions(struct edstate *, int);
-#define char_len(c)	((ISCTRL((unsigned char)c) && \
-			/* but not C1 */ (unsigned char)c < 0x80) ? 2 : 1)
-static void x_vi_zotc(int);
 static void vi_error(void);
 static void vi_macro_reset(void);
 static int x_vi_putbuf(const char *, size_t);
+#define char_len(c) (ksh_isctrl(c) ? 2 : 1)
 
 #define vC	0x01		/* a valid command that isn't a vM, vE, vU */
 #define vM	0x02		/* movement command (h, l, etc.) */
@@ -3410,14 +3426,14 @@
 #define vZ	0x40		/* repeat count defaults to 0 (not 1) */
 #define vS	0x80		/* search (/, ?) */
 
-#define is_bad(c)	(classify[(c)&0x7f]&vB)
-#define is_cmd(c)	(classify[(c)&0x7f]&(vM|vE|vC|vU))
-#define is_move(c)	(classify[(c)&0x7f]&vM)
-#define is_extend(c)	(classify[(c)&0x7f]&vE)
-#define is_long(c)	(classify[(c)&0x7f]&vX)
-#define is_undoable(c)	(!(classify[(c)&0x7f]&vU))
-#define is_srch(c)	(classify[(c)&0x7f]&vS)
-#define is_zerocount(c)	(classify[(c)&0x7f]&vZ)
+#define is_bad(c)	(classify[rtt2asc(c) & 0x7F] & vB)
+#define is_cmd(c)	(classify[rtt2asc(c) & 0x7F] & (vM | vE | vC | vU))
+#define is_move(c)	(classify[rtt2asc(c) & 0x7F] & vM)
+#define is_extend(c)	(classify[rtt2asc(c) & 0x7F] & vE)
+#define is_long(c)	(classify[rtt2asc(c) & 0x7F] & vX)
+#define is_undoable(c)	(!(classify[rtt2asc(c) & 0x7F] & vU))
+#define is_srch(c)	(classify[rtt2asc(c) & 0x7F] & vS)
+#define is_zerocount(c)	(classify[rtt2asc(c) & 0x7F] & vZ)
 
 static const unsigned char classify[128] = {
 /*	 0	1	2	3	4	5	6	7	*/
@@ -3587,13 +3603,14 @@
 		if (state != VLIT) {
 			if (isched(c, edchars.intr) ||
 			    isched(c, edchars.quit)) {
+				/* shove input buffer away */
+				xbuf = ebuf.cbuf;
+				xep = xbuf;
+				if (ebuf.linelen > 0)
+					xep += ebuf.linelen;
 				/* pretend we got an interrupt */
-				x_vi_zotc(c);
-				x_flush();
-				trapsig(isched(c, edchars.intr) ?
-				    SIGINT : SIGQUIT);
-				x_mode(false);
-				unwind(LSHELL);
+				x_intr(isched(c, edchars.intr) ?
+				    SIGINT : SIGQUIT, c);
 			} else if (isched(c, edchars.eof) &&
 			    state != VVERSION) {
 				if (vs->linelen == 0) {
@@ -3646,7 +3663,7 @@
 		default: ch = 0; goto vi_insert_failed;
 		}
 		if (insert != 0) {
-			if (ch == CTRL('v')) {
+			if (ch == CTRL_V) {
 				state = VLIT;
 				ch = '^';
 			}
@@ -3667,11 +3684,11 @@
 				return (1);
 			}
 		} else {
-			if (ch == '\r' || ch == '\n')
+			if (ctype(ch, C_CR | C_LF))
 				return (1);
 			cmdlen = 0;
 			argc1 = 0;
-			if (ch >= ord('1') && ch <= ord('9')) {
+			if (ctype(ch, C_DIGIT) && ord(ch) != ord('0')) {
 				argc1 = ksh_numdig(ch);
 				state = VARG1;
 			} else {
@@ -3716,7 +3733,7 @@
 		break;
 
 	case VARG1:
-		if (ksh_isdigit(ch))
+		if (ctype(ch, C_DIGIT))
 			argc1 = argc1 * 10 + ksh_numdig(ch);
 		else {
 			curcmd[cmdlen++] = ch;
@@ -3726,7 +3743,7 @@
 
 	case VEXTCMD:
 		argc2 = 0;
-		if (ch >= ord('1') && ch <= ord('9')) {
+		if (ctype(ch, C_DIGIT) && ord(ch) != ord('0')) {
 			argc2 = ksh_numdig(ch);
 			state = VARG2;
 			return (0);
@@ -3742,7 +3759,7 @@
 		break;
 
 	case VARG2:
-		if (ksh_isdigit(ch))
+		if (ctype(ch, C_DIGIT))
 			argc2 = argc2 * 10 + ksh_numdig(ch);
 		else {
 			if (argc1 == 0)
@@ -3760,7 +3777,7 @@
 		break;
 
 	case VXCH:
-		if (ch == CTRL('['))
+		if (ch == CTRL_BO)
 			state = VNORMAL;
 		else {
 			curcmd[cmdlen++] = ch;
@@ -3769,7 +3786,7 @@
 		break;
 
 	case VSEARCH:
-		if (ch == '\r' || ch == '\n' /*|| ch == CTRL('[')*/ ) {
+		if (ctype(ch, C_CR | C_LF) /* || ch == CTRL_BO */ ) {
 			restore_cbuf();
 			/* Repeat last search? */
 			if (srchlen == 0) {
@@ -3784,7 +3801,7 @@
 				memcpy(srchpat, locpat, srchlen + 1);
 			}
 			state = VCMD;
-		} else if (isched(ch, edchars.erase) || ch == CTRL('h')) {
+		} else if (isched(ch, edchars.erase) || ch == CTRL_H) {
 			if (srchlen != 0) {
 				srchlen--;
 				vs->linelen -= char_len(locpat[srchlen]);
@@ -3825,12 +3842,12 @@
 				vi_error();
 			else {
 				locpat[srchlen++] = ch;
-				if (ISCTRL(ch) && /* but not C1 */ ch < 0x80) {
+				if (ksh_isctrl(ch)) {
 					if ((size_t)vs->linelen + 2 >
 					    (size_t)vs->cbufsize)
 						vi_error();
 					vs->cbuf[vs->linelen++] = '^';
-					vs->cbuf[vs->linelen++] = UNCTRL(ch);
+					vs->cbuf[vs->linelen++] = ksh_unctrl(ch);
 				} else {
 					if (vs->linelen >= vs->cbufsize)
 						vi_error();
@@ -3903,8 +3920,8 @@
 			break;
 		case 0:
 			if (insert != 0) {
-				if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
-				    lastcmd[0] == 'C') {
+				if (lastcmd[0] == 's' ||
+				    ksh_eq(lastcmd[0], 'C', 'c')) {
 					if (redo_insert(1) != 0)
 						vi_error();
 				} else {
@@ -3942,7 +3959,7 @@
 		return (VXCH);
 	else if (ch == '.')
 		return (VREDO);
-	else if (ch == CTRL('v'))
+	else if (ch == CTRL_V)
 		return (VVERSION);
 	else if (is_cmd(ch))
 		return (VCMD);
@@ -3955,7 +3972,7 @@
 {
 	int tcursor;
 
-	if (isched(ch, edchars.erase) || ch == CTRL('h')) {
+	if (isched(ch, edchars.erase) || ch == CTRL_H) {
 		if (insert == REPLACE) {
 			if (vs->cursor == undo->cursor) {
 				vi_error();
@@ -4012,7 +4029,7 @@
 	 * buffer (if user inserts & deletes char, ibuf gets trashed and
 	 * we don't want to use it)
 	 */
-	if (first_insert && ch != CTRL('['))
+	if (first_insert && ch != CTRL_BO)
 		saved_inslen = 0;
 	switch (ch) {
 	case '\0':
@@ -4022,7 +4039,7 @@
 	case '\n':
 		return (1);
 
-	case CTRL('['):
+	case CTRL_BO:
 		expanded = NONE;
 		if (first_insert) {
 			first_insert = false;
@@ -4033,26 +4050,25 @@
 			lastcmd[0] = 'a';
 			lastac = 1;
 		}
-		if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
-		    lastcmd[0] == 'C')
+		if (lastcmd[0] == 's' || ksh_eq(lastcmd[0], 'C', 'c'))
 			return (redo_insert(0));
 		else
 			return (redo_insert(lastac - 1));
 
 	/* { start nonstandard vi commands */
-	case CTRL('x'):
+	case CTRL_X:
 		expand_word(0);
 		break;
 
-	case CTRL('f'):
+	case CTRL_F:
 		complete_word(0, 0);
 		break;
 
-	case CTRL('e'):
+	case CTRL_E:
 		print_expansions(vs, 0);
 		break;
 
-	case CTRL('i'):
+	case CTRL_I:
 		if (Flag(FVITABCOMPLETE)) {
 			complete_word(0, 0);
 			break;
@@ -4105,14 +4121,14 @@
 			lastac = argcnt;
 			memmove(lastcmd, cmd, MAXVICMD);
 		}
-		switch (*cmd) {
+		switch (ord(*cmd)) {
 
-		case CTRL('l'):
-		case CTRL('r'):
+		case CTRL_L:
+		case CTRL_R:
 			redraw_line(true);
 			break;
 
-		case '@':
+		case ord('@'):
 			{
 				static char alias[] = "_\0";
 				struct tbl *ap;
@@ -4153,7 +4169,7 @@
 			}
 			break;
 
-		case 'a':
+		case ord('a'):
 			modified = 1;
 			hnum = hlast;
 			if (vs->linelen != 0)
@@ -4161,7 +4177,7 @@
 			insert = INSERT;
 			break;
 
-		case 'A':
+		case ord('A'):
 			modified = 1;
 			hnum = hlast;
 			del_range(0, 0);
@@ -4169,36 +4185,35 @@
 			insert = INSERT;
 			break;
 
-		case 'S':
-			vs->cursor = domove(1, "^", 1);
+		case ord('S'):
+			vs->cursor = domovebeg();
 			del_range(vs->cursor, vs->linelen);
 			modified = 1;
 			hnum = hlast;
 			insert = INSERT;
 			break;
 
-		case 'Y':
+		case ord('Y'):
 			cmd = "y$";
 			/* ahhhhhh... */
 
 			/* FALLTHROUGH */
-		case 'c':
-		case 'd':
-		case 'y':
+		case ord('c'):
+		case ord('d'):
+		case ord('y'):
 			if (*cmd == cmd[1]) {
-				c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
+				c1 = *cmd == 'c' ? domovebeg() : 0;
 				c2 = vs->linelen;
 			} else if (!is_move(cmd[1]))
 				return (-1);
 			else {
 				if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
 					return (-1);
-				if (*cmd == 'c' &&
-				    (cmd[1] == 'w' || cmd[1] == 'W') &&
-				    !ksh_isspace(vs->cbuf[vs->cursor])) {
+				if (*cmd == 'c' && ksh_eq(cmd[1], 'W', 'w') &&
+				    !ctype(vs->cbuf[vs->cursor], C_SPACE)) {
 					do {
 						--ncursor;
-					} while (ksh_isspace(vs->cbuf[ncursor]));
+					} while (ctype(vs->cbuf[ncursor], C_SPACE));
 					ncursor++;
 				}
 				if (ncursor > vs->cursor) {
@@ -4224,7 +4239,7 @@
 			}
 			break;
 
-		case 'p':
+		case ord('p'):
 			modified = 1;
 			hnum = hlast;
 			if (vs->linelen != 0)
@@ -4238,7 +4253,7 @@
 				return (-1);
 			break;
 
-		case 'P':
+		case ord('P'):
 			modified = 1;
 			hnum = hlast;
 			any = 0;
@@ -4251,25 +4266,25 @@
 				return (-1);
 			break;
 
-		case 'C':
+		case ord('C'):
 			modified = 1;
 			hnum = hlast;
 			del_range(vs->cursor, vs->linelen);
 			insert = INSERT;
 			break;
 
-		case 'D':
+		case ord('D'):
 			yank_range(vs->cursor, vs->linelen);
 			del_range(vs->cursor, vs->linelen);
 			if (vs->cursor != 0)
 				vs->cursor--;
 			break;
 
-		case 'g':
+		case ord('g'):
 			if (!argcnt)
 				argcnt = hlast;
 			/* FALLTHROUGH */
-		case 'G':
+		case ord('G'):
 			if (!argcnt)
 				argcnt = 1;
 			else
@@ -4282,22 +4297,22 @@
 			}
 			break;
 
-		case 'i':
+		case ord('i'):
 			modified = 1;
 			hnum = hlast;
 			insert = INSERT;
 			break;
 
-		case 'I':
+		case ord('I'):
 			modified = 1;
 			hnum = hlast;
-			vs->cursor = domove(1, "^", 1);
+			vs->cursor = domovebeg();
 			insert = INSERT;
 			break;
 
-		case 'j':
-		case '+':
-		case CTRL('n'):
+		case ord('j'):
+		case ord('+'):
+		case CTRL_N:
 			if (grabhist(modified, hnum + argcnt) < 0)
 				return (-1);
 			else {
@@ -4306,9 +4321,9 @@
 			}
 			break;
 
-		case 'k':
-		case '-':
-		case CTRL('p'):
+		case ord('k'):
+		case ord('-'):
+		case CTRL_P:
 			if (grabhist(modified, hnum - argcnt) < 0)
 				return (-1);
 			else {
@@ -4317,7 +4332,7 @@
 			}
 			break;
 
-		case 'r':
+		case ord('r'):
 			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
@@ -4335,13 +4350,13 @@
 			}
 			break;
 
-		case 'R':
+		case ord('R'):
 			modified = 1;
 			hnum = hlast;
 			insert = REPLACE;
 			break;
 
-		case 's':
+		case ord('s'):
 			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
@@ -4352,7 +4367,7 @@
 			insert = INSERT;
 			break;
 
-		case 'v':
+		case ord('v'):
 			if (!argcnt) {
 				if (vs->linelen == 0)
 					return (-1);
@@ -4375,7 +4390,7 @@
 			vs->linelen = strlen(vs->cbuf);
 			return (2);
 
-		case 'x':
+		case ord('x'):
 			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
@@ -4386,7 +4401,7 @@
 			del_range(vs->cursor, vs->cursor + argcnt);
 			break;
 
-		case 'X':
+		case ord('X'):
 			if (vs->cursor > 0) {
 				modified = 1;
 				hnum = hlast;
@@ -4399,13 +4414,13 @@
 				return (-1);
 			break;
 
-		case 'u':
+		case ord('u'):
 			t = vs;
 			vs = undo;
 			undo = t;
 			break;
 
-		case 'U':
+		case ord('U'):
 			if (!modified)
 				return (-1);
 			if (grabhist(modified, ohnum) < 0)
@@ -4414,19 +4429,19 @@
 			hnum = ohnum;
 			break;
 
-		case '?':
+		case ord('?'):
 			if (hnum == hlast)
 				hnum = -1;
 			/* ahhh */
 
 			/* FALLTHROUGH */
-		case '/':
+		case ord('/'):
 			c3 = 1;
 			srchlen = 0;
 			lastsearch = *cmd;
 			/* FALLTHROUGH */
-		case 'n':
-		case 'N':
+		case ord('n'):
+		case ord('N'):
 			if (lastsearch == ' ')
 				return (-1);
 			if (lastsearch == '?')
@@ -4453,7 +4468,7 @@
 				return (0);
 			}
 			break;
-		case '_':
+		case ord('_'):
 			{
 				bool inspace;
 				char *p, *sp;
@@ -4461,14 +4476,13 @@
 				if (histnum(-1) < 0)
 					return (-1);
 				p = *histpos();
-#define issp(c)		(ksh_isspace(c) || (c) == '\n')
 				if (argcnt) {
-					while (*p && issp(*p))
+					while (ctype(*p, C_SPACE))
 						p++;
 					while (*p && --argcnt) {
-						while (*p && !issp(*p))
+						while (*p && !ctype(*p, C_SPACE))
 							p++;
-						while (*p && issp(*p))
+						while (ctype(*p, C_SPACE))
 							p++;
 					}
 					if (!*p)
@@ -4478,7 +4492,7 @@
 					sp = p;
 					inspace = false;
 					while (*p) {
-						if (issp(*p))
+						if (ctype(*p, C_SPACE))
 							inspace = true;
 						else if (inspace) {
 							inspace = false;
@@ -4492,7 +4506,7 @@
 				hnum = hlast;
 				if (vs->cursor != vs->linelen)
 					vs->cursor++;
-				while (*p && !issp(*p)) {
+				while (*p && !ctype(*p, C_SPACE)) {
 					argcnt++;
 					p++;
 				}
@@ -4506,7 +4520,7 @@
 			}
 			break;
 
-		case '~':
+		case ord('~'):
 			{
 				char *p;
 				int i;
@@ -4515,11 +4529,11 @@
 					return (-1);
 				for (i = 0; i < argcnt; i++) {
 					p = &vs->cbuf[vs->cursor];
-					if (ksh_islower(*p)) {
+					if (ctype(*p, C_LOWER)) {
 						modified = 1;
 						hnum = hlast;
 						*p = ksh_toupper(*p);
-					} else if (ksh_isupper(*p)) {
+					} else if (ctype(*p, C_UPPER)) {
 						modified = 1;
 						hnum = hlast;
 						*p = ksh_tolower(*p);
@@ -4530,7 +4544,7 @@
 				break;
 			}
 
-		case '#':
+		case ord('#'):
 			{
 				int ret = x_do_comment(vs->cbuf, vs->cbufsize,
 				    &vs->linelen);
@@ -4540,44 +4554,44 @@
 			}
 
 		/* AT&T ksh */
-		case '=':
+		case ord('='):
 		/* Nonstandard vi/ksh */
-		case CTRL('e'):
+		case CTRL_E:
 			print_expansions(vs, 1);
 			break;
 
 
 		/* Nonstandard vi/ksh */
-		case CTRL('i'):
+		case CTRL_I:
 			if (!Flag(FVITABCOMPLETE))
 				return (-1);
 			complete_word(1, argcnt);
 			break;
 
 		/* some annoying AT&T kshs */
-		case CTRL('['):
+		case CTRL_BO:
 			if (!Flag(FVIESCCOMPLETE))
 				return (-1);
 			/* FALLTHROUGH */
 		/* AT&T ksh */
-		case '\\':
+		case ord('\\'):
 		/* Nonstandard vi/ksh */
-		case CTRL('f'):
+		case CTRL_F:
 			complete_word(1, argcnt);
 			break;
 
 
 		/* AT&T ksh */
-		case '*':
+		case ord('*'):
 		/* Nonstandard vi/ksh */
-		case CTRL('x'):
+		case CTRL_X:
 			expand_word(1);
 			break;
 
 
 		/* mksh: cursor movement */
-		case '[':
-		case 'O':
+		case ord('['):
+		case ord('O'):
 			state = VPREFIX2;
 			if (vs->linelen != 0)
 				vs->cursor++;
@@ -4596,20 +4610,20 @@
 	int ncursor = 0, i = 0, t;
 	unsigned int bcount;
 
-	switch (*cmd) {
-	case 'b':
+	switch (ord(*cmd)) {
+	case ord('b'):
 		if (!sub && vs->cursor == 0)
 			return (-1);
 		ncursor = backword(argcnt);
 		break;
 
-	case 'B':
+	case ord('B'):
 		if (!sub && vs->cursor == 0)
 			return (-1);
 		ncursor = Backword(argcnt);
 		break;
 
-	case 'e':
+	case ord('e'):
 		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = endword(argcnt);
@@ -4617,7 +4631,7 @@
 			ncursor++;
 		break;
 
-	case 'E':
+	case ord('E'):
 		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = Endword(argcnt);
@@ -4625,18 +4639,18 @@
 			ncursor++;
 		break;
 
-	case 'f':
-	case 'F':
-	case 't':
-	case 'T':
+	case ord('f'):
+	case ord('F'):
+	case ord('t'):
+	case ord('T'):
 		fsavecmd = *cmd;
 		fsavech = cmd[1];
 		/* FALLTHROUGH */
-	case ',':
-	case ';':
+	case ord(','):
+	case ord(';'):
 		if (fsavecmd == ' ')
 			return (-1);
-		i = fsavecmd == 'f' || fsavecmd == 'F';
+		i = ksh_eq(fsavecmd, 'F', 'f');
 		t = fsavecmd > 'a';
 		if (*cmd == ',')
 			t = !t;
@@ -4647,8 +4661,8 @@
 			ncursor++;
 		break;
 
-	case 'h':
-	case CTRL('h'):
+	case ord('h'):
+	case CTRL_H:
 		if (!sub && vs->cursor == 0)
 			return (-1);
 		ncursor = vs->cursor - argcnt;
@@ -4656,8 +4670,8 @@
 			ncursor = 0;
 		break;
 
-	case ' ':
-	case 'l':
+	case ord(' '):
+	case ord('l'):
 		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		if (vs->linelen != 0) {
@@ -4667,30 +4681,27 @@
 		}
 		break;
 
-	case 'w':
+	case ord('w'):
 		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = forwword(argcnt);
 		break;
 
-	case 'W':
+	case ord('W'):
 		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = Forwword(argcnt);
 		break;
 
-	case '0':
+	case ord('0'):
 		ncursor = 0;
 		break;
 
-	case '^':
-		ncursor = 0;
-		while (ncursor < vs->linelen - 1 &&
-		    ksh_isspace(vs->cbuf[ncursor]))
-			ncursor++;
+	case ord('^'):
+		ncursor = domovebeg();
 		break;
 
-	case '|':
+	case ord('|'):
 		ncursor = argcnt;
 		if (ncursor > vs->linelen)
 			ncursor = vs->linelen;
@@ -4698,14 +4709,14 @@
 			ncursor--;
 		break;
 
-	case '$':
+	case ord('$'):
 		if (vs->linelen != 0)
 			ncursor = vs->linelen;
 		else
 			ncursor = 0;
 		break;
 
-	case '%':
+	case ord('%'):
 		ncursor = vs->cursor;
 		while (ncursor < vs->linelen &&
 		    (i = bracktype(vs->cbuf[ncursor])) == 0)
@@ -4738,6 +4749,17 @@
 }
 
 static int
+domovebeg(void)
+{
+	int ncursor = 0;
+
+	while (ncursor < vs->linelen - 1 &&
+	    ctype(vs->cbuf[ncursor], C_SPACE))
+		ncursor++;
+	return (ncursor);
+}
+
+static int
 redo_insert(int count)
 {
 	while (count-- > 0)
@@ -4760,24 +4782,24 @@
 static int
 bracktype(int ch)
 {
-	switch (ch) {
+	switch (ord(ch)) {
 
-	case '(':
+	case ord('('):
 		return (1);
 
-	case '[':
+	case ord('['):
 		return (2);
 
-	case '{':
+	case ord('{'):
 		return (3);
 
-	case ')':
+	case ord(')'):
 		return (-1);
 
-	case ']':
+	case ord(']'):
 		return (-2);
 
-	case '}':
+	case ord('}'):
 		return (-3);
 
 	default:
@@ -4912,17 +4934,16 @@
 
 	ncursor = vs->cursor;
 	while (ncursor < vs->linelen && argcnt--) {
-		if (ksh_isalnux(vs->cbuf[ncursor]))
+		if (ctype(vs->cbuf[ncursor], C_ALNUX))
 			while (ncursor < vs->linelen &&
-			    ksh_isalnux(vs->cbuf[ncursor]))
+			    ctype(vs->cbuf[ncursor], C_ALNUX))
 				ncursor++;
-		else if (!ksh_isspace(vs->cbuf[ncursor]))
+		else if (!ctype(vs->cbuf[ncursor], C_SPACE))
 			while (ncursor < vs->linelen &&
-			    !ksh_isalnux(vs->cbuf[ncursor]) &&
-			    !ksh_isspace(vs->cbuf[ncursor]))
+			    !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
 				ncursor++;
 		while (ncursor < vs->linelen &&
-		    ksh_isspace(vs->cbuf[ncursor]))
+		    ctype(vs->cbuf[ncursor], C_SPACE))
 			ncursor++;
 	}
 	return (ncursor);
@@ -4935,17 +4956,16 @@
 
 	ncursor = vs->cursor;
 	while (ncursor > 0 && argcnt--) {
-		while (--ncursor > 0 && ksh_isspace(vs->cbuf[ncursor]))
+		while (--ncursor > 0 && ctype(vs->cbuf[ncursor], C_SPACE))
 			;
 		if (ncursor > 0) {
-			if (ksh_isalnux(vs->cbuf[ncursor]))
+			if (ctype(vs->cbuf[ncursor], C_ALNUX))
 				while (--ncursor >= 0 &&
-				    ksh_isalnux(vs->cbuf[ncursor]))
+				    ctype(vs->cbuf[ncursor], C_ALNUX))
 					;
 			else
 				while (--ncursor >= 0 &&
-				    !ksh_isalnux(vs->cbuf[ncursor]) &&
-				    !ksh_isspace(vs->cbuf[ncursor]))
+				    !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
 					;
 			ncursor++;
 		}
@@ -4961,17 +4981,16 @@
 	ncursor = vs->cursor;
 	while (ncursor < vs->linelen && argcnt--) {
 		while (++ncursor < vs->linelen - 1 &&
-		    ksh_isspace(vs->cbuf[ncursor]))
+		    ctype(vs->cbuf[ncursor], C_SPACE))
 			;
 		if (ncursor < vs->linelen - 1) {
-			if (ksh_isalnux(vs->cbuf[ncursor]))
+			if (ctype(vs->cbuf[ncursor], C_ALNUX))
 				while (++ncursor < vs->linelen &&
-				    ksh_isalnux(vs->cbuf[ncursor]))
+				    ctype(vs->cbuf[ncursor], C_ALNUX))
 					;
 			else
 				while (++ncursor < vs->linelen &&
-				    !ksh_isalnux(vs->cbuf[ncursor]) &&
-				    !ksh_isspace(vs->cbuf[ncursor]))
+				    !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
 					;
 			ncursor--;
 		}
@@ -4987,10 +5006,10 @@
 	ncursor = vs->cursor;
 	while (ncursor < vs->linelen && argcnt--) {
 		while (ncursor < vs->linelen &&
-		    !ksh_isspace(vs->cbuf[ncursor]))
+		    !ctype(vs->cbuf[ncursor], C_SPACE))
 			ncursor++;
 		while (ncursor < vs->linelen &&
-		    ksh_isspace(vs->cbuf[ncursor]))
+		    ctype(vs->cbuf[ncursor], C_SPACE))
 			ncursor++;
 	}
 	return (ncursor);
@@ -5003,9 +5022,9 @@
 
 	ncursor = vs->cursor;
 	while (ncursor > 0 && argcnt--) {
-		while (--ncursor >= 0 && ksh_isspace(vs->cbuf[ncursor]))
+		while (--ncursor >= 0 && ctype(vs->cbuf[ncursor], C_SPACE))
 			;
-		while (ncursor >= 0 && !ksh_isspace(vs->cbuf[ncursor]))
+		while (ncursor >= 0 && !ctype(vs->cbuf[ncursor], C_SPACE))
 			ncursor--;
 		ncursor++;
 	}
@@ -5020,11 +5039,11 @@
 	ncursor = vs->cursor;
 	while (ncursor < vs->linelen - 1 && argcnt--) {
 		while (++ncursor < vs->linelen - 1 &&
-		    ksh_isspace(vs->cbuf[ncursor]))
+		    ctype(vs->cbuf[ncursor], C_SPACE))
 			;
 		if (ncursor < vs->linelen - 1) {
 			while (++ncursor < vs->linelen &&
-			    !ksh_isspace(vs->cbuf[ncursor]))
+			    !ctype(vs->cbuf[ncursor], C_SPACE))
 				;
 			ncursor--;
 		}
@@ -5187,10 +5206,10 @@
 				*twb1++ = ' ';
 			} while (++col < winwidth && (col & 7) != 0);
 		else if (col < winwidth) {
-			if (ISCTRL(ch) && /* but not C1 */ ch < 0x80) {
+			if (ksh_isctrl(ch)) {
 				*twb1++ = '^';
 				if (++col < winwidth) {
-					*twb1++ = UNCTRL(ch);
+					*twb1++ = ksh_unctrl(ch);
 					col++;
 				}
 			} else {
@@ -5460,24 +5479,26 @@
 	redraw_line(false);
 	return (0);
 }
+#endif /* !MKSH_S_NOVI */
 
 /* Similar to x_zotc(emacs.c), but no tab weirdness */
 static void
 x_vi_zotc(int c)
 {
-	if (ISCTRL(c)) {
+	if (ksh_isctrl(c)) {
 		x_putc('^');
-		c = UNCTRL(c);
+		c = ksh_unctrl(c);
 	}
 	x_putc(c);
 }
 
+#if !MKSH_S_NOVI
 static void
 vi_error(void)
 {
 	/* Beem out of any macros as soon as an error occurs */
 	vi_macro_reset();
-	x_putc(7);
+	x_putc(KSH_BEL);
 	x_flush();
 }
 
@@ -5602,7 +5623,7 @@
 	if (cp == NULL) {
 		/* command cannot be parsed */
  x_eval_region_err:
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		x_redraw('\r');
 		return (KSTD);
 	}
diff --git a/src/eval.c b/src/eval.c
index 23894d6..7a892c0 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.201 2017/04/06 01:59:54 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.215 2017/08/28 23:27:51 tg Exp $");
 
 /*
  * string expansion
@@ -65,6 +65,12 @@
 #define IFS_IWS		3	/* beginning of word, ignore IFS WS */
 #define IFS_QUOTE	4	/* beg.w/quote, become IFS_WORD unless "$@" */
 
+#define STYPE_CHAR	0xFF
+#define STYPE_DBL	0x100
+#define STYPE_AT	0x200
+#define STYPE_SINGLE	0x2FF
+#define STYPE_MASK	0x300
+
 static int varsub(Expand *, const char *, const char *, int *, int *);
 static int comsub(Expand *, const char *, int);
 static char *valsub(struct op *, Area *);
@@ -277,18 +283,18 @@
 		switch (type) {
 		case XBASE:
 			/* original prefixed string */
-			c = *sp++;
+			c = ord(*sp++);
 			switch (c) {
 			case EOS:
 				c = 0;
 				break;
 			case CHAR:
-				c = *sp++;
+				c = ord(*sp++);
 				break;
 			case QCHAR:
 				/* temporary quote */
 				quote |= 2;
-				c = *sp++;
+				c = ord(*sp++);
 				break;
 			case OQUOTE:
 				if (word != IFS_WORD)
@@ -314,21 +320,21 @@
 					case COMASUB:
 					case COMSUB:
 						*dp++ = '(';
-						c = ')';
+						c = ord(')');
 						break;
 					case FUNASUB:
 					case FUNSUB:
 					case VALSUB:
 						*dp++ = '{';
 						*dp++ = c == VALSUB ? '|' : ' ';
-						c = '}';
+						c = ord('}');
 						break;
 					}
 					while (*sp != '\0') {
 						Xcheck(ds, dp);
 						*dp++ = *sp++;
 					}
-					if (c == '}')
+					if (c == ord('}'))
 						*dp++ = ';';
 					*dp++ = c;
 				} else {
@@ -429,12 +435,12 @@
 					/* skip qualifier(s) */
 					if (stype)
 						sp += slen;
-					switch (stype & 0x17F) {
-					case 0x100 | '#':
+					switch (stype & STYPE_SINGLE) {
+					case ord('#') | STYPE_AT:
 						x.str = shf_smprintf("%08X",
 						    (unsigned int)hash(str_val(st->var)));
 						break;
-					case 0x100 | 'Q': {
+					case ord('Q') | STYPE_AT: {
 						struct shf shf;
 
 						shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf);
@@ -442,7 +448,7 @@
 						x.str = shf_sclose(&shf);
 						break;
 					    }
-					case '0': {
+					case ord('0'): {
 						char *beg, *mid, *end, *stg;
 						mksh_ari_t from = 0, num = -1, flen, finc = 0;
 
@@ -450,13 +456,13 @@
 						mid = beg + (wdscan(sp, ADELIM) - sp);
 						stg = beg + (wdscan(sp, CSUBST) - sp);
 						mid[-2] = EOS;
-						if (mid[-1] == /*{*/'}') {
+						if (ord(mid[-1]) == ord(/*{*/ '}')) {
 							sp += mid - beg - 1;
 							end = NULL;
 						} else {
 							end = mid +
 							    (wdscan(mid, ADELIM) - mid);
-							if (end[-1] != /*{*/ '}')
+							if (ord(end[-1]) != ord(/*{*/ '}'))
 								/* more than max delimiters */
 								goto unwind_substsyn;
 							end[-2] = EOS;
@@ -489,8 +495,8 @@
 						strndupx(x.str, beg, num, ATEMP);
 						goto do_CSUBST;
 					    }
-					case 0x100 | '/':
-					case '/': {
+					case ord('/') | STYPE_AT:
+					case ord('/'): {
 						char *s, *p, *d, *sbeg, *end;
 						char *pat = NULL, *rrep = null;
 						char fpat = 0, *tpat1, *tpat2;
@@ -500,18 +506,18 @@
 						p = s + (wdscan(sp, ADELIM) - sp);
 						d = s + (wdscan(sp, CSUBST) - sp);
 						p[-2] = EOS;
-						if (p[-1] == /*{*/'}')
+						if (ord(p[-1]) == ord(/*{*/ '}'))
 							d = NULL;
 						else
 							d[-2] = EOS;
 						sp += (d ? d : p) - s - 1;
-						if (!(stype & 0x180) &&
+						if (!(stype & STYPE_MASK) &&
 						    s[0] == CHAR &&
-						    (s[1] == '#' || s[1] == '%'))
+						    ctype(s[1], C_SUB2))
 							fpat = s[1];
 						wpat = s + (fpat ? 2 : 0);
 						wrep = d ? p : NULL;
-						if (!(stype & 0x100)) {
+						if (!(stype & STYPE_AT)) {
 							rrep = wrep ? evalstr(wrep,
 							    DOTILDE | DOSCALAR) :
 							    null;
@@ -531,21 +537,21 @@
 							 */
 							goto no_repl;
 						}
-						if ((stype & 0x180) &&
+						if ((stype & STYPE_MASK) &&
 						    gmatchx(null, pat, false)) {
 							/*
 							 * pattern matches empty
 							 * string => don't loop
 							 */
-							stype &= ~0x180;
+							stype &= ~STYPE_MASK;
 						}
 
 						/* first see if we have any match at all */
-						if (fpat == '#') {
+						if (ord(fpat) == ord('#')) {
 							/* anchor at the beginning */
 							tpat1 = shf_smprintf("%s%c*", pat, MAGIC);
 							tpat2 = tpat1;
-						} else if (fpat == '%') {
+						} else if (ord(fpat) == ord('%')) {
 							/* anchor at the end */
 							tpat1 = shf_smprintf("%c*%s", MAGIC, pat);
 							tpat2 = pat;
@@ -563,7 +569,7 @@
 							goto end_repl;
 						end = strnul(s);
 						/* now anchor the beginning of the match */
-						if (fpat != '#')
+						if (ord(fpat) != ord('#'))
 							while (sbeg <= end) {
 								if (gmatchx(sbeg, tpat2, false))
 									break;
@@ -572,7 +578,7 @@
 							}
 						/* now anchor the end of the match */
 						p = end;
-						if (fpat != '%')
+						if (ord(fpat) != ord('%'))
 							while (p >= sbeg) {
 								bool gotmatch;
 
@@ -587,7 +593,7 @@
 						strndupx(end, sbeg, p - sbeg, ATEMP);
 						record_match(end);
 						afree(end, ATEMP);
-						if (stype & 0x100) {
+						if (stype & STYPE_AT) {
 							if (rrep != null)
 								afree(rrep, ATEMP);
 							rrep = wrep ? evalstr(wrep,
@@ -600,11 +606,11 @@
 						sbeg = d + (sbeg - s) + strlen(rrep);
 						afree(s, ATEMP);
 						s = d;
-						if (stype & 0x100) {
+						if (stype & STYPE_AT) {
 							afree(tpat1, ATEMP);
 							afree(pat, ATEMP);
 							goto again_search;
-						} else if (stype & 0x80)
+						} else if (stype & STYPE_DBL)
 							goto again_repl;
  end_repl:
 						afree(tpat1, ATEMP);
@@ -616,8 +622,8 @@
 						afree(ws, ATEMP);
 						goto do_CSUBST;
 					    }
-					case '#':
-					case '%':
+					case ord('#'):
+					case ord('%'):
 						/* ! DOBLANK,DOBRACE */
 						f = (f & DONTRUNCOMMAND) |
 						    DOPAT | DOTILDE |
@@ -631,10 +637,10 @@
 						 */
 						if (!Flag(FSH)) {
 							*dp++ = MAGIC;
-							*dp++ = 0x80 | '@';
+							*dp++ = ord(0x80 | '@');
 						}
 						break;
-					case '=':
+					case ord('='):
 						/*
 						 * Tilde expansion for string
 						 * variables in POSIX mode is
@@ -658,7 +664,7 @@
 						f &= ~(DOBLANK|DOGLOB|DOBRACE);
 						tilde_ok = 1;
 						break;
-					case '?':
+					case ord('?'):
 						if (*sp == CSUBST)
 							errorf("%s: parameter null or not set",
 							    st->var->name);
@@ -692,9 +698,9 @@
 				f = st->f;
 				if (f & DOBLANK)
 					doblank--;
-				switch (st->stype & 0x17F) {
-				case '#':
-				case '%':
+				switch (st->stype & STYPE_SINGLE) {
+				case ord('#'):
+				case ord('%'):
 					if (!Flag(FSH)) {
 						/* Append end-pattern */
 						*dp++ = MAGIC;
@@ -724,7 +730,7 @@
 						doblank++;
 					st = st->prev;
 					continue;
-				case '=':
+				case ord('='):
 					/*
 					 * Restore our position and substitute
 					 * the value of st->var (may not be
@@ -757,17 +763,17 @@
 					st = st->prev;
 					word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS;
 					continue;
-				case '?':
+				case ord('?'):
 					dp = Xrestpos(ds, dp, st->base);
 
 					errorf(Tf_sD_s, st->var->name,
 					    debunk(dp, dp, strlen(dp) + 1));
 					break;
-				case '0':
-				case 0x100 | '/':
-				case '/':
-				case 0x100 | '#':
-				case 0x100 | 'Q':
+				case ord('0'):
+				case ord('/') | STYPE_AT:
+				case ord('/'):
+				case ord('#') | STYPE_AT:
+				case ord('Q') | STYPE_AT:
 					dp = Xrestpos(ds, dp, st->base);
 					type = XSUB;
 					word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS;
@@ -845,7 +851,7 @@
 						doblank--;
 					continue;
 				}
-				c = ifs0;
+				c = ord(ifs0);
 				if ((f & DOHEREDOC)) {
 					/* pseudo-field-split reliably */
 					if (c == 0)
@@ -891,10 +897,7 @@
 				--newlines;
 			} else {
 				while ((c = shf_getc(x.u.shf)) == 0 ||
-#ifdef MKSH_WITH_TEXTMODE
-				       c == '\r' ||
-#endif
-				       c == '\n') {
+				    ctype(c, C_NL)) {
 #ifdef MKSH_WITH_TEXTMODE
 					if (c == '\r') {
 						c = shf_getc(x.u.shf);
@@ -999,11 +1002,11 @@
 			tilde_ok <<= 1;
 			/* mark any special second pass chars */
 			if (!quote)
-				switch (c) {
-				case '[':
-				case '!':
-				case '-':
-				case ']':
+				switch (ord(c)) {
+				case ord('['):
+				case ord('!'):
+				case ord('-'):
+				case ord(']'):
 					/*
 					 * For character classes - doesn't hurt
 					 * to have magic !,-,]s outside of
@@ -1011,28 +1014,29 @@
 					 */
 					if (f & (DOPAT | DOGLOB)) {
 						fdo |= DOMAGIC;
-						if (c == '[')
+						if (c == ord('['))
 							fdo |= f & DOGLOB;
 						*dp++ = MAGIC;
 					}
 					break;
-				case '*':
-				case '?':
+				case ord('*'):
+				case ord('?'):
 					if (f & (DOPAT | DOGLOB)) {
 						fdo |= DOMAGIC | (f & DOGLOB);
 						*dp++ = MAGIC;
 					}
 					break;
-				case '{':
-				case '}':
-				case ',':
-					if ((f & DOBRACE) && (c == '{' /*}*/ ||
+				case ord('{'):
+				case ord('}'):
+				case ord(','):
+					if ((f & DOBRACE) &&
+					    (ord(c) == ord('{' /*}*/) ||
 					    (fdo & DOBRACE))) {
 						fdo |= DOBRACE|DOMAGIC;
 						*dp++ = MAGIC;
 					}
 					break;
-				case '=':
+				case ord('='):
 					/* Note first unquoted = for ~ */
 					if (!(f & DOTEMP) && (!Flag(FPOSIX) ||
 					    (f & DOASNTILDE)) && !saw_eq) {
@@ -1040,13 +1044,13 @@
 						tilde_ok = 1;
 					}
 					break;
-				case ':':
+				case ord(':'):
 					/* : */
 					/* Note unquoted : for ~ */
 					if (!(f & DOTEMP) && (f & DOASNTILDE))
 						tilde_ok = 1;
 					break;
-				case '~':
+				case ord('~'):
 					/*
 					 * tilde_ok is reset whenever
 					 * any of ' " $( $(( ${ } are seen.
@@ -1118,7 +1122,7 @@
 	struct tbl *vp;
 	bool zero_ok = false;
 
-	if ((stype = sp[0]) == '\0')
+	if ((stype = ord(sp[0])) == '\0')
 		/* Bad variable name */
 		return (-1);
 
@@ -1128,20 +1132,20 @@
 	 * ${#var}, string length (-U: characters, +U: octets) or array size
 	 * ${%var}, string width (-U: screen columns, +U: octets)
 	 */
-	c = sp[1];
-	if (stype == '%' && c == '\0')
+	c = ord(sp[1]);
+	if (stype == ord('%') && c == '\0')
 		return (-1);
-	if ((stype == '#' || stype == '%') && c != '\0') {
+	if (ctype(stype, C_SUB2) && c != '\0') {
 		/* Can't have any modifiers for ${#...} or ${%...} */
 		if (*word != CSUBST)
 			return (-1);
 		sp++;
 		/* Check for size of array */
-		if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
-		    p[2] == ']') {
+		if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ord('*') ||
+		    ord(p[1]) == ord('@')) && ord(p[2]) == ord(']')) {
 			int n = 0;
 
-			if (stype != '#')
+			if (stype != ord('#'))
 				return (-1);
 			vp = global(arrayname(sp));
 			if (vp->flag & (ISSET|ARRAY))
@@ -1150,14 +1154,14 @@
 				if (vp->flag & ISSET)
 					n++;
 			c = n;
-		} else if (c == '*' || c == '@') {
-			if (stype != '#')
+		} else if (c == ord('*') || c == ord('@')) {
+			if (stype != ord('#'))
 				return (-1);
 			c = e->loc->argc;
 		} else {
 			p = str_val(global(sp));
 			zero_ok = p != null;
-			if (stype == '#')
+			if (stype == ord('#'))
 				c = utflen(p);
 			else {
 				/* partial utf_mbswidth reimplementation */
@@ -1171,7 +1175,7 @@
 					if (!UTFMODE || (len = utf_mbtowc(&wc,
 					    s)) == (size_t)-1)
 						/* not UTFMODE or not UTF-8 */
-						wc = (unsigned char)(*s++);
+						wc = rtt2asc(*s++);
 					else
 						/* UTFMODE and UTF-8 */
 						s += len;
@@ -1192,11 +1196,11 @@
 		xp->str = shf_smprintf(Tf_d, c);
 		return (XSUB);
 	}
-	if (stype == '!' && c != '\0' && *word == CSUBST) {
+	if (stype == ord('!') && c != '\0' && *word == CSUBST) {
 		sp++;
-		if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
-		    p[2] == ']') {
-			c = '!';
+		if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ord('*') ||
+		    ord(p[1]) == ord('@')) && ord(p[2]) == ord(']')) {
+			c = ord('!');
 			stype = 0;
 			goto arraynames;
 		}
@@ -1209,43 +1213,46 @@
 
 	/* Check for qualifiers in word part */
 	stype = 0;
-	c = word[slen + 0] == CHAR ? word[slen + 1] : 0;
-	if (c == ':') {
+	c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0;
+	if (c == ord(':')) {
 		slen += 2;
-		stype = 0x80;
-		c = word[slen + 0] == CHAR ? word[slen + 1] : 0;
+		stype = STYPE_DBL;
+		c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0;
 	}
-	if (!stype && c == '/') {
+	if (!stype && c == ord('/')) {
 		slen += 2;
 		stype = c;
-		if (word[slen] == ADELIM && word[slen + 1] == c) {
+		if (word[slen] == ADELIM &&
+		    ord(word[slen + 1]) == (unsigned int)c) {
 			slen += 2;
-			stype |= 0x80;
+			stype |= STYPE_DBL;
 		}
-	} else if (stype == 0x80 && (c == ' ' || c == '0')) {
-		stype |= '0';
-	} else if (ctype(c, C_SUBOP1)) {
+	} else if (stype == STYPE_DBL && (c == ord(' ') || c == ord('0'))) {
+		stype |= ord('0');
+	} else if (ctype(c, C_SUB1)) {
 		slen += 2;
 		stype |= c;
-	} else if (ksh_issubop2(c)) {
+	} else if (ctype(c, C_SUB2)) {
 		/* Note: ksh88 allows :%, :%%, etc */
 		slen += 2;
 		stype = c;
-		if (word[slen + 0] == CHAR && c == word[slen + 1]) {
-			stype |= 0x80;
+		if (word[slen + 0] == CHAR &&
+		    ord(word[slen + 1]) == (unsigned int)c) {
+			stype |= STYPE_DBL;
 			slen += 2;
 		}
-	} else if (c == '@') {
+	} else if (c == ord('@')) {
 		/* @x where x is command char */
-		switch (c = word[slen + 2] == CHAR ? word[slen + 3] : 0) {
-		case '#':
-		case '/':
-		case 'Q':
+		switch (c = ord(word[slen + 2]) == CHAR ?
+		    ord(word[slen + 3]) : 0) {
+		case ord('#'):
+		case ord('/'):
+		case ord('Q'):
 			break;
 		default:
 			return (-1);
 		}
-		stype |= 0x100 | c;
+		stype |= STYPE_AT | c;
 		slen += 4;
 	} else if (stype)
 		/* : is not ok */
@@ -1253,51 +1260,51 @@
 	if (!stype && *word != CSUBST)
 		return (-1);
 
-	c = sp[0];
-	if (c == '*' || c == '@') {
-		switch (stype & 0x17F) {
+	c = ord(sp[0]);
+	if (c == ord('*') || c == ord('@')) {
+		switch (stype & STYPE_SINGLE) {
 		/* can't assign to a vector */
-		case '=':
+		case ord('='):
 		/* can't trim a vector (yet) */
-		case '%':
-		case '#':
-		case '?':
-		case '0':
-		case 0x100 | '/':
-		case '/':
-		case 0x100 | '#':
-		case 0x100 | 'Q':
+		case ord('%'):
+		case ord('#'):
+		case ord('?'):
+		case ord('0'):
+		case ord('/') | STYPE_AT:
+		case ord('/'):
+		case ord('#') | STYPE_AT:
+		case ord('Q') | STYPE_AT:
 			return (-1);
 		}
 		if (e->loc->argc == 0) {
 			xp->str = null;
 			xp->var = global(sp);
-			state = c == '@' ? XNULLSUB : XSUB;
+			state = c == ord('@') ? XNULLSUB : XSUB;
 		} else {
 			xp->u.strv = (const char **)e->loc->argv + 1;
 			xp->str = *xp->u.strv++;
 			/* $@ */
-			xp->split = tobool(c == '@');
+			xp->split = tobool(c == ord('@'));
 			state = XARG;
 		}
 		/* POSIX 2009? */
 		zero_ok = true;
-	} else if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
-	    p[2] == ']') {
+	} else if ((p = cstrchr(sp, '[')) && (ord(p[1]) == ord('*') ||
+	    ord(p[1]) == ord('@')) && ord(p[2]) == ord(']')) {
 		XPtrV wv;
 
-		switch (stype & 0x17F) {
+		switch (stype & STYPE_SINGLE) {
 		/* can't assign to a vector */
-		case '=':
+		case ord('='):
 		/* can't trim a vector (yet) */
-		case '%':
-		case '#':
-		case '?':
-		case '0':
-		case 0x100 | '/':
-		case '/':
-		case 0x100 | '#':
-		case 0x100 | 'Q':
+		case ord('%'):
+		case ord('#'):
+		case ord('?'):
+		case ord('0'):
+		case ord('/') | STYPE_AT:
+		case ord('/'):
+		case ord('#') | STYPE_AT:
+		case ord('Q') | STYPE_AT:
 			return (-1);
 		}
 		c = 0;
@@ -1307,45 +1314,45 @@
 		for (; vp; vp = vp->u.array) {
 			if (!(vp->flag&ISSET))
 				continue;
-			XPput(wv, c == '!' ? shf_smprintf(Tf_lu,
+			XPput(wv, c == ord('!') ? shf_smprintf(Tf_lu,
 			    arrayindex(vp)) :
 			    str_val(vp));
 		}
 		if (XPsize(wv) == 0) {
 			xp->str = null;
-			state = p[1] == '@' ? XNULLSUB : XSUB;
+			state = ord(p[1]) == ord('@') ? XNULLSUB : XSUB;
 			XPfree(wv);
 		} else {
 			XPput(wv, 0);
 			xp->u.strv = (const char **)XPptrv(wv);
 			xp->str = *xp->u.strv++;
 			/* ${foo[@]} */
-			xp->split = tobool(p[1] == '@');
+			xp->split = tobool(ord(p[1]) == ord('@'));
 			state = XARG;
 		}
 	} else {
 		xp->var = global(sp);
 		xp->str = str_val(xp->var);
 		/* can't assign things like $! or $1 */
-		if ((stype & 0x17F) == '=' && !*xp->str &&
+		if ((stype & STYPE_SINGLE) == ord('=') && !*xp->str &&
 		    ctype(*sp, C_VAR1 | C_DIGIT))
 			return (-1);
 		state = XSUB;
 	}
 
-	c = stype & 0x7F;
+	c = stype & STYPE_CHAR;
 	/* test the compiler's code generator */
-	if (((stype < 0x100) && (ksh_issubop2(c) ||
-	    (((stype & 0x80) ? *xp->str == '\0' : xp->str == null) &&
+	if ((!(stype & STYPE_AT) && (ctype(c, C_SUB2) ||
+	    (((stype & STYPE_DBL) ? *xp->str == '\0' : xp->str == null) &&
 	    (state != XARG || (ifs0 || xp->split ?
 	    (xp->u.strv[0] == NULL) : !hasnonempty(xp->u.strv))) ?
-	    c == '=' || c == '-' || c == '?' : c == '+'))) ||
-	    stype == (0x80 | '0') || stype == (0x100 | '#') ||
-	    stype == (0x100 | 'Q') || (stype & 0x7F) == '/')
+	    ctype(c, C_EQUAL | C_MINUS | C_QUEST) : c == ord('+')))) ||
+	    stype == (ord('0') | STYPE_DBL) || stype == (ord('#') | STYPE_AT) ||
+	    stype == (ord('Q') | STYPE_AT) || (stype & STYPE_CHAR) == ord('/'))
 		/* expand word instead of variable value */
 		state = XBASE;
 	if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
-	    (ksh_issubop2(c) || (state != XBASE && c != '+')))
+	    (ctype(c, C_SUB2) || (state != XBASE && c != ord('+'))))
 		errorf(Tf_parm, sp);
 	*stypep = stype;
 	*slenp = slen;
@@ -1408,7 +1415,7 @@
 			if (!herein(io, &name)) {
 				xp->str = name;
 				/* as $(…) requires, trim trailing newlines */
-				name += strlen(name);
+				name = strnul(name);
 				while (name > xp->str && name[-1] == '\n')
 					--name;
 				*name = '\0';
@@ -1483,8 +1490,8 @@
 	char *end = strnul(str);
 	char *p, c;
 
-	switch (how & 0xFF) {
-	case '#':
+	switch (how & (STYPE_CHAR | STYPE_DBL)) {
+	case ord('#'):
 		/* shortest match at beginning */
 		for (p = str; p <= end; p += utf_ptradj(p)) {
 			c = *p; *p = '\0';
@@ -1496,7 +1503,7 @@
 			*p = c;
 		}
 		break;
-	case '#'|0x80:
+	case ord('#') | STYPE_DBL:
 		/* longest match at beginning */
 		for (p = end; p >= str; p--) {
 			c = *p; *p = '\0';
@@ -1508,7 +1515,7 @@
 			*p = c;
 		}
 		break;
-	case '%':
+	case ord('%'):
 		/* shortest match at end */
 		p = end;
 		while (p >= str) {
@@ -1516,7 +1523,7 @@
 				goto trimsub_match;
 			if (UTFMODE) {
 				char *op = p;
-				while ((p-- > str) && ((*p & 0xC0) == 0x80))
+				while ((p-- > str) && ((rtt2asc(*p) & 0xC0) == 0x80))
 					;
 				if ((p < str) || (p + utf_ptradj(p) != op))
 					p = op - 1;
@@ -1524,7 +1531,7 @@
 				--p;
 		}
 		break;
-	case '%'|0x80:
+	case ord('%') | STYPE_DBL:
 		/* longest match at end */
 		for (p = str; p <= end; p++)
 			if (gmatchx(p, pat, false)) {
@@ -1555,7 +1562,7 @@
 		XPput(*wp, debunk(cp, cp, strlen(cp) + 1));
 	else
 		qsort(XPptrv(*wp) + oldsize, XPsize(*wp) - oldsize,
-		    sizeof(void *), xstrcmp);
+		    sizeof(void *), ascpstrcmp);
 }
 
 #define GF_NONE		0
@@ -1658,7 +1665,7 @@
 		*np++ = '\0';
 	} else {
 		odirsep = '\0'; /* keep gcc quiet */
-		se = sp + strlen(sp);
+		se = strnul(sp);
 	}
 
 
@@ -1669,10 +1676,10 @@
 	 * directory isn't readable - if no globbing is needed, only execute
 	 * permission should be required (as per POSIX)).
 	 */
-	if (!has_globbing(sp, se)) {
+	if (!has_globbing(sp)) {
 		XcheckN(*xs, xp, se - sp + 1);
 		debunk(xp, sp, Xnleft(*xs, xp));
-		xp += strlen(xp);
+		xp = strnul(xp);
 		*xpp = xp;
 		globit(xs, xpp, np, wp, check);
 	} else {
@@ -1701,9 +1708,8 @@
 			XcheckN(*xs, xp, len);
 			memcpy(xp, name, len);
 			*xpp = xp + len - 1;
-			globit(xs, xpp, np, wp,
-				(check & GF_MARKDIR) | GF_GLOBBED
-				| (np ? GF_EXCHECK : GF_NONE));
+			globit(xs, xpp, np, wp, (check & GF_MARKDIR) |
+			    GF_GLOBBED | (np ? GF_EXCHECK : GF_NONE));
 			xp = Xstring(*xs, xp) + prefix_len;
 		}
 		closedir(dirp);
@@ -1728,7 +1734,7 @@
 		memmove(dp, sp, s - sp);
 		for (d = dp + (s - sp); *s && (d - dp < (ssize_t)dlen); s++)
 			if (!ISMAGIC(*s) || !(*++s & 0x80) ||
-			    !vstrchr("*+?@! ", *s & 0x7f))
+			    !ctype(*s & 0x7F, C_PATMO | C_SPC))
 				*d++ = *s;
 			else {
 				/* extended pattern operators: *+?@! */
@@ -1857,7 +1863,7 @@
 	char *p = exp_start;
 
 	/* search for open brace */
-	while ((p = strchr(p, MAGIC)) && p[1] != '{' /*}*/)
+	while ((p = strchr(p, MAGIC)) && ord(p[1]) != ord('{' /*}*/))
 		p += 2;
 	brace_start = p;
 
@@ -1868,9 +1874,9 @@
 		p += 2;
 		while (*p && count) {
 			if (ISMAGIC(*p++)) {
-				if (*p == '{' /*}*/)
+				if (ord(*p) == ord('{' /*}*/))
 					++count;
-				else if (*p == /*{*/ '}')
+				else if (ord(*p) == ord(/*{*/ '}'))
 					--count;
 				else if (*p == ',' && count == 1)
 					comma = p;
@@ -1902,9 +1908,9 @@
 	count = 1;
 	for (p = brace_start + 2; p != brace_end; p++) {
 		if (ISMAGIC(*p)) {
-			if (*++p == '{' /*}*/)
+			if (ord(*++p) == ord('{' /*}*/))
 				++count;
-			else if ((*p == /*{*/ '}' && --count == 0) ||
+			else if ((ord(*p) == ord(/*{*/ '}') && --count == 0) ||
 			    (*p == ',' && count == 1)) {
 				char *news;
 				int l1, l2, l3;
diff --git a/src/exec.c b/src/exec.c
index 6307bce..56a42f6 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.196 2017/04/12 16:46:21 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.199 2017/08/07 21:16:31 tg Exp $");
 
 #ifndef MKSH_DEFAULT_EXECSHELL
 #define MKSH_DEFAULT_EXECSHELL	MKSH_UNIXROOT "/bin/sh"
@@ -554,6 +554,9 @@
 				}
 			ap += builtin_opt.optind;
 			flags |= XEXEC;
+			/* POSuX demands ksh88-like behaviour here */
+			if (Flag(FPOSIX))
+				fcflags = FC_PATH;
 		} else if (tp->val.f == c_command) {
 			bool saw_p = false;
 
@@ -885,7 +888,9 @@
 #ifndef MKSH_SMALL
 	if ((fd = binopen2(tp->str, O_RDONLY)) >= 0) {
 		unsigned char *cp;
+#ifndef MKSH_EBCDIC
 		unsigned short m;
+#endif
 		ssize_t n;
 
 #if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
@@ -905,7 +910,7 @@
 		    (buf[2] == 0xBF)) ? 3 : 0);
 
 		/* scan for newline or NUL (end of buffer) */
-		while (*cp && *cp != '\n')
+		while (!ctype(*cp, C_NL | C_NUL))
 			++cp;
 		/* if the shebang line is longer than MAXINTERP, bail out */
 		if (!*cp)
@@ -920,13 +925,13 @@
 			cp += 2;
 #ifdef __OS2__
 		else if (!strncmp(cp, Textproc, 7) &&
-		    (cp[7] == ' ' || cp[7] == '\t'))
+		    ctype(cp[7], C_BLANK))
 			cp += 8;
 #endif
 		else
 			goto noshebang;
 		/* skip whitespace before shell name */
-		while (*cp == ' ' || *cp == '\t')
+		while (ctype(*cp, C_BLANK))
 			++cp;
 		/* just whitespace on the line? */
 		if (*cp == '\0')
@@ -934,13 +939,13 @@
 		/* no, we actually found an interpreter name */
 		sh = (char *)cp;
 		/* look for end of shell/interpreter name */
-		while (*cp != ' ' && *cp != '\t' && *cp != '\0')
+		while (!ctype(*cp, C_BLANK | C_NUL))
 			++cp;
 		/* any arguments? */
 		if (*cp) {
 			*cp++ = '\0';
 			/* skip spaces before arguments */
-			while (*cp == ' ' || *cp == '\t')
+			while (ctype(*cp, C_BLANK))
 				++cp;
 			/* pass it all in ONE argument (historic reasons) */
 			if (*cp)
@@ -959,6 +964,7 @@
 #endif
 		goto nomagic;
  noshebang:
+#ifndef MKSH_EBCDIC
 		m = buf[0] << 8 | buf[1];
 		if (m == 0x7F45 && buf[2] == 'L' && buf[3] == 'F')
 			errorf("%s: not executable: %d-bit ELF file", tp->str,
@@ -977,6 +983,7 @@
 		    buf[4] == 'Z') || (m == /* 7zip */ 0x377A) ||
 		    (m == /* gzip */ 0x1F8B) || (m == /* .Z */ 0x1F9D))
 			errorf("%s: not executable: magic %04X", tp->str, m);
+#endif
 #ifdef __OS2__
 		cp = _getext(tp->str);
 		if (cp && (!stricmp(cp, ".cmd") || !stricmp(cp, ".bat"))) {
@@ -1337,7 +1344,7 @@
 	while (sp != NULL) {
 		xp = Xstring(xs, xp);
 		if (!(p = cstrchr(sp, MKSH_PATHSEPC)))
-			p = sp + strlen(sp);
+			p = strnul(sp);
 		if (p != sp) {
 			XcheckN(xs, xp, p - sp);
 			memcpy(xp, sp, p - sp);
diff --git a/src/expr.c b/src/expr.c
index 124dc17..12989d4 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.93 2017/04/02 16:47:41 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.100 2017/08/07 21:38:55 tg Exp $");
 
 #define EXPRTOK_DEFNS
 #include "exprtok.h"
@@ -558,9 +558,9 @@
 
 	/* skip whitespace */
  skip_spaces:
-	while ((c = *cp), ksh_isspace(c))
+	while (ctype(ord((c = *cp)), C_SPACE))
 		++cp;
-	if (es->tokp == es->expression && c == '#') {
+	if (es->tokp == es->expression && c == ord('#')) {
 		/* expression begins with # */
 		/* switch to unsigned */
 		es->natural = true;
@@ -571,11 +571,11 @@
 
 	if (c == '\0')
 		es->tok = END;
-	else if (ksh_isalphx(c)) {
+	else if (ctype(c, C_ALPHX)) {
 		do {
-			c = *++cp;
-		} while (ksh_isalnux(c));
-		if (c == '[') {
+			c = ord(*++cp);
+		} while (ctype(c, C_ALNUX));
+		if (c == ord('[')) {
 			size_t len;
 
 			len = array_ref_len(cp);
@@ -617,9 +617,9 @@
 		tvar[c] = '\0';
 		goto process_tvar;
 #endif
-	} else if (ksh_isdigit(c)) {
-		while (c != '_' && (ksh_isalnux(c) || c == '#'))
-			c = *cp++;
+	} else if (ctype(c, C_DIGIT)) {
+		while (ctype(c, C_ALNUM | C_HASH))
+			c = ord(*cp++);
 		strndupx(tvar, es->tokp, --cp - es->tokp, ATEMP);
  process_tvar:
 		es->val = tempvar("");
@@ -633,7 +633,7 @@
 	} else {
 		int i, n0;
 
-		for (i = 0; (n0 = opname[i][0]); i++)
+		for (i = 0; (n0 = ord(opname[i][0])); i++)
 			if (c == n0 && strncmp(cp, opname[i],
 			    (size_t)oplen[i]) == 0) {
 				es->tok = (enum token)i;
@@ -772,8 +772,7 @@
 {
 	register size_t n;
 
-	if (!UTFMODE ||
-	    *(const unsigned char *)(src) < 0xC2 ||
+	if (!UTFMODE || rtt2asc(*src) < 0xC2 ||
 	    (n = utf_mbtowc(NULL, src)) == (size_t)-1)
 		n = 1;
 	return (n);
@@ -791,7 +790,7 @@
 	const unsigned char *s = (const unsigned char *)src;
 	unsigned int c, wc;
 
-	if ((wc = *s++) < 0x80) {
+	if ((wc = ord(rtt2asc(*s++))) < 0x80) {
  out:
 		if (dst != NULL)
 			*dst = wc;
@@ -805,7 +804,7 @@
 
 	if (wc < 0xE0) {
 		wc = (wc & 0x1F) << 6;
-		if (((c = *s++) & 0xC0) != 0x80)
+		if (((c = ord(rtt2asc(*s++))) & 0xC0) != 0x80)
 			goto ilseq;
 		wc |= c & 0x3F;
 		goto out;
@@ -813,11 +812,11 @@
 
 	wc = (wc & 0x0F) << 12;
 
-	if (((c = *s++) & 0xC0) != 0x80)
+	if (((c = ord(rtt2asc(*s++))) & 0xC0) != 0x80)
 		goto ilseq;
 	wc |= (c & 0x3F) << 6;
 
-	if (((c = *s++) & 0xC0) != 0x80)
+	if (((c = ord(rtt2asc(*s++))) & 0xC0) != 0x80)
 		goto ilseq;
 	wc |= c & 0x3F;
 
@@ -834,18 +833,18 @@
 	unsigned char *d;
 
 	if (wc < 0x80) {
-		*dst = wc;
+		*dst = asc2rtt(wc);
 		return (1);
 	}
 
 	d = (unsigned char *)dst;
 	if (wc < 0x0800)
-		*d++ = (wc >> 6) | 0xC0;
+		*d++ = asc2rtt((wc >> 6) | 0xC0);
 	else {
-		*d++ = ((wc = wc > 0xFFFD ? 0xFFFD : wc) >> 12) | 0xE0;
-		*d++ = ((wc >> 6) & 0x3F) | 0x80;
+		*d++ = asc2rtt(((wc = wc > 0xFFFD ? 0xFFFD : wc) >> 12) | 0xE0);
+		*d++ = asc2rtt(((wc >> 6) & 0x3F) | 0x80);
 	}
-	*d++ = (wc & 0x3F) | 0x80;
+	*d++ = asc2rtt((wc & 0x3F) | 0x80);
 	return ((char *)d - dst);
 }
 
@@ -873,7 +872,7 @@
 }
 
 #ifndef MIRBSD_BOOTFLOPPY
-/* From: X11/xc/programs/xterm/wcwidth.c,v 1.9 */
+/* From: X11/xc/programs/xterm/wcwidth.c,v 1.10 */
 
 struct mb_ucsrange {
 	unsigned short beg;
@@ -884,8 +883,8 @@
     unsigned int val) MKSH_A_PURE;
 
 /*
- * Generated from the Unicode Character Database, Version 9.0.0, by
- * MirOS: contrib/code/Snippets/eawparse,v 1.3 2014/11/16 12:16:24 tg Exp $
+ * Generated from the Unicode Character Database, Version 10.0.0, by
+ * MirOS: contrib/code/Snippets/eawparse,v 1.10 2017/07/12 22:47:26 tg Exp $
  */
 
 static const struct mb_ucsrange mb_ucs_combining[] = {
@@ -941,6 +940,7 @@
 	{ 0x0AC7, 0x0AC8 },
 	{ 0x0ACD, 0x0ACD },
 	{ 0x0AE2, 0x0AE3 },
+	{ 0x0AFA, 0x0AFF },
 	{ 0x0B01, 0x0B01 },
 	{ 0x0B3C, 0x0B3C },
 	{ 0x0B3F, 0x0B3F },
@@ -963,7 +963,8 @@
 	{ 0x0CC6, 0x0CC6 },
 	{ 0x0CCC, 0x0CCD },
 	{ 0x0CE2, 0x0CE3 },
-	{ 0x0D01, 0x0D01 },
+	{ 0x0D00, 0x0D01 },
+	{ 0x0D3B, 0x0D3C },
 	{ 0x0D41, 0x0D44 },
 	{ 0x0D4D, 0x0D4D },
 	{ 0x0D62, 0x0D63 },
@@ -1048,7 +1049,7 @@
 	{ 0x1CED, 0x1CED },
 	{ 0x1CF4, 0x1CF4 },
 	{ 0x1CF8, 0x1CF9 },
-	{ 0x1DC0, 0x1DF5 },
+	{ 0x1DC0, 0x1DF9 },
 	{ 0x1DFB, 0x1DFF },
 	{ 0x200B, 0x200F },
 	{ 0x202A, 0x202E },
@@ -1136,14 +1137,16 @@
 	{ 0x2B1B, 0x2B1C },
 	{ 0x2B50, 0x2B50 },
 	{ 0x2B55, 0x2B55 },
-	{ 0x2E80, 0x303E },
-	{ 0x3040, 0xA4CF },
+	{ 0x2E80, 0x3029 },
+	{ 0x302E, 0x303E },
+	{ 0x3040, 0x3098 },
+	{ 0x309B, 0xA4CF },
 	{ 0xA960, 0xA97F },
 	{ 0xAC00, 0xD7A3 },
 	{ 0xF900, 0xFAFF },
 	{ 0xFE10, 0xFE19 },
 	{ 0xFE30, 0xFE6F },
-	{ 0xFF00, 0xFF60 },
+	{ 0xFF01, 0xFF60 },
 	{ 0xFFE0, 0xFFE6 }
 };
 
diff --git a/src/funcs.c b/src/funcs.c
index 930462d..38e66f8 100644
--- a/src/funcs.c
+++ b/src/funcs.c
@@ -38,7 +38,7 @@
 #endif
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.340 2017/04/12 17:46:29 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.350 2017/05/05 22:53:28 tg Exp $");
 
 #if HAVE_KILLPG
 /*
@@ -751,11 +751,15 @@
 bool
 valid_alias_name(const char *cp)
 {
+	if (ord(*cp) == ord('-'))
+		return (false);
+	if (ord(cp[0]) == ord('[') && ord(cp[1]) == ord('[') && !cp[2])
+		return (false);
 	while (*cp)
-		if (!ksh_isalias(*cp))
-			return (false);
-		else
+		if (ctype(*cp, C_ALIAS))
 			++cp;
+		else
+			return (false);
 	return (true);
 }
 
@@ -764,7 +768,7 @@
 {
 	struct table *t = &aliases;
 	int rv = 0, prefix = 0;
-	bool rflag = false, tflag, Uflag = false, pflag = false;
+	bool rflag = false, tflag, Uflag = false, pflag = false, chkalias;
 	uint32_t xflag = 0;
 	int optc;
 
@@ -809,12 +813,13 @@
 	wp += builtin_opt.optind;
 
 	if (!(builtin_opt.info & GI_MINUSMINUS) && *wp &&
-	    (wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0') {
+	    ctype(wp[0][0], C_MINUS | C_PLUS) && wp[0][1] == '\0') {
 		prefix = wp[0][0];
 		wp++;
 	}
 
 	tflag = t == &taliases;
+	chkalias = t == &aliases;
 
 	/* "hash -r" means reset all the tracked aliases.. */
 	if (rflag) {
@@ -857,7 +862,7 @@
 			strndupx(xalias, alias, val++ - alias, ATEMP);
 			alias = xalias;
 		}
-		if (!valid_alias_name(alias) || *alias == '-') {
+		if (chkalias && !valid_alias_name(alias)) {
 			bi_errorf(Tinvname, alias, Talias);
 			afree(xalias, ATEMP);
 			return (1);
@@ -1072,8 +1077,7 @@
 	int i, n, rv, sig;
 
 	/* assume old style options if -digits or -UPPERCASE */
-	if ((p = wp[1]) && *p == '-' && (ksh_isdigit(p[1]) ||
-	    ksh_isupper(p[1]))) {
+	if ((p = wp[1]) && *p == '-' && ctype(p[1], C_DIGIT | C_UPPER)) {
 		if (!(t = gettrap(p + 1, false, false))) {
 			bi_errorf(Tbad_sig_s, p + 1);
 			return (1);
@@ -1422,9 +1426,9 @@
 	} else {
 		mode_t new_umask;
 
-		if (ksh_isdigit(*cp)) {
+		if (ctype(*cp, C_DIGIT)) {
 			new_umask = 0;
-			while (*cp >= ord('0') && *cp <= ord('7')) {
+			while (ctype(*cp, C_OCTAL)) {
 				new_umask = new_umask * 8 + ksh_numdig(*cp);
 				++cp;
 			}
@@ -1462,7 +1466,7 @@
 				if (!positions)
 					/* default is a */
 					positions = 0111;
-				if (!vstrchr("=+-", op = *cp))
+				if (!ctype((op = *cp), C_EQUAL | C_MINUS | C_PLUS))
 					break;
 				cp++;
 				new_val = 0;
@@ -1503,7 +1507,7 @@
 				if (*cp == ',') {
 					positions = 0;
 					cp++;
-				} else if (!vstrchr("=+-", *cp))
+				} else if (!ctype(*cp, C_EQUAL | C_MINUS | C_PLUS))
 					break;
 			}
 			if (*cp) {
@@ -1585,7 +1589,7 @@
 	return (rv);
 }
 
-static char REPLY[] = "REPLY";
+static const char REPLY[] = "REPLY";
 int
 c_read(const char **wp)
 {
@@ -2300,8 +2304,9 @@
 			size_t n;
 
 			n = strlen(id);
-			if (n > 3 && id[n-3] == '[' && id[n-2] == '*' &&
-			    id[n-1] == ']') {
+			if (n > 3 && ord(id[n - 3]) == ord('[') &&
+			    ord(id[n - 2]) == ord('*') &&
+			    ord(id[n - 1]) == ord(']')) {
 				strndupx(cp, id, n - 3, ATEMP);
 				id = cp;
 				optc = 3;
@@ -3350,7 +3355,7 @@
 		 * If this causes problems, will have to add parameter to
 		 * evaluate() to control if unset params are 0 or an error.
 		 */
-		if (!rval && !ksh_isdigit(v[0])) {
+		if (!rval && !ctype(v[0], C_DIGIT)) {
 			bi_errorf("invalid %s limit: %s", l->name, v);
 			return (1);
 		}
diff --git a/src/histrap.c b/src/histrap.c
index 26dd521..6b9396e 100644
--- a/src/histrap.c
+++ b/src/histrap.c
@@ -3,7 +3,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2014, 2015, 2016
+ *		 2011, 2012, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -27,7 +27,7 @@
 #include <sys/file.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.160 2017/04/08 01:07:16 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.166 2017/08/07 23:25:09 tg Exp $");
 
 Trap sigtraps[ksh_NSIG + 1];
 static struct sigaction Sigact_ign;
@@ -629,7 +629,7 @@
 	if (svmode == HIST_FLUSH)
 		return;
 
-	ccp = cmd + strlen(cmd);
+	ccp = strnul(cmd);
 	while (ccp > cmd && ccp[-1] == '\n')
 		--ccp;
 	strndupx(c, cmd, ccp - cmd, APERM);
@@ -714,26 +714,66 @@
 
 #if HAVE_PERSISTENT_HISTORY
 static const unsigned char sprinkle[2] = { HMAGIC1, HMAGIC2 };
-#endif
 
-void
-hist_init(Source *s)
+static int
+hist_persist_back(int srcfd)
 {
-#if HAVE_PERSISTENT_HISTORY
+	off_t tot, mis;
+	ssize_t n, w;
+	char *buf, *cp;
+	int rv = 0;
+#define MKSH_HS_BUFSIZ 4096
+
+	if ((tot = lseek(srcfd, (off_t)0, SEEK_END)) < 0 ||
+	    lseek(srcfd, (off_t)0, SEEK_SET) < 0 ||
+	    lseek(histfd, (off_t)0, SEEK_SET) < 0)
+		return (1);
+
+	if ((buf = malloc_osfunc(MKSH_HS_BUFSIZ)) == NULL)
+		return (1);
+
+	mis = tot;
+	while (mis > 0) {
+		if ((n = blocking_read(srcfd, (cp = buf),
+		    MKSH_HS_BUFSIZ)) == -1) {
+			if (errno == EINTR) {
+				intrcheck();
+				continue;
+			}
+			goto copy_error;
+		}
+		mis -= n;
+		while (n) {
+			if (intrsig)
+				goto has_intrsig;
+			if ((w = write(histfd, cp, n)) != -1) {
+				n -= w;
+				cp += w;
+				continue;
+			}
+			if (errno == EINTR) {
+ has_intrsig:
+				intrcheck();
+				continue;
+			}
+			goto copy_error;
+		}
+	}
+	if (ftruncate(histfd, tot)) {
+ copy_error:
+		rv = 1;
+	}
+	free_osfunc(buf);
+	return (rv);
+}
+
+static void
+hist_persist_init(void)
+{
 	unsigned char *base;
 	int lines, fd;
-	enum { hist_init_first, hist_init_retry, hist_init_restore } hs;
-#endif
+	enum { hist_init_first, hist_init_retry, hist_use_it } hs;
 
-	histsave(NULL, NULL, HIST_DISCARD, true);
-
-	if (Flag(FTALKING) == 0)
-		return;
-
-	hstarted = true;
-	hist_source = s;
-
-#if HAVE_PERSISTENT_HISTORY
 	if (((hname = str_val(global("HISTFILE"))) == NULL) || !*hname) {
 		hname = NULL;
 		return;
@@ -745,17 +785,16 @@
 	/* we have a file and are interactive */
 	if ((fd = binopen3(hname, O_RDWR | O_CREAT | O_APPEND, 0600)) < 0)
 		return;
-
-	histfd = savefd(fd);
+	if ((histfd = savefd(fd)) < 0)
+		return;
 	if (histfd != fd)
 		close(fd);
 
 	mksh_lockfd(histfd);
 
 	histfsize = lseek(histfd, (off_t)0, SEEK_END);
-	if (histfsize > MKSH_MAXHISTFSIZE || hs == hist_init_restore) {
+	if (histfsize > MKSH_MAXHISTFSIZE) {
 		/* we ignore too large files but still append to them */
-		/* we also don't need to re-read after truncation */
 		goto hist_init_tail;
 	} else if (histfsize > 2) {
 		/* we have some data, check its validity */
@@ -781,6 +820,7 @@
 			if ((fd = binopen3(nhname, O_RDWR | O_CREAT | O_TRUNC |
 			    O_EXCL, 0600)) < 0) {
 				/* just don't truncate then, meh. */
+				hs = hist_use_it;
 				goto hist_trunc_dont;
 			}
 			if (fstat(histfd, &sb) >= 0 &&
@@ -795,28 +835,26 @@
 			hp = history;
 			while (hp < histptr) {
 				if (!writehistline(fd,
-				    s->line - (histptr - hp), *hp))
+				    hist_source->line - (histptr - hp), *hp))
 					goto hist_trunc_abort;
 				++hp;
 			}
-			/* now unlock, close both, rename, rinse, repeat */
+			/* now transfer back */
+			if (!hist_persist_back(fd)) {
+				/* success! */
+				hs = hist_use_it;
+			}
+ hist_trunc_abort:
+			/* remove temporary file */
 			close(fd);
 			fd = -1;
-			hist_finish();
-			if (rename(nhname, hname) < 0) {
- hist_trunc_abort:
-				if (fd != -1)
-					close(fd);
-				unlink(nhname);
-				if (fd != -1)
-					goto hist_trunc_dont;
-				/* darn! restore histfd and pray */
-			}
-			hs = hist_init_restore;
+			unlink(nhname);
+			/* use whatever is in the file now */
  hist_trunc_dont:
 			afree(nhname, ATEMP);
-			if (hs == hist_init_restore)
-				goto retry;
+			if (hs == hist_use_it)
+				goto hist_trunc_done;
+			goto hist_init_fail;
 		}
 	} else if (histfsize != 0) {
 		/* negative or too small... */
@@ -840,9 +878,26 @@
 			return;
 		}
 	}
+ hist_trunc_done:
 	histfsize = lseek(histfd, (off_t)0, SEEK_END);
  hist_init_tail:
 	mksh_unlkfd(histfd);
+}
+#endif
+
+void
+hist_init(Source *s)
+{
+	histsave(NULL, NULL, HIST_DISCARD, true);
+
+	if (Flag(FTALKING) == 0)
+		return;
+
+	hstarted = true;
+	hist_source = s;
+
+#if HAVE_PERSISTENT_HISTORY
+	hist_persist_init();
 #endif
 }
 
@@ -909,10 +964,11 @@
 	mksh_lockfd(histfd);
 	sizenow = lseek(histfd, (off_t)0, SEEK_END);
 	if (sizenow < histfsize) {
-		/* the file has shrunk; give up */
-		goto bad;
-	}
-	if (
+		/* the file has shrunk; trust it just appending the new data */
+		/* well, for now, anyway… since mksh strdups all into memory */
+		/* we can use a nicer approach some time later… */
+		;
+	} else if (
 		/* ignore changes when the file is too large */
 		sizenow <= MKSH_MAXHISTFSIZE
 	    &&
@@ -1114,7 +1170,7 @@
 
 	/* signal number (1..ksh_NSIG) or 0? */
 
-	if (ksh_isdigit(*cs))
+	if (ctype(*cs, C_DIGIT))
 		return ((getn(cs, &i) && 0 <= i && i < ksh_NSIG) ?
 		    (&sigtraps[i]) : NULL);
 
diff --git a/src/jobs.c b/src/jobs.c
index 0366004..4df98b7 100644
--- a/src/jobs.c
+++ b/src/jobs.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.121 2016/07/25 00:04:44 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.124 2017/08/08 14:30:10 tg Exp $");
 
 #if HAVE_KILLPG
 #define mksh_killpg		killpg
@@ -39,14 +39,27 @@
 #define PSTOPPED	3
 
 typedef struct proc Proc;
-struct proc {
-	Proc *next;		/* next process in pipeline (if any) */
-	pid_t pid;		/* process id */
+/* to take alignment into consideration */
+struct proc_dummy {
+	Proc *next;
+	pid_t pid;
 	int state;
-	int status;		/* wait status */
+	int status;
+	char command[128];
+};
+/* real structure */
+struct proc {
+	/* next process in pipeline (if any) */
+	Proc *next;
+	/* process id of this Unix process in the job */
+	pid_t pid;
+	/* one of the four P… above */
+	int state;
+	/* wait status */
+	int status;
 	/* process command string from vistree */
-	char command[256 - (ALLOC_OVERHEAD + sizeof(Proc *) +
-	    sizeof(pid_t) + 2 * sizeof(int))];
+	char command[256 - (ALLOC_OVERHEAD +
+	    offsetof(struct proc_dummy, command[0]))];
 };
 
 /* Notify/print flag - j_print() argument */
@@ -1009,8 +1022,14 @@
 	}
 	for (j = job_list; j; j = tmp) {
 		tmp = j->next;
-		if (j->flags & JF_REMOVE)
-			remove_job(j, "notify");
+		if (j->flags & JF_REMOVE) {
+			if (j == async_job || (j->flags & JF_KNOWN)) {
+				j->flags = (j->flags & ~JF_REMOVE) | JF_ZOMBIE;
+				j->job = -1;
+				nzombie++;
+			} else
+				remove_job(j, "notify");
+		}
 	}
 	shf_flush(shl_out);
 #ifndef MKSH_NOPROSPECTOFWORK
@@ -1651,7 +1670,7 @@
 	size_t len;
 	int job = 0;
 
-	if (ksh_isdigit(*cp) && getn(cp, &job)) {
+	if (ctype(*cp, C_DIGIT) && getn(cp, &job)) {
 		/* Look for last_proc->pid (what $! returns) first... */
 		for (j = job_list; j != NULL; j = j->next)
 			if (j->last_proc && j->last_proc->pid == job)
diff --git a/src/lex.c b/src/lex.c
index 78c2ee7..f450221 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.234 2017/04/06 01:59:55 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.239 2017/05/05 22:53:29 tg Exp $");
 
 /*
  * states while lexing word
@@ -131,7 +131,7 @@
 }
 
 #if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
-#define getsc getsc_i
+#define getsc()		ord(getsc_i())
 #else
 static int getsc_r(int);
 
@@ -141,7 +141,7 @@
 	o_getsc_r(c);
 }
 
-#define getsc()		getsc_r(o_getsc())
+#define getsc()		ord(getsc_r(o_getsc()))
 #endif
 
 #define STATE_BSIZE	8
@@ -220,11 +220,11 @@
 	} else {
 		/* normal lexing */
 		state = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
-		while ((c = getsc()) == ' ' || c == '\t')
+		while (ctype((c = getsc()), C_BLANK))
 			;
 		if (c == '#') {
 			ignore_backslash_newline++;
-			while ((c = getsc()) != '\0' && c != '\n')
+			while (!ctype((c = getsc()), C_NUL | C_LF))
 				;
 			ignore_backslash_newline--;
 		}
@@ -245,30 +245,30 @@
 	while (!((c = getsc()) == 0 ||
 	    ((state == SBASE || state == SHEREDELIM) && ctype(c, C_LEX1)))) {
 		if (state == SBASE &&
-		    subshell_nesting_type == /*{*/ '}' &&
-		    c == /*{*/ '}')
+		    subshell_nesting_type == ord(/*{*/ '}') &&
+		    c == ord(/*{*/ '}'))
 			/* possibly end ${ :;} */
 			break;
 		Xcheck(ws, wp);
 		switch (state) {
 		case SADELIM:
-			if (c == '(')
+			if (c == ord('('))
 				statep->nparen++;
-			else if (c == ')')
+			else if (c == ord(')'))
 				statep->nparen--;
-			else if (statep->nparen == 0 && (c == /*{*/ '}' ||
+			else if (statep->nparen == 0 && (c == ord(/*{*/ '}') ||
 			    c == (int)statep->ls_adelim.delimiter)) {
 				*wp++ = ADELIM;
 				*wp++ = c;
-				if (c == /*{*/ '}' || --statep->ls_adelim.num == 0)
+				if (c == ord(/*{*/ '}') || --statep->ls_adelim.num == 0)
 					POP_STATE();
-				if (c == /*{*/ '}')
+				if (c == ord(/*{*/ '}'))
 					POP_STATE();
 				break;
 			}
 			/* FALLTHROUGH */
 		case SBASE:
-			if (c == '[' && (cf & CMDASN)) {
+			if (c == ord('[') && (cf & CMDASN)) {
 				/* temporary */
 				*wp = EOS;
 				if (is_wdvarname(Xstring(ws, wp), false)) {
@@ -301,10 +301,9 @@
 			}
 			/* FALLTHROUGH */
  Sbase1:		/* includes *(...|...) pattern (*+?@!) */
-			if (c == '*' || c == '@' || c == '+' || c == '?' ||
-			    c == '!') {
+			if (ctype(c, C_PATMO)) {
 				c2 = getsc();
-				if (c2 == '(' /*)*/ ) {
+				if (c2 == ord('(' /*)*/)) {
 					*wp++ = OPAT;
 					*wp++ = c;
 					PUSH_STATE(SPATTERN);
@@ -315,7 +314,7 @@
 			/* FALLTHROUGH */
  Sbase2:		/* doesn't include *(...|...) pattern (*+?@!) */
 			switch (c) {
-			case '\\':
+			case ord('\\'):
  getsc_qchar:
 				if ((c = getsc())) {
 					/* trailing \ is lost */
@@ -323,7 +322,7 @@
 					*wp++ = c;
 				}
 				break;
-			case '\'':
+			case ord('\''):
  open_ssquote_unless_heredoc:
 				if ((cf & HEREDOC))
 					goto store_char;
@@ -331,12 +330,12 @@
 				ignore_backslash_newline++;
 				PUSH_STATE(SSQUOTE);
 				break;
-			case '"':
+			case ord('"'):
  open_sdquote:
 				*wp++ = OQUOTE;
 				PUSH_STATE(SDQUOTE);
 				break;
-			case '$':
+			case ord('$'):
 				/*
 				 * processing of dollar sign belongs into
 				 * Subst, except for those which can open
@@ -345,9 +344,9 @@
  subst_dollar_ex:
 				c = getsc();
 				switch (c) {
-				case '"':
+				case ord('"'):
 					goto open_sdquote;
-				case '\'':
+				case ord('\''):
 					goto open_sequote;
 				default:
 					goto SubstS;
@@ -359,15 +358,16 @@
 
  Subst:
 			switch (c) {
-			case '\\':
+			case ord('\\'):
 				c = getsc();
 				switch (c) {
-				case '"':
+				case ord('"'):
 					if ((cf & HEREDOC))
 						goto heredocquote;
 					/* FALLTHROUGH */
-				case '\\':
-				case '$': case '`':
+				case ord('\\'):
+				case ord('$'):
+				case ord('`'):
  store_qchar:
 					*wp++ = QCHAR;
 					*wp++ = c;
@@ -385,12 +385,12 @@
 					break;
 				}
 				break;
-			case '$':
+			case ord('$'):
 				c = getsc();
  SubstS:
-				if (c == '(') /*)*/ {
+				if (c == ord('(' /*)*/)) {
 					c = getsc();
-					if (c == '(') /*)*/ {
+					if (c == ord('(' /*)*/)) {
 						*wp++ = EXPRSUB;
 						PUSH_SRETRACE(SASPAREN);
 						statep->nparen = 2;
@@ -407,8 +407,8 @@
 						memcpy(wp, sp, cz);
 						wp += cz;
 					}
-				} else if (c == '{') /*}*/ {
-					if ((c = getsc()) == '|') {
+				} else if (c == ord('{' /*}*/)) {
+					if ((c = getsc()) == ord('|')) {
 						/*
 						 * non-subenvironment
 						 * value substitution
@@ -425,15 +425,15 @@
 					}
 					ungetsc(c);
 					*wp++ = OSUBST;
-					*wp++ = '{'; /*}*/
+					*wp++ = '{' /*}*/;
 					wp = get_brace_var(&ws, wp);
 					c = getsc();
 					/* allow :# and :% (ksh88 compat) */
-					if (c == ':') {
+					if (c == ord(':')) {
 						*wp++ = CHAR;
 						*wp++ = c;
 						c = getsc();
-						if (c == ':') {
+						if (c == ord(':')) {
 							*wp++ = CHAR;
 							*wp++ = '0';
 							*wp++ = ADELIM;
@@ -444,10 +444,9 @@
 							statep->ls_adelim.num = 1;
 							statep->nparen = 0;
 							break;
-						} else if (ksh_isdigit(c) ||
-						    c == '('/*)*/ || c == ' ' ||
+						} else if (ctype(c, C_DIGIT | C_DOLAR | C_SPC) ||
 						    /*XXX what else? */
-						    c == '$') {
+						    c == '(' /*)*/) {
 							/* substring subst. */
 							if (c != ' ') {
 								*wp++ = CHAR;
@@ -466,7 +465,7 @@
  parse_adelim_slash:
 						*wp++ = CHAR;
 						*wp++ = c;
-						if ((c = getsc()) == '/') {
+						if ((c = getsc()) == ord('/')) {
 							*wp++ = c2;
 							*wp++ = c;
 						} else
@@ -480,7 +479,7 @@
 					} else if (c == '@') {
 						c2 = getsc();
 						ungetsc(c2);
-						if (c2 == '/') {
+						if (c2 == ord('/')) {
 							c2 = CHAR;
 							goto parse_adelim_slash;
 						}
@@ -489,7 +488,7 @@
 					 * If this is a trim operation,
 					 * treat (,|,) specially in STBRACE.
 					 */
-					if (ksh_issubop2(c)) {
+					if (ctype(c, C_SUB2)) {
 						ungetsc(c);
 						if (Flag(FSH))
 							PUSH_STATE(STBRACEBOURNE);
@@ -503,14 +502,14 @@
 						else
 							PUSH_STATE(SBRACE);
 					}
-				} else if (ksh_isalphx(c)) {
+				} else if (ctype(c, C_ALPHX)) {
 					*wp++ = OSUBST;
 					*wp++ = 'X';
 					do {
 						Xcheck(ws, wp);
 						*wp++ = c;
 						c = getsc();
-					} while (ksh_isalnux(c));
+					} while (ctype(c, C_ALNUX));
 					*wp++ = '\0';
 					*wp++ = CSUBST;
 					*wp++ = 'X';
@@ -529,7 +528,7 @@
 					ungetsc(c);
 				}
 				break;
-			case '`':
+			case ord('`'):
  subst_gravis:
 				PUSH_STATE(SBQUOTE);
 				*wp++ = COMASUB;
@@ -573,11 +572,11 @@
 			break;
 
 		case SEQUOTE:
-			if (c == '\'') {
+			if (c == ord('\'')) {
 				POP_STATE();
 				*wp++ = CQUOTE;
 				ignore_backslash_newline--;
-			} else if (c == '\\') {
+			} else if (c == ord('\\')) {
 				if ((c2 = unbksl(true, getsc_i, ungetsc)) == -1)
 					c2 = getsc();
 				if (c2 == 0)
@@ -605,7 +604,7 @@
 			break;
 
 		case SSQUOTE:
-			if (c == '\'') {
+			if (c == ord('\'')) {
 				POP_STATE();
 				if ((cf & HEREDOC) || state == SQBRACE)
 					goto store_char;
@@ -618,7 +617,7 @@
 			break;
 
 		case SDQUOTE:
-			if (c == '"') {
+			if (c == ord('"')) {
 				POP_STATE();
 				*wp++ = CQUOTE;
 			} else
@@ -627,15 +626,15 @@
 
 		/* $(( ... )) */
 		case SASPAREN:
-			if (c == '(')
+			if (c == ord('('))
 				statep->nparen++;
-			else if (c == ')') {
+			else if (c == ord(')')) {
 				statep->nparen--;
 				if (statep->nparen == 1) {
 					/* end of EXPRSUB */
 					POP_SRETRACE();
 
-					if ((c2 = getsc()) == /*(*/ ')') {
+					if ((c2 = getsc()) == ord(/*(*/ ')')) {
 						cz = strlen(sp) - 2;
 						XcheckN(ws, wp, cz);
 						memcpy(wp, sp + 1, cz);
@@ -667,7 +666,7 @@
 			goto Sbase2;
 
 		case SQBRACE:
-			if (c == '\\') {
+			if (c == ord('\\')) {
 				/*
 				 * perform POSIX "quote removal" if the back-
 				 * slash is "special", i.e. same cases as the
@@ -676,26 +675,26 @@
 				 * write QCHAR+c, otherwise CHAR+\+CHAR+c are
 				 * emitted (in heredocquote:)
 				 */
-				if ((c = getsc()) == '"' || c == '\\' ||
-				    c == '$' || c == '`' || c == /*{*/'}')
+				if ((c = getsc()) == ord('"') || c == ord('\\') ||
+				    ctype(c, C_DOLAR | C_GRAVE) || c == ord(/*{*/ '}'))
 					goto store_qchar;
 				goto heredocquote;
 			}
 			goto common_SQBRACE;
 
 		case SBRACE:
-			if (c == '\'')
+			if (c == ord('\''))
 				goto open_ssquote_unless_heredoc;
-			else if (c == '\\')
+			else if (c == ord('\\'))
 				goto getsc_qchar;
  common_SQBRACE:
-			if (c == '"')
+			if (c == ord('"'))
 				goto open_sdquote;
-			else if (c == '$')
+			else if (c == ord('$'))
 				goto subst_dollar_ex;
-			else if (c == '`')
+			else if (c == ord('`'))
 				goto subst_gravis;
-			else if (c != /*{*/ '}')
+			else if (c != ord(/*{*/ '}'))
 				goto store_char;
 			POP_STATE();
 			*wp++ = CSUBST;
@@ -704,16 +703,16 @@
 
 		/* Same as SBASE, except (,|,) treated specially */
 		case STBRACEKORN:
-			if (c == '|')
+			if (c == ord('|'))
 				*wp++ = SPAT;
-			else if (c == '(') {
+			else if (c == ord('(')) {
 				*wp++ = OPAT;
 				/* simile for @ */
 				*wp++ = ' ';
 				PUSH_STATE(SPATTERN);
 			} else /* FALLTHROUGH */
 		case STBRACEBOURNE:
-			  if (c == /*{*/ '}') {
+			  if (c == ord(/*{*/ '}')) {
 				POP_STATE();
 				*wp++ = CSUBST;
 				*wp++ = /*{*/ '}';
@@ -722,20 +721,20 @@
 			break;
 
 		case SBQUOTE:
-			if (c == '`') {
+			if (c == ord('`')) {
 				*wp++ = 0;
 				POP_STATE();
-			} else if (c == '\\') {
+			} else if (c == ord('\\')) {
 				switch (c = getsc()) {
 				case 0:
 					/* trailing \ is lost */
 					break;
-				case '$':
-				case '`':
-				case '\\':
+				case ord('$'):
+				case ord('`'):
+				case ord('\\'):
 					*wp++ = c;
 					break;
-				case '"':
+				case ord('"'):
 					if (statep->ls_bool) {
 						*wp++ = c;
 						break;
@@ -756,10 +755,10 @@
 
 		/* LETEXPR: (( ... )) */
 		case SLETPAREN:
-			if (c == /*(*/ ')') {
+			if (c == ord(/*(*/ ')')) {
 				if (statep->nparen > 0)
 					--statep->nparen;
-				else if ((c2 = getsc()) == /*(*/ ')') {
+				else if ((c2 = getsc()) == ord(/*(*/ ')')) {
 					c = 0;
 					*wp++ = CQUOTE;
 					goto Done;
@@ -780,10 +779,10 @@
 					s->start = s->str = s->u.freeme = dp;
 					s->next = source;
 					source = s;
-					ungetsc('('/*)*/);
-					return ('('/*)*/);
+					ungetsc('(' /*)*/);
+					return (ord('(' /*)*/));
 				}
-			} else if (c == '(')
+			} else if (c == ord('('))
 				/*
 				 * parentheses inside quotes and
 				 * backslashes are lost, but AT&T ksh
@@ -799,26 +798,26 @@
 			 * $ and `...` are not to be treated specially
 			 */
 			switch (c) {
-			case '\\':
+			case ord('\\'):
 				if ((c = getsc())) {
 					/* trailing \ is lost */
 					*wp++ = QCHAR;
 					*wp++ = c;
 				}
 				break;
-			case '\'':
+			case ord('\''):
 				goto open_ssquote_unless_heredoc;
-			case '$':
-				if ((c2 = getsc()) == '\'') {
+			case ord('$'):
+				if ((c2 = getsc()) == ord('\'')) {
  open_sequote:
 					*wp++ = OQUOTE;
 					ignore_backslash_newline++;
 					PUSH_STATE(SEQUOTE);
 					statep->ls_bool = false;
 					break;
-				} else if (c2 == '"') {
+				} else if (c2 == ord('"')) {
 					/* FALLTHROUGH */
-			case '"':
+			case ord('"'):
 					PUSH_SRETRACE(SHEREDQUOTE);
 					break;
 				}
@@ -832,7 +831,7 @@
 
 		/* " in << or <<- delimiter */
 		case SHEREDQUOTE:
-			if (c != '"')
+			if (c != ord('"'))
 				goto Subst;
 			POP_SRETRACE();
 			dp = strnul(sp) - 1;
@@ -845,10 +844,10 @@
 			while ((c = *dp++)) {
 				if (c == '\\') {
 					switch ((c = *dp++)) {
-					case '\\':
-					case '"':
-					case '$':
-					case '`':
+					case ord('\\'):
+					case ord('"'):
+					case ord('$'):
+					case ord('`'):
 						break;
 					default:
 						*wp++ = CHAR;
@@ -866,12 +865,12 @@
 
 		/* in *(...|...) pattern (*+?@!) */
 		case SPATTERN:
-			if (c == /*(*/ ')') {
+			if (c == ord(/*(*/ ')')) {
 				*wp++ = CPAT;
 				POP_STATE();
-			} else if (c == '|') {
+			} else if (c == ord('|')) {
 				*wp++ = SPAT;
-			} else if (c == '(') {
+			} else if (c == ord('(')) {
 				*wp++ = OPAT;
 				/* simile for @ */
 				*wp++ = ' ';
@@ -894,14 +893,14 @@
 	dp = Xstring(ws, wp);
 	if (state == SBASE && (
 	    (c == '&' && !Flag(FSH) && !Flag(FPOSIX)) ||
-	    c == '<' || c == '>') && ((c2 = Xlength(ws, wp)) == 0 ||
-	    (c2 == 2 && dp[0] == CHAR && ksh_isdigit(dp[1])))) {
+	    ctype(c, C_ANGLE)) && ((c2 = Xlength(ws, wp)) == 0 ||
+	    (c2 == 2 && dp[0] == CHAR && ctype(dp[1], C_DIGIT)))) {
 		struct ioword *iop = alloc(sizeof(struct ioword), ATEMP);
 
 		iop->unit = c2 == 2 ? ksh_numdig(dp[1]) : c == '<' ? 0 : 1;
 
 		if (c == '&') {
-			if ((c2 = getsc()) != '>') {
+			if ((c2 = getsc()) != ord('>')) {
 				ungetsc(c2);
 				goto no_iop;
 			}
@@ -912,22 +911,22 @@
 
 		c2 = getsc();
 		/* <<, >>, <> are ok, >< is not */
-		if (c == c2 || (c == '<' && c2 == '>')) {
+		if (c == c2 || (c == ord('<') && c2 == ord('>'))) {
 			iop->ioflag |= c == c2 ?
-			    (c == '>' ? IOCAT : IOHERE) : IORDWR;
+			    (c == ord('>') ? IOCAT : IOHERE) : IORDWR;
 			if (iop->ioflag == IOHERE) {
-				if ((c2 = getsc()) == '-')
+				if ((c2 = getsc()) == ord('-'))
 					iop->ioflag |= IOSKIP;
-				else if (c2 == '<')
+				else if (c2 == ord('<'))
 					iop->ioflag |= IOHERESTR;
 				else
 					ungetsc(c2);
 			}
-		} else if (c2 == '&')
-			iop->ioflag |= IODUP | (c == '<' ? IORDUP : 0);
+		} else if (c2 == ord('&'))
+			iop->ioflag |= IODUP | (c == ord('<') ? IORDUP : 0);
 		else {
-			iop->ioflag |= c == '>' ? IOWRITE : IOREAD;
-			if (c == '>' && c2 == '|')
+			iop->ioflag |= c == ord('>') ? IOWRITE : IOREAD;
+			if (c == ord('>') && c2 == ord('|'))
 				iop->ioflag |= IOCLOB;
 			else
 				ungetsc(c2);
@@ -948,29 +947,30 @@
 		/* free word */
 		Xfree(ws, wp);
 		/* no word, process LEX1 character */
-		if ((c == '|') || (c == '&') || (c == ';') || (c == '('/*)*/)) {
+		if ((c == ord('|')) || (c == ord('&')) || (c == ord(';')) ||
+		    (c == ord('(' /*)*/))) {
 			if ((c2 = getsc()) == c)
-				c = (c == ';') ? BREAK :
-				    (c == '|') ? LOGOR :
-				    (c == '&') ? LOGAND :
-				    /* c == '(' ) */ MDPAREN;
-			else if (c == '|' && c2 == '&')
+				c = (c == ord(';')) ? BREAK :
+				    (c == ord('|')) ? LOGOR :
+				    (c == ord('&')) ? LOGAND :
+				    /* c == ord('(' )) */ MDPAREN;
+			else if (c == ord('|') && c2 == ord('&'))
 				c = COPROC;
-			else if (c == ';' && c2 == '|')
+			else if (c == ord(';') && c2 == ord('|'))
 				c = BRKEV;
-			else if (c == ';' && c2 == '&')
+			else if (c == ord(';') && c2 == ord('&'))
 				c = BRKFT;
 			else
 				ungetsc(c2);
 #ifndef MKSH_SMALL
 			if (c == BREAK) {
-				if ((c2 = getsc()) == '&')
+				if ((c2 = getsc()) == ord('&'))
 					c = BRKEV;
 				else
 					ungetsc(c2);
 			}
 #endif
-		} else if (c == '\n') {
+		} else if (c == ord('\n')) {
 			if (cf & HEREDELIM)
 				ungetsc(c);
 			else {
@@ -1025,7 +1025,7 @@
 
 		if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) &&
 		    (!(cf & ESACONLY) || p->val.i == ESAC ||
-		    p->val.i == /*{*/ '}')) {
+		    p->val.i == ord(/*{*/ '}'))) {
 			afree(yylval.cp, ATEMP);
 			return (p->val.i);
 		}
@@ -1038,7 +1038,7 @@
 			const char *cp = source->str;
 
 			/* prefer POSIX but not Korn functions over aliases */
-			while (*cp == ' ' || *cp == '\t')
+			while (ctype(*cp, C_BLANK))
 				/*
 				 * this is like getsc() without skipping
 				 * over Source boundaries (including not
@@ -1136,7 +1136,7 @@
 	if (!*eofp) {
 		/* end of here document marker, what to do? */
 		switch (c) {
-		case /*(*/ ')':
+		case ord(/*(*/ ')'):
 			if (!subshell_nesting_type)
 				/*-
 				 * not allowed outside $(...) or (...)
@@ -1151,7 +1151,7 @@
 			 * Allow EOF here to commands without trailing
 			 * newlines (mksh -c '...') will work as well.
 			 */
-		case '\n':
+		case ord('\n'):
 			/* Newline terminates here document marker */
 			goto heredoc_found_terminator;
 		}
@@ -1233,7 +1233,7 @@
 	Source *s = source;
 	int c;
 
-	while ((c = *s->str++) == 0) {
+	while ((c = ord(*s->str++)) == 0) {
 		/* return 0 for EOF by default */
 		s->str = NULL;
 		switch (s->type) {
@@ -1275,7 +1275,7 @@
 				source->flags |= s->flags & SF_ALIAS;
 				s = source;
 			} else if (*s->u.tblp->val.s &&
-			    (c = strnul(s->u.tblp->val.s)[-1], ksh_isspace(c))) {
+			    ctype((c = strnul(s->u.tblp->val.s)[-1]), C_SPACE)) {
 				/* pop source stack */
 				source = s = s->next;
 				/*
@@ -1435,7 +1435,7 @@
 	} else if (interactive && cur_prompt == PS1) {
  check_for_sole_return:
 		cp = Xstring(s->xs, xp);
-		while (*cp && ctype(*cp, C_IFSWS))
+		while (ctype(*cp, C_IFSWS))
 			++cp;
 		if (!*cp) {
 			histsave(&s->line, NULL, HIST_FLUSH, true);
@@ -1528,7 +1528,7 @@
 	for (; *cp; cp++) {
 		if (indelimit && *cp != delimiter)
 			;
-		else if (*cp == '\n' || *cp == '\r') {
+		else if (ctype(*cp, C_CR | C_LF)) {
 			lines += columns / x_cols + ((*cp == '\n') ? 1 : 0);
 			columns = 0;
 		} else if (*cp == '\t') {
@@ -1538,7 +1538,7 @@
 				columns--;
 		} else if (*cp == delimiter)
 			indelimit = !indelimit;
-		else if (UTFMODE && ((unsigned char)*cp > 0x7F)) {
+		else if (UTFMODE && (rtt2asc(*cp) > 0x7F)) {
 			const char *cp2;
 			columns += utf_widthadj(cp, &cp2);
 			if (doprint && (indelimit ||
@@ -1580,39 +1580,39 @@
 
 				c2 = getsc();
 				ungetsc(c2);
-				if (c2 != /*{*/ '}') {
+				if (ord(c2) != ord(/*{*/ '}')) {
 					ungetsc(c);
 					goto out;
 				}
 			}
 			goto ps_common;
 		case PS_SAW_BANG:
-			switch (c) {
-			case '@':
-			case '#':
-			case '-':
-			case '?':
+			switch (ord(c)) {
+			case ord('@'):
+			case ord('#'):
+			case ord('-'):
+			case ord('?'):
 				goto out;
 			}
 			goto ps_common;
 		case PS_INITIAL:
-			switch (c) {
-			case '%':
+			switch (ord(c)) {
+			case ord('%'):
 				state = PS_SAW_PERCENT;
 				goto next;
-			case '#':
+			case ord('#'):
 				state = PS_SAW_HASH;
 				goto next;
-			case '!':
+			case ord('!'):
 				state = PS_SAW_BANG;
 				goto next;
 			}
 			/* FALLTHROUGH */
 		case PS_SAW_PERCENT:
  ps_common:
-			if (ksh_isalphx(c))
+			if (ctype(c, C_ALPHX))
 				state = PS_IDENT;
-			else if (ksh_isdigit(c))
+			else if (ctype(c, C_DIGIT))
 				state = PS_NUMBER;
 			else if (ctype(c, C_VAR1))
 				state = PS_VAR1;
@@ -1620,14 +1620,15 @@
 				goto out;
 			break;
 		case PS_IDENT:
-			if (!ksh_isalnux(c)) {
-				if (c == '[') {
+			if (!ctype(c, C_ALNUX)) {
+				if (ord(c) == ord('[')) {
 					char *tmp, *p;
 
 					if (!arraysub(&tmp))
 						yyerror("missing ]");
 					*wp++ = c;
-					for (p = tmp; *p; ) {
+					p = tmp;
+					while (*p) {
 						Xcheck(*wsp, wp);
 						*wp++ = *p++;
 					}
@@ -1640,7 +1641,7 @@
  next:
 			break;
 		case PS_NUMBER:
-			if (!ksh_isdigit(c))
+			if (!ctype(c, C_DIGIT))
 				goto out;
 			break;
 		case PS_VAR1:
@@ -1675,9 +1676,9 @@
 		c = getsc();
 		Xcheck(ws, wp);
 		*wp++ = c;
-		if (c == '[')
+		if (ord(c) == ord('['))
 			depth++;
-		else if (c == ']')
+		else if (ord(c) == ord(']'))
 			depth--;
 	} while (depth > 0 && c && c != '\n');
 
@@ -1756,19 +1757,19 @@
 {
 	int c;
 
-	if ((unsigned char)(c = o_getsc_u()) != 0xEF) {
+	if (rtt2asc((c = o_getsc_u())) != 0xEF) {
 		ungetsc_i(c);
 		return;
 	}
-	if ((unsigned char)(c = o_getsc_u()) != 0xBB) {
+	if (rtt2asc((c = o_getsc_u())) != 0xBB) {
 		ungetsc_i(c);
-		ungetsc_i(0xEF);
+		ungetsc_i(asc2rtt(0xEF));
 		return;
 	}
-	if ((unsigned char)(c = o_getsc_u()) != 0xBF) {
+	if (rtt2asc((c = o_getsc_u())) != 0xBF) {
 		ungetsc_i(c);
-		ungetsc_i(0xBB);
-		ungetsc_i(0xEF);
+		ungetsc_i(asc2rtt(0xBB));
+		ungetsc_i(asc2rtt(0xEF));
 		return;
 	}
 	UTFMODE |= 8;
diff --git a/src/main.c b/src/main.c
index 1286b07..b4d7244 100644
--- a/src/main.c
+++ b/src/main.c
@@ -34,7 +34,7 @@
 #include <locale.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/main.c,v 1.332 2017/04/12 16:01:45 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.342 2017/04/28 11:13:47 tg Exp $");
 
 extern char **environ;
 
@@ -236,6 +236,11 @@
 	ssize_t k;
 #endif
 
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+	ebcdic_init();
+#endif
+	set_ifs(TC_IFSWS);
+
 #ifdef __OS2__
 	for (i = 0; i < 3; ++i)
 		if (!isatty(i))
@@ -333,8 +338,6 @@
 
 	initvar();
 
-	initctypes();
-
 	inittraps();
 
 	coproc_init();
@@ -409,12 +412,12 @@
 
 	/* override default PATH regardless of environment */
 #ifdef MKSH_DEFPATH_OVERRIDE
-	 vp = global(TPATH);
-	 setstr(vp, MKSH_DEFPATH_OVERRIDE, KSH_RETURN_ERROR);
+	vp = global(TPATH);
+	setstr(vp, MKSH_DEFPATH_OVERRIDE, KSH_RETURN_ERROR);
 #endif
 
 	/* for security */
-	typeset("IFS= \t\n", 0, 0, 0, 0);
+	typeset(TinitIFS, 0, 0, 0, 0);
 
 	/* assign default shell variable values */
 	typeset("PATHSEP=" MKSH_PATHSEPS, 0, 0, 0, 0);
@@ -497,7 +500,7 @@
 		if (!(s->start = s->str = argv[argi++]))
 			errorf(Tf_optfoo, "", "", 'c', Treq_arg);
 		while (*s->str) {
-			if (*s->str != ' ' && ctype(*s->str, C_QUOTE))
+			if (ctype(*s->str, C_QUOTE))
 				break;
 			s->str++;
 		}
@@ -1554,7 +1557,7 @@
 		goto illegal_fd_name;
 	if (name[0] == 'p')
 		return (coproc_getfd(mode, emsgp));
-	if (!ksh_isdigit(name[0])) {
+	if (!ctype(name[0], C_DIGIT)) {
  illegal_fd_name:
 		if (emsgp)
 			*emsgp = "illegal file descriptor name";
@@ -1893,7 +1896,7 @@
 	const struct tbl *a = *((const struct tbl * const *)p1);
 	const struct tbl *b = *((const struct tbl * const *)p2);
 
-	return (strcmp(a->name, b->name));
+	return (ascstrcmp(a->name, b->name));
 }
 
 struct tbl **
diff --git a/src/misc.c b/src/misc.c
index 6957c22..1205072 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -5,6 +5,8 @@
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
  *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
+ * Copyright (c) 2015
+ *	Daniel Richard G. <skunk@iSKUNK.ORG>
  *
  * Provided that these terms and disclaimer and all copyright notices
  * are retained or reproduced in an accompanying document, permission
@@ -30,7 +32,7 @@
 #include <grp.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.255 2017/04/12 16:46:22 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.279 2017/08/07 21:39:25 tg Exp $");
 
 #define KSH_CHVT_FLAG
 #ifdef MKSH_SMALL
@@ -47,7 +49,8 @@
 static const unsigned char *pat_scan(const unsigned char *,
     const unsigned char *, bool) MKSH_A_PURE;
 static int do_gmatch(const unsigned char *, const unsigned char *,
-    const unsigned char *, const unsigned char *) MKSH_A_PURE;
+    const unsigned char *, const unsigned char *,
+    const unsigned char *) MKSH_A_PURE;
 static const unsigned char *gmatch_cclass(const unsigned char *, unsigned char)
     MKSH_A_PURE;
 #ifdef KSH_CHVT_CODE
@@ -68,37 +71,6 @@
 #define DO_SETUID(func, argvec) func argvec
 #endif
 
-/*
- * Fast character classes
- */
-void
-setctypes(const char *s, int t)
-{
-	if (t & C_IFS) {
-		unsigned int i = 0;
-
-		while (++i <= UCHAR_MAX)
-			chtypes[i] &= ~C_IFS;
-		/* include '\0' in C_IFS */
-		chtypes[0] |= C_IFS;
-	}
-	while (*s != 0)
-		chtypes[(unsigned char)*s++] |= t;
-}
-
-void
-initctypes(void)
-{
-	setctypes(letters_uc, C_ALPHX);
-	setctypes(letters_lc, C_ALPHX);
-	chtypes['_'] |= C_ALPHX;
-	setctypes("0123456789", C_DIGIT);
-	setctypes(TC_LEX1, C_LEX1);
-	setctypes("*@#!$-?", C_VAR1);
-	setctypes(TC_IFSWS, C_IFSWS);
-	setctypes("=-+?", C_SUBOP1);
-	setctypes("\t\n \"#$&'()*;<=>?[\\]`|", C_QUOTE);
-}
 
 /* called from XcheckN() to grow buffer */
 char *
@@ -147,7 +119,7 @@
 {
 	size_t i = 0;
 
-	if ((n[0] == '-' || n[0] == '+') && n[1] && !n[2])
+	if (ctype(n[0], C_MINUS | C_PLUS) && n[1] && !n[2])
 		while (i < NELEM(options)) {
 			if (OFC(i) == n[1])
 				return (i);
@@ -299,6 +271,11 @@
 	} else if ((f == FPOSIX || f == FSH) && newval) {
 		/* Turning on -o posix or -o sh? */
 		Flag(FBRACEEXPAND) = 0;
+		/* Turning on -o posix? */
+		if (f == FPOSIX) {
+			/* C locale required for compliance */
+			UTFMODE = 0;
+		}
 	} else if (f == FTALKING) {
 		/* Changing interactive flag? */
 		if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid)
@@ -483,7 +460,7 @@
 		}
 	}
 	if (!(go.info & GI_MINUSMINUS) && argv[go.optind] &&
-	    (argv[go.optind][0] == '-' || argv[go.optind][0] == '+') &&
+	    ctype(argv[go.optind][0], C_MINUS | C_PLUS) &&
 	    argv[go.optind][1] == '\0') {
 		/* lone - clears -v and -x flags */
 		if (argv[go.optind][0] == '-') {
@@ -512,7 +489,7 @@
 		for (i = go.optind; argv[i]; i++)
 			;
 		qsort(&argv[go.optind], i - go.optind, sizeof(void *),
-		    xstrcmp);
+		    ascpstrcmp);
 	}
 	if (arrayset)
 		go.optind += set_array(array, tobool(arrayset > 0),
@@ -533,7 +510,7 @@
 
 	do {
 		c = *s++;
-	} while (ksh_isspace(c));
+	} while (ctype(c, C_SPACE));
 
 	switch (c) {
 	case '-':
@@ -545,7 +522,7 @@
 	}
 
 	do {
-		if (!ksh_isdigit(c))
+		if (!ctype(c, C_DIGIT))
 			/* not numeric */
 			return (0);
 		if (num.u > 214748364U)
@@ -585,7 +562,7 @@
 	sp = cp;
  simplify_gmatch_pat1a:
 	dp = cp;
-	se = sp + strlen((const void *)sp);
+	se = strnul(sp);
 	while ((c = *sp++)) {
 		if (!ISMAGIC(c)) {
 			*dp++ = c;
@@ -657,29 +634,30 @@
 	if (s == NULL || p == NULL)
 		return (0);
 
-	se = s + strlen(s);
-	pe = p + strlen(p);
+	pe = strnul(p);
 	/*
 	 * isfile is false iff no syntax check has been done on
-	 * the pattern. If check fails, just to a strcmp().
+	 * the pattern. If check fails, just do a strcmp().
 	 */
-	if (!isfile && !has_globbing(p, pe)) {
+	if (!isfile && !has_globbing(p)) {
 		size_t len = pe - p + 1;
 		char tbuf[64];
 		char *t = len <= sizeof(tbuf) ? tbuf : alloc(len, ATEMP);
 		debunk(t, p, len);
 		return (!strcmp(t, s));
 	}
+	se = strnul(s);
 
 	/*
 	 * since the do_gmatch() engine sucks so much, we must do some
 	 * pattern simplifications
 	 */
 	pnew = simplify_gmatch_pattern((const unsigned char *)p);
-	pe = pnew + strlen(pnew);
+	pe = strnul(pnew);
 
 	rv = do_gmatch((const unsigned char *)s, (const unsigned char *)se,
-	    (const unsigned char *)pnew, (const unsigned char *)pe);
+	    (const unsigned char *)pnew, (const unsigned char *)pe,
+	    (const unsigned char *)s);
 	afree(pnew, ATEMP);
 	return (rv);
 }
@@ -690,7 +668,7 @@
  * Syntax errors are:
  *	- [ with no closing ]
  *	- imbalanced $(...) expression
- *	- [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d))
+ *	- [...] and *(...) not nested (eg, @(a[b|)c], *(a[b|c]d))
  */
 /*XXX
  * - if no magic,
@@ -701,76 +679,101 @@
  *	return ?
  * - return ?
  */
-int
-has_globbing(const char *xp, const char *xpe)
+bool
+has_globbing(const char *pat)
 {
-	const unsigned char *p = (const unsigned char *) xp;
-	const unsigned char *pe = (const unsigned char *) xpe;
-	int c;
-	int nest = 0, bnest = 0;
+	unsigned char c, subc;
 	bool saw_glob = false;
-	/* inside [...] */
-	bool in_bracket = false;
+	unsigned int nest = 0;
+	const unsigned char *p = (const unsigned char *)pat;
+	const unsigned char *s;
 
-	for (; p < pe; p++) {
-		if (!ISMAGIC(*p))
+	while ((c = *p++)) {
+		/* regular character? ok. */
+		if (!ISMAGIC(c))
 			continue;
-		if ((c = *++p) == '*' || c == '?')
+		/* MAGIC + NUL? abort. */
+		if (!(c = *p++))
+			return (false);
+		/* some specials */
+		if (ord(c) == ord('*') || ord(c) == ord('?')) {
+			/* easy glob, accept */
 			saw_glob = true;
-		else if (c == '[') {
-			if (!in_bracket) {
-				saw_glob = true;
-				in_bracket = true;
-				if (ISMAGIC(p[1]) && p[2] == '!')
-					p += 2;
-				if (ISMAGIC(p[1]) && p[2] == ']')
-					p += 2;
+		} else if (ord(c) == ord('[')) {
+			/* bracket expression; eat negation and initial ] */
+			if (ISMAGIC(p[0]) && ord(p[1]) == ord('!'))
+				p += 2;
+			if (ISMAGIC(p[0]) && ord(p[1]) == ord(']'))
+				p += 2;
+			/* check next string part */
+			s = p;
+			while ((c = *s++)) {
+				/* regular chars are ok */
+				if (!ISMAGIC(c))
+					continue;
+				/* MAGIC + NUL cannot happen */
+				if (!(c = *s++))
+					return (false);
+				/* terminating bracket? */
+				if (ord(c) == ord(']')) {
+					/* accept and continue */
+					p = s;
+					saw_glob = true;
+					break;
+				}
+				/* sub-bracket expressions */
+				if (ord(c) == ord('[') && (
+				    /* collating element? */
+				    ord(*s) == ord('.') ||
+				    /* equivalence class? */
+				    ord(*s) == ord('=') ||
+				    /* character class? */
+				    ord(*s) == ord(':'))) {
+					/* must stop with exactly the same c */
+					subc = *s++;
+					/* arbitrarily many chars in betwixt */
+					while ((c = *s++))
+						/* but only this sequence... */
+						if (c == subc && ISMAGIC(*s) &&
+						    ord(s[1]) == ord(']')) {
+							/* accept, terminate */
+							s += 2;
+							break;
+						}
+					/* EOS without: reject bracket expr */
+					if (!c)
+						break;
+					/* continue; */
+				}
+				/* anything else just goes on */
 			}
-			/*XXX Do we need to check ranges here? POSIX Q */
-		} else if (c == ']') {
-			if (in_bracket) {
-				if (bnest)
-					/* [a*(b]) */
-					return (0);
-				in_bracket = false;
-			}
-		} else if ((c & 0x80) && vstrchr("*+?@! ", c & 0x7f)) {
+		} else if ((c & 0x80) && ctype(c & 0x7F, C_PATMO | C_SPC)) {
+			/* opening pattern */
 			saw_glob = true;
-			if (in_bracket)
-				bnest++;
-			else
-				nest++;
-		} else if (c == '|') {
-			if (in_bracket && !bnest)
-				/* *(a[foo|bar]) */
-				return (0);
-		} else if (c == /*(*/ ')') {
-			if (in_bracket) {
-				if (!bnest--)
-					/* *(a[b)c] */
-					return (0);
-			} else if (nest)
-				nest--;
+			++nest;
+		} else if (ord(c) == ord(/*(*/ ')')) {
+			/* closing pattern */
+			if (nest)
+				--nest;
 		}
-		/*
-		 * else must be a MAGIC-MAGIC, or MAGIC-!,
-		 * MAGIC--, MAGIC-], MAGIC-{, MAGIC-, MAGIC-}
-		 */
 	}
-	return (saw_glob && !in_bracket && !nest);
+	return (saw_glob && !nest);
 }
 
 /* Function must return either 0 or 1 (assumed by code for 0x80|'!') */
 static int
 do_gmatch(const unsigned char *s, const unsigned char *se,
-    const unsigned char *p, const unsigned char *pe)
+    const unsigned char *p, const unsigned char *pe,
+    const unsigned char *smin)
 {
-	unsigned char sc, pc;
+	unsigned char sc, pc, sl = 0;
 	const unsigned char *prest, *psub, *pnext;
 	const unsigned char *srest;
 
 	if (s == NULL || p == NULL)
 		return (0);
+	if (s > smin && s <= se)
+		sl = s[-1];
 	while (p < pe) {
 		pc = *p++;
 		sc = s < se ? *s : '\0';
@@ -778,15 +781,39 @@
 		if (!ISMAGIC(pc)) {
 			if (sc != pc)
 				return (0);
+			sl = sc;
 			continue;
 		}
-		switch (*p++) {
-		case '[':
+		switch (ord(*p++)) {
+		case ord('['):
+			/* BSD cclass extension? */
+			if (ISMAGIC(p[0]) && ord(p[1]) == ord('[') &&
+			    ord(p[2]) == ord(':') &&
+			    ctype((pc = p[3]), C_ANGLE) &&
+			    ord(p[4]) == ord(':') &&
+			    ISMAGIC(p[5]) && ord(p[6]) == ord(']') &&
+			    ISMAGIC(p[7]) && ord(p[8]) == ord(']')) {
+				/* zero-length match */
+				--s;
+				p += 9;
+				/* word begin? */
+				if (ord(pc) == ord('<') &&
+				    !ctype(sl, C_ALNUX) &&
+				    ctype(sc, C_ALNUX))
+					break;
+				/* word end? */
+				if (ord(pc) == ord('>') &&
+				    ctype(sl, C_ALNUX) &&
+				    !ctype(sc, C_ALNUX))
+					break;
+				/* neither */
+				return (0);
+			}
 			if (sc == 0 || (p = gmatch_cclass(p, sc)) == NULL)
 				return (0);
 			break;
 
-		case '?':
+		case ord('?'):
 			if (sc == 0)
 				return (0);
 			if (UTFMODE) {
@@ -795,39 +822,39 @@
 			}
 			break;
 
-		case '*':
+		case ord('*'):
 			if (p == pe)
 				return (1);
 			s--;
 			do {
-				if (do_gmatch(s, se, p, pe))
+				if (do_gmatch(s, se, p, pe, smin))
 					return (1);
 			} while (s++ < se);
 			return (0);
 
 		/**
-		 * [*+?@!](pattern|pattern|..)
+		 * [+*?@!](pattern|pattern|..)
 		 * This is also needed for ${..%..}, etc.
 		 */
 
 		/* matches one or more times */
-		case 0x80|'+':
+		case 0x80|ord('+'):
 		/* matches zero or more times */
-		case 0x80|'*':
+		case 0x80|ord('*'):
 			if (!(prest = pat_scan(p, pe, false)))
 				return (0);
 			s--;
 			/* take care of zero matches */
-			if (p[-1] == (0x80 | '*') &&
-			    do_gmatch(s, se, prest, pe))
+			if (ord(p[-1]) == (0x80 | ord('*')) &&
+			    do_gmatch(s, se, prest, pe, smin))
 				return (1);
 			for (psub = p; ; psub = pnext) {
 				pnext = pat_scan(psub, pe, true);
 				for (srest = s; srest <= se; srest++) {
-					if (do_gmatch(s, srest, psub, pnext - 2) &&
-					    (do_gmatch(srest, se, prest, pe) ||
-					    (s != srest && do_gmatch(srest,
-					    se, p - 2, pe))))
+					if (do_gmatch(s, srest, psub, pnext - 2, smin) &&
+					    (do_gmatch(srest, se, prest, pe, smin) ||
+					    (s != srest &&
+					    do_gmatch(srest, se, p - 2, pe, smin))))
 						return (1);
 				}
 				if (pnext == prest)
@@ -836,24 +863,24 @@
 			return (0);
 
 		/* matches zero or once */
-		case 0x80|'?':
+		case 0x80|ord('?'):
 		/* matches one of the patterns */
-		case 0x80|'@':
+		case 0x80|ord('@'):
 		/* simile for @ */
-		case 0x80|' ':
+		case 0x80|ord(' '):
 			if (!(prest = pat_scan(p, pe, false)))
 				return (0);
 			s--;
 			/* Take care of zero matches */
-			if (p[-1] == (0x80 | '?') &&
-			    do_gmatch(s, se, prest, pe))
+			if (ord(p[-1]) == (0x80 | ord('?')) &&
+			    do_gmatch(s, se, prest, pe, smin))
 				return (1);
 			for (psub = p; ; psub = pnext) {
 				pnext = pat_scan(psub, pe, true);
 				srest = prest == pe ? se : s;
 				for (; srest <= se; srest++) {
-					if (do_gmatch(s, srest, psub, pnext - 2) &&
-					    do_gmatch(srest, se, prest, pe))
+					if (do_gmatch(s, srest, psub, pnext - 2, smin) &&
+					    do_gmatch(srest, se, prest, pe, smin))
 						return (1);
 				}
 				if (pnext == prest)
@@ -862,7 +889,7 @@
 			return (0);
 
 		/* matches none of the patterns */
-		case 0x80|'!':
+		case 0x80|ord('!'):
 			if (!(prest = pat_scan(p, pe, false)))
 				return (0);
 			s--;
@@ -872,7 +899,7 @@
 				for (psub = p; ; psub = pnext) {
 					pnext = pat_scan(psub, pe, true);
 					if (do_gmatch(s, srest, psub,
-					    pnext - 2)) {
+					    pnext - 2, smin)) {
 						matched = 1;
 						break;
 					}
@@ -880,7 +907,7 @@
 						break;
 				}
 				if (!matched &&
-				    do_gmatch(srest, se, prest, pe))
+				    do_gmatch(srest, se, prest, pe, smin))
 					return (1);
 			}
 			return (0);
@@ -890,55 +917,245 @@
 				return (0);
 			break;
 		}
+		sl = sc;
 	}
 	return (s == se);
 }
 
-static const unsigned char *
-gmatch_cclass(const unsigned char *p, unsigned char sub)
-{
-	unsigned char c, d;
-	bool notp, found = false;
-	const unsigned char *orig_p = p;
+/*XXX this is a prime example for bsearch or a const hashtable */
+static const struct cclass {
+	const char *name;
+	uint32_t value;
+} cclasses[] = {
+	/* POSIX */
+	{ "alnum",	C_ALNUM	},
+	{ "alpha",	C_ALPHA	},
+	{ "blank",	C_BLANK	},
+	{ "cntrl",	C_CNTRL	},
+	{ "digit",	C_DIGIT	},
+	{ "graph",	C_GRAPH	},
+	{ "lower",	C_LOWER	},
+	{ "print",	C_PRINT	},
+	{ "punct",	C_PUNCT	},
+	{ "space",	C_SPACE	},
+	{ "upper",	C_UPPER	},
+	{ "xdigit",	C_SEDEC	},
+	/* BSD */
+	/* "<" and ">" are handled inline */
+	/* GNU bash */
+	{ "ascii",	C_ASCII	},
+	{ "word",	C_ALNUX	},
+	/* mksh */
+	{ "sh_alias",	C_ALIAS	},
+	{ "sh_edq",	C_EDQ	},
+	{ "sh_ifs",	C_IFS	},
+	{ "sh_ifsws",	C_IFSWS	},
+	{ "sh_nl",	C_NL	},
+	{ "sh_quote",	C_QUOTE	},
+	/* sentinel */
+	{ NULL,		0	}
+};
 
-	if ((notp = tobool(ISMAGIC(*p) && *++p == '!')))
-		p++;
-	do {
-		c = *p++;
+static const unsigned char *
+gmatch_cclass(const unsigned char *pat, unsigned char sc)
+{
+	unsigned char c, subc, lc;
+	const unsigned char *p = pat, *s;
+	bool found = false;
+	bool negated = false;
+	char *subp;
+
+	/* check for negation */
+	if (ISMAGIC(p[0]) && ord(p[1]) == ord('!')) {
+		p += 2;
+		negated = true;
+	}
+	/* make initial ] non-MAGIC */
+	if (ISMAGIC(p[0]) && ord(p[1]) == ord(']'))
+		++p;
+	/* iterate over bracket expression, debunk()ing on the fly */
+	while ((c = *p++)) {
+ nextc:
+		/* non-regular character? */
 		if (ISMAGIC(c)) {
-			c = *p++;
-			if ((c & 0x80) && !ISMAGIC(c)) {
-				/* extended pattern matching: *+?@! */
-				c &= 0x7F;
-				/* XXX the ( char isn't handled as part of [] */
-				if (c == ' ')
-					/* simile for @: plain (..) */
-					c = '(' /*)*/;
+			/* MAGIC + NUL cannot happen */
+			if (!(c = *p++))
+				break;
+			/* terminating bracket? */
+			if (ord(c) == ord(']')) {
+				/* accept and return */
+				return (found != negated ? p : NULL);
+			}
+			/* sub-bracket expressions */
+			if (ord(c) == ord('[') && (
+			    /* collating element? */
+			    ord(*p) == ord('.') ||
+			    /* equivalence class? */
+			    ord(*p) == ord('=') ||
+			    /* character class? */
+			    ord(*p) == ord(':'))) {
+				/* must stop with exactly the same c */
+				subc = *p++;
+				/* save away start of substring */
+				s = p;
+				/* arbitrarily many chars in betwixt */
+				while ((c = *p++))
+					/* but only this sequence... */
+					if (c == subc && ISMAGIC(*p) &&
+					    ord(p[1]) == ord(']')) {
+						/* accept, terminate */
+						p += 2;
+						break;
+					}
+				/* EOS without: reject bracket expr */
+				if (!c)
+					break;
+				/* debunk substring */
+				strndupx(subp, s, p - s - 3, ATEMP);
+				debunk(subp, subp, p - s - 3 + 1);
+ cclass_common:
+				/* whither subexpression */
+				if (ord(subc) == ord(':')) {
+					const struct cclass *cls = cclasses;
+
+					/* search for name in cclass list */
+					while (cls->name)
+						if (!strcmp(subp, cls->name)) {
+							/* found, match? */
+							if (ctype(sc,
+							    cls->value))
+								found = true;
+							/* break either way */
+							break;
+						} else
+							++cls;
+					/* that's all here */
+					afree(subp, ATEMP);
+					continue;
+				}
+				/* collating element or equivalence class */
+				/* Note: latter are treated as former */
+				if (ctype(subp[0], C_ASCII) && !subp[1])
+					/* [.a.] where a is one ASCII char */
+					c = subp[0];
+				else
+					/* force no match */
+					c = 0;
+				/* no longer needed */
+				afree(subp, ATEMP);
+			} else if (!ISMAGIC(c) && (c & 0x80)) {
+				/* 0x80|' ' is plain (...) */
+				if ((c &= 0x7F) != ' ') {
+					/* check single match NOW */
+					if (sc == c)
+						found = true;
+					/* next character is (...) */
+				}
+				c = '(' /*)*/;
 			}
 		}
-		if (c == '\0')
-			/* No closing ] - act as if the opening [ was quoted */
-			return (sub == '[' ? orig_p : NULL);
-		if (ISMAGIC(p[0]) && p[1] == '-' &&
-		    (!ISMAGIC(p[2]) || p[3] != ']')) {
-			/* MAGIC- */
-			p += 2;
-			d = *p++;
-			if (ISMAGIC(d)) {
-				d = *p++;
-				if ((d & 0x80) && !ISMAGIC(d))
-					d &= 0x7f;
-			}
-			/* POSIX says this is an invalid expression */
-			if (c > d)
-				return (NULL);
-		} else
-			d = c;
-		if (c == sub || (c <= sub && sub <= d))
-			found = true;
-	} while (!(ISMAGIC(p[0]) && p[1] == ']'));
+		/* range expression? */
+		if (!(ISMAGIC(p[0]) && ord(p[1]) == ord('-') &&
+		    /* not terminating bracket? */
+		    (!ISMAGIC(p[2]) || ord(p[3]) != ord(']')))) {
+			/* no, check single match */
+			if (sc == c)
+				/* note: sc is never NUL */
+				found = true;
+			/* do the next "first" character */
+			continue;
+		}
+		/* save lower range bound */
+		lc = c;
+		/* skip over the range operator */
+		p += 2;
+		/* do the same shit as above... almost */
+		subc = 0;
+		if (!(c = *p++))
+			break;
+		/* non-regular character? */
+		if (ISMAGIC(c)) {
+			/* MAGIC + NUL cannot happen */
+			if (!(c = *p++))
+				break;
+			/* sub-bracket expressions */
+			if (ord(c) == ord('[') && (
+			    /* collating element? */
+			    ord(*p) == ord('.') ||
+			    /* equivalence class? */
+			    ord(*p) == ord('=') ||
+			    /* character class? */
+			    ord(*p) == ord(':'))) {
+				/* must stop with exactly the same c */
+				subc = *p++;
+				/* save away start of substring */
+				s = p;
+				/* arbitrarily many chars in betwixt */
+				while ((c = *p++))
+					/* but only this sequence... */
+					if (c == subc && ISMAGIC(*p) &&
+					    ord(p[1]) == ord(']')) {
+						/* accept, terminate */
+						p += 2;
+						break;
+					}
+				/* EOS without: reject bracket expr */
+				if (!c)
+					break;
+				/* debunk substring */
+				strndupx(subp, s, p - s - 3, ATEMP);
+				debunk(subp, subp, p - s - 3 + 1);
+				/* whither subexpression */
+				if (ord(subc) == ord(':')) {
+					/* oops, not a range */
 
-	return ((found != notp) ? p+2 : NULL);
+					/* match single previous char */
+					if (lc && (sc == lc))
+						found = true;
+					/* match hyphen-minus */
+					if (ord(sc) == ord('-'))
+						found = true;
+					/* handle cclass common part */
+					goto cclass_common;
+				}
+				/* collating element or equivalence class */
+				/* Note: latter are treated as former */
+				if (ctype(subp[0], C_ASCII) && !subp[1])
+					/* [.a.] where a is one ASCII char */
+					c = subp[0];
+				else
+					/* force no match */
+					c = 0;
+				/* no longer needed */
+				afree(subp, ATEMP);
+				/* other meaning below */
+				subc = 0;
+			} else if (c == (0x80 | ' ')) {
+				/* 0x80|' ' is plain (...) */
+				c = '(' /*)*/;
+			} else if (!ISMAGIC(c) && (c & 0x80)) {
+				c &= 0x7F;
+				subc = '(' /*)*/;
+			}
+		}
+		/* now do the actual range match check */
+		if (lc != 0 /* && c != 0 */ &&
+		    asciibetical(lc) <= asciibetical(sc) &&
+		    asciibetical(sc) <= asciibetical(c))
+			found = true;
+		/* forced next character? */
+		if (subc) {
+			c = subc;
+			goto nextc;
+		}
+		/* otherwise, just go on with the pattern string */
+	}
+	/* if we broke here, the bracket expression was invalid */
+	if (ord(sc) == ord('['))
+		/* initial opening bracket as literal match */
+		return (pat);
+	/* or rather no match */
+	return (NULL);
 }
 
 /* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
@@ -953,16 +1170,30 @@
 		if ((*++p == /*(*/ ')' && nest-- == 0) ||
 		    (*p == '|' && match_sep && nest == 0))
 			return (p + 1);
-		if ((*p & 0x80) && vstrchr("*+?@! ", *p & 0x7f))
+		if ((*p & 0x80) && ctype(*p & 0x7F, C_PATMO | C_SPC))
 			nest++;
 	}
 	return (NULL);
 }
 
 int
-xstrcmp(const void *p1, const void *p2)
+ascstrcmp(const void *s1, const void *s2)
 {
-	return (strcmp(*(const char * const *)p1, *(const char * const *)p2));
+	const uint8_t *cp1 = s1, *cp2 = s2;
+
+	while (*cp1 == *cp2) {
+		if (*cp1++ == '\0')
+			return (0);
+		++cp2;
+	}
+	return ((int)asciibetical(*cp1) - (int)asciibetical(*cp2));
+}
+
+int
+ascpstrcmp(const void *pstr1, const void *pstr2)
+{
+	return (ascstrcmp(*(const char * const *)pstr1,
+	    *(const char * const *)pstr2));
 }
 
 /* Initialise a Getopt structure */
@@ -1032,7 +1263,7 @@
 		go->info |= flag == '-' ? GI_MINUS : GI_PLUS;
 	}
 	go->p++;
-	if (c == '?' || c == ':' || c == ';' || c == ',' || c == '#' ||
+	if (ctype(c, C_QUEST | C_COLON | C_HASH) || c == ';' || c == ',' ||
 	    !(o = cstrchr(optionsp, c))) {
 		if (optionsp[0] == ':') {
 			go->buf[0] = c;
@@ -1086,13 +1317,14 @@
 		 * argument is missing.
 		 */
 		if (argv[go->optind - 1][go->p]) {
-			if (ksh_isdigit(argv[go->optind - 1][go->p])) {
+			if (ctype(argv[go->optind - 1][go->p], C_DIGIT)) {
 				go->optarg = argv[go->optind - 1] + go->p;
 				go->p = 0;
 			} else
 				go->optarg = NULL;
 		} else {
-			if (argv[go->optind] && ksh_isdigit(argv[go->optind][0])) {
+			if (argv[go->optind] &&
+			    ctype(argv[go->optind][0], C_DIGIT)) {
 				go->optarg = argv[go->optind++];
 				go->p = 0;
 			} else
@@ -1115,8 +1347,8 @@
 	bool inquote = true;
 
 	/* first, check whether any quotes are needed */
-	while ((c = *p++) >= 32)
-		if (ctype(c, C_QUOTE))
+	while (rtt2asc(c = *p++) >= 32)
+		if (ctype(c, C_QUOTE | C_SPC))
 			inquote = false;
 
 	p = (const unsigned char *)s;
@@ -1154,6 +1386,7 @@
 		shf_putc('$', shf);
 		shf_putc('\'', shf);
 		while ((c = *p) != 0) {
+#ifndef MKSH_EBCDIC
 			if (c >= 0xC2) {
 				n = utf_mbtowc(&wc, (const char *)p);
 				if (n != (size_t)-1) {
@@ -1162,10 +1395,11 @@
 					continue;
 				}
 			}
+#endif
 			++p;
 			switch (c) {
 			/* see unbksl() in this file for comments */
-			case 7:
+			case KSH_BEL:
 				c = 'a';
 				if (0)
 					/* FALLTHROUGH */
@@ -1189,11 +1423,11 @@
 				  c = 't';
 				if (0)
 					/* FALLTHROUGH */
-			case 11:
+			case KSH_VTAB:
 				  c = 'v';
 				if (0)
 					/* FALLTHROUGH */
-			case '\033':
+			case KSH_ESC:
 				/* take E not e because \e is \ in *roff */
 				  c = 'E';
 				/* FALLTHROUGH */
@@ -1203,7 +1437,12 @@
 				if (0)
 					/* FALLTHROUGH */
 			default:
-				  if (c < 32 || c > 0x7E) {
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+				  if (ksh_isctrl(c))
+#else
+				  if (!ctype(c, C_PRINT))
+#endif
+				    {
 					/* FALLTHROUGH */
 			case '\'':
 					shf_fprintf(shf, "\\%03o", c);
@@ -2154,13 +2393,7 @@
 	fc = (*fg)();
 	switch (fc) {
 	case 'a':
-		/*
-		 * according to the comments in pdksh, \007 seems
-		 * to be more portable than \a (due to HP-UX cc,
-		 * Ultrix cc, old pcc, etc.) so we avoid the escape
-		 * sequence altogether in mksh and assume ASCII
-		 */
-		wc = 7;
+		wc = KSH_BEL;
 		break;
 	case 'b':
 		wc = '\b';
@@ -2169,11 +2402,11 @@
 		if (!cstyle)
 			goto unknown_escape;
 		c = (*fg)();
-		wc = CTRL(c);
+		wc = ksh_toctrl(c);
 		break;
 	case 'E':
 	case 'e':
-		wc = 033;
+		wc = KSH_ESC;
 		break;
 	case 'f':
 		wc = '\f';
@@ -2188,8 +2421,7 @@
 		wc = '\t';
 		break;
 	case 'v':
-		/* assume ASCII here as well */
-		wc = 11;
+		wc = KSH_VTAB;
 		break;
 	case '1':
 	case '2':
@@ -2212,7 +2444,7 @@
 		wc = 0;
 		i = 3;
 		while (i--)
-			if ((c = (*fg)()) >= ord('0') && c <= ord('7'))
+			if (ctype((c = (*fg)()), C_OCTAL))
 				wc = (wc << 3) + ksh_numdig(c);
 			else {
 				(*fp)(c);
@@ -2240,17 +2472,17 @@
 		n = 0;
 		while (n < i || i == -1) {
 			wc <<= 4;
-			if ((c = (*fg)()) >= ord('0') && c <= ord('9'))
-				wc += ksh_numdig(c);
-			else if (c >= ord('A') && c <= ord('F'))
-				wc += ksh_numuc(c) + 10;
-			else if (c >= ord('a') && c <= ord('f'))
-				wc += ksh_numlc(c) + 10;
-			else {
+			if (!ctype((c = (*fg)()), C_SEDEC)) {
 				wc >>= 4;
 				(*fp)(c);
 				break;
 			}
+			if (ctype(c, C_DIGIT))
+				wc += ksh_numdig(c);
+			else if (ctype(c, C_UPPER))
+				wc += ksh_numuc(c) + 10;
+			else
+				wc += ksh_numlc(c) + 10;
 			++n;
 		}
 		if (!n)
diff --git a/src/mksh.1 b/src/mksh.1
index 6a2609a..aa67ac9 100644
--- a/src/mksh.1
+++ b/src/mksh.1
@@ -1,4 +1,4 @@
-.\" $MirOS: src/bin/mksh/mksh.1,v 1.442 2017/04/12 18:30:58 tg Exp $
+.\" $MirOS: src/bin/mksh/mksh.1,v 1.451 2017/08/16 21:40:14 tg Exp $
 .\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $
 .\"-
 .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
@@ -76,7 +76,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" use our own definition. And .Dd must come *first*, always.
 .\"
-.Dd $Mdocdate: April 12 2017 $
+.Dd $Mdocdate: August 16 2017 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -2091,7 +2091,7 @@
 which did print the delimiter character so you were out of luck
 if you did not have any non-printing characters.
 .Pp
-Since Backslashes and other special characters may be
+Since backslashes and other special characters may be
 interpreted by the shell, to set
 .Ev PS1
 either escape the backslash itself
@@ -2106,7 +2106,7 @@
 .Pq colour would work, too ,
 in the prompt string:
 .Bd -literal -offset indent
-x=$(print \e\e001)
+x=$(print \e\e001) # otherwise unused char
 PS1="$x$(print \e\er)$x$(tput so)$x\e$PWD$x$(tput se)$x\*(Gt "
 .Ed
 .Pp
@@ -3066,11 +3066,13 @@
 .Ic alias
 lists all aliases.
 For any name without a value, the existing alias is listed.
-Any name with a value defines an alias (see
+Any name with a value defines an alias; see
 .Sx Aliases
-above).
-.Li \&[A\-Za\-z0\-9_!%,@\-]
-are valid in names except they may not begin with a hyphen-minus.
+above.
+.Li \&[][A\-Za\-z0\-9_!%,.@:\-]
+are valid in names, except they may not begin with a hyphen-minus, and
+.Ic \&[[
+is not a valid alias name.
 .Pp
 When listing aliases, one of two formats is used.
 Normally, aliases are listed as
@@ -3162,7 +3164,8 @@
 .Pp
 Control characters may be written using caret notation
 i.e. \*(haX represents Ctrl-X.
-Note that although only two prefix characters (usually ESC and \*(haX)
+The caret itself can be escaped by a backslash, which also escapes itself.
+Note that although only three prefix characters (usually ESC, \*(haX and NUL)
 are supported, some multi-character sequences can be supported.
 .Pp
 The following default bindings show how the arrow keys, the home, end and
@@ -4305,9 +4308,11 @@
 .Dq sh
 and this autodetection feature is compiled in
 .Pq not in MirBSD .
-As a side effect, setting this flag turns off
+As a side effect, setting this flag turns off the
 .Ic braceexpand
-mode, which can be turned back on manually, and
+and
+.Ic utf8\-mode
+flags, which can be turned back on manually, and
 .Ic sh
 mode (unless both are enabled at the same time).
 .It Fl o Ic sh
@@ -5382,6 +5387,11 @@
 .Ic getopts
 does not accept options with a leading
 .Ql + .
+.It
+.Ic exec
+skips builtins, functions and other commands and uses a
+.Ev PATH
+search to determine the utility to execute.
 .El
 .Ss SH mode
 Compatibility mode; intended for use with legacy scripts that
@@ -5537,7 +5547,7 @@
 .No INTR Pq \*(haC ,
 .No \*(haG
 .Xc
-Abort the current command, empty the line buffer and
+Abort the current command, save it to the history, empty the line buffer and
 set the exit state to interrupted.
 .It auto\-insert: Op Ar n
 Simply causes the character to appear as literal input.
@@ -5572,7 +5582,8 @@
 words, leaving the cursor past the end of the last word.
 .It clear\-screen: \*(ha[\*(haL
 Prints a compile-time configurable sequence to clear the screen and home
-the cursor, redraws the entire prompt and the currently edited input line.
+the cursor, redraws the last line of the prompt string and the currently
+edited input line.
 The default sequence works for almost all standard terminals.
 .It comment: \*(ha[#
 If the current line does not begin with a comment character, one is added at
@@ -6434,7 +6445,7 @@
 They move as expected, both in insert and command mode.
 .It Ar intr No and Ar quit
 The interrupt and quit terminal characters cause the current line to be
-deleted and a new prompt to be printed.
+removed to the history and a new prompt to be printed.
 .El
 .Sh FILES
 .Bl -tag -width XetcXsuid_profile -compact
@@ -6584,7 +6595,7 @@
 .An Michael Rendell .
 The effort of several projects, such as Debian and OpenBSD, and other
 contributors including our users, to improve the shell is appreciated.
-See the documentation, web site and CVS for details.
+See the documentation, website and source code (CVS) for details.
 .Pp
 .Nm mksh\-os2
 is developed by
@@ -6594,6 +6605,10 @@
 is developed by
 .An Michael Langguth Aq Mt lan@scalaris.com .
 .Pp
+.Nm mksh Ns / Ns Tn z/OS
+is contributed by
+.An Daniel Richard G. Aq Mt skunk@iSKUNK.ORG .
+.Pp
 The BSD daemon is Copyright \(co Marshall Kirk McKusick.
 The complete legalese is at:
 .Pa http://www.mirbsd.org/TaC\-mksh.txt
@@ -6633,12 +6648,14 @@
 locale.
 .Nm mksh Ns 's
 .Ic utf8\-mode
+.Em must
+be disabled in POSIX mode, and it
 only supports the Unicode BMP (Basic Multilingual Plane) and maps
 raw octets into the U+EF80..U+EFFF wide character range; compare
 .Sx Arithmetic expressions .
 The following
 .Tn POSIX
-.Nm sh
+.Nm sh Ns -compatible
 code toggles the
 .Ic utf8\-mode
 option dependent on the current
@@ -6680,7 +6697,7 @@
 .Xr memmove 3 .
 .Pp
 This document attempts to describe
-.Nm mksh\ R55
+.Nm mksh\ R56
 and up,
 .\" with vendor patches from insert-your-name-here,
 compiled without any options impacting functionality, such as
@@ -6881,3 +6898,9 @@
 .Nm
 separates the shortcuts: Cursor Up goes up one command
 and PgUp searches the history as described above.
+.Ss "My question is not answered here!"
+Check
+.Pa http://www.mirbsd.org/mksh\-faq.htm
+which contains a collection of frequently asked questions about
+.Nm
+in general, for packagers, etc. while these above are in user scope.
diff --git a/src/os2.c b/src/os2.c
index 5d39630..fc27d5a 100644
--- a/src/os2.c
+++ b/src/os2.c
@@ -1,6 +1,8 @@
 /*-
  * Copyright (c) 2015
  *	KO Myung-Hun <komh@chollian.net>
+ * Copyright (c) 2017
+ *	mirabilos <m@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
  * are retained or reproduced in an accompanying document, permission
@@ -28,7 +30,7 @@
 #include <unistd.h>
 #include <process.h>
 
-__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.1 2017/04/02 15:00:44 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.2 2017/04/29 22:04:29 tg Exp $");
 
 static char *remove_trailing_dots(char *);
 static int access_stat_ex(int (*)(), const char *, void *);
@@ -247,9 +249,9 @@
 static char *
 remove_trailing_dots(char *name)
 {
-	char *p;
+	char *p = strnul(name);
 
-	for (p = name + strlen(name); --p > name && *p == '.'; )
+	while (--p > name && *p == '.')
 		/* nothing */;
 
 	if (*p != '.' && *p != '/' && *p != '\\' && *p != ':')
diff --git a/src/sh.h b/src/sh.h
index 5b36378..88883cb 100644
--- a/src/sh.h
+++ b/src/sh.h
@@ -112,6 +112,13 @@
 #include <wchar.h>
 #endif
 
+/* monkey-patch known-bad offsetof versions to quell a warning */
+#if (defined(__KLIBC__) || defined(__dietlibc__)) && \
+    ((defined(__GNUC__) && (__GNUC__ > 3)) || defined(__NWCC__))
+#undef offsetof
+#define offsetof(s, e)		__builtin_offsetof(s, e)
+#endif
+
 #undef __attribute__
 #if HAVE_ATTRIBUTE_BOUNDED
 #define MKSH_A_BOUNDED(x,y,z)	__attribute__((__bounded__(x, y, z)))
@@ -175,9 +182,9 @@
 #endif
 
 #ifdef EXTERN
-__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.808 2017/04/12 17:38:46 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.841 2017/08/29 13:38:31 tg Exp $");
 #endif
-#define MKSH_VERSION "R55 2017/04/12"
+#define MKSH_VERSION "R56 2017/08/29"
 
 /* arithmetic types: C implementation */
 #if !HAVE_CAN_INTTYPES
@@ -257,6 +264,23 @@
 
 #ifndef MKSH_INCLUDES_ONLY
 
+/* EBCDIC fun */
+
+/* see the large comment in shf.c for an EBCDIC primer */
+
+#if defined(MKSH_FOR_Z_OS) && defined(__MVS__) && defined(__IBMC__) && defined(__CHARSET_LIB)
+# if !__CHARSET_LIB && !defined(MKSH_EBCDIC)
+#  error "Please compile with Build.sh -E for EBCDIC!"
+# endif
+# if __CHARSET_LIB && defined(MKSH_EBCDIC)
+#  error "Please compile without -E argument to Build.sh for ASCII!"
+# endif
+# if __CHARSET_LIB && !defined(_ENHANCED_ASCII_EXT)
+   /* go all-out on ASCII */
+#  define _ENHANCED_ASCII_EXT 0xFFFFFFFF
+# endif
+#endif
+
 /* extra types */
 
 /* getrusage does not exist on OS/2 kLIBC */
@@ -349,6 +373,8 @@
 #define ksh_NSIG (_SIGMAX + 1)
 #elif defined(NSIG_MAX)
 #define ksh_NSIG (NSIG_MAX)
+#elif defined(MKSH_FOR_Z_OS)
+#define ksh_NSIG 40
 #else
 # error Please have your platform define NSIG.
 #endif
@@ -487,6 +513,23 @@
 #define ISTRIP		0
 #endif
 
+#ifdef MKSH_EBCDIC
+#define KSH_BEL		'\a'
+#define KSH_ESC		047
+#define KSH_ESC_STRING	"\047"
+#define KSH_VTAB	'\v'
+#else
+/*
+ * According to the comments in pdksh, \007 seems to be more portable
+ * than \a (HP-UX cc, Ultrix cc, old pcc, etc.) so we avoid the escape
+ * sequence if ASCII can be assumed.
+ */
+#define KSH_BEL		7
+#define KSH_ESC		033
+#define KSH_ESC_STRING	"\033"
+#define KSH_VTAB	11
+#endif
+
 
 /* some useful #defines */
 #ifdef EXTERN
@@ -498,16 +541,22 @@
 #endif
 
 /* define bit in flag */
-#define BIT(i)		(1 << (i))
+#define BIT(i)		(1U << (i))
 #define NELEM(a)	(sizeof(a) / sizeof((a)[0]))
 
 /*
  * Make MAGIC a char that might be printed to make bugs more obvious, but
  * not a char that is used often. Also, can't use the high bit as it causes
  * portability problems (calling strchr(x, 0x80 | 'x') is error prone).
+ *
+ * MAGIC can be followed by MAGIC (to escape the octet itself) or one of:
+ * ' !)*,-?[]{|}' 0x80|' !*+?@' (probably… hysteric raisins abound)
+ *
+ * The |0x80 is likely unsafe on EBCDIC :( though the listed chars are
+ * low-bit7 at least on cp1047 so YMMV
  */
-#define MAGIC		(7)	/* prefix for *?[!{,} during expand */
-#define ISMAGIC(c)	((unsigned char)(c) == MAGIC)
+#define MAGIC		KSH_BEL	/* prefix for *?[!{,} during expand */
+#define ISMAGIC(c)	(ord(c) == ord(MAGIC))
 
 EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */
 
@@ -521,17 +570,21 @@
 #else
 #define KSH_VERSIONNAME_TEXTMODE	""
 #endif
+#ifdef MKSH_EBCDIC
+#define KSH_VERSIONNAME_EBCDIC		" +EBCDIC"
+#else
+#define KSH_VERSIONNAME_EBCDIC		""
+#endif
 #ifndef KSH_VERSIONNAME_VENDOR_EXT
 #define KSH_VERSIONNAME_VENDOR_EXT	""
 #endif
 EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)" KSH_VERSIONNAME_ISLEGACY \
-    " KSH " MKSH_VERSION KSH_VERSIONNAME_TEXTMODE KSH_VERSIONNAME_VENDOR_EXT);
+    " KSH " MKSH_VERSION KSH_VERSIONNAME_EBCDIC KSH_VERSIONNAME_TEXTMODE \
+    KSH_VERSIONNAME_VENDOR_EXT);
 #define KSH_VERSION	(initvsn + /* "KSH_VERSION=@(#)" */ 16)
 
 EXTERN const char digits_uc[] E_INIT("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
 EXTERN const char digits_lc[] E_INIT("0123456789abcdefghijklmnopqrstuvwxyz");
-#define letters_uc (digits_uc + 10)
-#define letters_lc (digits_lc + 10)
 
 /*
  * Evil hack for const correctness due to API brokenness
@@ -585,15 +638,12 @@
 #endif
 
 #if defined(DEBUG) || defined(__COVERITY__)
-#define mkssert(e)	do { if (!(e)) exit(255); } while (/* CONSTCOND */ 0)
 #ifndef DEBUG_LEAKS
 #define DEBUG_LEAKS
 #endif
-#else
-#define mkssert(e)	do { } while (/* CONSTCOND */ 0)
 #endif
 
-#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 551)
+#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 562)
 #error Must run Build.sh to compile this.
 extern void thiswillneverbedefinedIhope(void);
 int
@@ -605,7 +655,7 @@
 #endif
 
 /* use this ipv strchr(s, 0) but no side effects in s! */
-#define strnul(s)	((s) + strlen(s))
+#define strnul(s)	((s) + strlen((const void *)s))
 
 #define utf_ptradjx(src, dst) do {					\
 	(dst) = (src) + utf_ptradj(src);				\
@@ -621,7 +671,7 @@
 #else
 /* be careful to evaluate arguments only once! */
 #define strdupx(d, s, ap) do {						\
-	const char *strdup_src = (s);					\
+	const char *strdup_src = (const void *)(s);			\
 	char *strdup_dst = NULL;					\
 									\
 	if (strdup_src != NULL) {					\
@@ -632,7 +682,7 @@
 	(d) = strdup_dst;						\
 } while (/* CONSTCOND */ 0)
 #define strndupx(d, s, n, ap) do {					\
-	const char *strdup_src = (s);					\
+	const char *strdup_src = (const void *)(s);			\
 	char *strdup_dst = NULL;					\
 									\
 	if (strdup_src != NULL) {					\
@@ -753,8 +803,8 @@
 struct sretrace_info;
 struct yyrecursive_state;
 
-EXTERN struct sretrace_info *retrace_info E_INIT(NULL);
-EXTERN int subshell_nesting_type E_INIT(0);
+EXTERN struct sretrace_info *retrace_info;
+EXTERN int subshell_nesting_type;
 
 extern struct env {
 	ALLOC_ITEM alloc_INT;	/* internal, do not touch */
@@ -865,8 +915,8 @@
 EXTERN const char T4spaces[] E_INIT("    ");
 #define T1space (Treal_sp2 + 5)
 #define Tcolsp (Tf_sD_ + 2)
-EXTERN const char TC_LEX1[] E_INIT("|&;<>() \t\n");
-#define TC_IFSWS (TC_LEX1 + 7)
+#define TC_IFSWS (TinitIFS + 4)
+EXTERN const char TinitIFS[] E_INIT("IFS= \t\n");
 EXTERN const char TFCEDIT_dollaru[] E_INIT("${FCEDIT:-/bin/ed} $_");
 #define Tspdollaru (TFCEDIT_dollaru + 18)
 EXTERN const char Tsgdot[] E_INIT("*=.");
@@ -1026,8 +1076,8 @@
 #define T4spaces "    "
 #define T1space " "
 #define Tcolsp ": "
-#define TC_LEX1 "|&;<>() \t\n"
 #define TC_IFSWS " \t\n"
+#define TinitIFS "IFS= \t\n"
 #define TFCEDIT_dollaru "${FCEDIT:-/bin/ed} $_"
 #define Tspdollaru " $_"
 #define Tsgdot "*=."
@@ -1277,7 +1327,7 @@
 	TMOUT_LEAVING		/* have timed out */
 };
 EXTERN unsigned int ksh_tmout;
-EXTERN enum tmout_enum ksh_tmout_state E_INIT(TMOUT_EXECUTING);
+EXTERN enum tmout_enum ksh_tmout_state;
 
 /* For "You have stopped jobs" message */
 EXTERN bool really_exit;
@@ -1285,39 +1335,178 @@
 /*
  * fast character classes
  */
-#define C_ALPHX	 BIT(0)		/* A-Za-z_ */
-#define C_DIGIT	 BIT(1)		/* 0-9 */
-#define C_LEX1	 BIT(2)		/* \t \n\0|&;<>() */
-#define C_VAR1	 BIT(3)		/* *@#!$-? */
-#define C_IFSWS	 BIT(4)		/* \t \n (IFS white space) */
-#define C_SUBOP1 BIT(5)		/* "=-+?" */
-#define C_QUOTE	 BIT(6)		/* \t\n "#$&'()*;<=>?[\]`| (needing quoting) */
-#define C_IFS	 BIT(7)		/* $IFS */
 
-extern unsigned char chtypes[];
+/* internal types, do not reference */
 
-#define ctype(c, t)	tobool(chtypes[(unsigned char)(c)] & (t))
-#define ord(c)		((int)(unsigned char)(c))
-#define ksh_issubop2(c)	tobool((c) == ord('#') || (c) == ord('%'))
-#define ksh_isalias(c)	(ctype((c), C_ALPHX | C_DIGIT) || (c) == ord('!') || \
-			    (c) == ord('%') || (c) == ord(',') || \
-			    (c) == ord('@') || (c) == ord('-'))
-#define ksh_isalpha(c)	(ctype((c), C_ALPHX) && (c) != ord('_'))
-#define ksh_isalphx(c)	ctype((c), C_ALPHX)
-#define ksh_isalnux(c)	ctype((c), C_ALPHX | C_DIGIT)
-#define ksh_isdigit(c)	ctype((c), C_DIGIT)
-#define ksh_islower(c)	(((c) >= 'a') && ((c) <= 'z'))
-#define ksh_isupper(c)	(((c) >= 'A') && ((c) <= 'Z'))
-#define ksh_tolower(c)	(ksh_isupper(c) ? (c) - 'A' + 'a' : (c))
-#define ksh_toupper(c)	(ksh_islower(c) ? (c) - 'a' + 'A' : (c))
-#define ksh_isdash(s)	(((s)[0] == '-') && ((s)[1] == '\0'))
-#define ksh_isspace(c)	((((c) >= 0x09) && ((c) <= 0x0D)) || ((c) == 0x20))
-#define ksh_eq(c,u,l)	(((c) | 0x20) == (l))
-#define ksh_numdig(c)	((c) - ord('0'))
-#define ksh_numuc(c)	((c) - ord('A'))
-#define ksh_numlc(c)	((c) - ord('a'))
+/* initially empty — filled at runtime from $IFS */
+#define CiIFS	BIT(0)
+#define CiCNTRL	BIT(1)	/* \x01‥\x08\x0E‥\x1F\x7F	*/
+#define CiUPPER	BIT(2)	/* A‥Z				*/
+#define CiLOWER	BIT(3)	/* a‥z				*/
+#define CiHEXLT	BIT(4)	/* A‥Fa‥f			*/
+#define CiOCTAL	BIT(5)	/* 0‥7				*/
+#define CiQCL	BIT(6)	/* &();|			*/
+#define CiALIAS	BIT(7)	/* !,.@				*/
+#define CiQCX	BIT(8)	/* *[\\				*/
+#define CiVAR1	BIT(9)	/* !*@				*/
+#define CiQCM	BIT(10)	/* /^~				*/
+#define CiDIGIT	BIT(11)	/* 89				*/
+#define CiQC	BIT(12)	/* "'				*/
+#define CiSPX	BIT(13)	/* \x0B\x0C			*/
+#define CiCURLY	BIT(14)	/* {}				*/
+#define CiANGLE	BIT(15)	/* <>				*/
+#define CiNUL	BIT(16)	/* \x00				*/
+#define CiTAB	BIT(17)	/* \x09				*/
+#define CiNL	BIT(18)	/* \x0A				*/
+#define CiCR	BIT(19)	/* \x0D				*/
+#define CiSP	BIT(20)	/* \x20				*/
+#define CiHASH	BIT(21)	/* #				*/
+#define CiSS	BIT(22)	/* $				*/
+#define CiPERCT	BIT(23)	/* %				*/
+#define CiPLUS	BIT(24)	/* +				*/
+#define CiMINUS	BIT(25)	/* -				*/
+#define CiCOLON	BIT(26)	/* :				*/
+#define CiEQUAL	BIT(27)	/* =				*/
+#define CiQUEST	BIT(28)	/* ?				*/
+#define CiBRACK	BIT(29)	/* ]				*/
+#define CiUNDER	BIT(30)	/* _				*/
+#define CiGRAVE	BIT(31)	/* `				*/
+/* out of space, but one for *@ would make sense, possibly others */
 
-EXTERN int ifs0 E_INIT(' ');	/* for "$*" */
+/* compile-time initialised, ASCII only */
+extern const uint32_t tpl_ctypes[128];
+/* run-time, contains C_IFS as well, full 2⁸ octet range */
+EXTERN uint32_t ksh_ctypes[256];
+/* first octet of $IFS, for concatenating "$*" */
+EXTERN char ifs0;
+
+/* external types */
+
+/* !%,-.0‥9:@A‥Z[]_a‥z	valid characters in alias names */
+#define C_ALIAS	(CiALIAS | CiBRACK | CiCOLON | CiDIGIT | CiLOWER | CiMINUS | CiOCTAL | CiPERCT | CiUNDER | CiUPPER)
+/* 0‥9A‥Za‥z		alphanumerical */
+#define C_ALNUM	(CiDIGIT | CiLOWER | CiOCTAL | CiUPPER)
+/* 0‥9A‥Z_a‥z		alphanumerical plus underscore (“word character”) */
+#define C_ALNUX	(CiDIGIT | CiLOWER | CiOCTAL | CiUNDER | CiUPPER)
+/* A‥Za‥z		alphabetical (upper plus lower) */
+#define C_ALPHA	(CiLOWER | CiUPPER)
+/* A‥Z_a‥z		alphabetical plus underscore (identifier lead) */
+#define C_ALPHX	(CiLOWER | CiUNDER | CiUPPER)
+/* \x01‥\x7F		7-bit ASCII except NUL */
+#define C_ASCII (CiALIAS | CiANGLE | CiBRACK | CiCNTRL | CiCOLON | CiCR | CiCURLY | CiDIGIT | CiEQUAL | CiGRAVE | CiHASH | CiLOWER | CiMINUS | CiNL | CiOCTAL | CiPERCT | CiPLUS | CiQC | CiQCL | CiQCM | CiQCX | CiQUEST | CiSP | CiSPX | CiSS | CiTAB | CiUNDER | CiUPPER)
+/* \x09\x20		tab and space */
+#define C_BLANK	(CiSP | CiTAB)
+/* \x09\x20"'		separator for completion */
+#define C_CFS	(CiQC | CiSP | CiTAB)
+/* \x00‥\x1F\x7F	POSIX control characters */
+#define C_CNTRL	(CiCNTRL | CiCR | CiNL | CiNUL | CiSPX | CiTAB)
+/* 0‥9			decimal digits */
+#define C_DIGIT	(CiDIGIT | CiOCTAL)
+/* &();`|			editor x_locate_word() command */
+#define C_EDCMD	(CiGRAVE | CiQCL)
+/* \x09\x0A\x20"&'():;<=>`|	editor non-word characters */
+#define C_EDNWC	(CiANGLE | CiCOLON | CiEQUAL | CiGRAVE | CiNL | CiQC | CiQCL | CiSP | CiTAB)
+/* "#$&'()*:;<=>?[\\`{|}	editor quotes for tab completion */
+#define C_EDQ	(CiANGLE | CiCOLON | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiQC | CiQCL | CiQCX | CiQUEST | CiSS)
+/* !‥~			POSIX graphical (alphanumerical plus punctuation) */
+#define C_GRAPH	(C_PUNCT | CiDIGIT | CiLOWER | CiOCTAL | CiUPPER)
+/* A‥Fa‥f		hex letter */
+#define C_HEXLT	CiHEXLT
+/* \x00 + $IFS		IFS whitespace, IFS non-whitespace, NUL */
+#define C_IFS	(CiIFS | CiNUL)
+/* \x09\x0A\x20		IFS whitespace */
+#define C_IFSWS	(CiNL | CiSP | CiTAB)
+/* \x09\x0A\x20&();<>|	(for the lexer) */
+#define C_LEX1	(CiANGLE | CiNL | CiQCL | CiSP | CiTAB)
+/* a‥z			lowercase letters */
+#define C_LOWER	CiLOWER
+/* not alnux or dollar	separator for motion */
+#define C_MFS	(CiALIAS | CiANGLE | CiBRACK | CiCNTRL | CiCOLON | CiCR | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiMINUS | CiNL | CiNUL | CiPERCT | CiPLUS | CiQC | CiQCL | CiQCM | CiQCX | CiQUEST | CiSP | CiSPX | CiTAB)
+/* 0‥7			octal digit */
+#define C_OCTAL	CiOCTAL
+/* !*+?@		pattern magical operator, except space */
+#define C_PATMO	(CiPLUS | CiQUEST | CiVAR1)
+/* \x20‥~		POSIX printable characters (graph plus space) */
+#define C_PRINT	(C_GRAPH | CiSP)
+/* !"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~	POSIX punctuation */
+#define C_PUNCT	(CiALIAS | CiANGLE | CiBRACK | CiCOLON | CiCURLY | CiEQUAL | CiGRAVE | CiHASH | CiMINUS | CiPERCT | CiPLUS | CiQC | CiQCL | CiQCM | CiQCX | CiQUEST | CiSS | CiUNDER)
+/* \x09\x0A"#$&'()*;<=>?[\\]`|	characters requiring quoting, minus space */
+#define C_QUOTE	(CiANGLE | CiBRACK | CiEQUAL | CiGRAVE | CiHASH | CiNL | CiQC | CiQCL | CiQCX | CiQUEST | CiSS | CiTAB)
+/* 0‥9A‥Fa‥f		hexadecimal digit */
+#define C_SEDEC	(CiDIGIT | CiHEXLT | CiOCTAL)
+/* \x09‥\x0D\x20	POSIX space class */
+#define C_SPACE	(CiCR | CiNL | CiSP | CiSPX | CiTAB)
+/* +-=?			substitution operations with word */
+#define C_SUB1	(CiEQUAL | CiMINUS | CiPLUS | CiQUEST)
+/* #%			substitution operations with pattern */
+#define C_SUB2	(CiHASH | CiPERCT)
+/* A‥Z			uppercase letters */
+#define C_UPPER	CiUPPER
+/* !#$*-?@		substitution parameters, other than positional */
+#define C_VAR1	(CiHASH | CiMINUS | CiQUEST | CiSS | CiVAR1)
+
+/* individual chars you might like */
+#define C_ANGLE	CiANGLE		/* <>	angle brackets */
+#define C_COLON	CiCOLON		/* :	colon */
+#define C_CR	CiCR		/* \x0D	ASCII carriage return */
+#define C_DOLAR	CiSS		/* $	dollar sign */
+#define C_EQUAL	CiEQUAL		/* =	equals sign */
+#define C_GRAVE	CiGRAVE		/* `	accent gravis */
+#define C_HASH	CiHASH		/* #	hash sign */
+#define C_LF	CiNL		/* \x0A	ASCII line feed */
+#define C_MINUS	CiMINUS		/* -	hyphen-minus */
+#ifdef MKSH_WITH_TEXTMODE
+#define C_NL	(CiNL | CiCR)	/* 	CR or LF under OS/2 TEXTMODE */
+#else
+#define C_NL	CiNL		/* 	LF only like under Unix */
+#endif
+#define C_NUL	CiNUL		/* \x00	ASCII NUL */
+#define C_PLUS	CiPLUS		/* +	plus sign */
+#define C_QC	CiQC		/* "'	quote characters */
+#define C_QUEST	CiQUEST		/* ?	question mark */
+#define C_SPC	CiSP		/* \x20	ASCII space */
+#define C_TAB	CiTAB		/* \x09	ASCII horizontal tabulator */
+#define C_UNDER	CiUNDER		/* _	underscore */
+
+/* identity transform of octet */
+#define ord(c)		((unsigned int)(unsigned char)(c))
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+EXTERN unsigned short ebcdic_map[256];
+EXTERN unsigned char ebcdic_rtt_toascii[256];
+EXTERN unsigned char ebcdic_rtt_fromascii[256];
+extern void ebcdic_init(void);
+/* one-way to-ascii-or-high conversion, for POSIX locale ordering */
+#define asciibetical(c)	((unsigned int)ebcdic_map[(unsigned char)(c)])
+/* two-way round-trip conversion, for general use */
+#define rtt2asc(c)	ebcdic_rtt_toascii[(unsigned char)(c)]
+#define asc2rtt(c)	ebcdic_rtt_fromascii[(unsigned char)(c)]
+/* case-independent char comparison */
+#define ksh_eq(c,u,l)	(ord(c) == ord(u) || ord(c) == ord(l))
+#else
+#define asciibetical(c)	ord(c)
+#define rtt2asc(c)	((unsigned char)(c))
+#define asc2rtt(c)	((unsigned char)(c))
+#define ksh_eq(c,u,l)	((ord(c) | 0x20) == ord(l))
+#endif
+/* control character foo */
+#ifdef MKSH_EBCDIC
+#define ksh_isctrl(c)	(ord(c) < 0x40 || ord(c) == 0xFF)
+#else
+#define ksh_isctrl(c)	((ord(c) & 0x7F) < 0x20 || (c) == 0x7F)
+#endif
+/* new fast character classes */
+#define ctype(c,t)	tobool(ksh_ctypes[ord(c)] & (t))
+/* helper functions */
+#define ksh_isdash(s)	tobool(ord((s)[0]) == '-' && ord((s)[1]) == '\0')
+/* invariant distance even in EBCDIC */
+#define ksh_tolower(c)	(ctype(c, C_UPPER) ? (c) - 'A' + 'a' : (c))
+#define ksh_toupper(c)	(ctype(c, C_LOWER) ? (c) - 'a' + 'A' : (c))
+/* strictly speaking rtt2asc() here, but this works even in EBCDIC */
+#define ksh_numdig(c)	(ord(c) - ord('0'))
+#define ksh_numuc(c)	(rtt2asc(c) - rtt2asc('A'))
+#define ksh_numlc(c)	(rtt2asc(c) - rtt2asc('a'))
+#define ksh_toctrl(c)	asc2rtt(ord(c) == ord('?') ? 0x7F : rtt2asc(c) & 0x9F)
+#define ksh_unctrl(c)	asc2rtt(rtt2asc(c) ^ 0x40U)
 
 /* Argument parsing for built-in commands and getopts command */
 
@@ -1990,12 +2179,77 @@
 
 #define HERES		10	/* max number of << in line */
 
-#undef CTRL
-#define	CTRL(x)		((x) == '?' ? 0x7F : (x) & 0x1F)	/* ASCII */
-#define	UNCTRL(x)	((x) ^ 0x40)				/* ASCII */
-#define	ISCTRL(x)	(((signed char)((uint8_t)(x) + 1)) < 33)
+#ifdef MKSH_EBCDIC
+#define CTRL_AT	(0x00U)
+#define CTRL_A	(0x01U)
+#define CTRL_B	(0x02U)
+#define CTRL_C	(0x03U)
+#define CTRL_D	(0x37U)
+#define CTRL_E	(0x2DU)
+#define CTRL_F	(0x2EU)
+#define CTRL_G	(0x2FU)
+#define CTRL_H	(0x16U)
+#define CTRL_I	(0x05U)
+#define CTRL_J	(0x15U)
+#define CTRL_K	(0x0BU)
+#define CTRL_L	(0x0CU)
+#define CTRL_M	(0x0DU)
+#define CTRL_N	(0x0EU)
+#define CTRL_O	(0x0FU)
+#define CTRL_P	(0x10U)
+#define CTRL_Q	(0x11U)
+#define CTRL_R	(0x12U)
+#define CTRL_S	(0x13U)
+#define CTRL_T	(0x3CU)
+#define CTRL_U	(0x3DU)
+#define CTRL_V	(0x32U)
+#define CTRL_W	(0x26U)
+#define CTRL_X	(0x18U)
+#define CTRL_Y	(0x19U)
+#define CTRL_Z	(0x3FU)
+#define CTRL_BO	(0x27U)
+#define CTRL_BK	(0x1CU)
+#define CTRL_BC	(0x1DU)
+#define CTRL_CA	(0x1EU)
+#define CTRL_US	(0x1FU)
+#define CTRL_QM	(0x07U)
+#else
+#define CTRL_AT	(0x00U)
+#define CTRL_A	(0x01U)
+#define CTRL_B	(0x02U)
+#define CTRL_C	(0x03U)
+#define CTRL_D	(0x04U)
+#define CTRL_E	(0x05U)
+#define CTRL_F	(0x06U)
+#define CTRL_G	(0x07U)
+#define CTRL_H	(0x08U)
+#define CTRL_I	(0x09U)
+#define CTRL_J	(0x0AU)
+#define CTRL_K	(0x0BU)
+#define CTRL_L	(0x0CU)
+#define CTRL_M	(0x0DU)
+#define CTRL_N	(0x0EU)
+#define CTRL_O	(0x0FU)
+#define CTRL_P	(0x10U)
+#define CTRL_Q	(0x11U)
+#define CTRL_R	(0x12U)
+#define CTRL_S	(0x13U)
+#define CTRL_T	(0x14U)
+#define CTRL_U	(0x15U)
+#define CTRL_V	(0x16U)
+#define CTRL_W	(0x17U)
+#define CTRL_X	(0x18U)
+#define CTRL_Y	(0x19U)
+#define CTRL_Z	(0x1AU)
+#define CTRL_BO	(0x1BU)
+#define CTRL_BK	(0x1CU)
+#define CTRL_BC	(0x1DU)
+#define CTRL_CA	(0x1EU)
+#define CTRL_US	(0x1FU)
+#define CTRL_QM	(0x7FU)
+#endif
 
-#define IDENT		64
+#define IDENT	64
 
 EXTERN Source *source;		/* yyparse/yylex source */
 EXTERN YYSTYPE yylval;		/* result from yylex */
@@ -2273,8 +2527,6 @@
     MKSH_A_FORMAT(__printf__, 1, 2);
 #endif
 /* misc.c */
-void setctypes(const char *, int);
-void initctypes(void);
 size_t option(const char *) MKSH_A_PURE;
 char *getoptions(void);
 void change_flag(enum sh_flag, int, bool);
@@ -2282,8 +2534,9 @@
 int parse_args(const char **, int, bool *);
 int getn(const char *, int *);
 int gmatchx(const char *, const char *, bool);
-int has_globbing(const char *, const char *) MKSH_A_PURE;
-int xstrcmp(const void *, const void *) MKSH_A_PURE;
+bool has_globbing(const char *) MKSH_A_PURE;
+int ascstrcmp(const void *, const void *) MKSH_A_PURE;
+int ascpstrcmp(const void *, const void *) MKSH_A_PURE;
 void ksh_getopt_reset(Getopt *, int);
 int ksh_getopt(const char **, Getopt *, const char *);
 void print_value_quoted(struct shf *, const char *);
@@ -2346,6 +2599,7 @@
     MKSH_A_FORMAT(__printf__, 1, 2);
 ssize_t shf_vfprintf(struct shf *, const char *, va_list)
     MKSH_A_FORMAT(__printf__, 2, 0);
+void set_ifs(const char *);
 /* syn.c */
 void initkeywords(void);
 struct op *compile(Source *, bool, bool);
@@ -2483,7 +2737,7 @@
 #define mksh_abspath(s)			__extension__({			\
 	const char *mksh_abspath_s = (s);				\
 	(mksh_cdirsep(mksh_abspath_s[0]) ||				\
-	    (ksh_isalpha(mksh_abspath_s[0]) &&				\
+	    (ctype(mksh_abspath_s[0], C_ALPHA) &&			\
 	    mksh_abspath_s[1] == ':'));					\
 })
 #define mksh_cdirsep(c)			__extension__({			\
@@ -2492,15 +2746,15 @@
 })
 #define mksh_sdirsep(s)			__extension__({			\
 	const char *mksh_sdirsep_s = (s);				\
-	((char *)((ksh_isalphx(mksh_sdirsep_s[0]) &&			\
+	((char *)((ctype(mksh_sdirsep_s[0], C_ALPHA) &&			\
 	    mksh_sdirsep_s[1] == ':' &&					\
 	    !mksh_cdirsep(mksh_sdirsep_s[2])) ?				\
 	    (mksh_sdirsep_s + 1) : strpbrk(mksh_sdirsep_s, "/\\")));	\
 })
 #define mksh_vdirsep(s)			(mksh_sdirsep((s)) != NULL)
 #else
-#define mksh_abspath(s)			((s)[0] == '/')
-#define mksh_cdirsep(c)			((c) == '/')
+#define mksh_abspath(s)			(ord((s)[0]) == ord('/'))
+#define mksh_cdirsep(c)			(ord(c) == ord('/'))
 #define mksh_sdirsep(s)			strchr((s), '/')
 #define mksh_vdirsep(s)			vstrchr((s), '/')
 #endif
diff --git a/src/shf.c b/src/shf.c
index 09cc7c3..7e53352 100644
--- a/src/shf.c
+++ b/src/shf.c
@@ -4,6 +4,8 @@
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
  *		 2012, 2013, 2015, 2016, 2017
  *	mirabilos <m@mirbsd.org>
+ * Copyright (c) 2015
+ *	Daniel Richard G. <skunk@iSKUNK.ORG>
  *
  * Provided that these terms and disclaimer and all copyright notices
  * are retained or reproduced in an accompanying document, permission
@@ -25,7 +27,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.79 2017/04/12 17:08:49 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.95 2017/05/05 22:45:58 tg Exp $");
 
 /* flags to shf_emptybuf() */
 #define EB_READSW	0x01	/* about to switch to reading */
@@ -874,11 +876,11 @@
 				flags |= FL_SIZET;
 				continue;
 			}
-			if (ksh_isdigit(c)) {
+			if (ctype(c, C_DIGIT)) {
 				bool overflowed = false;
 
 				tmp = ksh_numdig(c);
-				while (c = *fmt++, ksh_isdigit(c))
+				while (ctype((c = *fmt++), C_DIGIT))
 					if (notok2mul(2147483647, tmp, 10))
 						overflowed = true;
 					else
@@ -899,7 +901,7 @@
 			/* nasty format */
 			break;
 
-		if (ksh_isupper(c)) {
+		if (ctype(c, C_UPPER)) {
 			flags |= FL_UPPER;
 			c = ksh_tolower(c);
 		}
@@ -1029,8 +1031,7 @@
 			if (!(flags & FL_RIGHT)) {
 				/* skip past sign or 0x when padding with 0 */
 				if ((flags & FL_ZERO) && (flags & FL_NUMBER)) {
-					if (*s == '+' || *s == '-' ||
-					    *s == ' ') {
+					if (ctype(*s, C_SPC | C_PLUS | C_MINUS)) {
 						shf_putc(*s, shf);
 						s++;
 						precision--;
@@ -1158,3 +1159,163 @@
 	}
 }
 #endif
+
+/* fast character classes */
+const uint32_t tpl_ctypes[128] = {
+	/* 0x00 */
+	CiNUL,		CiCNTRL,	CiCNTRL,	CiCNTRL,
+	CiCNTRL,	CiCNTRL,	CiCNTRL,	CiCNTRL,
+	CiCNTRL,	CiTAB,		CiNL,		CiSPX,
+	CiSPX,		CiCR,		CiCNTRL,	CiCNTRL,
+	/* 0x10 */
+	CiCNTRL,	CiCNTRL,	CiCNTRL,	CiCNTRL,
+	CiCNTRL,	CiCNTRL,	CiCNTRL,	CiCNTRL,
+	CiCNTRL,	CiCNTRL,	CiCNTRL,	CiCNTRL,
+	CiCNTRL,	CiCNTRL,	CiCNTRL,	CiCNTRL,
+	/* 0x20 */
+	CiSP,		CiALIAS | CiVAR1,	CiQC,	CiHASH,
+	CiSS,		CiPERCT,	CiQCL,		CiQC,
+	CiQCL,		CiQCL,		CiQCX | CiVAR1,	CiPLUS,
+	CiALIAS,	CiMINUS,	CiALIAS,	CiQCM,
+	/* 0x30 */
+	CiOCTAL,	CiOCTAL,	CiOCTAL,	CiOCTAL,
+	CiOCTAL,	CiOCTAL,	CiOCTAL,	CiOCTAL,
+	CiDIGIT,	CiDIGIT,	CiCOLON,	CiQCL,
+	CiANGLE,	CiEQUAL,	CiANGLE,	CiQUEST,
+	/* 0x40 */
+	CiALIAS | CiVAR1,	CiUPPER | CiHEXLT,
+	CiUPPER | CiHEXLT,	CiUPPER | CiHEXLT,
+	CiUPPER | CiHEXLT,	CiUPPER | CiHEXLT,
+	CiUPPER | CiHEXLT,	CiUPPER,
+	CiUPPER,	CiUPPER,	CiUPPER,	CiUPPER,
+	CiUPPER,	CiUPPER,	CiUPPER,	CiUPPER,
+	/* 0x50 */
+	CiUPPER,	CiUPPER,	CiUPPER,	CiUPPER,
+	CiUPPER,	CiUPPER,	CiUPPER,	CiUPPER,
+	CiUPPER,	CiUPPER,	CiUPPER,	CiQCX | CiBRACK,
+	CiQCX,		CiBRACK,	CiQCM,		CiUNDER,
+	/* 0x60 */
+	CiGRAVE,		CiLOWER | CiHEXLT,
+	CiLOWER | CiHEXLT,	CiLOWER | CiHEXLT,
+	CiLOWER | CiHEXLT,	CiLOWER | CiHEXLT,
+	CiLOWER | CiHEXLT,	CiLOWER,
+	CiLOWER,	CiLOWER,	CiLOWER,	CiLOWER,
+	CiLOWER,	CiLOWER,	CiLOWER,	CiLOWER,
+	/* 0x70 */
+	CiLOWER,	CiLOWER,	CiLOWER,	CiLOWER,
+	CiLOWER,	CiLOWER,	CiLOWER,	CiLOWER,
+	CiLOWER,	CiLOWER,	CiLOWER,	CiCURLY,
+	CiQCL,		CiCURLY,	CiQCM,		CiCNTRL
+};
+
+void
+set_ifs(const char *s)
+{
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+	int i = 256;
+
+	memset(ksh_ctypes, 0, sizeof(ksh_ctypes));
+	while (i--)
+		if (ebcdic_map[i] < 0x80U)
+			ksh_ctypes[i] = tpl_ctypes[ebcdic_map[i]];
+#else
+	memcpy(ksh_ctypes, tpl_ctypes, sizeof(tpl_ctypes));
+	memset((char *)ksh_ctypes + sizeof(tpl_ctypes), '\0',
+	    sizeof(ksh_ctypes) - sizeof(tpl_ctypes));
+#endif
+	ifs0 = *s;
+	while (*s)
+		ksh_ctypes[ord(*s++)] |= CiIFS;
+}
+
+#if defined(MKSH_EBCDIC) || defined(MKSH_FAUX_EBCDIC)
+#include <locale.h>
+
+/*
+ * Many headaches with EBCDIC:
+ * 1. There are numerous EBCDIC variants, and it is not feasible for us
+ *    to support them all. But we can support the EBCDIC code pages that
+ *    contain all (most?) of the characters in ASCII, and these
+ *    usually tend to agree on the code points assigned to the ASCII
+ *    subset. If you need a representative example, look at EBCDIC 1047,
+ *    which is first among equals in the IBM MVS development
+ *    environment: https://en.wikipedia.org/wiki/EBCDIC_1047
+ *    Unfortunately, the square brackets are not consistently mapped,
+ *    and for certain reasons, we need an unambiguous bijective
+ *    mapping between EBCDIC and "extended ASCII".
+ * 2. Character ranges that are contiguous in ASCII, like the letters
+ *    in [A-Z], are broken up into segments (i.e. [A-IJ-RS-Z]), so we
+ *    can't implement e.g. islower() as { return c >= 'a' && c <= 'z'; }
+ *    because it will also return true for a handful of extraneous
+ *    characters (like the plus-minus sign at 0x8F in EBCDIC 1047, a
+ *    little after 'i'). But at least '_' is not one of these.
+ * 3. The normal [0-9A-Za-z] characters are at codepoints beyond 0x80.
+ *    Not only do they require all 8 bits instead of 7, if chars are
+ *    signed, they will have negative integer values! Something like
+ *    (c - 'A') could actually become (c + 63)! Use the ord() macro to
+ *    ensure you're getting a value in [0, 255].
+ * 4. '\n' is actually NL (0x15, U+0085) instead of LF (0x25, U+000A).
+ *    EBCDIC has a proper newline character instead of "emulating" one
+ *    with line feeds, although this is mapped to LF for our purposes.
+ * 5. Note that it is possible to compile programs in ASCII mode on IBM
+ *    mainframe systems, using the -qascii option to the XL C compiler.
+ *    We can determine the build mode by looking at __CHARSET_LIB:
+ *    0 == EBCDIC, 1 == ASCII
+ */
+
+void
+ebcdic_init(void)
+{
+	int i = 256;
+	unsigned char t;
+	bool mapcache[256];
+
+	while (i--)
+		ebcdic_rtt_toascii[i] = i;
+	memset(ebcdic_rtt_fromascii, 0xFF, sizeof(ebcdic_rtt_fromascii));
+	setlocale(LC_ALL, "");
+#ifdef MKSH_EBCDIC
+	if (__etoa_l(ebcdic_rtt_toascii, 256) != 256) {
+		write(2, "mksh: could not map EBCDIC to ASCII\n", 36);
+		exit(255);
+	}
+#endif
+
+	memset(mapcache, 0, sizeof(mapcache));
+	i = 256;
+	while (i--) {
+		t = ebcdic_rtt_toascii[i];
+		/* ensure unique round-trip capable mapping */
+		if (mapcache[t]) {
+			write(2, "mksh: duplicate EBCDIC to ASCII mapping\n", 40);
+			exit(255);
+		}
+		/*
+		 * since there are 256 input octets, this also ensures
+		 * the other mapping direction is completely filled
+		 */
+		mapcache[t] = true;
+		/* fill the complete round-trip map */
+		ebcdic_rtt_fromascii[t] = i;
+		/*
+		 * Only use the converted value if it's in the range
+		 * [0x00; 0x7F], which I checked; the "extended ASCII"
+		 * characters can be any encoding, not just Latin1,
+		 * and the C1 control characters other than NEL are
+		 * hopeless, but we map EBCDIC NEL to ASCII LF so we
+		 * cannot even use C1 NEL.
+		 * If ever we map to Unicode, bump the table width to
+		 * an unsigned int, and or the raw unconverted EBCDIC
+		 * values with 0x01000000 instead.
+		 */
+		if (t < 0x80U)
+			ebcdic_map[i] = (unsigned short)ord(t);
+		else
+			ebcdic_map[i] = (unsigned short)(0x100U | ord(i));
+	}
+	if (ebcdic_rtt_toascii[0] || ebcdic_rtt_fromascii[0] || ebcdic_map[0]) {
+		write(2, "mksh: NUL not at position 0\n", 28);
+		exit(255);
+	}
+}
+#endif
diff --git a/src/signames.inc b/src/signames.inc
deleted file mode 100644
index 07811fd..0000000
--- a/src/signames.inc
+++ /dev/null
@@ -1,31 +0,0 @@
-		{ "ABRT", 6 },
-		{ "FPE", 8 },
-		{ "ILL", 4 },
-		{ "INT", 2 },
-		{ "SEGV", 11 },
-		{ "TERM", 15 },
-		{ "ALRM", 14 },
-		{ "BUS", 7 },
-		{ "CHLD", 17 },
-		{ "CONT", 18 },
-		{ "HUP", 1 },
-		{ "KILL", 9 },
-		{ "PIPE", 13 },
-		{ "QUIT", 3 },
-		{ "STOP", 19 },
-		{ "TSTP", 20 },
-		{ "TTIN", 21 },
-		{ "TTOU", 22 },
-		{ "USR1", 10 },
-		{ "USR2", 12 },
-		{ "POLL", 29 },
-		{ "PROF", 27 },
-		{ "SYS", 31 },
-		{ "TRAP", 5 },
-		{ "URG", 23 },
-		{ "VTALRM", 26 },
-		{ "XCPU", 24 },
-		{ "XFSZ", 25 },
-		{ "WINCH", 28 },
-		{ "PWR", 30 },
-		{ "STKFLT", 16 },
diff --git a/src/syn.c b/src/syn.c
index 0454488..c50c2ab 100644
--- a/src/syn.c
+++ b/src/syn.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.120 2017/04/06 01:59:57 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.124 2017/05/05 22:53:31 tg Exp $");
 
 struct nesting_state {
 	int start_token;	/* token than began nesting (eg, FOR) */
@@ -91,7 +91,7 @@
 	c = tpeek(0);
 	if (c == 0 && !outtree)
 		outtree = newtp(TEOF);
-	else if (c != '\n' && c != 0)
+	else if (!ctype(c, C_LF | C_NUL))
 		syntaxerr(NULL);
 }
 
@@ -330,7 +330,7 @@
 					XPput(args, yylval.cp);
 				break;
 
-			case '(' /*)*/:
+			case ord('(' /*)*/):
 				if (XPsize(args) == 0 && XPsize(vars) == 1 &&
 				    is_wdvarassign(yylval.cp)) {
 					char *tcp;
@@ -373,7 +373,7 @@
 					    XPsize(vars) != 0)
 						syntaxerr(NULL);
 					ACCEPT;
-					musthave(/*(*/')', 0);
+					musthave(/*(*/ ')', 0);
 					t = function_body(XPptrv(args)[0],
 					    sALIAS, false);
 				}
@@ -386,18 +386,18 @@
  Leave:
 		break;
 
-	case '(': /*)*/ {
+	case ord('(' /*)*/): {
 		int subshell_nesting_type_saved;
  Subshell:
 		subshell_nesting_type_saved = subshell_nesting_type;
-		subshell_nesting_type = ')';
-		t = nested(TPAREN, '(', ')', sALIAS);
+		subshell_nesting_type = ord(')');
+		t = nested(TPAREN, ord('('), ord(')'), sALIAS);
 		subshell_nesting_type = subshell_nesting_type_saved;
 		break;
 	    }
 
-	case '{': /*}*/
-		t = nested(TBRACE, '{', '}', sALIAS);
+	case ord('{' /*}*/):
+		t = nested(TBRACE, ord('{'), ord('}'), sALIAS);
 		break;
 
 	case MDPAREN:
@@ -407,8 +407,8 @@
 		switch (token(LETEXPR)) {
 		case LWORD:
 			break;
-		case '(': /*)*/
-			c = '(';
+		case ord('(' /*)*/):
+			c = ord('(');
 			goto Subshell;
 		default:
 			syntaxerr(NULL);
@@ -554,8 +554,8 @@
 	 */
 	if (c == DO)
 		c = DONE;
-	else if (c == '{')
-		c = '}';
+	else if (c == ord('{'))
+		c = ord('}');
 	else
 		syntaxerr(NULL);
 	list = c_list(sALIAS, true);
@@ -610,8 +610,8 @@
 	/* A {...} can be used instead of in...esac for case statements */
 	if (c == IN)
 		c = ESAC;
-	else if (c == '{')
-		c = '}';
+	else if (c == ord('{'))
+		c = ord('}');
 	else
 		syntaxerr(NULL);
 	t = tl = NULL;
@@ -636,17 +636,18 @@
 	XPinit(ptns, 16);
 	t = newtp(TPAT);
 	/* no ALIAS here */
-	if (token(CONTIN | KEYWORD) != '(')
+	if (token(CONTIN | KEYWORD) != ord('('))
 		REJECT;
 	do {
 		switch (token(0)) {
 		case LWORD:
 			break;
-		case '}':
+		case ord('}'):
 		case ESAC:
 			if (symbol != endtok) {
 				strdupx(yylval.cp,
-				    symbol == '}' ? Tcbrace : Tesac, ATEMP);
+				    symbol == ord('}') ? Tcbrace : Tesac,
+				    ATEMP);
 				break;
 			}
 			/* FALLTHROUGH */
@@ -658,23 +659,23 @@
 	REJECT;
 	XPput(ptns, NULL);
 	t->vars = (char **)XPclose(ptns);
-	musthave(')', 0);
+	musthave(ord(')'), 0);
 
 	t->left = c_list(sALIAS, true);
 
 	/* initialise to default for ;; or omitted */
-	t->u.charflag = ';';
+	t->u.charflag = ord(';');
 	/* SUSv4 requires the ;; except in the last casepart */
 	if ((tpeek(CONTIN|KEYWORD|sALIAS)) != endtok)
 		switch (symbol) {
 		default:
 			syntaxerr(NULL);
 		case BRKEV:
-			t->u.charflag = '|';
+			t->u.charflag = ord('|');
 			if (0)
 				/* FALLTHROUGH */
 		case BRKFT:
-			t->u.charflag = '&';
+			  t->u.charflag = ord('&');
 			/* FALLTHROUGH */
 		case BREAK:
 			/* initialised above, but we need to eat the token */
@@ -697,10 +698,10 @@
 	 * only allow [a-zA-Z_0-9] but this allows more as old pdkshs
 	 * have allowed more; the following were never allowed:
 	 *	NUL TAB NL SP " $ & ' ( ) ; < = > \ ` |
-	 * C_QUOTE covers all but adds # * ? [ ]
+	 * C_QUOTE|C_SPC covers all but adds # * ? [ ]
 	 */
 	for (p = sname; *p; p++)
-		if (ctype(*p, C_QUOTE))
+		if (ctype(*p, C_QUOTE | C_SPC))
 			yyerror(Tinvname, sname, Tfunction);
 
 	/*
@@ -710,14 +711,14 @@
 	 * only accepts an open-brace.
 	 */
 	if (ksh_func) {
-		if (tpeek(CONTIN|KEYWORD|sALIAS) == '(' /*)*/) {
+		if (tpeek(CONTIN|KEYWORD|sALIAS) == ord('(' /*)*/)) {
 			/* function foo () { //}*/
 			ACCEPT;
-			musthave(')', 0);
+			musthave(ord(/*(*/ ')'), 0);
 			/* degrade to POSIX function */
 			ksh_func = false;
 		}
-		musthave('{' /*}*/, CONTIN|KEYWORD|sALIAS);
+		musthave(ord('{' /*}*/), CONTIN|KEYWORD|sALIAS);
 		REJECT;
 	}
 
@@ -809,8 +810,8 @@
 	{ "in",		IN,	true },
 	{ Tfunction,	FUNCTION, true },
 	{ Ttime,	TIME,	true },
-	{ "{",		'{',	true },
-	{ Tcbrace,	'}',	true },
+	{ "{",		ord('{'), true },
+	{ Tcbrace,	ord('}'), true },
 	{ "!",		BANG,	true },
 	{ "[[",		DBRACKET, true },
 	/* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */
@@ -822,7 +823,7 @@
 	{ "((",		MDPAREN, false },
 	{ "|&",		COPROC,	false },
 	/* and some special cases... */
-	{ "newline",	'\n',	false },
+	{ "newline",	ord('\n'), false },
 	{ NULL,		0,	false }
 };
 
@@ -997,9 +998,9 @@
 		ret = (uqword && !strcmp(yylval.cp,
 		    dbtest_tokens[(int)TM_NOT])) ? TO_NONNULL : TO_NONOP;
 	else if (meta == TM_OPAREN)
-		ret = c == '(' /*)*/ ? TO_NONNULL : TO_NONOP;
+		ret = c == ord('(') /*)*/ ? TO_NONNULL : TO_NONOP;
 	else if (meta == TM_CPAREN)
-		ret = c == /*(*/ ')' ? TO_NONNULL : TO_NONOP;
+		ret = c == /*(*/ ord(')') ? TO_NONNULL : TO_NONOP;
 	else if (meta == TM_UNOP || meta == TM_BINOP) {
 		if (meta == TM_BINOP && c == REDIR &&
 		    (yylval.iop->ioflag == IOREAD ||
@@ -1079,7 +1080,7 @@
 
 	tv->tv_sec = 0;
 	/* parse integral part */
-	while (ksh_isdigit(*s)) {
+	while (ctype(*s, C_DIGIT)) {
 		tt.tv_sec = tv->tv_sec * 10 + ksh_numdig(*s++);
 		/*XXX this overflow check maybe UB */
 		if (tt.tv_sec / 10 != tv->tv_sec) {
@@ -1101,14 +1102,14 @@
 
 	/* parse decimal fraction */
 	i = 100000;
-	while (ksh_isdigit(*s)) {
+	while (ctype(*s, C_DIGIT)) {
 		tv->tv_usec += i * ksh_numdig(*s++);
 		if (i == 1)
 			break;
 		i /= 10;
 	}
 	/* check for junk after fractional part */
-	while (ksh_isdigit(*s))
+	while (ctype(*s, C_DIGIT))
 		++s;
 	if (*s) {
 		errno = EINVAL;
@@ -1133,11 +1134,11 @@
 	int stok, etok;
 
 	if (subtype != COMSUB) {
-		stok = '{';
-		etok = '}';
+		stok = ord('{');
+		etok = ord('}');
 	} else {
-		stok = '(';
-		etok = ')';
+		stok = ord('(');
+		etok = ord(')');
 	}
 
 	ys = alloc(sizeof(struct yyrecursive_state), ATEMP);
diff --git a/src/tree.c b/src/tree.c
index 1fd8f2a..1062feb 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.89 2017/04/12 16:46:23 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.93 2017/05/05 22:53:32 tg Exp $");
 
 #define INDENT	8
 
@@ -329,34 +329,34 @@
 		case EOS:
 			return (--wp);
 		case ADELIM:
-			if (*wp == /*{*/'}') {
+			if (ord(*wp) == ord(/*{*/ '}')) {
 				++wp;
 				goto wdvarput_csubst;
 			}
 			/* FALLTHROUGH */
 		case CHAR:
-			c = *wp++;
+			c = ord(*wp++);
 			shf_putc(c, shf);
 			break;
 		case QCHAR:
-			c = *wp++;
+			c = ord(*wp++);
 			if (opmode & WDS_TPUTS)
 				switch (c) {
-				case '\n':
+				case ord('\n'):
 					if (quotelevel == 0) {
-						c = '\'';
+						c = ord('\'');
 						shf_putc(c, shf);
-						shf_putc('\n', shf);
+						shf_putc(ord('\n'), shf);
 					}
 					break;
 				default:
 					if (quotelevel == 0)
 						/* FALLTHROUGH */
-				case '"':
-				case '`':
-				case '$':
-				case '\\':
-					  shf_putc('\\', shf);
+				case ord('"'):
+				case ord('`'):
+				case ord('$'):
+				case ord('\\'):
+					  shf_putc(ord('\\'), shf);
 					break;
 				}
 			shf_putc(c, shf);
@@ -365,7 +365,7 @@
 		case COMSUB:
 			shf_puts("$(", shf);
 			cs = ")";
-			if (*wp == '(' /*)*/)
+			if (ord(*wp) == ord('(' /*)*/))
 				shf_putc(' ', shf);
  pSUB:
 			while ((c = *wp++) != 0)
@@ -374,11 +374,11 @@
 			break;
 		case FUNASUB:
 		case FUNSUB:
-			c = ' ';
+			c = ord(' ');
 			if (0)
 				/* FALLTHROUGH */
 		case VALSUB:
-			  c = '|';
+			  c = ord('|');
 			shf_putc('$', shf);
 			shf_putc('{', shf);
 			shf_putc(c, shf);
@@ -403,14 +403,14 @@
 			break;
 		case OSUBST:
 			shf_putc('$', shf);
-			if (*wp++ == '{')
+			if (ord(*wp++) == ord('{'))
 				shf_putc('{', shf);
 			while ((c = *wp++) != 0)
 				shf_putc(c, shf);
 			wp = wdvarput(shf, wp, 0, opmode);
 			break;
 		case CSUBST:
-			if (*wp++ == '}') {
+			if (ord(*wp++) == ord('}')) {
  wdvarput_csubst:
 				shf_putc('}', shf);
 			}
@@ -420,11 +420,11 @@
 			shf_putc('(', shf);
 			break;
 		case SPAT:
-			c = '|';
+			c = ord('|');
 			if (0)
 				/* FALLTHROUGH */
 		case CPAT:
-			  c = /*(*/ ')';
+			  c = ord(/*(*/ ')');
 			shf_putc(c, shf);
 			break;
 		}
@@ -467,39 +467,39 @@
 {
 	int c;
 
-	while ((c = *fmt++)) {
+	while ((c = ord(*fmt++))) {
 		if (c == '%') {
-			switch ((c = *fmt++)) {
-			case 'c':
+			switch ((c = ord(*fmt++))) {
+			case ord('c'):
 				/* character (octet, probably) */
 				shf_putchar(va_arg(va, int), shf);
 				break;
-			case 's':
+			case ord('s'):
 				/* string */
 				shf_puts(va_arg(va, char *), shf);
 				break;
-			case 'S':
+			case ord('S'):
 				/* word */
 				wdvarput(shf, va_arg(va, char *), 0, WDS_TPUTS);
 				break;
-			case 'd':
+			case ord('d'):
 				/* signed decimal */
 				shf_fprintf(shf, Tf_d, va_arg(va, int));
 				break;
-			case 'u':
+			case ord('u'):
 				/* unsigned decimal */
 				shf_fprintf(shf, "%u", va_arg(va, unsigned int));
 				break;
-			case 'T':
+			case ord('T'):
 				/* format tree */
 				ptree(va_arg(va, struct op *), indent, shf);
 				goto dont_trash_prevent_semicolon;
-			case ';':
+			case ord(';'):
 				/* newline or ; */
-			case 'N':
+			case ord('N'):
 				/* newline or space */
 				if (shf->flags & SHF_STRING) {
-					if (c == ';' && !prevent_semicolon)
+					if (c == ord(';') && !prevent_semicolon)
 						shf_putc(';', shf);
 					shf_putc(' ', shf);
 				} else {
@@ -515,7 +515,7 @@
 						shf_putc(' ', shf);
 				}
 				break;
-			case 'R':
+			case ord('R'):
 				/* I/O redirection */
 				pioact(shf, va_arg(va, struct ioword *));
 				break;
@@ -613,7 +613,7 @@
 		case ADELIM:
 			if (c == ADELIM && nest == 0)
 				return (wp + 1);
-			if (*wp == /*{*/'}')
+			if (ord(*wp) == ord(/*{*/ '}'))
 				goto wdscan_csubst;
 			/* FALLTHROUGH */
 		case CHAR:
@@ -795,20 +795,20 @@
 			*dst++ = *cp++;
 		goto vist_loop;
 	}
-	if (--sz == 0 || (c = (unsigned char)(*cp++)) == 0)
+	if (--sz == 0 || (c = ord(*cp++)) == 0)
 		/* NUL or not enough free space */
 		goto vist_out;
-	if (ISCTRL(c & 0x7F)) {
+	if (ksh_isctrl(c)) {
 		/* C0 or C1 control character or DEL */
 		if (--sz == 0)
 			/* not enough free space for two chars */
 			goto vist_out;
-		*dst++ = (c & 0x80) ? '$' : '^';
-		c = UNCTRL(c & 0x7F);
-	} else if (UTFMODE && c > 0x7F) {
+		*dst++ = '^';
+		c = ksh_unctrl(c);
+	} else if (UTFMODE && rtt2asc(c) > 0x7F) {
 		/* better not try to display broken multibyte chars */
 		/* also go easy on the Unicode: no U+FFFD here */
-		c = '?';
+		c = ord('?');
 	}
 	*dst++ = c;
 	goto vist_loop;
@@ -822,10 +822,10 @@
 void
 dumpchar(struct shf *shf, int c)
 {
-	if (ISCTRL(c & 0x7F)) {
+	if (ksh_isctrl(c)) {
 		/* C0 or C1 control character or DEL */
-		shf_putc((c & 0x80) ? '$' : '^', shf);
-		c = UNCTRL(c & 0x7F);
+		shf_putc('^', shf);
+		c = ksh_unctrl(c);
 	}
 	shf_putc(c, shf);
 }
@@ -842,7 +842,7 @@
 			shf_puts("EOS", shf);
 			return (--wp);
 		case ADELIM:
-			if (*wp == /*{*/'}') {
+			if (ord(*wp) == ord(/*{*/ '}')) {
 				shf_puts(/*{*/ "]ADELIM(})", shf);
 				return (wp + 1);
 			}
@@ -855,9 +855,9 @@
 			break;
 		case QCHAR:
 			shf_puts("QCHAR<", shf);
-			c = *wp++;
-			if (quotelevel == 0 ||
-			    (c == '"' || c == '`' || c == '$' || c == '\\'))
+			c = ord(*wp++);
+			if (quotelevel == 0 || c == ord('"') ||
+			    c == ord('\\') || ctype(c, C_DOLAR | C_GRAVE))
 				shf_putc('\\', shf);
 			dumpchar(shf, c);
 			goto closeandout;
diff --git a/src/var.c b/src/var.c
index b83977f..a53fae8 100644
--- a/src/var.c
+++ b/src/var.c
@@ -28,7 +28,7 @@
 #include <sys/sysctl.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/var.c,v 1.214 2017/04/02 16:47:43 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.220 2017/07/26 23:02:28 tg Exp $");
 
 /*-
  * Variables
@@ -183,7 +183,7 @@
 	*arrayp = false;
  redo_from_ref:
 	p = skip_varname(n, false);
-	if (innermost_refflag == SRF_NOP && (p != n) && ksh_isalphx(n[0])) {
+	if (innermost_refflag == SRF_NOP && (p != n) && ctype(n[0], C_ALPHX)) {
 		struct tbl *vp;
 		char *vn;
 
@@ -204,7 +204,7 @@
 	}
 	innermost_refflag = SRF_NOP;
 
-	if (p != n && *p == '[' && (len = array_ref_len(p))) {
+	if (p != n && ord(*p) == ord('[') && (len = array_ref_len(p))) {
 		char *sub, *tmp;
 		mksh_ari_t rval;
 
@@ -249,14 +249,14 @@
 	vn = array_index_calc(n, &array, &val);
 	h = hash(vn);
 	c = (unsigned char)vn[0];
-	if (!ksh_isalphx(c)) {
+	if (!ctype(c, C_ALPHX)) {
 		if (array)
 			errorf(Tbadsubst);
 		vp = vtemp;
 		vp->flag = DEFINED;
 		vp->type = 0;
 		vp->areap = ATEMP;
-		if (ksh_isdigit(c)) {
+		if (ctype(c, C_DIGIT)) {
 			if (getn(vn, &c)) {
 				/* main.c:main_init() says 12 */
 				shf_snprintf(vp->name, 12, Tf_d, c);
@@ -339,7 +339,7 @@
 	 */
 	vn = array_index_calc(n, &array, &val);
 	h = hash(vn);
-	if (!ksh_isalphx(*vn)) {
+	if (!ctype(*vn, C_ALPHX)) {
 		vp = vtemp;
 		vp->flag = DEFINED|RDONLY;
 		vp->type = 0;
@@ -414,9 +414,11 @@
 
 			*(s = strbuf) = '1';
 			s[1] = '#';
-			if (!UTFMODE || ((n & 0xFF80) == 0xEF80))
+			if (!UTFMODE)
+				s[2] = (unsigned char)n;
+			else if ((n & 0xFF80) == 0xEF80)
 				/* OPTU-16 -> raw octet */
-				s[2] = n & 0xFF;
+				s[2] = asc2rtt(n & 0xFF);
 			else
 				sz = utf_wctomb(s + 2, n);
 			s[2 + sz] = '\0';
@@ -464,7 +466,7 @@
 #ifndef MKSH_SMALL
 			/* debugging */
 			if (s >= vq->val.s &&
-			    s <= vq->val.s + strlen(vq->val.s)) {
+			    s <= strnul(vq->val.s)) {
 				internal_errorf(
 				    "setstr: %s=%s: assigning to self",
 				    vq->name, s);
@@ -532,7 +534,7 @@
 
 	do {
 		c = (unsigned char)*s++;
-	} while (ksh_isspace(c));
+	} while (ctype(c, C_SPACE));
 
 	switch (c) {
 	case '-':
@@ -549,7 +551,7 @@
 			base = 16;
 			++s;
 			goto getint_c_style_base;
-		} else if (Flag(FPOSIX) && ksh_isdigit(s[0]) &&
+		} else if (Flag(FPOSIX) && ctype(s[0], C_DIGIT) &&
 		    !(vp->flag & ZEROFIL)) {
 			/* interpret as octal (deprecated) */
 			base = 8;
@@ -577,7 +579,7 @@
 					 * the same as 1#\x80 does, thus is
 					 * not round-tripping correctly XXX)
 					 */
-					wc = 0xEF00 + *(const unsigned char *)s;
+					wc = 0xEF00 + rtt2asc(*s);
 				nump->u = (mksh_uari_t)wc;
 				return (1);
 			} else if (base > 36)
@@ -586,11 +588,11 @@
 			have_base = true;
 			continue;
 		}
-		if (ksh_isdigit(c))
+		if (ctype(c, C_DIGIT))
 			c = ksh_numdig(c);
-		else if (ksh_isupper(c))
+		else if (ctype(c, C_UPPER))
 			c = ksh_numuc(c) + 10;
-		else if (ksh_islower(c))
+		else if (ctype(c, C_LOWER))
 			c = ksh_numlc(c) + 10;
 		else
 			return (-1);
@@ -670,7 +672,7 @@
 			qq = utf_skipcols(s, slen, &slen);
 
 			/* strip trailing spaces (AT&T uses qq[-1] == ' ') */
-			while (qq > s && ksh_isspace(qq[-1])) {
+			while (qq > s && ctype(qq[-1], C_SPACE)) {
 				--qq;
 				--slen;
 			}
@@ -700,7 +702,7 @@
 			    "%.*s", slen, s);
 		} else {
 			/* strip leading spaces/zeros */
-			while (ksh_isspace(*s))
+			while (ctype(*s, C_SPACE))
 				s++;
 			if (vp->flag & ZEROFIL)
 				while (*s == '0')
@@ -778,7 +780,7 @@
 		/* no variable name given */
 		return (NULL);
 	}
-	if (*val == '[') {
+	if (ord(*val) == ord('[')) {
 		if (new_refflag != SRF_NOP)
 			errorf(Tf_sD_s, var,
 			    "reference variable can't be an array");
@@ -796,18 +798,18 @@
 			size_t i;
 
 			for (i = 1; i < len - 1; i++)
-				if (!ksh_isdigit(val[i]))
+				if (!ctype(val[i], C_DIGIT))
 					return (NULL);
 		}
 		val += len;
 	}
-	if (val[0] == '=') {
+	if (ord(val[0]) == ord('=')) {
 		strndupx(tvar, var, val - var, ATEMP);
 		++val;
 	} else if (set & IMPORT) {
 		/* environment invalid variable name or no assignment */
 		return (NULL);
-	} else if (val[0] == '+' && val[1] == '=') {
+	} else if (ord(val[0]) == ord('+') && ord(val[1]) == ord('=')) {
 		strndupx(tvar, var, val - var, ATEMP);
 		val += 2;
 		vappend = true;
@@ -820,8 +822,9 @@
 		val = NULL;
 		/* handle foo[*] => foo (whole array) mapping for R39b */
 		len = strlen(tvar);
-		if (len > 3 && tvar[len - 3] == '[' && tvar[len - 2] == '*' &&
-		    tvar[len - 1] == ']')
+		if (len > 3 && ord(tvar[len - 3]) == ord('[') &&
+		    ord(tvar[len - 2]) == ord('*') &&
+		    ord(tvar[len - 1]) == ord(']'))
 			tvar[len - 3] = '\0';
 	}
 
@@ -845,7 +848,7 @@
 
 			if (!(c = (unsigned char)qval[0]))
 				goto nameref_empty;
-			else if (ksh_isdigit(c) && getn(qval, &c))
+			else if (ctype(c, C_DIGIT) && getn(qval, &c))
 				goto nameref_rhs_checked;
 			else if (qval[1] == '\0') switch (c) {
 			case '$':
@@ -858,7 +861,7 @@
  nameref_empty:
 			errorf(Tf_sD_s, var, "empty nameref target");
 		}
-		len = (*ccp == '[') ? array_ref_len(ccp) : 0;
+		len = (ord(*ccp) == ord('[')) ? array_ref_len(ccp) : 0;
 		if (ccp[len]) {
 			/*
 			 * works for cases "no array", "valid array with
@@ -914,12 +917,12 @@
 	vpbase = (vp->flag & ARRAY) ? global(arrayname(tvar)) : vp;
 
 	/*
-	 * only allow export flag to be set; AT&T ksh allows any
-	 * attribute to be changed which means it can be truncated or
-	 * modified (-L/-R/-Z/-i)
+	 * only allow export and readonly flag to be set; AT&T ksh
+	 * allows any attribute to be changed which means it can be
+	 * truncated or modified (-L/-R/-Z/-i)
 	 */
 	if ((vpbase->flag & RDONLY) &&
-	    (val || clr || (set & ~EXPORT)))
+	    (val || clr || (set & ~(EXPORT | RDONLY))))
 		/* XXX check calls - is error here ok by POSIX? */
 		errorfx(2, Tf_ro, tvar);
 	afree(tvar, ATEMP);
@@ -1064,11 +1067,11 @@
 {
 	size_t alen;
 
-	if (s && ksh_isalphx(*s)) {
+	if (s && ctype(*s, C_ALPHX)) {
 		do {
 			++s;
-		} while (ksh_isalnux(*s));
-		if (aok && *s == '[' && (alen = array_ref_len(s)))
+		} while (ctype(*s, C_ALNUX));
+		if (aok && ord(*s) == ord('[') && (alen = array_ref_len(s)))
 			s += alen;
 	}
 	return (s);
@@ -1080,11 +1083,11 @@
     /* skip array de-reference? */
     bool aok)
 {
-	if (s[0] == CHAR && ksh_isalphx(s[1])) {
+	if (s[0] == CHAR && ctype(s[1], C_ALPHX)) {
 		do {
 			s += 2;
-		} while (s[0] == CHAR && ksh_isalnux(s[1]));
-		if (aok && s[0] == CHAR && s[1] == '[') {
+		} while (s[0] == CHAR && ctype(s[1], C_ALNUX));
+		if (aok && s[0] == CHAR && ord(s[1]) == ord('[')) {
 			/* skip possible array de-reference */
 			const char *p = s;
 			char c;
@@ -1095,9 +1098,9 @@
 					break;
 				c = p[1];
 				p += 2;
-				if (c == '[')
+				if (ord(c) == ord('['))
 					depth++;
-				else if (c == ']' && --depth == 0) {
+				else if (ord(c) == ord(']') && --depth == 0) {
 					s = p;
 					break;
 				}
@@ -1307,8 +1310,7 @@
 		return;
 #endif
 	case V_IFS:
-		setctypes(s = str_val(vp), C_IFS);
-		ifs0 = *s;
+		set_ifs(str_val(vp));
 		return;
 	case V_PATH:
 		afree(path, APERM);
@@ -1436,8 +1438,7 @@
 		return;
 #endif
 	case V_IFS:
-		setctypes(TC_IFSWS, C_IFS);
-		ifs0 = ' ';
+		set_ifs(TC_IFSWS);
 		break;
 	case V_PATH:
 		afree(path, APERM);
@@ -1527,8 +1528,8 @@
 	char c;
 	int depth = 0;
 
-	while ((c = *s++) && (c != ']' || --depth))
-		if (c == '[')
+	while ((c = *s++) && (ord(c) != ord(']') || --depth))
+		if (ord(c) == ord('['))
 			depth++;
 	if (!c)
 		return (0);
@@ -1600,17 +1601,18 @@
 	}
 	while ((ccp = vals[i])) {
 #if 0 /* temporarily taken out due to regression */
-		if (*ccp == '[') {
+		if (ord(*ccp) == ord('[')) {
 			int level = 0;
 
 			while (*ccp) {
-				if (*ccp == ']' && --level == 0)
+				if (ord(*ccp) == ord(']') && --level == 0)
 					break;
-				if (*ccp == '[')
+				if (ord(*ccp) == ord('['))
 					++level;
 				++ccp;
 			}
-			if (*ccp == ']' && level == 0 && ccp[1] == '=') {
+			if (ord(*ccp) == ord(']') && level == 0 &&
+			    ord(ccp[1]) == ord('=')) {
 				strndupx(cp, vals[i] + 1, ccp - (vals[i] + 1),
 				    ATEMP);
 				evaluate(substitute(cp, 0), (mksh_ari_t *)&j,