Class: Ibex::Location

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

Overview

Immutable source range suitable for lexer tokens and parser diagnostics.

Coordinates are one-based. Byte offsets are optional, zero-based, and half-open. Keeping both forms lets Unicode-aware lexers report human columns without losing an exact source slice.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(line:, column:, file: nil, end_line: nil, end_column: nil, start_byte: nil, end_byte: nil, source_line: nil) ⇒ Location

Returns a new instance of Location.

RBS:

  • (?file: String?, line: Integer, column: Integer, ?end_line: Integer?, ?end_column: Integer?, ?start_byte: Integer?, ?end_byte: Integer?, ?source_line: String?) -> void

Parameters:

  • line: (Integer)
  • column: (Integer)
  • file: (String, nil) (defaults to: nil)
  • end_line: (Integer, nil) (defaults to: nil)
  • end_column: (Integer, nil) (defaults to: nil)
  • start_byte: (Integer, nil) (defaults to: nil)
  • end_byte: (Integer, nil) (defaults to: nil)
  • source_line: (String, nil) (defaults to: nil)


22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/ibex/location.rb', line 22

def initialize(line:, column:, file: nil, end_line: nil, end_column: nil,
               start_byte: nil, end_byte: nil, source_line: nil)
  @file = file&.dup&.freeze
  @line = positive_coordinate(:line, line)
  @column = positive_coordinate(:column, column)
  @end_line = positive_coordinate(:end_line, end_line || line)
  @end_column = positive_coordinate(:end_column, end_column || column)
  @start_byte = optional_offset(:start_byte, start_byte)
  @end_byte = optional_offset(:end_byte, end_byte)
  @source_line = source_line&.dup&.freeze
  validate_order!
  freeze
end

Instance Attribute Details

#columnInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


13
14
15
# File 'lib/ibex/location.rb', line 13

def column
  @column
end

#end_byteInteger? (readonly)

Signature:

  • Integer?

Returns:

  • (Integer, nil)


17
18
19
# File 'lib/ibex/location.rb', line 17

def end_byte
  @end_byte
end

#end_columnInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


15
16
17
# File 'lib/ibex/location.rb', line 15

def end_column
  @end_column
end

#end_lineInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


14
15
16
# File 'lib/ibex/location.rb', line 14

def end_line
  @end_line
end

#fileString? (readonly)

Signature:

  • String?

Returns:

  • (String, nil)


11
12
13
# File 'lib/ibex/location.rb', line 11

def file
  @file
end

#lineInteger (readonly)

Signature:

  • Integer

Returns:

  • (Integer)


12
13
14
# File 'lib/ibex/location.rb', line 12

def line
  @line
end

#source_lineString? (readonly)

Signature:

  • String?

Returns:

  • (String, nil)


18
19
20
# File 'lib/ibex/location.rb', line 18

def source_line
  @source_line
end

#start_byteInteger? (readonly)

Signature:

  • Integer?

Returns:

  • (Integer, nil)


16
17
18
# File 'lib/ibex/location.rb', line 16

def start_byte
  @start_byte
end

Class Method Details

.join(locations) ⇒ Location

Fold a nonempty collection into one covering range.

RBS:

  • (Enumerable[Location] locations) -> Location

Parameters:

Returns:



53
54
55
56
57
# File 'lib/ibex/location.rb', line 53

def self.join(locations)
  values = locations.to_a
  first = values.shift || raise(ArgumentError, "locations must not be empty")
  values.reduce(first) { |combined, location| combined.join(location) }
end

Instance Method Details

#before_or_equal?(left, right) ⇒ Boolean

RBS:

  • (Location left, Location right) -> bool

Parameters:

Returns:

  • (Boolean)


105
106
107
# File 'lib/ibex/location.rb', line 105

def before_or_equal?(left, right)
  left.line < right.line || (left.line == right.line && left.column <= right.column)
end

#before_or_equal_end?(left, right) ⇒ Boolean

RBS:

  • (Location left, Location right) -> bool

Parameters:

Returns:

  • (Boolean)


110
111
112
113
# File 'lib/ibex/location.rb', line 110

def before_or_equal_end?(left, right)
  left.end_line < right.end_line ||
    (left.end_line == right.end_line && left.end_column <= right.end_column)
end

#empty?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


60
61
62
63
# File 'lib/ibex/location.rb', line 60

def empty?
  @line == @end_line && @column == @end_column &&
    (@start_byte.nil? || @end_byte.nil? || @start_byte == @end_byte)
end

#join(other) ⇒ Location

Return the smallest range covering both locations.

RBS:

  • (Location other) -> Location

Parameters:

Returns:



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/ibex/location.rb', line 38

def join(other)
  raise ArgumentError, "location files differ" unless file == other.file

  first = before_or_equal?(self, other) ? self : other
  ending = before_or_equal_end?(self, other) ? other : self
  self.class.new(
    file: file, line: first.line, column: first.column,
    end_line: ending.end_line, end_column: ending.end_column,
    start_byte: joined_start_byte(other), end_byte: joined_end_byte(other),
    source_line: first.source_line
  )
end

#joined_end_byte(other) ⇒ Integer?

RBS:

  • (Location other) -> Integer?

Parameters:

Returns:

  • (Integer, nil)


123
124
125
126
127
# File 'lib/ibex/location.rb', line 123

def joined_end_byte(other)
  return unless @end_byte && other.end_byte

  [@end_byte, other.end_byte].max
end

#joined_start_byte(other) ⇒ Integer?

RBS:

  • (Location other) -> Integer?

Parameters:

Returns:

  • (Integer, nil)


116
117
118
119
120
# File 'lib/ibex/location.rb', line 116

def joined_start_byte(other)
  return unless @start_byte && other.start_byte

  [@start_byte, other.start_byte].min
end

#optional_offset(name, value) ⇒ Integer?

RBS:

  • (Symbol name, untyped value) -> Integer?

Parameters:

  • name (Symbol)
  • value (Object)

Returns:

  • (Integer, nil)


84
85
86
87
88
89
# File 'lib/ibex/location.rb', line 84

def optional_offset(name, value)
  return if value.nil?
  return value if value.is_a?(Integer) && value >= 0

  raise ArgumentError, "#{name} must be a nonnegative Integer or nil"
end

#positive_coordinate(name, value) ⇒ Integer

RBS:

  • (Symbol name, untyped value) -> Integer

Parameters:

  • name (Symbol)
  • value (Object)

Returns:

  • (Integer)


77
78
79
80
81
# File 'lib/ibex/location.rb', line 77

def positive_coordinate(name, value)
  return value if value.is_a?(Integer) && value.positive?

  raise ArgumentError, "#{name} must be a positive Integer"
end

#to_hHash[Symbol, untyped]

RBS:

  • () -> Hash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


66
67
68
69
70
71
72
# File 'lib/ibex/location.rb', line 66

def to_h
  {
    file: @file, line: @line, column: @column,
    end_line: @end_line, end_column: @end_column,
    start_byte: @start_byte, end_byte: @end_byte
  }.compact
end

#validate_order!void

This method returns an undefined value.

RBS:

  • () -> void



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/ibex/location.rb', line 92

def validate_order!
  if @end_line < @line || (@end_line == @line && @end_column < @column)
    raise ArgumentError, "end position precedes start position"
  end

  start_byte = @start_byte
  end_byte = @end_byte
  return unless start_byte && end_byte && end_byte < start_byte

  raise ArgumentError, "end_byte precedes start_byte"
end