Skip to content

Commit

Permalink
[ruby/prism] Convert pm_integer_t to strings
Browse files Browse the repository at this point in the history
  • Loading branch information
kddnewton authored and matzbot committed Feb 23, 2024
1 parent e458494 commit 96907f9
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 56 deletions.
21 changes: 16 additions & 5 deletions prism/extension.c
Expand Up @@ -953,9 +953,10 @@ named_captures(VALUE self, VALUE source) {

/**
* call-seq:
* Debug::integer_parse(source) -> Integer
* Debug::integer_parse(source) -> [Integer, String]
*
* Parses the given source string and returns the integer it represents.
* Parses the given source string and returns the integer it represents, as well
* as a decimal string representation.
*/
static VALUE
integer_parse(VALUE self, VALUE source) {
Expand All @@ -965,17 +966,27 @@ integer_parse(VALUE self, VALUE source) {
pm_integer_t integer = { 0 };
pm_integer_parse(&integer, PM_INTEGER_BASE_UNKNOWN, start, start + length);

VALUE result = UINT2NUM(integer.head.value);
VALUE number = UINT2NUM(integer.head.value);
size_t shift = 0;

for (pm_integer_word_t *node = integer.head.next; node != NULL; node = node->next) {
VALUE receiver = rb_funcall(UINT2NUM(node->value), rb_intern("<<"), 1, ULONG2NUM(++shift * 32));
result = rb_funcall(receiver, rb_intern("|"), 1, result);
number = rb_funcall(receiver, rb_intern("|"), 1, number);
}

if (integer.negative) result = rb_funcall(result, rb_intern("-@"), 0);
if (integer.negative) number = rb_funcall(number, rb_intern("-@"), 0);

pm_buffer_t buffer = { 0 };
pm_integer_string(&buffer, &integer);

VALUE string = rb_str_new(pm_buffer_value(&buffer), pm_buffer_length(&buffer));
pm_buffer_free(&buffer);
pm_integer_free(&integer);

VALUE result = rb_ary_new_capa(2);
rb_ary_push(result, number);
rb_ary_push(result, string);

return result;
}

Expand Down
23 changes: 1 addition & 22 deletions prism/templates/src/node.c.erb
Expand Up @@ -277,28 +277,7 @@ pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *no
<%- end -%>
pm_buffer_append_byte(buffer, ']');
<%- when Prism::IntegerField -%>
{
const pm_integer_t *integer = &cast-><%= field.name %>;
if (integer->length == 0) {
if (integer->negative) pm_buffer_append_byte(buffer, '-');
pm_buffer_append_string(buffer, "%" PRIu32, integer->head.value);
} else if (integer->length == 1) {
if (integer->negative) pm_buffer_append_byte(buffer, '-');
pm_buffer_append_format(buffer, "%" PRIu64, ((uint64_t) integer->head.value) | (((uint64_t) integer->head.next->value) << 32));
} else {
pm_buffer_append_byte(buffer, '{');
pm_buffer_append_format(buffer, "\"negative\": %s", integer->negative ? "true" : "false");
pm_buffer_append_string(buffer, ",\"values\":[", 11);

const pm_integer_word_t *node = &integer->head;
while (node != NULL) {
pm_buffer_append_format(buffer, "%" PRIu32, node->value);
node = node->next;
if (node != NULL) pm_buffer_append_byte(buffer, ',');
}
pm_buffer_append_string(buffer, "]}", 2);
}
}
pm_integer_string(buffer, &cast-><%= field.name %>);
<%- when Prism::DoubleField -%>
pm_buffer_append_format(buffer, "%f", cast-><%= field.name %>);
<%- else -%>
Expand Down
31 changes: 3 additions & 28 deletions prism/templates/src/prettyprint.c.erb
Expand Up @@ -128,34 +128,9 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm
pm_buffer_append_byte(output_buffer, '\n');
<%- when Prism::IntegerField -%>
const pm_integer_t *integer = &cast-><%= field.name %>;
if (integer->length == 0) {
pm_buffer_append_byte(output_buffer, ' ');
if (integer->negative) pm_buffer_append_byte(output_buffer, '-');
pm_buffer_append_format(output_buffer, "%" PRIu32 "\n", integer->head.value);
} else if (integer->length == 1) {
pm_buffer_append_byte(output_buffer, ' ');
if (integer->negative) pm_buffer_append_byte(output_buffer, '-');
pm_buffer_append_format(output_buffer, "%" PRIu64 "\n", ((uint64_t) integer->head.value) | (((uint64_t) integer->head.next->value) << 32));
} else {
pm_buffer_append_byte(output_buffer, ' ');

const pm_integer_word_t *node = &integer->head;
uint32_t index = 0;

while (node != NULL) {
if (index != 0) pm_buffer_append_string(output_buffer, " | ", 3);
pm_buffer_append_format(output_buffer, "%" PRIu32, node->value);

if (index != 0) {
pm_buffer_append_string(output_buffer, " << ", 4);
pm_buffer_append_format(output_buffer, "%" PRIu32, index * 32);
}

node = node->next;
index++;
}
pm_buffer_append_string(output_buffer, "]\n", 2);
}
pm_buffer_append_byte(output_buffer, ' ');
pm_integer_string(output_buffer, integer);
pm_buffer_append_byte(output_buffer, '\n');
<%- when Prism::DoubleField -%>
pm_buffer_append_format(output_buffer, " %f\n", cast-><%= field.name %>);
<%- else -%>
Expand Down
105 changes: 105 additions & 0 deletions prism/util/pm_integer.c
Expand Up @@ -14,6 +14,31 @@ pm_integer_node_create(pm_integer_t *integer, uint32_t value) {
return node;
}

/**
* Copy one integer onto another.
*/
static void
pm_integer_copy(pm_integer_t *dest, const pm_integer_t *src) {
dest->negative = src->negative;
dest->length = 0;

dest->head.value = src->head.value;
dest->head.next = NULL;

pm_integer_word_t *dest_current = &dest->head;
const pm_integer_word_t *src_current = src->head.next;

while (src_current != NULL) {
dest_current->next = pm_integer_node_create(dest, src_current->value);
if (dest_current->next == NULL) return;

dest_current = dest_current->next;
src_current = src_current->next;
}

dest_current->next = NULL;
}

/**
* Add a 32-bit integer to an integer.
*/
Expand Down Expand Up @@ -58,6 +83,37 @@ pm_integer_multiply(pm_integer_t *integer, uint32_t multiplier) {
}
}

/**
* Divide an individual word by a 32-bit integer. This will recursively divide
* any subsequent nodes in the linked list.
*/
static uint32_t
pm_integer_divide_word(pm_integer_t *integer, pm_integer_word_t *word, uint32_t dividend) {
uint32_t remainder = 0;
if (word->next != NULL) {
remainder = pm_integer_divide_word(integer, word->next, dividend);

if (integer->length > 0 && word->next->value == 0) {
free(word->next);
word->next = NULL;
integer->length--;
}
}

uint64_t value = ((uint64_t) remainder << 32) | word->value;
word->value = (uint32_t) (value / dividend);
return (uint32_t) (value % dividend);
}

/**
* Divide an integer by a 32-bit integer. In practice, this is only 10 so that
* we can format it as a string. It returns the remainder of the division.
*/
static uint32_t
pm_integer_divide(pm_integer_t *integer, uint32_t dividend) {
return pm_integer_divide_word(integer, &integer->head, dividend);
}

/**
* Return the value of a digit in a uint32_t.
*/
Expand Down Expand Up @@ -177,6 +233,55 @@ pm_integer_compare(const pm_integer_t *left, const pm_integer_t *right) {
return 0;
}

/**
* Convert an integer to a decimal string.
*/
PRISM_EXPORTED_FUNCTION void
pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
if (integer->negative) {
pm_buffer_append_byte(buffer, '-');
}

switch (integer->length) {
case 0: {
const uint32_t value = integer->head.value;
pm_buffer_append_format(buffer, "%" PRIu32, value);
return;
}
case 1: {
const uint64_t value = ((uint64_t) integer->head.value) | (((uint64_t) integer->head.next->value) << 32);
pm_buffer_append_format(buffer, "%" PRIu64, value);
return;
}
default: {
// First, allocate a buffer that we'll copy the decimal digits into.
size_t length = (integer->length + 1) * 10;
char *digits = calloc(length, sizeof(char));
if (digits == NULL) return;

// Next, create a new integer that we'll use to store the result of
// the division and modulo operations.
pm_integer_t copy;
pm_integer_copy(&copy, integer);

// Then, iterate through the integer, dividing by 10 and storing the
// result in the buffer.
char *ending = digits + length - 1;
char *current = ending;

while (copy.length > 0 || copy.head.value > 0) {
uint32_t remainder = pm_integer_divide(&copy, 10);
*current-- = (char) ('0' + remainder);
}

// Finally, append the string to the buffer and free the digits.
pm_buffer_append_string(buffer, current + 1, (size_t) (ending - current));
free(digits);
return;
}
}
}

