Repository: hylang/hy

Issue:
Calling a tag macro whose name starts with `!` is lexed as a shebang line
    => (deftag !a [x] 1)
    <function <lambda> at 0x7fcdb322ab70>
    => (+ 1 #!a 1)
      File "<stdin>", line 1, column 6

      (+ 1 #!a 1)
           ^
    LexException: Ran into a HASHBANG where it wasn't expected.

My thinking is that shebang lines shouldn't be expected by the lexer; instead, they should be removed in preprocessing. The trick will be to make sure that line numbers are adjusted to compensate.


Discussion:
Not really much of a trick TBH...I think a simple `re.replace('^#!.*', '', file)` should do the trick; since `.*` removes everything up to the newline, the line numbers will stay intact.

Resolving patch:
diff --git a/hy/importer.py b/hy/importer.py
--- a/hy/importer.py
+++ b/hy/importer.py
@@ -39,7 +39,10 @@ def import_file_to_hst(fpath):
     """Import content from fpath and return a Hy AST."""
     try:
         with open(fpath, 'r', encoding='utf-8') as f:
-            return import_buffer_to_hst(f.read())
+            buf = f.read()
+        # Strip the shebang line, if there is one.
+        buf = re.sub(r'\A#!.*', '', buf)
+        return import_buffer_to_hst(buf)
     except IOError as e:
         raise HyIOError(e.errno, e.strerror, e.filename)
 
diff --git a/hy/lex/lexer.py b/hy/lex/lexer.py
--- a/hy/lex/lexer.py
+++ b/hy/lex/lexer.py
@@ -25,7 +25,6 @@
 lg.add('QUASIQUOTE', r'`%s' % end_quote)
 lg.add('UNQUOTESPLICE', r'~@%s' % end_quote)
 lg.add('UNQUOTE', r'~%s' % end_quote)
-lg.add('HASHBANG', r'#!.*[^\r\n]')
 lg.add('HASHSTARS', r'#\*+')
 lg.add('HASHOTHER', r'#%s' % identifier)
 
diff --git a/hy/lex/parser.py b/hy/lex/parser.py
--- a/hy/lex/parser.py
+++ b/hy/lex/parser.py
@@ -90,23 +90,13 @@ def wrapped(p):
     return wrapped
 
 
-@pg.production("main : HASHBANG real_main")
-def main_hashbang(p):
-    return p[1]
-
-
-@pg.production("main : real_main")
+@pg.production("main : list_contents")
 def main(p):
     return p[0]
 
 
-@pg.production("real_main : list_contents")
-def real_main(p):
-    return p[0]
-
-
-@pg.production("real_main : $end")
-def real_main_empty(p):
+@pg.production("main : $end")
+def main_empty(p):
     return []
 
 


Test patch:
diff --git a/tests/native_tests/tag_macros.hy b/tests/native_tests/tag_macros.hy
--- a/tests/native_tests/tag_macros.hy
+++ b/tests/native_tests/tag_macros.hy
@@ -36,6 +36,15 @@
   (assert (= #spam_eggs 42 ['spam 42 'eggs])))
 
 
+(defn test-bang-tag-macro []
+  "Test tag macros whose names start with `!`"
+  ; https://github.com/hylang/hy/issues/1334
+  (deftag !a [x] `["foo" ~x])
+  (assert (= #!a 3 ["foo" 3]))
+  (deftag ! [x] `["bar" ~x])
+  (assert (= #! 4 ["bar" 4])))
+
+
 (defn test-tag-macro-whitespace []
   "Test whitespace after a tag macro"
   (deftag foo [expr]
diff --git a/tests/test_lex.py b/tests/test_lex.py
--- a/tests/test_lex.py
+++ b/tests/test_lex.py
@@ -301,12 +301,6 @@ def test_unicode_escapes():
     assert [ord(x) for x in entry] == [97, 172, 4660, 8364, 32768]
 
 
-def test_hashbang():
-    """ Ensure we can escape things """
-    entry = tokenize("#!this is a comment\n")
-    assert entry == []
-
-
 def test_complex():
     """Ensure we tokenize complex numbers properly"""
     # This is a regression test for #143