Repository: ruby/prism
Pull request #2560 (2024-03-06 19:52:25)

Expose diagnostic types internally
Fixes https://github.com/ruby/prism/issues/2514
Fixes https://github.com/ruby/prism/issues/2513


Patch:
diff --git a/.gitignore b/.gitignore
index b3486bee..cd7609b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@ out.svg
 /fuzz/output/
 /gemfiles/typecheck/bin/
 /include/prism/ast.h
+/include/prism/diagnostic.h
 /javascript/node_modules/
 /javascript/package-lock.json
 /javascript/src/deserialize.js
@@ -49,6 +50,7 @@ out.svg
 /lib/prism/serialize.rb
 /lib/prism/visitor.rb
 /sorbet/
+/src/diagnostic.c
 /src/node.c
 /src/prettyprint.c
 /src/serialize.c
diff --git a/bin/prism b/bin/prism
index d3bbb089..50d86f42 100755
--- a/bin/prism
+++ b/bin/prism
@@ -256,14 +256,10 @@ module Prism
       buffer.source = source
 
       puts "Parser:"
-      parser_ast, _, parser_tokens = Parser::Ruby33.new.tokenize(buffer)
-      pp parser_ast
-      pp parser_tokens
+      parser_parse(Parser::Ruby33.new, buffer)
 
       puts "Prism:"
-      prism_ast, _, prism_tokens = Prism::Translation::Parser33.new.tokenize(buffer)
-      pp prism_ast
-      pp prism_tokens
+      parser_parse(Prism::Translation::Parser33.new, buffer)
     end
 
     # bin/prism ripper [source]
@@ -302,6 +298,17 @@ module Prism
     # Helpers
     ############################################################################
 
+    # Parse a Parser::Source::Buffer with a given parser.
+    def parser_parse(parser, buffer)
+      diagnostics = []
+      parser.diagnostics.consumer = -> (diagnostic) { diagnostics << diagnostic }
+
+      parser_ast, _, parser_tokens = parser.tokenize(buffer, true)
+      pp diagnostics if diagnostics.any?
+      pp parser_ast
+      pp parser_tokens
+    end
+
     # Generate the list of values that will be used in a lookup table for a
     # given encoding.
     def lookup_table_values(encoding)
diff --git a/config.yml b/config.yml
index 8dc9c331..34086cd8 100644
--- a/config.yml
+++ b/config.yml
@@ -1,3 +1,241 @@
+errors:
+  - CANNOT_PARSE_EXPRESSION
+  - ALIAS_ARGUMENT
+  - AMPAMPEQ_MULTI_ASSIGN
+  - ARGUMENT_AFTER_BLOCK
+  - ARGUMENT_AFTER_FORWARDING_ELLIPSES
+  - ARGUMENT_BARE_HASH
+  - ARGUMENT_BLOCK_FORWARDING
+  - ARGUMENT_BLOCK_MULTI
+  - ARGUMENT_FORMAL_CLASS
+  - ARGUMENT_FORMAL_CONSTANT
+  - ARGUMENT_FORMAL_GLOBAL
+  - ARGUMENT_FORMAL_IVAR
+  - ARGUMENT_FORWARDING_UNBOUND
+  - ARGUMENT_IN
+  - ARGUMENT_NO_FORWARDING_AMP
+  - ARGUMENT_NO_FORWARDING_ELLIPSES
+  - ARGUMENT_NO_FORWARDING_STAR
+  - ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT
+  - ARGUMENT_SPLAT_AFTER_SPLAT
+  - ARGUMENT_TERM_PAREN
+  - ARGUMENT_UNEXPECTED_BLOCK
+  - ARRAY_ELEMENT
+  - ARRAY_EXPRESSION
+  - ARRAY_EXPRESSION_AFTER_STAR
+  - ARRAY_SEPARATOR
+  - ARRAY_TERM
+  - BEGIN_LONELY_ELSE
+  - BEGIN_TERM
+  - BEGIN_UPCASE_BRACE
+  - BEGIN_UPCASE_TERM
+  - BEGIN_UPCASE_TOPLEVEL
+  - BLOCK_PARAM_LOCAL_VARIABLE
+  - BLOCK_PARAM_PIPE_TERM
+  - BLOCK_TERM_BRACE
+  - BLOCK_TERM_END
+  - CANNOT_PARSE_STRING_PART
+  - CASE_EXPRESSION_AFTER_CASE
+  - CASE_EXPRESSION_AFTER_WHEN
+  - CASE_MATCH_MISSING_PREDICATE
+  - CASE_MISSING_CONDITIONS
+  - CASE_TERM
+  - CLASS_IN_METHOD
+  - CLASS_NAME
+  - CLASS_SUPERCLASS
+  - CLASS_TERM
+  - CLASS_UNEXPECTED_END
+  - CONDITIONAL_ELSIF_PREDICATE
+  - CONDITIONAL_IF_PREDICATE
+  - CONDITIONAL_PREDICATE_TERM
+  - CONDITIONAL_TERM
+  - CONDITIONAL_TERM_ELSE
+  - CONDITIONAL_UNLESS_PREDICATE
+  - CONDITIONAL_UNTIL_PREDICATE
+  - CONDITIONAL_WHILE_PREDICATE
+  - CONSTANT_PATH_COLON_COLON_CONSTANT
+  - DEF_ENDLESS
+  - DEF_ENDLESS_SETTER
+  - DEF_NAME
+  - DEF_NAME_AFTER_RECEIVER
+  - DEF_PARAMS_TERM
+  - DEF_PARAMS_TERM_PAREN
+  - DEF_RECEIVER
+  - DEF_RECEIVER_TERM
+  - DEF_TERM
+  - DEFINED_EXPRESSION
+  - EMBDOC_TERM
+  - EMBEXPR_END
+  - EMBVAR_INVALID
+  - END_UPCASE_BRACE
+  - END_UPCASE_TERM
+  - ESCAPE_INVALID_CONTROL
+  - ESCAPE_INVALID_CONTROL_REPEAT
+  - ESCAPE_INVALID_HEXADECIMAL
+  - ESCAPE_INVALID_META
+  - ESCAPE_INVALID_META_REPEAT
+  - ESCAPE_INVALID_UNICODE
+  - ESCAPE_INVALID_UNICODE_CM_FLAGS
+  - ESCAPE_INVALID_UNICODE_LITERAL
+  - ESCAPE_INVALID_UNICODE_LONG
+  - ESCAPE_INVALID_UNICODE_TERM
+  - EXPECT_ARGUMENT
+  - EXPECT_EOL_AFTER_STATEMENT
+  - EXPECT_EXPRESSION_AFTER_AMPAMPEQ
+  - EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ
+  - EXPECT_EXPRESSION_AFTER_COMMA
+  - EXPECT_EXPRESSION_AFTER_EQUAL
+  - EXPECT_EXPRESSION_AFTER_LESS_LESS
+  - EXPECT_EXPRESSION_AFTER_LPAREN
+  - EXPECT_EXPRESSION_AFTER_QUESTION
+  - EXPECT_EXPRESSION_AFTER_OPERATOR
+  - EXPECT_EXPRESSION_AFTER_SPLAT
+  - EXPECT_EXPRESSION_AFTER_SPLAT_HASH
+  - EXPECT_EXPRESSION_AFTER_STAR
+  - EXPECT_IDENT_REQ_PARAMETER
+  - EXPECT_LPAREN_REQ_PARAMETER
+  - EXPECT_RBRACKET
+  - EXPECT_RPAREN
+  - EXPECT_RPAREN_AFTER_MULTI
+  - EXPECT_RPAREN_REQ_PARAMETER
+  - EXPECT_STRING_CONTENT
+  - EXPECT_WHEN_DELIMITER
+  - EXPRESSION_BARE_HASH
+  - FLOAT_PARSE
+  - FOR_COLLECTION
+  - FOR_IN
+  - FOR_INDEX
+  - FOR_TERM
+  - HASH_EXPRESSION_AFTER_LABEL
+  - HASH_KEY
+  - HASH_ROCKET
+  - HASH_TERM
+  - HASH_VALUE
+  - HEREDOC_TERM
+  - INCOMPLETE_QUESTION_MARK
+  - INCOMPLETE_VARIABLE_CLASS_3_3_0
+  - INCOMPLETE_VARIABLE_CLASS
+  - INCOMPLETE_VARIABLE_INSTANCE_3_3_0
+  - INCOMPLETE_VARIABLE_INSTANCE
+  - INVALID_ENCODING_MAGIC_COMMENT
+  - INVALID_FLOAT_EXPONENT
+  - INVALID_NUMBER_BINARY
+  - INVALID_NUMBER_DECIMAL
+  - INVALID_NUMBER_HEXADECIMAL
+  - INVALID_NUMBER_OCTAL
+  - INVALID_NUMBER_UNDERSCORE
+  - INVALID_CHARACTER
+  - INVALID_MULTIBYTE_CHARACTER
+  - INVALID_PRINTABLE_CHARACTER
+  - INVALID_PERCENT
+  - INVALID_VARIABLE_GLOBAL_3_3_0
+  - INVALID_VARIABLE_GLOBAL
+  - IT_NOT_ALLOWED_NUMBERED
+  - IT_NOT_ALLOWED_ORDINARY
+  - LAMBDA_OPEN
+  - LAMBDA_TERM_BRACE
+  - LAMBDA_TERM_END
+  - LIST_I_LOWER_ELEMENT
+  - LIST_I_LOWER_TERM
+  - LIST_I_UPPER_ELEMENT
+  - LIST_I_UPPER_TERM
+  - LIST_W_LOWER_ELEMENT
+  - LIST_W_LOWER_TERM
+  - LIST_W_UPPER_ELEMENT
+  - LIST_W_UPPER_TERM
+  - MALLOC_FAILED
+  - MIXED_ENCODING
+  - MODULE_IN_METHOD
+  - MODULE_NAME
+  - MODULE_TERM
+  - MULTI_ASSIGN_MULTI_SPLATS
+  - MULTI_ASSIGN_UNEXPECTED_REST
+  - NOT_EXPRESSION
+  - NO_LOCAL_VARIABLE
+  - NUMBER_LITERAL_UNDERSCORE
+  - NUMBERED_PARAMETER_IT
+  - NUMBERED_PARAMETER_ORDINARY
+  - NUMBERED_PARAMETER_OUTER_SCOPE
+  - OPERATOR_MULTI_ASSIGN
+  - OPERATOR_WRITE_ARGUMENTS
+  - OPERATOR_WRITE_BLOCK
+  - PARAMETER_ASSOC_SPLAT_MULTI
+  - PARAMETER_BLOCK_MULTI
+  - PARAMETER_CIRCULAR
+  - PARAMETER_METHOD_NAME
+  - PARAMETER_NAME_REPEAT
+  - PARAMETER_NO_DEFAULT
+  - PARAMETER_NO_DEFAULT_KW
+  - PARAMETER_NUMBERED_RESERVED
+  - PARAMETER_ORDER
+  - PARAMETER_SPLAT_MULTI
+  - PARAMETER_STAR
+  - PARAMETER_UNEXPECTED_FWD
+  - PARAMETER_WILD_LOOSE_COMMA
+  - PATTERN_EXPRESSION_AFTER_BRACKET
+  - PATTERN_EXPRESSION_AFTER_HROCKET
+  - PATTERN_EXPRESSION_AFTER_COMMA
+  - PATTERN_EXPRESSION_AFTER_IN
+  - PATTERN_EXPRESSION_AFTER_KEY
+  - PATTERN_EXPRESSION_AFTER_PAREN
+  - PATTERN_EXPRESSION_AFTER_PIN
+  - PATTERN_EXPRESSION_AFTER_PIPE
+  - PATTERN_EXPRESSION_AFTER_RANGE
+  - PATTERN_EXPRESSION_AFTER_REST
+  - PATTERN_HASH_KEY
+  - PATTERN_HASH_KEY_LABEL
+  - PATTERN_IDENT_AFTER_HROCKET
+  - PATTERN_LABEL_AFTER_COMMA
+  - PATTERN_REST
+  - PATTERN_TERM_BRACE
+  - PATTERN_TERM_BRACKET
+  - PATTERN_TERM_PAREN
+  - PIPEPIPEEQ_MULTI_ASSIGN
+  - REGEXP_TERM
+  - RESCUE_EXPRESSION
+  - RESCUE_MODIFIER_VALUE
+  - RESCUE_TERM
+  - RESCUE_VARIABLE
+  - RETURN_INVALID
+  - SINGLETON_FOR_LITERALS
+  - STATEMENT_ALIAS
+  - STATEMENT_POSTEXE_END
+  - STATEMENT_PREEXE_BEGIN
+  - STATEMENT_UNDEF
+  - STRING_CONCATENATION
+  - STRING_INTERPOLATED_TERM
+  - STRING_LITERAL_EOF
+  - STRING_LITERAL_TERM
+  - SYMBOL_INVALID
+  - SYMBOL_TERM_DYNAMIC
+  - SYMBOL_TERM_INTERPOLATED
+  - TERNARY_COLON
+  - TERNARY_EXPRESSION_FALSE
+  - TERNARY_EXPRESSION_TRUE
+  - UNARY_RECEIVER
+  - UNEXPECTED_TOKEN_CLOSE_CONTEXT
+  - UNEXPECTED_TOKEN_IGNORE
+  - UNDEF_ARGUMENT
+  - UNTIL_TERM
+  - VOID_EXPRESSION
+  - WHILE_TERM
+  - WRITE_TARGET_IN_METHOD
+  - WRITE_TARGET_READONLY
+  - WRITE_TARGET_UNEXPECTED
+  - XSTRING_TERM
+warnings:
+  - AMBIGUOUS_FIRST_ARGUMENT_MINUS
+  - AMBIGUOUS_FIRST_ARGUMENT_PLUS
+  - AMBIGUOUS_PREFIX_STAR
+  - AMBIGUOUS_SLASH
+  - DOT_DOT_DOT_EOL
+  - EQUAL_IN_CONDITIONAL
+  - END_IN_METHOD
+  - DUPLICATED_HASH_KEY
+  - DUPLICATED_WHEN_CLAUSE
+  - FLOAT_OUT_OF_RANGE
+  - INTEGER_IN_FLIP_FLOP
+  - KEYWORD_EOL
 tokens:
   - name: EOF
     value: 1
