Как определить ограничение фиксированной ширины в parslet - PullRequest
3 голосов
/ 04 апреля 2011

Я ищу parslet , чтобы написать много кода для импорта данных.В целом, библиотека выглядит хорошо, но я борюсь с одной вещью.Многие из наших входных файлов имеют фиксированную ширину, а ширина различается в разных форматах, даже если фактическое поле этого не делает.Например, мы можем получить файл с 9-символьной валютой, а другой - с 11-символьным (или любым другим).Кто-нибудь знает, как определить фиксированное ограничение ширины для атома петрушки?

В идеале я хотел бы иметь возможность определить атом, который понимает валюту (с необязательными знаками доллара, разделителями тысяч и т. Д.)И тогда я смогу на лету создать новый атом на основе старого, который в точности эквивалентен, за исключением того, что он анализирует ровно N символов.

Существует ли такой комбинатор в parslet?Если нет, то было бы возможно / трудно написать это самому?

Ответы [ 3 ]

1 голос
/ 13 апреля 2011

Возможно, мое частичное решение поможет уточнить, что я имел в виду в вопросе.

Допустим, у вас есть несколько нетривиальный синтаксический анализатор:

class MyParser < Parslet::Parser
    rule(:dollars) {
        match('[0-9]').repeat(1).as(:dollars)
    }
    rule(:comma_separated_dollars) {
        match('[0-9]').repeat(1, 3).as(:dollars) >> ( match(',') >> match('[0-9]').repeat(3, 3).as(:dollars) ).repeat(1)
    }
    rule(:cents) {
        match('[0-9]').repeat(2, 2).as(:cents)
    }
    rule(:currency) {
        (str('$') >> (comma_separated_dollars | dollars) >> str('.') >> cents).as(:currency)
        # order is important in (comma_separated_dollars | dollars)
    }
end

Теперь, если мы хотим разобратьСтрока валюты фиксированной ширины;это не самая простая вещь.Конечно, вы могли бы точно выяснить, как выразить выражения повторения в терминах конечной ширины, но это становится действительно излишне хитрым, особенно в случае запятой.Кроме того, в моем случае использования валюта является лишь одним из примеров.Я хочу, чтобы у меня был простой способ придумать определения фиксированной ширины для адресов, почтовых индексов и т. Д.

Это похоже на то, что должно обрабатываться PEG.Мне удалось написать прототип, используя Lookahead в качестве шаблона:

class FixedWidth < Parslet::Atoms::Base
    attr_reader :bound_parslet
    attr_reader :width

    def initialize(width, bound_parslet) # :nodoc:
        super()

        @width = width
        @bound_parslet = bound_parslet
        @error_msgs = {
            :premature => "Premature end of input (expected #{width} characters)",
            :failed => "Failed fixed width",
        }
    end

    def try(source, context) # :nodoc:
        pos = source.pos
        teststring = source.read(width).to_s
        if (not teststring) || teststring.size != width
            return error(source, @error_msgs[:premature]) #if not teststring && teststring.size == width
        end
        fakesource = Parslet::Source.new(teststring)
        value = bound_parslet.apply(fakesource, context)
        return value if not value.error?

        source.pos = pos
        return error(source, @error_msgs[:failed])
    end

    def to_s_inner(prec) # :nodoc:
        "FIXED-WIDTH(#{width}, #{bound_parslet.to_s(prec)})"
    end

    def error_tree # :nodoc:
        Parslet::ErrorTree.new(self, bound_parslet.error_tree)
    end
end

# now we can easily define a fixed-width currency rule:
class SHPParser
    rule(:currency15) {
        FixedWidth.new(15, currency >> str(' ').repeat)
    }
end

Конечно, это довольно взломанное решение.Помимо прочего, номера строк и сообщения об ошибках не подходят для ограничения фиксированной ширины.Мне бы очень хотелось, чтобы эта идея была реализована лучше.

1 голос
/ 12 февраля 2014

Что-то вроде этого ...

class MyParser < Parslet::Parser
    def initialize(widths)
        @widths = widths
        super
    end

    rule(:currency)  {...}
    rule(:fixed_c)   {currency.fixed(@widths[:currency])}


    rule(:fixed_str) {str("bob").fixed(4)}
end 

puts MyParser.new.fixed_str.parse("bob").inspect

Это не удастся с:

"Expected 'bob' to be 4 long at line 1 char 1"

Вот как вы это сделаете:

require 'parslet'

class Parslet::Atoms::FixedLength < Parslet::Atoms::Base  
  attr_reader :len, :parslet
  def initialize(parslet, len, tag=:length)
    super()

    raise ArgumentError, 
      "Asking for zero length of a parslet. (#{parslet.inspect} length #{len})" \
      if len == 0

    @parslet = parslet
    @len = len
    @tag = tag
    @error_msgs = {
      :lenrep  => "Expected #{parslet.inspect} to be #{len} long", 
      :unconsumed => "Extra input after last repetition"
    }
  end

  def try(source, context, consume_all)
    start_pos = source.pos

    success, value = parslet.apply(source, context, false)

    return succ(value) if success && value.str.length == @len

    context.err_at(
      self, 
      source, 
      @error_msgs[:lenrep], 
      start_pos, 
      [value]) 
  end

  precedence REPETITION
  def to_s_inner(prec)
    parslet.to_s(prec) + "{len:#{@len}}"
  end
end

module Parslet::Atoms::DSL
  def fixed(len)
    Parslet::Atoms::FixedLength.new(self, len)
  end
end
1 голос
/ 08 апреля 2011

Методы в классах синтаксических анализаторов в основном являются генераторами для атомов петрушки. Простейшая форма, в которую входят эти методы, - это правила, методы, которые просто возвращают одни и те же атомы при каждом вызове. Так же легко создавать свои собственные генераторы, которые не такие простые звери. Пожалуйста, посмотрите на http://kschiess.github.com/parslet/tricks.html для иллюстрации этого трюка (соответствие строк не учитывает регистр).

Мне кажется, что ваш анализатор валюты является анализатором только с несколькими параметрами, и что вы, вероятно, могли бы создать метод (def ... end), который возвращает анализаторы валют с учетом ваших предпочтений. Может быть, даже использовать аргументы инициализации и конструктора? (например: MoneyParser.new (4,5))

Для получения дополнительной помощи, пожалуйста, отправьте свои вопросы в список рассылки. На такие вопросы часто легче ответить, если вы иллюстрируете это с помощью кода.

...