l43_rmap 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 +141 -0
- data/bin/rmap +12 -0
- data/lib/enumerable.rb +28 -0
- data/lib/l43_rmap/ast.rb +58 -0
- data/lib/l43_rmap/chunk.rb +8 -0
- data/lib/l43_rmap/cli/color.rb +29 -0
- data/lib/l43_rmap/cli/help.rb +89 -0
- data/lib/l43_rmap/cli.rb +108 -0
- data/lib/l43_rmap/compiler.rb +68 -0
- data/lib/l43_rmap/evaluator/evaluations.rb +23 -0
- data/lib/l43_rmap/evaluator.rb +28 -0
- data/lib/l43_rmap/function.rb +20 -0
- data/lib/l43_rmap/functions/predefined/shell.rb +21 -0
- data/lib/l43_rmap/functions/predefined.rb +55 -0
- data/lib/l43_rmap/functions.rb +67 -0
- data/lib/l43_rmap/parsing/chunk_parser.rb +171 -0
- data/lib/l43_rmap/parsing/input.rb +26 -0
- data/lib/l43_rmap/parsing/parse_state.rb +18 -0
- data/lib/l43_rmap/parsing/rgx_parser.rb +51 -0
- data/lib/l43_rmap/parsing/rgx_parsers.rb +71 -0
- data/lib/l43_rmap/predefined_patterns.rb +14 -0
- data/lib/l43_rmap/runtime/line_time.rb +58 -0
- data/lib/l43_rmap/runtime.rb +78 -0
- data/lib/l43_rmap/version.rb +6 -0
- data/lib/l43_rmap.rb +32 -0
- data/lib/peg.backup/all.rb +12 -0
- data/lib/peg.backup/combinators/implementation.rb +144 -0
- data/lib/peg.backup/combinators.rb +82 -0
- data/lib/peg.backup/parser/input.rb +36 -0
- data/lib/peg.backup/parser.rb +46 -0
- data/lib/peg.backup/parsers/advanced_parsers.rb +26 -0
- data/lib/peg.backup/parsers/base_parsers.rb +124 -0
- data/lib/peg.backup/parsers/common_parsers.rb +60 -0
- data/lib/peg.backup/parsers/true_set.rb +10 -0
- data/lib/peg.backup/parsers.rb +14 -0
- data/lib/peg.backup/result.rb +78 -0
- metadata +135 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'rgx_parser'
|
|
4
|
+
module L43Rmap
|
|
5
|
+
module Parsing
|
|
6
|
+
module RgxParsers
|
|
7
|
+
include Ast
|
|
8
|
+
|
|
9
|
+
BAREWORD = /\A[^\s\)]+/
|
|
10
|
+
BarewordParser = RgxParser.new(BAREWORD) { Bareword.new(it) }
|
|
11
|
+
|
|
12
|
+
DQUOTE = /\A"/
|
|
13
|
+
DquoteParser = RgxParser.new(DQUOTE)
|
|
14
|
+
|
|
15
|
+
ESC_DQUOTE = /\A"(")/
|
|
16
|
+
EscDquoteParser = RgxParser.new(ESC_DQUOTE)
|
|
17
|
+
|
|
18
|
+
ESC_SQUOTE = /\A'(')/
|
|
19
|
+
EscSquoteParser = RgxParser.new(ESC_SQUOTE)
|
|
20
|
+
|
|
21
|
+
ESCAPE = /\A%([%(])/
|
|
22
|
+
EscapeParser = RgxParser.new(ESCAPE) { Verb.new(it) }
|
|
23
|
+
|
|
24
|
+
INDEXED_FIELD = /\A%([-+]?\d+) ?/
|
|
25
|
+
IndexedFieldParser = RgxParser.new(INDEXED_FIELD) { IndexedField.new(it.to_i) }
|
|
26
|
+
|
|
27
|
+
NAMED_FIELD = /\A%([[:alpha:]][_[:alnum:]]*) ?/
|
|
28
|
+
NamedFieldParser = RgxParser.new(NAMED_FIELD) { NamedField.new(it) }
|
|
29
|
+
|
|
30
|
+
NAME = /\A[[:alpha:]][_[:alnum:]]*[\!\?]?/
|
|
31
|
+
NameParser = RgxParser.new(NAME) { Name.new(it) }
|
|
32
|
+
|
|
33
|
+
INT = /\A[-+]?\d+/
|
|
34
|
+
IntParser = RgxParser.new(INT) { Int.new(it.to_i) }
|
|
35
|
+
|
|
36
|
+
LPAR = /\A\(/
|
|
37
|
+
LparParser = RgxParser.new(LPAR) { :ignore }
|
|
38
|
+
|
|
39
|
+
NO_DQUOTE = /\A[^"]+/
|
|
40
|
+
NoDquoteParser = RgxParser.new(NO_DQUOTE)
|
|
41
|
+
|
|
42
|
+
NO_META = /\A[^%(]+/
|
|
43
|
+
NoMetaParser = RgxParser.new(NO_META) { Verb.new(it) }
|
|
44
|
+
|
|
45
|
+
NO_SQUOTE = /\A[^']+/
|
|
46
|
+
NoSquoteParser = RgxParser.new(NO_SQUOTE)
|
|
47
|
+
|
|
48
|
+
OP = /\A([-+*\/%])\s*/
|
|
49
|
+
OpParser = RgxParser.new(OP) { Name.new(it) }
|
|
50
|
+
|
|
51
|
+
RPAR = /\A\)/
|
|
52
|
+
RparParser = RgxParser.new(RPAR)
|
|
53
|
+
|
|
54
|
+
SQUOTE = /\A'/
|
|
55
|
+
SquoteParser = RgxParser.new(SQUOTE)
|
|
56
|
+
|
|
57
|
+
SYMBOL = /\A:([[:alpha:]][_[:alnum:]]*[\!\?]?)/
|
|
58
|
+
SymbolParser = RgxParser.new(SYMBOL) { Sym.new(it) }
|
|
59
|
+
|
|
60
|
+
WS = /\A\s+/
|
|
61
|
+
WsParser = RgxParser.new(WS)
|
|
62
|
+
|
|
63
|
+
ZEROFIELD = /\A%(?: |\z)/
|
|
64
|
+
ZerofieldParser = RgxParser.new(ZEROFIELD) { IndexedField.new(0) }
|
|
65
|
+
|
|
66
|
+
ZEROFIELD_INSIDE = /\A(%)(?: |\))/
|
|
67
|
+
ZerofieldInsideParser = RgxParser.new(ZEROFIELD_INSIDE, only_group: true) { IndexedField.new(0) }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module L43Rmap
|
|
4
|
+
module PredefinedPatterns extend self
|
|
5
|
+
|
|
6
|
+
Patterns = {
|
|
7
|
+
mv_to_ms: 'mv % (lpad %n 0 4).(ext)',
|
|
8
|
+
mv_to_mse: 'mv (se) (lpad %n 0 4).(ext)',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def get_pattern(key) = Patterns.fetch(key.to_sym)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative '../chunk'
|
|
3
|
+
require_relative '../evaluator'
|
|
4
|
+
require_relative '../function'
|
|
5
|
+
require "securerandom"
|
|
6
|
+
module L43Rmap
|
|
7
|
+
class Runtime
|
|
8
|
+
class LineTime
|
|
9
|
+
Ast = L43Rmap::Ast
|
|
10
|
+
Chunk = L43Rmap::Chunk
|
|
11
|
+
Function = L43Rmap::Function
|
|
12
|
+
|
|
13
|
+
attr_reader :line, :lnb, :rand, :runtime, :terminated, :time
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
chunks = runtime.chunks
|
|
17
|
+
# p(chunks: chunks.map(&:show))
|
|
18
|
+
loop do
|
|
19
|
+
case chunks
|
|
20
|
+
in []
|
|
21
|
+
return outstack.compact
|
|
22
|
+
in [chunk, *chunks]
|
|
23
|
+
result = chunk.(self)
|
|
24
|
+
# p(result:)
|
|
25
|
+
case result
|
|
26
|
+
when Symbol
|
|
27
|
+
return result
|
|
28
|
+
else
|
|
29
|
+
outstack.push(result)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fields = @__fields__ ||= line.split
|
|
36
|
+
|
|
37
|
+
def outstack = @__outstack__ ||= []
|
|
38
|
+
|
|
39
|
+
def terminate! = @terminated=true
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def initialize(line:, lnb:, runtime:)
|
|
43
|
+
@chunks = runtime.chunks
|
|
44
|
+
@line = line
|
|
45
|
+
@lnb = lnb
|
|
46
|
+
@runtime = runtime
|
|
47
|
+
@time = Time.new
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def terminate_runtime
|
|
51
|
+
runtime.terminate
|
|
52
|
+
return [:terminate]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require_relative 'runtime/line_time'
|
|
5
|
+
|
|
6
|
+
module L43Rmap
|
|
7
|
+
class Runtime
|
|
8
|
+
attr_reader :chunks, :current, :input_stream, :output_stream, :rand, :terminated, :time
|
|
9
|
+
|
|
10
|
+
DefaultDecRandomMax = 10_000
|
|
11
|
+
|
|
12
|
+
def run(input, output)
|
|
13
|
+
@output_stream = make_output_stream(output)
|
|
14
|
+
@input_stream = make_input_stream(input)
|
|
15
|
+
# @rand = SecureRandom.random_number
|
|
16
|
+
@time = Time.now
|
|
17
|
+
# p(time1: time.to_f)
|
|
18
|
+
|
|
19
|
+
execute!
|
|
20
|
+
rescue EOFError
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def random_hex = @__randomhex__ ||= SecureRandom.hex
|
|
24
|
+
|
|
25
|
+
def random_rd = @__randomrd__ ||= (format "%04d", SecureRandom.random_number(DefaultDecRandomMax))
|
|
26
|
+
|
|
27
|
+
def image = chunks.map(&:image).join("\n")
|
|
28
|
+
|
|
29
|
+
def terminate! = @terminated = true
|
|
30
|
+
|
|
31
|
+
def timestamp(base, mult: 1_000, now: nil)
|
|
32
|
+
# p(time2: time.to_f)
|
|
33
|
+
now ||= time
|
|
34
|
+
(now.to_f * mult).to_i.to_s(base)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def execute!
|
|
40
|
+
lnb = 0
|
|
41
|
+
loop do
|
|
42
|
+
line = input_stream.readline chomp: true
|
|
43
|
+
lnb = lnb + 1
|
|
44
|
+
@current = LineTime.new(line:, lnb:, runtime: self)
|
|
45
|
+
result = current.run
|
|
46
|
+
# p(transformed: result)
|
|
47
|
+
case result
|
|
48
|
+
in []
|
|
49
|
+
next
|
|
50
|
+
in :ignore
|
|
51
|
+
next
|
|
52
|
+
in :terminate | :term
|
|
53
|
+
break
|
|
54
|
+
in Array
|
|
55
|
+
output_stream << result.join
|
|
56
|
+
output_stream << "\n"
|
|
57
|
+
else
|
|
58
|
+
raise "BAD RETURN FROM LineTime: #{result.inspect}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def initialize(chunks)
|
|
64
|
+
@chunks = chunks
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def make_input_stream(stream)
|
|
68
|
+
return stream unless String === stream
|
|
69
|
+
File.open(stream)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def make_output_stream(stream)
|
|
73
|
+
return stream unless String === stream
|
|
74
|
+
File.open(stream, 'w')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
data/lib/l43_rmap.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Global Namespace for the Rmap language implementation
|
|
5
|
+
require_relative 'l43_rmap/parsing/chunk_parser'
|
|
6
|
+
require_relative 'l43_rmap/compiler'
|
|
7
|
+
require_relative 'l43_rmap/runtime'
|
|
8
|
+
|
|
9
|
+
module L43Rmap
|
|
10
|
+
PREDEFINED_FIELDS = [
|
|
11
|
+
"n",
|
|
12
|
+
"t",
|
|
13
|
+
"x"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
TAGS = [
|
|
17
|
+
:field,
|
|
18
|
+
:line,
|
|
19
|
+
:sexp,
|
|
20
|
+
:verb
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def compile(pattern)
|
|
25
|
+
ast = Parsing::ChunkParser.new.parse(pattern)
|
|
26
|
+
chunks = Compiler.new.compile(ast)
|
|
27
|
+
Runtime.new(chunks)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../enumerable'
|
|
4
|
+
module Peg
|
|
5
|
+
class InfiniteLoop < Exception; end
|
|
6
|
+
|
|
7
|
+
module Combinators
|
|
8
|
+
module Implementation
|
|
9
|
+
|
|
10
|
+
def _debug(parser, name: nil)
|
|
11
|
+
Parser.new(parser.name) do |input, _name|
|
|
12
|
+
puts "debugging #{name || parser.name}: #{input.inspect}"
|
|
13
|
+
result = Parser.parse(parser, input)
|
|
14
|
+
puts "debugging #{name || parser.name}: #{result}"
|
|
15
|
+
result
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def _lookahead(parsers, name:)
|
|
20
|
+
parser = _select(parsers)
|
|
21
|
+
Parser.new(name || "lookahead(#{parser.name})") do |input, name|
|
|
22
|
+
case Parser.parse(parser, input)
|
|
23
|
+
in {ok: true}
|
|
24
|
+
{ok: true, ast: nil, input:}
|
|
25
|
+
in error
|
|
26
|
+
error
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# def _map(parser, name, mapper)
|
|
32
|
+
# name ||= "map(#{parser.name})"
|
|
33
|
+
# Parser.new(name) do |input, _name|
|
|
34
|
+
# case Parser.parse(parser, input)
|
|
35
|
+
# in {ok: true, ast: ast, input: new_input}
|
|
36
|
+
# {ok: true, ast: mapper.(ast), input: new_input}
|
|
37
|
+
# in error
|
|
38
|
+
# error
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
|
|
43
|
+
def _map_result(parser, name, mapper)
|
|
44
|
+
Parser.new(name) do |input, name|
|
|
45
|
+
result = Parser.parse(parser, input)
|
|
46
|
+
# require "debug"; binding.break
|
|
47
|
+
mapper.(result)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def _many(parser, max:, min:, name:)
|
|
52
|
+
Parser.new(name) do |input, _name|
|
|
53
|
+
total_ast = []
|
|
54
|
+
original_input = input
|
|
55
|
+
current_input = input
|
|
56
|
+
match_count = 0
|
|
57
|
+
loop do
|
|
58
|
+
if current_input.empty?
|
|
59
|
+
break Result.ok(ast: total_ast, input:) if match_count >= min
|
|
60
|
+
break Result.nok(error: "many #{name} did not succeed the required #{min} times, but only #{match_count}", input: original_input, name:)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
case Parser.parse(parser, current_input)
|
|
64
|
+
in {ok: true, ast:, input:}
|
|
65
|
+
raise InfiniteLoop, "must not parse zero width inside many in parser: #{parser.name}" if input.pos == current_input.pos
|
|
66
|
+
current_input = input
|
|
67
|
+
total_ast = [*total_ast, ast]
|
|
68
|
+
match_count += 1
|
|
69
|
+
break Result.ok(ast: total_ast, input:) if max && match_count >= max
|
|
70
|
+
else
|
|
71
|
+
break Result.ok(ast: total_ast, input:) if match_count >= min
|
|
72
|
+
break Result.nok(error: "many #{name} did not succeed the required #{min} times, but only #{match_count}", input: original_input, name:)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def _satisfy(parser, name:, &satisfier)
|
|
79
|
+
Parser.new(name || "satisfy(#{parser.name})") do |input, name|
|
|
80
|
+
original_input = input
|
|
81
|
+
case Parser.parse(parser, input)
|
|
82
|
+
in {ok: false} => error
|
|
83
|
+
error
|
|
84
|
+
in {ok: true, ast:, input:} => result
|
|
85
|
+
ok = satisfier.(ast)
|
|
86
|
+
if ok == true
|
|
87
|
+
result
|
|
88
|
+
elsif ok
|
|
89
|
+
{ok: true, ast: ok, input:}
|
|
90
|
+
else
|
|
91
|
+
{ok: false, input: original_input, error: "satisfier #{name} failed", name: name}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def _select(*parsers, name: nil)
|
|
98
|
+
parsers = parsers.flatten
|
|
99
|
+
raise ArgumentError, "all parsers must be instances of Parser" unless parsers.all? { Parser === it }
|
|
100
|
+
Parser.new(name || "select #{parsers.map(&:name).join(", ")}") do |input, name|
|
|
101
|
+
result = {ok: false, error: "No parser matched in select named #{name}", input:}
|
|
102
|
+
parsers.each do |parser|
|
|
103
|
+
# case p(Parser.parse(parser, input))
|
|
104
|
+
this_result = Parser.parse(parser, input)
|
|
105
|
+
# require "debug"; binding.break
|
|
106
|
+
case this_result
|
|
107
|
+
in {ok: true} => result
|
|
108
|
+
break result
|
|
109
|
+
in _
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
result
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def _sequence(parsers, name)
|
|
118
|
+
name ||= "seq(#{parsers.map(&:name).join(", ")})"
|
|
119
|
+
Parser.new(name) do |input, _name|
|
|
120
|
+
original_input = input
|
|
121
|
+
result = parsers.reduce_while [input, []] do |(input, ast), parser|
|
|
122
|
+
# require "debug"; binding.break
|
|
123
|
+
parsed = Parser.parse(parser, input)
|
|
124
|
+
# p parsed
|
|
125
|
+
case parsed
|
|
126
|
+
in {ok: true, ast: ast_node, input:}
|
|
127
|
+
cont_reduce([input, [*ast, ast_node]])
|
|
128
|
+
in {ok: false, error:}
|
|
129
|
+
halt_reduce(Result.nok(input: original_input, error:, name: name))
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
case result
|
|
134
|
+
in {ok: false} => error
|
|
135
|
+
error
|
|
136
|
+
in [input, ast]
|
|
137
|
+
Result.ok(ast:, input:)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'parser'
|
|
4
|
+
require_relative 'combinators/implementation'
|
|
5
|
+
module Peg
|
|
6
|
+
module Combinators extend self
|
|
7
|
+
include Implementation
|
|
8
|
+
|
|
9
|
+
def lookahead(*parsers, name: nil)
|
|
10
|
+
parsers = make_parsers(*parsers)
|
|
11
|
+
_lookahead(parsers, name:)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def many(*parsers, max: nil, min: 0, name: nil)
|
|
15
|
+
name ||= "many(#{parsers.map(&:name).join(", ")})"
|
|
16
|
+
case parsers
|
|
17
|
+
in []
|
|
18
|
+
raise ArgumentError, "missing parser"
|
|
19
|
+
in [parser]
|
|
20
|
+
_many(parser, max:, min:, name:)
|
|
21
|
+
in _
|
|
22
|
+
many(select(*parsers), max:, min:, name:)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def map(parser, name: nil, &mapper)
|
|
27
|
+
raise ArgumentError, "missing mapper function" unless mapper
|
|
28
|
+
raise ArgumentError, "mapper function must have arity 1" unless mapper.arity == 1
|
|
29
|
+
name ||= "map(#{parser.name})"
|
|
30
|
+
Parser.new(name) do |input|
|
|
31
|
+
Parser.parse(parser, input).map(&mapper)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
alias_method :_map, :map
|
|
35
|
+
|
|
36
|
+
def map_result(parser, name: nil, &mapper)
|
|
37
|
+
raise ArgumentError, "missing mapper function" unless mapper
|
|
38
|
+
raise ArgumentError, "mapper function must have arity 1" unless mapper.arity == 1
|
|
39
|
+
_map_result(parser, name || "map_result(#{parser.name})", mapper)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def maybe(parser, name: nil)
|
|
43
|
+
Parser.new(name || "maybe(#{parser.name})") do |input, name|
|
|
44
|
+
case Parser.parse(parser, input)
|
|
45
|
+
in {ok: false}
|
|
46
|
+
{ok: true, ast: nil, input:}
|
|
47
|
+
in success
|
|
48
|
+
success
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def satisfy(parser, name: nil, &satisfier)
|
|
54
|
+
raise ArgumentError, "missing satisfier block" unless satisfier
|
|
55
|
+
parser = make_parser(parser)
|
|
56
|
+
_satisfy(parser, name, satisfier)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def select(*parsers, name: nil)
|
|
60
|
+
case parsers
|
|
61
|
+
in []
|
|
62
|
+
raise ArgumentError, "missing parser in select"
|
|
63
|
+
in [parser]
|
|
64
|
+
raise ArgumentError, "one parser in a selection is a NOP, remove the select call"
|
|
65
|
+
in _
|
|
66
|
+
_select(*make_parsers(parsers), name)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def sequence(*parsers, name: nil)
|
|
71
|
+
case parsers
|
|
72
|
+
in []
|
|
73
|
+
raise ArgumentError, "missing parser"
|
|
74
|
+
in [parser]
|
|
75
|
+
raise ArgumentError, "one parser in a sequence is a NOP, remove the sequence call"
|
|
76
|
+
in _
|
|
77
|
+
_sequence(make_parsers(parsers), name)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Peg
|
|
4
|
+
class Parser
|
|
5
|
+
class Input
|
|
6
|
+
attr_reader :content, :pos
|
|
7
|
+
|
|
8
|
+
def advance(by=1)
|
|
9
|
+
self.class.new(content.drop(by), pos+by)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def empty? = content.empty?
|
|
13
|
+
|
|
14
|
+
def show(count)
|
|
15
|
+
raise ArgumentError, "count must be an integer > 5" unless Integer === count && count > 5
|
|
16
|
+
return content.join("") if content.length < count
|
|
17
|
+
|
|
18
|
+
"#{content.take(count-3).join}..."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ==(other)
|
|
22
|
+
self.class === other &&
|
|
23
|
+
other.content == content &&
|
|
24
|
+
other.pos == pos
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
def initialize(content, pos=1)
|
|
29
|
+
@content = content
|
|
30
|
+
@pos = pos
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'combinators/implementation'
|
|
4
|
+
require_relative 'parser/input'
|
|
5
|
+
require_relative 'parsers'
|
|
6
|
+
require_relative 'result'
|
|
7
|
+
|
|
8
|
+
module Peg
|
|
9
|
+
class Parser
|
|
10
|
+
include Peg::Combinators::Implementation
|
|
11
|
+
|
|
12
|
+
attr_reader :parse_fn, :name
|
|
13
|
+
|
|
14
|
+
def self.parse(parser, input)
|
|
15
|
+
raise ArgumentError, "parser must be an instance of #{self}" unless self === parser
|
|
16
|
+
case input
|
|
17
|
+
when String
|
|
18
|
+
parser.parse_fn.(Peg::Parser::Input.new(input.grapheme_clusters))
|
|
19
|
+
when Peg::Parser::Input
|
|
20
|
+
parser.parse_fn.(input)
|
|
21
|
+
else
|
|
22
|
+
raise ArgumentError, "input must be a string or instance of Input" unless self.class === parser
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def and(*parsers, name: nil) = _sequence([self, *parsers], name)
|
|
27
|
+
|
|
28
|
+
def debug(name: nil) = _debug(self, name:)
|
|
29
|
+
|
|
30
|
+
def many(name: nil, max: nil, min: 0) = _many(self, name:, max:, min:)
|
|
31
|
+
def map(name: nil, &blk) = Peg::Combinators.map(self, name:, &blk)
|
|
32
|
+
|
|
33
|
+
def or(*parsers, name: nil) = _select(self, *parsers, name:)
|
|
34
|
+
|
|
35
|
+
def satisfy(name: nil, &satisfier) = _satisfy(self, name:, &satisfier)
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
def initialize(name, &blk)
|
|
39
|
+
raise ArgumentError, "blk must be provided as a parse function" unless blk
|
|
40
|
+
@name = name
|
|
41
|
+
@parse_fn = blk
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../combinators'
|
|
4
|
+
require_relative '../parsers'
|
|
5
|
+
module Peg
|
|
6
|
+
module Parsers
|
|
7
|
+
module AdvancedParsers
|
|
8
|
+
include Peg::Combinators
|
|
9
|
+
include Peg::Parsers
|
|
10
|
+
|
|
11
|
+
def list_parser(element_parser:, seperator_parser:, name: "list parser")
|
|
12
|
+
sequence(
|
|
13
|
+
element_parser,
|
|
14
|
+
many(
|
|
15
|
+
sequence(
|
|
16
|
+
seperator_parser, element_parser
|
|
17
|
+
)
|
|
18
|
+
.map { it[1] }
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
.map { it.flatten.compact }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|