Class: Ibex::Codegen::ActionLocations

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

Overview

Rewrites semantic-location expressions without touching Ruby literals, comments, heredoc bodies, or ordinary instance/class variables.

Constant Summary collapse

SEMANTIC_HELPERS =

Signature:

  • Array[String]

Returns:

  • (Array[String])
%w[loc result_loc].freeze

Instance Method Summary collapse

Constructor Details

#initialize(source, maximum:, location:) ⇒ ActionLocations

Returns a new instance of ActionLocations.

RBS:

  • (String source, maximum: Integer, location: IR::location) -> void

Parameters:

  • source (String)
  • maximum: (Integer)
  • location: (IR::location)


17
18
19
20
21
# File 'lib/ibex/codegen/action_locations.rb', line 17

def initialize(source, maximum:, location:)
  @source = source
  @maximum = maximum
  @location = location
end

Instance Method Details

#lexable_sourceString

Give every Ripper implementation syntactically valid input while preserving byte offsets and lexical string/comment boundaries.

RBS:

  • () -> String

Returns:

  • (String)


74
75
76
# File 'lib/ibex/codegen/action_locations.rb', line 74

def lexable_source
  @source.gsub(/@(?:\$|\d+)/) { |spelling| "_" * spelling.bytesize }
end

#line_offsetsArray[Integer]

RBS:

  • () -> Array[Integer]

Returns:

  • (Array[Integer])


79
80
81
82
83
84
85
86
# File 'lib/ibex/codegen/action_locations.rb', line 79

def line_offsets
  offset = 0
  @source.lines.map do |line|
    current = offset
    offset += line.bytesize
    current
  end
end

#mutable_binary_source_copyString

RBS:

  • () -> String

Returns:

  • (String)


50
51
52
# File 'lib/ibex/codegen/action_locations.rb', line 50

def mutable_binary_source_copy
  String.new(encoding: Encoding::BINARY).replace(@source.b)
end

#references?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


36
37
38
39
40
41
42
43
44
45
# File 'lib/ibex/codegen/action_locations.rb', line 36

def references?
  return true if @source.match?(/@(?:\$|\d+)/) && !semantic_references.empty?
  return false unless @source.match?(/\b(?:loc|result_loc)\b/)

  require "ripper"
  tokens = Object.const_get(:Ripper).__send__(:lex, lexable_source)
  tokens.any? do |_position, type, token, _state|
    type == :on_ident && SEMANTIC_HELPERS.include?(token)
  end
end

#replacement_for(spelling) ⇒ String

RBS:

  • (String spelling) -> String

Parameters:

  • spelling (String)

Returns:

  • (String)


89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/ibex/codegen/action_locations.rb', line 89

def replacement_for(spelling)
  return "_ibex_location" if spelling == "@$"

  index = spelling.delete_prefix("@").to_i
  if index.zero? || index > @maximum
    raise Ibex::Error,
          "#{@location[:file]}:#{@location[:line]}:#{@location[:column]}: " \
          "semantic location #{spelling} is outside 1..#{@maximum}"
  end

  "_ibex_locations[#{index - 1}]"
end

#rewriteString

RBS:

  • () -> String

Returns:

  • (String)


24
25
26
27
28
29
30
31
32
33
# File 'lib/ibex/codegen/action_locations.rb', line 24

def rewrite
  rewritten = mutable_binary_source_copy
  return rewritten.force_encoding(@source.encoding) unless @source.match?(/@(?:\$|\d+)/)

  replacements = semantic_references.map do |offset, spelling|
    [offset, spelling.bytesize, replacement_for(spelling)]
  end #: Array[[Integer, Integer, String]]
  replacements.reverse_each { |offset, length, replacement| rewritten[offset, length] = replacement }
  rewritten.force_encoding(@source.encoding)
end

#semantic_referencesArray[[ Integer, String ]]

RBS:

  • () -> Array[[Integer, String]]

Returns:

  • (Array[[ Integer, String ]])


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/ibex/codegen/action_locations.rb', line 55

def semantic_references
  require "ripper"

  offsets = line_offsets
  tokens = Object.const_get(:Ripper).__send__(:lex, lexable_source)
  # @type var tokens: Array[[[Integer, Integer], Symbol, String, untyped]]
  tokens.filter_map do |position, type, _token, _state|
    next unless type == :on_ident

    line, column = position
    offset = offsets.fetch(line - 1) + column
    spelling = @source.b.byteslice(offset..)&.match(/\A@(?:\$|\d+)/)&.[](0)
    [offset, spelling] if spelling
  end
end