seisu-ruby 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: d4e1e2c5f011ed26e3798e0eb7346713973e8f2a6076b43109530a0d0ee6fadd
4
+ data.tar.gz: 7945c7459a2cfb5bd643669bc7303d047c06140a8ac794ba14b349168cab0547
5
+ SHA512:
6
+ metadata.gz: 2f672f6866f9228a83df4d376191334e274f7fb7bad73049dad91795873f16736a0070978a5d963e7128c1992c6d4ef1deb6af3725f1ab874f1f21644a2c9749
7
+ data.tar.gz: 1861970f935e4f281510f8616d6dbadce7e0a694b9ccc673c1ad7d708550f98cbc4b82e6f87702e1c96b6e9c53bb80b6d130658e411f87f4ece99a4cc49c81ff
data/.editorconfig ADDED
@@ -0,0 +1,8 @@
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ insert_final_newline = true
6
+ charset = utf-8
7
+ indent_style = space
8
+ indent_size = 2
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,4 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ NewCops: enable
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,51 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2025-07-21 14:26:01 UTC using RuboCop version 1.78.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
11
+ Metrics/AbcSize:
12
+ Max: 57
13
+
14
+ # Offense count: 4
15
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
16
+ # AllowedMethods: refine
17
+ Metrics/BlockLength:
18
+ Max: 104
19
+
20
+ # Offense count: 1
21
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
22
+ Metrics/CyclomaticComplexity:
23
+ Max: 18
24
+
25
+ # Offense count: 1
26
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
27
+ Metrics/MethodLength:
28
+ Max: 39
29
+
30
+ # Offense count: 1
31
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
32
+ Metrics/PerceivedComplexity:
33
+ Max: 17
34
+
35
+ # Offense count: 4
36
+ # Configuration parameters: AllowedConstants.
37
+ Style/Documentation:
38
+ Exclude:
39
+ - 'spec/**/*'
40
+ - 'test/**/*'
41
+ - 'lib/seisu.rb'
42
+ - 'lib/seisu/evaluate.rb'
43
+ - 'lib/seisu/parser.rb'
44
+ - 'lib/seisu/transform.rb'
45
+
46
+ # Offense count: 1
47
+ # This cop supports unsafe autocorrection (--autocorrect-all).
48
+ # Configuration parameters: AllowedCompactTypes.
49
+ # SupportedStyles: compact, exploded
50
+ Style/RaiseArgs:
51
+ EnforcedStyle: compact
@@ -0,0 +1,9 @@
1
+ {
2
+ "[ruby]": {
3
+ "editor.defaultFormatter": "rubocop.vscode-rubocop",
4
+ // "editor.defaultFormatter": "Shopify.ruby-lsp",
5
+ "editor.formatOnSave": true
6
+ },
7
+ "rubocop.mode": "enableViaGemfile",
8
+ // "rubyLsp.formatter": "rubocop"
9
+ }
data/GEMINI.md ADDED
@@ -0,0 +1,39 @@
1
+ # Gemini Context: seisu-ruby
2
+
3
+ ## Project Overview
4
+
5
+ `seisu-ruby` is a Ruby library that parses a string written in a custom mini-language and evaluates it to return an array of integers.
6
+
7
+ For example, the input string `"1, [3..5], odd{[8..12]}"` would be evaluated to `[1, 3, 4, 5, 9, 11]`.
8
+
9
+ ## Core Technologies
10
+
11
+ - **Language:** Ruby
12
+ - **Parsing Library:** [Parslet](https://kschiess.github.io/parslet/)
13
+
14
+ ## Key Components
15
+
16
+ The process is divided into three main steps:
17
+
18
+ 1. **`Seisu::Parser` (`lib/seisu/parser.rb`)**: Parses the input string into a syntax tree using Parslet rules (a PEG dialect).
19
+ 2. **`Seisu::Transform` (`lib/seisu/transform.rb`)**: Transforms the raw syntax tree into a more structured Abstract Syntax Tree (AST).
20
+ 3. **`Seisu::Evaluate` (`lib/seisu/evaluate.rb`)**: Evaluates the AST recursively to compute the final array of integers.
21
+ 4. **`Seisu` (`lib/seisu.rb`)**: Utility class to provide a simple interface.
22
+
23
+ ## Supported Syntax
24
+
25
+ The mini-language supports the following features:
26
+
27
+ - **Numbers**: e.g., `1`, `+5`, `-10`
28
+ - **Ranges**: Support ascending and descending; e.g., `[1..5]`, `[-1..3]`, `[10..6]`, `[2..-2]`
29
+ - **Ranges with steps**: Steps for ascending and descending lists must be positive and negative integers, respectively; if this correspondence is reversed, the list evaluates to an empty array; e.g., `[1..10:2]`, `[2..-2:-2]`
30
+ - **Union**: `,` (e.g., `1, 2, 5`)
31
+ - **Difference**: `!` (e.g., `[1..5] ! 3`, `odd{[1..5]} ! {3, 4}`)
32
+ - **Union and Difference**: The difference has higher join precedence than the union operation; e.g., `1, 2, 3 ! 3, 4, 5 #=> [1, 2, 4, 5]`
33
+ - **Parity Filters**: `odd{...}` and `even{...}`
34
+ - **Grouping**: `{...}`
35
+
36
+ ## Testing
37
+
38
+ - The project uses [RSpec](https://rspec.info/) for testing.
39
+ - Test files are located in the `spec/` directory.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # Seisu::Ruby
2
+
3
+ `seisu-ruby` is a library that parses a string written in a custom mini-language and evaluates it to return an array of integers.
4
+
5
+ For example, the input string `"1, [3..5], odd{[8..12]}"` would be evaluated to `[1, 3, 4, 5, 9, 11]`.
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ ```bash
12
+ bundle add seisu-ruby
13
+ ```
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ ```bash
18
+ gem install seisu-ruby
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ The simplest way to use the library is to call the `Seisu.parse` method.
24
+
25
+ ```ruby
26
+ require 'seisu'
27
+
28
+ result = Seisu.parse("1, [3..5], odd{[8..12]}")
29
+ p result
30
+ #=> [1, 3, 4, 5, 9, 11]
31
+ ```
32
+
33
+ ## Supported Syntax
34
+
35
+ The mini-language supports the following features:
36
+
37
+ | Feature | Syntax | Example | Result |
38
+ |---|---|---|---|
39
+ | **Numbers** | `1`, `+5`, `-10` | `1, -5` | `[1, -5]` |
40
+ | **Ranges** | `[start..end]` | `[1..5]` | `[1, 2, 3, 4, 5]` |
41
+ | | | `[5..1]` | `[5, 4, 3, 2, 1]` |
42
+ | **Ranges with steps** | `[start..end:step]` | `[1..10:2]` | `[1, 3, 5, 7, 9]` |
43
+ | | | `[10..1:-2]` | `[10, 8, 6, 4, 2]` |
44
+ | **Union** | `,` | `1, 2, 5` | `[1, 2, 5]` |
45
+ | **Difference** | `!` | `[1..5] ! 3` | `[1, 2, 4, 5]` |
46
+ | **Parity Filters** | `odd{...}` / `even{...}` | `odd{[1..5]}` | `[1, 3, 5]` |
47
+ | | | `even{[1..5]}` | `[2, 4]` |
48
+ | **Grouping** | `{...}` | `{[1..3], 5}` | `[1, 2, 3, 5]` |
49
+
50
+ **Operator Precedence**
51
+
52
+ The difference operator (`!`) has higher precedence than the union operator (`,`).
53
+
54
+ ```ruby
55
+ Seisu.parse("1, 2, 3 ! 3, 4, 5")
56
+ # is equivalent to "1, 2, (3 ! 3), 4, 5"
57
+ #=> [1, 2, 4, 5]
58
+ ```
59
+
60
+ ## Development
61
+
62
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
63
+
64
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/cu39/seisu-ruby.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ RuboCop::RakeTask.new(:cop) do |task|
10
+ task.plugins << 'rubocop-rake'
11
+ end
12
+
13
+ RuboCop::RakeTask.new(:'cop:regen') do |task|
14
+ task.plugins << 'rubocop-rake'
15
+ task.options = ['--regenerate-todo']
16
+ end
17
+
18
+ task default: :spec
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Seisu
4
+ def self.range_to_array(start_num, end_num, step = nil)
5
+ # ステップを推定(デフォルト)または利用(明示指定)
6
+ step ||= start_num < end_num ? 1 : -1
7
+
8
+ raise ArgumentError.new('step must not be 0') if step.zero?
9
+
10
+ result = []
11
+ i = start_num
12
+
13
+ # 終了条件:stepの符号に応じて異なる
14
+ if step.positive?
15
+ while i <= end_num
16
+ result << i
17
+ i += step
18
+ end
19
+ else
20
+ while i >= end_num
21
+ result << i
22
+ i += step
23
+ end
24
+ end
25
+
26
+ result
27
+ end
28
+
29
+ def self.evaluate(node)
30
+ case node
31
+ when []
32
+ []
33
+ when Hash
34
+ case node[:type]
35
+ when :number
36
+ [node[:value]]
37
+ when :range_simple
38
+ from = node[:from][:value]
39
+ to = node[:to][:value]
40
+ Seisu.range_to_array(from, to)
41
+ when :range_step
42
+ from = node[:from][:value]
43
+ to = node[:to][:value]
44
+ step = node[:step][:value]
45
+ Seisu.range_to_array(from, to, step)
46
+ when :union
47
+ # 重複除去、ソートあり
48
+ # (evaluate(node[:left]) | evaluate(node[:right])).sort
49
+ # 重複保存、ソートなし
50
+ evaluate(node[:left]) + evaluate(node[:right])
51
+ when :parity
52
+ numbers = evaluate(node[:expression])
53
+ if node[:parity] == :odd
54
+ numbers.select(&:odd?)
55
+ else # :even
56
+ numbers.select(&:even?)
57
+ end
58
+ when :difference
59
+ evaluate(node[:left]) - evaluate(node[:right])
60
+ else
61
+ raise "Unknown node type: #{node[:type]}"
62
+ end
63
+ else
64
+ raise "Unsupported node: #{node.class} (#{node.inspect})"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parslet'
4
+
5
+ class Seisu
6
+ class Parser < Parslet::Parser
7
+ # 空白をスキップ
8
+ rule(:space) { match('\s').repeat(1) }
9
+ rule(:space?) { space.maybe }
10
+
11
+ # 基本要素
12
+ rule(:digit) { match('[0-9]') }
13
+ rule(:sign) { match('[+-]') }
14
+
15
+ # 数値
16
+ rule(:number) do
17
+ (sign.maybe >> digit.repeat(1)).as(:number) >> space?
18
+ end
19
+
20
+ # 範囲(ステップなし)
21
+ rule(:range_simple) do
22
+ (
23
+ str('[') >>
24
+ number.as(:from) >> str('..') >> number.as(:to) >>
25
+ str(']')
26
+ ).as(:range_simple)
27
+ end
28
+
29
+ # 範囲(ステップあり)
30
+ rule(:range_step) do
31
+ (
32
+ str('[') >>
33
+ number.as(:from) >> str('..') >> number.as(:to) >>
34
+ str(':') >> number.as(:step) >>
35
+ str(']') >> space?
36
+ ).as(:range_step)
37
+ end
38
+
39
+ # 範囲
40
+ rule(:range) do
41
+ range_step | range_simple
42
+ end
43
+
44
+ # 偶奇指定表現
45
+ rule(:parity) do
46
+ (
47
+ (str('odd') | str('even')).as(:parity) >>
48
+ str('{') >> expression.as(:expression) >> str('}')
49
+ ).as(:parity)
50
+ end
51
+
52
+ # グループ化された表現
53
+ rule(:braced_expression) do
54
+ (str('{') >> space? >>
55
+ expression.as(:expression) >>
56
+ str('}') >> space?).as(:braced)
57
+ end
58
+
59
+ # 主要な項
60
+ rule(:factor) do
61
+ braced_expression | parity | range | number
62
+ end
63
+
64
+ rule(:difference) do
65
+ (
66
+ factor.as(:head) >>
67
+ (str('!') >> space? >> factor).repeat.as(:tail)
68
+ ).as(:difference)
69
+ end
70
+
71
+ # 和集合の項
72
+ rule(:term) do
73
+ difference | factor
74
+ end
75
+
76
+ rule(:union) do
77
+ (
78
+ term.as(:head) >>
79
+ (str(',') >> space? >> term).repeat.as(:tail)
80
+ ).as(:union)
81
+ end
82
+
83
+ # 1. 最上位定義(空文字列を含む)
84
+ rule(:expression) do
85
+ union | str('').as(:empty)
86
+ end
87
+
88
+ # エントリポイント
89
+ root :expression
90
+ end
91
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parslet'
4
+
5
+ class Seisu
6
+ class Transform < Parslet::Transform
7
+ rule(empty: simple(:_)) { [] }
8
+
9
+ # 数値
10
+ rule(number: simple(:num)) do
11
+ { type: :number, value: Integer(num) }
12
+ end
13
+
14
+ # 範囲(ステップなし)
15
+ rule(range_simple: subtree(:r)) do
16
+ { type: :range_simple, from: r[:from], to: r[:to] }
17
+ end
18
+
19
+ # 範囲(ステップあり)
20
+ rule(range_step: subtree(:r)) do
21
+ { type: :range_step, from: r[:from], to: r[:to], step: r[:step] }
22
+ end
23
+
24
+ # 偶奇フィルター
25
+ rule(parity: { parity: simple(:p), expression: subtree(:expr) }) do
26
+ { type: :parity, parity: p.to_sym, expression: expr }
27
+ end
28
+
29
+ # 差集合
30
+ rule(difference: { head: subtree(:h), tail: subtree(:ts) }) do
31
+ ts.reduce(h) do |acc, t|
32
+ { type: :difference, left: acc, right: t }
33
+ end
34
+ end
35
+
36
+ # 和集合
37
+ rule(union: { head: subtree(:h), tail: subtree(:ts) }) do
38
+ ts.reduce(h) do |acc, t|
39
+ { type: :union, left: acc, right: t }
40
+ end
41
+ end
42
+
43
+ # グループ化された表現
44
+ rule(braced: { expression: subtree(:expr) }) do
45
+ expr
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Seisu
4
+ VERSION = '0.1.0'
5
+ end
data/lib/seisu.rb ADDED
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parslet'
4
+
5
+ require_relative 'seisu/version'
6
+ require_relative 'seisu/parser'
7
+ require_relative 'seisu/transform'
8
+ require_relative 'seisu/evaluate'
9
+
10
+ class Seisu
11
+ class Error < StandardError; end
12
+ attr_reader :parsed_tree, :transbormed_tree, :int_array
13
+
14
+ def self.parse(src = '')
15
+ seisu = new(src.strip)
16
+ seisu.to_a
17
+ end
18
+
19
+ def initialize(src = '')
20
+ unless src.is_a? String
21
+ msg = "Argument for Seisu.new have to be a String; Given #{src.class}."
22
+ raise TypeError.new(msg)
23
+ end
24
+
25
+ @raw = src.strip
26
+ @parser = Parser.new
27
+ @parsed_tree = nil
28
+ @transformer = Transform.new
29
+ @transformed_tree = nil
30
+ @int_array = nil
31
+ end
32
+
33
+ def parse
34
+ @parsed_tree = @parser.parse(@raw) if @parsed_tree.nil?
35
+ @parsed_tree
36
+ end
37
+
38
+ def transform
39
+ @transformed_tree = @transformer.apply(parse) if @transformed_tree.nil?
40
+ @transformed_tree
41
+ end
42
+
43
+ def to_a
44
+ @int_array = Seisu.evaluate(transform) if @int_array.nil?
45
+ @int_array
46
+ end
47
+
48
+ def to_s
49
+ to_a.to_s
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ module Seisu
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seisu-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - cu39
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-07-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parslet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.0
27
+ description: A Ruby library that parses a string written in a custom mini-language
28
+ and evaluates it to return an array of integers.
29
+ email:
30
+ - cu393uc@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".editorconfig"
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - ".rubocop_todo.yml"
39
+ - ".vscode/settings.json"
40
+ - GEMINI.md
41
+ - README.md
42
+ - Rakefile
43
+ - lib/seisu.rb
44
+ - lib/seisu/evaluate.rb
45
+ - lib/seisu/parser.rb
46
+ - lib/seisu/transform.rb
47
+ - lib/seisu/version.rb
48
+ - sig/seisu/ruby.rbs
49
+ homepage: https://github.com/cu39/seisu-ruby
50
+ licenses: []
51
+ metadata:
52
+ allowed_push_host: https://rubygems.org
53
+ homepage_uri: https://github.com/cu39/seisu-ruby
54
+ source_code_uri: https://github.com/cu39/seisu-ruby
55
+ rubygems_mfa_required: 'true'
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 3.1.0
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubygems_version: 3.5.22
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: Custom mini-language to build an array of integers.
75
+ test_files: []