/**
* Recursively destroy the linked list of an integer.
*/
Expand Down
9 changes: 9 additions & 0 deletions prism/util/pm_integer.h
Expand Up @@ -7,6 +7,7 @@
#define PRISM_NUMBER_H

#include "prism/defines.h"
#include "prism/util/pm_buffer.h"

#include <assert.h>
#include <stdbool.h>
Expand Down Expand Up @@ -104,6 +105,14 @@ size_t pm_integer_memsize(const pm_integer_t *integer);
*/
int pm_integer_compare(const pm_integer_t *left, const pm_integer_t *right);

/**
* Convert an integer to a decimal string.
*
* @param buffer The buffer to append the string to.
* @param integer The integer to convert to a string.
*/
PRISM_EXPORTED_FUNCTION void pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer);

/**
* Free the internal memory of an integer. This memory will only be allocated if
* the integer exceeds the size of a single node in the linked list.
Expand Down
4 changes: 3 additions & 1 deletion test/prism/integer_parse_test.rb
Expand Up @@ -31,7 +31,9 @@ def test_integer_parse
private

def assert_integer_parse(expected, source = expected.to_s)
assert_equal expected, Debug.integer_parse(source)
integer, string = Debug.integer_parse(source)
assert_equal expected, integer
assert_equal expected.to_s, string
end
end
end

0 comments on commit 96907f9

Please sign in to comment.