paco 0.2.1 → 0.2.2
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/.rbnext/2.3/paco/context.rb +51 -0
- data/lib/.rbnext/2.3/paco/index.rb +15 -0
- data/lib/.rbnext/2.3/paco/parse_error.rb +30 -0
- data/lib/.rbnext/2.5/paco/combinators.rb +174 -0
- data/lib/.rbnext/2.5/paco/parser.rb +180 -0
- data/lib/.rbnext/2.5/paco/rspec/parse_matcher.rb +47 -0
- data/lib/.rbnext/2.6/paco/context.rb +51 -0
- data/lib/paco/version.rb +1 -1
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d72f440d9727dc302366b842d7a7bf3d3d2ac84f4e13b41ce8954dad4b971f0
|
4
|
+
data.tar.gz: 81b0e2b377c35060db0416472115754cd62d45efda5f302265003b1340e639b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3aaa2ce601f6da71e1d24a0a13bee121abc3c6d52b9120e6795b57e5880cefbb2fae9c24b69f7e6284a54913b293e25c6d3fcbe7e503105e782fd84817e13413
|
7
|
+
data.tar.gz: fed4af90111069f4c39f4ce6ed85b72480aaf6bf10bcf4f06e8701a60f037095638647b423282a7dfe00ae0f1e8c17e94b907368879394b0f664ae06a812a47e
|
data/CHANGELOG.md
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "paco/callstack"
|
4
|
+
require "paco/index"
|
5
|
+
|
6
|
+
module Paco
|
7
|
+
class Context
|
8
|
+
attr_reader :input, :last_pos, :callstack
|
9
|
+
attr_accessor :pos
|
10
|
+
|
11
|
+
def initialize(input, pos: 0, with_callstack: false)
|
12
|
+
@input = input
|
13
|
+
@pos = pos
|
14
|
+
@callstack = Callstack.new if with_callstack
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(n)
|
18
|
+
input[pos, n]
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_all
|
22
|
+
input[pos..-1]
|
23
|
+
end
|
24
|
+
|
25
|
+
def eof?
|
26
|
+
pos >= input.length
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [Integer] from
|
30
|
+
# @return [Paco::Index]
|
31
|
+
def index(from = nil)
|
32
|
+
Index.calculate(input: input, pos: from || pos)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Paco::Parser] parser
|
36
|
+
def failure_parse(parser)
|
37
|
+
((((__safe_lvar__ = @callstack) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.failure(pos: pos, parser: parser.desc))
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [Paco::Parser] parser
|
41
|
+
def start_parse(parser)
|
42
|
+
((((__safe_lvar__ = @callstack) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.start(pos: pos, parser: parser.desc))
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [Object] result
|
46
|
+
# @param [Paco::Parser] parser
|
47
|
+
def success_parse(result, parser)
|
48
|
+
((((__safe_lvar__ = @callstack) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.success(pos: pos, result: result, parser: parser.desc))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Paco
|
2
|
+
Index = Struct.new(:pos, :line, :column) do
|
3
|
+
# @param [String] input
|
4
|
+
# @param [Integer] pos
|
5
|
+
def self.calculate(input:, pos:)
|
6
|
+
raise ArgumentError, "`pos` must be a non-negative integer" if pos < 0
|
7
|
+
raise ArgumentError, "`pos` is grater then input length" if pos > input.length
|
8
|
+
|
9
|
+
lines = input[0..pos].lines
|
10
|
+
line = lines.empty? ? 1 : lines.length
|
11
|
+
column = ((((__safe_lvar__ = lines.last) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.length) || 1
|
12
|
+
new(pos, line, column)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Paco
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class ParseError < Error
|
7
|
+
attr_reader :ctx, :pos, :expected
|
8
|
+
|
9
|
+
# @param [Paco::Context] ctx
|
10
|
+
def initialize(ctx, expected)
|
11
|
+
@ctx = ctx
|
12
|
+
@pos = ctx.pos
|
13
|
+
@expected = expected
|
14
|
+
end
|
15
|
+
|
16
|
+
def callstack
|
17
|
+
ctx.callstack
|
18
|
+
end
|
19
|
+
|
20
|
+
def message
|
21
|
+
index = ctx.index(pos)
|
22
|
+
<<-MSG
|
23
|
+
\nParsing error
|
24
|
+
line #{index.line}, column #{index.column}:
|
25
|
+
unexpected #{ctx.eof? ? "end of file" : ctx.input[pos].inspect}
|
26
|
+
expecting #{expected}
|
27
|
+
MSG
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "paco/combinators/char"
|
4
|
+
require "paco/memoizer"
|
5
|
+
|
6
|
+
module Paco
|
7
|
+
module Combinators
|
8
|
+
include Char
|
9
|
+
extend Char
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# Returns a parser that runs the passed `parser` without consuming the input, and
|
14
|
+
# returns `null` if the passed `parser` _does not match_ the input. Fails otherwise.
|
15
|
+
# @param [Paco::Parser] parser
|
16
|
+
# @return [Paco::Parser]
|
17
|
+
def not_followed_by(parser)
|
18
|
+
Parser.new("not #{parser.desc}") do |ctx, pars|
|
19
|
+
start_pos = ctx.pos
|
20
|
+
begin
|
21
|
+
parser._parse(ctx)
|
22
|
+
rescue ParseError
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
pars.failure(ctx)
|
26
|
+
ensure
|
27
|
+
ctx.pos = start_pos
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a parser that doesn't consume any input and always returns `result`.
|
33
|
+
# @return [Paco::Parser]
|
34
|
+
def succeed(result)
|
35
|
+
Parser.new("succeed(#{result})") { result }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a parser that doesn't consume any input and always fails with passed `message`.
|
39
|
+
# @param [String] message
|
40
|
+
# @return [Paco::Parser]
|
41
|
+
def failed(message)
|
42
|
+
Parser.new(message) { |ctx, parser| parser.failure(ctx) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a parser that runs the passed `parser` without consuming the input,
|
46
|
+
# and returns empty string.
|
47
|
+
# @param [Paco::Parser] parser
|
48
|
+
# @return [Paco::Parser]
|
49
|
+
def lookahead(parser)
|
50
|
+
Parser.new("lookahead(#{parser.desc})") do |ctx|
|
51
|
+
start_pos = ctx.pos
|
52
|
+
parser._parse(ctx)
|
53
|
+
ctx.pos = start_pos
|
54
|
+
""
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Accepts any number of parsers, and returns a parser that returns the value of the first parser that succeeds, backtracking in between.
|
59
|
+
# @param [Array<Paco::Parser>] parsers
|
60
|
+
# @return [Paco::Parser]
|
61
|
+
def alt(*parsers)
|
62
|
+
raise ArgumentError, "no parsers specified" if parsers.empty?
|
63
|
+
|
64
|
+
Parser.new("alt(#{parsers.map(&:desc).join(", ")})") do |ctx|
|
65
|
+
result = nil
|
66
|
+
last_error = nil
|
67
|
+
start_pos = ctx.pos
|
68
|
+
parsers.each do |pars|
|
69
|
+
begin;break result = {value: pars._parse(ctx)}
|
70
|
+
rescue ParseError => e
|
71
|
+
last_error = e
|
72
|
+
ctx.pos = start_pos
|
73
|
+
next;end
|
74
|
+
end
|
75
|
+
raise last_error unless result
|
76
|
+
result[:value]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Accepts one or more parsers, and returns a parser that expects them
|
81
|
+
# to match in order, returns an array of all their results.
|
82
|
+
# If `block` specified, passes results of the `parses` as an arguments
|
83
|
+
# to a `block`, and at the end returns its result.
|
84
|
+
# @param [Array<Paco::Parser>] parsers
|
85
|
+
# @return [Paco::Parser]
|
86
|
+
def seq(*parsers)
|
87
|
+
raise ArgumentError, "no parsers specified" if parsers.empty?
|
88
|
+
|
89
|
+
result = Parser.new("seq(#{parsers.map(&:desc).join(", ")})") do |ctx|
|
90
|
+
start_pos = ctx.pos
|
91
|
+
begin
|
92
|
+
parsers.map { |parser| parser._parse(ctx) }
|
93
|
+
rescue ParseError => e
|
94
|
+
ctx.pos = start_pos
|
95
|
+
raise e
|
96
|
+
end
|
97
|
+
end
|
98
|
+
return result unless block_given?
|
99
|
+
|
100
|
+
result.fmap { |results| yield(*results) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# Accepts a block that returns a parser, which is evaluated the first time the parser is used.
|
104
|
+
# This is useful for referencing parsers that haven't yet been defined, and for implementing recursive parsers.
|
105
|
+
# @return [Paco::Parser]
|
106
|
+
def lazy(desc = "", &block)
|
107
|
+
Parser.new(desc) { |ctx| block.call._parse(ctx) }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns a parser that expects zero or more matches for `parser`,
|
111
|
+
# separated by the parser `separator`. Returns an array of `parser` results.
|
112
|
+
# @param [Paco::Parser] parser
|
113
|
+
# @param [Paco::Parser] separator
|
114
|
+
# @return [Paco::Parser]
|
115
|
+
def sep_by(parser, separator)
|
116
|
+
alt(sep_by!(parser, separator), succeed([]))
|
117
|
+
.with_desc("sep_by(#{parser.desc}, #{separator.desc})")
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns a parser that expects one or more matches for `parser`,
|
121
|
+
# separated by the parser `separator`. Returns an array of `parser` results.
|
122
|
+
# @param [Paco::Parser] parser
|
123
|
+
# @param [Paco::Parser] separator
|
124
|
+
# @return [Paco::Parser]
|
125
|
+
def sep_by!(parser, separator)
|
126
|
+
seq(parser, many(separator.next(parser))) { |first, arr| [first] + arr }
|
127
|
+
.with_desc("sep_by!(#{parser.desc}, #{separator.desc})")
|
128
|
+
end
|
129
|
+
alias_method :sep_by_1, :sep_by!
|
130
|
+
|
131
|
+
# Expects the parser `before` before `parser` and `after` after `parser. Returns the result of the parser.
|
132
|
+
# @param [Paco::Parser] before
|
133
|
+
# @param [Paco::Parser] after
|
134
|
+
# @param [Paco::Parser] parser
|
135
|
+
# @return [Paco::Parser]
|
136
|
+
def wrap(before, after, parser)
|
137
|
+
before.next(parser).skip(after)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Expects `parser` zero or more times, and returns an array of the results.
|
141
|
+
# @param [Paco::Parser] parser
|
142
|
+
# @return [Paco::Parser]
|
143
|
+
def many(parser)
|
144
|
+
Parser.new("many(#{parser.desc})") do |ctx|
|
145
|
+
results = []
|
146
|
+
loop do
|
147
|
+
begin;results << parser._parse(ctx)
|
148
|
+
rescue ParseError
|
149
|
+
break;end
|
150
|
+
end
|
151
|
+
results
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Returns parser that returns result of the passed `parser` or nil if `parser` fails.
|
156
|
+
# @param [Paco::Parser] parser
|
157
|
+
# @return [Paco::Parser]
|
158
|
+
def optional(parser)
|
159
|
+
alt(parser, succeed(nil))
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns parser that returns `Paco::Index` representing
|
163
|
+
# the current offset into the parse without consuming the input.
|
164
|
+
# @return [Paco::Parser]
|
165
|
+
def index
|
166
|
+
Parser.new { |ctx| ctx.index }
|
167
|
+
end
|
168
|
+
|
169
|
+
# Helper used for memoization
|
170
|
+
def memoize(&block)
|
171
|
+
Memoizer.memoize(block.source_location, &block)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "paco/combinators"
|
4
|
+
|
5
|
+
module Paco
|
6
|
+
class Parser
|
7
|
+
attr_reader :desc
|
8
|
+
|
9
|
+
# @param [String] desc
|
10
|
+
def initialize(desc = "", &block)
|
11
|
+
@desc = desc
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [String] desc
|
16
|
+
# @return [Paco::Parser]
|
17
|
+
def with_desc(desc)
|
18
|
+
@desc = desc
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [String, Paco::Context] input
|
23
|
+
# @param [true, false] with_callstack
|
24
|
+
def parse(input, with_callstack: false)
|
25
|
+
ctx = input.is_a?(Context) ? input : Context.new(input, with_callstack: with_callstack)
|
26
|
+
skip(Paco::Combinators.eof)._parse(ctx)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [Paco::Context] ctx
|
30
|
+
def _parse(ctx)
|
31
|
+
ctx.start_parse(self)
|
32
|
+
res = @block.call(ctx, self)
|
33
|
+
ctx.success_parse(res, self)
|
34
|
+
res
|
35
|
+
end
|
36
|
+
|
37
|
+
# Raises ParseError
|
38
|
+
# @param [Paco::Context] ctx
|
39
|
+
# @raise [Paco::ParseError]
|
40
|
+
def failure(ctx)
|
41
|
+
ctx.failure_parse(self)
|
42
|
+
raise ParseError.new(ctx, desc), "", []
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a new parser which tries `parser`, and if it fails uses `other`.
|
46
|
+
# @param [Paco::Parser] other
|
47
|
+
# @return [Paco::Parser]
|
48
|
+
def or(other)
|
49
|
+
Parser.new("or(#{desc}, #{other.desc})") do |ctx|
|
50
|
+
begin;_parse(ctx)
|
51
|
+
rescue ParseError
|
52
|
+
other._parse(ctx);end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
alias_method :|, :or
|
56
|
+
|
57
|
+
# Expects `other` parser to follow `parser`, but returns only the value of `parser`.
|
58
|
+
# @param [Poco::Parser] other
|
59
|
+
# @return [Paco::Parser]
|
60
|
+
def skip(other)
|
61
|
+
Paco::Combinators.seq(self, other).fmap { |results| results[0] }.with_desc("#{desc}.skip(#{other.desc})")
|
62
|
+
end
|
63
|
+
alias_method :<, :skip
|
64
|
+
|
65
|
+
# Expects `other` parser to follow `parser`, but returns only the value of `other` parser.
|
66
|
+
# @param [Poco::Parser] other
|
67
|
+
# @return [Paco::Parser]
|
68
|
+
def next(other)
|
69
|
+
Paco::Combinators.seq(self, other).fmap { |results| results[1] }
|
70
|
+
.with_desc("#{desc}.next(#{other.desc})")
|
71
|
+
end
|
72
|
+
alias_method :>, :next
|
73
|
+
|
74
|
+
# Transforms the output of `parser` with the given block.
|
75
|
+
# @return [Paco::Parser]
|
76
|
+
def fmap(&block)
|
77
|
+
Parser.new("#{desc}.fmap") do |ctx|
|
78
|
+
block.call(_parse(ctx))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns a new parser which tries `parser`, and on success
|
83
|
+
# calls the `block` with the result of the parse, which is expected
|
84
|
+
# to return another parser, which will be tried next. This allows you
|
85
|
+
# to dynamically decide how to continue the parse, which is impossible
|
86
|
+
# with the other Paco::Combinators.
|
87
|
+
# @return [Paco::Parser]
|
88
|
+
def bind(&block)
|
89
|
+
Parser.new("#{desc}.bind") do |ctx|
|
90
|
+
block.call(_parse(ctx))._parse(ctx)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
alias_method :chain, :bind
|
94
|
+
|
95
|
+
# Expects `parser` zero or more times, and returns an array of the results.
|
96
|
+
# @return [Paco::Parser]
|
97
|
+
def many
|
98
|
+
Paco::Combinators.many(self)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns a new parser with the same behavior, but which returns passed `value`.
|
102
|
+
# @return [Paco::Parser]
|
103
|
+
def result(value)
|
104
|
+
fmap { value }
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns a new parser which tries `parser` and, if it fails, returns `value` without consuming any input.
|
108
|
+
# @return [Paco::Parser]
|
109
|
+
def fallback(value)
|
110
|
+
self.or(Paco::Combinators.succeed(value))
|
111
|
+
end
|
112
|
+
|
113
|
+
# Expects `other` parser before and after `parser`, and returns the result of the parser.
|
114
|
+
# @param [Paco::Parser] other
|
115
|
+
# @return [Paco::Parser]
|
116
|
+
def trim(other)
|
117
|
+
other.next(self).skip(other)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Expects the parser `before` before `parser` and `after` after `parser. Returns the result of the parser.
|
121
|
+
# @param [Paco::Parser] before
|
122
|
+
# @param [Paco::Parser] after
|
123
|
+
# @return [Paco::Parser]
|
124
|
+
def wrap(before, after)
|
125
|
+
Paco::Combinators.wrap(before, after, self)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns a parser that runs passed `other` parser without consuming the input, and
|
129
|
+
# returns result of the `parser` if the passed one _does not match_ the input. Fails otherwise.
|
130
|
+
# @param [Paco::Parser] other
|
131
|
+
# @return [Paco::Parser]
|
132
|
+
def not_followed_by(other)
|
133
|
+
skip(Paco::Combinators.not_followed_by(other))
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns a parser that runs `parser` and concatenate it results with the `separator`.
|
137
|
+
# @param [String] separator
|
138
|
+
# @return [Paco::Parser]
|
139
|
+
def join(separator = "")
|
140
|
+
fmap { |result| result.join(separator) }
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns a parser that runs `parser` between `min` and `max` times,
|
144
|
+
# and returns an array of the results. When `max` is not specified, `max` = `min`.
|
145
|
+
# @param [Integer] min
|
146
|
+
# @param [Integer] max
|
147
|
+
# @return [Paco::Parser]
|
148
|
+
def times(min, max = nil)
|
149
|
+
max ||= min
|
150
|
+
if min < 0 || max < min
|
151
|
+
raise ArgumentError, "invalid attributes: min `#{min}`, max `#{max}`"
|
152
|
+
end
|
153
|
+
|
154
|
+
Parser.new("#{desc}.times(#{min}, #{max})") do |ctx|
|
155
|
+
results = min.times.map { _parse(ctx) }
|
156
|
+
(max - min).times.each do
|
157
|
+
begin;results << _parse(ctx)
|
158
|
+
rescue ParseError
|
159
|
+
break;end
|
160
|
+
end
|
161
|
+
|
162
|
+
results
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns a parser that runs `parser` at least `num` times,
|
167
|
+
# and returns an array of the results.
|
168
|
+
def at_least(num)
|
169
|
+
Paco::Combinators.seq(times(num), many) do |head, rest|
|
170
|
+
head + rest
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns a parser that runs `parser` at most `num` times,
|
175
|
+
# and returns an array of the results.
|
176
|
+
def at_most(num)
|
177
|
+
times(0, num)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec::Matchers.define(:parse) do |input|
|
4
|
+
chain :as do |expected_output = nil, &block|
|
5
|
+
@expected = expected_output
|
6
|
+
@block = block
|
7
|
+
end
|
8
|
+
|
9
|
+
chain :fully do
|
10
|
+
@expected = input
|
11
|
+
end
|
12
|
+
|
13
|
+
match do |parser|
|
14
|
+
begin;@result = parser.parse(input)
|
15
|
+
return @block.call(@result) if @block
|
16
|
+
return @expected == @result if defined?(@expected)
|
17
|
+
|
18
|
+
true
|
19
|
+
rescue Paco::ParseError => e
|
20
|
+
@error_message = e.message
|
21
|
+
false;end
|
22
|
+
end
|
23
|
+
|
24
|
+
failure_message do |subject|
|
25
|
+
msg = "expected output of parsing #{input.inspect} with #{subject.inspect} to"
|
26
|
+
was = (@result ? "was #{@result.inspect}" : "raised an error #{@error_message}")
|
27
|
+
return "#{msg} meet block conditions, but it didn't. It #{was}" if @block
|
28
|
+
return "#{msg} equal #{@expected.inspect}, but it #{was}" if defined?(@expected)
|
29
|
+
|
30
|
+
"expected #{subject.inspect} to successfully parse #{input.inspect}, but it #{was}"
|
31
|
+
end
|
32
|
+
|
33
|
+
failure_message_when_negated do |subject|
|
34
|
+
msg = "expected output of parsing #{input.inspect} with #{subject.inspect} not to"
|
35
|
+
return "#{msg} meet block conditions, but it did" if @block
|
36
|
+
return "#{msg} equal #{@expected.inspect}" if defined?(@expected)
|
37
|
+
|
38
|
+
"expected #{subject.inspect} to not parse #{input.inspect}, but it did"
|
39
|
+
end
|
40
|
+
|
41
|
+
description do
|
42
|
+
return "parse #{input.inspect} with block conditions" if @block
|
43
|
+
return "parse #{input.inspect} as #{@expected.inspect}" if defined?(@expected)
|
44
|
+
|
45
|
+
"parse #{input.inspect}"
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "paco/callstack"
|
4
|
+
require "paco/index"
|
5
|
+
|
6
|
+
module Paco
|
7
|
+
class Context
|
8
|
+
attr_reader :input, :last_pos, :callstack
|
9
|
+
attr_accessor :pos
|
10
|
+
|
11
|
+
def initialize(input, pos: 0, with_callstack: false)
|
12
|
+
@input = input
|
13
|
+
@pos = pos
|
14
|
+
@callstack = Callstack.new if with_callstack
|
15
|
+
end
|
16
|
+
|
17
|
+
def read(n)
|
18
|
+
input[pos, n]
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_all
|
22
|
+
input[pos..-1]
|
23
|
+
end
|
24
|
+
|
25
|
+
def eof?
|
26
|
+
pos >= input.length
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [Integer] from
|
30
|
+
# @return [Paco::Index]
|
31
|
+
def index(from = nil)
|
32
|
+
Index.calculate(input: input, pos: from || pos)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Paco::Parser] parser
|
36
|
+
def failure_parse(parser)
|
37
|
+
@callstack&.failure(pos: pos, parser: parser.desc)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [Paco::Parser] parser
|
41
|
+
def start_parse(parser)
|
42
|
+
@callstack&.start(pos: pos, parser: parser.desc)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [Object] result
|
46
|
+
# @param [Paco::Parser] parser
|
47
|
+
def success_parse(result, parser)
|
48
|
+
@callstack&.success(pos: pos, result: result, parser: parser.desc)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/paco/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paco
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Svyatoslav Kryukov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-next-core
|
@@ -34,6 +34,13 @@ files:
|
|
34
34
|
- CHANGELOG.md
|
35
35
|
- LICENSE.txt
|
36
36
|
- README.md
|
37
|
+
- lib/.rbnext/2.3/paco/context.rb
|
38
|
+
- lib/.rbnext/2.3/paco/index.rb
|
39
|
+
- lib/.rbnext/2.3/paco/parse_error.rb
|
40
|
+
- lib/.rbnext/2.5/paco/combinators.rb
|
41
|
+
- lib/.rbnext/2.5/paco/parser.rb
|
42
|
+
- lib/.rbnext/2.5/paco/rspec/parse_matcher.rb
|
43
|
+
- lib/.rbnext/2.6/paco/context.rb
|
37
44
|
- lib/paco.rb
|
38
45
|
- lib/paco/callstack.rb
|
39
46
|
- lib/paco/combinators.rb
|
@@ -70,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
77
|
- !ruby/object:Gem::Version
|
71
78
|
version: '0'
|
72
79
|
requirements: []
|
73
|
-
rubygems_version: 3.
|
80
|
+
rubygems_version: 3.4.8
|
74
81
|
signing_key:
|
75
82
|
specification_version: 4
|
76
83
|
summary: Parser combinator library
|