Repository: lark-parser/lark

Issue:
Generic exception with (seemingly) valid grammar input
**Describe the bug**

When using Lark, I keep getting generic exceptions instead of any detailed report. I am unsure if this is caused by bad grammar input, or from a bug in Lark (I'm guessing the former), but I'd love it if we could get to the bottom of this.
```
Traceback (most recent call last):
  File "lark_test\venv\lib\site-packages\lark\load_grammar.py", line 327, in pattern
    term_name = self.term_reverse[p].name
KeyError: ''

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "lark_test\venv\lib\site-packages\lark\load_grammar.py", line 331, in pattern
    term_name = _TERMINAL_NAMES[value]
KeyError: ''

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "lark_test\venv\lib\site-packages\lark\visitors.py", line 91, in _call_userfunc
    return f.visit_wrapper(f, tree.data, children, tree.meta)
  File "lark_test\venv\lib\site-packages\lark\visitors.py", line 390, in _vargs_inline
    return f(*children)
  File "lark_test\venv\lib\site-packages\lark\visitors.py", line 374, in f
    return _f(self, *args, **kwargs)
  File "lark_test\venv\lib\site-packages\lark\load_grammar.py", line 333, in pattern
    if is_id_continue(value) and is_id_start(value[0]) and value.upper() not in self.term_set:
IndexError: string index out of range

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "lark_test\main.py", line 5, in <module>
    parser = Lark(grammar.read(), start="PROGRAM")
  File "lark_test\venv\lib\site-packages\lark\lark.py", line 348, in __init__
    self.terminals, self.rules, self.ignore_tokens = self.grammar.compile(self.options.start, terminals_to_keep)
  File "lark_test\venv\lib\site-packages\lark\load_grammar.py", line 604, in compile
    tree = transformer.transform(rule_tree)
  File "lark_test\venv\lib\site-packages\lark\visitors.py", line 170, in transform
    tree = t.transform(tree)
  File "lark_test\venv\lib\site-packages\lark\visitors.py", line 187, in transform
    subtree.children = list(self._transform_children(subtree.children))
  File "lark_test\venv\lib\site-packages\lark\visitors.py", line 116, in _transform_children
    yield self._transform_tree(c)
  File "lark_test\venv\lib\site-packages\lark\visitors.py", line 183, in _transform_tree
    return self._call_userfunc(tree)
  File "lark_test\venv\lib\site-packages\lark\visitors.py", line 97, in _call_userfunc
    raise VisitError(tree.data, tree, e)
lark.exceptions.VisitError: Error trying to process rule "pattern":

string index out of range

Process finished with exit code 1
````

**To Reproduce**

main.py
```
from lark import Lark

if __name__ == "__main__":
    with open('grammar.ebnf', 'r') as grammar:
        parser = Lark(grammar.read(), start="PROGRAM")
````

grammar.ebnf
```

program : statement_or_func ("\n" statement_or_func)*
statement_or_func : sequence | func_stat

sequence : statement (le statement)*
le : "\n" | ";"
brackets_seq : (sequence)
brack_or_stat : brackets_seq | statement
statement : ""
        | decl_stat assignment_stat
        | if_stat
        | do_until_stat
        | while_stat
        | return_stat
        | print_stat
        | input_stat
        | func_stat
        | call_stat
        | comment_stat

decl_stat : LET assignment_stat ("," declaration)*
declaration : ID | assignment_stat

assignment_stat : ID "=" expression

if_stat : IF (condition) THEN brack_or_stat elsepart

elsepart : "" | ELSE brack_or_stat

do_until_stat :  DO brack_or_stat LOOP UNTIL (condition)

while_stat : WHILE (condition) brack_or_stat WEND

return_stat : RETURN (expression)

print_stat : PRINT MESSAGE"," expression_list
expression_list : expression ("," expression)*

input_stat : INPUT MESSAGE"," ID ("," ID)*
func_stat : SUB ID funcbody
funcbody : formalpars brackets_seq
formalpars : ("" | formalparlist)
formalparlist : ID ("," ID)*
call_stat : CALL ID actualpars
actualpars : ("" | actualparlist)
actualparlist : actualparitem ("," actualparitem)*
actualparitem : expression | ID

comment_stat : REM MESSAGE
condition : boolterm (OR boolterm)*
boolterm : boolfactor (AND boolfactor)*
boolfactor : NOT [condition] | [condition] | expression RELATIONAL_OPER expression
expression : OPTIONAL_SIGN term (ADD_OPER term)*
term : factor (MUL_OPER factor)*
factor : CONSTANT | (expression) | ID | call_stat

RELATIONAL_OPER : "==" | "<" | "<=" | "<>" | ">=" | "<"
ADD_OPER : "+" | "-"
MUL_OPER : "*" | "/"
OPTIONAL_SIGN : "" | ADD_OPER
ID : (ALPHA)(ALPHA|DIGIT)*

LET : "LET"
DO : "DO"
LOOP: "LOOP"
UNTIL: "UNTIL"
WHILE: "WHILE"
WEND : "WEND"
SUB : "SUB"
CALL : "CALL"
INPUT : "INPUT"
PRINT : "PRINT"
IF : "IF"
THEN : "THEN"
ELSE : "ELSE"
AND : "AND"
NOT : "NOT"
OR : "OR"
RETURN : "RETURN"
REM : "REM"
MULTI_LINE_COMMENT_START : "%*"
MULTI_LINE_COMMENT_END : "*%"
ALPHA : "a".."z" | "A".."Z"
DIGIT : "0".."9"
INTEGER : /-?{DIGIT}{1,5}/
MESSAGE : /"(\\.|[^"\\])*"/
CONSTANT : INTEGER | MESSAGE
```


I'm using the latest version of Lark installed from PIP, with python 3.9.0

Comments:

Patch:
diff --git a/lark-stubs/reconstruct.pyi b/lark-stubs/reconstruct.pyi
index 9826428..a8d39e3 100644
--- a/lark-stubs/reconstruct.pyi
+++ b/lark-stubs/reconstruct.pyi
@@ -34,5 +34,6 @@ class Reconstructor:
     def __init__(self, parser: Lark, term_subs: Dict[str, Callable[[Symbol], str]] = ...):
         ...
 
-    def reconstruct(self, tree: Tree, postproc: Callable[[Iterable[str]], Iterable[str]]) -> str:
+    def reconstruct(self, tree: Tree, postproc: Callable[[Iterable[str]], Iterable[str]]=None,
+                    insert_spaces: bool = True) -> str:
         ...
diff --git a/lark-stubs/tree.pyi b/lark-stubs/tree.pyi
index 98aadff..ea99ff6 100644
--- a/lark-stubs/tree.pyi
+++ b/lark-stubs/tree.pyi
@@ -40,7 +40,7 @@ class Tree:
     def expand_kids_by_index(self, *indices: int) -> None:
         ...
 
-    def scan_values(self, pred: Callable[[Union[str, Tree]], bool]) -> List[str]:
+    def scan_values(self, pred: Callable[[Union[str, Tree]], bool]) -> Iterator[str]:
         ...
 
     def iter_subtrees(self) -> Iterator[Tree]:
diff --git a/lark/load_grammar.py b/lark/load_grammar.py
index dcb4c81..7b38a74 100644
--- a/lark/load_grammar.py
+++ b/lark/load_grammar.py
@@ -448,6 +448,9 @@ def _literal_to_pattern(literal):
 
     s = eval_escaping(x)
 
+    if s == "":
+        raise GrammarError("Can't have empty terminals (offending literal: %s)" % literal.value)
+
     if literal.type == 'STRING':
         s = s.replace('\\\\', '\\')
         return PatternStr(s, flags, raw=literal.value)
diff --git a/lark/reconstruct.py b/lark/reconstruct.py
index 2efc0ae..ab2fb38 100644
--- a/lark/reconstruct.py
+++ b/lark/reconstruct.py
@@ -87,14 +87,14 @@ class Reconstructor(TreeMatcher):
             else:
                 yield item
 
-    def reconstruct(self, tree, postproc=None):
+    def reconstruct(self, tree, postproc=None, insert_spaces=True):
         x = self._reconstruct(tree)
         if postproc:
             x = postproc(x)
         y = []
         prev_item = ''
         for item in x:
-            if prev_item and item and is_id_continue(prev_item[-1]) and is_id_continue(item[0]):
+            if insert_spaces and prev_item and item and is_id_continue(prev_item[-1]) and is_id_continue(item[0]):
                 y.append(' ')
             y.append(item)
             prev_item = item


Test patch:
diff --git a/tests/test_grammar.py b/tests/test_grammar.py
index 6a1aefa..a643117 100644
--- a/tests/test_grammar.py
+++ b/tests/test_grammar.py
@@ -22,6 +22,10 @@ class TestGrammar(TestCase):
                 else:
                     assert False, "example did not raise an error"
 
+    def test_empty_literal(self):
+        # Issues #888
+        self.assertRaises(GrammarError, Lark, "start: \"\"")
+
     def test_override_rule(self):
         # Overrides the 'sep' template in existing grammar to add an optional terminating delimiter
         # Thus extending it beyond its original capacity