Pattern.coffee 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. # Pattern is a zero-conflict wrapper extending RegExp features
  2. # in order to make YAML parsing regex more expressive.
  3. #
  4. class Pattern
  5. # @property [RegExp] The RegExp instance
  6. regex: null
  7. # @property [String] The raw regex string
  8. rawRegex: null
  9. # @property [String] The cleaned regex string (used to create the RegExp instance)
  10. cleanedRegex: null
  11. # @property [Object] The dictionary mapping names to capturing bracket numbers
  12. mapping: null
  13. # Constructor
  14. #
  15. # @param [String] rawRegex The raw regex string defining the pattern
  16. #
  17. constructor: (rawRegex, modifiers = '') ->
  18. cleanedRegex = ''
  19. len = rawRegex.length
  20. mapping = null
  21. # Cleanup raw regex and compute mapping
  22. capturingBracketNumber = 0
  23. i = 0
  24. while i < len
  25. _char = rawRegex.charAt(i)
  26. if _char is '\\'
  27. # Ignore next character
  28. cleanedRegex += rawRegex[i..i+1]
  29. i++
  30. else if _char is '('
  31. # Increase bracket number, only if it is capturing
  32. if i < len - 2
  33. part = rawRegex[i..i+2]
  34. if part is '(?:'
  35. # Non-capturing bracket
  36. i += 2
  37. cleanedRegex += part
  38. else if part is '(?<'
  39. # Capturing bracket with possibly a name
  40. capturingBracketNumber++
  41. i += 2
  42. name = ''
  43. while i + 1 < len
  44. subChar = rawRegex.charAt(i + 1)
  45. if subChar is '>'
  46. cleanedRegex += '('
  47. i++
  48. if name.length > 0
  49. # Associate a name with a capturing bracket number
  50. mapping ?= {}
  51. mapping[name] = capturingBracketNumber
  52. break
  53. else
  54. name += subChar
  55. i++
  56. else
  57. cleanedRegex += _char
  58. capturingBracketNumber++
  59. else
  60. cleanedRegex += _char
  61. else
  62. cleanedRegex += _char
  63. i++
  64. @rawRegex = rawRegex
  65. @cleanedRegex = cleanedRegex
  66. @regex = new RegExp @cleanedRegex, 'g'+modifiers.replace('g', '')
  67. @mapping = mapping
  68. # Executes the pattern's regex and returns the matching values
  69. #
  70. # @param [String] str The string to use to execute the pattern
  71. #
  72. # @return [Array] The matching values extracted from capturing brackets or null if nothing matched
  73. #
  74. exec: (str) ->
  75. @regex.lastIndex = 0
  76. matches = @regex.exec str
  77. if not matches?
  78. return null
  79. if @mapping?
  80. for name, index of @mapping
  81. matches[name] = matches[index]
  82. return matches
  83. # Tests the pattern's regex
  84. #
  85. # @param [String] str The string to use to test the pattern
  86. #
  87. # @return [Boolean] true if the string matched
  88. #
  89. test: (str) ->
  90. @regex.lastIndex = 0
  91. return @regex.test str
  92. # Replaces occurences matching with the pattern's regex with replacement
  93. #
  94. # @param [String] str The source string to perform replacements
  95. # @param [String] replacement The string to use in place of each replaced occurence.
  96. #
  97. # @return [String] The replaced string
  98. #
  99. replace: (str, replacement) ->
  100. @regex.lastIndex = 0
  101. return str.replace @regex, replacement
  102. # Replaces occurences matching with the pattern's regex with replacement and
  103. # get both the replaced string and the number of replaced occurences in the string.
  104. #
  105. # @param [String] str The source string to perform replacements
  106. # @param [String] replacement The string to use in place of each replaced occurence.
  107. # @param [Integer] limit The maximum number of occurences to replace (0 means infinite number of occurences)
  108. #
  109. # @return [Array] A destructurable array containing the replaced string and the number of replaced occurences. For instance: ["my replaced string", 2]
  110. #
  111. replaceAll: (str, replacement, limit = 0) ->
  112. @regex.lastIndex = 0
  113. count = 0
  114. while @regex.test(str) and (limit is 0 or count < limit)
  115. @regex.lastIndex = 0
  116. str = str.replace @regex, replacement
  117. count++
  118. return [str, count]
  119. module.exports = Pattern