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.
- checksums.yaml +4 -4
- data/LICENSE +235 -201
- data/README.md +74 -2
- data/lib/l43/match_data_extenstion.rb +6 -0
- data/lib/l43/open_object.rb +38 -0
- data/lib/l43/require_helper.rb +8 -0
- data/lib/l43_peg/cache.rb +9 -0
- data/lib/l43_peg/combinators/many.rb +38 -0
- data/lib/l43_peg/combinators/seq.rb +29 -0
- data/lib/l43_peg/combinators.rb +51 -0
- data/lib/l43_peg/failure.rb +9 -0
- data/lib/l43_peg/helper.rb +13 -0
- data/lib/l43_peg/input.rb +48 -0
- data/lib/l43_peg/mappers.rb +21 -0
- data/lib/l43_peg/parser.rb +19 -0
- data/lib/l43_peg/parsers/char_parser.rb +36 -0
- data/lib/l43_peg/parsers/end_parser.rb +27 -0
- data/lib/l43_peg/parsers/failure_parser.rb +21 -0
- data/lib/l43_peg/parsers/int_parser.rb +26 -0
- data/lib/l43_peg/parsers/rgx_parser.rb +45 -0
- data/lib/l43_peg/parsers/token_parser.rb +32 -0
- data/lib/l43_peg/parsers/tokens_parser.rb +61 -0
- data/lib/l43_peg/parsers/verb_parser.rb +23 -0
- data/lib/l43_peg/parsers.rb +19 -0
- data/lib/l43_peg/success.rb +30 -0
- data/lib/l43_peg/tokens.rb +46 -0
- data/lib/l43_peg.rb +6 -3
- metadata +25 -2
| @@ -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,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,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. | 
| 5 | 
            -
              
         | 
| 8 | 
            +
              VERSION = "0.1.1"
         | 
| 6 9 | 
             
            end
         | 
| 7 | 
            -
            # SPDX-License-Identifier:  | 
| 10 | 
            +
            # SPDX-License-Identifier: AGPL-3.0-or-later
         |