yaparc 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0166a0d68a1864c4c92132c88f6b74809e317d5c031c5e57bf9951b6aaedc7b8
4
- data.tar.gz: 86ae7441a45dbf7c8f168abf2bc4633dc317e8dc11ac29e9e572df3d3f91206b
3
+ metadata.gz: db3016ed938d0853130447ce2155b9e9978faf79798f137c99624d1539a9fc2b
4
+ data.tar.gz: 505b913fc07f99bf8cc7fb1be125aefedb767bb589d92f40c3bdc35ff6cccfb1
5
5
  SHA512:
6
- metadata.gz: cb4bc055e722070a425921ee0b23e05ae51764d6d76c6f3a955d2c96c3e32eb3eaff0c159710e7b7d067c360efa3308fc55b7868bbc5af94a8df9db1a8903c9b
7
- data.tar.gz: a8583f87131780d75a5ed7c46bb49265411fca899921486cf6f854d77c96c98ee41813015b6e5435a0d531bb06e3cf3c12cb2d08bd0effff553ae1e75b839f89
6
+ metadata.gz: d53c0470dac4fa661db80607a2e752ab830a751fdd14f3453f838a80ffcd13993b013d32a2aa4c171dc3e8d4cf85154651ed656179669796c8cdac5120503c66
7
+ data.tar.gz: 75a76818b13a77821106dd09c65c5ae2c3a005ca8361797b75167b708f57846c4664a267f1eb40bba2d7ec9e75ec995291a77e51353423bd232f921475374d59
data/.envrc ADDED
@@ -0,0 +1 @@
1
+ use guix ruby ruby-rubocop
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.0 - 2026-06-23
6
+
7
+ * Remove `message` attribute from `Yaparc::Base`.
8
+ * Rename `Yaparc::Fail` to `Yaparc::FailParser`.
9
+ * Move `OK`, `Fail`, `Error` outside of `Result` and removed `Result` module.
10
+ * Remove `tree` attribute of `Parsable` module.
11
+ * Use newer keyword arguments. This might cause troubles with Ruby version 2
12
+ or lower.
13
+ * Limit `Tokenize`'s `prefix` and `postfix` write only.
14
+
5
15
  ## 0.3.0 - 2024-12-31
6
16
 
7
17
  * Added `Yaparc::VERSION` constant.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
  gemspec
data/README CHANGED
@@ -26,7 +26,7 @@ Yaparc::Result::OK indicates success.
26
26
  === Primitive Parsers
27
27
 
28
28
  * Yaparc::Succeed
29
- * Yaparc::Fail
29
+ * Yaparc::FailParser
30
30
  * Yaparc::Item
31
31
  * Yaparc::Satisfy
32
32
 
@@ -41,14 +41,14 @@ returns the singleton array <tt>[[1, "blah, blah, blah"]]</tt>.
41
41
  parser.parse("blah, blah, blah")
42
42
  #=> #<Yaparc::Result::OK:0xb7aaaf5c @input="blah, blah, blah", @value=1>
43
43
 
44
- ==== Fail class
44
+ ==== FailParser class
45
45
 
46
- The parser Yaparc::Fail always fails, regardless of the contents of the input
47
- string.
46
+ The parser Yaparc::FailParser always fails, regardless of the contents of the
47
+ input string.
48
48
 
49
- parser = Yaparc::Fail.new
49
+ parser = Yaparc::FailParser.new
50
50
  parser.parse("abc")
51
- #=> #<Yaparc::Result::Fail:0xb7aa56b0 @value=nil>
51
+ #=> #<Yaparc::Result::FailParser:0xb7aa56b0 @value=nil>
52
52
 
53
53
  ==== Item class
54
54
 
data/Rakefile CHANGED
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+ require "net/http"
5
6
 
6
7
  Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
8
+ t.libs << 'test'
9
+ t.libs << 'lib'
10
+ t.test_files = FileList['test/**/*_test.rb']
10
11
  end
