286 lines
9.8 KiB
Python
286 lines
9.8 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company, L.P.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import pyparsing as p
|
|
|
|
import monasca_analytics.banana.emitter as emit
|
|
import monasca_analytics.banana.grammar.ast as ast
|
|
import monasca_analytics.banana.grammar.const as const
|
|
import monasca_analytics.exception.banana as exception
|
|
|
|
|
|
# This file describe the grammar for the banana config file.
|
|
# It make use of one sub grammar for certain configuration
|
|
# that requires expressions (see expression.py file)
|
|
|
|
|
|
def banana_grammar(emitter=emit.PrintEmitter()):
|
|
"""
|
|
Generate a banana parser that can be then used to
|
|
parse a banana content. It build an AST on which
|
|
operation can then be applied.
|
|
:return: Return a banana parser
|
|
:rtype: BananaScopeParser
|
|
"""
|
|
# Should debug
|
|
debug_grammar = False
|
|
|
|
# Actions
|
|
def action_str_lit(s, l, t):
|
|
return ast.StringLit(ast.make_span(s, l, t), t[0])
|
|
|
|
def action_num_lit(s, l, t):
|
|
return ast.Number(ast.make_span(s, l, t), t[0])
|
|
|
|
def action_ident(s, l, t):
|
|
return ast.Ident(ast.make_span(s, l, t), t[0])
|
|
|
|
def action_expr(s, l, t):
|
|
if len(t) != 1:
|
|
raise exception.BananaGrammarBug(
|
|
'Bug found in the grammar for expression,'
|
|
' Please report this bug.'
|
|
)
|
|
if isinstance(t[0], ast.Expr):
|
|
return t[0]
|
|
return ast.Expr(ast.make_span(s, l, t), t[0])
|
|
|
|
def action_dot_path(s, l, t):
|
|
# First token is the name of the variable
|
|
# The rest is the property path
|
|
if isinstance(t[0], ast.StringLit) and len(t[1:]) == 0:
|
|
return t[0]
|
|
return ast.DotPath(ast.make_span(s, l, t), t[0], t[1:])
|
|
|
|
def action_json_obj(s, l, t):
|
|
return ast.JsonObj(ast.make_span(s, l, t), t)
|
|
|
|
def action_parse_ctor_arg(s, l, t):
|
|
if len(t) > 1:
|
|
return ast.ComponentCtorArg(ast.make_span(s, l, t), t[1], t[0])
|
|
else:
|
|
return ast.ComponentCtorArg(ast.make_span(s, l, t), t[0])
|
|
|
|
def action_parse_comp_ctor(s, l, tokens):
|
|
comp = ast.Component(ast.make_span(s, l, tokens))
|
|
for tok in tokens:
|
|
if isinstance(tok, ast.Ident):
|
|
comp.set_ctor(tok)
|
|
elif isinstance(tok, ast.ComponentCtorArg):
|
|
comp.add_arg(tok)
|
|
else:
|
|
raise exception.BananaGrammarBug(
|
|
'Bug found in the grammar, Please report this bug'
|
|
)
|
|
return comp
|
|
|
|
def action_assignment(s, l, t):
|
|
return ast.Assignment(ast.make_span(s, l, t), t[0], t[1])
|
|
|
|
def action_create_connections(s, l, t):
|
|
ast_conn = ast.into_connection(t[0])
|
|
ast_conn.span = ast.make_span(s, l, t)
|
|
for i in range(1, len(t)):
|
|
next_conn = ast.into_connection(t[i])
|
|
ast_conn.connect_to(next_conn, emitter)
|
|
return ast_conn
|
|
|
|
def action_merge_connections(s, l, t):
|
|
ast_conn = ast.Connection(ast.make_span(s, l, t))
|
|
ast_conn.merge_all(t, emitter)
|
|
return ast_conn
|
|
|
|
def action_root_ast(s, l, tokens):
|
|
root = ast.BananaFile(emitter)
|
|
for tok in tokens:
|
|
if isinstance(tok, ast.Assignment):
|
|
if isinstance(tok.rhs, ast.Component):
|
|
root.add_component_ctor(tok.lhs, tok.rhs)
|
|
else:
|
|
root.add_assignment(tok.lhs, tok.rhs)
|
|
elif isinstance(tok, ast.Connection):
|
|
root.add_connections(tok)
|
|
else:
|
|
raise exception.BananaGrammarBug(
|
|
'Bug found in the grammar, Please report this bug.'
|
|
)
|
|
return root
|
|
|
|
# TODO(Joan): Remove once it is no longer needed
|
|
def print_stmt(s, l, t):
|
|
print("\nPRINT AST")
|
|
print((l, [str(x) for x in t]))
|
|
print("END PRINT AST\n")
|
|
|
|
def action_unimplemented(s, l, t):
|
|
raise exception.BananaGrammarBug("unimplemented code reached")
|
|
|
|
# Tokens
|
|
equals = p.Literal("=").suppress().setName('"="').setDebug(debug_grammar)
|
|
arrow = p.Literal("->").suppress().setName('"->"').setDebug(debug_grammar)
|
|
lbra = p.Literal("[").suppress().setName('"["').setDebug(debug_grammar)
|
|
rbra = p.Literal("]").suppress().setName('"]"').setDebug(debug_grammar)
|
|
colon = p.Literal(":").suppress().setName('":"')
|
|
comma = p.Literal(",").suppress().setName(",")
|
|
less = p.Literal("<").suppress().setName('"<"')
|
|
greater = p.Literal(">").suppress().setName('">"')
|
|
lbrace = p.Literal("{").suppress().setName('"{"').setDebug(debug_grammar)
|
|
rbrace = p.Literal("}").suppress().setName('"}"').setDebug(debug_grammar)
|
|
lpar = p.Literal("(").suppress().setName('"("')
|
|
rpar = p.Literal(")").suppress().setName('")"')
|
|
|
|
# Keywords
|
|
ing = p.Literal("ing").suppress()
|
|
imp = p.Literal("import").suppress()
|
|
fro = p.Literal("from").suppress()
|
|
|
|
# String Literal, Numbers, Identifiers
|
|
string_lit = p.quotedString()\
|
|
.setParseAction(action_str_lit)\
|
|
.setName(const.STRING_LIT)
|
|
number_lit = p.Regex(r'\d+(\.\d*)?([eE]\d+)?')\
|
|
.setParseAction(action_num_lit)\
|
|
.setName(const.NUMBER)
|
|
ident = p.Word(p.alphas + "_", p.alphanums + "_")\
|
|
.setParseAction(action_ident)\
|
|
.setName(const.IDENT)
|
|
|
|
# Path for properties
|
|
dot_prop = ident | string_lit
|
|
dot_path = p.delimitedList(dot_prop, ".")\
|
|
.setParseAction(action_dot_path)\
|
|
.setName(const.DOT_PATH)\
|
|
.setDebug(debug_grammar)
|
|
|
|
# Expressions
|
|
|
|
# Here to simplify the logic, we can match directly
|
|
# against ident and string_lit to avoid having to deal
|
|
# only with dot_path. It also allow to remove the confusion
|
|
# where '"a"' could be interpreted as a dot_path and would thus
|
|
# be the same as 'a'. With the following, the first we
|
|
# always be type-checked as a String whereas the latter will
|
|
# be as the type of the variable.
|
|
expr = p.infixNotation(number_lit | dot_path, [
|
|
(p.oneOf('* /'), 2, p.opAssoc.LEFT),
|
|
(p.oneOf('+ -'), 2, p.opAssoc.LEFT),
|
|
], lpar=lpar, rpar=rpar)
|
|
expr.setParseAction(action_expr)\
|
|
.setName(const.EXPR)\
|
|
.setDebug(debug_grammar)
|
|
|
|
# Json-like object (value are much more)
|
|
json_obj = p.Forward()
|
|
json_value = p.Forward()
|
|
json_array = p.Group(
|
|
lbra + p.Optional(p.delimitedList(json_value)) + rbra
|
|
)
|
|
json_array.setDebug(debug_grammar)
|
|
json_array.setName(const.JSON_ARRAY)
|
|
json_value <<= expr | json_obj | json_array
|
|
json_value.setDebug(debug_grammar)\
|
|
.setName(const.JSON_VALUE)
|
|
json_members = p.delimitedList(p.Group(dot_path + colon - json_value)) +\
|
|
p.Optional(comma)
|
|
json_members.setDebug(debug_grammar)\
|
|
.setName(const.JSON_MEMBERS)
|
|
json_obj <<= p.Dict(lbrace + p.Optional(json_members) - rbrace)
|
|
json_obj.setParseAction(action_json_obj)\
|
|
.setName(const.JSON_OBJ)\
|
|
.setDebug(debug_grammar)
|
|
|
|
# Component constructor
|
|
arg = (ident + equals - (expr | json_obj)) | expr | json_obj
|
|
arg.setParseAction(action_parse_ctor_arg)
|
|
params = p.delimitedList(arg)
|
|
comp_ctor = ident + lpar - p.Optional(params) + rpar
|
|
comp_ctor.setParseAction(action_parse_comp_ctor)\
|
|
.setName(const.COMP_CTOR)\
|
|
.setDebug(debug_grammar)
|
|
|
|
# Assignments
|
|
assignment = dot_path + equals - (comp_ctor | expr | json_obj)
|
|
assignment.setParseAction(action_assignment)
|
|
|
|
# Connections
|
|
connection = p.Forward()
|
|
array_of_connection = p.Group(
|
|
lbra + p.Optional(p.delimitedList(connection)) + rbra
|
|
)
|
|
array_of_connection.setParseAction(action_merge_connections)
|
|
last_expr = ident | array_of_connection
|
|
this_expr = p.Forward()
|
|
match_expr = p.FollowedBy(last_expr + arrow - last_expr) + \
|
|
(last_expr + p.OneOrMore(arrow - last_expr))
|
|
this_expr <<= match_expr | last_expr
|
|
connection <<= this_expr
|
|
|
|
match_expr.setDebug(debug_grammar)\
|
|
.setName(const.CONNECTION) \
|
|
.setParseAction(action_create_connections)
|
|
|
|
# Definitions
|
|
definition = ing - less - string_lit - greater - ident - lbrace - rbrace
|
|
definition.setDebug(debug_grammar)\
|
|
.setName(const.DEFINITION)\
|
|
.setParseAction(action_unimplemented)
|
|
|
|
# Import directive
|
|
module_def = (imp - ident) | fro - ident - imp - ident
|
|
module_def.setDebug(debug_grammar)\
|
|
.setName(const.MOD_IMPORT)\
|
|
.setParseAction(action_unimplemented)
|
|
|
|
# Comments
|
|
comments = "#" + p.restOfLine
|
|
|
|
statement = assignment | \
|
|
match_expr | \
|
|
definition | \
|
|
module_def
|
|
|
|
statement.setName(const.STATEMENT)
|
|
statement.setDebug(debug_grammar)
|
|
statement.setParseAction(print_stmt)
|
|
|
|
# Grammar
|
|
grammar = p.OneOrMore(statement).ignore(comments)
|
|
grammar.setParseAction(action_root_ast)
|
|
|
|
return BananaScopeParser(grammar)
|
|
|
|
|
|
class BananaScopeParser(object):
|
|
"""
|
|
Aggregate and resolve conflicts as everything was define
|
|
within the same scope. Usefull for have cpp "include"-like
|
|
functionality when importing another file.
|
|
"""
|
|
|
|
def __init__(self, grammar):
|
|
self._grammar = grammar
|
|
|
|
def parse(self, string):
|
|
"""
|
|
Parse the given input string.
|
|
:type string: str
|
|
:param string: Input string.
|
|
:rtype: ast.BananaFile
|
|
:return: Returns the ast root.
|
|
"""
|
|
tree = self._grammar.parseString(string, parseAll=True)[0]
|
|
return tree
|