regexp_parser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/ChangeLog +4 -0
  2. data/LICENSE +22 -0
  3. data/README.rdoc +307 -0
  4. data/Rakefile +91 -0
  5. data/lib/regexp_parser/ctype.rb +48 -0
  6. data/lib/regexp_parser/expression/property.rb +108 -0
  7. data/lib/regexp_parser/expression/set.rb +59 -0
  8. data/lib/regexp_parser/expression.rb +287 -0
  9. data/lib/regexp_parser/lexer.rb +105 -0
  10. data/lib/regexp_parser/parser.rb +417 -0
  11. data/lib/regexp_parser/scanner/property.rl +534 -0
  12. data/lib/regexp_parser/scanner/scanner.rl +712 -0
  13. data/lib/regexp_parser/scanner.rb +3325 -0
  14. data/lib/regexp_parser/syntax/ruby/1.8.6.rb +14 -0
  15. data/lib/regexp_parser/syntax/ruby/1.8.7.rb +14 -0
  16. data/lib/regexp_parser/syntax/ruby/1.8.rb +39 -0
  17. data/lib/regexp_parser/syntax/ruby/1.9.1.rb +39 -0
  18. data/lib/regexp_parser/syntax/ruby/1.9.2.rb +10 -0
  19. data/lib/regexp_parser/syntax/ruby/1.9.3.rb +24 -0
  20. data/lib/regexp_parser/syntax/ruby/1.9.rb +8 -0
  21. data/lib/regexp_parser/syntax/tokens.rb +332 -0
  22. data/lib/regexp_parser/syntax.rb +172 -0
  23. data/lib/regexp_parser.rb +45 -0
  24. data/test/helpers.rb +8 -0
  25. data/test/lexer/test_all.rb +26 -0
  26. data/test/lexer/test_literals.rb +120 -0
  27. data/test/lexer/test_nesting.rb +107 -0
  28. data/test/lexer/test_refcalls.rb +45 -0
  29. data/test/parser/test_all.rb +44 -0
  30. data/test/parser/test_alternation.rb +46 -0
  31. data/test/parser/test_anchors.rb +35 -0
  32. data/test/parser/test_errors.rb +59 -0
  33. data/test/parser/test_escapes.rb +48 -0
  34. data/test/parser/test_expression.rb +51 -0
  35. data/test/parser/test_groups.rb +69 -0
  36. data/test/parser/test_properties.rb +346 -0
  37. data/test/parser/test_quantifiers.rb +236 -0
  38. data/test/parser/test_refcalls.rb +101 -0
  39. data/test/parser/test_sets.rb +99 -0
  40. data/test/scanner/test_all.rb +30 -0
  41. data/test/scanner/test_anchors.rb +35 -0
  42. data/test/scanner/test_errors.rb +36 -0
  43. data/test/scanner/test_escapes.rb +49 -0
  44. data/test/scanner/test_groups.rb +41 -0
  45. data/test/scanner/test_literals.rb +85 -0
  46. data/test/scanner/test_meta.rb +36 -0
  47. data/test/scanner/test_properties.rb +315 -0
  48. data/test/scanner/test_quantifiers.rb +38 -0
  49. data/test/scanner/test_refcalls.rb +45 -0
  50. data/test/scanner/test_scripts.rb +314 -0
  51. data/test/scanner/test_sets.rb +80 -0
  52. data/test/scanner/test_types.rb +30 -0
  53. data/test/syntax/ruby/test_1.8.rb +57 -0
  54. data/test/syntax/ruby/test_1.9.1.rb +39 -0
  55. data/test/syntax/ruby/test_1.9.3.rb +38 -0
  56. data/test/syntax/ruby/test_all.rb +12 -0
  57. data/test/syntax/test_all.rb +19 -0
  58. data/test/test_all.rb +4 -0
  59. metadata +160 -0
