Repository: xonsh/xonsh

Issue:
Incorrect parsing of ~/.xonshrc when missing newline at end of file
## xonfig

<details>

```
+------------------+---------------------+
| xonsh            | 0.9.18              |
| Git SHA          | 26475146            |
| Commit Date      | May 5 06:42:28 2020 |
| Python           | 3.7.3               |
| PLY              | 3.11                |
| have readline    | True                |
| prompt toolkit   | 3.0.5               |
| shell type       | prompt_toolkit      |
| pygments         | None                |
| on posix         | True                |
| on linux         | True                |
| distro           | unknown             |
| 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     |
+------------------+---------------------+
```

</details>

## Expected Behavior

1.  I should be able to start my shell without any errors, regardless of whether I have a newline at the end of my `~/.xonshrc` or not.
2.  When there is an error, it should point to where the error is in the configuration, instead of what happened to fail because of that.

## Current Behavior - Case 1

When I load `xontrib` modules, I typically keep each of them on its own line, like so:

```
xontrib load first_module \
  second_module \
  third_module

... more configuration ...
```

However, this seems to require that the whole `.xonshrc` file has a newline at the end.

If I have a newline at the very end of the file, everything works, but if I don't, I get this error:

```
  File "<string>", line None
SyntaxError: /home/<MY USERNAME>/.xonshrc:1:8: ('code: load',)
xontrib load coreutils \
        ^
syntax error in xonsh run control file '/home/<MY USERNAME>/.xonshrc': /home/<MY USERNAME>/.xonshrc:1:8: ('code: load',)
xontrib load coreutils \
        ^
```

This is particularly confusing if you have a long `.xonshrc` file, because if you miss a newline at the end of the file, the error still points to the `xontrib load` line, which might be much further up the file.

One important thing to note here is that the following works as expected even if there is no newline at the end of the file:

```
xontrib load first_module second_module third_module

print('hello')
```

But by splitting the modules to individual lines like this causes an issue if there is no newline after the `print('hello')` statement, but causes no issue if there is a newline.

```
xontrib load \
  first_module \
  second_module \
  third_module

print('hello')
```

### Traceback - Case 1

<details>

```
Traceback (most recent call last):
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/environ.py", line 1757, in xonsh_script_run_control
    run_script_with_cache(filename, execer, ctx)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/codecache.py", line 160, in run_script_with_cache
    ccode = compile_code(filename, code, execer, glb, loc, mode)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/codecache.py", line 119, in compile_code
    ccode = execer.compile(code, glbs=glb, locs=loc, mode=mode, filename=filename)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/execer.py", line 135, in compile
    tree = self.parse(input, ctx, mode=mode, filename=filename, transform=transform)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/execer.py", line 95, in parse
    tree, input = self._parse_ctx_free(input, mode=mode, filename=filename)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/execer.py", line 238, in _parse_ctx_free
    raise original_error from None
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/execer.py", line 223, in _parse_ctx_free
    debug_level=(self.debug_level > 2),
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/parsers/base.py", line 543, in parse
    tree = self.parser.parse(input=s, lexer=self.lexer, debug=debug_level)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/ply/ply/yacc.py", line 335, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/ply/ply/yacc.py", line 1203, in parseopt_notrack
    tok = call_errorfunc(self.errorfunc, errtoken, self)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/ply/ply/yacc.py", line 194, in call_errorfunc
    r = errorfunc(token)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/parsers/base.py", line 3314, in p_error
    self._parse_error(msg, self.currloc(lineno=p.lineno, column=p.lexpos))
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/parsers/base.py", line 676, in _parse_error
    raise err
  File "<string>", line None
SyntaxError: /home/<MY USERNAME>/.xonshrc:1:8: ('code: load',)
xontrib load coreutils \
        ^
syntax error in xonsh run control file '/home/<MY USERNAME>/.xonshrc': /home/<MY USERNAME>/.xonshrc:1:8: ('code: load',)
xontrib load coreutils \
        ^
```

</details>

## Steps to Reproduce Case 1

Enter the following content into `~/.xonshc`:

```
xontrib load coreutils \
  prompt_ret_code

print('hello')
```

NOTE: Do not have an newline after the `print('hello')` statement to reproduce the error. If you do have a newline, everything works as expected.

## Current Behavior - Case 2

The following configuration also generates an error with no newline at the end:

```
xontrib load coreutils \
  prompt_ret_code
```

But that error is:

```
TypeError: expected Module node, got Expression
error running xonsh run control file '/home/<MY USERNAME>/.xonshrc': expected Module node, got Expression
```

### Traceback - Case 2

<details>

```
Traceback (most recent call last):
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/environ.py", line 1757, in xonsh_script_run_control
    run_script_with_cache(filename, execer, ctx)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/codecache.py", line 160, in run_script_with_cache
    ccode = compile_code(filename, code, execer, glb, loc, mode)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/codecache.py", line 119, in compile_code
    ccode = execer.compile(code, glbs=glb, locs=loc, mode=mode, filename=filename)
  File "/home/<MY USERNAME>/.local/lib/python3.7/site-packages/xonsh/execer.py", line 138, in compile
    code = compile(tree, filename, mode)
TypeError: expected Module node, got Expression
error running xonsh run control file '/home/<MY USERNAME>/.xonshrc': expected Module node, got Expression
```

</details>

## Steps to Reproduce Case 2

Enter the following content into `~/.xonshc`:

```
xontrib load coreutils \
  prompt_ret_code
```

NOTE: Do not have an newline after `prompt_ret_code` to reproduce the error. If you do have a newline, everything works as expected.



Discussion:
Hi @tommynyquist, thanks for reporting! I can reproduce the issue.
It is, however, very strange, because we have the following:
https://github.com/xonsh/xonsh/blob/14771a7e51441144148399ab286c81c0d5edbb3d/xonsh/codecache.py#L110-L116
I'm investigating the issue.
So no, not really. Case 1 fails for me regardless of the terminating EOL character. It is caused solely by the line continuation in the `xontrib load` command.

Resolving patch:
diff --git a/xonsh/execer.py b/xonsh/execer.py
--- a/xonsh/execer.py
+++ b/xonsh/execer.py
@@ -242,6 +242,8 @@ def _parse_ctx_free(self, input, mode="exec", filename=None, logical_input=False
                 last_error_line = e.loc.lineno
                 idx = last_error_line - 1
                 lines = input.splitlines()
+                if input.endswith("\n"):
+                    lines.append("")
                 line, nlogical, idx = get_logical_line(lines, idx)
                 if nlogical > 1 and not logical_input:
                     _, sbpline = self._parse_ctx_free(
@@ -254,8 +256,6 @@ def _parse_ctx_free(self, input, mode="exec", filename=None, logical_input=False
                     last_error_col += 3
                     input = "\n".join(lines)
                     continue
-                if input.endswith("\n"):
-                    lines.append("")
                 if len(line.strip()) == 0:
                     # whitespace only lines are not valid syntax in Python's
                     # interactive mode='single', who knew?! Just ignore them.


Test patch:
diff --git a/tests/test_integrations.py b/tests/test_integrations.py
--- a/tests/test_integrations.py
+++ b/tests/test_integrations.py
@@ -289,10 +289,11 @@ def _echo(args):
     print(' '.join(args))
 aliases['echo'] = _echo
 
-echo --option1 \
+echo --option1 \\
 --option2
-""",
-        "--option1 --option2\n",
+echo missing \\
+EOL""",
+        "--option1 --option2\nmissing EOL\n",
         0,
     ),
     #