l43_peg 0.0.2 → 0.1.1

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.
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ module OpenObject
5
+ def attributes(*atts, **defaults)
6
+ attr_reader(*atts)
7
+ attr_reader(*defaults.keys)
8
+
9
+ define_method :== do |other|
10
+ other&.to_h == to_h
11
+ end
12
+
13
+ define_method :initialize do |**kwds|
14
+ missing = atts - kwds.keys
15
+ raise ArgumentError, "missing required keyword parameters: #{missing.inspect}" unless missing.empty?
16
+ spurious = kwds.keys - atts - defaults.keys
17
+ raise ArgumentError, "spurious keyword parameters: #{spurious.inspect}" unless spurious.empty?
18
+
19
+ values = defaults.merge(kwds)
20
+ values.each do |key, value|
21
+ instance_variable_set("@#{key}", value)
22
+ end
23
+ _init if respond_to?(:_init)
24
+ end
25
+
26
+ define_method :to_h do |*|
27
+ first = atts.inject Hash.new do |h, attribute|
28
+ h.update(attribute => send(attribute))
29
+ end
30
+ defaults.keys.inject first do |h, attribute|
31
+ h.update(attribute => send(attribute))
32
+ end
33
+ end
34
+ alias_method :deconstruct_keys, :to_h
35
+ end
36
+ end
37
+ end
38
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Object
4
+ def require_subdir(path=nil, &blk)
5
+ Dir.glob(File.join([File.dirname(blk.source_location.first), path, '/**/*.rb'].compact)).each { require _1}
6
+ end
7
+ end
8
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Cache
5
+ extend L43::OpenObject
6
+ attributes cache: {}
7
+ end
8
+ end
9
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../helper"
4
+ module L43Peg
5
+ module Combinators
6
+ module Many extend self
7
+
8
+ include L43Peg::Helper
9
+ def many(input:, cache:, name:, parser:, min:, max:)
10
+ curr_input = input
11
+ curr_cache = cache
12
+ count = 0
13
+ ast = []
14
+
15
+ loop do
16
+ if max && count == max
17
+ return succeed_parser(ast, curr_input, cache: curr_cache)
18
+ end
19
+ case parser.(curr_input, cache: curr_cache)
20
+ in L43Peg::Success => success
21
+ ast.push(success.ast)
22
+ curr_input = success.rest
23
+ curr_cache = success.cache
24
+ count += 1
25
+ in L43Peg::Failure
26
+ if count < min
27
+ return fail_parser("many #{name} should match at least #{min} times but did only #{count} times")
28
+ end
29
+ return succeed_parser(ast, curr_input, cache: curr_cache)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
38
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../helper"
4
+ module L43Peg
5
+ module Combinators
6
+ module Seq extend self
7
+
8
+ include L43Peg::Helper
9
+ def seq(input:, cache:, name:, parsers:)
10
+ curr_input = input
11
+ curr_cache = cache
12
+ ast = []
13
+ parsers.each do |parser|
14
+ case parser.(curr_input, cache: curr_cache)
15
+ in L43Peg::Failure => failure
16
+ return L43Peg::Failure.new(reason: "#{failure.reason} in (#{name})", position: failure.position, input:)
17
+ in L43Peg::Success => success
18
+ ast.push(success.ast)
19
+ curr_input = success.rest
20
+ curr_cache = success.cache
21
+ end
22
+ end
23
+ succeed_parser(ast, curr_input, cache: curr_cache)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require_relative 'mappers'
5
+ require_relative 'parser'
6
+ require_relative 'parsers'
7
+ require_subdir('combinators') {}
8
+
9
+ module L43Peg
10
+ module Combinators extend self
11
+
12
+ include L43Peg::Helper
13
+ include L43Peg::Mappers
14
+ include L43Peg::Parsers
15
+
16
+ def args_parser(definitions, name: nil, &block)
17
+ parser = tokens_parser(definitions, &block)
18
+ name = name || "args_parser(#{definitions.inspect})"
19
+ inner = many(parser)
20
+ map(inner, name:, fn: join_maps)
21
+ end
22
+
23
+ def many(parser, name: nil, min: 0, max: nil)
24
+ Parser.new(name || "many(#{parser.name})") {|input, cache, name1=nil| Many.many(input:, cache:, name: name1 || name, parser:, min:, max:)}
25
+ end
26
+
27
+ def map(parser, name: nil, fn: nil, &mapper)
28
+ raise ArgumentError, "must not provide keyword parameyer fn and a block" if fn && mapper
29
+ mapper = fn || mapper
30
+ raise ArgumentError, "must provide keyword parameyer fn or a block" unless mapper
31
+ Parser.new(name || "map(#{parser.name})") {|input, cache, name=nil| _map(input:, cache:, name:, parser:, mapper:)}
32
+ end
33
+
34
+ def seq(*parsers, name: nil)
35
+ name ||= "seq(#{parsers.map(&:name).join(", ")})"
36
+ Parser.new(name) {|input, cache, _name=nil| Seq.seq(input:, cache:, name:, parsers:)}
37
+ end
38
+
39
+ private
40
+
41
+ def _map(input:, cache:, name:, parser:, mapper:)
42
+ case parser.(input, cache:)
43
+ in L43Peg::Failure => failure
44
+ failure
45
+ in L43Peg::Success => success
46
+ success.map(&mapper)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Failure
5
+ extend L43::OpenObject
6
+ attributes cache: nil, input: nil, parsed_by: nil, position: [1, 1], reason: ""
7
+ end
8
+ end
9
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Helper
5
+ def fail_parser(reason)
6
+ L43Peg::Failure.new(reason:)
7
+ end
8
+ def succeed_parser(ast, input, cache: nil)
9
+ L43Peg::Success.new(ast:, rest: input, cache: cache || input.cache)
10
+ end
11
+ end
12
+ end
13
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Input
5
+ extend L43::OpenObject
6
+
7
+ attributes col: 1, input: "", lnb: 1, context: {}
8
+
9
+ def drop(by=nil)
10
+ case by
11
+ when nil
12
+ _drop_by_n(1)
13
+ when String
14
+ _drop_by_n(by.length)
15
+ else
16
+ _drop_by_n(by)
17
+ end
18
+ end
19
+
20
+ def empty? = input.empty?
21
+
22
+ def position = [@col, @lnb]
23
+
24
+ private
25
+
26
+ def _drop_by_n(n)
27
+ return self if input.empty?
28
+ _split(n) => col, lnb, head, tail
29
+ self.class.new(input: tail, context:, col:, lnb:)
30
+ end
31
+
32
+ # Very inefficent but sufficent for my use cases so far
33
+ # and convenient for regex parsers
34
+ def _split(n)
35
+ head = input.slice(0...n)
36
+ lines = head.count("\n")
37
+ tail = input.slice(n..-1) || ''
38
+ new_col =
39
+ if lines.zero?
40
+ col + n
41
+ else
42
+ 1 + head.sub(%r{\A.*\n}m, "").length
43
+ end
44
+ [new_col, lnb + lines, head, tail]
45
+ end
46
+ end
47
+ end
48
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Mappers
5
+
6
+ def join_and_to_i
7
+ -> list do
8
+ list.join.to_i
9
+ end
10
+ end
11
+
12
+ def join_maps
13
+ -> maps do
14
+ maps.reduce Hash.new do |map, entry|
15
+ map.merge(entry) { |*args| args.drop(1).flatten }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Parser
5
+
6
+ attr_reader :fn, :name
7
+
8
+ def call(input, cache: L43Peg::Cache.new) = fn.(input, cache, name)
9
+
10
+ private
11
+
12
+ def initialize(name, &fn)
13
+ @name = name
14
+ @fn = fn
15
+ end
16
+
17
+ end
18
+ end
19
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class CharParser < L43Peg::Parser
6
+
7
+ private
8
+
9
+ def initialize(charset=nil)
10
+ charset = _mk_set(charset)
11
+ super("char_parser(#{charset})") do |input, cache, name|
12
+ if input.input.empty?
13
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "cannot parse at end of input #{name}")
14
+ elsif charset.nil? || charset.member?(input.input[0])
15
+ L43Peg::Success.new(ast: input.input[0], cache:, rest: input.drop, position: input.position)
16
+ else
17
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "char #{input.input[0].inspect}")
18
+ end
19
+ end
20
+ end
21
+
22
+ def _mk_set(charset)
23
+ return unless charset
24
+ case charset
25
+ when String
26
+ Set.new(charset.split(""))
27
+ when Set
28
+ charset
29
+ else
30
+ Set.new(charset)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../parsers'
4
+
5
+ module L43Peg
6
+ module Parsers
7
+ class EndParser < L43Peg::Parser
8
+
9
+ def self.instance
10
+ @__instance__ ||= new
11
+ end
12
+
13
+ private
14
+
15
+ def initialize
16
+ super('end_parser') do |input, cache, _name=nil|
17
+ if input.input.empty?
18
+ L43Peg::Success.new(cache:, rest: input, position: input.position)
19
+ else
20
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "not at end of input")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class FailureParser < L43Peg::Parser
6
+
7
+ def self.instance
8
+ @__instance__ ||= new
9
+ end
10
+
11
+ private
12
+
13
+ def initialize
14
+ super('failure_parser') do |input, cache, _name=nil|
15
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "this parser always fails")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../parsers'
4
+
5
+ module L43Peg
6
+ module Parsers
7
+ class IntParser < L43Peg::Parser
8
+ extend L43Peg::Combinators
9
+ extend L43Peg::Mappers
10
+
11
+ class << self
12
+ def instance
13
+ @__instance__ ||= _mk_int_parser
14
+ end
15
+
16
+ private
17
+
18
+ def _mk_int_parser
19
+ map(seq(many(Parsers.char_parser("+-"), max: 1), many(Parsers.char_parser("0".."9"), min: 1), name: "int_parser"), &join_and_to_i)
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class RgxParser < L43Peg::Parser
6
+
7
+ private
8
+
9
+ def initialize(rgx, name: nil, **options)
10
+ name = name || "rgx_parser(#{rgx.inspect})"
11
+ rgx = _mk_rgx(rgx)
12
+ super(name) do |input, cache, _name|
13
+ case rgx.match(input.input)
14
+ in MatchData => md
15
+ ast = _from_match(md, options)
16
+ L43Peg::Success.new(ast:, cache:, rest: input.drop(md[0]), position: input.position)
17
+ else
18
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "input does not match #{rgx.inspect} (in #{name})")
19
+ end
20
+ end
21
+ end
22
+
23
+ def _from_match(md, options)
24
+ case options[:capture]
25
+ in NilClass
26
+ md[1] || md[0]
27
+ in Integer => n
28
+ md[n]
29
+ in :all
30
+ md.to_a
31
+ end
32
+ end
33
+
34
+ def _mk_rgx(rgx)
35
+ case rgx
36
+ when String
37
+ Regexp.compile("\\A#{rgx}")
38
+ else
39
+ rgx
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class TokenParser < L43Peg::Parser
6
+
7
+ private
8
+
9
+ def initialize(token_spec, name: nil, **options)
10
+ _check_arg!(token_spec)
11
+ name = name || "token_parser(#{token_spec.inspect})"
12
+ super(name) do |tokens, cache, _name|
13
+ if result = tokens.match(token_spec, options[:capture])
14
+ L43Peg::Success.new(ast: result, cache:, rest: tokens.drop(1), position: tokens.position)
15
+ else
16
+ reason = "token #{tokens.head.inspect} does not match #{token_spec.inspect} (in #{name})"
17
+ L43Peg::Failure.new(cache:, input: tokens, parsed_by: self, reason:)
18
+ end
19
+ end
20
+ end
21
+
22
+ def _check_arg!(token_spec)
23
+ case token_spec
24
+ when Regexp, String
25
+ else
26
+ raise ArgumentError, "#{token_spec.inspect} must be a regular expression or a string"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class TokensParser < L43Peg::Parser
6
+
7
+ private
8
+
9
+ def initialize(map, name: nil, &blk)
10
+ map = _check_arg!(map)
11
+ name = name || "tokens_parser(#{map.inspect})"
12
+ super(name) do |tokens, cache, _name|
13
+ case _find_matching(map, tokens)
14
+ in [token, success]
15
+ _succeed(tokens:, token:, success:, &blk)
16
+ else
17
+ reason = "no token matches (in #{name})"
18
+ L43Peg::Failure.new(cache:, input: tokens, parsed_by: self, reason:)
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ def _check_arg!(map)
25
+ case map
26
+ when Hash
27
+ _mk_tokens(map)
28
+ else
29
+ raise ArgumentError, "#{map.inspect} must be a regular expression or a string"
30
+ end
31
+ end
32
+
33
+ def _find_matching(map, tokens)
34
+ map.each do |token, token_parser|
35
+ case token_parser.(tokens)
36
+ in L43Peg::Success => success
37
+ return [token, success]
38
+ else
39
+ end
40
+ end
41
+ nil
42
+ end
43
+
44
+ def _mk_tokens(map)
45
+ map.map do |token, spec|
46
+ [token, TokenParser.new(Regexp.compile(spec), capture: 1)]
47
+ end.to_h
48
+ end
49
+
50
+ def _succeed(success:, token:, tokens:, &blk)
51
+ result = if blk
52
+ blk.(success.ast)
53
+ else
54
+ success.ast
55
+ end
56
+ L43Peg::Success.new(ast: {token => result}, cache: success.cache, rest: success.rest, position: tokens.position)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ module Parsers
5
+ class VerbParser < L43Peg::Parser
6
+
7
+ private
8
+
9
+ def initialize(verb, name: nil)
10
+ name = name || "verb_parser(#{verb.inspect})"
11
+ super(name) do |input, cache, _name|
12
+ if input.input.start_with?(verb)
13
+ L43Peg::Success.new(ast: verb, cache:, rest: input.drop(verb), position: input.position)
14
+ else
15
+ L43Peg::Failure.new(cache:, input:, parsed_by: self, reason: "input does not start with #{verb.inspect} (in #{name})")
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'combinators'
4
+ require_subdir {}
5
+
6
+ module L43Peg
7
+ module Parsers extend self
8
+ def args_parser(args, name: nil) = L43Peg::Parsers::ArgsParser.new(args, name:)
9
+ def char_parser(charset = nil) = L43Peg::Parsers::CharParser.new(charset)
10
+ def end_parser = L43Peg::Parsers::EndParser.instance
11
+ def failure_parser = L43Peg::Parsers::FailureParser.instance
12
+ def int_parser = L43Peg::Parsers::IntParser.instance
13
+ def rgx_parser(rgx, name: nil, **o) = L43Peg::Parsers::RgxParser.new(rgx, name:, **o)
14
+ def token_parser(spc, name: nil, **o) = L43Peg::Parsers::TokenParser.new(spc, name:, **o)
15
+ def tokens_parser(map, name: nil, &b) = L43Peg::Parsers::TokensParser.new(map, name:, &b)
16
+ def verb_parser(verb, name: nil) = L43Peg::Parsers::VerbParser.new(verb, name:)
17
+ end
18
+ end
19
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Success
5
+ def _init
6
+ return if @position
7
+ @position = _position
8
+ end
9
+ extend L43::OpenObject
10
+
11
+ attributes ast: nil, cache: nil, position: nil, rest: nil
12
+
13
+ def map(&mapper)
14
+ self.class.new(ast: mapper.(ast), cache:, position:, rest:)
15
+ end
16
+
17
+ private
18
+
19
+
20
+ def _position
21
+ case @rest
22
+ when Tokens
23
+ 1
24
+ else
25
+ [1, 1]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43Peg
4
+ class Tokens
5
+ extend L43::OpenObject
6
+
7
+ attributes tnb: 1, tokens: [], context: {}
8
+
9
+ def drop(by=nil)
10
+ by = by || 1
11
+ return self if empty?
12
+ self.class.new(tokens: input.drop(by), context:, tnb: tnb + by)
13
+ end
14
+
15
+ def empty? = tokens.empty?
16
+ def head = tokens.first
17
+ def input = tokens
18
+
19
+ def match(str_or_rgx, option)
20
+ return if empty?
21
+
22
+ case str_or_rgx
23
+ when String
24
+ head == str_or_rgx && head
25
+ when Regexp
26
+ _match_rgx(str_or_rgx, option || 0)
27
+ end
28
+ end
29
+
30
+ def position = tnb
31
+
32
+ private
33
+
34
+ def _match_rgx(rgx, option)
35
+ md = rgx.match(head)
36
+ return unless md
37
+
38
+ if option == :all
39
+ md.to_a
40
+ else
41
+ md[option]
42
+ end
43
+ end
44
+ end
45
+ end
46
+ # SPDX-License-Identifier: Apache-2.0
data/lib/l43_peg.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'l43/require_helper'
4
+ require_relative 'l43/open_object'
5
+ require_subdir {}
6
+
3
7
  module L43Peg
4
- VERSION = "0.0.2"
5
-
8
+ VERSION = "0.1.1"
6
9
  end
7
- # SPDX-License-Identifier: Apache-2.0
10
+ # SPDX-License-Identifier: AGPL-3.0-or-later