11
12
 
12
- require "rubocop/rake_task"
13
+ require 'rubocop/rake_task'
13
14
 
14
15
  RuboCop::RakeTask.new
15
16
 
@@ -19,3 +20,8 @@ desc 'Generate signatures'
19
20
  task :gensig do
20
21
  sh 'typeprof', '-o', 'sig/yaparc.gen.rbs', 'sig/yaparc.rbs', *Dir['lib/**/*.rb']
21
22
  end
23
+
24
+ file "test/abc.html" do |t|
25
+ doc = Net::HTTP.get("web.archive.org", "/web/20120814155205/http://www.norbeck.nu:80/abc/bnf/abc20bnf.htm")
26
+ File.write(t.name, doc)
27
+ end
data/TODO ADDED
@@ -0,0 +1,11 @@
1
+ - [X] Result::Fail to Fail
2
+ - [X] Fail to FailParser
3
+ - [X] Remove raise
4
+ - [X] Use newer keyword arguments
5
+ - [X] Avoid accessors
6
+ - [X] Split modules into files
7
+ - [X] Split BNF files
8
+
9
+ # Local Variables:
10
+ # mode: org
11
+ # End:
@@ -0,0 +1,16 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class AbstractParser
5
+ include Parsable
6
+
7
+ def parse(input)
8
+ tree = @parser.call.parse(input)
9
+ @tree = if block_given?
10
+ yield tree
11
+ else
12
+ tree
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/yaparc/alt.rb ADDED
@@ -0,0 +1,22 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Alt
5
+ include Parsable
6
+
7
+ def initialize(*parsers)
8
+ @parser = lambda do |input|
9
+ final_result = Fail.new(input:)
10
+ parsers.each do |parser|
11
+ case result = parser.parse(input)
12
+ in Fail
13
+ next
14
+ in OK
15
+ break final_result = result
16
+ end
17
+ end
18
+ final_result
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Apply
5
+ include Parsable
6
+
7
+ def initialize(parser)
8
+ @parser = lambda do |input|
9
+ result = parser.parse(input)
10
+ if result.instance_of?(OK)
11
+ Succeed.new(yield(result.value)).parse(result.input)
12
+ else
13
+ FailParser.new.parse(input)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Char
5
+ include Parsable
6
+
7
+ def initialize(char, case_sensitive = true)
8
+ equal_char = if case_sensitive
9
+ ->(i) { i == char }
10
+ else # in case of case-insentive
11
+ ->(i) { i.casecmp(char) == 0 }
12
+ end
13
+ @parser = proc { Satisfy.new(equal_char) }
14
+ end
15
+ end
16
+ end
data/lib/yaparc/cr.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class CR
5
+ include Parsable
6
+
7
+ def initialize
8
+ @parser = proc { Regex.new(/\A[ \t]+\n[ \t\n]+/) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Digit
5
+ include Parsable
6
+
7
+ def initialize
8
+ @parser = proc { Satisfy.new(IS_DIGIT) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class FailParser
5
+ include Parsable
6
+
7
+ def initialize
8
+ @parser = ->(input) { Fail.new(input:) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Ident
5
+ include Parsable
6
+
7
+ def initialize
8
+ @parser = proc do
9
+ Seq.new(
10
+ Satisfy.new(IS_LOWER),
11
+ Many.new(Satisfy.new(IS_ALPHANUM), '')
12
+ ) do |head, tail|
13
+ head + tail
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ # Refer to http://www.cs.nott.ac.uk/~gmh/monparsing.pdf, p.23
5
+ class Identifier
6
+ include Yaparc::Parsable
7
+
8
+ IDENTIFIER_REGEX = /\A[a-zA-Z_]+[a-zA-Z0-9_]*/
9
+
10
+ def initialize(regex: nil, exclude: nil)
11
+ identifier_regex = ::Yaparc::Regex.new(regex || IDENTIFIER_REGEX)
12
+
13
+ tokenizer = Tokenize.new(identifier_regex)
14
+
15
+ unless exclude
16
+ @parser = proc { tokenizer }
17
+ return
18
+ end
19
+
20
+ @parser = lambda do |input|
21
+ keyword_parsers = exclude.map { |keyword| Yaparc::String.new(keyword) }
22
+
23
+ case result = Yaparc::Alt.new(*keyword_parsers).parse(input)
24
+ when Yaparc::OK
25
+ Yaparc::FailParser.new
26
+ else # Fail or Error
27
+ tokenizer
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Item
5
+ include Parsable
6
+
7
+ def initialize
8
+ @parser = lambda do |input|
9
+ if input.nil? || input.empty?
10
+ Fail.new(input:)
11
+ else
12
+ OK.new(value: input[0], input: input[1..])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Literal
5
+ include Parsable
6
+
7
+ def initialize(literal, case_sensitive = true)
8
+ @parser = proc {
9
+ Tokenize.new(Yaparc::String.new(literal, case_sensitive))
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ # permits zero or more applications of parser.
5
+ class Many
6
+ include Parsable
7
+
8
+ def initialize(parser, identity = [])
9
+ @parser = proc {
10
+ Alt.new(ManyOne.new(parser, identity), Succeed.new(identity))
11
+ }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ # requires at least one successfull application of parser.
5
+ class ManyOne
6
+ include Parsable
7
+
8
+ def initialize(parser, identity = [])
9
+ @parser = lambda do |_input|
10
+ Seq.new(parser, Many.new(parser, identity)) do |head, tail|
11
+ case head
12
+ when ::String, ::Array, ::Integer
13
+ head + tail
14
+ when ::Hash
15
+ head.merge(tail)
16
+ else
17
+ if tail.nil?
18
+ head
19
+ else
20
+ [head] + tail
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/yaparc/nat.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Nat
5
+ include Parsable
6
+
7
+ def initialize
8
+ @parser = proc { Seq.new(ManyOne.new(Digit.new, '')) { |vs| vs.to_i } }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Natural
5
+ include Parsable
6
+
7
+ # Accepts Yaparc::Tokenize::new's keyword arguments.
8
+ def initialize(**args)
9
+ @parser = proc { Tokenize.new(Nat.new, **args) }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ # hutton92:_higher_order_funct_parsin,p.19
5
+ # https://www.cambridge.org/core/journals/journal-of-functional-programming/article/higherorder-functions-for-parsing/0490F2C8511F7625F9FC15BFFEDBB0AA
6
+ class NoFail
7
+ include Parsable
8
+
9
+ def initialize(parser)
10
+ @parser = lambda do |input|
11
+ result = parser.parse(input)
12
+ if result.instance_of?(Fail)
13
+ Error.new(value: result.value, input: result.input)
14
+ else
15
+ Succeed.new(result.value)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Yaparc
2
+ module Parsable
3
+ IS_LOWER = ->(c) { c >= 'a' and c <= 'z' }
4
+ IS_ALPHANUM = ->(c) { (c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') }
5
+ IS_DIGIT = ->(i) { i >= '0' and i <= '9' }
6
+ IS_SPACE = ->(i) { i == ' ' }
7
+ IS_WHITESPACE = ->(i) { [' ', "\n", "\t"].include?(i) }
8
+ IS_CR = ->(i) { i == "\n" }
9
+
10
+ def parse(input)
11
+ result = @parser.call(input)
12
+
13
+ if result.respond_to?(:parse)
14
+ result.parse(input)
15
+ else
16
+ result
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Regex
5
+ include Parsable
6
+
7
+ def initialize(regex)
8
+ @regex = regex
9
+ @parser = lambda do |input|
10
+ if match = Regexp.new(regex).match(input)
11
+ if block_given?
12
+ Succeed.new(yield(*match.to_a[1..])).parse(match.post_match)
13
+ else
14
+ OK.new(value: match[0], input: match.post_match)
15
+ end
16
+ else
17
+ Fail.new(input:)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Satisfy
5
+ include Parsable
6
+
7
+ def initialize(predicate)
8
+ @parser = lambda do |input|
9
+ result = Item.new.parse(input)
10
+
11
+ if result.instance_of?(OK) && predicate.call(result.value)
12
+ Succeed.new(result.value, result.input)
13
+ else
14
+ FailParser.new
15
+ end
16
+ end
17
+ end
18
+
19
+ def parse(input)
20
+ case parser = @parser.call(input)
21
+ in Succeed
22
+ parser.parse(parser.remaining)
23
+ in FailParser
24
+ parser.parse(input)
25
+ end
26
+ end
27
+ end
28
+ end
data/lib/yaparc/seq.rb ADDED
@@ -0,0 +1,33 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Seq
5
+ include Parsable
6
+
7
+ def initialize(*parsers)
8
+ @parser = lambda do |input|
9
+ args = []
10
+ initial_result = OK.new(input:)
11
+ final_result = parsers.inject(initial_result) do |subsequent, parser|
12
+ result = parser.parse(subsequent.input)
13
+ break Fail.new(input: subsequent.input) if result.instance_of?(Fail)
14
+
15
+ args << result.value
16
+ result
17
+ end
18
+
19
+ case final_result
20
+ in Fail
21
+ Fail.new(input: final_result.input)
22
+ in OK
23
+ final_value = if block_given?
24
+ yield(*args)
25
+ else
26
+ args.last
27
+ end
28
+ OK.new(value: final_value, input: final_result.input)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Space
5
+ include Parsable
6
+
7
+ def initialize
8
+ @parser = proc { Regex.new(/\A */) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class String
5
+ include Parsable
6
+
7
+ def initialize(string, case_sensitive = true)
8
+ @parser = lambda do |_input|
9
+ result = Item.new.parse(string)
10
+ if result.instance_of?(OK)
11
+ Seq.new(
12
+ Char.new(result.value, case_sensitive),
13
+ Yaparc::String.new(result.input, case_sensitive),
14
+ Succeed.new(result.value + result.input)
15
+ ) do |_, _, succeed_result|
16
+ succeed_result
17
+ end
18
+ else
19
+ Succeed.new(result)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Succeed
5
+ include Parsable
6
+
7
+ attr_reader :remaining
8
+
9
+ def initialize(value, remaining = nil)
10
+ @parser = ->(input) { OK.new(value:, input:) }
11
+ @remaining = remaining
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Symbol
5
+ include Parsable
6
+
7
+ def initialize(literal)
8
+ @parser = proc { Literal.new(literal) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class Tokenize
5
+ include Parsable
6
+
7
+ attr_writer :prefix, :postfix
8
+
9
+ def initialize(parser, prefix: nil, postfix: nil)
10
+ @parser = lambda do |_input|
11
+ @prefix = prefix || WhiteSpace.new
12
+ @postfix = postfix || WhiteSpace.new
13
+ block_given? and yield self
14
+ Seq.new(@prefix, parser, @postfix) { |_, vs, _| vs }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class WhiteSpace
5
+ include Parsable
6
+
7
+ def initialize
8
+ @parser = proc { Regex.new(/\A[\t\n ]*/) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'parsable'
2
+
3
+ module Yaparc
4
+ class ZeroOne
5
+ include Parsable
6
+
7
+ def initialize(parser, identity = [])
8
+ @parser = lambda do |input|
9
+ case (result = parser.parse(input))
10
+ in Fail
11
+ OK.new(value: identity, input:)
12
+ in Error
13
+ Error.new(value: result.value, input: result.input)
14
+ in OK
15
+ result
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end