Skip to content

Commit b89000b

Browse files
authored
Merge pull request #3397 from ruby/freeze-ast
Freeze AST option
2 parents a0571d9 + 8ab2532 commit b89000b

File tree

18 files changed

+789
-390
lines changed

18 files changed

+789
-390
lines changed

docs/ruby_api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The full API is documented below.
2323
* `Prism.parse_stream(io)` - parse the syntax tree corresponding to the source that is read out of the given IO object using the `#gets` method and return it within a parse result
2424
* `Prism.parse_lex(source)` - parse the syntax tree corresponding to the given source string and return it within a parse result, along with the tokens
2525
* `Prism.parse_lex_file(filepath)` - parse the syntax tree corresponding to the given source file and return it within a parse result, along with the tokens
26-
* `Prism.load(source, serialized)` - load the serialized syntax tree using the source as a reference into a syntax tree
26+
* `Prism.load(source, serialized, freeze = false)` - load the serialized syntax tree using the source as a reference into a syntax tree
2727
* `Prism.parse_comments(source)` - parse the comments corresponding to the given source string and return them
2828
* `Prism.parse_file_comments(source)` - parse the comments corresponding to the given source file and return them
2929
* `Prism.parse_success?(source)` - parse the syntax tree corresponding to the given source string and return true if it was parsed without errors

ext/prism/extension.c

Lines changed: 171 additions & 102 deletions
Large diffs are not rendered by default.

ext/prism/extension.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
#include <ruby/encoding.h>
88
#include "prism.h"
99

10-
VALUE pm_source_new(const pm_parser_t *parser, rb_encoding *encoding);
11-
VALUE pm_token_new(const pm_parser_t *parser, const pm_token_t *token, rb_encoding *encoding, VALUE source);
12-
VALUE pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encoding, VALUE source);
10+
VALUE pm_source_new(const pm_parser_t *parser, rb_encoding *encoding, bool freeze);
11+
VALUE pm_token_new(const pm_parser_t *parser, const pm_token_t *token, rb_encoding *encoding, VALUE source, bool freeze);
12+
VALUE pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encoding, VALUE source, bool freeze);
1313
VALUE pm_integer_new(const pm_integer_t *integer);
1414

1515
void Init_prism_api_node(void);

include/prism/options.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ typedef struct pm_options {
160160
* inside another script.
161161
*/
162162
bool partial_script;
163+
164+
/**
165+
* Whether or not the parser should freeze the nodes that it creates. This
166+
* makes it possible to have a deeply frozen AST that is safe to share
167+
* between concurrency primitives.
168+
*/
169+
bool freeze;
163170
} pm_options_t;
164171

165172
/**
@@ -285,6 +292,14 @@ PRISM_EXPORTED_FUNCTION void pm_options_main_script_set(pm_options_t *options, b
285292
*/
286293
PRISM_EXPORTED_FUNCTION void pm_options_partial_script_set(pm_options_t *options, bool partial_script);
287294

