oo_peg 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.
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OOPeg
4
+ class Result
5
+ class IllegalState < RuntimeError
6
+ end
7
+
8
+ attr_reader :ast, :error, :input, :ok, :parser_name
9
+
10
+ def self.ok(ast:, input:, parser_name: nil) = new(ok: true, ast:, input:, parser_name:)
11
+
12
+ def self.nok(input:, error:, parser_name: nil)
13
+ new(ok: false, input:, error:, parser_name:)
14
+ end
15
+
16
+ def deconstruct_keys(*, **) = to_h
17
+
18
+ def map(&blk)
19
+ return self unless ok
20
+ self.class.ok(ast: blk.(ast), input:)
21
+ end
22
+
23
+ def merge(ast: nil, error: nil, input: nil, parser_name: nil)
24
+ _check_valid_merge_params!(ast:, error:, input:)
25
+ @ast = ast if ast
26
+ @error = error if error
27
+ @input = input if input
28
+ @parser_name = parser_name if parser_name
29
+ self
30
+ end
31
+
32
+ def or(input:, error:, parser_name: nil) = self.class.nok(input:, error:, parser_name:)
33
+
34
+ # def reduce(values, &blk)
35
+ # result = self
36
+ # values.each do |ele|
37
+ # result = blk.(result, ele)
38
+ # break result unless result.ok
39
+ # end
40
+ # result
41
+ # end
42
+
43
+ def to_h = {ast:, error:, input:, ok:, parser_name:}
44
+
45
+ def to_s = inspect
46
+
47
+ # def update(&updater)
48
+ # raise IllegalState, "must not update an error result" unless ok
49
+ # map(&updater)
50
+ # end
51
+
52
+ # def update_or(input:, error:, parser_name: nil, &updater)
53
+ # return map(&updater) if ok
54
+
55
+ # self.class.nok(input:, error:, parser_name:)
56
+ # end
57
+
58
+ # def while(input: nil, error: nil, name: nil, &blk)
59
+ # result = self
60
+ # count = 0
61
+ # loop do
62
+ # return input ? self.class.nok(input:, error:, name:) : result unless result.ok
63
+ # result = blk.(self, count)
64
+ # count += 1
65
+ # end
66
+ # result
67
+ # end
68
+
69
+ private
70
+ def initialize(ok:, input:, ast: nil, error: nil, parser_name: nil)
71
+ @ast = ast
72
+ @error = error
73
+ @input = input
74
+ @ok = ok
75
+ @parser_name = parser_name
76
+ end
77
+
78
+ def _check_valid_merge_params!(ast:, error:, input:)
79
+ if ok
80
+ raise ArgumentError, "must not merge error upon ok result" if error
81
+ else
82
+ raise ArgumentError, "must not merge ast or input upon nok result" if ast || input
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OOPeg
4
+ module Version
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
8
+ # SPDX-License-Identifier: AGPL-3.0-or-later
data/lib/oo_peg.rb ADDED
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'oo_peg/ostruct'
4
+ require_relative 'oo_peg/parser'
5
+ ##
6
+ # == OOPeg
7
+ # A peg parser not using custom sytnax but exposing an OO based API
8
+ #
9
+ # === Usage
10
+ #
11
+ # require 'oo_peg'
12
+ #
13
+ # Will be enough unless you need some advanced parsers which are provided by
14
+ # this gem too.
15
+ #
16
+ # Than you can combine the available parsers and combinators which should be
17
+ # enough to parse about any Context Free Language. However, later on we will
18
+ # show how to enhance your base parsers if needed.
19
+ #
20
+ # All available _parsers_ are defined in +OOPeg::Parsers+ a module that you
21
+ # can either include into your class/main or mixin.
22
+ #
23
+ # All available _combinators_ are class or instance methods of the +OOPeg::Parser+
24
+ # class.
25
+ #
26
+ # As a matter of fact, to achieve the same behavior in your code as in these
27
+ # +doc_rspecs+ (c.f. https://rubygems.org/gems/doc_rspec) you can use
28
+ #
29
+ # include OOPeg::Parsers
30
+ # include OOPeg # for the main parse method
31
+ #
32
+ # === Quick Start Examples
33
+ #
34
+ # An integer parser is already provided, so let us show how to parse a list
35
+ # of integers
36
+ #
37
+ # # example: Parsing A List Of Integers
38
+ #
39
+ # input = "42, 43, 44"
40
+ # head_parser = int_parser
41
+ # tail_parser = char_parser(",").and(ws_parser, int_parser)
42
+ # .map(&:last).many
43
+ # parser = head_parser.and(tail_parser).and(end_parser).map(&:flatten)
44
+ #
45
+ # result = parse(parser, input)
46
+ # result.ast => [42, 43, 44]
47
+ # result is! ok
48
+ #
49
+ # Now there is a lot going on here, so let us break down things a little bit
50
+ #
51
+ # ==== The Int Parser
52
+ #
53
+ # # example: The Int Parser
54
+ #
55
+ # parse(int_parser, "+0").ast => 0
56
+ # parse(int_parser, "-42").ast => -42
57
+ #
58
+ # ==== The Char And Whitespace parsers
59
+ #
60
+ # # example: White Space
61
+ #
62
+ # parse(ws_parser, " ").ast => nil
63
+ # parse(ws_parser, " ").ast => nil
64
+ # parse(ws_parser, " ") is! ok
65
+ # expect(parse(ws_parser, "")).not_to be_ok
66
+ #
67
+ # parse(char_parser(","), ",").ast => ","
68
+ # parse(char_parser(","), " ").ast => nil
69
+ # expect(parse(char_parser(",")," ")).not_to be_ok
70
+ #
71
+ # ==== The End Parser
72
+ #
73
+ # # example: The End Parser
74
+ #
75
+ # parse(end_parser, "") is! ok
76
+ # expect(parse(end_parser, " ")).not_to be_ok
77
+ # expect(parse(end_parser, "alpha")).not_to be_ok
78
+ # expect(parse(end_parser, "42")).not_to be_ok
79
+ #
80
+ # ==== Making Sequences with +.and+
81
+ #
82
+ # # example: A sequence of two parsers
83
+ #
84
+ # parser = char_parser(",").and(ws_parser)
85
+ #
86
+ # # N.B.: nil results are not included into the result list
87
+ # parse(parser, ", ").ast => [","]
88
+ #
89
+ # # example: Assuring a sequence parses all input with the end_parser
90
+ #
91
+ # parser = char_parser(",").and(ws_parser, end_parser)
92
+ #
93
+ # parse(parser, ", ").ast => [","]
94
+ # expect(parse(parser, ", hello")).not_to be_ok
95
+ #
96
+ # ==== Combining the sequence with many
97
+ #
98
+ # # example: many lets us parse lists
99
+ #
100
+ # parser = char_parser(",").and(ws_parser).many
101
+ #
102
+ # parse(parser, "").ast => []
103
+ # parse(parser, ", ").ast => [[","]]
104
+ # parse(parser, ", , ").ast => [[","], [","]]
105
+ #
106
+ # # example: use map and flatten to get simpler results
107
+ #
108
+ # parser = char_parser(",").and(ws_parser).many.map(&:flatten)
109
+ #
110
+ # parse(parser, "").ast => []
111
+ # parse(parser, ", ").ast => [","]
112
+ # parse(parser, ", , ").ast => [",", ","]
113
+ #
114
+ # # example: many takes a min: length constraint
115
+ #
116
+ # parser = char_parser(",").and(ws_parser).many(min: 2).map(&:flatten)
117
+ #
118
+ # parse(parser, "").ast => nil # A synonym for not_ok in this case
119
+ # parse(parser, ", ").ast => nil
120
+ # parse(parser, ", , ").ast => [",", ","]
121
+ #
122
+ # Now we have explained all the elements of the first usage by means of some examples.
123
+ #
124
+ # We will continue to use this approach to document all available parsers and combinators
125
+ # in the
126
+ #
127
+ # OOPeg@Detailed+API+Documentation
128
+ #
129
+ #
130
+ # === Basic Concepts
131
+ #
132
+ # - What Is A Parser?
133
+ #
134
+ # In the context of this gem, a +Parser+ is an instance of the class +OOPeg::Parser+.
135
+ #
136
+ # - How can we write parsers?
137
+ #
138
+ # Mostly we do not need as we have predefined parsers which we can parmetrize and
139
+ # combine with combinators to create new parsers.
140
+ #
141
+ # - So What Is A Combinator?
142
+ #
143
+ # Combinators are methods on parsers, oftentimes accepting more parsers as arguments
144
+ # which return parsers.
145
+ #
146
+ # - How does a parser parse?
147
+ #
148
+ # It will take some input and return a result.
149
+ #
150
+ # - When does a parser succeed?
151
+ #
152
+ # If its result is +ok+ IOW <tt>result.ok?</tt> or <tt>result => {ok: true}</tt>
153
+ #
154
+ # - When does a parser fail?
155
+ #
156
+ # Why don't you take a wild guess here?
157
+ #
158
+ # - But what is an input?
159
+ #
160
+ # - Short answer: A string
161
+ # - Correct answer: An object of class +OOPeg::Input+ that contains the graphemes of an input string
162
+ # and its character position
163
+ #
164
+ # - So, when I call <tt>parse(some_parser, "some string")</tt>, does the +parse+
165
+ # method convert the string?
166
+ #
167
+ # Indeed!
168
+ #
169
+ # - And a result?
170
+ #
171
+ # An instance of the class +OOPeg::Result+
172
+ #
173
+ # - Wait a minute, I have seen an +OpenStruct+
174
+ #
175
+ # Well +OOPeg#parse+ converts an +OOPeg::Result+ to an +OpenStruct+ after
176
+ # calling the _real_ parse method +OOPeg::Parser#parse+
177
+ #
178
+ # - So can I write my own parser?
179
+ #
180
+ # Again, indeed!
181
+ #
182
+ # Here is an example:
183
+ #
184
+ # # example: look Ma, my own parser
185
+ #
186
+ # R = OOPeg::Result
187
+ # my_parser = OOPeg::Parser.new("my parser") do |input|
188
+ # case input.content
189
+ # in [a, b, *]
190
+ # if a.to_i < b.to_i
191
+ # # it is important to advance the input
192
+ # R.ok(ast: "#{a.to_i} < #{b.to_i}", input: input.advance(2))
193
+ # else
194
+ # R.nok(input:, error: "not rising", parser_name: "my parser")
195
+ # end
196
+ # else
197
+ # R.nok(input:, error: "need two digits", parser_name: "my parser")
198
+ # end
199
+ # end
200
+ #
201
+ # parse(my_parser, "").error => "need two digits"
202
+ # parse(my_parser, "a").error => "need two digits"
203
+ # parse(my_parser, "42").error => "not rising"
204
+ # parse(my_parser, "02").ast => "0 < 2"
205
+ #
206
+ # === Detailed API Documentation
207
+ #
208
+ # ==== Basic Parsers
209
+ #
210
+ # The Basic Parsers are parsers that do not depend on other parsers or combinators.
211
+ # They are documented in detail in
212
+ # OOPeg::Parsers::BaseParsers
213
+ #
214
+ #
215
+ # ==== Common Parsers
216
+ #
217
+ # These are <em>convenience</em> parsers that rely on the basic parsers and combinators,
218
+ # they are documented in detail in
219
+ # OOPeg::Parsers::CommonParsers
220
+ #
221
+ # ==== Combinators
222
+ #
223
+ # A _combinator_ is a method that takes one or more parsers and returns another parser.
224
+ #
225
+ # They are exposed as methods on the +OOPeg::Parser+ class and implemented in
226
+ # OOPeg::Parser::Combinators
227
+ #
228
+ # ==== Advanced Parsers
229
+ #
230
+ # These are parsers that parse commonly used grammars, like +Symbols+, +Strings+ and
231
+ # complete +S-Expressions+.
232
+ #
233
+ # They are not automatically required and must be used as follows
234
+ #
235
+ # require 'oo_peg/parsers/advanced'
236
+ # extend OOPeg::Parsers::Advanced
237
+ #
238
+ #
239
+ # Here is their detailed documentation
240
+ # OOPeg::Parsers::Advanced
241
+ #
242
+ # Each parser, exposed in the +OOPeg::Parsers::Advanced+ module
243
+ # can also be required individually like so <tt>require 'oo_peg/parsers/advanced/string_parser'.
244
+ #
245
+ # This will expose the +OOPeg::Parsers::Advanced::StringParser+ class.
246
+ #
247
+ module OOPeg
248
+
249
+ def self.parse(parser, input) = OpenStruct.new(OOPeg::Parser.parse(parser, input).to_h)
250
+ def parse(parser, input) = OpenStruct.new(OOPeg::Parser.parse(parser, input).to_h)
251
+ # def parse(parser, input)
252
+ # result = OOPeg::Parser.parse(parser, input)
253
+ # OpenStruct.new(result.to_h)
254
+ # end
255
+
256
+ end
257
+ # SPDX-License-Identifier: AGPL-3.0-or-later
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oo_peg
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Dober
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ostruct
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.6.1
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.6.1
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 3.13.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 3.13.0
40
+ description: A peg parser not using custom sytnax but exposing an OO based API
41
+ email: robert.dober@gmail.com
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - LICENSE
47
+ - README.md
48
+ - lib/enumerable.rb
49
+ - lib/oo_peg.rb
50
+ - lib/oo_peg/input.rb
51
+ - lib/oo_peg/ostruct.rb
52
+ - lib/oo_peg/parser.rb
53
+ - lib/oo_peg/parser/class_methods.rb
54
+ - lib/oo_peg/parser/combinators.rb
55
+ - lib/oo_peg/parser/combinators/lazy.rb
56
+ - lib/oo_peg/parsers.rb
57
+ - lib/oo_peg/parsers/advanced.rb
58
+ - lib/oo_peg/parsers/advanced/operator_parser.rb
59
+ - lib/oo_peg/parsers/advanced/sexp_parser.rb
60
+ - lib/oo_peg/parsers/advanced/string_parser.rb
61
+ - lib/oo_peg/parsers/advanced/symbol_parser.rb
62
+ - lib/oo_peg/parsers/base_parsers.rb
63
+ - lib/oo_peg/parsers/common_parsers.rb
64
+ - lib/oo_peg/parsers/lispy_parser.rb
65
+ - lib/oo_peg/parsers/pseudo_parsers.rb
66
+ - lib/oo_peg/parsers/true_set.rb
67
+ - lib/oo_peg/result.rb
68
+ - lib/oo_peg/version.rb
69
+ homepage: https://codeberg.org/lab419/oo_peg
70
+ licenses:
71
+ - AGPL-3.0-or-later
72
+ metadata:
73
+ homepage_uri: https://codeberg.org/lab419/oo_peg
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 3.4.1
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.6.8
89
+ specification_version: 4
90
+ summary: '["A peg parser not using custom sytnax but exposing an OO based API"]'
91
+ test_files: []