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 +7 -0
- data/Gemfile +16 -0
- data/exe/README.md +3 -0
- data/lib/tokenzr/version.rb +5 -0
- data/lib/tokenzr.rb +245 -0
- data/tokenzr.gemspec +29 -0
- metadata +55 -0
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
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: []
|