Repository: xonsh/xonsh

Issue:
and semms to be interpreted inside a path
Example:

```
jbleger@gertrude ~ $ mkdir -p a/path/and/another/one                                                                           
xonsh: For full traceback set: $XONSH_SHOW_TRACEBACK = True
xonsh: subprocess mode: command not found: /another/one
```

## xonfig

```
jbleger@gertrude ~ [1] $ xonfig                                                                                                
+------------------+---------------------+
| xonsh            | 0.8.0               |
| Git SHA          | aca294b8            |
| Commit Date      | Oct 7 09:53:16 2018 |
| Python           | 3.6.7               |
| PLY              | 3.9                 |
| have readline    | True                |
| prompt toolkit   | 2.0.6               |
| shell type       | prompt_toolkit2     |
| pygments         | 2.2.0               |
| on posix         | True                |
| on linux         | True                |
| distro           | debian              |
| on darwin        | False               |
| on windows       | False               |
| on cygwin        | False               |
| on msys2         | False               |
| is superuser     | False               |
| default encoding | utf-8               |
| xonsh encoding   | utf-8               |
| encoding errors  | surrogateescape     |
+------------------+---------------------+
```
<!--- Please post the output of the `xonfig` command (run from inside xonsh) so we know more about your current setup -->

## Expected Behavior
<!--- Tell us what should happen -->

## Current Behavior
```
jbleger@gertrude:~$ XONSH_DEBUG=1 xonsh
jbleger@gertrude ~ $ cd /tmp/                                                                                                           
jbleger@gertrude /tmp $ mkdir -p a/path/and/another/one                                                                                 
xonsh: For full traceback set: $XONSH_SHOW_TRACEBACK = True
xonsh: subprocess mode: command not found: /another/one
jbleger@gertrude /tmp [1] $ $XONSH_SHOW_TRACEBACK=True                                                                                  
jbleger@gertrude /tmp $ mkdir -p a/path/and/another/one                                                                                 
xonsh: To log full traceback to a file set: $XONSH_TRACEBACK_LOGFILE = <filename>
Traceback (most recent call last):
  File "/home/jbleger/.local/lib/python3.6/site-packages/xonsh/built_ins.py", line 550, in _run_binary
    p = self.cls(self.cmd, bufsize=bufsize, **kwargs)
  File "/home/jbleger/.local/lib/python3.6/site-packages/xonsh/proc.py", line 582, in __init__
    *args, stdin=stdin, stdout=stdout, stderr=stderr, **kwargs
  File "/usr/lib/python3.6/subprocess.py", line 709, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.6/subprocess.py", line 1344, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: '/another/one': '/another/one'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/jbleger/.local/lib/python3.6/site-packages/xonsh/proc.py", line 1843, in __init__
    proc = spec.run(pipeline_group=pipeline_group)
  File "/home/jbleger/.local/lib/python3.6/site-packages/xonsh/built_ins.py", line 540, in run
    p = self._run_binary(kwargs)
  File "/home/jbleger/.local/lib/python3.6/site-packages/xonsh/built_ins.py", line 561, in _run_binary
    raise XonshError(e)
xonsh.tools.XonshError: xonsh: subprocess mode: command not found: /another/one
```

It is a very quick filled bug report, sorry.


Discussion:

Resolving patch:
diff --git a/xonsh/lexer.py b/xonsh/lexer.py
--- a/xonsh/lexer.py
+++ b/xonsh/lexer.py
@@ -4,6 +4,7 @@
 Written using a hybrid of ``tokenize`` and PLY.
 """
 import io
+import re
 
 # 'keyword' interferes with ast.keyword
 import keyword as kwmod
@@ -116,21 +117,33 @@ def token_map():
     return tm
 
 
+NEED_WHITESPACE = frozenset(["and", "or"])
+
+
+@lazyobject
+def RE_NEED_WHITESPACE():
+    pattern = r"\s?(" + "|".join(NEED_WHITESPACE) + r")(\s|[\\]$)"
+    return re.compile(pattern)
+
+
 def handle_name(state, token):
     """Function for handling name tokens"""
     typ = "NAME"
+    state["last"] = token
+    needs_whitespace = token.string in NEED_WHITESPACE
+    has_whitespace = needs_whitespace and RE_NEED_WHITESPACE.match(
+        token.line[max(0, token.start[1] - 1) :]
+    )
     if state["pymode"][-1][0]:
-        if token.string in kwmod.kwlist:
+        if needs_whitespace and not has_whitespace:
+            pass
+        elif token.string in kwmod.kwlist:
             typ = token.string.upper()
-        state["last"] = token
         yield _new_token(typ, token.string, token.start)
     else:
-        prev = state["last"]
-        state["last"] = token
-        has_whitespace = prev.end != token.start
-        if token.string == "and" and has_whitespace:
+        if has_whitespace and token.string == "and":
             yield _new_token("AND", token.string, token.start)
-        elif token.string == "or" and has_whitespace:
+        elif has_whitespace and token.string == "or":
             yield _new_token("OR", token.string, token.start)
         else:
             yield _new_token("NAME", token.string, token.start)


Test patch:
diff --git a/tests/sample.xsh b/tests/sample.xsh
--- a/tests/sample.xsh
+++ b/tests/sample.xsh
@@ -2,4 +2,4 @@
 aliases['echo'] = lambda args, stdin=None: print(' '.join(args))
 
 $WAKKA = "jawaka"
-x = $(echo "hello mom" $WAKKA)
\ No newline at end of file
+x = $(echo "hello mom" $WAKKA)
diff --git a/tests/test_lexer.py b/tests/test_lexer.py
--- a/tests/test_lexer.py
+++ b/tests/test_lexer.py
@@ -165,7 +165,10 @@ def test_atdollar_expression():
 
 
 def test_and():
-    assert check_token("and", ["AND", "and", 0])
+    # no preceding whitespace or other tokens, so this
+    # resolves to NAME, since it doesn't make sense for
+    # Python code to start with "and"
+    assert check_token("and", ["NAME", "and", 0])
 
 
 def test_ampersand():
diff --git a/tests/test_tools.py b/tests/test_tools.py
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -374,6 +374,29 @@ def test_subproc_toks_pyeval_nested():
     assert exp == obs
 
 
+@pytest.mark.parametrize('phrase', [
+    'xandy',
+    'xory',
+    'xand',
+    'andy',
+    'xor',
+    'ory',
+    'x-and',
+    'x-or',
+    'and-y',
+    'or-y',
+    'x-and-y',
+    'x-or-y',
+    'in/and/path',
+    'in/or/path',
+])
+def test_subproc_toks_and_or(phrase):
+    s = "echo " + phrase
+    exp = "![{0}]".format(s)
+    obs = subproc_toks(s, lexer=LEXER, returnline=True)
+    assert exp == obs
+
+
 def test_subproc_toks_pyeval_nested_parens():
     s = "echo @(min(1, 42))"
     inp = "({0})".format(s)