Skip to content

Commit

Permalink
[ruby/prism] Static literals inspect
Browse files Browse the repository at this point in the history
  • Loading branch information
kddnewton authored and matzbot committed Mar 12, 2024
1 parent cb4bc4d commit 21ea290
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 21 deletions.
35 changes: 35 additions & 0 deletions prism/extension.c
Expand Up @@ -1189,6 +1189,40 @@ format_errors(VALUE self, VALUE source, VALUE colorize) {
return result;
}

/**
* call-seq:
* Debug::static_inspect(source) -> String
*
* Inspect the node as it would be inspected by the warnings used in static
* literal sets.
*/
static VALUE
static_inspect(int argc, VALUE *argv, VALUE self) {
pm_string_t input;
pm_options_t options = { 0 };
string_options(argc, argv, &input, &options);

pm_parser_t parser;
pm_parser_init(&parser, pm_string_source(&input), pm_string_length(&input), &options);

pm_node_t *program = pm_parse(&parser);
pm_node_t *node = ((pm_program_node_t *) program)->statements->body.nodes[0];

pm_buffer_t buffer = { 0 };
pm_static_literal_inspect(&buffer, &parser, node);

rb_encoding *encoding = rb_enc_find(parser.encoding->name);
VALUE result = rb_enc_str_new(pm_buffer_value(&buffer), pm_buffer_length(&buffer), encoding);

pm_buffer_free(&buffer);
pm_node_destroy(&parser, program);
pm_parser_free(&parser);
pm_string_free(&input);
pm_options_free(&options);

return result;
}

/**
* call-seq: Debug::Encoding.all -> Array[Debug::Encoding]
*
Expand Down Expand Up @@ -1338,6 +1372,7 @@ Init_prism(void) {
rb_define_singleton_method(rb_cPrismDebug, "profile_file", profile_file, 1);
rb_define_singleton_method(rb_cPrismDebug, "inspect_node", inspect_node, 1);
rb_define_singleton_method(rb_cPrismDebug, "format_errors", format_errors, 2);
rb_define_singleton_method(rb_cPrismDebug, "static_inspect", static_inspect, -1);

// Next, define the functions that are exposed through the private
// Debug::Encoding class.
Expand Down
9 changes: 7 additions & 2 deletions prism/prism.c
Expand Up @@ -12129,15 +12129,20 @@ pm_hash_key_static_literals_add(pm_parser_t *parser, pm_static_literals_t *liter
const pm_node_t *duplicated = pm_static_literals_add(parser, literals, node);

if (duplicated != NULL) {
pm_buffer_t buffer = { 0 };
pm_static_literal_inspect(&buffer, parser, duplicated);

pm_diagnostic_list_append_format(
&parser->warning_list,
duplicated->location.start,
duplicated->location.end,
PM_WARN_DUPLICATED_HASH_KEY,
(int) (duplicated->location.end - duplicated->location.start),
duplicated->location.start,
(int) pm_buffer_length(&buffer),
pm_buffer_value(&buffer),
pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line).line
);

pm_buffer_free(&buffer);
}
}

Expand Down
179 changes: 179 additions & 0 deletions prism/static_literals.c
Expand Up @@ -371,3 +371,182 @@ pm_static_literals_free(pm_static_literals_t *literals) {
pm_node_hash_free(&literals->regexp_nodes);
pm_node_hash_free(&literals->symbol_nodes);
}

/**
* A helper to determine if the given node is a static literal that is positive.
* This is used for formatting imaginary nodes.
*/
static bool
pm_static_literal_positive_p(const pm_node_t *node) {
switch (PM_NODE_TYPE(node)) {
case PM_FLOAT_NODE:
return ((const pm_float_node_t *) node)->value > 0;
case PM_INTEGER_NODE:
return !((const pm_integer_node_t *) node)->value.negative;
case PM_RATIONAL_NODE:
return pm_static_literal_positive_p(((const pm_rational_node_t *) node)->numeric);
case PM_IMAGINARY_NODE:
return pm_static_literal_positive_p(((const pm_imaginary_node_t *) node)->numeric);
default:
assert(false && "unreachable");
return false;
}
}

