Kodiologist: @kirbyfan64 Is this good to merge?
Kodiologist: @kirbyfan64 Poke.
kirbyfan64: @Kodiologist FYI this has conflicts.
Kodiologist: Yep, I'll take care of it and merge. Thanks.
diff --git a/Makefile b/Makefile
index 1b618a76..4658b6fb 100644
--- a/Makefile
+++ b/Makefile
@@ -46,7 +46,7 @@ tox: venv
 	tox
 
 flake:
-	flake8 hy tests --ignore=E121,E123,E126,E226,E24,E704,W503,E305
+	flake8 hy tests --ignore=E121,E123,E126,E226,E24,E704,W503,E302,E305,E701
 
 clear:
 	clear
diff --git a/NEWS b/NEWS
index e96cd696..1a6c3d5e 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,11 @@ Changes from 0.13.0
    [ Language Changes ]
    * Single-character "sharp macros" changed to "tag macros", which can have
      longer names
+   * Periods are no longer allowed in keywords
+
+   [ Bug Fixes ]
+   * Numeric literals are no longer parsed as symbols when followed by a dot
+     and a symbol
 
 Changes from 0.12.1
 
diff --git a/hy/lex/parser.py b/hy/lex/parser.py
index aa579e3d..1be896b2 100755
--- a/hy/lex/parser.py
+++ b/hy/lex/parser.py
@@ -288,6 +288,24 @@ def t_partial_string(p):
 def t_identifier(p):
     obj = p[0].value
 
+    val = symbol_like(obj)
+    if val is not None:
+        return val
+
+    if "." in obj and symbol_like(obj.split(".", 1)[0]) is not None:
+        # E.g., `5.attr` or `:foo.attr`
+        raise LexException(
+            'Cannot access attribute on anything other than a name (in '
+            'order to get attributes of expressions, use '
+            '`(. <expression> <attr>)` or `(.<attr> <expression>)`)',
+            p[0].source_pos.lineno, p[0].source_pos.colno)
+
+    return HySymbol(".".join(hy_symbol_mangle(x) for x in obj.split(".")))
+
+
+def symbol_like(obj):
+    "Try to interpret `obj` as a number or keyword."
+
     try:
         return HyInteger(obj)
     except ValueError:
@@ -312,13 +330,9 @@ def t_identifier(p):
         except ValueError:
             pass
 
-    if obj.startswith(":"):
+    if obj.startswith(":") and "." not in obj:
         return HyKeyword(obj)
 
-    obj = ".".join([hy_symbol_mangle(part) for part in obj.split(".")])
-
-    return HySymbol(obj)
-
 
 @pg.error
 def error_handler(token):

Attributes of literals
TL;DR: the way attributes, integers, and identifiers are parsed is a hot, hot mess.

So, right now, parsing a number or symbol looks like this:

- Lexer sees token and tags it as an identifier.
- Parser grabs token, tries to covert it to an integer, float, or fraction, and, failing that, compiles it as a symbol.

The second step means that there are a lot of corner cases that are currently quite bizarre:

