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
data/lib/l43_rmap/cli.rb
ADDED
|
@@ -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
|