Repository: ruby/yarp
Pull request #1423 (2023-09-07 17:51:13)

Add ParseResult#attach_comments! to tie comments to their locations
Development tools like RDoc or LSPs need to show comments in certain situations as they are considered documentation. The most prominent example is to implement hover in an LSP, where we want to show the documentation for a given node.

Currently, there's no easy way to figure out which comments are associated with each node. Let's bring in SyntaxTree's algorithm for attaching comments to locations so that we can easily ask a location what comments exist there.


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6f0ddd64..37822f43 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -32,13 +32,6 @@ bundle exec rake compile test
 bundle exec rake compile:yarp test
 ```
 
-To test the rust bindings (with caveats about setting up your Rust environment properly first):
-
-``` sh
-bundle exec rake compile test:rust
-```
-
-
 ## Documentation
 
 We could always use more documentation! If you want to contribute documentation, feel free to open a pull request. These will get merged in as soon as possible. Documenting functions or methods is always useful, but we also need more guides and tutorials. If you have an idea for a guide or tutorial, feel free to open an issue and we can discuss it.
diff --git a/include/yarp/diagnostic.h b/include/yarp/diagnostic.h
index fd17945e..58228d84 100644
--- a/include/yarp/diagnostic.h
+++ b/include/yarp/diagnostic.h
@@ -6,7 +6,6 @@
 
 #include <stdbool.h>
 #include <stdlib.h>
-#include <assert.h>
 
 // This struct represents a diagnostic found during parsing.
 typedef struct {
@@ -16,204 +15,8 @@ typedef struct {
     const char *message;
 } yp_diagnostic_t;
 
-typedef enum {
-    YP_ERR_ALIAS_ARGUMENT,
-    YP_ERR_AMPAMPEQ_MULTI_ASSIGN,
-    YP_ERR_ARGUMENT_AFTER_BLOCK,
-    YP_ERR_ARGUMENT_BARE_HASH,
-    YP_ERR_ARGUMENT_BLOCK_MULTI,
-    YP_ERR_ARGUMENT_FORMAL_CLASS,
-    YP_ERR_ARGUMENT_FORMAL_CONSTANT,
-    YP_ERR_ARGUMENT_FORMAL_GLOBAL,
-    YP_ERR_ARGUMENT_FORMAL_IVAR,
-    YP_ERR_ARGUMENT_NO_FORWARDING_AMP,
-    YP_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES,
-    YP_ERR_ARGUMENT_NO_FORWARDING_STAR,
-    YP_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT,
-    YP_ERR_ARGUMENT_SPLAT_AFTER_SPLAT,
-    YP_ERR_ARGUMENT_TERM_PAREN,
-    YP_ERR_ARRAY_ELEMENT,
-    YP_ERR_ARRAY_EXPRESSION,
-    YP_ERR_ARRAY_EXPRESSION_AFTER_STAR,
-    YP_ERR_ARRAY_SEPARATOR,
-    YP_ERR_ARRAY_TERM,
-    YP_ERR_BEGIN_LONELY_ELSE,
-    YP_ERR_BEGIN_TERM,
-    YP_ERR_BEGIN_UPCASE_BRACE,
-    YP_ERR_BEGIN_UPCASE_TERM,
-    YP_ERR_BLOCK_PARAM_LOCAL_VARIABLE,
-    YP_ERR_BLOCK_PARAM_PIPE_TERM,
-    YP_ERR_BLOCK_TERM_BRACE,
-    YP_ERR_BLOCK_TERM_END,
-    YP_ERR_CANNOT_PARSE_EXPRESSION,
-    YP_ERR_CANNOT_PARSE_STRING_PART,
-    YP_ERR_CASE_EXPRESSION_AFTER_CASE,
-    YP_ERR_CASE_EXPRESSION_AFTER_WHEN,
-    YP_ERR_CASE_LONELY_ELSE,
-    YP_ERR_CASE_TERM,
-    YP_ERR_CLASS_IN_METHOD,
-    YP_ERR_CLASS_NAME,
-    YP_ERR_CLASS_SUPERCLASS,
-    YP_ERR_CLASS_TERM,
-    YP_ERR_CONDITIONAL_ELSIF_PREDICATE,
-    YP_ERR_CONDITIONAL_IF_PREDICATE,
-    YP_ERR_CONDITIONAL_TERM,
-    YP_ERR_CONDITIONAL_TERM_ELSE,
-    YP_ERR_CONDITIONAL_UNLESS_PREDICATE,
-    YP_ERR_CONDITIONAL_UNTIL_PREDICATE,
-    YP_ERR_CONDITIONAL_WHILE_PREDICATE,
-    YP_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT,
-    YP_ERR_DEF_ENDLESS,
-    YP_ERR_DEF_ENDLESS_SETTER,
-    YP_ERR_DEF_NAME,
-    YP_ERR_DEF_NAME_AFTER_RECEIVER,
-    YP_ERR_DEF_PARAMS_TERM,
-    YP_ERR_DEF_PARAMS_TERM_PAREN,
-    YP_ERR_DEF_RECEIVER,
-    YP_ERR_DEF_RECEIVER_TERM,
-    YP_ERR_DEF_TERM,
-    YP_ERR_DEFINED_EXPRESSION,
-    YP_ERR_EMBDOC_TERM,
-    YP_ERR_EMBEXPR_END,
-    YP_ERR_EMBVAR_INVALID,
-    YP_ERR_END_UPCASE_BRACE,
-    YP_ERR_END_UPCASE_TERM,
-    YP_ERR_ESCAPE_INVALID_CONTROL,
-    YP_ERR_ESCAPE_INVALID_CONTROL_REPEAT,
-    YP_ERR_ESCAPE_INVALID_HEXADECIMAL,
-    YP_ERR_ESCAPE_INVALID_META,
-    YP_ERR_ESCAPE_INVALID_META_REPEAT,
-    YP_ERR_ESCAPE_INVALID_UNICODE,
-    YP_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS,
-    YP_ERR_ESCAPE_INVALID_UNICODE_LITERAL,
-    YP_ERR_ESCAPE_INVALID_UNICODE_LONG,
-    YP_ERR_ESCAPE_INVALID_UNICODE_TERM,
-    YP_ERR_EXPECT_ARGUMENT,
-    YP_ERR_EXPECT_EOL_AFTER_STATEMENT,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_COMMA,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_EQUAL,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_LPAREN,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_QUESTION,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_SPLAT,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH,
-    YP_ERR_EXPECT_EXPRESSION_AFTER_STAR,
-    YP_ERR_EXPECT_IDENT_REQ_PARAMETER,
-    YP_ERR_EXPECT_LPAREN_REQ_PARAMETER,
-    YP_ERR_EXPECT_RBRACKET,
-    YP_ERR_EXPECT_RPAREN,
-    YP_ERR_EXPECT_RPAREN_AFTER_MULTI,
-    YP_ERR_EXPECT_RPAREN_REQ_PARAMETER,
-    YP_ERR_EXPECT_STRING_CONTENT,
-    YP_ERR_EXPECT_WHEN_DELIMITER,
-    YP_ERR_EXPRESSION_BARE_HASH,
-    YP_ERR_FOR_COLLECTION,
-    YP_ERR_FOR_IN,
-    YP_ERR_FOR_INDEX,
-    YP_ERR_FOR_TERM,
-    YP_ERR_HASH_EXPRESSION_AFTER_LABEL,
-    YP_ERR_HASH_KEY,
-    YP_ERR_HASH_ROCKET,
-    YP_ERR_HASH_TERM,
-    YP_ERR_HASH_VALUE,
-    YP_ERR_HEREDOC_TERM,
-    YP_ERR_INCOMPLETE_QUESTION_MARK,
-    YP_ERR_INCOMPLETE_VARIABLE_CLASS,
-    YP_ERR_INCOMPLETE_VARIABLE_INSTANCE,
-    YP_ERR_INVALID_ENCODING_MAGIC_COMMENT,
-    YP_ERR_INVALID_FLOAT_EXPONENT,
-    YP_ERR_INVALID_NUMBER_BINARY,
-    YP_ERR_INVALID_NUMBER_DECIMAL,
-    YP_ERR_INVALID_NUMBER_HEXADECIMAL,
-    YP_ERR_INVALID_NUMBER_OCTAL,
-    YP_ERR_INVALID_PERCENT,
-    YP_ERR_INVALID_TOKEN,
-    YP_ERR_INVALID_VARIABLE_GLOBAL,
-    YP_ERR_LAMBDA_OPEN,
-    YP_ERR_LAMBDA_TERM_BRACE,
-    YP_ERR_LAMBDA_TERM_END,
-    YP_ERR_LIST_I_LOWER_ELEMENT,
-    YP_ERR_LIST_I_LOWER_TERM,
-    YP_ERR_LIST_I_UPPER_ELEMENT,
-    YP_ERR_LIST_I_UPPER_TERM,
-    YP_ERR_LIST_W_LOWER_ELEMENT,
-    YP_ERR_LIST_W_LOWER_TERM,
-    YP_ERR_LIST_W_UPPER_ELEMENT,
-    YP_ERR_LIST_W_UPPER_TERM,
-    YP_ERR_MALLOC_FAILED,
-    YP_ERR_MODULE_IN_METHOD,
-    YP_ERR_MODULE_NAME,
-    YP_ERR_MODULE_TERM,
-    YP_ERR_MULTI_ASSIGN_MULTI_SPLATS,
-    YP_ERR_NOT_EXPRESSION,
-    YP_ERR_NUMBER_LITERAL_UNDERSCORE,
-    YP_ERR_OPERATOR_MULTI_ASSIGN,
-    YP_ERR_PARAMETER_ASSOC_SPLAT_MULTI,
-    YP_ERR_PARAMETER_BLOCK_MULTI,
-    YP_ERR_PARAMETER_NAME_REPEAT,
-    YP_ERR_PARAMETER_NO_DEFAULT,
-    YP_ERR_PARAMETER_NO_DEFAULT_KW,
-    YP_ERR_PARAMETER_NUMBERED_RESERVED,
-    YP_ERR_PARAMETER_ORDER,
-    YP_ERR_PARAMETER_SPLAT_MULTI,
-    YP_ERR_PARAMETER_STAR,
-    YP_ERR_PARAMETER_WILD_LOOSE_COMMA,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_BRACKET,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_HROCKET,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_COMMA,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_IN,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_KEY,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_PAREN,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_PIN,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_PIPE,
-    YP_ERR_PATTERN_EXPRESSION_AFTER_RANGE,
-    YP_ERR_PATTERN_HASH_KEY,
-    YP_ERR_PATTERN_HASH_KEY_LABEL,
-    YP_ERR_PATTERN_IDENT_AFTER_HROCKET,
-    YP_ERR_PATTERN_LABEL_AFTER_COMMA,
-    YP_ERR_PATTERN_REST,
-    YP_ERR_PATTERN_TERM_BRACE,
-    YP_ERR_PATTERN_TERM_BRACKET,
-    YP_ERR_PATTERN_TERM_PAREN,
-    YP_ERR_PIPEPIPEEQ_MULTI_ASSIGN,
-    YP_ERR_REGEXP_TERM,
-    YP_ERR_RESCUE_EXPRESSION,
-    YP_ERR_RESCUE_MODIFIER_VALUE,
-    YP_ERR_RESCUE_TERM,
-    YP_ERR_RESCUE_VARIABLE,
-    YP_ERR_RETURN_INVALID,
-    YP_ERR_STRING_CONCATENATION,
-    YP_ERR_STRING_INTERPOLATED_TERM,
-    YP_ERR_STRING_LITERAL_TERM,
-    YP_ERR_SYMBOL_INVALID,
-    YP_ERR_SYMBOL_TERM_DYNAMIC,
-    YP_ERR_SYMBOL_TERM_INTERPOLATED,
-    YP_ERR_TERNARY_COLON,
-    YP_ERR_TERNARY_EXPRESSION_FALSE,
-    YP_ERR_TERNARY_EXPRESSION_TRUE,
-    YP_ERR_UNDEF_ARGUMENT,
-    YP_ERR_UNARY_RECEIVER_BANG,
-    YP_ERR_UNARY_RECEIVER_MINUS,
-    YP_ERR_UNARY_RECEIVER_PLUS,
-    YP_ERR_UNARY_RECEIVER_TILDE,
-    YP_ERR_UNTIL_TERM,
-    YP_ERR_WHILE_TERM,
-    YP_ERR_WRITE_TARGET_READONLY,
-    YP_ERR_WRITE_TARGET_UNEXPECTED,
-    YP_ERR_XSTRING_TERM,
-    YP_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS,
-    YP_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS,
-    YP_WARN_AMBIGUOUS_PREFIX_STAR,
-    YP_WARN_AMBIGUOUS_SLASH,
-    /* This must be the last member. */
-    YP_DIAGNOSTIC_ID_LEN,
-} yp_diagnostic_id_t;
-
 // Append a diagnostic to the given list of diagnostics.
-bool yp_diagnostic_list_append(yp_list_t *list, const uint8_t *start, const uint8_t *end, yp_diagnostic_id_t diag_id);
+bool yp_diagnostic_list_append(yp_list_t *list, const uint8_t *start, const uint8_t *end, const char *message);
 
 // Deallocate the internal state of the given diagnostic list.
 void yp_diagnostic_list_free(yp_list_t *list);
diff --git a/lib/yarp.rb b/lib/yarp.rb
index 9ee9c4b3..32d4a1b5 100644
--- a/lib/yarp.rb
+++ b/lib/yarp.rb
@@ -20,6 +20,10 @@ module YARP
       offsets.bsearch_index { |offset| offset > value } || offsets.length
     end
 
+    def line_offset(value)
+      offsets[line(value) - 1]
+    end
+
     def column(value)
       value - offsets[line(value) - 1]
     end
@@ -46,10 +50,14 @@ module YARP
     # The length of this location in bytes.
     attr_reader :length
 
+    # The list of comments attached to this location
+    attr_reader :comments
+
     def initialize(source, start_offset, length)
       @source = source
       @start_offset = start_offset
       @length = length
+      @comments = []
     end
 
     # Create a new location object with the given options.
@@ -81,6 +89,12 @@ module YARP
       source.line(start_offset)
     end
 
+    # The content of the line where this location starts before this location.
+    def start_line_slice
+      offset = source.line_offset(start_offset)
+      source.slice(offset, start_offset - offset)
+    end
+
     # The line number where this location ends.
     def end_line
       source.line(end_offset - 1)
@@ -142,6 +156,11 @@ module YARP
       { type: type, location: location }
     end
 
+    # Returns true if the comment happens on the same line as other code and false if the comment is by itself
+    def trailing?
+      type == :inline && !location.start_line_slice.strip.empty?
+    end
+
     def inspect
       "#<YARP::Comment @type=#{@type.inspect} @location=#{@location.inspect}>"
     end
@@ -230,6 +249,154 @@ module YARP
       !success?
     end
 
+    # CommentAttacher is a utility class to attach comments to locations in the AST
+    class CommentAttacher
+      attr_reader :parse_result
+
+      def initialize(parse_result)
+        @parse_result = parse_result
+      end
+
+      def attach!
+        parse_result.comments.each do |comment|
+          preceding, enclosing, following = nearest_targets(parse_result.value, comment)
+          target =
+            if comment.trailing?
+              preceding || following || enclosing || NodeTarget.new(parse_result.value)
+            else
+              # If a comment exists on its own line, prefer a leading comment.
+              following || preceding || enclosing || NodeTarget.new(parse_result.value)
+            end
+
+          target << comment
+        end
+      end
+
+      # A target for attaching comments that is based on a specific node
+      class NodeTarget
+        attr_reader :node
+
+        def initialize(node)
+          @node = node
+        end
+
+        def start_offset
+          node.location.start_offset
+        end
+
+        def end_offset
+          node.location.end_offset
+        end
+
+        def encloses?(comment)
+          start_offset <= comment.location.start_offset && comment.location.end_offset <= end_offset
+        end
+
+        def <<(comment)
+          node.location.comments << comment
+        end
+      end
+
+      # A target for attaching comments that is based on a location, which could be a part of a node. For example, the
+      # `end` token of a ClassNode
+      class LocationTarget
+        attr_reader :location
+
+        def initialize(location)
+          @location = location
+        end
+
+        def start_offset
+          location.start_offset
+        end
+
+        def end_offset
+          location.end_offset
+        end
+
+        def encloses?(comment)
+          false
+        end
+
+        def <<(comment)
+          location.comments << comment
+        end
+      end
+
+      private
+
+      # Responsible for finding the nearest targets to the given comment within the context of the given encapsulating
+      # node.
+      def nearest_targets(node, comment)
+        comment_start = comment.location.start_offset
+        comment_end = comment.location.end_offset
+
+        targets = []
+        node.deconstruct_keys(nil).each do |key, value|
+          next if key == :location
+
+          case value
+          when StatementsNode
+            targets.concat(value.body.map { |node| NodeTarget.new(node) })
+          when Node
+            targets << NodeTarget.new(value)
+          when Location
+            targets << LocationTarget.new(value)
+          when Array
+            targets.concat(value.map { |node| NodeTarget.new(node) }) if value.first.is_a?(Node)
+          end
+        end
+
+        targets.sort_by!(&:start_offset)
+        preceding = nil
+        following = nil
+
+        left = 0
+        right = targets.length
+
+        # This is a custom binary search that finds the nearest nodes to the given comment. When it finds a node that
+        # completely encapsulates the comment, it recursed downward into the tree.
+        while left < right
+          middle = (left + right) / 2
+          target = targets[middle]
+
+          target_start = target.start_offset
+          target_end = target.end_offset
+
+          if target.encloses?(comment)
+            # The comment is completely contained by this target. Abandon the binary search at this level.
+            return nearest_targets(target.node, comment)
+          end
+
+          if target_end <= comment_start
+            # This target falls completely before the comment. Because we will never consider this target or any targets
+            # before it again, this target must be the closest preceding target we have encountered so far.
+            preceding = target
+            left = middle + 1
+            next
+          end
+
+          if comment_end <= target_start
+            # This target falls completely after the comment. Because we will never consider this target or any targets
+            # after it again, this target must be the closest following target we have encountered so far.
+            following = target
+            right = middle
+            next
+          end
+
+          # This should only happen if there is a bug in this parser.
+          raise "Comment location overlaps with target location"
+        end
+
+        [preceding, NodeTarget.new(node), following]
+      end
+    end
+
+    # Attach the list of comments to their respective locations in the AST
+    def attach_comments!
+      CommentAttacher.new(self).attach!
+    end
+
     # Keep in sync with Java MarkNewlinesVisitor
     class MarkNewlinesVisitor < YARP::Visitor
       def initialize(newline_marked)
diff --git a/src/diagnostic.c b/src/diagnostic.c
index e92a1ee4..b216d96a 100644
--- a/src/diagnostic.c
+++ b/src/diagnostic.c
@@ -1,264 +1,12 @@
 #include "yarp/diagnostic.h"
 
-/*
-  ## Message composition
-
-  When composing an error message, use sentence fragments.
-
-  Try describing the property of the code that caused the error, rather than the rule that is being
-  violated. It may help to use a fragment that completes a sentence beginning, "The parser
-  encountered (a) ...". If appropriate, add a description of the rule violation (or other helpful
-  context) after a semicolon.
-
-  For example:, instead of "Control escape sequence cannot be doubled", prefer:
-
-  > "Invalid control escape sequence; control cannot be repeated"
-
-  In some cases, where the failure is more general or syntax expectations are violated, it may make
-  more sense to use a fragment that completes a sentence beginning, "The parser ...".
-
-  For example:
-
-  > "Expected an expression after `(`"
-  > "Cannot parse the expression"
-
-
-  ## Message style guide
-
-  - Use articles like "a", "an", and "the" when appropriate.
-    - e.g., prefer "Cannot parse the expression" to "Cannot parse expression".
-  - Use the common name for tokens and nodes.
-    - e.g., prefer "keyword splat" to "assoc splat"
-    - e.g., prefer "embedded document" to "embdoc"
-  - Capitalize the initial word of the message.
-  - Use back ticks around token literals
-    - e.g., "Expected a `=>` between the hash key and value"
-  - Do not use `.` or other punctuation at the end of the message.
-  - Do not use contractions like "can't". Prefer "cannot" to "can not".
-  - For tokens that can have multiple meanings, reference the token and its meaning.
-    - e.g., "`*` splat argument" is clearer and more complete than "splat argument" or "`*` argument"
-
-
-  ## Error names (YP_ERR_*)
-
-  - When appropriate, prefer node name to token name.
-    - e.g., prefer "SPLAT" to "STAR" in the context of argument parsing.
-  - Prefer token name to common name.
-    - e.g., prefer "STAR" to "ASTERISK".
-  - Try to order the words in the name from more general to more specific,
-    - e.g., "INVALID_NUMBER_DECIMAL" is better than "DECIMAL_INVALID_NUMBER".
-    - When in doubt, look for similar patterns and name them so that they are grouped when lexically
-      sorted. See YP_ERR_ARGUMENT_NO_FORWARDING_* for an example.
-*/
-
-static const char* const diagnostic_messages[YP_DIAGNOSTIC_ID_LEN] = {
-    [YP_ERR_ALIAS_ARGUMENT]                     = "Invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable",
-    [YP_ERR_AMPAMPEQ_MULTI_ASSIGN]              = "Unexpected `&&=` in a multiple assignment",
-    [YP_ERR_ARGUMENT_AFTER_BLOCK]               = "Unexpected argument after a block argument",
-    [YP_ERR_ARGUMENT_BARE_HASH]                 = "Unexpected bare hash argument",
-    [YP_ERR_ARGUMENT_BLOCK_MULTI]               = "Multiple block arguments; only one block is allowed",
-    [YP_ERR_ARGUMENT_FORMAL_CLASS]              = "Invalid formal argument; formal argument cannot be a class variable",
-    [YP_ERR_ARGUMENT_FORMAL_CONSTANT]           = "Invalid formal argument; formal argument cannot be a constant",
-    [YP_ERR_ARGUMENT_FORMAL_GLOBAL]             = "Invalid formal argument; formal argument cannot be a global variable",
-    [YP_ERR_ARGUMENT_FORMAL_IVAR]               = "Invalid formal argument; formal argument cannot be an instance variable",
-    [YP_ERR_ARGUMENT_NO_FORWARDING_AMP]         = "Unexpected `&` when the parent method is not forwarding",
-    [YP_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES]    = "Unexpected `...` when the parent method is not forwarding",
-    [YP_ERR_ARGUMENT_NO_FORWARDING_STAR]        = "Unexpected `*` when the parent method is not forwarding",
-    [YP_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT]   = "Unexpected `*` splat argument after a `**` keyword splat argument",
-    [YP_ERR_ARGUMENT_SPLAT_AFTER_SPLAT]         = "Unexpected `*` splat argument after a `*` splat argument",
-    [YP_ERR_ARGUMENT_TERM_PAREN]                = "Expected a `)` to close the arguments",
-    [YP_ERR_ARRAY_ELEMENT]                      = "Expected an element for the array",
-    [YP_ERR_ARRAY_EXPRESSION]                   = "Expected an expression for the array element",
-    [YP_ERR_ARRAY_EXPRESSION_AFTER_STAR]        = "Expected an expression after `*` in the array",
-    [YP_ERR_ARRAY_SEPARATOR]                    = "Expected a `,` separator for the array elements",
-    [YP_ERR_ARRAY_TERM]                         = "Expected a `]` to close the array",
-    [YP_ERR_BEGIN_LONELY_ELSE]                  = "Unexpected `else` in `begin` block; a `rescue` clause must precede `else`",
-    [YP_ERR_BEGIN_TERM]                         = "Expected an `end` to close the `begin` statement",
-    [YP_ERR_BEGIN_UPCASE_BRACE]                 = "Expected a `{` after `BEGIN`",
-    [YP_ERR_BEGIN_UPCASE_TERM]                  = "Expected a `}` to close the `BEGIN` statement",
-    [YP_ERR_BLOCK_PARAM_LOCAL_VARIABLE]         = "Expected a local variable name in the block parameters",
-    [YP_ERR_BLOCK_PARAM_PIPE_TERM]              = "Expected the block parameters to end with `|`",
-    [YP_ERR_BLOCK_TERM_BRACE]                   = "Expected a block beginning with `{` to end with `}`",
-    [YP_ERR_BLOCK_TERM_END]                     = "Expected a block beginning with `do` to end with `end`",
-    [YP_ERR_CANNOT_PARSE_EXPRESSION]            = "Cannot parse the expression",
-    [YP_ERR_CANNOT_PARSE_STRING_PART]           = "Cannot parse the string part",
-    [YP_ERR_CASE_EXPRESSION_AFTER_CASE]         = "Expected an expression after `case`",
-    [YP_ERR_CASE_EXPRESSION_AFTER_WHEN]         = "Expected an expression after `when`",
-    [YP_ERR_CASE_LONELY_ELSE]                   = "Unexpected `else` in `case` statement; a `when` clause must precede `else`",
-    [YP_ERR_CASE_TERM]                          = "Expected an `end` to close the `case` statement",
-    [YP_ERR_CLASS_IN_METHOD]                    = "Unexpected class definition in a method body",
-    [YP_ERR_CLASS_NAME]                         = "Expected a constant name after `class`",
-    [YP_ERR_CLASS_SUPERCLASS]                   = "Expected a superclass after `<`",
-    [YP_ERR_CLASS_TERM]                         = "Expected an `end` to close the `class` statement",
-    [YP_ERR_CONDITIONAL_ELSIF_PREDICATE]        = "Expected a predicate expression for the `elsif` statement",
-    [YP_ERR_CONDITIONAL_IF_PREDICATE]           = "Expected a predicate expression for the `if` statement",
-    [YP_ERR_CONDITIONAL_TERM]                   = "Expected an `end` to close the conditional clause",
-    [YP_ERR_CONDITIONAL_TERM_ELSE]              = "Expected an `end` to close the `else` clause",
-    [YP_ERR_CONDITIONAL_UNLESS_PREDICATE]       = "Expected a predicate expression for the `unless` statement",
-    [YP_ERR_CONDITIONAL_UNTIL_PREDICATE]        = "Expected a predicate expression for the `until` statement",
-    [YP_ERR_CONDITIONAL_WHILE_PREDICATE]        = "Expected a predicate expression for the `while` statement",
-    [YP_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = "Expected a constant after the `::` operator",
-    [YP_ERR_DEF_ENDLESS]                        = "Could not parse the endless method body",
-    [YP_ERR_DEF_ENDLESS_SETTER]                 = "Invalid method name; a setter method cannot be defined in an endless method definition",
-    [YP_ERR_DEF_NAME]                           = "Expected a method name",
-    [YP_ERR_DEF_NAME_AFTER_RECEIVER]            = "Expected a method name after the receiver",
-    [YP_ERR_DEF_PARAMS_TERM]                    = "Expected a delimiter to close the parameters",
-    [YP_ERR_DEF_PARAMS_TERM_PAREN]              = "Expected a `)` to close the parameters",
-    [YP_ERR_DEF_RECEIVER]                       = "Expected a receiver for the method definition",
-    [YP_ERR_DEF_RECEIVER_TERM]                  = "Expected a `.` or `::` after the receiver in a method definition",
-    [YP_ERR_DEF_TERM]                           = "Expected an `end` to close the `def` statement",
-    [YP_ERR_DEFINED_EXPRESSION]                 = "Expected an expression after `defined?`",
-    [YP_ERR_EMBDOC_TERM]                        = "Could not find a terminator for the embedded document",
-    [YP_ERR_EMBEXPR_END]                        = "Expected a `}` to close the embedded expression",
-    [YP_ERR_EMBVAR_INVALID]                     = "Invalid embedded variable",
-    [YP_ERR_END_UPCASE_BRACE]                   = "Expected a `{` after `END`",
-    [YP_ERR_END_UPCASE_TERM]                    = "Expected a `}` to close the `END` statement",
-    [YP_ERR_ESCAPE_INVALID_CONTROL]             = "Invalid control escape sequence",
-    [YP_ERR_ESCAPE_INVALID_CONTROL_REPEAT]      = "Invalid control escape sequence; control cannot be repeated",
-    [YP_ERR_ESCAPE_INVALID_HEXADECIMAL]         = "Invalid hexadecimal escape sequence",
-    [YP_ERR_ESCAPE_INVALID_META]                = "Invalid meta escape sequence",
-    [YP_ERR_ESCAPE_INVALID_META_REPEAT]         = "Invalid meta escape sequence; meta cannot be repeated",
-    [YP_ERR_ESCAPE_INVALID_UNICODE]             = "Invalid Unicode escape sequence",
-    [YP_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS]    = "Invalid Unicode escape sequence; Unicode cannot be combined with control or meta flags",
-    [YP_ERR_ESCAPE_INVALID_UNICODE_LITERAL]     = "Invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal",
-    [YP_ERR_ESCAPE_INVALID_UNICODE_LONG]        = "Invalid Unicode escape sequence; maximum length is 6 digits",
-    [YP_ERR_ESCAPE_INVALID_UNICODE_TERM]        = "Invalid Unicode escape sequence; needs closing `}`",
-    [YP_ERR_EXPECT_ARGUMENT]                    = "Expected an argument",
-    [YP_ERR_EXPECT_EOL_AFTER_STATEMENT]         = "Expected a newline or semicolon after the statement",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ]   = "Expected an expression after `&&=`",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = "Expected an expression after `||=`",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_COMMA]      = "Expected an expression after `,`",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_EQUAL]      = "Expected an expression after `=`",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS]  = "Expected an expression after `<<`",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_LPAREN]     = "Expected an expression after `(`",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR]   = "Expected an expression after the operator",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_SPLAT]      = "Expected an expression after `*` splat in an argument",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH] = "Expected an expression after `**` in a hash",
-    [YP_ERR_EXPECT_EXPRESSION_AFTER_STAR]       = "Expected an expression after `*`",
-    [YP_ERR_EXPECT_IDENT_REQ_PARAMETER]         = "Expected an identifier for the required parameter",
-    [YP_ERR_EXPECT_LPAREN_REQ_PARAMETER]        = "Expected a `(` to start a required parameter",
-    [YP_ERR_EXPECT_RBRACKET]                    = "Expected a matching `]`",
-    [YP_ERR_EXPECT_RPAREN]                      = "Expected a matching `)`",
-    [YP_ERR_EXPECT_RPAREN_AFTER_MULTI]          = "Expected a `)` after multiple assignment",
-    [YP_ERR_EXPECT_RPAREN_REQ_PARAMETER]        = "Expected a `)` to end a required parameter",
-    [YP_ERR_EXPECT_STRING_CONTENT]              = "Expected string content after opening string delimiter",
-    [YP_ERR_EXPECT_WHEN_DELIMITER]              = "Expected a delimiter after the predicates of a `when` clause",
-    [YP_ERR_EXPRESSION_BARE_HASH]               = "Unexpected bare hash in expression",
-    [YP_ERR_FOR_COLLECTION]                     = "Expected a collection after the `in` in a `for` statement",
-    [YP_ERR_FOR_INDEX]                          = "Expected an index after `for`",
-    [YP_ERR_FOR_IN]                             = "Expected an `in` after the index in a `for` statement",
-    [YP_ERR_FOR_TERM]                           = "Expected an `end` to close the `for` loop",
-    [YP_ERR_HASH_EXPRESSION_AFTER_LABEL]        = "Expected an expression after the label in a hash",
-    [YP_ERR_HASH_KEY]                           = "Expected a key in the hash literal",
-    [YP_ERR_HASH_ROCKET]                        = "Expected a `=>` between the hash key and value",
-    [YP_ERR_HASH_TERM]                          = "Expected a `}` to close the hash literal",
-    [YP_ERR_HASH_VALUE]                         = "Expected a value in the hash literal",
-    [YP_ERR_HEREDOC_TERM]                       = "Could not find a terminator for the heredoc",
-    [YP_ERR_INCOMPLETE_QUESTION_MARK]           = "Incomplete expression at `?`",
-    [YP_ERR_INCOMPLETE_VARIABLE_CLASS]          = "Incomplete class variable",
-    [YP_ERR_INCOMPLETE_VARIABLE_INSTANCE]       = "Incomplete instance variable",
-    [YP_ERR_INVALID_ENCODING_MAGIC_COMMENT]     = "Unknown or invalid encoding in the magic comment",
-    [YP_ERR_INVALID_FLOAT_EXPONENT]             = "Invalid exponent",
-    [YP_ERR_INVALID_NUMBER_BINARY]              = "Invalid binary number",
-    [YP_ERR_INVALID_NUMBER_DECIMAL]             = "Invalid decimal number",
-    [YP_ERR_INVALID_NUMBER_HEXADECIMAL]         = "Invalid hexadecimal number",
-    [YP_ERR_INVALID_NUMBER_OCTAL]               = "Invalid octal number",
-    [YP_ERR_INVALID_PERCENT]                    = "Invalid `%` token", // TODO WHAT?
-    [YP_ERR_INVALID_TOKEN]                      = "Invalid token", // TODO WHAT?
-    [YP_ERR_INVALID_VARIABLE_GLOBAL]            = "Invalid global variable",
-    [YP_ERR_LAMBDA_OPEN]                        = "Expected a `do` keyword or a `{` to open the lambda block",
-    [YP_ERR_LAMBDA_TERM_BRACE]                  = "Expected a lambda block beginning with `{` to end with `}`",
-    [YP_ERR_LAMBDA_TERM_END]                    = "Expected a lambda block beginning with `do` to end with `end`",
-    [YP_ERR_LIST_I_LOWER_ELEMENT]               = "Expected a symbol in a `%i` list",
-    [YP_ERR_LIST_I_LOWER_TERM]                  = "Expected a closing delimiter for the `%i` list",
-    [YP_ERR_LIST_I_UPPER_ELEMENT]               = "Expected a symbol in a `%I` list",
-    [YP_ERR_LIST_I_UPPER_TERM]                  = "Expected a closing delimiter for the `%I` list",
-    [YP_ERR_LIST_W_LOWER_ELEMENT]               = "Expected a string in a `%w` list",
-    [YP_ERR_LIST_W_LOWER_TERM]                  = "Expected a closing delimiter for the `%w` list",
-    [YP_ERR_LIST_W_UPPER_ELEMENT]               = "Expected a string in a `%W` list",
-    [YP_ERR_LIST_W_UPPER_TERM]                  = "Expected a closing delimiter for the `%W` list",
-    [YP_ERR_MALLOC_FAILED]                      = "Failed to allocate memory",
-    [YP_ERR_MODULE_IN_METHOD]                   = "Unexpected module definition in a method body",
-    [YP_ERR_MODULE_NAME]                        = "Expected a constant name after `module`",
-    [YP_ERR_MODULE_TERM]                        = "Expected an `end` to close the `module` statement",
-    [YP_ERR_MULTI_ASSIGN_MULTI_SPLATS]          = "Multiple splats in multiple assignment",
-    [YP_ERR_NOT_EXPRESSION]                     = "Expected an expression after `not`",
-    [YP_ERR_NUMBER_LITERAL_UNDERSCORE]          = "Number literal ending with a `_`",
-    [YP_ERR_OPERATOR_MULTI_ASSIGN]              = "Unexpected operator for a multiple assignment",
-    [YP_ERR_PARAMETER_ASSOC_SPLAT_MULTI]        = "Unexpected multiple `**` splat parameters",
-    [YP_ERR_PARAMETER_BLOCK_MULTI]              = "Multiple block parameters; only one block is allowed",
-    [YP_ERR_PARAMETER_NAME_REPEAT]              = "Repeated parameter name",
-    [YP_ERR_PARAMETER_NO_DEFAULT]               = "Expected a default value for the parameter",
-    [YP_ERR_PARAMETER_NO_DEFAULT_KW]            = "Expected a default value for the keyword parameter",
-    [YP_ERR_PARAMETER_NUMBERED_RESERVED]        = "Token reserved for a numbered parameter",
-    [YP_ERR_PARAMETER_ORDER]                    = "Unexpected parameter order",
-    [YP_ERR_PARAMETER_SPLAT_MULTI]              = "Unexpected multiple `*` splat parameters",
-    [YP_ERR_PARAMETER_STAR]                     = "Unexpected parameter `*`",
-    [YP_ERR_PARAMETER_WILD_LOOSE_COMMA]         = "Unexpected `,` in parameters",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_BRACKET]   = "Expected a pattern expression after the `[` operator",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_COMMA]     = "Expected a pattern expression after `,`",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_HROCKET]   = "Expected a pattern expression after `=>`",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_IN]        = "Expected a pattern expression after the `in` keyword",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_KEY]       = "Expected a pattern expression after the key",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_PAREN]     = "Expected a pattern expression after the `(` operator",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_PIN]       = "Expected a pattern expression after the `^` pin operator",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_PIPE]      = "Expected a pattern expression after the `|` operator",
-    [YP_ERR_PATTERN_EXPRESSION_AFTER_RANGE]     = "Expected a pattern expression after the range operator",
-    [YP_ERR_PATTERN_HASH_KEY]                   = "Expected a key in the hash pattern",
-    [YP_ERR_PATTERN_HASH_KEY_LABEL]             = "Expected a label as the key in the hash pattern", // TODO // THIS // AND // ABOVE // IS WEIRD
-    [YP_ERR_PATTERN_IDENT_AFTER_HROCKET]        = "Expected an identifier after the `=>` operator",
-    [YP_ERR_PATTERN_LABEL_AFTER_COMMA]          = "Expected a label after the `,` in the hash pattern",
-    [YP_ERR_PATTERN_REST]                       = "Unexpected rest pattern",
-    [YP_ERR_PATTERN_TERM_BRACE]                 = "Expected a `}` to close the pattern expression",
-    [YP_ERR_PATTERN_TERM_BRACKET]               = "Expected a `]` to close the pattern expression",
-    [YP_ERR_PATTERN_TERM_PAREN]                 = "Expected a `)` to close the pattern expression",
-    [YP_ERR_PIPEPIPEEQ_MULTI_ASSIGN]            = "Unexpected `||=` in a multiple assignment",
-    [YP_ERR_REGEXP_TERM]                        = "Expected a closing delimiter for the regular expression",
-    [YP_ERR_RESCUE_EXPRESSION]                  = "Expected a rescued expression",
-    [YP_ERR_RESCUE_MODIFIER_VALUE]              = "Expected a value after the `rescue` modifier",
-    [YP_ERR_RESCUE_TERM]                        = "Expected a closing delimiter for the `rescue` clause",
-    [YP_ERR_RESCUE_VARIABLE]                    = "Expected an exception variable after `=>` in a rescue statement",
-    [YP_ERR_RETURN_INVALID]                     = "Invalid `return` in a class or module body",
-    [YP_ERR_STRING_CONCATENATION]               = "Expected a string for concatenation",
-    [YP_ERR_STRING_INTERPOLATED_TERM]           = "Expected a closing delimiter for the interpolated string",
-    [YP_ERR_STRING_LITERAL_TERM]                = "Expected a closing delimiter for the string literal",
-    [YP_ERR_SYMBOL_INVALID]                     = "Invalid symbol", // TODO expected symbol? yarp.c ~9719
-    [YP_ERR_SYMBOL_TERM_DYNAMIC]                = "Expected a closing delimiter for the dynamic symbol",
-    [YP_ERR_SYMBOL_TERM_INTERPOLATED]           = "Expected a closing delimiter for the interpolated symbol",
-    [YP_ERR_TERNARY_COLON]                      = "Expected a `:` after the true expression of a ternary operator",
-    [YP_ERR_TERNARY_EXPRESSION_FALSE]           = "Expected an expression after `:` in the ternary operator",
-    [YP_ERR_TERNARY_EXPRESSION_TRUE]            = "Expected an expression after `?` in the ternary operator",
-    [YP_ERR_UNDEF_ARGUMENT]                     = "Invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument",
-    [YP_ERR_UNARY_RECEIVER_BANG]                = "Expected a receiver for unary `!`",
-    [YP_ERR_UNARY_RECEIVER_MINUS]               = "Expected a receiver for unary `-`",
-    [YP_ERR_UNARY_RECEIVER_PLUS]                = "Expected a receiver for unary `+`",
-    [YP_ERR_UNARY_RECEIVER_TILDE]               = "Expected a receiver for unary `~`",
-    [YP_ERR_UNTIL_TERM]                         = "Expected an `end` to close the `until` statement",
-    [YP_ERR_WHILE_TERM]                         = "Expected an `end` to close the `while` statement",
-    [YP_ERR_WRITE_TARGET_READONLY]              = "Immutable variable as a write target",
-    [YP_ERR_WRITE_TARGET_UNEXPECTED]            = "Unexpected write target",
-    [YP_ERR_XSTRING_TERM]                       = "Expected a closing delimiter for the `%x` or backtick string",
-    [YP_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS]    = "Ambiguous first argument; put parentheses or a space even after `-` operator",
-    [YP_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS]     = "Ambiguous first argument; put parentheses or a space even after `+` operator",
-    [YP_WARN_AMBIGUOUS_PREFIX_STAR]             = "Ambiguous `*` has been interpreted as an argument prefix",
-    [YP_WARN_AMBIGUOUS_SLASH]                   = "Ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator",
-};
-
-static const char*
-yp_diagnostic_message(yp_diagnostic_id_t diag_id) {
-    assert(diag_id < YP_DIAGNOSTIC_ID_LEN);
-    const char *message = diagnostic_messages[diag_id];
-    assert(message);
-    return message;
-}
-
 // Append an error to the given list of diagnostic.
 bool
-yp_diagnostic_list_append(yp_list_t *list, const uint8_t *start, const uint8_t *end, yp_diagnostic_id_t diag_id) {
+yp_diagnostic_list_append(yp_list_t *list, const uint8_t *start, const uint8_t *end, const char *message) {
     yp_diagnostic_t *diagnostic = (yp_diagnostic_t *) malloc(sizeof(yp_diagnostic_t));
     if (diagnostic == NULL) return false;
 
-    *diagnostic = (yp_diagnostic_t) { .start = start, .end = end, .message = yp_diagnostic_message(diag_id) };
+    *diagnostic = (yp_diagnostic_t) { .start = start, .end = end, .message = message };
     yp_list_append(list, (yp_list_node_t *) diagnostic);
     return true;
 }
diff --git a/src/unescape.c b/src/unescape.c
index 9da52eae..830c5996 100644
--- a/src/unescape.c
+++ b/src/unescape.c
@@ -94,7 +94,7 @@ static inline size_t
 unescape_hexadecimal(const uint8_t *backslash, uint8_t *value, const uint8_t *end, yp_list_t *error_list) {
     *value = 0;
     if (backslash + 2 >= end || !yp_char_is_hexadecimal_digit(backslash[2])) {
-        if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, YP_ERR_ESCAPE_INVALID_HEXADECIMAL);
+        if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, "Invalid hex escape.");
         return 2;
     }
     *value = unescape_hexadecimal_digit(backslash[2]);
@@ -157,7 +157,7 @@ unescape_unicode_write(uint8_t *dest, uint32_t value, const uint8_t *start, cons
     // If we get here, then the value is too big. This is an error, but we don't
     // want to just crash, so instead we'll add an error to the error list and put
     // in a replacement character instead.
-    if (error_list) yp_diagnostic_list_append(error_list, start, end, YP_ERR_ESCAPE_INVALID_UNICODE);
+    if (error_list) yp_diagnostic_list_append(error_list, start, end, "Invalid Unicode escape sequence.");
     dest[0] = 0xEF;
     dest[1] = 0xBF;
     dest[2] = 0xBD;
@@ -235,7 +235,7 @@ unescape(
         // \unnnn       Unicode character, where nnnn is exactly 4 hexadecimal digits ([0-9a-fA-F])
         case 'u': {
             if ((flags & YP_UNESCAPE_FLAG_CONTROL) | (flags & YP_UNESCAPE_FLAG_META)) {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, YP_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, "Unicode escape sequence cannot be used with control or meta flags.");
                 return backslash + 2;
             }
 
@@ -252,11 +252,11 @@ unescape(
 
                     // \u{nnnn} character literal allows only 1-6 hexadecimal digits
                     if (hexadecimal_length > 6) {
-                        if (error_list) yp_diagnostic_list_append(error_list, unicode_cursor, unicode_cursor + hexadecimal_length, YP_ERR_ESCAPE_INVALID_UNICODE_LONG);
+                        if (error_list) yp_diagnostic_list_append(error_list, unicode_cursor, unicode_cursor + hexadecimal_length, "invalid Unicode escape.");
                     }
                     // there are not hexadecimal characters
                     else if (hexadecimal_length == 0) {
-                        if (error_list) yp_diagnostic_list_append(error_list, unicode_cursor, unicode_cursor + hexadecimal_length, YP_ERR_ESCAPE_INVALID_UNICODE);
+                        if (error_list) yp_diagnostic_list_append(error_list, unicode_cursor, unicode_cursor + hexadecimal_length, "unterminated Unicode escape");
                         return unicode_cursor;
                     }
 
@@ -277,13 +277,13 @@ unescape(
 
                 // ?\u{nnnn} character literal should contain only one codepoint and cannot be like ?\u{nnnn mmmm}
                 if (flags & YP_UNESCAPE_FLAG_EXPECT_SINGLE && codepoints_count > 1) {
-                    if (error_list) yp_diagnostic_list_append(error_list, extra_codepoints_start, unicode_cursor - 1, YP_ERR_ESCAPE_INVALID_UNICODE_LITERAL);
+                    if (error_list) yp_diagnostic_list_append(error_list, extra_codepoints_start, unicode_cursor - 1, "Multiple codepoints at single character literal");
                 }
 
                 if (unicode_cursor < end && *unicode_cursor == '}') {
                     unicode_cursor++;
                 } else {
-                    if (error_list) yp_diagnostic_list_append(error_list, backslash, unicode_cursor, YP_ERR_ESCAPE_INVALID_UNICODE_TERM);
+                    if (error_list) yp_diagnostic_list_append(error_list, backslash, unicode_cursor, "invalid Unicode escape.");
                 }
 
                 return unicode_cursor;
@@ -298,7 +298,7 @@ unescape(
                 return backslash + 6;
             }
 
-            if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, YP_ERR_ESCAPE_INVALID_UNICODE);
+            if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, "Invalid Unicode escape sequence");
             return backslash + 2;
         }
         // \c\M-x       meta control character, where x is an ASCII printable character
@@ -306,12 +306,12 @@ unescape(
         // \cx          control character, where x is an ASCII printable character
         case 'c':
             if (backslash + 2 >= end) {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, YP_ERR_ESCAPE_INVALID_CONTROL);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, "Invalid control escape sequence");
                 return end;
             }
 
             if (flags & YP_UNESCAPE_FLAG_CONTROL) {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, YP_ERR_ESCAPE_INVALID_CONTROL_REPEAT);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, "Control escape sequence cannot be doubled.");
                 return backslash + 2;
             }
 
@@ -325,7 +325,7 @@ unescape(
                     return backslash + 3;
                 default: {
                     if (!char_is_ascii_printable(backslash[2])) {
-                        if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, YP_ERR_ESCAPE_INVALID_CONTROL);
+                        if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, "Invalid control escape sequence");
                         return backslash + 2;
                     }
 
@@ -339,17 +339,17 @@ unescape(
         // \C-?         delete, ASCII 7Fh (DEL)
         case 'C':
             if (backslash + 3 >= end) {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, YP_ERR_ESCAPE_INVALID_CONTROL);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, "Invalid control escape sequence");
                 return end;
             }
 
             if (flags & YP_UNESCAPE_FLAG_CONTROL) {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, YP_ERR_ESCAPE_INVALID_CONTROL_REPEAT);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, "Control escape sequence cannot be doubled.");
                 return backslash + 2;
             }
 
             if (backslash[2] != '-') {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, YP_ERR_ESCAPE_INVALID_CONTROL);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, "Invalid control escape sequence");
                 return backslash + 2;
             }
 
@@ -363,7 +363,7 @@ unescape(
                     return backslash + 4;
                 default:
                     if (!char_is_ascii_printable(backslash[3])) {
-                        if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, YP_ERR_ESCAPE_INVALID_CONTROL);
+                        if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, "Invalid control escape sequence");
                         return backslash + 2;
                     }
 
@@ -377,17 +377,17 @@ unescape(
         // \M-x         meta character, where x is an ASCII printable character
         case 'M': {
             if (backslash + 3 >= end) {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, YP_ERR_ESCAPE_INVALID_META);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 1, "Invalid control escape sequence");
                 return end;
             }
 
             if (flags & YP_UNESCAPE_FLAG_META) {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, YP_ERR_ESCAPE_INVALID_META_REPEAT);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, "Meta escape sequence cannot be doubled.");
                 return backslash + 2;
             }
 
             if (backslash[2] != '-') {
-                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, YP_ERR_ESCAPE_INVALID_META);
+                if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, "Invalid meta escape sequence");
                 return backslash + 2;
             }
 
@@ -402,7 +402,7 @@ unescape(
                 return backslash + 4;
             }
 
-            if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, YP_ERR_ESCAPE_INVALID_META);
+            if (error_list) yp_diagnostic_list_append(error_list, backslash, backslash + 2, "Invalid meta escape sequence");
             return backslash + 3;
         }
         // \n
@@ -474,7 +474,7 @@ yp_unescape_manipulate_string_or_char_literal(yp_parser_t *parser, yp_string_t *
     // within the string.
     uint8_t *allocated = malloc(string->length);
     if (allocated == NULL) {
-        yp_diagnostic_list_append(&parser->error_list, string->source, string->source + string->length, YP_ERR_MALLOC_FAILED);
+        yp_diagnostic_list_append(&parser->error_list, string->source, string->source + string->length, "Failed to allocate memory for unescaping.");
         return;
     }
 
diff --git a/src/yarp.c b/src/yarp.c
index 07ee4d8a..cb2bf185 100644
--- a/src/yarp.c
+++ b/src/yarp.c
@@ -549,7 +549,7 @@ yp_arguments_validate(yp_parser_t *parser, yp_arguments_t *arguments) {
             &parser->error_list,
             arguments->block->base.location.start,
             arguments->block->base.location.end,
-            YP_ERR_ARGUMENT_BLOCK_MULTI
+            "both block arg and actual block given"
         );
     }
 }
@@ -643,14 +643,14 @@ parse_decimal_number(yp_parser_t *parser, const uint8_t *start, const uint8_t *e
     unsigned long value = strtoul(digits, &endptr, 10);
 
     if ((digits == endptr) || (*endptr != '\0') || (errno == ERANGE)) {
-        yp_diagnostic_list_append(&parser->error_list, start, end, YP_ERR_INVALID_NUMBER_DECIMAL);
+        yp_diagnostic_list_append(&parser->error_list, start, end, "invalid decimal number");
         value = UINT32_MAX;
     }
 
     free(digits);
 
     if (value > UINT32_MAX) {
-        yp_diagnostic_list_append(&parser->error_list, start, end, YP_ERR_INVALID_NUMBER_DECIMAL);
+        yp_diagnostic_list_append(&parser->error_list, start, end, "invalid decimal number");
         value = UINT32_MAX;
     }
 
@@ -4637,7 +4637,7 @@ yp_parser_parameter_name_check(yp_parser_t *parser, yp_token_t *name) {
     yp_constant_id_t constant_id = yp_parser_constant_id_token(parser, name);
 
     if (yp_constant_id_list_includes(&parser->current_scope->locals, constant_id)) {
-        yp_diagnostic_list_append(&parser->error_list, name->start, name->end, YP_ERR_PARAMETER_NAME_REPEAT);
+        yp_diagnostic_list_append(&parser->error_list, name->start, name->end, "Duplicated parameter name.");
     }
 }
 
@@ -4980,7 +4980,7 @@ parser_lex_encoding_comment(yp_parser_t *parser) {
     // didn't understand the encoding that the user was trying to use. In this
     // case we'll keep using the default encoding but add an error to the
     // parser to indicate an unsuccessful parse.
-    yp_diagnostic_list_append(&parser->error_list, encoding_start, encoding_end, YP_ERR_INVALID_ENCODING_MAGIC_COMMENT);
+    yp_diagnostic_list_append(&parser->error_list, encoding_start, encoding_end, "Could not understand the encoding specified in the magic comment.");
 }
 
 /******************************************************************************/
@@ -5140,7 +5140,7 @@ lex_optional_float_suffix(yp_parser_t *parser) {
             parser->current.end += yp_strspn_decimal_number(parser->current.end, parser->end - parser->current.end);
             type = YP_TOKEN_FLOAT;
         } else {
-            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_FLOAT_EXPONENT);
+            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Missing exponent.");
             type = YP_TOKEN_FLOAT;
         }
     }
@@ -5161,7 +5161,7 @@ lex_numeric_prefix(yp_parser_t *parser) {
                 if (yp_char_is_decimal_digit(peek(parser))) {
                     parser->current.end += yp_strspn_decimal_number(parser->current.end, parser->end - parser->current.end);
                 } else {
-                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_NUMBER_DECIMAL);
+                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid decimal number.");
                 }
 
                 break;
@@ -5173,7 +5173,7 @@ lex_numeric_prefix(yp_parser_t *parser) {
                 if (yp_char_is_binary_digit(peek(parser))) {
                     parser->current.end += yp_strspn_binary_number(parser->current.end, parser->end - parser->current.end);
                 } else {
-                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_NUMBER_BINARY);
+                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid binary number.");
                 }
 
                 break;
@@ -5185,7 +5185,7 @@ lex_numeric_prefix(yp_parser_t *parser) {
                 if (yp_char_is_octal_digit(peek(parser))) {
                     parser->current.end += yp_strspn_octal_number(parser->current.end, parser->end - parser->current.end);
                 } else {
-                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_NUMBER_OCTAL);
+                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid octal number.");
                 }
 
                 break;
@@ -5210,7 +5210,7 @@ lex_numeric_prefix(yp_parser_t *parser) {
                 if (yp_char_is_hexadecimal_digit(peek(parser))) {
                     parser->current.end += yp_strspn_hexadecimal_number(parser->current.end, parser->end - parser->current.end);
                 } else {
-                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_NUMBER_HEXADECIMAL);
+                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid hexadecimal number.");
                 }
 
                 break;
@@ -5240,7 +5240,7 @@ lex_numeric_prefix(yp_parser_t *parser) {
     // If the last character that we consumed was an underscore, then this is
     // actually an invalid integer value, and we should return an invalid token.
     if (peek_offset(parser, -1) == '_') {
-        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_NUMBER_LITERAL_UNDERSCORE);
+        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Number literal cannot end with a `_`.");
     }
 
     return type;
@@ -5292,7 +5292,7 @@ lex_numeric(yp_parser_t *parser) {
 static yp_token_type_t
 lex_global_variable(yp_parser_t *parser) {
     if (parser->current.end >= parser->end) {
-        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_VARIABLE_GLOBAL);
+        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid global variable.");
         return YP_TOKEN_GLOBAL_VARIABLE;
     }
 
@@ -5333,7 +5333,7 @@ lex_global_variable(yp_parser_t *parser) {
                 } while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0);
 
                 // $0 isn't allowed to be followed by anything.
-                yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_VARIABLE_GLOBAL);
+                yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid global variable.");
             }
 
             return YP_TOKEN_GLOBAL_VARIABLE;
@@ -5364,7 +5364,7 @@ lex_global_variable(yp_parser_t *parser) {
             } else {
                 // If we get here, then we have a $ followed by something that isn't
                 // recognized as a global variable.
-                yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_VARIABLE_GLOBAL);
+                yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid global variable.");
             }
 
             return YP_TOKEN_GLOBAL_VARIABLE;
@@ -5704,7 +5704,7 @@ lex_question_mark(yp_parser_t *parser) {
     }
 
     if (parser->current.end >= parser->end) {
-        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INCOMPLETE_QUESTION_MARK);
+        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Incomplete character syntax.");
         return YP_TOKEN_CHARACTER_LITERAL;
     }
 
@@ -5755,9 +5755,9 @@ lex_at_variable(yp_parser_t *parser) {
             parser->current.end += width;
         }
     } else if (type == YP_TOKEN_CLASS_VARIABLE) {
-        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INCOMPLETE_VARIABLE_CLASS);
+        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Incomplete class variable.");
     } else {
-        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INCOMPLETE_VARIABLE_INSTANCE);
+        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Incomplete instance variable.");
     }
 
     // If we're lexing an embedded variable, then we need to pop back into the
@@ -5856,7 +5856,7 @@ lex_embdoc(yp_parser_t *parser) {
         parser_lex_callback(parser);
     }
 
-    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_EMBDOC_TERM);
+    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Unterminated embdoc");
 
     comment->end = parser->current.end;
     yp_list_append(&parser->comment_list, (yp_list_node_t *) comment);
@@ -6286,7 +6286,7 @@ parser_lex(yp_parser_t *parser) {
                     yp_token_type_t type = YP_TOKEN_STAR;
 
                     if (lex_state_spcarg_p(parser, space_seen)) {
-                        yp_diagnostic_list_append(&parser->warning_list, parser->current.start, parser->current.end, YP_WARN_AMBIGUOUS_PREFIX_STAR);
+                        yp_diagnostic_list_append(&parser->warning_list, parser->current.start, parser->current.end, "`*' interpreted as argument prefix");
                         type = YP_TOKEN_USTAR;
                     } else if (lex_state_beg_p(parser)) {
                         type = YP_TOKEN_USTAR;
@@ -6430,7 +6430,7 @@ parser_lex(yp_parser_t *parser) {
                                         // this is not a valid heredoc declaration. In this case we
                                         // will add an error, but we will still return a heredoc
                                         // start.
-                                        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_EMBDOC_TERM);
+                                        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Unterminated heredoc.");
                                         body_start = parser->end;
                                     } else {
                                         // Otherwise, we want to indicate that the body of the
@@ -6627,7 +6627,7 @@ parser_lex(yp_parser_t *parser) {
                             &parser->warning_list,
                             parser->current.start,
                             parser->current.end,
-                            YP_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS
+                            "ambiguous first argument; put parentheses or a space even after `+` operator"
                         );
                     }
 
