Советы о том, как разобрать пользовательский формат файла - PullRequest
3 голосов
/ 10 января 2010

Извините за смутное название, но я действительно не знаю, как кратко описать эту проблему.

Я создал (более или менее) простой предметно-ориентированный язык , который я буду использовать, чтобы указать, какие правила проверки применяются к различным объектам (обычно формы, отправленные с веб-страницы). Внизу этого поста я включил пример того, как выглядит язык.

Моя проблема в том, что я понятия не имею, как начать синтаксический анализ этого языка в форму, которую я могу использовать (я буду использовать Python для анализа). Моя цель состоит в том, чтобы получить список правил / фильтров (в виде строк, включая аргументы, например, 'cocoa(99)'), которые должны применяться (по порядку) к каждому объекту / объекту (также строка, например, 'chocolate', 'chocolate.lindt' и т. д.).

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

Спасибо.

Пример файла языка:

# Comments start with the '#' character and last until the end of the line
# Indentation is significant (as in Python)


constant NINETY_NINE = 99       # Defines the constant `NINETY_NINE` to have the value `99`


*:      # Applies to all data
    isYummy             # Everything must be yummy

chocolate:              # To validate, say `validate("chocolate", object)`
    sweet               # chocolate must be sweet (but not necessarily chocolate.*)

    lindt:              # To validate, say `validate("chocolate.lindt", object)`
        tasty           # Applies only to chocolate.lindt (and not to chocolate.lindt.dark, for e.g.)

        *:              # Applies to all data under chocolate.lindt
            smooth      # Could also be written smooth()
            creamy(1)   # Level 1 creamy
        dark:           # dark has no special validation rules
            extraDark:
                melt            # Filter that modifies the object being examined
                c:bitter        # Must be bitter, but only validated on client
                s:cocoa(NINETY_NINE)    # Must contain 99% cocoa, but only validated on server. Note constant
        milk:
            creamy(2)   # Level 2 creamy, overrides creamy(1) of chocolate.lindt.* for chocolate.lindt.milk
            creamy(3)   # Overrides creamy(2) of previous line (all but the last specification of a given rule are ignored)



ruleset food:       # To define a chunk of validation rules that can be expanded from the placeholder `food` (think macro)
    caloriesWithin(10, 2000)        # Unlimited parameters allowed
    edible
    leftovers:      # Nested rules allowed in rulesets
        stale

# Rulesets may be nested and/or include other rulesets in their definition



chocolate:              # Previously defined groups can be re-opened and expanded later
    ferrero:
        hasHazelnut



cake:
    tasty               # Same rule used for different data (see chocolate.lindt)
    isLie
    ruleset food        # Substitutes with rules defined for food; cake.leftovers must now be stale


pasta:
    ruleset food        # pasta.leftovers must also be stale




# Sample use (in JavaScript):

# var choc = {
#   lindt: {
#       cocoa: {
#           percent: 67,
#           mass:    '27g'
#       }
#   }
#   // Objects/groups that are ommitted (e.g. ferrro in this example) are not validated and raise no errors
#   // Objects that are not defined in the validation rules do not raise any errors (e.g. cocoa in this example)
# };
# validate('chocolate', choc);

# `validate` called isYummy(choc), sweet(choc), isYummy(choc.lindt), smooth(choc.lindt), creamy(choc.lindt, 1), and tasty(choc.lindt) in that order
# `validate` returned an array of any validation errors that were found

# Order of rule validation for objects:
# The current object is initially the object passed in to the validation function (second argument).
# The entry point in the rule group hierarchy is given by the first argument to the validation function.
# 1. First all rules that apply to all objects (defined using '*') are applied to the current object,
#    starting with the most global rules and ending with the most local ones.
# 2. Then all specific rules for the current object are applied.
# 3. Then a depth-first traversal of the current object is done, repeating steps 1 and 2 with each object found as the current object
# When two rules have equal priority, they are applied in the order they were defined in the file.



# No need to end on blank line

Ответы [ 7 ]

9 голосов
/ 10 января 2010

Прежде всего, если вы хотите узнать о разборе, то напишите свой собственный анализатор рекурсивного спуска. Язык, который вы определили, требует только нескольких произведений. Я предлагаю использовать библиотеку Python tokenize, чтобы избавить себя от скучной задачи преобразования потока байтов в поток токенов.

Для практических вариантов разбора читайте дальше ...

Быстрое и грязное решение - использовать сам Python:

NINETY_NINE = 99       # Defines the constant `NINETY_NINE` to have the value `99`

