diff --git a/NEWS.rst b/NEWS.rst
index 345094bb..f9acfdcc 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -16,6 +16,11 @@ New Features
 ------------------------------
 * `defn`, `defn/a`, and `defclass` now support type parameters.
 
+Misc. Improvements
+------------------------------
+* Some syntax errors raised by core macros now have more informative
+  messages.
+
 0.27.0 (released 2023-07-06)
 =============================
 
diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py
index 1fad8e85..2db198d5 100644
--- a/hy/core/result_macros.py
+++ b/hy/core/result_macros.py
@@ -72,7 +72,9 @@ def pvalue(root, wanted):
 
 
 def maybe_annotated(target):
-    return pexpr(sym("annotate") + target + FORM) | target >> (lambda x: (x, None))
+    return (
+       pexpr(sym("annotate") + target + FORM).named('`annotate` form') |
+       (target >> (lambda x: (x, None))))
 
 
 def dotted(name):
@@ -727,9 +729,9 @@ loopers = many(
 )
 
 
-@pattern_macro(
-    ["for"], [brackets(loopers), many(notpexpr("else")) + maybe(dolike("else"))]
-)
+@pattern_macro(["for"], [
+    brackets(loopers, name = 'square-bracketed loop clauses'),
+    many(notpexpr("else")) + maybe(dolike("else"))])
 @pattern_macro(["lfor", "sfor", "gfor"], [loopers, FORM])
 @pattern_macro(["dfor"], [loopers, finished])
 # Here `finished` is a hack replacement for FORM + FORM:
diff --git a/hy/macros.py b/hy/macros.py
index d684310f..c1ee5604 100644
--- a/hy/macros.py
+++ b/hy/macros.py
@@ -68,7 +68,7 @@ def pattern_macro(names, pattern, shadow=None):
                     raise hy_compiler._syntax_error(
                         expr[min(e.state.pos + 1, len(expr) - 1)],
                         "parse error for pattern macro '{}': {}".format(
-                            root, e.msg.replace("<EOF>", "end of form")
+                            root, e.msg.replace("end of input", "end of macro call")
                         ),
                     )
                 return fn(hy_compiler, expr, root, *parse_tree)
diff --git a/hy/model_patterns.py b/hy/model_patterns.py
index 14d06a33..1d703395 100644
--- a/hy/model_patterns.py
+++ b/hy/model_patterns.py
@@ -31,11 +31,11 @@ from hy.models import (
     Tuple,
 )
 
-FORM = some(lambda _: True)
-SYM = some(lambda x: isinstance(x, Symbol))
-KEYWORD = some(lambda x: isinstance(x, Keyword))
-STR = some(lambda x: isinstance(x, String))  # matches literal strings only!
-LITERAL = some(lambda x: isinstance(x, (String, Integer, Float, Complex, Bytes)))
+FORM = some(lambda _: True).named('form')
+SYM = some(lambda x: isinstance(x, Symbol)).named('Symbol')
+KEYWORD = some(lambda x: isinstance(x, Keyword)).named('Keyword')
+STR = some(lambda x: isinstance(x, String)).named('String')  # matches literal strings only!
+LITERAL = some(lambda x: isinstance(x, (String, Integer, Float, Complex, Bytes))).named('literal')
 
 
 def sym(wanted):
@@ -49,9 +49,10 @@ def keepsym(wanted):
 
 
 def _sym(wanted, f=lambda x: x):
+    name = '`' + wanted + '`'
     if wanted.startswith(":"):
-        return f(a(Keyword(wanted[1:])))
-    return f(some(lambda x: x == Symbol(wanted)))
+        return f(a(Keyword(wanted[1:]))).named(name)
+    return f(some(lambda x: x == Symbol(wanted))).named(name)
 
 
 def whole(parsers):
@@ -64,29 +65,23 @@ def whole(parsers):
     return reduce(add, parsers) + skip(finished)
 
 