/**
* Inspect a rational node that wraps a float node. This is going to be a
* poor-man's version of the Ruby `Rational#to_s` method, because we're not
* going to try to reduce the rational by finding the GCD. We'll leave that for
* a future improvement.
*/
static void
pm_rational_inspect(pm_buffer_t *buffer, pm_rational_node_t *node) {
const uint8_t *start = node->base.location.start;
const uint8_t *end = node->base.location.end - 1; // r

while (start < end && *start == '0') start++; // 0.1 -> .1
while (end > start && end[-1] == '0') end--; // 1.0 -> 1.
size_t length = (size_t) (end - start);

const uint8_t *point = memchr(start, '.', length);
assert(point && "should have a decimal point");

uint8_t *digits = malloc(length - 1);
if (digits == NULL) return;

memcpy(digits, start, (unsigned long) (point - start));
memcpy(digits + (point - start), point + 1, (unsigned long) (end - point - 1));

pm_integer_t numerator = { 0 };
pm_integer_parse(&numerator, PM_INTEGER_BASE_DECIMAL, digits, digits + length - 1);

pm_buffer_append_byte(buffer, '(');
pm_integer_string(buffer, &numerator);
pm_buffer_append_string(buffer, "/1", 2);
for (size_t index = 0; index < (size_t) (end - point - 1); index++) pm_buffer_append_byte(buffer, '0');
pm_buffer_append_byte(buffer, ')');

pm_integer_free(&numerator);
free(digits);
}

/**
* Create a string-based representation of the given static literal.
*/
PRISM_EXPORTED_FUNCTION void
pm_static_literal_inspect(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node) {
assert(PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL));

switch (PM_NODE_TYPE(node)) {
case PM_FALSE_NODE:
pm_buffer_append_string(buffer, "false", 5);
break;
case PM_FLOAT_NODE: {
const double value = ((const pm_float_node_t *) node)->value;

if (isinf(value)) {
if (*node->location.start == '-') {
pm_buffer_append_byte(buffer, '-');
}
pm_buffer_append_string(buffer, "Infinity", 8);
} else if (value == 0.0) {
if (*node->location.start == '-') {
pm_buffer_append_byte(buffer, '-');
}
pm_buffer_append_string(buffer, "0.0", 3);
} else {
pm_buffer_append_format(buffer, "%g", value);

// %g will not insert a .0 for 1e100 (we'll get back 1e+100). So
// we check for the decimal point and add it in here if it's not
// present.
if (pm_buffer_index(buffer, '.') == -1) {
ssize_t exponent_index = pm_buffer_index(buffer, 'e');
size_t index = exponent_index == -1 ? pm_buffer_length(buffer) : (size_t) exponent_index;
pm_buffer_insert(buffer, index, ".0", 2);
}
}

break;
}
case PM_IMAGINARY_NODE: {
const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric;
pm_buffer_append_string(buffer, "(0", 2);
if (pm_static_literal_positive_p(numeric)) pm_buffer_append_byte(buffer, '+');
pm_static_literal_inspect(buffer, parser, numeric);
if (PM_NODE_TYPE_P(numeric, PM_RATIONAL_NODE)) pm_buffer_append_byte(buffer, '*');
pm_buffer_append_string(buffer, "i)", 2);
break;
}
case PM_INTEGER_NODE:
pm_integer_string(buffer, &((const pm_integer_node_t *) node)->value);
break;
case PM_NIL_NODE:
pm_buffer_append_string(buffer, "nil", 3);
break;
case PM_RATIONAL_NODE: {
const pm_node_t *numeric = ((const pm_rational_node_t *) node)->numeric;

switch (PM_NODE_TYPE(numeric)) {
case PM_INTEGER_NODE:
pm_buffer_append_byte(buffer, '(');
pm_static_literal_inspect(buffer, parser, numeric);
pm_buffer_append_string(buffer, "/1)", 3);
break;
case PM_FLOAT_NODE:
pm_rational_inspect(buffer, (pm_rational_node_t *) node);
break;
default:
assert(false && "unreachable");
break;
}

break;
}
case PM_REGULAR_EXPRESSION_NODE: {
const pm_string_t *unescaped = &((const pm_regular_expression_node_t *) node)->unescaped;
pm_buffer_append_byte(buffer, '/');
pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY);
pm_buffer_append_byte(buffer, '/');

if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_MULTI_LINE)) pm_buffer_append_string(buffer, "m", 1);
if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_IGNORE_CASE)) pm_buffer_append_string(buffer, "i", 1);
if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EXTENDED)) pm_buffer_append_string(buffer, "x", 1);
if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_ASCII_8BIT)) pm_buffer_append_string(buffer, "n", 1);

