Repository: ruby/prism (c)

Issue:
_1 (numbered parameters) should not be allowed as a name for def
defs (and possibly other Ruby features) will give a special syntaxerror if numbered parameters are used:

```ruby
def _1; end
```

Gives specialized SyntaxError: `_1 is reserved for numbered parameter`




Pull request description:
Disallow defining a numbered parameter method
Fixes #1775

Patch:
diff --git a/bin/encodings b/bin/encodings
index 819da7d2..77de0b75 100755
--- a/bin/encodings
+++ b/bin/encodings
@@ -85,6 +85,7 @@ when "iso-8859-15"  then table(Encoding::ISO8859_15)
 when "iso-8859-16"  then table(Encoding::ISO8859_16)
 when "koi8-r"       then table(Encoding::KOI8_R)
 when "maccenteuro"  then table(Encoding::MACCENTEURO)
+when "maccroatian"  then table(Encoding::MACCROATIAN)
 when "maccyrillic"  then table(Encoding::MACCYRILLIC)
 when "macgreek"     then table(Encoding::MACGREEK)
 when "maciceland"   then table(Encoding::MACICELAND)
diff --git a/docs/encoding.md b/docs/encoding.md
index 794d64ee..4ff8aa9c 100644
--- a/docs/encoding.md
+++ b/docs/encoding.md
@@ -51,6 +51,7 @@ The key of the comment can be either "encoding" or "coding". The value of the co
 * `ISO-8859-16`
 * `KOI8-R`
 * `macCentEuro`
+* `macCroatian`
 * `macCyrillic`
 * `macGreek`
 * `macIceland`
diff --git a/include/prism/enc/pm_encoding.h b/include/prism/enc/pm_encoding.h
index a062bbe5..7279dfd7 100644
--- a/include/prism/enc/pm_encoding.h
+++ b/include/prism/enc/pm_encoding.h
@@ -196,6 +196,7 @@ extern pm_encoding_t pm_encoding_iso_8859_15;
 extern pm_encoding_t pm_encoding_iso_8859_16;
 extern pm_encoding_t pm_encoding_koi8_r;
 extern pm_encoding_t pm_encoding_mac_cent_euro;
+extern pm_encoding_t pm_encoding_mac_croatian;
 extern pm_encoding_t pm_encoding_mac_cyrillic;
 extern pm_encoding_t pm_encoding_mac_greek;
 extern pm_encoding_t pm_encoding_mac_iceland;
diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb
index e1d3e0dc..c910fd3a 100644
--- a/lib/prism/ffi.rb
+++ b/lib/prism/ffi.rb
@@ -230,7 +230,7 @@ module Prism
         loader = Serialize::Loader.new(source, buffer.read)
 
         loader.load_header
-        loader.load_force_encoding
+        loader.load_encoding
         loader.load_start_line
         loader.load_comments
       end
diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb
index 170a529b..50c23bce 100644
--- a/lib/prism/parse_result.rb
+++ b/lib/prism/parse_result.rb
@@ -25,40 +25,50 @@ module Prism
 
     # Perform a byteslice on the source code using the given byte offset and
     # byte length.
-    def slice(offset, length)
-      source.byteslice(offset, length)
+    def slice(byte_offset, length)
+      source.byteslice(byte_offset, length)
     end
 
     # Binary search through the offsets to find the line number for the given
     # byte offset.
-    def line(value)
-      start_line + find_line(value)
+    def line(byte_offset)
+      start_line + find_line(byte_offset)
     end
 
     # Return the byte offset of the start of the line corresponding to the given
     # byte offset.
-    def line_offset(value)
-      offsets[find_line(value)]
+    def line_start(byte_offset)
+      offsets[find_line(byte_offset)]
     end
 
     # Return the column number for the given byte offset.
-    def column(value)
-      value - offsets[find_line(value)]
+    def column(byte_offset)
+      byte_offset - line_start(byte_offset)
+    end
+
+    # Return the character offset for the given byte offset.
+    def character_offset(byte_offset)
+      source.byteslice(0, byte_offset).length
+    end
+
+    # Return the column number in characters for the given byte offset.
+    def character_column(byte_offset)
+      character_offset(byte_offset) - character_offset(line_start(byte_offset))
     end
 
     private
 
     # Binary search through the offsets to find the line number for the given
     # byte offset.
-    def find_line(value)
+    def find_line(byte_offset)
       left = 0
       right = offsets.length - 1
 
       while left <= right
         mid = left + (right - left) / 2
-        return mid if offsets[mid] == value
+        return mid if offsets[mid] == byte_offset
 
