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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/LiCenSE +235 -0
  3. data/README.md +141 -0
  4. data/bin/rmap +12 -0
  5. data/lib/enumerable.rb +28 -0
  6. data/lib/l43_rmap/ast.rb +58 -0
  7. data/lib/l43_rmap/chunk.rb +8 -0
  8. data/lib/l43_rmap/cli/color.rb +29 -0
  9. data/lib/l43_rmap/cli/help.rb +89 -0
  10. data/lib/l43_rmap/cli.rb +108 -0
  11. data/lib/l43_rmap/compiler.rb +68 -0
  12. data/lib/l43_rmap/evaluator/evaluations.rb +23 -0
  13. data/lib/l43_rmap/evaluator.rb +28 -0
  14. data/lib/l43_rmap/function.rb +20 -0
  15. data/lib/l43_rmap/functions/predefined/shell.rb +21 -0
  16. data/lib/l43_rmap/functions/predefined.rb +55 -0
  17. data/lib/l43_rmap/functions.rb +67 -0
  18. data/lib/l43_rmap/parsing/chunk_parser.rb +171 -0
  19. data/lib/l43_rmap/parsing/input.rb +26 -0
  20. data/lib/l43_rmap/parsing/parse_state.rb +18 -0
  21. data/lib/l43_rmap/parsing/rgx_parser.rb +51 -0
  22. data/lib/l43_rmap/parsing/rgx_parsers.rb +71 -0
  23. data/lib/l43_rmap/predefined_patterns.rb +14 -0
  24. data/lib/l43_rmap/runtime/line_time.rb +58 -0
  25. data/lib/l43_rmap/runtime.rb +78 -0
  26. data/lib/l43_rmap/version.rb +6 -0
  27. data/lib/l43_rmap.rb +32 -0
  28. data/lib/peg.backup/all.rb +12 -0
  29. data/lib/peg.backup/combinators/implementation.rb +144 -0
  30. data/lib/peg.backup/combinators.rb +82 -0
  31. data/lib/peg.backup/parser/input.rb +36 -0
  32. data/lib/peg.backup/parser.rb +46 -0
  33. data/lib/peg.backup/parsers/advanced_parsers.rb +26 -0
  34. data/lib/peg.backup/parsers/base_parsers.rb +124 -0
  35. data/lib/peg.backup/parsers/common_parsers.rb +60 -0
  36. data/lib/peg.backup/parsers/true_set.rb +10 -0
  37. data/lib/peg.backup/parsers.rb +14 -0
  38. data/lib/peg.backup/result.rb +78 -0
  39. metadata +135 -0
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'l43/opt_parser'
4
+ require 'l43/simple_color'
5
+ require_relative '../l43_rmap'
6
+ require_relative 'predefined_patterns'
7
+ require_relative 'cli/color'
8
+ require_relative 'cli/help'
9
+ module L43Rmap
10
+ class Cli
11
+ include L43::SimpleColor
12
+ include Color
13
+ include Help
14
+
15
+ attr_reader :args, :kwds, :pattern, :runtime
16
+
17
+ def run(args)
18
+ parser.parse(args) => :ok, {kwds:, args:}
19
+ @kwds = kwds
20
+ @args = args
21
+ return help if kwds.help
22
+ return help_pattern if kwds.help_pattern
23
+ return help_named if kwds.help_named
24
+ return help_predefined if kwds.help_predefined
25
+ return help_sexp if kwds.help_sexp
26
+
27
+ return unless check_args
28
+
29
+ return dry_run if kwds.dry_run
30
+ replace_pattern_if_predefined
31
+ return show_parsed if kwds.parse_only
32
+
33
+ _compile
34
+ return compiled if kwds.compile_only
35
+
36
+ _run
37
+ end
38
+
39
+
40
+ private
41
+ def initialize
42
+ parser
43
+ .usage("rmap", options: true, args: 'pattern')
44
+ parser
45
+ .section("Flags")
46
+ .flag(:compile_only, :c, desc: ["Only compile ", :bold, :blue, "pattern"])
47
+ .flag(:parse_only, :p, desc: ["Only parse ", :bold, :blue, "pattern"])
48
+ .flag(:dry_run, :d, desc: "Only parse args")
49
+ .flag(:help_named, desc: ["Describe all ", :bold, :blue, "named fields"], stop: true)
50
+ .flag(:help_pattern, desc: ["Describe ", :bold, :blue, "pattern"], stop: true)
51
+ .flag(:help_predefined, desc: ["Describe ", :bold, :blue, "predefined patterns"], stop: true)
52
+ .flag(:help_sexp, desc: ["Describe ", :bold, :blue, "s-expressions"], stop: true)
53
+ .section("Options")
54
+ .keyword(:input, :i, desc: ["file to read from, defaults to ", :bold, :cyan, '$stdin'], default: $stdin)
55
+ .keyword(:output, :o, desc: ["file to write to, defaults to ", :bold, :cyan, '$stdout'], default: $stdout)
56
+ .keyword(:pattern, desc: ["use a predefined pattern instead of positional argument. ", :bold, "Conflicts with ", :blue, "pattern"], default: nil)
57
+
58
+ end
59
+
60
+ def _compile
61
+ @runtime = L43Rmap.compile(pattern)
62
+ end
63
+
64
+ def _run
65
+ untime.run(kwds.input, kwds.output)
66
+ :ok
67
+ end
68
+
69
+ def check_args
70
+ args in [pattern]
71
+ @pattern = pattern
72
+ return check_pattern if pattern
73
+ return true if kwds.pattern
74
+ puterr("need exactly one positional arg as ", :blue, "pattern", :reset, nil, "but got: ", :yellow, :bold, args.inspect)
75
+ puthint
76
+ end
77
+
78
+ def check_pattern
79
+ return true unless kwds.pattern
80
+ puterr("must not have positonal arg as ", :blue, :bold, "pattern", :white, " and", kwd("pattern"))
81
+ puthint
82
+ end
83
+
84
+ def compiled
85
+ putcol(:magenta, runtime.image)
86
+ :ok
87
+ end
88
+
89
+ def dry_run
90
+ putcol(:cyan, :bold, {kwds: kwds.to_h}.inspect)
91
+ putcol(:blue, :bold, {args:}.inspect)
92
+ :ok
93
+ end
94
+ def replace_pattern_if_predefined
95
+ return unless kwds.pattern
96
+ @pattern = PredefinedPatterns.get_pattern(kwds.pattern)
97
+ end
98
+
99
+ def show_parsed
100
+ ast = Parsing::ChunkParser.new.parse(pattern)
101
+
102
+ p ast.map(&:image)
103
+ end
104
+
105
+ def parser = @__parser__ ||= L43::OptParser.new(with_help: true)
106
+ end
107
+ end
108
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ast'
4
+ require_relative 'evaluator'
5
+ require_relative 'function'
6
+ require_relative 'functions'
7
+ module L43Rmap
8
+ class Compiler
9
+ Function = L43Rmap::Function
10
+ Functions = L43Rmap::Functions
11
+
12
+ def compile(ast) = ast.map { compile_chunk it }
13
+
14
+ private
15
+ def compile_chunk(chunk)
16
+ # p(compile_chunk: chunk.to_h)
17
+ case chunk
18
+ in Ast::Verb | Ast::Int
19
+ chunk
20
+ in {type: :named_field, value:}
21
+ compile_function(value)
22
+ in {type: :indexed_field, value:}
23
+ compile_field(value)
24
+ in {type: :sexp, value:}
25
+ compile_sexp(value)
26
+ in {type: :str, value:}
27
+ compile_str(value)
28
+ in {type: :sym, value:}
29
+ compile_sym(value)
30
+ end
31
+ end
32
+
33
+ def compile_field(value)
34
+ return compile_line if value.zero?
35
+ return compile_subfield value if value < 0
36
+ compile_subfield value.pred
37
+ end
38
+
39
+ def compile_function(name)
40
+ Functions.get_field(name)
41
+ # TODO: Fallbacks
42
+ # -> (rt, *a, **k) { rt.opstack.push(rt.opstack.pop.send(rt, *a, **k)) }
43
+ end
44
+
45
+ def compile_line
46
+ Function.new("line") { |rt| rt.line }
47
+ end
48
+
49
+ def compile_sexp(value)
50
+ value => head, *tail
51
+ compiled_head = Functions.get_function(head.value)
52
+ compiled_args = compile_sexp_args(tail)
53
+ # p(compiled_args:)
54
+ Evaluator.new(compiled_head, compiled_args)
55
+ end
56
+
57
+
58
+ def compile_sexp_args(tail) = tail.map { compile_chunk it }
59
+
60
+ def compile_str(value) = Function.new("str('#{value}')") { |*| value }
61
+
62
+ def compile_sym(value) = Function.new("sym('#{value}')") { |*| value.to_sym }
63
+
64
+ def compile_subfield(value) = Function.new("subfield #{value}") { |rt| rt.fields[value] }
65
+
66
+ end
67
+ end
68
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../ast'
4
+ require_relative '../function'
5
+ module L43Rmap
6
+ class Evaluator
7
+ module Evaluations extend self
8
+ def evaluate(chunk, rt:) = _eval(chunk, rt:)
9
+
10
+ private
11
+ def _eval(chunk, rt:)
12
+ # p(chunk:)
13
+ case chunk
14
+ in Function
15
+ chunk.(rt)
16
+ else
17
+ chunk.value
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'evaluator/evaluations'
4
+ module L43Rmap
5
+ class Evaluator
6
+
7
+ attr_reader :args, :fun
8
+
9
+ def call(rt)
10
+ #TODO: extract evaluate
11
+ real_args = args.map do |arg| arg.(rt) end
12
+
13
+ # p(real_args:)
14
+ fun.(rt, *real_args)
15
+ end
16
+
17
+ def image = "Evaluator #{fun.name}"
18
+
19
+ private
20
+ def initialize(fun, args)
21
+ @args = args
22
+ @fun = fun
23
+ end
24
+
25
+
26
+ end
27
+ end
28
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Rmap
4
+ class Function
5
+
6
+ attr_reader :name, :function
7
+
8
+ def call(rt, *a, **k) = function.(rt, *a, **k)
9
+
10
+ def image = "Function #{name}"
11
+
12
+ private
13
+ def initialize(name, &function)
14
+ @name = name
15
+ @function = function
16
+ end
17
+
18
+ end
19
+ end
20
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Rmap
4
+ module Functions
5
+ module Predefined
6
+ module Shell extend self
7
+ def se(rt, *args)
8
+ case args
9
+ in []
10
+ rt.line.shellescape
11
+ in [fn]
12
+ fn.shellescape
13
+ else
14
+ raise ArgumentError, "se needs at most 1 argument, but got: #{args.inspect}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../evaluator/evaluations'
4
+ require_relative 'predefined/shell'
5
+
6
+ module L43Rmap
7
+ module Functions
8
+ module Predefined
9
+ extend L43Rmap::Evaluator::Evaluations
10
+ class << self
11
+ def if_fn(_rt, cond, true_branch, false_branch=nil)
12
+ if cond
13
+ true_branch
14
+ else
15
+ false_branch
16
+ end
17
+ end
18
+
19
+ def inc(_rt, *args)
20
+ case args
21
+ in []
22
+ raise ArgumentError, "inc needs at least 1 argument" if args.empty?
23
+ in [arg]
24
+ arg.to_i.succ
25
+ else
26
+ args.map(&:to_i).sum
27
+ end
28
+ end
29
+
30
+ def file_extension(rt, *args)
31
+ case args
32
+ in []
33
+ File.extname(rt.line)[1..]
34
+ in [fn]
35
+ File.extname(fn.to_s)[1..]
36
+ else
37
+ raise ArgumentError, "ext needs at most 1 argument"
38
+ end
39
+ end
40
+
41
+ def lpad(_rt, subject, filler, length)
42
+ subject.to_s.rjust(length.to_i, filler.to_s)
43
+ end
44
+
45
+ def match(rt, rgx, subject=nil)
46
+ subject ||= rt.line
47
+ r = Regexp.compile(rgx)
48
+ r.match(subject)
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
55
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative 'function'
5
+ require_relative 'functions/predefined'
6
+
7
+ module L43Rmap
8
+ module Functions
9
+
10
+
11
+ DefinedFields = {
12
+ # Linenumber
13
+ "n" => Function.new("lnb") { |rt| rt.lnb },
14
+
15
+ # Timestamps
16
+ "m" => Function.new("timestamp m") { |rt| rt.runtime.timestamp(36) },
17
+ "ms" => Function.new("timestamp ms") { |rt| rt.runtime.timestamp(36, mult: 1) },
18
+ "M" => Function.new("timestamp M") { |rt| rt.runtime.timestamp(36, now: rt.time) },
19
+ "Ms" => Function.new("timestamp Ms") { |rt| rt.runtime.timestamp(36, now: rt.time, mult: 1) },
20
+ "t" => Function.new("timestamp X") { |rt| rt.runtime.timestamp(10) },
21
+ "ts" => Function.new("timestamp Xs") { |rt| rt.runtime.timestamp(10, mult: 1) },
22
+ "T" => Function.new("timestamp X") { |rt| rt.runtime.timestamp(10, now: rt.time) },
23
+ "Ts" => Function.new("timestamp Xs") { |rt| rt.runtime.timestamp(10, now: rt.time, mult: 1) },
24
+ "x" => Function.new("timestamp X") { |rt| rt.runtime.timestamp(16) },
25
+ "xs" => Function.new("timestamp Xs") { |rt| rt.runtime.timestamp(16, mult: 1) },
26
+ "X" => Function.new("timestamp X") { |rt| rt.runtime.timestamp(16, now: rt.time) },
27
+ "Xs" => Function.new("timestamp Xs") { |rt| rt.runtime.timestamp(16, now: rt.time, mult: 1) },
28
+
29
+ # Randomness
30
+ "rd" => Function.new("random rd") { |rt| rt.runtime.random_rd },
31
+ "rx" => Function.new("random rx") { |rt| rt.runtime.random_hex },
32
+ "Rd" => Function.new("random Rd") { |_rt| format "%04d", SecureRandom.random_number(10_000) },
33
+ "Rx" => Function.new("random Rx") { |_rt| SecureRandom.hex },
34
+ }
35
+
36
+ # Names only available inside of S-Expressions
37
+ DefinedFunctions = {
38
+ "ext" => Function.new("ext") { |*args| Predefined.file_extension(*args) },
39
+ "inc" => Function.new("inc") { |*args| Predefined.inc(*args) },
40
+ "if" => Function.new("if") { |*args| Predefined.if_fn(*args) },
41
+ "lpad" => Function.new("lpad") { |*args| Predefined.lpad(*args) },
42
+ "m" => Function.new("match") { |*args| Predefined.match(*args) },
43
+ "se" => Function.new("se") { |*args| Predefined::Shell.se(*args) },
44
+ }
45
+
46
+
47
+ AllFunctions = DefinedFields.merge(DefinedFunctions)
48
+
49
+
50
+ class << self
51
+ def get_field(name)
52
+ DefinedFields.fetch(name)
53
+ end
54
+
55
+ def get_function(name)
56
+ AllFunctions.fetch(name) do
57
+ Function.new("fallback to #{name}") do |rt, rcv, *args|
58
+ rcv.send(name, *args)
59
+ rescue NoMethodError
60
+ rcv.to_i.send(name, *args)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal true
2
+
3
+ require_relative '../ast'
4
+ require_relative 'input'
5
+ require_relative 'parse_state'
6
+ require_relative 'rgx_parsers'
7
+ require 'l43/simple_color'
8
+
9
+ # class MatchData
10
+ # def deconstruct_keys(*a)
11
+ # require "debug"; binding.break
12
+ # [self, $&.length]
13
+ # end
14
+ # end
15
+
16
+ module L43Rmap
17
+ module Parsing
18
+ class ChunkParser
19
+ include Ast
20
+ include L43::SimpleColor
21
+ include RgxParsers
22
+
23
+ SyntaxError = Class.new(RuntimeError)
24
+
25
+ def parse(input, pos: 1)
26
+ input = Input.new(current: input, pos:)
27
+ ast = []
28
+ loop do
29
+ # dbg("MAIN LOOP>>>", ast: ast.map(&:to_h), input: input.current)
30
+ return ast if input.empty?
31
+ result = parse_chunk(input)
32
+ result => chunk, input
33
+ # dbg("MAIN LOOP <<", ast: ast.map(&:to_h), chunk: chunk.to_h, input: input.current)
34
+ ast = unify_verb(ast << chunk)
35
+ # dbg("MAIN LOOP<<<", ast: ast.map(&:to_h), input: input.current)
36
+ end
37
+ end
38
+
39
+ def parse_chunk(input)
40
+ # p(input:)
41
+ if chunk = ZerofieldParser.parse(input)
42
+ chunk
43
+ elsif chunk = IndexedFieldParser.parse(input)
44
+ # dbg("FieldParser", chunk:)
45
+ chunk
46
+ elsif chunk = NoMetaParser.parse(input)
47
+ chunk
48
+ elsif chunk = EscapeParser.parse(input)
49
+ chunk
50
+ elsif chunk = NamedFieldParser.parse(input)
51
+ chunk
52
+ elsif LparParser.parse(input) in _, input
53
+ result = parse_sexp(input)
54
+ # p(result)
55
+ # dbg("MAIN <sexp", ast: result.first)
56
+ result
57
+ else
58
+ raise SyntaxError, input.to_h.inspect
59
+ end
60
+ end
61
+
62
+ private
63
+ def parse_dquoted_str(input, ast = String.new)
64
+ return nil unless DquoteParser.parse(input) in _, input
65
+
66
+ loop do
67
+ return if input.empty?
68
+
69
+ if EscDquoteParser.parse(input) in _, input
70
+ ast << '"'
71
+ elsif DquoteParser.parse(input) in _, input
72
+ return [Str.new(ast), input]
73
+ end
74
+
75
+ if NoDquoteParser.parse(input) in str, input
76
+ ast << str
77
+ end
78
+ end
79
+ end
80
+
81
+ def parse_squoted_str(input, ast = String.new)
82
+ return nil unless SquoteParser.parse(input) in _, input
83
+
84
+
85
+ loop do
86
+ return if input.empty?
87
+
88
+ if EscSquoteParser.parse(input) in _, input
89
+ ast << "'"
90
+ elsif SquoteParser.parse(input) in _, input
91
+ return [Str.new(ast), input]
92
+ end
93
+
94
+ if NoSquoteParser.parse(input) in str, input
95
+ ast << str
96
+ end
97
+ end
98
+ end
99
+
100
+ def parse_sexp(input, level: 1)
101
+ # p(level:)
102
+ ast =[]
103
+ # dbg('SEXP >>>', level:, ast: ast.map(&:to_h), input: input.current)
104
+ stack << input.pos
105
+ loop do
106
+ # p(ast: ast.map(&:to_h), input: input.current)
107
+ if RparParser.parse(input) in _, input
108
+ stack.pop
109
+ # dbg('SEXP <<<', level:, s_exp: ast.map(&:value), stack:)
110
+ result = [Sexp.new(ast), input]
111
+ # p(level:, result:)
112
+ return result
113
+ # return [sexp(ast), input]
114
+ elsif LparParser.parse(input) in _, input
115
+ parse_sexp(input, level: level.succ) => result, input
116
+ # dbg('SEXP >>', level:, s_exp: result.value, stack:)
117
+ ast << result
118
+ # dbg('PUSHING', ast:)
119
+ elsif WsParser.parse(input) in _, input
120
+ nil
121
+ elsif IntParser.parse(input) in int, input
122
+ ast << int
123
+ elsif ZerofieldInsideParser.parse(input) in field, input
124
+ # p(ast: ast.map(&:to_h), field: field.to_h, input: input.to_h)
125
+ ast << field
126
+ elsif IndexedFieldParser.parse(input) in field, input
127
+ ast << field
128
+ elsif NamedFieldParser.parse(input) in fn, input
129
+ ast << fn
130
+ elsif OpParser.parse(input) in op, input
131
+ ast << op
132
+ elsif NameParser.parse(input) in name, input
133
+ # p(ast: ast.map(&:to_h))
134
+ # puts input.show
135
+ ast << name
136
+ elsif parse_dquoted_str(input) in str, input
137
+ ast << str
138
+ elsif parse_squoted_str(input) in str, input
139
+ ast << str
140
+ elsif SymbolParser.parse(input) in bw, input
141
+ ast << bw
142
+ elsif BarewordParser.parse(input) in bw, input
143
+ ast << bw
144
+ else
145
+ raise SyntaxError, "bad S-EXP: #{input.show}"
146
+ end
147
+ end
148
+ end
149
+
150
+ def unify_verb(ast)
151
+ case ast
152
+ in [*head, {type: :verb, value: left}, {type: :verb, value: right}]
153
+ [*head, Verb.new(left + right)]
154
+ else
155
+ ast
156
+ end
157
+ end
158
+
159
+ #
160
+ # Memos and temporary code
161
+ # ========================
162
+
163
+ def stack = @__stack__ ||= []
164
+
165
+ def dbg(label, **vals)
166
+ putcol(:green__b, format("%-10s", label), :r, :magenta, " #{vals.inspect}")
167
+ end
168
+ end
169
+ end
170
+ end
171
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'l43/open_object'
4
+ module L43Rmap
5
+ module Parsing
6
+ class Input
7
+ extend L43::OpenObject
8
+ attributes :current, pos: 1, parsed: String.new do |atts|
9
+ {current: current.dup}
10
+ end
11
+
12
+ def advance(by=1)
13
+ by = by.length if String === by
14
+ update(
15
+ current: current.slice(by..) || '',
16
+ parsed: parsed << current.slice(0...by),
17
+ pos: pos + by)
18
+ end
19
+
20
+ def empty? = current == ''
21
+
22
+ def show(n=10) = [current[0...n].inspect, pos].join(" at position:")
23
+ end
24
+ end
25
+ end
26
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'l43/open_object'
4
+ module L43Rmap
5
+ module Parsing
6
+ class ParseState
7
+ extend L43::OpenObject
8
+ attributes :input, ast: []
9
+
10
+ def deconstruct(*) = [ast, input]
11
+
12
+ def show
13
+ $stderr.puts({ast: ast.map(&:to_h), input: input.current}.inspect)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parse_state'
4
+ class MatchData
5
+ def deconstruct(*)
6
+ [to_s, *captures]
7
+ end
8
+ end
9
+
10
+ module L43Rmap
11
+ module Parsing
12
+ class RgxParser
13
+ BadRgx = Class.new(RuntimeError)
14
+ attr_reader :blk, :only_group, :rgx
15
+
16
+ def parse(input)
17
+ match = rgx.match(input.current)
18
+ # p(rgx:, input:, match:) if ENV['DEBUG']
19
+ # p(match:) if match
20
+ case match
21
+ in nil
22
+ nil
23
+ in [m]
24
+ ParseState.new(ast: postprocess(m), input: advance(input, m, m))
25
+ in [m, a]
26
+ ParseState.new(ast: postprocess(a), input: advance(input, a, m))
27
+ in [_, *rest]
28
+ raise BadRgx, "too many captures; max = 1, had #{rest.count}"
29
+ end
30
+ end
31
+
32
+ private
33
+ def initialize(rgx, only_group: false, &blk)
34
+ @blk = blk
35
+ @only_group = only_group
36
+ @rgx = rgx
37
+ end
38
+
39
+ def advance(input, a, m)
40
+ if only_group
41
+ input.advance(a)
42
+ else
43
+ input.advance(m)
44
+ end
45
+ end
46
+
47
+ def postprocess(a) = blk ? blk.(a) : a
48
+ end
49
+ end
50
+ end
51
+ # SPDX-License-Identifier: AGPL-3.0-or-later