From 3eda59e9752baec109241983bb7f946db31b74d1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 4 Jun 2024 12:40:08 +0900 Subject: [PATCH] Sync strscan HEAD again. https://github.com/ruby/strscan/pull/99 split document with multi-byte chars. --- .rdoc_options | 2 + doc/strscan/helper_methods.md | 128 +++ doc/strscan/link_refs.txt | 17 + doc/strscan/methods/get_byte.md | 30 + doc/strscan/methods/get_charpos.md | 19 + doc/strscan/methods/get_pos.md | 14 + doc/strscan/methods/getch.md | 43 + doc/strscan/methods/scan.md | 51 ++ doc/strscan/methods/scan_until.md | 52 ++ doc/strscan/methods/set_pos.md | 27 + doc/strscan/methods/skip.md | 43 + doc/strscan/methods/skip_until.md | 49 ++ doc/strscan/methods/terminate.md | 30 + doc/strscan/strscan.md | 543 ++++++++++++ ext/strscan/strscan.c | 1277 +++++++++++++++++++--------- ext/strscan/strscan.gemspec | 5 + tool/sync_default_gems.rb | 1 + 17 files changed, 1908 insertions(+), 423 deletions(-) create mode 100644 doc/strscan/helper_methods.md create mode 100644 doc/strscan/link_refs.txt create mode 100644 doc/strscan/methods/get_byte.md create mode 100644 doc/strscan/methods/get_charpos.md create mode 100644 doc/strscan/methods/get_pos.md create mode 100644 doc/strscan/methods/getch.md create mode 100644 doc/strscan/methods/scan.md create mode 100644 doc/strscan/methods/scan_until.md create mode 100644 doc/strscan/methods/set_pos.md create mode 100644 doc/strscan/methods/skip.md create mode 100644 doc/strscan/methods/skip_until.md create mode 100644 doc/strscan/methods/terminate.md create mode 100644 doc/strscan/strscan.md diff --git a/.rdoc_options b/.rdoc_options index 57e03c9dd7..4dfbfa140c 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -5,3 +5,5 @@ encoding: UTF-8 main_page: README.md title: Documentation for Ruby development version visibility: :private +rdoc_include: +- doc diff --git a/doc/strscan/helper_methods.md b/doc/strscan/helper_methods.md new file mode 100644 index 0000000000..6555a2ce66 --- /dev/null +++ b/doc/strscan/helper_methods.md @@ -0,0 +1,128 @@ +## Helper Methods + +These helper methods display values returned by scanner's methods. + +### `put_situation(scanner)` + +Display scanner's situation: + +- Byte position (`#pos`). +- Character position (`#charpos`) +- Target string (`#rest`) and size (`#rest_size`). + +``` +scanner = StringScanner.new('foobarbaz') +scanner.scan(/foo/) +put_situation(scanner) +# Situation: +# pos: 3 +# charpos: 3 +# rest: "barbaz" +# rest_size: 6 +``` + +### `put_match_values(scanner)` + +Display the scanner's match values: + +``` +scanner = StringScanner.new('Fri Dec 12 1975 14:39') +pattern = /(?\w+) (?\w+) (?\d+) / +scanner.match?(pattern) +put_match_values(scanner) +# Basic match values: +# matched?: true +# matched_size: 11 +# pre_match: "" +# matched : "Fri Dec 12 " +# post_match: "1975 14:39" +# Captured match values: +# size: 4 +# captures: ["Fri", "Dec", "12"] +# named_captures: {"wday"=>"Fri", "month"=>"Dec", "day"=>"12"} +# values_at: ["Fri Dec 12 ", "Fri", "Dec", "12", nil] +# []: +# [0]: "Fri Dec 12 " +# [1]: "Fri" +# [2]: "Dec" +# [3]: "12" +# [4]: nil +``` + +### `match_values_cleared?(scanner)` + +Returns whether the scanner's match values are all properly cleared: + +``` +scanner = StringScanner.new('foobarbaz') +match_values_cleared?(scanner) # => true +put_match_values(scanner) +# Basic match values: +# matched?: false +# matched_size: nil +# pre_match: nil +# matched : nil +# post_match: nil +# Captured match values: +# size: nil +# captures: nil +# named_captures: {} +# values_at: nil +# [0]: nil +scanner.scan(/foo/) +match_values_cleared?(scanner) # => false +``` + +## The Code + +``` +def put_situation(scanner) + puts '# Situation:' + puts "# pos: #{scanner.pos}" + puts "# charpos: #{scanner.charpos}" + puts "# rest: #{scanner.rest.inspect}" + puts "# rest_size: #{scanner.rest_size}" +end +``` + +``` +def put_match_values(scanner) + puts '# Basic match values:' + puts "# matched?: #{scanner.matched?}" + value = scanner.matched_size || 'nil' + puts "# matched_size: #{value}" + puts "# pre_match: #{scanner.pre_match.inspect}" + puts "# matched : #{scanner.matched.inspect}" + puts "# post_match: #{scanner.post_match.inspect}" + puts '# Captured match values:' + puts "# size: #{scanner.size}" + puts "# captures: #{scanner.captures}" + puts "# named_captures: #{scanner.named_captures}" + if scanner.size.nil? + puts "# values_at: #{scanner.values_at(0)}" + puts "# [0]: #{scanner[0]}" + else + puts "# values_at: #{scanner.values_at(*(0..scanner.size))}" + puts "# []:" + scanner.size.times do |i| + puts "# [#{i}]: #{scanner[i].inspect}" + end + end +end +``` + +``` +def match_values_cleared?(scanner) + scanner.matched? == false && + scanner.matched_size.nil? && + scanner.matched.nil? && + scanner.pre_match.nil? && + scanner.post_match.nil? && + scanner.size.nil? && + scanner[0].nil? && + scanner.captures.nil? && + scanner.values_at(0..1).nil? && + scanner.named_captures == {} +end +``` + diff --git a/doc/strscan/link_refs.txt b/doc/strscan/link_refs.txt new file mode 100644 index 0000000000..19f6f7ce5c --- /dev/null +++ b/doc/strscan/link_refs.txt @@ -0,0 +1,17 @@ +[1]: rdoc-ref:StringScanner@Stored+String +[2]: rdoc-ref:StringScanner@Byte+Position+-28Position-29 +[3]: rdoc-ref:StringScanner@Target+Substring +[4]: rdoc-ref:StringScanner@Setting+the+Target+Substring +[5]: rdoc-ref:StringScanner@Traversing+the+Target+Substring +[6]: https://docs.ruby-lang.org/en/master/Regexp.html +[7]: rdoc-ref:StringScanner@Character+Position +[8]: https://docs.ruby-lang.org/en/master/String.html#method-i-5B-5D +[9]: rdoc-ref:StringScanner@Match+Values +[10]: rdoc-ref:StringScanner@Fixed-Anchor+Property +[11]: rdoc-ref:StringScanner@Positions +[13]: rdoc-ref:StringScanner@Captured+Match+Values +[14]: rdoc-ref:StringScanner@Querying+the+Target+Substring +[15]: rdoc-ref:StringScanner@Searching+the+Target+Substring +[16]: https://docs.ruby-lang.org/en/master/Regexp.html#class-Regexp-label-Groups+and+Captures +[17]: rdoc-ref:StringScanner@Matching +[18]: rdoc-ref:StringScanner@Basic+Match+Values diff --git a/doc/strscan/methods/get_byte.md b/doc/strscan/methods/get_byte.md new file mode 100644 index 0000000000..2f23be1899 --- /dev/null +++ b/doc/strscan/methods/get_byte.md @@ -0,0 +1,30 @@ +call-seq: + get_byte -> byte_as_character or nil + +Returns the next byte, if available: + +- If the [position][2] + is not at the end of the [stored string][1]: + + - Returns the next byte. + - Increments the [byte position][2]. + - Adjusts the [character position][7]. + + ``` + scanner = StringScanner.new(HIRAGANA_TEXT) + # => # + scanner.string # => "こんにちは" + [scanner.get_byte, scanner.pos, scanner.charpos] # => ["\xE3", 1, 1] + [scanner.get_byte, scanner.pos, scanner.charpos] # => ["\x81", 2, 2] + [scanner.get_byte, scanner.pos, scanner.charpos] # => ["\x93", 3, 1] + [scanner.get_byte, scanner.pos, scanner.charpos] # => ["\xE3", 4, 2] + [scanner.get_byte, scanner.pos, scanner.charpos] # => ["\x82", 5, 3] + [scanner.get_byte, scanner.pos, scanner.charpos] # => ["\x93", 6, 2] + ``` + +- Otherwise, returns `nil`, and does not change the positions. + + ``` + scanner.terminate + [scanner.get_byte, scanner.pos, scanner.charpos] # => [nil, 15, 5] + ``` diff --git a/doc/strscan/methods/get_charpos.md b/doc/strscan/methods/get_charpos.md new file mode 100644 index 0000000000..f77563c860 --- /dev/null +++ b/doc/strscan/methods/get_charpos.md @@ -0,0 +1,19 @@ +call-seq: + charpos -> character_position + +Returns the [character position][7] (initially zero), +which may be different from the [byte position][2] +given by method #pos: + +``` +scanner = StringScanner.new(HIRAGANA_TEXT) +scanner.string # => "こんにちは" +scanner.getch # => "こ" # 3-byte character. +scanner.getch # => "ん" # 3-byte character. +put_situation(scanner) +# Situation: +# pos: 6 +# charpos: 2 +# rest: "にちは" +# rest_size: 9 +``` diff --git a/doc/strscan/methods/get_pos.md b/doc/strscan/methods/get_pos.md new file mode 100644 index 0000000000..56bcef3274 --- /dev/null +++ b/doc/strscan/methods/get_pos.md @@ -0,0 +1,14 @@ +call-seq: + pos -> byte_position + +Returns the integer [byte position][2], +which may be different from the [character position][7]: + +``` +scanner = StringScanner.new(HIRAGANA_TEXT) +scanner.string # => "こんにちは" +scanner.pos # => 0 +scanner.getch # => "こ" # 3-byte character. +scanner.charpos # => 1 +scanner.pos # => 3 +``` diff --git a/doc/strscan/methods/getch.md b/doc/strscan/methods/getch.md new file mode 100644 index 0000000000..b57732ad7c --- /dev/null +++ b/doc/strscan/methods/getch.md @@ -0,0 +1,43 @@ +call-seq: + getch -> character or nil + +Returns the next (possibly multibyte) character, +if available: + +- If the [position][2] + is at the beginning of a character: + + - Returns the character. + - Increments the [character position][7] by 1. + - Increments the [byte position][2] + by the size (in bytes) of the character. + + ``` + scanner = StringScanner.new(HIRAGANA_TEXT) + scanner.string # => "こんにちは" + [scanner.getch, scanner.pos, scanner.charpos] # => ["こ", 3, 1] + [scanner.getch, scanner.pos, scanner.charpos] # => ["ん", 6, 2] + [scanner.getch, scanner.pos, scanner.charpos] # => ["に", 9, 3] + [scanner.getch, scanner.pos, scanner.charpos] # => ["ち", 12, 4] + [scanner.getch, scanner.pos, scanner.charpos] # => ["は", 15, 5] + [scanner.getch, scanner.pos, scanner.charpos] # => [nil, 15, 5] + ``` + +- If the [position][2] is within a multi-byte character + (that is, not at its beginning), + behaves like #get_byte (returns a 1-byte character): + + ``` + scanner.pos = 1 + [scanner.getch, scanner.pos, scanner.charpos] # => ["\x81", 2, 2] + [scanner.getch, scanner.pos, scanner.charpos] # => ["\x93", 3, 1] + [scanner.getch, scanner.pos, scanner.charpos] # => ["ん", 6, 2] + ``` + +- If the [position][2] is at the end of the [stored string][1], + returns `nil` and does not modify the positions: + + ``` + scanner.terminate + [scanner.getch, scanner.pos, scanner.charpos] # => [nil, 15, 5] + ``` diff --git a/doc/strscan/methods/scan.md b/doc/strscan/methods/scan.md new file mode 100644 index 0000000000..714fa9910a --- /dev/null +++ b/doc/strscan/methods/scan.md @@ -0,0 +1,51 @@ +call-seq: + scan(pattern) -> substring or nil + +Attempts to [match][17] the given `pattern` +at the beginning of the [target substring][3]. + +If the match succeeds: + +- Returns the matched substring. +- Increments the [byte position][2] by substring.bytesize, + and may increment the [character position][7]. +- Sets [match values][9]. + +``` +scanner = StringScanner.new(HIRAGANA_TEXT) +scanner.string # => "こんにちは" +scanner.pos = 6 +scanner.scan(/に/) # => "に" +put_match_values(scanner) +# Basic match values: +# matched?: true +# matched_size: 3 +# pre_match: "こん" +# matched : "に" +# post_match: "ちは" +# Captured match values: +# size: 1 +# captures: [] +# named_captures: {} +# values_at: ["に", nil] +# []: +# [0]: "に" +# [1]: nil +put_situation(scanner) +# Situation: +# pos: 9 +# charpos: 3 +# rest: "ちは" +# rest_size: 6 +``` + +If the match fails: + +- Returns `nil`. +- Does not increment byte and character positions. +- Clears match values. + +``` +scanner.scan(/nope/) # => nil +match_values_cleared?(scanner) # => true +``` diff --git a/doc/strscan/methods/scan_until.md b/doc/strscan/methods/scan_until.md new file mode 100644 index 0000000000..3b7ff2c3a9 --- /dev/null +++ b/doc/strscan/methods/scan_until.md @@ -0,0 +1,52 @@ +call-seq: + scan_until(pattern) -> substring or nil + +Attempts to [match][17] the given `pattern` +anywhere (at any [position][2]) in the [target substring][3]. + +If the match attempt succeeds: + +- Sets [match values][9]. +- Sets the [byte position][2] to the end of the matched substring; + may adjust the [character position][7]. +- Returns the matched substring. + + +``` +scanner = StringScanner.new(HIRAGANA_TEXT) +scanner.string # => "こんにちは" +scanner.pos = 6 +scanner.scan_until(/ち/) # => "にち" +put_match_values(scanner) +# Basic match values: +# matched?: true +# matched_size: 3 +# pre_match: "こんに" +# matched : "ち" +# post_match: "は" +# Captured match values: +# size: 1 +# captures: [] +# named_captures: {} +# values_at: ["ち", nil] +# []: +# [0]: "ち" +# [1]: nil +put_situation(scanner) +# Situation: +# pos: 12 +# charpos: 4 +# rest: "は" +# rest_size: 3 +``` + +If the match attempt fails: + +- Clears match data. +- Returns `nil`. +- Does not update positions. + +``` +scanner.scan_until(/nope/) # => nil +match_values_cleared?(scanner) # => true +``` diff --git a/doc/strscan/methods/set_pos.md b/doc/strscan/methods/set_pos.md new file mode 100644 index 0000000000..230177109c --- /dev/null +++ b/doc/strscan/methods/set_pos.md @@ -0,0 +1,27 @@ +call-seq: + pos = n -> n + pointer = n -> n + +Sets the [byte position][2] and the [character position][11]; +returns `n`. + +Does not affect [match values][9]. + +For non-negative `n`, sets the position to `n`: + +``` +scanner = StringScanner.new(HIRAGANA_TEXT) +scanner.string # => "こんにちは" +scanner.pos = 3 # => 3 +scanner.rest # => "んにちは" +scanner.charpos # => 1 +``` + +For negative `n`, counts from the end of the [stored string][1]: + +``` +scanner.pos = -9 # => -9 +scanner.pos # => 6 +scanner.rest # => "にちは" +scanner.charpos # => 2 +``` diff --git a/doc/strscan/methods/skip.md b/doc/strscan/methods/skip.md new file mode 100644 index 0000000000..656f134c5a --- /dev/null +++ b/doc/strscan/methods/skip.md @@ -0,0 +1,43 @@ +call-seq: + skip(pattern) match_size or nil + +Attempts to [match][17] the given `pattern` +at the beginning of the [target substring][3]; + +If the match succeeds: + +- Increments the [byte position][2] by substring.bytesize, + and may increment the [character position][7]. +- Sets [match values][9]. +- Returns the size (bytes) of the matched substring. + +``` +scanner = StringScanner.new(HIRAGANA_TEXT) +scanner.string # => "こんにちは" +scanner.pos = 6 +scanner.skip(/に/) # => 3 +put_match_values(scanner) +# Basic match values: +# matched?: true +# matched_size: 3 +# pre_match: "こん" +# matched : "に" +# post_match: "ちは" +# Captured match values: +# size: 1 +# captures: [] +# named_captures: {} +# values_at: ["に", nil] +# []: +# [0]: "に" +# [1]: nil +put_situation(scanner) +# Situation: +# pos: 9 +# charpos: 3 +# rest: "ちは" +# rest_size: 6 + +scanner.skip(/nope/) # => nil +match_values_cleared?(scanner) # => true +``` diff --git a/doc/strscan/methods/skip_until.md b/doc/strscan/methods/skip_until.md new file mode 100644 index 0000000000..5187a4826f --- /dev/null +++ b/doc/strscan/methods/skip_until.md @@ -0,0 +1,49 @@ +call-seq: + skip_until(pattern) -> matched_substring_size or nil + +Attempts to [match][17] the given `pattern` +anywhere (at any [position][2]) in the [target substring][3]; +does not modify the positions. + +If the match attempt succeeds: + +- Sets [match values][9]. +- Returns the size of the matched substring. + +``` +scanner = StringScanner.new(HIRAGANA_TEXT) +scanner.string # => "こんにちは" +scanner.pos = 6 +scanner.skip_until(/ち/) # => 6 +put_match_values(scanner) +# Basic match values: +# matched?: true +# matched_size: 3 +# pre_match: "こんに" +# matched : "ち" +# post_match: "は" +# Captured match values: +# size: 1 +# captures: [] +# named_captures: {} +# values_at: ["ち", nil] +# []: +# [0]: "ち" +# [1]: nil +put_situation(scanner) +# Situation: +# pos: 12 +# charpos: 4 +# rest: "は" +# rest_size: 3 +``` + +If the match attempt fails: + +- Clears match values. +- Returns `nil`. + +``` +scanner.skip_until(/nope/) # => nil +match_values_cleared?(scanner) # => true +``` diff --git a/doc/strscan/methods/terminate.md b/doc/strscan/methods/terminate.md new file mode 100644 index 0000000000..fd55727099 --- /dev/null +++ b/doc/strscan/methods/terminate.md @@ -0,0 +1,30 @@ +call-seq: + terminate -> self + +Sets the scanner to end-of-string; +returns +self+: + +- Sets both [positions][11] to end-of-stream. +- Clears [match values][9]. + +``` +scanner = StringScanner.new(HIRAGANA_TEXT) +scanner.string # => "こんにちは" +scanner.scan_until(/に/) +put_situation(scanner) +# Situation: +# pos: 9 +# charpos: 3 +# rest: "ちは" +# rest_size: 6 +match_values_cleared?(scanner) # => false + +scanner.terminate # => # +put_situation(scanner) +# Situation: +# pos: 15 +# charpos: 5 +# rest: "" +# rest_size: 0 +match_values_cleared?(scanner) # => true +``` diff --git a/doc/strscan/strscan.md b/doc/strscan/strscan.md new file mode 100644 index 0000000000..c2558e9cf1 --- /dev/null +++ b/doc/strscan/strscan.md @@ -0,0 +1,543 @@ +\Class `StringScanner` supports processing a stored string as a stream; +this code creates a new `StringScanner` object with string `'foobarbaz'`: + +``` +require 'strscan' +scanner = StringScanner.new('foobarbaz') +``` + +## About the Examples + +All examples here assume that `StringScanner` has been required: + +``` +require 'strscan' +``` + +Some examples here assume that these constants are defined: + +``` +MULTILINE_TEXT = <<~EOT +Go placidly amid the noise and haste, +and remember what peace there may be in silence. +EOT + +HIRAGANA_TEXT = 'こんにちは' + +ENGLISH_TEXT = 'Hello' +``` + +Some examples here assume that certain helper methods are defined: + +- `put_situation(scanner)`: + Displays the values of the scanner's + methods #pos, #charpos, #rest, and #rest_size. +- `put_match_values(scanner)`: + Displays the scanner's [match values][9]. +- `match_values_cleared?(scanner)`: + Returns whether the scanner's [match values][9] are cleared. + +See examples [here][ext/strscan/helper_methods_md.html]. + +## The `StringScanner` \Object + +This code creates a `StringScanner` object +(we'll call it simply a _scanner_), +and shows some of its basic properties: + +``` +scanner = StringScanner.new('foobarbaz') +scanner.string # => "foobarbaz" +put_situation(scanner) +# Situation: +# pos: 0 +# charpos: 0 +# rest: "foobarbaz" +# rest_size: 9 +``` + +The scanner has: + +* A stored string, which is: + + * Initially set by StringScanner.new(string) to the given `string` + (`'foobarbaz'` in the example above). + * Modifiable by methods #string=(new_string) and #concat(more_string). + * Returned by method #string. + + More at [Stored String][1] below. + +* A _position_; + a zero-based index into the bytes of the stored string (_not_ into its characters): + + * Initially set by StringScanner.new to `0`. + * Returned by method #pos. + * Modifiable explicitly by methods #reset, #terminate, and #pos=(new_pos). + * Modifiable implicitly (various traversing methods, among others). + + More at [Byte Position][2] below. + +* A target substring, + which is a trailing substring of the stored string; + it extends from the current position to the end of the stored string: + + * Initially set by StringScanner.new(string) to the given `string` + (`'foobarbaz'` in the example above). + * Returned by method #rest. + * Modified by any modification to either the stored string or the position. + + Most importantly: + the searching and traversing methods operate on the target substring, + which may be (and often is) less than the entire stored string. + + More at [Target Substring][3] below. + +## Stored \String + +The stored string is the string stored in the `StringScanner` object. + +Each of these methods sets, modifies, or returns the stored string: + +| Method | Effect | +|----------------------|-------------------------------------------------| +| ::new(string) | Creates a new scanner for the given string. | +| #string=(new_string) | Replaces the existing stored string. | +| #concat(more_string) | Appends a string to the existing stored string. | +| #string | Returns the stored string. | + +## Positions + +A `StringScanner` object maintains a zero-based byte position +and a zero-based character position. + +Each of these methods explicitly sets positions: + +| Method | Effect | +|--------------------------|----------------------------------------------------------| +| #reset | Sets both positions to zero (begining of stored string). | +| #terminate | Sets both positions to the end of the stored string. | +| #pos=(new_byte_position) | Sets byte position; adjusts character position. | + +### Byte Position (Position) + +The byte position (or simply _position_) +is a zero-based index into the bytes in the scanner's stored string; +for a new `StringScanner` object, the byte position is zero. + +When the byte position is: + +* Zero (at the beginning), the target substring is the entire stored string. +* Equal to the size of the stored string (at the end), + the target substring is the empty string `''`. + +To get or set the byte position: + +* \#pos: returns the byte position. +* \#pos=(new_pos): sets the byte position. + +Many methods use the byte position as the basis for finding matches; +many others set, increment, or decrement the byte position: + +``` +scanner = StringScanner.new('foobar') +scanner.pos # => 0 +scanner.scan(/foo/) # => "foo" # Match found. +scanner.pos # => 3 # Byte position incremented. +scanner.scan(/foo/) # => nil # Match not found. +scanner.pos # => 3 # Byte position not changed. +``` + +Some methods implicitly modify the byte position; +see: + +* [Setting the Target Substring][4]. +* [Traversing the Target Substring][5]. + +The values of these methods are derived directly from the values of #pos and #string: + +- \#charpos: the [character position][7]. +- \#rest: the [target substring][3]. +- \#rest_size: `rest.size`. + +### Character Position + +The character position is a zero-based index into the _characters_ +in the stored string; +for a new `StringScanner` object, the character position is zero. + +\Method #charpos returns the character position; +its value may not be reset explicitly. + +Some methods change (increment or reset) the character position; +see: + +* [Setting the Target Substring][4]. +* [Traversing the Target Substring][5]. + +Example (string includes multi-byte characters): + +``` +scanner = StringScanner.new(ENGLISH_TEXT) # Five 1-byte characters. +scanner.concat(HIRAGANA_TEXT) # Five 3-byte characters +scanner.string # => "Helloこんにちは" # Twenty bytes in all. +put_situation(scanner) +# Situation: +# pos: 0 +# charpos: 0 +# rest: "Helloこんにちは" +# rest_size: 20 +scanner.scan(/Hello/) # => "Hello" # Five 1-byte characters. +put_situation(scanner) +# Situation: +# pos: 5 +# charpos: 5 +# rest: "こんにちは" +# rest_size: 15 +scanner.getch # => "こ" # One 3-byte character. +put_situation(scanner) +# Situation: +# pos: 8 +# charpos: 6 +# rest: "んにちは" +# rest_size: 12``` + +## Target Substring + +The target substring is the the part of the [stored string][1] +that extends from the current [byte position][2] to the end of the stored string; +it is always either: + +- The entire stored string (byte position is zero). +- A trailing substring of the stored string (byte position positive). + +The target substring is returned by method #rest, +and its size is returned by method #rest_size. + +Examples: + +``` +scanner = StringScanner.new('foobarbaz') +put_situation(scanner) +# Situation: +# pos: 0 +# charpos: 0 +# rest: "foobarbaz" +# rest_size: 9 +scanner.pos = 3 +put_situation(scanner) +# Situation: +# pos: 3 +# charpos: 3 +# rest: "barbaz" +# rest_size: 6 +scanner.pos = 9 +put_situation(scanner) +# Situation: +# pos: 9 +# charpos: 9 +# rest: "" +# rest_size: 0 +``` + +### Setting the Target Substring + +The target substring is set whenever: + +* The [stored string][1] is set (position reset to zero; target substring set to stored string). +* The [byte position][2] is set (target substring adjusted accordingly). + +### Querying the Target Substring + +This table summarizes (details and examples at the links): + +| Method | Returns | +|------------|-----------------------------------| +| #rest | Target substring. | +| #rest_size | Size (bytes) of target substring. | + +### Searching the Target Substring + +A _search_ method examines the target substring, +but does not advance the [positions][11] +or (by implication) shorten the target substring. + +This table summarizes (details and examples at the links): + +| Method | Returns | Sets Match Values? | +|-----------------------|-----------------------------------------------|--------------------| +| #check(pattern) | Matched leading substring or +nil+. | Yes. | +| #check_until(pattern) | Matched substring (anywhere) or +nil+. | Yes. | +| #exist?(pattern) | Matched substring (anywhere) end index. | Yes. | +| #match?(pattern) | Size of matched leading substring or +nil+. | Yes. | +| #peek(size) | Leading substring of given length (bytes). | No. | +| #peek_byte | Integer leading byte or +nil+. | No. | +| #rest | Target substring (from byte position to end). | No. | + +### Traversing the Target Substring + +A _traversal_ method examines the target substring, +and, if successful: + +- Advances the [positions][11]. +- Shortens the target substring. + + +This table summarizes (details and examples at links): + +| Method | Returns | Sets Match Values? | +|----------------------|------------------------------------------------------|--------------------| +| #get_byte | Leading byte or +nil+. | No. | +| #getch | Leading character or +nil+. | No. | +| #scan(pattern) | Matched leading substring or +nil+. | Yes. | +| #scan_byte | Integer leading byte or +nil+. | No. | +| #scan_until(pattern) | Matched substring (anywhere) or +nil+. | Yes. | +| #skip(pattern) | Matched leading substring size or +nil+. | Yes. | +| #skip_until(pattern) | Position delta to end-of-matched-substring or +nil+. | Yes. | +| #unscan | +self+. | No. | + +## Querying the Scanner + +Each of these methods queries the scanner object +without modifying it (details and examples at links) + +| Method | Returns | +|---------------------|----------------------------------| +| #beginning_of_line? | +true+ or +false+. | +| #charpos | Character position. | +| #eos? | +true+ or +false+. | +| #fixed_anchor? | +true+ or +false+. | +| #inspect | String representation of +self+. | +| #pos | Byte position. | +| #rest | Target substring. | +| #rest_size | Size of target substring. | +| #string | Stored string. | + +## Matching + +`StringScanner` implements pattern matching via Ruby class [Regexp][6], +and its matching behaviors are the same as Ruby's +except for the [fixed-anchor property][10]. + +### Matcher Methods + +Each matcher method takes a single argument `pattern`, +and attempts to find a matching substring in the [target substring][3]. + +| Method | Pattern Type | Matches Target Substring | Success Return | May Update Positions? | +|--------------|-------------------|--------------------------|--------------------|-----------------------| +| #check | Regexp or String. | At beginning. | Matched substring. | No. | +| #check_until | Regexp. | Anywhere. | Substring. | No. | +| #match? | Regexp or String. | At beginning. | Updated position. | No. | +| #exist? | Regexp. | Anywhere. | Updated position. | No. | +| #scan | Regexp or String. | At beginning. | Matched substring. | Yes. | +| #scan_until | Regexp. | Anywhere. | Substring. | Yes. | +| #skip | Regexp or String. | At beginning. | Match size. | Yes. | +| #skip_until | Regexp. | Anywhere. | Position delta. | Yes. | + +
+ +Which matcher you choose will depend on: + +- Where you want to find a match: + + - Only at the beginning of the target substring: + #check, #match?, #scan, #skip. + - Anywhere in the target substring: + #check_until, #exist?, #scan_until, #skip_until. + +- Whether you want to: + + - Traverse, by advancing the positions: + #scan, #scan_until, #skip, #skip_until. + - Keep the positions unchanged: + #check, #check_until, #exist?, #match?. + +- What you want for the return value: + + - The matched substring: #check, #check_until, #scan, #scan_until. + - The updated position: #exist?, #match?. + - The position delta: #skip_until. + - The match size: #skip. + +### Match Values + +The match values in a `StringScanner` object +generally contain the results of the most recent attempted match. + +Each match value may be thought of as: + +* _Clear_: Initially, or after an unsuccessful match attempt: + usually, `false`, `nil`, or `{}`. +* _Set_: After a successful match attempt: + `true`, string, array, or hash. + +Each of these methods clears match values: + +- ::new(string). +- \#reset. +- \#terminate. + +Each of these methods attempts a match based on a pattern, +and either sets match values (if successful) or clears them (if not); + +- \#check(pattern) +- \#check_until(pattern) +- \#exist?(pattern) +- \#match?(pattern) +- \#scan(pattern) +- \#scan_until(pattern) +- \#skip(pattern) +- \#skip_until(pattern) + +#### Basic Match Values + +Basic match values are those not related to captures. + +Each of these methods returns a basic match value: + +| Method | Return After Match | Return After No Match | +|-----------------|----------------------------------------|-----------------------| +| #matched? | +true+. | +false+. | +| #matched_size | Size of matched substring. | +nil+. | +| #matched | Matched substring. | +nil+. | +| #pre_match | Substring preceding matched substring. | +nil+. | +| #post_match | Substring following matched substring. | +nil+. | + +
+ +See examples below. + +#### Captured Match Values + +Captured match values are those related to [captures][16]. + +Each of these methods returns a captured match value: + +| Method | Return After Match | Return After No Match | +|-----------------|-----------------------------------------|-----------------------| +| #size | Count of captured substrings. | +nil+. | +| #[](n) | nth captured substring. | +nil+. | +| #captures | Array of all captured substrings. | +nil+. | +| #values_at(*n) | Array of specified captured substrings. | +nil+. | +| #named_captures | Hash of named captures. | {}. | + +
+ +See examples below. + +#### Match Values Examples + +Successful basic match attempt (no captures): + +``` +scanner = StringScanner.new('foobarbaz') +scanner.exist?(/bar/) +put_match_values(scanner) +# Basic match values: +# matched?: true +# matched_size: 3 +# pre_match: "foo" +# matched : "bar" +# post_match: "baz" +# Captured match values: +# size: 1 +# captures: [] +# named_captures: {} +# values_at: ["bar", nil] +# []: +# [0]: "bar" +# [1]: nil +``` + +Failed basic match attempt (no captures); + +``` +scanner = StringScanner.new('foobarbaz') +scanner.exist?(/nope/) +match_values_cleared?(scanner) # => true +``` + +Successful unnamed capture match attempt: + +``` +scanner = StringScanner.new('foobarbazbatbam') +scanner.exist?(/(foo)bar(baz)bat(bam)/) +put_match_values(scanner) +# Basic match values: +# matched?: true +# matched_size: 15 +# pre_match: "" +# matched : "foobarbazbatbam" +# post_match: "" +# Captured match values: +# size: 4 +# captures: ["foo", "baz", "bam"] +# named_captures: {} +# values_at: ["foobarbazbatbam", "foo", "baz", "bam", nil] +# []: +# [0]: "foobarbazbatbam" +# [1]: "foo" +# [2]: "baz" +# [3]: "bam" +# [4]: nil +``` + +Successful named capture match attempt; +same as unnamed above, except for #named_captures: + +``` +scanner = StringScanner.new('foobarbazbatbam') +scanner.exist?(/(?foo)bar(?baz)bat(?bam)/) +scanner.named_captures # => {"x"=>"foo", "y"=>"baz", "z"=>"bam"} +``` + +Failed unnamed capture match attempt: + +``` +scanner = StringScanner.new('somestring') +scanner.exist?(/(foo)bar(baz)bat(bam)/) +match_values_cleared?(scanner) # => true +``` + +Failed named capture match attempt; +same as unnamed above, except for #named_captures: + +``` +scanner = StringScanner.new('somestring') +scanner.exist?(/(?foo)bar(?baz)bat(?bam)/) +match_values_cleared?(scanner) # => false +scanner.named_captures # => {"x"=>nil, "y"=>nil, "z"=>nil} +``` + +## Fixed-Anchor Property + +Pattern matching in `StringScanner` is the same as in Ruby's, +except for its fixed-anchor property, +which determines the meaning of `'\A'`: + +* `false` (the default): matches the current byte position. + + ``` + scanner = StringScanner.new('foobar') + scanner.scan(/\A./) # => "f" + scanner.scan(/\A./) # => "o" + scanner.scan(/\A./) # => "o" + scanner.scan(/\A./) # => "b" + ``` + +* `true`: matches the beginning of the target substring; + never matches unless the byte position is zero: + + ``` + scanner = StringScanner.new('foobar', fixed_anchor: true) + scanner.scan(/\A./) # => "f" + scanner.scan(/\A./) # => nil + scanner.reset + scanner.scan(/\A./) # => "f" + ``` + +The fixed-anchor property is set when the `StringScanner` object is created, +and may not be modified +(see StringScanner.new); +method #fixed_anchor? returns the setting. + diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 70a3ce5260..fad35925a8 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -218,16 +218,28 @@ strscan_s_allocate(VALUE klass) } /* + * :markup: markdown + * :include: strscan/link_refs.txt + * * call-seq: - * StringScanner.new(string, fixed_anchor: false) - * StringScanner.new(string, dup = false) + * StringScanner.new(string, fixed_anchor: false) -> string_scanner * - * Creates a new StringScanner object to scan over the given +string+. + * Returns a new `StringScanner` object whose [stored string][1] + * is the given `string`; + * sets the [fixed-anchor property][10]: * - * If +fixed_anchor+ is +true+, +\A+ always matches the beginning of - * the string. Otherwise, +\A+ always matches the current position. + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.string # => "foobarbaz" + * scanner.fixed_anchor? # => false + * put_situation(scanner) + * # Situation: + * # pos: 0 + * # charpos: 0 + * # rest: "foobarbaz" + * # rest_size: 9 + * ``` * - * +dup+ argument is obsolete and not used now. */ static VALUE strscan_initialize(int argc, VALUE *argv, VALUE self) @@ -266,11 +278,14 @@ check_strscan(VALUE obj) } /* - * call-seq: - * dup - * clone + * :markup: markdown + * :include: strscan/link_refs.txt * - * Duplicates a StringScanner object. + * call-seq: + * dup -> shallow_copy + * + * Returns a shallow copy of `self`; + * the [stored string][1] in the copy is the same string as in `self`. */ static VALUE strscan_init_copy(VALUE vself, VALUE vorig) @@ -297,10 +312,13 @@ strscan_init_copy(VALUE vself, VALUE vorig) ======================================================================= */ /* - * call-seq: StringScanner.must_C_version + * call-seq: + * StringScanner.must_C_version -> self * - * This method is defined for backward compatibility. + * Returns +self+; defined for backward compatibility. */ + + /* :nodoc: */ static VALUE strscan_s_mustc(VALUE self) { @@ -308,7 +326,30 @@ strscan_s_mustc(VALUE self) } /* - * Reset the scan pointer (index 0) and clear matching data. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * reset -> self + * + * Sets both [byte position][2] and [character position][7] to zero, + * and clears [match values][9]; + * returns +self+: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.exist?(/bar/) # => 6 + * scanner.reset # => # + * put_situation(scanner) + * # Situation: + * # pos: 0 + * # charpos: 0 + * # rest: "foobarbaz" + * # rest_size: 9 + * # => nil + * match_values_cleared?(scanner) # => true + * ``` + * */ static VALUE strscan_reset(VALUE self) @@ -322,11 +363,9 @@ strscan_reset(VALUE self) } /* - * call-seq: - * terminate - * clear - * - * Sets the scan pointer to the end of the string and clear matching data. + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/terminate.md */ static VALUE strscan_terminate(VALUE self) @@ -340,9 +379,13 @@ strscan_terminate(VALUE self) } /* - * Equivalent to #terminate. - * This method is obsolete; use #terminate instead. + * call-seq: + * clear -> self + * + * This method is obsolete; use the equivalent method StringScanner#terminate. */ + + /* :nodoc: */ static VALUE strscan_clear(VALUE self) { @@ -351,7 +394,21 @@ strscan_clear(VALUE self) } /* - * Returns the string being scanned. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * string -> stored_string + * + * Returns the [stored string][1]: + * + * ``` + * scanner = StringScanner.new('foobar') + * scanner.string # => "foobar" + * scanner.concat('baz') + * scanner.string # => "foobarbaz" + * ``` + * */ static VALUE strscan_get_string(VALUE self) @@ -363,10 +420,39 @@ strscan_get_string(VALUE self) } /* - * call-seq: string=(str) + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * string = other_string -> other_string + * + * Replaces the [stored string][1] with the given `other_string`: + * + * - Sets both [positions][11] to zero. + * - Clears [match values][9]. + * - Returns `other_string`. + * + * ``` + * scanner = StringScanner.new('foobar') + * scanner.scan(/foo/) + * put_situation(scanner) + * # Situation: + * # pos: 3 + * # charpos: 3 + * # rest: "bar" + * # rest_size: 3 + * match_values_cleared?(scanner) # => false + * + * scanner.string = 'baz' # => "baz" + * put_situation(scanner) + * # Situation: + * # pos: 0 + * # charpos: 0 + * # rest: "baz" + * # rest_size: 3 + * match_values_cleared?(scanner) # => true + * ``` * - * Changes the string being scanned to +str+ and resets the scanner. - * Returns +str+. */ static VALUE strscan_set_string(VALUE self, VALUE str) @@ -381,18 +467,33 @@ strscan_set_string(VALUE self, VALUE str) } /* + * :markup: markdown + * :include: strscan/link_refs.txt + * * call-seq: - * concat(str) - * <<(str) + * concat(more_string) -> self * - * Appends +str+ to the string being scanned. - * This method does not affect scan pointer. + * - Appends the given `more_string` + * to the [stored string][1]. + * - Returns `self`. + * - Does not affect the [positions][11] + * or [match values][9]. + * + * + * ``` + * scanner = StringScanner.new('foo') + * scanner.string # => "foo" + * scanner.terminate + * scanner.concat('barbaz') # => # + * scanner.string # => "foobarbaz" + * put_situation(scanner) + * # Situation: + * # pos: 3 + * # charpos: 3 + * # rest: "barbaz" + * # rest_size: 6 + * ``` * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.scan(/Fri /) - * s << " +1000 GMT" - * s.string # -> "Fri Dec 12 1975 14:39 +1000 GMT" - * s.scan(/Dec/) # -> "Dec" */ static VALUE strscan_concat(VALUE self, VALUE str) @@ -406,18 +507,9 @@ strscan_concat(VALUE self, VALUE str) } /* - * Returns the byte position of the scan pointer. In the 'reset' position, this - * value is zero. In the 'terminated' position (i.e. the string is exhausted), - * this value is the bytesize of the string. - * - * In short, it's a 0-based index into bytes of the string. - * - * s = StringScanner.new('test string') - * s.pos # -> 0 - * s.scan_until /str/ # -> "test str" - * s.pos # -> 8 - * s.terminate # -> # - * s.pos # -> 11 + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/get_pos.md */ static VALUE strscan_get_pos(VALUE self) @@ -429,17 +521,9 @@ strscan_get_pos(VALUE self) } /* - * Returns the character position of the scan pointer. In the 'reset' position, this - * value is zero. In the 'terminated' position (i.e. the string is exhausted), - * this value is the size of the string. - * - * In short, it's a 0-based index into the string. - * - * s = StringScanner.new("abc\u00e4def\u00f6ghi") - * s.charpos # -> 0 - * s.scan_until(/\u00e4/) # -> "abc\u00E4" - * s.pos # -> 5 - * s.charpos # -> 4 + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/get_charpos.md */ static VALUE strscan_get_charpos(VALUE self) @@ -452,13 +536,9 @@ strscan_get_charpos(VALUE self) } /* - * call-seq: pos=(n) - * - * Sets the byte position of the scan pointer. - * - * s = StringScanner.new('test string') - * s.pos = 7 # -> 7 - * s.rest # -> "ring" + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/set_pos.md */ static VALUE strscan_set_pos(VALUE self, VALUE v) @@ -662,20 +742,9 @@ strscan_do_scan(VALUE self, VALUE pattern, int succptr, int getstr, int headonly } /* - * call-seq: scan(pattern) => String - * - * Tries to match with +pattern+ at the current position. If there's a match, - * the scanner advances the "scan pointer" and returns the matched string. - * Otherwise, the scanner returns +nil+. - * - * s = StringScanner.new('test string') - * p s.scan(/\w+/) # -> "test" - * p s.scan(/\w+/) # -> nil - * p s.scan(/\s+/) # -> " " - * p s.scan("str") # -> "str" - * p s.scan(/\w+/) # -> "ing" - * p s.scan(/./) # -> nil - * + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/scan.md */ static VALUE strscan_scan(VALUE self, VALUE re) @@ -684,16 +753,60 @@ strscan_scan(VALUE self, VALUE re) } /* - * call-seq: match?(pattern) + * :markup: markdown + * :include: strscan/link_refs.txt * - * Tests whether the given +pattern+ is matched from the current scan pointer. - * Returns the length of the match, or +nil+. The scan pointer is not advanced. + * call-seq: + * match?(pattern) -> updated_position or nil + * + * Attempts to [match][17] the given `pattern` + * at the beginning of the [target substring][3]; + * does not modify the [positions][11]. + * + * If the match succeeds: + * + * - Sets [match values][9]. + * - Returns the size in bytes of the matched substring. + * + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.pos = 3 + * scanner.match?(/bar/) => 3 + * put_match_values(scanner) + * # Basic match values: + * # matched?: true + * # matched_size: 3 + * # pre_match: "foo" + * # matched : "bar" + * # post_match: "baz" + * # Captured match values: + * # size: 1 + * # captures: [] + * # named_captures: {} + * # values_at: ["bar", nil] + * # []: + * # [0]: "bar" + * # [1]: nil + * put_situation(scanner) + * # Situation: + * # pos: 3 + * # charpos: 3 + * # rest: "barbaz" + * # rest_size: 6 + * ``` + * + * If the match fails: + * + * - Clears match values. + * - Returns `nil`. + * - Does not increment positions. + * + * ``` + * scanner.match?(/nope/) # => nil + * match_values_cleared?(scanner) # => true + * ``` * - * s = StringScanner.new('test string') - * p s.match?(/\w+/) # -> 4 - * p s.match?(/\w+/) # -> 4 - * p s.match?("test") # -> 4 - * p s.match?(/\s+/) # -> nil */ static VALUE strscan_match_p(VALUE self, VALUE re) @@ -702,22 +815,9 @@ strscan_match_p(VALUE self, VALUE re) } /* - * call-seq: skip(pattern) - * - * Attempts to skip over the given +pattern+ beginning with the scan pointer. - * If it matches, the scan pointer is advanced to the end of the match, and the - * length of the match is returned. Otherwise, +nil+ is returned. - * - * It's similar to #scan, but without returning the matched string. - * - * s = StringScanner.new('test string') - * p s.skip(/\w+/) # -> 4 - * p s.skip(/\w+/) # -> nil - * p s.skip(/\s+/) # -> 1 - * p s.skip("st") # -> 2 - * p s.skip(/\w+/) # -> 4 - * p s.skip(/./) # -> nil - * + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/skip.md */ static VALUE strscan_skip(VALUE self, VALUE re) @@ -726,19 +826,59 @@ strscan_skip(VALUE self, VALUE re) } /* - * call-seq: check(pattern) + * :markup: markdown + * :include: strscan/link_refs.txt * - * This returns the value that #scan would return, without advancing the scan - * pointer. The match register is affected, though. + * call-seq: + * check(pattern) -> matched_substring or nil * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.check /Fri/ # -> "Fri" - * s.pos # -> 0 - * s.matched # -> "Fri" - * s.check /12/ # -> nil - * s.matched # -> nil + * Attempts to [match][17] the given `pattern` + * at the beginning of the [target substring][3]; + * does not modify the [positions][11]. + * + * If the match succeeds: + * + * - Returns the matched substring. + * - Sets all [match values][9]. + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.pos = 3 + * scanner.check('bar') # => "bar" + * put_match_values(scanner) + * # Basic match values: + * # matched?: true + * # matched_size: 3 + * # pre_match: "foo" + * # matched : "bar" + * # post_match: "baz" + * # Captured match values: + * # size: 1 + * # captures: [] + * # named_captures: {} + * # values_at: ["bar", nil] + * # []: + * # [0]: "bar" + * # [1]: nil + * # => 0..1 + * put_situation(scanner) + * # Situation: + * # pos: 3 + * # charpos: 3 + * # rest: "barbaz" + * # rest_size: 6 + * ``` + * + * If the match fails: + * + * - Returns `nil`. + * - Clears all [match values][9]. + * + * ``` + * scanner.check(/nope/) # => nil + * match_values_cleared?(scanner) # => true + * ``` * - * Mnemonic: it "checks" to see whether a #scan will return a value. */ static VALUE strscan_check(VALUE self, VALUE re) @@ -747,15 +887,24 @@ strscan_check(VALUE self, VALUE re) } /* - * call-seq: scan_full(pattern, advance_pointer_p, return_string_p) + * call-seq: + * scan_full(pattern, advance_pointer_p, return_string_p) -> matched_substring or nil * - * Tests whether the given +pattern+ is matched from the current scan pointer. - * Advances the scan pointer if +advance_pointer_p+ is true. - * Returns the matched string if +return_string_p+ is true. - * The match register is affected. + * Equivalent to one of the following: + * + * - +advance_pointer_p+ +true+: + * + * - +return_string_p+ +true+: StringScanner#scan(pattern). + * - +return_string_p+ +false+: StringScanner#skip(pattern). + * + * - +advance_pointer_p+ +false+: + * + * - +return_string_p+ +true+: StringScanner#check(pattern). + * - +return_string_p+ +false+: StringScanner#match?(pattern). * - * "full" means "#scan with full parameters". */ + + /* :nodoc: */ static VALUE strscan_scan_full(VALUE self, VALUE re, VALUE s, VALUE f) { @@ -763,16 +912,9 @@ strscan_scan_full(VALUE self, VALUE re, VALUE s, VALUE f) } /* - * call-seq: scan_until(pattern) - * - * Scans the string _until_ the +pattern+ is matched. Returns the substring up - * to and including the end of the match, advancing the scan pointer to that - * location. If there is no match, +nil+ is returned. - * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.scan_until(/1/) # -> "Fri Dec 1" - * s.pre_match # -> "Fri Dec " - * s.scan_until(/XYZ/) # -> nil + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/scan_until.md */ static VALUE strscan_scan_until(VALUE self, VALUE re) @@ -781,17 +923,61 @@ strscan_scan_until(VALUE self, VALUE re) } /* - * call-seq: exist?(pattern) + * :markup: markdown + * :include: strscan/link_refs.txt * - * Looks _ahead_ to see if the +pattern+ exists _anywhere_ in the string, - * without advancing the scan pointer. This predicates whether a #scan_until - * will return a value. + * call-seq: + * exist?(pattern) -> byte_offset or nil + * + * Attempts to [match][17] the given `pattern` + * anywhere (at any [position][2]) + * n the [target substring][3]; + * does not modify the [positions][11]. + * + * If the match succeeds: + * + * - Returns a byte offset: + * the distance in bytes between the current [position][2] + * and the end of the matched substring. + * - Sets all [match values][9]. + * + * ``` + * scanner = StringScanner.new('foobarbazbatbam') + * scanner.pos = 6 + * scanner.exist?(/bat/) # => 6 + * put_match_values(scanner) + * # Basic match values: + * # matched?: true + * # matched_size: 3 + * # pre_match: "foobarbaz" + * # matched : "bat" + * # post_match: "bam" + * # Captured match values: + * # size: 1 + * # captures: [] + * # named_captures: {} + * # values_at: ["bat", nil] + * # []: + * # [0]: "bat" + * # [1]: nil + * put_situation(scanner) + * # Situation: + * # pos: 6 + * # charpos: 6 + * # rest: "bazbatbam" + * # rest_size: 9 + * ``` + * + * If the match fails: + * + * - Returns `nil`. + * - Clears all [match values][9]. + * + * ``` + * scanner.exist?(/nope/) # => nil + * match_values_cleared?(scanner) # => true + * ``` * - * s = StringScanner.new('test string') - * s.exist? /s/ # -> 3 - * s.scan /test/ # -> "test" - * s.exist? /s/ # -> 2 - * s.exist? /e/ # -> nil */ static VALUE strscan_exist_p(VALUE self, VALUE re) @@ -800,20 +986,9 @@ strscan_exist_p(VALUE self, VALUE re) } /* - * call-seq: skip_until(pattern) - * - * Advances the scan pointer until +pattern+ is matched and consumed. Returns - * the number of bytes advanced, or +nil+ if no match was found. - * - * Look ahead to match +pattern+, and advance the scan pointer to the _end_ - * of the match. Return the number of characters advanced, or +nil+ if the - * match was unsuccessful. - * - * It's similar to #scan_until, but without returning the intervening string. - * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.skip_until /12/ # -> 10 - * s # + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/skip_until.md */ static VALUE strscan_skip_until(VALUE self, VALUE re) @@ -822,17 +997,61 @@ strscan_skip_until(VALUE self, VALUE re) } /* - * call-seq: check_until(pattern) + * :markup: markdown + * :include: strscan/link_refs.txt * - * This returns the value that #scan_until would return, without advancing the - * scan pointer. The match register is affected, though. + * call-seq: + * check_until(pattern) -> substring or nil * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.check_until /12/ # -> "Fri Dec 12" - * s.pos # -> 0 - * s.matched # -> 12 + * Attempts to [match][17] the given `pattern` + * anywhere (at any [position][2]) + * in the [target substring][3]; + * does not modify the [positions][11]. + * + * If the match succeeds: + * + * - Sets all [match values][9]. + * - Returns the matched substring, + * which extends from the current [position][2] + * to the end of the matched substring. + * + * ``` + * scanner = StringScanner.new('foobarbazbatbam') + * scanner.pos = 6 + * scanner.check_until(/bat/) # => "bazbat" + * put_match_values(scanner) + * # Basic match values: + * # matched?: true + * # matched_size: 3 + * # pre_match: "foobarbaz" + * # matched : "bat" + * # post_match: "bam" + * # Captured match values: + * # size: 1 + * # captures: [] + * # named_captures: {} + * # values_at: ["bat", nil] + * # []: + * # [0]: "bat" + * # [1]: nil + * put_situation(scanner) + * # Situation: + * # pos: 6 + * # charpos: 6 + * # rest: "bazbatbam" + * # rest_size: 9 + * ``` + * + * If the match fails: + * + * - Clears all [match values][9]. + * - Returns `nil`. + * + * ``` + * scanner.check_until(/nope/) # => nil + * match_values_cleared?(scanner) # => true + * ``` * - * Mnemonic: it "checks" to see whether a #scan_until will return a value. */ static VALUE strscan_check_until(VALUE self, VALUE re) @@ -841,14 +1060,24 @@ strscan_check_until(VALUE self, VALUE re) } /* - * call-seq: search_full(pattern, advance_pointer_p, return_string_p) + * call-seq: + * search_full(pattern, advance_pointer_p, return_string_p) -> matched_substring or position_delta or nil + * + * Equivalent to one of the following: + * + * - +advance_pointer_p+ +true+: + * + * - +return_string_p+ +true+: StringScanner#scan_until(pattern). + * - +return_string_p+ +false+: StringScanner#skip_until(pattern). + * + * - +advance_pointer_p+ +false+: + * + * - +return_string_p+ +true+: StringScanner#check_until(pattern). + * - +return_string_p+ +false+: StringScanner#exist?(pattern). * - * Scans the string _until_ the +pattern+ is matched. - * Advances the scan pointer if +advance_pointer_p+, otherwise not. - * Returns the matched string if +return_string_p+ is true, otherwise - * returns the number of bytes advanced. - * This method does affect the match register. */ + + /* :nodoc: */ static VALUE strscan_search_full(VALUE self, VALUE re, VALUE s, VALUE f) { @@ -868,17 +1097,9 @@ adjust_registers_to_matched(struct strscanner *p) } /* - * Scans one character and returns it. - * This method is multibyte character sensitive. - * - * s = StringScanner.new("ab") - * s.getch # => "a" - * s.getch # => "b" - * s.getch # => nil - * - * s = StringScanner.new("\244\242".force_encoding("euc-jp")) - * s.getch # => "\x{A4A2}" # Japanese hira-kana "A" in EUC-JP - * s.getch # => nil + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/getch.md */ static VALUE strscan_getch(VALUE self) @@ -903,19 +1124,13 @@ strscan_getch(VALUE self) } /* + * call-seq: + * scan_byte -> integer_byte + * * Scans one byte and returns it as an integer. * This method is not multibyte character sensitive. * See also: #getch. * - * s = StringScanner.new('ab') - * s.scan_byte # => 97 - * s.scan_byte # => 98 - * s.scan_byte # => nil - * - * s = StringScanner.new("\244\242".force_encoding("euc-jp")) - * s.scan_byte # => 0xA4 - * s.scan_byte # => 0xA2 - * s.scan_byte # => nil */ static VALUE strscan_scan_byte(VALUE self) @@ -954,19 +1169,9 @@ strscan_peek_byte(VALUE self) } /* - * Scans one byte and returns it. - * This method is not multibyte character sensitive. - * See also: #getch. - * - * s = StringScanner.new('ab') - * s.get_byte # => "a" - * s.get_byte # => "b" - * s.get_byte # => nil - * - * s = StringScanner.new("\244\242".force_encoding("euc-jp")) - * s.get_byte # => "\xA4" - * s.get_byte # => "\xA2" - * s.get_byte # => nil + * :markup: markdown + * :include: strscan/link_refs.txt + * :include: strscan/methods/get_byte.md */ static VALUE strscan_get_byte(VALUE self) @@ -988,9 +1193,14 @@ strscan_get_byte(VALUE self) } /* + * call-seq: + * getbyte + * * Equivalent to #get_byte. * This method is obsolete; use #get_byte instead. */ + + /* :nodoc: */ static VALUE strscan_getbyte(VALUE self) { @@ -999,14 +1209,22 @@ strscan_getbyte(VALUE self) } /* - * call-seq: peek(len) + * :markup: markdown + * :include: strscan/link_refs.txt * - * Extracts a string corresponding to string[pos,len], without - * advancing the scan pointer. + * call-seq: + * peek(length) -> substring * - * s = StringScanner.new('test string') - * s.peek(7) # => "test st" - * s.peek(7) # => "test st" + * Returns the substring `string[pos, length]`; + * does not update [match values][9] or [positions][11]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.pos = 3 + * scanner.peek(3) # => "bar" + * scanner.terminate + * scanner.peek(3) # => "" + * ``` * */ static VALUE @@ -1026,9 +1244,14 @@ strscan_peek(VALUE self, VALUE vlen) } /* + * call-seq: + * peep + * * Equivalent to #peek. * This method is obsolete; use #peek instead. */ + + /* :nodoc: */ static VALUE strscan_peep(VALUE self, VALUE vlen) { @@ -1037,15 +1260,42 @@ strscan_peep(VALUE self, VALUE vlen) } /* - * Sets the scan pointer to the previous position. Only one previous position is - * remembered, and it changes with each scanning operation. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * unscan -> self + * + * Sets the [position][2] to its value previous to the recent successful + * [match][17] attempt: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.scan(/foo/) + * put_situation(scanner) + * # Situation: + * # pos: 3 + * # charpos: 3 + * # rest: "barbaz" + * # rest_size: 6 + * scanner.unscan + * # => # + * put_situation(scanner) + * # Situation: + * # pos: 0 + * # charpos: 0 + * # rest: "foobarbaz" + * # rest_size: 9 + * ``` + * + * Raises an exception if match values are clear: + * + * ``` + * scanner.scan(/nope/) # => nil + * match_values_cleared?(scanner) # => true + * scanner.unscan # Raises StringScanner::Error. + * ``` * - * s = StringScanner.new('test string') - * s.scan(/\w+/) # => "test" - * s.unscan - * s.scan(/../) # => "te" - * s.scan(/\d/) # => nil - * s.unscan # ScanError: unscan failed: previous match record not exist */ static VALUE strscan_unscan(VALUE self) @@ -1061,16 +1311,37 @@ strscan_unscan(VALUE self) } /* - * Returns +true+ if and only if the scan pointer is at the beginning of the line. * - * s = StringScanner.new("test\ntest\n") - * s.bol? # => true - * s.scan(/te/) - * s.bol? # => false - * s.scan(/st\n/) - * s.bol? # => true - * s.terminate - * s.bol? # => true + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * beginning_of_line? -> true or false + * + * Returns whether the [position][2] is at the beginning of a line; + * that is, at the beginning of the [stored string][1] + * or immediately after a newline: + * + * scanner = StringScanner.new(MULTILINE_TEXT) + * scanner.string + * # => "Go placidly amid the noise and haste,\nand remember what peace there may be in silence.\n" + * scanner.pos # => 0 + * scanner.beginning_of_line? # => true + * + * scanner.scan_until(/,/) # => "Go placidly amid the noise and haste," + * scanner.beginning_of_line? # => false + * + * scanner.scan(/\n/) # => "\n" + * scanner.beginning_of_line? # => true + * + * scanner.terminate + * scanner.beginning_of_line? # => true + * + * scanner.concat('x') + * scanner.terminate + * scanner.beginning_of_line? # => false + * + * StringScanner#bol? is an alias for StringScanner#beginning_of_line?. */ static VALUE strscan_bol_p(VALUE self) @@ -1084,14 +1355,24 @@ strscan_bol_p(VALUE self) } /* - * Returns +true+ if the scan pointer is at the end of the string. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * eos? -> true or false + * + * Returns whether the [position][2] + * is at the end of the [stored string][1]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.eos? # => false + * pos = 3 + * scanner.eos? # => false + * scanner.terminate + * scanner.eos? # => true + * ``` * - * s = StringScanner.new('test string') - * p s.eos? # => false - * s.scan(/test/) - * p s.eos? # => false - * s.terminate - * p s.eos? # => true */ static VALUE strscan_eos_p(VALUE self) @@ -1103,9 +1384,14 @@ strscan_eos_p(VALUE self) } /* + * call-seq: + * empty? + * * Equivalent to #eos?. * This method is obsolete, use #eos? instead. */ + + /* :nodoc: */ static VALUE strscan_empty_p(VALUE self) { @@ -1114,6 +1400,9 @@ strscan_empty_p(VALUE self) } /* + * call-seq: + * rest? + * * Returns true if and only if there is more data in the string. See #eos?. * This method is obsolete; use #eos? instead. * @@ -1122,6 +1411,8 @@ strscan_empty_p(VALUE self) * s.eos? # => false * s.rest? # => true */ + + /* :nodoc: */ static VALUE strscan_rest_p(VALUE self) { @@ -1132,13 +1423,26 @@ strscan_rest_p(VALUE self) } /* - * Returns +true+ if and only if the last match was successful. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * matched? -> true or false + * + * Returns `true` of the most recent [match attempt][17] was successful, + * `false` otherwise; + * see [Basic Matched Values][18]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.matched? # => false + * scanner.pos = 3 + * scanner.exist?(/baz/) # => 6 + * scanner.matched? # => true + * scanner.exist?(/nope/) # => nil + * scanner.matched? # => false + * ``` * - * s = StringScanner.new('test string') - * s.match?(/\w+/) # => 4 - * s.matched? # => true - * s.match?(/\d+/) # => nil - * s.matched? # => false */ static VALUE strscan_matched_p(VALUE self) @@ -1150,11 +1454,27 @@ strscan_matched_p(VALUE self) } /* - * Returns the last matched string. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * matched -> matched_substring or nil + * + * Returns the matched substring from the most recent [match][17] attempt + * if it was successful, + * or `nil` otherwise; + * see [Basic Matched Values][18]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.matched # => nil + * scanner.pos = 3 + * scanner.match?(/bar/) # => 3 + * scanner.matched # => "bar" + * scanner.match?(/nope/) # => nil + * scanner.matched # => nil + * ``` * - * s = StringScanner.new('test string') - * s.match?(/\w+/) # -> 4 - * s.matched # -> "test" */ static VALUE strscan_matched(VALUE self) @@ -1169,15 +1489,29 @@ strscan_matched(VALUE self) } /* - * Returns the size of the most recent match in bytes, or +nil+ if there - * was no recent match. This is different than matched.size, - * which will return the size in characters. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * matched_size -> substring_size or nil + * + * Returns the size (in bytes) of the matched substring + * from the most recent match [match attempt][17] if it was successful, + * or `nil` otherwise; + * see [Basic Matched Values][18]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.matched_size # => nil + * + * pos = 3 + * scanner.exist?(/baz/) # => 9 + * scanner.matched_size # => 3 + * + * scanner.exist?(/nope/) # => nil + * scanner.matched_size # => nil + * ``` * - * s = StringScanner.new('test string') - * s.check /\w+/ # -> "test" - * s.matched_size # -> 4 - * s.check /\d+/ # -> nil - * s.matched_size # -> nil */ static VALUE strscan_matched_size(VALUE self) @@ -1208,30 +1542,75 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name } /* - * call-seq: [](n) * - * Returns the n-th subgroup in the most recent match. + * :markup: markdown + * :include: strscan/link_refs.txt * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.scan(/(\w+) (\w+) (\d+) /) # -> "Fri Dec 12 " - * s[0] # -> "Fri Dec 12 " - * s[1] # -> "Fri" - * s[2] # -> "Dec" - * s[3] # -> "12" - * s.post_match # -> "1975 14:39" - * s.pre_match # -> "" + * call-seq: + * [](specifier) -> substring or nil + * + * Returns a captured substring or `nil`; + * see [Captured Match Values][13]. + * + * When there are captures: + * + * ``` + * scanner = StringScanner.new('Fri Dec 12 1975 14:39') + * scanner.scan(/(?\w+) (?\w+) (?\d+) /) + * ``` + * + * - `specifier` zero: returns the entire matched substring: + * + * ``` + * scanner[0] # => "Fri Dec 12 " + * scanner.pre_match # => "" + * scanner.post_match # => "1975 14:39" + * ``` + * + * - `specifier` positive integer. returns the `n`th capture, or `nil` if out of range: + * + * ``` + * scanner[1] # => "Fri" + * scanner[2] # => "Dec" + * scanner[3] # => "12" + * scanner[4] # => nil + * ``` + * + * - `specifier` negative integer. counts backward from the last subgroup: + * + * ``` + * scanner[-1] # => "12" + * scanner[-4] # => "Fri Dec 12 " + * scanner[-5] # => nil + * ``` + * + * - `specifier` symbol or string. returns the named subgroup, or `nil` if no such: + * + * ``` + * scanner[:wday] # => "Fri" + * scanner['wday'] # => "Fri" + * scanner[:month] # => "Dec" + * scanner[:day] # => "12" + * scanner[:nope] # => nil + * ``` + * + * When there are no captures, only `[0]` returns non-`nil`: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.exist?(/bar/) + * scanner[0] # => "bar" + * scanner[1] # => nil + * ``` + * + * For a failed match, even `[0]` returns `nil`: + * + * ``` + * scanner.scan(/nope/) # => nil + * scanner[0] # => nil + * scanner[1] # => nil + * ``` * - * s.reset - * s.scan(/(?\w+) (?\w+) (?\d+) /) # -> "Fri Dec 12 " - * s[0] # -> "Fri Dec 12 " - * s[1] # -> "Fri" - * s[2] # -> "Dec" - * s[3] # -> "12" - * s[:wday] # -> "Fri" - * s[:month] # -> "Dec" - * s[:day] # -> "12" - * s.post_match # -> "1975 14:39" - * s.pre_match # -> "" */ static VALUE strscan_aref(VALUE self, VALUE idx) @@ -1268,14 +1647,28 @@ strscan_aref(VALUE self, VALUE idx) } /* - * call-seq: size + * :markup: markdown + * :include: strscan/link_refs.txt * - * Returns the amount of subgroups in the most recent match. - * The full match counts as a subgroup. + * call-seq: + * size -> captures_count + * + * Returns the count of captures if the most recent match attempt succeeded, `nil` otherwise; + * see [Captures Match Values][13]: + * + * ``` + * scanner = StringScanner.new('Fri Dec 12 1975 14:39') + * scanner.size # => nil + * + * pattern = /(?\w+) (?\w+) (?\d+) / + * scanner.match?(pattern) + * scanner.values_at(*0..scanner.size) # => ["Fri Dec 12 ", "Fri", "Dec", "12", nil] + * scanner.size # => 4 + * + * scanner.match?(/nope/) # => nil + * scanner.size # => nil + * ``` * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.scan(/(\w+) (\w+) (\d+) /) # -> "Fri Dec 12 " - * s.size # -> 4 */ static VALUE strscan_size(VALUE self) @@ -1288,16 +1681,30 @@ strscan_size(VALUE self) } /* - * call-seq: captures + * :markup: markdown + * :include: strscan/link_refs.txt * - * Returns the subgroups in the most recent match (not including the full match). - * If nothing was priorly matched, it returns nil. + * call-seq: + * captures -> substring_array or nil + * + * Returns the array of [captured match values][13] at indexes `(1..)` + * if the most recent match attempt succeeded, or `nil` otherwise: + * + * ``` + * scanner = StringScanner.new('Fri Dec 12 1975 14:39') + * scanner.captures # => nil + * + * scanner.exist?(/(?\w+) (?\w+) (?\d+) /) + * scanner.captures # => ["Fri", "Dec", "12"] + * scanner.values_at(*0..4) # => ["Fri Dec 12 ", "Fri", "Dec", "12", nil] + * + * scanner.exist?(/Fri/) + * scanner.captures # => [] + * + * scanner.scan(/nope/) + * scanner.captures # => nil + * ``` * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.scan(/(\w+) (\w+) (\d+) (1980)?/) # -> "Fri Dec 12 " - * s.captures # -> ["Fri", "Dec", "12", nil] - * s.scan(/(\w+) (\w+) (\d+) (1980)?/) # -> nil - * s.captures # -> nil */ static VALUE strscan_captures(VALUE self) @@ -1327,17 +1734,25 @@ strscan_captures(VALUE self) } /* - * call-seq: - * scanner.values_at( i1, i2, ... iN ) -> an_array + * :markup: markdown + * :include: strscan/link_refs.txt * - * Returns the subgroups in the most recent match at the given indices. - * If nothing was priorly matched, it returns nil. + * call-seq: + * values_at(*specifiers) -> array_of_captures or nil + * + * Returns an array of captured substrings, or `nil` of none. + * + * For each `specifier`, the returned substring is `[specifier]`; + * see #[]. + * + * ``` + * scanner = StringScanner.new('Fri Dec 12 1975 14:39') + * pattern = /(?\w+) (?\w+) (?\d+) / + * scanner.match?(pattern) + * scanner.values_at(*0..3) # => ["Fri Dec 12 ", "Fri", "Dec", "12"] + * scanner.values_at(*%i[wday month day]) # => ["Fri", "Dec", "12"] + * ``` * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.scan(/(\w+) (\w+) (\d+) /) # -> "Fri Dec 12 " - * s.values_at 0, -1, 5, 2 # -> ["Fri Dec 12 ", "12", nil, "Dec"] - * s.scan(/(\w+) (\w+) (\d+) /) # -> nil - * s.values_at 0, -1, 5, 2 # -> nil */ static VALUE @@ -1359,13 +1774,29 @@ strscan_values_at(int argc, VALUE *argv, VALUE self) } /* - * Returns the pre-match (in the regular expression sense) of the last scan. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * pre_match -> substring + * + * Returns the substring that precedes the matched substring + * from the most recent match attempt if it was successful, + * or `nil` otherwise; + * see [Basic Match Values][18]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.pre_match # => nil + * + * scanner.pos = 3 + * scanner.exist?(/baz/) # => 6 + * scanner.pre_match # => "foobar" # Substring of entire string, not just target string. + * + * scanner.exist?(/nope/) # => nil + * scanner.pre_match # => nil + * ``` * - * s = StringScanner.new('test string') - * s.scan(/\w+/) # -> "test" - * s.scan(/\s+/) # -> " " - * s.pre_match # -> "test" - * s.post_match # -> "string" */ static VALUE strscan_pre_match(VALUE self) @@ -1380,13 +1811,29 @@ strscan_pre_match(VALUE self) } /* - * Returns the post-match (in the regular expression sense) of the last scan. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * post_match -> substring + * + * Returns the substring that follows the matched substring + * from the most recent match attempt if it was successful, + * or `nil` otherwise; + * see [Basic Match Values][18]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.post_match # => nil + * + * scanner.pos = 3 + * scanner.match?(/bar/) # => 3 + * scanner.post_match # => "baz" + * + * scanner.match?(/nope/) # => nil + * scanner.post_match # => nil + * ``` * - * s = StringScanner.new('test string') - * s.scan(/\w+/) # -> "test" - * s.scan(/\s+/) # -> " " - * s.pre_match # -> "test" - * s.post_match # -> "string" */ static VALUE strscan_post_match(VALUE self) @@ -1401,8 +1848,24 @@ strscan_post_match(VALUE self) } /* - * Returns the "rest" of the string (i.e. everything after the scan pointer). - * If there is no more data (eos? = true), it returns "". + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * rest -> target_substring + * + * Returns the 'rest' of the [stored string][1] (all after the current [position][2]), + * which is the [target substring][3]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.rest # => "foobarbaz" + * scanner.pos = 3 + * scanner.rest # => "barbaz" + * scanner.terminate + * scanner.rest # => "" + * ``` + * */ static VALUE strscan_rest(VALUE self) @@ -1417,7 +1880,26 @@ strscan_rest(VALUE self) } /* - * s.rest_size is equivalent to s.rest.size. + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * rest_size -> integer + * + * Returns the size (in bytes) of the #rest of the [stored string][1]: + * + * ``` + * scanner = StringScanner.new('foobarbaz') + * scanner.rest # => "foobarbaz" + * scanner.rest_size # => 9 + * scanner.pos = 3 + * scanner.rest # => "barbaz" + * scanner.rest_size # => 6 + * scanner.terminate + * scanner.rest # => "" + * scanner.rest_size # => 0 + * ``` + * */ static VALUE strscan_rest_size(VALUE self) @@ -1434,9 +1916,14 @@ strscan_rest_size(VALUE self) } /* + * call-seq: + * restsize + * * s.restsize is equivalent to s.rest_size. * This method is obsolete; use #rest_size instead. */ + + /* :nodoc: */ static VALUE strscan_restsize(VALUE self) { @@ -1447,15 +1934,39 @@ strscan_restsize(VALUE self) #define INSPECT_LENGTH 5 /* - * Returns a string that represents the StringScanner object, showing: - * - the current position - * - the size of the string - * - the characters surrounding the scan pointer + * :markup: markdown + * :include: strscan/link_refs.txt + * + * call-seq: + * inspect -> string + * + * Returns a string representation of `self` that may show: + * + * 1. The current [position][2]. + * 2. The size (in bytes) of the [stored string][1]. + * 3. The substring preceding the current position. + * 4. The substring following the current position (which is also the [target substring][3]). + * + * ``` + * scanner = StringScanner.new("Fri Dec 12 1975 14:39") + * scanner.pos = 11 + * scanner.inspect # => "#" + * ``` + * + * If at beginning-of-string, item 4 above (following substring) is omitted: + * + * ``` + * scanner.reset + * scanner.inspect # => "#" + * ``` + * + * If at end-of-string, all items above are omitted: + * + * ``` + * scanner.terminate + * scanner.inspect # => "#" + * ``` * - * s = StringScanner.new("Fri Dec 12 1975 14:39") - * s.inspect # -> '#' - * s.scan_until /12/ # -> "Fri Dec 12" - * s.inspect # -> '#' */ static VALUE strscan_inspect(VALUE self) @@ -1527,13 +2038,13 @@ inspect2(struct strscanner *p) } /* + * :markup: markdown + * :include: strscan/link_refs.txt + * * call-seq: - * scanner.fixed_anchor? -> true or false + * fixed_anchor? -> true or false * - * Whether +scanner+ uses fixed anchor mode or not. - * - * If fixed anchor mode is used, +\A+ always matches the beginning of - * the string. Otherwise, +\A+ always matches the current position. + * Returns whether the [fixed-anchor property][10] is set. */ static VALUE strscan_fixed_anchor_p(VALUE self) @@ -1569,14 +2080,32 @@ named_captures_iter(const OnigUChar *name, } /* + * :markup: markdown + * :include: strscan/link_refs.txt + * * call-seq: - * scanner.named_captures -> hash + * named_captures -> hash * - * Returns a hash of string variables matching the regular expression. + * Returns the array of captured match values at indexes (1..) + * if the most recent match attempt succeeded, or nil otherwise; + * see [Captured Match Values][13]: + * + * ``` + * scanner = StringScanner.new('Fri Dec 12 1975 14:39') + * scanner.named_captures # => {} + * + * pattern = /(?\w+) (?\w+) (?\d+) / + * scanner.match?(pattern) + * scanner.named_captures # => {"wday"=>"Fri", "month"=>"Dec", "day"=>"12"} + * + * scanner.string = 'nope' + * scanner.match?(pattern) + * scanner.named_captures # => {"wday"=>nil, "month"=>nil, "day"=>nil} + * + * scanner.match?(/nosuch/) + * scanner.named_captures # => {} + * ``` * - * scan = StringScanner.new('foobarbaz') - * scan.match?(/(?foo)(?bar)(?baz)/) - * scan.named_captures # -> {"f"=>"foo", "r"=>"bar", "z"=>"baz"} */ static VALUE strscan_named_captures(VALUE self) @@ -1600,109 +2129,11 @@ strscan_named_captures(VALUE self) /* * Document-class: StringScanner * - * StringScanner provides for lexical scanning operations on a String. Here is - * an example of its usage: + * :markup: markdown * - * require 'strscan' + * :include: strscan/link_refs.txt + * :include: strscan/strscan.md * - * s = StringScanner.new('This is an example string') - * s.eos? # -> false - * - * p s.scan(/\w+/) # -> "This" - * p s.scan(/\w+/) # -> nil - * p s.scan(/\s+/) # -> " " - * p s.scan(/\s+/) # -> nil - * p s.scan(/\w+/) # -> "is" - * s.eos? # -> false - * - * p s.scan(/\s+/) # -> " " - * p s.scan(/\w+/) # -> "an" - * p s.scan(/\s+/) # -> " " - * p s.scan(/\w+/) # -> "example" - * p s.scan(/\s+/) # -> " " - * p s.scan(/\w+/) # -> "string" - * s.eos? # -> true - * - * p s.scan(/\s+/) # -> nil - * p s.scan(/\w+/) # -> nil - * - * Scanning a string means remembering the position of a scan pointer, - * which is just an index. The point of scanning is to move forward a bit at - * a time, so matches are sought after the scan pointer; usually immediately - * after it. - * - * Given the string "test string", here are the pertinent scan pointer - * positions: - * - * t e s t s t r i n g - * 0 1 2 ... 1 - * 0 - * - * When you #scan for a pattern (a regular expression), the match must occur - * at the character after the scan pointer. If you use #scan_until, then the - * match can occur anywhere after the scan pointer. In both cases, the scan - * pointer moves just beyond the last character of the match, ready to - * scan again from the next character onwards. This is demonstrated by the - * example above. - * - * == Method Categories - * - * There are other methods besides the plain scanners. You can look ahead in - * the string without actually scanning. You can access the most recent match. - * You can modify the string being scanned, reset or terminate the scanner, - * find out or change the position of the scan pointer, skip ahead, and so on. - * - * === Advancing the Scan Pointer - * - * - #getch - * - #get_byte - * - #scan_byte - * - #scan - * - #scan_until - * - #skip - * - #skip_until - * - * === Looking Ahead - * - * - #check - * - #check_until - * - #exist? - * - #match? - * - #peek - * - #peek_byte - * - * === Finding Where we Are - * - * - #beginning_of_line? (#bol?) - * - #eos? - * - #rest? - * - #rest_size - * - #pos - * - * === Setting Where we Are - * - * - #reset - * - #terminate - * - #pos= - * - * === Match Data - * - * - #matched - * - #matched? - * - #matched_size - * - #[] - * - #pre_match - * - #post_match - * - * === Miscellaneous - * - * - << - * - #concat - * - #string - * - #string= - * - #unscan - * - * There are aliases to several of the methods. */ void Init_strscan(void) diff --git a/ext/strscan/strscan.gemspec b/ext/strscan/strscan.gemspec index 8a61c7abe6..925edcd2d3 100644 --- a/ext/strscan/strscan.gemspec +++ b/ext/strscan/strscan.gemspec @@ -29,6 +29,11 @@ Gem::Specification.new do |s| s.require_paths = %w{lib} files << "ext/strscan/extconf.rb" files << "ext/strscan/strscan.c" + s.rdoc_options << "-idoc" + s.extra_rdoc_files = [ + ".rdoc_options", + *Dir.glob("doc/strscan/**/*") + ] s.extensions = %w{ext/strscan/extconf.rb} end s.files = files diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 0307028eb7..b624282314 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -273,6 +273,7 @@ module SyncDefaultGems cp_r("#{upstream}/ext/strscan", "ext") cp_r("#{upstream}/test/strscan", "test") cp_r("#{upstream}/strscan.gemspec", "ext/strscan") + cp_r("#{upstream}/doc/strscan", "doc") rm_rf(%w["ext/strscan/regenc.h ext/strscan/regint.h"]) `git checkout ext/strscan/depend` when "cgi"