@@ -6676,7 +6676,7 @@ parser_lex(yp_parser_t *parser) {
                             &parser->warning_list,
                             parser->current.start,
                             parser->current.end,
-                            YP_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS
+                            "ambiguous first argument; put parentheses or a space even after `-` operator"
                         );
                     }
 
@@ -6774,7 +6774,7 @@ parser_lex(yp_parser_t *parser) {
                     }
 
                     if (lex_state_spcarg_p(parser, space_seen)) {
-                        yp_diagnostic_list_append(&parser->warning_list, parser->current.start, parser->current.end, YP_WARN_AMBIGUOUS_SLASH);
+                        yp_diagnostic_list_append(&parser->warning_list, parser->current.start, parser->current.end, "ambiguity between regexp and two divisions: wrap regexp in parentheses or add a space after `/' operator");
                         lex_mode_push_regexp(parser, '\0', '/');
                         LEX(YP_TOKEN_REGEXP_BEGIN);
                     }
@@ -6813,7 +6813,7 @@ parser_lex(yp_parser_t *parser) {
                     // going to say it's the percent operator because we don't want to move into the
                     // string lex mode unnecessarily.
                     if ((lex_state_beg_p(parser) || lex_state_arg_p(parser)) && (parser->current.end >= parser->end)) {
-                        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_PERCENT);
+                        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Unexpected end of input");
                         LEX(YP_TOKEN_PERCENT);
                     }
 
