Class: RSyntaxTree::StringParser

Inherits:
Object
  • Object
show all
Defined in:
lib/rsyntaxtree/string_parser.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(str, fontset, fontsize, global) ⇒ StringParser

Returns a new instance of StringParser.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/rsyntaxtree/string_parser.rb', line 19

def initialize(str, fontset, fontsize, global)
  @global = global
  # Clean up the data a little to make processing easier
  # repeated newlines => a newline
  string = str.gsub(/[\n\r]+/m, "\n")
  # a backslash followed by a newline => a backslash followed by an 'n'
  string.gsub!(/\\\n\s*/m, "\\n")
  # repeated whitespace characters => " "
  string.gsub!(/\s+/, " ")
  string.gsub!(/\]\s+\[/, "][")
  string.gsub!(/\s+\[/, "[")
  string.gsub!(/\[\s+/, "[")
  string.gsub!(/\s+\]/, "]")
  string.gsub!(/\]\s+/, "]")
  string.gsub!(/<(\d*)>/) do
    num_padding = $1.to_i
    result = if num_padding.positive?
               WHITESPACE_BLOCK * num_padding
             else
               WHITESPACE_BLOCK
             end
    result
  end

  @data = string # Store it for later...
  @elist = ElementList.new # Initialize internal element list
  @pos = 0 # Position in the sentence
  @id = 1 # ID for the next element
  @level = 0 # Level in the diagram
  @fontset = fontset
  @fontsize = fontsize
end

Instance Attribute Details

#dataObject

Returns the value of attribute data.



17
18
19
# File 'lib/rsyntaxtree/string_parser.rb', line 17

def data
  @data
end

#elistObject

Returns the value of attribute elist.



17
18
19
# File 'lib/rsyntaxtree/string_parser.rb', line 17

def elist
  @elist
end

#idObject

Returns the value of attribute id.



17
18
19
# File 'lib/rsyntaxtree/string_parser.rb', line 17

def id
  @id
end

#levelObject

Returns the value of attribute level.



17
18
19
# File 'lib/rsyntaxtree/string_parser.rb', line 17

def level
  @level
end

#posObject

Returns the value of attribute pos.



17
18
19
# File 'lib/rsyntaxtree/string_parser.rb', line 17

def pos
  @pos
end

Class Method Details

.valid?(data) ⇒ Boolean

Returns:

  • (Boolean)

Raises:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/rsyntaxtree/string_parser.rb', line 52