diff --git a/docs/configuration.md b/docs/configuration.md
index ff766548..68c97eda 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -4,6 +4,7 @@ A lot of code in prism's repository is templated from a single configuration fil
 
 * `ext/prism/api_node.c` - for defining how to build Ruby objects for the nodes out of C structs
 * `include/prism/ast.h` - for defining the C structs that represent the nodes
+* `include/prism/diagnostic.h` - for defining the diagnostics
 * `javascript/src/deserialize.js` - for defining how to deserialize the nodes in JavaScript
 * `javascript/src/nodes.js` - for defining the nodes in JavaScript
 * `java/org/prism/AbstractNodeVisitor.java` - for defining the visitor interface for the nodes in Java
@@ -17,6 +18,7 @@ A lot of code in prism's repository is templated from a single configuration fil
 * `lib/prism/node.rb` - for defining the nodes in Ruby
 * `lib/prism/serialize.rb` - for defining how to deserialize the nodes in Ruby
 * `lib/prism/visitor.rb` - for defining the visitor interface for the nodes in Ruby
+* `src/diagnostic.c` - for defining how to build diagnostics
 * `src/node.c` - for defining how to free the nodes in C and calculate the size in memory in C
 * `src/prettyprint.c` - for defining how to prettyprint the nodes in C
 * `src/serialize.c` - for defining how to serialize the nodes in C
diff --git a/docs/serialization.md b/docs/serialization.md
index d48b10fa..af593017 100644
--- a/docs/serialization.md
+++ b/docs/serialization.md
@@ -55,6 +55,7 @@ The comment type is one of:
 
 | # bytes | field |
 | --- | --- |
+| `1` | type |
 | string | error message (ASCII-only characters) |
 | location | the location in the source this error applies to |
 | `1` | the level of the error: `0` for `fatal` |
@@ -63,6 +64,7 @@ The comment type is one of:
 
 | # bytes | field |
 | --- | --- |
+| `1` | type |
 | string | warning message (ASCII-only characters) |
 | location | the location in the source this warning applies to |
 | `1` | the level of the warning: `0` for `default` and `1` for `verbose` |
diff --git a/ext/prism/extension.c b/ext/prism/extension.c
index 0ae4a30b..09ce6a1c 100644
--- a/ext/prism/extension.c
+++ b/ext/prism/extension.c
@@ -452,12 +452,13 @@ parser_errors(pm_parser_t *parser, rb_encoding *encoding, VALUE source) {
         }
 
         VALUE error_argv[] = {
+            ID2SYM(rb_intern(pm_diagnostic_id_human(error->diag_id))),
             rb_enc_str_new_cstr(error->message, encoding),
             rb_class_new_instance(3, location_argv, rb_cPrismLocation),
             level
         };
 
-        rb_ary_push(errors, rb_class_new_instance(3, error_argv, rb_cPrismParseError));
+        rb_ary_push(errors, rb_class_new_instance(4, error_argv, rb_cPrismParseError));
     }
 
     return errors;
@@ -491,12 +492,13 @@ parser_warnings(pm_parser_t *parser, rb_encoding *encoding, VALUE source) {
         }
 
         VALUE warning_argv[] = {
+            ID2SYM(rb_intern(pm_diagnostic_id_human(warning->diag_id))),
             rb_enc_str_new_cstr(warning->message, encoding),
             rb_class_new_instance(3, location_argv, rb_cPrismLocation),
             level
         };
 
-        rb_ary_push(warnings, rb_class_new_instance(3, warning_argv, rb_cPrismParseWarning));
+        rb_ary_push(warnings, rb_class_new_instance(4, warning_argv, rb_cPrismParseWarning));
     }
 
     return warnings;