@@ -6938,7 +6938,7 @@ parser_lex(yp_parser_t *parser) {
                                 // unparseable. In this case we'll just drop it from the parser
                                 // and skip past it and hope that the next token is something
                                 // that we can parse.
-                                yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_PERCENT);
+                                yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid %% token");
                                 goto lex_next_token;
                         }
                     }
@@ -6974,7 +6974,7 @@ parser_lex(yp_parser_t *parser) {
                         // token as we've exhausted all of the other options. We'll skip past
                         // it and return the next token.
                         if (!width) {
-                            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_INVALID_TOKEN);
+                            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid token.");
                             goto lex_next_token;
                         }
 
@@ -7920,17 +7920,17 @@ accept_any(yp_parser_t *parser, size_t count, ...) {
 // valid) and create an artificial token instead. This allows us to recover from
 // the fact that the token isn't present and continue parsing.
 static void
-expect(yp_parser_t *parser, yp_token_type_t type, yp_diagnostic_id_t diag_id) {
+expect(yp_parser_t *parser, yp_token_type_t type, const char *message) {
     if (accept(parser, type)) return;
 
-    yp_diagnostic_list_append(&parser->error_list, parser->previous.end, parser->previous.end, diag_id);
+    yp_diagnostic_list_append(&parser->error_list, parser->previous.end, parser->previous.end, message);
 
     parser->previous =
         (yp_token_t) { .type = YP_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end };
 }
 
 static void
-expect_any(yp_parser_t *parser, yp_diagnostic_id_t diag_id, size_t count, ...) {
+expect_any(yp_parser_t *parser, const char*message, size_t count, ...) {
     va_list types;
     va_start(types, count);
 
@@ -7943,13 +7943,13 @@ expect_any(yp_parser_t *parser, yp_diagnostic_id_t diag_id, size_t count, ...) {
 
     va_end(types);
 
-    yp_diagnostic_list_append(&parser->error_list, parser->previous.end, parser->previous.end, diag_id);
+    yp_diagnostic_list_append(&parser->error_list, parser->previous.end, parser->previous.end, message);
     parser->previous =
         (yp_token_t) { .type = YP_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end };
 }
 
 static yp_node_t *
-parse_expression(yp_parser_t *parser, yp_binding_power_t binding_power, yp_diagnostic_id_t diag_id);
+parse_expression(yp_parser_t *parser, yp_binding_power_t binding_power, const char *message);
 
 // This function controls whether or not we will attempt to parse an expression
 // beginning at the subsequent token. It is used when we are in a context where
@@ -8026,14 +8026,14 @@ token_begins_expression_p(yp_token_type_t type) {
 // Parse an expression with the given binding power that may be optionally
 // prefixed by the * operator.
 static yp_node_t *
-parse_starred_expression(yp_parser_t *parser, yp_binding_power_t binding_power, yp_diagnostic_id_t diag_id) {
+parse_starred_expression(yp_parser_t *parser, yp_binding_power_t binding_power, const char *message) {
     if (accept(parser, YP_TOKEN_USTAR)) {
         yp_token_t operator = parser->previous;
-        yp_node_t *expression = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_STAR);
+        yp_node_t *expression = parse_expression(parser, binding_power, "Expected expression after `*'.");
         return (yp_node_t *) yp_splat_node_create(parser, &operator, expression);
     }
 
-    return parse_expression(parser, binding_power, diag_id);
+    return parse_expression(parser, binding_power, message);
 }
 
 // Convert the given node into a valid target node.
@@ -8059,7 +8059,7 @@ parse_target(yp_parser_t *parser, yp_node_t *target) {
             /* fallthrough */
         case YP_NUMBERED_REFERENCE_READ_NODE:
             assert(sizeof(yp_global_variable_target_node_t) == sizeof(yp_numbered_reference_read_node_t));
-            yp_diagnostic_list_append(&parser->error_list, target->location.start, target->location.end, YP_ERR_WRITE_TARGET_READONLY);
+            yp_diagnostic_list_append(&parser->error_list, target->location.start, target->location.end, "Can't set variable");
             /* fallthrough */
         case YP_GLOBAL_VARIABLE_READ_NODE:
             assert(sizeof(yp_global_variable_target_node_t) == sizeof(yp_global_variable_read_node_t));
@@ -8122,7 +8122,7 @@ parse_target(yp_parser_t *parser, yp_node_t *target) {
                     target->type = YP_LOCAL_VARIABLE_TARGET_NODE;
 
                     if (token_is_numbered_parameter(message.start, message.end)) {
-                        yp_diagnostic_list_append(&parser->error_list, message.start, message.end, YP_ERR_PARAMETER_NUMBERED_RESERVED);
+                        yp_diagnostic_list_append(&parser->error_list, message.start, message.end, "reserved for numbered parameter");
                     }
 
                     return target;
@@ -8166,7 +8166,7 @@ parse_target(yp_parser_t *parser, yp_node_t *target) {
             // In this case we have a node that we don't know how to convert
             // into a target. We need to treat it as an error. For now, we'll
             // mark it as an error and just skip right past it.
-            yp_diagnostic_list_append(&parser->error_list, target->location.start, target->location.end, YP_ERR_WRITE_TARGET_UNEXPECTED);
+            yp_diagnostic_list_append(&parser->error_list, target->location.start, target->location.end, "Unexpected write target.");
             return target;
     }
 }
@@ -8191,7 +8191,7 @@ parse_write(yp_parser_t *parser, yp_node_t *target, yp_token_t *operator, yp_nod
         }
         case YP_BACK_REFERENCE_READ_NODE:
         case YP_NUMBERED_REFERENCE_READ_NODE:
-            yp_diagnostic_list_append(&parser->error_list, target->location.start, target->location.end, YP_ERR_WRITE_TARGET_READONLY);
+            yp_diagnostic_list_append(&parser->error_list, target->location.start, target->location.end, "Can't set variable");
             /* fallthrough */
         case YP_GLOBAL_VARIABLE_READ_NODE: {
             yp_global_variable_write_node_t *node = yp_global_variable_write_node_create(parser, target, operator, value);
@@ -8263,7 +8263,7 @@ parse_write(yp_parser_t *parser, yp_node_t *target, yp_token_t *operator, yp_nod
                     target = (yp_node_t *) yp_local_variable_write_node_create(parser, constant_id, 0, value, &message, operator);
 
                     if (token_is_numbered_parameter(message.start, message.end)) {
-                        yp_diagnostic_list_append(&parser->error_list, message.start, message.end, YP_ERR_PARAMETER_NUMBERED_RESERVED);
+                        yp_diagnostic_list_append(&parser->error_list, message.start, message.end, "reserved for numbered parameter");
                     }
 
                     return target;
@@ -8336,7 +8336,7 @@ parse_write(yp_parser_t *parser, yp_node_t *target, yp_token_t *operator, yp_nod
             // In this case we have a node that we don't know how to convert into a
             // target. We need to treat it as an error. For now, we'll mark it as an
             // error and just skip right past it.
-            yp_diagnostic_list_append(&parser->error_list, operator->start, operator->end, YP_ERR_EXPECT_EXPRESSION_AFTER_EQUAL);
+            yp_diagnostic_list_append(&parser->error_list, operator->start, operator->end, "Unexpected `='.");
             return target;
     }
 }
@@ -8381,14 +8381,14 @@ parse_targets(yp_parser_t *parser, yp_node_t *first_target, yp_binding_power_t b
                 // others yet.
 
                 if (has_splat) {
-                    yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_MULTI_ASSIGN_MULTI_SPLATS);
+                    yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Multiple splats in multi-assignment.");
                 }
 
                 yp_token_t star_operator = parser->previous;
                 yp_node_t *name = NULL;
 
                 if (token_begins_expression_p(parser->current.type)) {
-                    name = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_STAR);
+                    name = parse_expression(parser, binding_power, "Expected an expression after '*'.");
                     name = parse_target(parser, name);
                 }
 
@@ -8401,10 +8401,10 @@ parse_targets(yp_parser_t *parser, yp_node_t *first_target, yp_binding_power_t b
                 // the node when it returns.
 
                 yp_token_t lparen = parser->previous;
-                yp_node_t *first_child_target = parse_expression(parser, YP_BINDING_POWER_STATEMENT, YP_ERR_EXPECT_EXPRESSION_AFTER_LPAREN);
+                yp_node_t *first_child_target = parse_expression(parser, YP_BINDING_POWER_STATEMENT, "Expected an expression after '('.");
                 yp_node_t *child_target = parse_targets(parser, first_child_target, YP_BINDING_POWER_STATEMENT);
 
-                expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN_AFTER_MULTI);
+                expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected an ')' after multi-assignment.");
                 yp_token_t rparen = parser->previous;
 
                 if (YP_NODE_TYPE_P(child_target, YP_MULTI_WRITE_NODE) && first_target == NULL && result->targets.size == 0) {
@@ -8447,7 +8447,7 @@ parse_targets(yp_parser_t *parser, yp_node_t *first_target, yp_binding_power_t b
                         // If we get here, then we weren't able to parse anything at all, so
                         // we need to return a missing node.
                         yp_node_destroy(parser, (yp_node_t *) result);
-                        yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, YP_ERR_FOR_INDEX);
+                        yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, "Expected index after for.");
                         return (yp_node_t *) yp_missing_node_create(parser, operator.start, operator.end);
                     }
 
@@ -8459,7 +8459,7 @@ parse_targets(yp_parser_t *parser, yp_node_t *first_target, yp_binding_power_t b
                     return (yp_node_t *) result;
                 }
 
-                yp_node_t *target = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_COMMA);
+                yp_node_t *target = parse_expression(parser, binding_power, "Expected another expression after ','.");
                 target = parse_target(parser, target);
 
                 yp_multi_write_node_targets_append(result, target);
@@ -8487,7 +8487,7 @@ parse_statements(yp_parser_t *parser, yp_context_t context) {
     context_push(parser, context);
 
     while (true) {
-        yp_node_t *node = parse_expression(parser, YP_BINDING_POWER_STATEMENT, YP_ERR_CANNOT_PARSE_EXPRESSION);
+        yp_node_t *node = parse_expression(parser, YP_BINDING_POWER_STATEMENT, "Expected to be able to parse an expression.");
         yp_statements_node_body_append(statements, node);
 
         // If we're recovering from a syntax error, then we need to stop parsing the
@@ -8532,7 +8532,7 @@ parse_statements(yp_parser_t *parser, yp_context_t context) {
             while (accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON));
             if (context_terminator(context, &parser->current)) break;
         } else {
-            expect(parser, YP_TOKEN_NEWLINE, YP_ERR_EXPECT_EOL_AFTER_STATEMENT);
+            expect(parser, YP_TOKEN_NEWLINE, "Expected a newline or semicolon after statement.");
         }
     }
 
@@ -8555,9 +8555,9 @@ parse_assocs(yp_parser_t *parser, yp_node_t *node) {
                 yp_node_t *value = NULL;
 
                 if (token_begins_expression_p(parser->current.type)) {
-                    value = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH);
+                    value = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected an expression after ** in hash.");
                 } else if (yp_parser_local_depth(parser, &operator) == -1) {
-                    yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, YP_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH);
+                    yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, "Expected an expression after ** in hash.");
                 }
 
                 element = (yp_node_t *) yp_assoc_splat_node_create(parser, value, &operator);
@@ -8571,24 +8571,24 @@ parse_assocs(yp_parser_t *parser, yp_node_t *node) {
                 yp_node_t *value = NULL;
 
                 if (token_begins_expression_p(parser->current.type)) {
-                    value = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_HASH_EXPRESSION_AFTER_LABEL);
+                    value = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected an expression after the label in hash.");
                 }
 
                 element = (yp_node_t *) yp_assoc_node_create(parser, key, &operator, value);
                 break;
             }
             default: {
-                yp_node_t *key = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_HASH_KEY);
+                yp_node_t *key = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a key in the hash literal.");
                 yp_token_t operator;
 
                 if (yp_symbol_node_label_p(key)) {
                     operator = not_provided(parser);
                 } else {
-                    expect(parser, YP_TOKEN_EQUAL_GREATER, YP_ERR_HASH_ROCKET);
+                    expect(parser, YP_TOKEN_EQUAL_GREATER, "Expected a => between the key and the value in the hash.");
                     operator = parser->previous;
                 }
 
-                yp_node_t *value = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_HASH_VALUE);
+                yp_node_t *value = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value in the hash literal.");
                 element = (yp_node_t *) yp_assoc_node_create(parser, key, &operator, value);
                 break;
             }
@@ -8636,7 +8636,7 @@ parse_arguments(yp_parser_t *parser, yp_arguments_t *arguments, bool accepts_for
 
     while (!match_type_p(parser, YP_TOKEN_EOF)) {
         if (parsed_block_argument) {
-            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_ARGUMENT_AFTER_BLOCK);
+            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Unexpected argument after block argument.");
         }
 
         yp_node_t *argument = NULL;
@@ -8645,7 +8645,7 @@ parse_arguments(yp_parser_t *parser, yp_arguments_t *arguments, bool accepts_for
             case YP_TOKEN_USTAR_STAR:
             case YP_TOKEN_LABEL: {
                 if (parsed_bare_hash) {
-                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_ARGUMENT_BARE_HASH);
+                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Unexpected bare hash.");
                 }
 
                 yp_keyword_hash_node_t *hash = yp_keyword_hash_node_create(parser);
@@ -8664,9 +8664,9 @@ parse_arguments(yp_parser_t *parser, yp_arguments_t *arguments, bool accepts_for
                 yp_node_t *expression = NULL;
 
                 if (token_begins_expression_p(parser->current.type)) {
-                    expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_EXPECT_ARGUMENT);
+                    expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected to be able to parse an argument.");
                 } else if (yp_parser_local_depth(parser, &operator) == -1) {
-                    yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, YP_ERR_ARGUMENT_NO_FORWARDING_AMP);
+                    yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, "unexpected & when parent method is not forwarding.");
                 }
 
                 argument = (yp_node_t *)yp_block_argument_node_create(parser, &operator, expression);