def self.valid?(data)
  raise RSTError.new(+"Error: input text is empty", code: :empty_input, retryable: false) if data.empty?

  if /\[\s*\]/m =~ data
    raise RSTError.new(+"Error: inside the brackets is empty", code: :empty_brackets,
                       hint: "A pair of brackets has no label between them. Give the node a label, or remove the pair.",
                       retryable: true)
  end

  text = data.strip
  text_r = text.split(//)
  open_br = []
  close_br = []
  escape = false
  text_r.each do |chr|
    if chr == "\\"
      escape = if escape
                 false
               else
                 true
               end
      next
    end

    if escape && /[\[\]]/ =~ chr
      escape = false
      next
    elsif chr == '['
      open_br.push(chr)
    elsif chr == ']'
      close_br.push(chr)
      break if open_br.length < close_br.length
    end
    escape = false
  end

  # No brackets at all is a label on its own, which draws as a single
  # leaf; only a count that does not match is a mistake.
  if open_br.length == close_br.length
    true
  else
    raise RSTError.new(+"Error: open and close brackets do not match", code: :unbalanced_brackets,
                       hint: "Count the brackets: every '[' needs one ']'. A bracket meant as text is written \\[ or \\].",
                       retryable: true)
  end
end

Instance Method Details

#get_elementlistObject



139
140
141
# File 'lib/rsyntaxtree/string_parser.rb', line 139

def get_elementlist
  @elist;
end

#get_next_tokenObject



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/rsyntaxtree/string_parser.rb', line 143

def get_next_token
  data = @data.split(//)
  gottoken = false
  token = ""
  i = 0

  return "" if (@pos + 1) >= data.length

  escape = false
  while ((@pos + i) < data.length) && !gottoken
    ch = data[@pos + i]
    case ch
    when "["
      if escape
        token += '\\['  # Preserve as escaped bracket
        escape = false
      elsif i.positive?
        gottoken = true
      else
        token += ch
      end
    when "]"
      if escape
        token += '\\]'  # Preserve as escaped bracket
        escape = false
      else
        token += ch if i.zero?
        gottoken = true
      end
    when "\\"
      if escape
        token += '\\\\'
        escape = false
      else
        escape = true
      end
    when " "
      if escape
        token += '\\n'
        escape = false
      else
        token += ch
      end
    # The characters a backslash may take, which is the grammar's own list
    # (Markup's `escaped` rule) plus the two letters that name a break. A
    # backslash before anything else is dropped here, and '#' was missing:
    # `\#` arrived at the grammar as a bare '#', which opens an enclosure,
    # so a label written with a hash in it lost the hash and everything
    # after it went inside brackets instead.
    when /[nt{}<>^+*_=~|%\-#]/
      if escape
        token += '\\' + ch
        escape = false
      else
        token += ch
      end
    else
      if escape
        token += ch
        escape = false
      else
        token += ch
      end
    end
    i += 1
  end

  @pos += if i > 1
            i - 1
          else
            1
          end
  token
end

#make_tree(parent) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/rsyntaxtree/string_parser.rb', line 218

def make_tree(parent)
  token = get_next_token.strip
  parts = []

  while token != "" && token != "]"
    token_r = token.split(//)
    case token_r[0]
    when "["
      # Check for escaped square brackets
      if token =~ /\A\\\[/ || token =~ /\A\\\]/
        # Treat escaped brackets as regular text
        element = Element.new(@id, parent, token, @level, @fontset, @fontsize, @global)
        @id += 1
        @elist.add(element)
      else
        # Existing processing below
        tl = token_r.length
        token_r = token_r[1, tl - 1]
        spaceat = token_r.index(" ")
        newparent = -1

        if spaceat
          parts[0] = token_r[0, spaceat].join
          tl = token_r.length
          parts[1] = token_r[spaceat, tl - spaceat].join

          element = begin
            Element.new(@id, parent, parts[0], @level, @fontset, @fontsize, @global, true)
          rescue RSTError => e
            # The first raw space splits a token into the node's label
            # and its children (that split is the notation's core rule,
            # so it stays). When the part before the space will not
            # parse, the likeliest story is that the space belongs
            # inside the label and cut a construct in two — say so,
            # unless a more specific cause is already known.
            #
            # Which story is right is not a thing to guess at: ask
            # whether the space is the one that breaks it. Put the whole
            # token back together with the spaces written as the notation
            # writes them, and try again. If it parses, the space was
            # cutting a construct in two and that is what to say. If it
            # fails the same way, the space is a red herring and the cause
            # already named is the one to keep.
            #
            # It used to keep :bare_hyphen and relabel every other cause,
            # so one error said two things: the message naming an unknown
            # colour while the code and the hint talked about spaces. A
            # caller acting on the code — which is what these are for —
            # was sent to fix what was not wrong.
            raise e unless space_is_the_cause?(token_r.join, parent)

            raise RSTError.new(e.message,
                               code: :label_split,
                               label: e.label,
                               position: e.position,
                               hint: "A raw space split this into a label and its children. If the space belongs inside the label, write it as <> (e.g. 'a<>toy').",
                               retryable: true)
          end
          @id += 1
          @elist.add(element)
          newparent = element.id

          element = Element.new(@id, @id - 1, parts[1], @level + 1, @fontset, @fontsize, @global)
          @id += 1
        else
          joined = token_r.join
          element = Element.new(@id, parent, joined, @level, @fontset, @fontsize, @global, true)
          @id += 1
          newparent = element.id
        end
        @elist.add(element)
        @level += 1
        make_tree(newparent)
      end
    else
      if token.strip != ""
        element = Element.new(@id, parent, token, @level, @fontset, @fontsize, @global)
        @id += 1
        @elist.add(element)
      end
    end
    token = get_next_token
  end
  @level -= 1
end

#parseObject



110
111
112
113
114
# File 'lib/rsyntaxtree/string_parser.rb', line 110

def parse
  make_tree(0);
  @elist.set_hierarchy
  restore_rule_names_without_a_rule
end

#restore_rule_names_without_a_ruleObject

A rule name names the step that produced a node from its daughters. A node with no daughters is the product of no step, so what looked like a name is a column of the label like any other, and it goes back.

Whether a node will have daughters is not known where the label is read — they arrive as later tokens — so the label is read first and put right here, once the tree is built. Left alone, turning the option on deleted a column: [A\tfoo] drew "A foo" with derivation off and "A" with it on, and said nothing about the difference.



125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/rsyntaxtree/string_parser.rb', line 125

def restore_rule_names_without_a_rule
  @elist.elements.each_with_index do |e, i|
    next if e.rule_name.nil? || e.rule_name.empty?
    next unless e.children.empty?
    next if e.label_with_rule_name.nil?

    restored = Element.new(e.id, e.parent, e.label_with_rule_name,
                           e.level, @fontset, @fontsize, @global)
    restored.children = e.children
    restored.type = e.type
    @elist.elements[i] = restored
  end
end

#space_is_the_cause?(token, parent) ⇒ Boolean

Whether the raw space that split this token is what stopped it parsing. Asked of the parser rather than reasoned about: the whole token, with its spaces written as <>, either reads as one label or it does not.

Returns:

  • (Boolean)


102
103
104
105
106
107
108
# File 'lib/rsyntaxtree/string_parser.rb', line 102

def space_is_the_cause?(token, parent)
  Element.new(-1, parent, token.gsub(" ", WHITESPACE_BLOCK), @level,
              @fontset, @fontsize, @global, true)
  true
rescue StandardError
  false
end