295+
/**
296+
* Set the freeze option on the given options struct.
297+
*
298+
* @param options The options struct to set the freeze value on.
299+
* @param freeze The freeze value to set.
300+
*/
301+
PRISM_EXPORTED_FUNCTION void pm_options_freeze_set(pm_options_t *options, bool freeze);
302+
288303
/**
289304
* Allocate and zero out the scopes array on the given options struct.
290305
*
@@ -355,6 +370,7 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options);
355370
* | `1` | encoding locked |
356371
* | `1` | main script |
357372
* | `1` | partial script |
373+
* | `1` | freeze |
358374
* | `4` | the number of scopes |
359375
* | ... | the scopes |
360376
*

java/org/prism/ParsingOptions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boole
8282
// partialScript
8383
output.write(partialScript ? 1 : 0);
8484

85+
// freeze
86+
output.write(0);
87+
8588
// scopes
8689

8790
// number of scopes

javascript/src/parsePrism.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ function dumpOptions(options) {
122122
template.push("C");
123123
values.push(dumpBooleanOption(options.partial_script));
124124

125+
template.push("C");
126+
values.push(0);
127+
125128
template.push("L");
126129
if (options.scopes) {
127130
const scopes = options.scopes;

lib/prism.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ def self.lex_ripper(source)
5959
end
6060

6161
# :call-seq:
62-
# Prism::load(source, serialized) -> ParseResult
62+
# Prism::load(source, serialized, freeze) -> ParseResult
6363
#
6464
# Load the serialized AST using the source as a reference into a tree.
65-
def self.load(source, serialized)
66-
Serialize.load(source, serialized)
65+
def self.load(source, serialized, freeze = false)
66+
Serialize.load_parse(source, serialized, freeze)
6767
end
6868
end
6969

lib/prism/ffi.rb

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ module LibRubyParser # :nodoc:
1515
# must align with the build shared library from make/rake.
1616
libprism_in_build = File.expand_path("../../build/libprism.#{RbConfig::CONFIG["SOEXT"]}", __dir__)
1717
libprism_in_libdir = "#{RbConfig::CONFIG["libdir"]}/prism/libprism.#{RbConfig::CONFIG["SOEXT"]}"
18-
if File.exist? libprism_in_build
18+
19+
if File.exist?(libprism_in_build)
1920
INCLUDE_DIR = File.expand_path("../../include", __dir__)
2021
ffi_lib libprism_in_build
2122
else
@@ -279,7 +280,7 @@ def parse_stream(stream, **options)
279280
# access to the IO object already through the closure of the lambda, we
280281
# can pass a null pointer here and not worry.
281282
LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options))
282-
Prism.load(source, buffer.read)
283+
Prism.load(source, buffer.read, options.fetch(:freeze, false))
283284
end
284285
end
285286

@@ -354,50 +355,37 @@ def profile_file(filepath, **options)
354355
def dump_common(string, options) # :nodoc:
355356
LibRubyParser::PrismBuffer.with do |buffer|
356357
LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options))
357-
buffer.read
358+
359+
dumped = buffer.read
360+
dumped.freeze if options.fetch(:freeze, false)
361+
362+
dumped
358363
end
359364
end
360365

361366
def lex_common(string, code, options) # :nodoc:
362-
serialized = LibRubyParser::PrismBuffer.with do |buffer|
367+
LibRubyParser::PrismBuffer.with do |buffer|
363368
LibRubyParser.pm_serialize_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
364-
buffer.read
369+
Serialize.load_lex(code, buffer.read, options.fetch(:freeze, false))
365370
end
366-
367-
Serialize.load_tokens(Source.for(code), serialized)
368371
end
369372

370373
def parse_common(string, code, options) # :nodoc:
371374
serialized = dump_common(string, options)
372-
Prism.load(code, serialized)
375+
Serialize.load_parse(code, serialized, options.fetch(:freeze, false))
373376
end
374377

375378
def parse_comments_common(string, code, options) # :nodoc:
376379
LibRubyParser::PrismBuffer.with do |buffer|
377380
LibRubyParser.pm_serialize_parse_comments(buffer.pointer, string.pointer, string.length, dump_options(options))
378-
379-
source = Source.for(code)
380-
loader = Serialize::Loader.new(source, buffer.read)
381-
382-
loader.load_header
383-
loader.load_encoding
384-
loader.load_start_line
385-
loader.load_comments
381+
Serialize.load_parse_comments(code, buffer.read, options.fetch(:freeze, false))
386382
end
387383
end
388384

389385
def parse_lex_common(string, code, options) # :nodoc:
390386
LibRubyParser::PrismBuffer.with do |buffer|
391387
LibRubyParser.pm_serialize_parse_lex(buffer.pointer, string.pointer, string.length, dump_options(options))
392-
393-
source = Source.for(code)
394-
loader = Serialize::Loader.new(source, buffer.read)
395-
396-
tokens = loader.load_tokens
397-
node, comments, magic_comments, data_loc, errors, warnings = loader.load_nodes
398-
tokens.each { |token,| token.value.force_encoding(loader.encoding) }
399-
400-
ParseLexResult.new([node, tokens], comments, magic_comments, data_loc, errors, warnings, source)
388+
Serialize.load_parse_lex(code, buffer.read, options.fetch(:freeze, false))
401389
end
402390
end
403391

@@ -482,6 +470,9 @@ def dump_options(options)
482470
template << "C"
483471
values << (options.fetch(:partial_script, false) ? 1 : 0)
484472

473+
template << "C"
474+
values << (options.fetch(:freeze, false) ? 1 : 0)
475+
485476
template << "L"
486477
if (scopes = options[:scopes])
487478
values << scopes.length

lib/prism/parse_result.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ def initialize(source, start_line = 1, offsets = [])
4848
@offsets = offsets # set after parsing is done
4949
end
5050

51+
# Replace the value of start_line with the given value.
52+
def replace_start_line(start_line)
53+
@start_line = start_line
54+
end
55+
56+
# Replace the value of offsets with the given value.
57+
def replace_offsets(offsets)
58+
@offsets.replace(offsets)
59+
end
60+
5161
# Returns the encoding of the source code, which is set by parameters to the
5262
# parser or by the encoding magic comment.
5363
def encoding
@@ -132,6 +142,13 @@ def code_units_column(byte_offset, encoding)
132142
code_units_offset(byte_offset, encoding) - code_units_offset(line_start(byte_offset), encoding)
133143
end
134144

145+
# Freeze this object and the objects it contains.
146+
def deep_freeze
147+
source.freeze
148+
offsets.freeze
149+
freeze
150+
end
151+
135152
private
136153

137154
# Binary search through the offsets to find the line number for the given
@@ -854,5 +871,12 @@ def inspect
854871
location
855872
super
856873
end
874+
875+
# Freeze this object and the objects it contains.
876+
def deep_freeze
877+
value.freeze
878+
location.freeze
879+
freeze
880+
end
857881
end
858882
end

0 commit comments

Comments
 (0)