@@ -8680,15 +8680,15 @@ parse_arguments(yp_parser_t *parser, yp_arguments_t *arguments, bool accepts_for
 
                 if (match_any_type_p(parser, 2, YP_TOKEN_PARENTHESIS_RIGHT, YP_TOKEN_COMMA)) {
                     if (yp_parser_local_depth(parser, &parser->previous) == -1) {
-                        yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, YP_ERR_ARGUMENT_NO_FORWARDING_STAR);
+                        yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, "unexpected * when parent method is not forwarding.");
                     }
 
                     argument = (yp_node_t *) yp_splat_node_create(parser, &operator, NULL);
                 } else {
-                    yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_EXPECT_EXPRESSION_AFTER_SPLAT);
+                    yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected an expression after '*' in argument.");
 
                     if (parsed_bare_hash) {
-                        yp_diagnostic_list_append(&parser->error_list, operator.start, expression->location.end, YP_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT);
+                        yp_diagnostic_list_append(&parser->error_list, operator.start, expression->location.end, "Unexpected splat argument after double splat.");
                     }
 
                     argument = (yp_node_t *) yp_splat_node_create(parser, &operator, expression);
@@ -8704,11 +8704,11 @@ parse_arguments(yp_parser_t *parser, yp_arguments_t *arguments, bool accepts_for
                         // If the token begins an expression then this ... was not actually
                         // argument forwarding but was instead a range.
                         yp_token_t operator = parser->previous;
-                        yp_node_t *right = parse_expression(parser, YP_BINDING_POWER_RANGE, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                        yp_node_t *right = parse_expression(parser, YP_BINDING_POWER_RANGE, "Expected a value after the operator.");
                         argument = (yp_node_t *) yp_range_node_create(parser, NULL, &operator, right);
                     } else {
                         if (yp_parser_local_depth(parser, &parser->previous) == -1) {
-                            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES);
+                            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "unexpected ... when parent method is not forwarding.");
                         }
 
                         argument = (yp_node_t *)yp_forwarding_arguments_node_create(parser, &parser->previous);
@@ -8719,12 +8719,12 @@ parse_arguments(yp_parser_t *parser, yp_arguments_t *arguments, bool accepts_for
             /* fallthrough */
             default: {
                 if (argument == NULL) {
-                    argument = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_EXPECT_ARGUMENT);
+                    argument = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected to be able to parse an argument.");
                 }
 
                 if (yp_symbol_node_label_p(argument) || accept(parser, YP_TOKEN_EQUAL_GREATER)) {
                     if (parsed_bare_hash) {
-                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_ARGUMENT_BARE_HASH);
+                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Unexpected bare hash argument.");
                     }
 
                     yp_token_t operator;
@@ -8737,7 +8737,7 @@ parse_arguments(yp_parser_t *parser, yp_arguments_t *arguments, bool accepts_for
                     yp_keyword_hash_node_t *bare_hash = yp_keyword_hash_node_create(parser);
 
                     // Finish parsing the one we are part way through
-                    yp_node_t *value = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_HASH_VALUE);
+                    yp_node_t *value = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value in the hash literal.");
 
                     argument = (yp_node_t *) yp_assoc_node_create(parser, argument, &operator, value);
                     yp_keyword_hash_node_elements_append(bare_hash, argument);
@@ -8793,7 +8793,7 @@ parse_arguments(yp_parser_t *parser, yp_arguments_t *arguments, bool accepts_for
 // It can recurse infinitely down, and splats are allowed to group arguments.
 static yp_required_destructured_parameter_node_t *
 parse_required_destructured_parameter(yp_parser_t *parser) {
-    expect(parser, YP_TOKEN_PARENTHESIS_LEFT, YP_ERR_EXPECT_LPAREN_REQ_PARAMETER);
+    expect(parser, YP_TOKEN_PARENTHESIS_LEFT, "Expected '(' to start a required parameter.");
 
     yp_token_t opening = parser->previous;
     yp_required_destructured_parameter_node_t *node = yp_required_destructured_parameter_node_create(parser, &opening);
@@ -8804,7 +8804,7 @@ parse_required_destructured_parameter(yp_parser_t *parser) {
 
         if (node->parameters.size > 0 && match_type_p(parser, YP_TOKEN_PARENTHESIS_RIGHT)) {
             if (parsed_splat) {
-                yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_ARGUMENT_SPLAT_AFTER_SPLAT);
+                yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Unexpected splat after splat.");
             }
 
             param = (yp_node_t *) yp_splat_node_create(parser, &parser->previous, NULL);
@@ -8816,7 +8816,7 @@ parse_required_destructured_parameter(yp_parser_t *parser) {
             param = (yp_node_t *) parse_required_destructured_parameter(parser);
         } else if (accept(parser, YP_TOKEN_USTAR)) {
             if (parsed_splat) {
-                yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_ARGUMENT_SPLAT_AFTER_SPLAT);
+                yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Unexpected splat after splat.");
             }
 
             yp_token_t star = parser->previous;
@@ -8831,7 +8831,7 @@ parse_required_destructured_parameter(yp_parser_t *parser) {
             param = (yp_node_t *) yp_splat_node_create(parser, &star, value);
             parsed_splat = true;
         } else {
-            expect(parser, YP_TOKEN_IDENTIFIER, YP_ERR_EXPECT_IDENT_REQ_PARAMETER);
+            expect(parser, YP_TOKEN_IDENTIFIER, "Expected an identifier for a required parameter.");
             yp_token_t name = parser->previous;
 
             param = (yp_node_t *) yp_required_parameter_node_create(parser, &name);
@@ -8841,7 +8841,7 @@ parse_required_destructured_parameter(yp_parser_t *parser) {
         yp_required_destructured_parameter_node_append_parameter(node, param);
     } while (accept(parser, YP_TOKEN_COMMA));
 
-    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN_REQ_PARAMETER);
+    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected ')' to end a required parameter.");
     yp_required_destructured_parameter_node_closing_set(node, &parser->previous);
 
     return node;
@@ -8896,12 +8896,12 @@ update_parameter_state(yp_parser_t *parser, yp_token_t *token, yp_parameters_ord
     }
 
     if (token->type == YP_TOKEN_USTAR && *current == YP_PARAMETERS_ORDER_AFTER_OPTIONAL) {
-        yp_diagnostic_list_append(&parser->error_list, token->start, token->end, YP_ERR_PARAMETER_STAR);
+        yp_diagnostic_list_append(&parser->error_list, token->start, token->end, "Unexpected parameter *");
     }
 
     if (*current == YP_PARAMETERS_ORDER_NOTHING_AFTER || state > *current) {
         // We know what transition we failed on, so we can provide a better error here.
-        yp_diagnostic_list_append(&parser->error_list, token->start, token->end, YP_ERR_PARAMETER_ORDER);
+        yp_diagnostic_list_append(&parser->error_list, token->start, token->end, "Unexpected parameter order");
     } else if (state < *current) {
         *current = state;
     }
@@ -8956,7 +8956,7 @@ parse_parameters(
                 if (params->block == NULL) {
                     yp_parameters_node_block_set(params, param);
                 } else {
-                    yp_diagnostic_list_append(&parser->error_list, param->base.location.start, param->base.location.end, YP_ERR_PARAMETER_BLOCK_MULTI);
+                    yp_diagnostic_list_append(&parser->error_list, param->base.location.start, param->base.location.end, "Unexpected multiple block parameter");
                     yp_parameters_node_posts_append(params, (yp_node_t *) param);
                 }
 
@@ -8964,7 +8964,7 @@ parse_parameters(
             }
             case YP_TOKEN_UDOT_DOT_DOT: {
                 if (!allows_forwarding_parameter) {
-                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES);
+                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Unexpected ...");
                 }
                 if (order > YP_PARAMETERS_ORDER_NOTHING_AFTER) {
                     update_parameter_state(parser, &parser->current, &order);
@@ -8987,16 +8987,16 @@ parse_parameters(
                 parser_lex(parser);
                 switch (parser->previous.type) {
                     case YP_TOKEN_CONSTANT:
-                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_ARGUMENT_FORMAL_CONSTANT);
+                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Formal argument cannot be a constant");
                         break;
                     case YP_TOKEN_INSTANCE_VARIABLE:
-                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_ARGUMENT_FORMAL_IVAR);
+                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Formal argument cannot be an instance variable");
                         break;
                     case YP_TOKEN_GLOBAL_VARIABLE:
-                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_ARGUMENT_FORMAL_GLOBAL);
+                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Formal argument cannot be a global variable");
                         break;
                     case YP_TOKEN_CLASS_VARIABLE:
-                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_ARGUMENT_FORMAL_CLASS);
+                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Formal argument cannot be a class variable");
                         break;
                     default: break;
                 }
@@ -9014,7 +9014,7 @@ parse_parameters(
                 if (accept(parser, YP_TOKEN_EQUAL)) {
                     yp_token_t operator = parser->previous;
                     context_push(parser, YP_CONTEXT_DEFAULT_PARAMS);
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_PARAMETER_NO_DEFAULT);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected to find a default value for the parameter.");
 
                     yp_optional_parameter_node_t *param = yp_optional_parameter_node_create(parser, &name, &operator, value);
                     yp_parameters_node_optionals_append(params, param);
@@ -9072,7 +9072,7 @@ parse_parameters(
                         yp_node_t *value = NULL;
                         if (token_begins_expression_p(parser->current.type)) {
                             context_push(parser, YP_CONTEXT_DEFAULT_PARAMS);
-                            value = parse_expression(parser, binding_power, YP_ERR_PARAMETER_NO_DEFAULT_KW);
+                            value = parse_expression(parser, binding_power, "Expected to find a default value for the keyword parameter.");
                             context_pop(parser);
                         }
 
@@ -9113,7 +9113,7 @@ parse_parameters(
                 if (params->rest == NULL) {
                     yp_parameters_node_rest_set(params, param);
                 } else {
-                    yp_diagnostic_list_append(&parser->error_list, param->base.location.start, param->base.location.end, YP_ERR_PARAMETER_SPLAT_MULTI);
+                    yp_diagnostic_list_append(&parser->error_list, param->base.location.start, param->base.location.end, "Unexpected multiple splat parameters.");
                     yp_parameters_node_posts_append(params, (yp_node_t *) param);
                 }
 
@@ -9147,7 +9147,7 @@ parse_parameters(
                 if (params->keyword_rest == NULL) {
                     yp_parameters_node_keyword_rest_set(params, param);
                 } else {
-                    yp_diagnostic_list_append(&parser->error_list, param->location.start, param->location.end, YP_ERR_PARAMETER_ASSOC_SPLAT_MULTI);
+                    yp_diagnostic_list_append(&parser->error_list, param->location.start, param->location.end, "Unexpected multiple double splat parameters.");
                     yp_parameters_node_posts_append(params, param);
                 }
 
@@ -9165,11 +9165,11 @@ parse_parameters(
                         if (params->rest == NULL) {
                             yp_parameters_node_rest_set(params, param);
                         } else {
-                            yp_diagnostic_list_append(&parser->error_list, param->base.location.start, param->base.location.end, YP_ERR_PARAMETER_SPLAT_MULTI);
+                            yp_diagnostic_list_append(&parser->error_list, param->base.location.start, param->base.location.end, "Unexpected multiple splat parameters.");
                             yp_parameters_node_posts_append(params, (yp_node_t *) param);
                         }
                     } else {
-                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_PARAMETER_WILD_LOOSE_COMMA);
+                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Unexpected ','.");
                     }
                 }
 
@@ -9210,7 +9210,7 @@ parse_rescues(yp_parser_t *parser, yp_begin_node_t *parent_node) {
                 parser_lex(parser);
                 yp_rescue_node_operator_set(rescue, &parser->previous);
 
-                yp_node_t *reference = parse_expression(parser, YP_BINDING_POWER_INDEX, YP_ERR_RESCUE_VARIABLE);
+                yp_node_t *reference = parse_expression(parser, YP_BINDING_POWER_INDEX, "Expected an exception variable after `=>` in rescue statement.");
                 reference = parse_target(parser, reference);
 
                 yp_rescue_node_reference_set(rescue, reference);
@@ -9228,7 +9228,7 @@ parse_rescues(yp_parser_t *parser, yp_begin_node_t *parent_node) {
                     // we'll attempt to parse it here and any others delimited by commas.
 
                     do {
-                        yp_node_t *expression = parse_starred_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_RESCUE_EXPRESSION);
+                        yp_node_t *expression = parse_starred_expression(parser, YP_BINDING_POWER_DEFINED, "Expected to find a rescued expression.");
                         yp_rescue_node_exceptions_append(rescue, expression);
 
                         // If we hit a newline, then this is the end of the rescue expression. We
@@ -9240,7 +9240,7 @@ parse_rescues(yp_parser_t *parser, yp_begin_node_t *parent_node) {
                         if (accept(parser, YP_TOKEN_EQUAL_GREATER)) {
                             yp_rescue_node_operator_set(rescue, &parser->previous);
 
-                            yp_node_t *reference = parse_expression(parser, YP_BINDING_POWER_INDEX, YP_ERR_RESCUE_VARIABLE);
+                            yp_node_t *reference = parse_expression(parser, YP_BINDING_POWER_INDEX, "Expected an exception variable after `=>` in rescue statement.");
                             reference = parse_target(parser, reference);
 
                             yp_rescue_node_reference_set(rescue, reference);
@@ -9254,7 +9254,7 @@ parse_rescues(yp_parser_t *parser, yp_begin_node_t *parent_node) {
         if (accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON)) {
             accept(parser, YP_TOKEN_KEYWORD_THEN);
         } else {
-            expect(parser, YP_TOKEN_KEYWORD_THEN, YP_ERR_RESCUE_TERM);
+            expect(parser, YP_TOKEN_KEYWORD_THEN, "Expected a terminator after rescue clause.");
         }
 
         if (!match_any_type_p(parser, 3, YP_TOKEN_KEYWORD_ELSE, YP_TOKEN_KEYWORD_ENSURE, YP_TOKEN_KEYWORD_END)) {
@@ -9374,7 +9374,7 @@ parse_block_parameters(
     yp_block_parameters_node_t *block_parameters = yp_block_parameters_node_create(parser, parameters, opening);
     if (accept(parser, YP_TOKEN_SEMICOLON)) {
         do {
-            expect(parser, YP_TOKEN_IDENTIFIER, YP_ERR_BLOCK_PARAM_LOCAL_VARIABLE);
+            expect(parser, YP_TOKEN_IDENTIFIER, "Expected a local variable name.");
             yp_parser_local_add_token(parser, &parser->previous);
 
             yp_block_local_variable_node_t *local = yp_block_local_variable_node_create(parser, &parser->previous);
@@ -9406,7 +9406,7 @@ parse_block(yp_parser_t *parser) {
             parameters = parse_block_parameters(parser, true, &block_parameters_opening, false);
             accept(parser, YP_TOKEN_NEWLINE);
             parser->command_start = true;
-            expect(parser, YP_TOKEN_PIPE, YP_ERR_BLOCK_PARAM_PIPE_TERM);
+            expect(parser, YP_TOKEN_PIPE, "Expected block parameters to end with '|'.");
         }
 
         yp_block_parameters_node_closing_set(parameters, &parser->previous);
@@ -9420,7 +9420,7 @@ parse_block(yp_parser_t *parser) {
             statements = (yp_node_t *) parse_statements(parser, YP_CONTEXT_BLOCK_BRACES);
         }
 
-        expect(parser, YP_TOKEN_BRACE_RIGHT, YP_ERR_BLOCK_TERM_BRACE);
+        expect(parser, YP_TOKEN_BRACE_RIGHT, "Expected block beginning with '{' to end with '}'.");
     } else {
         if (!match_type_p(parser, YP_TOKEN_KEYWORD_END)) {
             if (!match_any_type_p(parser, 3, YP_TOKEN_KEYWORD_RESCUE, YP_TOKEN_KEYWORD_ELSE, YP_TOKEN_KEYWORD_ENSURE)) {
@@ -9435,7 +9435,7 @@ parse_block(yp_parser_t *parser) {
             }
         }
 
-        expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_BLOCK_TERM_END);
+        expect(parser, YP_TOKEN_KEYWORD_END, "Expected block beginning with 'do' to end with 'end'.");
     }
 
     yp_constant_id_list_t locals = parser->current_scope->locals;
@@ -9462,7 +9462,7 @@ parse_arguments_list(yp_parser_t *parser, yp_arguments_t *arguments, bool accept
 
             yp_accepts_block_stack_push(parser, true);
             parse_arguments(parser, arguments, true, YP_TOKEN_PARENTHESIS_RIGHT);
-            expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_ARGUMENT_TERM_PAREN);
+            expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected a ')' to close the argument list.");
             yp_accepts_block_stack_pop(parser);
 
             arguments->closing_loc = YP_LOCATION_TOKEN_VALUE(&parser->previous);
@@ -9502,8 +9502,7 @@ parse_conditional(yp_parser_t *parser, yp_context_t context) {
     yp_token_t keyword = parser->previous;
 
     context_push(parser, YP_CONTEXT_PREDICATE);
-    yp_diagnostic_id_t error_id = context == YP_CONTEXT_IF ? YP_ERR_CONDITIONAL_IF_PREDICATE : YP_ERR_CONDITIONAL_UNLESS_PREDICATE;
-    yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_MODIFIER, error_id);
+    yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_MODIFIER, "Expected to find a predicate for the conditional.");
 
     // Predicates are closed by a term, a "then", or a term and then a "then".
     accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
@@ -9541,7 +9540,7 @@ parse_conditional(yp_parser_t *parser, yp_context_t context) {
     if (context == YP_CONTEXT_IF) {
         while (accept(parser, YP_TOKEN_KEYWORD_ELSIF)) {
             yp_token_t elsif_keyword = parser->previous;
-            yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_MODIFIER, YP_ERR_CONDITIONAL_ELSIF_PREDICATE);
+            yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_MODIFIER, "Expected to find a predicate for the elsif clause.");
 
             // Predicates are closed by a term, a "then", or a term and then a "then".
             accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
@@ -9568,7 +9567,7 @@ parse_conditional(yp_parser_t *parser, yp_context_t context) {
         yp_accepts_block_stack_pop(parser);
 
         accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
-        expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_CONDITIONAL_TERM_ELSE);
+        expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close `else` clause.");
 
         yp_else_node_t *else_node = yp_else_node_create(parser, &else_keyword, else_statements, &parser->previous);
 
@@ -9584,8 +9583,7 @@ parse_conditional(yp_parser_t *parser, yp_context_t context) {
                 break;
         }
     } else {
-        // We should specialize this error message to refer to 'if' or 'unless' explicitly.
-        expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_CONDITIONAL_TERM);
+        expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close conditional statement.");
     }
 
     // Set the appropriate end location for all of the nodes in the subtree.
@@ -9735,7 +9733,7 @@ parse_string_part(yp_parser_t *parser) {
             parser->brace_nesting = brace_nesting;
             lex_state_set(parser, state);
 
-            expect(parser, YP_TOKEN_EMBEXPR_END, YP_ERR_EMBEXPR_END);
+            expect(parser, YP_TOKEN_EMBEXPR_END, "Expected a closing delimiter for an embedded expression.");
             yp_token_t closing = parser->previous;
 
             return (yp_node_t *) yp_embedded_statements_node_create(parser, &opening, statements, &closing);
@@ -9789,7 +9787,7 @@ parse_string_part(yp_parser_t *parser) {
                 // we'll not attempt to lex this token and instead just return a
                 // missing node.
                 default:
-                    expect(parser, YP_TOKEN_IDENTIFIER, YP_ERR_EMBVAR_INVALID);
+                    expect(parser, YP_TOKEN_IDENTIFIER, "Expected a valid embedded variable.");
                     variable = (yp_node_t *) yp_missing_node_create(parser, parser->current.start, parser->current.end);
                     break;
             }
@@ -9798,7 +9796,7 @@ parse_string_part(yp_parser_t *parser) {
         }
         default:
             parser_lex(parser);
-            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_CANNOT_PARSE_STRING_PART);
+            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Could not understand string part");
             return NULL;
     }
 }
@@ -9829,7 +9827,7 @@ parse_symbol(yp_parser_t *parser, yp_lex_mode_t *lex_mode, yp_lex_state_t next_s
                 symbol = parser->previous;
                 break;
             default:
-                expect(parser, YP_TOKEN_IDENTIFIER, YP_ERR_SYMBOL_INVALID);
+                expect(parser, YP_TOKEN_IDENTIFIER, "Expected symbol.");
                 symbol = parser->previous;
                 break;
         }
@@ -9873,7 +9871,7 @@ parse_symbol(yp_parser_t *parser, yp_lex_mode_t *lex_mode, yp_lex_state_t next_s
         }
 
         if (next_state != YP_LEX_STATE_NONE) lex_state_set(parser, next_state);
-        expect(parser, YP_TOKEN_STRING_END, YP_ERR_SYMBOL_TERM_INTERPOLATED);
+        expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for an interpolated symbol.");
 
         return (yp_node_t *) yp_interpolated_symbol_node_create(parser, &opening, &node_list, &parser->previous);
     }
@@ -9888,7 +9886,7 @@ parse_symbol(yp_parser_t *parser, yp_lex_mode_t *lex_mode, yp_lex_state_t next_s
     if (next_state != YP_LEX_STATE_NONE) {
         lex_state_set(parser, next_state);
     }
-    expect(parser, YP_TOKEN_STRING_END, YP_ERR_SYMBOL_TERM_DYNAMIC);
+    expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for a dynamic symbol.");
 
     return (yp_node_t *) yp_symbol_node_create_and_unescape(parser, &opening, &content, &parser->previous, YP_UNESCAPE_ALL);
 }
@@ -9916,7 +9914,7 @@ parse_undef_argument(yp_parser_t *parser) {
             return parse_symbol(parser, &lex_mode, YP_LEX_STATE_NONE);
         }
         default:
-            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_UNDEF_ARGUMENT);
+            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Expected a bare word or symbol argument.");
             return (yp_node_t *) yp_missing_node_create(parser, parser->current.start, parser->current.end);
     }
 }
@@ -9958,7 +9956,7 @@ parse_alias_argument(yp_parser_t *parser, bool first) {
             parser_lex(parser);
             return (yp_node_t *) yp_global_variable_read_node_create(parser, &parser->previous);
         default:
-            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_ALIAS_ARGUMENT);
+            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Expected a bare word, symbol or global variable argument.");
             return (yp_node_t *) yp_missing_node_create(parser, parser->current.start, parser->current.end);
     }
 }
@@ -10177,7 +10175,7 @@ parse_heredoc_dedent(yp_parser_t *parser, yp_node_t *node, yp_heredoc_quote_t qu
 }
 
 static yp_node_t *
-parse_pattern(yp_parser_t *parser, bool top_pattern, yp_diagnostic_id_t diag_id);
+parse_pattern(yp_parser_t *parser, bool top_pattern, const char *message);
 
 // Accept any number of constants joined by :: delimiters.
 static yp_node_t *
@@ -10186,7 +10184,7 @@ parse_pattern_constant_path(yp_parser_t *parser, yp_node_t *node) {
     // path nodes.
     while (accept(parser, YP_TOKEN_COLON_COLON)) {
         yp_token_t delimiter = parser->previous;
-        expect(parser, YP_TOKEN_CONSTANT, YP_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT);
+        expect(parser, YP_TOKEN_CONSTANT, "Expected a constant after the :: operator.");
 
         yp_node_t *child = (yp_node_t *) yp_constant_read_node_create(parser, &parser->previous);
         node = (yp_node_t *)yp_constant_path_node_create(parser, node, &delimiter, child);
@@ -10208,9 +10206,9 @@ parse_pattern_constant_path(yp_parser_t *parser, yp_node_t *node) {
         accept(parser, YP_TOKEN_NEWLINE);
 
         if (!accept(parser, YP_TOKEN_BRACKET_RIGHT)) {
-            inner = parse_pattern(parser, true, YP_ERR_PATTERN_EXPRESSION_AFTER_BRACKET);
+            inner = parse_pattern(parser, true, "Expected a pattern expression after the [ operator.");
             accept(parser, YP_TOKEN_NEWLINE);
-            expect(parser, YP_TOKEN_BRACKET_RIGHT, YP_ERR_PATTERN_TERM_BRACKET);
+            expect(parser, YP_TOKEN_BRACKET_RIGHT, "Expected a ] to close the pattern expression.");
         }
 
         closing = parser->previous;
@@ -10219,8 +10217,8 @@ parse_pattern_constant_path(yp_parser_t *parser, yp_node_t *node) {
         opening = parser->previous;
 
         if (!accept(parser, YP_TOKEN_PARENTHESIS_RIGHT)) {
-            inner = parse_pattern(parser, true, YP_ERR_PATTERN_EXPRESSION_AFTER_PAREN);
-            expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_PATTERN_TERM_PAREN);
+            inner = parse_pattern(parser, true, "Expected a pattern expression after the ( operator.");
+            expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected a ) to close the pattern expression.");
         }
 
         closing = parser->previous;
@@ -10345,7 +10343,7 @@ parse_pattern_hash(yp_parser_t *parser, yp_node_t *first_assoc) {
         if (!match_any_type_p(parser, 7, YP_TOKEN_COMMA, YP_TOKEN_KEYWORD_THEN, YP_TOKEN_BRACE_RIGHT, YP_TOKEN_BRACKET_RIGHT, YP_TOKEN_PARENTHESIS_RIGHT, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON)) {
             // Here we have a value for the first assoc in the list, so we will parse it
             // now and update the first assoc.
-            yp_node_t *value = parse_pattern(parser, false, YP_ERR_PATTERN_EXPRESSION_AFTER_KEY);
+            yp_node_t *value = parse_pattern(parser, false, "Expected a pattern expression after the key.");
 
             yp_assoc_node_t *assoc = (yp_assoc_node_t *) first_assoc;
             assoc->base.location.end = value->location.end;
@@ -10375,12 +10373,12 @@ parse_pattern_hash(yp_parser_t *parser, yp_node_t *first_assoc) {
         if (match_type_p(parser, YP_TOKEN_USTAR_STAR)) {
             assoc = parse_pattern_keyword_rest(parser);
         } else {
-            expect(parser, YP_TOKEN_LABEL, YP_ERR_PATTERN_LABEL_AFTER_COMMA);
+            expect(parser, YP_TOKEN_LABEL, "Expected a label after the `,'.");
             yp_node_t *key = (yp_node_t *) yp_symbol_node_label_create(parser, &parser->previous);
             yp_node_t *value = NULL;
 
             if (!match_any_type_p(parser, 7, YP_TOKEN_COMMA, YP_TOKEN_KEYWORD_THEN, YP_TOKEN_BRACE_RIGHT, YP_TOKEN_BRACKET_RIGHT, YP_TOKEN_PARENTHESIS_RIGHT, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON)) {
-                value = parse_pattern(parser, false, YP_ERR_PATTERN_EXPRESSION_AFTER_KEY);
+                value = parse_pattern(parser, false, "Expected a pattern expression after the key.");
             } else {
                 const yp_location_t *value_loc = &((yp_symbol_node_t *) key)->value_loc;
                 yp_parser_local_add_location(parser, value_loc->start, value_loc->end);
@@ -10401,7 +10399,7 @@ parse_pattern_hash(yp_parser_t *parser, yp_node_t *first_assoc) {
 
 // Parse a pattern expression primitive.
 static yp_node_t *
-parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
+parse_pattern_primitive(yp_parser_t *parser, const char *message) {
     switch (parser->current.type) {
         case YP_TOKEN_IDENTIFIER: {
             parser_lex(parser);
@@ -10420,11 +10418,11 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
 
             // Otherwise, we'll parse the inner pattern, then deal with it depending
             // on the type it returns.
-            yp_node_t *inner = parse_pattern(parser, true, YP_ERR_PATTERN_EXPRESSION_AFTER_BRACKET);
+            yp_node_t *inner = parse_pattern(parser, true, "Expected a pattern expression after the [ operator.");
 
             accept(parser, YP_TOKEN_NEWLINE);
 
-            expect(parser, YP_TOKEN_BRACKET_RIGHT, YP_ERR_PATTERN_TERM_BRACKET);
+            expect(parser, YP_TOKEN_BRACKET_RIGHT, "Expected a ] to close the pattern expression.");
             yp_token_t closing = parser->previous;
 
             switch (YP_NODE_TYPE(inner)) {
@@ -10488,15 +10486,15 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
                         key = parse_pattern_keyword_rest(parser);
                         break;
                     case YP_TOKEN_STRING_BEGIN:
-                        key = parse_expression(parser, YP_BINDING_POWER_MAX, YP_ERR_PATTERN_HASH_KEY);
+                        key = parse_expression(parser, YP_BINDING_POWER_MAX, "Expected a key in the hash pattern.");
                         if (!yp_symbol_node_label_p(key)) {
-                            yp_diagnostic_list_append(&parser->error_list, key->location.start, key->location.end, YP_ERR_PATTERN_HASH_KEY_LABEL);
+                            yp_diagnostic_list_append(&parser->error_list, key->location.start, key->location.end, "Expected a label as the key in the hash pattern.");
                         }
 
                         break;
                     default:
                         parser_lex(parser);
-                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_PATTERN_HASH_KEY);
+                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Expected a key in the hash pattern.");
                         key = (yp_node_t *) yp_missing_node_create(parser, parser->previous.start, parser->previous.end);
                         break;
                 }
@@ -10505,7 +10503,7 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
                 node = parse_pattern_hash(parser, (yp_node_t *) yp_assoc_node_create(parser, key, &operator, NULL));
 
                 accept(parser, YP_TOKEN_NEWLINE);
-                expect(parser, YP_TOKEN_BRACE_RIGHT, YP_ERR_PATTERN_TERM_BRACE);
+                expect(parser, YP_TOKEN_BRACE_RIGHT, "Expected a } to close the pattern expression.");
                 yp_token_t closing = parser->previous;
 
                 node->base.location.start = opening.start;
@@ -10527,18 +10525,18 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
             // expression as the right side of the range.
             switch (parser->current.type) {
                 case YP_CASE_PRIMITIVE: {
-                    yp_node_t *right = parse_expression(parser, YP_BINDING_POWER_MAX, YP_ERR_PATTERN_EXPRESSION_AFTER_RANGE);
+                    yp_node_t *right = parse_expression(parser, YP_BINDING_POWER_MAX, "Expected an expression after the range operator.");
                     return (yp_node_t *) yp_range_node_create(parser, NULL, &operator, right);
                 }
                 default: {
-                    yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, YP_ERR_PATTERN_EXPRESSION_AFTER_RANGE);
+                    yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, "Expected an expression after the range operator.");
                     yp_node_t *right = (yp_node_t *) yp_missing_node_create(parser, operator.start, operator.end);
                     return (yp_node_t *) yp_range_node_create(parser, NULL, &operator, right);
                 }
             }
         }
         case YP_CASE_PRIMITIVE: {
-            yp_node_t *node = parse_expression(parser, YP_BINDING_POWER_MAX, diag_id);
+            yp_node_t *node = parse_expression(parser, YP_BINDING_POWER_MAX, message);
 
             // Now that we have a primitive, we need to check if it's part of a range.
             if (accept_any(parser, 2, YP_TOKEN_DOT_DOT, YP_TOKEN_DOT_DOT_DOT)) {
@@ -10549,7 +10547,7 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
                 // node. Otherwise, we'll create an endless range.
                 switch (parser->current.type) {
                     case YP_CASE_PRIMITIVE: {
-                        yp_node_t *right = parse_expression(parser, YP_BINDING_POWER_MAX, YP_ERR_PATTERN_EXPRESSION_AFTER_RANGE);
+                        yp_node_t *right = parse_expression(parser, YP_BINDING_POWER_MAX, "Expected an expression after the range operator.");
                         return (yp_node_t *) yp_range_node_create(parser, node, &operator, right);
                     }
                     default:
@@ -10609,17 +10607,17 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
                     yp_token_t lparen = parser->current;
                     parser_lex(parser);
 
-                    yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_STATEMENT, YP_ERR_PATTERN_EXPRESSION_AFTER_PIN);
+                    yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_STATEMENT, "Expected an expression after the pin operator.");
                     parser->pattern_matching_newlines = previous_pattern_matching_newlines;
 
                     accept(parser, YP_TOKEN_NEWLINE);
-                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_PATTERN_TERM_PAREN);
+                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected a closing parenthesis after the expression.");
                     return (yp_node_t *) yp_pinned_expression_node_create(parser, expression, &operator, &lparen, &parser->previous);
                 }
                 default: {
                     // If we get here, then we have a pin operator followed by something
                     // not understood. We'll create a missing node and return that.
-                    yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, YP_ERR_PATTERN_EXPRESSION_AFTER_PIN);
+                    yp_diagnostic_list_append(&parser->error_list, operator.start, operator.end, "Expected a variable after the pin operator.");
                     yp_node_t *variable = (yp_node_t *) yp_missing_node_create(parser, operator.start, operator.end);
                     return (yp_node_t *) yp_pinned_variable_node_create(parser, &operator, variable);
                 }
@@ -10629,7 +10627,7 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
             yp_token_t delimiter = parser->current;
             parser_lex(parser);
 
-            expect(parser, YP_TOKEN_CONSTANT, YP_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT);
+            expect(parser, YP_TOKEN_CONSTANT, "Expected a constant after the :: operator.");
             yp_node_t *child = (yp_node_t *) yp_constant_read_node_create(parser, &parser->previous);
             yp_constant_path_node_t *node = yp_constant_path_node_create(parser, NULL, &delimiter, child);
 
@@ -10643,7 +10641,7 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
             return parse_pattern_constant_path(parser, node);
         }
         default:
-            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, diag_id);
+            yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, message);
             return (yp_node_t *) yp_missing_node_create(parser, parser->current.start, parser->current.end);
     }
 }
@@ -10651,7 +10649,7 @@ parse_pattern_primitive(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
 // Parse any number of primitives joined by alternation and ended optionally by
 // assignment.
 static yp_node_t *
-parse_pattern_primitives(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
+parse_pattern_primitives(yp_parser_t *parser, const char *message) {
     yp_node_t *node = NULL;
 
     do {
@@ -10668,9 +10666,9 @@ parse_pattern_primitives(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
             case YP_TOKEN_UDOT_DOT_DOT:
             case YP_CASE_PRIMITIVE: {
                 if (node == NULL) {
-                    node = parse_pattern_primitive(parser, diag_id);
+                    node = parse_pattern_primitive(parser, message);
                 } else {
-                    yp_node_t *right = parse_pattern_primitive(parser, YP_ERR_PATTERN_EXPRESSION_AFTER_PIPE);
+                    yp_node_t *right = parse_pattern_primitive(parser, "Expected to be able to parse a pattern after `|'.");
                     node = (yp_node_t *) yp_alternation_pattern_node_create(parser, node, right, &operator);
                 }
 
@@ -10681,13 +10679,13 @@ parse_pattern_primitives(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
                 if (node != NULL) {
                     yp_node_destroy(parser, node);
                 }
-                node = parse_pattern(parser, false, YP_ERR_PATTERN_EXPRESSION_AFTER_PAREN);
+                node = parse_pattern(parser, false, "Expected a pattern after the opening parenthesis.");
 
-                expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_PATTERN_TERM_PAREN);
+                expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected a closing parenthesis after the pattern.");
                 break;
             }
             default: {
-                yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, diag_id);
+                yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, message);
                 yp_node_t *right = (yp_node_t *) yp_missing_node_create(parser, parser->current.start, parser->current.end);
 
                 if (node == NULL) {
@@ -10706,7 +10704,7 @@ parse_pattern_primitives(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
     while (accept(parser, YP_TOKEN_EQUAL_GREATER)) {
         yp_token_t operator = parser->previous;
 
-        expect(parser, YP_TOKEN_IDENTIFIER, YP_ERR_PATTERN_IDENT_AFTER_HROCKET);
+        expect(parser, YP_TOKEN_IDENTIFIER, "Expected an identifier after the `=>' operator.");
         yp_token_t identifier = parser->previous;
         yp_parser_local_add_token(parser, &identifier);
 
@@ -10719,7 +10717,7 @@ parse_pattern_primitives(yp_parser_t *parser, yp_diagnostic_id_t diag_id) {
 
 // Parse a pattern matching expression.
 static yp_node_t *
-parse_pattern(yp_parser_t *parser, bool top_pattern, yp_diagnostic_id_t diag_id) {
+parse_pattern(yp_parser_t *parser, bool top_pattern, const char *message) {
     yp_node_t *node = NULL;
 
     bool leading_rest = false;
@@ -10747,7 +10745,7 @@ parse_pattern(yp_parser_t *parser, bool top_pattern, yp_diagnostic_id_t diag_id)
         }
         /* fallthrough */
         default:
-            node = parse_pattern_primitives(parser, diag_id);
+            node = parse_pattern_primitives(parser, message);
             break;
     }
 
@@ -10779,12 +10777,12 @@ parse_pattern(yp_parser_t *parser, bool top_pattern, yp_diagnostic_id_t diag_id)
                 // will continue to parse the rest of the patterns, but we will indicate
                 // it as an error.
                 if (trailing_rest) {
-                    yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_PATTERN_REST);
+                    yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Unexpected rest pattern.");
                 }
 
                 trailing_rest = true;
             } else {
-                node = parse_pattern_primitives(parser, YP_ERR_PATTERN_EXPRESSION_AFTER_COMMA);
+                node = parse_pattern_primitives(parser, "Expected a pattern after the comma.");
             }
 
             yp_node_list_append(&nodes, node);
@@ -10852,7 +10850,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 }
 
                 if (yp_array_node_size(array) != 0) {
-                    expect(parser, YP_TOKEN_COMMA, YP_ERR_ARRAY_SEPARATOR);
+                    expect(parser, YP_TOKEN_COMMA, "Expected a separator for the elements in an array.");
                 }
 
                 // If we have a right bracket immediately following a comma, this is
@@ -10864,11 +10862,11 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
                 if (accept(parser, YP_TOKEN_USTAR)) {
                     yp_token_t operator = parser->previous;
-                    yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_ARRAY_EXPRESSION_AFTER_STAR);
+                    yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected an expression after '*' in the array.");
                     element = (yp_node_t *) yp_splat_node_create(parser, &operator, expression);
                 } else if (match_any_type_p(parser, 2, YP_TOKEN_LABEL, YP_TOKEN_USTAR_STAR)) {
                     if (parsed_bare_hash) {
-                        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_EXPRESSION_BARE_HASH);
+                        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Unexpected bare hash.");
                     }
 
                     yp_keyword_hash_node_t *hash = yp_keyword_hash_node_create(parser);
@@ -10880,11 +10878,11 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
                     parsed_bare_hash = true;
                 } else {
-                    element = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_ARRAY_EXPRESSION);
+                    element = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected an element for the array.");
 
                     if (yp_symbol_node_label_p(element) || accept(parser, YP_TOKEN_EQUAL_GREATER)) {
                         if (parsed_bare_hash) {
-                            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_EXPRESSION_BARE_HASH);
+                            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Unexpected bare hash.");
                         }
 
                         yp_keyword_hash_node_t *hash = yp_keyword_hash_node_create(parser);
@@ -10896,7 +10894,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                             operator = not_provided(parser);
                         }
 
-                        yp_node_t *value = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_HASH_VALUE);
+                        yp_node_t *value = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value in the hash literal.");
                         yp_node_t *assoc = (yp_node_t *) yp_assoc_node_create(parser, element, &operator, value);
                         yp_keyword_hash_node_elements_append(hash, assoc);
 
@@ -10914,7 +10912,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             }
 
             accept(parser, YP_TOKEN_NEWLINE);
-            expect(parser, YP_TOKEN_BRACKET_RIGHT, YP_ERR_ARRAY_TERM);
+            expect(parser, YP_TOKEN_BRACKET_RIGHT, "Expected a closing bracket for the array.");
             yp_array_node_close_set(array, &parser->previous);
             yp_accepts_block_stack_pop(parser);
 
@@ -10929,14 +10927,14 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             // If this is the end of the file or we match a right parenthesis, then
             // we have an empty parentheses node, and we can immediately return.
             if (match_any_type_p(parser, 2, YP_TOKEN_PARENTHESIS_RIGHT, YP_TOKEN_EOF)) {
-                expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN);
+                expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected a closing parenthesis.");
                 return (yp_node_t *) yp_parentheses_node_create(parser, &opening, NULL, &parser->previous);
             }
 
             // Otherwise, we're going to parse the first statement in the list of
             // statements within the parentheses.
             yp_accepts_block_stack_push(parser, true);
-            yp_node_t *statement = parse_expression(parser, YP_BINDING_POWER_STATEMENT, YP_ERR_CANNOT_PARSE_EXPRESSION);
+            yp_node_t *statement = parse_expression(parser, YP_BINDING_POWER_STATEMENT, "Expected to be able to parse an expression.");
             while (accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON));
 
             // If we hit a right parenthesis, then we're done parsing the parentheses
