Class: TracLang::Form

Inherits:
Object
  • Object
show all
Defined in:
lib/trac_lang/form.rb

Overview

A form is a defined string in TRAC, along with spaces in it for the insertion of parameters.

Defined Under Namespace

Classes: EndOfStringError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value) ⇒ Form

Creates a new form with the given value.



21
22
23
24
25
26
27
28
29
# File 'lib/trac_lang/form.rb', line 21

def initialize(value)
  @value = value
  @segments = Array.new(@value.length + 1) { [] }
  # character pointer is the index into the segments array

  @cp = 0
  # segment pointer is the index into @segments[@cp]

  # if @sp == @segments[@cp].lenth then it is pointing to @value[@cp]

  @sp = 0
end

Instance Attribute Details

#segmentsObject (readonly)

Segment positions of form. This is stored as an array of arrays. Each position in the array corresponds to a character in the form string, with an additional entry at the end. If the entry is non-empty it will have a list of the segment numbers at that location.



15
16
17
# File 'lib/trac_lang/form.rb', line 15

def segments
  @segments
end

#valueObject (readonly)

String value of form.



18
19
20
# File 'lib/trac_lang/form.rb', line 18

def value
  @value
end

Instance Method Details

#call_characterObject

Returns the character being pointed to and moves the pointer one unit to the right. Raises a EndOfStringError if the pointer is already at the end of the form.

Raises:



90
91
92
93
94
95
# File 'lib/trac_lang/form.rb', line 90

def call_character(*)
  raise EndOfStringError if @cp == @value.length
  result = @value[@cp, 1]
  increment
  result
end

#call_lookup(*args) ⇒ Object

Returns form string with segment gaps filled with the given arguments.



174
175
176
177
178
179
180
181
182
# File 'lib/trac_lang/form.rb', line 174

def call_lookup(*args)
  trimmed = @segments.dup
  # trim off everything before current pointer

  trimmed.slice!(0...@cp) unless @cp == 0
  trimmed[0].slice!(0...@sp) unless trimmed[0].empty? || @sp == 0
  trimmed.map.with_index do |a, i| 
    a.map { |v| args[v - 1] || '' }.join + (@value[@cp + i] || '')
  end.join
end

#call_n(nstr) ⇒ Object

Returns the given number of characters starting at the current pointer. If the number is negative, returns characters before the current pointer, but in the same order the characters are in the form. Raises an EndOfStringError if a negative number is given and you are at the start of the form, or if a positive number is given and you are at the end of the form. A value of zero can be given to test where the pointer is without changing it.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/trac_lang/form.rb', line 104

def call_n(nstr, *)
  tn = TracLang::Decimal.new(nstr)
  n = tn.value
  if tn.negative?
    raise EndOfStringError if @cp == 0 && @sp == 0
    # move left of seg gaps

    @sp = 0
    return '' if n == 0
    raise EndOfStringError if @cp == 0
    n = -@cp if n < -@cp
    result = @value.slice(@cp + n, -n)
    increment(n)
  else
    raise EndOfStringError if @value.length - @cp == 0 && @sp == @segments[@cp].length
    # move right of seg gaps

    @sp = @segments[@cp].length
    return '' if n == 0
    raise EndOfStringError if @value.length - @cp == 0
    n = @value.length - @cp if n > @value.length - @cp
    result = @value.slice(@cp, n)
    increment(n)
  end
  result
end

#call_returnObject

Returns the pointers to the start of the form.



82
83
84
85
# File 'lib/trac_lang/form.rb', line 82

def call_return(*)
  @sp = 0
  @cp = 0
end

#call_segmentObject

Returns characters between the current pointer and the next segment gap.

Raises:



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/trac_lang/form.rb', line 156

def call_segment(*)
  raise EndOfStringError if @cp == @value.length + 1 # would this ever be true?

  # on a character

  if @sp == @segments[@cp].length # may be zero

    raise EndOfStringError if @cp == @value.length
    result = find_chars(@cp)
    @cp += result.length
    # need check if you're at end of string

    @sp = @segments[@cp].empty? ? 0 : 1
  # else within segment list

  else
    result = ''
    @sp += 1
  end
  result
end

#find(search, start = 0) ⇒ Object

Finds the given search string in the string portion of the form, starting at the given space. A successful match cannot span segment gaps.



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/trac_lang/form.rb', line 33

def find(search, start = 0)
  loop do
    i = @value.index(search, start)
    return nil unless i
    # don't find over segment boundaries

    boundary = @segments.slice(i + 1, search.length - 1)
    unless boundary.all? { |v| v.empty? }
      start = i + 1
      next
    end
    return i
  end
end

#find_chars(start) ⇒ Object

Returns characters from the given position to the next segment gap.



148
149
150
151
152
# File 'lib/trac_lang/form.rb', line 148

def find_chars(start)
    # don't test start position because you might be at the end of the segment list

    len = 1 + @segments[(start + 1)..-2].take_while { |s| s.empty? }.count
    @value.slice(start, len)
