Class: Ibex::Frontend::SourceDocument

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/frontend/source_document.rb,
sig/ibex/frontend/source_document.rbs

Overview

Lossless grammar source plus its semantic parse and lexical concrete syntax tree.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source:, file:, tokens:, cst:, ast: nil) ⇒ SourceDocument

Returns a new instance of SourceDocument.

RBS:

  • (source: String, file: String, tokens: Array[Token], cst: CST::Document, ?ast: AST::Root | AST::Fragment | nil) -> void

Parameters:



106
107
108
109
110
111
112
113
114
115
# File 'lib/ibex/frontend/source_document.rb', line 106

def initialize(source:, file:, tokens:, cst:, ast: nil)
  @source = SourceEncoding.validated_utf8(source, file)
  @file = file.dup.freeze
  @tokens = tokens.dup.freeze
  @cst = cst
  @ast = ast
  @line_starts = build_line_starts.freeze #: Array[Integer]
  validate_render
  freeze
end

Instance Attribute Details

#astAST::Root, ... (readonly)

Signature:

  • AST::Root | AST::Fragment | nil

Returns:



102
103
104
# File 'lib/ibex/frontend/source_document.rb', line 102

def ast
  @ast
end

#cstCST::Document (readonly)

Signature:

  • CST::Document

Returns:



101
102
103
# File 'lib/ibex/frontend/source_document.rb', line 101

def cst
  @cst
end

#fileString (readonly)

Signature:

  • String

Returns:

  • (String)


99
100
101
# File 'lib/ibex/frontend/source_document.rb', line 99

def file
  @file
end

#sourceString (readonly)

Signature:

  • String

Returns:

  • (String)


98
99
100
# File 'lib/ibex/frontend/source_document.rb', line 98

def source
  @source
end

#tokensArray[Token] (readonly)

Signature:

  • Array[Token]

Returns:



100
101
102
# File 'lib/ibex/frontend/source_document.rb', line 100

def tokens
  @tokens
end

Instance Method Details

#build_line_startsArray[Integer]

RBS:

  • () -> Array[Integer]

Returns:

  • (Array[Integer])


175
176
177
178
179
180
181
182
183
184
# File 'lib/ibex/frontend/source_document.rb', line 175

def build_line_starts
  starts = [0]
  offset = 0
  binary_source = source.b
  while (newline = binary_source.index("\n", offset))
    offset = newline + 1
    starts << offset
  end
  starts
end

#byte_offset_at(line, column) ⇒ Integer

RBS:

  • (Integer line, Integer column) -> Integer

Parameters:

  • line (Integer)
  • column (Integer)

Returns:

  • (Integer)


141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/ibex/frontend/source_document.rb', line 141

def byte_offset_at(line, column)
  raise ArgumentError, "line and column must be positive" unless line.positive? && column.positive?

  line_start = @line_starts[line - 1]
  raise ArgumentError, "line is outside the source" unless line_start

  line_end = @line_starts[line] || source.bytesize
  line_text = source.byteslice(line_start, line_end - line_start) || ""
  line_text = line_text.delete_suffix("\n")
  character_count = column - 1
  raise ArgumentError, "column is outside the source line" if character_count > line_text.length

  line_start + line_text.each_char.take(character_count).join.bytesize
end

#full_source_coverage?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


194
195
196
197
198
199
200
201
202
203
# File 'lib/ibex/frontend/source_document.rb', line 194

def full_source_coverage?
  segments = cst.segments
  return source.empty? if segments.empty?

  first = segments.first
  last = segments.last
  return false unless first && last

  first.span.start_byte.zero? && last.span.end_byte == source.bytesize
end

#position_at(byte_offset) ⇒ SourcePosition

RBS:

  • (Integer byte_offset) -> SourcePosition

Parameters:

  • byte_offset (Integer)

Returns:



129
130
131
132
133
134
135
136
137
138
# File 'lib/ibex/frontend/source_document.rb', line 129

def position_at(byte_offset)
  validate_byte_offset(byte_offset)
  prefix = source.byteslice(0, byte_offset) || ""
  raise ArgumentError, "byte offset must be on a UTF-8 character boundary" unless prefix.valid_encoding?

  line_index = (@line_starts.bsearch_index { |start| start > byte_offset } || @line_starts.length) - 1
  line_start = @line_starts.fetch(line_index)
  line_prefix = source.byteslice(line_start, byte_offset - line_start) || ""
  SourcePosition.new(byte_offset: byte_offset, line: line_index + 1, column: line_prefix.length + 1)
end

#renderString

RBS:

  • () -> String

Returns:

  • (String)


118
119
120
# File 'lib/ibex/frontend/source_document.rb', line 118

def render
  cst.render
end

#segment_slices_match?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


206
207
208
209
210
211
# File 'lib/ibex/frontend/source_document.rb', line 206

def segment_slices_match?
  cst.segments.all? do |segment|
    segment.span.file == file &&
      source.byteslice(segment.span.start_byte, segment.span.length) == segment.text
  end
end

#slice(span) ⇒ String

RBS:

  • (SourceSpan span) -> String

Parameters:

Returns:

  • (String)


123
124
125
126
# File 'lib/ibex/frontend/source_document.rb', line 123

def slice(span)
  validate_span(span)
  source.byteslice(span.start_byte, span.length) || ""
end

#span(start_byte, end_byte) ⇒ SourceSpan

RBS:

  • (Integer start_byte, Integer end_byte) -> SourceSpan

Parameters:

  • start_byte (Integer)
  • end_byte (Integer)

Returns:



157
158
159
# File 'lib/ibex/frontend/source_document.rb', line 157

def span(start_byte, end_byte)
  SourceSpan.new(file: file, start: position_at(start_byte), finish: position_at(end_byte))
end

#token_for(segment) ⇒ Token?

RBS:

  • (Segment segment) -> Token?

Parameters:

Returns:



162
163
164
165
# File 'lib/ibex/frontend/source_document.rb', line 162

def token_for(segment)
  index = segment.token_index
  index && tokens[index]
end

#validate_byte_offset(byte_offset) ⇒ void

This method returns an undefined value.

RBS:

  • (Integer byte_offset) -> void

Parameters:

  • byte_offset (Integer)


214
215
216
217
218
# File 'lib/ibex/frontend/source_document.rb', line 214

def validate_byte_offset(byte_offset)
  return if byte_offset.between?(0, source.bytesize)

  raise ArgumentError, "byte offset is outside the source"
end

#validate_rendervoid

This method returns an undefined value.

RBS:

  • () -> void



187
188
189
190
191
# File 'lib/ibex/frontend/source_document.rb', line 187

def validate_render
  return if full_source_coverage? && segment_slices_match? && render == source

  raise ArgumentError, "concrete syntax tree does not reproduce its source"
end

#validate_span(span) ⇒ void

This method returns an undefined value.

RBS:

  • (SourceSpan span) -> void

Parameters:



221
222
223
224
225
226
# File 'lib/ibex/frontend/source_document.rb', line 221

def validate_span(span)
  raise ArgumentError, "span belongs to another source" unless span.file == file

  validate_byte_offset(span.start_byte)
  validate_byte_offset(span.end_byte)
end

#with_ast(ast) ⇒ SourceDocument

RBS:

  • (AST::Root | AST::Fragment ast) -> SourceDocument

Parameters:

Returns:



168
169
170
# File 'lib/ibex/frontend/source_document.rb', line 168

def with_ast(ast)
  self.class.new(source: source, file: file, tokens: tokens, cst: cst, ast: ast)
end