```
ryan@DevPC-LX /media/ryan/stuff/hy attrerr $ ./run --spy
hy 0.11.0 using CPython(default) 2.7.6 on Linux
=> (defn f [&rest args &kwargs kw])
def f(*args, **kw):
    pass
=> 

=> 2.2  ; This is a float.
2.2
2.2
=> a.2  ; This is interpreted as getting the attribute '2' on 'a'.
a.2
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'a' is not defined
=> 2.a  ; This is interpreted as getting the attribute 'a' on an identifier '2'.
2.a
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name '2' is not defined
=> a/b  ; This is parsed as a whole identifier 'a/b'...
a/b
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'a/b' is not defined
=> 1/2  ; ...but this is a fraction.
from hy.core.language import fraction
fraction(1L, 2L)
Fraction(1, 2)
=> (f :x? 1)  ; Here, the ? isn't converted to is_ like usual.
f(x?=1L)
=> (f :*x* 1)  ; The two stars stay uncoverted.
f(*x*=1L)
=> 
ryan@DevPC-LX /media/ryan/stuff/hy attrerr $ 
```
diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy
index 49b53056..5b2421f5 100644
--- a/tests/native_tests/language.hy
+++ b/tests/native_tests/language.hy
@@ -1446,7 +1446,6 @@
   (assert (= (keyword 'foo) :foo))
   (assert (= (keyword 'foo-bar) :foo-bar))
   (assert (= (keyword 1) :1))
-  (assert (= (keyword 1.0) :1.0))
   (assert (= (keyword :foo_bar) :foo-bar)))
 
 (defn test-name-conversion []
diff --git a/tests/test_lex.py b/tests/test_lex.py
index 33162e76..e5f43223 100644
--- a/tests/test_lex.py
+++ b/tests/test_lex.py
@@ -5,56 +5,31 @@
 from hy.models import (HyExpression, HyInteger, HyFloat, HyComplex, HySymbol,
                        HyString, HyDict, HyList, HySet, HyCons)
 from hy.lex import LexException, PrematureEndOfInput, tokenize
+import pytest
+
+def peoi(): return pytest.raises(PrematureEndOfInput)
+def lexe(): return pytest.raises(LexException)
 
 
 def test_lex_exception():
     """ Ensure tokenize throws a fit on a partial input """
-    try:
-        tokenize("(foo")
-        assert True is False
-    except PrematureEndOfInput:
-        pass
-    try:
-        tokenize("{foo bar")
-        assert True is False
-    except PrematureEndOfInput:
-        pass
-    try:
-        tokenize("(defn foo [bar]")
-        assert True is False
-    except PrematureEndOfInput:
-        pass
-    try:
-        tokenize("(foo \"bar")
-        assert True is False
-    except PrematureEndOfInput:
-        pass
+    with peoi(): tokenize("(foo")
+    with peoi(): tokenize("{foo bar")
+    with peoi(): tokenize("(defn foo [bar]")
+    with peoi(): tokenize("(foo \"bar")
 
 
 def test_unbalanced_exception():
     """Ensure the tokenization fails on unbalanced expressions"""
-    try:
-        tokenize("(bar))")
-        assert True is False
-    except LexException:
-        pass
-
-    try:
-        tokenize("(baz [quux]])")
-        assert True is False
-    except LexException:
-        pass
+    with lexe(): tokenize("(bar))")
+    with lexe(): tokenize("(baz [quux]])")
 
 
 def test_lex_single_quote_err():
     "Ensure tokenizing \"' \" throws a LexException that can be stringified"
     # https://github.com/hylang/hy/issues/1252
-    try:
-        tokenize("' ")
-    except LexException as e:
-        assert "Could not identify the next token" in str(e)
-    else:
-        assert False
+    with lexe() as e: tokenize("' ")
+    assert "Could not identify the next token" in str(e.value)
 
 
 def test_lex_expression_symbols():
@@ -154,6 +129,21 @@ def test_lex_digit_separators():
             [HySymbol(",,,,___,__1__,,__,,2__,q,__")])
 
 
+def test_lex_bad_attrs():
+    with lexe(): tokenize("1.foo")
+    with lexe(): tokenize("0.foo")
+    with lexe(): tokenize("1.5.foo")
+    with lexe(): tokenize("1e3.foo")
+    with lexe(): tokenize("5j.foo")
+    with lexe(): tokenize("3+5j.foo")
+    with lexe(): tokenize("3.1+5.1j.foo")
+    assert tokenize("j.foo")
+    with lexe(): tokenize("3/4.foo")
+    assert tokenize("a/1.foo")
+    assert tokenize("1/a.foo")
+    with lexe(): tokenize(":hello.foo")
+
+
 def test_lex_line_counting():
     """ Make sure we can count lines / columns """
     entry = tokenize("(foo (one two))")[0]

pip install -e .
parse_log_pytest
pytest
pytest
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
args==0.1.0
astor==0.8.1
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
certifi==2021.5.30
clint==0.5.1
-e git+https://github.com/hylang/hy.git@e92ef484a0f4102b964525527b485bcc18b47ef8#egg=hy
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
rply==0.7.8
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: hy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - args==0.1.0
      - astor==0.8.1
      - clint==0.5.1
      - rply==0.7.8
prefix: /opt/conda/envs/hy

tests/test_lex.py::test_lex_bad_attrs
tests/native_tests/language.hy::test_disassemble
tests/native_tests/language.hy::test_sys_argv tests/native_tests/language.hy::test_hex tests/native_tests/language.hy::test_octal tests/native_tests/language.hy::test_binary tests/native_tests/language.hy::test_fractions tests/native_tests/language.hy::test_lists tests/native_tests/language.hy::test_dicts tests/native_tests/language.hy::test_sets tests/native_tests/language.hy::test_setv_get tests/native_tests/language.hy::test_setv_builtin tests/native_tests/language.hy::test_setv_pairs tests/native_tests/language.hy::test_setv_returns_none tests/native_tests/language.hy::test_store_errors tests/native_tests/language.hy::test_fn_corner_cases tests/native_tests/language.hy::test_alias_names_in_errors tests/native_tests/language.hy::test_for_loop tests/native_tests/language.hy::test_nasty_for_nesting tests/native_tests/language.hy::test_while_loop tests/native_tests/language.hy::test_branching tests/native_tests/language.hy::test_branching_with_do tests/native_tests/language.hy::test_branching_expr_count_with_do tests/native_tests/language.hy::test_cond tests/native_tests/language.hy::test_if tests/native_tests/language.hy::test_index tests/native_tests/language.hy::test_fn tests/native_tests/language.hy::test_imported_bits tests/native_tests/language.hy::test_kwargs tests/native_tests/language.hy::test_apply tests/native_tests/language.hy::test_apply_with_methods tests/native_tests/language.hy::test_dotted tests/native_tests/language.hy::test_do tests/native_tests/language.hy::test_exceptions tests/native_tests/language.hy::test_earmuffs tests/native_tests/language.hy::test_threading tests/native_tests/language.hy::test_tail_threading tests/native_tests/language.hy::test_threading_two tests/native_tests/language.hy::test_as_threading tests/native_tests/language.hy::test_assoc tests/native_tests/language.hy::test_multiassoc tests/native_tests/language.hy::test_pass tests/native_tests/language.hy::test_yield tests/native_tests/language.hy::test_yield_with_return tests/native_tests/language.hy::test_yield_in_try tests/native_tests/language.hy::test_first tests/native_tests/language.hy::test_cut tests/native_tests/language.hy::test_rest tests/native_tests/language.hy::test_importas tests/native_tests/language.hy::test_context tests/native_tests/language.hy::test_context_yield tests/native_tests/language.hy::test_with_return tests/native_tests/language.hy::test_for_doodle tests/native_tests/language.hy::test_for_else tests/native_tests/language.hy::test_list_comprehensions tests/native_tests/language.hy::test_set_comprehensions tests/native_tests/language.hy::test_dict_comprehensions tests/native_tests/language.hy::test_generator_expressions tests/native_tests/language.hy::test_defn_order tests/native_tests/language.hy::test_defn_return tests/native_tests/language.hy::test_defn_lambdakey tests/native_tests/language.hy::test_defn_do tests/native_tests/language.hy::test_defn_do_return tests/native_tests/language.hy::test_defn_dunder_name tests/native_tests/language.hy::test_mangles tests/native_tests/language.hy::test_fn_return tests/native_tests/language.hy::test_if_mangler tests/native_tests/language.hy::test_nested_mangles tests/native_tests/language.hy::test_symbol_utf_8 tests/native_tests/language.hy::test_symbol_dash tests/native_tests/language.hy::test_symbol_question_mark tests/native_tests/language.hy::test_and tests/native_tests/language.hy::test_and_#1151_do tests/native_tests/language.hy::test_and_#1151_for tests/native_tests/language.hy::test_and_#1151_del tests/native_tests/language.hy::test_or tests/native_tests/language.hy::test_or_#1151_do tests/native_tests/language.hy::test_or_#1151_for tests/native_tests/language.hy::test_or_#1151_del tests/native_tests/language.hy::test_xor tests/native_tests/language.hy::test_if_return_branching tests/native_tests/language.hy::test_keyword tests/native_tests/language.hy::test_keyword_clash tests/native_tests/language.hy::test_empty_keyword tests/native_tests/language.hy::test_nested_if tests/native_tests/language.hy::test_eval tests/native_tests/language.hy::test_eval_false tests/native_tests/language.hy::test_eval_globals tests/native_tests/language.hy::test_eval_failure tests/native_tests/language.hy::test_import_syntax tests/native_tests/language.hy::test_lambda_keyword_lists tests/native_tests/language.hy::test_key_arguments tests/native_tests/language.hy::test_optional_arguments tests/native_tests/language.hy::test_undefined_name tests/native_tests/language.hy::test_if_in_if tests/native_tests/language.hy::test_try_except_return tests/native_tests/language.hy::test_try_else_return tests/native_tests/language.hy::test_require tests/native_tests/language.hy::test_require_native tests/native_tests/language.hy::test_encoding_nightmares tests/native_tests/language.hy::test_keyword_dict_access tests/native_tests/language.hy::test_break_breaking tests/native_tests/language.hy::test_continue_continuation tests/native_tests/language.hy::test_empty_list tests/native_tests/language.hy::test_string tests/native_tests/language.hy::test_del tests/native_tests/language.hy::test_macroexpand tests/native_tests/language.hy::test_macroexpand_1 tests/native_tests/language.hy::test_merge_with tests/native_tests/language.hy::test_calling_module_name tests/native_tests/language.hy::test_attribute_access tests/native_tests/language.hy::test_keyword_quoting tests/native_tests/language.hy::test_only_parse_lambda_list_in_defn tests/native_tests/language.hy::test_read tests/native_tests/language.hy::test_read_str tests/native_tests/language.hy::test_keyword_creation tests/native_tests/language.hy::test_name_conversion tests/native_tests/language.hy::test_keywords tests/native_tests/language.hy::test_keywords_and_macros tests/native_tests/language.hy::test_argument_destr tests/test_lex.py::test_lex_exception tests/test_lex.py::test_unbalanced_exception tests/test_lex.py::test_lex_single_quote_err tests/test_lex.py::test_lex_expression_symbols tests/test_lex.py::test_lex_expression_strings tests/test_lex.py::test_lex_expression_integer tests/test_lex.py::test_lex_symbols tests/test_lex.py::test_lex_strings tests/test_lex.py::test_lex_integers tests/test_lex.py::test_lex_fractions tests/test_lex.py::test_lex_expression_float tests/test_lex.py::test_lex_expression_complex tests/test_lex.py::test_lex_digit_separators tests/test_lex.py::test_lex_line_counting tests/test_lex.py::test_lex_line_counting_multi tests/test_lex.py::test_lex_line_counting_multi_inner tests/test_lex.py::test_dicts tests/test_lex.py::test_sets tests/test_lex.py::test_nospace tests/test_lex.py::test_escapes tests/test_lex.py::test_unicode_escapes tests/test_lex.py::test_hashbang tests/test_lex.py::test_complex tests/test_lex.py::test_tag_macro tests/test_lex.py::test_lex_comment_382 tests/test_lex.py::test_lex_mangling_star tests/test_lex.py::test_lex_mangling_hyphen tests/test_lex.py::test_lex_mangling_qmark tests/test_lex.py::test_lex_mangling_bang tests/test_lex.py::test_unmangle tests/test_lex.py::test_simple_cons tests/test_lex.py::test_dotted_list tests/test_lex.py::test_cons_list
Makefile NEWS hy/lex/parser.py
Makefile NEWS hy/lex/parser.py