data/ChangeLog ADDED
@@ -0,0 +1,4 @@
1
+
2
+ Sat Nov 20 16:40:10 EET 2010 Ammar Ali <ammarabuali@gmail.com>
3
+
4
+ * Initial version bump to 0.1.0
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010 Ammar Ali
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,307 @@
1
+ = Regexp::Parser
2
+
3
+ == What?
4
+ A ruby library to help with lexing, parsing, and transforming regular expressions.
5
+
6
+ * Multilayered
7
+
8
+ * A scanner based on ragel[http://www.complang.org/ragel/]
9
+ * A lexer that produces a "stream" of tokens
10
+ * A parser that produces a "tree" of Regexp::Expression objects (OO API)
11
+
12
+ * Lexes and parses both 1.8 and 1.9 regular expression flavors
13
+ * Supports ruby 1.8 and 1.9 runtime
14
+
15
+ For an example of regexp_parser in use, see the meta_re project[https://github.com/ammar/meta_re]
16
+
17
+ ---
18
+ == Requirements
19
+
20
+ * ruby '1.8.6'..'1.9.2'
21
+ * ragel, but only if you want to hack on the scanner
22
+
23
+
24
+ ---
25
+ == Install
26
+
27
+ gem install regexp_parser
28
+
29
+ ---
30
+ == Components
31
+ === Scanner
32
+ A ragel generated scanner that recognizes the cumulative syntax of both
33
+ supported flavors. Breaks the expression's text into tokens, including
34
+ their type, token, text, and start/end offsets within the original
35
+ pattern.
36
+
37
+ ==== Example
38
+ The following scans the given pattern and prints out the type, token, text and
39
+ start/end offsets for each token found.
40
+
41
+ require 'regexp_parser'
42
+
43
+ Regexp::Scanner.scan /(ab?(cd)*[e-h]+)/ do |type, token, text, ts, te|
44
+ puts "type: #{type}, token: #{token}, text: '#{text}' [#{ts}..#{te}]"
45
+ end
46
+
47
+ A one-liner that returns an array of the textual parts of the given pattern:
48
+
49
+ Regexp::Scanner.scan( /(cat?([b]at)){3,5}/ ).map {|token| token[2]}
50
+ #=> ["(", "cat", "?", "(", "[", "b", "]", "at", ")", ")", "{3,5}"]
51
+
52
+
53
+ ==== Notes
54
+ * The scanner performs basic syntax error checking, like detecting missing
55
+ balancing punctuation and premature end of pattern. Flavor validity checks
56
+ are performed in the lexer.
57
+
58
+ * To keep the scanner simple(r) and fairly reusable for other uses, it
59
+ does not perform lexical analysis on the tokens, sticking to the task
60
+ of tokenizing and leaving lexical analysis upto to the lexer.
61
+
62
+ * If the input is a ruby Regexp object, the scanner calls #source on it to
63
+ get its string representation. #source does not include the options of
64
+ expression (m, i, and x) To include the options the scan, #to_s should
65
+ be called on the Regexp before passing it to the scanner, or any of the
66
+ higher layers.
67
+
68
+
69
+ ---
70
+ === Syntax
71
+ Defines the supported tokens for a specific engine implementation (aka a
72
+ flavor). Syntax classes act as lookup tables, and are layered to create
73
+ flavor variations. Syntax only comes into play in the lexer.
74
+
75
+ ==== Example
76
+ The following instantiates the syntax for Ruby 1.9 and checks a couple of its
77
+ implementations features, and then does the same for Ruby 1.8:
78
+
79
+ require 'regexp_parser'
80
+
81
+ ruby_19 = Regexp::Syntax.new 'ruby/1.9'
82
+ ruby_19.implements? :quantifier, :zero_or_one # => true
83
+ ruby_19.implements? :quantifier, :zero_or_one_reluctant # => true
84
+ ruby_19.implements? :quantifier, :zero_or_one_possessive # => true
85
+
86
+ ruby_18 = Regexp::Syntax.new 'ruby/1.8'
87
+ ruby_18.implements? :quantifier, :zero_or_one # => true
88
+ ruby_18.implements? :quantifier, :zero_or_one_reluctant # => true
89
+ ruby_18.implements? :quantifier, :zero_or_one_possessive # => false
90
+
91
+
92
+ ==== Notes
93
+ * Variatiions on a token, for example a named group with < and > vs one with a
94
+ pair of single quotes, are specified with an underscore followed by two
95
+ characters appended to the base token. In the previous named group example,
96
+ the tokens would be :named_ab (angle brackets) and :named_sq (single quotes).
97
+ These variations are normalized by the syntax to :named.
98
+
99
+ ==== TODO
100
+ * Add flavor limits: like Ruby 1.8's maximum allowed number of grouped
101
+ expressions (253).
102
+
103
+
104
+ ---
105
+ === Lexer
106
+ Sits on top of the scanner and performs lexical analysis on the tokens that
107
+ it emits. Among its tasks are breaking quantified literal runs, collecting the
108
+ emitted token structures into an array of Token objects, calculating their
109
+ nesting depth, normalizing tokens for the parser, and checkng if the tokens
110
+ are implemented by the given syntax flavor.
111
+
112
+ Tokens objects are Structs, basically data objects, with a few helper methods,
113
+ like #next, #previous, #offsets and #length.
114
+
115
+ ==== Example
116
+ The following example scans the given pattern, checks it against the ruby 1.8
117
+ syntax, and prints the token objects' text.
118
+
119
+ require 'regexp_parser'
120
+
121
+ Regexp::Lexer.scan(/a?(b)*[c]+/, 'ruby/1.8') do |token|
122
+ puts "#{' ' * token.depth}#{token.text}"
123
+ end
124
+
125
+ A one-liner that returns an array of the textual parts of the given pattern.
126
+ Compare the output with that of the one-liner example of the Scanner.
127
+
128
+ Regexp::Lexer.scan( /(cat?([b]at)){3,5}/ ).map {|token| token.text}
129
+ #=> ["(", "ca", "t", "?", "(", "[", "b", "]", "at", ")", ")", "{3,5}"]
130
+
131
+ ==== Notes
132
+ * The default syntax is that of the latest released version of ruby.
133
+
134
+ * The lexer performs some basic parsing to determine the depth of a the
135
+ emitted tokens. This responsibility might be relegated to the scanner.
136
+
137
+
138
+ ---
139
+ === Parser
140
+ Sits on top of the lexer and transforms the "stream" of Token objects emitted
141
+ by it into a tree of Expression objects represented by an instance of the
142
+ Expression::Root class. See Expression below for more information.
143
+
144
+ ==== Example
145
+
146
+ require 'regexp_parser'
147
+
148
+ regex = /a?(b)*[c]+/m
149
+
150
+ # using #to_s on the Regexp object to include options
151
+ root = Regexp::Parser.parse( regex.to_s, 'ruby/1.8')
152
+
153
+ root.multiline? # => true (aliased as m?)
154
+ root.case_insensitive? # => false (aliased as i?)
155
+
156
+ # simple tree walking method
157
+ def walk(e, depth = 0)
158
+ puts "#{' ' * depth}> #{e.class}"
159
+ unless e.expressions.empty?
160
+ e.each {|s| walk(s, depth+1) }
161
+ end
162
+ end
163
+
164
+ walk(root)
165
+
166
+ # output
167
+ > Regexp::Expression::Root
168
+ > Regexp::Expression::Literal
169
+ > Regexp::Expression::Group::Capture
170
+ > Regexp::Expression::Literal
171
+ > Regexp::Expression::CharacterSet
172
+
173
+ Note: quantifiers do not appear in the output because they are members of the
174
+ Expression class. See the next section for more details.
175
+
176
+ ---
177
+ === Expression
178
+ The base class of all objects returned by the parser, implements most of the
179
+ functions that are common to all expression classes.
180
+
181
+ Each Expression object contains the following members:
182
+
183
+ * quantifier: an instance of Expression::Quantifier that holds the details
184
+ of repetition for the Expression. Has a nil value if the expressions is not
185
+ quantified.
186
+
187
+ * expressions: an array, holds the sub-expressions for the expression if it
188
+ is a group or alternation expression. Empty if the expression doesn't have
189
+ sub-expressions.
190
+
191
+ * options: a hash, holds the keys :i, :m, and :x with a boolean value that
192
+ indicates if the expression has a given option.
193
+
194
+ Expressions also contain the following "lower level" members
195
+ (from the scanner/lexer)
196
+
197
+ * type: a symbol, denoting the expression type, such as :group, :quantifier
198
+ * token: a symbol, for the object's token, or opening token (in the case of
199
+ groups and sets)
200
+ * text: a string, the text of the expression (same as token for nesting expressions)
201
+
202
+ Every expressions also has the following methods:
203
+
204
+ * to_s: returns the string representation of the expression.
205
+ * <<: adds sub-expresions to the expression.
206
+ * each: iterates over the expressions sub-expressions, if any.
207
+ * []: access sub-expressions by index.
208
+ * quantified?: return true if the expressions was followed by a quantifier.
209
+ * quantity: returns an array of the expression's min and max repetitions.
210
+ * greedy?: returns true if the expression's quantifier is greedy.
211
+ * reluctant? or lazy?: returns true if the expression's quantifier is
212
+ reluctant.
213
+ * possessive?: returns true if the expression's quantifier is possessive.
214
+ * multiline? or m?: returns true if the expression has the m option
215
+ * case_insensitive? or ignore_case? or i?: returns true if the expression
216
+ has the i option
217
+ * free_spacing? or extended? or x?: returns true if the expression has the x
218
+ option
219
+
220
+ A special expression class Expression::Sequence is used to hold the array of
221
+ possible alternatives within an Expression::Alternation expression.
222
+
223
+
224
+ == Scanner Syntax
225
+ The following syntax elements are supported by the scanner.
226
+
227
+ - Alternation: a|b|c, etc.
228
+ - Anchors: ^, $, \b, etc.
229
+ - Character Classes (aka Sets): [abc], [^\]]
230
+ - Character Types: \d, \H, \s, etc.
231
+ - Escape Sequences: \t, \+, \?, etc.
232
+ - Grouped Expressions
233
+ - Assertions
234
+ - Lookahead: (?=abc)
235
+ - Negative Lookahead: (?!abc)
236
+ - Lookabehind: (?<=abc)
237
+ - Negative Lookbehind: (?<\!abc)
238
+ - Atomic: (?>abc)
239
+ - Back-references:
240
+ - Named: \k<name>
241
+ - Nest Level: \k<n-1>
242
+ - Numbered: \k<1>
243
+ - Relative: \k<-2>
244
+ - Capturing: (abc)
245
+ - Comment: (?# comment)
246
+ - Named: (?<name>abc)
247
+ - Options: (?mi-x:abc)
248
+ - Passive: (?:abc)
249
+ - Sub-expression Calls: \g<name>, \g<1>
250
+ - Literals: abc, def?, etc.
251
+ - POSIX classes: [:alpha:], [:print:], etc.
252
+ - Quantifiers
253
+ - Greedy: ?, *, +, {m,M}
254
+ - Reluctant: ??, *?, +?, {m,M}?
255
+ - Possessive: ?+, *+, ++, {m,M}+
256
+ - String Escapes
257
+ - Control: \C-C, \cD, etc.
258
+ - Hex: \x20, \x{701230}, etc.
259
+ - Meta: \M-c, \M-\C-C etc.
260
+ - Octal: \0, \01, \012
261
+ - Unicode: \uHHHH, \u{H+ H+}
262
+ - Traditional Back-references: \1 thru \9
263
+ - Unicode Properties:
264
+ - Age: \p{Age=2.1}, \P{age=5.2}, etc.
265
+ - Classes: \p{Alpha}, \P{Space}, etc.
266
+ - Derived Properties: \p{Math}, \P{Lowercase}, etc.
267
+ - General Categories: \p{Lu}, \P{Cs}, etc.
268
+ - Scripts: \p{Arabic}, \P{Hiragana}, etc.
269
+ - Simple Properties: \p{Dash}, \p{Extender}, etc.
270
+
271
+ See something missing? Please submit an issue[https://github.com/ammar/regexp_parser/issues]
272
+
273
+ == References
274
+ Documentation and information being read while working on this project.
275
+
276
+ ==== Ruby Flavors
277
+ * Oniguruma Regular Expressions link[http://www.geocities.jp/kosako3/oniguruma/doc/RE.txt]
278
+ * Read Ruby > Regexps link[http://ruby.runpaint.org/regexps]
279
+
280
+
281
+ ==== General
282
+ * Enumerating the strings of regular languages link[http://www.cs.dartmouth.edu/~doug/nfa.ps.gz]
283
+ * Mastering Regular Expressions, By Jeffrey E.F. Friedl (2nd Edition) book[http://oreilly.com/catalog/9781565922570/]
284
+ * Regular Expression Flavor Comparison link[http://www.regular-expressions.info/refflavors.html]
285
+
286
+
287
+ ==== Unicode
288
+ * Unicode Derived Properties link[http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt]
289
+ * Unicode Explained, By Jukka K. Korpela. book[http://oreilly.com/catalog/9780596101213]
290
+ * Unicode Property Aliases link[http://www.unicode.org/Public/UNIDATA/PropertyAliases.txt]
291
+ * Unicode Regular Expressions link[http://www.unicode.org/reports/tr18/]
292
+ * Unicode Standard Annex #44 link[http://www.unicode.org/reports/tr44/]
293
+
294
+ == Thanks
295
+ This work is based on and inspired by the hard work and ideas of many people,
296
+ directly or indirectly. The following are only a few of those that should be
297
+ thanked.
298
+
299
+ * Adrian Thurston, for developing ragel[http://www.complang.org/ragel/].
300
+ * Caleb Clausen, for feedback, which inspired this, valuable insights on structuring the parser,
301
+ and lots of cool code[http://github.com/coatl].
302
+ * Jan Goyvaerts, for his excellent resource[http://www.regular-expressions.info] on regular expressions. I owe him a "steak dinner", at least.
303
+ * Run Paint Run Run, for his work on Read[http://ruby.runpaint.org/] Ruby
304
+ * Yukihiro Matsumoto, of course! For "The Ruby", of course!
305
+
306
+ == Copyright
307
+ Copyright (c) 2010 Ammar Ali. See LICENSE file for details.
data/Rakefile ADDED
@@ -0,0 +1,91 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/gempackagetask'
4
+
5
+ task :default => [:test]
6
+
7
+ RAGEL_SOURCE_DIR = File.expand_path '../lib/regexp_parser/scanner', __FILE__
8
+ RAGEL_OUTPUT_DIR = File.expand_path '../lib/regexp_parser', __FILE__
9
+
10
+ RAGEL_SOURCE_FILES = %w{scanner}
11
+
12
+ desc "Find and run all unit tests under test/ directory"
13
+ Rake::TestTask.new("test") do |t|
14
+ t.libs << "test"
15
+ t.test_files = FileList['test/**/test_*.rb']
16
+ end
17
+
18
+ task :test
19
+
20
+ namespace :test do
21
+ desc "Run all scanner tests"
22
+ Rake::TestTask.new("scanner") do |t|
23
+ t.libs << "test"
24
+ t.test_files = ['test/scanner/test_all.rb']
25
+ end
26
+
27
+ desc "Run all lexer tests"
28
+ Rake::TestTask.new("lexer") do |t|
29
+ t.libs << "test"
30
+ t.test_files = ['test/lexer/test_all.rb']
31
+ end
32
+
33
+ desc "Run all parser tests"
34
+ Rake::TestTask.new("parser") do |t|
35
+ t.libs << "test"
36
+ t.test_files = ['test/parser/test_all.rb']
37
+ end
38
+
39
+ desc "Run all syntax tests"
40
+ Rake::TestTask.new("syntax") do |t|
41
+ t.libs << "test"
42
+ t.test_files = ['test/syntax/test_all.rb']
43
+ end
44
+ end
45
+
46
+ namespace :ragel do
47
+ desc "Process the ragel source files and output ruby code"
48
+ task :rb do |t|
49
+ RAGEL_SOURCE_FILES.each do |file|
50
+ # using faster flat table driven FSM, about 25% larger code, but about 30% faster
51
+ sh "ragel -F1 -R #{RAGEL_SOURCE_DIR}/#{file}.rl -o #{RAGEL_OUTPUT_DIR}/#{file}.rb"
52
+ end
53
+ end
54
+
55
+ desc "Delete the ragel generated source file(s)"
56
+ task :clean do |t|
57
+ RAGEL_SOURCE_FILES.each do |file|
58
+ sh "rm -f #{RAGEL_OUTPUT_DIR}/#{file}.rb"
59
+ end
60
+ end
61
+ end
62
+
63
+ spec = Gem::Specification.new do |s|
64
+ s.name = 'regexp_parser'
65
+ s.version = '0.1.0'
66
+ s.summary = %q{Scanner, lexer, parser for ruby's regular expressions}
67
+ s.description = %q{A library for tokenizing, lexing, and parsing Ruby regular expressions.}
68
+ s.date = '2010-10-01'
69
+ s.authors = ["Ammar Ali"]
70
+ s.email = 'ammarabuali@gmail.com'
71
+ s.homepage = %q{http://github.com/ammar/regexp_parser}
72
+ s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
73
+ s.require_paths = ["lib"]
74
+ s.rubygems_version = %q{1.3.7}
75
+
76
+ s.files = Dir.glob("{lib,test}/**/*.rb") + Dir.glob("lib/**/*.rl") +
77
+ %w(Rakefile LICENSE README.rdoc ChangeLog)
78
+
79
+ s.test_files = Dir.glob("test/**/*.rb")
80
+ s.extra_rdoc_files = ["ChangeLog", "LICENSE", "README.rdoc"]
81
+ s.required_rubygems_version = ">= 1.3.7"
82
+ s.rubyforge_project = "regexp_parser"
83
+ s.require_path = 'lib'
84
+ end
85
+
86
+ Rake::GemPackageTask.new(spec) do |pkg|
87
+ Rake::Task['ragel:rb'].execute
88
+
89
+ pkg.need_zip = true
90
+ pkg.need_tar = true
91
+ end
@@ -0,0 +1,48 @@
1
+ # defines character type constants (as arrays) and methods that test
2
+ # whether a given character belongs in one of them.
3
+ module CType
4
+ Digit = ('0'..'9').to_a.freeze
5
+ Lower = ('a'..'z').to_a.freeze
6
+ Upper = ('A'..'Z').to_a.freeze
7
+
8
+ Alpha = [Lower, Upper].flatten.freeze
9
+ Alnum = [Alpha, Digit].flatten.freeze
10
+ Word = [Alnum, '_'].flatten.freeze
11
+
12
+ Blank = [' ', "\t"].freeze
13
+ Space = [" ", "\t", "\r", "\n", "\v", "\f"].freeze
14
+
15
+ Cntrl = ( 0..31 ).map {|c| c.chr}.freeze
16
+ Graph = (33..126).map {|c| c.chr}.freeze
17
+ Print = (32..126).map {|c| c.chr}.freeze
18
+ ASCII = ( 0..127).map {|c| c.chr}.freeze
19
+
20
+ Punct = [
21
+ ('!'..'/').to_a,
22
+ (':'..'@').to_a,
23
+ ('['..'`').to_a,
24
+ ('{'..'~').to_a
25
+ ].flatten.freeze
26
+
27
+ XDigit = [
28
+ Digit,
29
+ ('a'..'f').to_a,
30
+ ('A'..'F').to_a
31
+ ].flatten.freeze
32
+
33
+ def self.alnum?(c); Alnum.include?(c) end
34
+ def self.alpha?(c); Alpha.include?(c) end
35
+ def self.blank?(c); Blank.include?(c) end
36
+ def self.cntrl?(c); Cntrl.include?(c) end
37
+ def self.digit?(c); Digit.include?(c) end
38
+ def self.graph?(c); Graph.include?(c) end
39
+ def self.lower?(c); Lower.include?(c) end
40
+ def self.print?(c); Print.include?(c) end
41
+ def self.punct?(c); Punct.include?(c) end
42
+ def self.space?(c); Space.include?(c) end
43
+ def self.upper?(c); Upper.include?(c) end
44
+ def self.xdigit?(c); XDigit.include?(c) end
45
+
46
+ def self.word?(c); Word.include?(c) end
47
+ def self.ascii?(c); ASCII.include?(c) end
48
+ end