-        if offsets[mid] < value
+        if offsets[mid] < byte_offset
           left = mid + 1
         else
           right = mid - 1
@@ -121,11 +131,23 @@ module Prism
       source.slice(start_offset, length)
     end
 
+    # The character offset from the beginning of the source where this location
+    # starts.
+    def start_character_offset
+      source.character_offset(start_offset)
+    end
+
     # The byte offset from the beginning of the source where this location ends.
     def end_offset
       start_offset + length
     end
 
+    # The character offset from the beginning of the source where this location
+    # ends.
+    def end_character_offset
+      source.character_offset(end_offset)
+    end
+
     # The line number where this location starts.
     def start_line
       source.line(start_offset)
@@ -133,7 +155,7 @@ module Prism
 
     # The content of the line where this location starts before this location.
     def start_line_slice
-      offset = source.line_offset(start_offset)
+      offset = source.line_start(start_offset)
       source.slice(offset, start_offset - offset)
     end
 
@@ -148,12 +170,24 @@ module Prism
       source.column(start_offset)
     end
 
+    # The column number in characters where this location ends from the start of
+    # the line.
+    def start_character_column
+      source.character_column(start_offset)
+    end
+
     # The column number in bytes where this location ends from the start of the
     # line.
     def end_column
       source.column(end_offset)
     end
 
+    # The column number in characters where this location ends from the start of
+    # the line.
+    def end_character_column
+      source.character_column(end_offset)
+    end
+
     # Implement the hash pattern matching interface for Location.
     def deconstruct_keys(keys)
       { start_offset: start_offset, end_offset: end_offset }
diff --git a/src/enc/pm_tables.c b/src/enc/pm_tables.c
index 87c7bfb4..d0414590 100644
--- a/src/enc/pm_tables.c
+++ b/src/enc/pm_tables.c
@@ -841,6 +841,30 @@ static uint8_t pm_encoding_mac_cent_euro_table[256] = {
 };
 
 /**
+ * Each element of the following table contains a bitfield that indicates a
+ * piece of information about the corresponding macCroatian character.
+ */
+static uint8_t pm_encoding_mac_croatian_table[256] = {
+//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, // 3x
+    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 4x
+    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, // 5x
+    0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 6x
+    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, // 7x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ax
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Bx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Cx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Dx
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ex
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fx
+};
+
+ /**
  * Each element of the following table contains a bitfield that indicates a
  * piece of information about the corresponding macCyrillic character.
  */
@@ -1371,6 +1395,7 @@ PRISM_ENCODING_TABLE(iso_8859_15)
 PRISM_ENCODING_TABLE(iso_8859_16)
 PRISM_ENCODING_TABLE(koi8_r)
 PRISM_ENCODING_TABLE(mac_cent_euro)
+PRISM_ENCODING_TABLE(mac_croatian)
 PRISM_ENCODING_TABLE(mac_cyrillic)
 PRISM_ENCODING_TABLE(mac_greek)
 PRISM_ENCODING_TABLE(mac_iceland)
@@ -1752,6 +1777,16 @@ pm_encoding_t pm_encoding_mac_cent_euro = {
     .multibyte = false
 };
 
