From dfc587b61fc6b5783fd0986ad089ef44accc8506 Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Sun, 29 Oct 2017 10:53:19 -0400
Subject: [PATCH] Statement Splitter Optimization

---
 Cargo.lock                        | 134 +++++++++++++++++---------
 Cargo.toml                        |   4 +-
 src/parser/assignments/actions.rs |   6 +-
 src/parser/pipelines/collector.rs |  10 +-
 src/parser/statement/splitter.rs  | 151 +++++++++++++++---------------
 src/shell/variables/mod.rs        |  21 ++---
 6 files changed, 190 insertions(+), 136 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 48504811..9b292005 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,34 +1,9 @@
-[root]
-name = "ion-shell"
-version = "1.0.5"
-dependencies = [
- "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "app_dirs 1.1.1 (git+https://github.com/redox-os/app-dirs-rs.git)",
- "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "calculate 0.3.0 (git+https://github.com/redox-os/calc.git)",
- "errno 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
- "libloading 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "liner 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "users 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "aho-corasick"
 version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -47,6 +22,17 @@ dependencies = [
  "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "atty"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "bitflags"
 version = "0.9.1"
@@ -64,10 +50,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "calculate"
-version = "0.3.0"
-source = "git+https://github.com/redox-os/calc.git#9e1ddfe012d488f7a0659e9d77e011f49b184ea8"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
+ "clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "decimal 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "liner 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "clap"
+version = "2.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -77,10 +79,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "ord_subset 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -89,7 +91,7 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -108,6 +110,31 @@ name = "glob"
 version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "ion-shell"
+version = "1.0.5"
+dependencies = [
+ "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_dirs 1.1.1 (git+https://github.com/redox-os/app-dirs-rs.git)",
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "calculate 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "errno 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libloading 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "liner 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "users 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "kernel32-sys"
 version = "0.2.2"
@@ -124,7 +151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "libc"
-version = "0.2.32"
+version = "0.2.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -149,10 +176,10 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -193,7 +220,7 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -211,7 +238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "serde"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -241,16 +268,29 @@ name = "smallvec"
 version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "strsim"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "termion"
 version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
  "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "textwrap"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "thread_local"
 version = "0.3.4"
@@ -283,7 +323,7 @@ name = "users"
 version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -291,6 +331,11 @@ name = "utf8-ranges"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "vec_map"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "version_check"
 version = "0.1.3"
@@ -320,10 +365,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
 "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
 "checksum app_dirs 1.1.1 (git+https://github.com/redox-os/app-dirs-rs.git)" = "<none>"
+"checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860"
 "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
 "checksum bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5cde24d1b2e2216a726368b2363a273739c91f4e3eb4e0dd12d672d396ad989"
 "checksum bytecount 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4bbeb7c30341fce29f6078b4bdf876ea4779600866e98f5b2d203a534f195050"
-"checksum calculate 0.3.0 (git+https://github.com/redox-os/calc.git)" = "<none>"
+"checksum calculate 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5df70b5da660f26e3bd2c59160178ddd6afbbc58221f8fcbc5f6f88d17afb080"
+"checksum clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180"
 "checksum decimal 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8e3bfe070d7876527c8f901afc27b4190a715b6e52c8961f72fb35430960e80"
 "checksum errno 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2c858c42ac0b88532f48fca88b0ed947cad4f1f64d904bcd6c9f138f7b95d70"
 "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
@@ -331,10 +378,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
 "checksum lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9e5e58fa1a4c3b915a561a78a22ee0cac6ab97dca2504428bc1cb074375f8d5"
-"checksum libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "56cce3130fd040c28df6f495c8492e5ec5808fb4c9093c310df02b0c8f030148"
+"checksum libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "5ba3df4dcb460b9dfbd070d41c94c19209620c191b0340b929ce748a2bcd42d2"
 "checksum libloading 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3f92926a9a4ba7aeeb01f5fba3f0d577147243b6e7fa8261c219cd1d6fbe3b1c"
 "checksum liner 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aff4455f523eb06630ad00ea139701ba51a5425d0a8163935e7bb1841c81e6e8"
-"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
+"checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
 "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
 "checksum ord_subset 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb8b8c275824bc609c8294f20a55d37d808a1b071cec596da746ce4df0405e8"
 "checksum permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53b7d5b19a715ffab38693a9dd44b067fdfa2b18eef65bd93562dfe507022fae"
@@ -343,18 +390,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
 "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
 "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
-"checksum serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "6a7046c9d4c6c522d10b2d098f9bebe2bef227e0e74044d8c1bfcf6b476af799"
+"checksum serde 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "e11a631f964d4e6572712ea12075fb1d65eeef42b0058884195b430ac1e26809"
 "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
 "checksum smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30950abdb5b38f56a0e181ae56ed64a539b64fa77ea6325147203dc7faeb087f"
 "checksum smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4f8266519bc1d17d0b5b16f6c21295625d562841c708f6376f49028a43e9c11e"
 "checksum smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4f357e8cd37bf8822e1b964e96fd39e2cb5a0424f8aaa284ccaccc2162411c"
+"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
 "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
+"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
 "checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14"
 "checksum unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946"
 "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
 "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
 "checksum users 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e7d8fb16f17ce0e6a18a25ce39f08edb5fbe9a25f3f346c9dca5e6ffc0485cdf"
 "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
+"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
 "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
 "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
diff --git a/Cargo.toml b/Cargo.toml
index 87c359c4..774fb010 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,6 +25,7 @@ version_check = "0.1.3"
 
 [dependencies]
 bitflags = "0.9.1"
+calculate = "0.5"
 fnv = "1.0"
 glob = "0.2"
 lazy_static = "0.2"
@@ -38,9 +39,6 @@ unicode-segmentation = "1.2"
 [dependencies.app_dirs]
 git = "https://github.com/redox-os/app-dirs-rs.git"
 
-[dependencies.calculate]
-git = "https://github.com/redox-os/calc.git"
-
 [profile.release]
 panic = "abort"
 
diff --git a/src/parser/assignments/actions.rs b/src/parser/assignments/actions.rs
index 3793ab79..5c0f04c0 100644
--- a/src/parser/assignments/actions.rs
+++ b/src/parser/assignments/actions.rs
@@ -165,8 +165,7 @@ mod tests {
         );
 
         let (keys, op, vals) = split("a b[] c:int[] = one [two three] [4 5 6]");
-        let actions = AssignmentActions::new(&keys, op, &vals)
-            .collect::<Vec<_>>();
+        let actions = AssignmentActions::new(&keys, op, &vals).collect::<Vec<_>>();
         assert_eq!(actions.len(), 3);
         assert_eq!(
             actions[0],
@@ -203,8 +202,7 @@ mod tests {
         );
 
         let (keys, op, values) = split("a[] b c[] = [one two] three [four five]");
-        let actions = AssignmentActions::new(&keys, op, &values)
-            .collect::<Vec<_>>();
+        let actions = AssignmentActions::new(&keys, op, &values).collect::<Vec<_>>();
         assert_eq!(actions.len(), 3);
         assert_eq!(
             actions[0],
diff --git a/src/parser/pipelines/collector.rs b/src/parser/pipelines/collector.rs
index c5a647e7..3df924bd 100644
--- a/src/parser/pipelines/collector.rs
+++ b/src/parser/pipelines/collector.rs
@@ -345,9 +345,13 @@ impl<'a> Collector<'a> {
                             };
                             let heredoc = heredoc.lines().collect::<Vec<&str>>();
                             // Then collect the heredoc from standard input.
-                            inputs.as_mut().map(|x| {
-                                x.push(Input::HereString(heredoc[1..heredoc.len() - 1].join("\n")))
-                            });
+                            inputs.as_mut().map(
+                                |x| {
+                                    x.push(
+                                        Input::HereString(heredoc[1..heredoc.len() - 1].join("\n")),
+                                    )
+                                },
+                            );
                         }
                     } else if let Some(file) = self.arg(&mut bytes)? {
                         // Otherwise interpret it as stdin redirection
diff --git a/src/parser/statement/splitter.rs b/src/parser/statement/splitter.rs
index 6cf29e7a..4baac1ce 100644
--- a/src/parser/statement/splitter.rs
+++ b/src/parser/statement/splitter.rs
@@ -7,18 +7,16 @@ use std::u16;
 
 bitflags! {
     pub struct Flags : u16 {
-        const SQUOTE = 1;
-        const DQUOTE = 2;
-        const BACKSL = 4;
-        const COMM_1 = 8;
-        const COMM_2 = 16;
-        const VBRACE = 32;
-        const ARRAY  = 64;
-        const VARIAB = 128;
-        const METHOD = 256;
+        const DQUOTE = 1;
+        const COMM_1 = 2;
+        const COMM_2 = 4;
+        const VBRACE = 8;
+        const ARRAY  = 16;
+        const VARIAB = 32;
+        const METHOD = 64;
         /// Set while parsing through an inline arithmetic expression, e.g. $((foo * bar / baz))
-        const MATHEXPR = 512;
-        const POST_MATHEXPR = 1024;
+        const MATHEXPR = 128;
+        const POST_MATHEXPR = 256;
     }
 }
 
@@ -65,29 +63,49 @@ impl<'a> Display for StatementError<'a> {
     }
 }
 
+/// Returns true if the byte matches [^A-Za-z0-9_]
+fn is_invalid(byte: u8) -> bool {
+    byte <= 47 || (byte >= 58 && byte <= 64) || (byte >= 91 && byte <= 94) || byte == 96
+        || (byte >= 123 && byte <= 127)
+}
+
 pub(crate) struct StatementSplitter<'a> {
-    data:                &'a str,
-    read:                usize,
-    flags:               Flags,
-    array_level:         u8,
-    array_process_level: u8,
-    process_level:       u8,
-    brace_level:         u8,
-    math_paren_level:    i8,
+    data:             &'a str,
+    read:             usize,
+    flags:            Flags,
+    a_level:          u8,
+    ap_level:         u8,
+    p_level:          u8,
+    brace_level:      u8,
+    math_paren_level: i8,
 }
 
 impl<'a> StatementSplitter<'a> {
     pub(crate) fn new(data: &'a str) -> StatementSplitter<'a> {
         StatementSplitter {
-            data:                data,
-            read:                0,
-            flags:               Flags::empty(),
-            array_level:         0,
-            array_process_level: 0,
-            process_level:       0,
-            brace_level:         0,
-            math_paren_level:    0,
+            data:             data,
+            read:             0,
+            flags:            Flags::empty(),
+            a_level:          0,
+            ap_level:         0,
+            p_level:          0,
+            brace_level:      0,
+            math_paren_level: 0,
+        }
+    }
+
+    fn single_quote<B: Iterator<Item = u8>>(&mut self, bytes: &mut B) -> usize {
+        let mut read = 0;
+        while let Some(character) = bytes.next() {
+            read += 1;
+            if character == b'\\' {
+                read += 1;
+                bytes.next();
+            } else if character == b'\'' {
+                break;
+            }
         }
+        read
     }
 }
 
@@ -99,9 +117,14 @@ impl<'a> Iterator for StatementSplitter<'a> {
         let mut else_found = false;
         let mut else_pos = 0;
         let mut error = None;
-        for character in self.data.bytes().skip(self.read) {
+        let mut bytes = self.data.bytes().skip(self.read);
+        while let Some(character) = bytes.next() {
             self.read += 1;
             match character {
+                b'\\' => {
+                    self.read += 1;
+                    bytes.next();
+                }
                 _ if self.flags.contains(POST_MATHEXPR) => {
                     self.flags -= POST_MATHEXPR;
                 }
@@ -114,30 +137,25 @@ impl<'a> Iterator for StatementSplitter<'a> {
                         error = Some(StatementError::InvalidCharacter(character as char, self.read))
                     }
                 }
-                _ if self.flags.contains(BACKSL) => self.flags.toggle(BACKSL),
-                b'\\' => self.flags.toggle(BACKSL),
                 b'\'' if !self.flags.contains(DQUOTE) => {
-                    self.flags.toggle(SQUOTE);
-                    self.flags -= VARIAB | ARRAY;
-                }
-                b'"' if !self.flags.contains(SQUOTE) => {
-                    self.flags.toggle(DQUOTE);
                     self.flags -= VARIAB | ARRAY;
+                    self.read += self.single_quote(&mut bytes);
                 }
-                b'@' if !self.flags.contains(SQUOTE) => {
-                    self.flags -= COMM_1;
-                    self.flags |= COMM_2 | ARRAY;
+                // Toggle DQUOTE and disable VARIAB + ARRAY.
+                b'"' => self.flags = (self.flags ^ DQUOTE) - (VARIAB | ARRAY),
+                // Disable COMM_1 and enable COMM_2 + ARRAY.
+                b'@' => {
+                    self.flags = (self.flags - COMM_1) | (COMM_2 | ARRAY);
                     continue;
                 }
-                b'$' if !self.flags.contains(SQUOTE) => {
-                    self.flags -= COMM_2;
-                    self.flags |= COMM_1 | VARIAB;
+                b'$' => {
+                    self.flags = (self.flags - COMM_2) | (COMM_1 | VARIAB);
                     continue;
                 }
                 b'{' if self.flags.intersects(COMM_1 | COMM_2) => self.flags |= VBRACE,
-                b'{' if !self.flags.intersects(SQUOTE | DQUOTE) => self.brace_level += 1,
+                b'{' if !self.flags.contains(DQUOTE) => self.brace_level += 1,
                 b'}' if self.flags.contains(VBRACE) => self.flags.toggle(VBRACE),
-                b'}' if !self.flags.intersects(SQUOTE | DQUOTE) => if self.brace_level == 0 {
+                b'}' if !self.flags.contains(DQUOTE) => if self.brace_level == 0 {
                     if error.is_none() {
                         error = Some(StatementError::InvalidCharacter(character as char, self.read))
                     }
@@ -148,27 +166,25 @@ impl<'a> Iterator for StatementSplitter<'a> {
                     self.math_paren_level += 1;
                 }
                 b'(' if !self.flags.intersects(COMM_1 | VARIAB | ARRAY) => {
-                    if error.is_none() && !self.flags.intersects(SQUOTE | DQUOTE) {
+                    if error.is_none() && !self.flags.contains(DQUOTE) {
                         error = Some(StatementError::InvalidCharacter(character as char, self.read))
                     }
                 }
                 b'(' if self.flags.intersects(COMM_1 | METHOD) => {
                     self.flags -= VARIAB | ARRAY;
                     if self.data.as_bytes()[self.read] == b'(' {
-                        self.flags -= COMM_1;
-                        self.flags |= MATHEXPR;
+                        self.flags = (self.flags - COMM_1) | MATHEXPR;
                         // The next character will always be a left paren in this branch;
                         self.math_paren_level = -1;
                     } else {
-                        self.process_level += 1;
+                        self.p_level += 1;
                     }
                 }
                 b'(' if self.flags.contains(COMM_2) => {
-                    self.array_process_level += 1;
+                    self.ap_level += 1;
                 }
                 b'(' if self.flags.intersects(VARIAB | ARRAY) => {
-                    self.flags -= VARIAB | ARRAY;
-                    self.flags |= METHOD;
+                    self.flags = (self.flags - (VARIAB | ARRAY)) | METHOD;
                 }
                 b')' if self.flags.contains(MATHEXPR) => if self.math_paren_level == 0 {
                     if self.data.as_bytes().len() <= self.read {
@@ -178,8 +194,7 @@ impl<'a> Iterator for StatementSplitter<'a> {
                     } else {
                         let next_character = self.data.as_bytes()[self.read] as char;
                         if next_character == ')' {
-                            self.flags -= MATHEXPR;
-                            self.flags |= POST_MATHEXPR;
+                            self.flags = (self.flags - MATHEXPR) | POST_MATHEXPR;
                         } else if error.is_none() {
                             error =
                                 Some(StatementError::InvalidCharacter(next_character, self.read));
@@ -188,33 +203,24 @@ impl<'a> Iterator for StatementSplitter<'a> {
                 } else {
                     self.math_paren_level -= 1;
                 },
-                b')' if !self.flags.contains(SQUOTE) && self.flags.contains(METHOD)
-                    && self.process_level == 0 =>
-                {
+                b')' if self.flags.contains(METHOD) && self.p_level == 0 => {
                     self.flags ^= METHOD;
                 }
-                b')' if self.process_level == 0 && self.array_process_level == 0
-                    && !self.flags.contains(SQUOTE) =>
-                {
-                    if error.is_none() && !self.flags.intersects(SQUOTE | DQUOTE) {
+                b')' if self.p_level + self.ap_level == 0 => {
+                    if error.is_none() && !self.flags.contains(DQUOTE) {
                         error = Some(StatementError::InvalidCharacter(character as char, self.read))
                     }
                 }
-                b')' if !self.flags.contains(SQUOTE) && self.process_level != 0 => {
-                    self.process_level -= 1
-                }
-                b')' if !self.flags.contains(SQUOTE) => self.array_process_level -= 1,
-                b';' if !self.flags.intersects(SQUOTE | DQUOTE) && self.process_level == 0
-                    && self.array_process_level == 0 =>
-                {
+                b')' if self.p_level != 0 => self.p_level -= 1,
+                b')' => self.ap_level -= 1,
+                b';' if !self.flags.contains(DQUOTE) && self.p_level == 0 && self.ap_level == 0 => {
                     return match error {
                         Some(error) => Some(Err(error)),
                         None => Some(Ok(self.data[start..self.read - 1].trim())),
                     }
                 }
                 b'#' if self.read == 1
-                    || (!self.flags.intersects(SQUOTE | DQUOTE) && self.process_level == 0
-                        && self.array_process_level == 0
+                    || (!self.flags.contains(DQUOTE) && self.p_level + self.ap_level == 0
                         && match self.data.as_bytes()[self.read - 2] {
                             b' ' | b'\t' => true,
                             _ => false,
@@ -250,8 +256,9 @@ impl<'a> Iterator for StatementSplitter<'a> {
                     }
                 }
                 // [^A-Za-z0-9_]
-                0...47 | 58...64 | 91...94 | 96 | 123...127 => self.flags -= VARIAB | ARRAY,
-                _ => (),
+                byte => if self.flags.intersects(VARIAB | ARRAY) {
+                    self.flags -= if is_invalid(byte) { VARIAB | ARRAY } else { Flags::empty() };
+                },
             }
             self.flags -= COMM_1 | COMM_2;
         }
@@ -262,9 +269,7 @@ impl<'a> Iterator for StatementSplitter<'a> {
             self.read = self.data.len();
             match error {
                 Some(error) => Some(Err(error)),
-                None if self.process_level != 0 || self.array_process_level != 0
-                    || self.array_level != 0 =>
-                {
+                None if self.p_level != 0 || self.ap_level != 0 || self.a_level != 0 => {
                     Some(Err(StatementError::UnterminatedSubshell))
                 }
                 None if self.flags.contains(METHOD) => {
diff --git a/src/shell/variables/mod.rs b/src/shell/variables/mod.rs
index 5cd382db..aca03d0a 100644
--- a/src/shell/variables/mod.rs
+++ b/src/shell/variables/mod.rs
@@ -112,21 +112,20 @@ impl Variables {
     }
 
     pub fn set_var(&mut self, name: &str, value: &str) {
-        if name == "NS_PLUGINS" {
-            match value {
-                "0" => self.disable_plugins(),
-                "1" => self.enable_plugins(),
-                _ => {
-                    eprintln!("ion: unsupported value for NS_PLUGINS. Value must be either 0 or 1.")
-                }
-            }
-            return;
-        }
-
         if !name.is_empty() {
             if value.is_empty() {
                 self.variables.remove(name);
             } else {
+                if name == "NS_PLUGINS" {
+                    match value {
+                        "0" => self.disable_plugins(),
+                        "1" => self.enable_plugins(),
+                        _ => eprintln!(
+                            "ion: unsupported value for NS_PLUGINS. Value must be either 0 or 1."
+                        ),
+                    }
+                    return;
+                }
                 self.variables.insert(name.into(), value.into());
             }
         }
-- 
GitLab