@@ -10994,7 +10992,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 // Ignore semicolon without statements before them
                 if (accept_any(parser, 2, YP_TOKEN_SEMICOLON, YP_TOKEN_NEWLINE)) continue;
 
-                yp_node_t *node = parse_expression(parser, YP_BINDING_POWER_STATEMENT, YP_ERR_CANNOT_PARSE_EXPRESSION);
+                yp_node_t *node = parse_expression(parser, YP_BINDING_POWER_STATEMENT, "Expected to be able to parse an expression.");
                 yp_statements_node_body_append(statements, node);
 
                 // If we're recovering from a syntax error, then we need to stop parsing the
@@ -11011,7 +11009,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
             context_pop(parser);
             yp_accepts_block_stack_pop(parser);
-            expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN);
+            expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected a closing parenthesis.");
 
             return (yp_node_t *) yp_parentheses_node_create(parser, &opening, (yp_node_t *) statements, &parser->previous);
         }
@@ -11026,7 +11024,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             }
 
             yp_accepts_block_stack_pop(parser);
-            expect(parser, YP_TOKEN_BRACE_RIGHT, YP_ERR_HASH_TERM);
+            expect(parser, YP_TOKEN_BRACE_RIGHT, "Expected a closing delimiter for a hash literal.");
             yp_hash_node_closing_loc_set(node, &parser->previous);
 
             return (yp_node_t *) node;
@@ -11086,7 +11084,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
 
             yp_token_t delimiter = parser->previous;
-            expect(parser, YP_TOKEN_CONSTANT, YP_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT);
+            expect(parser, YP_TOKEN_CONSTANT, "Expected a constant after ::.");
 
             yp_node_t *constant = (yp_node_t *) yp_constant_read_node_create(parser, &parser->previous);
             yp_node_t *node = (yp_node_t *)yp_constant_path_node_create(parser, NULL, &delimiter, constant);
@@ -11102,7 +11100,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             yp_token_t operator = parser->current;
             parser_lex(parser);
 
-            yp_node_t *right = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+            yp_node_t *right = parse_expression(parser, binding_power, "Expected a value after the operator.");
             return (yp_node_t *) yp_range_node_create(parser, NULL, &operator, right);
         }
         case YP_TOKEN_FLOAT:
@@ -11231,7 +11229,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             }
 
             lex_state_set(parser, YP_LEX_STATE_END);
-            expect(parser, YP_TOKEN_HEREDOC_END, YP_ERR_HEREDOC_TERM);
+            expect(parser, YP_TOKEN_HEREDOC_END, "Expected a closing delimiter for heredoc.");
 
             if (quote == YP_HEREDOC_QUOTE_BACKTICK) {
                 assert(YP_NODE_TYPE_P(node, YP_INTERPOLATED_X_STRING_NODE));
@@ -11256,7 +11254,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 return (yp_node_t *) yp_string_concat_node_create(
                     parser,
                     node,
-                    parse_expression(parser, YP_BINDING_POWER_CALL, YP_ERR_CANNOT_PARSE_EXPRESSION)
+                    parse_expression(parser, YP_BINDING_POWER_CALL, "Expected string on the right side of concatenation.")
                 );
             } else {
                 return node;
@@ -11304,7 +11302,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 case YP_SYMBOL_NODE:
                 case YP_INTERPOLATED_SYMBOL_NODE: {
                     if (!YP_NODE_TYPE_P(old_name, YP_SYMBOL_NODE) && !YP_NODE_TYPE_P(old_name, YP_INTERPOLATED_SYMBOL_NODE)) {
-                        yp_diagnostic_list_append(&parser->error_list, old_name->location.start, old_name->location.end, YP_ERR_ALIAS_ARGUMENT);
+                        yp_diagnostic_list_append(&parser->error_list, old_name->location.start, old_name->location.end, "Expected a bare word or symbol argument.");
                     }
                     break;
                 }
@@ -11313,10 +11311,10 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 case YP_GLOBAL_VARIABLE_READ_NODE: {
                     if (YP_NODE_TYPE_P(old_name, YP_BACK_REFERENCE_READ_NODE) || YP_NODE_TYPE_P(old_name, YP_NUMBERED_REFERENCE_READ_NODE) || YP_NODE_TYPE_P(old_name, YP_GLOBAL_VARIABLE_READ_NODE)) {
                         if (YP_NODE_TYPE_P(old_name, YP_NUMBERED_REFERENCE_READ_NODE)) {
-                            yp_diagnostic_list_append(&parser->error_list, old_name->location.start, old_name->location.end, YP_ERR_ALIAS_ARGUMENT);
+                            yp_diagnostic_list_append(&parser->error_list, old_name->location.start, old_name->location.end, "Can't make alias for number variables.");
                         }
                     } else {
-                        yp_diagnostic_list_append(&parser->error_list, old_name->location.start, old_name->location.end, YP_ERR_ALIAS_ARGUMENT);
+                        yp_diagnostic_list_append(&parser->error_list, old_name->location.start, old_name->location.end, "Expected a global variable.");
                     }
                     break;
                 }
@@ -11338,7 +11336,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             ) {
                 predicate = NULL;
             } else {
-                predicate = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, YP_ERR_CASE_EXPRESSION_AFTER_CASE);
+                predicate = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, "Expected a value after case keyword.");
                 while (accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON));
             }
 
@@ -11362,14 +11360,14 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     do {
                         if (accept(parser, YP_TOKEN_USTAR)) {
                             yp_token_t operator = parser->previous;
-                            yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_EXPECT_EXPRESSION_AFTER_STAR);
+                            yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value after `*' operator.");
 
                             yp_splat_node_t *splat_node = yp_splat_node_create(parser, &operator, expression);
                             yp_when_node_conditions_append(when_node, (yp_node_t *) splat_node);
 
                             if (YP_NODE_TYPE_P(expression, YP_MISSING_NODE)) break;
                         } else {
-                            yp_node_t *condition = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_CASE_EXPRESSION_AFTER_WHEN);
+                            yp_node_t *condition = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value after when keyword.");
                             yp_when_node_conditions_append(when_node, condition);
 
                             if (YP_NODE_TYPE_P(condition, YP_MISSING_NODE)) break;
@@ -11379,7 +11377,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     if (accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON)) {
                         accept(parser, YP_TOKEN_KEYWORD_THEN);
                     } else {
-                        expect(parser, YP_TOKEN_KEYWORD_THEN, YP_ERR_EXPECT_WHEN_DELIMITER);
+                        expect(parser, YP_TOKEN_KEYWORD_THEN, "Expected a delimiter after the predicates of a `when' clause.");
                     }
 
                     if (!match_any_type_p(parser, 3, YP_TOKEN_KEYWORD_WHEN, YP_TOKEN_KEYWORD_ELSE, YP_TOKEN_KEYWORD_END)) {
@@ -11403,18 +11401,18 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     parser_lex(parser);
 
                     yp_token_t in_keyword = parser->previous;
-                    yp_node_t *pattern = parse_pattern(parser, true, YP_ERR_PATTERN_EXPRESSION_AFTER_IN);
+                    yp_node_t *pattern = parse_pattern(parser, true, "Expected a pattern after `in' keyword.");
                     parser->pattern_matching_newlines = previous_pattern_matching_newlines;
 
                     // Since we're in the top-level of the case-in node we need to check
                     // for guard clauses in the form of `if` or `unless` statements.
                     if (accept(parser, YP_TOKEN_KEYWORD_IF_MODIFIER)) {
                         yp_token_t keyword = parser->previous;
-                        yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_CONDITIONAL_IF_PREDICATE);
+                        yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value after guard keyword.");
                         pattern = (yp_node_t *) yp_if_node_modifier_create(parser, pattern, &keyword, predicate);
                     } else if (accept(parser, YP_TOKEN_KEYWORD_UNLESS_MODIFIER)) {
                         yp_token_t keyword = parser->previous;
-                        yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_CONDITIONAL_UNLESS_PREDICATE);
+                        yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value after guard keyword.");
                         pattern = (yp_node_t *) yp_unless_node_modifier_create(parser, pattern, &keyword, predicate);
                     }
 
