Skip to content

Commit

Permalink
Accept all 3.3.x and 3.4.x Ruby versions for Prism.parse
Browse files Browse the repository at this point in the history
  • Loading branch information
eregon committed Sep 20, 2024
1 parent 8197be8 commit a4fcd53
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 34 deletions.
6 changes: 4 additions & 2 deletions ext/prism/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -770,8 +770,10 @@ parse_input(pm_string_t *input, const pm_options_t *options) {
* * `version` - the version of Ruby syntax that prism should used to parse Ruby
* code. By default prism assumes you want to parse with the latest version
* of Ruby syntax (which you can trigger with `nil` or `"latest"`). You
* may also restrict the syntax to a specific version of Ruby. The
* supported values are `"3.3.0"` and `"3.4.0"`.
* may also restrict the syntax to a specific version of Ruby, e.g., with `"3.3.0"`.
* To parse with the same syntax version that the current Ruby is running
* use `version: RUBY_VERSION`. Raises ArgumentError if the version is not
* currently supported by Prism.
*/
static VALUE
parse(int argc, VALUE *argv, VALUE self) {
Expand Down
16 changes: 15 additions & 1 deletion lib/prism/ffi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,20 @@ def dump_options_command_line(options)
end
end

# Return the value that should be dumped for the version option.
def dump_options_version(version)
case version
when nil, "latest"
0
when /\A3\.3\.\d+\z/
1
when /\A3\.4\.\d+\z/
0
else
raise ArgumentError, "invalid version: #{version}"
end
end

# Convert the given options into a serialized options string.
def dump_options(options)
template = +""
Expand Down Expand Up @@ -443,7 +457,7 @@ def dump_options(options)
values << dump_options_command_line(options)

template << "C"
values << { nil => 0, "3.3.0" => 1, "3.3.1" => 1, "3.4.0" => 0, "latest" => 0 }.fetch(options[:version])
values << dump_options_version(options[:version])

template << "C"
values << (options[:encoding] == false ? 1 : 0)
Expand Down
56 changes: 26 additions & 30 deletions src/options.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "prism/options.h"
#include "prism/util/pm_char.h"

/**
* Set the shebang callback option on the given options struct.
Expand Down Expand Up @@ -57,47 +58,42 @@ pm_options_command_line_set(pm_options_t *options, uint8_t command_line) {
options->command_line = command_line;
}

static bool is_number(const char *string, size_t length) {
return pm_strspn_decimal_digit((const uint8_t *) string, (ptrdiff_t) length) == length;
}

/**
* Set the version option on the given options struct by parsing the given
* string. If the string contains an invalid option, this returns false.
* Otherwise, it returns true.
*/
PRISM_EXPORTED_FUNCTION bool
pm_options_version_set(pm_options_t *options, const char *version, size_t length) {
switch (length) {
case 0:
if (version == NULL) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}

return false;
case 5:
assert(version != NULL);

if ((strncmp(version, "3.3.0", length) == 0) || (strncmp(version, "3.3.1", length) == 0)) {
options->version = PM_OPTIONS_VERSION_CRUBY_3_3;
return true;
}

if (strncmp(version, "3.4.0", length) == 0) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
if (version == NULL) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}

return false;
case 6:
assert(version != NULL);
if (length >= 4) {
if (strncmp(version, "3.3.", 4) == 0 && is_number(version + 4, length - 4)) {
options->version = PM_OPTIONS_VERSION_CRUBY_3_3;
return true;
}

if (strncmp(version, "latest", length) == 0) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
if (strncmp(version, "3.4.", 4) == 0 && is_number(version + 4, length - 4)) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
}

return false;
default:
return false;
if (length >= 6) {
if (strncmp(version, "latest", 7) == 0) { // 7 to compare the \0 as well
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
}

return false;
}

/**
Expand Down
40 changes: 39 additions & 1 deletion test/prism/version_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,46 @@

module Prism
class VersionTest < TestCase
def test_version_is_set
def test_prism_version_is_set
refute_nil VERSION
end

def test_syntax_versions
assert Prism.parse("1 + 1", version: "3.3.0").success?
assert Prism.parse("1 + 1", version: "3.3.1").success?
assert Prism.parse("1 + 1", version: "3.3.9").success?
assert Prism.parse("1 + 1", version: "3.3.10").success?

assert Prism.parse("1 + 1", version: "3.4.0").success?
assert Prism.parse("1 + 1", version: "3.4.9").success?
assert Prism.parse("1 + 1", version: "3.4.10").success?

assert Prism.parse("1 + 1", version: "latest").success?

# Test edge case
error = assert_raise ArgumentError do
Prism.parse("1 + 1", version: "latest2")
end
assert_equal "invalid version: latest2", error.message

assert_raise ArgumentError do
Prism.parse("1 + 1", version: "3.3.a")
end

# Not supported version syntax
assert_raise ArgumentError do
Prism.parse("1 + 1", version: "3.3")
end

# Not supported version (too old)
assert_raise ArgumentError do
Prism.parse("1 + 1", version: "3.2.0")
end

# Not supported version (too new)
assert_raise ArgumentError do
Prism.parse("1 + 1", version: "3.5.0")
end
end
end
end

0 comments on commit a4fcd53

Please sign in to comment.