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.
- checksums.yaml +7 -0
- data/LICENSE +235 -0
- data/README.md +3 -0
- data/lib/enumerable.rb +28 -0
- data/lib/oo_peg/input.rb +34 -0
- data/lib/oo_peg/ostruct.rb +8 -0
- data/lib/oo_peg/parser/class_methods.rb +33 -0
- data/lib/oo_peg/parser/combinators/lazy.rb +17 -0
- data/lib/oo_peg/parser/combinators.rb +458 -0
- data/lib/oo_peg/parser.rb +72 -0
- data/lib/oo_peg/parsers/advanced/operator_parser.rb +22 -0
- data/lib/oo_peg/parsers/advanced/sexp_parser.rb +77 -0
- data/lib/oo_peg/parsers/advanced/string_parser.rb +40 -0
- data/lib/oo_peg/parsers/advanced/symbol_parser.rb +22 -0
- data/lib/oo_peg/parsers/advanced.rb +117 -0
- data/lib/oo_peg/parsers/base_parsers.rb +252 -0
- data/lib/oo_peg/parsers/common_parsers.rb +193 -0
- data/lib/oo_peg/parsers/lispy_parser.rb +18 -0
- data/lib/oo_peg/parsers/pseudo_parsers.rb +12 -0
- data/lib/oo_peg/parsers/true_set.rb +11 -0
- data/lib/oo_peg/parsers.rb +13 -0
- data/lib/oo_peg/result.rb +88 -0
- data/lib/oo_peg/version.rb +8 -0
- data/lib/oo_peg.rb +257 -0
- metadata +91 -0
@@ -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
|
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: []
|