diff --git a/include/prism/diagnostic.h b/include/prism/diagnostic.h
deleted file mode 100644
index ba78be0f..00000000
--- a/include/prism/diagnostic.h
+++ /dev/null
@@ -1,352 +0,0 @@
-/**
- * @file diagnostic.h
- *
- * A list of diagnostics generated during parsing.
- */
-#ifndef PRISM_DIAGNOSTIC_H
-#define PRISM_DIAGNOSTIC_H
-
-#include "prism/ast.h"
-#include "prism/defines.h"
-#include "prism/util/pm_list.h"
-
-#include <stdbool.h>
-#include <stdlib.h>
-#include <assert.h>
-
-/**
- * The levels of errors generated during parsing.
- */
-typedef enum {
-    /** For errors that cannot be recovered from. */
-    PM_ERROR_LEVEL_FATAL = 0,
-
-    /** For errors that should raise an argument error. */
-    PM_ERROR_LEVEL_ARGUMENT = 1
-} pm_error_level_t;
-
-/**
- * The levels of warnings generated during parsing.
- */
-typedef enum {
-    /** For warnings which should be emitted if $VERBOSE != nil. */
-    PM_WARNING_LEVEL_DEFAULT = 0,
-
-    /** For warnings which should be emitted if $VERBOSE == true. */
-    PM_WARNING_LEVEL_VERBOSE = 1
-} pm_warning_level_t;
-
-/**
- * This struct represents a diagnostic generated during parsing.
- *
- * @extends pm_list_node_t
- */
-typedef struct {
-    /** The embedded base node. */
-    pm_list_node_t node;
-
-    /** The location of the diagnostic in the source. */
-    pm_location_t location;
-
-    /** The message associated with the diagnostic. */
-    const char *message;
-
-    /**
-     * Whether or not the memory related to the message of this diagnostic is
-     * owned by this diagnostic. If it is, it needs to be freed when the
-     * diagnostic is freed.
-     */
-    bool owned;
-
-    /**
-     * The level of the diagnostic, see `pm_error_level_t` and
-     * `pm_warning_level_t` for possible values.
-     */
-    uint8_t level;
-} pm_diagnostic_t;
-
-/**
- * The diagnostic IDs of all of the diagnostics, used to communicate the types
- * of errors between the parser and the user.
- */
-typedef enum {
-    // This is a special error that we can potentially replace by others. For
-    // an example of how this is used, see parse_expression_prefix.
-    PM_ERR_CANNOT_PARSE_EXPRESSION,
-
-    // These are the error codes.
-    PM_ERR_ALIAS_ARGUMENT,
-    PM_ERR_AMPAMPEQ_MULTI_ASSIGN,
-    PM_ERR_ARGUMENT_AFTER_BLOCK,
-    PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES,
-    PM_ERR_ARGUMENT_BARE_HASH,
-    PM_ERR_ARGUMENT_BLOCK_FORWARDING,
-    PM_ERR_ARGUMENT_BLOCK_MULTI,
-    PM_ERR_ARGUMENT_FORMAL_CLASS,
-    PM_ERR_ARGUMENT_FORMAL_CONSTANT,
-    PM_ERR_ARGUMENT_FORMAL_GLOBAL,
-    PM_ERR_ARGUMENT_FORMAL_IVAR,
-    PM_ERR_ARGUMENT_FORWARDING_UNBOUND,
-    PM_ERR_ARGUMENT_IN,
-    PM_ERR_ARGUMENT_NO_FORWARDING_AMP,
-    PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES,
-    PM_ERR_ARGUMENT_NO_FORWARDING_STAR,
-    PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT,
-    PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT,
-    PM_ERR_ARGUMENT_TERM_PAREN,
-    PM_ERR_ARGUMENT_UNEXPECTED_BLOCK,
-    PM_ERR_ARRAY_ELEMENT,
-    PM_ERR_ARRAY_EXPRESSION,
-    PM_ERR_ARRAY_EXPRESSION_AFTER_STAR,
-    PM_ERR_ARRAY_SEPARATOR,
-    PM_ERR_ARRAY_TERM,
-    PM_ERR_BEGIN_LONELY_ELSE,
-    PM_ERR_BEGIN_TERM,
-    PM_ERR_BEGIN_UPCASE_BRACE,
-    PM_ERR_BEGIN_UPCASE_TERM,
-    PM_ERR_BEGIN_UPCASE_TOPLEVEL,
-    PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE,
-    PM_ERR_BLOCK_PARAM_PIPE_TERM,
-    PM_ERR_BLOCK_TERM_BRACE,
-    PM_ERR_BLOCK_TERM_END,
-    PM_ERR_CANNOT_PARSE_STRING_PART,
-    PM_ERR_CASE_EXPRESSION_AFTER_CASE,
-    PM_ERR_CASE_EXPRESSION_AFTER_WHEN,
-    PM_ERR_CASE_MATCH_MISSING_PREDICATE,
-    PM_ERR_CASE_MISSING_CONDITIONS,
-    PM_ERR_CASE_TERM,
-    PM_ERR_CLASS_IN_METHOD,
-    PM_ERR_CLASS_NAME,
-    PM_ERR_CLASS_SUPERCLASS,
-    PM_ERR_CLASS_TERM,
-    PM_ERR_CLASS_UNEXPECTED_END,
-    PM_ERR_CONDITIONAL_ELSIF_PREDICATE,
-    PM_ERR_CONDITIONAL_IF_PREDICATE,
-    PM_ERR_CONDITIONAL_PREDICATE_TERM,
-    PM_ERR_CONDITIONAL_TERM,
-    PM_ERR_CONDITIONAL_TERM_ELSE,
-    PM_ERR_CONDITIONAL_UNLESS_PREDICATE,
-    PM_ERR_CONDITIONAL_UNTIL_PREDICATE,
-    PM_ERR_CONDITIONAL_WHILE_PREDICATE,
-    PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT,
-    PM_ERR_DEF_ENDLESS,
-    PM_ERR_DEF_ENDLESS_SETTER,
-    PM_ERR_DEF_NAME,
-    PM_ERR_DEF_NAME_AFTER_RECEIVER,
-    PM_ERR_DEF_PARAMS_TERM,
-    PM_ERR_DEF_PARAMS_TERM_PAREN,
-    PM_ERR_DEF_RECEIVER,
-    PM_ERR_DEF_RECEIVER_TERM,
-    PM_ERR_DEF_TERM,
-    PM_ERR_DEFINED_EXPRESSION,
-    PM_ERR_EMBDOC_TERM,
-    PM_ERR_EMBEXPR_END,
-    PM_ERR_EMBVAR_INVALID,
-    PM_ERR_END_UPCASE_BRACE,
-    PM_ERR_END_UPCASE_TERM,
-    PM_ERR_ESCAPE_INVALID_CONTROL,
-    PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT,
-    PM_ERR_ESCAPE_INVALID_HEXADECIMAL,
-    PM_ERR_ESCAPE_INVALID_META,
-    PM_ERR_ESCAPE_INVALID_META_REPEAT,
-    PM_ERR_ESCAPE_INVALID_UNICODE,
-    PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS,
-    PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL,
-    PM_ERR_ESCAPE_INVALID_UNICODE_LONG,
-    PM_ERR_ESCAPE_INVALID_UNICODE_TERM,
-    PM_ERR_EXPECT_ARGUMENT,
-    PM_ERR_EXPECT_EOL_AFTER_STATEMENT,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_QUESTION,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH,
-    PM_ERR_EXPECT_EXPRESSION_AFTER_STAR,
-    PM_ERR_EXPECT_IDENT_REQ_PARAMETER,
-    PM_ERR_EXPECT_LPAREN_REQ_PARAMETER,
-    PM_ERR_EXPECT_RBRACKET,
-    PM_ERR_EXPECT_RPAREN,
-    PM_ERR_EXPECT_RPAREN_AFTER_MULTI,
-    PM_ERR_EXPECT_RPAREN_REQ_PARAMETER,
-    PM_ERR_EXPECT_STRING_CONTENT,
-    PM_ERR_EXPECT_WHEN_DELIMITER,
-    PM_ERR_EXPRESSION_BARE_HASH,
-    PM_ERR_FLOAT_PARSE,
-    PM_ERR_FOR_COLLECTION,
-    PM_ERR_FOR_IN,
-    PM_ERR_FOR_INDEX,
-    PM_ERR_FOR_TERM,
-    PM_ERR_HASH_EXPRESSION_AFTER_LABEL,
-    PM_ERR_HASH_KEY,
-    PM_ERR_HASH_ROCKET,
-    PM_ERR_HASH_TERM,
-    PM_ERR_HASH_VALUE,
-    PM_ERR_HEREDOC_TERM,
-    PM_ERR_INCOMPLETE_QUESTION_MARK,
-    PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3_0,
-    PM_ERR_INCOMPLETE_VARIABLE_CLASS,
-    PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3_0,
-    PM_ERR_INCOMPLETE_VARIABLE_INSTANCE,
-    PM_ERR_INVALID_ENCODING_MAGIC_COMMENT,
-    PM_ERR_INVALID_FLOAT_EXPONENT,
-    PM_ERR_INVALID_NUMBER_BINARY,
-    PM_ERR_INVALID_NUMBER_DECIMAL,
-    PM_ERR_INVALID_NUMBER_HEXADECIMAL,
-    PM_ERR_INVALID_NUMBER_OCTAL,
-    PM_ERR_INVALID_NUMBER_UNDERSCORE,
-    PM_ERR_INVALID_CHARACTER,
-    PM_ERR_INVALID_MULTIBYTE_CHARACTER,
-    PM_ERR_INVALID_PRINTABLE_CHARACTER,
-    PM_ERR_INVALID_PERCENT,
-    PM_ERR_INVALID_VARIABLE_GLOBAL_3_3_0,
-    PM_ERR_INVALID_VARIABLE_GLOBAL,
-    PM_ERR_IT_NOT_ALLOWED_NUMBERED,
-    PM_ERR_IT_NOT_ALLOWED_ORDINARY,
-    PM_ERR_LAMBDA_OPEN,
-    PM_ERR_LAMBDA_TERM_BRACE,
-    PM_ERR_LAMBDA_TERM_END,
-    PM_ERR_LIST_I_LOWER_ELEMENT,
-    PM_ERR_LIST_I_LOWER_TERM,
-    PM_ERR_LIST_I_UPPER_ELEMENT,
-    PM_ERR_LIST_I_UPPER_TERM,
-    PM_ERR_LIST_W_LOWER_ELEMENT,
-    PM_ERR_LIST_W_LOWER_TERM,
-    PM_ERR_LIST_W_UPPER_ELEMENT,
-    PM_ERR_LIST_W_UPPER_TERM,
-    PM_ERR_MALLOC_FAILED,
-    PM_ERR_MIXED_ENCODING,
-    PM_ERR_MODULE_IN_METHOD,
-    PM_ERR_MODULE_NAME,
-    PM_ERR_MODULE_TERM,
-    PM_ERR_MULTI_ASSIGN_MULTI_SPLATS,
-    PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST,
-    PM_ERR_NOT_EXPRESSION,
-    PM_ERR_NO_LOCAL_VARIABLE,
-    PM_ERR_NUMBER_LITERAL_UNDERSCORE,
-    PM_ERR_NUMBERED_PARAMETER_IT,
-    PM_ERR_NUMBERED_PARAMETER_ORDINARY,
-    PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE,
-    PM_ERR_OPERATOR_MULTI_ASSIGN,
-    PM_ERR_OPERATOR_WRITE_ARGUMENTS,
-    PM_ERR_OPERATOR_WRITE_BLOCK,
-    PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI,
-    PM_ERR_PARAMETER_BLOCK_MULTI,
-    PM_ERR_PARAMETER_CIRCULAR,
-    PM_ERR_PARAMETER_METHOD_NAME,
-    PM_ERR_PARAMETER_NAME_REPEAT,
-    PM_ERR_PARAMETER_NO_DEFAULT,
-    PM_ERR_PARAMETER_NO_DEFAULT_KW,
-    PM_ERR_PARAMETER_NUMBERED_RESERVED,
-    PM_ERR_PARAMETER_ORDER,
-    PM_ERR_PARAMETER_SPLAT_MULTI,
-    PM_ERR_PARAMETER_STAR,
-    PM_ERR_PARAMETER_UNEXPECTED_FWD,
-    PM_ERR_PARAMETER_WILD_LOOSE_COMMA,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_IN,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_KEY,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_PIN,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE,
-    PM_ERR_PATTERN_EXPRESSION_AFTER_REST,
-    PM_ERR_PATTERN_HASH_KEY,
-    PM_ERR_PATTERN_HASH_KEY_LABEL,
-    PM_ERR_PATTERN_IDENT_AFTER_HROCKET,
-    PM_ERR_PATTERN_LABEL_AFTER_COMMA,
-    PM_ERR_PATTERN_REST,
-    PM_ERR_PATTERN_TERM_BRACE,
-    PM_ERR_PATTERN_TERM_BRACKET,
-    PM_ERR_PATTERN_TERM_PAREN,
-    PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN,
-    PM_ERR_REGEXP_TERM,
-    PM_ERR_RESCUE_EXPRESSION,
-    PM_ERR_RESCUE_MODIFIER_VALUE,
-    PM_ERR_RESCUE_TERM,
-    PM_ERR_RESCUE_VARIABLE,
-    PM_ERR_RETURN_INVALID,
-    PM_ERR_SINGLETON_FOR_LITERALS,
-    PM_ERR_STATEMENT_ALIAS,
-    PM_ERR_STATEMENT_POSTEXE_END,
-    PM_ERR_STATEMENT_PREEXE_BEGIN,
-    PM_ERR_STATEMENT_UNDEF,
-    PM_ERR_STRING_CONCATENATION,
-    PM_ERR_STRING_INTERPOLATED_TERM,
-    PM_ERR_STRING_LITERAL_EOF,
-    PM_ERR_STRING_LITERAL_TERM,
-    PM_ERR_SYMBOL_INVALID,
-    PM_ERR_SYMBOL_TERM_DYNAMIC,
-    PM_ERR_SYMBOL_TERM_INTERPOLATED,
-    PM_ERR_TERNARY_COLON,
-    PM_ERR_TERNARY_EXPRESSION_FALSE,
-    PM_ERR_TERNARY_EXPRESSION_TRUE,
-    PM_ERR_UNARY_RECEIVER,
-    PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT,
-    PM_ERR_UNEXPECTED_TOKEN_IGNORE,
-    PM_ERR_UNDEF_ARGUMENT,
-    PM_ERR_UNTIL_TERM,
-    PM_ERR_VOID_EXPRESSION,
-    PM_ERR_WHILE_TERM,
-    PM_ERR_WRITE_TARGET_IN_METHOD,
-    PM_ERR_WRITE_TARGET_READONLY,
-    PM_ERR_WRITE_TARGET_UNEXPECTED,
-    PM_ERR_XSTRING_TERM,
-
-    // These are the warning codes.
-    PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS,
-    PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS,
-    PM_WARN_AMBIGUOUS_PREFIX_STAR,
-    PM_WARN_AMBIGUOUS_SLASH,
-    PM_WARN_DOT_DOT_DOT_EOL,
-    PM_WARN_EQUAL_IN_CONDITIONAL,
-    PM_WARN_END_IN_METHOD,
-    PM_WARN_DUPLICATED_HASH_KEY,
-    PM_WARN_DUPLICATED_WHEN_CLAUSE,
-    PM_WARN_FLOAT_OUT_OF_RANGE,
-    PM_WARN_INTEGER_IN_FLIP_FLOP,
-    PM_WARN_KEYWORD_EOL,
-
-    // This is the number of diagnostic codes.
-    PM_DIAGNOSTIC_ID_LEN,
-} pm_diagnostic_id_t;
-
-/**
- * Append a diagnostic to the given list of diagnostics that is using shared
- * memory for its message.
- *
- * @param list The list to append to.
- * @param start The start of the diagnostic.
- * @param end The end of the diagnostic.
- * @param diag_id The diagnostic ID.
- * @return Whether the diagnostic was successfully appended.
- */
-bool pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id);
-
-/**
- * Append a diagnostic to the given list of diagnostics that is using a format
- * string for its message.
- *
- * @param list The list to append to.
- * @param start The start of the diagnostic.
- * @param end The end of the diagnostic.
- * @param diag_id The diagnostic ID.
- * @param ... The arguments to the format string for the message.
- * @return Whether the diagnostic was successfully appended.
- */
-bool pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id, ...);
-
-/**
- * Deallocate the internal state of the given diagnostic list.
- *
- * @param list The list to deallocate.
- */
-void pm_diagnostic_list_free(pm_list_t *list);
-
-#endif
diff --git a/java/org/prism/ParseResult.java b/java/org/prism/ParseResult.java
index d2a6f2ef..40f8b04e 100644
--- a/java/org/prism/ParseResult.java
+++ b/java/org/prism/ParseResult.java
@@ -24,11 +24,13 @@ public final class ParseResult {
     public static ErrorLevel[] ERROR_LEVELS = ErrorLevel.values();
 
     public static final class Error {
+        public final Nodes.ErrorType type;
         public final String message;
         public final Nodes.Location location;
         public final ErrorLevel level;
 
-        public Error(String message, Nodes.Location location, ErrorLevel level) {
+        public Error(Nodes.ErrorType type, String message, Nodes.Location location, ErrorLevel level) {
+            this.type = type;
             this.message = message;
             this.location = location;
             this.level = level;
@@ -46,11 +48,13 @@ public final class ParseResult {
     public static WarningLevel[] WARNING_LEVELS = WarningLevel.values();
 
     public static final class Warning {
+        public final Nodes.WarningType type;
         public final String message;
         public final Nodes.Location location;
         public final WarningLevel level;
 
-        public Warning(String message, Nodes.Location location, WarningLevel level) {
+        public Warning(Nodes.WarningType type, String message, Nodes.Location location, WarningLevel level) {
+            this.type = type;
             this.message = message;
             this.location = location;
             this.level = level;
diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb
index c54c883e..b6109b09 100644
--- a/lib/prism/parse_result.rb
+++ b/lib/prism/parse_result.rb
@@ -366,6 +366,10 @@ module Prism
 
   # This represents an error that was encountered during parsing.
   class ParseError
+    # The type of error. This is an _internal_ symbol that is used for
+    # communicating with translation layers. It is not meant to be public API.
+    attr_reader :type
+
     # The message associated with this error.
     attr_reader :message
 
@@ -376,7 +380,8 @@ module Prism
     attr_reader :level
 
     # Create a new error object with the given message and location.
-    def initialize(message, location, level)
+    def initialize(type, message, location, level)
+      @type = type
       @message = message
       @location = location
       @level = level
@@ -384,17 +389,21 @@ module Prism
 
     # Implement the hash pattern matching interface for ParseError.
     def deconstruct_keys(keys)
-      { message: message, location: location, level: level }
+      { type: type, message: message, location: location, level: level }
     end
 
     # Returns a string representation of this error.
     def inspect
-      "#<Prism::ParseError @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
+      "#<Prism::ParseError @type=#{@type.inspect} @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
     end
   end
 
   # This represents a warning that was encountered during parsing.
   class ParseWarning
+    # The type of warning. This is an _internal_ symbol that is used for
+    # communicating with translation layers. It is not meant to be public API.
+    attr_reader :type
+
     # The message associated with this warning.
     attr_reader :message
 
@@ -405,7 +414,8 @@ module Prism
     attr_reader :level
 
     # Create a new warning object with the given message and location.
-    def initialize(message, location, level)
+    def initialize(type, message, location, level)
+      @type = type
       @message = message
       @location = location
       @level = level
@@ -413,12 +423,12 @@ module Prism
 
     # Implement the hash pattern matching interface for ParseWarning.
     def deconstruct_keys(keys)
-      { message: message, location: location, level: level }
+      { type: type, message: message, location: location, level: level }
     end
 
     # Returns a string representation of this warning.
     def inspect
-      "#<Prism::ParseWarning @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
+      "#<Prism::ParseWarning @type=#{@type.inspect} @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level.inspect}>"
     end
   end
 
diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb
index fd130282..7a2d8e54 100644
--- a/lib/prism/translation/parser.rb
+++ b/lib/prism/translation/parser.rb
@@ -9,11 +9,14 @@ module Prism
     # the parser gem, and overrides the parse* methods to parse with prism and
     # then translate.
     class Parser < ::Parser::Base
+      Diagnostic = ::Parser::Diagnostic # :nodoc:
+      private_constant :Diagnostic
+
       # The parser gem has a list of diagnostics with a hard-coded set of error
       # messages. We create our own diagnostic class in order to set our own
       # error messages.
-      class Diagnostic < ::Parser::Diagnostic
-        # The message generated by prism.
+      class PrismDiagnostic < Diagnostic
+        # This is the cached message coming from prism.
         attr_reader :message
 
         # Initialize a new diagnostic with the given message and location.
@@ -112,20 +115,109 @@ module Prism
         true
       end
 
+      # Build a diagnostic from the given prism parse error.
+      def error_diagnostic(error, offset_cache)
+        location = error.location
+        diagnostic_location = build_range(location, offset_cache)
+
+        case error.type
+        when :argument_block_multi
+          Diagnostic.new(:error, :block_and_blockarg, {}, diagnostic_location, [])
+        when :argument_formal_constant
+          Diagnostic.new(:error, :formal_argument, {}, diagnostic_location, [])
+        when :argument_formal_class
+          Diagnostic.new(:error, :argument_cvar, {}, diagnostic_location, [])
+        when :argument_formal_global
+          Diagnostic.new(:error, :argument_gvar, {}, diagnostic_location, [])
+        when :argument_formal_ivar
+          Diagnostic.new(:error, :argument_ivar, {}, diagnostic_location, [])
+        when :argument_no_forwarding_amp
+          Diagnostic.new(:error, :no_anonymous_blockarg, {}, diagnostic_location, [])
+        when :argument_no_forwarding_star
+          Diagnostic.new(:error, :no_anonymous_restarg, {}, diagnostic_location, [])
+        when :begin_lonely_else
+          location = location.copy(length: 4)
+          diagnostic_location = build_range(location, offset_cache)
+          Diagnostic.new(:error, :useless_else, {}, diagnostic_location, [])
+        when :class_name, :module_name
+          Diagnostic.new(:error, :module_name_const, {}, diagnostic_location, [])
+        when :class_in_method
+          Diagnostic.new(:error, :class_in_def, {}, diagnostic_location, [])
+        when :def_endless_setter
+          Diagnostic.new(:error, :endless_setter, {}, diagnostic_location, [])
+        when :embdoc_term
+          Diagnostic.new(:error, :embedded_document, {}, diagnostic_location, [])
+        when :incomplete_variable_class, :incomplete_variable_class_3_3_0
+          location = location.copy(length: location.length + 1)
+          diagnostic_location = build_range(location, offset_cache)
+
+          Diagnostic.new(:error, :cvar_name, { name: location.slice }, diagnostic_location, [])
+        when :incomplete_variable_instance, :incomplete_variable_instance_3_3_0
+          location = location.copy(length: location.length + 1)
+          diagnostic_location = build_range(location, offset_cache)
+
+          Diagnostic.new(:error, :ivar_name, { name: location.slice }, diagnostic_location, [])
+        when :invalid_variable_global, :invalid_variable_global_3_3_0
+          Diagnostic.new(:error, :gvar_name, { name: location.slice }, diagnostic_location, [])
+        when :module_in_method
+          Diagnostic.new(:error, :module_in_def, {}, diagnostic_location, [])
+        when :numbered_parameter_ordinary
+          Diagnostic.new(:error, :ordinary_param_defined, {}, diagnostic_location, [])
+        when :numbered_parameter_outer_scope
+          Diagnostic.new(:error, :numparam_used_in_outer_scope, {}, diagnostic_location, [])
+        when :parameter_circular
+          Diagnostic.new(:error, :circular_argument_reference, { var_name: location.slice }, diagnostic_location, [])
+        when :parameter_name_repeat
+          Diagnostic.new(:error, :duplicate_argument, {}, diagnostic_location, [])
+        when :parameter_numbered_reserved
+          Diagnostic.new(:error, :reserved_for_numparam, { name: location.slice }, diagnostic_location, [])
+        when :singleton_for_literals
+          Diagnostic.new(:error, :singleton_literal, {}, diagnostic_location, [])
+        when :string_literal_eof
+          Diagnostic.new(:error, :string_eof, {}, diagnostic_location, [])
+        when :unexpected_token_ignore
+          Diagnostic.new(:error, :unexpected_token, { token: location.slice }, diagnostic_location, [])
+        when :write_target_in_method
+          Diagnostic.new(:error, :dynamic_const, {}, diagnostic_location, [])
+        else
+          PrismDiagnostic.new(error.message, :error, error.type, diagnostic_location)
+        end
+      end
+
+      # Build a diagnostic from the given prism parse warning.
+      def warning_diagnostic(warning, offset_cache)
+        diagnostic_location = build_range(warning.location, offset_cache)
+
+        case warning.type
+        when :ambiguous_first_argument_plus
+          Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "+" }, diagnostic_location, [])
+        when :ambiguous_first_argument_minus
+          Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "-" }, diagnostic_location, [])
+        when :ambiguous_prefix_star
+          Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "*" }, diagnostic_location, [])
+        when :ambiguous_slash
+          Diagnostic.new(:warning, :ambiguous_regexp, {}, diagnostic_location, [])
+        when :dot_dot_dot_eol
+          Diagnostic.new(:warning, :triple_dot_at_eol, {}, diagnostic_location, [])
+        when :duplicated_hash_key
+          # skip, parser does this on its own
+        else
+          PrismDiagnostic.new(warning.message, :warning, warning.type, diagnostic_location)
+        end
+      end
+
       # If there was a error generated during the parse, then raise an
       # appropriate syntax error. Otherwise return the result.
       def unwrap(result, offset_cache)
         result.errors.each do |error|
           next unless valid_error?(error)
-
-          location = build_range(error.location, offset_cache)
-          diagnostics.process(Diagnostic.new(error.message, :error, :prism_error, location))
+          diagnostics.process(error_diagnostic(error, offset_cache))
         end
+
         result.warnings.each do |warning|
           next unless valid_warning?(warning)
-
-          location = build_range(warning.location, offset_cache)
-          diagnostics.process(Diagnostic.new(warning.message, :warning, :prism_warning, location))
+          diagnostic = warning_diagnostic(warning, offset_cache)
+          diagnostics.process(diagnostic) if diagnostic
         end
 
         result
diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb
index bc7e77f2..9a0a48a0 100644
--- a/lib/prism/translation/parser/lexer.rb
+++ b/lib/prism/translation/parser/lexer.rb
@@ -213,9 +213,11 @@ module Prism
         # Convert the prism tokens into the expected format for the parser gem.
         def to_a
           tokens = []
+
           index = 0
+          length = lexed.length
 
-          while index < lexed.length
+          while index < length
             token, state = lexed[index]
             index += 1
             next if %i[IGNORED_NEWLINE __END__ EOF].include?(token.type)
@@ -229,14 +231,18 @@ module Prism
               value.delete_prefix!("?")
             when :tCOMMENT
               if token.type == :EMBDOC_BEGIN
-                until (next_token = lexed[index][0]) && next_token.type == :EMBDOC_END
+                start_index = index
+
+                while !((next_token = lexed[index][0]) && next_token.type == :EMBDOC_END) && (index < length - 1)
                   value += next_token.value
                   index += 1
                 end
 
-                value += next_token.value
-                location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index][0].location.end_offset])
-                index += 1
+                if start_index != index
+                  value += next_token.value
+                  location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index][0].location.end_offset])
+                  index += 1
+                end
               else
                 value.chomp!
                 location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.end_offset - 1])
diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb
index b56ca40e..94156d49 100644
--- a/lib/prism/translation/ripper.rb
+++ b/lib/prism/translation/ripper.rb
@@ -19,15 +19,10 @@ module Prism
     # The main known difference is that we may omit dispatching some events in
     # some cases. This impacts the following events:
     #
-    # * on_alias_error
-    # * on_arg_ambiguous
     # * on_assign_error
-    # * on_class_name_error
-    # * on_operator_ambiguous
-    # * on_param_error
-    #
     # * on_comma
     # * on_ignored_nl
+    # * on_ignored_sp
     # * on_kw
     # * on_label_end
     # * on_lbrace
@@ -35,17 +30,15 @@ module Prism
     # * on_lparen
     # * on_nl
     # * on_op
+    # * on_operator_ambiguous
     # * on_rbrace
     # * on_rbracket
     # * on_rparen
     # * on_semicolon
     # * on_sp
     # * on_symbeg
-    # * on_tlambda
-    # * on_tlambeg
     # * on_tstring_beg
     # * on_tstring_end
-    # * on_ignored_sp
     #
     class Ripper < Compiler
       # Parses the given Ruby program read from +src+.
@@ -502,16 +495,45 @@ module Prism
         end
 
         result.warnings.each do |warning|
+          bounds(warning.location)
+
           if warning.level == :default
             warning(warning.message)
           else