@@ -11429,7 +11427,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                             then_keyword = not_provided(parser);
                         }
                     } else {
-                        expect(parser, YP_TOKEN_KEYWORD_THEN, YP_ERR_EXPECT_WHEN_DELIMITER);
+                        expect(parser, YP_TOKEN_KEYWORD_THEN, "Expected a delimiter after the predicates of an `in' clause.");
                         then_keyword = parser->previous;
                     }
 
@@ -11452,7 +11450,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
             if (accept(parser, YP_TOKEN_KEYWORD_ELSE)) {
                 if (case_node->conditions.size < 1) {
-                    yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_CASE_LONELY_ELSE);
+                    yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Unexpected else without no when clauses in case statement.");
                 }
 
                 yp_token_t else_keyword = parser->previous;
@@ -11467,7 +11465,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 yp_case_node_consequent_set(case_node, else_node);
             }
 
-            expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_CASE_TERM);
+            expect(parser, YP_TOKEN_KEYWORD_END, "Expected case statement to end with an end keyword.");
             yp_case_node_end_keyword_loc_set(case_node, &parser->previous);
             return (yp_node_t *) case_node;
         }
@@ -11488,7 +11486,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             yp_begin_node_t *begin_node = yp_begin_node_create(parser, &begin_keyword, begin_statements);
             parse_rescues(parser, begin_node);
 
-            expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_BEGIN_TERM);
+            expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close `begin` statement.");
             begin_node->base.location.end = parser->previous.end;
             yp_begin_node_end_keyword_set(begin_node, &parser->previous);
 
@@ -11497,7 +11495,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     &parser->error_list,
                     begin_node->else_clause->base.location.start,
                     begin_node->else_clause->base.location.end,
-                    YP_ERR_BEGIN_LONELY_ELSE
+                    "else without rescue is useless"
                 );
             }
 
@@ -11507,11 +11505,11 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
             yp_token_t keyword = parser->previous;
 
-            expect(parser, YP_TOKEN_BRACE_LEFT, YP_ERR_BEGIN_UPCASE_BRACE);
+            expect(parser, YP_TOKEN_BRACE_LEFT, "Expected '{' after 'BEGIN'.");
             yp_token_t opening = parser->previous;
             yp_statements_node_t *statements = parse_statements(parser, YP_CONTEXT_PREEXE);
 
-            expect(parser, YP_TOKEN_BRACE_RIGHT, YP_ERR_BEGIN_UPCASE_TERM);
+            expect(parser, YP_TOKEN_BRACE_RIGHT, "Expected '}' after 'BEGIN' statements.");
             return (yp_node_t *) yp_pre_execution_node_create(parser, &keyword, &opening, statements, &parser->previous);
         }
         case YP_TOKEN_KEYWORD_BREAK:
@@ -11544,7 +11542,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                         (parser->current_context->context == YP_CONTEXT_CLASS) ||
                         (parser->current_context->context == YP_CONTEXT_MODULE)
                     ) {
-                        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_RETURN_INVALID);
+                        yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Invalid return in class/module body");
                     }
                     return (yp_node_t *) yp_return_node_create(parser, &keyword, arguments.arguments);
                 }
@@ -11582,7 +11580,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
             if (accept(parser, YP_TOKEN_LESS_LESS)) {
                 yp_token_t operator = parser->previous;
-                yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_NOT, YP_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS);
+                yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_NOT, "Expected to find an expression after `<<`.");
 
                 yp_parser_scope_push(parser, true);
                 accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
@@ -11599,7 +11597,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     statements = (yp_node_t *) parse_rescues_as_begin(parser, (yp_statements_node_t *) statements);
                 }
 
-                expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_CLASS_TERM);
+                expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close `class` statement.");
 
                 yp_constant_id_list_t locals = parser->current_scope->locals;
                 yp_parser_scope_pop(parser);
@@ -11607,10 +11605,10 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 return (yp_node_t *) yp_singleton_class_node_create(parser, &locals, &class_keyword, &operator, expression, statements, &parser->previous);
             }
 
-            yp_node_t *constant_path = parse_expression(parser, YP_BINDING_POWER_INDEX, YP_ERR_CLASS_NAME);
+            yp_node_t *constant_path = parse_expression(parser, YP_BINDING_POWER_INDEX, "Expected to find a class name after `class`.");
             yp_token_t name = parser->previous;
             if (name.type != YP_TOKEN_CONSTANT) {
-                yp_diagnostic_list_append(&parser->error_list, name.start, name.end, YP_ERR_CLASS_NAME);
+                yp_diagnostic_list_append(&parser->error_list, name.start, name.end, "Expected a constant name after `class`.");
             }
 
             yp_token_t inheritance_operator;
@@ -11623,7 +11621,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 parser->command_start = true;
                 parser_lex(parser);
 
-                superclass = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, YP_ERR_CLASS_SUPERCLASS);
+                superclass = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, "Expected to find a superclass after `<`.");
             } else {
                 inheritance_operator = not_provided(parser);
                 superclass = NULL;
@@ -11644,10 +11642,10 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 statements = (yp_node_t *) parse_rescues_as_begin(parser, (yp_statements_node_t *) statements);
             }
 
-            expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_CLASS_TERM);
+            expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close `class` statement.");
 
             if (context_def_p(parser)) {
-                yp_diagnostic_list_append(&parser->error_list, class_keyword.start, class_keyword.end, YP_ERR_CLASS_IN_METHOD);
+                yp_diagnostic_list_append(&parser->error_list, class_keyword.start, class_keyword.end, "Class definition in method body");
             }
 
             yp_constant_id_list_t locals = parser->current_scope->locals;
@@ -11686,7 +11684,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                         name = parse_method_definition_name(parser);
 
                         if (name.type == YP_TOKEN_MISSING) {
-                            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_DEF_NAME_AFTER_RECEIVER);
+                            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Expected a method name after receiver.");
                         }
                     } else {
                         name = parser->previous;
@@ -11754,7 +11752,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
                         name = parse_method_definition_name(parser);
                         if (name.type == YP_TOKEN_MISSING) {
-                            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_DEF_NAME_AFTER_RECEIVER);
+                            yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Expected a method name after receiver.");
                         }
                     } else {
                         name = identifier;
@@ -11764,13 +11762,13 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 case YP_TOKEN_PARENTHESIS_LEFT: {
                     parser_lex(parser);
                     yp_token_t lparen = parser->previous;
-                    yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_STATEMENT, YP_ERR_DEF_RECEIVER);
+                    yp_node_t *expression = parse_expression(parser, YP_BINDING_POWER_STATEMENT, "Expected to be able to parse receiver.");
 
-                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN);
+                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected closing ')' for receiver.");
                     yp_token_t rparen = parser->previous;
 
                     lex_state_set(parser, YP_LEX_STATE_FNAME);
-                    expect_any(parser, YP_ERR_DEF_RECEIVER_TERM, 2, YP_TOKEN_DOT, YP_TOKEN_COLON_COLON);
+                    expect_any(parser, "Expected '.' or '::' after receiver", 2, YP_TOKEN_DOT, YP_TOKEN_COLON_COLON);
 
                     operator = parser->previous;
                     receiver = (yp_node_t *) yp_parentheses_node_create(parser, &lparen, expression, &rparen);
@@ -11784,7 +11782,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     name = parse_method_definition_name(parser);
 
                     if (name.type == YP_TOKEN_MISSING) {
-                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_DEF_NAME);
+                        yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Expected a method name after receiver.");
                     }
                     break;
             }
@@ -11807,7 +11805,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     lex_state_set(parser, YP_LEX_STATE_BEG);
                     parser->command_start = true;
 
-                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_DEF_PARAMS_TERM_PAREN);
+                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected ')' after left parenthesis.");
                     rparen = parser->previous;
                     break;
                 }
@@ -11838,18 +11836,18 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
             if (accept(parser, YP_TOKEN_EQUAL)) {
                 if (token_is_setter_name(&name)) {
-                    yp_diagnostic_list_append(&parser->error_list, name.start, name.end, YP_ERR_DEF_ENDLESS_SETTER);
+                    yp_diagnostic_list_append(&parser->error_list, name.start, name.end, "Setter method cannot be defined in an endless method definition");
                 }
                 equal = parser->previous;
 
                 context_push(parser, YP_CONTEXT_DEF);
                 statements = (yp_node_t *) yp_statements_node_create(parser);
 
-                yp_node_t *statement = parse_expression(parser, YP_BINDING_POWER_DEFINED + 1, YP_ERR_DEF_ENDLESS);
+                yp_node_t *statement = parse_expression(parser, YP_BINDING_POWER_DEFINED + 1, "Expected to be able to parse body of endless method definition.");
 
                 if (accept(parser, YP_TOKEN_KEYWORD_RESCUE_MODIFIER)) {
                     yp_token_t rescue_keyword = parser->previous;
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_RESCUE_MODIFIER_VALUE);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the rescue keyword.");
                     yp_rescue_modifier_node_t *rescue_node = yp_rescue_modifier_node_create(parser, statement, &rescue_keyword, value);
                     statement = (yp_node_t *)rescue_node;
                 }
@@ -11863,7 +11861,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 if (lparen.type == YP_TOKEN_NOT_PROVIDED) {
                     lex_state_set(parser, YP_LEX_STATE_BEG);
                     parser->command_start = true;
-                    expect_any(parser, YP_ERR_DEF_PARAMS_TERM, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
+                    expect_any(parser, "Expected a terminator after the parameters", 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
                 } else {
                     accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
                 }
@@ -11884,7 +11882,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
                 yp_accepts_block_stack_pop(parser);
                 yp_do_loop_stack_pop(parser);
-                expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_DEF_TERM);
+                expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close `def` statement.");
                 end_keyword = parser->previous;
             }
 
@@ -11916,18 +11914,18 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
             if (accept(parser, YP_TOKEN_PARENTHESIS_LEFT)) {
                 lparen = parser->previous;
-                expression = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, YP_ERR_DEFINED_EXPRESSION);
+                expression = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, "Expected expression after `defined?`.");
 
                 if (parser->recovering) {
                     rparen = not_provided(parser);
                 } else {
-                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN);
+                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected ')' after 'defined?' expression.");
                     rparen = parser->previous;
                 }
             } else {
                 lparen = not_provided(parser);
                 rparen = not_provided(parser);
-                expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_DEFINED_EXPRESSION);
+                expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected expression after `defined?`.");
             }
 
             return (yp_node_t *) yp_defined_node_create(
@@ -11942,11 +11940,11 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
             yp_token_t keyword = parser->previous;
 
-            expect(parser, YP_TOKEN_BRACE_LEFT, YP_ERR_END_UPCASE_BRACE);
+            expect(parser, YP_TOKEN_BRACE_LEFT, "Expected '{' after 'END'.");
             yp_token_t opening = parser->previous;
             yp_statements_node_t *statements = parse_statements(parser, YP_CONTEXT_POSTEXE);
 
-            expect(parser, YP_TOKEN_BRACE_RIGHT, YP_ERR_END_UPCASE_TERM);
+            expect(parser, YP_TOKEN_BRACE_RIGHT, "Expected '}' after 'END' statements.");
             return (yp_node_t *) yp_post_execution_node_create(parser, &keyword, &opening, statements, &parser->previous);
         }
         case YP_TOKEN_KEYWORD_FALSE:
@@ -11959,10 +11957,10 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             yp_node_t *index = parse_targets(parser, NULL, YP_BINDING_POWER_INDEX);
             yp_do_loop_stack_push(parser, true);
 
-            expect(parser, YP_TOKEN_KEYWORD_IN, YP_ERR_FOR_IN);
+            expect(parser, YP_TOKEN_KEYWORD_IN, "Expected keyword in.");
             yp_token_t in_keyword = parser->previous;
 
-            yp_node_t *collection = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, YP_ERR_FOR_COLLECTION);
+            yp_node_t *collection = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, "Expected collection.");
             yp_do_loop_stack_pop(parser);
 
             yp_token_t do_keyword;
@@ -11977,7 +11975,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
             if (!accept(parser, YP_TOKEN_KEYWORD_END)) {
                 statements = parse_statements(parser, YP_CONTEXT_FOR);
-                expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_FOR_TERM);
+                expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close for loop.");
             }
 
             return (yp_node_t *) yp_for_node_create(parser, index, collection, statements, &for_keyword, &in_keyword, &do_keyword, &parser->previous);
@@ -12026,17 +12024,17 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 if (accept(parser, YP_TOKEN_PARENTHESIS_RIGHT)) {
                     arguments.closing_loc = YP_LOCATION_TOKEN_VALUE(&parser->previous);
                 } else {
-                    receiver = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, YP_ERR_NOT_EXPRESSION);
+                    receiver = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, "Expected expression after `not`.");
                     yp_flip_flop(receiver);
 
                     if (!parser->recovering) {
                         accept(parser, YP_TOKEN_NEWLINE);
-                        expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN);
+                        expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected ')' after 'not' expression.");
                         arguments.closing_loc = YP_LOCATION_TOKEN_VALUE(&parser->previous);
                     }
                 }
             } else {
-                receiver = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_NOT_EXPRESSION);
+                receiver = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected expression after `not`.");
                 yp_flip_flop(receiver);
             }
 
@@ -12049,7 +12047,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
 
             yp_token_t module_keyword = parser->previous;
-            yp_node_t *constant_path = parse_expression(parser, YP_BINDING_POWER_INDEX, YP_ERR_MODULE_NAME);
+            yp_node_t *constant_path = parse_expression(parser, YP_BINDING_POWER_INDEX, "Expected to find a module name after `module`.");
             yp_token_t name;
 
             // If we can recover from a syntax error that occurred while parsing
@@ -12062,7 +12060,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             while (accept(parser, YP_TOKEN_COLON_COLON)) {
                 yp_token_t double_colon = parser->previous;
 
-                expect(parser, YP_TOKEN_CONSTANT, YP_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT);
+                expect(parser, YP_TOKEN_CONSTANT, "Expected to find a module name after `::`.");
                 yp_node_t *constant = (yp_node_t *) yp_constant_read_node_create(parser, &parser->previous);
 
                 constant_path = (yp_node_t *) yp_constant_path_node_create(parser, constant_path, &double_colon, constant);
@@ -12073,7 +12071,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             // syntax error. We handle that here as well.
             name = parser->previous;
             if (name.type != YP_TOKEN_CONSTANT) {
-                yp_diagnostic_list_append(&parser->error_list, name.start, name.end, YP_ERR_MODULE_NAME);
+                yp_diagnostic_list_append(&parser->error_list, name.start, name.end, "Expected to find a module name after `module`.");
             }
 
             yp_parser_scope_push(parser, true);
@@ -12094,10 +12092,10 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             yp_constant_id_list_t locals = parser->current_scope->locals;
             yp_parser_scope_pop(parser);
 
-            expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_MODULE_TERM);
+            expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close `module` statement.");
 
             if (context_def_p(parser)) {
-                yp_diagnostic_list_append(&parser->error_list, module_keyword.start, module_keyword.end, YP_ERR_MODULE_IN_METHOD);
+                yp_diagnostic_list_append(&parser->error_list, module_keyword.start, module_keyword.end, "Module definition in method body");
             }
 
             return (yp_node_t *) yp_module_node_create(parser, &locals, &module_keyword, constant_path, &name, statements, &parser->previous);
@@ -12122,7 +12120,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
             yp_token_t keyword = parser->previous;
 
-            yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, YP_ERR_CONDITIONAL_UNTIL_PREDICATE);
+            yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, "Expected predicate expression after `until`.");
             yp_do_loop_stack_pop(parser);
 
             accept_any(parser, 3, YP_TOKEN_KEYWORD_DO_LOOP, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
@@ -12133,7 +12131,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 statements = parse_statements(parser, YP_CONTEXT_UNTIL);
                 yp_accepts_block_stack_pop(parser);
                 accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
-                expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_UNTIL_TERM);
+                expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close `until` statement.");
             }
 
             return (yp_node_t *) yp_until_node_create(parser, &keyword, &parser->previous, predicate, statements, 0);
@@ -12143,7 +12141,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
             yp_token_t keyword = parser->previous;
 
-            yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, YP_ERR_CONDITIONAL_WHILE_PREDICATE);
+            yp_node_t *predicate = parse_expression(parser, YP_BINDING_POWER_COMPOSITION, "Expected predicate expression after `while`.");
             yp_do_loop_stack_pop(parser);
 
             accept_any(parser, 3, YP_TOKEN_KEYWORD_DO_LOOP, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
@@ -12154,7 +12152,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 statements = parse_statements(parser, YP_CONTEXT_WHILE);
                 yp_accepts_block_stack_pop(parser);
                 accept_any(parser, 2, YP_TOKEN_NEWLINE, YP_TOKEN_SEMICOLON);
-                expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_WHILE_TERM);
+                expect(parser, YP_TOKEN_KEYWORD_END, "Expected `end` to close `while` statement.");
             }
 
             return (yp_node_t *) yp_while_node_create(parser, &keyword, &parser->previous, predicate, statements, 0);
@@ -12167,7 +12165,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 accept(parser, YP_TOKEN_WORDS_SEP);
                 if (match_type_p(parser, YP_TOKEN_STRING_END)) break;
 
-                expect(parser, YP_TOKEN_STRING_CONTENT, YP_ERR_LIST_I_LOWER_ELEMENT);
+                expect(parser, YP_TOKEN_STRING_CONTENT, "Expected a symbol in a `%i` list.");
 
                 yp_token_t opening = not_provided(parser);
                 yp_token_t closing = not_provided(parser);
@@ -12176,7 +12174,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 yp_array_node_elements_append(array, symbol);
             }
 
-            expect(parser, YP_TOKEN_STRING_END, YP_ERR_LIST_I_LOWER_TERM);
+            expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for a `%i` list.");
             yp_array_node_close_set(array, &parser->previous);
 
             return (yp_node_t *) array;
@@ -12312,7 +12310,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                         break;
                     }
                     default:
-                        expect(parser, YP_TOKEN_STRING_CONTENT, YP_ERR_LIST_I_UPPER_ELEMENT);
+                        expect(parser, YP_TOKEN_STRING_CONTENT, "Expected a symbol in a `%I` list.");
                         parser_lex(parser);
                         break;
                 }
@@ -12323,7 +12321,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 yp_array_node_elements_append(array, current);
             }
 
-            expect(parser, YP_TOKEN_STRING_END, YP_ERR_LIST_I_UPPER_TERM);
+            expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for a `%I` list.");
             yp_array_node_close_set(array, &parser->previous);
 
             return (yp_node_t *) array;
@@ -12339,7 +12337,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 accept(parser, YP_TOKEN_WORDS_SEP);
                 if (match_type_p(parser, YP_TOKEN_STRING_END)) break;
 
-                expect(parser, YP_TOKEN_STRING_CONTENT, YP_ERR_LIST_W_LOWER_ELEMENT);
+                expect(parser, YP_TOKEN_STRING_CONTENT, "Expected a string in a `%w` list.");
 
                 yp_token_t opening = not_provided(parser);
                 yp_token_t closing = not_provided(parser);
@@ -12347,7 +12345,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 yp_array_node_elements_append(array, string);
             }
 
-            expect(parser, YP_TOKEN_STRING_END, YP_ERR_LIST_W_LOWER_TERM);
+            expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for a `%w` list.");
             yp_array_node_close_set(array, &parser->previous);
 
             return (yp_node_t *) array;
@@ -12463,7 +12461,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                         break;
                     }
                     default:
-                        expect(parser, YP_TOKEN_STRING_CONTENT, YP_ERR_LIST_W_UPPER_ELEMENT);
+                        expect(parser, YP_TOKEN_STRING_CONTENT, "Expected a string in a `%W` list.");
                         parser_lex(parser);
                         break;
                 }
@@ -12474,7 +12472,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 yp_array_node_elements_append(array, current);
             }
 
-            expect(parser, YP_TOKEN_STRING_END, YP_ERR_LIST_W_UPPER_TERM);
+            expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for a `%W` list.");
             yp_array_node_close_set(array, &parser->previous);
 
             return (yp_node_t *) array;
@@ -12538,7 +12536,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 }
             }
 
-            expect(parser, YP_TOKEN_REGEXP_END, YP_ERR_REGEXP_TERM);
+            expect(parser, YP_TOKEN_REGEXP_END, "Expected a closing delimiter for a regular expression.");
             yp_interpolated_regular_expression_node_closing_set(node, &parser->previous);
 
             return (yp_node_t *) node;
@@ -12602,7 +12600,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 }
             }
 
-            expect(parser, YP_TOKEN_STRING_END, YP_ERR_XSTRING_TERM);
+            expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for an xstring.");
             yp_interpolated_xstring_node_closing_set(node, &parser->previous);
             return (yp_node_t *) node;
         }
@@ -12620,7 +12618,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             yp_node_t *name = NULL;
 
             if (token_begins_expression_p(parser->current.type)) {
-                name = parse_expression(parser, YP_BINDING_POWER_INDEX, YP_ERR_EXPECT_EXPRESSION_AFTER_STAR);
+                name = parse_expression(parser, YP_BINDING_POWER_INDEX, "Expected an expression after '*'.");
             }
 
             yp_node_t *splat = (yp_node_t *) yp_splat_node_create(parser, &operator, name);
@@ -12630,7 +12628,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
 
             yp_token_t operator = parser->previous;
-            yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, YP_ERR_UNARY_RECEIVER_BANG);
+            yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, "Expected a receiver after unary !.");
             yp_call_node_t *node = yp_call_node_unary_create(parser, &operator, receiver, "!");
 
             yp_flip_flop(receiver);
@@ -12640,7 +12638,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
 
             yp_token_t operator = parser->previous;
-            yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, YP_ERR_UNARY_RECEIVER_TILDE);
+            yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, "Expected a receiver after unary ~.");
             yp_call_node_t *node = yp_call_node_unary_create(parser, &operator, receiver, "~");
 
             return (yp_node_t *) node;
@@ -12649,7 +12647,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
 
             yp_token_t operator = parser->previous;
-            yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, YP_ERR_UNARY_RECEIVER_MINUS);
+            yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, "Expected a receiver after unary -.");
             yp_call_node_t *node = yp_call_node_unary_create(parser, &operator, receiver, "-@");
 
             return (yp_node_t *) node;
@@ -12658,7 +12656,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
 
             yp_token_t operator = parser->previous;
-            yp_node_t *node = parse_expression(parser, yp_binding_powers[parser->previous.type].right, YP_ERR_UNARY_RECEIVER_MINUS);
+            yp_node_t *node = parse_expression(parser, yp_binding_powers[parser->previous.type].right, "Expected a receiver after unary -.");
 
             switch (YP_NODE_TYPE(node)) {
                 case YP_INTEGER_NODE:
@@ -12697,7 +12695,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     }
 
                     accept(parser, YP_TOKEN_NEWLINE);
-                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, YP_ERR_EXPECT_RPAREN);
+                    expect(parser, YP_TOKEN_PARENTHESIS_RIGHT, "Expected ')' after left parenthesis.");
 
                     yp_block_parameters_node_closing_set(params, &parser->previous);
                     break;
@@ -12724,10 +12722,10 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 
                 if (!accept(parser, YP_TOKEN_BRACE_RIGHT)) {
                     body = (yp_node_t *) parse_statements(parser, YP_CONTEXT_LAMBDA_BRACES);
-                    expect(parser, YP_TOKEN_BRACE_RIGHT, YP_ERR_LAMBDA_TERM_BRACE);
+                    expect(parser, YP_TOKEN_BRACE_RIGHT, "Expecting '}' to close lambda block.");
                 }
             } else {
-                expect(parser, YP_TOKEN_KEYWORD_DO, YP_ERR_LAMBDA_OPEN);
+                expect(parser, YP_TOKEN_KEYWORD_DO, "Expected a 'do' keyword or a '{' to open lambda block.");
                 opening = parser->previous;
 
                 if (!match_any_type_p(parser, 3, YP_TOKEN_KEYWORD_END, YP_TOKEN_KEYWORD_RESCUE, YP_TOKEN_KEYWORD_ENSURE)) {
@@ -12741,7 +12739,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     body = (yp_node_t *) parse_rescues_as_begin(parser, (yp_statements_node_t *) body);
                 }
 
-                expect(parser, YP_TOKEN_KEYWORD_END, YP_ERR_LAMBDA_TERM_END);
+                expect(parser, YP_TOKEN_KEYWORD_END, "Expecting 'end' keyword to close lambda block.");
             }
 
             yp_constant_id_list_t locals = parser->current_scope->locals;
