ibex-runtime 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +337 -0
- data/lib/ibex/runtime/ast_data.rb +23 -0
- data/lib/ibex/runtime/cst/annotation.rb +16 -0
- data/lib/ibex/runtime/cst/cursor.rb +68 -0
- data/lib/ibex/runtime/cst/diff.rb +80 -0
- data/lib/ibex/runtime/cst/editing.rb +56 -0
- data/lib/ibex/runtime/cst/editor.rb +94 -0
- data/lib/ibex/runtime/cst/green/builder.rb +211 -0
- data/lib/ibex/runtime/cst/green/cache.rb +150 -0
- data/lib/ibex/runtime/cst/green/node.rb +78 -0
- data/lib/ibex/runtime/cst/green/token.rb +91 -0
- data/lib/ibex/runtime/cst/green/trivia.rb +36 -0
- data/lib/ibex/runtime/cst/incremental/blender.rb +233 -0
- data/lib/ibex/runtime/cst/incremental/lexed_syntax.rb +28 -0
- data/lib/ibex/runtime/cst/incremental/parse_memo.rb +95 -0
- data/lib/ibex/runtime/cst/incremental/relexer.rb +134 -0
- data/lib/ibex/runtime/cst/incremental/session.rb +229 -0
- data/lib/ibex/runtime/cst/incremental/token_memo.rb +66 -0
- data/lib/ibex/runtime/cst/kind.rb +133 -0
- data/lib/ibex/runtime/cst/parse_result.rb +41 -0
- data/lib/ibex/runtime/cst/rewriter.rb +75 -0
- data/lib/ibex/runtime/cst/serialize.rb +127 -0
- data/lib/ibex/runtime/cst/serialized_tree.rb +44 -0
- data/lib/ibex/runtime/cst/source_text.rb +112 -0
- data/lib/ibex/runtime/cst/syntax_node.rb +432 -0
- data/lib/ibex/runtime/cst/syntax_token.rb +160 -0
- data/lib/ibex/runtime/cst/text_edit.rb +77 -0
- data/lib/ibex/runtime/cst/typed_node.rb +87 -0
- data/lib/ibex/runtime/cst/validator.rb +428 -0
- data/lib/ibex/runtime/cst.rb +38 -0
- data/lib/ibex/runtime/event.rb +44 -0
- data/lib/ibex/runtime/event_jsonl_tracer.rb +37 -0
- data/lib/ibex/runtime/event_sanitizer.rb +273 -0
- data/lib/ibex/runtime/generated_lexer.rb +514 -0
- data/lib/ibex/runtime/jsonl_tracer.rb +75 -0
- data/lib/ibex/runtime/lexer_input.rb +107 -0
- data/lib/ibex/runtime/location_span.rb +114 -0
- data/lib/ibex/runtime/observation.rb +82 -0
- data/lib/ibex/runtime/parser.rb +3027 -0
- data/lib/ibex/runtime/parser_sync_recovery.rb +108 -0
- data/lib/ibex/runtime/repair.rb +126 -0
- data/lib/ibex/runtime/repair_priority_queue.rb +60 -0
- data/lib/ibex/runtime/repair_search.rb +318 -0
- data/lib/ibex/runtime/resource_limits.rb +64 -0
- data/lib/ibex/runtime/table_format.rb +11 -0
- data/lib/ibex/runtime/version.rb +7 -0
- data/lib/ibex/runtime.rb +27 -0
- data/lib/ibex/tables/compact.rb +186 -0
- data/lib/ibex/tables/compact_actions.rb +135 -0
- data/lib/ibex/tables/compact_productions.rb +167 -0
- data/sig/ibex/runtime/ast_data.rbs +12 -0
- data/sig/ibex/runtime/cst/annotation.rbs +13 -0
- data/sig/ibex/runtime/cst/cursor.rbs +34 -0
- data/sig/ibex/runtime/cst/diff.rbs +29 -0
- data/sig/ibex/runtime/cst/editing.rbs +23 -0
- data/sig/ibex/runtime/cst/editor.rbs +43 -0
- data/sig/ibex/runtime/cst/green/builder.rbs +84 -0
- data/sig/ibex/runtime/cst/green/cache.rbs +60 -0
- data/sig/ibex/runtime/cst/green/node.rbs +49 -0
- data/sig/ibex/runtime/cst/green/token.rbs +59 -0
- data/sig/ibex/runtime/cst/green/trivia.rbs +29 -0
- data/sig/ibex/runtime/cst/incremental/blender.rbs +114 -0
- data/sig/ibex/runtime/cst/incremental/lexed_syntax.rbs +20 -0
- data/sig/ibex/runtime/cst/incremental/parse_memo.rbs +59 -0
- data/sig/ibex/runtime/cst/incremental/relexer.rbs +54 -0
- data/sig/ibex/runtime/cst/incremental/session.rbs +89 -0
- data/sig/ibex/runtime/cst/incremental/token_memo.rbs +31 -0
- data/sig/ibex/runtime/cst/kind.rbs +93 -0
- data/sig/ibex/runtime/cst/parse_result.rbs +35 -0
- data/sig/ibex/runtime/cst/rewriter.rbs +31 -0
- data/sig/ibex/runtime/cst/serialize.rbs +39 -0
- data/sig/ibex/runtime/cst/serialized_tree.rbs +34 -0
- data/sig/ibex/runtime/cst/source_text.rbs +51 -0
- data/sig/ibex/runtime/cst/syntax_node.rbs +195 -0
- data/sig/ibex/runtime/cst/syntax_token.rbs +112 -0
- data/sig/ibex/runtime/cst/text_edit.rbs +33 -0
- data/sig/ibex/runtime/cst/typed_node.rbs +50 -0
- data/sig/ibex/runtime/cst/validator.rbs +108 -0
- data/sig/ibex/runtime/cst.rbs +9 -0
- data/sig/ibex/runtime/event.rbs +26 -0
- data/sig/ibex/runtime/event_jsonl_tracer.rbs +25 -0
- data/sig/ibex/runtime/event_sanitizer.rbs +92 -0
- data/sig/ibex/runtime/generated_lexer.rbs +169 -0
- data/sig/ibex/runtime/jsonl_tracer.rbs +42 -0
- data/sig/ibex/runtime/lexer_input.rbs +44 -0
- data/sig/ibex/runtime/location_span.rbs +64 -0
- data/sig/ibex/runtime/observation.rbs +35 -0
- data/sig/ibex/runtime/parser.rbs +926 -0
- data/sig/ibex/runtime/parser_sync_recovery.rbs +60 -0
- data/sig/ibex/runtime/repair.rbs +92 -0
- data/sig/ibex/runtime/repair_priority_queue.rbs +27 -0
- data/sig/ibex/runtime/repair_search.rbs +114 -0
- data/sig/ibex/runtime/resource_limits.rbs +39 -0
- data/sig/ibex/runtime/table_format.rbs +11 -0
- data/sig/ibex/runtime/version.rbs +7 -0
- data/sig/ibex/runtime.rbs +6 -0
- data/sig/ibex/tables/compact.rbs +70 -0
- data/sig/ibex/tables/compact_actions.rbs +57 -0
- data/sig/ibex/tables/compact_productions.rbs +68 -0
- metadata +144 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ibex
|
|
5
|
+
module Runtime
|
|
6
|
+
module CST
|
|
7
|
+
# Validated CST serialization document and its reconstructed Red root.
|
|
8
|
+
class SerializedTree
|
|
9
|
+
attr_reader :grammar_digest #: String
|
|
10
|
+
attr_reader :table_format #: Integer
|
|
11
|
+
attr_reader :state_count #: Integer
|
|
12
|
+
attr_reader :production_count #: Integer
|
|
13
|
+
attr_reader :trivia_policy #: Symbol
|
|
14
|
+
attr_reader :kinds #: Kind
|
|
15
|
+
attr_reader :green_root #: GreenNode
|
|
16
|
+
attr_reader :memo #: ParseMemo?
|
|
17
|
+
|
|
18
|
+
# @rbs (grammar_digest: String, table_format: Integer, state_count: Integer,
|
|
19
|
+
# production_count: Integer, trivia_policy: Symbol, kinds: Kind, green_root: GreenNode,
|
|
20
|
+
# ?memo: ParseMemo?) -> void
|
|
21
|
+
def initialize(grammar_digest:, table_format:, state_count:, production_count:, trivia_policy:, kinds:,
|
|
22
|
+
green_root:, memo: nil)
|
|
23
|
+
@grammar_digest = grammar_digest.dup.freeze
|
|
24
|
+
@table_format = table_format
|
|
25
|
+
@state_count = state_count
|
|
26
|
+
@production_count = production_count
|
|
27
|
+
@trivia_policy = trivia_policy
|
|
28
|
+
@kinds = kinds
|
|
29
|
+
@green_root = green_root
|
|
30
|
+
@memo = memo
|
|
31
|
+
freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @rbs () -> SyntaxNode
|
|
35
|
+
def syntax_root
|
|
36
|
+
source = SourceText.new(@green_root.to_source)
|
|
37
|
+
SyntaxNode.new(
|
|
38
|
+
green: @green_root, kinds: @kinds, trivia_policy: @trivia_policy, source_text: source
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ibex
|
|
5
|
+
module Runtime
|
|
6
|
+
module CST
|
|
7
|
+
# Immutable byte-oriented source with lazy line/column conversion.
|
|
8
|
+
class SourceText
|
|
9
|
+
# @rbs! type position = [Integer, Integer]
|
|
10
|
+
|
|
11
|
+
attr_reader :text #: String
|
|
12
|
+
attr_reader :file #: String?
|
|
13
|
+
|
|
14
|
+
# @rbs (String text, ?file: String?) -> void
|
|
15
|
+
def initialize(text, file: nil)
|
|
16
|
+
@text = text.encoding == Encoding::BINARY && text.frozen? ? text : text.b.freeze
|
|
17
|
+
@file = file&.then { |value| value.frozen? ? value : value.dup.freeze }
|
|
18
|
+
@line_starts = build_line_starts.freeze #: Array[Integer]
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @rbs () -> Integer
|
|
23
|
+
def bytesize = @text.bytesize
|
|
24
|
+
|
|
25
|
+
# Apply non-overlapping byte edits expressed against this source.
|
|
26
|
+
# @rbs (Array[TextEdit] edits) -> SourceText
|
|
27
|
+
def apply(edits)
|
|
28
|
+
ordered = TextEdit.normalize(edits)
|
|
29
|
+
output = String.new(encoding: Encoding::BINARY)
|
|
30
|
+
cursor = 0
|
|
31
|
+
ordered.each do |edit|
|
|
32
|
+
raise ArgumentError, "text edits overlap" if edit.start < cursor
|
|
33
|
+
raise RangeError, "text edit exceeds source" if edit.range.end > @text.bytesize
|
|
34
|
+
|
|
35
|
+
output << (@text.byteslice(cursor, edit.start - cursor) || "".b)
|
|
36
|
+
output << edit.insert_text
|
|
37
|
+
cursor = edit.range.end
|
|
38
|
+
end
|
|
39
|
+
output << (@text.byteslice(cursor, @text.bytesize - cursor) || "".b)
|
|
40
|
+
SourceText.new(output, file: @file)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Return one-based line and Unicode-scalar column for a byte offset.
|
|
44
|
+
# Invalid UTF-8 bytes count as one replacement scalar each.
|
|
45
|
+
# @rbs (Integer offset) -> position
|
|
46
|
+
def position(offset)
|
|
47
|
+
validate_offset(offset)
|
|
48
|
+
line_index = @line_starts.bsearch_index { |start| start > offset }
|
|
49
|
+
line_index = line_index ? line_index - 1 : @line_starts.length - 1
|
|
50
|
+
line_start = @line_starts.fetch(line_index)
|
|
51
|
+
prefix = @text.byteslice(line_start, offset - line_start) || "".b
|
|
52
|
+
value = [line_index + 1, unicode_scalar_length(prefix) + 1] #: position
|
|
53
|
+
value.freeze
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @rbs (Range[Integer] range) -> Ibex::Location
|
|
57
|
+
def location(range)
|
|
58
|
+
start_offset, end_offset = normalized_range(range)
|
|
59
|
+
line, column = position(start_offset)
|
|
60
|
+
end_line, end_column = position(end_offset)
|
|
61
|
+
Ibex::Location.new(
|
|
62
|
+
file: @file, line: line, column: column, end_line: end_line, end_column: end_column,
|
|
63
|
+
start_byte: start_offset, end_byte: end_offset, source_line: line_text(line)
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @rbs (Integer line) -> String
|
|
68
|
+
def line_text(line)
|
|
69
|
+
raise ArgumentError, "line must be positive" unless line.positive?
|
|
70
|
+
|
|
71
|
+
start_offset = @line_starts.fetch(line - 1)
|
|
72
|
+
end_offset = @line_starts.fetch(line, @text.bytesize)
|
|
73
|
+
value = @text.byteslice(start_offset, end_offset - start_offset) || "".b
|
|
74
|
+
value.delete_suffix("\n".b).delete_suffix("\r".b)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# @rbs () -> Array[Integer]
|
|
80
|
+
def build_line_starts
|
|
81
|
+
starts = [0]
|
|
82
|
+
@text.bytes.each_with_index { |byte, index| starts << (index + 1) if byte == 10 }
|
|
83
|
+
starts
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @rbs (Integer offset) -> void
|
|
87
|
+
def validate_offset(offset)
|
|
88
|
+
return if offset.between?(0, @text.bytesize)
|
|
89
|
+
|
|
90
|
+
raise RangeError, "source offset #{offset} is outside 0..#{@text.bytesize}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @rbs (Range[Integer] range) -> position
|
|
94
|
+
def normalized_range(range)
|
|
95
|
+
start_offset = range.begin
|
|
96
|
+
end_offset = range.end + (range.exclude_end? ? 0 : 1)
|
|
97
|
+
validate_offset(start_offset)
|
|
98
|
+
validate_offset(end_offset)
|
|
99
|
+
raise RangeError, "range end precedes start" if end_offset < start_offset
|
|
100
|
+
|
|
101
|
+
value = [start_offset, end_offset] #: position
|
|
102
|
+
value.freeze
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @rbs (String bytes) -> Integer
|
|
106
|
+
def unicode_scalar_length(bytes)
|
|
107
|
+
bytes.dup.force_encoding(Encoding::UTF_8).scrub.length
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require_relative "source_text" unless defined?(Ibex::Runtime::CST::SourceText)
|
|
5
|
+
require_relative "syntax_token" unless defined?(Ibex::Runtime::CST::SyntaxToken)
|
|
6
|
+
|
|
7
|
+
module Ibex
|
|
8
|
+
module Runtime
|
|
9
|
+
module CST
|
|
10
|
+
# Lazy Red navigation facade for one Green node occurrence.
|
|
11
|
+
class SyntaxNode # rubocop:disable Metrics/ClassLength -- navigation and compatibility form one facade.
|
|
12
|
+
# @rbs! type element = SyntaxNode | SyntaxToken
|
|
13
|
+
# @rbs! include Enumerable[element]
|
|
14
|
+
# @rbs skip
|
|
15
|
+
include Enumerable
|
|
16
|
+
|
|
17
|
+
attr_reader :green #: GreenNode
|
|
18
|
+
attr_reader :parent #: SyntaxNode?
|
|
19
|
+
attr_reader :index #: Integer
|
|
20
|
+
attr_reader :offset #: Integer
|
|
21
|
+
attr_reader :kinds #: Kind
|
|
22
|
+
attr_reader :trivia_policy #: Symbol
|
|
23
|
+
attr_reader :source_text #: SourceText
|
|
24
|
+
|
|
25
|
+
# @rbs (green: GreenNode, kinds: Kind, ?parent: SyntaxNode?, ?index: Integer, ?offset: Integer,
|
|
26
|
+
# ?trivia_policy: Symbol, ?source_text: SourceText?) -> void
|
|
27
|
+
def initialize(green:, kinds:, parent: nil, index: 0, offset: 0, trivia_policy: :leading, source_text: nil)
|
|
28
|
+
@green = green
|
|
29
|
+
@kinds = kinds
|
|
30
|
+
@parent = parent
|
|
31
|
+
@index = index
|
|
32
|
+
@offset = offset
|
|
33
|
+
@trivia_policy = trivia_policy
|
|
34
|
+
@source_text = source_text || SourceText.new(root_source)
|
|
35
|
+
@children = Array.new(@green.children.length) #: Array[element?]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs () -> Integer
|
|
39
|
+
def kind = @green.kind
|
|
40
|
+
|
|
41
|
+
# @rbs () -> String
|
|
42
|
+
def kind_name = @kinds.name(kind)
|
|
43
|
+
|
|
44
|
+
# Compatibility name for the physical grammar symbol.
|
|
45
|
+
# @rbs () -> String
|
|
46
|
+
def symbol = @kinds.name(@kinds.nonterminal_of(kind))
|
|
47
|
+
|
|
48
|
+
# Green kinds replace occurrence-local production ids. Keep the legacy
|
|
49
|
+
# key and reader with the historical synthetic sentinel.
|
|
50
|
+
# @rbs () -> Integer
|
|
51
|
+
def production_id = -1
|
|
52
|
+
|
|
53
|
+
# @rbs () -> Array[element]
|
|
54
|
+
def children
|
|
55
|
+
@green.children.each_index.map { |child_index| child_at(child_index) }.freeze
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @rbs!
|
|
59
|
+
# def each: () -> Enumerator[element, self]
|
|
60
|
+
# | () { (element) -> void } -> self
|
|
61
|
+
# @rbs skip
|
|
62
|
+
def each(&block)
|
|
63
|
+
return enum_for(:each) unless block
|
|
64
|
+
|
|
65
|
+
children.each(&block)
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @rbs (Integer child_index) -> element
|
|
70
|
+
def child_at(child_index)
|
|
71
|
+
cached = @children.fetch(child_index)
|
|
72
|
+
return cached if cached
|
|
73
|
+
|
|
74
|
+
green_child = @green.children.fetch(child_index)
|
|
75
|
+
child_offset = offset_for(child_index)
|
|
76
|
+
value = if green_child.is_a?(GreenNode)
|
|
77
|
+
self.class.new(
|
|
78
|
+
green: green_child, kinds: @kinds, parent: self, index: child_index,
|
|
79
|
+
offset: child_offset, trivia_policy: @trivia_policy, source_text: @source_text
|
|
80
|
+
)
|
|
81
|
+
else
|
|
82
|
+
SyntaxToken.new(green: green_child, parent: self, index: child_index, offset: child_offset)
|
|
83
|
+
end
|
|
84
|
+
@children[child_index] = value
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @rbs () -> Array[SyntaxNode]
|
|
88
|
+
def child_nodes
|
|
89
|
+
nodes = [] #: Array[SyntaxNode]
|
|
90
|
+
children.each { |child| nodes << child if child.is_a?(SyntaxNode) }
|
|
91
|
+
nodes.freeze
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @rbs () -> Array[SyntaxToken]
|
|
95
|
+
def tokens
|
|
96
|
+
values = [] #: Array[SyntaxToken]
|
|
97
|
+
descendants.each { |element| values << element if element.is_a?(SyntaxToken) }
|
|
98
|
+
values.freeze
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @rbs () -> SyntaxToken?
|
|
102
|
+
def first_token = tokens.first
|
|
103
|
+
|
|
104
|
+
# @rbs () -> SyntaxToken?
|
|
105
|
+
def last_token = tokens.last
|
|
106
|
+
|
|
107
|
+
# @rbs () -> element?
|
|
108
|
+
def next_sibling
|
|
109
|
+
parent = @parent
|
|
110
|
+
return unless parent
|
|
111
|
+
|
|
112
|
+
parent.children[@index + 1]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @rbs () -> element?
|
|
116
|
+
def prev_sibling
|
|
117
|
+
parent = @parent
|
|
118
|
+
return unless parent
|
|
119
|
+
return if @index.zero?
|
|
120
|
+
|
|
121
|
+
parent.children[@index - 1]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @rbs () -> Enumerator[SyntaxNode, void]
|
|
125
|
+
def ancestors
|
|
126
|
+
Enumerator.new do |yielder|
|
|
127
|
+
ancestor = @parent
|
|
128
|
+
while ancestor
|
|
129
|
+
yielder << ancestor
|
|
130
|
+
ancestor = ancestor.parent
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @rbs () -> Enumerator[element, void]
|
|
136
|
+
def descendants
|
|
137
|
+
Enumerator.new do |yielder|
|
|
138
|
+
visit = lambda do |node|
|
|
139
|
+
node.children.each do |child|
|
|
140
|
+
yielder << child
|
|
141
|
+
visit.call(child) if child.is_a?(SyntaxNode)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
visit.call(self)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# @rbs () -> SyntaxNode
|
|
149
|
+
def root
|
|
150
|
+
parent = @parent
|
|
151
|
+
parent ? parent.root : self
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @rbs () -> Range[Integer]
|
|
155
|
+
def full_span
|
|
156
|
+
ensure_coordinates!
|
|
157
|
+
@offset...(@offset + @green.full_width)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# @rbs () -> Range[Integer]
|
|
161
|
+
def span
|
|
162
|
+
ensure_coordinates!
|
|
163
|
+
(@offset + @green.leading_width)...(@offset + @green.full_width - @green.trailing_width)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# @rbs () -> Ibex::Location
|
|
167
|
+
def location = @source_text.location(span)
|
|
168
|
+
|
|
169
|
+
# @rbs () -> String
|
|
170
|
+
def full_text = @green.to_source
|
|
171
|
+
alias to_source full_text
|
|
172
|
+
|
|
173
|
+
# @rbs () -> String
|
|
174
|
+
def text
|
|
175
|
+
start_offset = @green.leading_width
|
|
176
|
+
width = @green.full_width - @green.leading_width - @green.trailing_width
|
|
177
|
+
full_text.byteslice(start_offset, width) || "".b
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Compatibility view of file-tail trivia. The Red/Green layout owns it
|
|
181
|
+
# on EOF rather than on the start node.
|
|
182
|
+
# @rbs () -> Array[GreenTrivia]
|
|
183
|
+
def trailing_trivia
|
|
184
|
+
eof = compatibility_eof
|
|
185
|
+
return (eof.green.leading + eof.green.trailing).freeze if eof
|
|
186
|
+
|
|
187
|
+
token = last_token
|
|
188
|
+
token ? token.green.trailing : []
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @rbs () -> bool
|
|
192
|
+
def error? = @kinds.error?(kind)
|
|
193
|
+
|
|
194
|
+
# @rbs () -> bool
|
|
195
|
+
def missing? = false
|
|
196
|
+
|
|
197
|
+
# @rbs () -> bool
|
|
198
|
+
def contains_error? = @green.flags.anybits?(Flags::CONTAINS_ERROR)
|
|
199
|
+
|
|
200
|
+
# @rbs () -> bool
|
|
201
|
+
def incomplete_input? = @green.flags.anybits?(Flags::INCOMPLETE_INPUT)
|
|
202
|
+
|
|
203
|
+
# @rbs (GreenNode | GreenToken | SyntaxNode | SyntaxToken replacement) -> SyntaxNode
|
|
204
|
+
def replace_with(replacement) = Editing.replace(self, replacement)
|
|
205
|
+
|
|
206
|
+
# @rbs (Integer child_index, GreenNode | GreenToken | SyntaxNode | SyntaxToken child) -> SyntaxNode
|
|
207
|
+
def with_child(child_index, child)
|
|
208
|
+
children = @green.children.dup
|
|
209
|
+
children.fetch(child_index)
|
|
210
|
+
children[child_index] = Editing.green_element(child)
|
|
211
|
+
replace_with(
|
|
212
|
+
GreenNode.new(
|
|
213
|
+
kind: @green.kind, children: children,
|
|
214
|
+
flags: @green.intrinsic_flags, annotations: @green.annotations
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# @rbs (Integer child_index, GreenNode | GreenToken | SyntaxNode | SyntaxToken child) -> SyntaxNode
|
|
220
|
+
def insert_child(child_index, child)
|
|
221
|
+
raise IndexError, "child index #{child_index} is outside 0..#{@green.children.length}" unless
|
|
222
|
+
child_index.between?(0, @green.children.length)
|
|
223
|
+
|
|
224
|
+
children = @green.children.dup
|
|
225
|
+
children.insert(child_index, Editing.green_element(child))
|
|
226
|
+
replace_with(
|
|
227
|
+
GreenNode.new(
|
|
228
|
+
kind: @green.kind, children: children,
|
|
229
|
+
flags: @green.intrinsic_flags, annotations: @green.annotations
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# @rbs (Integer child_index) -> SyntaxNode
|
|
235
|
+
def remove_child(child_index)
|
|
236
|
+
children = @green.children.dup
|
|
237
|
+
children.fetch(child_index)
|
|
238
|
+
children.delete_at(child_index)
|
|
239
|
+
replace_with(
|
|
240
|
+
GreenNode.new(
|
|
241
|
+
kind: @green.kind, children: children,
|
|
242
|
+
flags: @green.intrinsic_flags, annotations: @green.annotations
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# @rbs (SyntaxAnnotation annotation) -> SyntaxNode
|
|
248
|
+
def annotate(annotation)
|
|
249
|
+
raise TypeError, "annotation must be a SyntaxAnnotation" unless annotation.is_a?(SyntaxAnnotation)
|
|
250
|
+
return root if @green.annotations.include?(annotation)
|
|
251
|
+
|
|
252
|
+
replace_with(
|
|
253
|
+
GreenNode.new(
|
|
254
|
+
kind: @green.kind, children: @green.children,
|
|
255
|
+
flags: @green.intrinsic_flags, annotations: @green.annotations + [annotation]
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# @rbs (SyntaxAnnotation annotation) -> Enumerator[SyntaxNode, void]
|
|
261
|
+
def annotated(annotation)
|
|
262
|
+
Enumerator.new do |yielder|
|
|
263
|
+
visit = lambda do |node|
|
|
264
|
+
yielder << node if node.green.annotations.include?(annotation)
|
|
265
|
+
node.child_nodes.each { |child| visit.call(child) }
|
|
266
|
+
end
|
|
267
|
+
visit.call(self)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Find the full-span-owning token for a byte offset.
|
|
272
|
+
# @rbs (Integer source_offset) -> SyntaxToken?
|
|
273
|
+
def token_at(source_offset)
|
|
274
|
+
ensure_coordinates!
|
|
275
|
+
return unless source_offset >= @offset && source_offset < @offset + @green.full_width
|
|
276
|
+
|
|
277
|
+
node = self
|
|
278
|
+
loop do
|
|
279
|
+
child = node.child_covering_offset(source_offset)
|
|
280
|
+
return unless child
|
|
281
|
+
return child if child.is_a?(SyntaxToken)
|
|
282
|
+
|
|
283
|
+
node = child
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Return the smallest syntax element whose full span covers a range.
|
|
288
|
+
# @rbs (Range[Integer] range) -> element?
|
|
289
|
+
def covering(range)
|
|
290
|
+
ensure_coordinates!
|
|
291
|
+
start_offset, end_offset = normalize_range(range)
|
|
292
|
+
return unless covers_offsets?(start_offset, end_offset)
|
|
293
|
+
|
|
294
|
+
node = self
|
|
295
|
+
loop do
|
|
296
|
+
child = node.children.find do |candidate|
|
|
297
|
+
candidate_start = candidate.offset
|
|
298
|
+
candidate_end = candidate.offset + candidate.green.full_width
|
|
299
|
+
candidate_start <= start_offset && candidate_end >= end_offset
|
|
300
|
+
end
|
|
301
|
+
return node unless child
|
|
302
|
+
return child if child.is_a?(SyntaxToken)
|
|
303
|
+
|
|
304
|
+
node = child
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# @rbs (kind: Integer | String | Symbol) -> Enumerator[element, void]
|
|
309
|
+
def find(kind:)
|
|
310
|
+
expected = kind.is_a?(Integer) ? kind : @kinds.fetch(kind)
|
|
311
|
+
Enumerator.new do |yielder|
|
|
312
|
+
descendants.each { |element| yielder << element if element.kind == expected }
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# @rbs () -> Enumerator[element, void]
|
|
317
|
+
# @rbs () { (element) -> void } -> self
|
|
318
|
+
def each_error(&block)
|
|
319
|
+
errors = Enumerator.new do |yielder|
|
|
320
|
+
descendants.each do |element|
|
|
321
|
+
yielder << element if element.error? || element.missing?
|
|
322
|
+
end
|
|
323
|
+
end #: Enumerator[element, void]
|
|
324
|
+
return errors unless block
|
|
325
|
+
|
|
326
|
+
errors.each(&block)
|
|
327
|
+
self
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# @rbs () { (Symbol, element) -> void } -> self
|
|
331
|
+
def walk(&block)
|
|
332
|
+
raise ArgumentError, "walk requires a block" unless block
|
|
333
|
+
|
|
334
|
+
visit = lambda do |element|
|
|
335
|
+
block.call(:enter, element)
|
|
336
|
+
element.children.each { |child| visit.call(child) } if element.is_a?(SyntaxNode)
|
|
337
|
+
block.call(:leave, element)
|
|
338
|
+
end
|
|
339
|
+
visit.call(self)
|
|
340
|
+
self
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# @rbs () -> Cursor
|
|
344
|
+
def cursor = Cursor.new(self)
|
|
345
|
+
|
|
346
|
+
# @rbs (SyntaxNode other) -> bool
|
|
347
|
+
def same_node?(other)
|
|
348
|
+
root.equal?(other.root) && @green.equal?(other.green) && @offset == other.offset
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# @rbs (untyped other) -> bool
|
|
352
|
+
def ==(other)
|
|
353
|
+
other.is_a?(SyntaxNode) && @green == other.green
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# @rbs () -> Array[element]
|
|
357
|
+
def deconstruct = children
|
|
358
|
+
|
|
359
|
+
# @rbs (Array[Symbol]?) -> Hash[Symbol, untyped]
|
|
360
|
+
def deconstruct_keys(_keys)
|
|
361
|
+
values = {
|
|
362
|
+
kind: :node, symbol: symbol, production_id: production_id, children: children,
|
|
363
|
+
location: location, trailing_trivia: trailing_trivia
|
|
364
|
+
} #: Hash[Symbol, untyped]
|
|
365
|
+
@kinds.fields(kind).each do |name, slot|
|
|
366
|
+
index = slot.is_a?(Hash) ? slot.fetch(:index) : slot
|
|
367
|
+
values[name.to_sym] = child_at(index)
|
|
368
|
+
end
|
|
369
|
+
values.freeze
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# @rbs () -> Hash[Symbol, untyped]
|
|
373
|
+
def to_h = deconstruct_keys(nil)
|
|
374
|
+
|
|
375
|
+
protected
|
|
376
|
+
|
|
377
|
+
# @rbs (Integer source_offset) -> element?
|
|
378
|
+
def child_covering_offset(source_offset)
|
|
379
|
+
children.find do |child|
|
|
380
|
+
source_offset >= child.offset && source_offset < child.offset + child.green.full_width
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
private
|
|
385
|
+
|
|
386
|
+
# @rbs () -> SyntaxToken?
|
|
387
|
+
def compatibility_eof
|
|
388
|
+
if kind_name == "source_file"
|
|
389
|
+
token = last_token
|
|
390
|
+
return token if token&.kind_name == "$eof"
|
|
391
|
+
end
|
|
392
|
+
container = @parent
|
|
393
|
+
return unless container
|
|
394
|
+
return unless container.kind_name == "source_file"
|
|
395
|
+
return unless @index.zero?
|
|
396
|
+
|
|
397
|
+
token = container.last_token
|
|
398
|
+
token if token&.kind_name == "$eof"
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# @rbs () -> String
|
|
402
|
+
def root_source
|
|
403
|
+
parent = @parent
|
|
404
|
+
parent ? parent.source_text.text : @green.to_source
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# @rbs (Integer child_index) -> Integer
|
|
408
|
+
def offset_for(child_index)
|
|
409
|
+
@offset + @green.children.first(child_index).sum(&:full_width)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# @rbs () -> void
|
|
413
|
+
def ensure_coordinates!
|
|
414
|
+
return unless @trivia_policy == :drop
|
|
415
|
+
|
|
416
|
+
raise TriviaDroppedError, "source coordinates are unavailable when CST trivia is dropped"
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# @rbs (Range[Integer] range) -> [Integer, Integer]
|
|
420
|
+
def normalize_range(range)
|
|
421
|
+
value = [range.begin, range.end + (range.exclude_end? ? 0 : 1)] #: [Integer, Integer]
|
|
422
|
+
value.freeze
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# @rbs (Integer start_offset, Integer end_offset) -> bool
|
|
426
|
+
def covers_offsets?(start_offset, end_offset)
|
|
427
|
+
start_offset >= @offset && end_offset <= @offset + @green.full_width && end_offset >= start_offset
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|