/src/lib/regular_expression/regular_expression.e

http://github.com/tybor/Liberty · Specman e · 799 lines · 581 code · 52 blank · 166 comment · 23 complexity · 4609aef74b10375870996f3b6412dc02 MD5 · raw file

  1. -- This file is part of a Liberty Eiffel library.
  2. -- See the full copyright at the end.
  3. --
  4. deferred class REGULAR_EXPRESSION
  5. --
  6. -- Regular expression matching and substitution capabilities.
  7. -- Use REGULAR_EXPRESSION_BUILDER to create REGULAR_EXPRESSION objects.
  8. --
  9. -- See tutorial/regular_expression for usage.
  10. --
  11. feature {ANY} -- matching capabilities
  12. match (text: ABSTRACT_STRING): BOOLEAN
  13. -- Returns True if `Current' regular_expression can match the `text'.
  14. --
  15. -- See also `match_next', `match_from', `last_match_succeeded', `last_match_first_index'.
  16. require
  17. text /= Void
  18. save_matching_text(text)
  19. do
  20. Result := match_from(text, text.lower)
  21. ensure
  22. Result = last_match_succeeded
  23. Result implies valid_substrings(text)
  24. Result implies last_match_first_index.in_range(text.lower, text.upper + 1)
  25. Result implies last_match_first_index <= last_match_last_index + 1
  26. end
  27. match_from (text: ABSTRACT_STRING; first_index: INTEGER): BOOLEAN
  28. -- Returns True if `Current' regular_expression can match the `text' starting from `first_index'.
  29. --
  30. -- See also `match', `last_match_succeeded', `last_match_first_index'.
  31. require
  32. text /= Void
  33. first_index.in_range(text.lower, text.upper + 1)
  34. save_matching_text(text)
  35. deferred
  36. ensure
  37. Result = last_match_succeeded
  38. Result implies valid_substrings(text)
  39. Result implies last_match_first_index >= first_index
  40. Result implies last_match_first_index.in_range(text.lower, text.upper + 1)
  41. Result implies last_match_first_index <= last_match_last_index + 1
  42. end
  43. match_next (text: ABSTRACT_STRING): BOOLEAN
  44. -- Returns True if `Current' regular_expression can match the same `text' one more time.
  45. -- Must be called after a successful `match' or `math_from' or `match_next' using the same `text'.
  46. --
  47. -- See also `match', `match_from', `last_match_succeeded'.
  48. require
  49. text /= Void
  50. last_match_succeeded
  51. text.has_prefix(last_match_text)
  52. save_matching_text(text)
  53. do
  54. Result := match_from(text, last_match_last_index + 1)
  55. ensure
  56. Result = last_match_succeeded
  57. Result implies valid_substrings(text)
  58. Result implies last_match_first_index.in_range(text.lower, text.upper + 1)
  59. Result implies last_match_first_index <= last_match_last_index + 1
  60. end
  61. last_match_succeeded: BOOLEAN
  62. -- Did last match succeed?
  63. --
  64. -- See also `match', `match_from'.
  65. do
  66. Result := substrings_first_indexes.item(0) > 0
  67. end
  68. last_match_first_index: INTEGER
  69. -- The starting position in the text where starts the sub-string who is matching the whole pattern.
  70. --
  71. -- See also `match', `match_from'.
  72. require
  73. last_match_succeeded
  74. do
  75. Result := substrings_first_indexes.item(0)
  76. ensure
  77. Result > 0
  78. end
  79. last_match_last_index: INTEGER
  80. -- The last position in the text where starts the sub-string who is matching the whole pattern.
  81. --
  82. -- See also `match', `match_from'.
  83. require
  84. last_match_succeeded
  85. do
  86. Result := substrings_last_indexes.item(0)
  87. ensure
  88. Result + 1 >= last_match_first_index
  89. end
  90. last_match_count: INTEGER
  91. -- Length of the string matching the whole pattern.
  92. --
  93. -- See also `last_match_first_index', `last_match_last_index', `match', `match_from'.
  94. require
  95. last_match_succeeded
  96. do
  97. Result := last_match_last_index - last_match_first_index + 1
  98. ensure
  99. Result >= 0
  100. definition: Result = last_match_last_index - last_match_first_index + 1
  101. end
  102. group_count: INTEGER
  103. -- Number of groups in `Current' regular expression.
  104. --
  105. -- See also `ith_group_matched', `ith_group_first_index'.
  106. do
  107. Result := substrings_first_indexes.upper
  108. end
  109. group_names: TRAVERSABLE[FIXED_STRING]
  110. -- The names of the matched named group.
  111. --
  112. -- See also `named_group_matched', `named_group_first_index'.
  113. do
  114. set_group_names_memory
  115. Result := group_names_memory
  116. ensure
  117. Result /= Void
  118. Result.for_all(agent (s: FIXED_STRING): BOOLEAN do Result := s /= Void end (?))
  119. Result.count = substrings_names.count
  120. Result.for_all(agent (s: FIXED_STRING): BOOLEAN do Result := substrings_names.fast_has(s) end (?))
  121. Result.for_all(agent (s: FIXED_STRING): BOOLEAN do Result := has_group_name(s) end (?))
  122. end
  123. has_group_name (name: ABSTRACT_STRING): BOOLEAN
  124. -- Is there a group names `name'?
  125. require
  126. name /= Void
  127. do
  128. set_group_names_memory
  129. Result := group_names_memory.fast_has(name.intern)
  130. end
  131. ith_group_matched (i: INTEGER): BOOLEAN
  132. -- Did the `i'th group match during last match?
  133. --
  134. -- See also `group_count', `ith_group_first_index'.
  135. require
  136. i.in_range(0, group_count)
  137. last_match_succeeded
  138. do
  139. Result := substrings_first_indexes.item(i) > 0
  140. end
  141. named_group_matched (name: ABSTRACT_STRING): BOOLEAN
  142. -- Did the group named `name' match during the last match?
  143. --
  144. -- See also `group_names', `named_group_first_index'.
  145. require
  146. name /= Void
  147. has_group_name(name)
  148. do
  149. Result := ith_group_matched(substrings_names.fast_at(name.intern))
  150. end
  151. ith_group_first_index (i: INTEGER): INTEGER
  152. -- First index in the last matching text of the `i'th group.
  153. --
  154. -- See also `group_count'.
  155. require
  156. i.in_range(0, group_count)
  157. last_match_succeeded
  158. ith_group_matched(i)
  159. do
  160. Result := substrings_first_indexes.item(i)
  161. ensure
  162. Result.in_range(0, last_match_text.upper + 1)
  163. end
  164. named_group_first_index (name: ABSTRACT_STRING): INTEGER
  165. -- First index in the last matching text of the group named `name'.
  166. --
  167. -- See also `group_names'.
  168. require
  169. name /= Void
  170. has_group_name(name)
  171. last_match_succeeded
  172. named_group_matched(name)
  173. do
  174. Result := ith_group_first_index(substrings_names.fast_at(name.intern))
  175. end
  176. ith_group_last_index (i: INTEGER): INTEGER
  177. -- Last index in the last matching text of the `i'th group.
  178. --
  179. -- See also `ith_group_first_index', `group_count'.
  180. require
  181. i.in_range(0, group_count)
  182. last_match_succeeded
  183. ith_group_matched(i)
  184. do
  185. Result := substrings_last_indexes.item(i)
  186. ensure
  187. Result.in_range(ith_group_first_index(i) - 1, last_match_text.upper)
  188. end
  189. named_group_last_index (name: ABSTRACT_STRING): INTEGER
  190. -- Last index in the last matching text of the group named `name'.
  191. --
  192. -- See also `named_group_first_index', `group_names'.
  193. require
  194. name /= Void
  195. has_group_name(name)
  196. last_match_succeeded
  197. named_group_matched(name)
  198. do
  199. Result := ith_group_last_index(substrings_names.fast_at(name.intern))
  200. end
  201. ith_group_count (i: INTEGER): INTEGER
  202. -- Length of the `i'th group of `Current' in the last matching.
  203. --
  204. -- See also `ith_group_first_index', `append_ith_group', `group_count'.
  205. require
  206. i.in_range(0, group_count)
  207. last_match_succeeded
  208. ith_group_matched(i)
  209. do
  210. Result := substrings_last_indexes.item(i) - substrings_first_indexes.item(i) + 1
  211. ensure
  212. Result >= 0
  213. Result = ith_group_last_index(i) - ith_group_first_index(i) + 1
  214. end
  215. named_group_count (name: ABSTRACT_STRING): INTEGER
  216. -- Length of the group named `name' in the last matching.
  217. --
  218. -- See also `named_group_first_index', `append_named_group', `group_names'.
  219. require
  220. name /= Void
  221. has_group_name(name)
  222. last_match_succeeded
  223. named_group_matched(name)
  224. local
  225. i: INTEGER
  226. do
  227. i := substrings_names.fast_at(name.intern)
  228. Result := substrings_last_indexes.item(i) - substrings_first_indexes.item(i) + 1
  229. ensure
  230. Result >= 0
  231. Result = named_group_last_index(name) - named_group_first_index(name) + 1
  232. end
  233. for_all_matched_named_groups (text: ABSTRACT_STRING; action: PROCEDURE[TUPLE[FIXED_STRING, STRING]])
  234. -- Call the `action' for each group that matched during the last match.
  235. -- The first action argument is the name of the group; the second is its content.
  236. -- The order of the action calls is the ascending order of the group definitions in the pattern.
  237. --
  238. -- Note: the same STRING objects may be reused, so be sure to copy them if you want to keep them.
  239. require
  240. text /= Void
  241. action /= Void
  242. last_match_succeeded
  243. text.has_prefix(last_match_text)
  244. local
  245. i: INTEGER; group_name: FIXED_STRING; group_data: STRING
  246. do
  247. from
  248. group_data := once ""
  249. i := 1
  250. until
  251. i > group_count
  252. loop
  253. if ith_group_matched(i) and then substrings_names.fast_has_value(i) then
  254. group_name := substrings_names.fast_key_at(i)
  255. group_data.clear_count
  256. append_ith_group(text, group_data, i)
  257. action.call([group_name, group_data])
  258. end
  259. i := i + 1
  260. end
  261. end
  262. append_heading_text (text: ABSTRACT_STRING; buffer: STRING)
  263. -- Append in `buffer' the text before the matching area.
  264. -- `text' is the same as used in last matching.
  265. --
  266. -- See also `append_pattern_text', `append_tailing_text', `append_ith_group'.
  267. require
  268. text /= Void
  269. buffer /= Void
  270. last_match_succeeded
  271. text.has_prefix(last_match_text)
  272. do
  273. buffer.append_substring(text, 1, substrings_first_indexes.item(0))
  274. ensure
  275. buffer.count = old buffer.count + last_match_first_index - 1
  276. end
  277. append_pattern_text (text: ABSTRACT_STRING; buffer: STRING)
  278. -- Append in `buffer' the text matching the pattern.
  279. -- `text' is the same as used in last matching.
  280. --
  281. -- See also `append_heading_text', `append_tailing_text', `append_ith_group'.
  282. require
  283. text /= Void
  284. buffer /= Void
  285. last_match_succeeded
  286. text.has_prefix(last_match_text)
  287. do
  288. buffer.append_substring(text, substrings_first_indexes.item(0), substrings_last_indexes.item(0))
  289. ensure
  290. buffer.count = old buffer.count + last_match_count
  291. end
  292. append_tailing_text (text: ABSTRACT_STRING; buffer: STRING)
  293. -- Append in `buffer' the text after the matching area.
  294. -- `text' is the same as used in last matching.
  295. --
  296. -- See also `append_heading_text', `append_pattern_text', `append_ith_group'.
  297. require
  298. text /= Void
  299. buffer /= Void
  300. last_match_succeeded
  301. text.is_equal(last_match_text)
  302. do
  303. buffer.append_substring(text, substrings_last_indexes.item(0) + 1, text.count)
  304. ensure
  305. buffer.count = old buffer.count + text.count - last_match_last_index
  306. end
  307. append_ith_group (text: ABSTRACT_STRING; buffer: STRING; i: INTEGER)
  308. -- Append in `buffer' the text of the `i'th group.
  309. -- `text' is the same as used in last matching.
  310. --
  311. -- See also `append_pattern_text', `group_count'.
  312. require
  313. text /= Void
  314. buffer /= Void
  315. last_match_succeeded
  316. text.is_equal(last_match_text)
  317. i.in_range(0, group_count)
  318. ith_group_matched(i)
  319. do
  320. buffer.append_substring(text, substrings_first_indexes.item(i), substrings_last_indexes.item(i))
  321. ensure
  322. buffer.count = old buffer.count + ith_group_count(i)
  323. end
  324. append_named_group (text: ABSTRACT_STRING; buffer: STRING; name: ABSTRACT_STRING)
  325. -- Append in `buffer' the text of the group named `name'.
  326. -- `text' is the same as used in last matching.
  327. --
  328. -- See also `append_pattern_text', `group_name'.
  329. require
  330. text /= Void
  331. buffer /= Void
  332. last_match_succeeded
  333. text.is_equal(last_match_text)
  334. name /= Void
  335. has_group_name(name)
  336. named_group_matched(name)
  337. local
  338. i: INTEGER
  339. do
  340. i := substrings_names.fast_at(name.intern)
  341. buffer.append_substring(text, substrings_first_indexes.item(i), substrings_last_indexes.item(i))
  342. ensure
  343. buffer.count = old buffer.count + named_group_count(name)
  344. end
  345. named_group_value (text, name: ABSTRACT_STRING): STRING
  346. -- Returns the text of the group named `name' (always the same STRING!)
  347. -- `text' is the same as used in last matching.
  348. --
  349. -- See also `append_named_group', `group_name'.
  350. require
  351. text /= Void
  352. last_match_succeeded
  353. text.is_equal(last_match_text)
  354. name /= Void
  355. has_group_name(name)
  356. named_group_matched(name)
  357. do
  358. Result := once ""
  359. Result.clear_count
  360. append_named_group(text, Result, name)
  361. end
  362. feature {ANY} -- substitution capabilities
  363. prepare_substitution (p: ABSTRACT_STRING)
  364. -- Set pattern `p' for substitution. If pattern `p' is not compatible with the `Current' regular
  365. -- expression, the `pattern_error_message' is updated as well as `pattern_error_position'.
  366. --
  367. -- See also `substitute_in', `substitute_for', `substitute_all_in', `substitute_all_for'.
  368. require
  369. p /= Void
  370. local
  371. in_verbatim_text: BOOLEAN; i: INTEGER
  372. do
  373. from
  374. if compiled_substitution_pattern = Void then
  375. create compiled_substitution_pattern.with_capacity(4)
  376. else
  377. compiled_substitution_pattern.clear_count
  378. end
  379. substitution_pattern_ready := True
  380. substitution_pattern.make_from_string(p)
  381. substrings_first_indexes.resize(0, substrings_first_indexes.upper)
  382. substrings_last_indexes.resize(0, substrings_last_indexes.upper)
  383. i := 1
  384. until
  385. i > substitution_pattern.count
  386. loop
  387. if substitution_pattern.item(i) = '\' and then i < substitution_pattern.count and then substitution_pattern.item(i + 1).is_digit then
  388. if in_verbatim_text then
  389. substrings_last_indexes.add_first(i - 1)
  390. substrings_last_indexes.reindex(substrings_last_indexes.lower - 1)
  391. in_verbatim_text := False
  392. end
  393. i := i + 1
  394. if substitution_pattern.item(i).value > substrings_first_indexes.upper then
  395. pattern_error_position := i
  396. pattern_error_message := once "Invalid reference for current pattern."
  397. substitution_pattern_ready := False
  398. i := substitution_pattern.count
  399. end
  400. compiled_substitution_pattern.add_last(substitution_pattern.item(i).value)
  401. else
  402. if substitution_pattern.item(i) = '\' and then i < substitution_pattern.count then
  403. substitution_pattern.remove(i)
  404. end
  405. if not in_verbatim_text then
  406. substrings_first_indexes.add_first(i)
  407. substrings_first_indexes.reindex(substrings_first_indexes.lower - 1)
  408. compiled_substitution_pattern.add_last(substrings_first_indexes.lower)
  409. in_verbatim_text := True
  410. end
  411. end
  412. i := i + 1
  413. end
  414. if in_verbatim_text then
  415. check
  416. i - 1 = substitution_pattern.count
  417. end
  418. substrings_last_indexes.add_first(i - 1)
  419. substrings_last_indexes.reindex(substrings_last_indexes.lower - 1)
  420. end
  421. ensure
  422. substitution_pattern_ready implies valid_substitution
  423. substitution_pattern_ready xor pattern_error_message /= Void
  424. end
  425. last_substitution: STRING
  426. -- You need to copy this STRING if you want to keep it.
  427. do
  428. Result := last_substitution_memory
  429. if Result = Void then
  430. create Result.make(128)
  431. last_substitution_memory := Result
  432. end
  433. end
  434. substitute_for (text: ABSTRACT_STRING)
  435. -- This call has to be preceded by a successful matching on the same text.
  436. -- Then the substitution is made on the matching part. The result is in `last_substitution'.
  437. --
  438. -- See also `prepare_substitution', `last_substitution', `substitute_in'.
  439. require
  440. can_substitute
  441. text /= Void
  442. text.is_equal(last_match_text)
  443. local
  444. i, first: INTEGER; src: ABSTRACT_STRING; index: INTEGER
  445. do
  446. from
  447. last_substitution.copy_substring(text, 1, last_match_first_index - 1)
  448. i := compiled_substitution_pattern.lower
  449. until
  450. i > compiled_substitution_pattern.upper
  451. loop
  452. index := compiled_substitution_pattern.item(i)
  453. if index < 0 then
  454. src := substitution_pattern
  455. else
  456. src := text
  457. end
  458. first := substrings_first_indexes.item(index)
  459. if first > 0 then
  460. last_substitution.append_substring(src, first, substrings_last_indexes.item(index))
  461. end
  462. i := i + 1
  463. end
  464. last_substitution.append_substring(text, last_match_last_index + 1, text.count)
  465. invalidate_last_match
  466. ensure
  467. last_substitution /= Void
  468. substitution_pattern_ready
  469. only_one_substitution_per_match: not can_substitute
  470. end
  471. substitute_in (text: STRING)
  472. -- This call has to be preceded by a successful matching on the same text.
  473. -- Then the substitution is made in `text' on the matching
  474. -- part (`text' is modified).
  475. --
  476. -- See also `prepare_substitution', `substitute_for'.
  477. require
  478. can_substitute
  479. text /= Void
  480. text.is_equal(last_match_text)
  481. do
  482. substitute_for(text)
  483. text.copy(last_substitution)
  484. ensure
  485. substitution_pattern_ready
  486. only_one_substitution_per_match: not can_substitute
  487. end
  488. substitute_all_for (text: ABSTRACT_STRING)
  489. -- Every matching part is substituted. No preliminary matching is required.
  490. -- The result is in `last_substitution'.
  491. --
  492. -- See also `prepare_substitution', `last_substitution', `substitute_all_in'.
  493. require
  494. substitution_pattern_ready
  495. text /= Void
  496. local
  497. text_pos: INTEGER
  498. do
  499. text_pos := substitute_all_without_tail(text)
  500. if text_pos = 1 then
  501. last_substitution.make_from_string(text)
  502. else
  503. last_substitution.append_substring(text, text_pos, text.count)
  504. end
  505. ensure
  506. last_substitution /= Void
  507. substitution_pattern_ready
  508. end
  509. substitute_all_in (text: STRING)
  510. -- Every matching part is substituted. No preliminary matching is required.
  511. -- `text' is modified according to the substitutions is any.
  512. --
  513. -- See also `prepare_substitution', `last_substitution', `substitute_all_for'.
  514. require
  515. substitution_pattern_ready
  516. text /= Void
  517. local
  518. text_pos: INTEGER
  519. do
  520. text_pos := substitute_all_without_tail(text)
  521. if text_pos /= 1 then
  522. text.replace_substring(last_substitution, 1, text_pos - 1)
  523. end
  524. ensure
  525. substitution_pattern_ready
  526. end
  527. can_substitute: BOOLEAN
  528. -- Substitution is only allowed when some valid substitution
  529. -- pattern has been registered and after a successful pattern matching.
  530. --
  531. -- See also `substitute_in', `substitute_for'.
  532. do
  533. Result := substitution_pattern_ready and last_match_succeeded
  534. ensure
  535. definition: Result = (substitution_pattern_ready and last_match_succeeded)
  536. end
  537. substitution_pattern_ready: BOOLEAN -- True if some valid substitution pattern has been registered.
  538. feature {ANY} -- Error informations
  539. pattern_error_message: STRING
  540. -- Error message for the substitution pattern.
  541. --
  542. -- See also `prepare_substitution'.
  543. pattern_error_position: INTEGER
  544. -- Error position in the substitution pattern.
  545. --
  546. -- See also `prepare_substitution'.
  547. feature {}
  548. save_matching_text (text: ABSTRACT_STRING): BOOLEAN
  549. -- Used in assertion only. Side-effect: save the text
  550. do
  551. last_match_text.make_from_string(text)
  552. Result := True
  553. ensure
  554. Result -- Assertion only feature
  555. end
  556. invalidate_last_match
  557. -- Used to prevent 2 substitutions without intermediate matching.
  558. require
  559. last_match_succeeded
  560. do
  561. substrings_first_indexes.put(0, 0)
  562. ensure
  563. not last_match_succeeded
  564. not can_substitute
  565. end
  566. valid_substrings (text: ABSTRACT_STRING): BOOLEAN
  567. -- Used in assertion only.
  568. require
  569. last_match_succeeded
  570. local
  571. i, first, last: INTEGER
  572. do
  573. from
  574. i := 0
  575. first := substrings_first_indexes.item(0)
  576. last := substrings_last_indexes.item(0)
  577. Result := text.valid_index(first)
  578. if not Result then
  579. Result := text.upper + 1 = first and then first = last + 1
  580. elseif last < first then
  581. Result := first = last + 1
  582. else
  583. Result := text.valid_index(last) and then first <= last
  584. end
  585. until
  586. not Result or i >= substrings_first_indexes.upper
  587. loop
  588. i := i + 1
  589. first := substrings_first_indexes.item(i)
  590. last := substrings_last_indexes.item(i)
  591. Result := first = 0
  592. if not Result then
  593. Result := first.in_range(last_match_text.lower, last_match_text.upper + 1)
  594. Result := Result and then last.in_range(first - 1, last_match_text.upper)
  595. end
  596. end
  597. ensure
  598. Result -- Method for assertion only (error position is element item `i')
  599. end
  600. valid_substitution: BOOLEAN
  601. -- Used in assertion only.
  602. local
  603. i, size: INTEGER
  604. do
  605. if substrings_first_indexes.valid_index(-1) then
  606. from
  607. i := -1
  608. Result := substitution_pattern.valid_index(substrings_first_indexes.item(-1))
  609. until
  610. not Result or i < substrings_first_indexes.lower
  611. loop
  612. Result := substrings_first_indexes.item(i) <= substrings_last_indexes.item(i)
  613. if substrings_first_indexes.valid_index(i - 1) then
  614. Result := Result and then substrings_last_indexes.item(i) < substrings_first_indexes.item(i - 1)
  615. end
  616. size := size + substrings_last_indexes.item(i) - substrings_first_indexes.item(i) + 1
  617. i := i - 1
  618. end
  619. Result := Result and then substitution_pattern.valid_index(substrings_last_indexes.first)
  620. else
  621. Result := compiled_substitution_pattern.count = 0
  622. end
  623. from
  624. i := compiled_substitution_pattern.upper
  625. until
  626. i < compiled_substitution_pattern.lower
  627. loop
  628. Result := Result and then substrings_last_indexes.valid_index(compiled_substitution_pattern.item(i))
  629. i := i - 1
  630. end
  631. if Result then
  632. Result := substitution_pattern.count - size = (compiled_substitution_pattern.count - -substrings_last_indexes.lower) * 2
  633. end
  634. ensure
  635. Result -- Method for assertion only
  636. end
  637. substitute_all_without_tail (text: ABSTRACT_STRING): INTEGER
  638. -- Substitute all matching parts from `text'. The resulting text
  639. -- in `last_substitution', except the end. The part of `text' from
  640. -- `Result' up to the end is not copied.
  641. require
  642. substitution_pattern_ready
  643. text /= Void
  644. local
  645. i: INTEGER; src: ABSTRACT_STRING; index: INTEGER
  646. do
  647. from
  648. last_substitution.clear_count
  649. Result := 1
  650. until
  651. not match_from(text, Result)
  652. loop
  653. last_substitution.append_substring(text, Result, last_match_first_index - 1)
  654. from
  655. i := compiled_substitution_pattern.lower
  656. until
  657. i > compiled_substitution_pattern.upper
  658. loop
  659. index := compiled_substitution_pattern.item(i)
  660. if index < 0 then
  661. src := substitution_pattern
  662. else
  663. src := text
  664. end
  665. last_substitution.append_substring(src, substrings_first_indexes.item(index), substrings_last_indexes.item(index))
  666. i := i + 1
  667. end
  668. Result := last_match_last_index + 1
  669. end
  670. ensure
  671. last_substitution /= Void
  672. substitution_pattern_ready
  673. end
  674. substrings_first_indexes: ARRAY[INTEGER]
  675. -- Item(0) is the starting position in the text where
  676. -- starts the substring who is matching the whole pattern.
  677. -- Next elements are the starting positions in the text of
  678. -- substrings matching sub-elements of the pattern.
  679. --
  680. -- Elements before item(0) refers to positions in the
  681. -- `substitution_pattern'. They are stored in reverse order,
  682. -- the first verbatim string being at index -1, the
  683. -- second one at index -2...
  684. substrings_last_indexes: ARRAY[INTEGER]
  685. -- The ending position of the string starting at position
  686. -- found in `matching_position' at the same index.
  687. substrings_names: BIJECTIVE_DICTIONARY[INTEGER, FIXED_STRING]
  688. -- The names of the groups, if those names exist
  689. group_names_memory: COLLECTION[FIXED_STRING]
  690. -- Cache for `group_names'
  691. substitution_pattern: STRING
  692. once
  693. create Result.make_empty
  694. end
  695. compiled_substitution_pattern: FAST_ARRAY[INTEGER]
  696. -- This array describe the substitution text as a suite of
  697. -- strings from `substrings_first_indexes'.
  698. last_match_text: STRING
  699. -- For assertion only.
  700. do
  701. Result := last_match_text_memory
  702. if Result = Void then
  703. create Result.make(128)
  704. last_match_text_memory := Result
  705. end
  706. end
  707. set_group_names_memory
  708. do
  709. if group_names_memory = Void then
  710. create {FAST_ARRAY[FIXED_STRING]} group_names_memory.with_capacity(substrings_names.count)
  711. else
  712. group_names_memory.clear_count
  713. end
  714. substrings_names.key_map_in(group_names_memory)
  715. end
  716. last_match_text_memory: STRING -- For assertion only.
  717. last_substitution_memory: STRING
  718. invariant
  719. substrings_first_indexes.lower = substrings_last_indexes.lower
  720. substrings_first_indexes.upper = substrings_last_indexes.upper
  721. substrings_names.is_empty or else (substrings_names.count <= substrings_first_indexes.count
  722. and then substrings_names.for_all(agent (i: INTEGER; s: FIXED_STRING): BOOLEAN
  723. do
  724. Result := s /= Void
  725. and then substrings_first_indexes.valid_index(i)
  726. end (?, ?)))
  727. end -- class REGULAR_EXPRESSION
  728. --
  729. -- Copyright (C) 2009-2017: by all the people cited in the AUTHORS file.
  730. --
  731. -- Permission is hereby granted, free of charge, to any person obtaining a copy
  732. -- of this software and associated documentation files (the "Software"), to deal
  733. -- in the Software without restriction, including without limitation the rights
  734. -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  735. -- copies of the Software, and to permit persons to whom the Software is
  736. -- furnished to do so, subject to the following conditions:
  737. --
  738. -- The above copyright notice and this permission notice shall be included in
  739. -- all copies or substantial portions of the Software.
  740. --
  741. -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  742. -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  743. -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  744. -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  745. -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  746. -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  747. -- THE SOFTWARE.