-            warn(warning.message)
+            case warning.type
+            when :ambiguous_first_argument_plus
+              on_arg_ambiguous("+")
+            when :ambiguous_first_argument_minus
+              on_arg_ambiguous("-")
+            when :ambiguous_slash
+              on_arg_ambiguous("/")
+            else
+              warn(warning.message)
+            end
           end
         end
 
         if error?
           result.errors.each do |error|
-            on_parse_error(error.message)
+            location = error.location
+            bounds(location)
+
+            case error.type
+            when :alias_argument
+              on_alias_error("can't make alias for the number variables", location.slice)
+            when :argument_formal_class
+              on_param_error("formal argument cannot be a class variable", location.slice)
+            when :argument_format_constant
+              on_param_error("formal argument cannot be a constant", location.slice)
+            when :argument_formal_global
+              on_param_error("formal argument cannot be a global variable", location.slice)
+            when :argument_formal_ivar
+              on_param_error("formal argument cannot be an instance variable", location.slice)
+            when :class_name, :module_name
+              on_class_name_error("class/module name must be CONSTANT", location.slice)
+            else
+              on_parse_error(error.message)
+            end
           end
 
           nil
@@ -2219,6 +2241,9 @@ module Prism
 
       # -> {}
       def visit_lambda_node(node)
