Class: SuperExpressiveRuby

Inherits:
Object
  • Object
show all
Defined in:
lib/super-expressive-ruby/super-expressive-ruby.rb

Constant Summary collapse

NamedGroupRegex =
/^[a-z]+\w*$/i.freeze
QuantifierTable =
{
  oneOrMore: '+',
  oneOrMoreLazy: '+?',
  zeroOrMore: '*',
  zeroOrMoreLazy: '*?',
  optional: '?',
  exactly: proc { |times| "{#{times}}" },
  atLeast: proc { |times| "{#{times},}" },
  between: proc { |times| "{#{times[0]},#{times[1]}}" },
  betweenLazy: proc { |times| "{#{times[0]},#{times[1]}}?" }
}.freeze
@@t =
{
  root: as_type('root').call,
  noop: as_type('noop').call,
  startOfInput: as_type('startOfInput').call,
  endOfInput: as_type('endOfInput').call,
  anyChar: as_type('anyChar').call,
  whitespaceChar: as_type('whitespaceChar').call,
  nonWhitespaceChar: as_type('nonWhitespaceChar').call,
  digit: as_type('digit').call,
  nonDigit: as_type('nonDigit').call,
  word: as_type('word').call,
  nonWord: as_type('nonWord').call,
  wordBoundary: as_type('wordBoundary').call,
  nonWordBoundary: as_type('nonWordBoundary').call,
  newline: as_type('newline').call,
  carriageReturn: as_type('carriageReturn').call,
  tab: as_type('tab').call,
  nullByte: as_type('nullByte').call,
  anyOfChars: as_type('anyOfChars'),
  anythingButString: as_type('anythingButString'),
  anythingButChars: as_type('anythingButChars'),
  anythingButRange: as_type('anythingButRange'),
  char: as_type('char'),
  range: as_type('range'),
  string: as_type('string', { quantifierRequiresGroup: true }),
  namedBackreference: proc { |name| deferred_type('namedBackreference', { name: name }) },
  backreference: proc { |index| deferred_type('backreference', { index: index }) },
  capture: deferred_type('capture', { containsChildren: true }),
  subexpression: as_type('subexpression', { containsChildren: true, quantifierRequiresGroup: true }),
  namedCapture: proc { |name| deferred_type('namedCapture', { name: name, containsChildren: true }) },
  group: deferred_type('group', { containsChildren: true }),
  anyOf: deferred_type('anyOf', { containsChildren: true }),
  assertAhead: deferred_type('assertAhead', { containsChildren: true }),
  assertNotAhead: deferred_type('assertNotAhead', { containsChildren: true }),
  exactly: proc { |times| deferred_type('exactly', { times: times, containsChild: true }) },
  atLeast: proc { |times| deferred_type('atLeast', { times: times, containsChild: true }) },
  between: proc { |x, y| deferred_type('between', { times: [x, y], containsChild: true }) },
  betweenLazy: proc { |x, y| deferred_type('betweenLazy', { times: [x, y], containsChild: true }) },
  zeroOrMore: deferred_type('zeroOrMore', { containsChild: true }),
  zeroOrMoreLazy: deferred_type('zeroOrMoreLazy', { containsChild: true }),
  oneOrMore: deferred_type('oneOrMore', { containsChild: true }),
  oneOrMoreLazy: deferred_type('oneOrMoreLazy', { containsChild: true }),
  optional: deferred_type('optional', { containsChild: true })
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSuperExpressiveRuby

Returns a new instance of SuperExpressiveRuby.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 230

def initialize
  self.state = {
    hasDefinedStart: false,
    hasDefinedEnd: false,
    flags: {
      g: false,
      y: false,
      m: false,
      i: false,
      u: false,
      s: false
    },
    stack: [create_stack_frame(t[:root])],
    namedGroups: [],
    totalCaptureGroups: 0
  }
end

Instance Attribute Details

#stateObject

Returns the value of attribute state.



7
8
9
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 7

def state
  @state
end

Class Method Details

.as_type(type, opts = {}) ⇒ Object



131
132
133
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 131

def as_type(type, opts={})
  proc { |value| { type: type, value: value }.merge(opts) }
end

.assert(condition, message) ⇒ Object

Raises:

  • (StandardError)


140
141
142
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 140

def assert(condition, message)
  raise StandardError, message unless condition
end

.camelize(snake_case_str) ⇒ Object



174
175
176
177
178
179
180
181
182
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 174

def camelize(snake_case_str)
  snake_case_str.split('_').each_with_object([]).with_index do |(s, acc), idx|
    acc << if idx.zero?
             s
           else
             s.capitalize
           end
  end.join
end

.deferred_type(type, opts = {}) ⇒ Object



135
136
137
138
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 135

def deferred_type(type, opts={})
  type_fn = as_type(type, opts)
  type_fn.call(type_fn)
end

.evaluate(el) ⇒ Object



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
51
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 23

def evaluate(el)
  case el[:type]
  when 'noop'
    ''
  when 'anyChar'
    '.'
  when 'whitespaceChar'
    '\\s'
  when 'nonWhitespaceChar'
    '\\S'
  when 'digit'
    '\\d'
  when 'nonDigit'
    '\\D'
  when 'word'
    '\\w'
  when 'nonWord'
    '\\W'
  when 'wordBoundary'
    '\\b'
  when 'nonWordBoundary'
    '\\B'
  when 'startOfInput'
    '^'
  when 'endOfInput'
    '$'
  when 'newline'
    '\\n'
  when 'carriageReturn'
    '\\r'
  when 'tab'
    '\\t'
  when 'nullByte'
    '\\0'
  when 'string'
    el[:value]
  when 'char'
    el[:value]
  when 'range'
    "[#{el[:value][0]}-#{el[:value][1]}]"
  when 'anythingButRange'
    "[^#{el[:value][0]}-#{el[:value][1]}]"
  when 'anyOfChars'
    "[#{el[:value]}]"
  when 'anythingButChars'
    "[^#{el[:value]}]"
  when 'namedBackreference'
    "\\k<#{el[:name]}>"
  when 'backreference'
    "\\#{el[:index]}"
  when 'subexpression'
    el[:value].map { |value| evaluate(value) }.join('')
  when 'optional',
       'zeroOrMore',
       'zeroOrMoreLazy',
       'oneOrMore',
       'oneOrMoreLazy'
    inner = evaluate(el[:value])
    with_group =
      if el[:value][:quantifierRequiresGroup]
        "(?:#{inner})"
      else
        inner
      end
    symbol = QuantifierTable[el[:type].to_sym]
    "#{with_group}#{symbol}"
  when 'betweenLazy',
  'between',
  'atLeast',
  'exactly'
    inner = evaluate(el[:value])
    withGroup =
      if el[:value][:quantifierRequiresGroup]
        "(?:#{inner})"
      else
        inner
      end
    "#{withGroup}#{QuantifierTable[el[:type].to_sym].call(el[:times])}"
  when 'anythingButString'
    chars = el[:value].split('').map { |c| "[^#{c}]" }.join('')
    "(?:#{chars})"
  when 'assertAhead'
    evaluated = el[:value].map { |v| evaluate(v) }.join('')
    "(?=#{evaluated})"
  when 'assertNotAhead'
    evaluated = el[:value].map { |v| evaluate(v) }.join('')
    "(?!#{evaluated})"
  when 'anyOf'
    fused, rest = fuse_elements(el[:value])
    return "[#{fused}]" unless rest.length

    evaluatedRest = rest.map { |v| evaluate(v) }
    separator = evaluatedRest.length > 0 && fused.length > 0 ? '|' : ''
    "(?:#{evaluatedRest.join('|')}#{separator}#{fused ? "[#{fused}]" : ''})"
  when 'capture'
    evaluated = el[:value].map { |v| evaluate(v) }
    "(#{evaluated.join('')})"
  when 'namedCapture'
    evaluated = el[:value].map { |v| evaluate(v) }
    "(?<#{el[:name]}>#{evaluated.join('')})"
  when 'group'
    evaluated = el[:value].map { |v| evaluate(v) }
    "(?:#{evaluated.join('')})"
  else
    raise "Can't process unsupported element type: #{el[:type]}"
  end
end

.fuse_elements(elements) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 162

def fuse_elements(elements)
  fusables, rest = partition(elements)
  fused = fusables.map do |el|
    if %w[char anyOfChars].include?(el[:type])
      el[:value]
    else
      "#{el[:value][0]}-#{el[:value][1]}"
    end
  end.join('')
  [fused, rest]
end

.is_fusable(element) ⇒ Object



156
157
158
159
160
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 156

def is_fusable(element)
  element[:type] == 'range' ||
    element[:type] == 'char' ||
    element[:type] == 'anyOfChars'
end

.partition(a) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 144

def partition(a)
  r = a.each_with_object([[], []]) do |cur, acc|
    if is_fusable(cur)
      acc[0].push(cur)
    else
      acc[1].push(cur)
    end
    acc
  end
  [r[0], r[1]]
end

Instance Method Details

#allow_multiple_matchesObject



260
261
262
263
264
265
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 260

def allow_multiple_matches
  # warn("Warning: Ruby does not have a allow multiple matches option. use String#gsub or String#scan")
  n = clone
  n.state[:flags][:g] = true
  n
end

#any_charObject



305
306
307
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 305

def any_char
  match_element(t[:anyChar])
end

#any_ofObject



376
377
378
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 376

def any_of
  frame_creating_element(t[:anyOf])
end

#any_of_chars(s) ⇒ Object



524
525
526
527
528
529
530
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 524

def any_of_chars(s)
  n = clone
  element_value = t[:anyOfChars].call(escape_special(s))
  current_frame = n.get_current_frame
  current_frame[:elements].push(n.apply_quantifier(element_value))
  n
end

#anything_but_chars(chars) ⇒ Object



553
554
555
556
557
558
559
560
561
562
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 553

def anything_but_chars(chars)
  assert(chars.is_a?(String), "chars must be a string (got #{chars})")
  assert(chars.length > 0, 'chars must have at least one character')

  n = clone
  element_value = t[:anythingButChars].call(escape_special(chars))
  current_frame = n.get_current_frame
  current_frame[:elements].push(n.apply_quantifier(element_value))
  n
end

#anything_but_range(a, b) ⇒ Object



564
565
566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 564

def anything_but_range(a, b)
  str_a = a.to_s
  str_b = b.to_s

  assert(str_a.length === 1, "a must be a single character or number (got #{str_a})")
  assert(str_b.length === 1, "b must be a single character or number (got #{str_b})")
  assert(str_a[0].ord < str_b[0].ord, "a must have a smaller character value than b (a = #{str_a[0].ord}, b = #{str_b[0].ord})")

  n = clone
  element_value = t[:anythingButRange].call([a, b])
  current_frame = n.get_current_frame
  current_frame[:elements].push(n.apply_quantifier(element_value))
  n
end

#anything_but_string(str) ⇒ Object



542
543
544
545
546
547
548
549
550
551
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 542

def anything_but_string(str)
  assert(str.is_a?(String), "str must be a string (got #{str})")
  assert(str.length > 0, 'str must have least one character')

  n = clone
  element_value - t[:anythingButString].call(escape_special(str))
  current_frame = n.get_current_frame
  current_frame[:elements].push(n.apply_quantifier(element_value))
  n
end

#apply_quantifier(element) ⇒ Object



783
784
785
786
787
788
789
790
791
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 783

def apply_quantifier(element)
  current_frame = get_current_frame
  if current_frame[:quantifier]
    wrapped = current_frame[:quantifier][:value].call(element)
    current_frame[:quantifier] = nil
    return wrapped
  end
  element
end

#apply_subexpression_defaults(expr) ⇒ Object



700
701
702
703
704
705
706
707
708
709
710
711
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 700

def apply_subexpression_defaults(expr)
  out = {}.merge(expr)

  out[:namespace] = out.has_key?(:namespace) ? out[:namespace] : ''
  out[:ignoreFlags] = out.has_key?(:ignoreFlags) ? out[:ignoreFlags] : true
  out[:ignoreStartAndEnd] = out.has_key?(:ignoreStartAndEnd) ? out[:ignoreStartAndEnd] : true
  assert(out[:namespace].is_a?(String), 'namespace must be a string')
  assert(out[:ignoreFlags].is_a?(TrueClass) || out[:ignoreFlags].is_a?(FalseClass), 'ignoreFlags must be a boolean')
  assert(out[:ignoreStartAndEnd].is_a?(TrueClass) || out[:ignoreStartAndEnd].is_a?(FalseClass), 'ignoreStartAndEnd must be a boolean')

  out
end

#assert(condition, message) ⇒ Object

Raises:

  • (StandardError)


801
802
803
804
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 801

def assert(condition, message)
  self.class.assert(condition, message)
  raise StandardError, message unless condition
end

#assert_aheadObject



384
385
386
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 384

def assert_ahead
  frame_creating_element(t[:assertAhead])
end

#assert_not_aheadObject



388
389
390
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 388

def assert_not_ahead
  frame_creating_element(t[:assertNotAhead])
end

#at_least(n) ⇒ Object



463
464
465
466
467
468
469
470
471
472
473
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 463

def at_least(n)
  assert(n.is_a?(Integer) && n > 0, "n must be a positive integer (got #{n})")
  nxt = clone
  current_frame = nxt.get_current_frame
  if current_frame[:quantifier]
    raise StandardError, "cannot quantify regular expression with 'atLeast' because it's already being quantified with '#{currentFrame.quantifier.type}'"
  end

  current_frame[:quantifier] = t[:atLeast].call(n)
  nxt
end

#backreference(index) ⇒ Object



362
363
364
365
366
367
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 362

def backreference(index)
  assert(index.is_a?(Integer), 'index must be a number')
  assert(index > 0 && index <= state[:totalCaptureGroups],
         "invalid index #{index}. There are #{state[:totalCaptureGroups]} capture groups on this SuperExpression")
  match_element(t[:backreference].call(index))
end

#between(x, y) ⇒ Object



475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 475

def between(x, y)
  assert(x.is_a?(Integer) && x >= 0, "x must be an integer (got #{x})")
  assert(y.is_a?(Integer) && y > 0, "y must be an integer greater than 0 (got #{y})")
  assert(x < y, "x must be less than y (x = #{x}, y = #{y})")

  nxt = clone
  current_frame = nxt.get_current_frame
  if current_frame[:quantifier]
    raise StandardError, "cannot quantify regular expression with 'between' because it's already being quantified with '#{currentFrame.quantifier.type}'"
  end

  current_frame[:quantifier] = t[:between].call(x, y)
  nxt
end

#between_lazy(x, y) ⇒ Object



490
491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 490

def between_lazy(x, y)
  assert(x.is_a?(Integer) && x >= 0, "x must be an integer (got #{x})")
  assert(y.is_a?(Integer) && y > 0, "y must be an integer greater than 0 (got #{y})")
  assert(x < y, "x must be less than y (x = #{x}, y = #{y})")

  n = clone
  current_frame = n.get_current_frame
  if current_frame[:quantifier]
    raise StandardError, "cannot quantify regular expression with 'betweenLazy' because it's already being quantified with '#{current_frame[:quantifier][:type]}'"
  end

  current_frame[:quantifier] = t[:betweenLazy].call(x, y)
  n
end

#captureObject



392
393
394
395
396
397
398
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 392

def capture
  n = clone
  new_frame = create_stack_frame(t[:capture])
  n.state[:stack].push(new_frame)
  n.state[:totalCaptureGroups] += 1
  n
end

#carriage_returnObject



345
346
347
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 345

def carriage_return
  match_element(t[:carriageReturn])
end

#case_insensitiveObject



274
275
276
277
278
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 274

def case_insensitive
  n = clone
  n.state[:flags][:i] = true
  n
end

#char(c) ⇒ Object



596
597
598
599
600
601
602
603
604
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 596

def char(c)
  assert(c.is_a?(String), "c must be a string (got #{c})")
  assert(c.length == 1, "char() can only be called with a single character (got #{c})")

  n = clone
  current_frame = n.get_current_frame
  current_frame[:elements].push(n.apply_quantifier(t[:char].call(escape_special(c))))
  n
end

#create_stack_frame(type) ⇒ Object



256
257
258
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 256

def create_stack_frame(type)
  { type: type, quantifier: nil, elements: [] }
end

#digitObject



317
318
319
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 317

def digit
  match_element(t[:digit])
end

#endObject



532
533
534
535
536
537
538
539
540
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 532

def end
  assert(state[:stack].length > 1, 'Cannot call end while building the root expression.')

  n = clone
  old_frame = n.state[:stack].pop
  current_frame = n.get_current_frame
  current_frame[:elements].push(n.apply_quantifier(old_frame[:type][:value].call(old_frame[:elements])))
  n
end

#end_of_inputObject



515
516
517
518
519
520
521
522
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 515

def end_of_input
  assert(!state[:hasDefinedEnd], 'This regex already has a defined end of input')

  n = clone
  n.state[:hasDefinedEnd] = true
  n.get_current_element_array.push(t[:endOfInput])
  n
end

#escape_special(s) ⇒ Object



252
253
254
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 252

def escape_special(s)
  Regexp.escape(s)
end

#exactly(n) ⇒ Object



450
451
452
453
454
455
456
457
458
459
460
461
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 450

def exactly(n)
  assert(n.is_a?(Integer) && n > 0, "n must be a positive integer (got #{n})")

  nxt = clone
  current_frame = nxt.get_current_frame
  if current_frame[:quantifier]
    raise StandardError, "cannot quantify regular expression with 'exactly' because it's already being quantified with '#{current_frame[:quantifier][:type]}'"
  end

  current_frame[:quantifier] = t[:exactly].call(n)
  nxt
end

#frame_creating_element(type_fn) ⇒ Object



369
370
371
372
373
374
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 369

def frame_creating_element(type_fn)
  n = clone
  new_frame = create_stack_frame(type_fn)
  n.state[:stack].push(new_frame)
  n
end

#get_current_element_arrayObject



797
798
799
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 797

def get_current_element_array
  get_current_frame[:elements]
end

#get_current_frameObject



793
794
795
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 793

def get_current_frame
  state[:stack][state[:stack].length - 1]
end

#get_regex_pattern_and_flagsObject



760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 760

def get_regex_pattern_and_flags
  assert state[:stack].length === 1,
         "Cannot compute the value of a not yet fully specified regex object.
           \n(Try adding a .end() call to match the '#{get_current_frame[:type][:type]}')\n"
  pattern = get_current_element_array.map { |el| self.class.evaluate(el) }.join('')
  flag = nil
  state[:flags].map do |name, is_on|
    if is_on
      flag = 0 if !flag
      case name
      when :s
        flag = flag | Regexp::MULTILINE
      when :i
        flag = flag | Regexp::IGNORECASE
      when :x
        flag = flag | Regexp::EXTENDED
      end
    end
  end
  pat = (pattern == '' ? '(?:)' : pattern)
  [pat, flag]
end

#groupObject



380
381
382
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 380

def group
  frame_creating_element(t[:group])
end

#line_by_lineObject



267
268
269
270
271
272
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 267

def 
  # warn("Warning: Ruby does not have a line by line option. use \A or \z as an alternative")
  n = clone
  n.state[:flags][:m] = true
  n
end

#match_element(type_fn) ⇒ Object



299
300
301
302
303
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 299

def match_element(type_fn)
  n = clone
  n.get_current_element_array.push(n.apply_quantifier(type_fn))
  n
end

#merge_subexpression(el, options, parent, increment_capture_groups) ⇒ Object



622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 622

def merge_subexpression(el, options, parent, increment_capture_groups)
  next_el = el.clone
  next_el[:index] += parent.state[:totalCaptureGroups] if next_el[:type] == 'backreference'

  increment_capture_groups.call if next_el[:type] == 'capture'

  if next_el[:type] === 'namedCapture'
    group_name =
      if options[:namespace]
        "#{options[:namespace]}#{next_el[:name]}"
      else
        next_el[:name]
      end

    parent.track_named_group(group_name)
    next_el[:name] = group_name
  end

  if next_el[:type] == 'namedBackreference'
    next_el[:name] =
      if options[:namespace]
        "#{options[:namespace]}#{next_el[:name]}"
      else
        next_el[:name]
      end
  end

  if next_el[:containsChild]
    next_el[:value] = merge_subexpression(
      next_el[:value],
      options,
      parent,
      increment_capture_groups
    )
  elsif next_el[:containsChildren]
    next_el[:value] = next_el[:value].map do |e|
      merge_subexpression(
        e,
        options,
        parent,
        increment_capture_groups
      )
    end
  end

  if next_el[:type] == 'startOfInput'

    return @@t[:noop] if options[:ignoreStartAndEnd]

    assert(
      !parent.state[:hasDefinedStart],
      'The parent regex already has a defined start of input. ' +
      'You can ignore a subexpressions startOfInput/endOfInput markers with the ignoreStartAndEnd option'
    )

    assert(
      !parent.state[:hasDefinedEnd],
      'The parent regex already has a defined end of input. ' +
        'You can ignore a subexpressions startOfInput/endOfInput markers with the ignoreStartAndEnd option'
    )

    parent.state[:hasDefinedStart] = true
  end

  if next_el[:type] == 'endOfInput'
    return @@t[:noop] if options[:ignoreStartAndEnd]

    assert(
      !parent.state[:hasDefinedEnd],
      'The parent regex already has a defined start of input. ' +
      'You can ignore a subexpressions startOfInput/endOfInput markers with the ignoreStartAndEnd option'
    )

    parent.state[:hasDefinedEnd] = true
  end
  next_el
end

#named_backreference(name) ⇒ Object



357
358
359
360
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 357

def named_backreference(name)
  assert(state[:namedGroups].include?(name), "no capture group called '#{name}' exists (create one with .namedCapture())")
  match_element(t[:namedBackreference].call(name))
end

#named_capture(name) ⇒ Object



409
410
411
412
413
414
415
416
417
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 409

def named_capture(name)
  n = clone
  new_frame = create_stack_frame(t[:namedCapture].call(name))

  n.track_named_group(name)
  n.state[:stack].push(new_frame)
  n.state[:totalCaptureGroups] += 1
  n
end

#newlineObject



341
342
343
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 341

def newline
  match_element(t[:newline])
end

#non_digitObject



321
322
323
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 321

def non_digit
  match_element(t[:nonDigit])
end

#non_whitespace_charObject



313
314
315
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 313

def non_whitespace_char
  match_element(t[:nonWhitespaceChar])
end

#non_wordObject



329
330
331
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 329

def non_word
  match_element(t[:nonWord])
end

#non_word_boundaryObject



337
338
339
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 337

def non_word_boundary
  match_element(t[:nonWordBoundary])
end

#null_byteObject



353
354
355
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 353

def null_byte
  match_element(t[:nullByte])
end

#one_or_moreObject



442
443
444
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 442

def one_or_more
  quantifier_element('oneOrMore')
end

#one_or_more_lazyObject



446
447
448
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 446

def one_or_more_lazy
  quantifier_element('oneOrMoreLazy')
end

#optionalObject



430
431
432
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 430

def optional
  quantifier_element('optional')
end

#quantifier_element(type_fn_name) ⇒ Object



419
420
421
422
423
424
425
426
427
428
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 419

def quantifier_element(type_fn_name)
  n = clone
  current_frame = n.get_current_frame
  if current_frame[:quantifier]
    raise StandardError, "cannot quantify regular expression with '#{type_fn_name}' because it's already being quantified with '#{current_frame[:quantifier][:type]}'"
  end

  current_frame[:quantifier] = t[type_fn_name.to_sym]
  n
end

#range(a, b) ⇒ Object



606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 606

def range(a, b)
  str_a = a.to_s
  str_b = b.to_s

  assert(str_a.length == 1, "a must be a single character or number (got #{str_a})")
  assert(str_b.length == 1, "b must be a single character or number (got #{str_b})")
  assert(str_a[0].ord < str_b[0].ord, "a must have a smaller character value than b (a = #{str_a[0].ord}, b = #{str_b[0].ord})")

  n = clone
  element_value = t[:range].call([str_a, str_b])
  current_frame = n.get_current_frame

  current_frame[:elements].push(n.apply_quantifier(element_value))
  n
end

#single_lineObject



293
294
295
296
297
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 293

def single_line
  n = clone
  n.state[:flags][:s] = true
  n
end

#start_of_inputObject



505
506
507
508
509
510
511
512
513
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 505

def start_of_input
  assert(!state[:hasDefinedStart], 'This regex already has a defined start of input')
  assert(!state[:hasDefinedEnd], 'Cannot define the start of input after the end of input')

  n = clone
  n.state[:hasDefinedStart] = true
  n.get_current_element_array.push(t[:startOfInput])
  n
end

#stickyObject



280
281
282
283
284
285
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 280

def sticky
  # warn("Warning: Ruby does not have a sticky option")
  n = clone
  n.state[:flags][:y] = true
  n
end

#string(str) ⇒ Object



579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 579

def string(str)
  assert('' != str, 'str cannot be an empty string')
  n = clone

  element_value =
    if str.length > 1
      t[:string].call(escape_special(str))
    else
      t[:char].call(str)
    end

  current_frame = n.get_current_frame
  current_frame[:elements].push(n.apply_quantifier(element_value))

  n
end

#subexpression(expr, opts = {}) ⇒ Object



713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 713

def subexpression(expr, opts = {})
  assert(expr.is_a?(SuperExpressiveRuby), 'expr must be a SuperExpressive instance')
  assert(
    expr.state[:stack].length === 1,
    'Cannot call subexpression with a not yet fully specified regex object.' +
    "\n(Try adding a .end() call to match the '#{expr.get_current_frame[:type][:type]}' on the subexpression)\n"
  )

  options = apply_subexpression_defaults(opts)

  expr_n = expr.clone
  expr_n.state = expr.state.deep_dup
  n = clone
  additional_capture_groups = 0

  expr_frame = expr_n.get_current_frame
  closure = proc { additional_capture_groups += 1 }

  expr_frame[:elements] = expr_frame[:elements].map do |e|
    merge_subexpression(e, options, n, closure)
  end

  n.state[:totalCaptureGroups] += additional_capture_groups

  unless options[:ignoreFlags]
    expr_n.state[:flags].to_a.each do |e|
      flag_name = e[0]
      enabled = e[1]
      n.state[:flags][flag_name] = enabled || n.state[:flags][flag_name]
    end
  end

  current_frame = n.get_current_frame
  current_frame[:elements].push(n.apply_quantifier(t[:subexpression].call(expr_frame[:elements])))
  n
end

#tObject



248
249
250
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 248

def t
  @@t
end

#tabObject



349
350
351
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 349

def tab
  match_element(t[:tab])
end

#to_regexObject



755
756
757
758
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 755

def to_regex
  pattern, flags = get_regex_pattern_and_flags
  Regexp.new(pattern, flags)
end

#to_regex_stringObject



750
751
752
753
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 750

def to_regex_string
  pattern, flags = get_regex_pattern_and_flags
  Regexp.new(pattern, flags).to_s
end

#track_named_group(name) ⇒ Object



400
401
402
403
404
405
406
407
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 400

def track_named_group(name)
  assert(name.is_a?(String), "name must be a string (got #{name})")
  assert(name.length > 0, 'name must be at least one character')
  assert(!state[:namedGroups].include?(name), "cannot use #{name} again for a capture group")
  assert(name.scan(NamedGroupRegex).any?, "name '#{name}' is not valid (only letters, numbers, and underscores)")

  state[:namedGroups].push name
end

#unicodeObject



287
288
289
290
291
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 287

def unicode
  n = clone
  n.state[:flags][:u] = true
  n
end

#whitespace_charObject



309
310
311
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 309

def whitespace_char
  match_element(t[:whitespaceChar])
end

#wordObject



325
326
327
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 325

def word
  match_element(t[:word])
end

#word_boundaryObject



333
334
335
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 333

def word_boundary
  match_element(t[:wordBoundary])
end

#zero_or_moreObject



434
435
436
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 434

def zero_or_more
  quantifier_element('zeroOrMore')
end

#zero_or_more_lazyObject



438
439
440
# File 'lib/super-expressive-ruby/super-expressive-ruby.rb', line 438

def zero_or_more_lazy
  quantifier_element('zeroOrMoreLazy')
end