tokenzr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2371c68d11dadc0ca627f29af05d9d4a2f8bbb73d1986d29c554f61d16b1fd65
4
+ data.tar.gz: 7b03309a452cb7d4aefb575b480c84fd573c7513b869cd377e241bfa0fcf9f30
5
+ SHA512:
6
+ metadata.gz: 1009de75d1520829b58454d43195481cfd143b769672a8d56f9126f4480089f1ac8f50059cc34a19a05d8b7b8e30a2d4051887f8596b7818226d132fbacbf6ec
7
+ data.tar.gz: 8a31c6066cd5e3c8b1549d9d30a97a4963f7d5dc2d1c85cedecdeada9a7c88e6091b3cdb48b2588d64f6a79e14a063a94cfd660911c01e9e9ec3d79ecd29e24f
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in tokenzr.gemspec
6
+ gemspec
7
+
8
+ # Specify development dependencies here and not in the gemspec
9
+ gem 'rake'
10
+ gem 'rspec'
11
+ gem 'rubocop'
12
+ gem 'rubocop-rake'
13
+ gem 'rubocop-rspec'
14
+ gem 'rubocop-yard'
15
+ gem 'yard'
16
+ gem 'yard-rspec'
data/exe/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Executable folder
2
+
3
+ There should only be one file here which has the same name as the gem, if this gem is a commandline gem.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tokenzr
4
+ VERSION = '0.1.0'
5
+ end
data/lib/tokenzr.rb ADDED
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'tokenzr/version'
5
+
6
+ module Tokenzr
7
+ class Error < StandardError; end
8
+ class UnknownCharError < Error; end
9
+ class UnterminatedStringError < Error; end
10
+
11
+ class Token
12
+ attr_reader :content, :type, :line, :column
13
+
14
+ def initialize(content = nil, type = nil, line = nil, column = nil)
15
+ @content = content
16
+ @type = type
17
+ @line = line
18
+ @column = column
19
+ end
20
+ end
21
+
22
+ class Tokenizer
23
+ def text_chars
24
+ @text_chars ||= Set.new('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'.chars)
25
+ end
26
+
27
+ def digit_chars
28
+ @digit_chars ||= Set.new('0123456789'.chars)
29
+ end
30
+
31
+ def lone_chars
32
+ @lone_chars ||= Set.new('()[]<>{}!#$%&*+,-./:;=?@\\^`|~'.chars)
33
+ end
34
+
35
+ def space_chars
36
+ @space_chars ||= Set.new(" \t\n\r\v\f".chars)
37
+ end
38
+
39
+ def string_quotes
40
+ @string_quotes ||= Set.new(%q{"'}.chars)
41
+ end
42
+
43
+ def parse(content)
44
+ results = []
45
+ current_token = nil
46
+ enum = content.each_char
47
+ @line = 1
48
+ @column = 1
49
+ @cur_line = 1
50
+ @cur_col = 1
51
+
52
+ while (chr = next_char(enum))
53
+ start_line = @cur_line
54
+ start_col = @cur_col
55
+
56
+ if string_quotes.include?(chr)
57
+ results << current_token unless current_token.nil?
58
+ current_token = nil
59
+ results << read_string(enum, chr, start_line, start_col)
60
+ next
61
+ end
62
+
63
+ if space_chars.include?(chr)
64
+ results << current_token unless current_token.nil?
65
+ current_token = nil
66
+ next
67
+ end
68
+
69
+ if digit_chars.include?(chr)
70
+ if !current_token.nil? && current_token.type == :text
71
+ # digits continue an identifier started by text/underscore
72
+ current_token = Token.new(current_token.content + chr, :text, current_token.line, current_token.column)
73
+ next
74
+ end
75
+
76
+ results << current_token unless current_token.nil?
77
+ current_token = nil
78
+ result = read_number(enum, chr, start_line, start_col)
79
+ if result.is_a?(Array)
80
+ results.concat(result)
81
+ else
82
+ results << result
83
+ end
84
+ next
85
+ end
86
+
87
+ if text_chars.include?(chr)
88
+ if !current_token.nil? && current_token.type == :text
89
+ current_token = Token.new(current_token.content + chr, :text, current_token.line, current_token.column)
90
+ else
91
+ results << current_token unless current_token.nil?
92
+ current_token = Token.new(chr, :text, start_line, start_col)
93
+ end
94
+ next
95
+ end
96
+
97
+ if lone_chars.include?(chr)
98
+ results << current_token unless current_token.nil?
99
+ current_token = nil
100
+ results << Token.new(chr, :lone, start_line, start_col)
101
+ next
102
+ end
103
+
104
+ raise UnknownCharError, "Unknown character: #{chr.inspect}"
105
+ end
106
+
107
+ results << current_token unless current_token.nil?
108
+ results
109
+ end
110
+
111
+ private
112
+
113
+ def next_char(enum)
114
+ pos_line = @line
115
+ pos_col = @column
116
+ chr = enum.next
117
+ @cur_line = pos_line
118
+ @cur_col = pos_col
119
+ if chr == "\n"
120
+ @line = pos_line + 1
121
+ @column = 1
122
+ else
123
+ @column = pos_col + 1
124
+ end
125
+ chr
126
+ rescue StopIteration
127
+ nil
128
+ end
129
+
130
+ def peek_char(enum)
131
+ enum.peek
132
+ rescue StopIteration
133
+ nil
134
+ end
135
+
136
+ def read_string(enum, quote, line, column)
137
+ content = quote
138
+ loop do
139
+ chr = next_char(enum)
140
+ raise UnterminatedStringError, "Unterminated string: #{quote}" if chr.nil?
141
+
142
+ content += chr
143
+ break if chr == quote
144
+
145
+ next unless chr == '\\'
146
+
147
+ escaped = next_char(enum)
148
+ raise UnterminatedStringError, "Unterminated string: #{quote}" if escaped.nil?
149
+
150
+ content += escaped
151
+ end
152
+ Token.new(content, :string, line, column)
153
+ end
154
+
155
+ def read_number(enum, first, line, column)
156
+ content = first
157
+
158
+ # hex: 0x or 0X followed by at least one hex digit
159
+ if first == '0'
160
+ peeked = peek_char(enum)
161
+ if peeked == 'x' || peeked == 'X'
162
+ next_char(enum) # consume the x/X
163
+ x_line = @cur_line
164
+ x_col = @cur_col
165
+ hex_body = read_hex(enum)
166
+ return [Token.new(content, :number, line, column), Token.new(peeked, :text, x_line, x_col)] if hex_body.empty?
167
+
168
+ content += peeked + hex_body
169
+ return Token.new(content, :number, line, column)
170
+ end
171
+ end
172
+
173
+ content += read_digits(enum)
174
+
175
+ # fraction: . followed by a digit
176
+ if peek_char(enum) == '.'
177
+ next_char(enum) # consume the .
178
+ dot_line = @cur_line
179
+ dot_col = @cur_col
180
+ if digit_char?(peek_char(enum))
181
+ content += '.' + read_digits(enum)
182
+ else
183
+ # . not followed by a digit: put it back as a lone token
184
+ return [Token.new(content, :number, line, column), Token.new('.', :lone, dot_line, dot_col)]
185
+ end
186
+ end
187
+
188
+ # exponent: e or E, optional +/-, then at least one digit
189
+ peeked = peek_char(enum)
190
+ if peeked == 'e' || peeked == 'E'
191
+ saved = peeked
192
+ next_char(enum) # consume the e/E
193
+ e_line = @cur_line
194
+ e_col = @cur_col
195
+ sign = peek_char(enum)
196
+ consumed_sign = nil
197
+ sign_line = nil
198
+ sign_col = nil
199
+ if sign == '+' || sign == '-'
200
+ next_char(enum) # consume the sign
201
+ consumed_sign = sign
202
+ sign_line = @cur_line
203
+ sign_col = @cur_col
204
+ end
205
+ if digit_char?(peek_char(enum))
206
+ content += saved + (consumed_sign || '') + read_digits(enum)
207
+ else
208
+ # e/E (and optional sign) not followed by a digit: emit number, then the
209
+ # consumed e/E and sign as separate tokens (already consumed from enum)
210
+ fallback = [Token.new(content, :number, line, column), Token.new(saved, :text, e_line, e_col)]
211
+ fallback << Token.new(consumed_sign, :lone, sign_line, sign_col) if consumed_sign
212
+ return fallback
213
+ end
214
+ end
215
+
216
+ Token.new(content, :number, line, column)
217
+ end
218
+
219
+ def read_hex(enum)
220
+ digits = ''
221
+ while hex_char?(peek_char(enum))
222
+ digits += next_char(enum)
223
+ end
224
+ digits
225
+ end
226
+
227
+ def read_digits(enum)
228
+ digits = ''
229
+ while digit_char?(peek_char(enum))
230
+ digits += next_char(enum)
231
+ end
232
+ digits
233
+ end
234
+
235
+ def hex_char?(chr)
236
+ return false if chr.nil?
237
+
238
+ digit_char?(chr) || ('a'..'f').cover?(chr) || ('A'..'F').cover?(chr)
239
+ end
240
+
241
+ def digit_char?(chr)
242
+ !chr.nil? && digit_chars.include?(chr)
243
+ end
244
+ end
245
+ end
data/tokenzr.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/tokenzr/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'tokenzr'
7
+ spec.version = Tokenzr::VERSION
8
+ spec.authors = ['David Siaw']
9
+ spec.email = ['874280+davidsiaw@users.noreply.github.com']
10
+
11
+ spec.summary = 'Tokenzr gem'
12
+ spec.description = 'Tokenizer for most common cases of tokenization'
13
+ spec.homepage = 'https://github.com/davidsiaw/tokenzr'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.0')
16
+
17
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/davidsiaw/tokenzr'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/davidsiaw/tokenzr'
22
+ spec.metadata['documentation_uri'] = 'https://davidsiaw.github.io/tokenzr'
23
+ spec.metadata['rubygems_mfa_required'] = 'true'
24
+
25
+ spec.files = Dir['{exe,data,lib}/**/*'] + %w[Gemfile tokenzr.gemspec]
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tokenzr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Siaw
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-07-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Tokenizer for most common cases of tokenization
14
+ email:
15
+ - 874280+davidsiaw@users.noreply.github.com
16
+ executables:
17
+ - README.md
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - Gemfile
22
+ - exe/README.md
23
+ - lib/tokenzr.rb
24
+ - lib/tokenzr/version.rb
25
+ - tokenzr.gemspec
26
+ homepage: https://github.com/davidsiaw/tokenzr
27
+ licenses:
28
+ - MIT
29
+ metadata:
30
+ allowed_push_host: https://rubygems.org
31
+ homepage_uri: https://github.com/davidsiaw/tokenzr
32
+ source_code_uri: https://github.com/davidsiaw/tokenzr
33
+ changelog_uri: https://github.com/davidsiaw/tokenzr
34
+ documentation_uri: https://davidsiaw.github.io/tokenzr
35
+ rubygems_mfa_required: 'true'
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '3.0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.5.22
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Tokenzr gem
55
+ test_files: []