l43_peg 0.0.1 → 0.1.1

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.
data/README.md CHANGED
@@ -1,91 +1,84 @@
1
- # l43_my_ruby
1
+ # L43Peg
2
2
 
3
+ ## A Parse Expression Grammar library for Ruby
3
4
 
4
- ## Getting started
5
+ ### This Version (v0.1.x) is Alpha Quality (many PEG features are missing, like recursion and even alternatives.
5
6
 
6
- To make it easy for you to get started with GitLab, here's a list of recommended next steps.
7
+ It is however released because it offers quite some nice parsing of ARGV which shall be demonstrated by the following
8
+ [speculations](https://rubygems.org/gems/speculate_about)
7
9
 
8
- Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
10
+ See [README_spec.rb](spec/speculations/README_spec.rb) for the generated code for details
9
11
 
10
- ## Add your files
12
+ ### Context: `arg_parser`
11
13
 
12
- - [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
13
- - [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
14
+ Given the following argument specification
14
15
 
16
+ ```ruby
17
+ include L43Peg::Combinators
18
+ let :args_spec do
19
+ {
20
+ start: "--start=(.*)",
21
+ end: "(?:--end|-e)=(.*)",
22
+ kwd: "--(alpha|beta|gamma)"
23
+ }
24
+ end
15
25
  ```
16
- cd existing_repo
17
- git remote add origin https://gitlab.com/lab421/l43_my_ruby.git
18
- git branch -M main
19
- git push -uf origin main
20
- ```
21
-
22
- ## Integrate with your tools
23
-
24
- - [ ] [Set up project integrations](https://gitlab.com/lab421/l43_my_ruby/-/settings/integrations)
25
-
26
- ## Collaborate with your team
27
-
28
- - [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
29
- - [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
30
- - [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
31
- - [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
32
- - [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
33
-
34
- ## Test and Deploy
35
-
36
- Use the built-in continuous integration in GitLab.
37
26
 
38
- - [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
39
- - [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
40
- - [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
41
- - [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
42
- - [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
27
+ And the assoicated parser
43
28
 
44
- ***
29
+ ```ruby
30
+ let(:parser) { args_parser(args_spec) }
31
+ ```
45
32
 
46
- # Editing this README
33
+ Then we can parse some input
47
34
 
48
- When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
35
+ ```ruby
36
+ assert_parse_success(parser, %w[--start=42 --beta -e=44], ast: {start: "42", kwd: "beta", end: "44"}, rest: [])
37
+ ```
49
38
 
50
- ## Suggestions for a good README
51
- Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
39
+ And we can get the rest in a list of tokens
52
40
 
53
- ## Name
54
- Choose a self-explaining name for your project.
41
+ ```ruby
42
+ assert_parse_success(parser, %w[--start=42 --beta -e=44 -s=not_an_arg --end=too_late], ast: {start: "42", kwd: "beta", end: "44"}, rest: %w[-s=not_an_arg --end=too_late])
43
+ ```
55
44
 
56
- ## Description
57
- Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
45
+ Also note that multiple values are passed into an array
58
46
 
59
- ## Badges
60
- On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
47
+ ```ruby
48
+ input = %w[--end=42 --beta -e=44 --beta --end=not_too_late --gamma]
49
+ ast = {end: %w[42 44 not_too_late], kwd: %w[beta beta gamma]}
50
+ assert_parse_success(parser, input, ast:, rest: [])
51
+ ```
61
52
 
62
- ## Visuals
63
- Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
53
+ #### Context: Postprocessing
64
54
 
65
- ## Installation
66
- Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
55
+ When we map the parser
67
56
 
68
- ## Usage
69
- Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
57
+ ```ruby
58
+ let :int_args do
59
+ {
60
+ start: "--start=(.*)",
61
+ end: "--end=(.*)",
62
+ inc: "--inc=(.*)"
63
+ }
64
+ end
65
+ let(:int_arg_parser) {args_parser(int_args, name: "int parser", &:to_i)}
66
+ ```
70
67
 
71
- ## Support
72
- Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
68
+ Then we can convert the string valus
73
69
 
74
- ## Roadmap
75
- If you have ideas for releases in the future, it is a good idea to list them in the README.
70
+ ```ruby
71
+ assert_parse_success(parser, %w[--start=42 --end=44 --inc=2], ast: {start: 42, end: 44, inc: 2}, rest: [])
72
+ ```
76
73
 
77
- ## Contributing
78
- State if you are open to contributions and what your requirements are for accepting them.
79
74
 
80
- For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
75
+ ## Author
81
76
 
82
- You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
77
+ Copyright © 2024 Robert Dober
78
+ robert.dober@gmail.com
83
79
 
84
- ## Authors and acknowledgment
85
- Show your appreciation to those who have contributed to the project.
80
+ # LICENSE
86
81
 
87
- ## License
88
- For open source projects, say how it is licensed.
82
+ GNU AFFERO GENERAL PUBLIC LICENSE, Version 3, 19 November 2007. Please refer to [LICENSE](LICENSE) for details.
89
83
 
90
- ## Project status
91
- If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
84
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MatchData
4
+ def deconstruct(*) = to_a
5
+ end
6
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ module OpenObject
5
+ def attributes(*atts, **defaults)
6
+ attr_reader(*atts)
7
+ attr_reader(*defaults.keys)
8
+
9
+ define_method :== do |other|
10
+ other&.to_h == to_h
11
+ end
12
+
13
+ define_method :initialize do |**kwds|
14
+ missing = atts - kwds.keys
15
+ raise ArgumentError, "missing required keyword parameters: #{missing.inspect}" unless missing.empty?
16
+ spurious = kwds.keys - atts - defaults.keys
17
+ raise ArgumentError, "spurious keyword parameters: #{spurious.inspect}" unless spurious.empty?
18
+
19
+ values = defaults.merge(kwds)
20
+ values.each do |key, value|
21
+ instance_variable_set("@#{key}", value)
22
+ end
23
+ _init if respond_to?(:_init)
24
+ end
25
+
26
+ define_method :to_h do |*|
27
+ first = atts.inject Hash.new do |h, attribute|
28
+ h.update(attribute => send(attribute))
29
+ end
30
+ defaults.keys.inject first do |h, attribute|
31
+ h.update(attribute => send(attribute))
32
+ end
33
+ end
34
+ alias_method :deconstruct_keys, :to_h
35
+ end
36
+ end
37
+ end
38
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Object
4
+ def require_subdir(path=nil, &blk)
5
+ Dir.glob(File.join([File.dirname(blk.source_location.first), path, '/**/*.rb'].compact)).each { require _1}
6
+ end
7
+ end
8
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Cache
5
+ extend L43::OpenObject
6
+ attributes cache: {}
7
+ end
8
+ end
9
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../helper"
4
+ module L43Peg
5
+ module Combinators
6
+ module Many extend self
7
+
8
+ include L43Peg::Helper
9
+ def many(input:, cache:, name:, parser:, min:, max:)
10
+ curr_input = input
11
+ curr_cache = cache
12
+ count = 0
13
+ ast = []
14
+
15
+ loop do
16
+ if max && count == max
17
+ return succeed_parser(ast, curr_input, cache: curr_cache)
18
+ end
19
+ case parser.(curr_input, cache: curr_cache)
20
+ in L43Peg::Success => success
21
+ ast.push(success.ast)
22
+ curr_input = success.rest
23
+ curr_cache = success.cache
24
+ count += 1
25
+ in L43Peg::Failure
26
+ if count < min
27
+ return fail_parser("many #{name} should match at least #{min} times but did only #{count} times")
28
+ end
29
+ return succeed_parser(ast, curr_input, cache: curr_cache)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
38
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../helper"
4
+ module L43Peg
5
+ module Combinators
6
+ module Seq extend self
7
+
8
+ include L43Peg::Helper
9
+ def seq(input:, cache:, name:, parsers:)
10
+ curr_input = input
11
+ curr_cache = cache
12
+ ast = []
13
+ parsers.each do |parser|
14
+ case parser.(curr_input, cache: curr_cache)
15
+ in L43Peg::Failure => failure
16
+ return L43Peg::Failure.new(reason: "#{failure.reason} in (#{name})", position: failure.position, input:)
17
+ in L43Peg::Success => success
18
+ ast.push(success.ast)
19
+ curr_input = success.rest
20
+ curr_cache = success.cache
21
+ end
22
+ end
23
+ succeed_parser(ast, curr_input, cache: curr_cache)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require_relative 'mappers'
5
+ require_relative 'parser'
6
+ require_relative 'parsers'
7
+ require_subdir('combinators') {}
8
+
9
+ module L43Peg
10
+ module Combinators extend self
11
+
12
+ include L43Peg::Helper
13
+ include L43Peg::Mappers
14
+ include L43Peg::Parsers
15
+
16
+ def args_parser(definitions, name: nil, &block)
17
+ parser = tokens_parser(definitions, &block)
18
+ name = name || "args_parser(#{definitions.inspect})"
19
+ inner = many(parser)
20
+ map(inner, name:, fn: join_maps)
21
+ end
22
+
23
+ def many(parser, name: nil, min: 0, max: nil)
24
+ Parser.new(name || "many(#{parser.name})") {|input, cache, name1=nil| Many.many(input:, cache:, name: name1 || name, parser:, min:, max:)}
25
+ end
26
+
27
+ def map(parser, name: nil, fn: nil, &mapper)
28
+ raise ArgumentError, "must not provide keyword parameyer fn and a block" if fn && mapper
29
+ mapper = fn || mapper
30
+ raise ArgumentError, "must provide keyword parameyer fn or a block" unless mapper
31
+ Parser.new(name || "map(#{parser.name})") {|input, cache, name=nil| _map(input:, cache:, name:, parser:, mapper:)}
32
+ end
33
+
34
+ def seq(*parsers, name: nil)
35
+ name ||= "seq(#{parsers.map(&:name).join(", ")})"
36
+ Parser.new(name) {|input, cache, _name=nil| Seq.seq(input:, cache:, name:, parsers:)}
37
+ end
38
+
39
+ private
40
+
41
+ def _map(input:, cache:, name:, parser:, mapper:)
42
+ case parser.(input, cache:)
43
+ in L43Peg::Failure => failure
44
+ failure
45
+ in L43Peg::Success => success
46
+ success.map(&mapper)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Failure
5
+ extend L43::OpenObject
6
+ attributes cache: nil, input: nil, parsed_by: nil, position: [1, 1], reason: ""
7
+ end
8
+ end
9
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Helper
5
+ def fail_parser(reason)
6
+ L43Peg::Failure.new(reason:)
7
+ end
8
+ def succeed_parser(ast, input, cache: nil)
9
+ L43Peg::Success.new(ast:, rest: input, cache: cache || input.cache)
10
+ end
11
+ end
12
+ end
13
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Input
5
+ extend L43::OpenObject
6
+
7
+ attributes col: 1, input: "", lnb: 1, context: {}
8
+
9
+ def drop(by=nil)
10
+ case by
11
+ when nil
12
+ _drop_by_n(1)
13
+ when String
14
+ _drop_by_n(by.length)
15
+ else
16
+ _drop_by_n(by)
17
+ end
18
+ end
19
+
20
+ def empty? = input.empty?
21
+
22
+ def position = [@col, @lnb]
23
+
24
+ private
25
+
26
+ def _drop_by_n(n)
27
+ return self if input.empty?
28
+ _split(n) => col, lnb, head, tail
29
+ self.class.new(input: tail, context:, col:, lnb:)
30
+ end
31
+
32
+ # Very inefficent but sufficent for my use cases so far
33
+ # and convenient for regex parsers
34
+ def _split(n)
35
+ head = input.slice(0...n)
36
+ lines = head.count("\n")
37
+ tail = input.slice(n..-1) || ''
38
+ new_col =
39
+ if lines.zero?
40
+ col + n
41
+ else
42
+ 1 + head.sub(%r{\A.*\n}m, "").length
43
+ end
44
+ [new_col, lnb + lines, head, tail]
45
+ end
46
+ end
47
+ end
48
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Mappers
5
+
6
+ def join_and_to_i
7
+ -> list do
8
+ list.join.to_i
9
+ end
10
+ end
11
+
12
+ def join_maps
13
+ -> maps do
14
+ maps.reduce Hash.new do |map, entry|
15
+ map.merge(entry) { |*args| args.drop(1).flatten }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Parser
5
+
6
+ attr_reader :fn, :name
7
+
8
+ def call(input, cache: L43Peg::Cache.new) = fn.(input, cache, name)
9
+
10
+ private
11
+
12
+ def initialize(name, &fn)
13
+ @name = name
14
+ @fn = fn
15
+ end
16
+
17
+ end
18
+ end
19
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class CharParser < L43Peg::Parser
6
+
7
+ private
8
+
9
+ def initialize(charset=nil)
10
+ charset = _mk_set(charset)
11
+ super("char_parser(#{charset})") do |input, cache, name|
12
+ if input.input.empty?
13
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "cannot parse at end of input #{name}")
14
+ elsif charset.nil? || charset.member?(input.input[0])
15
+ L43Peg::Success.new(ast: input.input[0], cache:, rest: input.drop, position: input.position)
16
+ else
17
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "char #{input.input[0].inspect}")
18
+ end
19
+ end
20
+ end
21
+
22
+ def _mk_set(charset)
23
+ return unless charset
24
+ case charset
25
+ when String
26
+ Set.new(charset.split(""))
27
+ when Set
28
+ charset
29
+ else
30
+ Set.new(charset)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../parsers'
4
+
5
+ module L43Peg
6
+ module Parsers
7
+ class EndParser < L43Peg::Parser
8
+
9
+ def self.instance
10
+ @__instance__ ||= new
11
+ end
12
+
13
+ private
14
+
15
+ def initialize
16
+ super('end_parser') do |input, cache, _name=nil|
17
+ if input.input.empty?
18
+ L43Peg::Success.new(cache:, rest: input, position: input.position)
19
+ else
20
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "not at end of input")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class FailureParser < L43Peg::Parser
6
+
7
+ def self.instance
8
+ @__instance__ ||= new
9
+ end
10
+
11
+ private
12
+
13
+ def initialize
14
+ super('failure_parser') do |input, cache, _name=nil|
15
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "this parser always fails")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../parsers'
4
+
5
+ module L43Peg
6
+ module Parsers
7
+ class IntParser < L43Peg::Parser
8
+ extend L43Peg::Combinators
9
+ extend L43Peg::Mappers
10
+
11
+ class << self
12
+ def instance
13
+ @__instance__ ||= _mk_int_parser
14
+ end
15
+
16
+ private
17
+
18
+ def _mk_int_parser
19
+ map(seq(many(Parsers.char_parser("+-"), max: 1), many(Parsers.char_parser("0".."9"), min: 1), name: "int_parser"), &join_and_to_i)
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class RgxParser < L43Peg::Parser
6
+
7
+ private
8
+
9
+ def initialize(rgx, name: nil, **options)
10
+ name = name || "rgx_parser(#{rgx.inspect})"
11
+ rgx = _mk_rgx(rgx)
12
+ super(name) do |input, cache, _name|
13
+ case rgx.match(input.input)
14
+ in MatchData => md
15
+ ast = _from_match(md, options)
16
+ L43Peg::Success.new(ast:, cache:, rest: input.drop(md[0]), position: input.position)
17
+ else
18
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "input does not match #{rgx.inspect} (in #{name})")
19
+ end
20
+ end
21
+ end
22
+
23
+ def _from_match(md, options)
24
+ case options[:capture]
25
+ in NilClass
26
+ md[1] || md[0]
27
+ in Integer => n
28
+ md[n]
29
+ in :all
30
+ md.to_a
31
+ end
32
+ end
33
+
34
+ def _mk_rgx(rgx)
35
+ case rgx
36
+ when String
37
+ Regexp.compile("\\A#{rgx}")
38
+ else
39
+ rgx
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class TokenParser < L43Peg::Parser
6
+
7
+ private
8
+
9
+ def initialize(token_spec, name: nil, **options)
10
+ _check_arg!(token_spec)
11
+ name = name || "token_parser(#{token_spec.inspect})"
12
+ super(name) do |tokens, cache, _name|
13
+ if result = tokens.match(token_spec, options[:capture])
14
+ L43Peg::Success.new(ast: result, cache:, rest: tokens.drop(1), position: tokens.position)
15
+ else
16
+ reason = "token #{tokens.head.inspect} does not match #{token_spec.inspect} (in #{name})"
17
+ L43Peg::Failure.new(cache:, input: tokens, parsed_by: self, reason:)
18
+ end
19
+ end
20
+ end
21
+
22
+ def _check_arg!(token_spec)
23
+ case token_spec
24
+ when Regexp, String
25
+ else
26
+ raise ArgumentError, "#{token_spec.inspect} must be a regular expression or a string"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ # SPDX-License-Identifier: AGPL-3.0-or-later