prism 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
- 4. The `Makefile` compiles both `libprism.a` and `libprism.{so,dylib,dll}` from the `src/**/*.c` and `include/**/*.h` files
20
- 5. 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`), which uses `libprism.a`
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 JRuby and TruffleRuby, no C extension is built, so instead of the last step,
43
- there is Ruby code using FFI which uses `libprism.{so,dylib,dll}`
44
- to implement the same methods as the C extension, but using serialization instead of many native calls/accesses
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/master/src/main/c/prism_bindings/src/prism_bindings.c)
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
@@ -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[8];
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[5] = cast->closing_loc.start == NULL ? Qnil : pm_location_new(parser, cast->closing_loc.start, cast->closing_loc.end);
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[6] = rb_ary_pop(value_stack);
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[7] = rb_ary_pop(value_stack);
6110
+ argv[8] = rb_ary_pop(value_stack);
6107
6111
 
6108
- rb_ary_push(value_stack, rb_class_new_instance(8, argv, rb_cPrismUntilNode));
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[8];
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[5] = cast->closing_loc.start == NULL ? Qnil : pm_location_new(parser, cast->closing_loc.start, cast->closing_loc.end);
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[6] = rb_ary_pop(value_stack);
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[7] = rb_ary_pop(value_stack);
6189
+ argv[8] = rb_ary_pop(value_stack);
6182
6190
 
6183
- rb_ary_push(value_stack, rb_class_new_instance(8, argv, rb_cPrismWhileNode));
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
- # We need to link against the libprism.a archive, which is built by the
131
- # project's `Makefile`. We'll build it if it doesn't exist yet, and then add it
132
- # to `mkmf`'s list of local libraries.
133
- archive_target = "build/libprism.a"
134
- archive_path = File.expand_path("../../#{archive_target}", __dir__)
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
- make(env, archive_target) unless File.exist?(archive_path)
137
- $LOCAL_LIBS << " #{archive_path}"
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
@@ -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();
@@ -1,7 +1,7 @@
1
1
  #ifndef PRISM_EXT_NODE_H
2
2
  #define PRISM_EXT_NODE_H
3
3
 
4
- #define EXPECTED_PRISM_VERSION "1.2.0"
4
+ #define EXPECTED_PRISM_VERSION "1.3.0"
5
5
 
6
6
  #include <ruby.h>
7
7
  #include <ruby/encoding.h>