Class: Ibex::Frontend::SourceCursor

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

Overview

Advances through source text while maintaining one-based coordinates.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, file) ⇒ SourceCursor

Returns a new instance of SourceCursor.

RBS:

  • (String source, String file) -> void

Parameters:

  • source (String)
  • file (String)


51
52
53
54
55
56
57
58
59
60
# File 'lib/ibex/frontend/source_cursor.rb', line 51

def initialize(source, file)
  @source = SourceEncoding.validated_utf8(source, file)
  @file = file.dup.freeze
  @ascii_only = @source.ascii_only?
  @character_length = @ascii_only ? @source.bytesize : @source.length
  @index = 0
  @byte_offset = 0
  @line = 1
  @column = 1
end

Instance Attribute Details

#byte_offsetInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


46
47
48
# File 'lib/ibex/frontend/source_cursor.rb', line 46

def byte_offset
  @byte_offset
end

#columnInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


48
49
50
# File 'lib/ibex/frontend/source_cursor.rb', line 48

def column
  @column
end

#fileString (readonly)

Signature:

  • String

Returns:

  • (String)


44
45
46
# File 'lib/ibex/frontend/source_cursor.rb', line 44

def file
  @file
end

#indexInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


45
46
47
# File 'lib/ibex/frontend/source_cursor.rb', line 45

def index
  @index
end

#lineInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


47
48
49
# File 'lib/ibex/frontend/source_cursor.rb', line 47

def line
  @line
end

#sourceString (readonly)

Signature:

  • String

Returns:

  • (String)


43
44
45
# File 'lib/ibex/frontend/source_cursor.rb', line 43

def source
  @source
end

Instance Method Details

#advance(count = 1) ⇒ void

This method returns an undefined value.

RBS:

  • (?Integer count) -> void

Parameters:

  • count (Integer) (defaults to: 1)


111
112
113
114
115
# File 'lib/ibex/frontend/source_cursor.rb', line 111

def advance(count = 1)
  return advance_utf8(count) unless @ascii_only

  advance_ascii(count)
end

#advance_ascii(count) ⇒ void

This method returns an undefined value.

RBS:

  • (Integer count) -> void

Parameters:

  • count (Integer)


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/ibex/frontend/source_cursor.rb', line 120

def advance_ascii(count)
  count.times do
    break if @index >= @character_length

    newline = @source.getbyte(@index) == 10
    @index += 1
    @byte_offset = @index
    if newline
      @line += 1
      @column = 1
    else
      @column += 1
    end
  end
end

#advance_utf8(count) ⇒ void

This method returns an undefined value.

RBS:

  • (Integer count) -> void

Parameters:

  • count (Integer)


137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/ibex/frontend/source_cursor.rb', line 137

def advance_utf8(count)
  count.times do
    break if @index >= @character_length

    leading_byte = @source.getbyte(@byte_offset)
    raise Ibex::Error, "#{@file}: input must be valid UTF-8" unless leading_byte

    @index += 1
    @byte_offset += if leading_byte < 0x80
                      1
                    elsif leading_byte < 0xE0
                      2
                    elsif leading_byte < 0xF0
                      3
                    else
                      4
                    end
    if leading_byte == 10
      @line += 1
      @column = 1
    else
      @column += 1
    end
  end
end

#byte_offset_for_character(character_index) ⇒ Integer

RBS:

  • (Integer character_index) -> Integer

Parameters:

  • character_index (Integer)

Returns:

  • (Integer)


164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/ibex/frontend/source_cursor.rb', line 164

def byte_offset_for_character(character_index)
  start_distance = character_index
  current_distance = (character_index - @index).abs
  finish_distance = @character_length - character_index

  if current_distance <= start_distance && current_distance <= finish_distance
    walk_to_character(@byte_offset, @index, character_index)
  elsif start_distance <= finish_distance
    walk_to_character(0, 0, character_index)
  else
    walk_to_character(@source.bytesize, @character_length, character_index)
  end
end

#continuation_byte?(byte) ⇒ Boolean

RBS:

  • (Integer byte) -> bool