break;
}
case PM_SOURCE_ENCODING_NODE:
pm_buffer_append_format(buffer, "#<Encoding:%s>", parser->encoding->name);
break;
case PM_SOURCE_FILE_NODE: {
const pm_string_t *filepath = &((const pm_source_file_node_t *) node)->filepath;
pm_buffer_append_byte(buffer, '"');
pm_buffer_append_source(buffer, pm_string_source(filepath), pm_string_length(filepath), PM_BUFFER_ESCAPING_RUBY);
pm_buffer_append_byte(buffer, '"');
break;
}
case PM_SOURCE_LINE_NODE:
pm_buffer_append_format(buffer, "%d", pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line).line);
break;
case PM_STRING_NODE: {
const pm_string_t *unescaped = &((const pm_string_node_t *) node)->unescaped;
pm_buffer_append_byte(buffer, '"');
pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY);
pm_buffer_append_byte(buffer, '"');
break;
}
case PM_SYMBOL_NODE: {
const pm_string_t *unescaped = &((const pm_symbol_node_t *) node)->unescaped;
pm_buffer_append_byte(buffer, ':');
pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY);
break;
}
case PM_TRUE_NODE:
pm_buffer_append_string(buffer, "true", 4);
break;
default:
assert(false && "unreachable");
break;
}
}
9 changes: 9 additions & 0 deletions prism/static_literals.h
Expand Up @@ -106,4 +106,13 @@ pm_node_t * pm_static_literals_add(const pm_parser_t *parser, pm_static_literals
*/
void pm_static_literals_free(pm_static_literals_t *literals);

/**
* Create a string-based representation of the given static literal.
*
* @param buffer The buffer to write the string to.
* @param parser The parser that created the node.
* @param node The node to create a string representation of.
*/
PRISM_EXPORTED_FUNCTION void pm_static_literal_inspect(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node);

#endif
23 changes: 23 additions & 0 deletions prism/util/pm_buffer.c
Expand Up @@ -283,6 +283,29 @@ pm_buffer_rstrip(pm_buffer_t *buffer) {
}
}

/**
* Checks if the buffer includes the given value.
*/
ssize_t pm_buffer_index(const pm_buffer_t *buffer, char value) {
const char *first = memchr(buffer->value, value, buffer->length);
return (first == NULL) ? -1 : (ssize_t) (first - buffer->value);
}

/**
* Insert the given string into the buffer at the given index.
*/
void pm_buffer_insert(pm_buffer_t *buffer, size_t index, const char *value, size_t length) {
assert(index <= buffer->length);

if (index == buffer->length) {
pm_buffer_append_string(buffer, value, length);
} else {
pm_buffer_append_zeroes(buffer, length);
memmove(buffer->value + index + length, buffer->value + index, buffer->length - length - index);
memcpy(buffer->value + index, value, length);
}
}

/**
* Free the memory associated with the buffer.
*/
Expand Down
20 changes: 20 additions & 0 deletions prism/util/pm_buffer.h
Expand Up @@ -188,6 +188,26 @@ void pm_buffer_clear(pm_buffer_t *buffer);
*/
void pm_buffer_rstrip(pm_buffer_t *buffer);

/**
* Checks if the buffer includes the given value.
*
* @param buffer The buffer to check.
* @param value The value to check for.
* @returns The index of the first occurrence of the value in the buffer, or -1
* if the value is not found.
*/
ssize_t pm_buffer_index(const pm_buffer_t *buffer, char value);

/**
* Insert the given string into the buffer at the given index.
*
* @param buffer The buffer to insert into.
* @param index The index to insert at.
* @param value The string to insert.
* @param length The length of the string to insert.
*/
void pm_buffer_insert(pm_buffer_t *buffer, size_t index, const char *value, size_t length);

/**
* Free the memory associated with the buffer.
*
Expand Down

0 comments on commit 21ea290

Please sign in to comment.