Прежде всего, если вы хотите узнать о разборе, то напишите свой собственный анализатор рекурсивного спуска. Язык, который вы определили, требует только нескольких произведений. Я предлагаю использовать библиотеку 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.