+/** macCroatian */
+pm_encoding_t pm_encoding_mac_croatian = {
+    .name = "macCroatian",
+    .char_width = pm_encoding_single_char_width,
+    .alnum_char = pm_encoding_mac_croatian_alnum_char,
+    .alpha_char = pm_encoding_mac_croatian_alpha_char,
+    .isupper_char = pm_encoding_mac_croatian_isupper_char,
+    .multibyte = false
+};
+
 /** macCyrillic */
 pm_encoding_t pm_encoding_mac_cyrillic = {
     .name = "macCyrillic",
diff --git a/src/prism.c b/src/prism.c
index 5dcd3f27..2607a84c 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -4007,21 +4007,33 @@ pm_local_variable_write_node_create(pm_parser_t *parser, pm_constant_id_t name,
     return node;
 }
 
+/**
+ * Returns true if the given bounds comprise a numbered parameter (i.e., they
+ * are of the form /^_\d$/).
+ */
 static inline bool
-token_is_numbered_parameter(const uint8_t *start, const uint8_t *end) {
+pm_token_is_numbered_parameter(const uint8_t *start, const uint8_t *end) {
     return (end - start == 2) && (start[0] == '_') && (start[1] != '0') && (pm_char_is_decimal_digit(start[1]));
 }
 
+/**
+ * Ensure the given bounds do not comprise a numbered parameter. If they do, add
+ * an appropriate error message to the parser.
+ */
+static inline void
+pm_refute_numbered_parameter(pm_parser_t *parser, const uint8_t *start, const uint8_t *end) {
+    if (pm_token_is_numbered_parameter(start, end)) {
+        pm_parser_err(parser, start, end, PM_ERR_PARAMETER_NUMBERED_RESERVED);
+    }
+}
+
 /**
  * Allocate and initialize a new LocalVariableTargetNode node.
  */
 static pm_local_variable_target_node_t *
 pm_local_variable_target_node_create(pm_parser_t *parser, const pm_token_t *name) {
     pm_local_variable_target_node_t *node = PM_ALLOC_NODE(parser, pm_local_variable_target_node_t);
-
-    if (token_is_numbered_parameter(name->start, name->end)) {
-        pm_parser_err_token(parser, name, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-    }
+    pm_refute_numbered_parameter(parser, name->start, name->end);
 
     *node = (pm_local_variable_target_node_t) {
         {
@@ -5752,9 +5764,7 @@ static void
 pm_parser_parameter_name_check(pm_parser_t *parser, const pm_token_t *name) {
     // We want to check whether the parameter name is a numbered parameter or
     // not.
-    if (token_is_numbered_parameter(name->start, name->end)) {
-        pm_parser_err_token(parser, name, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-    }
+    pm_refute_numbered_parameter(parser, name->start, name->end);
 
     // We want to ignore any parameter name that starts with an underscore.
     if ((name->start < name->end) && (*name->start == '_')) return;
@@ -6145,6 +6155,7 @@ parser_lex_magic_comment_encoding_value(pm_parser_t *parser, const uint8_t *star
                 break;
             case 'M': case 'm':
                 ENCODING1("macCentEuro", pm_encoding_mac_cent_euro);
+                ENCODING1("macCroatian", pm_encoding_mac_croatian);
                 ENCODING1("macCyrillic", pm_encoding_mac_cyrillic);
                 ENCODING1("macGreek", pm_encoding_mac_greek);
                 ENCODING1("macIceland", pm_encoding_mac_iceland);
@@ -9149,7 +9160,7 @@ parser_lex(pm_parser_t *parser) {
                         !(last_state & (PM_LEX_STATE_DOT | PM_LEX_STATE_FNAME)) &&
                         (type == PM_TOKEN_IDENTIFIER) &&
                         ((pm_parser_local_depth(parser, &parser->current) != -1) ||
-                         token_is_numbered_parameter(parser->current.start, parser->current.end))
+                         pm_token_is_numbered_parameter(parser->current.start, parser->current.end))
                     ) {
                         lex_state_set(parser, PM_LEX_STATE_END | PM_LEX_STATE_LABEL);
                     }
@@ -10487,7 +10498,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target) {
             target->type = PM_GLOBAL_VARIABLE_TARGET_NODE;
             return target;
         case PM_LOCAL_VARIABLE_READ_NODE:
-            if (token_is_numbered_parameter(target->location.start, target->location.end)) {
+            if (pm_token_is_numbered_parameter(target->location.start, target->location.end)) {
                 pm_parser_err_node(parser, target, PM_ERR_PARAMETER_NUMBERED_RESERVED);
             } else {
                 assert(sizeof(pm_local_variable_target_node_t) == sizeof(pm_local_variable_read_node_t));
@@ -10546,10 +10557,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target) {
                     assert(sizeof(pm_local_variable_target_node_t) == sizeof(pm_local_variable_read_node_t));
                     target->type = PM_LOCAL_VARIABLE_TARGET_NODE;
 
-                    if (token_is_numbered_parameter(message.start, message.end)) {
-                        pm_parser_err_location(parser, &message, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-                    }
-
+                    pm_refute_numbered_parameter(parser, message.start, message.end);
                     return target;
                 }
 
@@ -10630,10 +10638,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod
             return (pm_node_t *) node;
         }
         case PM_LOCAL_VARIABLE_READ_NODE: {
-            if (token_is_numbered_parameter(target->location.start, target->location.end)) {
-                pm_parser_err_node(parser, target, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-            }
-
+            pm_refute_numbered_parameter(parser, target->location.start, target->location.end);
             pm_local_variable_read_node_t *local_read = (pm_local_variable_read_node_t *) target;
 
             pm_constant_id_t constant_id = local_read->name;
@@ -10695,10 +10700,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod
                     pm_constant_id_t constant_id = pm_parser_constant_id_location(parser, message.start, message.end);
                     target = (pm_node_t *) pm_local_variable_write_node_create(parser, constant_id, 0, value, &message, operator);
 
-                    if (token_is_numbered_parameter(message.start, message.end)) {
-                        pm_parser_err_location(parser, &message, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-                    }
-
+                    pm_refute_numbered_parameter(parser, message.start, message.end);
                     return target;
                 }
 
@@ -12505,7 +12507,7 @@ parse_variable_call(pm_parser_t *parser) {
             return (pm_node_t *) pm_local_variable_read_node_create(parser, &parser->previous, (uint32_t) depth);
         }
 
-        if (!parser->current_scope->closed && token_is_numbered_parameter(parser->previous.start, parser->previous.end)) {
+        if (!parser->current_scope->closed && pm_token_is_numbered_parameter(parser->previous.start, parser->previous.end)) {
             // Indicate that this scope is using numbered params so that child
             // scopes cannot.
             parser->current_scope->numbered_params = true;
@@ -12552,15 +12554,23 @@ parse_variable_call(pm_parser_t *parser) {
     return (pm_node_t *) node;
 }
 
+/**
+ * Parse the method definition name based on the current token available on the
+ * parser. If it does not match a valid method definition name, then a missing
+ * token is returned.
+ */
 static inline pm_token_t
 parse_method_definition_name(pm_parser_t *parser) {
     switch (parser->current.type) {
         case PM_CASE_KEYWORD:
         case PM_TOKEN_CONSTANT:
-        case PM_TOKEN_IDENTIFIER:
         case PM_TOKEN_METHOD_NAME:
             parser_lex(parser);
             return parser->previous;
+        case PM_TOKEN_IDENTIFIER:
+            pm_refute_numbered_parameter(parser, parser->current.start, parser->current.end);
+            parser_lex(parser);
+            return parser->previous;
         case PM_CASE_OPERATOR:
             lex_state_set(parser, PM_LEX_STATE_ENDFN);
             parser_lex(parser);
@@ -14512,6 +14522,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
                         operator = parser->previous;
                         name = parse_method_definition_name(parser);
                     } else {
+                        pm_refute_numbered_parameter(parser, parser->previous.start, parser->previous.end);
                         pm_parser_scope_push(parser, true);
                         name = parser->previous;
                     }
@@ -15770,10 +15781,7 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t *
                 // If the unescaped string is a slice of the source, then we can
                 // copy the names directly. The pointers will line up.
                 local = pm_parser_local_add_location(parser, source, source + length);
-
-                if (token_is_numbered_parameter(source, source + length)) {
-                    pm_parser_err(parser, source, source + length, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-                }
+                pm_refute_numbered_parameter(parser, source, source + length);
             } else {
                 // Otherwise, the name is a slice of the malloc-ed owned string,
                 // in which case we need to copy it out into a new string.
@@ -15786,7 +15794,7 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t *
                 // NOLINTNEXTLINE(clang-analyzer-*)
                 local = pm_parser_local_add_owned(parser, (const uint8_t *) memory, length);
 
-                if (token_is_numbered_parameter(source, source + length)) {
+                if (pm_token_is_numbered_parameter(source, source + length)) {
                     const pm_location_t *location = &call->receiver->location;
                     pm_parser_err_location(parser, location, PM_ERR_PARAMETER_NUMBERED_RESERVED);
                 }
@@ -15923,13 +15931,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     // receiver that could have been a local variable) then we
                     // will transform it into a local variable write.
                     if (pm_call_node_variable_call_p(cast)) {
-                        pm_location_t message_loc = cast->message_loc;
-                        pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc.start, message_loc.end);
-
-                        if (token_is_numbered_parameter(message_loc.start, message_loc.end)) {
-                            pm_parser_err_location(parser, &message_loc, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-                        }
+                        pm_location_t *message_loc = &cast->message_loc;
+                        pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end);
 
+                        pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end);
                         pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
                         pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
 
@@ -16037,13 +16042,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     // receiver that could have been a local variable) then we
                     // will transform it into a local variable write.
                     if (pm_call_node_variable_call_p(cast)) {
-                        pm_location_t message_loc = cast->message_loc;
-                        pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc.start, message_loc.end);
-
-                        if (token_is_numbered_parameter(message_loc.start, message_loc.end)) {
-                            pm_parser_err_location(parser, &message_loc, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-                        }
+                        pm_location_t *message_loc = &cast->message_loc;
+                        pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end);
 
+                        pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end);
                         pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
                         pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
 
@@ -16161,13 +16163,10 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
                     // receiver that could have been a local variable) then we
                     // will transform it into a local variable write.
                     if (pm_call_node_variable_call_p(cast)) {
-                        pm_location_t message_loc = cast->message_loc;
-                        pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc.start, message_loc.end);
-
-                        if (token_is_numbered_parameter(message_loc.start, message_loc.end)) {
-                            pm_parser_err_location(parser, &message_loc, PM_ERR_PARAMETER_NUMBERED_RESERVED);
-                        }
+                        pm_location_t *message_loc = &cast->message_loc;
+                        pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end);
 
+                        pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end);
                         pm_node_t *value = parse_assignment_value(parser, binding_power, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
                         pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0);
 
diff --git a/templates/lib/prism/serialize.rb.erb b/templates/lib/prism/serialize.rb.erb
index 28375045..e5a88ae9 100644
--- a/templates/lib/prism/serialize.rb.erb
+++ b/templates/lib/prism/serialize.rb.erb
@@ -73,12 +73,9 @@ module Prism
       end
 
       def load_encoding
-        Encoding.find(io.read(load_varint))
-      end
-
-      def load_force_encoding
-        @encoding = load_encoding
+        @encoding = Encoding.find(io.read(load_varint))
         @input = input.force_encoding(@encoding).freeze
+        @encoding
       end
 
       def load_start_line
@@ -121,10 +118,7 @@ module Prism
         encoding = load_encoding
         load_start_line
         comments, magic_comments, errors, warnings = load_metadata
-
-        if encoding != @encoding
-          tokens.each { |token,| token.value.force_encoding(encoding) }
-        end
+        tokens.each { |token,| token.value.force_encoding(encoding) }
 
         raise "Expected to consume all bytes while deserializing" unless @io.eof?
         Prism::ParseResult.new(tokens, comments, magic_comments, errors, warnings, @source)
@@ -132,7 +126,7 @@ module Prism
 
       def load_nodes
         load_header
-        load_force_encoding
+        load_encoding
         load_start_line
 
         comments, magic_comments, errors, warnings = load_metadata


Test patch:
diff --git a/test/prism/encoding_test.rb b/test/prism/encoding_test.rb
index bc9aae8e..a059e227 100644
--- a/test/prism/encoding_test.rb
+++ b/test/prism/encoding_test.rb
@@ -43,6 +43,7 @@ module Prism
       Encoding::ISO_8859_16 =>  0x00...0x100,
       Encoding::KOI8_R =>       0x00...0x100,
       Encoding::MACCENTEURO =>  0x00...0x100,
+      Encoding::MACCROATIAN =>  0x00...0x100,
       Encoding::MACCYRILLIC =>  0x00...0x100,
       Encoding::MACGREEK =>     0x00...0x100,
       Encoding::MACICELAND =>   0x00...0x100,
diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb
index 11036b1f..070b36de 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -1263,6 +1263,13 @@ module Prism
       ]
     end
 
+    def test_defining_numbered_parameter
+      error_messages = ["Token reserved for a numbered parameter"]
+
+      assert_error_messages "def _1; end", error_messages
+      assert_error_messages "def self._1; end", error_messages
+    end
+
     def test_double_scope_numbered_parameters
       source = "-> { _1 + -> { _2 } }"
       errors = [["Numbered parameter is already used in outer scope", 15..17]]
diff --git a/test/prism/ruby_api_test.rb b/test/prism/ruby_api_test.rb
index a61282cc..cd87a813 100644
--- a/test/prism/ruby_api_test.rb
+++ b/test/prism/ruby_api_test.rb
@@ -71,6 +71,38 @@ module Prism
       end
     end
 
+    def test_location_character_offsets
+      program = Prism.parse("😀 + 😀\n😍 ||= 😍").value
+
+      # first 😀
+      location = program.statements.body.first.receiver.location
+      assert_equal 0, location.start_character_offset
+      assert_equal 1, location.end_character_offset
+      assert_equal 0, location.start_character_column
+      assert_equal 1, location.end_character_column
+
+      # second 😀
+      location = program.statements.body.first.arguments.arguments.first.location
+      assert_equal 4, location.start_character_offset
+      assert_equal 5, location.end_character_offset
+      assert_equal 4, location.start_character_column
+      assert_equal 5, location.end_character_column
+
+      # first 😍
+      location = program.statements.body.last.name_loc
+      assert_equal 6, location.start_character_offset
+      assert_equal 7, location.end_character_offset
+      assert_equal 0, location.start_character_column
+      assert_equal 1, location.end_character_column
+
+      # second 😍
+      location = program.statements.body.last.value.location
+      assert_equal 12, location.start_character_offset
+      assert_equal 13, location.end_character_offset
+      assert_equal 6, location.start_character_column
+      assert_equal 7, location.end_character_column
+    end
+
     private
 
     def parse_expression(source)