@@ -12753,7 +12751,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
             parser_lex(parser);
 
             yp_token_t operator = parser->previous;
-            yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, YP_ERR_UNARY_RECEIVER_PLUS);
+            yp_node_t *receiver = parse_expression(parser, yp_binding_powers[parser->previous.type].right, "Expected a receiver after unary +.");
             yp_call_node_t *node = yp_call_node_unary_create(parser, &operator, receiver, "+@");
 
             return (yp_node_t *) node;
@@ -12795,7 +12793,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                 } else if (!lex_interpolation) {
                     // If we don't accept interpolation then we expect the
                     // string to start with a single string content node.
-                    expect(parser, YP_TOKEN_STRING_CONTENT, YP_ERR_EXPECT_STRING_CONTENT);
+                    expect(parser, YP_TOKEN_STRING_CONTENT, "Expected string content after opening delimiter.");
                     yp_token_t content = parser->previous;
 
                     // It is unfortunately possible to have multiple string
@@ -12822,12 +12820,12 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                             yp_node_list_append(&parts, part);
                         }
 
-                        expect(parser, YP_TOKEN_STRING_END, YP_ERR_STRING_LITERAL_TERM);
+                        expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for a string literal.");
                         node = (yp_node_t *) yp_interpolated_string_node_create(parser, &opening, &parts, &parser->previous);
                     } else if (accept(parser, YP_TOKEN_LABEL_END)) {
                         node = (yp_node_t *) yp_symbol_node_create_and_unescape(parser, &opening, &content, &parser->previous, YP_UNESCAPE_ALL);
                     } else {
-                        expect(parser, YP_TOKEN_STRING_END, YP_ERR_STRING_LITERAL_TERM);
+                        expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for a string literal.");
                         node = (yp_node_t *) yp_string_node_create_and_unescape(parser, &opening, &content, &parser->previous, YP_UNESCAPE_MINIMAL);
                     }
                 } else if (match_type_p(parser, YP_TOKEN_STRING_CONTENT)) {
@@ -12859,7 +12857,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                         if (accept(parser, YP_TOKEN_LABEL_END)) {
                             node = (yp_node_t *) yp_interpolated_symbol_node_create(parser, &opening, &parts, &parser->previous);
                         } else {
-                            expect(parser, YP_TOKEN_STRING_END, YP_ERR_STRING_INTERPOLATED_TERM);
+                            expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for an interpolated string.");
                             node = (yp_node_t *) yp_interpolated_string_node_create(parser, &opening, &parts, &parser->previous);
                         }
                     }
@@ -12877,7 +12875,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     if (accept(parser, YP_TOKEN_LABEL_END)) {
                         node = (yp_node_t *) yp_interpolated_symbol_node_create(parser, &opening, &parts, &parser->previous);
                     } else {
-                        expect(parser, YP_TOKEN_STRING_END, YP_ERR_STRING_INTERPOLATED_TERM);
+                        expect(parser, YP_TOKEN_STRING_END, "Expected a closing delimiter for an interpolated string.");
                         node = (yp_node_t *) yp_interpolated_string_node_create(parser, &opening, &parts, &parser->previous);
                     }
                 }
@@ -12898,7 +12896,7 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
                     // parsed. If it cannot be concatenated with the previous
                     // node, then we'll need to add a syntax error.
                     if (!YP_NODE_TYPE_P(node, YP_STRING_NODE) && !YP_NODE_TYPE_P(node, YP_INTERPOLATED_STRING_NODE)) {
-                        yp_diagnostic_list_append(&parser->error_list, node->location.start, node->location.end, YP_ERR_STRING_CONCATENATION);
+                        yp_diagnostic_list_append(&parser->error_list, node->location.start, node->location.end, "Unexpected string concatenation.");
                     }
 
                     // Either way we will create a concat node to hold the
@@ -12925,8 +12923,8 @@ parse_expression_prefix(yp_parser_t *parser, yp_binding_power_t binding_power) {
 }
 
 static inline yp_node_t *
-parse_assignment_value(yp_parser_t *parser, yp_binding_power_t previous_binding_power, yp_binding_power_t binding_power, yp_diagnostic_id_t diag_id) {
-    yp_node_t *value = parse_starred_expression(parser, binding_power, diag_id);
+parse_assignment_value(yp_parser_t *parser, yp_binding_power_t previous_binding_power, yp_binding_power_t binding_power, const char *message) {
+    yp_node_t *value = parse_starred_expression(parser, binding_power, message);
 
     if (previous_binding_power == YP_BINDING_POWER_STATEMENT && (YP_NODE_TYPE_P(value, YP_SPLAT_NODE) || match_type_p(parser, YP_TOKEN_COMMA))) {
         yp_token_t opening = not_provided(parser);
@@ -12936,7 +12934,7 @@ parse_assignment_value(yp_parser_t *parser, yp_binding_power_t previous_binding_
         value = (yp_node_t *) array;
 
         while (accept(parser, YP_TOKEN_COMMA)) {
-            yp_node_t *element = parse_starred_expression(parser, binding_power, YP_ERR_ARRAY_ELEMENT);
+            yp_node_t *element = parse_starred_expression(parser, binding_power, "Expected an element for the array.");
             yp_array_node_elements_append(array, element);
             if (YP_NODE_TYPE_P(element, YP_MISSING_NODE)) break;
         }
@@ -12965,7 +12963,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 /* fallthrough */
                 case YP_CASE_WRITABLE: {
                     parser_lex(parser);
-                    yp_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_EQUAL);
+                    yp_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, "Expected a value after =.");
                     return parse_write(parser, node, &token, value);
                 }
                 case YP_SPLAT_NODE: {
@@ -12974,7 +12972,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     switch (YP_NODE_TYPE(splat_node->expression)) {
                         case YP_CASE_WRITABLE:
                             parser_lex(parser);
-                            yp_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_EQUAL);
+                            yp_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, "Expected a value after =.");
                             return parse_write(parser, (yp_node_t *) splat_node, &token, value);
                         default:
                             break;
@@ -12987,7 +12985,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     // In this case we have an = sign, but we don't know what it's for. We
                     // need to treat it as an error. For now, we'll mark it as an error
                     // and just skip right past it.
-                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, YP_ERR_EXPECT_EXPRESSION_AFTER_EQUAL);
+                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, "Unexpected `='.");
                     return node;
             }
         }
@@ -12995,12 +12993,12 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
             switch (YP_NODE_TYPE(node)) {
                 case YP_BACK_REFERENCE_READ_NODE:
                 case YP_NUMBERED_REFERENCE_READ_NODE:
-                    yp_diagnostic_list_append(&parser->error_list, node->location.start, node->location.end, YP_ERR_WRITE_TARGET_READONLY);
+                    yp_diagnostic_list_append(&parser->error_list, node->location.start, node->location.end, "Can't set variable");
                 /* fallthrough */
                 case YP_GLOBAL_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after &&=");
                     yp_node_t *result = (yp_node_t *) yp_global_variable_and_write_node_create(parser, node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13009,7 +13007,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_CLASS_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after &&=");
                     yp_node_t *result = (yp_node_t *) yp_class_variable_and_write_node_create(parser, (yp_class_variable_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13018,13 +13016,13 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_CONSTANT_PATH_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after &&=");
                     return (yp_node_t *) yp_constant_path_and_write_node_create(parser, (yp_constant_path_node_t *) node, &token, value);
                 }
                 case YP_CONSTANT_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after &&=");
                     yp_node_t *result = (yp_node_t *) yp_constant_and_write_node_create(parser, (yp_constant_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13033,7 +13031,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_INSTANCE_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after &&=");
                     yp_node_t *result = (yp_node_t *) yp_instance_variable_and_write_node_create(parser, (yp_instance_variable_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13043,7 +13041,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     yp_local_variable_read_node_t *cast = (yp_local_variable_read_node_t *) node;
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after &&=");
                     yp_node_t *result = (yp_node_t *) yp_local_variable_and_write_node_create(parser, node, &token, value, cast->name, cast->depth);
 
                     yp_node_destroy(parser, node);
@@ -13060,11 +13058,11 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                         yp_constant_id_t constant_id = yp_parser_local_add_location(parser, message_loc.start, message_loc.end);
 
                         if (token_is_numbered_parameter(message_loc.start, message_loc.end)) {
-                            yp_diagnostic_list_append(&parser->error_list, message_loc.start, message_loc.end, YP_ERR_PARAMETER_NUMBERED_RESERVED);
+                            yp_diagnostic_list_append(&parser->error_list, message_loc.start, message_loc.end, "reserved for numbered parameter");
                         }
 
                         parser_lex(parser);
-                        yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                        yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after &&=");
                         yp_node_t *result = (yp_node_t *) yp_local_variable_and_write_node_create(parser, node, &token, value, constant_id, 0);
 
                         yp_node_destroy(parser, node);
@@ -13074,12 +13072,12 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     parser_lex(parser);
                     node = parse_target(parser, node);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after &&=");
                     return (yp_node_t *) yp_call_and_write_node_create(parser, (yp_call_node_t *) node, &token, value);
                 }
                 case YP_MULTI_WRITE_NODE: {
                     parser_lex(parser);
-                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, YP_ERR_AMPAMPEQ_MULTI_ASSIGN);
+                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, "Cannot use `&&=' on a multi-write.");
                     return node;
                 }
                 default:
@@ -13088,7 +13086,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     // In this case we have an &&= sign, but we don't know what it's for.
                     // We need to treat it as an error. For now, we'll mark it as an error
                     // and just skip right past it.
-                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, YP_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
+                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, "Unexpected `&&='.");
                     return node;
             }
         }
@@ -13096,12 +13094,12 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
             switch (YP_NODE_TYPE(node)) {
                 case YP_BACK_REFERENCE_READ_NODE:
                 case YP_NUMBERED_REFERENCE_READ_NODE:
-                    yp_diagnostic_list_append(&parser->error_list, node->location.start, node->location.end, YP_ERR_WRITE_TARGET_READONLY);
+                    yp_diagnostic_list_append(&parser->error_list, node->location.start, node->location.end, "Can't set variable");
                 /* fallthrough */
                 case YP_GLOBAL_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after ||=");
                     yp_node_t *result = (yp_node_t *) yp_global_variable_or_write_node_create(parser, node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13110,7 +13108,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_CLASS_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after ||=");
                     yp_node_t *result = (yp_node_t *) yp_class_variable_or_write_node_create(parser, (yp_class_variable_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13119,13 +13117,13 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_CONSTANT_PATH_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after ||=");
                     return (yp_node_t *) yp_constant_path_or_write_node_create(parser, (yp_constant_path_node_t *) node, &token, value);
                 }
                 case YP_CONSTANT_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after ||=");
                     yp_node_t *result = (yp_node_t *) yp_constant_or_write_node_create(parser, (yp_constant_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13134,7 +13132,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_INSTANCE_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after ||=");
                     yp_node_t *result = (yp_node_t *) yp_instance_variable_or_write_node_create(parser, (yp_instance_variable_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13144,7 +13142,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     yp_local_variable_read_node_t *cast = (yp_local_variable_read_node_t *) node;
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after ||=");
                     yp_node_t *result = (yp_node_t *) yp_local_variable_or_write_node_create(parser, node, &token, value, cast->name, cast->depth);
 
                     yp_node_destroy(parser, node);
@@ -13161,11 +13159,11 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                         yp_constant_id_t constant_id = yp_parser_local_add_location(parser, message_loc.start, message_loc.end);
 
                         if (token_is_numbered_parameter(message_loc.start, message_loc.end)) {
-                            yp_diagnostic_list_append(&parser->error_list, message_loc.start, message_loc.end, YP_ERR_PARAMETER_NUMBERED_RESERVED);
+                            yp_diagnostic_list_append(&parser->error_list, message_loc.start, message_loc.end, "reserved for numbered parameter");
                         }
 
                         parser_lex(parser);
-                        yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                        yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after ||=");
                         yp_node_t *result = (yp_node_t *) yp_local_variable_or_write_node_create(parser, node, &token, value, constant_id, 0);
 
                         yp_node_destroy(parser, node);
@@ -13175,12 +13173,12 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     parser_lex(parser);
                     node = parse_target(parser, node);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after ||=");
                     return (yp_node_t *) yp_call_or_write_node_create(parser, (yp_call_node_t *) node, &token, value);
                 }
                 case YP_MULTI_WRITE_NODE: {
                     parser_lex(parser);
-                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, YP_ERR_PIPEPIPEEQ_MULTI_ASSIGN);
+                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, "Cannot use `||=' on a multi-write.");
                     return node;
                 }
                 default:
@@ -13189,7 +13187,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     // In this case we have an ||= sign, but we don't know what it's for.
                     // We need to treat it as an error. For now, we'll mark it as an error
                     // and just skip right past it.
-                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, YP_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
+                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, "Unexpected `||='.");
                     return node;
             }
         }
@@ -13207,12 +13205,12 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
             switch (YP_NODE_TYPE(node)) {
                 case YP_BACK_REFERENCE_READ_NODE:
                 case YP_NUMBERED_REFERENCE_READ_NODE:
-                    yp_diagnostic_list_append(&parser->error_list, node->location.start, node->location.end, YP_ERR_WRITE_TARGET_READONLY);
+                    yp_diagnostic_list_append(&parser->error_list, node->location.start, node->location.end, "Can't set variable");
                 /* fallthrough */
                 case YP_GLOBAL_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the operator.");
                     yp_node_t *result = (yp_node_t *) yp_global_variable_operator_write_node_create(parser, node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13221,7 +13219,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_CLASS_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the operator.");
                     yp_node_t *result = (yp_node_t *) yp_class_variable_operator_write_node_create(parser, (yp_class_variable_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13230,13 +13228,13 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_CONSTANT_PATH_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the operator.");
                     return (yp_node_t *) yp_constant_path_operator_write_node_create(parser, (yp_constant_path_node_t *) node, &token, value);
                 }
                 case YP_CONSTANT_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the operator.");
                     yp_node_t *result = (yp_node_t *) yp_constant_operator_write_node_create(parser, (yp_constant_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13245,7 +13243,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 case YP_INSTANCE_VARIABLE_READ_NODE: {
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the operator.");
                     yp_node_t *result = (yp_node_t *) yp_instance_variable_operator_write_node_create(parser, (yp_instance_variable_read_node_t *) node, &token, value);
 
                     yp_node_destroy(parser, node);
@@ -13255,7 +13253,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     yp_local_variable_read_node_t *cast = (yp_local_variable_read_node_t *) node;
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the operator.");
                     yp_node_t *result = (yp_node_t *) yp_local_variable_operator_write_node_create(parser, node, &token, value, cast->name, cast->depth);
 
                     yp_node_destroy(parser, node);
@@ -13272,11 +13270,11 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                         yp_constant_id_t constant_id = yp_parser_local_add_location(parser, message_loc.start, message_loc.end);
 
                         if (token_is_numbered_parameter(message_loc.start, message_loc.end)) {
-                            yp_diagnostic_list_append(&parser->error_list, message_loc.start, message_loc.end, YP_ERR_PARAMETER_NUMBERED_RESERVED);
+                            yp_diagnostic_list_append(&parser->error_list, message_loc.start, message_loc.end, "reserved for numbered parameter");
                         }
 
                         parser_lex(parser);
-                        yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                        yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the operator.");
                         yp_node_t *result = (yp_node_t *) yp_local_variable_operator_write_node_create(parser, node, &token, value, constant_id, 0);
 
                         yp_node_destroy(parser, node);
@@ -13286,12 +13284,12 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     node = parse_target(parser, node);
                     parser_lex(parser);
 
-                    yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the operator.");
                     return (yp_node_t *) yp_call_operator_write_node_create(parser, (yp_call_node_t *) node, &token, value);
                 }
                 case YP_MULTI_WRITE_NODE: {
                     parser_lex(parser);
-                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, YP_ERR_OPERATOR_MULTI_ASSIGN);
+                    yp_diagnostic_list_append(&parser->error_list, token.start, token.end, "Unexpected operator.");
                     return node;
                 }
                 default:
@@ -13300,7 +13298,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     // In this case we have an operator but we don't know what it's for.
                     // We need to treat it as an error. For now, we'll mark it as an error
                     // and just skip right past it.
-                    yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                    yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, "Unexpected operator.");
                     return node;
             }
         }
@@ -13308,14 +13306,14 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
         case YP_TOKEN_KEYWORD_AND: {
             parser_lex(parser);
 
-            yp_node_t *right = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+            yp_node_t *right = parse_expression(parser, binding_power, "Expected a value after the operator.");
             return (yp_node_t *) yp_and_node_create(parser, node, &token, right);
         }
         case YP_TOKEN_KEYWORD_OR:
         case YP_TOKEN_PIPE_PIPE: {
             parser_lex(parser);
 
-            yp_node_t *right = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+            yp_node_t *right = parse_expression(parser, binding_power, "Expected a value after the operator.");
             return (yp_node_t *) yp_or_node_create(parser, node, &token, right);
         }
         case YP_TOKEN_EQUAL_TILDE: {
@@ -13326,7 +13324,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
             //
             // In this case, `foo` should be a method call and not a local yet.
             parser_lex(parser);
-            yp_node_t *argument = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+            yp_node_t *argument = parse_expression(parser, binding_power, "Expected a value after the operator.");
 
             // If the receiver of this =~ is a regular expression node, then we need
             // to introduce local variables for it based on its named capture groups.
@@ -13377,7 +13375,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
         case YP_TOKEN_STAR_STAR: {
             parser_lex(parser);
 
-            yp_node_t *argument = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+            yp_node_t *argument = parse_expression(parser, binding_power, "Expected a value after the operator.");
             return (yp_node_t *) yp_call_node_binary_create(parser, node, &token, argument);
         }
         case YP_TOKEN_AMPERSAND_DOT:
@@ -13404,7 +13402,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     break;
                 }
                 default: {
-                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, YP_ERR_DEF_NAME);
+                    yp_diagnostic_list_append(&parser->error_list, parser->current.start, parser->current.end, "Expected a valid method name");
                     message = (yp_token_t) { .type = YP_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end };
                 }
             }
@@ -13429,7 +13427,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
 
             yp_node_t *right = NULL;
             if (token_begins_expression_p(parser->current.type)) {
-                right = parse_expression(parser, binding_power, YP_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
+                right = parse_expression(parser, binding_power, "Expected a value after the operator.");
             }
 
             return (yp_node_t *) yp_range_node_create(parser, node, &token, right);
@@ -13438,14 +13436,14 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
             yp_token_t keyword = parser->current;
             parser_lex(parser);
 
-            yp_node_t *predicate = parse_expression(parser, binding_power, YP_ERR_CONDITIONAL_IF_PREDICATE);
+            yp_node_t *predicate = parse_expression(parser, binding_power, "Expected a predicate after `if'.");
             return (yp_node_t *) yp_if_node_modifier_create(parser, node, &keyword, predicate);
         }
         case YP_TOKEN_KEYWORD_UNLESS_MODIFIER: {
             yp_token_t keyword = parser->current;
             parser_lex(parser);
 
-            yp_node_t *predicate = parse_expression(parser, binding_power, YP_ERR_CONDITIONAL_UNLESS_PREDICATE);
+            yp_node_t *predicate = parse_expression(parser, binding_power, "Expected a predicate after `unless'.");
             return (yp_node_t *) yp_unless_node_modifier_create(parser, node, &keyword, predicate);
         }
         case YP_TOKEN_KEYWORD_UNTIL_MODIFIER: {
@@ -13453,7 +13451,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
             yp_statements_node_t *statements = yp_statements_node_create(parser);
             yp_statements_node_body_append(statements, node);
 
-            yp_node_t *predicate = parse_expression(parser, binding_power, YP_ERR_CONDITIONAL_UNTIL_PREDICATE);
+            yp_node_t *predicate = parse_expression(parser, binding_power, "Expected a predicate after 'until'");
             return (yp_node_t *) yp_until_node_modifier_create(parser, &token, predicate, statements, YP_NODE_TYPE_P(node, YP_BEGIN_NODE) ? YP_LOOP_FLAGS_BEGIN_MODIFIER : 0);
         }
         case YP_TOKEN_KEYWORD_WHILE_MODIFIER: {
@@ -13461,12 +13459,12 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
             yp_statements_node_t *statements = yp_statements_node_create(parser);
             yp_statements_node_body_append(statements, node);
 
-            yp_node_t *predicate = parse_expression(parser, binding_power, YP_ERR_CONDITIONAL_WHILE_PREDICATE);
+            yp_node_t *predicate = parse_expression(parser, binding_power, "Expected a predicate after 'while'");
             return (yp_node_t *) yp_while_node_modifier_create(parser, &token, predicate, statements, YP_NODE_TYPE_P(node, YP_BEGIN_NODE) ? YP_LOOP_FLAGS_BEGIN_MODIFIER : 0);
         }
         case YP_TOKEN_QUESTION_MARK: {
             parser_lex(parser);
-            yp_node_t *true_expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_TERNARY_EXPRESSION_TRUE);
+            yp_node_t *true_expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value after '?'");
 
             if (parser->recovering) {
                 // If parsing the true expression of this ternary resulted in a syntax
@@ -13482,10 +13480,10 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
             }
 
             accept(parser, YP_TOKEN_NEWLINE);
-            expect(parser, YP_TOKEN_COLON, YP_ERR_TERNARY_COLON);
+            expect(parser, YP_TOKEN_COLON, "Expected ':' after true expression in ternary operator.");
 
             yp_token_t colon = parser->previous;
-            yp_node_t *false_expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, YP_ERR_TERNARY_EXPRESSION_FALSE);
+            yp_node_t *false_expression = parse_expression(parser, YP_BINDING_POWER_DEFINED, "Expected a value after ':'");
 
             return (yp_node_t *) yp_if_node_ternary_create(parser, node, true_expression, &colon, false_expression);
         }
@@ -13554,7 +13552,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                     return (yp_node_t *) yp_call_node_shorthand_create(parser, node, &delimiter, &arguments);
                 }
                 default: {
-                    yp_diagnostic_list_append(&parser->error_list, delimiter.start, delimiter.end, YP_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT);
+                    yp_diagnostic_list_append(&parser->error_list, delimiter.start, delimiter.end, "Expected identifier or constant after '::'");
                     yp_node_t *child = (yp_node_t *) yp_missing_node_create(parser, delimiter.start, delimiter.end);
                     return (yp_node_t *)yp_constant_path_node_create(parser, node, &delimiter, child);
                 }
@@ -13563,7 +13561,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
         case YP_TOKEN_KEYWORD_RESCUE_MODIFIER: {
             parser_lex(parser);
             accept(parser, YP_TOKEN_NEWLINE);
-            yp_node_t *value = parse_expression(parser, binding_power, YP_ERR_RESCUE_MODIFIER_VALUE);
+            yp_node_t *value = parse_expression(parser, binding_power, "Expected a value after the rescue keyword.");
 
             return (yp_node_t *) yp_rescue_modifier_node_create(parser, node, &token, value);
         }
@@ -13580,7 +13578,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
                 parse_arguments(parser, &arguments, false, YP_TOKEN_BRACKET_RIGHT);
                 yp_accepts_block_stack_pop(parser);
 
-                expect(parser, YP_TOKEN_BRACKET_RIGHT, YP_ERR_EXPECT_RBRACKET);
+                expect(parser, YP_TOKEN_BRACKET_RIGHT, "Expected ']' to close the bracket expression.");
             }
 
             arguments.closing_loc = YP_LOCATION_TOKEN_VALUE(&parser->previous);
@@ -13614,7 +13612,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
 
             parser_lex(parser);
 
-            yp_node_t *pattern = parse_pattern(parser, true, YP_ERR_PATTERN_EXPRESSION_AFTER_IN);
+            yp_node_t *pattern = parse_pattern(parser, true, "Expected a pattern after `in'.");
             parser->pattern_matching_newlines = previous_pattern_matching_newlines;
 
             return (yp_node_t *) yp_match_predicate_node_create(parser, node, pattern, &operator);
@@ -13629,7 +13627,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
 
             parser_lex(parser);
 
-            yp_node_t *pattern = parse_pattern(parser, true, YP_ERR_PATTERN_EXPRESSION_AFTER_HROCKET);
+            yp_node_t *pattern = parse_pattern(parser, true, "Expected a pattern after `=>'.");
             parser->pattern_matching_newlines = previous_pattern_matching_newlines;
 
             return (yp_node_t *) yp_match_required_node_create(parser, node, pattern, &operator);