+        bounds(node.operator_loc)
+        on_tlambda(node.operator)
+
         parameters =
           if node.parameters.is_a?(BlockParametersNode)
             # Ripper does not track block-locals within lambdas, so we skip
@@ -2243,6 +2268,11 @@ module Prism
           end
 
         braces = node.opening == "{"
+        if braces
+          bounds(node.opening_loc)
+          on_tlambeg(node.opening)
+        end
+
         body =
           case node.body
           when nil
diff --git a/rbi/prism/parse_result.rbi b/rbi/prism/parse_result.rbi
index 4068d82b..00bf3514 100644
--- a/rbi/prism/parse_result.rbi
+++ b/rbi/prism/parse_result.rbi
@@ -192,6 +192,9 @@ class Prism::MagicComment
 end
 
 class Prism::ParseError
+  sig { returns(Symbol) }
+  attr_reader :type
+
   sig { returns(String) }
   attr_reader :message
 
@@ -201,8 +204,8 @@ class Prism::ParseError
   sig { returns(Symbol) }
   attr_reader :level
 
-  sig { params(message: String, location: Prism::Location, level: Symbol).void }
-  def initialize(message, location, level); end
+  sig { params(type: Symbol, message: String, location: Prism::Location, level: Symbol).void }
+  def initialize(type, message, location, level); end
 
   sig { params(keys: T.nilable(T::Array[Symbol])).returns(T::Hash[Symbol, T.untyped]) }
   def deconstruct_keys(keys); end
@@ -212,6 +215,9 @@ class Prism::ParseError
 end
 
 class Prism::ParseWarning
+  sig { returns(Symbol) }
+  attr_reader :type
+
   sig { returns(String) }
   attr_reader :message
 
@@ -221,8 +227,8 @@ class Prism::ParseWarning
   sig { returns(Symbol) }
   attr_reader :level
 
-  sig { params(message: String, location: Prism::Location, level: Symbol).void }
-  def initialize(message, location, level); end
+  sig { params(type: Symbol, message: String, location: Prism::Location, level: Symbol).void }
+  def initialize(type, message, location, level); end
 
   sig { params(keys: T.nilable(T::Array[Symbol])).returns(T::Hash[Symbol, T.untyped]) }
   def deconstruct_keys(keys); end
diff --git a/sig/prism/parse_result.rbs b/sig/prism/parse_result.rbs
index 343dbbb2..1faf6ac1 100644
--- a/sig/prism/parse_result.rbs
+++ b/sig/prism/parse_result.rbs
@@ -79,20 +79,22 @@ module Prism
   end
 
   class ParseError
+    attr_reader type: Symbol
     attr_reader message: String
     attr_reader location: Location
     attr_reader level: Symbol
 
-    def initialize: (String message, Location location, Symbol level) -> void
+    def initialize: (Symbol type, String message, Location location, Symbol level) -> void
     def deconstruct_keys: (Array[Symbol] keys) -> { message: String, location: Location, level: Symbol }
   end
 
   class ParseWarning
+    attr_reader type: Symbol
     attr_reader message: String
     attr_reader location: Location
     attr_reader level: Symbol
 
-    def initialize: (String message, Location location, Symbol level) -> void
+    def initialize: (Symbol type, String message, Location location, Symbol level) -> void
     def deconstruct_keys: (Array[Symbol] keys) -> { message: String, location: Location, level: Symbol }
   end
 
