[tarent-useful-scripts SCM] {wtf-mksh} branch sarge updated. mksh-56b_wtf2-181-geaaa598

mirabilos at evolvis.org mirabilos at evolvis.org
Sat Nov 4 21:24:36 CET 2017 • <20171104202436.6FF6B4A01A5@evolvis.org>


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Supplemental git repository wtf-mksh for Evolvis project useful-scripts".

The branch, sarge has been updated
       via  eaaa598ad864d640b2555781b5c1147d1b593756 (commit)
       via  476d2e1ccd9b586cc88377fff4342bc1e85703d4 (commit)
       via  fb11b19800c34b67eddac49ae14a33e5734ab187 (commit)
       via  c8f161f33c91a7341c964472dee29e6b21816d8b (commit)
       via  5bba20a92d03b8bf5b75e4b1d3cafdffc35a56ea (commit)
       via  bc45ebc547a28b443d7aa6343bf4fd7fb9e97faa (commit)
       via  737c89496f0e525ffe46753aced3ac54bd4e5df0 (commit)
       via  99f42493e1e1cac125390dd35407ae4d29570a15 (commit)
       via  d9d7ee63ca2d77feb597528ff22c10770f55c540 (commit)
       via  16c725ce3042e1237118e476a0ff3a0b5393f5d6 (commit)
       via  2e061211875f75cb890c9d9487313ff684f4d18a (commit)
       via  afe7e87e55f2611f73420168c84aeb184eee59fb (commit)
       via  2fb23e28377cbd73e74bbc41fdac5fe115cd0ace (commit)
       via  ff95e94326b92dc090cfe938a5e2d077681affb8 (commit)
       via  f751439266a072c6fcfc5d91dfad8a14d3e04d39 (commit)
       via  97d9e91d8f245c12e61e1d90484055bdf9c0b738 (commit)
       via  0e478dceae027a466e8e427fdc156e6725e557f3 (commit)
       via  c94bfd71e8bd826b16d38fcb1e6464f70b30e075 (commit)
       via  cfa08b1c3fcb78a28f5d4833379aab0f9257b022 (commit)
       via  d76a7e581369fc3a7d322cdc09d802d8ee708b9c (commit)
       via  63cf261f11baeaf89bf5a83ce3c62c9415dfcc84 (commit)
       via  5468d726b3c2425228db55f296bd6d50b9c0babd (commit)
       via  524e63b4a1461d1ff09edefb695755a2d37f1a0c (commit)
       via  7d18c762d29affdca3145714e97db4158624b171 (commit)
       via  0ba04f9bb7c4d1690af2af5952e9a7e746bf7d7f (commit)
       via  34fc7afc8cfdc23fac3cb3039b993097b06235a1 (commit)
       via  5db583cd8106ac57e937fe90ab1c4cc052cacc93 (commit)
       via  d5a29d5e608822fa28529514265d62eb0ba9619e (commit)
       via  2cdd9d77dec021d49d7d85e0013d9ce307e89c74 (commit)
       via  af61fd186a876dd8c7d1a14b082e01d3018a34b5 (commit)
       via  5a89f6d95959df9476f2409c38a2e1030937de4a (commit)
       via  f578d7cd10283fe5ec171712e6c7f25bf32c0490 (commit)
       via  99db3ecb5c5de2b61c2eac3250178d2e0dd8458c (commit)
       via  9dc689dd17aeec4fe675eebb7e37cd6313cc5162 (commit)
       via  2b329c9343c79a0da127dcf146803c009ddba4ed (commit)
       via  f20af3a14fe76703285857652a0dcf3271c5691c (commit)
       via  694aab50ce7232255716be6778927b898f58282a (commit)
       via  7f38eafe269cb9bbfff3f31a592eef52a3d5d1c6 (commit)
       via  9b2abcbb4ab830617055a7e650c24d292d8da000 (commit)
       via  2638ed984cf4c8493f25f111778932a674c66ed1 (commit)
       via  e29fec0eb14b47964ea5b0fb82856f170dbd566c (commit)
       via  daa6fe73f9f8afb4ef79b4fa9c4d705b4c457965 (commit)
       via  aacd143dbb830cdbbe36ca55559d7235b8625b46 (commit)
       via  3cd1ca700ad96ffc9c970bd006128d062f2188ff (commit)
       via  8be11301156205cc723bf37e706ad869a5569538 (commit)
       via  038ab5b9ce120bf88d8ca465b141da9e1887a16c (commit)
       via  70c6bf970c19c3dff857bdaf0f9acf86b95d5583 (commit)
       via  8658f05739a4fad3592796714029ddbf7c40d1b4 (commit)
       via  b17cc42044e5c713443c31062acff565b22051af (commit)
       via  cc9ea47b805206ad2203847291887509d86b798c (commit)
       via  2a874997f7856fa92353238764daa4041555a32d (commit)
       via  e52d1458da3aed905e2ca83f22c9faaf2a34a420 (commit)
       via  4d23a65454a9ae7d175c5647c321c56a4e0ce07d (commit)
       via  7cbce8f98edc000e346cd5dfb2fb5b21baa6ec72 (commit)
       via  2423541e47c2da2f3ccec24cf0879101f994b87d (commit)
       via  e0c55209c985306e25db573ae536440a67fd2a11 (commit)
       via  c4acfa38ccf3f82226d630c87f687a478fc99b81 (commit)
       via  7656d3f02a38036b3a941848b03de73e1b3d707e (commit)
       via  3a4eaf6f5930231eb9d2b89e8b8533c62d469b7f (commit)
       via  53fbbc9693ef85a1dd7e139d729a9e08d174a043 (commit)
       via  b5a2171c61d7f45c6d1b75d9ce158ed0081b5052 (commit)
       via  889105436dd52bfc2465a12d17deaa0091620f2a (commit)
       via  a20ca48fbd3308654c5cfbcf1e0a9588e2e3632b (commit)
       via  f9bfadd36921dde775016aa52d9a57525ec4bacd (commit)
       via  83fc2b686a3aea42c2d85f608ab5380b39685a42 (commit)
       via  a17506f81ca6d50a05facb58ad77033a150817cc (commit)
       via  523e9e0334a1a2701b452d27167b1f1d18eff32d (commit)
       via  d766304b6abb155d42a9fed81a34dbe8915c8186 (commit)
       via  bee53eb667048a38025f2d463046931200d3f0f6 (commit)
       via  b52badd958bbebd916c9d6aa8b1a91afb6ed54b6 (commit)
       via  b00e5b01b49de4c64fd588d8a89f2f4cc77eab2e (commit)
       via  bc1746bccbe8082dd1cb135309b910fa1e10e9ca (commit)
       via  0af974d9052f1aa9bd498fabc2c9f320bae2e167 (commit)
       via  7f7fb752059e490f73ce41a2fba350ca1edbc67c (commit)
       via  252c4b181b91b5da77975e9fd4dc72ec0954e9cf (commit)
       via  ce64148abb66288bdbc69d712ad03fb570c8a64f (commit)
       via  1aed01576e7cdf7667d7c8ac7df57d6192fdaa19 (commit)
       via  4ee8dca30e2615a8d282271e97d8a138e2e972bf (commit)
       via  3b91f8282387784db0d68b28e907dc4eda4ea4dc (commit)
       via  6327ca02f2df98a7367425f42e347dcfb115fdaa (commit)
       via  1fa38c9c2cde3eaf50bb5ba8d71a37be794df6ed (commit)
       via  d8d8ec44662eedaa403c44b6ae62beb4b636ec6f (commit)
       via  23320b7a7222dd7f78894a619ce323cc821ebcde (commit)
       via  a318b33acefd33a95f9e76410bcdb7e4fd572ba5 (commit)
       via  144268cf94e1b48c40211c75d3d54d99728db23d (commit)
       via  5f58b04c549352a60fcfc9b6e77badc38b1ce221 (commit)
       via  de7e092ad77f0582e840522fb0374299ff5ff373 (commit)
       via  df8681e880450f746cc6cfc8615036e54f8768de (commit)
       via  36dcaf624b2dcd890cacf72c4d5df0d22b5bee9c (commit)
       via  1854fb9b3d3345100358f6a08848f18889ac86f7 (commit)
       via  c203e7c5b20f530e87917bd0986e9d274bdedaa0 (commit)
       via  bfa79a4e4bb89e8a08564d305693040a9f3f5182 (commit)
       via  b903a5e66a79d6de6a54de07e323d54290264fdf (commit)
       via  ac262e5395071382f6080d7e7bd4df85a8984439 (commit)
       via  ed36b0956dd7987e46c2ea177614f0196e2628a9 (commit)
       via  4acbb4822968e708e5ace5f7442c68d91dd983c9 (commit)
       via  fa5eb879c4c67120a8277c33c11d0bb250b7e57e (commit)
       via  868d982efb1f905a21f05898c9e69f48c25c91a1 (commit)
       via  5c72925bf347bf613169780e6008b878393bb7f1 (commit)
       via  e2dcf35cf5caa1163878e2648bdcebdaa17bb8bf (commit)
       via  cc725e67ca8039e625b4390b95b7680d24c9edd1 (commit)
       via  6dc1ab03791aa1ba048829e1013bf56df22b3e15 (commit)
       via  01b0c107b5a11afae8d56739ee91fcc156da1422 (commit)
       via  efa4d0d2cfecb846333ce5ace9280dd33c32f44f (commit)
       via  c03372e8a4d602695141b972204421d38200aa56 (commit)
       via  515df6ab7c262e2e38bc80e7a64048a469a7362b (commit)
       via  46865d7fc80e2376d3646012befa4de6be08a792 (commit)
       via  c4bcfd944e711c7b24fe97b7f73e002106c60516 (commit)
       via  4c5d033f75a9933636ff612a32a3b9a678df671c (commit)
       via  0ee37a982de0acc24e3d514c7412e087198c2298 (commit)
       via  d8bd19e23e58137db35e1b6611f6580144ef39f0 (commit)
       via  60a9292b2fd603db97805118f49b8e5dd5e6c7bc (commit)
       via  241b072099602d72d174ec57d151f5f881949f8d (commit)
       via  86f45be10927524ee3d464f63c907c5c0a67c69c (commit)
       via  7a68ad12d2fc1a7e865d1be3d292c9bbb72b9a33 (commit)
       via  1b3ef94b7d251746fac2ebdfb1083e1c74e43d8d (commit)
       via  7c433ba94b6a79a852880d01fcf14d4d331354af (commit)
       via  e6395046dfbcdea6389a449aa9748ceebd8d0fa3 (commit)
       via  f2de19c41f9ca8fc301f2b3e26311311cce34d0c (commit)
       via  bcf313023092051cec66bfd6948c13228a27cd98 (commit)
       via  a6686cfec57b0649802da20ff5ad3b8fab398eee (commit)
       via  55d20ee9d2b40a4a1d6745eafa7204ca3f8ad321 (commit)
       via  12988793b1c29db5c0db24d493902dbf74116ed0 (commit)
       via  53ae897e1be16cab0ef2b3fbd29d9f3080c8848e (commit)
       via  bf0404e331834e67c47c60402de0e771403b5934 (commit)
       via  1e763eeb7b8fad621a697db7180ed19ef13af983 (commit)
       via  c7f9c5a201d27599dba339427f2436dcd0626375 (commit)
       via  cb4bac06150b2cb0a019266c663c591c14f75110 (commit)
       via  7ff7821d6a583d62a72e7ab4c0ec772f6710f471 (commit)
       via  5743c00a5b503e8e584c6d7d0150593ec70ca8a0 (commit)
       via  5c2a0a1efc75e13f5362eb625f8462c6a85cca4d (commit)
       via  dba2efa290f4a7f0bd3e75a736e7d4b486d38202 (commit)
       via  d905bd16e1ed2104e620827c25ed3811b991816e (commit)
       via  1c6b2d1cb8aa94959d8a52dfc0acea4f84c83e3d (commit)
       via  fba6940ba4d09de755b6c6c26bb960aa82a26099 (commit)
       via  2482c8b73d7f2a6812ef314595d15237beff744d (commit)
       via  1289bc69fbf6db0f01894c91cdc6fd5b9ffb67a2 (commit)
       via  d658ad626b2e77658cd88e129ec935ff84be2e41 (commit)
       via  8df7c0c94a3a41d7edeb263e2b49ee361d331c88 (commit)
       via  2231ff566d9779be96e35c3ccc92169ea02222e4 (commit)
       via  4405a995ad4e99ce6fedde6b83716347940decf6 (commit)
       via  884153131d5c901f6e936a3a9f924cdda7f014c4 (commit)
       via  d3ffed0331e3701a0aeb0a2d002d94ef332577c2 (commit)
       via  394a8507e8206bc0e710823b083bf2eff49022fd (commit)
       via  9db3cb57b9b87fa081eadb9e1ab3f8d48e5df74f (commit)
       via  8db6d22188ab7df2417611e49905fdb041275cb8 (commit)
       via  13e91621ca0177f2fffaa7e71bd9c0bb3217dbda (commit)
       via  8bd529a08cc0703e19170d82a929e39c17e968eb (commit)
       via  d54d4aab50b2c574fa1e86947bc3edacb8237e4b (commit)
       via  1080008a8fabdd299e1adcc875c819466d48ff9e (commit)
       via  91a3d6751e2f8ae169e416dabd8a72e5fc31fb47 (commit)
       via  b228c59895a23cd51c0badc435dc43335fb43503 (commit)
       via  5c6936ddc80f5d54bc27dac9615d2f83fddcd004 (commit)
       via  3dff460cde91e100beb2133e4a0b074cb0048da7 (commit)
       via  d3be19ac6980129bc9d5dab08793afec0f125f84 (commit)
       via  e18a509a80401664e748fc460f23b0c3f2ebbc6c (commit)
       via  36ea8e6171a95d5badcde04bf6f44e001eae723c (commit)
       via  74bd8b61a3132a18278b95f9e53f47b06ec1de44 (commit)
       via  c35a5db0ac9dbadcfb58543a546fb7d95948bcdf (commit)
       via  efc7856c460c9942575b4f0763f1333256476479 (commit)
       via  cb7280db1d146d8faf2a717f58a75a90f4302008 (commit)
       via  b874c66f617c08852c11cb18f2cab6843bd034eb (commit)
       via  2699a0686e03e034c9cb4c4b239e40d70270a665 (commit)
       via  4bdb761a7e9cd719b1fb42d100bc66b33ea9e4fb (commit)
       via  6c47e896b778a7d0fb14470b4a48d71cb2861c0d (commit)
       via  1a61f728439258fb02eba6a0ec6fc4e51f12909d (commit)
       via  b13382b54f607b294dadc89994033fbe7b697a43 (commit)
       via  cfe262d6ec1d8a98daab26f2006389a2b30a6cc7 (commit)
       via  a3f57ed3b12eaa56f8a056c4568d5827ffdcd19a (commit)
       via  77bd0c02c4b876a66b1585c2d67e9b6284485285 (commit)
       via  c93897d1f1e683dacf524937eadc46c31d80d4c9 (commit)
       via  c47524c6cc784cf5c0f74b43cde95c67f31faad6 (commit)
       via  f5c804d416679e639f3ecb74565c66490e6ef3ec (commit)
       via  a0993c690cde2a49cf688d4ef8678a265f4c789a (commit)
       via  cad44b9f01844d70c5746eab538f8cad2ab54e0a (commit)
       via  833cb27f5ced30a239ed51571a8e23e37693bff8 (commit)
       via  1513145b0e1b2d5bd59dbbceba6a836e7121e6ce (commit)
       via  30cae6246884bfccaa5b2be23b6d881247598d12 (commit)
       via  efe13f6942e1630870c27c862f1065b461048cf3 (commit)
       via  86cb621c88d0107e4b50471ebe450ef23d9c61a2 (commit)
       via  0ec3cfee0d0d9406e7ffb06263a69b1d052c4b7f (commit)
       via  b714bb8bcbae34fccd71a62392424beb714f5587 (commit)
       via  b1406db10647cd47f6690f1ddd4ff72fc772d6a0 (commit)
       via  6cd65f7a6c42bed7802ed620f52b9eed7e18c6c4 (commit)
       via  8b38c8e42f5f750b04ebc60ec342b45f8b40409f (commit)
       via  86831e93479d06448266a7d3151e956cff553cc9 (commit)
       via  eb0d402d4dec05d0316a625e098e3918e0983753 (commit)
       via  1163f3f22606f43265d73477bfe36b206e32fa64 (commit)
       via  c532d29007a65783c3afd2d6a9915461c202f0c2 (commit)
       via  15e4861f71a3246c4c7847043e30837a4b15f251 (commit)
       via  ca6bd1f99b295f106710131f01ed8c12d7758014 (commit)
       via  fae945962be300f21310b404faf5e47c65e28341 (commit)
       via  cadc884008526419f85c40773ac6623e2bd166c5 (commit)
       via  6f991beb908de8d6141a6f129bd80217631847bc (commit)
       via  3909a42540e4da646eb8b2a2167bbc2f49c717ba (commit)
       via  094cce63c583e15492bafe454d83f1d9234fd1a1 (commit)
       via  e00f693cba68833411147084e95137775dfbd006 (commit)
       via  bcde17a8fcf5665e1ee699f0fad8e4d5ee2c3025 (commit)
       via  e52d30ac2e0e184effbe60765df2d9dfcd3e7d5c (commit)
       via  397e1398d907d1b60c9b2c0ec0b722a2f9d05939 (commit)
       via  95b7fcb4fc0901cfd691c0e5ba88c59df16b2955 (commit)
       via  fa5cfa12ed4141fbe3debbe6c89e370731fda8e6 (commit)
       via  0e2c9e55f87bd89c738d0486a89e02a436724e18 (commit)
       via  fd1dda24018ca173507ff60eb146ea758f4d2d25 (commit)
       via  ab976fd8d3f4d5c12b545f8248baeda34a2eee88 (commit)
       via  ced705ae045f1d8f70b6601d3bff54d07cf4e443 (commit)
       via  b22af76755ab15ae9f9113fd179cea9ffd4dd1b8 (commit)
       via  b446727285d7f27998f08b409f7074921257f56a (commit)
       via  718d397fff9890c1ef470efea9195d02fb7b82d2 (commit)
       via  9d00cfc02bcbdcebdf094026b352df5c01156768 (commit)
       via  a6619d1ef2b6a9e75e2472e45d4581dd820bdfc8 (commit)
       via  7fb9d283c5bc4acce37cee4d59108f35dd9db634 (commit)
       via  8bf6cad8bce368787d058706dacb082ae4f5c426 (commit)
       via  344ca3a12adaeea08b0549b9907a97a313fc609f (commit)
       via  d68e69bd5983a0484ad2999830dc9034007d4ae4 (commit)
       via  7529e350cf040a2169db0345ad488514d5f924a6 (commit)
       via  e216226f6852b6581eec838da48d5ceb2580d22b (commit)
       via  859746b30b759568cfdd2b4ae7bcfa995afda50b (commit)
       via  62a5331aee8bd9964bb54d5c8ff1231361f24dba (commit)
       via  de3d3aa5d2a7a797f623c118a85a154590490c2b (commit)
       via  c19b8b104cded7f440f04675e98c4f97296c337e (commit)
       via  034d0c02694126c809e173be254ddbcabf13a93b (commit)
       via  e991806f2b8a1092ccc1f3441302aa08b712c4a5 (commit)
       via  9466b49fe82ecb07c1f91bc8f881bc5bf7cc562c (commit)
       via  05302642938cc6ec861a874bfde6c04056f228c3 (commit)
       via  0935faa52b0b023bf4b50785c6c9bb58551a04c7 (commit)
       via  1dd5ae69ae44a37542ad082b4fa3436f011e10ed (commit)
       via  06b2d53ea2744be025d2c6131ceea6706de7c040 (commit)
       via  73dca6e90f05f910391922a4d95276776c463a80 (commit)
       via  c3fe9d5fb560112d7745f7717538132ccc2b6266 (commit)
       via  c98c8e9f5fac237126b47840cdfe61d35b14cbbe (commit)
       via  a741defa53274c9239ffb8f3a284495533b7cdee (commit)
       via  5b63e5dcb6c7b8b22fb477d1ac2ea11de133474b (commit)
       via  065f9554fac1f29c019b27397d878596578b788b (commit)
       via  a7654b26572bf4ba67070eb43e98a025ad8d0a0a (commit)
       via  acf7909fe89e1acd051e44a357d560014eeb2b48 (commit)
       via  59219161fba52d9a3735bd24b1e9b7b1de026098 (commit)
       via  b531baa7fdfeb527a1ec7ec18aec5189f75c490d (commit)
       via  86773fcf2131a57a229494ac114eee3f711d73c0 (commit)
       via  950df6491dd7f7ce3a823927bbe07cd4d1593c9a (commit)
       via  ff72ba8399f2ff01aec3c3f56176f8008a1c1a0f (commit)
       via  27cdd075c6dfa5faaf72f7e3f5202eb3b490a3bc (commit)
       via  bc4397e28a122f6efe1e82a17b09c3029ffdd113 (commit)
       via  a112d69c562666bc3d6df4961f6c03f5895c3e19 (commit)
       via  512c1524797e2cc95ec30a7a9c86982b6c3def6f (commit)
       via  4cc0d144667a6feaf1e9f04f4b0c7920497b86a8 (commit)
       via  2584cd0634fbba1e251276c21d2f4b821c366212 (commit)
       via  18051b7e8af7a9e71aac73cea9aa215ce50173c5 (commit)
       via  a64e29f82b54fa7b03e27b36ff11306ee62c9902 (commit)
       via  7ca05919b76d8ddcf3b1f635e1fdd829dc6f74f8 (commit)
       via  43a41845e5f430eadbc86104b573a92edd485f43 (commit)
       via  7b02f1ec8dc21bd06d1a0619407216067dcebb99 (commit)
       via  287507fb216cdc12e147828a31762db87a030307 (commit)
       via  ca6529e0a2d7b4bb0f424933e6555821a0cf96f3 (commit)
       via  21f70afb265a0f542b1c6f5c9763fae5f880617f (commit)
       via  0cdc87055f6929304f70e3e7c7a3d3b65b2cae3c (commit)
       via  93162d8c3a0ac5b3bed65038c0fd8de3f5fba598 (commit)
       via  d9b0b71b1d63a8aa2673fc1644fe919a85dbc64d (commit)
       via  87cea300458fea8c776096c41e38563f986b2e31 (commit)
       via  a5da1fa0b609801985ec982bc2c373c21f23cf43 (commit)
       via  e0355b78a9a51f49e01498bf040f2199de59ce82 (commit)
       via  f814ccfb8f9d460cd685f182dfc7cdfcb7a7c105 (commit)
       via  bf88879b0d1a801aa4fd3e804dc5071f2e710a06 (commit)
       via  de671a4e1c47b1445bb4b99229872d874f59afd4 (commit)
       via  6739812ea684e338a8df7378bdd16ab7d803acdc (commit)
       via  1b4d572b777b508db33d819288dd4ea188e65aa8 (commit)
       via  eb43e2899660afa1173f7d20233523c5e3045a36 (commit)
       via  9f84c65e766cfb3ddda1979dee70c87616325231 (commit)
       via  89d461dd83db48540668ffe1283dfaa0faf3432b (commit)
       via  20dbf6b45b49847ce5e256e7eacc9cb8a4d2584f (commit)
       via  18ff277047887f9befc28cd243bbedd964f8b513 (commit)
       via  2b9eed78a0cf8781cea709b63a3dd1d460d3bf46 (commit)
       via  224fd5aebe1bceb130129f0f7d8b182894661105 (commit)
       via  cc49df380edc43b4dd7f18a2fc2cb902cc92c8f7 (commit)
       via  a2e965cad3bc903bc7a7aa8aa1bf18815a8a4526 (commit)
       via  7d387fd54e80dec9f95055d1387b124f75d5e5b5 (commit)
       via  936597ab658b7475878e39ba4d877c382c8dc737 (commit)
       via  67c98320a5056a4b00e06841a789c907d18863b4 (commit)
       via  76627b43e3b5a3846c7b01820fdb359bffa54895 (commit)
       via  71355ff24c2b0802843aa98bab53f1e80daa6a95 (commit)
       via  128b8541ea2340ee656906bf7c7d51bed3e73fe5 (commit)
       via  831bd35ac8122fc16885dc838c2d568afc23794d (commit)
       via  a8cd796f8801b3dc3152bd1ffad27ef56430771a (commit)
       via  a4e7c082dd5f5ad6b182e1e2c950bddb6028ae88 (commit)
       via  00317779b8f4e05fbcc2f2b68abc6aa780cff422 (commit)
       via  a27bcbe1e42dfde3576915245d45ab94d0dfdfdc (commit)
       via  a12d5690e622a2c1ae1e313427180ff5f6b85a54 (commit)
       via  293ccb1aba889cb901f960e0d9c18f6a953e0380 (commit)
       via  003581990cc269b4a6cb093e6f7476d364dfba37 (commit)
       via  55d0c400cc5f4609d6188ca5c8c983bde3429c0a (commit)
       via  85123ea210163e837ee3dad1d472132581868b4c (commit)
       via  cbdae606155e915f970874d3b2ae85ceb572f5e4 (commit)
       via  f2dee32577c8e9c3e3bf258ca11fceac9b097a89 (commit)
       via  7a82a619b2484a79488a84989a9e6c7223e31ad7 (commit)
       via  2dff0494916002e50f5fbb9d52fc47beed14d37c (commit)
       via  53b51a1bd8b87460ff49e0a314c7bcb6cb197637 (commit)
       via  6d63eb300a42646d115c0c46b14d8f6bea43af05 (commit)
       via  9d2f6320c5835bad179eac316b74fe483523dacb (commit)
       via  6d5496a81cdbda61242a98afdc8c23400b8191bc (commit)
       via  1f3b172dc87be7af51aa378af36b3bcca31d2a98 (commit)
       via  c09a4b808e26e7310b897775aae75b218639d2d7 (commit)
       via  0ba220d2c4a8eabdd8608d38a73ebc5365579841 (commit)
       via  7b4bee7e58f76d1875496c044902eb2077e5761e (commit)
       via  3aac3c30b5e3771235552e83f87af4f6fee5a389 (commit)
       via  945ef46af16b454f4c609b75f93e3af731131a4c (commit)
       via  6267ea36a4712a8022629ec9df66997eb2c4cfa4 (commit)
       via  6c04e4a665912191c6ad39b767d27e3277a340d4 (commit)
       via  26689a7c8103e9a2cb44f862988eaabaa6020411 (commit)
       via  2f63478bc979bc351ec3e35d716d8844bd1e7f77 (commit)
       via  5ba6be5837d4638286cb3c191a0181dc47f24468 (commit)
       via  e16b0b9d5b02ebf5381c791f0d37238f2e9125ee (commit)
       via  6a9dae2144fea2464ac1dbbef9b19aef0f00b201 (commit)
       via  8c2eda2f0fb8e8e2f65163f02fb1730a6c66b4ee (commit)
       via  d4b81df050993dbc64904a684f6808ac7ee29345 (commit)
       via  1c07e75fc0a65bc1c87bfdb642d0da14cc924393 (commit)
       via  4ed09871a9f4cbca89b5fe74d3646592f9d603a0 (commit)
       via  4e855b1be28894f68c888fd579e7bc07ff118211 (commit)
       via  5ee8ba10b3ce5b44ad753d760c3a5029411c403b (commit)
       via  7fef6b129ea41208d56bb584a337d37246d363bd (commit)
       via  038cfe747614a086dfc690b32048f3fc813b4d88 (commit)
       via  ac405dd6b7f58af6a819be546f9a2fe5f3e76950 (commit)
       via  537c1c45dcef3f1c11cd50db78f2988280aaf34b (commit)
       via  46ed22a2bb4ae4711b5b062e57d8bdcf03e44792 (commit)
       via  ef42532640076e17ea358e611a4abda17938d419 (commit)
       via  d4be6d435800ce7f255efa29669d3fb7105d2dfd (commit)
       via  61a50ea6ba94a05520f92e420f1c00c4ddf6be44 (commit)
       via  fe672422e72877b050b5c3c0480ccc8ff03cf683 (commit)
       via  0d730e1ad939ee105b014ccb7acfdf338d6b8ff5 (commit)
       via  e8966076c8055257149b92d3f270ab01e3a82749 (commit)
       via  52c47ec69e1f174836f5ae97e5ea5ee4be03ff67 (commit)
       via  8178a963f48c94d398ba6cabd13ad3bedbc5b657 (commit)
       via  49f8d6617519d5ec20ff4611248b626bc854bed3 (commit)
       via  76419a6813a0e77bb23db9446d4156686d4fa4f4 (commit)
       via  68f6e899ef77da1bc6cd91cb6c0829a26be225ca (commit)
       via  10d43a16229db4ddbf3eabff33a37d79b70aef69 (commit)
       via  e9364a43186449d916625c084e8a0f4cbe794792 (commit)
       via  36ea6b5f88d95d1d5bdfed38e5edf979ef6ffbbd (commit)
       via  6eb125c18a7a2da1992a8d81021edf1489bd42d4 (commit)
       via  ae3c5b848299f7773573676b22167ed84ca7c8fd (commit)
       via  0c41f2fdc8e4f8b6ca8d32084d9453354e205ead (commit)
       via  788fdcd188dc83b2a88a2f6b829f3ad9ef46e33e (commit)
       via  780f220e23e7c4c43241d3f302d9993ae2723e06 (commit)
       via  bd9de444e3dae7c33e9a0dd671469b9ba933082e (commit)
       via  7668f92e0577fde7ee87764cc936fec7899c9942 (commit)
       via  df20ee1fffbb7ecd8089c883d19b338daf9789d1 (commit)
       via  12271f0adc49cf28d5278a1eed536382b6b0df4a (commit)
       via  a83f87baf07ca8daa6c4daa85ef5d0b1fe512da7 (commit)
       via  26c9cc93188918e5c3f680efabe5c97895505eb4 (commit)
       via  4c61aeebe5ba8626677bd28eea364cbddc4de5a5 (commit)
       via  2c5474b3266ed13f40989c635a84220f8664067b (commit)
       via  834349f3bef5d1510e6991ddf837b6b3154e4a1c (commit)
       via  65d8ad9dae2b60c0d3467ea9478b42b2e98674f3 (commit)
       via  edc36cce7f8d8ae2ea4d67ee722c8657d8dd6ee7 (commit)
       via  01648d4495f6e239d1925d57516da99652d64a55 (commit)
       via  df2cf83f8b02955473f0ad573fdffdf1ab1484ac (commit)
       via  b27e10a32e74154bbee12100bb9bd75bc55f514e (commit)
       via  25bb7dcec4d71f7537f6e12f0b5e28fd440de109 (commit)
       via  a041295dae11110b8a9c66fd37e02fde73595201 (commit)
       via  645b53743926c7d48574b1bc78f2a3d8190b076f (commit)
       via  011d0abf2ced5a60ce558595d7192f1ebd859731 (commit)
       via  89324074c4d365e96b566559c77cc27f049872c5 (commit)
       via  0fbbbed49c6896e5820d50a6e3787d5257582c11 (commit)
       via  c8bdf4d1497f3c9c01456901ff9d1284eb6abe6a (commit)
       via  d601372feb0b1eb119b92b077cd1e7c33cc8f3b0 (commit)
       via  3af76d7f9492a78aeb0f4709c917e2576f08ccb0 (commit)
       via  29161973e3f5a3d1e5af9d25c875f97352197919 (commit)
       via  8168ee626f2e6da2221d24e371cfb3ee4107024a (commit)
       via  431f250762bd84b93b1ff2f1a0a1fc5e6ea1e809 (commit)
       via  a1a8bd3f42ed77d098a31815dee19cbf4fbd6ee5 (commit)
       via  fc17d48a59a4e0e03a600af42c66c674dd2f8d36 (commit)
       via  4076b74ae81c6e6c70203335dac95873b9305bf3 (commit)
       via  ffb5e7cd1986bad4771e7bea60daf6cf3fa84df6 (commit)
       via  a32d1d70bfaaada1f47d6794bfb7da386dce31cd (commit)
       via  148c01959515a512ef8b22d6fde27436d5b35870 (commit)
       via  07e5cadc54c6778bcc4ea4725b16a261a800e2f7 (commit)
       via  759b6ec59fb2fda8951cdd482e9365d72ec2bc73 (commit)
       via  a03cf6560275b5053097293fe47929c8ab6fbf57 (commit)
       via  fdd0831a58790d20367beed56234f4fd3dc08351 (commit)
       via  9b30ce1a6d28190df1a49d5dfe9d59fca2e9dc68 (commit)
       via  5127457319637df4140e388a4e69ef88d15ffdfd (commit)
       via  a3fdbc68029122eda147d088d3a8d017fe05950a (commit)
       via  5ed2481df25d774d0ef5939f8ec3b43729e02027 (commit)
       via  8f633da789b1a05f5ba5f24ce4eb71c5f3b8867a (commit)
       via  c29f8924377a52329644927f21c10836683c968c (commit)
       via  fb22b940070720b8a9d6af4658ce85600ec198f0 (commit)
       via  aeeabefbe5d2a06e0a22cdce4e40b80af6ed1cc6 (commit)
       via  a4139c6666cfb39fc3b360072c36fb78deff6cfa (commit)
      from  42c3833a07afdfe1925dd1138dd17231545e42eb (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit eaaa598ad864d640b2555781b5c1147d1b593756
Author: mirabilos <t.glaser at tarent.de>
Date:   Sat Nov 4 21:16:55 2017 +0100

    bump

commit 476d2e1ccd9b586cc88377fff4342bc1e85703d4
Merge: c8f161f fb11b19
Author: mirabilos <t.glaser at tarent.de>
Date:   Sat Nov 4 21:13:28 2017 +0100

    Merge branch 'lenny' into sarge

commit c8f161f33c91a7341c964472dee29e6b21816d8b
Merge: 42c3833 5bba20a
Author: mirabilos <t.glaser at tarent.de>
Date:   Sat Nov 4 21:10:10 2017 +0100

    Merge tag 'mksh-56b.20171017_wtf1_deb5u1' into sarge
    
    mksh (56b.20171017+wtf1~deb5u1) wtf; urgency=medium

-----------------------------------------------------------------------

Summary of changes:
 Build.sh             |  264 ++++++----
 Makefile             |   40 +-
 README               |    9 +-
 check.pl             |   88 +++-
 check.t              | 1335 ++++++++++++++++++++++++++++++++++++--------------
 debian/.mkshrc       |   32 +-
 debian/README.Debian |    8 +-
 debian/changelog     |  281 ++++++++++-
 debian/control       |    2 +-
 debian/copyright     |    6 +-
 debian/meat          |   18 +-
 debian/mksh.NEWS     |   40 ++
 debian/uhr           |  595 ++++++++++++----------
 dot.mkshrc           |  504 ++++++++++---------
 edit.c               | 1251 +++++++++++++++++++++++-----------------------
 eval.c               |  395 ++++++++-------
 exec.c               |  132 +++--
 expr.c               |   87 ++--
 funcs.c              |  809 +++++++++++-------------------
 histrap.c            |  163 ++++--
 jobs.c               |   37 +-
 lex.c                |  318 ++++++------
 lksh.1               |   92 +---
 main.c               |  264 +++++++---
 misc.c               |  776 ++++++++++++++++++++---------
 mksh.1               |  742 +++++++++++++++++-----------
 os2.c                |  576 ++++++++++++++++++++++
 sh.h                 |  625 +++++++++++++++++------
 sh_flags.opt         |   16 +-
 shf.c                |  219 ++++++++-
 syn.c                |  263 +++++-----
 tree.c               |  117 +++--
 var.c                |  558 ++++++++++++++++++---
 var_spec.h           |   11 +-
 34 files changed, 6973 insertions(+), 3700 deletions(-)
 create mode 100644 os2.c

diff --git a/Build.sh b/Build.sh
index a4cac2f..78fe347 100644
--- a/Build.sh
+++ b/Build.sh
@@ -1,8 +1,8 @@
 #!/bin/sh
-srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.705 2016/09/01 12:59:04 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
+#		2011, 2012, 2013, 2014, 2015, 2016, 2017
 #	mirabilos <m at mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -53,6 +53,16 @@ alll=qwertyuiopasdfghjklzxcvbnm
 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 @@ ac_header() {
 		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
@@ -495,6 +505,8 @@ check_categories=
 last=
 tfn=
 legacy=0
+textmode=0
+ebcdic=false
 
 for i
 do
@@ -518,6 +530,9 @@ do
 	:-c)
 		last=c
 		;;
+	:-E)
+		ebcdic=true
+		;;
 	:-G)
 		echo "$me: Do not call me with '-G'!" >&2
 		exit 1
@@ -551,6 +566,12 @@ do
 	:-r)
 		r=1
 		;;
+	:-T)
+		textmode=1
+		;;
+	:+T)
+		textmode=0
+		;;
 	:-t)
 		last=t
 		;;
@@ -584,18 +605,27 @@ if test -d $tfn || test -d $tfn.exe; then
 	exit 1
 fi
 rmf a.exe* a.out* conftest.c conftest.exe* *core core.* ${tfn}* *.bc *.dbg \
-    *.ll *.o *.gen Rebuild.sh lft no signames.inc test.sh x vv.out
+    *.ll *.o *.gen *.cat1 Rebuild.sh lft no signames.inc test.sh x vv.out
 
-SRCS="lalloc.c eval.c exec.c expr.c funcs.c histrap.c jobs.c"
+SRCS="lalloc.c edit.c eval.c exec.c expr.c funcs.c histrap.c jobs.c"
 SRCS="$SRCS lex.c main.c misc.c shf.c syn.c tree.c var.c"
 
 if test $legacy = 0; then
-	SRCS="$SRCS edit.c"
 	check_categories="$check_categories shell:legacy-no int:32"
 else
 	check_categories="$check_categories shell:legacy-yes"
 	add_cppflags -DMKSH_LEGACY_MODE
-	HAVE_PERSISTENT_HISTORY=0
+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
+	check_categories="$check_categories shell:textmode-yes shell:binmode-no"
+	add_cppflags -DMKSH_WITH_TEXTMODE
 fi
 
 if test x"$srcdir" = x"."; then
@@ -753,7 +783,29 @@ GNU/kFreeBSD)
 	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
+	add_cppflags -D_LIMITS_EXTENSION
+	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
+	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}"
+	: "${HAVE_CAN_FNOSTRICTALIASING=0}"
+	: "${HAVE_CAN_FSTACKPROTECTORSTRONG=0}"
 	;;
 HP-UX)
 	;;
@@ -797,7 +849,9 @@ Minix3)
 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)
@@ -831,14 +885,51 @@ OpenBSD)
 	: "${HAVE_SETLOCALE_CTYPE=0}"
 	;;
 OS/2)
+	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 currently being ported"
+	oswarn="; it is being ported"
 	check_categories="$check_categories nosymlink"
 	: "${CC=gcc}"
 	: "${SIZE=: size}"
+	SRCS="$SRCS os2.c"
 	add_cppflags -DMKSH_UNEMPLOYED
 	add_cppflags -DMKSH_NOPROSPECTOFWORK
+	add_cppflags -DMKSH_NO_LIMITS
+	add_cppflags -DMKSH_DOSPATH
+	if test $textmode = 0; then
+		x='dis'
+		y='standard OS/2 tools'
+	else
+		x='en'
+		y='standard Unix mksh and other tools'
+	fi
+	echo >&2 "
+OS/2 Note: mksh can be built with or without 'textmode'.
+Without 'textmode' it will behave like a standard Unix utility,
+compatible to mksh on all other platforms, using only ASCII LF
+(0x0A) as line ending character. This is supported by the mksh
+upstream developer.
+With 'textmode', mksh will be modified to behave more like other
+OS/2 utilities, supporting ASCII CR+LF (0x0D 0x0A) as line ending
+at the cost of deviation from standard mksh. This is supported by
+the mksh-os2 porter.
+
+] You are currently compiling with textmode ${x}abled, introducing
+] 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
@@ -853,7 +944,9 @@ Plan9)
 	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'
@@ -993,7 +1086,7 @@ $e $bi$me: Scanning for functions... please ignore any errors.$ao
 # - 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)
@@ -1243,7 +1336,7 @@ unknown)
 	# 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
 
 #
@@ -1338,8 +1431,16 @@ watcom)
 	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
@@ -1487,8 +1588,12 @@ msc)
 	ac_flags 1 wp64 "${ccpc}/Wp64" 'to enable 64-bit warnings'
 	;;
 nwcc)
-	i=1
 	#broken# ac_flags 1 ssp -stackprotect
+	i=1
+	;;
+pcc)
+	ac_flags 1 fstackprotectorall -fstack-protector-all
+	i=1
 	;;
 sunpro)
 	phase=u
@@ -1505,10 +1610,24 @@ tendra)
 	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
@@ -1647,6 +1766,10 @@ ac_ifcpp 'ifdef MKSH_NOPROSPECTOFWORK' isset_MKSH_NOPROSPECTOFWORK '' \
     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"
@@ -1967,6 +2090,11 @@ ac_test mmap lock_fcntl 0 'for mmap and munmap' <<-'EOF'
 	    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)); }
@@ -2121,75 +2249,13 @@ EOF
 # 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
 ac_cppflags
 
-save_CFLAGS=$CFLAGS
-ac_testn compile_time_asserts_$$ '' 'whether compile-time assertions pass' <<-'EOF'
-	#define MKSH_INCLUDES_ONLY
-	#include "sh.h"
-	#ifndef CHAR_BIT
-	#define CHAR_BIT 8	/* defuse this test on really legacy systems */
-	#endif
-	struct ctasserts {
-	#define cta(name, assertion) char name[(assertion) ? 1 : -1]
-/* this one should be defined by the standard */
-cta(char_is_1_char, (sizeof(char) == 1) && (sizeof(signed char) == 1) &&
-    (sizeof(unsigned char) == 1));
-cta(char_is_8_bits, ((CHAR_BIT) == 8) && ((int)(unsigned char)0xFF == 0xFF) &&
-    ((int)(unsigned char)0x100 == 0) && ((int)(unsigned char)(int)-1 == 0xFF));
-/* the next assertion is probably not really needed */
-cta(short_is_2_char, sizeof(short) == 2);
-cta(short_size_no_matter_of_signedness, sizeof(short) == sizeof(unsigned short));
-/* the next assertion is probably not really needed */
-cta(int_is_4_char, sizeof(int) == 4);
-cta(int_size_no_matter_of_signedness, sizeof(int) == sizeof(unsigned int));
-
-cta(long_ge_int, sizeof(long) >= sizeof(int));
-cta(long_size_no_matter_of_signedness, sizeof(long) == sizeof(unsigned long));
-
-#ifndef MKSH_LEGACY_MODE
-/* the next assertion is probably not really needed */
-cta(ari_is_4_char, sizeof(mksh_ari_t) == 4);
-/* but this is */
-cta(ari_has_31_bit, 0 < (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 1));
-/* the next assertion is probably not really needed */
-cta(uari_is_4_char, sizeof(mksh_uari_t) == 4);
-/* but the next three are; we REQUIRE unsigned integer wraparound */
-cta(uari_has_31_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 2 + 1));
-cta(uari_has_32_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3));
-cta(uari_wrap_32_bit,
-    (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3) >
-    (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 4));
-#define NUM 22
-#else
-#define NUM 16
-#endif
-/* these are always required */
-cta(ari_is_signed, (mksh_ari_t)-1 < (mksh_ari_t)0);
-cta(uari_is_unsigned, (mksh_uari_t)-1 > (mksh_uari_t)0);
-/* we require these to have the precisely same size and assume 2s complement */
-cta(ari_size_no_matter_of_signedness, sizeof(mksh_ari_t) == sizeof(mksh_uari_t));
-
-cta(sizet_size_no_matter_of_signedness, sizeof(ssize_t) == sizeof(size_t));
-cta(sizet_voidptr_same_size, sizeof(size_t) == sizeof(void *));
-cta(sizet_funcptr_same_size, sizeof(size_t) == sizeof(void (*)(void)));
-/* our formatting routines assume this */
-cta(ptr_fits_in_long, sizeof(size_t) <= sizeof(long));
-cta(ari_fits_in_long, sizeof(mksh_ari_t) <= sizeof(long));
-/* for struct alignment people */
-		char padding[64 - NUM];
-	};
-char ctasserts_dblcheck[sizeof(struct ctasserts) == 64 ? 1 : -1];
-	int main(void) { return (sizeof(ctasserts_dblcheck) + isatty(0)); }
-EOF
-CFLAGS=$save_CFLAGS
-eval test 1 = \$HAVE_COMPILE_TIME_ASSERTS_$$ || exit 1
-
 #
 # extra checks for legacy mksh
 #
@@ -2343,7 +2409,7 @@ addsrcs '!' HAVE_STRLCPY strlcpy.c
 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=531
+add_cppflags -DMKSH_BUILD_R=562
 
 $e $bi$me: Finished configuration testing, now producing output.$ao
 
@@ -2370,8 +2436,8 @@ cat >test.sh <<-EOF
 	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 ;;
@@ -2384,6 +2450,7 @@ cat >test.sh <<-EOF
 	(+S)	Sflag=0 ;;
 	(s)	sflag=\$OPTARG ;;
 	(t)	uset=1; tflag=\$OPTARG ;;
+	(U)	useU=1; Uflag=\$OPTARG ;;
 	(v)	vflag=1 ;;
 	(+v)	vflag=0 ;;
 	(*)	xflag=1 ;;
@@ -2391,6 +2458,9 @@ cat >test.sh <<-EOF
 	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
@@ -2408,6 +2478,10 @@ cat >test.sh <<-EOF
 		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
@@ -2589,8 +2663,8 @@ esac
 tcfn=$mkshexe
 test $cm = combine || v "$CC $CFLAGS $LDFLAGS -o $tcfn $lobjs $LIBS $ccpr"
 test -f $tcfn || exit 1
-test 1 = $r || v "$NROFF -mdoc <'$srcdir/mksh.1' >$tfn.cat1" || \
-    rmf $tfn.cat1
+test 1 = $r || v "$NROFF -mdoc <'$srcdir/lksh.1' >lksh.cat1" || rmf lksh.cat1
+test 1 = $r || v "$NROFF -mdoc <'$srcdir/mksh.1' >mksh.cat1" || rmf mksh.cat1
 test 0 = $eq && v $SIZE $tcfn
 i=install
 test -f /usr/ucb/$i && i=/usr/ucb/$i
@@ -2604,12 +2678,14 @@ if test $legacy = 0; then
 fi
 $e
 $e Installing the manual:
-if test -f $tfn.cat1; then
-	$e "# $i -c -o root -g bin -m 444 $tfn.cat1" \
-	    "/usr/share/man/cat1/$tfn.0"
+if test -f mksh.cat1; then
+	$e "# $i -c -o root -g bin -m 444 lksh.cat1" \
+	    "/usr/share/man/cat1/lksh.0"
+	$e "# $i -c -o root -g bin -m 444 mksh.cat1" \
+	    "/usr/share/man/cat1/mksh.0"
 	$e or
 fi
-$e "# $i -c -o root -g bin -m 444 $tfn.1 /usr/share/man/man1/$tfn.1"
+$e "# $i -c -o root -g bin -m 444 lksh.1 mksh.1 /usr/share/man/man1/"
 $e
 $e Run the regression test suite: ./test.sh
 $e Please also read the sample file dot.mkshrc and the fine manual.
@@ -2649,7 +2725,7 @@ MKSH_A4PB			force use of arc4random_pushb
 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/Makefile b/Makefile
index b359210..ab625f2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
-# $MirOS: src/bin/mksh/Makefile,v 1.152 2016/08/25 16:21:30 tg Exp $
+# $MirOS: src/bin/mksh/Makefile,v 1.162 2017/08/29 13:38:29 tg Exp $
 #-
 # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
-#		2011, 2012, 2013, 2014, 2015, 2016
+#		2011, 2012, 2013, 2014, 2015, 2016, 2017
 #	mirabilos <m at mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -52,12 +52,13 @@ CPPFLAGS+=	-DMKSH_ASSUME_UTF8 -DMKSH_DISABLE_DEPRECATED \
 		-DHAVE_SYS_SIGLIST=1 -DHAVE_FLOCK=1 -DHAVE_LOCK_FCNTL=1 \
 		-DHAVE_GETRUSAGE=1 -DHAVE_GETSID=1 -DHAVE_GETTIMEOFDAY=1 \
 		-DHAVE_KILLPG=1 -DHAVE_MEMMOVE=1 -DHAVE_MKNOD=0 -DHAVE_MMAP=1 \
-		-DHAVE_NICE=1 -DHAVE_REVOKE=1 -DHAVE_SETLOCALE_CTYPE=0 \
-		-DHAVE_LANGINFO_CODESET=0 -DHAVE_SELECT=1 -DHAVE_SETRESUGID=1 \
-		-DHAVE_SETGROUPS=1 -DHAVE_STRERROR=0 -DHAVE_STRSIGNAL=0 \
-		-DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 -DHAVE_REVOKE_DECL=1 \
+		-DHAVE_FTRUNCATE=1 -DHAVE_NICE=1 -DHAVE_REVOKE=1 \
+		-DHAVE_SETLOCALE_CTYPE=0 -DHAVE_LANGINFO_CODESET=0 \
+		-DHAVE_SELECT=1 -DHAVE_SETRESUGID=1 -DHAVE_SETGROUPS=1 \
+		-DHAVE_STRERROR=0 -DHAVE_STRSIGNAL=0 -DHAVE_STRLCPY=1 \
+		-DHAVE_FLOCK_DECL=1 -DHAVE_REVOKE_DECL=1 \
 		-DHAVE_SYS_ERRLIST_DECL=1 -DHAVE_SYS_SIGLIST_DECL=1 \
-		-DHAVE_PERSISTENT_HISTORY=1 -DMKSH_BUILD_R=531
+		-DHAVE_PERSISTENT_HISTORY=1 -DMKSH_BUILD_R=562
 CPPFLAGS+=	-D${${PROG:L}_tf:C/(Mir${MAN:E}{0,1}){2}/4/:S/x/mksh_BUILD/:U}
 CPPFLAGS+=	-I.
 COPTS+=		-std=c89 -Wall
@@ -94,13 +95,18 @@ CLEANFILES+=	${GENERATED}
 
 ${PROG} beforedepend: ${GENERATED}
 
+REGRESS_CATEGORIES:=shell:legacy-no,int:32,shell:textmode-no,shell:binmode-yes,fastbox
+.if !empty(GCEXTRA:M-DMKSH_FAUX_EBCDIC)
+REGRESS_CATEGORIES:=${REGRESS_CATEGORIES},shell:faux-ebcdic
+.endif
+
 regress: ${PROG} check.pl check.t
 	-rm -rf regress-dir
 	mkdir -p regress-dir
 	echo export FNORD=666 >regress-dir/.mkshrc
 	HOME=$$(realpath regress-dir) perl ${SRCDIR}/check.pl \
 	    -s ${SRCDIR}/check.t -v -p ./${PROG} \
-	    -C shell:legacy-no,int:32,fastbox
+	    -C ${REGRESS_CATEGORIES}
 
 TEST_BUILD_ENV:=	TARGET_OS= CPP=
 TEST_BUILD_ENV+=	HAVE_STRING_POOLING=0
@@ -135,6 +141,13 @@ distribution:
 	chown ${BINOWN}:${CONFGRP} ${DESTDIR}/etc/skel/.mkshrc
 	chmod 0644 ${DESTDIR}/etc/skel/.mkshrc
 
+.if make(cats) || make(clean) || make(cleandir)
+MAN=		lksh.1 mksh.1
+.endif
+CLEANFILES+=	${MANALL:S/.cat/.ps/} ${MAN:S/$/.pdf/} ${MANALL:S/$/.gz/}
+CLEANFILES+=	${MAN:S/$/.htm/} ${MAN:S/$/.htm.gz/}
+CLEANFILES+=	${MAN:S/$/.txt/} ${MAN:S/$/.txt.gz/}
+
 .include <bsd.prog.mk>
 
 .ifmake cats
@@ -145,16 +158,14 @@ V_GHOSTSCRIPT!=	pkg_info -e 'ghostscript-*'
 .  endif
 .endif
 
-CLEANFILES+=	${MANALL:S/.cat/.ps/} ${MAN:S/$/.pdf/} ${MANALL:S/$/.gz/}
-CLEANFILES+=	${MAN:S/$/.htm/} ${MAN:S/$/.htm.gz/}
-CLEANFILES+=	${MAN:S/$/.txt/} ${MAN:S/$/.txt.gz/}
-CATS_KW=	mksh, ksh, sh
+CATS_KW=	mksh, lksh, ksh, sh, Korn Shell, Android
+CATS_TITLE_lksh_1=lksh - Legacy Korn shell built on mksh
 CATS_TITLE_mksh_1=mksh - The MirBSD Korn Shell
 cats: ${MANALL} ${MANALL:S/.cat/.ps/}
 .if "${MANALL:Nlksh.cat1:Nmksh.cat1}" != ""
 .  error Adjust here.
 .endif
-.for _m _n in mksh 1
+.for _m _n in lksh 1 mksh 1
 	x=$$(ident ${SRCDIR:Q}/${_m}.${_n} | \
 	    awk '/Mir''OS:/ { print $$4$$5; }' | \
 	    tr -dc 0-9); (( $${#x} == 14 )) || exit 1; exec \
@@ -190,6 +201,9 @@ dr:
 	p=$$(realpath ${PROG:Q}) && cd ${SRCDIR:Q} && exec ${MKSH} \
 	    ${BSDSRCDIR:Q}/contrib/hosted/tg/sdmksh "$$p"
 
+r:
+	cd ${.CURDIR:Q} && exec env ENV=/nonexistent PS1='% ' ${.OBJDIR:Q}/mksh
+
 repool:
 	cd ${.CURDIR:Q} && \
 	    exec ${MKSH} ${BSDSRCDIR:Q}/scripts/stringpool.sh sh.h
diff --git a/README b/README
index 16c39b2..a158560 100644
--- a/README
+++ b/README
@@ -13,8 +13,9 @@ The PPA in which packages for a certain derivate lie is:
 ‣ https://launchpad.net/~mirabilos/+archive/
 
 Distribution targets and their git branches:
-• master ⇒ sid
-  ‣ stretch via shared binaries
+• master ⇒ stretch; sid on arches not in stretch
+  ‣ sid via shared binaries
+  ‣ buster via shared binaries
 • jessie ⇒ jessie
   ‣ xenial (16.04) via dch --bpo, unchanged, via PPA
 • wheezy ⇒ wheezy with libklibc-dev from jessie
@@ -22,5 +23,9 @@ Distribution targets and their git branches:
 • lenny ⇒ lenny
   ‣ squeeze via binNMU
   ‣ precise (12.04) via dch --bpo, unchanged, via PPA
+  ‣ hardy (8.04) via dch --bpo, unchanged, via PPA, untested
+  ‣ UCS 2.4, untested (UCS = Univention Corporate Server)
 • sarge ⇒ sarge
   ‣ etch via binNMU
+  ‣ dapper (6.06), untested
+  ‣ UCS 2.2 and 2.3, untested
diff --git a/check.pl b/check.pl
index a80d4e1..e9c2437 100644
--- a/check.pl
+++ b/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 at 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 @@ BEGIN {
 };
 
 use Getopt::Std;
-use Config;
 
 $os = defined $^O ? $^O : 'unknown';
 
@@ -180,7 +185,7 @@ $os = defined $^O ? $^O : 'unknown';
 
 $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 @@ Usage: $prog [-Pv] [-C cat] [-e e=v] [-p prog] [-s fn] [-T dir] \
 		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 @@ $nxpassed = 0;
 
 %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 program specified (use -p)\n" if !defined $opt_p;
 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 @@ if (defined $opt_t) {
 }
 $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 @@ foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME',
 }
 $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 @@ first_diff
 	    $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 @@ read_test
 	    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/check.t b/check.t
index 78c1223..702625e 100644
--- a/check.t
+++ b/check.t
@@ -1,8 +1,8 @@
-# $MirOS: src/bin/mksh/check.t,v 1.754 2016/10/02 22:21:43 tg Exp $
+# $MirOS: src/bin/mksh/check.t,v 1.799 2017/10/17 23:45:16 tg Exp $
 # -*- mode: sh -*-
 #-
 # Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
-#	      2011, 2012, 2013, 2014, 2015, 2016
+#	      2011, 2012, 2013, 2014, 2015, 2016, 2017
 #	mirabilos <m at mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -30,22 +30,62 @@
 # (2013/12/02 20:39:44) http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/bin/ksh/?sortby=date
 
 expected-stdout:
-	@(#)MIRBSD KSH R53 2016/10/02
+	@(#)MIRBSD KSH R56 2017/10/17
 description:
-	Check version of shell.
+	Check base version of full shell
 stdin:
-	echo $KSH_VERSION
+	echo ${KSH_VERSION%%' +'*}
 name: KSH_VERSION
-category: shell:legacy-no
+category: !shell:legacy-yes
 ---
 expected-stdout:
-	@(#)LEGACY KSH R53 2016/10/02
+	@(#)LEGACY KSH R56 2017/10/17
 description:
-	Check version of legacy shell.
+	Check base version of legacy shell
 stdin:
-	echo $KSH_VERSION
+	echo ${KSH_VERSION%%' +'*}
 name: KSH_VERSION-legacy
-category: shell:legacy-yes
+category: !shell:legacy-no
+---
+name: KSH_VERSION-ascii
+description:
+	Check that the shell version tag does not include EBCDIC
+category: !shell:ebcdic-yes
+stdin:
+	for x in $KSH_VERSION; do
+		[[ $x = '+EBCDIC' ]] && exit 1
+	done
+	exit 0
+---
+name: KSH_VERSION-ebcdic
+description:
+	Check that the shell version tag includes EBCDIC
+category: !shell:ebcdic-no
+stdin:
+	for x in $KSH_VERSION; do
+		[[ $x = '+EBCDIC' ]] && exit 0
+	done
+	exit 1
+---
+name: KSH_VERSION-binmode
+description:
+	Check that the shell version tag does not include TEXTMODE
+category: !shell:textmode-yes
+stdin:
+	for x in $KSH_VERSION; do
+		[[ $x = '+TEXTMODE' ]] && exit 1
+	done
+	exit 0
+---
+name: KSH_VERSION-textmode
+description:
+	Check that the shell version tag includes TEXTMODE
+category: !shell:textmode-no
+stdin:
+	for x in $KSH_VERSION; do
+		[[ $x = '+TEXTMODE' ]] && exit 0
+	done
+	exit 1
 ---
 name: selftest-1
 description:
@@ -92,23 +132,6 @@ category: disabled
 stdin:
 	set
 ---
-name: selftest-legacy
-description:
-	Check some things in the LEGACY KSH
-category: shell:legacy-yes
-stdin:
-	set +o emacs
-	set +o vi
-	[[ "$(set +o) -o" = *"-o emacs -o"* ]] && echo 1=emacs
-	[[ "$(set +o) -o" = *"-o vi -o"* ]] && echo 1=vi
-	set -o emacs
-	set -o vi
-	[[ "$(set +o) -o" = *"-o emacs -o"* ]] && echo 2=emacs
-	[[ "$(set +o) -o" = *"-o vi -o"* ]] && echo 2=vi
-expected-stdout:
-	2=emacs
-	2=vi
----
 name: selftest-direct-builtin-call
 description:
 	Check that direct builtin calls work
@@ -119,6 +142,26 @@ stdin:
 expected-stdout:
 	-c echo  foo
 ---
+name: selftest-pathsep-unix
+description:
+	Check that $PATHSEP is set correctly.
+category: !os:os2
+stdin:
+	PATHSEP=.; export PATHSEP
+	"$__progname" -c 'print -r -- $PATHSEP'
+expected-stdout:
+	:
+---
+name: selftest-pathsep-dospath
+description:
+	Check that $PATHSEP is set correctly.
+category: os:os2
+stdin:
+	PATHSEP=.; export PATHSEP
+	"$__progname" -c 'print -r -- $PATHSEP'
+expected-stdout:
+	;
+---
 name: alias-1
 description:
 	Check that recursion is detected/avoided in aliases.
@@ -261,14 +304,42 @@ name: alias-11
 description:
 	Check that special argument handling still applies with escaped aliases
 stdin:
-	alias local='\typeset'
-	function foo {
-		local x=$1 y=z
+	alias local1='\typeset'
+	alias local2='\\builtin typeset'
+	function fooa {
+		local1 x=$1 y=z
+		print -r -- "$x,$y"
+	}
+	function foob {
+		local2 x=$1 y=z
 		print -r -- "$x,$y"
 	}
-	foo 'bar - baz'
+	x=1 y=2; fooa 'bar - baz'
+	x=1 y=2; foob 'bar - baz'
 expected-stdout:
 	bar - baz,z
+	bar - baz,z
+---
+name: alias-12
+description:
+	Something weird from Martijn Dekker
+stdin:
+	alias echo=print
+	x() { echo a; (echo b); x=$(echo c); }
+	typeset -f x
+	alias OPEN='{' CLOSE='};'
+	{ OPEN echo hi1; CLOSE }
+	var=`{ OPEN echo hi2; CLOSE }` && echo "$var"
+	var=$({ OPEN echo hi3; CLOSE }) && echo "$var"
+expected-stdout:
+	x() {
+		\print a 
+		( \print b ) 
+		x=$(\print c ) 
+	} 
+	hi1
+	hi2
+	hi3
 ---
 name: arith-compound
 description:
@@ -340,6 +411,62 @@ expected-stdout:
 	2
 	0
 ---
+name: arith-lazy-5-arr-n
+description: Check lazy evaluation with side effects
+stdin:
+	a=0; echo "$((0&&b[a++],a))"
+expected-stdout:
+	0
+---
+name: arith-lazy-5-arr-p
+description: Check lazy evaluation with side effects
+stdin:
+	a=0; echo "$((0&&(b[a++]),a))"
+expected-stdout:
+	0
+---
+name: arith-lazy-5-str-n
+description: Check lazy evaluation with side effects
+stdin:
+	a=0 b=a++; ((0&&b)); echo $a
+expected-stdout:
+	0
+---
+name: arith-lazy-5-str-p
+description: Check lazy evaluation with side effects
+stdin:
+	a=0 b=a++; ((0&&(b))); echo $a
+expected-stdout:
+	0
+---
+name: arith-lazy-5-tern-l-n
+description: Check lazy evaluation with side effects
+stdin:
+	a=0; echo "$((0?b[a++]:999,a))"
+expected-stdout:
+	0
+---
+name: arith-lazy-5-tern-l-p
+description: Check lazy evaluation with side effects
+stdin:
+	a=0; echo "$((0?(b[a++]):999,a))"
+expected-stdout:
+	0
+---
+name: arith-lazy-5-tern-r-n
+description: Check lazy evaluation with side effects
+stdin:
+	a=0; echo "$((1?999:b[a++],a))"
+expected-stdout:
+	0
+---
+name: arith-lazy-5-tern-r-p
+description: Check lazy evaluation with side effects
+stdin:
+	a=0; echo "$((1?999:(b[a++]),a))"
+expected-stdout:
+	0
+---
 name: arith-ternary-prec-1
 description:
 	Check precedence of ternary operator vs assignment
@@ -896,6 +1023,8 @@ stdin:
 	    echo end-$i
 	done
 	echo end-3
+	for i in a b c; do echo $i; eval break; echo bad-$i; done
+	echo end-4
 expected-stdout:
 	a
 	end-1
@@ -908,6 +1037,8 @@ expected-stdout:
 	c:x
 	end-c
 	end-3
+	a
+	end-4
 ---
 name: break-2
 description:
@@ -977,6 +1108,8 @@ stdin:
 	    echo end-$i
 	done
 	echo end-3
+	for i in a b c; do echo $i; eval continue; echo bad-$i ; done
+	echo end-4
 expected-stdout:
 	a
 	b
@@ -999,6 +1132,10 @@ expected-stdout:
 	c:z
 	end-c
 	end-3
+	a
+	b
+	c
+	end-4
 ---
 name: continue-2
 description:
@@ -1219,7 +1356,7 @@ name: cd-pe
 description:
 	Check package for cd -Pe
 need-pass: no
-# the mv command fails on Cygwin
+# the mv command fails on Cygwin and z/OS
 # Hurd aborts the testsuite (permission denied)
 # QNX does not find subdir to cd into
 category: !os:cygwin,!os:gnu,!os:msys,!os:nto,!os:os390,!nosymlink
@@ -1240,7 +1377,7 @@ file-setup: file 644 "x"
 	cd -P$1 subdir
 	echo 2=$?,${PWD#$bwd/}
 	cd $bwd
-	chmod 755 renamed
+	chmod 755 noread renamed 2>/dev/null
 	rm -rf noread link renamed
 stdin:
 	export TSHELL="$__progname"
@@ -1829,15 +1966,12 @@ expected-stdout:
 name: eglob-bad-1
 description:
 	Check that globbing isn't done when glob has syntax error
-file-setup: file 644 "abcx"
-file-setup: file 644 "abcz"
-file-setup: file 644 "bbc"
+category: !os:cygwin,!os:msys,!os:os2
+file-setup: file 644 "@(a[b|)c]foo"
 stdin:
-	echo !([*)*
-	echo +(a|b[)*
+	echo @(a[b|)c]*
 expected-stdout:
-	!([*)*
-	+(a|b[)*
+	@(a[b|)c]*
 ---
 name: eglob-bad-2
 description:
@@ -1924,9 +2058,11 @@ stdin:
 	case foo in *(a|b[)) echo yes;; *) echo no;; esac
 	case foo in *(a|b[)|f*) echo yes;; *) echo no;; esac
 	case '*(a|b[)' in *(a|b[)) echo yes;; *) echo no;; esac
+	case 'aab[b[ab[a' in *(a|b[)) echo yes;; *) echo no;; esac
 expected-stdout:
 	no
 	yes
+	no
 	yes
 ---
 name: eglob-trim-1
@@ -2190,6 +2326,7 @@ expected-stdout:
 name: eglob-utf8-1
 description:
 	UTF-8 mode differences for eglobbing
+category: !shell:ebcdic-yes
 stdin:
 	s=blöd
 	set +U
@@ -2221,17 +2358,26 @@ expected-stdout:
 ---
 name: glob-bad-1
 description:
-	Check that globbing isn't done when glob has syntax error
+	Check that [ matches itself if it's not a valid bracket expr
+	but does not prevent globbing, while backslash-escaping does
 file-setup: dir 755 "[x"
 file-setup: file 644 "[x/foo"
 stdin:
 	echo [*
 	echo *[x
 	echo [x/*
-expected-stdout:
-	[*
-	*[x
+	:>'ab[x'
+	:>'a[a-z][x'
+	echo a[a-z][*
+	echo a[a-z]*
+	echo a[a\-z]*
+expected-stdout:
+	[x
+	[x
 	[x/foo
+	ab[x
+	ab[x
+	a[a-z]*
 ---
 name: glob-bad-2
 description:
@@ -2250,6 +2396,18 @@ expected-stdout:
 	dir/abc
 	dir/abc
 ---
+name: glob-bad-3
+description:
+	Check that the slash is parsed before the glob
+stdin:
+	mkdir a 'a[b'
+	(cd 'a[b'; echo ok >'c]d')
+	echo nok >abd
+	echo fail >a/d
+	cat a[b/c]d
+expected-stdout:
+	ok
+---
 name: glob-range-1
 description:
 	Test range matching
@@ -2258,24 +2416,31 @@ file-setup: file 644 "abc"
 file-setup: file 644 "bbc"
 file-setup: file 644 "cbc"
 file-setup: file 644 "-bc"
+file-setup: file 644 "!bc"
+file-setup: file 644 "^bc"
+file-setup: file 644 "+bc"
+file-setup: file 644 ",bc"
+file-setup: file 644 "0bc"
+file-setup: file 644 "1bc"
 stdin:
 	echo [ab-]*
 	echo [-ab]*
 	echo [!-ab]*
 	echo [!ab]*
 	echo []ab]*
-	:>'./!bc'
-	:>'./^bc'
 	echo [^ab]*
-	echo [!ab]*
+	echo [+--]*
+	echo [--1]*
+
 expected-stdout:
 	-bc abc bbc
 	-bc abc bbc
-	cbc
-	-bc cbc
+	!bc +bc ,bc 0bc 1bc ^bc cbc
+	!bc +bc ,bc -bc 0bc 1bc ^bc cbc
 	abc bbc
 	^bc abc bbc
-	!bc -bc ^bc cbc
+	+bc ,bc -bc
+	-bc 0bc 1bc
 ---
 name: glob-range-2
 description:
@@ -2293,7 +2458,7 @@ description:
 # breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition)
 # breaks on Cygwin 1.7 (files are now UTF-16 or something)
 # breaks on QNX 6.4.1 (says RT)
-category: !os:cygwin,!os:darwin,!os:msys,!os:nto,!os:os2
+category: !os:cygwin,!os:darwin,!os:msys,!os:nto,!os:os2,!os:os390
 need-pass: no
 file-setup: file 644 "aÂc"
 stdin:
@@ -2320,10 +2485,32 @@ file-setup: file 644 "cbc"
 file-setup: file 644 "dbc"
 file-setup: file 644 "ebc"
 file-setup: file 644 "-bc"
+file-setup: file 644 "@bc"
 stdin:
 	echo [a-c-e]*
+	echo [a--@]*
 expected-stdout:
 	-bc abc bbc cbc ebc
+	@bc
+---
+name: glob-word-1
+description:
+	Check BSD word boundary matches
+stdin:
+	t() { [[ $1 = *[[:\<:]]bar[[:\>:]]* ]]; echo =$?; }
+	t 'foo bar baz'
+	t 'foobar baz'
+	t 'foo barbaz'
+	t 'bar'
+	t '_bar'
+	t 'bar_'
+expected-stdout:
+	=0
+	=1
+	=1
+	=0
+	=1
+	=1
 ---
 name: glob-trim-1
 description:
@@ -2580,6 +2767,7 @@ expected-stdout:
 name: heredoc-10
 description:
 	Check direct here document assignment
+category: !shell:ebcdic-yes
 stdin:
 	x=u
 	va=<<EOF
@@ -2632,6 +2820,62 @@ expected-stdout:
 	} |
 	| vapp1^vapp2^ |
 ---
+name: heredoc-10-ebcdic
+description:
+	Check direct here document assignment
+category: !shell:ebcdic-no
+stdin:
+	x=u
+	va=<<EOF
+	=a $x \x7C=
+	EOF
+	vb=<<'EOF'
+	=b $x \x7C=
+	EOF
+	function foo {
+		vc=<<-EOF
+			=c $x \x7C=
+		EOF
+	}
+	fnd=$(typeset -f foo)
+	print -r -- "$fnd"
+	function foo {
+		echo blub
+	}
+	foo
+	eval "$fnd"
+	foo
+	# rather nonsensical, but…
+	vd=<<<"=d $x \x7C="
+	ve=<<<'=e $x \x7C='
+	vf=<<<$'=f $x \x7C='
+	# now check
+	print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} ve={$ve} vf={$vf} |"
+	# check append
+	v=<<-EOF
+		vapp1
+	EOF
+	v+=<<-EOF
+		vapp2
+	EOF
+	print -r -- "| ${v//$'\n'/^} |"
+expected-stdout:
+	function foo {
+		vc=<<-EOF 
+	=c $x \x7C=
+	EOF
+	
+	} 
+	blub
+	| va={=a u \x7C=
+	} vb={=b $x \x7C=
+	} vc={=c u \x7C=
+	} vd={=d u \x7C=
+	} ve={=e $x \x7C=
+	} vf={=f $x @=
+	} |
+	| vapp1^vapp2^ |
+---
 name: heredoc-11
 description:
 	Check here documents with no or empty delimiter
@@ -2776,7 +3020,7 @@ stdin:
 expected-stdout:
 	0
 	bar() {
-		foo 4<<-a <<-b 5<<-c 
+		\foo 4<<-a <<-b 5<<-c 
 	four
 	a
 	zero
@@ -4655,6 +4899,23 @@ expected-stdout:
 	8 ok
 	<9> <ab> <a b> .
 ---
+name: IFS-subst-10
+description:
+	Scalar context in ${var=$subst}
+stdin:
+	showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+	set -- one "two three" four
+	unset -v var
+	save_IFS=$IFS
+	IFS=
+	set -- ${var=$*}
+	IFS=$save_IFS
+	echo "var=$var"
+	showargs "$@"
+expected-stdout:
+	var=onetwo threefour
+	<onetwo threefour> .
+---
 name: IFS-arith-1
 description:
 	http://austingroupbugs.net/view.php?id=832
@@ -4892,7 +5153,7 @@ name: integer-base-check-flat
 description:
 	Check behaviour does not match POSuX (except if set -o posix),
 	because a not type-safe scripting language has *no* business
-	interpreting the string "010" as octal numer eight (dangerous).
+	interpreting the string "010" as octal number eight (dangerous).
 stdin:
 	echo 1 "$("$__progname" -c 'echo :$((10))/$((010)),$((0x10)):')" .
 	echo 2 "$("$__progname" -o posix -c 'echo :$((10))/$((010)),$((0x10)):')" .
@@ -4902,18 +5163,34 @@ expected-stdout:
 	2 :10/8,16: .
 	3 :10/10,16: .
 ---
-name: integer-base-check-numeric-from
+name: integer-base-check-numeric-from-1
+description:
+	Check behaviour for base one
+category: !shell:ebcdic-yes
+stdin:
+	echo 1:$((1#1))0.
+expected-stdout:
+	1:490.
+---
+name: integer-base-check-numeric-from-1-ebcdic
 description:
-	Check behaviour for base one to 36, and that 37 degrades to 10
+	Check behaviour for base one
+category: !shell:ebcdic-no
 stdin:
 	echo 1:$((1#1))0.
+expected-stdout:
+	1:2410.
+---
+name: integer-base-check-numeric-from-2
+description:
+	Check behaviour for base two to 36, and that 37 degrades to 10
+stdin:
 	i=1
 	while (( ++i <= 37 )); do
 		eval 'echo '$i':$(('$i'#10)).'
 	done
 	echo 37:$($__progname -c 'echo $((37#10))').$?:
 expected-stdout:
-	1:490.
 	2:2.
 	3:3.
 	4:4.
@@ -4952,18 +5229,41 @@ expected-stdout:
 	37:10.
 	37:10.0:
 ---
-name: integer-base-check-numeric-to
+name: integer-base-check-numeric-to-1
 description:
-	Check behaviour for base one to 36, and that 37 degrades to 10
+	Check behaviour for base one
+category: !shell:ebcdic-yes
 stdin:
-	i=0
+	i=1
+	typeset -Uui$i x=0x40
+	eval "typeset -i10 y=$x"
+	print $i:$x.$y.
+expected-stdout:
+	1:1#@.64.
+---
+name: integer-base-check-numeric-to-1-ebcdic
+description:
+	Check behaviour for base one
+category: !shell:ebcdic-no
+stdin:
+	i=1
+	typeset -Uui$i x=0x7C
+	eval "typeset -i10 y=$x"
+	print $i:$x.$y.
+expected-stdout:
+	1:1#@.124.
+---
+name: integer-base-check-numeric-to-2
+description:
+	Check behaviour for base two to 36, and that 37 degrades to 10
+stdin:
+	i=1
 	while (( ++i <= 37 )); do
 		typeset -Uui$i x=0x40
 		eval "typeset -i10 y=$x"
 		print $i:$x.$y.
 	done
 expected-stdout:
-	1:1#@.64.
 	2:2#1000000.64.
 	3:3#2101.64.
 	4:4#1000.64.
@@ -5149,6 +5449,24 @@ expected-stdout:
 	line <6>
 expected-exit: 1
 ---
+name: lineno-eval-alias
+description:
+	Check if LINENO is trapped in eval and aliases
+stdin:
+	${ZSH_VERSION+false} || emulate sh; echo $LINENO
+	echo $LINENO
+	eval '	echo $LINENO
+		echo $LINENO
+		echo $LINENO'
+	echo $LINENO
+expected-stdout:
+	1
+	2
+	3
+	3
+	3
+	6
+---
 name: unknown-trap
 description:
 	Ensure unknown traps are not a syntax error
@@ -6270,7 +6588,7 @@ name: regression-62
 description:
 	Check if test -nt/-ot succeeds if second(first) file is missing.
 stdin:
-	touch a
+	:>a
 	test a -nt b && echo nt OK || echo nt BAD
 	test b -ot a && echo ot OK || echo ot BAD
 expected-stdout:
@@ -6588,6 +6906,13 @@ expected-exit: e != 0
 expected-stderr-pattern:
 	/read[ -]?only/
 ---
+name: readonly-5
+description:
+	Ensure readonly is idempotent
+stdin:
+	readonly x=1
+	readonly x
+---
 name: syntax-1
 description:
 	Check that lone ampersand is a syntax error
@@ -6721,6 +7046,48 @@ expected-stdout:
 	y1-
 	x2-3- z1-
 ---
+name: exec-modern-korn-shell
+description:
+	Check that exec can execute any command that makes it
+	through syntax and parser
+stdin:
+	print '#!'"$__progname"'\necho tf' >lq
+	chmod +x lq
+	PATH=$PWD
+	exec 2>&1
+	foo() { print two; }
+	print =1
+	(exec print one)
+	print =2
+	(exec foo)
+	print =3
+	(exec ls)
+	print =4
+	(exec lq)
+expected-stdout-pattern:
+	/=1\none\n=2\ntwo\n=3\n.*: ls: not found\n=4\ntf\n/
+---
+name: exec-ksh88
+description:
+	Check that exec only executes after a PATH search
+arguments: !-o!posix!
+stdin:
+	print '#!'"$__progname"'\necho tf' >lq
+	chmod +x lq
+	PATH=$PWD
+	exec 2>&1
+	foo() { print two; }
+	print =1
+	(exec print one)
+	print =2
+	(exec foo)
+	print =3
+	(exec ls)
+	print =4
+	(exec lq)
+expected-stdout-pattern:
+	/=1\n.*: print: not found\n=2\n.*: foo: not found\n=3\n.*: ls: not found\n=4\ntf\n/
+---
 name: xxx-what-do-you-call-this-1
 stdin:
 	echo "${foo:-"a"}*"
@@ -7002,6 +7369,11 @@ description:
 	Check tilde expansion works
 env-setup: !HOME=/sweet!
 stdin:
+	:>'c=a'
+	typeset c=[ab]
+	:>'d=a'
+	x=typeset; $x d=[ab]
+	echo "<$c>" "<$d>"
 	wd=$PWD
 	cd /
 	plus=$(print -r -- ~+)
@@ -7011,10 +7383,106 @@ stdin:
 	[[ $minus = "$wd" ]]; echo two $? .
 	[[ $nix = /sweet ]]; echo nix $? .
 expected-stdout:
+	<[ab]> <a>
 	one 0 .
 	two 0 .
 	nix 0 .
 ---
+name: tilde-expand-3
+description:
+	Check mostly Austin 351 stuff
+stdin:
+	showargs() { for s_arg in "$@"; do echo -n "<$s_arg> "; done; echo .; }
+	set "1 b=2" "3 d=4"
+	export a=$1 \c=$2
+	showargs 1 "$a" "$b" "$c" "$d"
+	unset a b c d
+	HOME=/tmp
+	export \a=~ b=~
+	command export c=~
+	builtin export d=~
+	\\builtin export e=~
+	showargs 2 "$a" "$b" "$c" "$d" "$e" ksh
+	unset a b c d e
+	set -o posix
+	export \a=~ b=~
+	command export c=~
+	builtin export d=~
+	\\builtin export e=~
+	showargs 3 "$a" "$b" "$c" "$d" "$e" posix
+	unset a b c d e
+	set +o posix
+	export a=$1
+	showargs 4 "$a" "$b" ksh
+	unset a b
+	showargs 5 a=$1 ksh
+	export \a=$1
+	showargs 6 "$a" "$b" ksh
+	unset a b
+	set -o posix
+	export a=$1
+	showargs 7 "$a" "$b" posix
+	unset a b
+	showargs 8 a=$1 posix
+	export \a=$1
+	showargs 9 "$a" "$b" posix
+	unset a b
+	set +o posix
+	command echo 10 ksh a=~
+	command command export a=~
+	showargs 11 "$a"
+	unset a
+	set -o posix
+	command echo 12 posix a=~
+	command command export a=~
+	showargs 13 "$a"
+	unset a
+	# unspecified whether /tmp or ~
+	var=export; command $var a=~
+	showargs 14 "$a"
+	echo 'echo "<$foo>"' >bar
+	"$__progname" bar
+	var=foo
+	export $var=1
+	"$__progname" bar
+	export $var=~
+	"$__progname" bar
+	# unspecified
+	command -- export a=~
+	showargs 18 "$a"
+	set -A bla
+	typeset bla[1]=~:~
+	global gbl=~ g2=$1
+	local lcl=~ l2=$1
+	readonly ro=~ r2=$1
+	showargs 19 "${bla[1]}" a=~ "$gbl" "$lcl" "$ro" "$g2" "$l2" "$r2"
+	set +o posix
+	echo "20 some arbitrary stuff "=~
+	set -o posix
+	echo "21 some arbitrary stuff "=~
+expected-stdout:
+	<1> <1 b=2> <> <3> <4> .
+	<2> </tmp> </tmp> </tmp> </tmp> </tmp> <ksh> .
+	<3> <~> </tmp> </tmp> <~> </tmp> <posix> .
+	<4> <1 b=2> <> <ksh> .
+	<5> <a=1> <b=2> <ksh> .
+	<6> <1> <2> <ksh> .
+	<7> <1 b=2> <> <posix> .
+	<8> <a=1> <b=2> <posix> .
+	<9> <1> <2> <posix> .
+	10 ksh a=/tmp
+	<11> </tmp> .
+	12 posix a=~
+	<13> </tmp> .
+	<14> <~> .
+	<>
+	<1>
+	<~>
+	<18> <~> .
+	<19> </tmp:/tmp> <a=~> </tmp> </tmp> </tmp> <1 b=2> <1 b=2> <1 b=2> .
+	20 some arbitrary stuff =/tmp
+	21 some arbitrary stuff =~
+---
 name: exit-err-1
 description:
 	Check some "exit on error" conditions
@@ -7387,7 +7855,7 @@ expected-stdout:
 	After error 2
 	Exit trap
 expected-stderr-pattern:
-	/syntax error: 'newline' unexpected/
+	/syntax error: unexpected 'newline'/
 ---
 name: test-stlt-1
 description:
@@ -7497,6 +7965,58 @@ expected-stdout:
 	2- 1 1 1 =
 	3- 0 0 0 =
 ---
+name: test-varset-1
+description:
+	Test the test -v operator
+stdin:
+	[[ -v a ]]
+	rv=$?; echo $((++i)) $rv
+	a=
+	[[ -v a ]]
+	rv=$?; echo $((++i)) $rv
+	unset a
+	[[ -v a ]]
+	rv=$?; echo $((++i)) $rv
+	a=x
+	[[ -v a ]]
+	rv=$?; echo $((++i)) $rv
+	nameref b=a
+	[[ -v b ]]
+	rv=$?; echo $((++i)) $rv
+	unset a
+	[[ -v b ]]
+	rv=$?; echo $((++i)) $rv
+	x[1]=y
+	[[ -v x ]]
+	rv=$?; echo $((++i)) $rv
+	[[ -v x[0] ]]
+	rv=$?; echo $((++i)) $rv
+	[[ -v x[1] ]]
+	rv=$?; echo $((++i)) $rv
+	[[ -v x[2] ]]
+	rv=$?; echo $((++i)) $rv
+expected-stdout:
+	1 1
+	2 0
+	3 1
+	4 0
+	5 0
+	6 1
+	7 1
+	8 1
+	9 0
+	10 1
+---
+name: test-varset-2
+description:
+	test -v works only on scalars
+stdin:
+	[[ -v x[*] ]]
+	echo ok
+expected-exit: e != 0
+expected-stderr-pattern:
+	/unexpected '\*'/
+---
 name: test-stnze-1
 description:
 	Check that the short form [ $x ] works
@@ -7847,11 +8367,11 @@ expected-stderr-pattern:
 ---
 name: typeset-1
 description:
-	Check that global does what typeset is supposed to do
+	Check that typeset -g works correctly
 stdin:
 	set -A arrfoo 65
 	foo() {
-		global -Uui16 arrfoo[*]
+		typeset -g -Uui16 arrfoo[*]
 	}
 	echo before ${arrfoo[0]} .
 	foo
@@ -7861,7 +8381,7 @@ stdin:
 		echo inside before ${arrbar[0]} .
 		arrbar[0]=97
 		echo inside changed ${arrbar[0]} .
-		global -Uui16 arrbar[*]
+		typeset -g -Uui16 arrbar[*]
 		echo inside typeset ${arrbar[0]} .
 		arrbar[0]=48
 		echo inside changed ${arrbar[0]} .
@@ -7879,6 +8399,24 @@ expected-stdout:
 	inside changed 16#30 .
 	after 16#30 .
 ---
+name: typeset-2
+description:
+	Check that typeset -p on arrays works correctly
+stdin:
+	set -A x -- a b c
+	echo =
+	typeset -p x
+	echo =
+	typeset -p x[1]
+expected-stdout:
+	=
+	set -A x
+	typeset x[0]=a
+	typeset x[1]=b
+	typeset x[2]=c
+	=
+	typeset x[1]=b
+---
 name: typeset-padding-1
 description:
 	Check if left/right justification works as per TFM
@@ -7912,7 +8450,7 @@ description:
 	multibyte character of the shell input (with -c, from standard
 	input, as file, or as eval argument), but nowhere else
 # breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition)
-category: !os:darwin
+category: !os:darwin,!shell:ebcdic-yes
 stdin:
 	mkdir foo
 	print '#!/bin/sh\necho ohne' >foo/fnord
@@ -7989,25 +8527,10 @@ expected-stdout:
 expected-stderr-pattern:
 	/(Unrecognized character .... ignored at \..t4 line 1)*/
 ---
-name: utf8opt-1a
-description:
-	Check that the utf8-mode flag is not set at non-interactive startup
-category: !os:hpux
-env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8!
-stdin:
-	if [[ $- = *U* ]]; then
-		echo is set
-	else
-		echo is not set
-	fi
-expected-stdout:
-	is not set
----
-name: utf8opt-1b
+name: utf8opt-1
 description:
 	Check that the utf8-mode flag is not set at non-interactive startup
-category: os:hpux
-env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8!
+env-setup: !PS1=!PS2=!LC_CTYPE=@utflocale@!
 stdin:
 	if [[ $- = *U* ]]; then
 		echo is set
@@ -8017,37 +8540,15 @@ stdin:
 expected-stdout:
 	is not set
 ---
-name: utf8opt-2a
+name: utf8opt-2
 description:
 	Check that the utf8-mode flag is set at interactive startup.
-	-DMKSH_ASSUME_UTF8=0 => expected failure, please ignore
-	-DMKSH_ASSUME_UTF8=1 => not expected, please investigate
-	-UMKSH_ASSUME_UTF8 => not expected, but if your OS is old,
-	 try passing HAVE_SETLOCALE_CTYPE=0 to Build.sh
+	If your OS is old, try passing HAVE_SETLOCALE_CTYPE=0 to Build.sh
 need-pass: no
-category: !os:hpux,!os:msys
+category: !noutf8
 need-ctty: yes
 arguments: !-i!
-env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8!
-stdin:
-	if [[ $- = *U* ]]; then
-		echo is set
-	else
-		echo is not set
-	fi
-expected-stdout:
-	is set
-expected-stderr-pattern:
-	/(# )*/
----
-name: utf8opt-2b
-description:
-	Check that the utf8-mode flag is set at interactive startup
-	Expected failure if -DMKSH_ASSUME_UTF8=0
-category: os:hpux
-need-ctty: yes
-arguments: !-i!
-env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8!
+env-setup: !PS1=!PS2=!LC_CTYPE=@utflocale@!
 stdin:
 	if [[ $- = *U* ]]; then
 		echo is set
@@ -8117,17 +8618,17 @@ stdin:
 	alias
 	typeset -f
 expected-stdout:
-	autoload='\typeset -fu'
-	functions='\typeset -f'
-	hash='\builtin alias -t'
-	history='\builtin fc -l'
-	integer='\typeset -i'
-	local='\typeset'
-	login='\exec login'
-	nameref='\typeset -n'
+	autoload='\\builtin typeset -fu'
+	functions='\\builtin typeset -f'
+	hash='\\builtin alias -t'
+	history='\\builtin fc -l'
+	integer='\\builtin typeset -i'
+	local='\\builtin typeset'
+	login='\\builtin exec login'
+	nameref='\\builtin typeset -n'
 	nohup='nohup '
-	r='\builtin fc -e -'
-	type='\builtin whence -v'
+	r='\\builtin fc -e -'
+	type='\\builtin whence -v'
 ---
 name: aliases-2b
 description:
@@ -8137,17 +8638,17 @@ stdin:
 	alias
 	typeset -f
 expected-stdout:
-	autoload='\typeset -fu'
-	functions='\typeset -f'
-	hash='\builtin alias -t'
-	history='\builtin fc -l'
-	integer='\typeset -i'
-	local='\typeset'
-	login='\exec login'
-	nameref='\typeset -n'
+	autoload='\\builtin typeset -fu'
+	functions='\\builtin typeset -f'
+	hash='\\builtin alias -t'
+	history='\\builtin fc -l'
+	integer='\\builtin typeset -i'
+	local='\\builtin typeset'
+	login='\\builtin exec login'
+	nameref='\\builtin typeset -n'
 	nohup='nohup '
-	r='\builtin fc -e -'
-	type='\builtin whence -v'
+	r='\\builtin fc -e -'
+	type='\\builtin whence -v'
 ---
 name: aliases-3b
 description:
@@ -8157,17 +8658,17 @@ stdin:
 	./sh -c 'alias; typeset -f'
 	rm -f sh
 expected-stdout:
-	autoload='\typeset -fu'
-	functions='\typeset -f'
-	hash='\builtin alias -t'
-	history='\builtin fc -l'
-	integer='\typeset -i'
-	local='\typeset'
-	login='\exec login'
-	nameref='\typeset -n'
+	autoload='\\builtin typeset -fu'
+	functions='\\builtin typeset -f'
+	hash='\\builtin alias -t'
+	history='\\builtin fc -l'
+	integer='\\builtin typeset -i'
+	local='\\builtin typeset'
+	login='\\builtin exec login'
+	nameref='\\builtin typeset -n'
 	nohup='nohup '
-	r='\builtin fc -e -'
-	type='\builtin whence -v'
+	r='\\builtin fc -e -'
+	type='\\builtin whence -v'
 ---
 name: aliases-cmdline
 description:
@@ -8224,8 +8725,8 @@ stdin:
 	:|| local() { :; }
 	alias local
 expected-stdout:
-	local='\typeset'
-	local='\typeset'
+	local='\\builtin typeset'
+	local='\\builtin typeset'
 ---
 name: arrays-1
 description:
@@ -8635,21 +9136,21 @@ expected-stdout:
 name: arrassign-fnc-global
 description:
 	Check locality of array access inside a function
-	with the mksh-specific global keyword
+	with the bash4/mksh/yash/zsh typeset -g keyword
 stdin:
 	function fn {
-		global x
+		typeset -g x
 		x+=(f)
 		echo ".fn:${x[0]}.${x[1]}.${x[2]}.${x[3]}:"
 	}
 	function rfn {
 		set -A y
-		global y
+		typeset -g y
 		y+=(f)
 		echo ".rfn:${y[0]}.${y[1]}.${y[2]}.${y[3]}:"
 	}
 	function fnr {
-		global z
+		typeset -g z
 		set -A z
 		z+=(f)
 		echo ".fnr:${z[0]}.${z[1]}.${z[2]}.${z[3]}:"
@@ -8787,21 +9288,21 @@ expected-stdout:
 name: strassign-fnc-global
 description:
 	Check locality of string access inside a function
-	with the mksh-specific global keyword
+	with the bash4/mksh/yash/zsh typeset -g keyword
 stdin:
 	function fn {
-		global x
+		typeset -g x
 		x+=f
 		echo ".fn:$x:"
 	}
 	function rfn {
 		y=
-		global y
+		typeset -g y
 		y+=f
 		echo ".rfn:$y:"
 	}
 	function fnr {
-		global z
+		typeset -g z
 		z=
 		z+=f
 		echo ".fnr:$z:"
@@ -9027,6 +9528,7 @@ expected-stdout:
 name: varexpand-special-hash
 description:
 	Check special ${var at x} expansion for x=hash
+category: !shell:ebcdic-yes
 stdin:
 	typeset -i8 foo=10
 	bar=baz
@@ -9035,9 +9537,22 @@ stdin:
 expected-stdout:
 	9B15FBFB CFBDD32B 00000000 .
 ---
+name: varexpand-special-hash-ebcdic
+description:
+	Check special ${var at x} expansion for x=hash
+category: !shell:ebcdic-no
+stdin:
+	typeset -i8 foo=10
+	bar=baz
+	unset baz
+	print ${foo@#} ${bar@#} ${baz@#} .
+expected-stdout:
+	016AE33D 9769C4AF 00000000 .
+---
 name: varexpand-special-quote
 description:
 	Check special ${var at Q} expansion for quoted strings
+category: !shell:faux-ebcdic
 stdin:
 	set +U
 	i=x
@@ -9057,6 +9572,29 @@ expected-stdout:
 	typeset v='a b'
 	typeset w=$'c\nd\240e\u20ACf'
 ---
+name: varexpand-special-quote-faux-EBCDIC
+description:
+	Check special ${var at Q} expansion for quoted strings
+category: shell:faux-ebcdic
+stdin:
+	set +U
+	i=x
+	j=a\ b
+	k=$'c
+	d\xA0''e€f'
+	print -r -- "<i=$i j=$j k=$k>"
+	s="u=${i at Q} v=${j at Q} w=${k at Q}"
+	print -r -- "s=\"$s\""
+	eval "$s"
+	typeset -p u v w
+expected-stdout:
+	<i=x j=a b k=c
+	d e€f>
+	s="u=x v='a b' w=$'c\nd e\u20ACf'"
+	typeset u=x
+	typeset v='a b'
+	typeset w=$'c\nd e\u20ACf'
+---
 name: varexpand-null-1
 description:
 	Ensure empty strings expand emptily
@@ -9165,7 +9703,7 @@ stdin:
 			while (( i < ${#line[*]} )); do
 				hv=${line[i++]}
 				if (( (pos & 15) == 0 )); then
-					(( pos )) && print "$dasc|"
+					(( pos )) && print -r -- "$dasc|"
 					print -n "${pos#16#}  "
 					dasc=' |'
 				fi
@@ -9182,7 +9720,7 @@ stdin:
 			print -n '   '
 			(( (pos++ & 15) == 7 )) && print -n -- '- '
 		done
-		(( hv == 2147483647 )) || print "$dasc|"
+		(( hv == 2147483647 )) || print -r -- "$dasc|"
 	}
 expected-stdout:
 	00000000  3C 64 E4 DB C3 9B E2 82 - AC C3 9B 40 3E 0A 3C 00  |<d.........@>.<.|
@@ -9221,6 +9759,7 @@ expected-stdout:
 name: print-crlf
 description:
 	Check that CR+LF is shown and read as-is
+category: shell:textmode-no
 stdin:
 	cat >foo <<-'EOF'
 		x='bar
@@ -9243,6 +9782,32 @@ expected-stdout:
 	{.5}
 	{<bar
}
 ---
+name: print-crlf-textmode
+description:
+	Check that CR+LF is treated as newline
+category: shell:textmode-yes
+stdin:
+	cat >foo <<-'EOF'
+		x='bar
+		' #
+		echo .${#x} #
+		if test x"$KSH_VERSION" = x""; then #
+			printf '<%s>' "$x" #
+		else #
+			print -nr -- "<$x>" #
+		fi #
+	EOF
+	echo "[$("$__progname" foo)]"
+	"$__progname" foo | while IFS= read -r line; do
+		print -r -- "{$line}"
+	done
+expected-stdout:
+	[.4
+	<bar
+	>]
+	{.4}
+	{<bar}
+---
 name: print-lf
 description:
 	Check that LF-only is shown and read as-is
@@ -9308,7 +9873,7 @@ stdin:
 			while [[ -n $line ]]; do
 				hv=1#${line::1}
 				if (( (pos & 15) == 0 )); then
-					(( pos )) && print "$dasc|"
+					(( pos )) && print -r -- "$dasc|"
 					print -n "${pos#16#}  "
 					dasc=' |'
 				fi
@@ -9326,7 +9891,7 @@ stdin:
 			print -n '   '
 			(( (pos++ & 15) == 7 )) && print -n -- '- '
 		done
-		(( hv == 2147483647 )) || print "$dasc|"
+		(( hv == 2147483647 )) || print -r -- "$dasc|"
 	}
 expected-stdout:
 	00000000  5C 20 5C 21 5C 22 5C 23 - 5C 24 5C 25 5C 26 5C 27  |\ \!\"\#\$\%\&\'|
@@ -9335,13 +9900,13 @@ expected-stdout:
 	00000030  20 5C 39 5C 3A 5C 3B 5C - 3C 5C 3D 5C 3E 5C 3F 5C  | \9\:\;\<\=\>\?\|
 	00000040  40 5C 41 5C 42 5C 43 5C - 44 1B 5C 46 5C 47 5C 48  |@\A\B\C\D.\F\G\H|
 	00000050  5C 49 5C 4A 5C 4B 5C 4C - 5C 4D 5C 4E 5C 4F 5C 50  |\I\J\K\L\M\N\O\P|
-	00000060  5C 51 5C 52 5C 53 5C 54 - 20 5C 56 5C 57 5C 58 5C  |\Q\R\S\T \V\W\X\|
-	00000070  59 5C 5A 5C 5B 5C 5C 5D - 5C 5E 5C 5F 5C 60 07 08  |Y\Z\[\]\^\_\`..|
-	00000080  20 20 5C 64 1B 0C 5C 67 - 5C 68 5C 69 5C 6A 5C 6B  |  \d..\g\h\i\j\k|
-	00000090  5C 6C 5C 6D 0A 5C 6F 5C - 70 20 5C 71 0D 5C 73 09  |\l\m.\o\p \q.\s.|
-	000000A0  0B 5C 77 5C 79 5C 7A 5C - 7B 5C 7C 5C 7D 5C 7E 20  |.\w\y\z\{\|\}\~ |
-	000000B0  E2 82 AC 64 20 EF BF BD - 20 12 33 20 78 20 53 20  |...d ... .3 x S |
-	000000C0  53 34 0A                -                          |S4.|
+	00000060  5C 51 5C 52 5C 53 5C 54 - 20 5C 55 5C 56 5C 57 5C  |\Q\R\S\T \U\V\W\|
+	00000070  58 5C 59 5C 5A 5C 5B 5C - 5C 5D 5C 5E 5C 5F 5C 60  |X\Y\Z\[\\]\^\_\`|
+	00000080  07 08 20 20 5C 64 1B 0C - 5C 67 5C 68 5C 69 5C 6A  |..  \d..\g\h\i\j|
+	00000090  5C 6B 5C 6C 5C 6D 0A 5C - 6F 5C 70 20 5C 71 0D 5C  |\k\l\m.\o\p \q.\|
+	000000A0  73 09 5C 75 0B 5C 77 5C - 78 5C 79 5C 7A 5C 7B 5C  |s.\u.\w\x\y\z\{\|
+	000000B0  7C 5C 7D 5C 7E 20 E2 82 - AC 64 20 EF BF BD 20 12  ||\}\~ ...d ... .|
+	000000C0  33 20 78 20 53 20 53 34 - 0A                       |3 x S S4.|
 ---
 name: dollar-doublequoted-strings
 description:
@@ -9370,7 +9935,7 @@ stdin:
 	    $'\J\K\L\M\N\O\P\Q\R\S\T\U1\V\W\X\Y\Z\[\\\]\^\_\`\a\b\d\e' \
 	    $'\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u1\v\w\x1\y\z\{\|\}\~ $x' \
 	    $'\u20acd' $'\U20acd' $'\x123' $'fn\x0rd' $'\0234' $'\234' \
-	    $'\2345' $'\ca' $'\c!' $'\c?' $'\c€' $'a\
+	    $'\2345' $'\ca' $'\c!' $'\c?' $'\c…' $'a\
 	b' | {
 		# integer-base-one-3As
 		typeset -Uui16 -Z11 pos=0
@@ -9383,7 +9948,7 @@ stdin:
 			while [[ -n $line ]]; do
 				hv=1#${line::1}
 				if (( (pos & 15) == 0 )); then
-					(( pos )) && print "$dasc|"
+					(( pos )) && print -r -- "$dasc|"
 					print -n "${pos#16#}  "
 					dasc=' |'
 				fi
@@ -9401,7 +9966,7 @@ stdin:
 			print -n '   '
 			(( (pos++ & 15) == 7 )) && print -n -- '- '
 		done
-		(( hv == 2147483647 )) || print "$dasc|"
+		(( hv == 2147483647 )) || print -r -- "$dasc|"
 	}
 expected-stdout:
 	00000000  20 21 22 23 24 25 26 27 - 28 29 2A 2B 2C 2D 2E 2F  | !"#$%&'()*+,-./|
@@ -9412,7 +9977,7 @@ expected-stdout:
 	00000050  68 69 6A 6B 6C 6D 0A 6F - 70 71 0D 73 09 01 0B 77  |hijklm.opq.s...w|
 	00000060  01 79 7A 7B 7C 7D 7E 20 - 24 78 0A E2 82 AC 64 0A  |.yz{|}~ $x....d.|
 	00000070  EF BF BD 0A C4 A3 0A 66 - 6E 0A 13 34 0A 9C 0A 9C  |.......fn..4....|
-	00000080  35 0A 01 0A 01 0A 7F 0A - 02 82 AC 0A 61 0A 62 0A  |5...........a.b.|
+	00000080  35 0A 01 0A 01 0A 7F 0A - 82 80 A6 0A 61 0A 62 0A  |5...........a.b.|
 ---
 name: dollar-quotes-in-heredocs-strings
 description:
@@ -9711,7 +10276,7 @@ stdin:
 			while [[ -n $line ]]; do
 				hv=1#${line::1}
 				if (( (pos & 15) == 0 )); then
-					(( pos )) && print "$dasc|"
+					(( pos )) && print -r -- "$dasc|"
 					print -n "${pos#16#}  "
 					dasc=' |'
 				fi
@@ -9729,7 +10294,7 @@ stdin:
 			print -n '   '
 			(( (pos++ & 15) == 7 )) && print -n -- '- '
 		done
-		(( hv == 2147483647 )) || print "$dasc|"
+		(( hv == 2147483647 )) || print -r -- "$dasc|"
 	}
 expected-stdout:
 	00000000  48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3  |Hello, World!\..|
@@ -9799,7 +10364,7 @@ stdin:
 					dasc=$dasc$dch
 					dch=
 				elif (( (pos & 7) == 0 )); then
-					(( pos )) && print "$dasc|"
+					(( pos )) && print -r -- "$dasc|"
 					print -n "${pos#16#}  "
 					dasc=' |'
 				fi
@@ -9814,7 +10379,7 @@ stdin:
 			print -n '     '
 			(( (pos++ & 7) == 3 )) && print -n -- '- '
 		done
-		(( hv == 2147483647 )) || print "$dasc|"
+		(( hv == 2147483647 )) || print -r -- "$dasc|"
 	}
 expected-stdout:
 	00000000  0048 0065 006C 006C - 006F 002C 0020 0057  |Hello, W|
@@ -9880,7 +10445,7 @@ stdin:
 			while (( i < ${#line[*]} )); do
 				hv=${line[i++]}
 				if (( (pos & 15) == 0 )); then
-					(( pos )) && print "$dasc|"
+					(( pos )) && print -r -- "$dasc|"
 					print -n "${pos#16#}  "
 					dasc=' |'
 				fi
@@ -9897,7 +10462,7 @@ stdin:
 			print -n '   '
 			(( (pos++ & 15) == 7 )) && print -n -- '- '
 		done
-		(( hv == 2147483647 )) || print "$dasc|"
+		(( hv == 2147483647 )) || print -r -- "$dasc|"
 	}
 expected-stdout:
 	00000000  48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3  |Hello, World!\..|
@@ -9963,7 +10528,7 @@ stdin:
 					dasc=$dasc$dch
 					dch=
 				elif (( (pos & 7) == 0 )); then
-					(( pos )) && print "$dasc|"
+					(( pos )) && print -r -- "$dasc|"
 					print -n "${pos#16#}  "
 					dasc=' |'
 				fi
@@ -9977,7 +10542,7 @@ stdin:
 			print -n '     '
 			(( (pos++ & 7) == 3 )) && print -n -- '- '
 		done
-		(( hv == 2147483647 )) || print "$dasc|"
+		(( hv == 2147483647 )) || print -r -- "$dasc|"
 	}
 expected-stdout:
 	00000000  0048 0065 006C 006C - 006F 002C 0020 0057  |Hello, W|
@@ -10043,6 +10608,7 @@ expected-stdout:
 name: integer-base-one-5A
 description:
 	Check to see that we’re NUL and Unicode safe
+category: !shell:ebcdic-yes
 stdin:
 	set +U
 	print 'a\0b\xfdz' >x
@@ -10053,6 +10619,20 @@ stdin:
 expected-stdout:
 	16#61 16#0 16#62 16#FD 16#7A .
 ---
+name: integer-base-one-5E
+description:
+	Check to see that we’re NUL and Unicode safe
+category: !shell:ebcdic-no
+stdin:
+	set +U
+	print 'a\0b\xfdz' >x
+	read -a y <x
+	set -U
+	typeset -Uui16 y
+	print ${y[*]} .
+expected-stdout:
+	16#81 16#0 16#82 16#FD 16#A9 .
+---
 name: integer-base-one-5W
 description:
 	Check to see that we’re NUL and Unicode safe
@@ -10068,8 +10648,15 @@ expected-stdout:
 ---
 name: ulimit-1
 description:
+	Check that ulimit as used in dot.mksh works or is stubbed
+stdin:
+	ulimit -c 0
+---
+name: ulimit-2
+description:
 	Check if we can use a specific syntax idiom for ulimit
-category: !os:syllable
+	XXX Haiku works, but only for -n and -V
+category: !os:haiku,!os:syllable
 stdin:
 	if ! x=$(ulimit -d) || [[ $x = unknown ]]; then
 		#echo expected to fail on this OS
@@ -10114,7 +10701,6 @@ name: bashiop-1
 description:
 	Check if GNU bash-like I/O redirection works
 	Part 1: this is also supported by GNU bash
-category: shell:legacy-no
 stdin:
 	exec 3>&1
 	function threeout {
@@ -10135,7 +10721,6 @@ name: bashiop-2a
 description:
 	Check if GNU bash-like I/O redirection works
 	Part 2: this is *not* supported by GNU bash
-category: shell:legacy-no
 stdin:
 	exec 3>&1
 	function threeout {
@@ -10156,7 +10741,6 @@ name: bashiop-2b
 description:
 	Check if GNU bash-like I/O redirection works
 	Part 2: this is *not* supported by GNU bash
-category: shell:legacy-no
 stdin:
 	exec 3>&1
 	function threeout {
@@ -10177,7 +10761,6 @@ name: bashiop-2c
 description:
 	Check if GNU bash-like I/O redirection works
 	Part 2: this is supported by GNU bash 4 only
-category: shell:legacy-no
 stdin:
 	echo mir >foo
 	set -o noclobber
@@ -10201,7 +10784,6 @@ name: bashiop-3a
 description:
 	Check if GNU bash-like I/O redirection fails correctly
 	Part 1: this is also supported by GNU bash
-category: shell:legacy-no
 stdin:
 	echo mir >foo
 	set -o noclobber
@@ -10223,7 +10805,6 @@ name: bashiop-3b
 description:
 	Check if GNU bash-like I/O redirection fails correctly
 	Part 2: this is *not* supported by GNU bash
-category: shell:legacy-no
 stdin:
 	echo mir >foo
 	set -o noclobber
@@ -10247,7 +10828,6 @@ description:
 	Check if GNU bash-like I/O redirection works
 	Part 4: this is also supported by GNU bash,
 	but failed in some mksh versions
-category: shell:legacy-no
 stdin:
 	exec 3>&1
 	function threeout {
@@ -10269,11 +10849,10 @@ expected-stdout:
 	ras
 	dwa
 ---
-name: bashiop-5-normal
+name: bashiop-5
 description:
 	Check if GNU bash-like I/O redirection is only supported
 	in !POSIX !sh mode as it breaks existing scripts' syntax
-category: shell:legacy-no
 stdin:
 	:>x; echo 1 "$("$__progname" -c 'echo foo>/dev/null&>x echo bar')" = "$(<x)" .
 	:>x; echo 2 "$("$__progname" -o posix -c 'echo foo>/dev/null&>x echo bar')" = "$(<x)" .
@@ -10283,20 +10862,6 @@ expected-stdout:
 	2  = bar .
 	3  = bar .
 ---
-name: bashiop-5-legacy
-description:
-	Check if GNU bash-like I/O redirection is not parsed
-	in lksh as it breaks existing scripts' syntax
-category: shell:legacy-yes
-stdin:
-	:>x; echo 1 "$("$__progname" -c 'echo foo>/dev/null&>x echo bar')" = "$(<x)" .
-	:>x; echo 2 "$("$__progname" -o posix -c 'echo foo>/dev/null&>x echo bar')" = "$(<x)" .
-	:>x; echo 3 "$("$__progname" -o sh -c 'echo foo>/dev/null&>x echo bar')" = "$(<x)" .
-expected-stdout:
-	1  = bar .
-	2  = bar .
-	3  = bar .
----
 name: oksh-eval
 description:
 	Check expansions.
@@ -10533,7 +11098,6 @@ description:
 	AT&T ksh93 does this still, which means we must keep it as well
 	XXX fails on some old Perl installations
 need-pass: no
-category: shell:legacy-no
 stdin:
 	cat >cld <<-EOF
 		#!$__perlname
@@ -10566,22 +11130,6 @@ stdin:
 expected-stdout:
 	Fowl
 ---
-name: fd-cloexec-3
-description:
-	Verify that file descriptors > 2 are not private for LEGACY KSH
-category: shell:legacy-yes
-stdin:
-	cat >cld <<-EOF
-		#!$__perlname
-		open(my \$fh, ">&", 9) or die "E: open \$!";
-		syswrite(\$fh, "Fowl\\n", 5) or die "E: write \$!";
-	EOF
-	chmod +x cld
-	exec 9>&1
-	./cld
-expected-stdout:
-	Fowl
----
 name: comsub-1a
 description:
 	COMSUB are now parsed recursively, so this works
@@ -10670,10 +11218,10 @@ expected-stdout:
 	x() {
 		case $1 in
 		(u)
-			echo x 
+			\echo x 
 			;|
 		(*)
-			echo $1 
+			\echo $1 
 			;;
 		esac 
 	} 
@@ -10681,20 +11229,36 @@ expected-stdout:
 name: comsub-5
 description:
 	Check COMSUB works with aliases (does not expand them twice)
+	and reentrancy safety
 stdin:
 	print '#!'"$__progname"'\nfor x in "$@"; do print -r -- "$x"; done' >pfn
 	chmod +x pfn
 	alias echo='echo a'
 	foo() {
+		echo moo
 		./pfn "$(echo foo)"
 	}
 	./pfn "$(echo b)"
+	typeset -f foo >x
+	cat x
+	foo
+	. ./x
 	typeset -f foo
+	foo
 expected-stdout:
 	a b
 	foo() {
-		./pfn "$(echo foo )" 
+		\echo a moo 
+		./pfn "$(\echo a foo )" 
+	} 
+	a moo
+	a foo
+	foo() {
+		\echo a moo 
+		./pfn "$(\echo a foo )" 
 	} 
+	a moo
+	a foo
 ---
 name: comsub-torture
 description:
@@ -10805,56 +11369,56 @@ expected-stdout:
 		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
 	}
 	inline_TCOM() {
-		vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" 
+		vara=1 varb="2  3" \cmd arg1 $arg2 "$arg3  4" 
 	} 
 	function comsub_TCOM { x=$(
 		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
 	); }
 	function comsub_TCOM {
-		x=$(vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" ) 
+		x=$(vara=1 varb="2  3" \cmd arg1 $arg2 "$arg3  4" ) 
 	} 
 	function reread_TCOM { x=$((
 		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4"
 	)|tr u x); }
 	function reread_TCOM {
-		x=$(( vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" ) | tr u x ) 
+		x=$( ( vara=1 varb="2  3" \cmd arg1 $arg2 "$arg3  4" ) | \tr u x ) 
 	} 
 	inline_TPAREN_TPIPE_TLIST() {
 		(echo $foo  |  tr -dc 0-9; echo)
 	}
 	inline_TPAREN_TPIPE_TLIST() {
-		( echo $foo | tr -dc 0-9 
-		  echo ) 
+		( \echo $foo | \tr -dc 0-9 
+		  \echo ) 
 	} 
 	function comsub_TPAREN_TPIPE_TLIST { x=$(
 		(echo $foo  |  tr -dc 0-9; echo)
 	); }
 	function comsub_TPAREN_TPIPE_TLIST {
-		x=$(( echo $foo | tr -dc 0-9 ; echo ) ) 
+		x=$( ( \echo $foo | \tr -dc 0-9 ; \echo ) ) 
 	} 
 	function reread_TPAREN_TPIPE_TLIST { x=$((
 		(echo $foo  |  tr -dc 0-9; echo)
 	)|tr u x); }
 	function reread_TPAREN_TPIPE_TLIST {
-		x=$(( ( echo $foo | tr -dc 0-9 ; echo ) ) | tr u x ) 
+		x=$( ( ( \echo $foo | \tr -dc 0-9 ; \echo ) ) | \tr u x ) 
 	} 
 	inline_TAND_TOR() {
 		cmd  &&  echo ja  ||  echo nein
 	}
 	inline_TAND_TOR() {
-		cmd && echo ja || echo nein 
+		\cmd && \echo ja || \echo nein 
 	} 
 	function comsub_TAND_TOR { x=$(
 		cmd  &&  echo ja  ||  echo nein
 	); }
 	function comsub_TAND_TOR {
-		x=$(cmd && echo ja || echo nein ) 
+		x=$(\cmd && \echo ja || \echo nein ) 
 	} 
 	function reread_TAND_TOR { x=$((
 		cmd  &&  echo ja  ||  echo nein
 	)|tr u x); }
 	function reread_TAND_TOR {
-		x=$(( cmd && echo ja || echo nein ) | tr u x ) 
+		x=$( ( \cmd && \echo ja || \echo nein ) | \tr u x ) 
 	} 
 	inline_TSELECT() {
 		select  file  in  *;  do  echo  "<$file>" ;  break ;  done
@@ -10862,21 +11426,21 @@ expected-stdout:
 	inline_TSELECT() {
 		select file in * 
 		do
-			echo "<$file>" 
-			break 
+			\echo "<$file>" 
+			\break 
 		done 
 	} 
 	function comsub_TSELECT { x=$(
 		select  file  in  *;  do  echo  "<$file>" ;  break ;  done
 	); }
 	function comsub_TSELECT {
-		x=$(select file in * ; do echo "<$file>" ; break ; done ) 
+		x=$(select file in * ; do \echo "<$file>" ; \break ; done ) 
 	} 
 	function reread_TSELECT { x=$((
 		select  file  in  *;  do  echo  "<$file>" ;  break ;  done
 	)|tr u x); }
 	function reread_TSELECT {
-		x=$(( select file in * ; do echo "<$file>" ; break ; done ) | tr u x ) 
+		x=$( ( select file in * ; do \echo "<$file>" ; \break ; done ) | \tr u x ) 
 	} 
 	inline_TFOR_TTIME() {
 		time  for  i  in  {1,2,3}  ;  do  echo  $i ;  done
@@ -10884,20 +11448,20 @@ expected-stdout:
 	inline_TFOR_TTIME() {
 		time for i in {1,2,3} 
 		do
-			echo $i 
+			\echo $i 
 		done 
 	} 
 	function comsub_TFOR_TTIME { x=$(
 		time  for  i  in  {1,2,3}  ;  do  echo  $i ;  done
 	); }
 	function comsub_TFOR_TTIME {
-		x=$(time for i in {1,2,3} ; do echo $i ; done ) 
+		x=$(time for i in {1,2,3} ; do \echo $i ; done ) 
 	} 
 	function reread_TFOR_TTIME { x=$((
 		time  for  i  in  {1,2,3}  ;  do  echo  $i ;  done
 	)|tr u x); }
 	function reread_TFOR_TTIME {
-		x=$(( time for i in {1,2,3} ; do echo $i ; done ) | tr u x ) 
+		x=$( ( time for i in {1,2,3} ; do \echo $i ; done ) | \tr u x ) 
 	} 
 	inline_TCASE() {
 		case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
@@ -10905,13 +11469,13 @@ expected-stdout:
 	inline_TCASE() {
 		case $foo in
 		(1)
-			echo eins 
+			\echo eins 
 			;&
 		(2)
-			echo zwei 
+			\echo zwei 
 			;|
 		(*)
-			echo kann net bis drei zählen 
+			\echo kann net bis drei zählen 
 			;;
 		esac 
 	} 
@@ -10919,13 +11483,13 @@ expected-stdout:
 		case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
 	); }
 	function comsub_TCASE {
-		x=$(case $foo in (1) echo eins  ;& (2) echo zwei  ;| (*) echo kann net bis drei zählen  ;; esac ) 
+		x=$(case $foo in (1) \echo eins  ;& (2) \echo zwei  ;| (*) \echo kann net bis drei zählen  ;; esac ) 
 	} 
 	function reread_TCASE { x=$((
 		case  $foo  in  1)  echo eins;& 2) echo zwei  ;| *) echo kann net bis drei zählen;;  esac
 	)|tr u x); }
 	function reread_TCASE {
-		x=$(( case $foo in (1) echo eins  ;& (2) echo zwei  ;| (*) echo kann net bis drei zählen  ;; esac ) | tr u x ) 
+		x=$( ( case $foo in (1) \echo eins  ;& (2) \echo zwei  ;| (*) \echo kann net bis drei zählen  ;; esac ) | \tr u x ) 
 	} 
 	inline_TIF_TBANG_TDBRACKET_TELIF() {
 		if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
@@ -10933,25 +11497,25 @@ expected-stdout:
 	inline_TIF_TBANG_TDBRACKET_TELIF() {
 		if ! [[ 1 = 1 ]] 
 		then
-			echo eins 
+			\echo eins 
 		elif [[ 1 = 2 ]] 
 		then
-			echo zwei 
+			\echo zwei 
 		else
-			echo drei 
+			\echo drei 
 		fi 
 	} 
 	function comsub_TIF_TBANG_TDBRACKET_TELIF { x=$(
 		if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
 	); }
 	function comsub_TIF_TBANG_TDBRACKET_TELIF {
-		x=$(if ! [[ 1 = 1 ]] ; then echo eins ; elif [[ 1 = 2 ]] ; then echo zwei ; else echo drei ; fi ) 
+		x=$(if ! [[ 1 = 1 ]] ; then \echo eins ; elif [[ 1 = 2 ]] ; then \echo zwei ; else \echo drei ; fi ) 
 	} 
 	function reread_TIF_TBANG_TDBRACKET_TELIF { x=$((
 		if  !  [[  1  =  1  ]]  ;  then  echo eins;  elif [[ 1 = 2 ]]; then echo zwei  ;else echo drei; fi
 	)|tr u x); }
 	function reread_TIF_TBANG_TDBRACKET_TELIF {
-		x=$(( if ! [[ 1 = 1 ]] ; then echo eins ; elif [[ 1 = 2 ]] ; then echo zwei ; else echo drei ; fi ) | tr u x ) 
+		x=$( ( if ! [[ 1 = 1 ]] ; then \echo eins ; elif [[ 1 = 2 ]] ; then \echo zwei ; else \echo drei ; fi ) | \tr u x ) 
 	} 
 	inline_TWHILE() {
 		i=1; while (( i < 10 )); do echo $i; let ++i; done
@@ -10959,24 +11523,24 @@ expected-stdout:
 	inline_TWHILE() {
 		i=1 
 		while {
-			      \let] " i < 10 " 
+			      \\builtin let " i < 10 " 
 		      } 
 		do
-			echo $i 
-			let ++i 
+			\echo $i 
+			\let ++i 
 		done 
 	} 
 	function comsub_TWHILE { x=$(
 		i=1; while (( i < 10 )); do echo $i; let ++i; done
 	); }
 	function comsub_TWHILE {
-		x=$(i=1 ; while { \let] " i < 10 " ; } ; do echo $i ; let ++i ; done ) 
+		x=$(i=1 ; while { \\builtin let " i < 10 " ; } ; do \echo $i ; \let ++i ; done ) 
 	} 
 	function reread_TWHILE { x=$((
 		i=1; while (( i < 10 )); do echo $i; let ++i; done
 	)|tr u x); }
 	function reread_TWHILE {
-		x=$(( i=1 ; while { \let] " i < 10 " ; } ; do echo $i ; let ++i ; done ) | tr u x ) 
+		x=$( ( i=1 ; while { \\builtin let " i < 10 " ; } ; do \echo $i ; \let ++i ; done ) | \tr u x ) 
 	} 
 	inline_TUNTIL() {
 		i=10; until  (( !--i )) ; do echo $i; done
@@ -10984,42 +11548,42 @@ expected-stdout:
 	inline_TUNTIL() {
 		i=10 
 		until {
-			      \let] " !--i " 
+			      \\builtin let " !--i " 
 		      } 
 		do
-			echo $i 
+			\echo $i 
 		done 
 	} 
 	function comsub_TUNTIL { x=$(
 		i=10; until  (( !--i )) ; do echo $i; done
 	); }
 	function comsub_TUNTIL {
-		x=$(i=10 ; until { \let] " !--i " ; } ; do echo $i ; done ) 
+		x=$(i=10 ; until { \\builtin let " !--i " ; } ; do \echo $i ; done ) 
 	} 
 	function reread_TUNTIL { x=$((
 		i=10; until  (( !--i )) ; do echo $i; done
 	)|tr u x); }
 	function reread_TUNTIL {
-		x=$(( i=10 ; until { \let] " !--i " ; } ; do echo $i ; done ) | tr u x ) 
+		x=$( ( i=10 ; until { \\builtin let " !--i " ; } ; do \echo $i ; done ) | \tr u x ) 
 	} 
 	inline_TCOPROC() {
 		cat  *  |&  ls
 	}
 	inline_TCOPROC() {
-		cat * |& 
-		ls 
+		\cat * |& 
+		\ls 
 	} 
 	function comsub_TCOPROC { x=$(
 		cat  *  |&  ls
 	); }
 	function comsub_TCOPROC {
-		x=$(cat * |&  ls ) 
+		x=$(\cat * |&  \ls ) 
 	} 
 	function reread_TCOPROC { x=$((
 		cat  *  |&  ls
 	)|tr u x); }
 	function reread_TCOPROC {
-		x=$(( cat * |&  ls ) | tr u x ) 
+		x=$( ( \cat * |&  \ls ) | \tr u x ) 
 	} 
 	inline_TFUNCT_TBRACE_TASYNC() {
 		function  korn  {  echo eins; echo zwei ;  }
@@ -11027,11 +11591,11 @@ expected-stdout:
 	}
 	inline_TFUNCT_TBRACE_TASYNC() {
 		function korn {
-			echo eins 
-			echo zwei 
+			\echo eins 
+			\echo zwei 
 		} 
 		bourne() {
-			logger * & 
+			\logger * & 
 		} 
 	} 
 	function comsub_TFUNCT_TBRACE_TASYNC { x=$(
@@ -11039,32 +11603,32 @@ expected-stdout:
 		bourne  ()  {  logger *  &  }
 	); }
 	function comsub_TFUNCT_TBRACE_TASYNC {
-		x=$(function korn { echo eins ; echo zwei ; } ; bourne() { logger * &  } ) 
+		x=$(function korn { \echo eins ; \echo zwei ; } ; bourne() { \logger * &  } ) 
 	} 
 	function reread_TFUNCT_TBRACE_TASYNC { x=$((
 		function  korn  {  echo eins; echo zwei ;  }
 		bourne  ()  {  logger *  &  }
 	)|tr u x); }
 	function reread_TFUNCT_TBRACE_TASYNC {
-		x=$(( function korn { echo eins ; echo zwei ; } ; bourne() { logger * &  } ) | tr u x ) 
+		x=$( ( function korn { \echo eins ; \echo zwei ; } ; bourne() { \logger * &  } ) | \tr u x ) 
 	} 
 	inline_IOREAD_IOCAT() {
 		tr  x  u  0<foo  >>bar
 	}
 	inline_IOREAD_IOCAT() {
-		tr x u <foo >>bar 
+		\tr x u <foo >>bar 
 	} 
 	function comsub_IOREAD_IOCAT { x=$(
 		tr  x  u  0<foo  >>bar
 	); }
 	function comsub_IOREAD_IOCAT {
-		x=$(tr x u <foo >>bar ) 
+		x=$(\tr x u <foo >>bar ) 
 	} 
 	function reread_IOREAD_IOCAT { x=$((
 		tr  x  u  0<foo  >>bar
 	)|tr u x); }
 	function reread_IOREAD_IOCAT {
-		x=$(( tr x u <foo >>bar ) | tr u x ) 
+		x=$( ( \tr x u <foo >>bar ) | \tr u x ) 
 	} 
 	inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() {
 		cat  >|bar  <<'EOFN'
@@ -11072,7 +11636,7 @@ expected-stdout:
 	EOFN
 	}
 	inline_IOWRITE_IOCLOB_IOHERE_noIOSKIP() {
-		cat >|bar <<"EOFN" 
+		\cat >|bar <<"EOFN" 
 		foo
 	EOFN
 	
@@ -11083,7 +11647,7 @@ expected-stdout:
 	EOFN
 	); }
 	function comsub_IOWRITE_IOCLOB_IOHERE_noIOSKIP {
-		x=$(cat >|bar <<"EOFN" 
+		x=$(\cat >|bar <<"EOFN" 
 		foo
 	EOFN
 	) 
@@ -11094,10 +11658,10 @@ expected-stdout:
 	EOFN
 	)|tr u x); }
 	function reread_IOWRITE_IOCLOB_IOHERE_noIOSKIP {
-		x=$(( cat >|bar <<"EOFN" 
+		x=$( ( \cat >|bar <<"EOFN" 
 		foo
 	EOFN
-	) | tr u x ) 
+	) | \tr u x ) 
 	} 
 	inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() {
 		cat  1>bar  <<-EOFI
@@ -11105,7 +11669,7 @@ expected-stdout:
 		EOFI
 	}
 	inline_IOWRITE_noIOCLOB_IOHERE_IOSKIP() {
-		cat >bar <<-EOFI 
+		\cat >bar <<-EOFI 
 	foo
 	EOFI
 	
@@ -11116,7 +11680,7 @@ expected-stdout:
 		EOFI
 	); }
 	function comsub_IOWRITE_noIOCLOB_IOHERE_IOSKIP {
-		x=$(cat >bar <<-EOFI 
+		x=$(\cat >bar <<-EOFI 
 	foo
 	EOFI
 	) 
@@ -11127,46 +11691,46 @@ expected-stdout:
 		EOFI
 	)|tr u x); }
 	function reread_IOWRITE_noIOCLOB_IOHERE_IOSKIP {
-		x=$(( cat >bar <<-EOFI 
+		x=$( ( \cat >bar <<-EOFI 
 	foo
 	EOFI
-	) | tr u x ) 
+	) | \tr u x ) 
 	} 
 	inline_IORDWR_IODUP() {
 		sh  1<>/dev/console  0<&1  2>&1
 	}
 	inline_IORDWR_IODUP() {
-		sh 1<>/dev/console <&1 2>&1 
+		\sh 1<>/dev/console <&1 2>&1 
 	} 
 	function comsub_IORDWR_IODUP { x=$(
 		sh  1<>/dev/console  0<&1  2>&1
 	); }
 	function comsub_IORDWR_IODUP {
-		x=$(sh 1<>/dev/console <&1 2>&1 ) 
+		x=$(\sh 1<>/dev/console <&1 2>&1 ) 
 	} 
 	function reread_IORDWR_IODUP { x=$((
 		sh  1<>/dev/console  0<&1  2>&1
 	)|tr u x); }
 	function reread_IORDWR_IODUP {
-		x=$(( sh 1<>/dev/console <&1 2>&1 ) | tr u x ) 
+		x=$( ( \sh 1<>/dev/console <&1 2>&1 ) | \tr u x ) 
 	} 
 	inline_COMSUB_EXPRSUB_FUNSUB_VALSUB() {
 		echo $(true) $((1+ 2)) ${  :;} ${| REPLY=x;}
 	}
 	inline_COMSUB_EXPRSUB_FUNSUB_VALSUB() {
-		echo $(true ) $((1+ 2)) ${ : ;} ${|REPLY=x ;} 
+		\echo $(\true ) $((1+ 2)) ${ \: ;} ${|REPLY=x ;} 
 	} 
 	function comsub_COMSUB_EXPRSUB_FUNSUB_VALSUB { x=$(
 		echo $(true) $((1+ 2)) ${  :;} ${| REPLY=x;}
 	); }
 	function comsub_COMSUB_EXPRSUB_FUNSUB_VALSUB {
-		x=$(echo $(true ) $((1+ 2)) ${ : ;} ${|REPLY=x ;} ) 
+		x=$(\echo $(\true ) $((1+ 2)) ${ \: ;} ${|REPLY=x ;} ) 
 	} 
 	function reread_COMSUB_EXPRSUB_FUNSUB_VALSUB { x=$((
 		echo $(true) $((1+ 2)) ${  :;} ${| REPLY=x;}
 	)|tr u x); }
 	function reread_COMSUB_EXPRSUB_FUNSUB_VALSUB {
-		x=$(( echo $(true ) $((1+ 2)) ${ : ;} ${|REPLY=x ;} ) | tr u x ) 
+		x=$( ( \echo $(\true ) $((1+ 2)) ${ \: ;} ${|REPLY=x ;} ) | \tr u x ) 
 	} 
 	inline_QCHAR_OQUOTE_CQUOTE() {
 		echo fo\ob\"a\`r\'b\$az
@@ -11174,9 +11738,9 @@ expected-stdout:
 		echo 'fo\ob\"a\`r'\''b\$az'
 	}
 	inline_QCHAR_OQUOTE_CQUOTE() {
-		echo fo\ob\"a\`r\'b\$az 
-		echo "fo\ob\"a\`r\'b\$az" 
-		echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" 
+		\echo fo\ob\"a\`r\'b\$az 
+		\echo "fo\ob\"a\`r\'b\$az" 
+		\echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" 
 	} 
 	function comsub_QCHAR_OQUOTE_CQUOTE { x=$(
 		echo fo\ob\"a\`r\'b\$az
@@ -11184,7 +11748,7 @@ expected-stdout:
 		echo 'fo\ob\"a\`r'\''b\$az'
 	); }
 	function comsub_QCHAR_OQUOTE_CQUOTE {
-		x=$(echo fo\ob\"a\`r\'b\$az ; echo "fo\ob\"a\`r\'b\$az" ; echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) 
+		x=$(\echo fo\ob\"a\`r\'b\$az ; \echo "fo\ob\"a\`r\'b\$az" ; \echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) 
 	} 
 	function reread_QCHAR_OQUOTE_CQUOTE { x=$((
 		echo fo\ob\"a\`r\'b\$az
@@ -11192,7 +11756,7 @@ expected-stdout:
 		echo 'fo\ob\"a\`r'\''b\$az'
 	)|tr u x); }
 	function reread_QCHAR_OQUOTE_CQUOTE {
-		x=$(( echo fo\ob\"a\`r\'b\$az ; echo "fo\ob\"a\`r\'b\$az" ; echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) | tr u x ) 
+		x=$( ( \echo fo\ob\"a\`r\'b\$az ; \echo "fo\ob\"a\`r\'b\$az" ; \echo "fo\\ob\\\"a\\\`r"\'"b\\\$az" ) | \tr u x ) 
 	} 
 	inline_OSUBST_CSUBST_OPAT_SPAT_CPAT() {
 		[[ ${foo#bl\(u\)b} = @(bar|baz) ]]
@@ -11210,7 +11774,7 @@ expected-stdout:
 		[[ ${foo#bl\(u\)b} = @(bar|baz) ]]
 	)|tr u x); }
 	function reread_OSUBST_CSUBST_OPAT_SPAT_CPAT {
-		x=$(( [[ ${foo#bl\(u\)b} = @(bar|baz) ]] ) | tr u x ) 
+		x=$( ( [[ ${foo#bl\(u\)b} = @(bar|baz) ]] ) | \tr u x ) 
 	} 
 	inline_heredoc_closed() {
 		x=$(cat <<EOFN
@@ -11218,11 +11782,11 @@ expected-stdout:
 	EOFN); echo $x
 	}
 	inline_heredoc_closed() {
-		x=$(cat <<EOFN 
+		x=$(\cat <<EOFN 
 		note there must be no space between EOFN and )
 	EOFN
 	) 
-		echo $x 
+		\echo $x 
 	} 
 	function comsub_heredoc_closed { x=$(
 		x=$(cat <<EOFN
@@ -11230,10 +11794,10 @@ expected-stdout:
 	EOFN); echo $x
 	); }
 	function comsub_heredoc_closed {
-		x=$(x=$(cat <<EOFN 
+		x=$(x=$(\cat <<EOFN 
 		note there must be no space between EOFN and )
 	EOFN
-	) ; echo $x ) 
+	) ; \echo $x ) 
 	} 
 	function reread_heredoc_closed { x=$((
 		x=$(cat <<EOFN
@@ -11241,10 +11805,10 @@ expected-stdout:
 	EOFN); echo $x
 	)|tr u x); }
 	function reread_heredoc_closed {
-		x=$(( x=$(cat <<EOFN 
+		x=$( ( x=$(\cat <<EOFN 
 		note there must be no space between EOFN and )
 	EOFN
-	) ; echo $x ) | tr u x ) 
+	) ; \echo $x ) | \tr u x ) 
 	} 
 	inline_heredoc_space() {
 		x=$(cat <<EOFN\ 
@@ -11252,11 +11816,11 @@ expected-stdout:
 	EOFN ); echo $x
 	}
 	inline_heredoc_space() {
-		x=$(cat <<EOFN\  
+		x=$(\cat <<EOFN\  
 		note the space between EOFN and ) is actually part of the here document marker
 	EOFN 
 	) 
-		echo $x 
+		\echo $x 
 	} 
 	function comsub_heredoc_space { x=$(
 		x=$(cat <<EOFN\ 
@@ -11264,10 +11828,10 @@ expected-stdout:
 	EOFN ); echo $x
 	); }
 	function comsub_heredoc_space {
-		x=$(x=$(cat <<EOFN\  
+		x=$(x=$(\cat <<EOFN\  
 		note the space between EOFN and ) is actually part of the here document marker
 	EOFN 
-	) ; echo $x ) 
+	) ; \echo $x ) 
 	} 
 	function reread_heredoc_space { x=$((
 		x=$(cat <<EOFN\ 
@@ -11275,10 +11839,10 @@ expected-stdout:
 	EOFN ); echo $x
 	)|tr u x); }
 	function reread_heredoc_space {
-		x=$(( x=$(cat <<EOFN\  
+		x=$( ( x=$(\cat <<EOFN\  
 		note the space between EOFN and ) is actually part of the here document marker
 	EOFN 
-	) ; echo $x ) | tr u x ) 
+	) ; \echo $x ) | \tr u x ) 
 	} 
 	inline_patch_motd() {
 		x=$(sysctl -n kern.version | sed 1q)
@@ -11297,8 +11861,8 @@ expected-stdout:
 		fi
 	}
 	inline_patch_motd() {
-		x=$(sysctl -n kern.version | sed 1q ) 
-		[[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+		x=$(\sysctl -n kern.version | \sed 1q ) 
+		[[ -s /etc/motd && "$([[ "$(\head -1 /etc/motd )" != $x ]] && \ed -s /etc/motd 2>&1 <<-EOF 
 	1,/^\$/d
 	0a
 	$x
@@ -11306,11 +11870,11 @@ expected-stdout:
 	.
 	wq
 	EOF
-	)" = @(?) ]] && rm -f /etc/motd 
+	)" = @(?) ]] && \rm -f /etc/motd 
 		if [[ ! -s /etc/motd ]] 
 		then
-			install -c -o root -g wheel -m 664 /dev/null /etc/motd 
-			print -- "$x\n" >/etc/motd 
+			\install -c -o root -g wheel -m 664 /dev/null /etc/motd 
+			\print -- "$x\n" >/etc/motd 
 		fi 
 	} 
 	function comsub_patch_motd { x=$(
@@ -11330,7 +11894,7 @@ expected-stdout:
 		fi
 	); }
 	function comsub_patch_motd {
-		x=$(x=$(sysctl -n kern.version | sed 1q ) ; [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+		x=$(x=$(\sysctl -n kern.version | \sed 1q ) ; [[ -s /etc/motd && "$([[ "$(\head -1 /etc/motd )" != $x ]] && \ed -s /etc/motd 2>&1 <<-EOF 
 	1,/^\$/d
 	0a
 	$x
@@ -11338,7 +11902,7 @@ expected-stdout:
 	.
 	wq
 	EOF
-	)" = @(?) ]] && rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then install -c -o root -g wheel -m 664 /dev/null /etc/motd ; print -- "$x\n" >/etc/motd ; fi ) 
+	)" = @(?) ]] && \rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then \install -c -o root -g wheel -m 664 /dev/null /etc/motd ; \print -- "$x\n" >/etc/motd ; fi ) 
 	} 
 	function reread_patch_motd { x=$((
 		x=$(sysctl -n kern.version | sed 1q)
@@ -11357,7 +11921,7 @@ expected-stdout:
 		fi
 	)|tr u x); }
 	function reread_patch_motd {
-		x=$(( x=$(sysctl -n kern.version | sed 1q ) ; [[ -s /etc/motd && "$([[ "$(head -1 /etc/motd )" != $x ]] && ed -s /etc/motd 2>&1 <<-EOF 
+		x=$( ( x=$(\sysctl -n kern.version | \sed 1q ) ; [[ -s /etc/motd && "$([[ "$(\head -1 /etc/motd )" != $x ]] && \ed -s /etc/motd 2>&1 <<-EOF 
 	1,/^\$/d
 	0a
 	$x
@@ -11365,7 +11929,7 @@ expected-stdout:
 	.
 	wq
 	EOF
-	)" = @(?) ]] && rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then install -c -o root -g wheel -m 664 /dev/null /etc/motd ; print -- "$x\n" >/etc/motd ; fi ) | tr u x ) 
+	)" = @(?) ]] && \rm -f /etc/motd ; if [[ ! -s /etc/motd ]] ; then \install -c -o root -g wheel -m 664 /dev/null /etc/motd ; \print -- "$x\n" >/etc/motd ; fi ) | \tr u x ) 
 	} 
 	inline_wdarrassign() {
 		case x in
@@ -11376,7 +11940,7 @@ expected-stdout:
 		case x in
 		(x)
 			a+=b 
-			\set -A c+ -- d e 
+			\\builtin set -A c+ -- d e 
 			;;
 		esac 
 	} 
@@ -11386,7 +11950,7 @@ expected-stdout:
 		esac
 	); }
 	function comsub_wdarrassign {
-		x=$(case x in (x) a+=b ; \set -A c+ -- d e  ;; esac ) 
+		x=$(case x in (x) a+=b ; \\builtin set -A c+ -- d e  ;; esac ) 
 	} 
 	function reread_wdarrassign { x=$((
 		case x in
@@ -11394,7 +11958,7 @@ expected-stdout:
 		esac
 	)|tr u x); }
 	function reread_wdarrassign {
-		x=$(( case x in (x) a+=b ; \set -A c+ -- d e  ;; esac ) | tr u x ) 
+		x=$( ( case x in (x) a+=b ; \\builtin set -A c+ -- d e  ;; esac ) | \tr u x ) 
 	} 
 ---
 name: comsub-torture-io
@@ -11461,56 +12025,56 @@ expected-stdout:
 		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4" >&3
 	}
 	inline_TCOM() {
-		vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" >&3 
+		vara=1 varb="2  3" \cmd arg1 $arg2 "$arg3  4" >&3 
 	} 
 	function comsub_TCOM { x=$(
 		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4" >&3
 	); }
 	function comsub_TCOM {
-		x=$(vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" >&3 ) 
+		x=$(vara=1 varb="2  3" \cmd arg1 $arg2 "$arg3  4" >&3 ) 
 	} 
 	function reread_TCOM { x=$((
 		vara=1  varb='2  3'  cmd  arg1  $arg2  "$arg3  4" >&3
 	)|tr u x); }
 	function reread_TCOM {
-		x=$(( vara=1 varb="2  3" cmd arg1 $arg2 "$arg3  4" >&3 ) | tr u x ) 
+		x=$( ( vara=1 varb="2  3" \cmd arg1 $arg2 "$arg3  4" >&3 ) | \tr u x ) 
 	} 
 	inline_TPAREN_TPIPE_TLIST() {
 		(echo $foo  |  tr -dc 0-9 >&3; echo >&3) >&3
 	}
 	inline_TPAREN_TPIPE_TLIST() {
-		( echo $foo | tr -dc 0-9 >&3 
-		  echo >&3 ) >&3 
+		( \echo $foo | \tr -dc 0-9 >&3 
+		  \echo >&3 ) >&3 
 	} 
 	function comsub_TPAREN_TPIPE_TLIST { x=$(
 		(echo $foo  |  tr -dc 0-9 >&3; echo >&3) >&3
 	); }
 	function comsub_TPAREN_TPIPE_TLIST {
-		x=$(( echo $foo | tr -dc 0-9 >&3 ; echo >&3 ) >&3 ) 
+		x=$( ( \echo $foo | \tr -dc 0-9 >&3 ; \echo >&3 ) >&3 ) 
 	} 
 	function reread_TPAREN_TPIPE_TLIST { x=$((
 		(echo $foo  |  tr -dc 0-9 >&3; echo >&3) >&3
 	)|tr u x); }
 	function reread_TPAREN_TPIPE_TLIST {
-		x=$(( ( echo $foo | tr -dc 0-9 >&3 ; echo >&3 ) >&3 ) | tr u x ) 
+		x=$( ( ( \echo $foo | \tr -dc 0-9 >&3 ; \echo >&3 ) >&3 ) | \tr u x ) 
 	} 
 	inline_TAND_TOR() {
 		cmd  >&3 &&  >&3 echo ja  ||  echo >&3 nein
 	}
 	inline_TAND_TOR() {
-		cmd >&3 && echo ja >&3 || echo nein >&3 
+		\cmd >&3 && \echo ja >&3 || \echo nein >&3 
 	} 
 	function comsub_TAND_TOR { x=$(
 		cmd  >&3 &&  >&3 echo ja  ||  echo >&3 nein
 	); }
 	function comsub_TAND_TOR {
-		x=$(cmd >&3 && echo ja >&3 || echo nein >&3 ) 
+		x=$(\cmd >&3 && \echo ja >&3 || \echo nein >&3 ) 
 	} 
 	function reread_TAND_TOR { x=$((
 		cmd  >&3 &&  >&3 echo ja  ||  echo >&3 nein
 	)|tr u x); }
 	function reread_TAND_TOR {
-		x=$(( cmd >&3 && echo ja >&3 || echo nein >&3 ) | tr u x ) 
+		x=$( ( \cmd >&3 && \echo ja >&3 || \echo nein >&3 ) | \tr u x ) 
 	} 
 	inline_TSELECT() {
 		select  file  in  *;  do  echo  "<$file>" ;  break >&3 ;  done >&3
@@ -11518,21 +12082,21 @@ expected-stdout:
 	inline_TSELECT() {
 		select file in * 
 		do
-			echo "<$file>" 
-			break >&3 
+			\echo "<$file>" 
+			\break >&3 
 		done >&3 
 	} 
 	function comsub_TSELECT { x=$(
 		select  file  in  *;  do  echo  "<$file>" ;  break >&3 ;  done >&3
 	); }
 	function comsub_TSELECT {
-		x=$(select file in * ; do echo "<$file>" ; break >&3 ; done >&3 ) 
+		x=$(select file in * ; do \echo "<$file>" ; \break >&3 ; done >&3 ) 
 	} 
 	function reread_TSELECT { x=$((
 		select  file  in  *;  do  echo  "<$file>" ;  break >&3 ;  done >&3
 	)|tr u x); }
 	function reread_TSELECT {
-		x=$(( select file in * ; do echo "<$file>" ; break >&3 ; done >&3 ) | tr u x ) 
+		x=$( ( select file in * ; do \echo "<$file>" ; \break >&3 ; done >&3 ) | \tr u x ) 
 	} 
 	inline_TFOR_TTIME() {
 		for  i  in  {1,2,3}  ;  do  time  >&3 echo  $i ;  done >&3
@@ -11540,20 +12104,20 @@ expected-stdout:
 	inline_TFOR_TTIME() {
 		for i in {1,2,3} 
 		do
-			time echo $i >&3 
+			time \echo $i >&3 
 		done >&3 
 	} 
 	function comsub_TFOR_TTIME { x=$(
 		for  i  in  {1,2,3}  ;  do  time  >&3 echo  $i ;  done >&3
 	); }
 	function comsub_TFOR_TTIME {
-		x=$(for i in {1,2,3} ; do time echo $i >&3 ; done >&3 ) 
+		x=$(for i in {1,2,3} ; do time \echo $i >&3 ; done >&3 ) 
 	} 
 	function reread_TFOR_TTIME { x=$((
 		for  i  in  {1,2,3}  ;  do  time  >&3 echo  $i ;  done >&3
 	)|tr u x); }
 	function reread_TFOR_TTIME {
-		x=$(( for i in {1,2,3} ; do time echo $i >&3 ; done >&3 ) | tr u x ) 
+		x=$( ( for i in {1,2,3} ; do time \echo $i >&3 ; done >&3 ) | \tr u x ) 
 	} 
 	inline_TCASE() {
 		case  $foo  in  1)  echo eins >&3;& 2) echo zwei >&3  ;| *) echo kann net bis drei zählen >&3;;  esac >&3
@@ -11561,13 +12125,13 @@ expected-stdout:
 	inline_TCASE() {
 		case $foo in
 		(1)
-			echo eins >&3 
+			\echo eins >&3 
 			;&
 		(2)
-			echo zwei >&3 
+			\echo zwei >&3 
 			;|
 		(*)
-			echo kann net bis drei zählen >&3 
+			\echo kann net bis drei zählen >&3 
 			;;
 		esac >&3 
 	} 
@@ -11575,13 +12139,13 @@ expected-stdout:
 		case  $foo  in  1)  echo eins >&3;& 2) echo zwei >&3  ;| *) echo kann net bis drei zählen >&3;;  esac >&3
 	); }
 	function comsub_TCASE {
-		x=$(case $foo in (1) echo eins >&3  ;& (2) echo zwei >&3  ;| (*) echo kann net bis drei zählen >&3  ;; esac >&3 ) 
+		x=$(case $foo in (1) \echo eins >&3  ;& (2) \echo zwei >&3  ;| (*) \echo kann net bis drei zählen >&3  ;; esac >&3 ) 
 	} 
 	function reread_TCASE { x=$((
 		case  $foo  in  1)  echo eins >&3;& 2) echo zwei >&3  ;| *) echo kann net bis drei zählen >&3;;  esac >&3
 	)|tr u x); }
 	function reread_TCASE {
-		x=$(( case $foo in (1) echo eins >&3  ;& (2) echo zwei >&3  ;| (*) echo kann net bis drei zählen >&3  ;; esac >&3 ) | tr u x ) 
+		x=$( ( case $foo in (1) \echo eins >&3  ;& (2) \echo zwei >&3  ;| (*) \echo kann net bis drei zählen >&3  ;; esac >&3 ) | \tr u x ) 
 	} 
 	inline_TIF_TBANG_TDBRACKET_TELIF() {
 		if  !  [[  1  =  1  ]]  >&3 ;  then  echo eins;  elif [[ 1 = 2 ]] >&3; then echo zwei  ;else echo drei; fi >&3
@@ -11589,25 +12153,25 @@ expected-stdout:
 	inline_TIF_TBANG_TDBRACKET_TELIF() {
 		if ! [[ 1 = 1 ]] >&3 
 		then
-			echo eins 
+			\echo eins 
 		elif [[ 1 = 2 ]] >&3 
 		then
-			echo zwei 
+			\echo zwei 
 		else
-			echo drei 
+			\echo drei 
 		fi >&3 
 	} 
 	function comsub_TIF_TBANG_TDBRACKET_TELIF { x=$(
 		if  !  [[  1  =  1  ]]  >&3 ;  then  echo eins;  elif [[ 1 = 2 ]] >&3; then echo zwei  ;else echo drei; fi >&3
 	); }
 	function comsub_TIF_TBANG_TDBRACKET_TELIF {
-		x=$(if ! [[ 1 = 1 ]] >&3 ; then echo eins ; elif [[ 1 = 2 ]] >&3 ; then echo zwei ; else echo drei ; fi >&3 ) 
+		x=$(if ! [[ 1 = 1 ]] >&3 ; then \echo eins ; elif [[ 1 = 2 ]] >&3 ; then \echo zwei ; else \echo drei ; fi >&3 ) 
 	} 
 	function reread_TIF_TBANG_TDBRACKET_TELIF { x=$((
 		if  !  [[  1  =  1  ]]  >&3 ;  then  echo eins;  elif [[ 1 = 2 ]] >&3; then echo zwei  ;else echo drei; fi >&3
 	)|tr u x); }
 	function reread_TIF_TBANG_TDBRACKET_TELIF {
-		x=$(( if ! [[ 1 = 1 ]] >&3 ; then echo eins ; elif [[ 1 = 2 ]] >&3 ; then echo zwei ; else echo drei ; fi >&3 ) | tr u x ) 
+		x=$( ( if ! [[ 1 = 1 ]] >&3 ; then \echo eins ; elif [[ 1 = 2 ]] >&3 ; then \echo zwei ; else \echo drei ; fi >&3 ) | \tr u x ) 
 	} 
 	inline_TWHILE() {
 		i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3
@@ -11615,24 +12179,24 @@ expected-stdout:
 	inline_TWHILE() {
 		i=1 
 		while {
-			      \let] " i < 10 " 
+			      \\builtin let " i < 10 " 
 		      } >&3 
 		do
-			echo $i 
-			let ++i 
+			\echo $i 
+			\let ++i 
 		done >&3 
 	} 
 	function comsub_TWHILE { x=$(
 		i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3
 	); }
 	function comsub_TWHILE {
-		x=$(i=1 ; while { \let] " i < 10 " ; } >&3 ; do echo $i ; let ++i ; done >&3 ) 
+		x=$(i=1 ; while { \\builtin let " i < 10 " ; } >&3 ; do \echo $i ; \let ++i ; done >&3 ) 
 	} 
 	function reread_TWHILE { x=$((
 		i=1; while (( i < 10 )) >&3; do echo $i; let ++i; done >&3
 	)|tr u x); }
 	function reread_TWHILE {
-		x=$(( i=1 ; while { \let] " i < 10 " ; } >&3 ; do echo $i ; let ++i ; done >&3 ) | tr u x ) 
+		x=$( ( i=1 ; while { \\builtin let " i < 10 " ; } >&3 ; do \echo $i ; \let ++i ; done >&3 ) | \tr u x ) 
 	} 
 	inline_TUNTIL() {
 		i=10; until  (( !--i )) >&3 ; do echo $i; done >&3
@@ -11640,42 +12204,42 @@ expected-stdout:
 	inline_TUNTIL() {
 		i=10 
 		until {
-			      \let] " !--i " 
+			      \\builtin let " !--i " 
 		      } >&3 
 		do
-			echo $i 
+			\echo $i 
 		done >&3 
 	} 
 	function comsub_TUNTIL { x=$(
 		i=10; until  (( !--i )) >&3 ; do echo $i; done >&3
 	); }
 	function comsub_TUNTIL {
-		x=$(i=10 ; until { \let] " !--i " ; } >&3 ; do echo $i ; done >&3 ) 
+		x=$(i=10 ; until { \\builtin let " !--i " ; } >&3 ; do \echo $i ; done >&3 ) 
 	} 
 	function reread_TUNTIL { x=$((
 		i=10; until  (( !--i )) >&3 ; do echo $i; done >&3
 	)|tr u x); }
 	function reread_TUNTIL {
-		x=$(( i=10 ; until { \let] " !--i " ; } >&3 ; do echo $i ; done >&3 ) | tr u x ) 
+		x=$( ( i=10 ; until { \\builtin let " !--i " ; } >&3 ; do \echo $i ; done >&3 ) | \tr u x ) 
 	} 
 	inline_TCOPROC() {
 		cat  *  >&3 |&  >&3 ls
 	}
 	inline_TCOPROC() {
-		cat * >&3 |& 
-		ls >&3 
+		\cat * >&3 |& 
+		\ls >&3 
 	} 
 	function comsub_TCOPROC { x=$(
 		cat  *  >&3 |&  >&3 ls
 	); }
 	function comsub_TCOPROC {
-		x=$(cat * >&3 |&  ls >&3 ) 
+		x=$(\cat * >&3 |&  \ls >&3 ) 
 	} 
 	function reread_TCOPROC { x=$((
 		cat  *  >&3 |&  >&3 ls
 	)|tr u x); }
 	function reread_TCOPROC {
-		x=$(( cat * >&3 |&  ls >&3 ) | tr u x ) 
+		x=$( ( \cat * >&3 |&  \ls >&3 ) | \tr u x ) 
 	} 
 	inline_TFUNCT_TBRACE_TASYNC() {
 		function  korn  {  echo eins; echo >&3 zwei ;  }
@@ -11683,11 +12247,11 @@ expected-stdout:
 	}
 	inline_TFUNCT_TBRACE_TASYNC() {
 		function korn {
-			echo eins 
-			echo zwei >&3 
+			\echo eins 
+			\echo zwei >&3 
 		} 
 		bourne() {
-			logger * >&3 & 
+			\logger * >&3 & 
 		} 
 	} 
 	function comsub_TFUNCT_TBRACE_TASYNC { x=$(
@@ -11695,32 +12259,32 @@ expected-stdout:
 		bourne  ()  {  logger *  >&3 &  }
 	); }
 	function comsub_TFUNCT_TBRACE_TASYNC {
-		x=$(function korn { echo eins ; echo zwei >&3 ; } ; bourne() { logger * >&3 &  } ) 
+		x=$(function korn { \echo eins ; \echo zwei >&3 ; } ; bourne() { \logger * >&3 &  } ) 
 	} 
 	function reread_TFUNCT_TBRACE_TASYNC { x=$((
 		function  korn  {  echo eins; echo >&3 zwei ;  }
 		bourne  ()  {  logger *  >&3 &  }
 	)|tr u x); }
 	function reread_TFUNCT_TBRACE_TASYNC {
-		x=$(( function korn { echo eins ; echo zwei >&3 ; } ; bourne() { logger * >&3 &  } ) | tr u x ) 
+		x=$( ( function korn { \echo eins ; \echo zwei >&3 ; } ; bourne() { \logger * >&3 &  } ) | \tr u x ) 
 	} 
 	inline_COMSUB_EXPRSUB() {
 		echo $(true >&3) $((1+ 2))
 	}
 	inline_COMSUB_EXPRSUB() {
-		echo $(true >&3 ) $((1+ 2)) 
+		\echo $(\true >&3 ) $((1+ 2)) 
 	} 
 	function comsub_COMSUB_EXPRSUB { x=$(
 		echo $(true >&3) $((1+ 2))
 	); }
 	function comsub_COMSUB_EXPRSUB {
-		x=$(echo $(true >&3 ) $((1+ 2)) ) 
+		x=$(\echo $(\true >&3 ) $((1+ 2)) ) 
 	} 
 	function reread_COMSUB_EXPRSUB { x=$((
 		echo $(true >&3) $((1+ 2))
 	)|tr u x); }
 	function reread_COMSUB_EXPRSUB {
-		x=$(( echo $(true >&3 ) $((1+ 2)) ) | tr u x ) 
+		x=$( ( \echo $(\true >&3 ) $((1+ 2)) ) | \tr u x ) 
 	} 
 ---
 name: funsub-1
@@ -12166,12 +12730,23 @@ expected-stdout:
 name: echo-test-1
 description:
 	Test what the echo builtin does (mksh)
+category: !shell:ebcdic-yes
 stdin:
 	echo -n 'foo\x40bar'
 	echo -e '\tbaz'
 expected-stdout:
 	foo at bar	baz
 ---
+name: echo-test-1-ebcdic
+description:
+	Test what the echo builtin does (mksh)
+category: !shell:ebcdic-no
+stdin:
+	echo -n 'foo\x7Cbar'
+	echo -e '\tbaz'
+expected-stdout:
+	foo at bar	baz
+---
 name: echo-test-2
 description:
 	Test what the echo builtin does (POSIX)
@@ -12202,7 +12777,7 @@ expected-stdout:
 name: echo-test-3-normal
 description:
 	Test what the echo builtin does, and test a compatibility flag.
-category: !mnbsdash
+category: !mnbsdash,!shell:ebcdic-yes
 stdin:
 	"$__progname" -c 'echo -n 1=\\x40$1; echo -e \\x2E' -- foo bar
 	"$__progname" -o posix -c 'echo -n 2=\\x40$1; echo -e \\x2E' -- foo bar
@@ -12212,6 +12787,19 @@ expected-stdout:
 	2=\x40foo-e \x2E
 	3=\x40foo-e \x2E
 ---
+name: echo-test-3-ebcdic
+description:
+	Test what the echo builtin does, and test a compatibility flag.
+category: !mnbsdash,!shell:ebcdic-no
+stdin:
+	"$__progname" -c 'echo -n 1=\\x7C$1; echo -e \\x4B' -- foo bar
+	"$__progname" -o posix -c 'echo -n 2=\\x7C$1; echo -e \\x4B' -- foo bar
+	"$__progname" -o sh -c 'echo -n 3=\\x7C$1; echo -e \\x4B' -- foo bar
+expected-stdout:
+	1=@foo.
+	2=\x7Cfoo-e \x4B
+	3=\x7Cfoo-e \x4B
+---
 name: utilities-getopts-1
 description:
 	getopts sets OPTIND correctly for unparsed option
@@ -12647,6 +13235,7 @@ name: duffs-device
 description:
 	Check that the compiler did not optimise-break them
 	(lex.c has got a similar one in SHEREDELIM)
+category: !shell:faux-ebcdic,!shell:ebcdic-yes
 stdin:
 	set +U
 	s=
@@ -12659,6 +13248,38 @@ stdin:
 expected-stdout:
 	typeset s=$'\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\E\034\035\036\037 !"#$%&\047()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377\u00A0\u20AC\uFFFD\357\277\276\357\277\277\360\220\200\200.'
 ---
+name: duffs-device-ebcdic
+description:
+	Check that the compiler did not optimise-break them
+category: !shell:ebcdic-no
+stdin:
+	set +U
+	s=
+	typeset -i1 i=0
+	while (( ++i < 256 )); do
+		s+=${i#1#}
+	done
+	#s+=$'\xC2\xA0\xE2\x82\xAC\xEF\xBF\xBD\xEF\xBF\xBE\xEF\xBF\xBF\xF0\x90\x80\x80.' #XXX
+	typeset -p s
+expected-stdout:
+	typeset s=$'\001\002\003\004\t\006\007\010\011\012\v\f\r\016\017\020\021\022\023\024\n\b\027\030\031\032\033\034\035\036\037\040\041\042\043\044\045\046\E\050\051\052\053\054\055\056\a\060\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077  âäàáãåçñ¢.<(+|&éêëèíîïìß!$*);^-/ÂÄÀÁÃÅÇѦ,%_>?øÉÊËÈÍÎÏÌ`:#@\175="Øabcdefghi«»ðýþ±°jklmnopqrªºæ¸Æ¤µ~stuvwxyz¡¿Ð[Þ®¬£¥·©§¶¼½¾Ý¨¯]´×{ABCDEFGHI­ôöòóõ}JKLMNOPQR¹ûüùúÿ\\÷STUVWXYZ²ÔÖÒÓÕ0123456789³ÛÜÙÚ\377'
+---
+name: duffs-device-faux-EBCDIC
+description:
+	Check that the compiler did not optimise-break them
+category: shell:faux-ebcdic
+stdin:
+	set +U
+	s=
+	typeset -i1 i=0
+	while (( ++i < 256 )); do
+		s+=${i#1#}
+	done
+	s+=$'\xC2\xA0\xE2\x82\xAC\xEF\xBF\xBD\xEF\xBF\xBE\xEF\xBF\xBF\xF0\x90\x80\x80.'
+	typeset -p s
+expected-stdout:
+	typeset s=$'\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\E\034\035\036\037 !"#$%&\047()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237 ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\u00A0\u20AC\uFFFD￾￿ð\220\200\200.'
+---
 name: stateptr-underflow
 description:
 	This check overflows an Xrestpos stored in a short in R40
diff --git a/debian/.mkshrc b/debian/.mkshrc
index 7028b95..ad236a4 100644
--- a/debian/.mkshrc
+++ b/debian/.mkshrc
@@ -3,13 +3,23 @@
 
 # check if this is really mksh	# {((
 case ${KSH_VERSION:-} in
-*MIRBSD\ KSH*) ;;
-*) return 0 ;;
+*LEGACY\ KSH*|*MIRBSD\ KSH*) ;;
+*) \return 0 ;;
 esac				# }
 
+# customise your favourite editor here; first one found is used
+for EDITOR in "${EDITOR:-}" sensible-editor jupp jstar mcedit ed vi editor; do
+	EDITOR=$(\\builtin whence -p "$EDITOR") || EDITOR=
+	[[ -n $EDITOR && -x $EDITOR ]] && break
+	EDITOR=
+done
+
 # source the system-wide mkshrc file
 [[ -s /etc/mkshrc ]] && \. /etc/mkshrc
 
+# work around #813164
+\\builtin alias ls='ls -N'
+
 # prepend $debian_chroot support to PS1
 p=$'\001'
 if [[ ${PS1:1:1} = $'\r' ]]; then
@@ -21,7 +31,7 @@ fi
 [[ -z ${debian_chroot:-} && -r /etc/debian_chroot ]] && \
     debian_chroot=$(</etc/debian_chroot)
 PS1=$p$'\r${debian_chroot:+'$p$'\e[0;1m'$p'($debian_chroot)'$p$'\e[0m'$p'}'$PS1
-\unset p
+\\builtin unset p
 
 \: put your local alias/function definitions, patches, etc. here
 
@@ -29,21 +39,21 @@ PS1=$p$'\r${debian_chroot:+'$p$'\e[0;1m'$p'($debian_chroot)'$p$'\e[0m'$p'}'$PS1
 [[ $TERM = screen.* ]] && TERM=screen
 
 # force sane environment (e.g. for scripting), Debian-specific libc patch
-#\export LC_ALL=C.UTF-8
+#\\builtin export LC_ALL=C.UTF-8
 # - or - switch to a slightly user-friendly and more portable locale
-\unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \
+\\builtin unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \
     LC_NAME LC_NUMERIC LC_TELEPHONE LC_TIME
 p=en_GB.UTF-8
-\export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
+\\builtin export LANG=C LC_CTYPE=C.UTF-8 LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
 
 # in either case: sync mksh UTF-8 mode to current locale (expected on GNU)
-set -U; [[ ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} = *[Uu][Tt][Ff]?(-)8* ]] || set +U
+\\builtin set -U; [[ ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} = *[Uu][Tt][Ff]?(-)8* ]] || \\builtin set +U
 
 # set a sensible editor
-p=$(\builtin whence -p jupp) || p=
-[[ -n $p ]] || p=$(\builtin whence -p jstar) || p=
-[[ -n $p ]] && export EDITOR=$p VISUAL=$p
+p=$(\\builtin whence -p jupp) || p=
+[[ -n $p ]] || p=$(\\builtin whence -p jstar) || p=
+[[ -n $p ]] && \\builtin export VISUAL=$p
 
-\unset p
+\\builtin unset p
 
 \: make sure this is the last line, to ensure a good return code
diff --git a/debian/README.Debian b/debian/README.Debian
index d1901d6..2853b32 100644
--- a/debian/README.Debian
+++ b/debian/README.Debian
@@ -42,10 +42,10 @@ Notes for the mksh binary package
   installation however is not automatic due to Debian #540512 (a
   wontfix RC bug in dash). It is strongly recommended to use its
   “legacy” flavour for this, as it uses the host “long” C type for
-  integer arithmetics, enables POSIX mode if called as /bin/sh, and
-  contains some workarounds for broken Debian maintainer scripts.
-  First dpkg-reconfigure -plow dash to disable it sitting on /bin/sh,
-  then use these commands:
+  integer arithmetic operations, enables POSIX mode if called as
+  /bin/sh, and contains some workarounds for broken Debian maintainer
+  scripts. First dpkg-reconfigure -plow dash to disable it sitting on
+  /bin/sh, then use these commands:
   $ sudo ln -sf lksh /bin/sh
   $ sudo ln -sf lksh.1.gz /usr/share/man/man1/sh.1.gz
   Please note that lksh, as opposed to mksh, is not suited yet for
diff --git a/debian/changelog b/debian/changelog
index 80357e4..eeced35 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-mksh (53a.20161002+wtf2~deb3.1u1) wtf; urgency=medium
+mksh (56b.20171017+wtf1~deb3.1u1) wtf; urgency=medium
 
   * Backport to sarge (etch):
     - drop lenny specific remarks from README.Debian
@@ -10,12 +10,12 @@ mksh (53a.20161002+wtf2~deb3.1u1) wtf; urgency=medium
     - use gcc -W instead of -Wextra
     - call dpkg-parsechangelog without -n1
     - use sarge/etch-era menu categories
-  * Note: this backport also (untested) targets dapper, UCS 2.2, UCS 2.3
   * Update lintian overrides, ignore some though as usual for backports
+  * (actually, Built-Using does not work and is thus dropped entirely)
 
- -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 13:48:27 +0200
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 04 Nov 2017 21:16:29 +0100
 
-mksh (53a.20161002+wtf2~deb5u1) wtf; urgency=medium
+mksh (56b.20171017+wtf1~deb5u1) wtf; urgency=medium
 
   * Backport to lenny (squeeze, precise):
     - update documentation
@@ -26,14 +26,66 @@ mksh (53a.20161002+wtf2~deb5u1) wtf; urgency=medium
     - drop avr32 locales exclusion (avr32 was not in lenny or older)
     - drop Multi-Arch, Built-Using from debian/control
     - build as gzip; drop Pre-Depends on xz-capable dpkg
-    - remove Built-Using generation
+    - move Built-Using generation to write to README.Debian
     - debian/source/format 1.0
-  * Note: this backport also (untested) targets hardy, UCS 2.4
   * Update lintian overrides, ignore some though as usual for backports
 
- -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 13:41:58 +0200
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 04 Nov 2017 20:33:03 +0100
+
+mksh (56b.20171017+wtf1~deb7u1) wtf; urgency=medium
+
+  * Backport to wheezy (trusty):
+    - update documentation
+    - drop klibc for s390 which was buggy in wheezy
+    - drop libraries that did not exist in wheezy (musl; klibc/ppc64el)
+    - re-add xz compression and related dpkg Pre-Depends
+    - drop dash-mksh Suggests
+    - revert handling of the ksh alternative and conflict with usrmerge
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 04 Nov 2017 20:03:35 +0100
+
+mksh (56b.20171017+wtf1~deb8u1) wtf; urgency=medium
+
+  * Backport to jessie (xenial):
+    - back to debhelper compat 9
+    - revert other libcs’ dependencies to what we used in jessie
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 04 Nov 2017 19:39:50 +0100
+
+mksh (56b.20171017+wtf1) wtf; urgency=medium
+
+  * Merge mksh (56b-3) unstable; urgency=medium
 
-mksh (53a.20161002+wtf2~deb7u1) wtf; urgency=medium
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 04 Nov 2017 17:55:45 +0100
+
+mksh (56b+wtf2) wtf; urgency=high
+
+  * Merge mksh (56b-2) unstable; urgency=high
+    to work around #813164
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Wed, 04 Oct 2017 13:21:55 +0200
+
+mksh (56b+wtf1) wtf; urgency=medium
+
+  * New release to match 56b-1 celebrating MirBSD’s 15ᵗʰ anniversary
+  * Setting LC_CTYPE=C.UTF-8 is more reliable in the general case
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Tue, 29 Aug 2017 16:31:25 +0200
+
+mksh (56a+wtf1) wtf; urgency=medium
+
+  * New release to match 56-1
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Wed, 09 Aug 2017 06:26:19 +0200
+
+mksh (55a+wtf1) wtf; urgency=low
+
+  * Merge Debian packaging (55-1)
+  * Move to a repository on evolvis.org, away from GitHub
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Wed, 12 Apr 2017 22:06:09 +0200
+
+mksh (54+wtf1~deb7u1) wtf; urgency=medium
 
   * Backport to wheezy (trusty):
     - update documentation
@@ -43,15 +95,56 @@ mksh (53a.20161002+wtf2~deb7u1) wtf; urgency=medium
     - drop dash-mksh Suggests
     - revert handling of the ksh alternative and conflict with usrmerge
 
- -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 13:34:30 +0200
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 12 Nov 2016 03:10:08 +0100
 
-mksh (53a.20161002+wtf2~deb8u1) wtf; urgency=medium
+mksh (54+wtf1~deb8u1) wtf; urgency=medium
 
   * Backport to jessie (xenial):
     - back to debhelper compat 9
     - revert other libcs’ dependencies to what we used in jessie
 
- -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 13:30:11 +0200
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 12 Nov 2016 02:59:57 +0100
+
+mksh (54+wtf1) wtf; urgency=medium
+
+  * Merge Debian packaging (54-2)
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 12 Nov 2016 02:43:46 +0100
+
+mksh (53a.20161002+wtf2~deb3.1u1) wtf; urgency=medium
+
+  * Backport to sarge (etch):
+    - drop lenny specific remarks from README.Debian
+    - lower debhelper compat again to 5 (needs sarge-backports)
+    - change format of control files for sarge/etch era tools
+      + no/less comment lines
+      + remove headers that weren’t extant; use old-style Homepage pseudo
+      + no line continuations
+    - use gcc -W instead of -Wextra
+    - call dpkg-parsechangelog without -n1
+    - use sarge/etch-era menu categories
+  * Note: this backport also (untested) targets dapper, UCS 2.2, UCS 2.3
+  * Update lintian overrides, ignore some though as usual for backports
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 13:48:27 +0200
+
+mksh (53a.20161002+wtf2~deb5u1) wtf; urgency=medium
+
+  * Backport to lenny (squeeze, precise):
+    - update documentation
+    - mention diversion issues with /bin/sh
+    - lower debhelper compat to 7
+    - reduce dietlibc dependencies on what existed in lenny;
+      klibc did not exist so kill it completely
+    - drop avr32 locales exclusion (avr32 was not in lenny or older)
+    - drop Multi-Arch, Built-Using from debian/control
+    - build as gzip; drop Pre-Depends on xz-capable dpkg
+    - remove Built-Using generation
+    - debian/source/format 1.0
+  * Note: this backport also (untested) targets hardy, UCS 2.4
+  * Update lintian overrides, ignore some though as usual for backports
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 13:41:58 +0200
 
 mksh (53a.20161002+wtf2) wtf; urgency=medium
 
@@ -473,37 +566,189 @@ mksh (50j20141215wtf1) wtf; urgency=high
 
  -- Thorsten Glaser <t.glaser at tarent.de>  Tue, 16 Dec 2014 16:38:09 +0100
 
-mksh (53a-2~bpo7+1) wheezy-backports-sloppy; urgency=medium
+mksh (54-1~bpo7+1) wheezy-backports-sloppy; urgency=medium
 
   * Rebuild for wheezy-backports-sloppy.
     - drop klibc for s390 which was buggy in wheezy
     - use (default) gzip compression for debian.tar.gz
     - drop libraries that did not exist in wheezy (musl; klibc/ppc64el)
     - revert handling of the ksh alternative and conflict with usrmerge
+    - bring back a message related to older dietlibc versions
 
- -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 11:54:14 +0200
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 12 Nov 2016 02:12:03 +0100
 
-mksh (53a-2~bpo8+1) jessie-backports; urgency=medium
+mksh (54-1~bpo8+1) jessie-backports; urgency=medium
 
   * Rebuild for jessie-backports.
     - back to debhelper compat 9
     - revert other libcs’ dependencies to what we used in jessie
     - drop Built-Using for backports
 
- -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 11:53:37 +0200
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 12 Nov 2016 02:11:42 +0100
+
+mksh (56b-3) unstable; urgency=medium
+
+  * The “I’m not gonna do the release dance until you tell me you’re
+    done checking this” upload
+  * Update from MirBSD CVS HEAD (Closes: #878947)
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 21 Oct 2017 21:25:15 +0200
+
+mksh (56b-2) unstable; urgency=high
+
+  * Work around #813164 in /etc/skel/.mkshrc
+  * Policy 4.1.1 (no change)
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Tue, 03 Oct 2017 17:26:54 +0200
 
-mksh (53a-2) UNRELEASED; urgency=medium
+mksh (56b-1) unstable; urgency=medium
+
+  * Add $(which editor) to the fallback editor list, lowest priority
+  * New upstream bugfix-only release, celebrating 15 years of MirBSD:
+    - [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
+  * Handle “note:” compiler output for mirtoconf consistently
+  * Bump S-V, no relevant changes
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Tue, 29 Aug 2017 16:11:40 +0200
+
+mksh (56-1) unstable; urgency=medium
+
+  * Upload to unstable
+  * New upstream formal release:
+    - [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
+  * Policy 4.0.1; no changes AFAICT
+  * Update lintian overrides, NEWS entry
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Wed, 09 Aug 2017 04:35:42 +0200
+
+mksh (55-1) experimental; urgency=low
+
+  * Upload to experimental during the freeze
+  * New upstream formal release:
+    - [komh] Fix OS/2 search_access() and UNC path logic
+    - [tg] Undocument printf(1) to avoid user confusion
+    - [Jean Delvare, tg] Fix printf builtin -R option
+    - [tg] Make ${var at x}, unknown x, fail (thanks izabera)
+    - [tg] ${var=x} must evaluate x in scalar context (10x Martijn Dekker)
+    - [tg] Fixup relation between lksh and mksh, reduce delta
+    - [tg] Improve manpage display; add OS/2 $PATH FAQ
+    - [Jean Delvare] Fix bugs in manpage
+    - [tg] Review tilde expansion, removing “odd use of KEEPASN” and introduce
+      POSIX “declaration utility” concept; wait isn’t one
+    - [tg] Add \builtin utility, declaration utility forwarder
+    - [tg] Make $'\xz' expand to xz, not \0
+    - [tg] Use fixed string pooling (requires the above change in host mksh)
+    - [tg] POSIX declaration commands can have varassign and redirections
+    - [Martijn Dekker] Add typeset -g, replacing homegrown “global”
+    - [Harvey-OS] Disable NOPROSPECTOFWORK, APEX is reportedly fixed now
+    - [tg] Display ulimit -a output with flags; improve Haiku
+    - [tg] Drop old let] hack, use \builtin internally
+    - [tg] Fix padding in Lb64encode in dot.mkshrc
+    - [tg] Move FAQ content to a separate, new FAQ section in the manpage
+    - [tg] Add new standard variable PATHSEP (‘:’, ‘;’ on OS/2)
+    - [Martijn Dekker] Fix LINENO in eval and alias
+    - [komh] Fix “\builtin” on OS/2
+    - [tg] Improve (internal) character classes code for speed
+    - [tg] Fix: the underscore is no drive letter
+    - [tg] No longer hard-disable persistent history support in lksh
+    - [tg] Introduce build flag -T for enabling “textmode” on OS/2 (supporting
+      CR+LF line endings, but incompatible with mksh proper)
+    - [tg] Merge mksh-os2
+    - [tg] Permit changing $OS2_SHELL during a running shell
+    - [tg] Fix multibyte handling in ^R (Emacs search-history)
+    - [tg] Allow “typeset -p arrname[2]” to work
+    - [tg] Make some error messages more consistent
+    - [tg, komh] Disable UTF-8 detection code for OS/2 as unrealistic
+    - [tg, sdaoden] Limit alias name chars to POSIX plus non-leading ‘-’
+    - [tg, Martijn Dekker] Expand aliases at COMSUB parse time
+    - [tg] Make “typeset -f” output alias-resistent
+    - [tg, Martijn Dekker] Permit “eval break” and “eval continue”
+    - [tg] Make -masm=intel safe on i386
+    - [tg] Disambiguate $((…)) vs. $((…)…) in “typeset -f” output
+    - [Jean Delvare] Clarify the effect of exit and return in a subshell
+    - [tg] Simplify compile-time asserts and make them actually compile-time
+    - [tg] Fix ^O in Emacs mode if the line was modified (LP#1675842)
+    - [tg] Address Coverity Scan… stuff… now that it builds again
+    - [Martijn Dekker, tg] Add test -v
+    - [tg] Document set -o posix/sh completely
+  * Update “uhr” from MirBSD CVS
+  * Add NEWS entry with user-visible changes
+  * Update lintian overrides
+  * Apply scripting changes (“\builtin”, etc.) to /etc/skel/.mkshrc
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Wed, 12 Apr 2017 21:52:34 +0200
+
+mksh (54-2) unstable; urgency=medium
+
+  * Drop all '-specs=*' from CFLAGS and LDFLAGS, this nonsense
+    causes FTBFS all over the place, e.g. in openssl1.0 too ☹
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 12 Nov 2016 02:37:10 +0100
+
+mksh (54-1) unstable; urgency=medium
 
   [ John Paul A̲d̲r̲i̲a̲n̲ Glaubitz ]
   * Explain precisely which device nodes are needed in the chroot,
     and which permissions and ownership are required for openpty
+    (cf. #841935, #817236)
 
   [ Thorsten Glaser ]
   * Fix wrong URL in debian/copyright
   * Move to debhelper compat 10 for stretch/sid
   * debian/control: Add explicit branches to VCS-* URIs
-
- -- Thorsten Glaser <tg at mirbsd.de>  Fri, 07 Oct 2016 11:52:51 +0200
+  * Remove obsolete dietlibc-specific warning message from the logs
+  * New upstream formal release:
+    - [tg] Simplify and improve code and manual page
+    - [tg] Try GCC 5’s new -malign-data=abi
+    - [tg] Allow interrupting builtin cat even on fast devices (LP#1616692)
+    - [tg] Update to Unicode 9.0.0
+    - [Andreas Buschka] Correct English spelling
+    - [tg] Handle set -e-related error propagation in || and && constructs
+      correctly
+    - [tg] Initialise memory for RNG even when not targeting Valgrind
+    - [tg] Shrink binary size
+    - [Brian Callahan] Improve support for the contemporary pcc compiler
+    - [tg] Fix side effects with lazy evaluation; spotted by ormaaj
+    - [tg] New flags -c (columnise), -l, -N for the print builtin
+    - [Larry Hynes] Fix English, spelling mistakes, typos in the manpage
+    - [tg, ormaah] Return 128+SIGALRM if read -t times out, like GNU bash
+    - [Martijn Dekker] Install both manpages from Build.sh
+    - [Martijn Dekker] Document case changes are ASCII-only
+    - [Ronald G. Minnich, Elbing Miss, Álvaro Jurado, tg] Begin porting to
+      Harvey-OS and APEX (similar to Plan 9 and APE)
+    - [KO Myung-Hun] More infrastructure for the OS/2 (EMX, KLIBC) port
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Sat, 12 Nov 2016 02:07:59 +0100
 
 mksh (53a-1) unstable; urgency=medium
 
diff --git a/debian/control b/debian/control
index 46dc614..7aab093 100644
--- a/debian/control
+++ b/debian/control
@@ -6,7 +6,7 @@ Origin: WTF
 Bugs: mailto:wtf at mirbsd.org
 Build-Depends: bsdmainutils, debhelper (>= 5), ed, dietlibc-dev [alpha amd64 arm i386 ia64 mips mipsel powerpc sparc], locales | belocs-locales-bin
 Build-Conflicts: dietlibc-dev (<< 0.33~cvs20111108-5~) [hppa]
-Standards-Version: 3.9.8
+Standards-Version: 4.1.1
 
 Package: mksh
 Architecture: any
diff --git a/debian/copyright b/debian/copyright
index 5d0528d..741afb4 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -28,8 +28,12 @@ of https://www.mirbsd.org/pics/mksh.svg (the mksh logo).
 
 The MirBSD Korn Shell (mksh) is
 Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
-	    2011, 2012, 2013, 2014, 2015, 2016
+	    2011, 2012, 2013, 2014, 2015, 2016, 2017
 	mirabilos <m at mirbsd.org>
+Copyright (c) 2015
+	Daniel Richard G. <skunk at iSKUNK.ORG>
+Copyright (c) 2015
+	KO Myung-Hun <komh at chollian.net>
 All rights reserved.
 
 The mksh logo is
diff --git a/debian/meat b/debian/meat
index f4687b8..b209c4e 100644
--- a/debian/meat
+++ b/debian/meat
@@ -41,6 +41,7 @@ buildmeat() {
 	(set -x; env "$@"; echo $? >buildmeat.tmp) 2>&1 | sed \
 	    -e 's!conftest.c:\([0-9]*\(:[0-9]*\)*\): error:!cE(\1) -!g' \
 	    -e 's!conftest.c:\([0-9]*\(:[0-9]*\)*\): warning:!cW(\1) -!g' \
+	    -e 's!conftest.c:\([0-9]*\(:[0-9]*\)*\): note:!cN(\1) -!g' \
 
 	test -s buildmeat.tmp || return 255
 	return $(cat buildmeat.tmp)
@@ -360,7 +361,7 @@ sCFLAGS=
 # drop optimisation, debugging and PIC flags for mksh-static
 for x in $dCFLAGS; do
 	case $x in
-	(-O*|-g*|-fPIE) ;;
+	(-O*|-g*|-fPIE|-specs=*) ;;
 	(*) sCFLAGS="$sCFLAGS $x" ;;
 	esac
 done
@@ -370,7 +371,7 @@ sCPPFLAGS="$dCPPFLAGS -DMKSH_BINSHPOSIX -DMKSH_BINSHREDUCED"
 sLDFLAGS=
 for x in $dLDFLAGS; do
 	case $x in
-	(-pie|-fPIE) ;;
+	(-pie|-fPIE|-specs=*) ;;
 	(*) sLDFLAGS="$sLDFLAGS $x" ;;
 	esac
 done
@@ -487,6 +488,7 @@ lCFLAGS=$dCFLAGS
 lCPPFLAGS="$dCPPFLAGS -DMKSH_BINSHPOSIX -DMKSH_BINSHREDUCED"
 lLDFLAGS=$dLDFLAGS
 lLIBS=
+HAVE_PERSISTENT_HISTORY=0; export HAVE_PERSISTENT_HISTORY
 # Debian #532343, #539158
 USE_PRINTF_BUILTIN=1; export USE_PRINTF_BUILTIN
 buildok=0
@@ -500,7 +502,7 @@ for x in $lkshlibs; do echo Building mksh-legacy with $x; case $x in
 	CFLAGS=
 	for x in $lCFLAGS; do
 		case $x in
-		(-O*|-g*|-fPIE) ;;
+		(-O*|-g*|-fPIE|-specs=*) ;;
 		(*) CFLAGS="$CFLAGS $x" ;;
 		esac
 	done
@@ -510,7 +512,7 @@ for x in $lkshlibs; do echo Building mksh-legacy with $x; case $x in
 	LDFLAGS=
 	for x in $lLDFLAGS; do
 		case $x in
-		(-pie|-fPIE) ;;
+		(-pie|-fPIE|-specs=*) ;;
 		(*) LDFLAGS="$LDFLAGS $x" ;;
 		esac
 	done
@@ -539,7 +541,7 @@ for x in $lkshlibs; do echo Building mksh-legacy with $x; case $x in
 	LDFLAGS=
 	for x in $lLDFLAGS; do
 		case $x in
-		(-pie|-fPIE) ;;
+		(-pie|-fPIE|-specs=*) ;;
 		(*) LDFLAGS="$LDFLAGS $x" ;;
 		esac
 	done
@@ -563,7 +565,7 @@ for x in $lkshlibs; do echo Building mksh-legacy with $x; case $x in
 	CFLAGS=
 	for x in $lCFLAGS; do
 		case $x in
-		(-O*|-g*|-fPIE) ;;
+		(-O*|-g*|-fPIE|-specs=*) ;;
 		(*) CFLAGS="$CFLAGS $x" ;;
 		esac
 	done
@@ -572,7 +574,7 @@ for x in $lkshlibs; do echo Building mksh-legacy with $x; case $x in
 	LDFLAGS=
 	for x in $lLDFLAGS; do
 		case $x in
-		(-pie|-fPIE) ;;
+		(-pie|-fPIE|-specs=*) ;;
 		(*) LDFLAGS="$LDFLAGS $x" ;;
 		esac
 	done
@@ -608,7 +610,7 @@ if test $buildok = 0; then
 fi
 
 echo Logging build information...
-pkgvsn=$(dpkg-parsechangelog | sed -n '/^Version: */s///p')
+pkgvsn=$(dpkg-parsechangelog | sed -n '/^Version: */s///p' | head -1)
 {
 	cat debian/README.Debian
 	echo Build information for mksh R${pkgvsn%%@(-|wtf)*([!-])}:
diff --git a/debian/mksh.NEWS b/debian/mksh.NEWS
index 79d1a3f..8e1ff92 100644
--- a/debian/mksh.NEWS
+++ b/debian/mksh.NEWS
@@ -1,3 +1,43 @@
+mksh (56-1) unstable; urgency=medium
+
+  The new /etc/skel/.mkshrc moves selection of the default EDITOR
+  (for when the parameter was not set prior to running it) to near
+  the top, before /etc/mkshrc runs. It contains a user-editable
+  priority list (first match wins) and, newly, defaults to Debian’s
+  sensible-editor (after a previously-set $EDITOR, of course). It
+  is therefore recommended to update from the skeleton file.
+
+  Read the full user’s caveat at: http://www.mirbsd.org/mksh.htm#c56
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Wed, 09 Aug 2017 04:34:54 +0200
+
+mksh (55-1) experimental; urgency=low
+
+  User-visible changes to the shell language (R55):
+  - The POSIX declaration utility concept is introduced, which also
+    applies to commands having variable assignments and redirections
+    preceding them. "wait" however does not keep assignments any longer.
+  - The new "\builtin" utility forwards the declaration utility flag
+    exactly like "command" does.
+  - The new "typeset -g" replaces mksh’s previous home-grown "global"
+    builtin, which is now deprecated and *will* be removed from a future
+    version.
+  - Aliases are now expanded for command, function and value substitutions
+    at parse time (like for functions, and excepting ‘`’-style ones).
+  - "typeset -f" output is now alias-resistent and thus more reentrant.
+  - Alias names are now limited to [A-Za-z0-9_!%,@], following POSIX,
+    although a non-leading hyphen-minus is also permitted.
+  - "print -R" is now (correctly) roughly equivalent to POSIX mode echo.
+  - The "let]" hack is gone.
+  - "ulimit -a" output changed to display the associated flag.
+  - $PATHSEP is now pre-defined to ‘:’ (‘;’ on OS/2).
+
+  The delta between mksh and lksh and the deltas between normal, posix
+  and “sh” mode are now properly documented in the manual pages; see
+  README.Debian for which options are enabled in which Debian binaries.
+
+ -- Thorsten Glaser <tg at mirbsd.de>  Wed, 12 Apr 2017 21:52:27 +0200
+
 mksh (53-1) unstable; urgency=medium
 
   User-visible changes to the shell language (R53):
diff --git a/debian/uhr b/debian/uhr
index 929d867..40123e5 100644
--- a/debian/uhr
+++ b/debian/uhr
@@ -1,7 +1,7 @@
 #!/bin/mksh
-# $MirOS: contrib/hosted/tg/uhr,v 1.17 2015/11/29 21:34:06 tg Exp $
+# $MirOS: contrib/hosted/tg/uhr,v 1.21 2017/03/31 02:49:41 tg Exp $
 #-
-# Copyright © 2012, 2013, 2015
+# Copyright © 2012, 2013, 2015, 2017
 #	mirabilos <m at mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -27,53 +27,91 @@ if [[ $KSH_VERSION != @(\@\(#\)MIRBSD KSH R)@(4[1-9]|[5-9][0-9]|[1-9][0-9]+([0-9
 	print -u2 Uhr requires mksh R41 or newer.
 	exit 1
 fi
-typeset -Z6 tosleep
+set -U
+print '\r\e[K\rLoading…'
 
-# stupid GNU idiots breaking everything by default… grml…
-bcopt=
-bc --help >/dev/null 2>&1 && bcopt=-q
+function graceful {
+	print -n '\033[;H\033[J'
+	exit 0
+}
+trap graceful INT TERM HUP
+
+# Shell library for easy display of a progress bar, modified for Uhr
+#
+# Usage:
+# – before:	init_progress_bar $n
+# – $n times:	draw_progress_bar
+# – after:	done_progress_bar
+#
+# init_progress_bar trashes the EXIT trap, done_progress_bar clears it.
 
-# global variables used by progress bar
+# global variables used by this library
 _cnt_progress_bar=0
 _cur_progress_bar=0
 isin_progress_bar=0
 nlin_progress_bar=0
 
+if [[ $KSH_VERSION = @(\@\(#\)MIRBSD KSH R)@(5[5-9]|[6-9][0-9]|[1-9][0-9][0-9])\ * ]]; then
+	alias global='typeset -g'
+else
+	alias global=global
+fi
+
 # args: $1 = number of draw_progress_bar calls to make up 100%
 function init_progress_bar {
 	global -i _cnt_progress_bar=$1 _cur_progress_bar=0
 	global -i nlin_progress_bar=$LINES isin_progress_bar=1
 
-	trap 'done_progress_bar' EXIT
-	# newline; up one line (to ensure we are not in the last line);
-	# save position; set scrolling region; restore position
-	print -n "\\n\\e[A\\e7\\e[1;$((# nlin_progress_bar - 1))r\\e8"
+	trap 'done_progress_bar 1' EXIT
+	# set up scrolling region, draw initial empty bar
+	sigwinch_uhr
+	got_sigwinch=0
 }
 
+unalias global
+
 function sigwinch_uhr {
 	got_sigwinch=1
 	(( isin_progress_bar )) || return 0
 
 	# get new terminal size
 	nlin_progress_bar=$LINES
-	# newline; up one line (to ensure we are not in the last line);
-	# save position; set scrolling region; restore position
-	print -n "\\n\\e[A\\e7\\e[1;$((# nlin_progress_bar - 1))r\\e8"
+
+	# save position; clear scrolling region; restore position; newline;
+	# up one line (to ensure we are not in the last line); save position;
+	# clear rest of screen; set new scrolling region; restore position
+	print -nu2 "\\e7\\e[0;0r\\e8\\n\\e[A\\e7\\e[J\\e[1;$((# nlin_progress_bar - 1))r\\e8"
+
+	# redraw progress bar
+	draw_progress_bar_internal
 }
 
 function done_progress_bar {
 	(( isin_progress_bar )) || return 0
-	# save position; clear scrolling region;
-	# go to last line; delete line; restore position
-	print "\\e7\\e[0;0r\\e[$nlin_progress_bar;0H\\e[M\\e8"
 	isin_progress_bar=0
+	# save position; clear scrolling region; restore position;
+	# save position; clear rest of screen; restore position
+	print -nu2 "\\e7\\e[0;0r\\e8\\e7\\e[J\\e8"
 	trap - EXIT
+	[[ -n $1 ]] || (( _cur_progress_bar == _cnt_progress_bar )) || \
+	    print -ru2 W: expected $_cnt_progress_bar draw_progress_bar calls, \
+	    got only $_cur_progress_bar
 }
 
 function draw_progress_bar {
+	# increment current progress
+	if (( ++_cur_progress_bar > _cnt_progress_bar )); then
+		print -ru2 "W: too many draw_progress_bar calls"
+		_cur_progress_bar=$_cnt_progress_bar
+	fi
+	# remaining drawing code
+	draw_progress_bar_internal
+}
+
+function draw_progress_bar_internal {
 	local bar num w=$COLUMNS
 
-	((# num = (++_cur_progress_bar * w * 8) / _cnt_progress_bar ))
+	((# num = (_cur_progress_bar * w * 8) / _cnt_progress_bar ))
 	while ((# num >= 8 )); do
 		bar+=█
 		((# num -= 8 ))
@@ -94,39 +132,14 @@ function draw_progress_bar {
 	# save position; go to last line; set colours;
 	# output a line full of spaces (and completion percentage);
 	# jump to first column; output bar (line præfix); restore position
-	print -n -- "\\e7\\e[$nlin_progress_bar;0H\\e[0;1;33;44m$spc\\r$bar\\e8"
+	print -nu2 -- "\\e7\\e[$nlin_progress_bar;1H\\e[0;1;33;44m$spc\\r$bar\\e8"
 }
 
-function graceful {
-	print -n '\033[;H\033[J'
-	exit 0
-}
-trap graceful INT TERM HUP
-
 trap sigwinch_uhr WINCH
-while :; do
-got_sigwinch=0
 
-init_progress_bar 135
-draw_progress_bar
-S='Pregenerating arrays, please wait...'
-if (( (r = (COLUMNS - ${%S}) / 2 - 2) < 1 )); then
-	d="\\e[0m\\n$S"
-else
-	d=
-	(( n = ${%S} + 2 ))
-	while (( n-- )); do
-		d+=─
-	done
-	d="\\e[0m\\e[$((LINES / 2 - 1));${r}H\\e7┌$d┐\\e8\\e[B│ $S │\\e8\\e[2B└$d┘"
-fi
-print "$d"
-
-(( r = LINES * 2 ))
-(( r = (r > COLUMNS ? COLUMNS : r) / 2 - 1))
-(( n = 2 * r + 1 ))
-set -A fb
-integer fb
+# stupid GNU idiots breaking everything by default… grml…
+bcopt=
+bc --help >/dev/null 2>&1 && bcopt=-q
 
 integer F_NO=0x00 M_NO=0x1F
 integer F_BG=0x01 M_BG=0x1E
@@ -136,16 +149,14 @@ integer F_MP=0x08 M_MP=0x17
 integer F_SP=0x10 M_SP=0x0F
 integer B_BG=0x01 B_BLK=0x02 B_NB=0x0C B_DOT=0x10
 
-set -U
 #	-	BLK	BG	NB	DOT	NB|DOT
 set -A m2c \
-	0x20	1#▀	1#*	1#▀	1#·	1#░	\
-	1#▄	1#█	1#█	1#█	1#▆	1#█	\
-	1#*	1#█	1##	1#◘	1#⁂	1#◙	\
-	1#▄	1#█	1#▆	1#█	1#▒	1#▓	\
-	1#.	1#▛	1#☿	1#▛	1#:	1#▒	\
-	1#▄	1#█	1#◙	1#█	1#▆	1#▓
-typeset -i1 m2c[*]
+	' '	'▀'	'*'	'▀'	'·'	'░'	\
+	'▄'	'█'	'█'	'█'	'▆'	'█'	\
+	'*'	'█'	'#'	'◘'	'⁂'	'◙'	\
+	'▄'	'█'	'▆'	'█'	'▒'	'▓'	\
+	'.'	'▛'	'☿'	'▛'	':'	'▒'	\
+	'▄'	'█'	'◙'	'█'	'▆'	'▓'
 
 set -A m2m
 integer m2m
@@ -157,17 +168,22 @@ while (( ++i <= 0x1F )); do
 done
 
 function refresh {
-	local -i10 i j z s c
+	local -i10 i j k l=-2 q=0
 	local t
 
-	for i in "$@"; do
-		(( z = (i / n) & 0xFFFE ))
-		(( s = i % n ))
-		(( i = m2m[fb[z * n + s]] ))
-		(( j = m2m[fb[(z + 1) * n + s]] ))
-		print -n "\e[$((z / 2 + 1));$((s + 1))H${m2c[j * 6 + i]#1#}"
+	unset changed[$(((r / 2) * n + r))]
+	for k in "${!changed[@]}"; do
+		(( i = m2m[fb[phys_v[k]]] ))
+		(( j = m2m[fb[phys_v[k] + n]] ))
+		(( phys_l[k] == l )) || t+=${phys_p[k]}
+		(( l = k ))
+		t+=${m2c[j * 6 + i]}
+		(( ++q & 7 )) && continue
+		print -nr -- "$t"
+		t=
 	done
-	print -n "\e[$((r / 2 + 1));$((r + 1))H\e[7mⓄ\e[0m"
+	set -A changed
+	print -n "$t\e[$((r / 2 + 1));$((r + 1))H\e[7mⓄ\e[0m\e[1;9H"
 }
 
 # put arrayname x y
@@ -180,178 +196,35 @@ function put {
 }
 
 # retrace arrayname maskname colourname
-set -A px
 function retrace {
 	nameref _px=$1 _m=$2 _c=$3
-	local _i
+	local _i _k _z _s
 
 	for _i in "${_px[@]}"; do
 		(( fb[_i] = (fb[_i] & _m) | _c ))
-	done
-	px+=("${_px[@]}")
-}
-
-draw_progress_bar
-
-# precalculate all lines’ endpoints with bc and paths with Bresenham
-integer x y dx sx dy sy e f
-bc -l $bcopt |&
-print -p scale=20
-print -p r=$r
-print -p o=r
-print -p 'define p(t) {
-	auto d
-	d = 90 - t
-	if (d < 0) d = 360 + d
-	return (d * 3.1415926535897932 / 180)
-}'
-# minutes and seconds – full length, 60 items
-i=-1
-while (( ++i < 60 )); do
-	draw_progress_bar
-	eval set -A lms$i
-	print -p "r * c(p($i * 6))"
-	read -p S; [[ $S = ?(-).* ]] && S=0
-	x=${S%%.*}
-	print -p "r * s(p($i * 6))"
-	read -p S; [[ $S = ?(-).* ]] && S=0
-	y=${S%%.*}
-	(( dx = x < 0 ? -x : x ))
-	(( sx = x < 0 ? 1 : -1 ))
-	(( dy = y < 0 ? y : -y ))
-	(( sy = y < 0 ? 1 : -1 ))
-	(( e = dx + dy ))
-	while :; do
-		put lms$i x y
-		(( !x && !y )) && break
-		(( f = 2 * e ))
-		if (( f > dy )); then
-			(( e += dy ))
-			(( x += sx ))
-		fi
-		if (( f < dx )); then
-			(( e += dx ))
-			(( y += sy ))
-		fi
-	done
-done
-# hours – 2/3 length, 60 items (5 per hour)
-print -p 'r = o * 2 / 3'
-i=-1
-while (( ++i < 60 )); do
-	draw_progress_bar
-	eval set -A lh$i
-	print -p "r * c(p($i * 6))"
-	read -p S; [[ $S = ?(-).* ]] && S=0
-	x=${S%%.*}
-	print -p "r * s(p($i * 6))"
-	read -p S; [[ $S = ?(-).* ]] && S=0
-	y=${S%%.*}
-	(( dx = x < 0 ? -x : x ))
-	(( sx = x < 0 ? 1 : -1 ))
-	(( dy = y < 0 ? y : -y ))
-	(( sy = y < 0 ? 1 : -1 ))
-	(( e = dx + dy ))
-	while :; do
-		put lh$i x y
-		(( !x && !y )) && break
-		(( f = 2 * e ))
-		if (( f > dy )); then
-			(( e += dy ))
-			(( x += sx ))
+		# map to physical coordinates
+		if [[ -z ${phys_z[_i]} ]]; then
+			(( phys_z[_i] = _z = (_i / n) / 2 ))
+			(( phys_s[_i] = _s = _i % n ))
+			(( phys_i[_i] = _z * n + _s ))
 		fi
-		if (( f < dx )); then
-			(( e += dx ))
-			(( y += sy ))
+		_k=${phys_i[_i]}
+		if [[ -z ${phys_v[_k]} ]]; then
+			_z=${phys_z[_i]}
+			_s=${phys_s[_i]}
+			(( phys_v[_k] = _z * n * 2 + _s ))
+			(( phys_l[_k] = (_s && _z) ? _k - 1 : -3 ))
+			phys_p[_k]=$'\e['$((_z + 1))\;$((_s + 1))H
 		fi
+		changed[_k]= #set
 	done
-done
-# hour markers – 80% length, 12 items
-print -p 'r = o * 8 / 10'
-i=-1
-set -A mkx
-set -A mky
-while (( ++i < 12 )); do
-	draw_progress_bar
-	print -p "r * c(p($i * 30))"
-	read -p S; [[ $S = ?(-).* ]] && S=0
-	mkx[i]=${S%%.*}
-	print -p "r * s(p($i * 30))"
-	read -p S; [[ $S = ?(-).* ]] && S=0
-	mky[i]=${S%%.*}
-done
-exec 3>&p; exec 3>&-
-
-draw_progress_bar
-(( L = LINES >= (COLUMNS / 2) ? (COLUMNS / 2) : LINES ))
-# fine-tuning of roman numeral position via screen size
-(( ++mkx[7] ))
-(( ++mkx[8] ))
-case $L {
-(22|23)	(( ++mkx[6] )) ;|
-(23)
-	(( mky[1] += 2 ))
-	(( mky[2] += 2 ))
-	(( mky[10] += 2 ))
-	(( mky[11] += 2 ))
-	;;
-(24|25|29|30|31|34)
-	(( mky[4] += 2 ))
-	(( mky[8] += 2 ))
-	;|
-(27|28|29)
-	(( ++mkx[10] ))
-	(( mky[8] += 2 ))
-	(( mky[9] += 2 ))
-	(( mky[10] += 2 ))
-	;|
-(27|29|31)
-	(( mky[0] -= 2 ))
-	;|
-(27)
-	(( --mkx[4] ))
-	(( --mkx[5] ))
-	(( ++mkx[6] ))
-	(( mkx[7] += 2 ))
-	(( ++mkx[8] ))
-	(( ++mkx[10] ))
-	;;
-(29)
-	(( mky[5] += 2 ))
-	(( mky[7] += 2 ))
-	;;
-(30)
-	(( mky[11] -= 2 ))
-	;;
 }
-(( mky[0] += 2 * (L & 1) ))
-done_progress_bar
-
-# clear framebuffer and screen
-set -A fb
-integer fb
-print -n -- '\e[H\e[J'
-
-# draw hour markers
-set -A lb
-integer e f=-1 k
-(( L > 21 )) && while (( ++f < 12 )); do
-	(( i=mkx[f] ))
+
+function draw_hour_marker {
+	draw_progress_bar
+	f=$1 e=$2 S=$3
+	(( i = mkx[f] ))
 	(( j = mky[f] & ~1 ))
-	case $f {
-	(0) e=7 S='# # # # #  # ## # # #' ;;
-	(1) e=1 S='###' ;;
-	(2) e=3 S='# ## ## #' ;;
-	(3) e=5 S='# # ## # ## # #' ;;
-	(4) e=5 S='# # ## # ##  # ' ;;
-	(5) e=3 S='# ## # # ' ;;
-	(6) e=5 S='# # ## # # #  #' ;;
-	(7) e=7 S='# # # ## # # # #  # #' ;;
-	(8) e=9 S='# # # # ## # # # # #  # # #' ;;
-	(9) e=5 S='# # ##  # # # #' ;;
-	(10) e=3 S='# # # # #' ;;
-	(11) e=5 S='# # # #  ## # #' ;;
-	}
 	Y='0 1 2'
 	if (( L > 26 )); then
 		d='###########'
@@ -372,39 +245,59 @@ integer e f=-1 k
 			put lb x dy
 		done
 	done
-done
-retrace lb M_BG F_BG
+}
+
+function draw_hour_markers {
+	set -A lb
+	draw_hour_marker  0 7 '# # # # #  # ## # # #'
+	draw_hour_marker  1 1 '###'
+	draw_hour_marker  2 3 '# ## ## #'
+	draw_hour_marker  3 5 '# # ## # ## # #'
+	draw_hour_marker  4 5 '# # ## # ##  # '
+	draw_hour_marker  5 3 '# ## # # '
+	draw_hour_marker  6 5 '# # ## # # #  #'
+	draw_hour_marker  7 7 '# # # ## # # # #  # #'
+	draw_hour_marker  8 9 '# # # # ## # # # # #  # # #'
+	draw_hour_marker  9 5 '# # ##  # # # #'
+	draw_hour_marker 10 3 '# # # # #'
+	draw_hour_marker 11 5 '# # # #  ## # #'
+	retrace lb M_BG F_BG
+}
 
 # draw outer circle with Bresenham
-set -A lc
-integer x=r y=-1 f=r dx dy
-while (( y < x )); do
-	(( dy = y++ * 2 + 1 ))
-	if (( y )); then
-		(( f -= dy ))
-		if (( f < 0 )); then
-			(( dx = 1 - x-- * 2 ))
-			(( f -= dx ))
+function draw_outer_circle {
+	draw_progress_bar
+	set -A lc
+	integer x=r y=-1 f=r dx dy
+	while (( y < x )); do
+		(( dy = y++ * 2 + 1 ))
+		if (( y )); then
+			(( f -= dy ))
+			if (( f < 0 )); then
+				(( dx = 1 - x-- * 2 ))
+				(( f -= dx ))
+			fi
 		fi
-	fi
-	put lc x y
-	put lc -x y
-	put lc -x -y
-	put lc x -y
-	put lc y x
-	put lc -y x
-	put lc -y -x
-	put lc y -x
-done
-retrace lc M_CC F_CC
-refresh "${px[@]}"; set -A px
-
-set -A do -- -1 -1 -1
-isfirst=1
-while (( !got_sigwinch )); do
-	if (( isfirst )); then
-		isfirst=0
-	else
+		put lc x y
+		put lc -x y
+		put lc -x -y
+		put lc x -y
+		put lc y x
+		put lc -y x
+		put lc -y -x
+		put lc y -x
+	done
+	retrace lc M_CC F_CC
+}
+
+function main_loop {
+	typeset -Z6 tosleep
+
+	set -A do -- -1 -1 -1
+	dodate_get
+	(( got_sigwinch )) && return
+	dodate_draw
+	while (( !got_sigwinch )); do
 		(( tosleep = 1000000 - ${EPOCHREALTIME#*.} ))
 		if (( tosleep > 999999 )); then
 			sleep 0.2
@@ -421,25 +314,197 @@ while (( !got_sigwinch )); do
 		else
 			sleep 0.$tosleep
 		fi
-	fi
+		dodate_get
+		retrace lms$((do[2])) M_SP F_NO
+		(( do[1] == dt[1] )) || retrace lms$((do[1])) M_MP F_NO
+		(( do[0] == dt[0] )) || retrace lh$((do[0])) M_HP F_NO
+		(( got_sigwinch )) || dodate_draw
+	done
+}
 
+function dodate_get {
 	d=$(date +'%H %M %S,%d %b %Y')
 	S=${d#*,}
 	d=${d%,*}
+	print -n "\e[1;$((n - ${%S} + 1))H$S\e[1;1H${d// /:}"
 	set -A dt $d
-
 	(( dt[0] = (dt[0] % 12) * 5 + (dt[1] / 12) ))
-	if (( do[2] != -1 )); then
-		retrace lms$((do[2])) M_SP F_NO
-		(( do[1] == dt[1] )) || retrace lms$((do[1])) M_MP F_NO
-		(( do[0] == dt[0] )) || retrace lh$((do[0])) M_HP F_NO
-	fi
+}
+
+function dodate_draw {
 	(( do[0] == dt[0] )) || retrace lh$((dt[0])) M_HP F_HP
 	(( do[1] == dt[1] )) || retrace lms$((dt[1])) M_MP F_MP
 	retrace lms$((dt[2])) M_SP F_SP
-	refresh "${px[@]}"; set -A px
+	refresh
 	set -A do -- "${dt[@]}"
+}
 
-	print -n "\e[1;$((n - ${%S} + 1))H$S\e[1;1H${d// /:}"
-done
+while :; do
+	(( L = LINES >= (COLUMNS / 2) ? (COLUMNS / 2) : LINES ))
+	init_progress_bar $((60 + 60 + (L > 21 ? (12 + 1 + 12) : 0) + 1 ))
+	S='Pregenerating arrays, please wait...'
+	if (( (r = (COLUMNS - ${%S}) / 2 - 2) > 0 )); then
+		d=
+		(( n = ${%S} + 2 ))
+		while (( n-- )); do
+			d+=─
+		done
+		S="\\e[$((LINES / 2 - 1));${r}H┌$d┐\\e[$((LINES / 2));${r}H│ $S │\\e[$((LINES / 2 + 1));${r}H└$d┘"
+	fi
+	print "\\e7\\e[0m$S\\e8"
+
+	(( r = LINES * 2 ))
+	(( r = (r > COLUMNS ? COLUMNS : r) / 2 - 1))
+	(( n = 2 * r + 1 ))
+	set -A fb
+	integer fb
+	set -A changed
+	set -A phys_z
+	set -A phys_s
+	set -A phys_i
+	set -A phys_v
+	set -A phys_p
+	# doch eine (minimale) Voroptimierung der Bildschirmausgabe
+	set -A phys_l
+
+	# precalculate all lines’ endpoints with bc and paths with Bresenham
+	integer x y dx sx dy sy e f
+	bc -l $bcopt |&
+	print -p scale=8
+	print -p r=$r
+	print -p o=r
+	print -p 'define p(t) {
+		auto d
+		d = 90 - t
+		if (d < 0) d = 360 + d
+		return (d * 3.1415926535897932 / 180)
+	}'
+	# minutes and seconds – full length, 60 items
+	i=-1
+	while (( ++i < 60 )); do
+		draw_progress_bar
+		eval set -A lms$i
+		print -p "r * c(p($i * 6))"
+		read -p S; [[ $S = ?(-).* ]] && S=0
+		x=${S%%.*}
+		print -p "r * s(p($i * 6))"
+		read -p S; [[ $S = ?(-).* ]] && S=0
+		y=${S%%.*}
+		(( dx = x < 0 ? -x : x ))
+		(( sx = x < 0 ? 1 : -1 ))
+		(( dy = y < 0 ? y : -y ))
+		(( sy = y < 0 ? 1 : -1 ))
+		(( e = dx + dy ))
+		while :; do
+			put lms$i x y
+			(( !x && !y )) && break
+			(( f = 2 * e ))
+			if (( f > dy )); then
+				(( e += dy ))
+				(( x += sx ))
+			fi
+			if (( f < dx )); then
+				(( e += dx ))
+				(( y += sy ))
+			fi
+		done
+	done
+	# hours – 2/3 length, 60 items (5 per hour)
+	print -p 'r = o * 2 / 3'
+	i=-1
+	while (( ++i < 60 )); do
+		draw_progress_bar
+		eval set -A lh$i
+		print -p "r * c(p($i * 6))"
+		read -p S; [[ $S = ?(-).* ]] && S=0
+		x=${S%%.*}
+		print -p "r * s(p($i * 6))"
+		read -p S; [[ $S = ?(-).* ]] && S=0
+		y=${S%%.*}
+		(( dx = x < 0 ? -x : x ))
+		(( sx = x < 0 ? 1 : -1 ))
+		(( dy = y < 0 ? y : -y ))
+		(( sy = y < 0 ? 1 : -1 ))
+		(( e = dx + dy ))
+		while :; do
+			put lh$i x y
+			(( !x && !y )) && break
+			(( f = 2 * e ))
+			if (( f > dy )); then
+				(( e += dy ))
+				(( x += sx ))
+			fi
+			if (( f < dx )); then
+				(( e += dx ))
+				(( y += sy ))
+			fi
+		done
+	done
+	# hour markers – 80% length, 12 items
+	if (( L > 21 )); then
+		print -p 'r = o * 8 / 10'
+		i=-1
+		set -A mkx
+		set -A mky
+		while (( ++i < 12 )); do
+			draw_progress_bar
+			print -p "r * c(p($i * 30))"
+			read -p S; [[ $S = ?(-).* ]] && S=0
+			mkx[i]=${S%%.*}
+			print -p "r * s(p($i * 30))"
+			read -p S; [[ $S = ?(-).* ]] && S=0
+			mky[i]=${S%%.*}
+		done
+		draw_progress_bar
+		# fine-tuning of roman numeral position via screen size
+		(( ++mkx[7] ))
+		(( ++mkx[8] ))
+		case $L {
+		(22|23)	(( ++mkx[6] )) ;|
+		(23)
+			(( mky[1] += 2 ))
+			(( mky[2] += 2 ))
+			(( mky[10] += 2 ))
+			(( mky[11] += 2 ))
+			;;
+		(24|25|29|30|31|34)
+			(( mky[4] += 2 ))
+			(( mky[8] += 2 ))
+			;|
+		(27|28|29)
+			(( ++mkx[10] ))
+			(( mky[8] += 2 ))
+			(( mky[9] += 2 ))
+			(( mky[10] += 2 ))
+			;|
+		(27|29|31)
+			(( mky[0] -= 2 ))
+			;|
+		(27)
+			(( --mkx[4] ))
+			(( --mkx[5] ))
+			(( ++mkx[6] ))
+			(( mkx[7] += 2 ))
+			(( ++mkx[8] ))
+			(( ++mkx[10] ))
+			;;
+		(29)
+			(( mky[5] += 2 ))
+			(( mky[7] += 2 ))
+			;;
+		(30)
+			(( mky[11] -= 2 ))
+			;;
+		}
+		(( mky[0] += 2 * (L & 1) ))
+	fi
+	exec 3>&p; exec 3>&-
+
+	draw_outer_circle
+	(( L > 21 )) && draw_hour_markers
+	done_progress_bar
+	print -n -- '\e[H\e[J'
+	refresh
+
+	main_loop
 done
diff --git a/dot.mkshrc b/dot.mkshrc
index 13a1f54..4a3dfea 100644
--- a/dot.mkshrc
+++ b/dot.mkshrc
@@ -1,8 +1,8 @@
 # $Id$
-# $MirOS: src/bin/mksh/dot.mkshrc,v 1.108 2016/07/26 22:03:41 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
+#		2011, 2012, 2013, 2014, 2015, 2016, 2017
 #	mirabilos <m at mirbsd.org>
 #
 # Provided that these terms and disclaimer and all copyright notices
@@ -22,135 +22,185 @@
 #-
 # ${ENV:-~/.mkshrc}: mksh initialisation file for interactive shells
 
-# catch non-mksh (including lksh) trying to run this file
+# catch non-mksh, non-lksh, trying to run this file
 case ${KSH_VERSION:-} in
-*MIRBSD\ KSH*) ;;
-*) return 0 ;;
+*LEGACY\ KSH*|*MIRBSD\ KSH*) ;;
+*) \return 0 ;;
 esac
 
-PS1='#'; (( USER_ID )) && PS1='$'; \: "${TERM:=vt100}${HOSTNAME:=$(\ulimit -c \
-    0; hostname 2>/dev/null)}${EDITOR:=/bin/ed}${USER:=$(\ulimit -c 0; id -un \
-    2>/dev/null || \echo \?)}${MKSH:=$(\builtin whence -p mksh)}"
-HOSTNAME=${HOSTNAME%%*([	 ]).*}; HOSTNAME=${HOSTNAME##*([	 ])}
-[[ $HOSTNAME = ?(ip6-)localhost?(6) ]] && HOSTNAME=
-\: "${HOSTNAME:=nil}${MKSH:=/bin/mksh}"; \export EDITOR HOSTNAME MKSH TERM USER
-PS4='[$EPOCHREALTIME] '; PS1=$'\001\r''${|
-	\typeset e=$?
+# give MidnightBSD's laffer1 a bit of csh feeling
+function setenv {
+	if (( $# )); then
+		\\builtin eval '\\builtin export "$1"="${2:-}"'
+	else
+		\\builtin typeset -x
+	fi
+}
+
+# pager (not control character safe)
+smores() (
+	\\builtin set +m
+	\\builtin cat "$@" |&
+	\\builtin trap "rv=\$?; \\\\builtin kill $! >/dev/null 2>&1; \\\\builtin exit \$rv" EXIT
+	while IFS= \\builtin read -pr line; do
+		llen=${%line}
+		(( llen == -1 )) && llen=${#line}
+		(( llen = llen ? (llen + COLUMNS - 1) / COLUMNS : 1 ))
+		if (( (curlin += llen) >= LINES )); then
+			\\builtin print -nr -- $'\e[7m--more--\e[0m'
+			\\builtin read -u1 || \\builtin exit $?
+			[[ $REPLY = [Qq]* ]] && \\builtin exit 0
+			curlin=$llen
+		fi
+		\\builtin print -r -- "$line"
+	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 \
+    -c 0; id -un 2>/dev/null)}${USER:=?}"
+[[ $HOSTNAME = ?(?(ip6-)localhost?(6)) ]] && HOSTNAME=nil; \\builtin unalias ls
+\\builtin export EDITOR HOSTNAME TERM USER
+
+# minimal support for lksh users
+if [[ $KSH_VERSION = *LEGACY\ KSH* ]]; then
+	PS1='$USER@${HOSTNAME%%.*}:$PWD>'
+	\\builtin return 0
+fi
+
+# mksh-specific from here
+\: "${MKSH:=$(\\builtin whence -p mksh)}${MKSH:=/bin/mksh}"
+\\builtin export MKSH
+
+# prompts
+PS4='[$EPOCHREALTIME] '; PS1='#'; (( USER_ID )) && PS1='$'; PS1=$'\001\r''${|
+	\\builtin typeset e=$?
 
 	(( e )) && REPLY+="$e|"
 	REPLY+=${USER}@${HOSTNAME%%.*}:
 
-	\typeset d=${PWD:-?}/ p=~; [[ $p = ?(*/) ]] || d=${d/#$p\//\~/}
-	d=${d%/}; \typeset m=${%d} n p=...; (( m > 0 )) || m=${#d}
+	\\builtin typeset d=${PWD:-?}/ p=~; [[ $p = ?(*/) ]] || d=${d/#$p\//\~/}
+	d=${d%/}; \\builtin typeset m=${%d} n p=...; (( m > 0 )) || m=${#d}
 	(( m > (n = (COLUMNS/3 < 7 ? 7 : COLUMNS/3)) )) && d=${d:(-n)} || p=
 	REPLY+=$p$d
 
-	\return $e
+	\\builtin return $e
 } '"$PS1 "
-\alias ls=ls
-\unalias ls
-\alias l='ls -F'
-\alias la='l -a'
-\alias ll='l -l'
-\alias lo='l -alo'
-\alias doch='sudo mksh -c "$(\builtin fc -ln -1)"'
-\command -v rot13 >/dev/null || \alias rot13='tr \
+
+# utilities
+\\builtin alias doch='sudo mksh -c "$(\\builtin fc -ln -1)"'
+\\builtin command -v rot13 >/dev/null || \\builtin alias rot13='tr \
     abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \
     nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
-if \command -v hd >/dev/null; then \:; elif \command -v hexdump >/dev/null; then
+if \\builtin command -v hd >/dev/null; then
+	\:
+elif \\builtin command -v hexdump >/dev/null; then
 	function hd {
 		hexdump -e '"%08.8_ax  " 8/1 "%02X " " - " 8/1 "%02X "' \
 		    -e '"  |" "%_p"' -e '"|\n"' "$@"
 	}
 else
 	function hd {
-		\typeset -Uui16 -Z11 pos=0
-		\typeset -Uui16 -Z5 hv=2147483647
-		\typeset dasc line i
-		\set +U
-
-		\cat "$@" | if \read -arN -1 line; then
-			\typeset -i1 'line[*]'
-			i=0
-			while (( i < ${#line[*]} )); do
-				hv=${line[i++]}
-				if (( (pos & 15) == 0 )); then
-					(( pos )) && \
-					    \builtin print -r -- "$dasc|"
-					\builtin print -n "${pos#16#}  "
-					dasc=' |'
-				fi
-				\builtin print -n "${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 -n -- '- '
-			done
-			while (( pos & 15 )); do
-				\builtin print -n '   '
-				(( (pos++ & 15) == 7 )) && \
-				    \builtin print -n -- '- '
-			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 || \
-    \builtin print -nr -- "${HOME:-/}")
-set -A DIRSTACK
+DIRSTACKBASE=$(\\builtin realpath ~/. 2>/dev/null || \
+    \\builtin print -nr -- "${HOME:-/}")
+\\builtin set -A DIRSTACK
 function chpwd {
-	DIRSTACK[0]=$(\builtin realpath . 2>/dev/null || \
-	    \builtin print -r -- "$PWD")
+	DIRSTACK[0]=$(\\builtin realpath . 2>/dev/null || \
+	    \\builtin print -nr -- "$PWD")
 	[[ $DIRSTACKBASE = ?(*/) ]] || \
 	    DIRSTACK[0]=${DIRSTACK[0]/#$DIRSTACKBASE/\~}
 	\:
 }
 \chpwd .
 cd() {
-	\builtin cd "$@" || \return $?
+	\\builtin cd "$@" || \\builtin return $?
 	\chpwd "$@"
 }
 function cd_csh {
-	\typeset d t=${1/#\~/$DIRSTACKBASE}
+	\\builtin typeset d t=${1/#\~/$DIRSTACKBASE}
 
-	if ! d=$(\builtin cd "$t" 2>&1); then
-		\builtin print -u2 "${1}: ${d##*cd: $t: }."
-		\return 1
+	if ! d=$(\\builtin cd "$t" 2>&1); then
+		\\builtin print -ru2 "${1}: ${d##*cd: $t: }."
+		\\builtin return 1
 	fi
 	\cd "$t"
 }
 function dirs {
-	\typeset d dwidth
-	\typeset -i fl=0 fv=0 fn=0 cpos=0
+	\\builtin typeset d dwidth
+	\\builtin typeset -i fl=0 fv=0 fn=0 cpos=0
 
-	while \getopts ":lvn" d; do
+	while \\builtin getopts ":lvn" d; do
 		case $d {
 		(l)	fl=1 ;;
 		(v)	fv=1 ;;
 		(n)	fn=1 ;;
-		(*)	\builtin print -u2 'Usage: dirs [-lvn].'
-			\return 1 ;;
+		(*)	\\builtin print -ru2 'Usage: dirs [-lvn].'
+			\\builtin return 1 ;;
 		}
 	done
-	\shift $((OPTIND - 1))
+	\\builtin shift $((OPTIND - 1))
 	if (( $# > 0 )); then
-		\builtin print -u2 'Usage: dirs [-lvn].'
-		\return 1
+		\\builtin print -ru2 'Usage: dirs [-lvn].'
+		\\builtin return 1
 	fi
 	if (( fv )); then
 		fv=0
 		while (( fv < ${#DIRSTACK[*]} )); do
 			d=${DIRSTACK[fv]}
 			(( fl )) && d=${d/#\~/$DIRSTACKBASE}
-			\builtin print -r -- "$fv	$d"
-			\builtin let fv++
+			\\builtin print -r -- "$fv	$d"
+			(( ++fv ))
 		done
 	else
 		fv=0
@@ -160,136 +210,117 @@ function dirs {
 			(( dwidth = (${%d} > 0 ? ${%d} : ${#d}) ))
 			if (( fn && (cpos += dwidth + 1) >= 79 && \
 			    dwidth < 80 )); then
-				\builtin print
+				\\builtin print
 				(( cpos = dwidth + 1 ))
 			fi
-			\builtin print -nr -- "$d "
-			\builtin let fv++
+			\\builtin print -nr -- "$d "
+			(( ++fv ))
 		done
-		\builtin print
+		\\builtin print
 	fi
-	\return 0
+	\\builtin return 0
 }
 function popd {
-	\typeset d fa
-	\typeset -i n=1
+	\\builtin typeset d fa
+	\\builtin typeset -i n=1
 
-	while \getopts ":0123456789lvn" d; do
+	while \\builtin getopts ":0123456789lvn" d; do
 		case $d {
 		(l|v|n)	fa+=" -$d" ;;
 		(+*)	n=2
-			\break ;;
-		(*)	\builtin print -u2 'Usage: popd [-lvn] [+<n>].'
-			\return 1 ;;
+			\\builtin break ;;
+		(*)	\\builtin print -ru2 'Usage: popd [-lvn] [+<n>].'
+			\\builtin return 1 ;;
 		}
 	done
-	\shift $((OPTIND - n))
+	\\builtin shift $((OPTIND - n))
 	n=0
 	if (( $# > 1 )); then
-		\builtin print -u2 popd: Too many arguments.
-		\return 1
+		\\builtin print -ru2 popd: Too many arguments.
+		\\builtin return 1
 	elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
 		if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
-			\builtin print -u2 popd: Directory stack not that deep.
-			\return 1
+			\\builtin print -ru2 popd: Directory stack not that deep.
+			\\builtin return 1
 		fi
 	elif [[ -n $1 ]]; then
-		\builtin print -u2 popd: Bad directory.
-		\return 1
+		\\builtin print -ru2 popd: Bad directory.
+		\\builtin return 1
 	fi
 	if (( ${#DIRSTACK[*]} < 2 )); then
-		\builtin print -u2 popd: Directory stack empty.
-		\return 1
+		\\builtin print -ru2 popd: Directory stack empty.
+		\\builtin return 1
 	fi
-	\unset DIRSTACK[n]
-	\set -A DIRSTACK -- "${DIRSTACK[@]}"
-	\cd_csh "${DIRSTACK[0]}" || \return 1
+	\\builtin unset DIRSTACK[n]
+	\\builtin set -A DIRSTACK -- "${DIRSTACK[@]}"
+	\cd_csh "${DIRSTACK[0]}" || \\builtin return 1
 	\dirs $fa
 }
 function pushd {
-	\typeset d fa
-	\typeset -i n=1
+	\\builtin typeset d fa
+	\\builtin typeset -i n=1
 
-	while \getopts ":0123456789lvn" d; do
+	while \\builtin getopts ":0123456789lvn" d; do
 		case $d {
 		(l|v|n)	fa+=" -$d" ;;
 		(+*)	n=2
-			\break ;;
-		(*)	\builtin print -u2 'Usage: pushd [-lvn] [<dir>|+<n>].'
-			\return 1 ;;
+			\\builtin break ;;
+		(*)	\\builtin print -ru2 'Usage: pushd [-lvn] [<dir>|+<n>].'
+			\\builtin return 1 ;;
 		}
 	done
-	\shift $((OPTIND - n))
+	\\builtin shift $((OPTIND - n))
 	if (( $# == 0 )); then
 		if (( ${#DIRSTACK[*]} < 2 )); then
-			\builtin print -u2 pushd: No other directory.
-			\return 1
+			\\builtin print -ru2 pushd: No other directory.
+			\\builtin return 1
 		fi
 		d=${DIRSTACK[1]}
 		DIRSTACK[1]=${DIRSTACK[0]}
-		\cd_csh "$d" || \return 1
+		\cd_csh "$d" || \\builtin return 1
 	elif (( $# > 1 )); then
-		\builtin print -u2 pushd: Too many arguments.
-		\return 1
+		\\builtin print -ru2 pushd: Too many arguments.
+		\\builtin return 1
 	elif [[ $1 = ++([0-9]) && $1 != +0 ]]; then
 		if (( (n = ${1#+}) >= ${#DIRSTACK[*]} )); then
-			\builtin print -u2 pushd: Directory stack not that deep.
-			\return 1
+			\\builtin print -ru2 pushd: Directory stack not that deep.
+			\\builtin return 1
 		fi
 		while (( n-- )); do
 			d=${DIRSTACK[0]}
-			\unset DIRSTACK[0]
-			\set -A DIRSTACK -- "${DIRSTACK[@]}" "$d"
+			\\builtin unset DIRSTACK[0]
+			\\builtin set -A DIRSTACK -- "${DIRSTACK[@]}" "$d"
 		done
-		\cd_csh "${DIRSTACK[0]}" || \return 1
+		\cd_csh "${DIRSTACK[0]}" || \\builtin return 1
 	else
-		\set -A DIRSTACK -- placeholder "${DIRSTACK[@]}"
-		\cd_csh "$1" || \return 1
+		\\builtin set -A DIRSTACK -- placeholder "${DIRSTACK[@]}"
+		\cd_csh "$1" || \\builtin return 1
 	fi
 	\dirs $fa
 }
 
-# pager (not control character safe)
-smores() (
-	\set +m
-	\cat "$@" |&
-	\trap "rv=\$?; 'kill' $! >/dev/null 2>&1; 'exit' \$rv" EXIT
-	while IFS= \read -pr line; do
-		llen=${%line}
-		(( llen == -1 )) && llen=${#line}
-		(( llen = llen ? (llen + COLUMNS - 1) / COLUMNS : 1 ))
-		if (( (curlin += llen) >= LINES )); then
-			\builtin print -n -- '\e[7m--more--\e[0m'
-			\read -u1 || \exit $?
-			[[ $REPLY = [Qq]* ]] && \exit 0
-			curlin=$llen
-		fi
-		\builtin print -r -- "$line"
-	done
-)
-
 # base64 encoder and decoder, RFC compliant, NUL safe, not EBCDIC safe
 function Lb64decode {
-	\set +U
-	\typeset c s="$*" t
-	[[ -n $s ]] || { s=$(\cat; \builtin print x); s=${s%x}; }
-	\typeset -i i=0 j=0 n=${#s} p=0 v x
-	\typeset -i16 o
+	\\builtin set +U
+	\\builtin typeset c s="$*" t
+	[[ -n $s ]] || { s=$(\\builtin cat; \\builtin print x); s=${s%x}; }
+	\\builtin typeset -i i=0 j=0 n=${#s} p=0 v x
+	\\builtin typeset -i16 o
 
 	while (( i < n )); do
 		c=${s:(i++):1}
 		case $c {
-		(=)	\break ;;
+		(=)	\\builtin break ;;
 		([A-Z])	(( v = 1#$c - 65 )) ;;
 		([a-z])	(( v = 1#$c - 71 )) ;;
 		([0-9])	(( v = 1#$c + 4 )) ;;
 		(+)	v=62 ;;
 		(/)	v=63 ;;
-		(*)	\continue ;;
+		(*)	\\builtin continue ;;
 		}
 		(( x = (x << 6) | v ))
 		case $((p++)) {
-		(0)	\continue ;;
+		(0)	\\builtin continue ;;
 		(1)	(( o = (x >> 4) & 255 )) ;;
 		(2)	(( o = (x >> 2) & 255 )) ;;
 		(3)	(( o = x & 255 ))
@@ -297,63 +328,60 @@ function Lb64decode {
 			;;
 		}
 		t+=\\x${o#16#}
-		(( ++j & 4095 )) && \continue
-		\builtin print -n $t
+		(( ++j & 4095 )) && \\builtin continue
+		\\builtin print -n $t
 		t=
 	done
-	\builtin print -n $t
+	\\builtin print -n $t
 }
-
-\set -A Lb64encode_tbl -- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \
-    a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /
 function Lb64encode {
-	\set +U
-	\typeset c s t
+	\\builtin set +U
+	\\builtin typeset c s t table
+	\\builtin set -A table -- A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \
+	    a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + /
 	if (( $# )); then
-		\read -raN-1 s <<<"$*"
-		\unset s[${#s[*]}-1]
+		\\builtin read -raN-1 s <<<"$*"
+		\\builtin unset s[${#s[*]}-1]
 	else
-		\read -raN-1 s
+		\\builtin read -raN-1 s
 	fi
-	\typeset -i i=0 n=${#s[*]} j v
+	\\builtin typeset -i i=0 n=${#s[*]} v
 
 	while (( i < n )); do
 		(( v = s[i++] << 16 ))
-		(( j = i < n ? s[i++] : 0 ))
-		(( v |= j << 8 ))
-		(( j = i < n ? s[i++] : 0 ))
-		(( v |= j ))
-		t+=${Lb64encode_tbl[v >> 18]}${Lb64encode_tbl[v >> 12 & 63]}
-		c=${Lb64encode_tbl[v >> 6 & 63]}
+		(( v |= s[i++] << 8 ))
+		(( v |= s[i++] ))
+		t+=${table[v >> 18]}${table[v >> 12 & 63]}
+		c=${table[v >> 6 & 63]}
 		if (( i <= n )); then
-			t+=$c${Lb64encode_tbl[v & 63]}
+			t+=$c${table[v & 63]}
 		elif (( i == n + 1 )); then
 			t+=$c=
 		else
 			t+===
 		fi
 		if (( ${#t} == 76 || i >= n )); then
-			\builtin print $t
+			\\builtin print -r $t
 			t=
 		fi
 	done
 }
 
 # Better Avalanche for the Jenkins Hash
-\typeset -Z11 -Uui16 Lbafh_v
+\\builtin typeset -Z11 -Uui16 Lbafh_v
 function Lbafh_init {
 	Lbafh_v=0
 }
 function Lbafh_add {
-	\set +U
-	\typeset s
+	\\builtin set +U
+	\\builtin typeset s
 	if (( $# )); then
-		\read -raN-1 s <<<"$*"
-		\unset s[${#s[*]}-1]
+		\\builtin read -raN-1 s <<<"$*"
+		\\builtin unset s[${#s[*]}-1]
 	else
-		\read -raN-1 s
+		\\builtin read -raN-1 s
 	fi
-	\typeset -i i=0 n=${#s[*]}
+	\\builtin typeset -i i=0 n=${#s[*]}
 
 	while (( i < n )); do
 		((# Lbafh_v = (Lbafh_v + s[i++] + 1) * 1025 ))
@@ -361,7 +389,7 @@ function Lbafh_add {
 	done
 }
 function Lbafh_finish {
-	\typeset -Ui t
+	\\builtin typeset -Ui t
 
 	((# t = (((Lbafh_v >> 7) & 0x01010101) * 0x1B) ^ \
 	    ((Lbafh_v << 1) & 0xFEFEFEFE) ))
@@ -373,42 +401,33 @@ function Lbafh_finish {
 # strip comments (and leading/trailing whitespace if IFS is set) from
 # any file(s) given as argument, or stdin if none, and spew to stdout
 function Lstripcom {
-	\set -o noglob
-	\cat "$@" | while \read _line; do
+	\\builtin set -o noglob
+	\\builtin cat "$@" | while \\builtin read _line; do
 		_line=${_line%%#*}
-		[[ -n $_line ]] && \builtin print -r -- $_line
+		[[ -n $_line ]] && \\builtin print -r -- $_line
 	done
 }
 
-# give MidnightBSD's laffer1 a bit of csh feeling
-function setenv {
-	if (( $# )); then
-		\eval '\export "$1"="${2:-}"'
-	else
-		\typeset -x
-	fi
-}
-
 # toggle built-in aliases and utilities, and aliases and functions from mkshrc
 function enable {
-	\typeset doprnt=0 mode=1 x y z rv=0
-	\typeset b_alias i_alias i_func nalias=0 nfunc=0 i_all
-	\set -A b_alias
-	\set -A i_alias
-	\set -A i_func
+	\\builtin typeset doprnt=0 mode=1 x y z rv=0
+	\\builtin typeset b_alias i_alias i_func nalias=0 nfunc=0 i_all
+	\\builtin set -A b_alias
+	\\builtin set -A i_alias
+	\\builtin set -A i_func
 
 	# accumulate mksh built-in aliases, in ASCIIbetical order
-	i_alias[nalias]=autoload; b_alias[nalias++]='\typeset -fu'
-	i_alias[nalias]=functions; b_alias[nalias++]='\typeset -f'
-	i_alias[nalias]=hash; b_alias[nalias++]='\builtin alias -t'
-	i_alias[nalias]=history; b_alias[nalias++]='\builtin fc -l'
-	i_alias[nalias]=integer; b_alias[nalias++]='\typeset -i'
-	i_alias[nalias]=local; b_alias[nalias++]='\typeset'
-	i_alias[nalias]=login; b_alias[nalias++]='\exec login'
-	i_alias[nalias]=nameref; b_alias[nalias++]='\typeset -n'
+	i_alias[nalias]=autoload; b_alias[nalias++]='\\builtin typeset -fu'
+	i_alias[nalias]=functions; b_alias[nalias++]='\\builtin typeset -f'
+	i_alias[nalias]=hash; b_alias[nalias++]='\\builtin alias -t'
+	i_alias[nalias]=history; b_alias[nalias++]='\\builtin fc -l'
+	i_alias[nalias]=integer; b_alias[nalias++]='\\builtin typeset -i'
+	i_alias[nalias]=local; b_alias[nalias++]='\\builtin typeset'
+	i_alias[nalias]=login; b_alias[nalias++]='\\builtin exec login'
+	i_alias[nalias]=nameref; b_alias[nalias++]='\\builtin typeset -n'
 	i_alias[nalias]=nohup; b_alias[nalias++]='nohup '
-	i_alias[nalias]=r; b_alias[nalias++]='\builtin fc -e -'
-	i_alias[nalias]=type; b_alias[nalias++]='\builtin whence -v'
+	i_alias[nalias]=r; b_alias[nalias++]='\\builtin fc -e -'
+	i_alias[nalias]=type; b_alias[nalias++]='\\builtin whence -v'
 
 	# accumulate mksh built-in utilities, in definition order, even ifndef
 	i_func[nfunc++]=.
@@ -416,6 +435,7 @@ function enable {
 	i_func[nfunc++]='['
 	i_func[nfunc++]=alias
 	i_func[nfunc++]=break
+	# \\builtin cannot, by design, be overridden
 	i_func[nfunc++]=builtin
 	i_func[nfunc++]=cat
 	i_func[nfunc++]=cd
@@ -434,7 +454,6 @@ function enable {
 	i_func[nfunc++]=jobs
 	i_func[nfunc++]=kill
 	i_func[nfunc++]=let
-	i_func[nfunc++]='let]'
 	i_func[nfunc++]=print
 	i_func[nfunc++]=pwd
 	i_func[nfunc++]=read
@@ -471,33 +490,34 @@ function enable {
 	i_alias[nalias]=la; b_alias[nalias++]='l -a'
 	i_alias[nalias]=ll; b_alias[nalias++]='l -l'
 	i_alias[nalias]=lo; b_alias[nalias++]='l -alo'
-	i_alias[nalias]=doch; b_alias[nalias++]='sudo mksh -c "$(\builtin fc -ln -1)"'
+	i_alias[nalias]=doch; b_alias[nalias++]='sudo mksh -c "$(\\builtin fc -ln -1)"'
 	i_alias[nalias]=rot13; b_alias[nalias++]='tr abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
-	i_alias[nalias]=cls; b_alias[nalias++]='\builtin print -n \\ec'
+	i_alias[nalias]=cls; b_alias[nalias++]='\\builtin print -n \\ec'
 
 	# accumulate functions from dot.mkshrc, in definition order
+	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
 	i_func[nfunc++]=dirs
 	i_func[nfunc++]=popd
 	i_func[nfunc++]=pushd
-	i_func[nfunc++]=smores
 	i_func[nfunc++]=Lb64decode
 	i_func[nfunc++]=Lb64encode
 	i_func[nfunc++]=Lbafh_init
 	i_func[nfunc++]=Lbafh_add
 	i_func[nfunc++]=Lbafh_finish
 	i_func[nfunc++]=Lstripcom
-	i_func[nfunc++]=setenv
 	i_func[nfunc++]=enable
 
 	# collect all identifiers, sorted ASCIIbetically
-	\set -sA i_all -- "${i_alias[@]}" "${i_func[@]}"
+	\\builtin set -sA i_all -- "${i_alias[@]}" "${i_func[@]}"
 
 	# handle options, we don't do dynamic loading
-	while \getopts "adf:nps" x; do
+	while \\builtin getopts "adf:nps" x; do
 		case $x {
 		(a)
 			mode=-1
@@ -506,8 +526,8 @@ function enable {
 			# deliberately causing an error, like bash-static
 			;|
 		(f)
-			\builtin print -u2 enable: dynamic loading not available
-			\return 2
+			\\builtin print -ru2 enable: dynamic loading not available
+			\\builtin return 2
 			;;
 		(n)
 			mode=0
@@ -516,88 +536,94 @@ function enable {
 			doprnt=1
 			;;
 		(s)
-			\set -sA i_all -- . : break continue eval exec exit \
-			    export readonly return set shift times trap unset
+			\\builtin set -sA i_all -- . : break continue eval \
+			    exec exit export readonly return set shift times \
+			    trap unset
 			;;
 		(*)
-			\builtin print -u2 enable: usage: \
+			\\builtin print -ru2 enable: usage: \
 			    "enable [-adnps] [-f filename] [name ...]"
 			return 2
 			;;
 		}
 	done
-	\shift $((OPTIND - 1))
+	\\builtin shift $((OPTIND - 1))
 
 	# display builtins enabled/disabled/all/special?
 	if (( doprnt || ($# == 0) )); then
 		for x in "${i_all[@]}"; do
-			y=$(\alias "$x") || y=
-			[[ $y = "$x='\\builtin whence -p $x >/dev/null || (\\builtin print mksh: $x: not found; exit 127) && \$(\\builtin whence -p $x)'" ]]; z=$?
+			y=$(\\builtin alias "$x") || y=
+			[[ $y = "$x='\\\\builtin whence -p $x >/dev/null || (\\\\builtin print -r mksh: $x: not found; \\\\builtin exit 127) && \$(\\\\builtin whence -p $x)'" ]]; z=$?
 			case $mode:$z {
 			(-1:0|0:0)
-				\print -r -- "enable -n $x"
+				\\builtin print -r -- "enable -n $x"
 				;;
 			(-1:1|1:1)
-				\print -r -- "enable $x"
+				\\builtin print -r -- "enable $x"
 				;;
 			}
 		done
-		\return 0
+		\\builtin return 0
 	fi
 
 	for x in "$@"; do
 		z=0
 		for y in "${i_alias[@]}" "${i_func[@]}"; do
-			[[ $x = "$y" ]] || \continue
+			[[ $x = "$y" ]] || \\builtin continue
 			z=1
-			\break
+			\\builtin break
 		done
 		if (( !z )); then
-			\builtin print -ru2 enable: "$x": not a shell builtin
+			\\builtin print -ru2 enable: "$x": not a shell builtin
 			rv=1
-			\continue
+			\\builtin continue
 		fi
 		if (( !mode )); then
 			# disable this
-			\alias "$x=\\builtin whence -p $x >/dev/null || (\\builtin print mksh: $x: not found; exit 127) && \$(\\builtin whence -p $x)"
+			\\builtin alias "$x=\\\\builtin whence -p $x >/dev/null || (\\\\builtin print -r mksh: $x: not found; \\\\builtin exit 127) && \$(\\\\builtin whence -p $x)"
 		else
 			# find out if this is an alias or not, first
 			z=0
 			y=-1
 			while (( ++y < nalias )); do
-				[[ $x = "${i_alias[y]}" ]] || \continue
+				[[ $x = "${i_alias[y]}" ]] || \\builtin continue
 				z=1
-				\break
+				\\builtin break
 			done
 			if (( z )); then
 				# re-enable the original alias body
-				\alias "$x=${b_alias[y]}"
+				\\builtin alias "$x=${b_alias[y]}"
 			else
 				# re-enable the original utility/function
-				\unalias "$x"
+				\\builtin unalias "$x"
 			fi
 		fi
 	done
-	\return $rv
+	\\builtin return $rv
 }
 
 \: 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/. ]] || \continue
-	#XXX OS/2
-	[[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH
+	[[ -d $p/. ]] || \\builtin continue
+	[[ $PATHSEP$PATH$PATHSEP = *"$PATHSEP$p$PATHSEP"* ]] || \
+	    PATH=$p$PATHSEP$PATH
 done
 
-\export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=-
-\alias cls='\builtin print -n \\ec'
+\\builtin export SHELL=$MKSH MANWIDTH=80 LESSHISTFILE=-
+\\builtin alias cls='\\builtin print -n \\ec'
 
-#\unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \
+#\\builtin unset LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_IDENTIFICATION LC_MONETARY \
 #    LC_NAME LC_NUMERIC LC_TELEPHONE LC_TIME
 #p=en_GB.UTF-8
-#\export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
-#\set -U
+#\\builtin export LANG=C LC_CTYPE=$p LC_MEASUREMENT=$p LC_MESSAGES=$p LC_PAPER=$p
+#\\builtin set -U
 
-\unset p
+\\builtin unset p
 
 \: place customisations above this line
diff --git a/edit.c b/edit.c
index dd767ff..8bccf13 100644
--- a/edit.c
+++ b/edit.c
@@ -5,7 +5,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -28,16 +28,16 @@
 
 #ifndef MKSH_NO_CMDLINE_EDITING
 
-__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.307 2016/09/01 12:59:08 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 int modified;			/* buffer has been "modified" */
 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 @@ static void x_init_prompt(bool);
 #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)
@@ -145,6 +146,9 @@ x_read(char *buf)
 static int
 x_getc(void)
 {
+#ifdef __OS2__
+	return (_read_kbd(0, 1, 0));
+#else
 	char c;
 	ssize_t n;
 
@@ -166,6 +170,7 @@ x_getc(void)
 			x_mode(true);
 		}
 	return ((n == 1) ? (int)(unsigned char)c : -1);
+#endif
 }
 
 static void
@@ -238,6 +243,7 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
 	bool use_copy = false;
 	size_t prefix_len;
 	XPtrV l = { NULL, 0, 0 };
+	struct columnise_opts co;
 
 	/*
 	 * Check if all matches are in the same directory (in this
@@ -257,7 +263,8 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
 				break;
 		/* All in same directory? */
 		if (i == nwords) {
-			while (prefix_len > 0 && words[0][prefix_len - 1] != '/')
+			while (prefix_len > 0 &&
+			    !mksh_cdirsep(words[0][prefix_len - 1]))
 				prefix_len--;
 			use_copy = true;
 			XPinit(l, nwords + 1);
@@ -271,7 +278,11 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
 	 */
 	x_putc('\r');
 	x_putc('\n');
-	pr_list(use_copy ? (char **)XPptrv(l) : words);
+	co.shf = shl_out;
+	co.linesep = '\n';
+	co.do_last = true;
+	co.prefcol = false;
+	pr_list(&co, use_copy ? (char **)XPptrv(l) : words);
 
 	if (use_copy)
 		/* not x_free_words() */
@@ -299,14 +310,14 @@ x_glob_hlp_add_qchar(char *cp)
 			 * 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;
 			}
@@ -333,7 +344,7 @@ x_glob_hlp_tilde_and_rem_qchar(char *s, bool magic_flag)
 	 * and if so, discern "~foo/bar" and "~/baz" from "~blah";
 	 * if we have a directory part (the former), try to expand
 	 */
-	if (*s == '~' && (cp = strchr(s, '/')) != NULL) {
+	if (*s == '~' && (cp = /* not sdirsep */ strchr(s, '/')) != NULL) {
 		/* ok, so split into "~foo"/"bar" or "~"/"baz" */
 		*cp++ = 0;
 		/* try to expand the tilde */
@@ -457,7 +468,7 @@ path_order_cmp(const void *aa, const void *bb)
 	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);
@@ -525,7 +536,7 @@ x_command_glob(int flags, char *toglob, char ***wordsp)
 		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];
@@ -542,8 +553,7 @@ x_command_glob(int flags, char *toglob, char ***wordsp)
 	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,
@@ -578,9 +588,9 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp,
 		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;
@@ -588,7 +598,7 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp,
 			 * like file globbing.
 			 */
 			for (p = start; p < end; p++)
-				if (buf[p] == '/')
+				if (mksh_cdirsep(buf[p]))
 					break;
 			iscmd = p == end;
 		}
@@ -639,11 +649,12 @@ x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp,
 		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
@@ -652,7 +663,7 @@ x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp,
 			}
 		}
 
-		if (*toglob == '~' && !vstrchr(toglob, '/')) {
+		if (*toglob == '~' && /* not vdirsep */ !vstrchr(toglob, '/')) {
 			/* neither for '~foo' (but '~foo/bar') */
 			*flagsp |= XCF_IS_NOSPACE;
 			goto dont_add_glob;
@@ -704,8 +715,8 @@ x_longest_prefix(int nwords, char * const * words)
 				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);
 }
@@ -737,17 +748,19 @@ x_basename(const char *s, const char *se)
 	const char *p;
 
 	if (se == NULL)
-		se = s + strlen(s);
+		se = strnul(s);
 	if (s == se)
 		return (0);
 
-	/* Skip trailing slashes */
-	for (p = se - 1; p > s && *p == '/'; p--)
-		;
-	for (; p > s && *p != '/'; p--)
-		;
-	if (*p == '/' && p + 1 < se)
-		p++;
+	/* skip trailing directory separators */
+	p = se - 1;
+	while (p > s && mksh_cdirsep(*p))
+		--p;
+	/* drop last component */
+	while (p > s && !mksh_cdirsep(*p))
+		--p;
+	if (mksh_cdirsep(*p) && p + 1 < se)
+		++p;
 
 	return (p - s);
 }
@@ -787,7 +800,7 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
 	while (sp) {
 		xp = Xstring(xs, xp);
 		if (!(p = cstrchr(sp, MKSH_PATHSEPC)))
-			p = sp + strlen(sp);
+			p = strnul(sp);
 		pathlen = p - sp;
 		if (pathlen) {
 			/*
@@ -846,8 +859,7 @@ x_escape(const char *s, size_t len, int (*putbuf_func)(const char *, size_t))
 	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;
@@ -896,11 +908,6 @@ struct x_defbindings {
 #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 */
 
@@ -941,6 +948,7 @@ static bool xlp_valid;		/* lastvis pointer was recalculated */
 static char **x_histp;		/* history position */
 static int x_nextcmd;		/* for newline-and-next */
 static char **x_histncp;	/* saved x_histp for " */
+static char **x_histmcp;	/* saved x_histp for " */
 static char *xmp;		/* mark pointer */
 static unsigned char x_last_command;
 static unsigned char (*x_tab)[X_TABSZ];	/* key definition */
@@ -978,6 +986,7 @@ static void x_bs3(char **);
 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
@@ -1023,56 +1032,56 @@ static const struct x_ftab x_ftab[] = {
 };
 
 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'	},
@@ -1135,7 +1144,7 @@ static struct x_defbindings const x_defbindings[] = {
 #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
 };
@@ -1155,6 +1164,7 @@ static void
 x_modified(void)
 {
 	if (!modified) {
+		x_histmcp = x_histp;
 		x_histp = histptr + 1;
 		modified = 1;
 	}
@@ -1177,17 +1187,19 @@ x_e_getmbc(char *sbuf)
 	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)
@@ -1231,7 +1243,7 @@ x_emacs(char *buf)
 	xlp_valid = true;
 	xmp = NULL;
 	x_curprefix = 0;
-	x_histp = histptr + 1;
+	x_histmcp = x_histp = histptr + 1;
 	x_last_command = XFUNC_error;
 
 	x_init_prompt(true);
@@ -1285,9 +1297,7 @@ x_emacs(char *buf)
 			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);
@@ -1306,11 +1316,11 @@ x_insert(int c)
 	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';
@@ -1368,7 +1378,7 @@ static int
 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);
@@ -1408,7 +1418,7 @@ x_del_back(int c MKSH_A_UNUSED)
 	ssize_t i = 0;
 
 	if (xcp == xbuf) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	do {
@@ -1434,7 +1444,7 @@ x_del_char(int c MKSH_A_UNUSED)
 	}
 
 	if (!i) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	x_delete(i, false);
@@ -1544,15 +1554,15 @@ x_bword(void)
 	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++;
 		}
@@ -1568,13 +1578,13 @@ x_fword(bool move)
 	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);
@@ -1607,7 +1617,7 @@ x_bs0(char *cp, char *lower_bound)
 {
 	if (UTFMODE)
 		while ((!lower_bound || (cp > lower_bound)) &&
-		    ((*(unsigned char *)cp & 0xC0) == 0x80))
+		    ((rtt2asc(*cp) & 0xC0) == 0x80))
 			--cp;
 	return (cp);
 }
@@ -1628,14 +1638,14 @@ x_size2(char *cp, char **dcp)
 {
 	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);
@@ -1660,9 +1670,9 @@ x_zotc3(char **cp)
 		/* 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);
@@ -1672,7 +1682,7 @@ static int
 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--) {
@@ -1689,7 +1699,7 @@ x_mv_forw(int c MKSH_A_UNUSED)
 	char *cp = xcp, *cp2;
 
 	if (xcp == xep) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	while (x_arg--) {
@@ -1710,13 +1720,13 @@ x_search_char_forw(int c MKSH_A_UNUSED)
 
 	*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);
 		}
 	}
@@ -1731,7 +1741,7 @@ x_search_char_back(int c MKSH_A_UNUSED)
 	bool b;
 
 	if (x_e_getmbc(tmp) < 0) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	for (; x_arg--; cp = p)
@@ -1739,7 +1749,7 @@ x_search_char_back(int c MKSH_A_UNUSED)
 			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)) ||
@@ -1772,12 +1782,11 @@ x_newline(int c MKSH_A_UNUSED)
 static int
 x_end_of_text(int c MKSH_A_UNUSED)
 {
-	unsigned char tmp;
-	char *cp = (void *)&tmp;
+	unsigned char tmp[1], *cp = tmp;
 
-	tmp = isedchar(edchars.eof) ? (unsigned char)edchars.eof :
-	    (unsigned char)CTRL('D');
-	x_zotc3(&cp);
+	*tmp = isedchar(edchars.eof) ? (unsigned char)edchars.eof :
+	    (unsigned char)CTRL_D;
+	x_zotc3((char **)&cp);
 	x_putc('\r');
 	x_putc('\n');
 	x_flush();
@@ -1836,7 +1845,7 @@ x_load_hist(char **hp)
 		sp = holdbufp;
 		modified = 0;
 	} else if (hp < history || hp > histptr) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return;
 	}
 	if (sp == NULL)
@@ -1846,7 +1855,7 @@ x_load_hist(char **hp)
 		strlcpy(holdbufp, xbuf, LINE);
 	strlcpy(xbuf, sp, xend - xbuf);
 	xbp = xbuf;
-	xep = xcp = xbuf + strlen(xbuf);
+	xep = xcp = strnul(xbuf);
 	x_adjust();
 	modified = 0;
 }
@@ -1854,9 +1863,11 @@ x_load_hist(char **hp)
 static int
 x_nl_next_com(int c MKSH_A_UNUSED)
 {
-	if (!x_histncp || (x_histp != x_histncp && x_histp != histptr + 1))
+	if (!modified)
+		x_histmcp = x_histp;
+	if (!x_histncp || (x_histmcp != x_histncp && x_histmcp != histptr + 1))
 		/* fresh start of ^O */
-		x_histncp = x_histp;
+		x_histncp = x_histmcp;
 	x_nextcmd = source->line - (histptr - x_histncp) + 1;
 	return (x_newline('\n'));
 }
@@ -1889,13 +1900,13 @@ x_search_hist(int c)
 		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;
@@ -1914,8 +1925,10 @@ x_search_hist(int c)
 				offset = -1;
 				break;
 			}
-			if (p > pat)
-				*--p = '\0';
+			if (p > pat) {
+				p = x_bs0(p - 1, pat);
+				*p = '\0';
+			}
 			if (p == pat)
 				offset = -1;
 			else
@@ -1925,7 +1938,7 @@ x_search_hist(int c)
 			/* 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';
@@ -1971,7 +1984,7 @@ x_search(char *pat, int sameline, int offset)
 			return (i);
 		}
 	}
-	x_e_putc2(7);
+	x_e_putc2(KSH_BEL);
 	x_histp = histptr;
 	return (-1);
 }
@@ -2077,7 +2090,7 @@ x_clrtoeol(int lastch, bool line_was_cleared)
 	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)
@@ -2151,11 +2164,11 @@ x_transpose(int c MKSH_A_UNUSED)
 	 * 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);
 		}
 		/*
@@ -2164,12 +2177,12 @@ x_transpose(int c MKSH_A_UNUSED)
 		 */
 		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);
@@ -2182,12 +2195,12 @@ x_transpose(int c MKSH_A_UNUSED)
 		 * 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);
@@ -2296,21 +2309,35 @@ x_meta_yank(int c MKSH_A_UNUSED)
 	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);
 }
 
@@ -2370,19 +2397,18 @@ x_mapin(const char *cp, Area *ap)
 	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';
@@ -2395,9 +2421,9 @@ x_mapout2(int c, char **buf)
 {
 	char *p = *buf;
 
-	if (ISCTRL(c)) {
+	if (ksh_isctrl(c)) {
 		*p++ = '^';
-		*p++ = UNCTRL(c);
+		*p++ = ksh_unctrl(c);
 	} else
 		*p++ = c;
 	*p = 0;
@@ -2420,9 +2446,9 @@ x_print(int prefix, int key)
 	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
@@ -2586,7 +2612,7 @@ x_kill_region(int c MKSH_A_UNUSED)
 	char *xr;
 
 	if (xmp == NULL) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	if (xmp > xcp) {
@@ -2608,7 +2634,7 @@ x_xchg_point_mark(int c MKSH_A_UNUSED)
 	char *tmp;
 
 	if (xmp == NULL) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	tmp = xmp;
@@ -2686,7 +2712,7 @@ x_expand(int c MKSH_A_UNUSED)
 	    &start, &end, &words);
 
 	if (nwords == 0) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return (KSTD);
 	}
 	x_goto(xbuf + start);
@@ -2696,7 +2722,7 @@ x_expand(int c MKSH_A_UNUSED)
 	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);
 		}
 	}
@@ -2720,7 +2746,7 @@ do_complete(
 	    &start, &end, &words);
 	/* no match */
 	if (nwords == 0) {
-		x_e_putc2(7);
+		x_e_putc2(KSH_BEL);
 		return;
 	}
 	if (type == CT_LIST) {
@@ -2778,7 +2804,7 @@ do_complete(
 	 * append a space if this is a single non-directory match
 	 * and not a parameter or homedir substitution
 	 */
-	if (nwords == 1 && words[0][nlen - 1] != '/' &&
+	if (nwords == 1 && !mksh_cdirsep(words[0][nlen - 1]) &&
 	    !(flags & XCF_IS_NOSPACE)) {
 		x_ins(T1space);
 	}
@@ -2877,9 +2903,10 @@ x_e_putc2(int c)
 {
 	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;
@@ -2894,9 +2921,10 @@ x_e_putc2(int c)
 				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':
@@ -2918,21 +2946,31 @@ x_e_putc3(const char **cp)
 {
 	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)) {
 			char *cp2;
 
 			width = utf_widthadj(*cp, (const char **)&cp2);
-			while (*cp < cp2)
-				x_putcf(*(*cp)++);
+			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)++);
 		} else {
 			(*cp)++;
 			x_putc(c);
 		}
 		switch (c) {
-		case 7:
+		case KSH_BEL:
 			break;
 		case '\r':
 		case '\n':
@@ -2976,7 +3014,7 @@ x_set_arg(int c)
 
 	/* 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 */
@@ -2986,7 +3024,7 @@ x_set_arg(int c)
 	}
 	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 {
@@ -3005,7 +3043,7 @@ x_comment(int c MKSH_A_UNUSED)
 	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;
@@ -3028,7 +3066,7 @@ x_version(int c MKSH_A_UNUSED)
 	strdupx(v, KSH_VERSION, ATEMP);
 
 	xbuf = xbp = xcp = v;
-	xend = xep = v + strlen(v);
+	xend = xep = strnul(v);
 	x_redraw('\r');
 	x_flush();
 
@@ -3056,7 +3094,7 @@ x_edit_line(int c MKSH_A_UNUSED)
 {
 	if (x_arg_defaulted) {
 		if (xep == xbuf) {
-			x_e_putc2(7);
+			x_e_putc2(KSH_BEL);
 			return (KSTD);
 		}
 		if (modified) {
@@ -3071,7 +3109,7 @@ x_edit_line(int c MKSH_A_UNUSED)
 		    "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
@@ -3111,7 +3149,7 @@ x_prev_histword(int c MKSH_A_UNUSED)
 		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);
 	}
@@ -3123,11 +3161,11 @@ x_prev_histword(int c MKSH_A_UNUSED)
 		/*
 		 * 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 {
@@ -3138,22 +3176,24 @@ x_prev_histword(int c MKSH_A_UNUSED)
 		/*
 		 * 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';
 		x_ins(cp);
 		*rcp = ch;
 	}
+	if (!modified)
+		x_histmcp = x_histp;
 	modified = m + 1;
 	return (KSTD);
 }
@@ -3197,14 +3237,14 @@ x_fold_case(int c)
 	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
@@ -3222,7 +3262,7 @@ x_fold_case(int c)
 		/*
 		 * 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);
@@ -3301,17 +3341,17 @@ x_mode(bool onoff)
 #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);
@@ -3345,6 +3385,7 @@ static int nextstate(int);
 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);
@@ -3371,12 +3412,10 @@ static void ed_mov_opt(int, char *);
 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.) */
@@ -3387,14 +3426,14 @@ static int x_vi_putbuf(const char *, size_t);
 #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	*/
@@ -3458,7 +3497,7 @@ static void		free_edstate(struct edstate *old);
 static struct edstate	ebuf;
 static struct edstate	undobuf;
 
-static struct edstate	*es;		/* current editor state */
+static struct edstate	*vs;		/* current Vi editing mode state */
 static struct edstate	*undo;
 
 static char *ibuf;			/* input buffer */
@@ -3522,7 +3561,7 @@ x_vi(char *buf)
 	undobuf.linelen = ebuf.linelen = 0;
 	undobuf.cursor = ebuf.cursor = 0;
 	undobuf.winleft = ebuf.winleft = 0;
-	es = &ebuf;
+	vs = &ebuf;
 	undo = &undobuf;
 
 	x_init_prompt(true);
@@ -3564,16 +3603,17 @@ x_vi(char *buf)
 		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 (es->linelen == 0) {
+				if (vs->linelen == 0) {
 					x_vi_zotc(c);
 					c = -1;
 					break;
@@ -3590,15 +3630,15 @@ x_vi(char *buf)
 	x_putc('\n');
 	x_flush();
 
-	if (c == -1 || (ssize_t)LINE <= es->linelen)
+	if (c == -1 || (ssize_t)LINE <= vs->linelen)
 		return (-1);
 
-	if (es->cbuf != buf)
-		memcpy(buf, es->cbuf, es->linelen);
+	if (vs->cbuf != buf)
+		memcpy(buf, vs->cbuf, vs->linelen);
 
-	buf[es->linelen++] = '\n';
+	buf[vs->linelen++] = '\n';
 
-	return (es->linelen);
+	return (vs->linelen);
 }
 
 static int
@@ -3623,7 +3663,7 @@ vi_hook(int ch)
 		default: ch = 0; goto vi_insert_failed;
 		}
 		if (insert != 0) {
-			if (ch == CTRL('v')) {
+			if (ch == CTRL_V) {
 				state = VLIT;
 				ch = '^';
 			}
@@ -3635,7 +3675,7 @@ vi_hook(int ch)
 				break;
 			case 0:
 				if (state == VLIT) {
-					es->cursor--;
+					vs->cursor--;
 					refresh(0);
 				} else
 					refresh(insert != 0);
@@ -3644,11 +3684,11 @@ vi_hook(int ch)
 				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 {
@@ -3657,8 +3697,8 @@ vi_hook(int ch)
 				state = nextstate(ch);
 				if (state == VSEARCH) {
 					save_cbuf();
-					es->cursor = 0;
-					es->linelen = 0;
+					vs->cursor = 0;
+					vs->linelen = 0;
 					if (putbuf(ch == '/' ? "/" : "?", 1,
 					    false) != 0)
 						return (-1);
@@ -3666,8 +3706,8 @@ vi_hook(int ch)
 				}
 				if (state == VVERSION) {
 					save_cbuf();
-					es->cursor = 0;
-					es->linelen = 0;
+					vs->cursor = 0;
+					vs->linelen = 0;
 					putbuf(KSH_VERSION,
 					    strlen(KSH_VERSION), false);
 					refresh(0);
@@ -3678,10 +3718,10 @@ vi_hook(int ch)
 
 	case VLIT:
 		if (is_bad(ch)) {
-			del_range(es->cursor, es->cursor + 1);
+			del_range(vs->cursor, vs->cursor + 1);
 			vi_error();
 		} else
-			es->cbuf[es->cursor++] = ch;
+			vs->cbuf[vs->cursor++] = ch;
 		refresh(1);
 		state = VNORMAL;
 		break;
@@ -3693,7 +3733,7 @@ vi_hook(int ch)
 		break;
 
 	case VARG1:
-		if (ksh_isdigit(ch))
+		if (ctype(ch, C_DIGIT))
 			argc1 = argc1 * 10 + ksh_numdig(ch);
 		else {
 			curcmd[cmdlen++] = ch;
@@ -3703,7 +3743,7 @@ vi_hook(int ch)
 
 	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);
@@ -3719,7 +3759,7 @@ vi_hook(int ch)
 		break;
 
 	case VARG2:
-		if (ksh_isdigit(ch))
+		if (ctype(ch, C_DIGIT))
 			argc2 = argc2 * 10 + ksh_numdig(ch);
 		else {
 			if (argc1 == 0)
@@ -3737,7 +3777,7 @@ vi_hook(int ch)
 		break;
 
 	case VXCH:
-		if (ch == CTRL('['))
+		if (ch == CTRL_BO)
 			state = VNORMAL;
 		else {
 			curcmd[cmdlen++] = ch;
@@ -3746,7 +3786,7 @@ vi_hook(int ch)
 		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) {
@@ -3761,11 +3801,11 @@ vi_hook(int ch)
 				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--;
-				es->linelen -= char_len(locpat[srchlen]);
-				es->cursor = es->linelen;
+				vs->linelen -= char_len(locpat[srchlen]);
+				vs->cursor = vs->linelen;
 				refresh(0);
 				return (0);
 			}
@@ -3774,8 +3814,8 @@ vi_hook(int ch)
 			refresh(0);
 		} else if (isched(ch, edchars.kill)) {
 			srchlen = 0;
-			es->linelen = 1;
-			es->cursor = 1;
+			vs->linelen = 1;
+			vs->cursor = 1;
 			refresh(0);
 			return (0);
 		} else if (isched(ch, edchars.werase)) {
@@ -3785,16 +3825,16 @@ vi_hook(int ch)
 			new_es.cursor = srchlen;
 			new_es.cbuf = locpat;
 
-			save_es = es;
-			es = &new_es;
+			save_es = vs;
+			vs = &new_es;
 			n = backword(1);
-			es = save_es;
+			vs = save_es;
 
 			i = (unsigned)srchlen;
 			while (--i >= n)
-				es->linelen -= char_len(locpat[i]);
+				vs->linelen -= char_len(locpat[i]);
 			srchlen = (int)n;
-			es->cursor = es->linelen;
+			vs->cursor = vs->linelen;
 			refresh(0);
 			return (0);
 		} else {
@@ -3802,18 +3842,18 @@ vi_hook(int ch)
 				vi_error();
 			else {
 				locpat[srchlen++] = ch;
-				if (ISCTRL(ch) && /* but not C1 */ ch < 0x80) {
-					if ((size_t)es->linelen + 2 >
-					    (size_t)es->cbufsize)
+				if (ksh_isctrl(ch)) {
+					if ((size_t)vs->linelen + 2 >
+					    (size_t)vs->cbufsize)
 						vi_error();
-					es->cbuf[es->linelen++] = '^';
-					es->cbuf[es->linelen++] = UNCTRL(ch);
+					vs->cbuf[vs->linelen++] = '^';
+					vs->cbuf[vs->linelen++] = ksh_unctrl(ch);
 				} else {
-					if (es->linelen >= es->cbufsize)
+					if (vs->linelen >= vs->cbufsize)
 						vi_error();
-					es->cbuf[es->linelen++] = ch;
+					vs->cbuf[vs->linelen++] = ch;
 				}
-				es->cursor = es->linelen;
+				vs->cursor = vs->linelen;
 				refresh(0);
 			}
 			return (0);
@@ -3826,18 +3866,18 @@ vi_hook(int ch)
 		switch (ch) {
 		case 'A':
 			/* the cursor may not be at the BOL */
-			if (!es->cursor)
+			if (!vs->cursor)
 				break;
 			/* nor further in the line than we can search for */
-			if ((size_t)es->cursor >= sizeof(srchpat) - 1)
-				es->cursor = sizeof(srchpat) - 2;
+			if ((size_t)vs->cursor >= sizeof(srchpat) - 1)
+				vs->cursor = sizeof(srchpat) - 2;
 			/* anchor the search pattern */
 			srchpat[0] = '^';
 			/* take the current line up to the cursor */
-			memmove(srchpat + 1, es->cbuf, es->cursor);
-			srchpat[es->cursor + 1] = '\0';
+			memmove(srchpat + 1, vs->cbuf, vs->cursor);
+			srchpat[vs->cursor + 1] = '\0';
 			/* set a magic flag */
-			argc1 = 2 + (int)es->cursor;
+			argc1 = 2 + (int)vs->cursor;
 			/* and emulate a backwards history search */
 			lastsearch = '/';
 			*curcmd = 'n';
@@ -3880,8 +3920,8 @@ vi_hook(int ch)
 			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 {
@@ -3919,7 +3959,7 @@ nextstate(int ch)
 		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);
@@ -3932,54 +3972,54 @@ vi_insert(int ch)
 {
 	int tcursor;
 
-	if (isched(ch, edchars.erase) || ch == CTRL('h')) {
+	if (isched(ch, edchars.erase) || ch == CTRL_H) {
 		if (insert == REPLACE) {
-			if (es->cursor == undo->cursor) {
+			if (vs->cursor == undo->cursor) {
 				vi_error();
 				return (0);
 			}
 			if (inslen > 0)
 				inslen--;
-			es->cursor--;
-			if (es->cursor >= undo->linelen)
-				es->linelen--;
+			vs->cursor--;
+			if (vs->cursor >= undo->linelen)
+				vs->linelen--;
 			else
-				es->cbuf[es->cursor] = undo->cbuf[es->cursor];
+				vs->cbuf[vs->cursor] = undo->cbuf[vs->cursor];
 		} else {
-			if (es->cursor == 0)
+			if (vs->cursor == 0)
 				return (0);
 			if (inslen > 0)
 				inslen--;
-			es->cursor--;
-			es->linelen--;
-			memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor + 1],
-			    es->linelen - es->cursor + 1);
+			vs->cursor--;
+			vs->linelen--;
+			memmove(&vs->cbuf[vs->cursor], &vs->cbuf[vs->cursor + 1],
+			    vs->linelen - vs->cursor + 1);
 		}
 		expanded = NONE;
 		return (0);
 	}
 	if (isched(ch, edchars.kill)) {
-		if (es->cursor != 0) {
+		if (vs->cursor != 0) {
 			inslen = 0;
-			memmove(es->cbuf, &es->cbuf[es->cursor],
-			    es->linelen - es->cursor);
-			es->linelen -= es->cursor;
-			es->cursor = 0;
+			memmove(vs->cbuf, &vs->cbuf[vs->cursor],
+			    vs->linelen - vs->cursor);
+			vs->linelen -= vs->cursor;
+			vs->cursor = 0;
 		}
 		expanded = NONE;
 		return (0);
 	}
 	if (isched(ch, edchars.werase)) {
-		if (es->cursor != 0) {
+		if (vs->cursor != 0) {
 			tcursor = backword(1);
-			memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
-			    es->linelen - es->cursor);
-			es->linelen -= es->cursor - tcursor;
-			if (inslen < es->cursor - tcursor)
+			memmove(&vs->cbuf[tcursor], &vs->cbuf[vs->cursor],
+			    vs->linelen - vs->cursor);
+			vs->linelen -= vs->cursor - tcursor;
+			if (inslen < vs->cursor - tcursor)
 				inslen = 0;
 			else
-				inslen -= es->cursor - tcursor;
-			es->cursor = tcursor;
+				inslen -= vs->cursor - tcursor;
+			vs->cursor = tcursor;
 		}
 		expanded = NONE;
 		return (0);
@@ -3989,7 +4029,7 @@ vi_insert(int ch)
 	 * 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':
@@ -3999,7 +4039,7 @@ vi_insert(int ch)
 	case '\n':
 		return (1);
 
-	case CTRL('['):
+	case CTRL_BO:
 		expanded = NONE;
 		if (first_insert) {
 			first_insert = false;
@@ -4010,26 +4050,25 @@ vi_insert(int ch)
 			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'):
-		print_expansions(es, 0);
+	case CTRL_E:
+		print_expansions(vs, 0);
 		break;
 
-	case CTRL('i'):
+	case CTRL_I:
 		if (Flag(FVITABCOMPLETE)) {
 			complete_word(0, 0);
 			break;
@@ -4038,17 +4077,17 @@ vi_insert(int ch)
 	/* end nonstandard vi commands } */
 
 	default:
-		if (es->linelen >= es->cbufsize - 1)
+		if (vs->linelen >= vs->cbufsize - 1)
 			return (-1);
 		ibuf[inslen++] = ch;
 		if (insert == INSERT) {
-			memmove(&es->cbuf[es->cursor + 1], &es->cbuf[es->cursor],
-			    es->linelen - es->cursor);
-			es->linelen++;
+			memmove(&vs->cbuf[vs->cursor + 1], &vs->cbuf[vs->cursor],
+			    vs->linelen - vs->cursor);
+			vs->linelen++;
 		}
-		es->cbuf[es->cursor++] = ch;
-		if (insert == REPLACE && es->cursor > es->linelen)
-			es->linelen++;
+		vs->cbuf[vs->cursor++] = ch;
+		if (insert == REPLACE && vs->cursor > vs->linelen)
+			vs->linelen++;
 		expanded = NONE;
 	}
 	return (0);
@@ -4067,29 +4106,29 @@ vi_cmd(int argcnt, const char *cmd)
 
 	if (is_move(*cmd)) {
 		if ((cur = domove(argcnt, cmd, 0)) >= 0) {
-			if (cur == es->linelen && cur != 0)
+			if (cur == vs->linelen && cur != 0)
 				cur--;
-			es->cursor = cur;
+			vs->cursor = cur;
 		} else
 			return (-1);
 	} else {
 		/* Don't save state in middle of macro.. */
 		if (is_undoable(*cmd) && !macro.p) {
-			undo->winleft = es->winleft;
-			memmove(undo->cbuf, es->cbuf, es->linelen);
-			undo->linelen = es->linelen;
-			undo->cursor = es->cursor;
+			undo->winleft = vs->winleft;
+			memmove(undo->cbuf, vs->cbuf, vs->linelen);
+			undo->linelen = vs->linelen;
+			undo->cursor = vs->cursor;
 			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;
@@ -4130,58 +4169,59 @@ vi_cmd(int argcnt, const char *cmd)
 			}
 			break;
 
-		case 'a':
+		case ord('a'):
 			modified = 1;
 			hnum = hlast;
-			if (es->linelen != 0)
-				es->cursor++;
+			if (vs->linelen != 0)
+				vs->cursor++;
 			insert = INSERT;
 			break;
 
-		case 'A':
+		case ord('A'):
 			modified = 1;
 			hnum = hlast;
 			del_range(0, 0);
-			es->cursor = es->linelen;
+			vs->cursor = vs->linelen;
 			insert = INSERT;
 			break;
 
-		case 'S':
-			es->cursor = domove(1, "^", 1);
-			del_range(es->cursor, es->linelen);
+		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... */
-		case 'c':
-		case 'd':
-		case 'y':
+
+			/* FALLTHROUGH */
+		case ord('c'):
+		case ord('d'):
+		case ord('y'):
 			if (*cmd == cmd[1]) {
-				c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
-				c2 = es->linelen;
+				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(es->cbuf[es->cursor])) {
+				if (*cmd == 'c' && ksh_eq(cmd[1], 'W', 'w') &&
+				    !ctype(vs->cbuf[vs->cursor], C_SPACE)) {
 					do {
 						--ncursor;
-					} while (ksh_isspace(es->cbuf[ncursor]));
+					} while (ctype(vs->cbuf[ncursor], C_SPACE));
 					ncursor++;
 				}
-				if (ncursor > es->cursor) {
-					c1 = es->cursor;
+				if (ncursor > vs->cursor) {
+					c1 = vs->cursor;
 					c2 = ncursor;
 				} else {
 					c1 = ncursor;
-					c2 = es->cursor;
+					c2 = vs->cursor;
 					if (cmd[1] == '%')
 						c2++;
 				}
@@ -4190,7 +4230,7 @@ vi_cmd(int argcnt, const char *cmd)
 				yank_range(c1, c2);
 			if (*cmd != 'y') {
 				del_range(c1, c2);
-				es->cursor = c1;
+				vs->cursor = c1;
 			}
 			if (*cmd == 'c') {
 				modified = 1;
@@ -4199,52 +4239,52 @@ vi_cmd(int argcnt, const char *cmd)
 			}
 			break;
 
-		case 'p':
+		case ord('p'):
 			modified = 1;
 			hnum = hlast;
-			if (es->linelen != 0)
-				es->cursor++;
+			if (vs->linelen != 0)
+				vs->cursor++;
 			while (putbuf(ybuf, yanklen, false) == 0 &&
 			    --argcnt > 0)
 				;
-			if (es->cursor != 0)
-				es->cursor--;
+			if (vs->cursor != 0)
+				vs->cursor--;
 			if (argcnt != 0)
 				return (-1);
 			break;
 
-		case 'P':
+		case ord('P'):
 			modified = 1;
 			hnum = hlast;
 			any = 0;
 			while (putbuf(ybuf, yanklen, false) == 0 &&
 			    --argcnt > 0)
 				any = 1;
-			if (any && es->cursor != 0)
-				es->cursor--;
+			if (any && vs->cursor != 0)
+				vs->cursor--;
 			if (argcnt != 0)
 				return (-1);
 			break;
 
-		case 'C':
+		case ord('C'):
 			modified = 1;
 			hnum = hlast;
-			del_range(es->cursor, es->linelen);
+			del_range(vs->cursor, vs->linelen);
 			insert = INSERT;
 			break;
 
-		case 'D':
-			yank_range(es->cursor, es->linelen);
-			del_range(es->cursor, es->linelen);
-			if (es->cursor != 0)
-				es->cursor--;
+		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
@@ -4257,22 +4297,22 @@ vi_cmd(int argcnt, const char *cmd)
 			}
 			break;
 
-		case 'i':
+		case ord('i'):
 			modified = 1;
 			hnum = hlast;
 			insert = INSERT;
 			break;
 
-		case 'I':
+		case ord('I'):
 			modified = 1;
 			hnum = hlast;
-			es->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 {
@@ -4281,9 +4321,9 @@ vi_cmd(int argcnt, const char *cmd)
 			}
 			break;
 
-		case 'k':
-		case '-':
-		case CTRL('p'):
+		case ord('k'):
+		case ord('-'):
+		case CTRL_P:
 			if (grabhist(modified, hnum - argcnt) < 0)
 				return (-1);
 			else {
@@ -4292,8 +4332,8 @@ vi_cmd(int argcnt, const char *cmd)
 			}
 			break;
 
-		case 'r':
-			if (es->linelen == 0)
+		case ord('r'):
+			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
 			hnum = hlast;
@@ -4302,85 +4342,85 @@ vi_cmd(int argcnt, const char *cmd)
 			else {
 				int n;
 
-				if (es->cursor + argcnt > es->linelen)
+				if (vs->cursor + argcnt > vs->linelen)
 					return (-1);
 				for (n = 0; n < argcnt; ++n)
-					es->cbuf[es->cursor + n] = cmd[1];
-				es->cursor += n - 1;
+					vs->cbuf[vs->cursor + n] = cmd[1];
+				vs->cursor += n - 1;
 			}
 			break;
 
-		case 'R':
+		case ord('R'):
 			modified = 1;
 			hnum = hlast;
 			insert = REPLACE;
 			break;
 
-		case 's':
-			if (es->linelen == 0)
+		case ord('s'):
+			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
 			hnum = hlast;
-			if (es->cursor + argcnt > es->linelen)
-				argcnt = es->linelen - es->cursor;
-			del_range(es->cursor, es->cursor + argcnt);
+			if (vs->cursor + argcnt > vs->linelen)
+				argcnt = vs->linelen - vs->cursor;
+			del_range(vs->cursor, vs->cursor + argcnt);
 			insert = INSERT;
 			break;
 
-		case 'v':
+		case ord('v'):
 			if (!argcnt) {
-				if (es->linelen == 0)
+				if (vs->linelen == 0)
 					return (-1);
 				if (modified) {
-					es->cbuf[es->linelen] = '\0';
-					histsave(&source->line, es->cbuf,
+					vs->cbuf[vs->linelen] = '\0';
+					histsave(&source->line, vs->cbuf,
 					    HIST_STORE, true);
 				} else
 					argcnt = source->line + 1 -
 					    (hlast - hnum);
 			}
 			if (argcnt)
-				shf_snprintf(es->cbuf, es->cbufsize, Tf_sd,
+				shf_snprintf(vs->cbuf, vs->cbufsize, Tf_sd,
 				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
 				    argcnt);
 			else
-				strlcpy(es->cbuf,
+				strlcpy(vs->cbuf,
 				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
-				    es->cbufsize);
-			es->linelen = strlen(es->cbuf);
+				    vs->cbufsize);
+			vs->linelen = strlen(vs->cbuf);
 			return (2);
 
-		case 'x':
-			if (es->linelen == 0)
+		case ord('x'):
+			if (vs->linelen == 0)
 				return (-1);
 			modified = 1;
 			hnum = hlast;
-			if (es->cursor + argcnt > es->linelen)
-				argcnt = es->linelen - es->cursor;
-			yank_range(es->cursor, es->cursor + argcnt);
-			del_range(es->cursor, es->cursor + argcnt);
+			if (vs->cursor + argcnt > vs->linelen)
+				argcnt = vs->linelen - vs->cursor;
+			yank_range(vs->cursor, vs->cursor + argcnt);
+			del_range(vs->cursor, vs->cursor + argcnt);
 			break;
 
-		case 'X':
-			if (es->cursor > 0) {
+		case ord('X'):
+			if (vs->cursor > 0) {
 				modified = 1;
 				hnum = hlast;
-				if (es->cursor < argcnt)
-					argcnt = es->cursor;
-				yank_range(es->cursor - argcnt, es->cursor);
-				del_range(es->cursor - argcnt, es->cursor);
-				es->cursor -= argcnt;
+				if (vs->cursor < argcnt)
+					argcnt = vs->cursor;
+				yank_range(vs->cursor - argcnt, vs->cursor);
+				del_range(vs->cursor - argcnt, vs->cursor);
+				vs->cursor -= argcnt;
 			} else
 				return (-1);
 			break;
 
-		case 'u':
-			t = es;
-			es = undo;
+		case ord('u'):
+			t = vs;
+			vs = undo;
 			undo = t;
 			break;
 
-		case 'U':
+		case ord('U'):
 			if (!modified)
 				return (-1);
 			if (grabhist(modified, ohnum) < 0)
@@ -4389,17 +4429,19 @@ vi_cmd(int argcnt, const char *cmd)
 			hnum = ohnum;
 			break;
 
-		case '?':
+		case ord('?'):
 			if (hnum == hlast)
 				hnum = -1;
 			/* ahhh */
-		case '/':
+
+			/* FALLTHROUGH */
+		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 == '?')
@@ -4422,11 +4464,11 @@ vi_cmd(int argcnt, const char *cmd)
 			}
 			if (argcnt >= 2) {
 				/* flag from cursor-up command */
-				es->cursor = argcnt - 2;
+				vs->cursor = argcnt - 2;
 				return (0);
 			}
 			break;
-		case '_':
+		case ord('_'):
 			{
 				bool inspace;
 				char *p, *sp;
@@ -4434,14 +4476,13 @@ vi_cmd(int argcnt, const char *cmd)
 				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)
@@ -4451,7 +4492,7 @@ vi_cmd(int argcnt, const char *cmd)
 					sp = p;
 					inspace = false;
 					while (*p) {
-						if (issp(*p))
+						if (ctype(*p, C_SPACE))
 							inspace = true;
 						else if (inspace) {
 							inspace = false;
@@ -4463,101 +4504,102 @@ vi_cmd(int argcnt, const char *cmd)
 				}
 				modified = 1;
 				hnum = hlast;
-				if (es->cursor != es->linelen)
-					es->cursor++;
-				while (*p && !issp(*p)) {
+				if (vs->cursor != vs->linelen)
+					vs->cursor++;
+				while (*p && !ctype(*p, C_SPACE)) {
 					argcnt++;
 					p++;
 				}
 				if (putbuf(T1space, 1, false) != 0 ||
 				    putbuf(sp, argcnt, false) != 0) {
-					if (es->cursor != 0)
-						es->cursor--;
+					if (vs->cursor != 0)
+						vs->cursor--;
 					return (-1);
 				}
 				insert = INSERT;
 			}
 			break;
 
-		case '~':
+		case ord('~'):
 			{
 				char *p;
 				int i;
 
-				if (es->linelen == 0)
+				if (vs->linelen == 0)
 					return (-1);
 				for (i = 0; i < argcnt; i++) {
-					p = &es->cbuf[es->cursor];
-					if (ksh_islower(*p)) {
+					p = &vs->cbuf[vs->cursor];
+					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);
 					}
-					if (es->cursor < es->linelen - 1)
-						es->cursor++;
+					if (vs->cursor < vs->linelen - 1)
+						vs->cursor++;
 				}
 				break;
 			}
 
-		case '#':
+		case ord('#'):
 			{
-				int ret = x_do_comment(es->cbuf, es->cbufsize,
-				    &es->linelen);
+				int ret = x_do_comment(vs->cbuf, vs->cbufsize,
+				    &vs->linelen);
 				if (ret >= 0)
-					es->cursor = 0;
+					vs->cursor = 0;
 				return (ret);
 			}
 
 		/* AT&T ksh */
-		case '=':
+		case ord('='):
 		/* Nonstandard vi/ksh */
-		case CTRL('e'):
-			print_expansions(es, 1);
+		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 (es->linelen != 0)
-				es->cursor++;
+			if (vs->linelen != 0)
+				vs->cursor++;
 			insert = INSERT;
 			return (0);
 		}
-		if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
-			es->cursor--;
+		if (insert == 0 && vs->cursor != 0 && vs->cursor >= vs->linelen)
+			vs->cursor--;
 	}
 	return (0);
 }
@@ -4568,48 +4610,47 @@ domove(int argcnt, const char *cmd, int sub)
 	int ncursor = 0, i = 0, t;
 	unsigned int bcount;
 
-	switch (*cmd) {
-	case 'b':
-		if (!sub && es->cursor == 0)
+	switch (ord(*cmd)) {
+	case ord('b'):
+		if (!sub && vs->cursor == 0)
 			return (-1);
 		ncursor = backword(argcnt);
 		break;
 
-	case 'B':
-		if (!sub && es->cursor == 0)
+	case ord('B'):
+		if (!sub && vs->cursor == 0)
 			return (-1);
 		ncursor = Backword(argcnt);
 		break;
 
-	case 'e':
-		if (!sub && es->cursor + 1 >= es->linelen)
+	case ord('e'):
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = endword(argcnt);
-		if (sub && ncursor < es->linelen)
+		if (sub && ncursor < vs->linelen)
 			ncursor++;
 		break;
 
-	case 'E':
-		if (!sub && es->cursor + 1 >= es->linelen)
+	case ord('E'):
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = Endword(argcnt);
-		if (sub && ncursor < es->linelen)
+		if (sub && ncursor < vs->linelen)
 			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];
-		/* drop through */
-
-	case ',':
-	case ';':
+		/* FALLTHROUGH */
+	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;
@@ -4620,81 +4661,78 @@ domove(int argcnt, const char *cmd, int sub)
 			ncursor++;
 		break;
 
-	case 'h':
-	case CTRL('h'):
-		if (!sub && es->cursor == 0)
+	case ord('h'):
+	case CTRL_H:
+		if (!sub && vs->cursor == 0)
 			return (-1);
-		ncursor = es->cursor - argcnt;
+		ncursor = vs->cursor - argcnt;
 		if (ncursor < 0)
 			ncursor = 0;
 		break;
 
-	case ' ':
-	case 'l':
-		if (!sub && es->cursor + 1 >= es->linelen)
+	case ord(' '):
+	case ord('l'):
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
-		if (es->linelen != 0) {
-			ncursor = es->cursor + argcnt;
-			if (ncursor > es->linelen)
-				ncursor = es->linelen;
+		if (vs->linelen != 0) {
+			ncursor = vs->cursor + argcnt;
+			if (ncursor > vs->linelen)
+				ncursor = vs->linelen;
 		}
 		break;
 
-	case 'w':
-		if (!sub && es->cursor + 1 >= es->linelen)
+	case ord('w'):
+		if (!sub && vs->cursor + 1 >= vs->linelen)
 			return (-1);
 		ncursor = forwword(argcnt);
 		break;
 
-	case 'W':
-		if (!sub && es->cursor + 1 >= es->linelen)
+	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 < es->linelen - 1 &&
-		    ksh_isspace(es->cbuf[ncursor]))
-			ncursor++;
+	case ord('^'):
+		ncursor = domovebeg();
 		break;
 
-	case '|':
+	case ord('|'):
 		ncursor = argcnt;
-		if (ncursor > es->linelen)
-			ncursor = es->linelen;
+		if (ncursor > vs->linelen)
+			ncursor = vs->linelen;
 		if (ncursor)
 			ncursor--;
 		break;
 
-	case '$':
-		if (es->linelen != 0)
-			ncursor = es->linelen;
+	case ord('$'):
+		if (vs->linelen != 0)
+			ncursor = vs->linelen;
 		else
 			ncursor = 0;
 		break;
 
-	case '%':
-		ncursor = es->cursor;
-		while (ncursor < es->linelen &&
-		    (i = bracktype(es->cbuf[ncursor])) == 0)
+	case ord('%'):
+		ncursor = vs->cursor;
+		while (ncursor < vs->linelen &&
+		    (i = bracktype(vs->cbuf[ncursor])) == 0)
 			ncursor++;
-		if (ncursor == es->linelen)
+		if (ncursor == vs->linelen)
 			return (-1);
 		bcount = 1;
 		do {
 			if (i > 0) {
-				if (++ncursor >= es->linelen)
+				if (++ncursor >= vs->linelen)
 					return (-1);
 			} else {
 				if (--ncursor < 0)
 					return (-1);
 			}
-			t = bracktype(es->cbuf[ncursor]);
+			t = bracktype(vs->cbuf[ncursor]);
 			if (t == i)
 				bcount++;
 			else if (t == -i)
@@ -4711,13 +4749,24 @@ domove(int argcnt, const char *cmd, int sub)
 }
 
 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)
 		if (putbuf(ibuf, inslen, tobool(insert == REPLACE)) != 0)
 			return (-1);
-	if (es->cursor > 0)
-		es->cursor--;
+	if (vs->cursor > 0)
+		vs->cursor--;
 	insert = 0;
 	return (0);
 }
@@ -4727,30 +4776,30 @@ yank_range(int a, int b)
 {
 	yanklen = b - a;
 	if (yanklen != 0)
-		memmove(ybuf, &es->cbuf[a], yanklen);
+		memmove(ybuf, &vs->cbuf[a], yanklen);
 }
 
 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:
@@ -4765,17 +4814,17 @@ bracktype(int ch)
 static void
 save_cbuf(void)
 {
-	memmove(holdbufp, es->cbuf, es->linelen);
-	holdlen = es->linelen;
+	memmove(holdbufp, vs->cbuf, vs->linelen);
+	holdlen = vs->linelen;
 	holdbufp[holdlen] = '\0';
 }
 
 static void
 restore_cbuf(void)
 {
-	es->cursor = 0;
-	es->linelen = holdlen;
-	memmove(es->cbuf, holdbufp, holdlen);
+	vs->cursor = 0;
+	vs->linelen = holdlen;
+	memmove(vs->cbuf, holdbufp, holdlen);
 }
 
 /* return a new edstate */
@@ -4826,28 +4875,28 @@ putbuf(const char *buf, ssize_t len, bool repl)
 	if (len == 0)
 		return (0);
 	if (repl) {
-		if (es->cursor + len >= es->cbufsize)
+		if (vs->cursor + len >= vs->cbufsize)
 			return (-1);
-		if (es->cursor + len > es->linelen)
-			es->linelen = es->cursor + len;
+		if (vs->cursor + len > vs->linelen)
+			vs->linelen = vs->cursor + len;
 	} else {
-		if (es->linelen + len >= es->cbufsize)
+		if (vs->linelen + len >= vs->cbufsize)
 			return (-1);
-		memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
-		    es->linelen - es->cursor);
-		es->linelen += len;
+		memmove(&vs->cbuf[vs->cursor + len], &vs->cbuf[vs->cursor],
+		    vs->linelen - vs->cursor);
+		vs->linelen += len;
 	}
-	memmove(&es->cbuf[es->cursor], buf, len);
-	es->cursor += len;
+	memmove(&vs->cbuf[vs->cursor], buf, len);
+	vs->cursor += len;
 	return (0);
 }
 
 static void
 del_range(int a, int b)
 {
-	if (es->linelen != b)
-		memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
-	es->linelen -= b - a;
+	if (vs->linelen != b)
+		memmove(&vs->cbuf[a], &vs->cbuf[b], vs->linelen - b);
+	vs->linelen -= b - a;
 }
 
 static int
@@ -4855,19 +4904,19 @@ findch(int ch, int cnt, bool forw, bool incl)
 {
 	int ncursor;
 
-	if (es->linelen == 0)
+	if (vs->linelen == 0)
 		return (-1);
-	ncursor = es->cursor;
+	ncursor = vs->cursor;
 	while (cnt--) {
 		do {
 			if (forw) {
-				if (++ncursor == es->linelen)
+				if (++ncursor == vs->linelen)
 					return (-1);
 			} else {
 				if (--ncursor < 0)
 					return (-1);
 			}
-		} while (es->cbuf[ncursor] != ch);
+		} while (vs->cbuf[ncursor] != ch);
 	}
 	if (!incl) {
 		if (forw)
@@ -4883,19 +4932,18 @@ forwword(int argcnt)
 {
 	int ncursor;
 
-	ncursor = es->cursor;
-	while (ncursor < es->linelen && argcnt--) {
-		if (ksh_isalnux(es->cbuf[ncursor]))
-			while (ncursor < es->linelen &&
-			    ksh_isalnux(es->cbuf[ncursor]))
+	ncursor = vs->cursor;
+	while (ncursor < vs->linelen && argcnt--) {
+		if (ctype(vs->cbuf[ncursor], C_ALNUX))
+			while (ncursor < vs->linelen &&
+			    ctype(vs->cbuf[ncursor], C_ALNUX))
 				ncursor++;
-		else if (!ksh_isspace(es->cbuf[ncursor]))
-			while (ncursor < es->linelen &&
-			    !ksh_isalnux(es->cbuf[ncursor]) &&
-			    !ksh_isspace(es->cbuf[ncursor]))
+		else if (!ctype(vs->cbuf[ncursor], C_SPACE))
+			while (ncursor < vs->linelen &&
+			    !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
 				ncursor++;
-		while (ncursor < es->linelen &&
-		    ksh_isspace(es->cbuf[ncursor]))
+		while (ncursor < vs->linelen &&
+		    ctype(vs->cbuf[ncursor], C_SPACE))
 			ncursor++;
 	}
 	return (ncursor);
@@ -4906,19 +4954,18 @@ backword(int argcnt)
 {
 	int ncursor;
 
-	ncursor = es->cursor;
+	ncursor = vs->cursor;
 	while (ncursor > 0 && argcnt--) {
-		while (--ncursor > 0 && ksh_isspace(es->cbuf[ncursor]))
+		while (--ncursor > 0 && ctype(vs->cbuf[ncursor], C_SPACE))
 			;
 		if (ncursor > 0) {
-			if (ksh_isalnux(es->cbuf[ncursor]))
+			if (ctype(vs->cbuf[ncursor], C_ALNUX))
 				while (--ncursor >= 0 &&
-				    ksh_isalnux(es->cbuf[ncursor]))
+				    ctype(vs->cbuf[ncursor], C_ALNUX))
 					;
 			else
 				while (--ncursor >= 0 &&
-				    !ksh_isalnux(es->cbuf[ncursor]) &&
-				    !ksh_isspace(es->cbuf[ncursor]))
+				    !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
 					;
 			ncursor++;
 		}
@@ -4931,20 +4978,19 @@ endword(int argcnt)
 {
 	int ncursor;
 
-	ncursor = es->cursor;
-	while (ncursor < es->linelen && argcnt--) {
-		while (++ncursor < es->linelen - 1 &&
-		    ksh_isspace(es->cbuf[ncursor]))
+	ncursor = vs->cursor;
+	while (ncursor < vs->linelen && argcnt--) {
+		while (++ncursor < vs->linelen - 1 &&
+		    ctype(vs->cbuf[ncursor], C_SPACE))
 			;
-		if (ncursor < es->linelen - 1) {
-			if (ksh_isalnux(es->cbuf[ncursor]))
-				while (++ncursor < es->linelen &&
-				    ksh_isalnux(es->cbuf[ncursor]))
+		if (ncursor < vs->linelen - 1) {
+			if (ctype(vs->cbuf[ncursor], C_ALNUX))
+				while (++ncursor < vs->linelen &&
+				    ctype(vs->cbuf[ncursor], C_ALNUX))
 					;
 			else
-				while (++ncursor < es->linelen &&
-				    !ksh_isalnux(es->cbuf[ncursor]) &&
-				    !ksh_isspace(es->cbuf[ncursor]))
+				while (++ncursor < vs->linelen &&
+				    !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE))
 					;
 			ncursor--;
 		}
@@ -4957,13 +5003,13 @@ Forwword(int argcnt)
 {
 	int ncursor;
 
-	ncursor = es->cursor;
-	while (ncursor < es->linelen && argcnt--) {
-		while (ncursor < es->linelen &&
-		    !ksh_isspace(es->cbuf[ncursor]))
+	ncursor = vs->cursor;
+	while (ncursor < vs->linelen && argcnt--) {
+		while (ncursor < vs->linelen &&
+		    !ctype(vs->cbuf[ncursor], C_SPACE))
 			ncursor++;
-		while (ncursor < es->linelen &&
-		    ksh_isspace(es->cbuf[ncursor]))
+		while (ncursor < vs->linelen &&
+		    ctype(vs->cbuf[ncursor], C_SPACE))
 			ncursor++;
 	}
 	return (ncursor);
@@ -4974,11 +5020,11 @@ Backword(int argcnt)
 {
 	int ncursor;
 
-	ncursor = es->cursor;
+	ncursor = vs->cursor;
 	while (ncursor > 0 && argcnt--) {
-		while (--ncursor >= 0 && ksh_isspace(es->cbuf[ncursor]))
+		while (--ncursor >= 0 && ctype(vs->cbuf[ncursor], C_SPACE))
 			;
-		while (ncursor >= 0 && !ksh_isspace(es->cbuf[ncursor]))
+		while (ncursor >= 0 && !ctype(vs->cbuf[ncursor], C_SPACE))
 			ncursor--;
 		ncursor++;
 	}
@@ -4990,14 +5036,14 @@ Endword(int argcnt)
 {
 	int ncursor;
 
-	ncursor = es->cursor;
-	while (ncursor < es->linelen - 1 && argcnt--) {
-		while (++ncursor < es->linelen - 1 &&
-		    ksh_isspace(es->cbuf[ncursor]))
+	ncursor = vs->cursor;
+	while (ncursor < vs->linelen - 1 && argcnt--) {
+		while (++ncursor < vs->linelen - 1 &&
+		    ctype(vs->cbuf[ncursor], C_SPACE))
 			;
-		if (ncursor < es->linelen - 1) {
-			while (++ncursor < es->linelen &&
-			    !ksh_isspace(es->cbuf[ncursor]))
+		if (ncursor < vs->linelen - 1) {
+			while (++ncursor < vs->linelen &&
+			    !ctype(vs->cbuf[ncursor], C_SPACE))
 				;
 			ncursor--;
 		}
@@ -5024,10 +5070,10 @@ grabhist(int save, int n)
 	}
 	if (save)
 		save_cbuf();
-	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
-		es->linelen = es->cbufsize - 1;
-	memmove(es->cbuf, hptr, es->linelen);
-	es->cursor = 0;
+	if ((vs->linelen = strlen(hptr)) >= vs->cbufsize)
+		vs->linelen = vs->cbufsize - 1;
+	memmove(vs->cbuf, hptr, vs->linelen);
+	vs->cursor = 0;
 	ohnum = n;
 	return (0);
 }
@@ -5058,10 +5104,10 @@ grabsearch(int save, int start, int fwd, const char *pat)
 		save_cbuf();
 	histnum(hist);
 	hptr = *histpos();
-	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
-		es->linelen = es->cbufsize - 1;
-	memmove(es->cbuf, hptr, es->linelen);
-	es->cursor = 0;
+	if ((vs->linelen = strlen(hptr)) >= vs->cbufsize)
+		vs->linelen = vs->cbufsize - 1;
+	memmove(vs->cbuf, hptr, vs->linelen);
+	vs->cursor = 0;
 	return (hist);
 }
 
@@ -5096,12 +5142,12 @@ outofwin(void)
 {
 	int cur, col;
 
-	if (es->cursor < es->winleft)
+	if (vs->cursor < vs->winleft)
 		return (1);
 	col = 0;
-	cur = es->winleft;
-	while (cur < es->cursor)
-		col = newcol((unsigned char)es->cbuf[cur++], col);
+	cur = vs->winleft;
+	while (cur < vs->cursor)
+		col = newcol((unsigned char)vs->cbuf[cur++], col);
 	if (col >= winwidth)
 		return (1);
 	return (0);
@@ -5116,19 +5162,19 @@ rewindow(void)
 
 	holdcur1 = holdcur2 = tcur = 0;
 	holdcol1 = holdcol2 = tcol = 0;
-	while (tcur < es->cursor) {
+	while (tcur < vs->cursor) {
 		if (tcol - holdcol2 > winwidth / 2) {
 			holdcur1 = holdcur2;
 			holdcol1 = holdcol2;
 			holdcur2 = tcur;
 			holdcol2 = tcol;
 		}
-		tcol = newcol((unsigned char)es->cbuf[tcur++], tcol);
+		tcol = newcol((unsigned char)vs->cbuf[tcur++], tcol);
 	}
 	while (tcol - holdcol1 > winwidth / 2)
-		holdcol1 = newcol((unsigned char)es->cbuf[holdcur1++],
+		holdcol1 = newcol((unsigned char)vs->cbuf[holdcur1++],
 		    holdcol1);
-	es->winleft = holdcur1;
+	vs->winleft = holdcur1;
 }
 
 static int
@@ -5149,21 +5195,21 @@ display(char *wb1, char *wb2, int leftside)
 	int moreright;
 
 	col = 0;
-	cur = es->winleft;
+	cur = vs->winleft;
 	moreright = 0;
 	twb1 = wb1;
-	while (col < winwidth && cur < es->linelen) {
-		if (cur == es->cursor && leftside)
+	while (col < winwidth && cur < vs->linelen) {
+		if (cur == vs->cursor && leftside)
 			ncol = col + pwidth;
-		if ((ch = es->cbuf[cur]) == '\t')
+		if ((ch = vs->cbuf[cur]) == '\t')
 			do {
 				*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 {
@@ -5171,11 +5217,11 @@ display(char *wb1, char *wb2, int leftside)
 				col++;
 			}
 		}
-		if (cur == es->cursor && !leftside)
+		if (cur == vs->cursor && !leftside)
 			ncol = col + pwidth - 1;
 		cur++;
 	}
-	if (cur == es->cursor)
+	if (cur == vs->cursor)
 		ncol = col + pwidth;
 	if (col < winwidth) {
 		while (col < winwidth) {
@@ -5201,13 +5247,13 @@ display(char *wb1, char *wb2, int leftside)
 		twb2++;
 		col++;
 	}
-	if (es->winleft > 0 && moreright)
+	if (vs->winleft > 0 && moreright)
 		/*
 		 * POSIX says to use * for this but that is a globbing
 		 * character and may confuse people; + is more innocuous
 		 */
 		mc = '+';
-	else if (es->winleft > 0)
+	else if (vs->winleft > 0)
 		mc = '<';
 	else if (moreright)
 		mc = '>';
@@ -5255,7 +5301,7 @@ expand_word(int cmd)
 
 	/* Undo previous expansion */
 	if (cmd == 0 && expanded == EXPAND && buf) {
-		restore_edstate(es, buf);
+		restore_edstate(vs, buf);
 		buf = 0;
 		expanded = NONE;
 		return (0);
@@ -5266,17 +5312,17 @@ expand_word(int cmd)
 	}
 
 	i = XCF_COMMAND_FILE | XCF_FULLPATH;
-	nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor,
+	nwords = x_cf_glob(&i, vs->cbuf, vs->linelen, vs->cursor,
 	    &start, &end, &words);
 	if (nwords == 0) {
 		vi_error();
 		return (-1);
 	}
 
-	buf = save_edstate(es);
+	buf = save_edstate(vs);
 	expanded = EXPAND;
 	del_range(start, end);
-	es->cursor = start;
+	vs->cursor = start;
 	i = 0;
 	while (i < nwords) {
 		if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
@@ -5290,7 +5336,7 @@ expand_word(int cmd)
 	}
 	i = buf->cursor - end;
 	if (rval == 0 && i > 0)
-		es->cursor += i;
+		vs->cursor += i;
 	modified = 1;
 	hnum = hlast;
 	insert = INSERT;
@@ -5316,7 +5362,7 @@ complete_word(int cmd, int count)
 		return (0);
 	}
 	if (cmd == 0 && expanded == PRINT && buf) {
-		restore_edstate(es, buf);
+		restore_edstate(vs, buf);
 		buf = 0;
 		expanded = NONE;
 		return (0);
@@ -5333,7 +5379,7 @@ complete_word(int cmd, int count)
 	flags = XCF_COMMAND_FILE;
 	if (count)
 		flags |= XCF_FULLPATH;
-	nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor,
+	nwords = x_cf_glob(&flags, vs->cbuf, vs->linelen, vs->cursor,
 	    &start, &end, &words);
 	if (nwords == 0) {
 		vi_error();
@@ -5378,9 +5424,9 @@ complete_word(int cmd, int count)
 		is_unique = nwords == 1;
 	}
 
-	buf = save_edstate(es);
+	buf = save_edstate(vs);
 	del_range(start, end);
-	es->cursor = start;
+	vs->cursor = start;
 
 	/*
 	 * escape all shell-sensitive characters and put the result into
@@ -5399,7 +5445,7 @@ complete_word(int cmd, int count)
 		 * append a space if this is a non-directory match
 		 * and not a parameter or homedir substitution
 		 */
-		if (match_len > 0 && match[match_len - 1] != '/' &&
+		if (match_len > 0 && !mksh_cdirsep(match[match_len - 1]) &&
 		    !(flags & XCF_IS_NOSPACE))
 			rval = putbuf(T1space, 1, false);
 	}
@@ -5433,24 +5479,26 @@ print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
 	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();
 }
 
@@ -5532,12 +5580,13 @@ x_eval_region_helper(const char *cmd, size_t len)
 	if (!kshsetjmp(e->jbuf)) {
 		char *wds = alloc(len + 3, ATEMP);
 
-		wds[0] = FUNSUB;
+		wds[0] = FUNASUB;
 		memcpy(wds + 1, cmd, len);
 		wds[len + 1] = '\0';
 		wds[len + 2] = EOS;
 
 		cp = evalstr(wds, DOSCALAR);
+		afree(wds, ATEMP);
 		strdupx(cp, cp, AEDIT);
 	} else
 		cp = NULL;
@@ -5574,7 +5623,7 @@ x_eval_region(int c MKSH_A_UNUSED)
 	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/eval.c b/eval.c
index bc77a54..7a892c0 100644
--- a/eval.c
+++ b/eval.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.193 2016/09/01 12:59:09 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 @@ typedef struct {
 #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 @@ expand(
 		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)
@@ -301,25 +307,36 @@ expand(
 					word = IFS_WORD;
 				quote = st->quotew;
 				continue;
+			case COMASUB:
 			case COMSUB:
+			case FUNASUB:
 			case FUNSUB:
 			case VALSUB:
 				tilde_ok = 0;
 				if (f & DONTRUNCOMMAND) {
 					word = IFS_WORD;
 					*dp++ = '$';
-					*dp++ = c == COMSUB ? '(' : '{';
-					if (c != COMSUB)
-						*dp++ = c == FUNSUB ? ' ' : '|';
+					switch (c) {
+					case COMASUB:
+					case COMSUB:
+						*dp++ = '(';
+						c = ord(')');
+						break;
+					case FUNASUB:
+					case FUNSUB:
+					case VALSUB:
+						*dp++ = '{';
+						*dp++ = c == VALSUB ? '|' : ' ';
+						c = ord('}');
+						break;
+					}
 					while (*sp != '\0') {
 						Xcheck(ds, dp);
 						*dp++ = *sp++;
 					}
-					if (c != COMSUB) {
+					if (c == ord('}'))
 						*dp++ = ';';
-						*dp++ = '}';
-					} else
-						*dp++ = ')';
+					*dp++ = c;
 				} else {
 					type = comsub(&x, sp, c);
 					if (type != XBASE && (f & DOBLANK))
@@ -418,12 +435,12 @@ expand(
 					/* 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);
@@ -431,7 +448,7 @@ expand(
 						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;
 
@@ -439,13 +456,13 @@ expand(
 						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;
@@ -478,8 +495,8 @@ expand(
 						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;
@@ -489,18 +506,18 @@ expand(
 						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;
@@ -520,21 +537,21 @@ expand(
 							 */
 							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;
@@ -552,7 +569,7 @@ expand(
 							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;
@@ -561,7 +578,7 @@ expand(
 							}
 						/* now anchor the end of the match */
 						p = end;
-						if (fpat != '%')
+						if (ord(fpat) != ord('%'))
 							while (p >= sbeg) {
 								bool gotmatch;
 
@@ -576,7 +593,7 @@ expand(
 						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,
@@ -589,11 +606,11 @@ expand(
 						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);
@@ -605,8 +622,8 @@ expand(
 						afree(ws, ATEMP);
 						goto do_CSUBST;
 					    }
-					case '#':
-					case '%':
+					case ord('#'):
+					case ord('%'):
 						/* ! DOBLANK,DOBRACE */
 						f = (f & DONTRUNCOMMAND) |
 						    DOPAT | DOTILDE |
@@ -620,18 +637,17 @@ expand(
 						 */
 						if (!Flag(FSH)) {
 							*dp++ = MAGIC;
-							*dp++ = 0x80 | '@';
+							*dp++ = ord(0x80 | '@');
 						}
 						break;
-					case '=':
+					case ord('='):
 						/*
-						 * Enabling tilde expansion
-						 * after :s here is
-						 * non-standard ksh, but is
-						 * consistent with rules for
-						 * other assignments. Not
-						 * sure what POSIX thinks of
-						 * this.
+						 * Tilde expansion for string
+						 * variables in POSIX mode is
+						 * governed by Austinbug 351.
+						 * In non-POSIX mode historic
+						 * ksh behaviour (enable it!)
+						 * us followed.
 						 * Not doing tilde expansion
 						 * for integer variables is a
 						 * non-POSIX thing - makes
@@ -640,7 +656,7 @@ expand(
 						 */
 						if (!(x.var->flag & INTEGER))
 							f |= DOASNTILDE | DOTILDE;
-						f |= DOTEMP;
+						f |= DOTEMP | DOSCALAR;
 						/*
 						 * These will be done after the
 						 * value has been assigned.
@@ -648,7 +664,7 @@ expand(
 						f &= ~(DOBLANK|DOGLOB|DOBRACE);
 						tilde_ok = 1;
 						break;
-					case '?':
+					case ord('?'):
 						if (*sp == CSUBST)
 							errorf("%s: parameter null or not set",
 							    st->var->name);
@@ -682,9 +698,9 @@ expand(
 				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;
@@ -714,7 +730,7 @@ expand(
 						doblank++;
 					st = st->prev;
 					continue;
-				case '=':
+				case ord('='):
 					/*
 					 * Restore our position and substitute
 					 * the value of st->var (may not be
@@ -747,17 +763,17 @@ expand(
 					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;
@@ -835,7 +851,7 @@ expand(
 						doblank--;
 					continue;
 				}
-				c = ifs0;
+				c = ord(ifs0);
 				if ((f & DOHEREDOC)) {
 					/* pseudo-field-split reliably */
 					if (c == 0)
@@ -880,10 +896,27 @@ expand(
 				c = '\n';
 				--newlines;
 			} else {
-				while ((c = shf_getc(x.u.shf)) == 0 || c == '\n')
+				while ((c = shf_getc(x.u.shf)) == 0 ||
+				    ctype(c, C_NL)) {
+#ifdef MKSH_WITH_TEXTMODE
+					if (c == '\r') {
+						c = shf_getc(x.u.shf);
+						switch (c) {
+						case '\n':
+							break;
+						default:
+							shf_ungetc(c, x.u.shf);
+							/* FALLTHROUGH */
+						case -1:
+							c = '\r';
+							break;
+						}
+					}
+#endif
 					if (c == '\n')
 						/* save newlines */
 						newlines++;
+				}
 				if (newlines && c != -1) {
 					shf_ungetc(c, x.u.shf);
 					c = '\n';
@@ -969,11 +1002,11 @@ expand(
 			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
@@ -981,28 +1014,29 @@ expand(
 					 */
 					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) {
@@ -1010,13 +1044,13 @@ expand(
 						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.
@@ -1088,7 +1122,7 @@ varsub(Expand *xp, const char *sp, const char *word,
 	struct tbl *vp;
 	bool zero_ok = false;
 
-	if ((stype = sp[0]) == '\0')
+	if ((stype = ord(sp[0])) == '\0')
 		/* Bad variable name */
 		return (-1);
 
@@ -1098,20 +1132,20 @@ varsub(Expand *xp, const char *sp, const char *word,
 	 * ${#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))
@@ -1120,14 +1154,14 @@ varsub(Expand *xp, const char *sp, const char *word,
 				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 */
@@ -1141,7 +1175,7 @@ varsub(Expand *xp, const char *sp, const char *word,
 					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;
@@ -1162,11 +1196,11 @@ varsub(Expand *xp, const char *sp, const char *word,
 		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;
 		}
@@ -1179,91 +1213,98 @@ varsub(Expand *xp, const char *sp, const char *word,
 
 	/* 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 (ctype(c, C_SUBOP2)) {
+	} 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 */
-		slen += 2;
-		stype |= 0x100;
-		if (word[slen] == CHAR) {
-			stype |= word[slen + 1];
-			slen += 2;
+		switch (c = ord(word[slen + 2]) == CHAR ?
+		    ord(word[slen + 3]) : 0) {
+		case ord('#'):
+		case ord('/'):
+		case ord('Q'):
+			break;
+		default:
+			return (-1);
 		}
+		stype |= STYPE_AT | c;
+		slen += 4;
 	} else if (stype)
 		/* : is not ok */
 		return (-1);
 	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;
@@ -1273,45 +1314,45 @@ varsub(Expand *xp, const char *sp, const char *word,
 		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) && (ctype(c, C_SUBOP2) ||
-	    (((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 &&
-	    (ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
+	    (ctype(c, C_SUB2) || (state != XBASE && c != ord('+'))))
 		errorf(Tf_parm, sp);
 	*stypep = stype;
 	*slenp = slen;
@@ -1322,17 +1363,28 @@ varsub(Expand *xp, const char *sp, const char *word,
  * Run the command in $(...) and read its output.
  */
 static int
-comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED)
+comsub(Expand *xp, const char *cp, int fn)
 {
 	Source *s, *sold;
 	struct op *t;
 	struct shf *shf;
+	bool doalias = false;
 	uint8_t old_utfmode = UTFMODE;
 
+	switch (fn) {
+	case COMASUB:
+		fn = COMSUB;
+		if (0)
+			/* FALLTHROUGH */
+	case FUNASUB:
+		  fn = FUNSUB;
+		doalias = true;
+	}
+
 	s = pushs(SSTRING, ATEMP);
 	s->start = s->str = cp;
 	sold = source;
-	t = compile(s, true);
+	t = compile(s, true, doalias);
 	afree(s, ATEMP);
 	source = sold;
 
@@ -1363,7 +1415,7 @@ comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED)
 			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';
@@ -1438,8 +1490,8 @@ trimsub(char *str, char *pat, int how)
 	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';
@@ -1451,7 +1503,7 @@ trimsub(char *str, char *pat, int how)
 			*p = c;
 		}
 		break;
-	case '#'|0x80:
+	case ord('#') | STYPE_DBL:
 		/* longest match at beginning */
 		for (p = end; p >= str; p--) {
 			c = *p; *p = '\0';
@@ -1463,7 +1515,7 @@ trimsub(char *str, char *pat, int how)
 			*p = c;
 		}
 		break;
-	case '%':
+	case ord('%'):
 		/* shortest match at end */
 		p = end;
 		while (p >= str) {
@@ -1471,7 +1523,7 @@ trimsub(char *str, char *pat, int how)
 				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;
@@ -1479,7 +1531,7 @@ trimsub(char *str, char *pat, int how)
 				--p;
 		}
 		break;
-	case '%'|0x80:
+	case ord('%') | STYPE_DBL:
 		/* longest match at end */
 		for (p = str; p <= end; p++)
 			if (gmatchx(p, pat, false)) {
@@ -1510,7 +1562,7 @@ glob(char *cp, XPtrV *wp, bool markdirs)
 		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
@@ -1576,7 +1628,7 @@ globit(XString *xs,	/* dest string */
 			 * SunOS 4.1.3 does this...
 			 */
 			if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp) &&
-			    xp[-1] == '/' && !S_ISDIR(lstatb.st_mode) &&
+			    mksh_cdirsep(xp[-1]) && !S_ISDIR(lstatb.st_mode) &&
 			    (!S_ISLNK(lstatb.st_mode) ||
 			    stat_check() < 0 || !S_ISDIR(statb.st_mode)))
 				return;
@@ -1586,7 +1638,7 @@ globit(XString *xs,	/* dest string */
 			 * directory
 			 */
 			if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) &&
-			    xp > Xstring(*xs, xp) && xp[-1] != '/' &&
+			    xp > Xstring(*xs, xp) && !mksh_cdirsep(xp[-1]) &&
 			    (S_ISDIR(lstatb.st_mode) ||
 			    (S_ISLNK(lstatb.st_mode) && stat_check() > 0 &&
 			    S_ISDIR(statb.st_mode)))) {
@@ -1601,11 +1653,11 @@ globit(XString *xs,	/* dest string */
 
 	if (xp > Xstring(*xs, xp))
 		*xp++ = '/';
-	while (*sp == '/') {
+	while (mksh_cdirsep(*sp)) {
 		Xcheck(*xs, xp);
 		*xp++ = *sp++;
 	}
-	np = strchr(sp, '/');
+	np = mksh_sdirsep(sp);
 	if (np != NULL) {
 		se = np;
 		/* don't assume '/', can be multiple kinds */
@@ -1613,7 +1665,7 @@ globit(XString *xs,	/* dest string */
 		*np++ = '\0';
 	} else {
 		odirsep = '\0'; /* keep gcc quiet */
-		se = sp + strlen(sp);
+		se = strnul(sp);
 	}
 
 
@@ -1624,10 +1676,10 @@ globit(XString *xs,	/* dest string */
 	 * 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 {
@@ -1656,9 +1708,8 @@ globit(XString *xs,	/* dest string */
 			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);
@@ -1683,7 +1734,7 @@ debunk(char *dp, const char *sp, size_t dlen)
 		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: *+?@! */
@@ -1713,8 +1764,8 @@ maybe_expand_tilde(const char *p, XString *dsp, char **dpp, bool isassign)
 
 	Xinit(ts, tp, 16, ATEMP);
 	/* : only for DOASNTILDE form */
-	while (p[0] == CHAR && p[1] != '/' && (!isassign || p[1] != ':'))
-	{
+	while (p[0] == CHAR && /* not cdirsep */ p[1] != '/' &&
+	    (!isassign || p[1] != ':')) {
 		Xcheck(ts, tp);
 		*tp++ = p[1];
 		p += 2;
@@ -1812,7 +1863,7 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
 	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;
 
@@ -1823,9 +1874,9 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
 		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;
@@ -1857,9 +1908,9 @@ alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
 	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/exec.c b/exec.c
index 11c8a80..8330174 100644
--- a/exec.c
+++ b/exec.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.182 2016/10/02 22:21:46 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.201 2017/10/11 21:09:24 tg Exp $");
 
 #ifndef MKSH_DEFAULT_EXECSHELL
 #define MKSH_DEFAULT_EXECSHELL	MKSH_UNIXROOT "/bin/sh"
@@ -376,9 +376,8 @@ execute(struct op * volatile t,
 		if (t->right == NULL)
 			/* should be error */
 			break;
-		rv = execute(t->left, XERROK, NULL) == 0 ?
-		    execute(t->right->left, flags & XERROK, xerrok) :
-		    execute(t->right->right, flags & XERROK, xerrok);
+		rv = execute(execute(t->left, XERROK, NULL) == 0 ?
+		    t->right->left : t->right->right, flags & XERROK, xerrok);
 		break;
 
 	case TCASE:
@@ -555,6 +554,9 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap,
 				}
 			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;
 
@@ -672,7 +674,7 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap,
 		rv = subst_exstat;
 		goto Leave;
 	} else if (!tp) {
-		if (Flag(FRESTRICTED) && vstrchr(cp, '/')) {
+		if (Flag(FRESTRICTED) && mksh_vdirsep(cp)) {
 			warningf(true, Tf_sD_s, cp, "restricted");
 			rv = 1;
 			goto Leave;
@@ -806,7 +808,7 @@ comexec(struct op *t, struct tbl * volatile tp, const char **ap,
 			/* NOTREACHED */
 		default:
 			quitenv(NULL);
-			internal_errorf(Tf_sd, "CFUNC", i);
+			internal_errorf(Tunexpected_type, Tunwind, Tfunction, i);
 		}
 		break;
 	}
@@ -886,9 +888,14 @@ scriptexec(struct op *tp, const char **ap)
 #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)
+		setmode(fd, O_TEXT);
+#endif
 		/* read first couple of octets from file */
 		n = read(fd, buf, sizeof(buf) - 1);
 		close(fd);
@@ -903,7 +910,7 @@ scriptexec(struct op *tp, const char **ap)
 		    (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)
@@ -918,13 +925,13 @@ scriptexec(struct op *tp, const char **ap)
 			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')
@@ -932,20 +939,36 @@ scriptexec(struct op *tp, const char **ap)
 		/* 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)
 				*tp->args-- = (char *)cp;
 		}
+#ifdef __OS2__
+		/*
+		 * On OS/2, the directory structure differs from normal
+		 * Unix, which can make many scripts whose shebang
+		 * hardcodes the path to an interpreter fail (and there
+		 * might be no /usr/bin/env); for user convenience, if
+		 * the specified interpreter is not usable, do a PATH
+		 * search to find it.
+		 */
+		if (mksh_vdirsep(sh) && !search_path(sh, path, X_OK, NULL)) {
+			cp = search_path(_getname(sh), path, X_OK, NULL);
+			if (cp)
+				sh = cp;
+		}
+#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,
@@ -964,6 +987,20 @@ scriptexec(struct op *tp, const char **ap)
 		    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"))) {
+			/* execute .cmd and .bat with OS2_SHELL, usually CMD.EXE */
+			sh = str_val(global("OS2_SHELL"));
+			*tp->args-- = "/c";
+			/* convert slahes to backslashes */
+			for (cp = tp->str; *cp; cp++) {
+				if (*cp == '/')
+					*cp = '\\';
+			}
+		}
+#endif
  nomagic:
 		;
 	}
@@ -978,13 +1015,17 @@ scriptexec(struct op *tp, const char **ap)
 	errorf(Tf_sD_sD_s, tp->str, sh, cstrerror(errno));
 }
 
+/* actual 'builtin' built-in utility call is handled in comexec() */
 int
-shcomexec(const char **wp)
+c_builtin(const char **wp)
 {
-	struct tbl *tp;
+	return (call_builtin(get_builtin(*wp), wp, Tbuiltin, false));
+}
 
-	tp = ktsearch(&builtins, *wp, hash(*wp));
-	return (call_builtin(tp, wp, "shcomexec", false));
+struct tbl *
+get_builtin(const char *s)
+{
+	return (s && *s ? ktsearch(&builtins, s, hash(s)) : NULL);
 }
 
 /*
@@ -1090,6 +1131,14 @@ builtin(const char *name, int (*func) (const char **))
 		/* external utility overrides built-in utility, with flags */
 		flag |= LOW_BI;
 		break;
+	case '-':
+		/* is declaration utility if argv[1] is one (POSIX: command) */
+		flag |= DECL_FWDR;
+		break;
+	case '^':
+		/* is declaration utility (POSIX: export, readonly) */
+		flag |= DECL_UTIL;
+		break;
 	default:
 		goto flags_seen;
 	}
@@ -1123,7 +1172,7 @@ findcom(const char *name, int flags)
 	char *fpath;
 	union mksh_cchack npath;
 
-	if (vstrchr(name, '/')) {
+	if (mksh_vdirsep(name)) {
 		insert = 0;
 		/* prevent FPATH search below */
 		flags &= ~FC_FUNC;
@@ -1240,13 +1289,24 @@ search_access(const char *fn, int mode)
 		eno = errno;
 		return (eno ? eno : EACCES);
 	}
+#ifdef __OS2__
+	/* treat all files as executable on OS/2 */
+	sb.st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
+#endif
 	if (mode == X_OK && (!S_ISREG(sb.st_mode) ||
-	    !(sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))))
+	    !(sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))))
 		/* access(2) may say root can execute everything */
 		return (S_ISDIR(sb.st_mode) ? EISDIR : EACCES);
 	return (0);
 }
 
+#ifdef __OS2__
+/* check if path is something we want to find, adding executable extensions */
+#define search_access(fn, mode)	access_ex((search_access), (fn), (mode))
+#else
+#define search_access(fn, mode)	(search_access)((fn), (mode))
+#endif
+
 /*
  * search for command with PATH
  */
@@ -1263,12 +1323,16 @@ search_path(const char *name, const char *lpath,
 	size_t namelen;
 	int ec = 0, ev;
 
-	if (vstrchr(name, '/')) {
+	if (mksh_vdirsep(name)) {
 		if ((ec = search_access(name, mode)) == 0) {
  search_path_ok:
 			if (errnop)
 				*errnop = 0;
+#ifndef __OS2__
 			return (name);
+#else
+			return (real_exec_name(name));
+#endif
 		}
 		goto search_path_err;
 	}
@@ -1280,11 +1344,15 @@ search_path(const char *name, const char *lpath,
 	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);
 			xp += p - sp;
+#ifdef __OS2__
+			if (xp > Xstring(xs, xp) && mksh_cdirsep(xp[-1]))
+				xp--;
+#endif
 			*xp++ = '/';
 		}
 		sp = p;
@@ -1315,9 +1383,7 @@ call_builtin(struct tbl *tp, const char **wp, const char *where, bool resetspec)
 	if (!tp)
 		internal_errorf(Tf_sD_s, where, wp[0]);
 	builtin_argv0 = wp[0];
-	builtin_spec = tobool(!resetspec &&
-	    /*XXX odd use of KEEPASN */
-	    ((tp->flag & SPEC_BI) || (Flag(FPOSIX) && (tp->flag & KEEPASN))));
+	builtin_spec = tobool(!resetspec && (tp->flag & SPEC_BI));
 	shf_reopen(1, SHF_WR, shl_stdout);
 	shl_stdout_ok = true;
 	ksh_getopt_reset(&builtin_opt, GF_ERROR);
@@ -1446,8 +1512,11 @@ iosetup(struct ioword *iop, struct tbl *tp)
 		/* herein() may already have printed message */
 		if (u == -1) {
 			u = errno;
-			warningf(true, Tf_cant,
+			warningf(true, Tf_cant_ss_s,
+#if 0
+			    /* can't happen */
 			    iotype == IODUP ? "dup" :
+#endif
 			    (iotype == IOREAD || iotype == IOHERE) ?
 			    Topen : Tcreate, cp, cstrerror(u));
 		}
@@ -1656,6 +1725,7 @@ pr_menu(const char * const *ap)
 	const char * const *pp;
 	size_t acols = 0, aocts = 0, i;
 	unsigned int n;
+	struct columnise_opts co;
 
 	/*
 	 * width/column calculations were done once and saved, but this
@@ -1684,9 +1754,11 @@ pr_menu(const char * const *ap)
 		smi.num_width++;
 
 	smi.args = ap;
-	print_columns(shl_out, n, select_fmt_entry, (void *)&smi,
-	    smi.num_width + 2 + aocts, smi.num_width + 2 + acols,
-	    true);
+	co.shf = shl_out;
+	co.linesep = '\n';
+	co.prefcol = co.do_last = true;
+	print_columns(&co, n, select_fmt_entry, (void *)&smi,
+	    smi.num_width + 2 + aocts, smi.num_width + 2 + acols);
 }
 
 static void
@@ -1696,7 +1768,7 @@ plain_fmt_entry(char *buf, size_t buflen, unsigned int i, const void *arg)
 }
 
 void
-pr_list(char * const *ap)
+pr_list(struct columnise_opts *cop, char * const *ap)
 {
 	size_t acols = 0, aocts = 0, i;
 	unsigned int n;
@@ -1711,8 +1783,8 @@ pr_list(char * const *ap)
 			acols = i;
 	}
 
-	print_columns(shl_out, n, plain_fmt_entry, (const void *)ap,
-	    aocts, acols, false);
+	print_columns(cop, n, plain_fmt_entry, (const void *)ap,
+	    aocts, acols);
 }
 
 /*
diff --git a/expr.c b/expr.c
index 4b830ab..12989d4 100644
--- a/expr.c
+++ b/expr.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2016
+ *		 2011, 2012, 2013, 2014, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.89 2016/09/01 12:55:21 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"
@@ -203,7 +203,7 @@ evalerr(Expr_state *es, enum error_type type, const char *str)
 
 	case ET_BADLIT:
 		warningf(true, Tf_sD_s_qs, es->expression,
-		    "bad number", str);
+		    Tbadnum, str);
 		break;
 
 	case ET_RECURSIVE:
@@ -326,7 +326,15 @@ evalexpr(Expr_state *es, unsigned int prec)
 	vl = evalexpr(es, prec - 1);
 	while ((int)(op = es->tok) >= (int)O_EQ && (int)op <= (int)O_COMMA &&
 	    opprec[(int)op] == prec) {
-		exprtoken(es);
+		switch ((int)op) {
+		case O_TERN:
+		case O_LAND:
+		case O_LOR:
+			break;
+		default:
+			exprtoken(es);
+		}
+
 		vasn = vl;
 		if (op != O_ASN)
 			/* vl may not have a value yet */
@@ -340,14 +348,15 @@ evalexpr(Expr_state *es, unsigned int prec)
 
 			if (!ev)
 				es->noassign++;
+			exprtoken(es);
 			vl = evalexpr(es, MAX_PREC);
 			if (!ev)
 				es->noassign--;
 			if (es->tok != CTERN)
 				evalerr(es, ET_STR, "missing :");
-			exprtoken(es);
 			if (ev)
 				es->noassign++;
+			exprtoken(es);
 			vr = evalexpr(es, P_TERN);
 			if (ev)
 				es->noassign--;
@@ -502,6 +511,7 @@ evalexpr(Expr_state *es, unsigned int prec)
 		case O_LAND:
 			if (!t1)
 				es->noassign++;
+			exprtoken(es);
 			vr = intvar(es, evalexpr(es, prec - 1));
 			res = t1 && vr->val.u;
 			if (!t1)
@@ -510,6 +520,7 @@ evalexpr(Expr_state *es, unsigned int prec)
 		case O_LOR:
 			if (t1)
 				es->noassign++;
+			exprtoken(es);
 			vr = intvar(es, evalexpr(es, prec - 1));
 			res = t1 || vr->val.u;
 			if (t1)
@@ -547,9 +558,9 @@ exprtoken(Expr_state *es)
 
 	/* 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;
@@ -560,10 +571,11 @@ exprtoken(Expr_state *es)
 
 	if (c == '\0')
 		es->tok = END;
-	else if (ksh_isalphx(c)) {
-		for (; ksh_isalnux(c); c = *cp)
-			cp++;
-		if (c == '[') {
+	else if (ctype(c, C_ALPHX)) {
+		do {
+			c = ord(*++cp);
+		} while (ctype(c, C_ALNUX));
+		if (c == ord('[')) {
 			size_t len;
 
 			len = array_ref_len(cp);
@@ -605,9 +617,9 @@ exprtoken(Expr_state *es)
 		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("");
@@ -621,7 +633,7 @@ exprtoken(Expr_state *es)
 	} 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;
@@ -760,8 +772,7 @@ utf_ptradj(const char *src)
 {
 	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);
@@ -779,7 +790,7 @@ utf_mbtowc(unsigned int *dst, const char *src)
 	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;
@@ -793,7 +804,7 @@ utf_mbtowc(unsigned int *dst, const char *src)
 
 	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;
@@ -801,11 +812,11 @@ utf_mbtowc(unsigned int *dst, const char *src)
 
 	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;
 
@@ -822,18 +833,18 @@ utf_wctomb(char *dst, unsigned int wc)
 	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);
 }
 
@@ -845,6 +856,9 @@ utf_wctomb(char *dst, unsigned int wc)
 int
 ksh_access(const char *fn, int mode)
 {
+#ifdef __OS2__
+	return (access_ex(access, fn, mode));
+#else
 	int rv;
 	struct stat sb;
 
@@ -854,10 +868,11 @@ ksh_access(const char *fn, int mode)
 		rv = -1;
 
 	return (rv);
+#endif
 }
 
 #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;
@@ -868,8 +883,8 @@ static int mb_ucsbsearch(const struct mb_ucsrange arr[], size_t elems,
     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[] = {
@@ -925,6 +940,7 @@ static const struct mb_ucsrange mb_ucs_combining[] = {
 	{ 0x0AC7, 0x0AC8 },
 	{ 0x0ACD, 0x0ACD },
 	{ 0x0AE2, 0x0AE3 },
+	{ 0x0AFA, 0x0AFF },
 	{ 0x0B01, 0x0B01 },
 	{ 0x0B3C, 0x0B3C },
 	{ 0x0B3F, 0x0B3F },
@@ -947,7 +963,8 @@ static const struct mb_ucsrange mb_ucs_combining[] = {
 	{ 0x0CC6, 0x0CC6 },
 	{ 0x0CCC, 0x0CCD },
 	{ 0x0CE2, 0x0CE3 },
-	{ 0x0D01, 0x0D01 },
+	{ 0x0D00, 0x0D01 },
+	{ 0x0D3B, 0x0D3C },
 	{ 0x0D41, 0x0D44 },
 	{ 0x0D4D, 0x0D4D },
 	{ 0x0D62, 0x0D63 },
@@ -1032,7 +1049,7 @@ static const struct mb_ucsrange mb_ucs_combining[] = {
 	{ 0x1CED, 0x1CED },
 	{ 0x1CF4, 0x1CF4 },
 	{ 0x1CF8, 0x1CF9 },
-	{ 0x1DC0, 0x1DF5 },
+	{ 0x1DC0, 0x1DF9 },
 	{ 0x1DFB, 0x1DFF },
 	{ 0x200B, 0x200F },
 	{ 0x202A, 0x202E },
@@ -1120,14 +1137,16 @@ static const struct mb_ucsrange mb_ucs_fullwidth[] = {
 	{ 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/funcs.c b/funcs.c
index 9ec837a..9142bbc 100644
--- a/funcs.c
+++ b/funcs.c
@@ -5,7 +5,7 @@
 
 /*-
  * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
- *		 2010, 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -38,7 +38,7 @@
 #endif
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.306 2016/08/25 16:21:14 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.350 2017/05/05 22:53:28 tg Exp $");
 
 #if HAVE_KILLPG
 /*
@@ -73,7 +73,7 @@ bi_getn(const char *as, int *ai)
 	int rv;
 
 	if (!(rv = getn(as, ai)))
-		bi_errorf(Tf_sD_s, as, "bad number");
+		bi_errorf(Tf_sD_s, Tbadnum, as);
 	return (rv);
 }
 
@@ -92,6 +92,7 @@ c_false(const char **wp MKSH_A_UNUSED)
 /*
  * A leading = means assignments before command are kept.
  * A leading * means a POSIX special builtin.
+ * A leading ^ means declaration utility, - forwarder.
  */
 const struct builtin mkshbuiltins[] = {
 	{Tsgdot, c_dot},
@@ -99,37 +100,38 @@ const struct builtin mkshbuiltins[] = {
 	{Tbracket, c_test},
 	/* no =: AT&T manual wrong */
 	{Talias, c_alias},
-	{"*=break", c_brkcont},
-	{Tgbuiltin, c_builtin},
+	{Tsgbreak, c_brkcont},
+	{T__builtin, c_builtin},
+	{Tbuiltin, c_builtin},
 	{Tbcat, c_cat},
 	{Tcd, c_cd},
 	/* dash compatibility hack */
 	{"chdir", c_cd},
-	{Tcommand, c_command},
-	{"*=continue", c_brkcont},
+	{T_command, c_command},
+	{Tsgcontinue, c_brkcont},
 	{"echo", c_print},
 	{"*=eval", c_eval},
 	{"*=exec", c_exec},
 	{"*=exit", c_exitreturn},
-	{Tsgexport, c_typeset},
+	{Tdsgexport, c_typeset},
 	{Tfalse, c_false},
 	{"fc", c_fc},
 	{Tgetopts, c_getopts},
-	{"=global", c_typeset},
+	/* deprecated, replaced by typeset -g */
+	{"^=global", c_typeset},
 	{Tjobs, c_jobs},
 	{"kill", c_kill},
 	{"let", c_let},
-	{"let]", c_let},
 	{"print", c_print},
 	{"pwd", c_pwd},
 	{Tread, c_read},
-	{Tsgreadonly, c_typeset},
+	{Tdsgreadonly, c_typeset},
 	{"!realpath", c_realpath},
 	{"~rename", c_rename},
 	{"*=return", c_exitreturn},
 	{Tsgset, c_set},
 	{"*=shift", c_shift},
-	{"=source", c_dot},
+	{Tgsource, c_dot},
 #if !defined(MKSH_UNEMPLOYED) && HAVE_GETSID
 	{Tsuspend, c_suspend},
 #endif
@@ -137,12 +139,12 @@ const struct builtin mkshbuiltins[] = {
 	{"*=times", c_times},
 	{"*=trap", c_trap},
 	{Ttrue, c_true},
-	{Tgtypeset, c_typeset},
+	{Tdgtypeset, c_typeset},
 	{"ulimit", c_ulimit},
 	{"umask", c_umask},
 	{Tunalias, c_unalias},
 	{"*=unset", c_unset},
-	{"=wait", c_wait},
+	{"wait", c_wait},
 	{"whence", c_whence},
 #ifndef MKSH_UNEMPLOYED
 	{Tbg, c_fgbg},
@@ -187,8 +189,8 @@ static const struct t_op {
 	{"-f",	TO_FILREG },
 	{"-G",	TO_FILGID },
 	{"-g",	TO_FILSETG },
-	{"-h",	TO_FILSYM },
 	{"-H",	TO_FILCDF },
+	{"-h",	TO_FILSYM },
 	{"-k",	TO_FILSTCK },
 	{"-L",	TO_FILSYM },
 	{"-n",	TO_STNZE },
@@ -196,10 +198,11 @@ static const struct t_op {
 	{"-o",	TO_OPTION },
 	{"-p",	TO_FILFIFO },
 	{"-r",	TO_FILRD },
-	{"-s",	TO_FILGZ },
 	{"-S",	TO_FILSOCK },
+	{"-s",	TO_FILGZ },
 	{"-t",	TO_FILTT },
 	{"-u",	TO_FILSETU },
+	{"-v",	TO_ISSET },
 	{"-w",	TO_FILWR },
 	{"-x",	TO_FILEX },
 	{"-z",	TO_STZER },
@@ -280,57 +283,76 @@ static void s_put(int);
 int
 c_print(const char **wp)
 {
-	int fd = 1, c;
+	int c;
 	const char *s;
-	XString xs;
 	char *xp;
-	/* print newline;  expand backslash sequences */
-	bool po_nl = true, po_exp = true;
-	/* print to history instead of file descriptor / stdout */
-	bool po_hist = false;
-	/* print characters */
-	bool po_char = false;
-	char ts[4];
+	XString xs;
+	struct {
+		/* storage for columnisation */
+		XPtrV words;
+		/* temporary storage for a wide character */
+		mksh_ari_t wc;
+		/* output file descriptor (if any) */
+		int fd;
+		/* temporary storage for a multibyte character */
+		char ts[4];
+		/* output word separator */
+		char ws;
+		/* output line separator */
+		char ls;
+		/* output a trailing line separator? */
+		bool nl;
+		/* expand backslash sequences? */
+		bool exp;
+		/* columnise output? */
+		bool col;
+		/* print to history instead of file descriptor / stdout? */
+		bool hist;
+		/* print words as wide characters? */
+		bool chars;
+		/* writing to a coprocess (SIGPIPE blocked)? */
+		bool coproc;
+		bool copipe;
+	} po;
+
+	memset(&po, 0, sizeof(po));
+	po.fd = 1;
+	po.ws = ' ';
+	po.ls = '\n';
+	po.nl = true;
 
 	if (wp[0][0] == 'e') {
 		/* "echo" builtin */
-		++wp;
-#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
-		if (Flag(FSH)) {
-			/*
-			 * MidnightBSD /bin/sh needs a BSD echo, that is,
-			 * one that supports -e but does not enable it by
-			 * default
-			 */
-			po_exp = false;
-		}
-#endif
 		if (Flag(FPOSIX) ||
 #ifndef MKSH_MIDNIGHTBSD01ASH_COMPAT
 		    Flag(FSH) ||
 #endif
 		    Flag(FAS_BUILTIN)) {
-			/* Debian Policy 10.4 compliant "echo" builtin */
+			/* BSD "echo" cmd, Debian Policy 10.4 compliant */
+			++wp;
+ bsd_echo:
 			if (*wp && !strcmp(*wp, "-n")) {
-				/* recognise "-n" only as the first arg */
-				po_nl = false;
+				po.nl = false;
 				++wp;
 			}
-			/* print everything as-is */
-			po_exp = false;
+			po.exp = false;
 		} else {
-			bool new_exp = po_exp, new_nl = po_nl;
-
-			/**
-			 * a compromise between sysV and BSD echo commands:
-			 * escape sequences are enabled by default, and -n,
-			 * -e and -E are recognised if they appear in argu-
-			 * ments with no illegal options (ie, echo -nq will
-			 * print -nq).
-			 * Different from sysV echo since options are reco-
-			 * gnised, different from BSD echo since escape se-
-			 * quences are enabled by default.
+			bool new_exp, new_nl = true;
+
+			/*-
+			 * compromise between various historic echos: only
+			 * recognise -Een if they appear in arguments with
+			 * no illegal options; e.g. echo -nq outputs '-nq'
 			 */
+#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT
+			/* MidnightBSD /bin/sh needs -e supported but off */
+			if (Flag(FSH))
+				new_exp = false;
+			else
+#endif
+			/* otherwise compromise on -e enabled by default */
+			  new_exp = true;
+			goto print_tradparse_beg;
 
  print_tradparse_arg:
 			if ((s = *wp) && *s++ == '-' && *s) {
@@ -346,8 +368,9 @@ c_print(const char **wp)
 					new_nl = false;
 					goto print_tradparse_ch;
 				case '\0':
-					po_exp = new_exp;
-					po_nl = new_nl;
+ print_tradparse_beg:
+					po.exp = new_exp;
+					po.nl = new_nl;
 					++wp;
 					goto print_tradparse_arg;
 				}
@@ -355,44 +378,52 @@ c_print(const char **wp)
 		}
 	} else {
 		/* "print" builtin */
-		const char *opts = "AnpRrsu,";
+		const char *opts = "AcelNnpRrsu,";
 		const char *emsg;
-		/* print a "--" argument */
-		bool po_pminusminus = false;
+
+		po.exp = true;
 
 		while ((c = ksh_getopt(wp, &builtin_opt, opts)) != -1)
 			switch (c) {
 			case 'A':
-				po_char = true;
+				po.chars = true;
+				break;
+			case 'c':
+				po.col = true;
 				break;
 			case 'e':
-				po_exp = true;
+				po.exp = true;
+				break;
+			case 'l':
+				po.ws = '\n';
+				break;
+			case 'N':
+				po.ws = '\0';
+				po.ls = '\0';
 				break;
 			case 'n':
-				po_nl = false;
+				po.nl = false;
 				break;
 			case 'p':
-				if ((fd = coproc_getfd(W_OK, &emsg)) < 0) {
+				if ((po.fd = coproc_getfd(W_OK, &emsg)) < 0) {
 					bi_errorf(Tf_coproc, emsg);
 					return (1);
 				}
 				break;
 			case 'R':
-				/* fake BSD echo command */
-				po_pminusminus = true;
-				po_exp = false;
-				opts = "en";
-				break;
+				/* fake BSD echo but don't reset other flags */
+				wp += builtin_opt.optind;
+				goto bsd_echo;
 			case 'r':
-				po_exp = false;
+				po.exp = false;
 				break;
 			case 's':
-				po_hist = true;
+				po.hist = true;
 				break;
 			case 'u':
 				if (!*(s = builtin_opt.optarg))
-					fd = 0;
-				else if ((fd = check_fd(s, W_OK, &emsg)) < 0) {
+					po.fd = 0;
+				else if ((po.fd = check_fd(s, W_OK, &emsg)) < 0) {
 					bi_errorf("-u%s: %s", s, emsg);
 					return (1);
 				}
@@ -406,35 +437,44 @@ c_print(const char **wp)
 			if (wp[builtin_opt.optind] &&
 			    ksh_isdash(wp[builtin_opt.optind]))
 				builtin_opt.optind++;
-			} else if (po_pminusminus)
-				builtin_opt.optind--;
+		}
 		wp += builtin_opt.optind;
 	}
 
-	Xinit(xs, xp, 128, ATEMP);
+	if (po.col) {
+		if (*wp == NULL)
+			return (0);
 
-	if (*wp != NULL && po_char) {
-		mksh_ari_t wc;
+		XPinit(po.words, 16);
+	}
 
-		do {
-			if (!evaluate(*wp, &wc, KSH_RETURN_ERROR, true))
+	Xinit(xs, xp, 128, ATEMP);
+
+	if (*wp == NULL)
+		goto print_no_arg;
+ print_read_arg:
+	if (po.chars) {
+		while (*wp != NULL) {
+			s = *wp++;
+			if (*s == '\0')
+				break;
+			if (!evaluate(s, &po.wc, KSH_RETURN_ERROR, true))
 				return (1);
 			Xcheck(xs, xp);
 			if (UTFMODE) {
-				ts[utf_wctomb(ts, wc)] = 0;
+				po.ts[utf_wctomb(po.ts, po.wc)] = 0;
 				c = 0;
 				do {
-					Xput(xs, xp, ts[c]);
-				} while (ts[++c]);
+					Xput(xs, xp, po.ts[c]);
+				} while (po.ts[++c]);
 			} else
-				Xput(xs, xp, wc & 0xFF);
-		} while (*++wp);
-	} else if (*wp != NULL) {
- print_read_arg:
-		s = *wp;
+				Xput(xs, xp, po.wc & 0xFF);
+		}
+	} else {
+		s = *wp++;
 		while ((c = *s++) != '\0') {
 			Xcheck(xs, xp);
-			if (po_exp && c == '\\') {
+			if (po.exp && c == '\\') {
 				s_ptr = s;
 				c = unbksl(false, s_get, s_put);
 				s = s_ptr;
@@ -442,7 +482,7 @@ c_print(const char **wp)
 					/* rejected by generic function */
 					switch ((c = *s++)) {
 					case 'c':
-						po_nl = false;
+						po.nl = false;
 						/* AT&T brain damage */
 						continue;
 					case '\0':
@@ -454,33 +494,55 @@ c_print(const char **wp)
 					}
 				} else if ((unsigned int)c > 0xFF) {
 					/* generic function returned Unicode */
-					ts[utf_wctomb(ts, c - 0x100)] = 0;
+					po.ts[utf_wctomb(po.ts, c - 0x100)] = 0;
 					c = 0;
 					do {
-						Xput(xs, xp, ts[c]);
-					} while (ts[++c]);
+						Xput(xs, xp, po.ts[c]);
+					} while (po.ts[++c]);
 					continue;
 				}
 			}
 			Xput(xs, xp, c);
 		}
-		if (*++wp != NULL) {
-			Xput(xs, xp, ' ');
-			goto print_read_arg;
-		}
 	}
-	if (po_nl)
-		Xput(xs, xp, '\n');
+	if (po.col) {
+		Xput(xs, xp, '\0');
+		XPput(po.words, Xclose(xs, xp));
+		Xinit(xs, xp, 128, ATEMP);
+	}
+	if (*wp != NULL) {
+		if (!po.col)
+			Xput(xs, xp, po.ws);
+		goto print_read_arg;
+	}
+	if (po.col) {
+		size_t w = XPsize(po.words);
+		struct columnise_opts co;
+
+		XPput(po.words, NULL);
+		co.shf = shf_sopen(NULL, 128, SHF_WR | SHF_DYNAMIC, NULL);
+		co.linesep = po.ls;
+		co.prefcol = co.do_last = false;
+		pr_list(&co, (char **)XPptrv(po.words));
+		while (w--)
+			afree(XPptrv(po.words)[w], ATEMP);
+		XPfree(po.words);
+		w = co.shf->wp - co.shf->buf;
+		XcheckN(xs, xp, w);
+		memcpy(xp, co.shf->buf, w);
+		xp += w;
+		shf_sclose(co.shf);
+	}
+ print_no_arg:
+	if (po.nl)
+		Xput(xs, xp, po.ls);
 
 	c = 0;
-	if (po_hist) {
+	if (po.hist) {
 		Xput(xs, xp, '\0');
 		histsave(&source->line, Xstring(xs, xp), HIST_STORE, false);
-		Xfree(xs, xp);
 	} else {
 		size_t len = Xlength(xs, xp);
-		bool po_coproc = false;
-		int opipe = 0;
 
 		/*
 		 * Ensure we aren't killed by a SIGPIPE while writing to
@@ -488,24 +550,25 @@ c_print(const char **wp)
 		 * to just check that the co-process is alive which is
 		 * not enough).
 		 */
-		if (coproc.write >= 0 && coproc.write == fd) {
-			po_coproc = true;
-			opipe = block_pipe();
-		}
+		if (coproc.write >= 0 && coproc.write == po.fd) {
+			po.coproc = true;
+			po.copipe = block_pipe();
+		} else
+			po.coproc = po.copipe = false;
 
 		s = Xstring(xs, xp);
 		while (len > 0) {
 			ssize_t nwritten;
 
-			if ((nwritten = write(fd, s, len)) < 0) {
+			if ((nwritten = write(po.fd, s, len)) < 0) {
 				if (errno == EINTR) {
-					if (po_coproc)
-						restore_pipe(opipe);
+					if (po.copipe)
+						restore_pipe();
 					/* give the user a chance to ^C out */
 					intrcheck();
 					/* interrupted, try again */
-					if (po_coproc)
-						opipe = block_pipe();
+					if (po.coproc)
+						po.copipe = block_pipe();
 					continue;
 				}
 				c = 1;
@@ -514,9 +577,10 @@ c_print(const char **wp)
 			s += nwritten;
 			len -= nwritten;
 		}
-		if (po_coproc)
-			restore_pipe(opipe);
+		if (po.copipe)
+			restore_pipe();
 	}
+	Xfree(xs, xp);
 
 	return (c);
 }
@@ -668,7 +732,7 @@ do_whence(const char **wp, int fcflags, bool vflag, bool iscommand)
 			break;
 #ifndef MKSH_SMALL
 		default:
-			bi_errorf("%s is of unknown type %d", id, tp->type);
+			bi_errorf(Tunexpected_type, id, Tcommand, tp->type);
 			return (1);
 #endif
 		}
@@ -678,380 +742,19 @@ do_whence(const char **wp, int fcflags, bool vflag, bool iscommand)
 	return (rv);
 }
 
-/* typeset, global, export, and readonly */
-static void c_typeset_vardump(struct tbl *, uint32_t, int, bool, bool);
-static void c_typeset_vardump_recursive(struct block *, uint32_t, int, bool,
-    bool);
-int
-c_typeset(const char **wp)
+bool
+valid_alias_name(const char *cp)
 {
-	struct tbl *vp, **p;
-	uint32_t fset = 0, fclr = 0, flag;
-	int thing = 0, field = 0, base = 0, i;
-	struct block *l;
-	const char *opts;
-	const char *fieldstr = NULL, *basestr = NULL;
-	bool localv = false, func = false, pflag = false, istset = true;
-	enum namerefflag new_refflag = SRF_NOP;
-
-	switch (**wp) {
-
-	/* export */
-	case 'e':
-		fset |= EXPORT;
-		istset = false;
-		break;
-
-	/* readonly */
-	case 'r':
-		fset |= RDONLY;
-		istset = false;
-		break;
-
-	/* set */
-	case 's':
-		/* called with 'typeset -' */
-		break;
-
-	/* typeset */
-	case 't':
-		localv = true;
-		break;
-	}
-
-	/* see comment below regarding possible opions */
-	opts = istset ? "L#R#UZ#afi#lnprtux" : "p";
-
-	builtin_opt.flags |= GF_PLUSOPT;
-	/*
-	 * AT&T ksh seems to have 0-9 as options which are multiplied
-	 * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
-	 * sets right justify in a field of 12). This allows options
-	 * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
-	 * does not allow the number to be specified as a separate argument
-	 * Here, the number must follow the RLZi option, but is optional
-	 * (see the # kludge in ksh_getopt()).
-	 */
-	while ((i = ksh_getopt(wp, &builtin_opt, opts)) != -1) {
-		flag = 0;
-		switch (i) {
-		case 'L':
-			flag = LJUST;
-			fieldstr = builtin_opt.optarg;
-			break;
-		case 'R':
-			flag = RJUST;
-			fieldstr = builtin_opt.optarg;
-			break;
-		case 'U':
-			/*
-			 * AT&T ksh uses u, but this conflicts with
-			 * upper/lower case. If this option is changed,
-			 * need to change the -U below as well
-			 */
-			flag = INT_U;
-			break;
-		case 'Z':
-			flag = ZEROFIL;
-			fieldstr = builtin_opt.optarg;
-			break;
-		case 'a':
-			/*
-			 * this is supposed to set (-a) or unset (+a) the
-			 * indexed array attribute; it does nothing on an
-			 * existing regular string or indexed array though
-			 */
-			break;
-		case 'f':
-			func = true;
-			break;
-		case 'i':
-			flag = INTEGER;
-			basestr = builtin_opt.optarg;
-			break;
-		case 'l':
-			flag = LCASEV;
-			break;
-		case 'n':
-			new_refflag = (builtin_opt.info & GI_PLUS) ?
-			    SRF_DISABLE : SRF_ENABLE;
-			break;
-		/* export, readonly: POSIX -p flag */
-		case 'p':
-			/* typeset: show values as well */
-			pflag = true;
-			if (istset)
-				continue;
-			break;
-		case 'r':
-			flag = RDONLY;
-			break;
-		case 't':
-			flag = TRACE;
-			break;
-		case 'u':
-			/* upper case / autoload */
-			flag = UCASEV_AL;
-			break;
-		case 'x':
-			flag = EXPORT;
-			break;
-		case '?':
-			return (1);
-		}
-		if (builtin_opt.info & GI_PLUS) {
-			fclr |= flag;
-			fset &= ~flag;
-			thing = '+';
-		} else {
-			fset |= flag;
-			fclr &= ~flag;
-			thing = '-';
-		}
-	}
-
-	if (fieldstr && !bi_getn(fieldstr, &field))
-		return (1);
-	if (basestr) {
-		if (!getn(basestr, &base)) {
-			bi_errorf(Tf_sD_s, "bad integer base", basestr);
-			return (1);
-		}
-		if (base < 1 || base > 36)
-			base = 10;
-	}
-
-	if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
-	    (wp[builtin_opt.optind][0] == '-' ||
-	    wp[builtin_opt.optind][0] == '+') &&
-	    wp[builtin_opt.optind][1] == '\0') {
-		thing = wp[builtin_opt.optind][0];
-		builtin_opt.optind++;
-	}
-
-	if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) ||
-	    new_refflag != SRF_NOP)) {
-		bi_errorf("only -t, -u and -x options may be used with -f");
-		return (1);
-	}
-	if (wp[builtin_opt.optind]) {
-		/*
-		 * Take care of exclusions.
-		 * At this point, flags in fset are cleared in fclr and vice
-		 * versa. This property should be preserved.
-		 */
-		if (fset & LCASEV)
-			/* LCASEV has priority over UCASEV_AL */
-			fset &= ~UCASEV_AL;
-		if (fset & LJUST)
-			/* LJUST has priority over RJUST */
-			fset &= ~RJUST;
-		if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) {
-			/* -Z implies -ZR */
-			fset |= RJUST;
-			fclr &= ~RJUST;
-		}
-		/*
-		 * Setting these attributes clears the others, unless they
-		 * are also set in this command
-		 */
-		if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
-		    INTEGER | INT_U | INT_L)) || new_refflag != SRF_NOP)
-			fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
-			    LCASEV | INTEGER | INT_U | INT_L);
-	}
-	if (new_refflag != SRF_NOP) {
-		fclr &= ~(ARRAY | ASSOC);
-		fset &= ~(ARRAY | ASSOC);
-		fclr |= EXPORT;
-		fset |= ASSOC;
-		if (new_refflag == SRF_DISABLE)
-			fclr |= ASSOC;
-	}
-
-	/* set variables and attributes */
-	if (wp[builtin_opt.optind] &&
-	    /* not "typeset -p varname" */
-	    !(!func && pflag && !(fset | fclr))) {
-		int rv = 0;
-		struct tbl *f;
-
-		if (localv && !func)
-			fset |= LOCAL;
-		for (i = builtin_opt.optind; wp[i]; i++) {
-			if (func) {
-				f = findfunc(wp[i], hash(wp[i]),
-				    tobool(fset & UCASEV_AL));
-				if (!f) {
-					/* AT&T ksh does ++rv: bogus */
-					rv = 1;
-					continue;
-				}
-				if (fset | fclr) {
-					f->flag |= fset;
-					f->flag &= ~fclr;
-				} else {
-					fpFUNCTf(shl_stdout, 0,
-					    tobool(f->flag & FKSH),
-					    wp[i], f->val.t);
-					shf_putc('\n', shl_stdout);
-				}
-			} else if (!typeset(wp[i], fset, fclr, field, base)) {
-				bi_errorf(Tf_sD_s, wp[i], Tnot_ident);
-				return (1);
-			}
-		}
-		return (rv);
-	}
-
-	/* list variables and attributes */
-
-	/* no difference at this point.. */
-	flag = fset | fclr;
-	if (func) {
-		for (l = e->loc; l; l = l->next) {
-			for (p = ktsort(&l->funs); (vp = *p++); ) {
-				if (flag && (vp->flag & flag) == 0)
-					continue;
-				if (thing == '-')
-					fpFUNCTf(shl_stdout, 0,
-					    tobool(vp->flag & FKSH),
-					    vp->name, vp->val.t);
-				else
-					shf_puts(vp->name, shl_stdout);
-				shf_putc('\n', shl_stdout);
-			}
-		}
-	} else if (wp[builtin_opt.optind]) {
-		for (i = builtin_opt.optind; wp[i]; i++) {
-			varsearch(e->loc, &vp, wp[i], hash(wp[i]));
-			c_typeset_vardump(vp, flag, thing, pflag, istset);
-		}
-	} else
-		c_typeset_vardump_recursive(e->loc, flag, thing, pflag, istset);
-	return (0);
-}
-
-static void
-c_typeset_vardump_recursive(struct block *l, uint32_t flag, int thing,
-    bool pflag, bool istset)
-{
-	struct tbl **blockvars, *vp;
-
-	if (l->next)
-		c_typeset_vardump_recursive(l->next, flag, thing, pflag, istset);
-	blockvars = ktsort(&l->vars);
-	while ((vp = *blockvars++))
-		c_typeset_vardump(vp, flag, thing, pflag, istset);
-	/*XXX doesn’t this leak? */
-}
-
-static void
-c_typeset_vardump(struct tbl *vp, uint32_t flag, int thing, bool pflag,
-    bool istset)
-{
-	struct tbl *tvp;
-	int any_set = 0;
-	char *s;
-
-	if (!vp)
-		return;
-
-	/*
-	 * See if the parameter is set (for arrays, if any
-	 * element is set).
-	 */
-	for (tvp = vp; tvp; tvp = tvp->u.array)
-		if (tvp->flag & ISSET) {
-			any_set = 1;
-			break;
-		}
-
-	/*
-	 * Check attributes - note that all array elements
-	 * have (should have?) the same attributes, so checking
-	 * the first is sufficient.
-	 *
-	 * Report an unset param only if the user has
-	 * explicitly given it some attribute (like export);
-	 * otherwise, after "echo $FOO", we would report FOO...
-	 */
-	if (!any_set && !(vp->flag & USERATTRIB))
-		return;
-	if (flag && (vp->flag & flag) == 0)
-		return;
-	if (!(vp->flag & ARRAY))
-		/* optimise later conditionals */
-		any_set = 0;
-	do {
-		/*
-		 * Ignore array elements that aren't set unless there
-		 * are no set elements, in which case the first is
-		 * reported on
-		 */
-		if (any_set && !(vp->flag & ISSET))
-			continue;
-		/* no arguments */
-		if (!thing && !flag) {
-			if (any_set == 1) {
-				shprintf(Tf_s_s_sN, Tset, "-A", vp->name);
-				any_set = 2;
-			}
-			/*
-			 * AT&T ksh prints things like export, integer,
-			 * leftadj, zerofill, etc., but POSIX says must
-			 * be suitable for re-entry...
-			 */
-			shprintf(Tf_s_s, Ttypeset, "");
-			if (((vp->flag & (ARRAY | ASSOC)) == ASSOC))
-				shprintf(Tf__c_, 'n');
-			if ((vp->flag & INTEGER))
-				shprintf(Tf__c_, 'i');
-			if ((vp->flag & EXPORT))
-				shprintf(Tf__c_, 'x');
-			if ((vp->flag & RDONLY))
-				shprintf(Tf__c_, 'r');
-			if ((vp->flag & TRACE))
-				shprintf(Tf__c_, 't');
-			if ((vp->flag & LJUST))
-				shprintf("-L%d ", vp->u2.field);
-			if ((vp->flag & RJUST))
-				shprintf("-R%d ", vp->u2.field);
-			if ((vp->flag & ZEROFIL))
-				shprintf(Tf__c_, 'Z');
-			if ((vp->flag & LCASEV))
-				shprintf(Tf__c_, 'l');
-			if ((vp->flag & UCASEV_AL))
-				shprintf(Tf__c_, 'u');
-			if ((vp->flag & INT_U))
-				shprintf(Tf__c_, 'U');
-		} else if (pflag) {
-			shprintf(Tf_s_s, istset ? Ttypeset :
-			    (flag & EXPORT) ? Texport : Treadonly, "");
-		}
-		if (any_set)
-			shprintf("%s[%lu]", vp->name, arrayindex(vp));
+	if (ord(*cp) == ord('-'))
+		return (false);
+	if (ord(cp[0]) == ord('[') && ord(cp[1]) == ord('[') && !cp[2])
+		return (false);
+	while (*cp)
+		if (ctype(*cp, C_ALIAS))
+			++cp;
 		else
-			shf_puts(vp->name, shl_stdout);
-		if ((!thing && !flag && pflag) ||
-		    (thing == '-' && (vp->flag & ISSET))) {
-			s = str_val(vp);
-			shf_putc('=', shl_stdout);
-			/* AT&T ksh can't have justified integers... */
-			if ((vp->flag & (INTEGER | LJUST | RJUST)) == INTEGER)
-				shf_puts(s, shl_stdout);
-			else
-				print_value_quoted(shl_stdout, s);
-		}
-		shf_putc('\n', shl_stdout);
-
-		/*
-		 * Only report first 'element' of an array with
-		 * no set elements.
-		 */
-		if (!any_set)
-			return;
-	} while ((vp = vp->u.array));
+			return (false);
+	return (true);
 }
 
 int
@@ -1059,7 +762,7 @@ c_alias(const char **wp)
 {
 	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;
 
@@ -1104,12 +807,13 @@ c_alias(const char **wp)
 	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) {
@@ -1152,6 +856,11 @@ c_alias(const char **wp)
 			strndupx(xalias, alias, val++ - alias, ATEMP);
 			alias = xalias;
 		}
+		if (chkalias && !valid_alias_name(alias)) {
+			bi_errorf(Tinvname, alias, Talias);
+			afree(xalias, ATEMP);
+			return (1);
+		}
 		h = hash(alias);
 		if (val == NULL && !tflag && !xflag) {
 			ap = ktsearch(t, alias, h);
@@ -1362,8 +1071,7 @@ c_kill(const char **wp)
 	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);
@@ -1425,6 +1133,7 @@ c_kill(const char **wp)
 			ssize_t w, mess_cols = 0, mess_octs = 0;
 			int j = ksh_NSIG - 1;
 			struct kill_info ki = { 0, 0 };
+			struct columnise_opts co;
 
 			do {
 				ki.num_width++;
@@ -1442,11 +1151,14 @@ c_kill(const char **wp)
 					mess_cols = w;
 			}
 
-			print_columns(shl_stdout, (unsigned int)(ksh_NSIG - 1),
+			co.shf = shl_stdout;
+			co.linesep = '\n';
+			co.prefcol = co.do_last = true;
+
+			print_columns(&co, (unsigned int)(ksh_NSIG - 1),
 			    kill_fmt_entry, (void *)&ki,
 			    ki.num_width + 1 + ki.name_width + 1 + mess_octs,
-			    ki.num_width + 1 + ki.name_width + 1 + mess_cols,
-			    true);
+			    ki.num_width + 1 + ki.name_width + 1 + mess_cols);
 		}
 		return (0);
 	}
@@ -1556,7 +1268,7 @@ c_getopts(const char **wp)
 	if (user_opt.optarg == NULL)
 		unset(voptarg, 1);
 	else
-		/* This can't fail (have cleared readonly/integer) */
+		/* this can't fail (haing cleared readonly/integer) */
 		setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR);
 
 	rv = 0;
@@ -1654,7 +1366,7 @@ c_shift(const char **wp)
 		/* nothing to do */
 		return (0);
 	} else if (n < 0) {
-		bi_errorf(Tf_sD_s, arg, "bad number");
+		bi_errorf(Tf_sD_s, Tbadnum, arg);
 		return (1);
 	}
 	if (l->argc < n) {
@@ -1708,14 +1420,14 @@ c_umask(const char **wp)
 	} 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;
 			}
 			if (*cp) {
-				bi_errorf("bad number");
+				bi_errorf(Tbadnum);
 				return (1);
 			}
 		} else {
@@ -1748,7 +1460,7 @@ c_umask(const char **wp)
 				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;
@@ -1789,7 +1501,7 @@ c_umask(const char **wp)
 				if (*cp == ',') {
 					positions = 0;
 					cp++;
-				} else if (!vstrchr("=+-", *cp))
+				} else if (!ctype(*cp, C_EQUAL | C_MINUS | C_PLUS))
 					break;
 			}
 			if (*cp) {
@@ -1871,7 +1583,7 @@ c_wait(const char **wp)
 	return (rv);
 }
 
-static char REPLY[] = "REPLY";
+static const char REPLY[] = "REPLY";
 int
 c_read(const char **wp)
 {
@@ -1899,6 +1611,10 @@ c_read(const char **wp)
 #else
 #define c_read_opts "Aad:N:n:prsu,"
 #endif
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+	int saved_mode;
+	int saved_errno;
+#endif
 
 	while ((c = ksh_getopt(wp, &builtin_opt, c_read_opts)) != -1)
 	switch (c) {
@@ -2007,7 +1723,7 @@ c_read(const char **wp)
 		timersub(&tvlim, &tv, &tv);
 		if (tv.tv_sec < 0) {
 			/* timeout expired globally */
-			rv = 1;
+			rv = 3;
 			goto c_read_out;
 		}
 
@@ -2017,8 +1733,8 @@ c_read(const char **wp)
 		case 0:
 			/* timeout expired for this call */
 			bytesread = 0;
-			/* fake EOF read; all cases return 1 */
-			goto c_read_didread;
+			rv = 3;
+			goto c_read_readdone;
 		default:
 			bi_errorf(Tf_sD_s, Tselect, cstrerror(errno));
 			rv = 2;
@@ -2027,7 +1743,15 @@ c_read(const char **wp)
 	}
 #endif
 
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+	saved_mode = setmode(fd, O_TEXT);
+#endif
 	if ((bytesread = blocking_read(fd, xp, bytesleft)) == (size_t)-1) {
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+		saved_errno = errno;
+		setmode(fd, saved_mode);
+		errno = saved_errno;
+#endif
 		if (errno == EINTR) {
 			/* check whether the signal would normally kill */
 			if (!fatal_trap_check()) {
@@ -2042,8 +1766,10 @@ c_read(const char **wp)
 		rv = 2;
 		goto c_read_out;
 	}
+#if defined(__OS2__) && defined(MKSH_WITH_TEXTMODE)
+	setmode(fd, saved_mode);
+#endif
 
- c_read_didread:
 	switch (readmode) {
 	case READALL:
 		if (bytesread == 0) {
@@ -2117,13 +1843,13 @@ c_read(const char **wp)
 	/*-
 	 * state: we finished reading the input and NUL terminated it
 	 * Xstring(xs, xp) -> xp-1 = input string without trailing delim
-	 * rv = 1 if EOF, 0 otherwise (errors handled already)
+	 * rv = 3 if timeout, 1 if EOF, 0 otherwise (errors handled already)
 	 */
 
-	if (rv == 1) {
-		/* clean up coprocess if needed, on EOF */
+	if (rv) {
+		/* clean up coprocess if needed, on EOF/error/timeout */
 		coproc_read_close(fd);
-		if (readmode == READALL)
+		if (readmode == READALL && (rv == 1 || (rv == 3 && bytesread)))
 			/* EOF is no error here */
 			rv = 0;
 	}
@@ -2289,7 +2015,7 @@ c_read(const char **wp)
 	Xfree(xs, xp);
 	if (restore_tios)
 		mksh_tcset(fd, &tios);
-	return (rv);
+	return (rv == 3 ? ksh_sigmask(SIGALRM) : rv);
 #undef is_ifsws
 }
 
@@ -2304,6 +2030,7 @@ c_eval(const char **wp)
 		return (1);
 	s = pushs(SWORDS, ATEMP);
 	s->u.strv = wp + builtin_opt.optind;
+	s->line = current_lineno;
 
 	/*-
 	 * The following code handles the case where the command is
@@ -2341,7 +2068,7 @@ c_eval(const char **wp)
 
 	savef = Flag(FERREXIT);
 	Flag(FERREXIT) |= 0x80;
-	rv = shell(s, false);
+	rv = shell(s, 2);
 	Flag(FERREXIT) = savef;
 	source = saves;
 	afree(s, ATEMP);
@@ -2477,7 +2204,7 @@ c_brkcont(const char **wp)
 		 * scripts, but don't generate an error (ie, keep going).
 		 */
 		if ((unsigned int)n == quit) {
-			warningf(true, "%s: can't %s", wp[0], wp[0]);
+			warningf(true, Tf_cant_s, wp[0], wp[0]);
 			return (0);
 		}
 		/*
@@ -2571,8 +2298,9 @@ c_unset(const char **wp)
 			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;
@@ -2745,7 +2473,6 @@ c_exec(const char **wp MKSH_A_UNUSED)
 		for (i = 0; i < NUFILE; i++) {
 			if (e->savefd[i] > 0)
 				close(e->savefd[i]);
-#ifndef MKSH_LEGACY_MODE
 			/*
 			 * keep all file descriptors > 2 private for ksh,
 			 * but not for POSIX or legacy/kludge sh
@@ -2753,14 +2480,13 @@ c_exec(const char **wp MKSH_A_UNUSED)
 			if (!Flag(FPOSIX) && !Flag(FSH) && i > 2 &&
 			    e->savefd[i])
 				fcntl(i, F_SETFD, FD_CLOEXEC);
-#endif
 		}
 		e->savefd = NULL;
 	}
 	return (0);
 }
 
-#if HAVE_MKNOD
+#if HAVE_MKNOD && !defined(__OS2__)
 int
 c_mknod(const char **wp)
 {
@@ -2857,14 +2583,13 @@ c_mknod(const char **wp)
 		| "(" oexpr ")"
 		;
 
-	unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
-			   "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
-			   "-L"|"-h"|"-S"|"-H";
+	unary-operator ::= "-a"|"-b"|"-c"|"-d"|"-e"|"-f"|"-G"|"-g"|"-H"|"-h"|
+			   "-k"|"-L"|"-n"|"-O"|"-o"|"-p"|"-r"|"-S"|"-s"|"-t"|
+			   "-u"|"-v"|"-w"|"-x"|"-z";
+
+	binary-operator ::= "="|"=="|"!="|"<"|">"|"-eq"|"-ne"|"-gt"|"-ge"|
+			    "-lt"|"-le"|"-ef"|"-nt"|"-ot";
 
-	binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
-			    "-nt"|"-ot"|"-ef"|
-			    "<"|">"	# rules used for [[ ... ]] expressions
-			    ;
 	operand ::= <anything>
 */
 
@@ -3017,6 +2742,14 @@ test_isop(Test_meta meta, const char *s)
 	return (TO_NONOP);
 }
 
+#ifdef __OS2__
+#define test_access(name, mode) access_ex(access, (name), (mode))
+#define test_stat(name, buffer) stat_ex((name), (buffer))
+#else
+#define test_access(name, mode) access((name), (mode))
+#define test_stat(name, buffer) stat((name), (buffer))
+#endif
+
 int
 test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
     bool do_eval)
@@ -3025,6 +2758,7 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
 	size_t k;
 	struct stat b1, b2;
 	mksh_ari_t v1, v2;
+	struct tbl *vp;
 
 	if (!do_eval)
 		return (0);
@@ -3071,6 +2805,10 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
 	case TO_STZER:
 		return (*opnd1 == '\0');
 
+	/* -v */
+	case TO_ISSET:
+		return ((vp = isglobal(opnd1, false)) && (vp->flag & ISSET));
+
 	/* -o */
 	case TO_OPTION:
 		if ((i = *opnd1) == '!' || i == '?')
@@ -3082,12 +2820,12 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
 	/* -r */
 	case TO_FILRD:
 		/* LINTED use of access */
-		return (access(opnd1, R_OK) == 0);
+		return (test_access(opnd1, R_OK) == 0);
 
 	/* -w */
 	case TO_FILWR:
 		/* LINTED use of access */
-		return (access(opnd1, W_OK) == 0);
+		return (test_access(opnd1, W_OK) == 0);
 
 	/* -x */
 	case TO_FILEX:
@@ -3097,11 +2835,11 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
 	case TO_FILAXST:
 	/* -e */
 	case TO_FILEXST:
-		return (stat(opnd1, &b1) == 0);
+		return (test_stat(opnd1, &b1) == 0);
 
-	/* -r */
+	/* -f */
 	case TO_FILREG:
-		return (stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode));
+		return (test_stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode));
 
 	/* -d */
 	case TO_FILID:
@@ -3200,7 +2938,7 @@ test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
 	 * Binary Operators
 	 */
 
-	/* = */
+	/* =, == */
 	case TO_STEQL:
 		if (te->flags & TEF_DBRACKET) {
 			if ((i = gmatchx(opnd1, opnd2, false)))
@@ -3586,7 +3324,7 @@ c_ulimit(const char **wp)
 	if (!all)
 		print_ulimit(rlimits[i], how);
 	else for (i = 0; i < NELEM(rlimits); ++i) {
-		shprintf("%-20s ", rlimits[i]->name);
+		shprintf("-%c: %-20s  ", rlimits[i]->optchar, rlimits[i]->name);
 		print_ulimit(rlimits[i], how);
 	}
 	return (0);
@@ -3611,7 +3349,7 @@ set_ulimit(const struct limits *l, const char *v, int how)
 		 * 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);
 		}
@@ -3721,7 +3459,7 @@ c_cat(const char **wp)
 	ssize_t n, w;
 	const char *fn = "<stdin>";
 	char *buf, *cp;
-	int opipe = 0;
+	bool opipe;
 #define MKSH_CAT_BUFSIZ 4096
 
 	/* parse options: POSIX demands we support "-u" as no-op */
@@ -3761,7 +3499,8 @@ c_cat(const char **wp)
 			if ((n = blocking_read(fd, (cp = buf),
 			    MKSH_CAT_BUFSIZ)) == -1) {
 				if (errno == EINTR) {
-					restore_pipe(opipe);
+					if (opipe)
+						restore_pipe();
 					/* give the user a chance to ^C out */
 					intrcheck();
 					/* interrupted, try again */
@@ -3785,7 +3524,8 @@ c_cat(const char **wp)
 				}
 				if (errno == EINTR) {
  has_intrsig:
-					restore_pipe(opipe);
+					if (opipe)
+						restore_pipe();
 					/* give the user a chance to ^C out */
 					intrcheck();
 					/* interrupted, try again */
@@ -3811,7 +3551,8 @@ c_cat(const char **wp)
 	} while (*wp);
 
  out:
-	restore_pipe(opipe);
+	if (opipe)
+		restore_pipe();
 	free_osfunc(buf);
 	return (rv);
 }
diff --git a/histrap.c b/histrap.c
index c0f87f8..6b9396e 100644
--- a/histrap.c
+++ b/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 at 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.158 2016/08/04 20:31:00 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 @@ histsave(int *lnp, const char *cmd, int svmode, bool ignoredups)
 	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 @@ histsave(int *lnp, const char *cmd, int svmode, bool ignoredups)
 
 #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
-	unsigned char *base;
-	int lines, fd;
-	enum { hist_init_first, hist_init_retry, hist_init_restore } hs;
-#endif
+	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);
 
-	histsave(NULL, NULL, HIST_DISCARD, true);
+	if ((buf = malloc_osfunc(MKSH_HS_BUFSIZ)) == NULL)
+		return (1);
 
-	if (Flag(FTALKING) == 0)
-		return;
+	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);
+}
 
-	hstarted = true;
-	hist_source = s;
+static void
+hist_persist_init(void)
+{
+	unsigned char *base;
+	int lines, fd;
+	enum { hist_init_first, hist_init_retry, hist_use_it } hs;
 
-#if HAVE_PERSISTENT_HISTORY
 	if (((hname = str_val(global("HISTFILE"))) == NULL) || !*hname) {
 		hname = NULL;
 		return;
@@ -745,17 +785,16 @@ hist_init(Source *s)
 	/* 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 @@ hist_init(Source *s)
 			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 @@ hist_init(Source *s)
 			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... */
@@ -829,7 +867,7 @@ hist_init(Source *s)
 			goto retry;
 		}
 		if (hs != hist_init_retry)
-			bi_errorf(Tf_cant,
+			bi_errorf(Tf_cant_ss_s,
 			    "unlink HISTFILE", hname, cstrerror(errno));
 		histfsize = 0;
 		return;
@@ -840,9 +878,26 @@ hist_init(Source *s)
 			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 @@ writehistfile(int lno, const char *cmd)
 	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
 	    &&
@@ -1033,8 +1089,8 @@ inittraps(void)
 			if (!strcmp(sigtraps[i].name, "EXIT") ||
 			    !strcmp(sigtraps[i].name, "ERR")) {
 #ifndef MKSH_SMALL
-				internal_warningf("ignoring invalid signal name %s",
-				    sigtraps[i].name);
+				internal_warningf(Tinvname, sigtraps[i].name,
+				    "signal");
 #endif
 				sigtraps[i].name = null;
 			}
@@ -1114,7 +1170,7 @@ gettrap(const char *cs, bool igncase, bool allsigs)
 
 	/* 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);
 
@@ -1399,33 +1455,32 @@ settrap(Trap *p, const char *s)
 }
 
 /*
- * Called by c_print() when writing to a co-process to ensure SIGPIPE won't
- * kill shell (unless user catches it and exits)
+ * called by c_print() when writing to a co-process to ensure
+ * SIGPIPE won't kill shell (unless user catches it and exits)
  */
-int
+bool
 block_pipe(void)
 {
-	int restore_dfl = 0;
+	bool restore_dfl = false;
 	Trap *p = &sigtraps[SIGPIPE];
 
 	if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
 		setsig(p, SIG_IGN, SS_RESTORE_CURR);
 		if (p->flags & TF_ORIG_DFL)
-			restore_dfl = 1;
+			restore_dfl = true;
 	} else if (p->cursig == SIG_DFL) {
 		setsig(p, SIG_IGN, SS_RESTORE_CURR);
 		/* restore to SIG_DFL */
-		restore_dfl = 1;
+		restore_dfl = true;
 	}
 	return (restore_dfl);
 }
 
-/* Called by c_print() to undo whatever block_pipe() did */
+/* called by c_print() to undo whatever block_pipe() did */
 void
-restore_pipe(int restore_dfl)
+restore_pipe(void)
 {
-	if (restore_dfl)
-		setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
+	setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
 }
 
 /*
diff --git a/jobs.c b/jobs.c
index 0366004..4df98b7 100644
--- a/jobs.c
+++ b/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 @@ __RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.121 2016/07/25 00:04:44 tg Exp $");
 #define PSTOPPED	3
 
 typedef struct proc Proc;
+/* to take alignment into consideration */
+struct proc_dummy {
+	Proc *next;
+	pid_t pid;
+	int state;
+	int status;
+	char command[128];
+};
+/* real structure */
 struct proc {
-	Proc *next;		/* next process in pipeline (if any) */
-	pid_t pid;		/* process id */
+	/* 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;
-	int status;		/* wait status */
+	/* 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 @@ j_notify(void)
 	}
 	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 @@ j_lookup(const char *cp, int *ecodep)
 	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/lex.c b/lex.c
index 741bb93..e75ab2c 100644
--- a/lex.c
+++ b/lex.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.228 2016/08/01 21:38:03 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.240 2017/10/17 23:45:18 tg Exp $");
 
 /*
  * states while lexing word
@@ -131,7 +131,7 @@ getsc_i(void)
 }
 
 #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 @@ getsc_r(int c)
 	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 @@ yylex(int cf)
 	} 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 @@ yylex(int cf)
 	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)) {
@@ -284,15 +284,6 @@ yylex(int cf)
 						}
 						afree(tmp, ATEMP);
 						break;
-					} else {
-						Source *s;
-
-						s = pushs(SREREAD,
-						    source->areap);
-						s->start = s->str =
-						    s->u.freeme = tmp;
-						s->next = source;
-						source = s;
 					}
 				}
 				*wp++ = CHAR;
@@ -301,10 +292,9 @@ yylex(int cf)
 			}
 			/* 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 +305,7 @@ yylex(int cf)
 			/* FALLTHROUGH */
  Sbase2:		/* doesn't include *(...|...) pattern (*+?@!) */
 			switch (c) {
-			case '\\':
+			case ord('\\'):
  getsc_qchar:
 				if ((c = getsc())) {
 					/* trailing \ is lost */
@@ -323,7 +313,7 @@ yylex(int cf)
 					*wp++ = c;
 				}
 				break;
-			case '\'':
+			case ord('\''):
  open_ssquote_unless_heredoc:
 				if ((cf & HEREDOC))
 					goto store_char;
@@ -331,12 +321,12 @@ yylex(int cf)
 				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 +335,9 @@ yylex(int cf)
  subst_dollar_ex:
 				c = getsc();
 				switch (c) {
-				case '"':
+				case ord('"'):
 					goto open_sdquote;
-				case '\'':
+				case ord('\''):
 					goto open_sequote;
 				default:
 					goto SubstS;
@@ -359,15 +349,16 @@ yylex(int cf)
 
  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 +376,12 @@ yylex(int cf)
 					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 +398,8 @@ yylex(int cf)
 						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 +416,15 @@ yylex(int cf)
 					}
 					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 +435,9 @@ yylex(int cf)
 							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 +456,7 @@ yylex(int cf)
  parse_adelim_slash:
 						*wp++ = CHAR;
 						*wp++ = c;
-						if ((c = getsc()) == '/') {
+						if ((c = getsc()) == ord('/')) {
 							*wp++ = c2;
 							*wp++ = c;
 						} else
@@ -480,7 +470,7 @@ yylex(int cf)
 					} else if (c == '@') {
 						c2 = getsc();
 						ungetsc(c2);
-						if (c2 == '/') {
+						if (c2 == ord('/')) {
 							c2 = CHAR;
 							goto parse_adelim_slash;
 						}
@@ -489,7 +479,7 @@ yylex(int cf)
 					 * If this is a trim operation,
 					 * treat (,|,) specially in STBRACE.
 					 */
-					if (ctype(c, C_SUBOP2)) {
+					if (ctype(c, C_SUB2)) {
 						ungetsc(c);
 						if (Flag(FSH))
 							PUSH_STATE(STBRACEBOURNE);
@@ -503,14 +493,14 @@ yylex(int cf)
 						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,10 +519,10 @@ yylex(int cf)
 					ungetsc(c);
 				}
 				break;
-			case '`':
+			case ord('`'):
  subst_gravis:
 				PUSH_STATE(SBQUOTE);
-				*wp++ = COMSUB;
+				*wp++ = COMASUB;
 				/*
 				 * We need to know whether we are within double
 				 * quotes in order to translate \" to " within
@@ -573,11 +563,11 @@ yylex(int cf)
 			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 +595,7 @@ yylex(int cf)
 			break;
 
 		case SSQUOTE:
-			if (c == '\'') {
+			if (c == ord('\'')) {
 				POP_STATE();
 				if ((cf & HEREDOC) || state == SQBRACE)
 					goto store_char;
@@ -618,7 +608,7 @@ yylex(int cf)
 			break;
 
 		case SDQUOTE:
-			if (c == '"') {
+			if (c == ord('"')) {
 				POP_STATE();
 				*wp++ = CQUOTE;
 			} else
@@ -627,15 +617,15 @@ yylex(int cf)
 
 		/* $(( ... )) */
 		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 +657,7 @@ yylex(int cf)
 			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 +666,26 @@ yylex(int cf)
 				 * 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 +694,16 @@ yylex(int cf)
 
 		/* 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 +712,20 @@ yylex(int cf)
 			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 +746,10 @@ yylex(int cf)
 
 		/* 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 +770,10 @@ yylex(int cf)
 					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 +789,26 @@ yylex(int cf)
 			 * $ 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 +822,7 @@ yylex(int cf)
 
 		/* " in << or <<- delimiter */
 		case SHEREDQUOTE:
-			if (c != '"')
+			if (c != ord('"'))
 				goto Subst;
 			POP_SRETRACE();
 			dp = strnul(sp) - 1;
@@ -845,10 +835,10 @@ yylex(int cf)
 			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 +856,12 @@ yylex(int cf)
 
 		/* 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++ = ' ';
@@ -885,7 +875,7 @@ yylex(int cf)
 	Xcheck(ws, wp);
 	if (statep != &states[1])
 		/* XXX figure out what is missing */
-		yyerror("no closing quote\n");
+		yyerror("no closing quote");
 
 	/* This done to avoid tests for SHEREDELIM wherever SBASE tested */
 	if (state == SHEREDELIM)
@@ -893,17 +883,15 @@ yylex(int cf)
 
 	dp = Xstring(ws, wp);
 	if (state == SBASE && (
-#ifndef MKSH_LEGACY_MODE
 	    (c == '&' && !Flag(FSH) && !Flag(FPOSIX)) ||
-#endif
-	    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;
 			}
@@ -914,22 +902,22 @@ yylex(int cf)
 
 		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);
@@ -950,29 +938,30 @@ yylex(int cf)
 		/* 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 {
@@ -1016,21 +1005,18 @@ yylex(int cf)
 	while ((dp - ident) < IDENT && (c = *sp++) == CHAR)
 		*dp++ = *sp++;
 	if (c != EOS)
-		/* word is not unquoted */
+		/* word is not unquoted, or space ran out */
 		dp = ident;
 	/* make sure the ident array stays NUL padded */
 	memset(dp, 0, (ident + IDENT) - dp + 1);
 
-	if (!(cf & (KEYWORD | ALIAS)))
-		return (LWORD);
-
-	if (*ident != '\0') {
+	if (*ident != '\0' && (cf & (KEYWORD | ALIAS))) {
 		struct tbl *p;
 		uint32_t h = hash(ident);
 
 		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);
 		}
@@ -1043,7 +1029,7 @@ yylex(int cf)
 			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
@@ -1068,6 +1054,7 @@ yylex(int cf)
 				s->start = s->str = p->val.s;
 				s->u.tblp = p;
 				s->flags |= SF_HASALIAS;
+				s->line = source->line;
 				s->next = source;
 				if (source->type == SEOF) {
 					/* prevent infinite recursion at EOS */
@@ -1079,9 +1066,12 @@ yylex(int cf)
 				goto Again;
 			}
 		}
-	} else if (cf & ALIAS) {
+	} else if (*ident == '\0') {
 		/* retain typeset et al. even when quoted */
-		if (assign_command((dp = wdstrip(yylval.cp, 0)), true))
+		struct tbl *tt = get_builtin((dp = wdstrip(yylval.cp, 0)));
+		uint32_t flag = tt ? tt->flag : 0;
+
+		if (flag & (DECL_UTIL | DECL_FWDR))
 			strlcpy(ident, dp, sizeof(ident));
 		afree(dp, ATEMP);
 	}
@@ -1137,7 +1127,7 @@ readhere(struct ioword *iop)
 	if (!*eofp) {
 		/* end of here document marker, what to do? */
 		switch (c) {
-		case /*(*/ ')':
+		case ord(/*(*/ ')'):
 			if (!subshell_nesting_type)
 				/*-
 				 * not allowed outside $(...) or (...)
@@ -1152,7 +1142,7 @@ readhere(struct ioword *iop)
 			 * 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;
 		}
@@ -1204,6 +1194,7 @@ yyerror(const char *fmt, ...)
 	error_prefix(true);
 	va_start(va, fmt);
 	shf_vfprintf(shl_out, fmt, va);
+	shf_putc('\n', shl_out);
 	va_end(va);
 	errorfz();
 }
@@ -1233,7 +1224,7 @@ getsc_uu(void)
 	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 +1266,7 @@ getsc_uu(void)
 				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 +1426,7 @@ getsc_line(Source *s)
 	} 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 +1519,7 @@ pprompt(const char *cp, int ntruncate)
 	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 +1529,7 @@ pprompt(const char *cp, int ntruncate)
 				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 +1571,39 @@ get_brace_var(XString *wsp, char *wp)
 
 				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 +1611,15 @@ get_brace_var(XString *wsp, char *wp)
 				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 ]\n");
+						yyerror("missing ]");
 					*wp++ = c;
-					for (p = tmp; *p; ) {
+					p = tmp;
+					while (*p) {
 						Xcheck(*wsp, wp);
 						*wp++ = *p++;
 					}
@@ -1640,7 +1632,7 @@ get_brace_var(XString *wsp, char *wp)
  next:
 			break;
 		case PS_NUMBER:
-			if (!ksh_isdigit(c))
+			if (!ctype(c, C_DIGIT))
 				goto out;
 			break;
 		case PS_VAR1:
@@ -1675,9 +1667,9 @@ arraysub(char **strp)
 		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 +1748,19 @@ yyskiputf8bom(void)
 {
 	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/lksh.1 b/lksh.1
index 8d297b7..c3a82cb 100644
--- a/lksh.1
+++ b/lksh.1
@@ -1,6 +1,6 @@
-.\" $MirOS: src/bin/mksh/lksh.1,v 1.19 2016/08/25 16:21:33 tg Exp $
+.\" $MirOS: src/bin/mksh/lksh.1,v 1.23 2017/04/02 13:38:02 tg Exp $
 .\"-
-.\" Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016
+.\" Copyright (c) 2008, 2009, 2010, 2012, 2013, 2015, 2016, 2017
 .\"	mirabilos <m at mirbsd.org>
 .\"
 .\" Provided that these terms and disclaimer and all copyright notices
@@ -74,7 +74,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" use our own definition. And .Dd must come *first*, always.
 .\"
-.Dd $Mdocdate: August 25 2016 $
+.Dd $Mdocdate: April 2 2017 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -173,37 +173,34 @@ It is built on
 refer to its manual page for details on the scripting language.
 It is recommended to port scripts to
 .Nm mksh
-instead of relying on legacy or idiotic POSIX-mandated behaviour,
+instead of relying on legacy or objectionable POSIX-mandated behaviour,
 since the MirBSD Korn Shell scripting language is much more consistent.
 .Pp
+Do not use
+.Nm
+as an interactive or login shell; use
+.Nm mksh
+instead.
+.Pp
 Note that it's strongly recommended to invoke
 .Nm
-with at least the
+with
 .Fl o Ic posix
-option, if not both that
-.Em and Fl o Ic sh ,
 to fully enjoy better compatibility to the
 .Tn POSIX
 standard (which is probably why you use
 .Nm
 over
 .Nm mksh
-in the first place) or legacy scripts, respectively.
+in the first place);
+.Fl o Ic sh
+(possibly additionally to the above) may be needed for some legacy scripts.
 .Sh LEGACY MODE
 .Nm
 currently has the following differences from
 .Nm mksh :
 .Bl -bullet
 .It
-.\"XXX TODO: remove (some systems may wish to have lksh as ksh)
-There is no explicit support for interactive use,
-nor any command line editing or history code.
-Hence,
-.Nm
-is not suitable as a user's login shell, either; use
-.Nm mksh
-instead.
-.It
 The
 .Ev KSH_VERSION
 string identifies
@@ -221,8 +218,8 @@ for the versions this document applies to.
 .Nm
 uses
 .Tn POSIX
-arithmetics, which has quite a few implications:
-The data type for arithmetics is the host
+arithmetic, which has quite a few implications:
+The data type for arithmetic operations is the host
 .Tn ISO
 C
 .Vt long
@@ -241,25 +238,11 @@ Division of the largest negative number by \-1 is Undefined Behaviour.
 The compiler is permitted to delete all data and crash the system
 if Undefined Behaviour occurs (see above for an example).
 .It
-.\"XXX TODO: move this to FPOSIX
 The rotation arithmetic operators are not available.
 .It
 The shift arithmetic operators take all bits of the second operand into
 account; if they exceed permitted precision, the result is unspecified.
 .It
-.\"XXX TODO: move this to FPOSIX
-The
-.Tn GNU
-.Nm bash
-extension &\*(Gt to redirect stdout and stderr in one go is not parsed.
-.It
-.\"XXX TODO: drop along with allowing interactivity
-The
-.Nm mksh
-command line option
-.Fl T
-is not available.
-.It
 Unless
 .Ic set -o posix
 is active,
@@ -275,19 +258,6 @@ passes through the errorlevel from the
 .Xr getopt 1
 command.
 .It
-.\"XXX TODO: move to FPOSIX/FSH
-Unlike
-.At
-.Nm ksh ,
-.Nm mksh
-in
-.Fl o Ic posix
-or
-.Fl o Ic sh
-mode and
-.Nm lksh
-do not keep file descriptors \*(Gt 2 private from sub-processes.
-.It
 Functions defined with the
 .Ic function
 reserved word share the shell options
@@ -297,16 +267,10 @@ instead of locally scoping them.
 .Sh SEE ALSO
 .Xr mksh 1
 .Pp
-.Pa https://www.mirbsd.org/mksh.htm
+.Pa http://www.mirbsd.org/mksh.htm
 .Pp
-.Pa https://www.mirbsd.org/ksh\-chan.htm
+.Pa http://www.mirbsd.org/ksh\-chan.htm
 .Sh CAVEATS
-The distinction between the shell variants
-.Pq Nm lksh / Nm mksh
-and shell flags
-.Pq Fl o Ic posix / Ic sh
-will be reworked for an upcoming release.
-.Pp
 To use
 .Nm
 as
@@ -315,16 +279,22 @@ compilation to enable
 .Ic set -o posix
 by default if called as
 .Nm sh
+.Pq adding Dv \-DMKSH_BINSHPOSIX to Dv CPPFLAGS
 is highly recommended for better standards compliance.
+.Pp
 For better compatibility with legacy scripts, such as many
 .Tn Debian
 maintainer scripts, Upstart and SYSV init scripts, and other
-unfixed scripts, using the compile-time options for enabling
+unfixed scripts, also adding the
+.Dv \-DMKSH_BINSHREDUCED
+compile-time option to enable
 .Em both
 .Ic set -o posix -o sh
 when the shell is run as
-.Nm sh
-is recommended.
+.Nm sh ,
+as well as integrating the optional disrecommended
+.Xr printf 1
+builtin, might be necessary.
 .Pp
 .Nm
 tries to make a cross between a legacy bourne/posix compatibl-ish
@@ -332,14 +302,6 @@ shell and a legacy pdksh-alike but
 .Dq legacy
 is not exactly specified.
 .Pp
-The
-.Ic set
-built-in command does not currently have all options one would expect
-from a full-blown
-.Nm mksh
-or
-.Nm pdksh .
-.Pp
 Talk to the
 .Mx
 development team using the mailing list at
diff --git a/main.c b/main.c
index 14b1a17..0b48639 100644
--- a/main.c
+++ b/main.c
@@ -5,7 +5,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -34,9 +34,7 @@
 #include <locale.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/main.c,v 1.318 2016/09/01 12:59:10 tg Exp $");
-
-extern char **environ;
+__RCSID("$MirOS: src/bin/mksh/main.c,v 1.345 2017/10/14 21:05:22 tg Exp $");
 
 #ifndef MKSHRC_PATH
 #define MKSHRC_PATH	"~/.mkshrc"
@@ -52,32 +50,36 @@ void chvt_reinit(void);
 static void reclaim(void);
 static void remove_temps(struct temp *);
 static mksh_uari_t rndsetup(void);
+static void init_environ(void);
 #ifdef SIGWINCH
 static void x_sigwinch(int);
 #endif
 
-static const char initifs[] = "IFS= \t\n";
-
 static const char initsubs[] =
-    "${PS2=> } ${PS3=#? } ${PS4=+ } ${SECONDS=0} ${TMOUT=0} ${EPOCHREALTIME=}";
+    "${PS2=> }"
+    "${PS3=#? }"
+    "${PS4=+ }"
+    "${SECONDS=0}"
+    "${TMOUT=0}"
+    "${EPOCHREALTIME=}";
 
 static const char *initcoms[] = {
 	Ttypeset, "-r", initvsn, NULL,
 	Ttypeset, "-x", "HOME", TPATH, TSHELL, NULL,
 	Ttypeset, "-i10", "COLUMNS", "LINES", "SECONDS", "TMOUT", NULL,
 	Talias,
-	"integer=\\typeset -i",
-	"local=\\typeset",
+	"integer=\\\\builtin typeset -i",
+	"local=\\\\builtin typeset",
 	/* not "alias -t --": hash -r needs to work */
-	"hash=\\builtin alias -t",
-	"type=\\builtin whence -v",
-	"autoload=\\typeset -fu",
-	"functions=\\typeset -f",
-	"history=\\builtin fc -l",
-	"nameref=\\typeset -n",
+	"hash=\\\\builtin alias -t",
+	"type=\\\\builtin whence -v",
+	"autoload=\\\\builtin typeset -fu",
+	"functions=\\\\builtin typeset -f",
+	"history=\\\\builtin fc -l",
+	"nameref=\\\\builtin typeset -n",
 	"nohup=nohup ",
-	"r=\\builtin fc -e -",
-	"login=\\exec login",
+	"r=\\\\builtin fc -e -",
+	"login=\\\\builtin exec login",
 	NULL,
 	 /* this is what AT&T ksh seems to track, with the addition of emacs */
 	Talias, "-tU",
@@ -96,6 +98,51 @@ static bool initio_done;
 static struct env env;
 struct env *e = &env;
 
+/* compile-time assertions */
+#define cta(name, expr) struct cta_ ## name { char t[(expr) ? 1 : -1]; }
+
+/* this one should be defined by the standard */
+cta(char_is_1_char, (sizeof(char) == 1) && (sizeof(signed char) == 1) &&
+    (sizeof(unsigned char) == 1));
+cta(char_is_8_bits, ((CHAR_BIT) == 8) && ((int)(unsigned char)0xFF == 0xFF) &&
+    ((int)(unsigned char)0x100 == 0) && ((int)(unsigned char)(int)-1 == 0xFF));
+/* the next assertion is probably not really needed */
+cta(short_is_2_char, sizeof(short) == 2);
+cta(short_size_no_matter_of_signedness, sizeof(short) == sizeof(unsigned short));
+/* the next assertion is probably not really needed */
+cta(int_is_4_char, sizeof(int) == 4);
+cta(int_size_no_matter_of_signedness, sizeof(int) == sizeof(unsigned int));
+
+cta(long_ge_int, sizeof(long) >= sizeof(int));
+cta(long_size_no_matter_of_signedness, sizeof(long) == sizeof(unsigned long));
+
+#ifndef MKSH_LEGACY_MODE
+/* the next assertion is probably not really needed */
+cta(ari_is_4_char, sizeof(mksh_ari_t) == 4);
+/* but this is */
+cta(ari_has_31_bit, 0 < (mksh_ari_t)(((((mksh_ari_t)1 << 15) << 15) - 1) * 2 + 1));
+/* the next assertion is probably not really needed */
+cta(uari_is_4_char, sizeof(mksh_uari_t) == 4);
+/* but the next three are; we REQUIRE unsigned integer wraparound */
+cta(uari_has_31_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 2 + 1));
+cta(uari_has_32_bit, 0 < (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3));
+cta(uari_wrap_32_bit,
+    (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 3) >
+    (mksh_uari_t)(((((mksh_uari_t)1 << 15) << 15) - 1) * 4 + 4));
+#endif
+/* these are always required */
+cta(ari_is_signed, (mksh_ari_t)-1 < (mksh_ari_t)0);
+cta(uari_is_unsigned, (mksh_uari_t)-1 > (mksh_uari_t)0);
+/* we require these to have the precisely same size and assume 2s complement */
+cta(ari_size_no_matter_of_signedness, sizeof(mksh_ari_t) == sizeof(mksh_uari_t));
+
+cta(sizet_size_no_matter_of_signedness, sizeof(ssize_t) == sizeof(size_t));
+cta(sizet_voidptr_same_size, sizeof(size_t) == sizeof(void *));
+cta(sizet_funcptr_same_size, sizeof(size_t) == sizeof(void (*)(void)));
+/* our formatting routines assume this */
+cta(ptr_fits_in_long, sizeof(size_t) <= sizeof(long));
+cta(ari_fits_in_long, sizeof(mksh_ari_t) <= sizeof(long));
+
 static mksh_uari_t
 rndsetup(void)
 {
@@ -111,10 +158,8 @@ rndsetup(void)
 	char *cp;
 
 	cp = alloc(sizeof(*bufptr) - sizeof(ALLOC_ITEM), APERM);
-#ifdef DEBUG
-	/* clear the allocated space, for valgrind */
+	/* clear the allocated space, for valgrind and to avoid UB */
 	memset(cp, 0, sizeof(*bufptr) - sizeof(ALLOC_ITEM));
-#endif
 	/* undo what alloc() did to the malloc result address */
 	bufptr = (void *)(cp - sizeof(ALLOC_ITEM));
 	/* PIE or something similar provides us with deltas here */
@@ -190,10 +235,13 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 	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))
-			setmode(i, O_BINARY);
+	os2_init(&argc, &argv);
 #endif
 
 	/* do things like getpgrp() et al. */
@@ -224,7 +272,7 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 	ccp = kshname;
 	goto begin_parsing_kshname;
 	while ((i = ccp[argi++])) {
-		if (i == '/') {
+		if (mksh_cdirsep(i)) {
 			ccp += argi;
  begin_parsing_kshname:
 			argi = 0;
@@ -285,8 +333,6 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 
 	initvar();
 
-	initctypes();
-
 	inittraps();
 
 	coproc_init();
@@ -350,19 +396,13 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 #endif
 
 	/* import environment */
-	if (environ != NULL) {
-		wp = (const char **)environ;
-		while (*wp != NULL) {
-			rndpush(*wp);
-			typeset(*wp, IMPORT | EXPORT, 0, 0, 0);
-			++wp;
-		}
-	}
+	init_environ();
 
 	/* for security */
-	typeset(initifs, 0, 0, 0, 0);
+	typeset(TinitIFS, 0, 0, 0, 0);
 
 	/* assign default shell variable values */
+	typeset("PATHSEP=" MKSH_PATHSEPS, 0, 0, 0, 0);
 	substitute(initsubs, 0);
 
 	/* Figure out the current working directory and set $PWD */
@@ -379,7 +419,7 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 		setstr(vp, current_wd, KSH_RETURN_ERROR);
 
 	for (wp = initcoms; *wp != NULL; wp++) {
-		shcomexec(wp);
+		c_builtin(wp);
 		while (*wp != NULL)
 			wp++;
 	}
@@ -442,7 +482,7 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 		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++;
 		}
@@ -465,7 +505,9 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 		 * to search for it. This changes the behaviour of a
 		 * simple "mksh foo", but can't be helped.
 		 */
-		s->file = search_path(argv[argi++], path, X_OK, NULL);
+		s->file = argv[argi++];
+		if (search_access(s->file, X_OK) != 0)
+			s->file = search_path(s->file, path, X_OK, NULL);
 		if (!s->file || !*s->file)
 			s->file = argv[argi - 1];
 #else
@@ -563,8 +605,8 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 #endif
 		if (!isuc(ccp))
 			ccp = null;
-		/* FALLTHROUGH */
 #endif
+		/* FALLTHROUGH */
 
 	/* auto-detect from environment */
 	case 3:
@@ -611,8 +653,7 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 		if (Flag(FLOGIN))
 			include(substitute("$HOME/.profile", 0), 0, NULL, true);
 		if (Flag(FTALKING)) {
-			cp = substitute(substitute("${ENV:-" MKSHRC_PATH "}",
-			    0), DOTILDE);
+			cp = substitute("${ENV:-" MKSHRC_PATH "}", DOTILDE);
 			if (cp[0] != '\0')
 				include(cp, 0, NULL, true);
 		}
@@ -624,7 +665,7 @@ main_init(int argc, const char *argv[], Source **sp, struct block **lp)
 	}
 
 	if (restricted_shell) {
-		shcomexec(restr_com);
+		c_builtin(restr_com);
 		/* After typeset command... */
 		Flag(FRESTRICTED) = 1;
 	}
@@ -654,9 +695,9 @@ main(int argc, const char *argv[])
 
 	if ((rv = main_init(argc, argv, &s, &l)) == 0) {
 		if (Flag(FAS_BUILTIN)) {
-			rv = shcomexec(l->argv);
+			rv = c_builtin(l->argv);
 		} else {
-			shell(s, true);
+			shell(s, 0);
 			/* NOTREACHED */
 		}
 	}
@@ -709,7 +750,7 @@ include(const char *name, int argc, const char **argv, bool intr_ok)
 			unwind(i);
 			/* NOTREACHED */
 		default:
-			internal_errorf("include %d", i);
+			internal_errorf(Tunexpected_type, Tunwind, Tsource, i);
 			/* NOTREACHED */
 		}
 	}
@@ -720,7 +761,7 @@ include(const char *name, int argc, const char **argv, bool intr_ok)
 	s = pushs(SFILE, ATEMP);
 	s->u.shf = shf;
 	strdupx(s->file, name, ATEMP);
-	i = shell(s, false);
+	i = shell(s, 1);
 	quitenv(s->u.shf);
 	if (old_argv) {
 		e->loc->argv = old_argv;
@@ -740,7 +781,7 @@ command(const char *comm, int line)
 	s = pushs(SSTRING, ATEMP);
 	s->start = s->str = comm;
 	s->line = line;
-	rv = shell(s, false);
+	rv = shell(s, 1);
 	source = sold;
 	return (rv);
 }
@@ -749,22 +790,33 @@ command(const char *comm, int line)
  * run the commands from the input source, returning status.
  */
 int
-shell(Source * volatile s, volatile bool toplevel)
+shell(Source * volatile s, volatile int level)
 {
 	struct op *t;
 	volatile bool wastty = tobool(s->flags & SF_TTY);
 	volatile uint8_t attempts = 13;
-	volatile bool interactive = Flag(FTALKING) && toplevel;
+	volatile bool interactive = (level == 0) && Flag(FTALKING);
 	volatile bool sfirst = true;
 	Source *volatile old_source = source;
 	int i;
 
-	newenv(E_PARSE);
+	newenv(level == 2 ? E_EVAL : E_PARSE);
 	if (interactive)
 		really_exit = false;
 	switch ((i = kshsetjmp(e->jbuf))) {
 	case 0:
 		break;
+	case LBREAK:
+	case LCONTIN:
+		if (level != 2) {
+			source = old_source;
+			quitenv(NULL);
+			internal_errorf(Tf_cant_s, Tshell,
+			    i == LBREAK ? Tbreak : Tcontinue);
+			/* NOTREACHED */
+		}
+		/* assert: interactive == false */
+		/* FALLTHROUGH */
 	case LINTR:
 		/* we get here if SIGINT not caught or ignored */
 	case LERROR:
@@ -804,7 +856,7 @@ shell(Source * volatile s, volatile bool toplevel)
 	default:
 		source = old_source;
 		quitenv(NULL);
-		internal_errorf("shell %d", i);
+		internal_errorf(Tunexpected_type, Tunwind, Tshell, i);
 		/* NOTREACHED */
 	}
 	while (/* CONSTCOND */ 1) {
@@ -821,7 +873,7 @@ shell(Source * volatile s, volatile bool toplevel)
 			j_notify();
 			set_prompt(PS1, s);
 		}
-		t = compile(s, sfirst);
+		t = compile(s, sfirst, true);
 		if (interactive)
 			histsave(&s->line, NULL, HIST_FLUSH, true);
 		sfirst = false;
@@ -842,7 +894,7 @@ shell(Source * volatile s, volatile bool toplevel)
 				 * immediately after the last command
 				 * executed.
 				 */
-				if (toplevel)
+				if (level == 0)
 					unwind(LEXIT);
 				break;
 			}
@@ -905,6 +957,7 @@ unwind(int i)
 		case E_INCL:
 		case E_LOOP:
 		case E_ERRH:
+		case E_EVAL:
 			kshlongjmp(e->jbuf, i);
 			/* NOTREACHED */
 		case E_NONE:
@@ -1268,12 +1321,10 @@ bi_errorf(const char *fmt, ...)
 	    VWARNINGF_BUILTIN, fmt, va);
 	va_end(va);
 
-	/*
-	 * POSIX special builtins and ksh special builtins cause
-	 * non-interactive shells to exit. XXX may not want LERROR here
-	 */
+	/* POSIX special builtins cause non-interactive shells to exit */
 	if (builtin_spec) {
 		builtin_argv0 = NULL;
+		/* may not want to use LERROR here */
 		unwind(LERROR);
 	}
 }
@@ -1376,19 +1427,19 @@ initio(void)
 #ifdef DF
 	if ((lfp = getenv("SDMKSH_PATH")) == NULL) {
 		if ((lfp = getenv("HOME")) == NULL || !mksh_abspath(lfp))
-			errorf("cannot get home directory");
+			errorf("can't get home directory");
 		lfp = shf_smprintf(Tf_sSs, lfp, "mksh-dbg.txt");
 	}
 
 	if ((shl_dbg_fd = open(lfp, O_WRONLY | O_APPEND | O_CREAT, 0600)) < 0)
-		errorf("cannot open debug output file %s", lfp);
+		errorf("can't open debug output file %s", lfp);
 	if (shl_dbg_fd < FDBASE) {
 		int nfd;
 
 		nfd = fcntl(shl_dbg_fd, F_DUPFD, FDBASE);
 		close(shl_dbg_fd);
 		if ((shl_dbg_fd = nfd) == -1)
-			errorf("cannot dup debug output file");
+			errorf("can't dup debug output file");
 	}
 	fcntl(shl_dbg_fd, F_SETFD, FD_CLOEXEC);
 	shf_fdopen(shl_dbg_fd, SHF_WR, shl_dbg);
@@ -1404,7 +1455,7 @@ ksh_dup2(int ofd, int nfd, bool errok)
 	int rv;
 
 	if (((rv = dup2(ofd, nfd)) < 0) && !errok && (errno != EBADF))
-		errorf("too many files open in shell");
+		errorf(Ttoo_many_files);
 
 #ifdef __ultrix
 	/*XXX imake style */
@@ -1428,7 +1479,7 @@ savefd(int fd)
 	    (errno == EBADF || errno == EPERM))
 		return (-1);
 	if (nfd < 0 || nfd > SHRT_MAX)
-		errorf("too many files open in shell");
+		errorf(Ttoo_many_files);
 	fcntl(nfd, F_SETFD, FD_CLOEXEC);
 	return ((short)nfd);
 }
@@ -1461,6 +1512,10 @@ openpipe(int *pv)
 	pv[1] = savefd(lpv[1]);
 	if (pv[1] != lpv[1])
 		close(lpv[1]);
+#ifdef __OS2__
+	setmode(pv[0], O_BINARY);
+	setmode(pv[1], O_BINARY);
+#endif
 }
 
 void
@@ -1483,7 +1538,7 @@ check_fd(const char *name, int mode, const char **emsgp)
 		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";
@@ -1822,7 +1877,7 @@ tnamecmp(const void *p1, const void *p2)
 	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 **
@@ -1913,3 +1968,84 @@ x_mkraw(int fd, mksh_ttyst *ocb, bool forread)
 
 	mksh_tcset(fd, &cb);
 }
+
+#ifdef MKSH_ENVDIR
+static void
+init_environ(void)
+{
+	char *xp;
+	ssize_t n;
+	XString xs;
+	struct shf *shf;
+	DIR *dirp;
+	struct dirent *dent;
+
+	if ((dirp = opendir(MKSH_ENVDIR)) == NULL) {
+		warningf(false, "cannot read environment from %s: %s",
+		    MKSH_ENVDIR, cstrerror(errno));
+		return;
+	}
+	XinitN(xs, 256, ATEMP);
+ read_envfile:
+	errno = 0;
+	if ((dent = readdir(dirp)) != NULL) {
+		if (skip_varname(dent->d_name, true)[0] == '\0') {
+			xp = shf_smprintf(Tf_sSs, MKSH_ENVDIR, dent->d_name);
+			if (!(shf = shf_open(xp, O_RDONLY, 0, 0))) {
+				warningf(false,
+				    "cannot read environment %s from %s: %s",
+				    dent->d_name, MKSH_ENVDIR,
+				    cstrerror(errno));
+				goto read_envfile;
+			}
+			afree(xp, ATEMP);
+			n = strlen(dent->d_name);
+			xp = Xstring(xs, xp);
+			XcheckN(xs, xp, n + 32);
+			memcpy(xp, dent->d_name, n);
+			xp += n;
+			*xp++ = '=';
+			while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
+				xp += n;
+				if (Xnleft(xs, xp) <= 0)
+					XcheckN(xs, xp, Xlength(xs, xp));
+			}
+			if (n < 0) {
+				warningf(false,
+				    "cannot read environment %s from %s: %s",
+				    dent->d_name, MKSH_ENVDIR,
+				    cstrerror(shf_errno(shf)));
+			} else {
+				*xp = '\0';
+				xp = Xstring(xs, xp);
+				rndpush(xp);
+				typeset(xp, IMPORT | EXPORT, 0, 0, 0);
+			}
+			shf_close(shf);
+		}
+		goto read_envfile;
+	} else if (errno)
+		warningf(false, "cannot read environment from %s: %s",
+		    MKSH_ENVDIR, cstrerror(errno));
+	closedir(dirp);
+	Xfree(xs, xp);
+}
+#else
+extern char **environ;
+
+static void
+init_environ(void)
+{
+	const char **wp;
+
+	if (environ == NULL)
+		return;
+
+	wp = (const char **)environ;
+	while (*wp != NULL) {
+		rndpush(*wp);
+		typeset(*wp, IMPORT | EXPORT, 0, 0, 0);
+		++wp;
+	}
+}
+#endif
diff --git a/misc.c b/misc.c
index 1cdbf6b..44b5f0d 100644
--- a/misc.c
+++ b/misc.c
@@ -3,8 +3,10 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
+ * Copyright (c) 2015
+ *	Daniel Richard G. <skunk at 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.245 2016/08/01 18:42:42 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.288 2017/10/15 20:21:51 tg Exp $");
 
 #define KSH_CHVT_FLAG
 #ifdef MKSH_SMALL
@@ -40,10 +42,6 @@ __RCSID("$MirOS: src/bin/mksh/misc.c,v 1.245 2016/08/01 18:42:42 tg Exp $");
 #define KSH_CHVT_CODE
 #define KSH_CHVT_FLAG
 #endif
-#ifdef MKSH_LEGACY_MODE
-#undef KSH_CHVT_CODE
-#undef KSH_CHVT_FLAG
-#endif
 
 /* type bits for unsigned char */
 unsigned char chtypes[UCHAR_MAX + 1];
@@ -51,7 +49,8 @@ unsigned char chtypes[UCHAR_MAX + 1];
 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
@@ -72,38 +71,6 @@ static int make_path(const char *, const char *, char **, XString *, int *);
 #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_ALPHA);
-	setctypes(letters_lc, C_ALPHA);
-	chtypes['_'] |= C_ALPHA;
-	setctypes("0123456789", C_DIGIT);
-	/* \0 added automatically */
-	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 *
@@ -152,7 +119,7 @@ option(const char *n)
 {
 	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);
@@ -195,6 +162,7 @@ printoptions(bool verbose)
 	if (verbose) {
 		size_t n = 0, len, octs = 0;
 		struct options_info oi;
+		struct columnise_opts co;
 
 		/* verbose version */
 		shf_puts("Current option settings\n", shl_stdout);
@@ -211,8 +179,11 @@ printoptions(bool verbose)
 			}
 			++i;
 		}
-		print_columns(shl_stdout, n, options_fmt_entry, &oi,
-		    octs + 4, oi.opt_width + 4, true);
+		co.shf = shl_stdout;
+		co.linesep = '\n';
+		co.prefcol = co.do_last = true;
+		print_columns(&co, n, options_fmt_entry, &oi,
+		    octs + 4, oi.opt_width + 4);
 	} else {
 		/* short version like AT&T ksh93 */
 		shf_puts(Tset, shl_stdout);
@@ -300,6 +271,11 @@ change_flag(enum sh_flag f, int what, bool newset)
 	} 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)
@@ -387,7 +363,7 @@ parse_args(const char **argv,
 		 */
 		if (*p != '-')
 			for (q = p; *q; )
-				if (*q++ == '/')
+				if (mksh_cdirsep(*q++))
 					p = q;
 		Flag(FLOGIN) = (*p == '-');
 		opts = cmd_opts;
@@ -484,7 +460,7 @@ parse_args(const char **argv,
 		}
 	}
 	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] == '-') {
@@ -502,7 +478,7 @@ parse_args(const char **argv,
 	if (arrayset) {
 		const char *ccp = NULL;
 
-		if (*array)
+		if (array && *array)
 			ccp = skip_varname(array, false);
 		if (!ccp || !(!ccp[0] || (ccp[0] == '+' && !ccp[1]))) {
 			bi_errorf(Tf_sD_s, array, Tnot_ident);
@@ -513,7 +489,7 @@ parse_args(const char **argv,
 		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),
@@ -534,7 +510,7 @@ getn(const char *s, int *ai)
 
 	do {
 		c = *s++;
-	} while (ksh_isspace(c));
+	} while (ctype(c, C_SPACE));
 
 	switch (c) {
 	case '-':
@@ -546,7 +522,7 @@ getn(const char *s, int *ai)
 	}
 
 	do {
-		if (!ksh_isdigit(c))
+		if (!ctype(c, C_DIGIT))
 			/* not numeric */
 			return (0);
 		if (num.u > 214748364U)
@@ -586,7 +562,7 @@ simplify_gmatch_pattern(const unsigned char *sp)
 	sp = cp;
  simplify_gmatch_pat1a:
 	dp = cp;
-	se = sp + strlen((const void *)sp);
+	se = strnul(sp);
 	while ((c = *sp++)) {
 		if (!ISMAGIC(c)) {
 			*dp++ = c;
@@ -658,29 +634,30 @@ gmatchx(const char *s, const char *p, bool isfile)
 	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);
 }
@@ -691,7 +668,7 @@ gmatchx(const char *s, const char *p, bool isfile)
  * 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,
@@ -702,76 +679,101 @@ gmatchx(const char *s, const char *p, bool isfile)
  *	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;
-			}
-			/*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 (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 */
 			}
-		} 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';
@@ -779,15 +781,39 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
 		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) {
@@ -796,39 +822,39 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
 			}
 			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)
@@ -837,24 +863,24 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
 			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)
@@ -863,7 +889,7 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
 			return (0);
 
 		/* matches none of the patterns */
-		case 0x80|'!':
+		case 0x80|ord('!'):
 			if (!(prest = pat_scan(p, pe, false)))
 				return (0);
 			s--;
@@ -873,7 +899,7 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
 				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;
 					}
@@ -881,7 +907,7 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
 						break;
 				}
 				if (!matched &&
-				    do_gmatch(srest, se, prest, pe))
+				    do_gmatch(srest, se, prest, pe, smin))
 					return (1);
 			}
 			return (0);
@@ -891,55 +917,245 @@ do_gmatch(const unsigned char *s, const unsigned char *se,
 				return (0);
 			break;
 		}
+		sl = sc;
 	}
 	return (s == se);
 }
 
+/*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	}
+};
+
 static const unsigned char *
-gmatch_cclass(const unsigned char *p, unsigned char sub)
+gmatch_cclass(const unsigned char *pat, unsigned char sc)
 {
-	unsigned char c, d;
-	bool notp, found = false;
-	const unsigned char *orig_p = p;
-
-	if ((notp = tobool(ISMAGIC(*p) && *++p == '!')))
-		p++;
-	do {
-		c = *p++;
+	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;
+		/* 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 */
+
+					/* 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 = '(' /*)*/;
 			}
-			/* POSIX says this is an invalid expression */
-			if (c > d)
-				return (NULL);
-		} else
-			d = c;
-		if (c == sub || (c <= sub && sub <= d))
+		}
+		/* now do the actual range match check */
+		if (lc != 0 /* && c != 0 */ &&
+		    asciibetical(lc) <= asciibetical(sc) &&
+		    asciibetical(sc) <= asciibetical(c))
 			found = true;
-	} while (!(ISMAGIC(p[0]) && p[1] == ']'));
-
-	return ((found != notp) ? p+2 : NULL);
+		/* 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 */
@@ -954,16 +1170,30 @@ pat_scan(const unsigned char *p, const unsigned char *pe, bool match_sep)
 		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)
+{
+	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 (strcmp(*(const char * const *)p1, *(const char * const *)p2));
+	return (ascstrcmp(*(const char * const *)pstr1,
+	    *(const char * const *)pstr2));
 }
 
 /* Initialise a Getopt structure */
@@ -1033,7 +1263,7 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
 		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;
@@ -1087,13 +1317,14 @@ ksh_getopt(const char **argv, Getopt *go, const char *optionsp)
 		 * 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
@@ -1116,8 +1347,8 @@ print_value_quoted(struct shf *shf, const char *s)
 	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;
@@ -1155,6 +1386,7 @@ print_value_quoted(struct shf *shf, const char *s)
 		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) {
@@ -1163,10 +1395,11 @@ print_value_quoted(struct shf *shf, const char *s)
 					continue;
 				}
 			}
+#endif
 			++p;
 			switch (c) {
 			/* see unbksl() in this file for comments */
-			case 7:
+			case KSH_BEL:
 				c = 'a';
 				if (0)
 					/* FALLTHROUGH */
@@ -1190,11 +1423,11 @@ print_value_quoted(struct shf *shf, const char *s)
 				  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 */
@@ -1204,7 +1437,12 @@ print_value_quoted(struct shf *shf, const char *s)
 				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);
@@ -1226,11 +1464,11 @@ print_value_quoted(struct shf *shf, const char *s)
  * the i-th element
  */
 void
-print_columns(struct shf *shf, unsigned int n,
+print_columns(struct columnise_opts *opts, unsigned int n,
     void (*func)(char *, size_t, unsigned int, const void *),
-    const void *arg, size_t max_oct, size_t max_colz, bool prefcol)
+    const void *arg, size_t max_oct, size_t max_colz)
 {
-	unsigned int i, r, c, rows, cols, nspace, max_col;
+	unsigned int i, r = 0, c, rows, cols, nspace, max_col;
 	char *str;
 
 	if (!n)
@@ -1265,16 +1503,18 @@ print_columns(struct shf *shf, unsigned int n,
 
 	/* if we can only print one column anyway, skip the goo */
 	if (cols < 2) {
-		for (i = 0; i < n; ++i) {
-			(*func)(str, max_oct, i, arg);
-			shf_puts(str, shf);
-			shf_putc('\n', shf);
+		goto prcols_easy;
+		while (r < n) {
+			shf_putc(opts->linesep, opts->shf);
+ prcols_easy:
+			(*func)(str, max_oct, r++, arg);
+			shf_puts(str, opts->shf);
 		}
 		goto out;
 	}
 
 	rows = (n + cols - 1) / cols;
-	if (prefcol && cols > rows) {
+	if (opts->prefcol && cols > rows) {
 		cols = rows;
 		rows = (n + cols - 1) / cols;
 	}
@@ -1283,20 +1523,25 @@ print_columns(struct shf *shf, unsigned int n,
 	if (nspace < 2)
 		nspace = 2;
 	max_col = -max_col;
-	for (r = 0; r < rows; r++) {
+	goto prcols_hard;
+	while (r < rows) {
+		shf_putchar(opts->linesep, opts->shf);
+ prcols_hard:
 		for (c = 0; c < cols; c++) {
 			if ((i = c * rows + r) >= n)
 				break;
 			(*func)(str, max_oct, i, arg);
 			if (i + rows >= n)
-				shf_puts(str, shf);
+				shf_puts(str, opts->shf);
 			else
-				shf_fprintf(shf, "%*s%*s",
+				shf_fprintf(opts->shf, "%*s%*s",
 				    (int)max_col, str, (int)nspace, null);
 		}
-		shf_putchar('\n', shf);
+		++r;
 	}
  out:
+	if (opts->do_last)
+		shf_putchar(opts->linesep, opts->shf);
 	afree(str, ATEMP);
 }
 
@@ -1416,6 +1661,15 @@ do_realpath(const char *upath)
 	if (mksh_abspath(upath)) {
 		/* upath is an absolute pathname */
 		strdupx(ipath, upath, ATEMP);
+#ifdef MKSH_DOSPATH
+	} else if (mksh_drvltr(upath)) {
+		/* upath is a drive-relative pathname */
+		if (getdrvwd(&ldest, ord(*upath)))
+			return (NULL);
+		/* A:foo -> A:/cwd/foo; A: -> A:/cwd */
+		ipath = shf_smprintf(Tf_sss, ldest,
+		    upath[2] ? "/" : "", upath + 2);
+#endif
 	} else {
 		/* upath is a relative pathname, prepend cwd */
 		if ((tp = ksh_get_wd()) == NULL || !mksh_abspath(tp))
@@ -1432,14 +1686,14 @@ do_realpath(const char *upath)
 
 	while (*ip) {
 		/* skip slashes in input */
-		while (*ip == '/')
+		while (mksh_cdirsep(*ip))
 			++ip;
 		if (!*ip)
 			break;
 
 		/* get next pathname component from input */
 		tp = ip;
-		while (*ip && *ip != '/')
+		while (*ip && !mksh_cdirsep(*ip))
 			++ip;
 		len = ip - tp;
 
@@ -1450,8 +1704,9 @@ do_realpath(const char *upath)
 				continue;
 			else if (len == 2 && tp[1] == '.') {
 				/* strip off last pathname component */
+				/*XXX consider a rooted pathname */
 				while (xp > Xstring(xs, xp))
-					if (*--xp == '/')
+					if (mksh_cdirsep(*--xp))
 						break;
 				/* then continue with the next one */
 				continue;
@@ -1474,7 +1729,7 @@ do_realpath(const char *upath)
 			/* lstat failed */
 			if (errno == ENOENT) {
 				/* because the pathname does not exist */
-				while (*ip == '/')
+				while (mksh_cdirsep(*ip))
 					/* skip any trailing slashes */
 					++ip;
 				/* no more components left? */
@@ -1517,11 +1772,23 @@ do_realpath(const char *upath)
 			 * restart if symlink target is an absolute path,
 			 * otherwise continue with currently resolved prefix
 			 */
+#ifdef MKSH_DOSPATH
+ assemble_symlink:
+#endif
 			/* append rest of current input path to link target */
 			tp = shf_smprintf(Tf_sss, ldest, *ip ? "/" : "", ip);
 			afree(ipath, ATEMP);
 			ip = ipath = tp;
-			if (!mksh_abspath(ldest)) {
+			if (!mksh_abspath(ipath)) {
+#ifdef MKSH_DOSPATH
+				/* symlink target might be drive-relative */
+				if (mksh_drvltr(ipath)) {
+					if (getdrvwd(&ldest, ord(*ipath)))
+						goto notfound;
+					ip += 2;
+					goto assemble_symlink;
+				}
+#endif
 				/* symlink target is a relative path */
 				xp = Xrestpos(xs, xp, pos);
 			} else
@@ -1530,14 +1797,22 @@ do_realpath(const char *upath)
 				/* symlink target is an absolute path */
 				xp = Xstring(xs, xp);
  beginning_of_a_pathname:
-				/* assert: (ip == ipath)[0] == '/' */
+				/* assert: mksh_abspath(ip == ipath) */
 				/* assert: xp == xs.beg => start of path */
 
 				/* exactly two leading slashes? (SUSv4 3.266) */
-				if (ip[1] == '/' && ip[2] != '/') {
+				if (ip[1] == ip[0] && !mksh_cdirsep(ip[2])) {
 					/* keep them, e.g. for UNC pathnames */
 					Xput(xs, xp, '/');
 				}
+#ifdef MKSH_DOSPATH
+				/* drive letter? */
+				if (mksh_drvltr(ip)) {
+					/* keep it */
+					Xput(xs, xp, *ip++);
+					Xput(xs, xp, *ip++);
+				}
+#endif
 			}
 		}
 		/* otherwise (no symlink) merely go on */
@@ -1559,7 +1834,7 @@ do_realpath(const char *upath)
 	 * if source path had a trailing slash, check if target path
 	 * is not a non-directory existing file
 	 */
-	if (ip > ipath && ip[-1] == '/') {
+	if (ip > ipath && mksh_cdirsep(ip[-1])) {
 		if (stat(Xstring(xs, xp), &sb)) {
 			if (errno != ENOENT)
 				goto notfound;
@@ -1628,7 +1903,7 @@ make_path(const char *cwd, const char *file,
 
 			if (c == '.')
 				c = file[2];
-			if (c == '/' || c == '\0')
+			if (mksh_cdirsep(c) || c == '\0')
 				use_cdpath = false;
 		}
 
@@ -1650,7 +1925,7 @@ make_path(const char *cwd, const char *file,
 			XcheckN(*xsp, xp, len);
 			memcpy(xp, cwd, len);
 			xp += len;
-			if (cwd[len - 1] != '/')
+			if (!mksh_cdirsep(cwd[len - 1]))
 				Xput(*xsp, xp, '/');
 		}
 		*phys_pathp = Xlength(*xsp, xp);
@@ -1658,7 +1933,7 @@ make_path(const char *cwd, const char *file,
 			XcheckN(*xsp, xp, plen);
 			memcpy(xp, plist, plen);
 			xp += plen;
-			if (plist[plen - 1] != '/')
+			if (!mksh_cdirsep(plist[plen - 1]))
 				Xput(*xsp, xp, '/');
 			rval = 1;
 		}
@@ -1687,6 +1962,15 @@ make_path(const char *cwd, const char *file,
  * ..					..
  * ./foo				foo
  * foo/../../../bar			../../bar
+ * C:/foo/../..				C:/
+ * C:.					C:
+ * C:..					C:..
+ * C:foo/../../blah			C:../blah
+ *
+ * XXX consider a rooted pathname: we cannot really 'cd ..' for
+ * pathnames like: '/', 'c:/', '//foo', '//foo/', '/@unixroot/'
+ * (no effect), 'c:', 'c:.' (effect is retaining the '../') but
+ * we need to honour this throughout the shell
  */
 void
 simplify_path(char *p)
@@ -1694,13 +1978,27 @@ simplify_path(char *p)
 	char *dp, *ip, *sp, *tp;
 	size_t len;
 	bool needslash;
+#ifdef MKSH_DOSPATH
+	bool needdot = true;
+
+	/* keep drive letter */
+	if (mksh_drvltr(p)) {
+		p += 2;
+		needdot = false;
+	}
+#else
+#define needdot true
+#endif
 
 	switch (*p) {
 	case 0:
 		return;
 	case '/':
+#ifdef MKSH_DOSPATH
+	case '\\':
+#endif
 		/* exactly two leading slashes? (SUSv4 3.266) */
-		if (p[1] == '/' && p[2] != '/')
+		if (p[1] == p[0] && !mksh_cdirsep(p[2]))
 			/* keep them, e.g. for UNC pathnames */
 			++p;
 		needslash = true;
@@ -1712,14 +2010,14 @@ simplify_path(char *p)
 
 	while (*ip) {
 		/* skip slashes in input */
-		while (*ip == '/')
+		while (mksh_cdirsep(*ip))
 			++ip;
 		if (!*ip)
 			break;
 
 		/* get next pathname component from input */
 		tp = ip;
-		while (*ip && *ip != '/')
+		while (*ip && !mksh_cdirsep(*ip))
 			++ip;
 		len = ip - tp;
 
@@ -1729,7 +2027,7 @@ simplify_path(char *p)
 				/* just continue with the next one */
 				continue;
 			else if (len == 2 && tp[1] == '.') {
-				/* parent level, but how? */
+				/* parent level, but how? (see above) */
 				if (mksh_abspath(p))
 					/* absolute path, only one way */
 					goto strip_last_component;
@@ -1739,7 +2037,7 @@ simplify_path(char *p)
  strip_last_component:
 					/* strip off last pathname component */
 					while (dp > sp)
-						if (*--dp == '/')
+						if (mksh_cdirsep(*--dp))
 							break;
 				} else {
 					/* relative path, at its beginning */
@@ -1768,10 +2066,15 @@ simplify_path(char *p)
 		needslash = true;
 		/* try next component */
 	}
-	if (dp == p)
-		/* empty path -> dot */
-		*dp++ = needslash ? '/' : '.';
+	if (dp == p) {
+		/* empty path -> dot (or slash, when absolute) */
+		if (needslash)
+			*dp++ = '/';
+		else if (needdot)
+			*dp++ = '.';
+	}
 	*dp = '\0';
+#undef needdot
 }
 
 void
@@ -1885,6 +2188,18 @@ c_cd(const char **wp)
 		return (2);
 	}
 
+#ifdef MKSH_DOSPATH
+	tryp = NULL;
+	if (mksh_drvltr(dir) && !mksh_cdirsep(dir[2]) &&
+	    !getdrvwd(&tryp, ord(*dir))) {
+		dir = shf_smprintf(Tf_sss, tryp,
+		    dir[2] ? "/" : "", dir + 2);
+		afree(tryp, ATEMP);
+		afree(allocd, ATEMP);
+		allocd = dir;
+	}
+#endif
+
 #ifdef MKSH__NO_PATH_MAX
 	/* only a first guess; make_path will enlarge xs if necessary */
 	XinitN(xs, 1024, ATEMP);
@@ -2140,18 +2455,12 @@ getrusage(int what, struct rusage *ru)
 int
 unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
 {
-	int wc, i, c, fc;
+	int wc, i, c, fc, n;
 
 	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';
@@ -2160,11 +2469,11 @@ unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
 		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';
@@ -2179,8 +2488,7 @@ unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
 		wc = '\t';
 		break;
 	case 'v':
-		/* assume ASCII here as well */
-		wc = 11;
+		wc = KSH_VTAB;
 		break;
 	case '1':
 	case '2':
@@ -2203,7 +2511,7 @@ unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
 		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);
@@ -2228,20 +2536,24 @@ unbksl(bool cstyle, int (*fg)(void), void (*fp)(int))
 		 *	four (U: eight) digits; convert to Unicode
 		 */
 		wc = 0;
-		while (i--) {
+		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)
+			goto unknown_escape;
 		if ((cstyle && wc > 0xFF) || fc != 'x')
 			/* Unicode marker */
 			wc += 0x100;
diff --git a/mksh.1 b/mksh.1
index b65ada2..aa67ac9 100644
--- a/mksh.1
+++ b/mksh.1
@@ -1,8 +1,8 @@
-.\" $MirOS: src/bin/mksh/mksh.1,v 1.417 2016/09/29 09:04:56 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,
-.\"		2010, 2011, 2012, 2013, 2014, 2015, 2016
+.\"		2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
 .\"	mirabilos <m at mirbsd.org>
 .\"
 .\" Provided that these terms and disclaimer and all copyright notices
@@ -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: September 29 2016 $
+.Dd $Mdocdate: August 16 2017 $
 .\"
 .\" Check which macro package we use, and do other -mdoc setup.
 .\"
@@ -186,23 +186,8 @@ sometimes does take portable shell scripting or various standards
 into account all information is first and foremost presented with
 .Nm
 in mind and should be taken as such.
-.Ss I'm an Android user, so what's mksh?
-.Nm mksh
-is a
-.Ux
-shell / command interpreter, similar to
-.Nm COMMAND.COM
-or
-.Nm CMD.EXE ,
-which has been included with
-.Tn Android Open Source Project
-for a while now.
-Basically, it's a program that runs in a terminal (console window),
-takes user input and runs commands or scripts, which it can also
-be asked to do by other programs, even in the background.
-Any privilege pop-ups you might be encountering are thus not
-.Nm mksh
-issues but questions by some other program utilising it.
+.Ss I use Android, OS/2, etc. so what...?
+Please see the FAQ at the end of this document.
 .Ss Invocation
 Most builtins can be called directly, for example if a link points from its
 name to the shell; not all make sense, have been tested or work at all though.
@@ -1060,7 +1045,7 @@ For
 and
 .Dq Li \eu#### ,
 .Dq #
-means a hexadecimal digit, of thich there may be none up to four or eight;
+means a hexadecimal digit, of which there may be none up to four or eight;
 these escapes translate a Unicode codepoint to UTF-8.
 Furthermore,
 .Dq Li \eE
@@ -1130,17 +1115,17 @@ also by newline) may be one same parse tree.
 .Pp
 The following command aliases are defined automatically by the shell:
 .Bd -literal -offset indent
-autoload=\*(aq\etypeset \-fu\*(aq
-functions=\*(aq\etypeset \-f\*(aq
-hash=\*(aq\ebuiltin alias \-t\*(aq
-history=\*(aq\ebuiltin fc \-l\*(aq
-integer=\*(aq\etypeset \-i\*(aq
-local=\*(aq\etypeset\*(aq
-login=\*(aq\eexec login\*(aq
-nameref=\*(aq\etypeset \-n\*(aq
+autoload=\*(aq\e\ebuiltin typeset \-fu\*(aq
+functions=\*(aq\e\ebuiltin typeset \-f\*(aq
+hash=\*(aq\e\ebuiltin alias \-t\*(aq
+history=\*(aq\e\ebuiltin fc \-l\*(aq
+integer=\*(aq\e\ebuiltin typeset \-i\*(aq
+local=\*(aq\e\ebuiltin typeset\*(aq
+login=\*(aq\e\ebuiltin exec login\*(aq
+nameref=\*(aq\e\ebuiltin typeset \-n\*(aq
 nohup=\*(aqnohup \*(aq
-r=\*(aq\ebuiltin fc \-e \-\*(aq
-type=\*(aq\ebuiltin whence \-v\*(aq
+r=\*(aq\e\ebuiltin fc \-e \-\*(aq
+type=\*(aq\e\ebuiltin whence \-v\*(aq
 .Ed
 .Pp
 Tracked aliases allow the shell to remember where it found a particular
@@ -2035,9 +2020,11 @@ searched when looking for commands and files sourced using the
 .Dq Li \&.
 command (see below).
 An empty string resulting from a leading or trailing
-colon, or two adjacent colons, is treated as a
+(semi)colon, or two adjacent ones, is treated as a
 .Dq Li \&.
 (the current directory).
+.It Ev PATHSEP
+A colon (semicolon on OS/2), for the user's convenience.
 .It Ev PGRP
 The process ID of the shell's process group leader.
 .It Ev PIPESTATUS
@@ -2104,7 +2091,7 @@ this hack; it's derived from the original
 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
@@ -2119,7 +2106,7 @@ in reverse video
 .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
@@ -2127,7 +2114,7 @@ Due to a strong suggestion from David G. Korn,
 .Nm
 now also supports the following form:
 .Bd -literal -offset indent
-PS1=$'\e1\er\e1\ee[7m\e1$PWD\e1\ee[0m\e1\*(Gt '
+PS1=$\*(aq\e1\er\e1\ee[7m\e1$PWD\e1\ee[0m\e1\*(Gt \*(aq
 .Ed
 .It Ev PS2
 Secondary prompt string, by default
@@ -2185,9 +2172,18 @@ files are created in
 The effective user id of the shell.
 .El
 .Ss Tilde expansion
-Tilde expansion which is done in parallel with parameter substitution, is done
-on words starting with an unquoted
+Tilde expansion, which is done in parallel with parameter substitution,
+is applied to words starting with an unquoted
 .Ql \*(TI .
+In parameter assignments (such as those preceding a simple-command or those
+occurring in the arguments of a declaration utility), tilde expansion is done
+after any assignment (i.e. after the equals sign) or after an unquoted colon
+.Pq Ql \&: ;
+login names are also delimited by colons.
+The Korn shell, except in POSIX mode, always expands tildes after unquoted
+equals signs, not just in assignment context (see below), and enables tab
+completion for tildes after all unquoted colons during command line editing.
+.Pp
 The characters following the tilde, up to the first
 .Ql / ,
 if any, are assumed to be a login name.
@@ -2208,21 +2204,6 @@ If the login name is not found in the password file or
 if any quoting or parameter substitution occurs in the login name, no
 substitution is performed.
 .Pp
-In parameter assignments
-(such as those preceding a simple-command or those occurring
-in the arguments of
-.Ic alias ,
-.Ic export ,
-.Ic global ,
-.Ic readonly
-and
-.Ic typeset ) ,
-tilde expansion is done after any assignment
-(i.e. after the equals sign)
-or after an unquoted colon
-.Pq Ql \&: ;
-login names are also delimited by colons.
-.Pp
 The home directory of previously expanded login names are cached and re-used.
 The
 .Ic alias Fl d
@@ -2586,7 +2567,7 @@ Redirections are processed after
 pipelines are created and in the order they are given, so the following
 will print an error with a line number prepended to it:
 .Pp
-.D1 $ cat /foo/bar 2\*(Gt&1 \*(Gt/dev/null \*(Ba pr \-n \-t
+.Dl $ cat /foo/bar 2\*(Gt&1 \*(Gt/dev/null \*(Ba pr \-n \-t
 .Pp
 File descriptors created by I/O redirections are private to the shell.
 .Ss Arithmetic expressions
@@ -2602,7 +2583,7 @@ This also affects implicit conversion to integer, for example as done by the
 .Ic let
 command.
 .Em Never
-use unchecked user input, e.g. from the environment, in arithmetics!
+use unchecked user input, e.g. from the environment, in an arithmetic context!
 .Pp
 Expressions are calculated using signed arithmetic and the
 .Vt mksh_ari_t
@@ -2946,6 +2927,12 @@ function.
 A function can be made to finish immediately using the
 .Ic return
 command; this may also be used to explicitly specify the exit status.
+Note that when called in a subshell,
+.Ic return
+will only exit that subshell and will not cause the original shell to exit
+a running function (see the
+.Ic while Ns Li \&... Ns Ic read
+loop FAQ below).
 .Pp
 Functions defined with the
 .Ic function
@@ -3022,18 +3009,18 @@ Additional
 .Nm
 commands keeping assignments:
 .Pp
-.Ic builtin , global , source , typeset ,
-.Ic wait
+.Ic global , source , typeset
 .Pp
 Builtins that are not special:
 .Pp
 .Ic [ , alias , bg , bind ,
-.Ic cat , cd , command , echo ,
-.Ic false , fc , fg , getopts ,
-.Ic jobs , kill , let , print ,
-.Ic pwd , read , realpath , rename ,
-.Ic sleep , suspend , test , true ,
-.Ic ulimit , umask , unalias , whence
+.Ic builtin , cat , cd , command ,
+.Ic echo , false , fc , fg ,
+.Ic getopts , jobs , kill , let ,
+.Ic print , pwd , read , realpath ,
+.Ic rename , sleep , suspend , test ,
+.Ic true , ulimit , umask , unalias ,
+.Ic wait , whence
 .Pp
 Once the type of command has been determined, any command-line parameter
 assignments are performed and exported for the duration of the command.
@@ -3079,9 +3066,13 @@ Without arguments,
 .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).
+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
@@ -3173,7 +3164,8 @@ other trailing character will be processed afterwards.
 .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
@@ -3217,6 +3209,18 @@ Execute the built-in command
 .Ar command .
 .Pp
 .It Xo
+.Ic \ebuiltin
+.Ar command Op Ar arg ...
+.Xc
+Same as
+.Ic builtin .
+Additionally acts as declaration utility forwarder, i.e. this is a
+declaration utility (see
+.Sx Tilde expansion )
+.No iff Ar command
+is a declaration utility.
+.Pp
+.It Xo
 .Ic cat
 .Op Fl u
 .Op Ar
@@ -3346,6 +3350,7 @@ cannot be a shell function;
 and secondly, special built-in commands lose their specialness
 (i.e. redirection and utility errors do not cause the shell to
 exit, and command assignments are not permanent).
+The declaration utility property is not reset.
 .Pp
 If the
 .Fl p
@@ -3421,8 +3426,10 @@ If the
 .Ic posix
 or
 .Ic sh
-option is set or this is a direct builtin call, only the first argument
-is treated as an option, and only if it is exactly
+option is set or this is a direct builtin call or
+.Ic print
+.Fl R ,
+only the first argument is treated as an option, and only if it is exactly
 .Dq Li \-n .
 Backslash interpretation is disabled.
 .Pp
@@ -3463,7 +3470,7 @@ Note that the Bourne shell differs here;
 it does pass these file descriptors on.
 .Pp
 .It Ic exit Op Ar status
-The shell exits with the specified exit status.
+The shell or subshell exits with the specified exit status.
 If
 .Ar status
 is not specified, the exit status is the current value of the
@@ -3478,6 +3485,7 @@ parameter.
 Sets the export attribute of the named parameters.
 Exported parameters are passed in the environment to executed commands.
 If values are specified, the named parameters are also assigned.
+This is a declaration utility.
 .Pp
 If no parameters are specified, all parameters with the export attribute
 set are printed one per line; either their names, or, if a
@@ -3632,9 +3640,22 @@ resetting
 .Ev OPTIND
 may lead to unexpected results.
 .Pp
-.It global Ar ...
+.It Xo
+.Ic global
+.Op Ic +\-aglpnrtUux
+.Oo Fl L Ns Op Ar n
+.No \*(Ba Fl R Ns Op Ar n
+.No \*(Ba Fl Z Ns Op Ar n Oc
+.Op Fl i Ns Op Ar n
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
 See
-.Ic typeset .
+.Ic typeset Fl g .
+.No Deprecated , Em will
+be removed from a future version of
+.Nm .
 .Pp
 .It Xo
 .Ic hash
@@ -3712,12 +3733,8 @@ If an error occurs during
 the parsing or evaluation of an expression, the exit status is greater than 1.
 Since expressions may need to be quoted,
 .No \&(( Ar expr No ))
-is syntactic sugar for
-.No "{ let '" Ns Ar expr Ns "'; }" .
-.Pp
-.It Ic let]
-Internally used alias for
-.Ic let .
+is syntactic sugar for:
+.Dl "{ \e\ebuiltin let \*(aq" Ns Ar expr Ns "\*(aq; }"
 .Pp
 .It Xo
 .Ic mknod
@@ -3757,74 +3774,70 @@ however, distributors may have added this as builtin as a speed hack.
 .Pp
 .It Xo
 .Ic print
-.Oo Fl Anprsu Ns Oo Ar n Oc \*(Ba
-.Fl R Op Fl en Oc
+.Oo Fl AcelNnprsu Ns Oo Ar n Oc \*(Ba
+.Fl R Op Fl n Oc
 .Op Ar argument ...
 .Xc
-.Ic print
-prints its arguments on the standard output, separated by spaces and
-terminated with a newline.
-The
-.Fl n
-option suppresses the newline.
-By default, certain C escapes are translated.
-These include these mentioned in
+Print the specified argument(s) on the standard output,
+separated by spaces, terminated with a newline.
+The escapes mentioned in
 .Sx Backslash expansion
 above, as well as
 .Dq Li \ec ,
 which is equivalent to using the
 .Fl n
-option.
-Backslash expansion may be inhibited with the
-.Fl r
-option.
-The
-.Fl s
-option prints to the history file instead of standard output; the
-.Fl u
-option prints to file descriptor
-.Ar n
-.Po
-.Ar n
-defaults to 1 if omitted
-.Pc ;
-and the
-.Fl p
-option prints to the co-process (see
+option, are interpreted.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl A
+Each
+.Ar argument
+is arithmetically evaluated; the character corresponding to the
+resulting value is printed.
+Empty
+.Ar argument Ns s
+separate input words.
+.It Fl c
+The output is printed columnised, line by line, similar to how the
+.Xr rs 1
+utility, tab completion, the
+.Ic kill Fl l
+built-in utility and the
+.Ic select
+statement do.
+.It Fl e
+Restore backslash expansion after a previous
+.Fl r .
+.It Fl l
+Change the output word separator to newline.
+.It Fl N
+Change the output word and line separator to ASCII NUL.
+.It Fl n
+Do not print the trailing line separator.
+.It Fl p
+Print to the co-process (see
 .Sx Co-processes
 above).
-The
-.Fl A
-option prints the character corresponding to each
-.Ar argument Ns 's value .
+.It Fl r
+Inhibit backslash expansion.
+.It Fl s
+Print to the history file instead of standard output.
+.It Fl u Ns Op Ar n
+Print to the file descriptor
+.Ar n Pq defaults to 1 if omitted
+instead of standard output.
+.El
 .Pp
 The
 .Fl R
-option is used to emulate, to some degree, the
+option mostly emulates the
 .Bx
 .Xr echo 1
-command which does not process
-.Ql \e
-sequences unless the
-.Fl e
-option is given.
-As above, the
-.Fl n
-option suppresses the trailing newline.
-.Pp
-.It Ic printf Ar format Op Ar arguments ...
-Formatted output.
-Approximately the same as the
-.Xr printf 1 ,
-utility, except it uses the same
-.Sx Backslash expansion
-and I/O code and does not handle floating point as the rest of
-.Nm mksh .
-An external utility is preferred over the builtin.
-This is not normally part of
-.Nm mksh ;
-however, distributors may have added this as builtin as a speed hack.
-Do not use in new code.
+command which does not expand backslashes and interprets
+its first argument as option only if it is exactly
+.Dq Li \-n
+.Pq to suppress the trailing newline .
 .Pp
 .It Ic pwd Op Fl LP
 Print the present working directory.
@@ -3901,7 +3914,10 @@ if empty, instead of the ASCII newline character as input line delimiter.
 Instead of reading till end-of-line, read exactly
 .Ar z
 bytes.
-If EOF or a timeout occurs, a partial read is returned with exit status 1.
+Upon EOF, a partial read is returned with exit status 1.
+After timeout, a partial read is returned with an exit status as if
+.Dv SIGALRM
+were caught.
 .It Fl n Ar z
 Instead of reading till end-of-line, read up to
 .Ar z
@@ -3922,7 +3938,9 @@ Interrupt reading after
 seconds (specified as positive decimal value with an optional fractional part).
 The exit status of
 .Nm read
-is 1 if the timeout occurred, but partial reads may still be returned.
+is the same as if
+.Dv SIGALRM
+were caught if the timeout occurred, but partial reads may still be returned.
 .It Fl r
 Normally, the ASCII backslash character escapes the special
 meaning of the following character and is stripped from the input;
@@ -3954,40 +3972,6 @@ If no input is read or a timeout occurred,
 .Ic read
 exits with a non-zero status.
 .Pp
-Another handy set of tricks:
-If
-.Ic read
-is run in a loop such as
-.Ic while read foo; do ...; done
-then leading whitespace will be removed (IFS) and backslashes processed.
-You might want to use
-.Ic while IFS= read \-r foo; do ...; done
-for pristine I/O.
-Similarly, when using the
-.Fl a
-option, use of the
-.Fl r
-option might be prudent; the same applies for:
-.Bd -literal -offset indent
-find . \-type f \-print0 \*(Ba& \e
-    while IFS= read \-d \*(aq\*(aq \-pr filename; do
-	print \-r \-\- "found \*(Lt${filename#./}\*(Gt"
-done
-.Ed
-.Pp
-The inner loop will be executed in a subshell and variable changes
-cannot be propagated if executed in a pipeline:
-.Bd -literal -offset indent
-bar \*(Ba baz \*(Ba while read foo; do ...; done
-.Ed
-.Pp
-Use co-processes instead:
-.Bd -literal -offset indent
-bar \*(Ba baz \*(Ba&
-while read \-p foo; do ...; done
-exec 3\*(Gt&p; exec 3\*(Gt&\-
-.Ed
-.Pp
 .It Xo
 .Ic readonly
 .Op Fl p
@@ -3996,6 +3980,7 @@ exec 3\*(Gt&p; exec 3\*(Gt&\-
 .Ar ... Oc
 .Xc
 Sets the read-only attribute of the named parameters.
+This is a declaration utility.
 If values are given,
 parameters are set to them before setting the attribute.
 Once a parameter is
@@ -4250,7 +4235,6 @@ Background jobs are run with lower priority.
 .It Fl o Ic braceexpand
 Enable brace expansion (a.k.a. alternation).
 This is enabled by default.
-If disabled, tilde expansion after an equals sign is disabled as a side effect.
 .It Fl o Ic emacs
 Enable BRL emacs-like command-line editing (interactive shells only); see
 .Sx Emacs editing mode .
@@ -4324,9 +4308,11 @@ Automatically enabled if the basename of the shell invocation begins with
 .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
@@ -4491,34 +4477,6 @@ is a symbolic link.
 .It Fl O Ar file
 .Ar file Ns 's
 owner is the shell's effective user ID.
-.It Fl o Ar option
-Shell
-.Ar option
-is set (see the
-.Ic set
-command above for a list of options).
-As a non-standard extension, if the option starts with a
-.Ql \&! ,
-the test is negated; the test always fails if
-.Ar option
-doesn't exist (so [ \-o foo \-o \-o !foo ] returns true if and only if option
-.Ar foo
-exists).
-The same can be achieved with [ \-o ?foo ] like in
-.At
-.Nm ksh93 .
-.Ar option
-can also be the short flag led by either
-.Ql \-
-or
-.Ql +
-.Pq no logical negation ,
-for example
-.Dq Li \-x
-or
-.Dq Li +x
-instead of
-.Dq Li xtrace .
 .It Fl p Ar file
 .Ar file
 is a named pipe
@@ -4580,6 +4538,38 @@ is not empty.
 .It Fl z Ar string
 .Ar string
 is empty.
+.It Fl v Ar name
+The shell parameter
+.Ar name
+is set.
+.It Fl o Ar option
+Shell
+.Ar option
+is set (see the
+.Ic set
+command above for a list of options).
+As a non-standard extension, if the option starts with a
+.Ql \&! ,
+the test is negated; the test always fails if
+.Ar option
+doesn't exist (so [ \-o foo \-o \-o !foo ] returns true if and only if option
+.Ar foo
+exists).
+The same can be achieved with [ \-o ?foo ] like in
+.At
+.Nm ksh93 .
+.Ar option
+can also be the short flag led by either
+.Ql \-
+or
+.Ql +
+.Pq no logical negation ,
+for example
+.Dq Li \-x
+or
+.Dq Li +x
+instead of
+.Dq Li xtrace .
 .It Ar string No = Ar string
 Strings are equal.
 .It Ar string No == Ar string
@@ -4784,28 +4774,23 @@ traps in functions are not yet implemented.
 A command that exits with a zero value.
 .Pp
 .It Xo
-.Ic global
-.Oo Op Ic +\-alpnrtUux
-.Op Fl L Ns Op Ar n
-.Op Fl R Ns Op Ar n
-.Op Fl Z Ns Op Ar n
+.Ic typeset
+.Op Ic +\-aglpnrtUux
+.Oo Fl L Ns Op Ar n
+.No \*(Ba Fl R Ns Op Ar n
+.No \*(Ba Fl Z Ns Op Ar n Oc
 .Op Fl i Ns Op Ar n
-.No \*(Ba Fl f Op Fl tux Oc
 .Oo Ar name
 .Op Ns = Ns Ar value
 .Ar ... Oc
 .Xc
 .It Xo
 .Ic typeset
-.Oo Op Ic +\-alpnrtUux
-.Op Fl LRZ Ns Op Ar n
-.Op Fl i Ns Op Ar n
-.No \*(Ba Fl f Op Fl tux Oc
-.Oo Ar name
-.Op Ns = Ns Ar value
-.Ar ... Oc
+.Fl f Op Fl tux
+.Op Ar name ...
 .Xc
 Display or set parameter attributes.
+This is a declaration utility.
 With no
 .Ar name
 arguments, parameter attributes are displayed; if no options are used, the
@@ -4821,27 +4806,16 @@ parameter values are not printed.
 If
 .Ar name
 arguments are given, the attributes of the named parameters are set
-.Pq Ic \-
+.Pq Ic \&\-
 or cleared
-.Pq Ic + .
+.Pq Ic \&+ ;
+inside a function, this will cause the parameters to be created
+(with no value) in the local scope (but see
+.Fl g ) .
 Values for parameters may optionally be specified.
 For
 .Ar name Ns \&[*] ,
-the change affects the entire array, and no value may be specified.
-.Pp
-If
-.Ic typeset
-is used inside a function, any parameters specified are localised.
-This is not done by the otherwise identical
-.Ic global .
-.Em Note :
-This means that
-.Nm No 's Ic global
-command is
-.Em not
-equivalent to other programming languages' as it does not allow a
-function called from another function to access a parameter at truly
-global scope, but only prevents putting an accessed one into local scope.
+the change affects all elements of the array, and no value may be specified.
 .Pp
 When
 .Fl f
@@ -4861,6 +4835,9 @@ Indexed array attribute.
 .It Fl f
 Function mode.
 Display or set functions and their attributes, instead of parameters.
+.It Fl g
+Do not cause named parameters to be created in
+the local scope when called inside a function.
 .It Fl i Ns Op Ar n
 Integer attribute.
 .Ar n
@@ -4883,7 +4860,7 @@ If necessary, values are either truncated or space padded
 to fit the field width.
 .It Fl l
 Lower case attribute.
-All upper case characters in values are converted to lower case.
+All upper case ASCII characters in values are converted to lower case.
 (In the original Korn shell, this parameter meant
 .Dq long integer
 when used with the
@@ -4907,7 +4884,7 @@ is lazily evaluated at the time
 .Ar name
 is accessed.
 This can be used by functions to access variables whose names are
-passed as parametres, instead of using
+passed as parameters, instead of using
 .Ic eval .
 .It Fl p
 Print complete
@@ -4948,7 +4925,7 @@ option).
 This option is not in the original Korn shell.
 .It Fl u
 Upper case attribute.
-All lower case characters in values are converted to upper case.
+All lower case ASCII characters in values are converted to upper case.
 (In the original Korn shell, this parameter meant
 .Dq unsigned integer
 when used with the
@@ -5012,7 +4989,7 @@ once they are set.
 Also note that the types of limits available are system
 dependent \*(en some systems have only the
 .Fl f
-limit.
+limit, or not even that, or can set only the soft limits
 .Bl -tag -width 5n
 .It Fl a
 Display all limits; unless
@@ -5365,11 +5342,11 @@ to behave even more
 compliant in places where the defaults or opinions differ.
 Note that
 .Nm mksh
-will still operate with unsigned 32-bit arithmetics; use
+will still operate with unsigned 32-bit arithmetic; use
 .Nm lksh
-if arithmetics on the host
+if arithmetic on the host
 .Vt long
-data type, complete with ISO C Undefined Behaviour, are required;
+data type, complete with ISO C Undefined Behaviour, is required;
 refer to the
 .Xr lksh 1
 manual page for details.
@@ -5379,11 +5356,11 @@ Most other historic,
 or opinionated differences can be disabled by using this mode; these are:
 .Bl -bullet
 .It
-The GNU
+The incompatible GNU
 .Nm bash
 I/O redirection
 .Ic &\*(Gt Ns Ar file
-is no longer supported.
+is not supported.
 .It
 File descriptors created by I/O redirections are inherited by
 child processes.
@@ -5393,20 +5370,39 @@ Numbers with a leading digit zero are interpreted as octal.
 The
 .Nm echo
 builtin does not interpret backslashes and only supports the exact option
-.Dq Li \-n .
+.Fl n .
 .It
-\&... (list is incomplete and may change for R54)
+Alias expansion with a trailing space only reruns on command words.
+.It
+Tilde expansion follows POSIX instead of Korn shell rules.
+.It
+The exit status of
+.Ic fg
+is always 0.
+.It
+.Ic kill
+.Fl l
+only lists signal names, all in one line.
+.It
+.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
 cannot easily be fixed; the changes are as follows:
 .Bl -bullet
 .It
-The GNU
+The incompatible GNU
 .Nm bash
 I/O redirection
 .Ic &\*(Gt Ns Ar file
-is no longer supported.
+is not supported.
 .It
 File descriptors created by I/O redirections are inherited by
 child processes.
@@ -5414,7 +5410,9 @@ child processes.
 The
 .Nm echo
 builtin does not interpret backslashes and only supports the exact option
-.Dq Li \-n .
+.Fl n ,
+unless built with
+.Ev \-DMKSH_MIDNIGHTBSD01ASH_COMPAT .
 .It
 The substitution operations
 .Sm off
@@ -5444,7 +5442,16 @@ and
 .Xc
 wrongly do not require a parenthesis to be escaped and do not parse extglobs.
 .It
-\&... (list is incomplete and may change for R54)
+The getopt construct from
+.Xr lksh 1
+passes through the errorlevel.
+.It
+.Nm sh
+.Fl c
+eats a leading
+.Fl \-
+if built with
+.Ev \-DMKSH_MIDNIGHTBSD01ASH_COMPAT .
 .El
 .Ss Interactive input line editing
 The shell supports three modes of reading command lines from a
@@ -5540,7 +5547,7 @@ Emacs key bindings:
 .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.
@@ -5570,12 +5577,13 @@ Moves the cursor to the beginning of the edited input line.
 .Op Ar n
 .No \*(ha[C , \*(ha[c
 .Xc
-Uppercase the first character in the next
+Uppercase the first ASCII character in the next
 .Ar n
 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
@@ -5728,9 +5736,6 @@ Goes to history number
 .No KILL Pq \*(haU
 .Xc
 Deletes the entire input line.
-If Ctrl-U should only delete the line up to the cursor, use:
-.Pp
-.D1 $ bind \-m \*(haU='\*(ha[0\*(haK'
 .It kill\-region: \*(haW
 Deletes the input between the cursor and the mark.
 .It Xo kill\-to\-eol:
@@ -6440,7 +6445,7 @@ Undo all changes that have been made to the current line.
 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
@@ -6498,7 +6503,7 @@ contains the system and suid profile.
 .Xr utf\-8 7 ,
 .Xr mknod 8
 .Pp
-.Pa https://www.mirbsd.org/ksh\-chan.htm
+.Pa http://www.mirbsd.org/ksh\-chan.htm
 .Rs
 .%A Morris Bolsky
 .%B "The KornShell Command and Programming Language"
@@ -6570,8 +6575,8 @@ This shell is based on the public domain 7th edition Bourne shell clone by
 .An Charles Forsyth ,
 who kindly agreed to, in countries where the Public Domain status of the work
 may not be valid, grant a copyright licence to the general public to deal in
-the work without restriction and permission to sublicence derivates under the
-terms of any (OSI approved) Open Source licence,
+the work without restriction and permission to sublicence derivatives under
+the terms of any (OSI approved) Open Source licence,
 and parts of the BRL shell by
 .An Doug A. Gwyn ,
 .An Doug Kingston ,
@@ -6590,11 +6595,23 @@ and
 .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
+.An KO Myung-Hun Aq Mt komh at chollian.net .
+.Pp
+.Nm mksh\-w32
+is developed by
+.An Michael Langguth Aq Mt lan at scalaris.com .
+.Pp
+.Nm mksh Ns / Ns Tn z/OS
+is contributed by
+.An Daniel Richard G. Aq Mt skunk at iSKUNK.ORG .
 .Pp
 The BSD daemon is Copyright \(co Marshall Kirk McKusick.
 The complete legalese is at:
-.Pa https://www.mirbsd.org/TaC\-mksh.txt
+.Pa http://www.mirbsd.org/TaC\-mksh.txt
 .\"
 .\" This boils down to: feel free to use mksh.ico as application icon
 .\" or shortcut for mksh or mksh/Win32 or OS/2; distro patches are ok
@@ -6609,28 +6626,10 @@ The complete legalese is at:
 .\" to protect it or lose it, which McKusick almost did.
 .\"
 .Sh CAVEATS
-.Nm
-has a different scope model from
-.At
-.Nm ksh ,
-which leads to subtile differences in semantics for identical builtins.
-This can cause issues with a
-.Ic nameref
-to suddenly point to a local variable by accident; fixing this is hard.
-.Pp
-The parts of a pipeline, like below, are executed in subshells.
-Thus, variable assignments inside them are not visible in the
-surrounding execution environment.
-Use co-processes instead.
-.Bd -literal -offset indent
-foo \*(Ba bar \*(Ba read baz            # will not change $baz
-foo \*(Ba bar \*(Ba& read \-p baz        # will, however, do so
-.Ed
-.Pp
 .Nm mksh
-provides a consistent set of 32-bit integer arithmetics, both signed
-and unsigned, with defined wraparound and sign of the result of a
-remainder operation, even (defying POSIX) on 36-bit and 64-bit systems.
+provides a consistent 32-bit integer arithmetic implementation, both
+signed and unsigned, with sign of the result of a remainder operation
+and wraparound defined, even (defying POSIX) on 36-bit and 64-bit systems.
 .Pp
 .Nm mksh
 provides a consistent, clear interface normally.
@@ -6649,12 +6648,14 @@ supports only the
 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
@@ -6671,6 +6672,8 @@ case ${KSH_VERSION:\-} in
 esac
 .Ed
 In near future, (Unicode) locale tracking will be implemented though.
+.Pp
+See also the FAQ below.
 .Sh BUGS
 Suspending (using \*(haZ) pipelines like the one below will only suspend
 the currently running part of the pipeline; in this example,
@@ -6685,7 +6688,7 @@ The truncation process involved when changing
 .Ev HISTFILE
 does not free old history entries (leaks memory) and leaks
 old entries into the new history if their line numbers are
-not overwritten by same-numer entries from the persistent
+not overwritten by same-number entries from the persistent
 history file; truncating the on-disc file to
 .Ev HISTSIZE
 lines has always been broken and prone to history file corruption
@@ -6694,7 +6697,7 @@ for the in-memory portion of the history is slow, should use
 .Xr memmove 3 .
 .Pp
 This document attempts to describe
-.Nm mksh\ R53
+.Nm mksh\ R56
 and up,
 .\" with vendor patches from insert-your-name-here,
 compiled without any options impacting functionality, such as
@@ -6711,9 +6714,8 @@ for an operating environment supporting all of its advanced needs.
 Please report bugs in
 .Nm
 to the
-.Mx
-mailing list at
 .Aq Mt miros\-mksh at mirbsd.org
+mailing list
 or in the
 .Li \&#\&!/bin/mksh
 .Pq or Li \&#ksh
@@ -6722,3 +6724,183 @@ IRC channel at
 .Pq Port 6697 SSL, 6667 unencrypted ,
 or at:
 .Pa https://launchpad.net/mksh
+.Sh FREQUENTLY ASKED QUESTIONS
+This FAQ attempts to document some of the questions users of
+.Nm
+or readers of this manual page may encounter.
+.Ss I'm an Android user, so what's mksh?
+.Nm mksh
+is a
+.Ux
+shell / command interpreter, similar to
+.Nm COMMAND.COM
+or
+.Nm CMD.EXE ,
+which has been included with
+.Tn Android Open Source Project
+for a while now.
+Basically, it's a program that runs in a terminal (console window),
+takes user input and runs commands or scripts, which it can also
+be asked to do by other programs, even in the background.
+Any privilege pop-ups you might be encountering are thus not
+.Nm mksh
+issues but questions by some other program utilising it.
+.Ss "I'm an OS/2 user, what do I need to know?"
+Unlike the native command prompt, the current working directory is,
+for security reasons common on Unix systems which the shell is designed for,
+not in the search path at all; if you really need this, run the command
+.Li PATH=.$PATHSEP$PATH
+or add that to a suitable initialisation file.
+.Pp
+There are two different newline modes for mksh-os2: standard (Unix) mode,
+in which only LF (0A hex) is supported as line separator, and "textmode",
+which also accepts ASCII newlines (CR+LF), like most other tools on OS/2,
+but creating an incompatibility with standard
+.Nm .
+If you compiled mksh from source, you will get the standard Unix mode unless
+.Fl T
+is added during compilation; you will most likely have gotten this shell
+through komh's port on Hobbes, or from his OS/2 Factory on eComStation
+Korea, which uses "textmode", though.
+Most OS/2 users will want to use "textmode" unless they need absolute
+compatibility with Unix
+.Nm .
+.Ss "How do I start mksh on a specific terminal?"
+Normally:
+.Dl mksh \-T/dev/tty2
+.Pp
+However, if you want for it to return (e.g. for an embedded
+system rescue shell), use this on your real console device instead:
+.Dl mksh \-T!/dev/ttyACM0
+.Pp
+.Nm
+can also daemonise (send to the background):
+.Dl mksh \-T\- \-c \*(aqexec cdio lock\*(aq
+.Ss "POSIX says..."
+Run the shell in POSIX mode (and possibly
+.Nm lksh
+instead of
+.Nm mksh ) :
+.Dl set \-o posix
+.Ss "My prompt from <some other shell> does not work!"
+Contact us on the mailing list or on IRC, we'll convert it for you.
+.Ss "Something is going wrong with my while...read loop"
+Most likely, you've encountered the problem in which the shell runs
+all parts of a pipeline as subshell.
+The inner loop will be executed in a subshell and variable changes
+cannot be propagated if run in a pipeline:
+.Bd -literal -offset indent
+bar \*(Ba baz \*(Ba while read foo; do ...; done
+.Ed
+.Pp
+Note that
+.Ic exit
+in the inner loop will only exit the subshell and not the original shell.
+Likewise, if the code is inside a function,
+.Ic return
+in the inner loop will only exit the subshell and won't terminate the function.
+.Pp
+Use co-processes instead:
+.Bd -literal -offset indent
+bar \*(Ba baz \*(Ba&
+while read \-p foo; do ...; done
+exec 3\*(Gt&p; exec 3\*(Gt&\-
+.Ed
+.Pp
+If
+.Ic read
+is run in a loop such as
+.Ic while read foo; do ...; done
+then leading whitespace will be removed (IFS) and backslashes processed.
+You might want to use
+.Ic while IFS= read \-r foo; do ...; done
+for pristine I/O.
+Similarly, when using the
+.Fl a
+option, use of the
+.Fl r
+option might be prudent
+.Pq Dq Li read \-raN\-1 arr \*(Ltfile ;
+the same applies for NUL-terminated lines:
+.Bd -literal -offset indent
+find . \-type f \-print0 \*(Ba& \e
+    while IFS= read \-d \*(aq\*(aq \-pr filename; do
+	print \-r \-\- "found \*(Lt${filename#./}\*(Gt"
+done
+.Ed
+.Pp
+.Ss "What differences in function-local scopes are there?"
+.Nm
+has a different scope model from
+.At
+.Nm ksh ,
+which leads to subtle differences in semantics for identical builtins.
+This can cause issues with a
+.Ic nameref
+to suddenly point to a local variable by accident.
+.Pp
+.Tn GNU
+.Nm bash
+allows unsetting local variables; in
+.Nm ,
+doing so in a function allows back access to the global variable
+(actually the one in the next scope up) with the same name.
+The following code, when run before the function definitions, changes
+the behaviour of
+.Ic unset
+to behave like other shells (the alias can be removed after the definitions):
+.Bd -literal -offset indent
+case ${KSH_VERSION:\-} in
+*MIRBSD\ KSH*\*(Ba*LEGACY\ KSH*)
+	function unset_compat {
+		\e\ebuiltin typeset unset_compat_x
+
+		for unset_compat_x in "$@"; do
+			eval "\e\e\e\ebuiltin unset $unset_compat_x[*]"
+		done
+	}
+	\e\ebuiltin alias unset=unset_compat
+	;;
+esac
+.Ed
+.Pp
+When a local variable is created (e.g. using
+.Ic local ,
+.Ic typeset ,
+.Ic integer ,
+.Ic \e\ebuiltin typeset )
+it does not, like in other shells, inherit the value from the global
+(next scope up) variable with the same name; it is rather created
+without any value (unset but defined).
+.Ss "I get an error in this regex comparison"
+Use extglobs instead of regexes:
+.Dl "[[ foo =~ (foo\*(Babar).*baz ]]	# becomes"
+.Dl "[[ foo = *@(foo\*(Babar)*baz* ]]	# instead"
+.Ss "Are there any extensions to avoid?"
+.Tn GNU
+.Nm bash
+supports
+.Dq Li &\*(Gt
+.Pq and Dq Li \*(Ba&
+to redirect both stdout and stderr in one go, but this breaks POSIX
+and Korn Shell syntax; use POSIX redirections instead:
+.Dl "foo \*(Ba& bar \*(Ba& baz &\*(Gtlog			     # GNU bash"
+.Dl "foo 2\*(Gt&1 \*(Ba bar 2\*(Gt&1 \*(Ba baz \*(Gtlog 2\*(Gt&1	# POSIX"
+.Ss "\*(haL (Ctrl-L) does not clear the screen"
+Use \*(ha[\*(haL (Escape+Ctrl-L) or rebind it:
+.Dl bind \*(aq\*(haL=clear-screen\*(aq
+.Ss "\*(haU (Ctrl-U) clears the entire line"
+If it should only delete the line up to the cursor, use:
+.Dl bind \-m \*(haU=\*(aq\*(ha[0\*(haK\*(aq
+.Ss "Cursor Up behaves differently from zsh"
+Some shells make Cursor Up search in the history only for
+commands starting with what was already entered.
+.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/os2.c b/os2.c
new file mode 100644
index 0000000..ece459f
--- /dev/null
+++ b/os2.c
@@ -0,0 +1,576 @@
+/*-
+ * Copyright (c) 2015
+ *	KO Myung-Hun <komh at chollian.net>
+ * Copyright (c) 2017
+ *	mirabilos <m at mirbsd.org>
+ *
+ * Provided that these terms and disclaimer and all copyright notices
+ * are retained or reproduced in an accompanying document, permission
+ * is granted to deal in this work without restriction, including un-
+ * limited rights to use, publicly perform, distribute, sell, modify,
+ * merge, give away, or sublicence.
+ *
+ * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
+ * the utmost extent permitted by applicable law, neither express nor
+ * implied; without malicious intent or gross negligence. In no event
+ * may a licensor, author or contributor be held liable for indirect,
+ * direct, other damage, loss, or other issues arising in any way out
+ * of dealing in the work, even if advised of the possibility of such
+ * damage or existence of a defect, except proven that it results out
+ * of said person's immediate fault when using the work as intended.
+ */
+
+#define INCL_DOS
+#include <os2.h>
+
+#include "sh.h"
+
+#include <klibc/startup.h>
+#include <errno.h>
+#include <io.h>
+#include <unistd.h>
+#include <process.h>
+
+__RCSID("$MirOS: src/bin/mksh/os2.c,v 1.6 2017/10/13 23:34:49 tg Exp $");
+
+static char *remove_trailing_dots(char *);
+static int access_stat_ex(int (*)(), const char *, void *);
+static int test_exec_exist(const char *, char *);
+static void response(int *, const char ***);
+static char *make_response_file(char * const *);
+static void add_temp(const char *);
+static void cleanup_temps(void);
+static void cleanup(void);
+
+#define RPUT(x) do {					\
+	if (new_argc >= new_alloc) {			\
+		new_alloc += 20;			\
+		if (!(new_argv = realloc(new_argv,	\
+		    new_alloc * sizeof(char *))))	\
+			goto exit_out_of_memory;	\
+	}						\
+	new_argv[new_argc++] = (x);			\
+} while (/* CONSTCOND */ 0)
+
+#define KLIBC_ARG_RESPONSE_EXCLUDE	\
+	(__KLIBC_ARG_DQUOTE | __KLIBC_ARG_WILDCARD | __KLIBC_ARG_SHELL)
+
+static void
+response(int *argcp, const char ***argvp)
+{
+	int i, old_argc, new_argc, new_alloc = 0;
+	const char **old_argv, **new_argv;
+	char *line, *l, *p;
+	FILE *f;
+
+	old_argc = *argcp;
+	old_argv = *argvp;
+	for (i = 1; i < old_argc; ++i)
+		if (old_argv[i] &&
+		    !(old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) &&
+		    old_argv[i][0] == '@')
+			break;
+
+	if (i >= old_argc)
+		/* do nothing */
+		return;
+
+	new_argv = NULL;
+	new_argc = 0;
+	for (i = 0; i < old_argc; ++i) {
+		if (i == 0 || !old_argv[i] ||
+		    (old_argv[i][-1] & KLIBC_ARG_RESPONSE_EXCLUDE) ||
+		    old_argv[i][0] != '@' ||
+		    !(f = fopen(old_argv[i] + 1, "rt")))
+			RPUT(old_argv[i]);
+		else {
+			long filesize;
+
+			fseek(f, 0, SEEK_END);
+			filesize = ftell(f);
+			fseek(f, 0, SEEK_SET);
+
+			line = malloc(filesize + /* type */ 1 + /* NUL */ 1);
+			if (!line) {
+ exit_out_of_memory:
+				fputs("Out of memory while reading response file\n", stderr);
+				exit(255);
+			}
+
+			line[0] = __KLIBC_ARG_NONZERO | __KLIBC_ARG_RESPONSE;
+			l = line + 1;
+			while (fgets(l, (filesize + 1) - (l - (line + 1)), f)) {
+				p = strchr(l, '\n');
+				if (p) {
+					/*
+					 * if a line ends with a backslash,
+					 * concatenate with the next line
+					 */
+					if (p > l && p[-1] == '\\') {
+						char *p1;
+						int count = 0;
+
+						for (p1 = p - 1; p1 >= l &&
+						    *p1 == '\\'; p1--)
+							count++;
+
+						if (count & 1) {
+							l = p + 1;
+
+							continue;
+						}
+					}
+
+					*p = 0;
+				}
+				p = strdup(line);
+				if (!p)
+					goto exit_out_of_memory;
+
+				RPUT(p + 1);
+
+				l = line + 1;
+			}
+
+			free(line);
+
+			if (ferror(f)) {
+				fputs("Cannot read response file\n", stderr);
+				exit(255);
+			}
+
+			fclose(f);
+		}
+	}
+
+	RPUT(NULL);
+	--new_argc;
+
+	*argcp = new_argc;
+	*argvp = new_argv;
+}
+
+static void
+init_extlibpath(void)
+{
+	const char *vars[] = {
+		"BEGINLIBPATH",
+		"ENDLIBPATH",
+		"LIBPATHSTRICT",
+		NULL
+	};
+	char val[512];
+	int flag;
+
+	for (flag = 0; vars[flag]; flag++) {
+		DosQueryExtLIBPATH(val, flag + 1);
+		if (val[0])
+			setenv(vars[flag], val, 1);
+	}
+}
+
+void
+os2_init(int *argcp, const char ***argvp)
+{
+	response(argcp, argvp);
+
+	init_extlibpath();
+
+	if (!isatty(STDIN_FILENO))
+		setmode(STDIN_FILENO, O_BINARY);
+	if (!isatty(STDOUT_FILENO))
+		setmode(STDOUT_FILENO, O_BINARY);
+	if (!isatty(STDERR_FILENO))
+		setmode(STDERR_FILENO, O_BINARY);
+
+	atexit(cleanup);
+}
+
+void
+setextlibpath(const char *name, const char *val)
+{
+	int flag;
+	char *p, *cp;
+
+	if (!strcmp(name, "BEGINLIBPATH"))
+		flag = BEGIN_LIBPATH;
+	else if (!strcmp(name, "ENDLIBPATH"))
+		flag = END_LIBPATH;
+	else if (!strcmp(name, "LIBPATHSTRICT"))
+		flag = LIBPATHSTRICT;
+	else
+		return;
+
+	/* convert slashes to backslashes */
+	strdupx(cp, val, ATEMP);
+	for (p = cp; *p; p++) {
+		if (*p == '/')
+			*p = '\\';
+	}
+
+	DosSetExtLIBPATH(cp, flag);
+
+	afree(cp, ATEMP);
+}
+
+/* remove trailing dots */
+static char *
+remove_trailing_dots(char *name)
+{
+	char *p = strnul(name);
+
+	while (--p > name && *p == '.')
+		/* nothing */;
+
+	if (*p != '.' && *p != '/' && *p != '\\' && *p != ':')
+		p[1] = '\0';
+
+	return (name);
+}
+
+#define REMOVE_TRAILING_DOTS(name)	\
+	remove_trailing_dots(memcpy(alloca(strlen(name) + 1), name, strlen(name) + 1))
+
+/* alias of stat() */
+extern int _std_stat(const char *, struct stat *);
+
+/* replacement for stat() of kLIBC which fails if there are trailing dots */
+int
+stat(const char *name, struct stat *buffer)
+{
+	return (_std_stat(REMOVE_TRAILING_DOTS(name), buffer));
+}
+
+/* alias of access() */
+extern int _std_access(const char *, int);
+
+/* replacement for access() of kLIBC which fails if there are trailing dots */
+int
+access(const char *name, int mode)
+{
+	/*
+	 * On OS/2 kLIBC, X_OK is set only for executable files.
+	 * This prevents scripts from being executed.
+	 */
+	if (mode & X_OK)
+		mode = (mode & ~X_OK) | R_OK;
+
+	return (_std_access(REMOVE_TRAILING_DOTS(name), mode));
+}
+
+#define MAX_X_SUFFIX_LEN	4
+
+static const char *x_suffix_list[] =
+    { "", ".ksh", ".exe", ".sh", ".cmd", ".com", ".bat", NULL };
+
+/* call fn() by appending executable extensions */
+static int
+access_stat_ex(int (*fn)(), const char *name, void *arg)
+{
+	char *x_name;
+	const char **x_suffix;
+	int rc = -1;
+	size_t x_namelen = strlen(name) + MAX_X_SUFFIX_LEN + 1;
+
+	/* otherwise, try to append executable suffixes */
+	x_name = alloc(x_namelen, ATEMP);
+
+	for (x_suffix = x_suffix_list; rc && *x_suffix; x_suffix++) {
+		strlcpy(x_name, name, x_namelen);
+		strlcat(x_name, *x_suffix, x_namelen);
+
+		rc = fn(x_name, arg);
+	}
+
+	afree(x_name, ATEMP);
+
+	return (rc);
+}
+
+/* access()/search_access() version */
+int
+access_ex(int (*fn)(const char *, int), const char *name, int mode)
+{
+	/*XXX this smells fishy --mirabilos */
+	return (access_stat_ex(fn, name, (void *)mode));
+}
+
+/* stat() version */
+int
+stat_ex(const char *name, struct stat *buffer)
+{
+	return (access_stat_ex(stat, name, buffer));
+}
+
+static int
+test_exec_exist(const char *name, char *real_name)
+{
+	struct stat sb;
+
+	if (stat(name, &sb) < 0 || !S_ISREG(sb.st_mode))
+		return (-1);
+
+	/* safe due to calculations in real_exec_name() */
+	memcpy(real_name, name, strlen(name) + 1);
+
+	return (0);
+}
+
+const char *
+real_exec_name(const char *name)
+{
+	char x_name[strlen(name) + MAX_X_SUFFIX_LEN + 1];
+	const char *real_name = name;
+
+	if (access_stat_ex(test_exec_exist, real_name, x_name) != -1)
+		/*XXX memory leak */
+		strdupx(real_name, x_name, ATEMP);
+
+	return (real_name);
+}
+
+/* OS/2 can process a command line up to 32 KiB */
+#define MAX_CMD_LINE_LEN 32768
+
+/* make a response file to pass a very long command line */
+static char *
+make_response_file(char * const *argv)
+{
+	char rsp_name_arg[] = "@mksh-rsp-XXXXXX";
+	char *rsp_name = &rsp_name_arg[1];
+	int arg_len = 0;
+	int i;
+
+	for (i = 0; argv[i]; i++)
+		arg_len += strlen(argv[i]) + 1;
+
+	/*
+	 * If a length of command line is longer than MAX_CMD_LINE_LEN, then
+	 * use a response file. OS/2 cannot process a command line longer
+	 * than 32K. Of course, a response file cannot be recognised by a
+	 * normal OS/2 program, that is, neither non-EMX or non-kLIBC. But
+	 * it cannot accept a command line longer than 32K in itself. So
+	 * using a response file in this case, is an acceptable solution.
+	 */
+	if (arg_len > MAX_CMD_LINE_LEN) {
+		int fd;
+		char *result;
+
+		if ((fd = mkstemp(rsp_name)) == -1)
+			return (NULL);
+
+		/* write all the arguments except a 0th program name */
+		for (i = 1; argv[i]; i++) {
+			write(fd, argv[i], strlen(argv[i]));
+			write(fd, "\n", 1);
+		}
+
+		close(fd);
+		add_temp(rsp_name);
+		strdupx(result, rsp_name_arg, ATEMP);
+		return (result);
+	}
+
+	return (NULL);
+}
+
+/* alias of execve() */
+extern int _std_execve(const char *, char * const *, char * const *);
+
+/* replacement for execve() of kLIBC */
+int
+execve(const char *name, char * const *argv, char * const *envp)
+{
+	const char *exec_name;
+	FILE *fp;
+	char sign[2];
+	char *rsp_argv[3];
+	char *rsp_name_arg;
+	int pid;
+	int status;
+	int fd;
+	int rc;
+
+	/*
+	 * #! /bin/sh : append .exe
+	 * extproc sh : search sh.exe in PATH
+	 */
+	exec_name = search_path(name, path, X_OK, NULL);
+	if (!exec_name) {
+		errno = ENOENT;
+		return (-1);
+	}
+
+	/*-
+	 * kLIBC execve() has problems when executing scripts.
+	 * 1. it fails to execute a script if a directory whose name
+	 *    is same as an interpreter exists in a current directory.
+	 * 2. it fails to execute a script not starting with sharpbang.
+	 * 3. it fails to execute a batch file if COMSPEC is set to a shell
+	 *    incompatible with cmd.exe, such as /bin/sh.
+	 * And ksh process scripts more well, so let ksh process scripts.
+	 */
+	errno = 0;
+	if (!(fp = fopen(exec_name, "rb")))
+		errno = ENOEXEC;
+
+	if (!errno && fread(sign, 1, sizeof(sign), fp) != sizeof(sign))
+		errno = ENOEXEC;
+
+	if (fp && fclose(fp))
+		errno = ENOEXEC;
+
+	if (!errno &&
+	    !((sign[0] == 'M' && sign[1] == 'Z') ||
+	      (sign[0] == 'N' && sign[1] == 'E') ||
+	      (sign[0] == 'L' && sign[1] == 'X')))
+		errno = ENOEXEC;
+
+	if (errno == ENOEXEC)
+		return (-1);
+
+	rsp_name_arg = make_response_file(argv);
+
+	if (rsp_name_arg) {
+		rsp_argv[0] = argv[0];
+		rsp_argv[1] = rsp_name_arg;
+		rsp_argv[2] = NULL;
+
+		argv = rsp_argv;
+	}
+
+	pid = spawnve(P_NOWAIT, exec_name, argv, envp);
+
+	afree(rsp_name_arg, ATEMP);
+
+	if (pid == -1) {
+		cleanup_temps();
+
+		return (-1);
+	}
+
+	/* close all opened handles */
+	for (fd = 0; fd < NUFILE; fd++) {
+		if (fcntl(fd, F_GETFD) == -1)
+			continue;
+
+		close(fd);
+	}
+
+	while ((rc = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
+		/* nothing */;
+
+	cleanup_temps();
+
+	/* Is this possible? And is this right? */
+	if (rc == -1)
+		return (-1);
+
+	if (WIFSIGNALED(status))
+		_exit(ksh_sigmask(WTERMSIG(status)));
+
+	_exit(WEXITSTATUS(status));
+}
+
+static struct temp *templist = NULL;
+
+static void
+add_temp(const char *name)
+{
+	struct temp *tp;
+
+	tp = alloc(offsetof(struct temp, tffn[0]) + strlen(name) + 1, APERM);
+	memcpy(tp->tffn, name, strlen(name) + 1);
+	tp->next = templist;
+	templist = tp;
+}
+
+/* alias of unlink() */
+extern int _std_unlink(const char *);
+
+/*
+ * Replacement for unlink() of kLIBC not supporting to remove files used by
+ * another processes.
+ */
+int
+unlink(const char *name)
+{
+	int rc;
+
+	rc = _std_unlink(name);
+	if (rc == -1 && errno != ENOENT)
+		add_temp(name);
+
+	return (rc);
+}
+
+static void
+cleanup_temps(void)
+{
+	struct temp *tp;
+	struct temp **tpnext;
+
+	for (tpnext = &templist, tp = templist; tp; tp = *tpnext) {
+		if (_std_unlink(tp->tffn) == 0 || errno == ENOENT) {
+			*tpnext = tp->next;
+			afree(tp, APERM);
+		} else {
+			tpnext = &tp->next;
+		}
+	}
+}
+
+static void
+cleanup(void)
+{
+	cleanup_temps();
+}
+
+int
+getdrvwd(char **cpp, unsigned int drvltr)
+{
+	PBYTE cp;
+	ULONG sz;
+	APIRET rc;
+	ULONG drvno;
+
+	if (DosQuerySysInfo(QSV_MAX_PATH_LENGTH, QSV_MAX_PATH_LENGTH,
+	    &sz, sizeof(sz)) != 0) {
+		errno = EDOOFUS;
+		return (-1);
+	}
+
+	/* allocate 'X:/' plus sz plus NUL */
+	checkoktoadd((size_t)sz, (size_t)4);
+	cp = aresize(*cpp, (size_t)sz + (size_t)4, ATEMP);
+	cp[0] = ksh_toupper(drvltr);
+	cp[1] = ':';
+	cp[2] = '/';
+	drvno = ksh_numuc(cp[0]) + 1;
+	/* NUL is part of space within buffer passed */
+	++sz;
+	if ((rc = DosQueryCurrentDir(drvno, cp + 3, &sz)) == 0) {
+		/* success! */
+		*cpp = cp;
+		return (0);
+	}
+	afree(cp, ATEMP);
+	*cpp = NULL;
+	switch (rc) {
+	case 15: /* invalid drive */
+		errno = ENOTBLK;
+		break;
+	case 26: /* not dos disk */
+		errno = ENODEV;
+		break;
+	case 108: /* drive locked */
+		errno = EDEADLK;
+		break;
+	case 111: /* buffer overflow */
+		errno = ENAMETOOLONG;
+		break;
+	default:
+		errno = EINVAL;
+	}
+	return (-1);
+}
diff --git a/sh.h b/sh.h
index 7c7df8f..096f1bc 100644
--- a/sh.h
+++ b/sh.h
@@ -10,7 +10,7 @@
 
 /*-
  * Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *	       2011, 2012, 2013, 2014, 2015, 2016
+ *	       2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -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.789 2016/10/02 22:21:47 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.849 2017/10/17 23:45:19 tg Exp $");
 #endif
-#define MKSH_VERSION "R53 2016/10/02"
+#define MKSH_VERSION "R56 2017/10/17"
 
 /* arithmetic types: C implementation */
 #if !HAVE_CAN_INTTYPES
@@ -257,6 +264,23 @@ typedef MKSH_TYPEDEF_SSIZE_T ssize_t;
 
 #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 @@ struct rusage {
 #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
@@ -392,13 +418,20 @@ struct rusage {
 #endif
 
 #ifdef __OS2__
+#define MKSH_UNIXROOT	"/@unixroot"
+#else
+#define MKSH_UNIXROOT	""
+#endif
+
+#ifdef MKSH_DOSPATH
+#ifndef __GNUC__
+# error GCC extensions needed later on
+#endif
 #define MKSH_PATHSEPS	";"
 #define MKSH_PATHSEPC	';'
-#define MKSH_UNIXROOT	"/@unixroot"
 #else
 #define MKSH_PATHSEPS	":"
 #define MKSH_PATHSEPC	':'
-#define MKSH_UNIXROOT	""
 #endif
 
 #if !HAVE_FLOCK_DECL
@@ -480,6 +513,23 @@ extern int __cdecl setegid(gid_t);
 #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
@@ -491,32 +541,50 @@ extern int __cdecl setegid(gid_t);
 #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 */
 
 #ifdef MKSH_LEGACY_MODE
-#define KSH_VERSIONNAME	"LEGACY"
+#define KSH_VERSIONNAME_ISLEGACY	"LEGACY"
 #else
-#define KSH_VERSIONNAME	"MIRBSD"
+#define KSH_VERSIONNAME_ISLEGACY	"MIRBSD"
 #endif
-EXTERN const char initvsn[] E_INIT("KSH_VERSION=@(#)" KSH_VERSIONNAME \
-    " KSH " MKSH_VERSION);
+#ifdef MKSH_WITH_TEXTMODE
+#define KSH_VERSIONNAME_TEXTMODE	" +TEXTMODE"
+#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_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
@@ -570,15 +638,12 @@ char *ucstrstr(char *, const char *);
 #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 != 531)
+#if (!defined(MKSH_BUILDMAKEFILE4BSD) && !defined(MKSH_BUILDSH)) || (MKSH_BUILD_R != 562)
 #error Must run Build.sh to compile this.
 extern void thiswillneverbedefinedIhope(void);
 int
@@ -590,7 +655,7 @@ im_sorry_dave(void)
 #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);				\
@@ -606,7 +671,7 @@ im_sorry_dave(void)
 #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) {					\
@@ -617,7 +682,7 @@ im_sorry_dave(void)
 	(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) {					\
@@ -630,14 +695,6 @@ im_sorry_dave(void)
 } while (/* CONSTCOND */ 0)
 #endif
 
-#ifdef MKSH_LEGACY_MODE
-#ifndef MKSH_NO_CMDLINE_EDITING
-#define MKSH_NO_CMDLINE_EDITING	/* defined */
-#endif
-#undef MKSH_S_NOVI
-#define MKSH_S_NOVI		1
-#endif
-
 #ifdef MKSH_SMALL
 #ifndef MKSH_NOPWNAM
 #define MKSH_NOPWNAM		/* defined */
@@ -746,8 +803,8 @@ enum sh_flag {
 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 */
@@ -772,6 +829,7 @@ extern struct env {
 #define E_LOOP	5	/* executing for/while # */
 #define E_ERRH	6	/* general error handler # */
 #define E_GONE	7	/* hidden in child */
+#define E_EVAL	8	/* running eval # */
 /* # indicates env has valid jbuf (see unwind()) */
 
 /* struct env.flag values */
@@ -855,25 +913,29 @@ EXTERN char null[] E_INIT("");
 
 #ifndef HAVE_STRING_POOLING /* helpers for pooled strings */
 EXTERN const char T4spaces[] E_INIT("    ");
-#define T1space (T4spaces + 3)
-EXTERN const char Tcolsp[] E_INIT(": ");
-EXTERN const char TC_LEX1[] E_INIT("|&;<>() \t\n");
-#define TC_IFSWS (TC_LEX1 + 7)
+#define T1space (Treal_sp2 + 5)
+#define Tcolsp (Tf_sD_ + 2)
+#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("*=.");
 EXTERN const char Taugo[] E_INIT("augo");
 EXTERN const char Tbracket[] E_INIT("[");
 #define Tdot (Tsgdot + 2)
-EXTERN const char Talias[] E_INIT("alias");
-EXTERN const char Tbadsubst[] E_INIT("bad substitution");
+#define Talias (Tunalias + 2)
+EXTERN const char Tbadnum[] E_INIT("bad number");
+#define Tbadsubst (Tfg_badsubst + 10)
 EXTERN const char Tbg[] E_INIT("bg");
 EXTERN const char Tbad_bsize[] E_INIT("bad shf/buf/bsize");
 #define Tbsize (Tbad_bsize + 12)
 EXTERN const char Tbad_sig_ss[] E_INIT("%s: bad signal '%s'");
 #define Tbad_sig_s (Tbad_sig_ss + 4)
-EXTERN const char Tgbuiltin[] E_INIT("=builtin");
-#define Tbuiltin (Tgbuiltin + 1)
+EXTERN const char Tsgbreak[] E_INIT("*=break");
+#define Tbreak (Tsgbreak + 2)
+EXTERN const char T__builtin[] E_INIT("-\\builtin");
+#define T_builtin (T__builtin + 1)
+#define Tbuiltin (T__builtin + 2)
 EXTERN const char Toomem[] E_INIT("can't allocate %zu data bytes");
 EXTERN const char Tcant_cd[] E_INIT("restricted shell - can't cd");
 EXTERN const char Tcant_find[] E_INIT("can't find");
@@ -882,31 +944,35 @@ EXTERN const char Tcant_open[] E_INIT("can't open");
 EXTERN const char Tbcat[] E_INIT("!cat");
 #define Tcat (Tbcat + 1)
 #define Tcd (Tcant_cd + 25)
-EXTERN const char Tcommand[] E_INIT("command");
+#define T_command (T_funny_command + 9)
+#define Tcommand (T_funny_command + 10)
+EXTERN const char Tsgcontinue[] E_INIT("*=continue");
+#define Tcontinue (Tsgcontinue + 2)
 EXTERN const char Tcreate[] E_INIT("create");
 EXTERN const char TELIF_unexpected[] E_INIT("TELIF unexpected");
 EXTERN const char TEXECSHELL[] E_INIT("EXECSHELL");
-EXTERN const char Tsgexport[] E_INIT("*=export");
-#define Texport (Tsgexport + 2)
+EXTERN const char Tdsgexport[] E_INIT("^*=export");
+#define Texport (Tdsgexport + 3)
 #ifdef __OS2__
 EXTERN const char Textproc[] E_INIT("extproc");
 #endif
 EXTERN const char Tfalse[] E_INIT("false");
 EXTERN const char Tfg[] E_INIT("fg");
 EXTERN const char Tfg_badsubst[] E_INIT("fileglob: bad substitution");
-EXTERN const char Tfile[] E_INIT("file");
+#define Tfile (Tfile_fd + 20)
 EXTERN const char Tfile_fd[] E_INIT("function definition file");
 EXTERN const char TFPATH[] E_INIT("FPATH");
 EXTERN const char T_function[] E_INIT(" function");
 #define Tfunction (T_function + 1)
-EXTERN const char T_funny_command[] E_INIT("funny $() command");
+EXTERN const char T_funny_command[] E_INIT("funny $()-command");
 EXTERN const char Tgetopts[] E_INIT("getopts");
-EXTERN const char Thistory[] E_INIT("history");
+#define Thistory (Tnot_in_history + 7)
 EXTERN const char Tintovfl[] E_INIT("integer overflow %zu %c %zu prevented");
+EXTERN const char Tinvname[] E_INIT("%s: invalid %s name");
 EXTERN const char Tjobs[] E_INIT("jobs");
 EXTERN const char Tjob_not_started[] E_INIT("job not started");
 EXTERN const char Tmksh[] E_INIT("mksh");
-EXTERN const char Tname[] E_INIT("name");
+#define Tname (Tinvname + 15)
 EXTERN const char Tno_args[] E_INIT("missing argument");
 EXTERN const char Tno_OLDPWD[] E_INIT("no OLDPWD");
 EXTERN const char Tnot_ident[] E_INIT("is not an identifier");
@@ -917,77 +983,84 @@ EXTERN const char Tnot_found_s[] E_INIT("%s not found");
 #define TOLDPWD (Tno_OLDPWD + 3)
 #define Topen (Tcant_open + 6)
 #define TPATH (TFPATH + 1)
-EXTERN const char Tpv[] E_INIT("pv");
+#define Tpv (TpVv + 1)
 EXTERN const char TpVv[] E_INIT("Vpv");
 #define TPWD (Tno_OLDPWD + 6)
-EXTERN const char Tread[] E_INIT("read");
-EXTERN const char Tsgreadonly[] E_INIT("*=readonly");
-#define Treadonly (Tsgreadonly + 2)
+#define Tread (Tshf_read + 4)
+EXTERN const char Tdsgreadonly[] E_INIT("^*=readonly");
+#define Treadonly (Tdsgreadonly + 3)
 EXTERN const char Tredirection_dup[] E_INIT("can't finish (dup) redirection");
 #define Tredirection (Tredirection_dup + 19)
-EXTERN const char Treal_sp1[] E_INIT("real ");
+#define Treal_sp1 (Treal_sp2 + 1)
 EXTERN const char Treal_sp2[] E_INIT(" real ");
 EXTERN const char Treq_arg[] E_INIT("requires an argument");
 EXTERN const char Tselect[] E_INIT("select");
 EXTERN const char Tsgset[] E_INIT("*=set");
-#define Tset (Tsgset + 2)
+#define Tset (Tf_parm + 18)
 #define Tsh (Tmksh + 2)
 #define TSHELL (TEXECSHELL + 4)
+#define Tshell (Ttoo_many_files + 23)
 EXTERN const char Tshf_read[] E_INIT("shf_read");
 EXTERN const char Tshf_write[] E_INIT("shf_write");
+EXTERN const char Tgsource[] E_INIT("=source");
+#define Tsource (Tgsource + 1)
 EXTERN const char Tj_suspend[] E_INIT("j_suspend");
 #define Tsuspend (Tj_suspend + 2)
 EXTERN const char Tsynerr[] E_INIT("syntax error");
 EXTERN const char Ttime[] E_INIT("time");
 EXTERN const char Ttoo_many_args[] E_INIT("too many arguments");
+EXTERN const char Ttoo_many_files[] E_INIT("too many open files in shell");
 EXTERN const char Ttrue[] E_INIT("true");
 EXTERN const char Ttty_fd_dupof[] E_INIT("dup of tty fd");
 #define Ttty_fd (Ttty_fd_dupof + 7)
-EXTERN const char Tgtypeset[] E_INIT("=typeset");
-#define Ttypeset (Tgtypeset + 1)
+EXTERN const char Tdgtypeset[] E_INIT("^=typeset");
+#define Ttypeset (Tdgtypeset + 2)
 #define Tugo (Taugo + 1)
 EXTERN const char Tunalias[] E_INIT("unalias");
 #define Tunexpected (TELIF_unexpected + 6)
+EXTERN const char Tunexpected_type[] E_INIT("%s: unexpected %s type %d");
 EXTERN const char Tunknown_option[] E_INIT("unknown option");
-EXTERN const char Tuser_sp1[] E_INIT("user ");
+EXTERN const char Tunwind[] E_INIT("unwind");
+#define Tuser_sp1 (Tuser_sp2 + 1)
 EXTERN const char Tuser_sp2[] E_INIT(" user ");
 #define Twrite (Tshf_write + 4)
 EXTERN const char Tf__S[] E_INIT(" %S");
-EXTERN const char Tf__d[] E_INIT(" %d");
+#define Tf__d (Tunexpected_type + 22)
 EXTERN const char Tf__ss[] E_INIT(" %s%s");
-EXTERN const char Tf__sN[] E_INIT(" %s\n");
+#define Tf__sN (Tf_s_s_sN + 5)
 EXTERN const char Tf_sSs[] E_INIT("%s/%s");
-EXTERN const char Tf_T[] E_INIT("%T");
+#define Tf_T (Tf_s_T + 3)
 EXTERN const char Tf_dN[] E_INIT("%d\n");
 EXTERN const char Tf_s_[] E_INIT("%s ");
 EXTERN const char Tf_s_T[] E_INIT("%s %T");
 EXTERN const char Tf_s_s_sN[] E_INIT("%s %s %s\n");
-EXTERN const char Tf_s_s[] E_INIT("%s %s");
-EXTERN const char Tf_s_sD_s[] E_INIT("%s %s: %s");
+#define Tf_s_s (Tf_sD_s_s + 4)
+#define Tf_s_sD_s (Tf_cant_ss_s + 6)
 EXTERN const char Tf_optfoo[] E_INIT("%s%s-%c: %s");
 EXTERN const char Tf_sD_[] E_INIT("%s: ");
 EXTERN const char Tf_szs[] E_INIT("%s: %zd %s");
 EXTERN const char Tf_parm[] E_INIT("%s: parameter not set");
 EXTERN const char Tf_coproc[] E_INIT("-p: %s");
-EXTERN const char Tf_cant[] E_INIT("can't %s %s: %s");
-EXTERN const char Tf_heredoc[] E_INIT("here document '%s' unclosed\n");
+EXTERN const char Tf_cant_s[] E_INIT("%s: can't %s");
+EXTERN const char Tf_cant_ss_s[] E_INIT("can't %s %s: %s");
+EXTERN const char Tf_heredoc[] E_INIT("here document '%s' unclosed");
 #if HAVE_MKNOD
 EXTERN const char Tf_nonnum[] E_INIT("non-numeric %s %s '%s'");
 #endif
 EXTERN const char Tf_S_[] E_INIT("%S ");
 #define Tf_S (Tf__S + 1)
-EXTERN const char Tf_lu[] E_INIT("%lu");
+#define Tf_lu (Tf_toolarge + 17)
 EXTERN const char Tf_toolarge[] E_INIT("%s %s too large: %lu");
 EXTERN const char Tf_ldfailed[] E_INIT("%s %s(%d, %ld) failed: %s");
-#define Tf_ss (Tf__ss + 1)
+#define Tf_ss (Tf_sss + 2)
 EXTERN const char Tf_sss[] E_INIT("%s%s%s");
 EXTERN const char Tf_sD_s_sD_s[] E_INIT("%s: %s %s: %s");
-EXTERN const char Tf_toomany[] E_INIT("too many %ss\n");
+EXTERN const char Tf_toomany[] E_INIT("too many %ss");
 EXTERN const char Tf_sd[] E_INIT("%s %d");
-#define Tf_s (Tf__ss + 3)
+#define Tf_s (Tf_temp + 28)
 EXTERN const char Tft_end[] E_INIT("%;");
 EXTERN const char Tft_R[] E_INIT("%R");
-#define Tf_d (Tf__d + 1)
+#define Tf_d (Tunexpected_type + 23)
 EXTERN const char Tf_sD_s_qs[] E_INIT("%s: %s '%s'");
 EXTERN const char Tf_ro[] E_INIT("read-only: %s");
 EXTERN const char Tf_flags[] E_INIT("%s: flags 0x%X");
@@ -996,15 +1069,15 @@ EXTERN const char Tf_ssfaileds[] E_INIT("%s: %s failed: %s");
 EXTERN const char Tf_sD_sD_s[] E_INIT("%s: %s: %s");
 EXTERN const char Tf__c_[] E_INIT("-%c ");
 EXTERN const char Tf_sD_s_s[] E_INIT("%s: %s %s");
-#define Tf_sN (Tf__sN + 1)
-#define Tf_sD_s (Tf_s_sD_s + 3)
+#define Tf_sN (Tf_s_s_sN + 6)
+#define Tf_sD_s (Tf_temp + 24)
 EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #else /* helpers for string pooling */
 #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 "*=."
@@ -1012,13 +1085,17 @@ EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #define Tbracket "["
 #define Tdot "."
 #define Talias "alias"
+#define Tbadnum "bad number"
 #define Tbadsubst "bad substitution"
 #define Tbg "bg"
 #define Tbad_bsize "bad shf/buf/bsize"
 #define Tbsize "bsize"
 #define Tbad_sig_ss "%s: bad signal '%s'"
 #define Tbad_sig_s "bad signal '%s'"
-#define Tgbuiltin "=builtin"
+#define Tsgbreak "*=break"
+#define Tbreak "break"
+#define T__builtin "-\\builtin"
+#define T_builtin "\\builtin"
 #define Tbuiltin "builtin"
 #define Toomem "can't allocate %zu data bytes"
 #define Tcant_cd "restricted shell - can't cd"
@@ -1028,11 +1105,14 @@ EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #define Tbcat "!cat"
 #define Tcat "cat"
 #define Tcd "cd"
+#define T_command "-command"
 #define Tcommand "command"
+#define Tsgcontinue "*=continue"
+#define Tcontinue "continue"
 #define Tcreate "create"
 #define TELIF_unexpected "TELIF unexpected"
 #define TEXECSHELL "EXECSHELL"
-#define Tsgexport "*=export"
+#define Tdsgexport "^*=export"
 #define Texport "export"
 #ifdef __OS2__
 #define Textproc "extproc"
@@ -1045,10 +1125,11 @@ EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #define TFPATH "FPATH"
 #define T_function " function"
 #define Tfunction "function"
-#define T_funny_command "funny $() command"
+#define T_funny_command "funny $()-command"
 #define Tgetopts "getopts"
 #define Thistory "history"
 #define Tintovfl "integer overflow %zu %c %zu prevented"
+#define Tinvname "%s: invalid %s name"
 #define Tjobs "jobs"
 #define Tjob_not_started "job not started"
 #define Tmksh "mksh"
@@ -1067,7 +1148,7 @@ EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #define TpVv "Vpv"
 #define TPWD "PWD"
 #define Tread "read"
-#define Tsgreadonly "*=readonly"
+#define Tdsgreadonly "^*=readonly"
 #define Treadonly "readonly"
 #define Tredirection_dup "can't finish (dup) redirection"
 #define Tredirection "redirection"
@@ -1079,22 +1160,28 @@ EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #define Tset "set"
 #define Tsh "sh"
 #define TSHELL "SHELL"
+#define Tshell "shell"
 #define Tshf_read "shf_read"
 #define Tshf_write "shf_write"
+#define Tgsource "=source"
+#define Tsource "source"
 #define Tj_suspend "j_suspend"
 #define Tsuspend "suspend"
 #define Tsynerr "syntax error"
 #define Ttime "time"
 #define Ttoo_many_args "too many arguments"
+#define Ttoo_many_files "too many open files in shell"
 #define Ttrue "true"
 #define Ttty_fd_dupof "dup of tty fd"
 #define Ttty_fd "tty fd"
-#define Tgtypeset "=typeset"
+#define Tdgtypeset "^=typeset"
 #define Ttypeset "typeset"
 #define Tugo "ugo"
 #define Tunalias "unalias"
 #define Tunexpected "unexpected"
+#define Tunexpected_type "%s: unexpected %s type %d"
 #define Tunknown_option "unknown option"
+#define Tunwind "unwind"
 #define Tuser_sp1 "user "
 #define Tuser_sp2 " user "
 #define Twrite "write"
@@ -1115,8 +1202,9 @@ EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #define Tf_szs "%s: %zd %s"
 #define Tf_parm "%s: parameter not set"
 #define Tf_coproc "-p: %s"
-#define Tf_cant "can't %s %s: %s"
-#define Tf_heredoc "here document '%s' unclosed\n"
+#define Tf_cant_s "%s: can't %s"
+#define Tf_cant_ss_s "can't %s %s: %s"
+#define Tf_heredoc "here document '%s' unclosed"
 #if HAVE_MKNOD
 #define Tf_nonnum "non-numeric %s %s '%s'"
 #endif
@@ -1128,7 +1216,7 @@ EXTERN const char T_devtty[] E_INIT("/dev/tty");
 #define Tf_ss "%s%s"
 #define Tf_sss "%s%s%s"
 #define Tf_sD_s_sD_s "%s: %s %s: %s"
-#define Tf_toomany "too many %ss\n"
+#define Tf_toomany "too many %ss"
 #define Tf_sd "%s %d"
 #define Tf_s "%s"
 #define Tft_end "%;"
@@ -1239,7 +1327,7 @@ enum tmout_enum {
 	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;
@@ -1247,37 +1335,178 @@ EXTERN bool really_exit;
 /*
  * fast character classes
  */
-#define C_ALPHA	 BIT(0)		/* a-z_A-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 */
-#define C_SUBOP2 BIT(8)		/* "#%" (magic, see below) */
-
-extern unsigned char chtypes[];
-
-#define ctype(c, t)	tobool( ((t) == C_SUBOP2) ?			\
-			    (((c) == '#' || (c) == '%') ? 1 : 0) :	\
-			    (chtypes[(unsigned char)(c)] & (t)) )
-#define ord(c)		((int)(unsigned char)(c))
-#define ksh_isalphx(c)	ctype((c), C_ALPHA)
-#define ksh_isalnux(c)	ctype((c), C_ALPHA | C_DIGIT)
-#define ksh_isdigit(c)	(((c) >= '0') && ((c) <= '9'))
-#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'))
-
-EXTERN int ifs0 E_INIT(' ');	/* for "$*" */
+
+/* internal types, do not reference */
+
+/* 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 */
+
+/* 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 */
 
@@ -1330,7 +1559,7 @@ EXTERN sigset_t		sm_default, sm_sigchld;
 
 /* name of called builtin function (used by error functions) */
 EXTERN const char *builtin_argv0;
-/* is called builtin SPEC_BI? (also KEEPASN, odd use though) */
+/* is called builtin a POSIX special builtin? (error functions only) */
 EXTERN bool builtin_spec;
 
 /* current working directory */
@@ -1350,7 +1579,7 @@ EXTERN mksh_ari_t x_lins E_INIT(24);
 /* Determine the location of the system (common) profile */
 
 #ifndef MKSH_DEFAULT_PROFILEDIR
-#define MKSH_DEFAULT_PROFILEDIR	"/etc"
+#define MKSH_DEFAULT_PROFILEDIR	MKSH_UNIXROOT "/etc"
 #endif
 
 #define MKSH_SYSTEM_PROFILE	MKSH_DEFAULT_PROFILEDIR "/profile"
@@ -1463,7 +1692,7 @@ struct tbl {
 };
 
 EXTERN struct tbl *vtemp;
-/* set by global() and local() */
+/* set by isglobal(), global() and local() */
 EXTERN bool last_lookup_was_array;
 
 /* common flag bits */
@@ -1500,6 +1729,8 @@ EXTERN bool last_lookup_was_array;
 #define SPEC_BI		BIT(12)	/* a POSIX special builtin */
 #define LOWER_BI	BIT(13)	/* (with LOW_BI) override even w/o flags */
 #define LOW_BI		BIT(14)	/* external utility overrides built-in one */
+#define DECL_UTIL	BIT(15)	/* is declaration utility */
+#define DECL_FWDR	BIT(16) /* is declaration utility forwarder */
 
 /*
  * Attributes that can be set by the user (used to decide if an unset
@@ -1672,6 +1903,8 @@ struct op {
 #define ADELIM	12	/* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */
 #define FUNSUB	14	/* ${ foo;} substitution (NUL terminated) */
 #define VALSUB	15	/* ${|foo;} substitution (NUL terminated) */
+#define COMASUB	16	/* `…` substitution (COMSUB but expand aliases) */
+#define FUNASUB	17	/* function substitution but expand aliases */
 
 /*
  * IO redirection
@@ -1748,8 +1981,6 @@ typedef struct XString {
 	Area *areap;
 } XString;
 
-typedef char *XStringP;
-
 /* initialise expandable string */
 #define XinitN(xs, length, area) do {				\
 	(xs).len = (length);					\
@@ -1825,6 +2056,15 @@ typedef struct {
 #define XPclose(x)	aresize2((x).beg, XPsize(x), sizeof(void *), ATEMP)
 #define XPfree(x)	afree((x).beg, ATEMP)
 
+/* for print_columns */
+
+struct columnise_opts {
+	struct shf *shf;
+	char linesep;
+	bool do_last;
+	bool prefcol;
+};
+
 /*
  * Lexer internals
  */
@@ -1939,12 +2179,77 @@ typedef union {
 
 #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)
-
-#define IDENT		64
+#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
 
 EXTERN Source *source;		/* yyparse/yylex source */
 EXTERN YYSTYPE yylval;		/* result from yylex */
@@ -2012,7 +2317,8 @@ int glob_str(char *, XPtrV *, bool);
 char *do_tilde(char *);
 /* exec.c */
 int execute(struct op * volatile, volatile int, volatile int * volatile);
-int shcomexec(const char **);
+int c_builtin(const char **);
+struct tbl *get_builtin(const char *);
 struct tbl *findfunc(const char *, uint32_t, bool);
 int define(const char *, struct op *);
 const char *builtin(const char *, int (*)(const char **));
@@ -2021,7 +2327,7 @@ void flushcom(bool);
 int search_access(const char *, int);
 const char *search_path(const char *, const char *, int, int *);
 void pr_menu(const char * const *);
-void pr_list(char * const *);
+void pr_list(struct columnise_opts *, char * const *);
 int herein(struct ioword *, char **);
 /* expr.c */
 int evaluate(const char *, mksh_ari_t *, int, bool);
@@ -2050,6 +2356,7 @@ int c_printf(const char **);
 int c_whence(const char **);
 int c_command(const char **);
 int c_typeset(const char **);
+bool valid_alias_name(const char *);
 int c_alias(const char **);
 int c_unalias(const char **);
 int c_let(const char **);
@@ -2079,8 +2386,6 @@ int c_times(const char **);
 int timex(struct op *, int, volatile int *);
 void timex_hook(struct op *, char ** volatile *);
 int c_exec(const char **);
-/* dummy function (just need pointer value), special case in comexec() */
-#define c_builtin shcomexec
 int c_test(const char **);
 #if HAVE_MKNOD
 int c_mknod(const char **);
@@ -2122,8 +2427,8 @@ void runtrap(Trap *, bool);
 void cleartraps(void);
 void restoresigs(void);
 void settrap(Trap *, const char *);
-int block_pipe(void);
-void restore_pipe(int);
+bool block_pipe(void);
+void restore_pipe(void);
 int setsig(Trap *, sig_t, int);
 void setexecsig(Trap *, int);
 #if HAVE_FLOCK || HAVE_LOCK_FCNTL
@@ -2163,7 +2468,7 @@ int pprompt(const char *, int);
 /* main.c */
 int include(const char *, int, const char **, bool);
 int command(const char *, int);
-int shell(Source * volatile, volatile bool);
+int shell(Source * volatile, volatile int);
 /* argument MUST NOT be 0 */
 void unwind(int) MKSH_A_NORETURN;
 void newenv(int);
@@ -2222,8 +2527,6 @@ void DF(const char *, ...)
     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);
@@ -2231,15 +2534,16 @@ void change_xtrace(unsigned char, bool);
 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 *);
 char *quote_value(const char *);
-void print_columns(struct shf *, unsigned int,
+void print_columns(struct columnise_opts *, unsigned int,
     void (*)(char *, size_t, unsigned int, const void *),
-    const void *, size_t, size_t, bool);
+    const void *, size_t, size_t);
 void strip_nuls(char *, size_t)
     MKSH_A_BOUNDED(__string__, 1, 2);
 ssize_t blocking_read(int, char *, size_t)
@@ -2255,6 +2559,14 @@ char *strdup_i(const char *, Area *);
 char *strndup_i(const char *, size_t, Area *);
 #endif
 int unbksl(bool, int (*)(void), void (*)(int));
+#ifdef __OS2__
+/* os2.c */
+void os2_init(int *, const char ***);
+void setextlibpath(const char *, const char *);
+int access_ex(int (*)(const char *, int), const char *, int);
+int stat_ex(const char *, struct stat *);
+const char *real_exec_name(const char *);
+#endif
 /* shf.c */
 struct shf *shf_open(const char *, int, int, int);
 struct shf *shf_fdopen(int, int, struct shf *);
@@ -2287,10 +2599,10 @@ char *shf_smprintf(const char *, ...)
     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 */
-int assign_command(const char *, bool) MKSH_A_PURE;
 void initkeywords(void);
-struct op *compile(Source *, bool);
+struct op *compile(Source *, bool, bool);
 bool parse_usec(const char *, struct timeval *);
 char *yyrecursive(int);
 void yyrecursive_pop(bool);
@@ -2316,6 +2628,7 @@ void popblock(void);
 void initvar(void);
 struct block *varsearch(struct block *, struct tbl **, const char *, uint32_t);
 struct tbl *global(const char *);
+struct tbl *isglobal(const char *, bool);
 struct tbl *local(const char *, bool);
 char *str_val(struct tbl *);
 int setstr(struct tbl *, const char *, int);
@@ -2345,7 +2658,7 @@ enum Test_op {
 	/* non-operator */
 	TO_NONOP = 0,
 	/* unary operators */
-	TO_STNZE, TO_STZER, TO_OPTION,
+	TO_STNZE, TO_STZER, TO_ISSET, TO_OPTION,
 	TO_FILAXST,
 	TO_FILEXST,
 	TO_FILREG, TO_FILBDEV, TO_FILCDEV, TO_FILSYM, TO_FILFIFO, TO_FILSOCK,
@@ -2403,9 +2716,6 @@ EXTERN bool tty_hasstate;	/* true if tty_state is valid */
 extern int tty_init_fd(void);	/* initialise tty_fd, tty_devtty */
 
 #ifdef __OS2__
-#ifndef __GNUC__
-# error oops?
-#endif
 #define binopen2(path,flags)		__extension__({			\
 	int binopen2_fd = open((path), (flags) | O_BINARY);		\
 	if (binopen2_fd >= 0)						\
@@ -2418,15 +2728,40 @@ extern int tty_init_fd(void);	/* initialise tty_fd, tty_devtty */
 		setmode(binopen3_fd, O_BINARY);				\
 	(binopen3_fd);							\
 })
+#else
+#define binopen2(path,flags)		open((path), (flags) | O_BINARY)
+#define binopen3(path,flags,mode)	open((path), (flags) | O_BINARY, (mode))
+#endif
+
+#ifdef MKSH_DOSPATH
+#define mksh_drvltr(s)			__extension__({			\
+	const char *mksh_drvltr_s = (s);				\
+	(ctype(mksh_drvltr_s[0], C_ALPHA) && mksh_drvltr_s[1] == ':');	\
+})
 #define mksh_abspath(s)			__extension__({			\
 	const char *mksh_abspath_s = (s);				\
-	(mksh_abspath_s[0] == '/' || (ksh_isalphx(mksh_abspath_s[0]) &&	\
-	    mksh_abspath_s[1] == ':'));					\
+	(mksh_cdirsep(mksh_abspath_s[0]) ||				\
+	    (mksh_drvltr(mksh_abspath_s) &&				\
+	    mksh_cdirsep(mksh_abspath_s[2])));				\
+})
+#define mksh_cdirsep(c)			__extension__({			\
+	char mksh_cdirsep_c = (c);					\
+	(mksh_cdirsep_c == '/' || mksh_cdirsep_c == '\\');		\
 })
+#define mksh_sdirsep(s)			strpbrk((s), "/\\")
+#define mksh_vdirsep(s)			__extension__({			\
+	const char *mksh_vdirsep_s = (s);				\
+	(((mksh_drvltr(mksh_vdirsep_s) &&				\
+	    !mksh_cdirsep(mksh_vdirsep_s[2])) ? (!0) :			\
+	    (mksh_sdirsep(mksh_vdirsep_s) != NULL)) &&			\
+	    (strcmp(mksh_vdirsep_s, T_builtin) != 0));			\
+})
+int getdrvwd(char **, unsigned int);
 #else
-#define binopen2(path,flags)		open((path), (flags) | O_BINARY)
-#define binopen3(path,flags,mode)	open((path), (flags) | O_BINARY, (mode))
-#define mksh_abspath(s)			((s)[0] == '/')
+#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
 
 /* be sure not to interfere with anyone else's idea about EXTERN */
diff --git a/sh_flags.opt b/sh_flags.opt
index 9f364d5..795d198 100644
--- a/sh_flags.opt
+++ b/sh_flags.opt
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2013, 2014, 2015
+ * Copyright (c) 2013, 2014, 2015, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -19,7 +19,7 @@
  */
 
 @SHFLAGS_DEFNS
-__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.4 2015/12/12 21:08:44 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/sh_flags.opt,v 1.5 2017/02/18 02:33:15 tg Exp $");
 @SHFLAGS_ENUMS
 #define FN(sname,cname,flags,ochar)	cname,
 #define F0(sname,cname,flags,ochar)	cname = 0,
@@ -52,7 +52,7 @@ FN("bgnice", FBGNICE, OF_ANY
 FN("braceexpand", FBRACEEXPAND, OF_ANY
 
 /* ./.	Emacs command line editing mode */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("emacs", FEMACS, OF_ANY
 
 /* -e	quit on error */
@@ -60,7 +60,7 @@ FN("emacs", FEMACS, OF_ANY
 FN("errexit", FERREXIT, OF_ANY
 
 /* ./.	Emacs command line editing mode, gmacs variant */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("gmacs", FGMACS, OF_ANY
 
 /* ./.	reading EOF does not exit */
@@ -160,19 +160,19 @@ FN("utf8-mode", FUNICODE, OF_ANY
 FN("verbose", FVERBOSE, OF_ANY
 
 /* ./.	Vi command line editing mode */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("vi", FVI, OF_ANY
 
 /* ./.	enable ESC as file name completion character (non-standard) */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("vi-esccomplete", FVIESCCOMPLETE, OF_ANY
 
 /* ./.	enable Tab as file name completion character (non-standard) */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("vi-tabcomplete", FVITABCOMPLETE, OF_ANY
 
 /* ./.	always read in raw mode (no effect) */
->|!defined(MKSH_NO_CMDLINE_EDITING) || defined(MKSH_LEGACY_MODE)
+>|!MKSH_NO_CMDLINE_EDITING
 FN("viraw", FVIRAW, OF_ANY
 
 /* -x	execution trace (display commands as they are run) */
diff --git a/shf.c b/shf.c
index ace0ab6..7e53352 100644
--- a/shf.c
+++ b/shf.c
@@ -2,8 +2,10 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2011,
- *		 2012, 2013, 2015, 2016
+ *		 2012, 2013, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
+ * Copyright (c) 2015
+ *	Daniel Richard G. <skunk at 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.76 2016/07/25 00:04:47 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 */
@@ -289,29 +291,31 @@ shf_sclose(struct shf *shf)
 int
 shf_flush(struct shf *shf)
 {
-	if (shf->flags & SHF_STRING)
-		return ((shf->flags & SHF_WR) ? -1 : 0);
+	int rv = 0;
 
-	if (shf->fd < 0)
+	if (shf->flags & SHF_STRING)
+		rv = (shf->flags & SHF_WR) ? -1 : 0;
+	else if (shf->fd < 0)
 		internal_errorf(Tf_sD_s, "shf_flush", "no fd");
-
-	if (shf->flags & SHF_ERROR) {
+	else if (shf->flags & SHF_ERROR) {
 		errno = shf->errnosv;
-		return (-1);
-	}
-
-	if (shf->flags & SHF_READING) {
+		rv = -1;
+	} else if (shf->flags & SHF_READING) {
 		shf->flags &= ~(SHF_EOF | SHF_READING);
 		if (shf->rnleft > 0) {
-			lseek(shf->fd, (off_t)-shf->rnleft, SEEK_CUR);
+			if (lseek(shf->fd, (off_t)-shf->rnleft,
+			    SEEK_CUR) == -1) {
+				shf->flags |= SHF_ERROR;
+				shf->errnosv = errno;
+				rv = -1;
+			}
 			shf->rnleft = 0;
 			shf->rp = shf->buf;
 		}
-		return (0);
 	} else if (shf->flags & SHF_WRITING)
-		return (shf_emptybuf(shf, 0));
+		rv = shf_emptybuf(shf, 0);
 
-	return (0);
+	return (rv);
 }
 
 /*
@@ -518,7 +522,23 @@ shf_getse(char *buf, ssize_t bsize, struct shf *shf)
 		shf->rnleft -= ncopy;
 		buf += ncopy;
 		bsize -= ncopy;
+#ifdef MKSH_WITH_TEXTMODE
+		if (end && buf > orig_buf + 1 && buf[-2] == '\r') {
+			buf--;
+			bsize++;
+			buf[-1] = '\n';
+		}
+#endif
 	} while (!end && bsize);
+#ifdef MKSH_WITH_TEXTMODE
+	if (!bsize && buf[-1] == '\r') {
+		int c = shf_getc(shf);
+		if (c == '\n')
+			buf[-1] = '\n';
+		else if (c != -1)
+			shf_ungetc(c, shf);
+	}
+#endif
 	*buf = '\0';
 	return (buf);
 }
@@ -856,11 +876,11 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
 				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
@@ -881,7 +901,7 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
 			/* nasty format */
 			break;
 
-		if (ksh_isupper(c)) {
+		if (ctype(c, C_UPPER)) {
 			flags |= FL_UPPER;
 			c = ksh_tolower(c);
 		}
@@ -1011,8 +1031,7 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
 			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--;
@@ -1140,3 +1159,163 @@ cstrerror(int errnum)
 	}
 }
 #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/syn.c b/syn.c
index 87b7c75..c50c2ab 100644
--- a/syn.c
+++ b/syn.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.115 2016/09/01 12:59:12 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) */
@@ -35,25 +35,24 @@ struct yyrecursive_state {
 	struct yyrecursive_state *next;
 	struct ioword **old_herep;
 	int old_symbol;
-	int old_salias;
 	int old_nesting_type;
 	bool old_reject;
 };
 
-static void yyparse(void);
-static struct op *pipeline(int);
-static struct op *andor(void);
-static struct op *c_list(bool);
+static void yyparse(bool);
+static struct op *pipeline(int, int);
+static struct op *andor(int);
+static struct op *c_list(int, bool);
 static struct ioword *synio(int);
-static struct op *nested(int, int, int);
-static struct op *get_command(int);
-static struct op *dogroup(void);
-static struct op *thenpart(void);
-static struct op *elsepart(void);
-static struct op *caselist(void);
-static struct op *casepart(int);
-static struct op *function_body(char *, bool);
-static char **wordlist(void);
+static struct op *nested(int, int, int, int);
+static struct op *get_command(int, int);
+static struct op *dogroup(int);
+static struct op *thenpart(int);
+static struct op *elsepart(int);
+static struct op *caselist(int);
+static struct op *casepart(int, int);
+static struct op *function_body(char *, int, bool);
+static char **wordlist(int);
 static struct op *block(int, struct op *, struct op *);
 static struct op *newtp(int);
 static void syntaxerr(const char *) MKSH_A_NORETURN;
@@ -71,7 +70,6 @@ static struct nesting_state nesting;	/* \n changed to ; */
 
 static bool reject;			/* token(cf) gets symbol again */
 static int symbol;			/* yylex value */
-static int sALIAS = ALIAS;		/* 0 in yyrecursive */
 
 #define REJECT		(reject = true)
 #define ACCEPT		(reject = false)
@@ -83,29 +81,29 @@ static const char Tcbrace[] = "}";
 static const char Tesac[] = "esac";
 
 static void
-yyparse(void)
+yyparse(bool doalias)
 {
 	int c;
 
 	ACCEPT;
 
-	outtree = c_list(source->type == SSTRING);
+	outtree = c_list(doalias ? ALIAS : 0, source->type == SSTRING);
 	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);
 }
 
 static struct op *
-pipeline(int cf)
+pipeline(int cf, int sALIAS)
 {
 	struct op *t, *p, *tl = NULL;
 
-	t = get_command(cf);
+	t = get_command(cf, sALIAS);
 	if (t != NULL) {
 		while (token(0) == '|') {
-			if ((p = get_command(CONTIN)) == NULL)
+			if ((p = get_command(CONTIN, sALIAS)) == NULL)
 				syntaxerr(NULL);
 			if (tl == NULL)
 				t = tl = block(TPIPE, t, p);
@@ -118,15 +116,15 @@ pipeline(int cf)
 }
 
 static struct op *
-andor(void)
+andor(int sALIAS)
 {
 	struct op *t, *p;
 	int c;
 
-	t = pipeline(0);
+	t = pipeline(0, sALIAS);
 	if (t != NULL) {
 		while ((c = token(0)) == LOGAND || c == LOGOR) {
-			if ((p = pipeline(CONTIN)) == NULL)
+			if ((p = pipeline(CONTIN, sALIAS)) == NULL)
 				syntaxerr(NULL);
 			t = block(c == LOGAND? TAND: TOR, t, p);
 		}
@@ -136,14 +134,14 @@ andor(void)
 }
 
 static struct op *
-c_list(bool multi)
+c_list(int sALIAS, bool multi)
 {
 	struct op *t = NULL, *p, *tl = NULL;
 	int c;
 	bool have_sep;
 
 	while (/* CONSTCOND */ 1) {
-		p = andor();
+		p = andor(sALIAS);
 		/*
 		 * Token has always been read/rejected at this point, so
 		 * we don't worry about what flags to pass token()
@@ -232,23 +230,27 @@ synio(int cf)
 }
 
 static struct op *
-nested(int type, int smark, int emark)
+nested(int type, int smark, int emark, int sALIAS)
 {
 	struct op *t;
 	struct nesting_state old_nesting;
 
 	nesting_push(&old_nesting, smark);
-	t = c_list(true);
+	t = c_list(sALIAS, true);
 	musthave(emark, KEYWORD|sALIAS);
 	nesting_pop(&old_nesting);
 	return (block(type, t, NULL));
 }
 
+static const char builtin_cmd[] = {
+	QCHAR, '\\', CHAR, 'b', CHAR, 'u', CHAR, 'i',
+	CHAR, 'l', CHAR, 't', CHAR, 'i', CHAR, 'n', EOS
+};
 static const char let_cmd[] = {
-	QCHAR, 'l', CHAR, 'e', CHAR, 't', CHAR, ']', EOS
+	CHAR, 'l', CHAR, 'e', CHAR, 't', EOS
 };
 static const char setA_cmd0[] = {
-	QCHAR, 's', CHAR, 'e', CHAR, 't', EOS
+	CHAR, 's', CHAR, 'e', CHAR, 't', EOS
 };
 static const char setA_cmd1[] = {
 	CHAR, '-', CHAR, 'A', EOS
@@ -258,7 +260,7 @@ static const char setA_cmd2[] = {
 };
 
 static struct op *
-get_command(int cf)
+get_command(int cf, int sALIAS)
 {
 	struct op *t;
 	int c, iopn = 0, syniocf, lno;
@@ -289,11 +291,11 @@ get_command(int cf)
 		t->lineno = source->line;
 		goto get_command_start;
 		while (/* CONSTCOND */ 1) {
-			bool check_assign_cmd;
+			bool check_decl_utility;
 
 			if (XPsize(args) == 0) {
  get_command_start:
-				check_assign_cmd = true;
+				check_decl_utility = true;
 				cf = sALIAS | CMDASN;
 			} else if (t->u.evalflags)
 				cf = CMDWORD | CMDASN;
@@ -311,16 +313,15 @@ get_command(int cf)
 
 			case LWORD:
 				ACCEPT;
-				/*
-				 * the iopn == 0 and XPsize(vars) == 0 are
-				 * dubious but AT&T ksh acts this way
-				 */
-				if (iopn == 0 && XPsize(vars) == 0 &&
-				    check_assign_cmd) {
-					if (assign_command(ident, false))
+				if (check_decl_utility) {
+					struct tbl *tt = get_builtin(ident);
+					uint32_t flag;
+
+					flag = tt ? tt->flag : 0;
+					if (flag & DECL_UTIL)
 						t->u.evalflags = DOVACHECK;
-					else if (strcmp(ident, Tcommand) != 0)
-						check_assign_cmd = false;
+					if (!(flag & DECL_FWDR))
+						check_decl_utility = false;
 				}
 				if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
 				    is_wdvarassign(yylval.cp))
@@ -329,7 +330,7 @@ get_command(int cf)
 					XPput(args, yylval.cp);
 				break;
 
-			case '(' /*)*/:
+			case ord('(' /*)*/):
 				if (XPsize(args) == 0 && XPsize(vars) == 1 &&
 				    is_wdvarassign(yylval.cp)) {
 					char *tcp;
@@ -343,6 +344,7 @@ get_command(int cf)
 					tcp[wdscan(tcp, EOS) - tcp - 3] = EOS;
 
 					/* construct new args strings */
+					XPput(args, wdcopy(builtin_cmd, ATEMP));
 					XPput(args, wdcopy(setA_cmd0, ATEMP));
 					XPput(args, wdcopy(setA_cmd1, ATEMP));
 					XPput(args, tcp);
@@ -371,8 +373,9 @@ get_command(int cf)
 					    XPsize(vars) != 0)
 						syntaxerr(NULL);
 					ACCEPT;
-					musthave(/*(*/')', 0);
-					t = function_body(XPptrv(args)[0], false);
+					musthave(/*(*/ ')', 0);
+					t = function_body(XPptrv(args)[0],
+					    sALIAS, false);
 				}
 				goto Leave;
 
@@ -383,18 +386,18 @@ get_command(int cf)
  Leave:
 		break;
 
-	case '(': /*)*/ {
+	case ord('(' /*)*/): {
 		int subshell_nesting_type_saved;
  Subshell:
 		subshell_nesting_type_saved = subshell_nesting_type;
-		subshell_nesting_type = ')';
-		t = nested(TPAREN, '(', ')');
+		subshell_nesting_type = ord(')');
+		t = nested(TPAREN, ord('('), ord(')'), sALIAS);
 		subshell_nesting_type = subshell_nesting_type_saved;
 		break;
 	    }
 
-	case '{': /*}*/
-		t = nested(TBRACE, '{', '}');
+	case ord('{' /*}*/):
+		t = nested(TBRACE, ord('{'), ord('}'), sALIAS);
 		break;
 
 	case MDPAREN:
@@ -404,14 +407,15 @@ get_command(int cf)
 		switch (token(LETEXPR)) {
 		case LWORD:
 			break;
-		case '(': /*)*/
-			c = '(';
+		case ord('(' /*)*/):
+			c = ord('(');
 			goto Subshell;
 		default:
 			syntaxerr(NULL);
 		}
 		t = newtp(TCOM);
 		t->lineno = lno;
+		XPput(args, wdcopy(builtin_cmd, ATEMP));
 		XPput(args, wdcopy(let_cmd, ATEMP));
 		XPput(args, yylval.cp);
 		break;
@@ -439,12 +443,12 @@ get_command(int cf)
 		t = newtp((c == FOR) ? TFOR : TSELECT);
 		musthave(LWORD, CMDASN);
 		if (!is_wdvarname(yylval.cp, true))
-			yyerror("%s: bad identifier\n",
+			yyerror("%s: bad identifier",
 			    c == FOR ? "for" : Tselect);
 		strdupx(t->str, ident, ATEMP);
 		nesting_push(&old_nesting, c);
-		t->vars = wordlist();
-		t->left = dogroup();
+		t->vars = wordlist(sALIAS);
+		t->left = dogroup(sALIAS);
 		nesting_pop(&old_nesting);
 		break;
 
@@ -452,8 +456,8 @@ get_command(int cf)
 	case UNTIL:
 		nesting_push(&old_nesting, c);
 		t = newtp((c == WHILE) ? TWHILE : TUNTIL);
-		t->left = c_list(true);
-		t->right = dogroup();
+		t->left = c_list(sALIAS, true);
+		t->right = dogroup(sALIAS);
 		nesting_pop(&old_nesting);
 		break;
 
@@ -462,22 +466,22 @@ get_command(int cf)
 		musthave(LWORD, 0);
 		t->str = yylval.cp;
 		nesting_push(&old_nesting, c);
-		t->left = caselist();
+		t->left = caselist(sALIAS);
 		nesting_pop(&old_nesting);
 		break;
 
 	case IF:
 		nesting_push(&old_nesting, c);
 		t = newtp(TIF);
-		t->left = c_list(true);
-		t->right = thenpart();
+		t->left = c_list(sALIAS, true);
+		t->right = thenpart(sALIAS);
 		musthave(FI, KEYWORD|sALIAS);
 		nesting_pop(&old_nesting);
 		break;
 
 	case BANG:
 		syniocf &= ~(KEYWORD|sALIAS);
-		t = pipeline(0);
+		t = pipeline(0, sALIAS);
 		if (t == NULL)
 			syntaxerr(NULL);
 		t = block(TBANG, NULL, t);
@@ -485,7 +489,7 @@ get_command(int cf)
 
 	case TIME:
 		syniocf &= ~(KEYWORD|sALIAS);
-		t = pipeline(0);
+		t = pipeline(0, sALIAS);
 		if (t && t->type == TCOM) {
 			t->str = alloc(2, ATEMP);
 			/* TF_* flags */
@@ -497,7 +501,7 @@ get_command(int cf)
 
 	case FUNCTION:
 		musthave(LWORD, 0);
-		t = function_body(yylval.cp, true);
+		t = function_body(yylval.cp, sALIAS, true);
 		break;
 	}
 
@@ -536,7 +540,7 @@ get_command(int cf)
 }
 
 static struct op *
-dogroup(void)
+dogroup(int sALIAS)
 {
 	int c;
 	struct op *list;
@@ -550,44 +554,44 @@ dogroup(void)
 	 */
 	if (c == DO)
 		c = DONE;
-	else if (c == '{')
-		c = '}';
+	else if (c == ord('{'))
+		c = ord('}');
 	else
 		syntaxerr(NULL);
-	list = c_list(true);
+	list = c_list(sALIAS, true);
 	musthave(c, KEYWORD|sALIAS);
 	return (list);
 }
 
 static struct op *
-thenpart(void)
+thenpart(int sALIAS)
 {
 	struct op *t;
 
 	musthave(THEN, KEYWORD|sALIAS);
 	t = newtp(0);
-	t->left = c_list(true);
+	t->left = c_list(sALIAS, true);
 	if (t->left == NULL)
 		syntaxerr(NULL);
-	t->right = elsepart();
+	t->right = elsepart(sALIAS);
 	return (t);
 }
 
 static struct op *
-elsepart(void)
+elsepart(int sALIAS)
 {
 	struct op *t;
 
 	switch (token(KEYWORD|sALIAS|CMDASN)) {
 	case ELSE:
-		if ((t = c_list(true)) == NULL)
+		if ((t = c_list(sALIAS, true)) == NULL)
 			syntaxerr(NULL);
 		return (t);
 
 	case ELIF:
 		t = newtp(TELIF);
-		t->left = c_list(true);
-		t->right = thenpart();
+		t->left = c_list(sALIAS, true);
+		t->right = thenpart(sALIAS);
 		return (t);
 
 	default:
@@ -597,7 +601,7 @@ elsepart(void)
 }
 
 static struct op *
-caselist(void)
+caselist(int sALIAS)
 {
 	struct op *t, *tl;
 	int c;
@@ -606,14 +610,14 @@ caselist(void)
 	/* 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;
 	/* no ALIAS here */
 	while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) {
-		struct op *tc = casepart(c);
+		struct op *tc = casepart(c, sALIAS);
 		if (tl == NULL)
 			t = tl = tc, tl->right = NULL;
 		else
@@ -624,7 +628,7 @@ caselist(void)
 }
 
 static struct op *
-casepart(int endtok)
+casepart(int endtok, int sALIAS)
 {
 	struct op *t;
 	XPtrV ptns;
@@ -632,17 +636,18 @@ casepart(int endtok)
 	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 */
@@ -654,23 +659,23 @@ casepart(int endtok)
 	REJECT;
 	XPput(ptns, NULL);
 	t->vars = (char **)XPclose(ptns);
-	musthave(')', 0);
+	musthave(ord(')'), 0);
 
-	t->left = c_list(true);
+	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 */
@@ -680,7 +685,7 @@ casepart(int endtok)
 }
 
 static struct op *
-function_body(char *name,
+function_body(char *name, int sALIAS,
     /* function foo { ... } vs foo() { .. } */
     bool ksh_func)
 {
@@ -693,11 +698,11 @@ function_body(char *name,
 	 * 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))
-			yyerror("%s: invalid function name\n", sname);
+		if (ctype(*p, C_QUOTE | C_SPC))
+			yyerror(Tinvname, sname, Tfunction);
 
 	/*
 	 * Note that POSIX allows only compound statements after foo(),
@@ -706,14 +711,14 @@ function_body(char *name,
 	 * 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;
 	}
 
@@ -722,7 +727,7 @@ function_body(char *name,
 	t->u.ksh_func = tobool(ksh_func);
 	t->lineno = source->line;
 
-	if ((t->left = get_command(CONTIN)) == NULL) {
+	if ((t->left = get_command(CONTIN, sALIAS)) == NULL) {
 		char *tv;
 		/*
 		 * Probably something like foo() followed by EOF or ';'.
@@ -747,7 +752,7 @@ function_body(char *name,
 }
 
 static char **
-wordlist(void)
+wordlist(int sALIAS)
 {
 	int c;
 	XPtrV args;
@@ -805,8 +810,8 @@ static const struct tokeninfo {
 	{ "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) */
@@ -818,7 +823,7 @@ static const struct tokeninfo {
 	{ "((",		MDPAREN, false },
 	{ "|&",		COPROC,	false },
 	/* and some special cases... */
-	{ "newline",	'\n',	false },
+	{ "newline",	ord('\n'), false },
 	{ NULL,		0,	false }
 };
 
@@ -864,7 +869,7 @@ syntaxerr(const char *what)
 			goto Again;
 		}
 		/* don't quote the EOF */
-		yyerror("%s: unexpected EOF\n", Tsynerr);
+		yyerror("%s: unexpected EOF", Tsynerr);
 		/* NOTREACHED */
 
 	case LWORD:
@@ -891,7 +896,7 @@ syntaxerr(const char *what)
 			s = redir;
 		}
 	}
-	yyerror("%s: '%s' %s\n", Tsynerr, s, what);
+	yyerror(Tf_sD_s_qs, Tsynerr, what, s);
 }
 
 static void
@@ -925,7 +930,7 @@ newtp(int type)
 }
 
 struct op *
-compile(Source *s, bool skiputf8bom)
+compile(Source *s, bool skiputf8bom, bool doalias)
 {
 	nesting.start_token = 0;
 	nesting.start_line = 0;
@@ -933,33 +938,10 @@ compile(Source *s, bool skiputf8bom)
 	source = s;
 	if (skiputf8bom)
 		yyskiputf8bom();
-	yyparse();
+	yyparse(doalias);
 	return (outtree);
 }
 
-/*-
- * This kludge exists to take care of sh/AT&T ksh oddity in which
- * the arguments of alias/export/readonly/typeset have no field
- * splitting, file globbing, or (normal) tilde expansion done.
- * AT&T ksh seems to do something similar to this since
- *	$ touch a=a; typeset a=[ab]; echo "$a"
- *	a=[ab]
- *	$ x=typeset; $x a=[ab]; echo "$a"
- *	a=a
- *	$
- */
-int
-assign_command(const char *s, bool docommand)
-{
-	if (!*s)
-		return (0);
-	return ((strcmp(s, Talias) == 0) ||
-	    (strcmp(s, Texport) == 0) ||
-	    (strcmp(s, Treadonly) == 0) ||
-	    (docommand && (strcmp(s, Tcommand) == 0)) ||
-	    (strcmp(s, Ttypeset) == 0));
-}
-
 /* Check if we are in the middle of reading an alias */
 static int
 inalias(struct source *s)
@@ -1016,9 +998,9 @@ dbtestp_isa(Test_env *te, Test_meta meta)
 		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 ||
@@ -1098,7 +1080,7 @@ parse_usec(const char *s, struct timeval *tv)
 
 	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) {
@@ -1120,14 +1102,14 @@ parse_usec(const char *s, struct timeval *tv)
 
 	/* 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;
@@ -1144,7 +1126,7 @@ parse_usec(const char *s, struct timeval *tv)
  * a COMSUB recursively using the main shell parser and lexer
  */
 char *
-yyrecursive(int subtype MKSH_A_UNUSED)
+yyrecursive(int subtype)
 {
 	struct op *t;
 	char *cp;
@@ -1152,11 +1134,11 @@ yyrecursive(int subtype MKSH_A_UNUSED)
 	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);
@@ -1172,12 +1154,10 @@ yyrecursive(int subtype MKSH_A_UNUSED)
 	memcpy(ys->old_heres, heres, sizeof(heres));
 	ys->old_herep = herep;
 	herep = heres;
-	ys->old_salias = sALIAS;
-	sALIAS = 0;
 	ys->next = e->yyrecursive_statep;
 	e->yyrecursive_statep = ys;
 	/* we use TPAREN as a helper container here */
-	t = nested(TPAREN, stok, etok);
+	t = nested(TPAREN, stok, etok, ALIAS);
 	yyrecursive_pop(false);
 
 	/* t->left because nested(TPAREN, ...) hides our goodies there */
@@ -1197,7 +1177,6 @@ yyrecursive_pop(bool popall)
 		return;
 	e->yyrecursive_statep = ys->next;
 
-	sALIAS = ys->old_salias;
 	memcpy(heres, ys->old_heres, sizeof(heres));
 	herep = ys->old_herep;
 	reject = ys->old_reject;
diff --git a/tree.c b/tree.c
index ff15077..1062feb 100644
--- a/tree.c
+++ b/tree.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2015, 2016
+ *		 2011, 2012, 2013, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -23,7 +23,7 @@
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.86 2016/07/25 00:04:48 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.93 2017/05/05 22:53:32 tg Exp $");
 
 #define INDENT	8
 
@@ -58,7 +58,7 @@ ptree(struct op *t, int indent, struct shf *shf)
 	case TCOM:
 		prevent_semicolon = false;
 		/* special-case 'var=<<EOF' (cf. exec.c:execute) */
-		if (
+		if (t->args &&
 		    /* we have zero arguments, i.e. no program to run */
 		    t->args[0] == NULL &&
 		    /* we have exactly one variable assignment */
@@ -86,6 +86,15 @@ ptree(struct op *t, int indent, struct shf *shf)
 			shf_puts("#no-vars# ", shf);
 		if (t->args) {
 			w = t->args;
+			if (*w && **w == CHAR) {
+				char *cp = wdstrip(*w++, WDS_TPUTS);
+
+				if (valid_alias_name(cp))
+					shf_putc('\\', shf);
+				shf_puts(cp, shf);
+				shf_putc(' ', shf);
+				afree(cp, ATEMP);
+			}
 			while (*w)
 				fptreef(shf, indent, Tf_S_, *w++);
 		} else
@@ -320,52 +329,56 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
 		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);
 			break;
+		case COMASUB:
 		case COMSUB:
 			shf_puts("$(", shf);
 			cs = ")";
+			if (ord(*wp) == ord('(' /*)*/))
+				shf_putc(' ', shf);
  pSUB:
 			while ((c = *wp++) != 0)
 				shf_putc(c, shf);
 			shf_puts(cs, shf);
 			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);
@@ -390,14 +403,14 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
 			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);
 			}
@@ -407,10 +420,11 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
 			shf_putc('(', shf);
 			break;
 		case SPAT:
-			c = '|';
+			c = ord('|');
 			if (0)
+				/* FALLTHROUGH */
 		case CPAT:
-				c = /*(*/ ')';
+			  c = ord(/*(*/ ')');
 			shf_putc(c, shf);
 			break;
 		}
@@ -453,39 +467,39 @@ vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
 {
 	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 {
@@ -501,7 +515,7 @@ vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
 						shf_putc(' ', shf);
 				}
 				break;
-			case 'R':
+			case ord('R'):
 				/* I/O redirection */
 				pioact(shf, va_arg(va, struct ioword *));
 				break;
@@ -599,14 +613,16 @@ wdscan(const char *wp, int c)
 		case ADELIM:
 			if (c == ADELIM && nest == 0)
 				return (wp + 1);
-			if (*wp == /*{*/'}')
+			if (ord(*wp) == ord(/*{*/ '}'))
 				goto wdscan_csubst;
 			/* FALLTHROUGH */
 		case CHAR:
 		case QCHAR:
 			wp++;
 			break;
+		case COMASUB:
 		case COMSUB:
+		case FUNASUB:
 		case FUNSUB:
 		case VALSUB:
 		case EXPRSUB:
@@ -779,20 +795,20 @@ vistree(char *dst, size_t sz, struct op *t)
 			*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;
@@ -806,10 +822,10 @@ vistree(char *dst, size_t sz, struct op *t)
 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);
 }
@@ -826,24 +842,28 @@ dumpwdvar_i(struct shf *shf, const char *wp, int quotelevel)
 			shf_puts("EOS", shf);
 			return (--wp);
 		case ADELIM:
-			if (*wp == /*{*/'}') {
+			if (ord(*wp) == ord(/*{*/ '}')) {
 				shf_puts(/*{*/ "]ADELIM(})", shf);
 				return (wp + 1);
 			}
 			shf_puts("ADELIM=", shf);
 			if (0)
+				/* FALLTHROUGH */
 		case CHAR:
-				shf_puts("CHAR=", shf);
+			  shf_puts("CHAR=", shf);
 			dumpchar(shf, *wp++);
 			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;
+		case COMASUB:
+			shf_puts("COMASUB<", shf);
+			goto dumpsub;
 		case COMSUB:
 			shf_puts("COMSUB<", shf);
  dumpsub:
@@ -852,6 +872,9 @@ dumpwdvar_i(struct shf *shf, const char *wp, int quotelevel)
  closeandout:
 			shf_putc('>', shf);
 			break;
+		case FUNASUB:
+			shf_puts("FUNASUB<", shf);
+			goto dumpsub;
 		case FUNSUB:
 			shf_puts("FUNSUB<", shf);
 			goto dumpsub;
diff --git a/var.c b/var.c
index bcde2f4..ceabba5 100644
--- a/var.c
+++ b/var.c
@@ -2,7 +2,7 @@
 
 /*-
  * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
- *		 2011, 2012, 2013, 2014, 2015, 2016
+ *		 2011, 2012, 2013, 2014, 2015, 2016, 2017
  *	mirabilos <m at mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
@@ -28,7 +28,7 @@
 #include <sys/sysctl.h>
 #endif
 
-__RCSID("$MirOS: src/bin/mksh/var.c,v 1.207 2016/08/01 21:38:07 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var.c,v 1.221 2017/10/13 23:34:49 tg Exp $");
 
 /*-
  * Variables
@@ -45,6 +45,9 @@ static uint32_t lcg_state = 5381, qh_state = 4711;
 /* may only be set by typeset() just before call to array_index_calc() */
 static enum namerefflag innermost_refflag = SRF_NOP;
 
+static void c_typeset_vardump(struct tbl *, uint32_t, int, int, bool, bool);
+static void c_typeset_vardump_recursive(struct block *, uint32_t, int, bool,
+    bool);
 static char *formatstr(struct tbl *, const char *);
 static void exportprep(struct tbl *, const char *);
 static int special(const char *);
@@ -133,7 +136,7 @@ initvar(void)
 	struct tbl *tp;
 
 	ktinit(APERM, &specials,
-	    /* currently 15 specials: 75% of 32 = 2^5 */
+	    /* currently 18 specials: 75% of 32 = 2^5 */
 	    5);
 	while (i < V_MAX - 1) {
 		tp = ktenter(&specials, initvar_names[i],
@@ -180,7 +183,7 @@ array_index_calc(const char *n, bool *arrayp, uint32_t *valp)
 	*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;
 
@@ -201,7 +204,7 @@ array_index_calc(const char *n, bool *arrayp, uint32_t *valp)
 	}
 	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;
 
@@ -225,6 +228,13 @@ array_index_calc(const char *n, bool *arrayp, uint32_t *valp)
 struct tbl *
 global(const char *n)
 {
+	return (isglobal(n, true));
+}
+
+/* search for variable; if not found, return NULL or create globally */
+struct tbl *
+isglobal(const char *n, bool docreate)
+{
 	struct tbl *vp;
 	union mksh_cchack vname;
 	struct block *l = e->loc;
@@ -239,14 +249,14 @@ global(const char *n)
 	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);
@@ -291,17 +301,19 @@ global(const char *n)
 		goto out;
 	}
 	l = varsearch(e->loc, &vp, vn, h);
+	if (vp == NULL && docreate)
+		vp = ktenter(&l->vars, vn, h);
+	else
+		docreate = false;
 	if (vp != NULL) {
 		if (array)
 			vp = arraysearch(vp, val);
-		goto out;
+		if (docreate) {
+			vp->flag |= DEFINED;
+			if (special(vn))
+				vp->flag |= SPECIAL;
+		}
 	}
-	vp = ktenter(&l->vars, vn, h);
-	if (array)
-		vp = arraysearch(vp, val);
-	vp->flag |= DEFINED;
-	if (special(vn))
-		vp->flag |= SPECIAL;
  out:
 	last_lookup_was_array = array;
 	if (vn != n)
@@ -327,7 +339,7 @@ local(const char *n, bool copy)
 	 */
 	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;
@@ -402,9 +414,11 @@ str_val(struct tbl *vp)
 
 			*(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';
@@ -452,7 +466,7 @@ setstr(struct tbl *vq, const char *s, int error_ok)
 #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);
@@ -520,7 +534,7 @@ getint(struct tbl *vp, mksh_ari_u *nump, bool arith)
 
 	do {
 		c = (unsigned char)*s++;
-	} while (ksh_isspace(c));
+	} while (ctype(c, C_SPACE));
 
 	switch (c) {
 	case '-':
@@ -537,7 +551,7 @@ getint(struct tbl *vp, mksh_ari_u *nump, bool arith)
 			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;
@@ -565,7 +579,7 @@ getint(struct tbl *vp, mksh_ari_u *nump, bool arith)
 					 * 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)
@@ -574,11 +588,11 @@ getint(struct tbl *vp, mksh_ari_u *nump, bool arith)
 			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);
@@ -658,7 +672,7 @@ formatstr(struct tbl *vp, const char *s)
 			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;
 			}
@@ -688,7 +702,7 @@ formatstr(struct tbl *vp, const char *s)
 			    "%.*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')
@@ -766,7 +780,7 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
 		/* 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");
@@ -784,18 +798,18 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
 			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;
@@ -808,8 +822,9 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
 		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';
 	}
 
@@ -833,7 +848,7 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
 
 			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 '$':
@@ -846,7 +861,7 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
  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
@@ -902,12 +917,12 @@ typeset(const char *var, uint32_t set, uint32_t clr, int field, int base)
 	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);
@@ -1052,10 +1067,11 @@ skip_varname(const char *s, bool aok)
 {
 	size_t alen;
 
-	if (s && ksh_isalphx(*s)) {
-		while (*++s && ksh_isalnux(*s))
-			;
-		if (aok && *s == '[' && (alen = array_ref_len(s)))
+	if (s && ctype(*s, C_ALPHX)) {
+		do {
+			++s;
+		} while (ctype(*s, C_ALNUX));
+		if (aok && ord(*s) == ord('[') && (alen = array_ref_len(s)))
 			s += alen;
 	}
 	return (s);
@@ -1067,11 +1083,11 @@ skip_wdvarname(const char *s,
     /* 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;
@@ -1082,9 +1098,9 @@ skip_wdvarname(const char *s,
 					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;
 				}
@@ -1149,6 +1165,13 @@ makenv(void)
 					/* setstr can't fail here */
 					setstr(vp, val, KSH_RETURN_ERROR);
 				}
+#ifdef __OS2__
+				/* these special variables are not exported */
+				if (!strcmp(vp->name, "BEGINLIBPATH") ||
+				    !strcmp(vp->name, "ENDLIBPATH") ||
+				    !strcmp(vp->name, "LIBPATHSTRICT"))
+					continue;
+#endif
 				XPput(denv, vp->val.s);
 			}
 		if (l->flags & BF_STOPENV)
@@ -1271,17 +1294,43 @@ setspec(struct tbl *vp)
 {
 	mksh_ari_u num;
 	char *s;
-	int st;
+	int st = special(vp->name);
 
-	switch ((st = special(vp->name))) {
+#ifdef MKSH_DOSPATH
+	switch (st) {
+	case V_PATH:
+	case V_TMPDIR:
+#ifdef __OS2__
+	case V_BEGINLIBPATH:
+	case V_ENDLIBPATH:
+#endif
+		/* convert backslashes to slashes for convenience */
+		if (!(vp->flag&INTEGER)) {
+			s = str_val(vp);
+			do {
+				if (*s == ord('\\'))
+					*s = '/';
+			} while (*s++);
+		}
+		break;
+	}
+#endif
+
+	switch (st) {
+#ifdef __OS2__
+	case V_BEGINLIBPATH:
+	case V_ENDLIBPATH:
+	case V_LIBPATHSTRICT:
+		setextlibpath(vp->name, str_val(vp));
+		return;
+#endif
 #if HAVE_PERSISTENT_HISTORY
 	case V_HISTFILE:
 		sethistfile(str_val(vp));
 		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);
@@ -1332,7 +1381,7 @@ setspec(struct tbl *vp)
 		if (getint(vp, &num, false) == -1) {
 			s = str_val(vp);
 			if (st != V_RANDOM)
-				errorf(Tf_sD_sD_s, vp->name, "bad number", s);
+				errorf(Tf_sD_sD_s, vp->name, Tbadnum, s);
 			num.u = hash(s);
 		}
 		vp->flag |= SPECIAL;
@@ -1396,14 +1445,20 @@ unsetspec(struct tbl *vp)
 	 */
 
 	switch (special(vp->name)) {
+#ifdef __OS2__
+	case V_BEGINLIBPATH:
+	case V_ENDLIBPATH:
+	case V_LIBPATHSTRICT:
+		setextlibpath(vp->name, "");
+		return;
+#endif
 #if HAVE_PERSISTENT_HISTORY
 	case V_HISTFILE:
 		sethistfile(NULL);
 		return;
 #endif
 	case V_IFS:
-		setctypes(TC_IFSWS, C_IFS);
-		ifs0 = ' ';
+		set_ifs(TC_IFSWS);
 		break;
 	case V_PATH:
 		afree(path, APERM);
@@ -1493,8 +1548,8 @@ array_ref_len(const char *cp)
 	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);
@@ -1566,17 +1621,18 @@ set_array(const char *var, bool reset, const char **vals)
 	}
 	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,
@@ -1686,10 +1742,8 @@ rndset(unsigned long v)
 		short r;
 	} z;
 
-#ifdef DEBUG
-	/* clear the allocated space, for valgrind */
+	/* clear the allocated space, for valgrind and to avoid UB */
 	memset(&z, 0, sizeof(z));
-#endif
 
 	h = lcg_state;
 	BAFHFinish_reg(h);
@@ -1751,3 +1805,381 @@ record_match(const char *istr)
 	vp->flag = DEFINED | RDONLY;
 	setstr(vp, istr, 0x4);
 }
+
+/* typeset, global(deprecated), export, and readonly */
+int
+c_typeset(const char **wp)
+{
+	struct tbl *vp, **p;
+	uint32_t fset = 0, fclr = 0, flag;
+	int thing = 0, field = 0, base = 0, i;
+	struct block *l;
+	const char *opts;
+	const char *fieldstr = NULL, *basestr = NULL;
+	bool localv = false, func = false, pflag = false, istset = true;
+	enum namerefflag new_refflag = SRF_NOP;
+
+	switch (**wp) {
+
+	/* export */
+	case 'e':
+		fset |= EXPORT;
+		istset = false;
+		break;
+
+	/* readonly */
+	case 'r':
+		fset |= RDONLY;
+		istset = false;
+		break;
+
+	/* set */
+	case 's':
+		/* called with 'typeset -' */
+		break;
+
+	/* typeset */
+	case 't':
+		localv = true;
+		break;
+	}
+
+	/* see comment below regarding possible opions */
+	opts = istset ? "L#R#UZ#afgi#lnprtux" : "p";
+
+	builtin_opt.flags |= GF_PLUSOPT;
+	/*
+	 * AT&T ksh seems to have 0-9 as options which are multiplied
+	 * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
+	 * sets right justify in a field of 12). This allows options
+	 * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
+	 * does not allow the number to be specified as a separate argument
+	 * Here, the number must follow the RLZi option, but is optional
+	 * (see the # kludge in ksh_getopt()).
+	 */
+	while ((i = ksh_getopt(wp, &builtin_opt, opts)) != -1) {
+		flag = 0;
+		switch (i) {
+		case 'L':
+			flag = LJUST;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'R':
+			flag = RJUST;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'U':
+			/*
+			 * AT&T ksh uses u, but this conflicts with
+			 * upper/lower case. If this option is changed,
+			 * need to change the -U below as well
+			 */
+			flag = INT_U;
+			break;
+		case 'Z':
+			flag = ZEROFIL;
+			fieldstr = builtin_opt.optarg;
+			break;
+		case 'a':
+			/*
+			 * this is supposed to set (-a) or unset (+a) the
+			 * indexed array attribute; it does nothing on an
+			 * existing regular string or indexed array though
+			 */
+			break;
+		case 'f':
+			func = true;
+			break;
+		case 'g':
+			localv = (builtin_opt.info & GI_PLUS) ? true : false;
+			break;
+		case 'i':
+			flag = INTEGER;
+			basestr = builtin_opt.optarg;
+			break;
+		case 'l':
+			flag = LCASEV;
+			break;
+		case 'n':
+			new_refflag = (builtin_opt.info & GI_PLUS) ?
+			    SRF_DISABLE : SRF_ENABLE;
+			break;
+		/* export, readonly: POSIX -p flag */
+		case 'p':
+			/* typeset: show values as well */
+			pflag = true;
+			if (istset)
+				continue;
+			break;
+		case 'r':
+			flag = RDONLY;
+			break;
+		case 't':
+			flag = TRACE;
+			break;
+		case 'u':
+			/* upper case / autoload */
+			flag = UCASEV_AL;
+			break;
+		case 'x':
+			flag = EXPORT;
+			break;
+		case '?':
+			return (1);
+		}
+		if (builtin_opt.info & GI_PLUS) {
+			fclr |= flag;
+			fset &= ~flag;
+			thing = '+';
+		} else {
+			fset |= flag;
+			fclr &= ~flag;
+			thing = '-';
+		}
+	}
+
+	if (fieldstr && !getn(fieldstr, &field)) {
+		bi_errorf(Tf_sD_s, Tbadnum, fieldstr);
+		return (1);
+	}
+	if (basestr) {
+		if (!getn(basestr, &base)) {
+			bi_errorf(Tf_sD_s, "bad integer base", basestr);
+			return (1);
+		}
+		if (base < 1 || base > 36)
+			base = 10;
+	}
+
+	if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
+	    (wp[builtin_opt.optind][0] == '-' ||
+	    wp[builtin_opt.optind][0] == '+') &&
+	    wp[builtin_opt.optind][1] == '\0') {
+		thing = wp[builtin_opt.optind][0];
+		builtin_opt.optind++;
+	}
+
+	if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) ||
+	    new_refflag != SRF_NOP)) {
+		bi_errorf("only -t, -u and -x options may be used with -f");
+		return (1);
+	}
+	if (wp[builtin_opt.optind]) {
+		/*
+		 * Take care of exclusions.
+		 * At this point, flags in fset are cleared in fclr and vice
+		 * versa. This property should be preserved.
+		 */
+		if (fset & LCASEV)
+			/* LCASEV has priority over UCASEV_AL */
+			fset &= ~UCASEV_AL;
+		if (fset & LJUST)
+			/* LJUST has priority over RJUST */
+			fset &= ~RJUST;
+		if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) {
+			/* -Z implies -ZR */
+			fset |= RJUST;
+			fclr &= ~RJUST;
+		}
+		/*
+		 * Setting these attributes clears the others, unless they
+		 * are also set in this command
+		 */
+		if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
+		    INTEGER | INT_U | INT_L)) || new_refflag != SRF_NOP)
+			fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
+			    LCASEV | INTEGER | INT_U | INT_L);
+	}
+	if (new_refflag != SRF_NOP) {
+		fclr &= ~(ARRAY | ASSOC);
+		fset &= ~(ARRAY | ASSOC);
+		fclr |= EXPORT;
+		fset |= ASSOC;
+		if (new_refflag == SRF_DISABLE)
+			fclr |= ASSOC;
+	}
+
+	/* set variables and attributes */
+	if (wp[builtin_opt.optind] &&
+	    /* not "typeset -p varname" */
+	    !(!func && pflag && !(fset | fclr))) {
+		int rv = 0;
+		struct tbl *f;
+
+		if (localv && !func)
+			fset |= LOCAL;
+		for (i = builtin_opt.optind; wp[i]; i++) {
+			if (func) {
+				f = findfunc(wp[i], hash(wp[i]),
+				    tobool(fset & UCASEV_AL));
+				if (!f) {
+					/* AT&T ksh does ++rv: bogus */
+					rv = 1;
+					continue;
+				}
+				if (fset | fclr) {
+					f->flag |= fset;
+					f->flag &= ~fclr;
+				} else {
+					fpFUNCTf(shl_stdout, 0,
+					    tobool(f->flag & FKSH),
+					    wp[i], f->val.t);
+					shf_putc('\n', shl_stdout);
+				}
+			} else if (!typeset(wp[i], fset, fclr, field, base)) {
+				bi_errorf(Tf_sD_s, wp[i], Tnot_ident);
+				return (1);
+			}
+		}
+		return (rv);
+	}
+
+	/* list variables and attributes */
+
+	/* no difference at this point.. */
+	flag = fset | fclr;
+	if (func) {
+		for (l = e->loc; l; l = l->next) {
+			for (p = ktsort(&l->funs); (vp = *p++); ) {
+				if (flag && (vp->flag & flag) == 0)
+					continue;
+				if (thing == '-')
+					fpFUNCTf(shl_stdout, 0,
+					    tobool(vp->flag & FKSH),
+					    vp->name, vp->val.t);
+				else
+					shf_puts(vp->name, shl_stdout);
+				shf_putc('\n', shl_stdout);
+			}
+		}
+	} else if (wp[builtin_opt.optind]) {
+		for (i = builtin_opt.optind; wp[i]; i++) {
+			vp = isglobal(wp[i], false);
+			c_typeset_vardump(vp, flag, thing,
+			    last_lookup_was_array ? 4 : 0, pflag, istset);
+		}
+	} else
+		c_typeset_vardump_recursive(e->loc, flag, thing, pflag, istset);
+	return (0);
+}
+
+static void
+c_typeset_vardump_recursive(struct block *l, uint32_t flag, int thing,
+    bool pflag, bool istset)
+{
+	struct tbl **blockvars, *vp;
+
+	if (l->next)
+		c_typeset_vardump_recursive(l->next, flag, thing, pflag, istset);
+	blockvars = ktsort(&l->vars);
+	while ((vp = *blockvars++))
+		c_typeset_vardump(vp, flag, thing, 0, pflag, istset);
+	/*XXX doesn’t this leak? */
+}
+
+static void
+c_typeset_vardump(struct tbl *vp, uint32_t flag, int thing, int any_set,
+    bool pflag, bool istset)
+{
+	struct tbl *tvp;
+	char *s;
+
+	if (!vp)
+		return;
+
+	/*
+	 * See if the parameter is set (for arrays, if any
+	 * element is set).
+	 */
+	for (tvp = vp; tvp; tvp = tvp->u.array)
+		if (tvp->flag & ISSET) {
+			any_set |= 1;
+			break;
+		}
+
+	/*
+	 * Check attributes - note that all array elements
+	 * have (should have?) the same attributes, so checking
+	 * the first is sufficient.
+	 *
+	 * Report an unset param only if the user has
+	 * explicitly given it some attribute (like export);
+	 * otherwise, after "echo $FOO", we would report FOO...
+	 */
+	if (!any_set && !(vp->flag & USERATTRIB))
+		return;
+	if (flag && (vp->flag & flag) == 0)
+		return;
+	if (!(vp->flag & ARRAY))
+		/* optimise later conditionals */
+		any_set = 0;
+	do {
+		/*
+		 * Ignore array elements that aren't set unless there
+		 * are no set elements, in which case the first is
+		 * reported on
+		 */
+		if (any_set && !(vp->flag & ISSET))
+			continue;
+		/* no arguments */
+		if (!thing && !flag) {
+			if (any_set == 1) {
+				shprintf(Tf_s_s_sN, Tset, "-A", vp->name);
+				any_set = 2;
+			}
+			/*
+			 * AT&T ksh prints things like export, integer,
+			 * leftadj, zerofill, etc., but POSIX says must
+			 * be suitable for re-entry...
+			 */
+			shprintf(Tf_s_s, Ttypeset, "");
+			if (((vp->flag & (ARRAY | ASSOC)) == ASSOC))
+				shprintf(Tf__c_, 'n');
+			if ((vp->flag & INTEGER))
+				shprintf(Tf__c_, 'i');
+			if ((vp->flag & EXPORT))
+				shprintf(Tf__c_, 'x');
+			if ((vp->flag & RDONLY))
+				shprintf(Tf__c_, 'r');
+			if ((vp->flag & TRACE))
+				shprintf(Tf__c_, 't');
+			if ((vp->flag & LJUST))
+				shprintf("-L%d ", vp->u2.field);
+			if ((vp->flag & RJUST))
+				shprintf("-R%d ", vp->u2.field);
+			if ((vp->flag & ZEROFIL))
+				shprintf(Tf__c_, 'Z');
+			if ((vp->flag & LCASEV))
+				shprintf(Tf__c_, 'l');
+			if ((vp->flag & UCASEV_AL))
+				shprintf(Tf__c_, 'u');
+			if ((vp->flag & INT_U))
+				shprintf(Tf__c_, 'U');
+		} else if (pflag) {
+			shprintf(Tf_s_s, istset ? Ttypeset :
+			    (flag & EXPORT) ? Texport : Treadonly, "");
+		}
+		if (any_set)
+			shprintf("%s[%lu]", vp->name, arrayindex(vp));
+		else
+			shf_puts(vp->name, shl_stdout);
+		if ((!thing && !flag && pflag) ||
+		    (thing == '-' && (vp->flag & ISSET))) {
+			s = str_val(vp);
+			shf_putc('=', shl_stdout);
+			/* AT&T ksh can't have justified integers... */
+			if ((vp->flag & (INTEGER | LJUST | RJUST)) == INTEGER)
+				shf_puts(s, shl_stdout);
+			else
+				print_value_quoted(shl_stdout, s);
+		}
+		shf_putc('\n', shl_stdout);
+
+		/*
+		 * Only report first 'element' of an array with
+		 * no set elements.
+		 */
+		if (!any_set)
+			return;
+	} while (!(any_set & 4) && (vp = vp->u.array));
+}
diff --git a/var_spec.h b/var_spec.h
index 51cda3e..45fa0eb 100644
--- a/var_spec.h
+++ b/var_spec.h
@@ -19,7 +19,7 @@
  */
 
 #if defined(VARSPEC_DEFNS)
-__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.9 2016/07/25 21:02:13 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.10 2016/11/11 23:31:39 tg Exp $");
 #define FN(name)			/* nothing */
 #elif defined(VARSPEC_ENUMS)
 #define FN(name)			V_##name,
@@ -40,13 +40,22 @@ F0(NONE)
 
 /* 1 and up are special variables */
 FN(BASHPID)
+#ifdef __OS2__
+FN(BEGINLIBPATH)
+#endif
 FN(COLUMNS)
+#ifdef __OS2__
+FN(ENDLIBPATH)
+#endif
 FN(EPOCHREALTIME)
 #if HAVE_PERSISTENT_HISTORY
 FN(HISTFILE)
 #endif
 FN(HISTSIZE)
 FN(IFS)
+#ifdef __OS2__
+FN(LIBPATHSTRICT)
+#endif
 FN(LINENO)
 FN(LINES)
 FN(OPTIND)


hooks/post-receive
-- 
Supplemental git repository wtf-mksh for Evolvis project useful-scripts
(Evolvis project useful-scripts repository wtf-mksh)


More information about the useful-scripts-commits mailing list