Parameters:

  • byte (Integer)

Returns:

  • (Boolean)


213
214
215
# File 'lib/ibex/frontend/source_cursor.rb', line 213

def continuation_byte?(byte)
  byte >= 0x80 && byte < 0xC0
end

#eof?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


63
64
65
# File 'lib/ibex/frontend/source_cursor.rb', line 63

def eof?
  @index >= @character_length
end

#locationLocation

RBS:

  • () -> Location

Returns:



96
97
98
# File 'lib/ibex/frontend/source_cursor.rb', line 96

def location
  Location.new(file: @file, line: @line, column: @column)
end

#peek(offset = 0) ⇒ String?

RBS:

  • (?Integer offset) -> String?

Parameters:

  • offset (Integer) (defaults to: 0)

Returns:

  • (String, nil)


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ibex/frontend/source_cursor.rb', line 68

def peek(offset = 0)
  character_index = @index + offset
  if @ascii_only
    return if character_index >= @character_length

    return @source.byteslice(character_index, 1)
  end

  character_index += @character_length if character_index.negative?
  return if character_index.negative? || character_index >= @character_length

  start_byte = if character_index == @index
                 @byte_offset
               elsif character_index == @index + 1
                 @byte_offset + utf8_character_width(@byte_offset)
               else
                 byte_offset_for_character(character_index)
               end
  @source.byteslice(start_byte, utf8_character_width(start_byte))
end

#positionSourcePosition

RBS:

  • () -> SourcePosition

Returns:



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

def position
  SourcePosition.new(byte_offset: @byte_offset, line: @line, column: @column)
end

#previous_character_byte_offset(byte_offset) ⇒ Integer

RBS:

  • (Integer byte_offset) -> Integer

Parameters:

  • byte_offset (Integer)

Returns:

  • (Integer)


192
193
194
195
196
197
198
# File 'lib/ibex/frontend/source_cursor.rb', line 192

def previous_character_byte_offset(byte_offset)
  byte_offset -= 1
  while (byte = @source.getbyte(byte_offset)) && continuation_byte?(byte)
    byte_offset -= 1
  end
  byte_offset
end

#restString

RBS:

  • () -> String

Returns:

  • (String)


90
91
92
93
# File 'lib/ibex/frontend/source_cursor.rb', line 90

def rest
  @source.byteslice(@byte_offset, @source.bytesize - @byte_offset) ||
    String.new(encoding: Encoding::UTF_8)
end

#span_from(start) ⇒ SourceSpan

RBS:

  • (SourcePosition start) -> SourceSpan

Parameters:

Returns:



106
107
108
# File 'lib/ibex/frontend/source_cursor.rb', line 106

def span_from(start)
  SourceSpan.new(file: @file, start: start, finish: position)
end

#utf8_character_width(byte_offset) ⇒ Integer

RBS:

  • (Integer byte_offset) -> Integer

Parameters:

  • byte_offset (Integer)

Returns:

  • (Integer)


201
202
203
204
205
206
207
208
209
210
# File 'lib/ibex/frontend/source_cursor.rb', line 201

def utf8_character_width(byte_offset)
  leading_byte = @source.getbyte(byte_offset)
  raise Ibex::Error, "#{@file}: input must be valid UTF-8" unless leading_byte

  return 1 if leading_byte < 0x80
  return 2 if leading_byte < 0xE0
  return 3 if leading_byte < 0xF0

  4
end

#walk_to_character(byte_offset, from_index, to_index) ⇒ Integer

RBS:

  • (Integer byte_offset, Integer from_index, Integer to_index) -> Integer

Parameters:

  • byte_offset (Integer)
  • from_index (Integer)
  • to_index (Integer)

Returns:

  • (Integer)


179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ibex/frontend/source_cursor.rb', line 179

def walk_to_character(byte_offset, from_index, to_index)
  while from_index < to_index
    byte_offset += utf8_character_width(byte_offset)
    from_index += 1
  end
  while from_index > to_index
    byte_offset = previous_character_byte_offset(byte_offset)
    from_index -= 1
  end
  byte_offset
end