diff --git a/templates/include/prism/diagnostic.h.erb b/templates/include/prism/diagnostic.h.erb
new file mode 100644
index 00000000..53a700d4
--- /dev/null
+++ b/templates/include/prism/diagnostic.h.erb
@@ -0,0 +1,127 @@
+/**
+ * @file diagnostic.h
+ *
+ * A list of diagnostics generated during parsing.
+ */
+#ifndef PRISM_DIAGNOSTIC_H
+#define PRISM_DIAGNOSTIC_H
+
+#include "prism/ast.h"
+#include "prism/defines.h"
+#include "prism/util/pm_list.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <assert.h>
+
+/**
+ * The diagnostic IDs of all of the diagnostics, used to communicate the types
+ * of errors between the parser and the user.
+ */
+typedef enum {
+    // These are the error diagnostics.
+    <%- errors.each do |error| -%>
+    PM_ERR_<%= error.name %>,
+    <%- end -%>
+
+    // These are the warning diagnostics.
+    <%- warnings.each do |warning| -%>
+    PM_WARN_<%= warning.name %>,
+    <%- end -%>
+} pm_diagnostic_id_t;
+
+/**
+ * This struct represents a diagnostic generated during parsing.
+ *
+ * @extends pm_list_node_t
+ */
+typedef struct {
+    /** The embedded base node. */
+    pm_list_node_t node;
+
+    /** The location of the diagnostic in the source. */
+    pm_location_t location;
+
+    /** The ID of the diagnostic. */
+    pm_diagnostic_id_t diag_id;
+
+    /** The message associated with the diagnostic. */
+    const char *message;
+
+    /**
+     * Whether or not the memory related to the message of this diagnostic is
+     * owned by this diagnostic. If it is, it needs to be freed when the
+     * diagnostic is freed.
+     */
+    bool owned;
+
+    /**
+     * The level of the diagnostic, see `pm_error_level_t` and
+     * `pm_warning_level_t` for possible values.
+     */
+    uint8_t level;
+} pm_diagnostic_t;
+
+/**
+ * The levels of errors generated during parsing.
+ */
+typedef enum {
+    /** For errors that cannot be recovered from. */
+    PM_ERROR_LEVEL_FATAL = 0,
+
+    /** For errors that should raise an argument error. */
+    PM_ERROR_LEVEL_ARGUMENT = 1
+} pm_error_level_t;
+
+/**
+ * The levels of warnings generated during parsing.
+ */
+typedef enum {
+    /** For warnings which should be emitted if $VERBOSE != nil. */
+    PM_WARNING_LEVEL_DEFAULT = 0,
+
+    /** For warnings which should be emitted if $VERBOSE == true. */
+    PM_WARNING_LEVEL_VERBOSE = 1
+} pm_warning_level_t;
+
+/**
+ * Get the human-readable name of the given diagnostic ID.
+ *
+ * @param diag_id The diagnostic ID.
+ * @return The human-readable name of the diagnostic ID.
+ */
+const char * pm_diagnostic_id_human(pm_diagnostic_id_t diag_id);
+
+/**
+ * Append a diagnostic to the given list of diagnostics that is using shared
+ * memory for its message.
+ *
+ * @param list The list to append to.
+ * @param start The start of the diagnostic.
+ * @param end The end of the diagnostic.
+ * @param diag_id The diagnostic ID.
+ * @return Whether the diagnostic was successfully appended.
+ */
+bool pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id);
+
+/**
+ * Append a diagnostic to the given list of diagnostics that is using a format
+ * string for its message.
+ *
+ * @param list The list to append to.
+ * @param start The start of the diagnostic.
+ * @param end The end of the diagnostic.
+ * @param diag_id The diagnostic ID.
+ * @param ... The arguments to the format string for the message.
+ * @return Whether the diagnostic was successfully appended.
+ */
+bool pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id, ...);
+
+/**
+ * Deallocate the internal state of the given diagnostic list.
+ *
+ * @param list The list to deallocate.
+ */
+void pm_diagnostic_list_free(pm_list_t *list);
+
+#endif
diff --git a/templates/java/org/prism/Loader.java.erb b/templates/java/org/prism/Loader.java.erb
index ab4ae27b..1a20b825 100644
--- a/templates/java/org/prism/Loader.java.erb
+++ b/templates/java/org/prism/Loader.java.erb
@@ -120,7 +120,7 @@ public class Loader {
 
         ParseResult.MagicComment[] magicComments = loadMagicComments();
         Nodes.Location dataLocation = loadOptionalLocation();
-        ParseResult.Error[] errors = loadSyntaxErrors();
+        ParseResult.Error[] errors = loadErrors();
         ParseResult.Warning[] warnings = loadWarnings();
 
         int constantPoolBufferOffset = buffer.getInt();
@@ -187,18 +187,19 @@ public class Loader {
         return magicComments;
     }
 
-    private ParseResult.Error[] loadSyntaxErrors() {
+    private ParseResult.Error[] loadErrors() {
         int count = loadVarUInt();
         ParseResult.Error[] errors = new ParseResult.Error[count];
 
         // error messages only contain ASCII characters
         for (int i = 0; i < count; i++) {
+            Nodes.ErrorType type = Nodes.ERROR_TYPES[buffer.get() & 0xFF];
             byte[] bytes = loadEmbeddedString();
             String message = new String(bytes, StandardCharsets.US_ASCII);
             Nodes.Location location = loadLocation();
             ParseResult.ErrorLevel level = ParseResult.ERROR_LEVELS[buffer.get()];
 
-            ParseResult.Error error = new ParseResult.Error(message, location, level);
+            ParseResult.Error error = new ParseResult.Error(type, message, location, level);
             errors[i] = error;
         }
 
@@ -211,12 +212,13 @@ public class Loader {
 
         // warning messages only contain ASCII characters
         for (int i = 0; i < count; i++) {
+            Nodes.WarningType type = Nodes.WARNING_TYPES[(buffer.get() & 0xFF) - <%= errors.length %>];
             byte[] bytes = loadEmbeddedString();
             String message = new String(bytes, StandardCharsets.US_ASCII);
             Nodes.Location location = loadLocation();
             ParseResult.WarningLevel level = ParseResult.WARNING_LEVELS[buffer.get()];
 
-            ParseResult.Warning warning = new ParseResult.Warning(message, location, level);
+            ParseResult.Warning warning = new ParseResult.Warning(type, message, location, level);
             warnings[i] = warning;
         }
 
diff --git a/templates/java/org/prism/Nodes.java.erb b/templates/java/org/prism/Nodes.java.erb
index e048f4ba..48fbd66d 100644
--- a/templates/java/org/prism/Nodes.java.erb
+++ b/templates/java/org/prism/Nodes.java.erb
@@ -360,5 +360,20 @@ public abstract class Nodes {
     }
     <%- end -%>
 
+    public enum ErrorType {
+        <%- errors.each do |error| -%>
+        <%= error.name %>,
+        <%- end -%>
+    }
+
+    public static ErrorType[] ERROR_TYPES = ErrorType.values();
+
+    public enum WarningType {
+        <%- warnings.each do |warning| -%>
+        <%= warning.name %>,
+        <%- end -%>
+    }
+
+    public static WarningType[] WARNING_TYPES = WarningType.values();
 }
 // @formatter:on
diff --git a/templates/javascript/src/deserialize.js.erb b/templates/javascript/src/deserialize.js.erb
index 7c56add7..5bed36e7 100644
--- a/templates/javascript/src/deserialize.js.erb
+++ b/templates/javascript/src/deserialize.js.erb
@@ -145,13 +145,13 @@ class SerializationBuffer {
 /**
  * An error in the source code.
  *
- * @typedef {{ message: string, location: Location, level: string }} ParseError
+ * @typedef {{ type: string, message: string, location: Location, level: string }} ParseError
  */
 
 /**
  * A warning in the source code.
  *
- * @typedef {{ message: string, location: Location, level: string }} ParseWarning
+ * @typedef {{ type: string, message: string, location: Location, level: string }} ParseWarning
  */
 
 /**
@@ -211,7 +211,18 @@ export class ParseResult {
 }
 
 const errorLevels = ["fatal", "argument"];
+const errorTypes = [
+  <%- errors.each do |error| -%>
+  "<%= error.name.downcase %>",
+  <%- end -%>
+];
+
 const warningLevels = ["default", "verbose"];
+const warningTypes = [
+  <%- warnings.each do |warning| -%>
+  "<%= warning.name.downcase %>",
+  <%- end -%>
+];
 
 /**
  * Accept two Uint8Arrays, one for the source and one for the serialized format.
@@ -264,12 +275,14 @@ export function deserialize(source, array) {
   const dataLoc = buffer.readOptionalLocation();
 
   const errors = Array.from({ length: buffer.readVarInt() }, () => ({
+    type: errorTypes[buffer.readByte()],
     message: buffer.readString(buffer.readVarInt()),
     location: buffer.readLocation(),
     level: errorLevels[buffer.readByte()]
   }));
 
   const warnings = Array.from({ length: buffer.readVarInt() }, () => ({
+    type: warningTypes[buffer.readByte()],
     message: buffer.readString(buffer.readVarInt()),
     location: buffer.readLocation(),
     level: warningLevels[buffer.readByte()]
diff --git a/templates/lib/prism/serialize.rb.erb b/templates/lib/prism/serialize.rb.erb
index 097fb257..88d61ec4 100644
--- a/templates/lib/prism/serialize.rb.erb
+++ b/templates/lib/prism/serialize.rb.erb
@@ -116,12 +116,23 @@ module Prism
         end
       end
 
+      DIAGNOSTIC_TYPES = [
+        <%- errors.each do |error| -%>
+        <%= error.name.downcase.to_sym.inspect %>,
+        <%- end -%>
+        <%- warnings.each do |warning| -%>
+        <%= warning.name.downcase.to_sym.inspect %>,
+        <%- end -%>
+      ].freeze
+
+      private_constant :DIAGNOSTIC_TYPES
+
       def load_metadata
         comments = load_comments
         magic_comments = Array.new(load_varuint) { MagicComment.new(load_location_object, load_location_object) }
         data_loc = load_optional_location_object
-        errors = Array.new(load_varuint) { ParseError.new(load_embedded_string, load_location_object, load_error_level) }
-        warnings = Array.new(load_varuint) { ParseWarning.new(load_embedded_string, load_location_object, load_warning_level) }
+        errors = Array.new(load_varuint) { ParseError.new(DIAGNOSTIC_TYPES[io.getbyte], load_embedded_string, load_location_object, load_error_level) }
+        warnings = Array.new(load_varuint) { ParseWarning.new(DIAGNOSTIC_TYPES[io.getbyte], load_embedded_string, load_location_object, load_warning_level) }
         [comments, magic_comments, data_loc, errors, warnings]
       end
 
diff --git a/src/diagnostic.c b/templates/src/diagnostic.c.erb
similarity index 97%
rename from src/diagnostic.c
rename to templates/src/diagnostic.c.erb
index 3c43363f..c8c35bd4 100644
--- a/src/diagnostic.c
+++ b/templates/src/diagnostic.c.erb
@@ -1,5 +1,7 @@
 #include "prism/diagnostic.h"
 
+#define PM_DIAGNOSTIC_ID_MAX <%= errors.length + warnings.length %>
+
 /** This struct holds the data for each diagnostic. */
 typedef struct {
     /** The message associated with the diagnostic. */
@@ -71,7 +73,7 @@ typedef struct {
  * * `PM_WARNING_LEVEL_DEFAULT` - Warnings that appear for `ruby -c -e 'code'`.
  * * `PM_WARNING_LEVEL_VERBOSE` - Warnings that appear with `-w`, as in `ruby -w -c -e 'code'`.
  */
-static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
+static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
     // Special error that can be replaced
     [PM_ERR_CANNOT_PARSE_EXPRESSION]            = { "cannot parse the expression", PM_ERROR_LEVEL_FATAL },
 
@@ -316,9 +318,27 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
     [PM_WARN_KEYWORD_EOL]                       = { "`%.*s` at the end of line without an expression", PM_WARNING_LEVEL_VERBOSE }
 };
 
+/**
+ * Get the human-readable name of the given diagnostic ID.
+ */
+const char *
+pm_diagnostic_id_human(pm_diagnostic_id_t diag_id) {
+    switch (diag_id) {
+        <%- errors.each do |error| -%>
+        case PM_ERR_<%= error.name %>: return "<%= error.name.downcase %>";
+        <%- end -%>
+        <%- warnings.each do |warning| -%>
+        case PM_WARN_<%= warning.name %>: return "<%= warning.name.downcase %>";
+        <%- end -%>
+    }
+
+    assert(false && "unreachable");
+    return "";
+}
+
 static inline const char *
 pm_diagnostic_message(pm_diagnostic_id_t diag_id) {
-    assert(diag_id < PM_DIAGNOSTIC_ID_LEN);
+    assert(diag_id < PM_DIAGNOSTIC_ID_MAX);
 
     const char *message = diagnostic_messages[diag_id].message;
     assert(message);
@@ -328,7 +348,7 @@ pm_diagnostic_message(pm_diagnostic_id_t diag_id) {
 
 static inline uint8_t
 pm_diagnostic_level(pm_diagnostic_id_t diag_id) {
-    assert(diag_id < PM_DIAGNOSTIC_ID_LEN);
+    assert(diag_id < PM_DIAGNOSTIC_ID_MAX);
 
     return (uint8_t) diagnostic_messages[diag_id].level;
 }
@@ -343,6 +363,7 @@ pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *
 
     *diagnostic = (pm_diagnostic_t) {
         .location = { start, end },
+        .diag_id = diag_id,
         .message = pm_diagnostic_message(diag_id),
         .owned = false,
         .level = pm_diagnostic_level(diag_id)
@@ -387,6 +408,7 @@ pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const ui
 
     *diagnostic = (pm_diagnostic_t) {
         .location = { start, end },
+        .diag_id = diag_id,
         .message = message,
         .owned = true,
         .level = pm_diagnostic_level(diag_id)
diff --git a/templates/src/serialize.c.erb b/templates/src/serialize.c.erb
index 63db07c2..0313f43d 100644
--- a/templates/src/serialize.c.erb
+++ b/templates/src/serialize.c.erb
@@ -210,6 +210,9 @@ pm_serialize_data_loc(const pm_parser_t *parser, pm_buffer_t *buffer) {
 
 static void
 pm_serialize_diagnostic(pm_parser_t *parser, pm_diagnostic_t *diagnostic, pm_buffer_t *buffer) {
+    // serialize the type
+    pm_buffer_append_byte(buffer, (uint8_t) diagnostic->diag_id);
+
     // serialize message
     size_t message_length = strlen(diagnostic->message);
     pm_buffer_append_varuint(buffer, pm_sizet_to_u32(message_length));
diff --git a/templates/template.rb b/templates/template.rb
index d5955c6b..31257ef1 100755
--- a/templates/template.rb
+++ b/templates/template.rb
@@ -13,6 +13,22 @@ module Prism
     JAVA_BACKEND = ENV["PRISM_JAVA_BACKEND"] || "truffleruby"
     JAVA_STRING_TYPE = JAVA_BACKEND == "jruby" ? "org.jruby.RubySymbol" : "String"
 
+    class Error
+      attr_reader :name
+
+      def initialize(name)
+        @name = name
+      end
+    end
+
+    class Warning
+      attr_reader :name
+
+      def initialize(name)
+        @name = name
+      end
+    end
+
     # This module contains methods for escaping characters in JavaDoc comments.
     module JavaDoc
       ESCAPES = {
@@ -510,48 +526,49 @@ module Prism
         erb = read_template(template)
         extension = File.extname(filepath.gsub(".erb", ""))
 
-        heading = case extension
-        when ".rb"
-          <<~HEADING
-          # frozen_string_literal: true
-
-          =begin
-          This file is generated by the templates/template.rb script and should not be
-          modified manually. See #{filepath}
-          if you are looking to modify the template
-          =end
-
-          HEADING
-        when ".rbs"
-          <<~HEADING
-          # This file is generated by the templates/template.rb script and should not be
-          # modified manually. See #{filepath}
-          # if you are looking to modify the template
-
-          HEADING
-        when ".rbi"
-          <<~HEADING
-          # typed: strict
-
-          =begin
-          This file is generated by the templates/template.rb script and should not be
-          modified manually. See #{filepath}
-          if you are looking to modify the template
-          =end
-
-          HEADING
-        else
-          <<~HEADING
-          /******************************************************************************/
-          /* This file is generated by the templates/template.rb script and should not  */
-          /* be modified manually. See                                                  */
-          /* #{filepath + " " * (74 - filepath.size) } */
-          /* if you are looking to modify the                                           */
-          /* template                                                                   */
-          /******************************************************************************/
-
-          HEADING
-        end
+        heading =
+          case extension
+          when ".rb"
+            <<~HEADING
+            # frozen_string_literal: true
+
+            =begin
+            This file is generated by the templates/template.rb script and should not be
+            modified manually. See #{filepath}
+            if you are looking to modify the template
+            =end
+
+            HEADING
+          when ".rbs"
+            <<~HEADING
+            # This file is generated by the templates/template.rb script and should not be
+            # modified manually. See #{filepath}
+            # if you are looking to modify the template
+
+            HEADING
+          when ".rbi"
+            <<~HEADING
+            # typed: strict
+
+            =begin
+            This file is generated by the templates/template.rb script and should not be
+            modified manually. See #{filepath}
+            if you are looking to modify the template
+            =end
+
+            HEADING
+          else
+            <<~HEADING
+            /******************************************************************************/
+            /* This file is generated by the templates/template.rb script and should not  */
+            /* be modified manually. See                                                  */
+            /* #{filepath + " " * (74 - filepath.size) } */
+            /* if you are looking to modify the                                           */
+            /* template                                                                   */
+            /******************************************************************************/
+
+            HEADING
+          end
 
         write_to ||= File.expand_path("../#{name}", __dir__)
         contents = heading + erb.result_with_hash(locals)
@@ -588,6 +605,8 @@ module Prism
             config = YAML.load_file(File.expand_path("../config.yml", __dir__))
 
             {
+              errors: config.fetch("errors").map { |name| Error.new(name) },
+              warnings: config.fetch("warnings").map { |name| Warning.new(name) },
               nodes: config.fetch("nodes").map { |node| NodeType.new(node) }.sort_by(&:name),
               tokens: config.fetch("tokens").map { |token| Token.new(token) },
               flags: config.fetch("flags").map { |flags| Flags.new(flags) }
@@ -599,6 +618,7 @@ module Prism
     TEMPLATES = [
       "ext/prism/api_node.c",
       "include/prism/ast.h",
+      "include/prism/diagnostic.h",
       "javascript/src/deserialize.js",
       "javascript/src/nodes.js",
       "javascript/src/visitor.js",
@@ -613,6 +633,7 @@ module Prism
       "lib/prism/node.rb",
       "lib/prism/serialize.rb",
       "lib/prism/visitor.rb",
+      "src/diagnostic.c",
       "src/node.c",
       "src/prettyprint.c",
       "src/serialize.c",


Test patch:
diff --git a/test/prism/parser_test.rb b/test/prism/parser_test.rb
index 67051289..d3bf52d9 100644
--- a/test/prism/parser_test.rb
+++ b/test/prism/parser_test.rb
@@ -95,21 +95,6 @@ module Prism
       end
     end
 
-    def test_warnings
-      buffer = Parser::Source::Buffer.new("inline ruby with warning", 1)
-      buffer.source = "do_something *array"
-
-      parser = Prism::Translation::Parser33.new
-      parser.diagnostics.all_errors_are_fatal = false
-
-      warning = nil
-      parser.diagnostics.consumer = ->(received) { warning = received }
-      parser.parse(buffer)
-
-      assert_equal :warning, warning.level
-      assert_includes warning.message, "has been interpreted as"
-    end
-
     private
 
     def assert_equal_parses(filepath, compare_tokens: true)
@@ -186,8 +171,6 @@ module Prism
             actual_token[0] = expected_token[0] if %i[kDO_BLOCK kDO_LAMBDA].include?(expected_token[0])
           when :tLPAREN
             actual_token[0] = expected_token[0] if expected_token[0] == :tLPAREN2
-          when :tLCURLY
-            actual_token[0] = expected_token[0] if %i[tLBRACE tLBRACE_ARG].include?(expected_token[0])
           when :tPOW
             actual_token[0] = expected_token[0] if expected_token[0] == :tDSTAR
           end