end

#formatObject

Find format of args that works



207
208
209
210
211
212
213
# File 'lib/trac_lang/form.rb', line 207

def format
  pair = [['<','>'],['[',']'],['{','}']].find { |p| !matched_pair_used?(*p) }
  return pair if pair
  special = (126..255).find { |n| !special_used?(n.chr) }
  return [special.chr, special.chr] if special
  # what to do if nothing works?

end

#in_neutral(search) ⇒ Object

Searches for the given string in the form. If found, returns all characters between the current pointer and the start of the match, while moving the character pointer past the match. Raises an EndOfStringError if you are at the end of the form or no match is found. An empty search string counts as always not matching.

Raises:



134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/trac_lang/form.rb', line 134

def in_neutral(search, *)
  raise EndOfStringError if @cp == @value.length || search.empty?
  found = find(search, @cp)
  if found
    result = @value[@cp...found]
    increment(found - @cp + search.length)
    return result
  else
    # form pointer is not moved if not found

    raise EndOfStringError
  end
end

#increment(n = 1) ⇒ Object

Increments the character pointer by the given amount.



74
75
76
77
78
79
# File 'lib/trac_lang/form.rb', line 74

def increment(n = 1)
  @cp += n
  @cp = @value.length if @cp > @value.length
  @cp = 0 if @cp < 0
  @sp = n > 0 ? 0 : @segments[@cp].length
end

#matched_pair_used?(open, close) ⇒ Boolean

Tests if matched pair of symbols is used anywhere in this form. Used to find an unused pair for writing this form to a file.

Returns:

  • (Boolean)


196
197
198
# File 'lib/trac_lang/form.rb', line 196

def matched_pair_used?(open, close)
  max_punch.times.map { |i| "#{open}#{i + 1}#{close}" }.any? { |s| @value.include?(s) }
end

#max_punchObject

Finds the biggest punch index.



190
191
192
# File 'lib/trac_lang/form.rb', line 190

def max_punch
  @segments.map { |s| s.max }.compact.max
end

#punch(punch_in, n) ⇒ Object

Adds segement gaps for one punch.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/trac_lang/form.rb', line 48

def punch(punch_in, n)
  return if punch_in.empty?
  start = 0
  punch = punch_in.dup
  len = punch.length
  loop do
    found = find(punch)
    break unless found
    if @segments[found].empty?
      @segments.slice!(found, len)
      @segments[found].unshift(n)
    else
      @segments[found].push(n)
      @segments[found] += @segments[found + len]
      @segments.slice!(found + 1, len)
    end
    @value.slice!(found, len)
  end
end

#punchedObject

Checks if any punches have been done on this form.



185
186
187
# File 'lib/trac_lang/form.rb', line 185

def punched
  @segments.any? { |s| !s.empty? }
end

#segment_string(*punches) ⇒ Object

Add segment gaps for multiple punches.



69
70
71
# File 'lib/trac_lang/form.rb', line 69

def segment_string(*punches)
  punches.each.with_index { |p, n| punch(p, n + 1) }
end

#special_used?(char) ⇒ Boolean

Test if given special character is used anywhere in this form. Used to find an unused special character for writing this form to a file.

Returns:

  • (Boolean)


202
203
204
# File 'lib/trac_lang/form.rb', line 202

def special_used?(char)
  max_punch.times.map { |i| "#{char}#{i + 1}" }.any? { |s| @value.include?(s) }
end

#to_sObject

Converts this form into a string for display. Follows format of TRAC display defined in language definition.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/trac_lang/form.rb', line 236

def to_s
  str = ''
  @segments.each.with_index do |s, cp|
    s.each.with_index do |n, sp|
      str += '<^>' if @cp == cp && @sp == sp
      str += "<#{n}>"
    end
    str += '<^>' if @cp == cp && @sp == s.length
    if cp < @value.length
      c = @value[cp]
      # escape non-printable characters

      str << (c =~ /[[:print:]]/ ? c : sprintf("\\x%02.2x", c.ord))
    end
  end
  str
end

#to_trac(name) ⇒ Object

Converts current state of this form into TRAC commands.



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/trac_lang/form.rb', line 216

def to_trac(name)
  cp, sp = @cp, @sp
  @cp, @sp = 0, 0
  if punched
    pair = format
    args = max_punch.times.map { |i| "#{pair[0]}#{i + 1}#{pair[1]}"}
    trac = "#(DS,#{name},#{call_lookup(*args)})\n"
    trac += "#(SS,#{name},#{args.join(',')})\n" unless args.empty?
  else 
    trac = "#(DS,#{name},#{@value})\n"
  end
  trac += "#(CN,#{name},#{cp})\n" unless cp == 0
  trac += "#(CS,#{name})" * sp + "\n" unless sp == 0
  trac += "\n"
  @cp, @sp = cp, sp
  trac
end