rules = {
  '*': {     # Applies to all data
    'isYummy': {},      # Everything must be yummy

    'chocolate': {        # To validate, say `validate("chocolate", object)`
      'sweet': {},        # chocolate must be sweet (but not necessarily chocolate.*)

      'lindt': {          # To validate, say `validate("chocolate.lindt", object)`
        'tasty':{}        # Applies only to chocolate.lindt (and not to chocolate.lindt.dark, for e.g.)

        '*': {            # Applies to all data under chocolate.lindt
          'smooth': {}  # Could also be written smooth()
          'creamy': 1   # Level 1 creamy
        },
# ...
    }
  }
}

Есть несколько способов осуществить этот трюк, например, вот более чистый (хотя и несколько необычный) подход с использованием классов:

class _:
    class isYummy: pass

    class chocolate:
        class sweet: pass

        class lindt:
            class tasty: pass

            class _:
                class smooth: pass
                class creamy: level = 1
# ...

В качестве промежуточного шага к полному анализатору вы можете использовать Python-анализатор «с батареями», который анализирует синтаксис Python и возвращает AST. AST очень глубокий, с множеством ненужных уровней (IMO). Вы можете отфильтровать их до гораздо более простой структуры, выбрав все узлы, у которых есть только один дочерний элемент. При таком подходе вы можете сделать что-то вроде этого:

import parser, token, symbol, pprint

_map = dict(token.tok_name.items() + symbol.sym_name.items())

def clean_ast(ast):
    if not isinstance(ast, list):
        return ast
    elif len(ast) == 2: # Elide single-child nodes.
        return clean_ast(ast[1])
    else:
        return [_map[ast[0]]] + [clean_ast(a) for a in ast[1:]]

ast = parser.expr('''{

'*': {     # Applies to all data
  isYummy: _,    # Everything must be yummy

  chocolate: {        # To validate, say `validate("chocolate", object)`
    sweet: _,        # chocolate must be sweet (but not necessarily chocolate.*)

    lindt: {          # To validate, say `validate("chocolate.lindt", object)`
      tasty: _,        # Applies only to chocolate.lindt (and not to chocolate.lindt.dark, for e.g.)

      '*': {            # Applies to all data under chocolate.lindt
        smooth: _,  # Could also be written smooth()
        creamy: 1   # Level 1 creamy
      }
# ...
    }
  }
}

}''').tolist()
pprint.pprint(clean_ast(ast))

Этот подход имеет свои ограничения. Окончательный AST все еще немного шумный, и язык, который вы определяете, должен интерпретироваться как допустимый код Python. Например, вы не можете поддержать это ...

*:
    isYummy

... потому что этот синтаксис не анализируется как код Python. Однако его большим преимуществом является то, что вы контролируете преобразование AST, поэтому невозможно внедрить произвольный код Python.

5 голосов
/ 02 марта 2010

Опять не учу вас разбирать, но ваш формат настолько близок к юридическому YAML , что вы можете просто переопределить ваш язык как подмножество YAML и использовать стандартный синтаксический анализатор YAML .

2 голосов
/ 10 января 2010

Если ваша цель - изучить синтаксический анализ, я настоятельно рекомендую библиотеку стилей OO, например PyParsing . Они не такие быстрые, как более сложные опции рога, лекса, яка, но вы сразу приступаете к разбору.

1 голос
/ 10 января 2010

Язык, для которого вы показали пример, вероятно, слишком сложен, чтобы написать простую (и без ошибок) функцию синтаксического анализа. Я бы посоветовал ознакомиться с методами синтаксического анализа, такими как рекурсивный спуск или табличный синтаксический анализ, например LL (1), LL (k) и т. Д.

Но это может быть слишком общим и / или сложным. Может быть проще упростить ваш язык правил до чего-то простого, например текста с разделителями.

Например, что-то вроде

шоколад: сладкий
chocolate.lindt: вкусно
chocolate.lindt. *: гладкая, сливочный (1)

Это было бы легче проанализировать и можно было бы сделать без формальных синтаксических анализаторов.

1 голос
/ 10 января 2010

Как сказал 'Marcelo Cantos', вы можете использовать python dict, преимущество в том, что вам не нужно ничего анализировать, вы можете использовать те же правила на стороне сервера, что и на python dict, и на стороне клиента, используя объекты JavaScript, и можете передавать их с сервера на клиент или наоборот как JSON.

Если вы действительно хотите разбирать сами, посмотрите это http://nedbatchelder.com/text/python-parsers.html

но я не уверен, что вы легко сможете разобрать язык с отступами.

0 голосов
/ 10 января 2010

Какова мотивация для настраиваемой файловой структуры? Возможно ли будет переделать ваши данные в более известную структуру, такую ​​как XML? Если это так, вы можете использовать один из множества для анализа вашего файла. Использование принятого инструмента синтаксического анализа может сэкономить вам много времени на отладку, и это может сделать ваш файл более читабельным, если это важно

0 голосов
/ 10 января 2010

Существуют библиотеки и инструменты, облегчающие анализ. Одним из наиболее известных является lex / yacc. Есть библиотека питонов с именем ' lex ' и учебник по ее использованию.

...