prism 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -1
- data/Makefile +1 -1
- data/config.yml +420 -2
- data/docs/build_system.md +8 -11
- data/docs/relocation.md +34 -0
- data/ext/prism/api_node.c +18 -10
- data/ext/prism/extconf.rb +13 -36
- data/ext/prism/extension.c +68 -0
- data/ext/prism/extension.h +1 -1
- data/include/prism/ast.h +426 -2
- data/include/prism/defines.h +22 -7
- data/include/prism/version.h +2 -2
- data/include/prism.h +47 -0
- data/lib/prism/dot_visitor.rb +10 -0
- data/lib/prism/dsl.rb +4 -4
- data/lib/prism/ffi.rb +49 -2
- data/lib/prism/inspect_visitor.rb +2 -0
- data/lib/prism/node.rb +1838 -95
- data/lib/prism/parse_result/errors.rb +1 -1
- data/lib/prism/parse_result.rb +2 -2
- data/lib/prism/reflection.rb +2 -2
- data/lib/prism/relocation.rb +504 -0
- data/lib/prism/serialize.rb +5 -5
- data/lib/prism/string_query.rb +30 -0
- data/lib/prism/translation/parser/compiler.rb +36 -26
- data/lib/prism/translation/ruby_parser.rb +12 -3
- data/lib/prism.rb +6 -4
- data/prism.gemspec +7 -1
- data/rbi/prism/dsl.rbi +4 -4
- data/rbi/prism/node.rbi +22 -10
- data/rbi/prism/string_query.rbi +12 -0
- data/sig/prism/dsl.rbs +2 -2
- data/sig/prism/node.rbs +12 -8
- data/sig/prism/relocation.rbs +185 -0
- data/sig/prism/string_query.rbs +11 -0
- data/src/node.c +18 -0
- data/src/prettyprint.c +32 -0
- data/src/prism.c +364 -81
- data/src/regexp.c +7 -3
- data/src/serialize.c +12 -0
- data/src/static_literals.c +1 -1
- data/src/util/pm_char.c +1 -1
- data/src/util/pm_string.c +1 -0
- metadata +9 -3
data/docs/build_system.md
CHANGED
@@ -16,9 +16,9 @@ The main solution for the second point seems a Makefile, otherwise many of the u
|
|
16
16
|
## General Design
|
17
17
|
|
18
18
|
1. Templates are generated by `templates/template.rb`
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
2. The `Makefile` compiles both `libprism.a` and `libprism.{so,dylib,dll}` from the `src/**/*.c` and `include/**/*.h` files
|
20
|
+
3. The `Rakefile` `:compile` task ensures the above prerequisites are done, then calls `make`,
|
21
|
+
and uses `Rake::ExtensionTask` to compile the C extension (using its `extconf.rb`)
|
22
22
|
|
23
23
|
This way there is minimal duplication, and each layer builds on the previous one and has its own responsibilities.
|
24
24
|
|
@@ -35,14 +35,11 @@ loaded per process (i.e., at most one version of the prism *gem* loaded in a pro
|
|
35
35
|
### Building the prism gem by `gem install/bundle install`
|
36
36
|
|
37
37
|
The gem contains the pre-generated templates.
|
38
|
-
When installing the gem, `extconf.rb` is used and that:
|
39
|
-
* runs `make build/libprism.a`
|
40
|
-
* compiles the C extension with mkmf
|
41
38
|
|
42
|
-
When installing the gem on
|
43
|
-
|
44
|
-
|
45
|
-
(JRuby does not support C extensions, serialization is faster on TruffleRuby than the C extension).
|
39
|
+
When installing the gem on CRuby, `extconf.rb` is used and that compiles the C extension with mkmf, including both the extension files and the sources of prism itself.
|
40
|
+
|
41
|
+
When installing the gem on JRuby and TruffleRuby, no C extension is built, so instead the `extconf.rb` runs `make build/libprism.{so,dylib,dll}`.
|
42
|
+
There is Ruby code using FFI which uses `libprism.{so,dylib,dll}` to implement the same methods as the C extension, but using serialization instead of many native calls/accesses (JRuby does not support C extensions, serialization is faster on TruffleRuby than the C extension).
|
46
43
|
|
47
44
|
### Building the prism gem from git, e.g. `gem "prism", github: "ruby/prism"`
|
48
45
|
|
@@ -66,7 +63,7 @@ The script generates the templates when importing.
|
|
66
63
|
|
67
64
|
Then when `mx build` builds TruffleRuby and the `prism` mx project inside, it runs `make`.
|
68
65
|
|
69
|
-
Then the `prism bindings` mx project is built, which contains the [bindings](https://github.com/oracle/truffleruby/blob/
|
66
|
+
Then the `prism bindings` mx project is built, which contains the [bindings](https://github.com/oracle/truffleruby/blob/vm-24.1.1/src/main/c/yarp_bindings/src/yarp_bindings.c)
|
70
67
|
and links to `libprism.a` (to avoid exporting symbols, so no conflict when installing the prism gem).
|
71
68
|
|
72
69
|
### Building prism as part of JRuby
|
data/docs/relocation.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Relocation
|
2
|
+
|
3
|
+
Prism parses deterministically for the same input. This provides a nice property that is exposed through the `#node_id` API on nodes. Effectively this means that for the same input, these values will remain consistent every time the source is parsed. This means we can reparse the source same with a `#node_id` value and find the exact same node again.
|
4
|
+
|
5
|
+
The `Relocation` module provides an API around this property. It allows you to "save" nodes and locations using a minimal amount of memory (just the node_id and a field identifier) and then reify them later. This minimizes the amount of memory you need to allocate to store this information because it does not keep around a pointer to the source string.
|
6
|
+
|
7
|
+
## Getting started
|
8
|
+
|
9
|
+
To get started with the `Relocation` module, you would first instantiate a `Repository` object. You do this through a DSL that chains method calls for configuration. For example, if for every entry in the repository you want to store the start and end lines, the start and end code unit columns for in UTF-16, and the leading comments, you would:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
repository = Prism::Relocation.filepath("path/to/file").lines.code_unit_columns(Encoding::UTF_16).leading_comments
|
13
|
+
```
|
14
|
+
|
15
|
+
Now that you have the repository, you can pass it into any of the `save*` APIs on nodes or locations to create entries in the repository that will be lazily reified.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# assume that node is a Prism::ClassNode object
|
19
|
+
entry = node.constant_path.save(repository)
|
20
|
+
```
|
21
|
+
|
22
|
+
Now that you have the entry object, you do not need to keep around a reference to the repository, it will be cleaned up on its own when the last entry is reified. Now, whenever you need to, you may call the associated field methods on the entry object, as in:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
entry.start_line
|
26
|
+
entry.end_line
|
27
|
+
|
28
|
+
entry.start_code_units_column
|
29
|
+
entry.end_code_units_column
|
30
|
+
|
31
|
+
entry.leading_comments
|
32
|
+
```
|
33
|
+
|
34
|
+
Note that if you had configured other fields to be saved, you would be able to access them as well. The first time one of these fields is accessed, the repository will reify every entry it knows about and then clean itself up. In this way, you can effectively treat them as if you had kept around lightweight versions of `Prism::Node` or `Prism::Location` objects.
|
data/ext/prism/api_node.c
CHANGED
@@ -6075,7 +6075,7 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi
|
|
6075
6075
|
#line 172 "prism/templates/ext/prism/api_node.c.erb"
|
6076
6076
|
case PM_UNTIL_NODE: {
|
6077
6077
|
pm_until_node_t *cast = (pm_until_node_t *) node;
|
6078
|
-
VALUE argv[
|
6078
|
+
VALUE argv[9];
|
6079
6079
|
|
6080
6080
|
// source
|
6081
6081
|
argv[0] = source;
|
@@ -6093,19 +6093,23 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi
|
|
6093
6093
|
#line 220 "prism/templates/ext/prism/api_node.c.erb"
|
6094
6094
|
argv[4] = pm_location_new(parser, cast->keyword_loc.start, cast->keyword_loc.end);
|
6095
6095
|
|
6096
|
+
// do_keyword_loc
|
6097
|
+
#line 223 "prism/templates/ext/prism/api_node.c.erb"
|
6098
|
+
argv[5] = cast->do_keyword_loc.start == NULL ? Qnil : pm_location_new(parser, cast->do_keyword_loc.start, cast->do_keyword_loc.end);
|
6099
|
+
|
6096
6100
|
// closing_loc
|
6097
6101
|
#line 223 "prism/templates/ext/prism/api_node.c.erb"
|
6098
|
-
argv[
|
6102
|
+
argv[6] = cast->closing_loc.start == NULL ? Qnil : pm_location_new(parser, cast->closing_loc.start, cast->closing_loc.end);
|
6099
6103
|
|
6100
6104
|
// predicate
|
6101
6105
|
#line 195 "prism/templates/ext/prism/api_node.c.erb"
|
6102
|
-
argv[
|
6106
|
+
argv[7] = rb_ary_pop(value_stack);
|
6103
6107
|
|
6104
6108
|
// statements
|
6105
6109
|
#line 195 "prism/templates/ext/prism/api_node.c.erb"
|
6106
|
-
argv[
|
6110
|
+
argv[8] = rb_ary_pop(value_stack);
|
6107
6111
|
|
6108
|
-
rb_ary_push(value_stack, rb_class_new_instance(
|
6112
|
+
rb_ary_push(value_stack, rb_class_new_instance(9, argv, rb_cPrismUntilNode));
|
6109
6113
|
break;
|
6110
6114
|
}
|
6111
6115
|
#line 172 "prism/templates/ext/prism/api_node.c.erb"
|
@@ -6150,7 +6154,7 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi
|
|
6150
6154
|
#line 172 "prism/templates/ext/prism/api_node.c.erb"
|
6151
6155
|
case PM_WHILE_NODE: {
|
6152
6156
|
pm_while_node_t *cast = (pm_while_node_t *) node;
|
6153
|
-
VALUE argv[
|
6157
|
+
VALUE argv[9];
|
6154
6158
|
|
6155
6159
|
// source
|
6156
6160
|
argv[0] = source;
|
@@ -6168,19 +6172,23 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi
|
|
6168
6172
|
#line 220 "prism/templates/ext/prism/api_node.c.erb"
|
6169
6173
|
argv[4] = pm_location_new(parser, cast->keyword_loc.start, cast->keyword_loc.end);
|
6170
6174
|
|
6175
|
+
// do_keyword_loc
|
6176
|
+
#line 223 "prism/templates/ext/prism/api_node.c.erb"
|
6177
|
+
argv[5] = cast->do_keyword_loc.start == NULL ? Qnil : pm_location_new(parser, cast->do_keyword_loc.start, cast->do_keyword_loc.end);
|
6178
|
+
|
6171
6179
|
// closing_loc
|
6172
6180
|
#line 223 "prism/templates/ext/prism/api_node.c.erb"
|
6173
|
-
argv[
|
6181
|
+
argv[6] = cast->closing_loc.start == NULL ? Qnil : pm_location_new(parser, cast->closing_loc.start, cast->closing_loc.end);
|
6174
6182
|
|
6175
6183
|
// predicate
|
6176
6184
|
#line 195 "prism/templates/ext/prism/api_node.c.erb"
|
6177
|
-
argv[
|
6185
|
+
argv[7] = rb_ary_pop(value_stack);
|
6178
6186
|
|
6179
6187
|
// statements
|
6180
6188
|
#line 195 "prism/templates/ext/prism/api_node.c.erb"
|
6181
|
-
argv[
|
6189
|
+
argv[8] = rb_ary_pop(value_stack);
|
6182
6190
|
|
6183
|
-
rb_ary_push(value_stack, rb_class_new_instance(
|
6191
|
+
rb_ary_push(value_stack, rb_class_new_instance(9, argv, rb_cPrismWhileNode));
|
6184
6192
|
break;
|
6185
6193
|
}
|
6186
6194
|
#line 172 "prism/templates/ext/prism/api_node.c.erb"
|
data/ext/prism/extconf.rb
CHANGED
@@ -70,26 +70,6 @@ if RUBY_ENGINE != "ruby"
|
|
70
70
|
return
|
71
71
|
end
|
72
72
|
|
73
|
-
# We're going to need to run `make` using prism's `Makefile`.
|
74
|
-
# We want to use the same toolchain (compiler, flags, etc) to compile libprism.a and
|
75
|
-
# the C extension since they will be linked together.
|
76
|
-
# The C extension uses RbConfig, which contains values from the toolchain that built the running Ruby.
|
77
|
-
env = RbConfig::CONFIG.slice("SOEXT", "CPPFLAGS", "CFLAGS", "CC", "AR", "ARFLAGS", "MAKEDIRS", "RMALL")
|
78
|
-
|
79
|
-
# It's possible that the Ruby that is being run wasn't actually compiled on this
|
80
|
-
# machine, in which case parts of RbConfig might be incorrect. In this case
|
81
|
-
# we'll need to do some additional checks and potentially fall back to defaults.
|
82
|
-
if env.key?("CC") && !File.exist?(env["CC"])
|
83
|
-
env.delete("CC")
|
84
|
-
env.delete("CFLAGS")
|
85
|
-
env.delete("CPPFLAGS")
|
86
|
-
end
|
87
|
-
|
88
|
-
if env.key?("AR") && !File.exist?(env["AR"])
|
89
|
-
env.delete("AR")
|
90
|
-
env.delete("ARFLAGS")
|
91
|
-
end
|
92
|
-
|
93
73
|
require "mkmf"
|
94
74
|
|
95
75
|
# First, ensure that we can find the header for the prism library.
|
@@ -127,24 +107,21 @@ end
|
|
127
107
|
# By default, all symbols are hidden in the shared library.
|
128
108
|
append_cflags("-fvisibility=hidden")
|
129
109
|
|
130
|
-
|
131
|
-
|
132
|
-
#
|
133
|
-
|
134
|
-
|
110
|
+
def src_list(path)
|
111
|
+
srcdir = path.dup
|
112
|
+
RbConfig.expand(srcdir) # mutates srcdir :-/
|
113
|
+
Dir[File.join(srcdir, "*.{#{SRC_EXT.join(%q{,})}}")]
|
114
|
+
end
|
135
115
|
|
136
|
-
|
137
|
-
$
|
116
|
+
def add_libprism_source(path)
|
117
|
+
$VPATH << path
|
118
|
+
src_list path
|
119
|
+
end
|
120
|
+
|
121
|
+
$srcs = src_list("$(srcdir)") +
|
122
|
+
add_libprism_source("$(srcdir)/../../src") +
|
123
|
+
add_libprism_source("$(srcdir)/../../src/util")
|
138
124
|
|
139
125
|
# Finally, we'll create the `Makefile` that is going to be used to configure and
|
140
126
|
# build the C extension.
|
141
127
|
create_makefile("prism/prism")
|
142
|
-
|
143
|
-
# Now that the `Makefile` for the C extension is built, we'll append on an extra
|
144
|
-
# rule that dictates that the extension should be rebuilt if the archive is
|
145
|
-
# updated.
|
146
|
-
File.open("Makefile", "a") do |mf|
|
147
|
-
mf.puts
|
148
|
-
mf.puts("# Automatically rebuild the extension if libprism.a changed")
|
149
|
-
mf.puts("$(TARGET_SO): $(LOCAL_LIBS)")
|
150
|
-
end
|
data/ext/prism/extension.c
CHANGED
@@ -23,6 +23,7 @@ VALUE rb_cPrismResult;
|
|
23
23
|
VALUE rb_cPrismParseResult;
|
24
24
|
VALUE rb_cPrismLexResult;
|
25
25
|
VALUE rb_cPrismParseLexResult;
|
26
|
+
VALUE rb_cPrismStringQuery;
|
26
27
|
|
27
28
|
VALUE rb_cPrismDebugEncoding;
|
28
29
|
|
@@ -1133,6 +1134,68 @@ parse_file_failure_p(int argc, VALUE *argv, VALUE self) {
|
|
1133
1134
|
return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue;
|
1134
1135
|
}
|
1135
1136
|
|
1137
|
+
/******************************************************************************/
|
1138
|
+
/* String query methods */
|
1139
|
+
/******************************************************************************/
|
1140
|
+
|
1141
|
+
/**
|
1142
|
+
* Process the result of a call to a string query method and return an
|
1143
|
+
* appropriate value.
|
1144
|
+
*/
|
1145
|
+
static VALUE
|
1146
|
+
string_query(pm_string_query_t result) {
|
1147
|
+
switch (result) {
|
1148
|
+
case PM_STRING_QUERY_ERROR:
|
1149
|
+
rb_raise(rb_eArgError, "Invalid or non ascii-compatible encoding");
|
1150
|
+
return Qfalse;
|
1151
|
+
case PM_STRING_QUERY_FALSE:
|
1152
|
+
return Qfalse;
|
1153
|
+
case PM_STRING_QUERY_TRUE:
|
1154
|
+
return Qtrue;
|
1155
|
+
}
|
1156
|
+
return Qfalse;
|
1157
|
+
}
|
1158
|
+
|
1159
|
+
/**
|
1160
|
+
* call-seq:
|
1161
|
+
* Prism::StringQuery::local?(string) -> bool
|
1162
|
+
*
|
1163
|
+
* Returns true if the string constitutes a valid local variable name. Note that
|
1164
|
+
* this means the names that can be set through Binding#local_variable_set, not
|
1165
|
+
* necessarily the ones that can be set through a local variable assignment.
|
1166
|
+
*/
|
1167
|
+
static VALUE
|
1168
|
+
string_query_local_p(VALUE self, VALUE string) {
|
1169
|
+
const uint8_t *source = (const uint8_t *) check_string(string);
|
1170
|
+
return string_query(pm_string_query_local(source, RSTRING_LEN(string), rb_enc_get(string)->name));
|
1171
|
+
}
|
1172
|
+
|
1173
|
+
/**
|
1174
|
+
* call-seq:
|
1175
|
+
* Prism::StringQuery::constant?(string) -> bool
|
1176
|
+
*
|
1177
|
+
* Returns true if the string constitutes a valid constant name. Note that this
|
1178
|
+
* means the names that can be set through Module#const_set, not necessarily the
|
1179
|
+
* ones that can be set through a constant assignment.
|
1180
|
+
*/
|
1181
|
+
static VALUE
|
1182
|
+
string_query_constant_p(VALUE self, VALUE string) {
|
1183
|
+
const uint8_t *source = (const uint8_t *) check_string(string);
|
1184
|
+
return string_query(pm_string_query_constant(source, RSTRING_LEN(string), rb_enc_get(string)->name));
|
1185
|
+
}
|
1186
|
+
|
1187
|
+
/**
|
1188
|
+
* call-seq:
|
1189
|
+
* Prism::StringQuery::method_name?(string) -> bool
|
1190
|
+
*
|
1191
|
+
* Returns true if the string constitutes a valid method name.
|
1192
|
+
*/
|
1193
|
+
static VALUE
|
1194
|
+
string_query_method_name_p(VALUE self, VALUE string) {
|
1195
|
+
const uint8_t *source = (const uint8_t *) check_string(string);
|
1196
|
+
return string_query(pm_string_query_method_name(source, RSTRING_LEN(string), rb_enc_get(string)->name));
|
1197
|
+
}
|
1198
|
+
|
1136
1199
|
/******************************************************************************/
|
1137
1200
|
/* Initialization of the extension */
|
1138
1201
|
/******************************************************************************/
|
@@ -1170,6 +1233,7 @@ Init_prism(void) {
|
|
1170
1233
|
rb_cPrismParseResult = rb_define_class_under(rb_cPrism, "ParseResult", rb_cPrismResult);
|
1171
1234
|
rb_cPrismLexResult = rb_define_class_under(rb_cPrism, "LexResult", rb_cPrismResult);
|
1172
1235
|
rb_cPrismParseLexResult = rb_define_class_under(rb_cPrism, "ParseLexResult", rb_cPrismResult);
|
1236
|
+
rb_cPrismStringQuery = rb_define_class_under(rb_cPrism, "StringQuery", rb_cObject);
|
1173
1237
|
|
1174
1238
|
// Intern all of the IDs eagerly that we support so that we don't have to do
|
1175
1239
|
// it every time we parse.
|
@@ -1211,6 +1275,10 @@ Init_prism(void) {
|
|
1211
1275
|
rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1);
|
1212
1276
|
#endif
|
1213
1277
|
|
1278
|
+
rb_define_singleton_method(rb_cPrismStringQuery, "local?", string_query_local_p, 1);
|
1279
|
+
rb_define_singleton_method(rb_cPrismStringQuery, "constant?", string_query_constant_p, 1);
|
1280
|
+
rb_define_singleton_method(rb_cPrismStringQuery, "method_name?", string_query_method_name_p, 1);
|
1281
|
+
|
1214
1282
|
// Next, initialize the other APIs.
|
1215
1283
|
Init_prism_api_node();
|
1216
1284
|
Init_prism_pack();
|