-def _grouped(group_type, parsers):
-    return some(lambda x: isinstance(x, group_type)) >> (
-        lambda x: group_type(whole(parsers).parse(x)).replace(x, recursive=False)
+def _grouped(group_type, syntax_example, name, parsers):
+    return (
+        some(lambda x: isinstance(x, group_type)).named(name or
+            f'{group_type.__name__} (i.e., `{syntax_example}`)') >>
+        (lambda x: group_type(whole(parsers).parse(x)).replace(x, recursive=False))
     )
-
-
-def brackets(*parsers):
+def brackets(*parsers, name = None):
     "Parse the given parsers inside square brackets."
-    return _grouped(List, parsers)
-
-
-def in_tuple(*parsers):
-    return _grouped(Tuple, parsers)
-
-
-def braces(*parsers):
+    return _grouped(List, '[ … ]', name, parsers)
+def in_tuple(*parsers, name = None):
+    return _grouped(Tuple, '#( … )', name, parsers)
+def braces(*parsers, name = None):
     "Parse the given parsers inside curly braces"
-    return _grouped(Dict, parsers)
-
-
-def pexpr(*parsers):
+    return _grouped(Dict, '{ … }', name, parsers)
+def pexpr(*parsers, name = None):
     "Parse the given parsers inside a parenthesized expression."
-    return _grouped(Expression, parsers)
+    return _grouped(Expression, '( … )', name, parsers)
 
 
 def dolike(head):

diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
index d3d91a5d..56e90389 100644
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -628,3 +628,15 @@ def test_module_prelude():
 def test_pragma():
     cant_compile("(pragma)")
     cant_compile("(pragma :native-code :namespaced-symbols :give-user-a-pony)")
+
+
+def test_error_with_expectation():
+    def check(code, expected):
+        assert cant_compile(code).msg.endswith("expected: " + expected)
+
+    check("(defmacro)",    "Symbol")
+    check("(quote)",       "form")
+    check("(py)",          "String")
+    check("(py a)",        "String")
+    check('(py "foo" a)',  "end of macro call")
+    check('(for a)',       "square-bracketed loop clauses")

Improvement of error messages for pattern macros
Advancements to `funcpaserlib` such as https://github.com/vlasovskikh/funcparserlib/issues/52 should allow improving error messages produced while parsing calls to Hy's core pattern macros. In an error like the below example, `expected: some(...)` could probably be changed to `expected symbol`.

```
Traceback (most recent call last):
  File "<string>", line 1
    (defmacro 1)
              ^
hy.errors.HySyntaxError: parse error for pattern macro 'defmacro': got unexpected token: hy.models.Integer(1), expected: some(...)
```

It's conceivable that most of the patterns for Hy's core macros are too complex to admit useful automatic descriptions of the kind of form that's expected. If so, it's probably better to forget about this than to hand-write descriptions for lots of patterns. Manually writing lots of error messages like that is the sort of thing I intended to avoid by using `funcparserlib` in the first place.


A simpler sort of improvement would be rewording "unexpected end of input", as provoked by e.g. `(defmacro)`, to something that makes it clearer that the form, as opposed to the entire file, ended prematurely.
tests/compilers/test_ast.py::test_error_with_expectation
tests/compilers/test_ast.py::test_ast_bad_type tests/compilers/test_ast.py::test_empty_expr tests/compilers/test_ast.py::test_dot_unpacking tests/compilers/test_ast.py::test_ast_bad_if tests/compilers/test_ast.py::test_ast_valid_if tests/compilers/test_ast.py::test_ast_bad_while tests/compilers/test_ast.py::test_ast_good_do tests/compilers/test_ast.py::test_ast_good_raise tests/compilers/test_ast.py::test_ast_raise_from tests/compilers/test_ast.py::test_ast_bad_raise tests/compilers/test_ast.py::test_ast_good_try tests/compilers/test_ast.py::test_ast_bad_try tests/compilers/test_ast.py::test_ast_good_except tests/compilers/test_ast.py::test_ast_bad_except tests/compilers/test_ast.py::test_ast_good_assert tests/compilers/test_ast.py::test_ast_bad_assert tests/compilers/test_ast.py::test_ast_good_global tests/compilers/test_ast.py::test_ast_bad_global tests/compilers/test_ast.py::test_ast_good_nonlocal tests/compilers/test_ast.py::test_ast_bad_nonlocal tests/compilers/test_ast.py::test_ast_good_defclass tests/compilers/test_ast.py::test_ast_good_defclass_with_metaclass tests/compilers/test_ast.py::test_ast_bad_defclass tests/compilers/test_ast.py::test_ast_good_lambda tests/compilers/test_ast.py::test_ast_bad_lambda tests/compilers/test_ast.py::test_ast_good_yield tests/compilers/test_ast.py::test_ast_bad_yield tests/compilers/test_ast.py::test_ast_import_mangle_dotted tests/compilers/test_ast.py::test_ast_good_import_from tests/compilers/test_ast.py::test_ast_require tests/compilers/test_ast.py::test_ast_import_require_dotted tests/compilers/test_ast.py::test_ast_multi_require tests/compilers/test_ast.py::test_ast_good_get tests/compilers/test_ast.py::test_ast_bad_get tests/compilers/test_ast.py::test_ast_good_cut tests/compilers/test_ast.py::test_ast_bad_cut tests/compilers/test_ast.py::test_ast_bad_with tests/compilers/test_ast.py::test_ast_valid_while tests/compilers/test_ast.py::test_ast_valid_for tests/compilers/test_ast.py::test_nullary_break_continue tests/compilers/test_ast.py::test_ast_expression_basics tests/compilers/test_ast.py::test_ast_anon_fns_basics tests/compilers/test_ast.py::test_ast_lambda_lists tests/compilers/test_ast.py::test_ast_print tests/compilers/test_ast.py::test_ast_tuple tests/compilers/test_ast.py::test_lambda_list_keywords_rest tests/compilers/test_ast.py::test_lambda_list_keywords_kwargs tests/compilers/test_ast.py::test_lambda_list_keywords_kwonly tests/compilers/test_ast.py::test_lambda_list_keywords_mixed tests/compilers/test_ast.py::test_missing_keyword_argument_value tests/compilers/test_ast.py::test_ast_unicode_strings tests/compilers/test_ast.py::test_ast_unicode_vs_bytes tests/compilers/test_ast.py::test_format_string tests/compilers/test_ast.py::test_ast_bracket_string tests/compilers/test_ast.py::test_literal_newlines tests/compilers/test_ast.py::test_compile_error tests/compilers/test_ast.py::test_for_compile_error tests/compilers/test_ast.py::test_attribute_access tests/compilers/test_ast.py::test_misplaced_dots tests/compilers/test_ast.py::test_bad_setv tests/compilers/test_ast.py::test_defn tests/compilers/test_ast.py::test_setv_builtins tests/compilers/test_ast.py::test_top_level_unquote tests/compilers/test_ast.py::test_bad_exception tests/compilers/test_ast.py::test_lots_of_comment_lines tests/compilers/test_ast.py::test_compiler_macro_tag_try tests/compilers/test_ast.py::test_ast_good_yield_from tests/compilers/test_ast.py::test_ast_bad_yield_from tests/compilers/test_ast.py::test_eval_generator_with_return tests/compilers/test_ast.py::test_futures_imports tests/compilers/test_ast.py::test_py tests/compilers/test_ast.py::test_pys tests/compilers/test_ast.py::test_models_accessible tests/compilers/test_ast.py::test_module_prelude tests/compilers/test_ast.py::test_pragma