@@ -13647,7 +13645,7 @@ parse_expression_infix(yp_parser_t *parser, yp_node_t *node, yp_binding_power_t
 // Consumers of this function should always check parser->recovering to
 // determine if they need to perform additional cleanup.
 static yp_node_t *
-parse_expression(yp_parser_t *parser, yp_binding_power_t binding_power, yp_diagnostic_id_t diag_id) {
+parse_expression(yp_parser_t *parser, yp_binding_power_t binding_power, const char *message) {
     yp_token_t recovery = parser->previous;
     yp_node_t *node = parse_expression_prefix(parser, binding_power);
 
@@ -13655,7 +13653,7 @@ parse_expression(yp_parser_t *parser, yp_binding_power_t binding_power, yp_diagn
     // parse_expression_prefix is going to be a missing node. In that case we need
     // to add the error message to the parser's error list.
     if (YP_NODE_TYPE_P(node, YP_MISSING_NODE)) {
-        yp_diagnostic_list_append(&parser->error_list, recovery.end, recovery.end, diag_id);
+        yp_diagnostic_list_append(&parser->error_list, recovery.end, recovery.end, message);
         return node;
     }
 


Test patch:
diff --git a/rakelib/test.rake b/rakelib/test.rake
index 739bebd1..8215d208 100644
--- a/rakelib/test.rake
+++ b/rakelib/test.rake
@@ -50,13 +50,4 @@ namespace :test do
   end
 
   LldbTestTask.new(lldb: :compile, &config)
-
-  desc "Run the tests for the rust bindings"
-  task :rust do
-    ["rust/yarp", "rust/yarp-sys"].each do |dir|
-      Dir.chdir(dir) do
-        sh("cargo test")
-      end
-    end
-  end
 end
diff --git a/rust/yarp-sys/tests/parser_tests.rs b/rust/yarp-sys/tests/parser_tests.rs
index 7e0246e4..98d79ec5 100644
--- a/rust/yarp-sys/tests/parser_tests.rs
+++ b/rust/yarp-sys/tests/parser_tests.rs
@@ -86,7 +86,7 @@ fn diagnostics_test() {
         let message = CStr::from_ptr((*error).message);
         assert_eq!(
             message.to_string_lossy(),
-            "Cannot parse the expression"
+            "Expected to be able to parse an expression."
         );
 
         let location = {
diff --git a/test/yarp/comments_test.rb b/test/yarp/comments_test.rb
index ac91eab4..ec419a9f 100644
--- a/test/yarp/comments_test.rb
+++ b/test/yarp/comments_test.rb
@@ -55,6 +55,30 @@ module YARP
       assert_comment source, :embdoc, 0..24
     end
 
+    def test_attaching_comments
+      source = <<~RUBY
+        # Foo class
+        class Foo
+          # bar method
+          def bar
+            # baz invocation
+            baz
+          end # bar end
+        end # Foo end
+      RUBY
+
+      result = YARP.parse(source)
+      result.attach_comments!
+      tree = result.value
+      class_node = tree.statements.body.first
+      method_node = class_node.body.body.first
+      call_node = method_node.body.body.first
+
+      assert_equal("# Foo class\n# Foo end\n", class_node.location.comments.map { |c| c.location.slice }.join)
+      assert_equal("# bar method\n# bar end\n", method_node.location.comments.map { |c| c.location.slice }.join)
+      assert_equal("# baz invocation\n", call_node.location.comments.map { |c| c.location.slice }.join)
+    end
+
     private
 
     def assert_comment(source, type, location)
diff --git a/test/yarp/errors_test.rb b/test/yarp/errors_test.rb
index 61955d61..608af077 100644
--- a/test/yarp/errors_test.rb
+++ b/test/yarp/errors_test.rb
@@ -8,8 +8,8 @@ module YARP
 
     def test_constant_path_with_invalid_token_after
       assert_error_messages "A::$b", [
-        "Expected a constant after the `::` operator",
-        "Expected a newline or semicolon after the statement"
+        "Expected identifier or constant after '::'",
+        "Expected a newline or semicolon after statement."
       ]
     end
 
@@ -26,7 +26,7 @@ module YARP
       )
 
       assert_errors expected, "module Parent module end", [
-        ["Expected a constant name after `module`", 20..20]
+        ["Expected to find a module name after `module`.", 20..20]
       ]
     end
 
@@ -42,7 +42,7 @@ module YARP
       )
 
       assert_errors expected, "for in 1..10\ni\nend", [
-        ["Expected an index after `for`", 0..0]
+        ["Expected index after for.", 0..0]
       ]
     end
 
@@ -58,9 +58,9 @@ module YARP
       )
 
       assert_errors expected, "for end", [
-        ["Expected an index after `for`", 0..0],
-        ["Expected an `in` after the index in a `for` statement", 3..3],
-        ["Expected a collection after the `in` in a `for` statement", 3..3]
+        ["Expected index after for.", 0..0],
+        ["Expected keyword in.", 3..3],
+        ["Expected collection.", 3..3]
       ]
     end
 
@@ -73,7 +73,7 @@ module YARP
       )
 
       assert_errors expected, "BEGIN 1 }", [
-        ["Expected a `{` after `BEGIN`", 5..5]
+        ["Expected '{' after 'BEGIN'.", 5..5]
       ]
     end
 
@@ -98,37 +98,37 @@ module YARP
       )
 
       assert_errors expected, "BEGIN { 1 + }", [
-        ["Expected an expression after the operator", 11..11]
+        ["Expected a value after the operator.", 11..11]
       ]
     end
 
     def test_unterminated_embdoc
       assert_errors expression("1"), "1\n=begin\n", [
-        ["Could not find a terminator for the embedded document", 2..9]
+        ["Unterminated embdoc", 2..9]
       ]
     end
 
     def test_unterminated_i_list
       assert_errors expression("%i["), "%i[", [
-        ["Expected a closing delimiter for the `%i` list", 3..3]
+        ["Expected a closing delimiter for a `%i` list.", 3..3]
       ]
     end
 
     def test_unterminated_w_list
       assert_errors expression("%w["), "%w[", [
-        ["Expected a closing delimiter for the `%w` list", 3..3]
+        ["Expected a closing delimiter for a `%w` list.", 3..3]
       ]
     end
 
     def test_unterminated_W_list
       assert_errors expression("%W["), "%W[", [
-        ["Expected a closing delimiter for the `%W` list", 3..3]
+        ["Expected a closing delimiter for a `%W` list.", 3..3]
       ]
     end
 
     def test_unterminated_regular_expression
       assert_errors expression("/hello"), "/hello", [
-        ["Expected a closing delimiter for the regular expression", 1..1]
+        ["Expected a closing delimiter for a regular expression.", 1..1]
       ]
     end
 
@@ -136,178 +136,178 @@ module YARP
       source = "<<-END + /b\nEND\n"
 
       assert_errors expression(source), source, [
-        ["Expected a closing delimiter for the regular expression", 10..10]
+        ["Expected a closing delimiter for a regular expression.", 10..10]
       ]
     end
 
     def test_unterminated_xstring
       assert_errors expression("`hello"), "`hello", [
-        ["Expected a closing delimiter for the `%x` or backtick string", 1..1]
+        ["Expected a closing delimiter for an xstring.", 1..1]
       ]
     end
 
     def test_unterminated_string
       assert_errors expression('"hello'), '"hello', [
-        ["Expected a closing delimiter for the interpolated string", 1..1]
+        ["Expected a closing delimiter for an interpolated string.", 1..1]
       ]
     end
 
     def test_unterminated_s_symbol
       assert_errors expression("%s[abc"), "%s[abc", [
-        ["Expected a closing delimiter for the dynamic symbol", 3..3]
+        ["Expected a closing delimiter for a dynamic symbol.", 3..3]
       ]
     end
 
     def test_unterminated_parenthesized_expression
       assert_errors expression('(1 + 2'), '(1 + 2', [
-        ["Cannot parse the expression", 6..6],
-        ["Expected a matching `)`", 6..6]
+        ["Expected to be able to parse an expression.", 6..6],
+        ["Expected a closing parenthesis.", 6..6]
       ]
     end
 
     def test_unterminated_argument_expression
       assert_errors expression('a %'), 'a %', [
-        ["Invalid `%` token", 2..3],
-        ["Expected an expression after the operator", 3..3],
+        ["Unexpected end of input", 2..3],
+        ["Expected a value after the operator.", 3..3],
       ]
     end
 
     def test_cr_without_lf_in_percent_expression
       assert_errors expression("%\r"), "%\r", [
-        ["Invalid `%` token", 0..2],
+        ["Invalid %% token", 0..2],
       ]
     end
 
     def test_1_2_3
       assert_errors expression("(1, 2, 3)"), "(1, 2, 3)", [
-        ["Cannot parse the expression", 2..2],
-        ["Expected a matching `)`", 2..2],
-        ["Expected a newline or semicolon after the statement", 2..2],
-        ["Cannot parse the expression", 2..2],
-        ["Expected a newline or semicolon after the statement", 5..5],
-        ["Cannot parse the expression", 5..5],
-        ["Expected a newline or semicolon after the statement", 8..8],
-        ["Cannot parse the expression", 8..8],
+        ["Expected to be able to parse an expression.", 2..2],
+        ["Expected a closing parenthesis.", 2..2],
+        ["Expected a newline or semicolon after statement.", 2..2],
+        ["Expected to be able to parse an expression.", 2..2],
+        ["Expected a newline or semicolon after statement.", 5..5],
+        ["Expected to be able to parse an expression.", 5..5],
+        ["Expected a newline or semicolon after statement.", 8..8],
+        ["Expected to be able to parse an expression.", 8..8],
       ]
     end
 
     def test_return_1_2_3
       assert_error_messages "return(1, 2, 3)", [
-        "Cannot parse the expression",
-        "Expected a matching `)`",
-        "Expected a newline or semicolon after the statement",
-        "Cannot parse the expression",
+        "Expected to be able to parse an expression.",
+        "Expected a closing parenthesis.",
+        "Expected a newline or semicolon after statement.",
+        "Expected to be able to parse an expression."
       ]
     end
 
     def test_return_1
       assert_errors expression("return 1,;"), "return 1,;", [
-        ["Expected an argument", 9..9]
+        ["Expected to be able to parse an argument.", 9..9]
       ]
     end
 
     def test_next_1_2_3
       assert_errors expression("next(1, 2, 3)"), "next(1, 2, 3)", [
-        ["Cannot parse the expression", 6..6],
-        ["Expected a matching `)`", 6..6],
-        ["Expected a newline or semicolon after the statement", 12..12],
-        ["Cannot parse the expression", 12..12],
+        ["Expected to be able to parse an expression.", 6..6],
+        ["Expected a closing parenthesis.", 6..6],
+        ["Expected a newline or semicolon after statement.", 12..12],
+        ["Expected to be able to parse an expression.", 12..12]
       ]
     end
 
     def test_next_1
       assert_errors expression("next 1,;"), "next 1,;", [
-        ["Expected an argument", 7..7]
+        ["Expected to be able to parse an argument.", 7..7]
       ]
     end
 
     def test_break_1_2_3
       assert_errors expression("break(1, 2, 3)"), "break(1, 2, 3)", [
-        ["Cannot parse the expression", 7..7],
-        ["Expected a matching `)`", 7..7],
-        ["Expected a newline or semicolon after the statement", 13..13],
-        ["Cannot parse the expression", 13..13],
+        ["Expected to be able to parse an expression.", 7..7],
+        ["Expected a closing parenthesis.", 7..7],
+        ["Expected a newline or semicolon after statement.", 13..13],
+        ["Expected to be able to parse an expression.", 13..13],
       ]
     end
 
     def test_break_1
       assert_errors expression("break 1,;"), "break 1,;", [
-        ["Expected an argument", 8..8]
+        ["Expected to be able to parse an argument.", 8..8]
       ]
     end
 
     def test_argument_forwarding_when_parent_is_not_forwarding
       assert_errors expression('def a(x, y, z); b(...); end'), 'def a(x, y, z); b(...); end', [
-        ["Unexpected `...` when the parent method is not forwarding", 18..21]
+        ["unexpected ... when parent method is not forwarding.", 18..21]
       ]
     end
 
     def test_argument_forwarding_only_effects_its_own_internals
       assert_errors expression('def a(...); b(...); end; def c(x, y, z); b(...); end'),
         'def a(...); b(...); end; def c(x, y, z); b(...); end', [
-          ["Unexpected `...` when the parent method is not forwarding", 43..46]
+          ["unexpected ... when parent method is not forwarding.", 43..46]
         ]
     end
 
     def test_top_level_constant_with_downcased_identifier
       assert_error_messages "::foo", [
-        "Expected a constant after the `::` operator",
-        "Expected a newline or semicolon after the statement"
+        "Expected a constant after ::.",
+        "Expected a newline or semicolon after statement."
       ]
     end
 
     def test_top_level_constant_starting_with_downcased_identifier
       assert_error_messages "::foo::A", [
-        "Expected a constant after the `::` operator",
-        "Expected a newline or semicolon after the statement"
+        "Expected a constant after ::.",
+        "Expected a newline or semicolon after statement."
       ]
     end
 
     def test_aliasing_global_variable_with_non_global_variable
       assert_errors expression("alias $a b"), "alias $a b", [
-        ["Invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", 9..10]
+        ["Expected a global variable.", 9..10]
       ]
     end
 
     def test_aliasing_non_global_variable_with_global_variable
       assert_errors expression("alias a $b"), "alias a $b", [
-        ["Invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", 8..10]
+        ["Expected a bare word or symbol argument.", 8..10]
       ]
     end
 
     def test_aliasing_global_variable_with_global_number_variable
       assert_errors expression("alias $a $1"), "alias $a $1", [
-        ["Invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", 9..11]
+        ["Can't make alias for number variables.", 9..11]
       ]
     end
 
     def test_def_with_expression_receiver_and_no_identifier
       assert_errors expression("def (a); end"), "def (a); end", [
-        ["Expected a `.` or `::` after the receiver in a method definition", 7..7]
+        ["Expected '.' or '::' after receiver", 7..7]
       ]
     end
 
     def test_def_with_multiple_statements_receiver
       assert_errors expression("def (\na\nb\n).c; end"), "def (\na\nb\n).c; end", [
-        ["Expected a matching `)`", 7..7],
-        ["Expected a `.` or `::` after the receiver in a method definition", 7..7],
-        ["Cannot parse the expression", 10..10],
-        ["Cannot parse the expression", 11..11]
+        ["Expected closing ')' for receiver.", 7..7],
+        ["Expected '.' or '::' after receiver", 7..7],
+        ["Expected to be able to parse an expression.", 10..10],
+        ["Expected to be able to parse an expression.", 11..11]
       ]
     end
 
     def test_def_with_empty_expression_receiver
       assert_errors expression("def ().a; end"), "def ().a; end", [
-        ["Expected a receiver for the method definition", 5..5]
+        ["Expected to be able to parse receiver.", 5..5]
       ]
     end
 
     def test_block_beginning_with_brace_and_ending_with_end
       assert_error_messages "x.each { x end", [
-        "Expected a newline or semicolon after the statement",
-        "Cannot parse the expression",
-        "Cannot parse the expression",
-        "Expected a block beginning with `{` to end with `}`"
+        "Expected a newline or semicolon after statement.",
+        "Expected to be able to parse an expression.",
+        "Expected to be able to parse an expression.",
+        "Expected block beginning with '{' to end with '}'."
       ]
     end
 
@@ -328,7 +328,7 @@ module YARP
       )
 
       assert_errors expected, "a(**kwargs, *args)", [
-        ["Unexpected `*` splat argument after a `**` keyword splat argument", 12..17]
+        ["Unexpected splat argument after double splat.", 12..17]
       ]
     end
 
@@ -349,15 +349,15 @@ module YARP
       )
 
       assert_errors expected, "a(&block, foo)", [
-        ["Unexpected argument after a block argument", 10..13]
+        ["Unexpected argument after block argument.", 10..13]
       ]
     end
 
     def test_arguments_binding_power_for_and
       assert_error_messages "foo(*bar and baz)", [
-        "Expected a `)` to close the arguments",
-        "Expected a newline or semicolon after the statement",
-        "Cannot parse the expression"
+        "Expected a ')' to close the argument list.",
+        "Expected a newline or semicolon after statement.",
+        "Expected to be able to parse an expression."
       ]
     end
 
@@ -384,7 +384,7 @@ module YARP
       )
 
       assert_errors expected, "a(foo: bar, *args)", [
-        ["Unexpected `*` splat argument after a `**` keyword splat argument", 12..17]
+        ["Unexpected splat argument after double splat.", 12..17]
       ]
     end
 
@@ -405,7 +405,7 @@ module YARP
       )
 
       assert_errors expected, "def foo;module A;end;end", [
-        ["Unexpected module definition in a method body", 8..14]
+        ["Module definition in method body", 8..14]
       ]
     end
 
@@ -443,7 +443,7 @@ module YARP
         Location()
       )
 
-      assert_errors expected, <<~RUBY, [["Unexpected module definition in a method body", 21..27]]
+      assert_errors expected, <<~RUBY, [["Module definition in method body", 21..27]]
         def foo
           bar do
             module Foo;end
@@ -480,7 +480,7 @@ module YARP
       )
 
       assert_errors expected, "def foo;class A;end;end", [
-        ["Unexpected class definition in a method body", 8..13]
+        ["Class definition in method body", 8..13]
       ]
     end
 
@@ -506,10 +506,10 @@ module YARP
       )
 
       assert_errors expected, "def foo(A, @a, $A, @@a);end", [
-        ["Invalid formal argument; formal argument cannot be a constant", 8..9],
-        ["Invalid formal argument; formal argument cannot be an instance variable", 11..13],
-        ["Invalid formal argument; formal argument cannot be a global variable", 15..17],
-        ["Invalid formal argument; formal argument cannot be a class variable", 19..22],
+        ["Formal argument cannot be a constant", 8..9],
+        ["Formal argument cannot be an instance variable", 11..13],
+        ["Formal argument cannot be a global variable", 15..17],
+        ["Formal argument cannot be a class variable", 19..22],
       ]
     end
 
@@ -540,15 +540,15 @@ module YARP
       end
       RUBY
       assert_errors expected, source, [
-        ["Token reserved for a numbered parameter", 8..10],
-        ["Token reserved for a numbered parameter", 14..16],
-        ["Token reserved for a numbered parameter", 20..22],
-        ["Token reserved for a numbered parameter", 26..28],
-        ["Token reserved for a numbered parameter", 32..34],
-        ["Token reserved for a numbered parameter", 40..42],
-        ["Token reserved for a numbered parameter", 46..48],
-        ["Token reserved for a numbered parameter", 52..54],
-        ["Token reserved for a numbered parameter", 58..60],
+        ["reserved for numbered parameter", 8..10],
+        ["reserved for numbered parameter", 14..16],
+        ["reserved for numbered parameter", 20..22],
+        ["reserved for numbered parameter", 26..28],
+        ["reserved for numbered parameter", 32..34],
+        ["reserved for numbered parameter", 40..42],
+        ["reserved for numbered parameter", 46..48],
+        ["reserved for numbered parameter", 52..54],
+        ["reserved for numbered parameter", 58..60],
       ]
     end
 
@@ -577,7 +577,7 @@ module YARP
       )
 
       assert_errors expected, "def foo(a,b,c,);end", [
-        ["Unexpected `,` in parameters", 13..14]
+        ["Unexpected ','.", 13..14]
       ]
     end
 
@@ -596,7 +596,7 @@ module YARP
         nil
       )
       assert_errors expected, "-> (a, b, ) {}", [
-        ["Unexpected `,` in parameters", 8..9]
+        ["Unexpected ','.", 8..9]
       ]
     end
 
@@ -604,13 +604,13 @@ module YARP
       expected = StringNode(Location(), Location(), nil, "\u0001\u0002")
 
       assert_errors expected, '?\u{0001 0002}', [
-        ["Invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal", 9..12]
+        ["Multiple codepoints at single character literal", 9..12]
       ]
     end
 
     def test_invalid_hex_escape
       assert_errors expression('"\\xx"'), '"\\xx"', [
-        ["Invalid hexadecimal escape sequence", 1..3],
+        ["Invalid hex escape.", 1..3],
       ]
     end
 
@@ -618,7 +618,7 @@ module YARP
       expected = StringNode(Location(), Location(), Location(), "\u0001")
 
       assert_errors expected, '"\u{0000001}"', [
-        ["Invalid Unicode escape sequence; maximum length is 6 digits", 4..11],
+        ["invalid Unicode escape.", 4..11],
       ]
     end
 
@@ -626,13 +626,13 @@ module YARP
       expected = StringNode(Location(), Location(), Location(), "\u0000z}")
 
       assert_errors expected, '"\u{000z}"', [
-        ["Invalid Unicode escape sequence", 7..7],
+        ["unterminated Unicode escape", 7..7],
       ]
     end
 
     def test_unterminated_unicode_brackets_should_be_a_syntax_error
       assert_errors expression('?\\u{3'), '?\\u{3', [
-        ["Invalid Unicode escape sequence; needs closing `}`", 1..5],
+        ["invalid Unicode escape.", 1..5],
       ]
     end
 
@@ -923,7 +923,7 @@ module YARP
       )
 
       assert_errors expected, "case :a\nelse\nend", [
-        ["Unexpected `else` in `case` statement; a `when` clause must precede `else`", 8..12]
+        ["Unexpected else without no when clauses in case statement.", 8..12]
       ]
     end
 
@@ -944,7 +944,7 @@ module YARP
       )
 
       assert_errors expected, "def a=() = 42", [
-        ["Invalid method name; a setter method cannot be defined in an endless method definition", 4..6]
+        ["Setter method cannot be defined in an endless method definition", 4..6]
       ]
     end
 
@@ -959,7 +959,7 @@ module YARP
       )
 
       assert_errors expected, "->(...) {}", [
-        ["Unexpected `...` when the parent method is not forwarding", 3..6]
+        ["Unexpected ...", 3..6]
       ]
     end
 
@@ -983,7 +983,7 @@ module YARP
       )
 
       assert_errors expected, "a {|...|}", [
-        ["Unexpected `...` when the parent method is not forwarding", 4..7]
+        ["Unexpected ...", 4..7]
       ]
     end
 
@@ -1000,7 +1000,7 @@ module YARP
       )
 
       assert_errors expected, "class A; return; end", [
-        ["Invalid `return` in a class or module body", 15..16]
+        ["Invalid return in class/module body", 15..16]
       ]
     end
 
@@ -1015,7 +1015,7 @@ module YARP
       )
 
       assert_errors expected, "module A; return; end", [
-        ["Invalid `return` in a class or module body", 16..17]
+        ["Invalid return in class/module body", 16..17]
       ]
     end
 
@@ -1033,8 +1033,8 @@ module YARP
       )
 
       assert_errors expected, "begin\n$+ = nil\n$1466 = nil\nend", [
-        ["Immutable variable as a write target", 6..8],
-        ["Immutable variable as a write target", 15..20]
+        ["Can't set variable", 6..8],
+        ["Can't set variable", 15..20]
       ]
     end
 
@@ -1058,7 +1058,7 @@ module YARP
         )
 
         assert_errors expected, "def foo(a,b,a);end", [
-          ["Repeated parameter name", 12..13]
+          ["Duplicated parameter name.", 12..13]
         ]
       end
 
@@ -1078,7 +1078,7 @@ module YARP
       )
 
       assert_errors expected, "def foo(a,b,*a);end", [
-        ["Repeated parameter name", 13..14]
+        ["Duplicated parameter name.", 13..14]
       ]
 
       expected = DefNode(
@@ -1097,7 +1097,7 @@ module YARP
       )
 
       assert_errors expected, "def foo(a,b,**a);end", [
-        ["Repeated parameter name", 14..15]
+        ["Duplicated parameter name.", 14..15]
       ]
 
       expected = DefNode(
@@ -1116,7 +1116,7 @@ module YARP
       )
 
       assert_errors expected, "def foo(a,b,&a);end", [
-        ["Repeated parameter name", 13..14]
+        ["Duplicated parameter name.", 13..14]
       ]
 
       expected = DefNode(
@@ -1134,12 +1134,12 @@ module YARP
         Location()
       )
 
-      assert_errors expected, "def foo(a = 1,b,*c);end", [["Unexpected parameter `*`", 16..17]]
+      assert_errors expected, "def foo(a = 1,b,*c);end", [["Unexpected parameter *", 16..17]]
     end
 
     def test_unterminated_global_variable
       assert_errors expression("$"), "$", [
-        ["Invalid global variable", 0..1]
+        ["Invalid global variable.", 0..1]
       ]
     end