Fixed #55 : DefConceptParser: failed to recognize concept

Fixed #62 : DefConceptParser: parsing error
Fixed #64 : DefConceptParser: Failed to parse when too many concept keyword
Fixed #65 : DefConceptParser : Add auto_eval keyword
Fixed #66 : DefConceptParser : Add def_var keyword
Fixed #67 : Add get_errors()
This commit is contained in:
2021-04-13 15:15:17 +02:00
parent 81e67147e9
commit bef5f3208c
17 changed files with 838 additions and 235 deletions
+118 -42
View File
@@ -14,7 +14,12 @@ from parsers.PythonParser import get_python_node
@dataclass(eq=True, frozen=True)
class MandatoryVariable:
class ConceptEvaluatorVariable:
name: str
@dataclass(eq=True, frozen=True)
class MandatoryVariable(ConceptEvaluatorVariable):
"""
When we are searching for variables, we are searching for potential variable
So if the variable found has no match in the concept definition, it's not a problem
@@ -24,19 +29,51 @@ class MandatoryVariable:
But there are cases where the variable found must exist, otherwise, it's an error
example:
def concept foo from bnf xxx
'xxx' is detected as a variable (assuming that there is no concept named 'xxx' and a match must be
found in the the name of the variable
def concept foo from bnf unknown_concept
'unknown_concept' will be detected and considered as a variable . But it is not, as it's not
declared in the name of the concept.
To distinguish between mandatory and not mandatory variable, we use MandatoryVariable
We return MandatoryVariable (instead of a variable name) to let the evaluator know that if the variable is not
declared in the name of the concept, it's an error
"""
name: str
def __hash__(self):
return hash(("MandatoryVariable", self.name))
class ConceptOrRuleNameVisitor(ParsingExpressionVisitor):
@dataclass(eq=True, frozen=True)
class PossibleVariable(ConceptEvaluatorVariable):
"""
When a name/identifier is found in a concept part (pre, post, where, body...)
It is considered as a possible variable. It will only added as a variable if the exact name / identifier
is found in the name of the concept
example:
def concept a plus b as a + b
'a' and 'b' are found in the body and thy also exist in the name of the concept
-> They will be added as variable
"""
def __hash__(self):
return hash(("PossibleVariable", self.name))
@dataclass(eq=True, frozen=True)
class CertainVariable(ConceptEvaluatorVariable):
"""
A certain variable will be added as a variable regardless of a possible match in the name
example:
def concept number
def concept plus from bnf number=n1 plus number=n2
'n1' and 'n2' do not appear in the name of the concept, but they are variables
"""
def __hash__(self):
return hash(("PossibleVariable", self.name))
class ConceptOrRuleVariableVisitor(ParsingExpressionVisitor):
"""
Gets the concepts referenced by BNF
If a rule_name is given, it will also be considered as a potential property
@@ -44,22 +81,22 @@ class ConceptOrRuleNameVisitor(ParsingExpressionVisitor):
def __init__(self):
super().__init__()
self.names = set()
self.variables = []
def visit_ConceptExpression(self, node):
if node.rule_name:
self.names.add(node.rule_name)
self.variables.append(CertainVariable(node.rule_name))
elif isinstance(node.concept, Concept):
self.names.add(node.concept.name)
self.variables.append(CertainVariable(node.concept.name))
else:
self.names.add(node.concept)
self.variables.append(CertainVariable(node.concept))
def visit_VariableExpression(self, node):
self.names.add(MandatoryVariable(node.rule_name))
self.variables.append(MandatoryVariable(node.rule_name))
def visit_all(self, node):
if node.rule_name:
self.names.add(node.rule_name)
self.variables.append(CertainVariable(node.rule_name))
class DefConceptEvaluator(OneReturnValueEvaluator):
@@ -89,12 +126,18 @@ class DefConceptEvaluator(OneReturnValueEvaluator):
# validate the node
variables_found = set()
mandatory_variables = set() # these variable MUST have a match in the name (if the name is not None)
certain_variables = []
skip_variables_resolution = False
concept = Concept(str(def_concept_node.name))
concept.get_metadata().definition_type = def_concept_node.definition_type
name_to_use = self.get_name_to_use(def_concept_node)
# get variables
if def_concept_node.variables != NotInit:
certain_variables = def_concept_node.variables.copy()
skip_variables_resolution = True
# get variables and set the sources
for prop in ("definition", "where", "pre", "post", "body", "ret"):
part_ret_val = getattr(def_concept_node, prop)
@@ -111,26 +154,29 @@ class DefConceptEvaluator(OneReturnValueEvaluator):
raise Exception("Unexpected")
setattr(concept.get_metadata(), prop, source)
if skip_variables_resolution:
continue
# Do not try to resolve variables from itself
if prop == "definition" and concept.get_metadata().definition_type == DEFINITION_TYPE_DEF:
continue
# try to find what can be a property
for p in self.get_variables(context, part_ret_val, name_to_use):
variables_found.add(p.name)
if isinstance(p, MandatoryVariable):
variables_found.add(p.name)
mandatory_variables.add(p.name)
else:
variables_found.add(p)
elif isinstance(p, CertainVariable):
certain_variables.append(p.name)
# add variables by order of appearance when possible
for name_part in name_to_use:
# add variables by order of appearance
for name_part in [name_part for name_part in name_to_use if str(name_part).isalnum()]:
if name_part in variables_found:
concept.def_var(name_part, None)
# check that all mandatory variables are defined in the name
# KSI: 2021-02-17
# The mandatory variables come for bnf definition where it was not possible to resolve to a concept
# The mandatory variables come from bnf definition where it was not possible to resolve to a concept
# So rather that issuing a 'UnresolvedVariableError' I prefer UNKNOWN_CONCEPT
if (diff := mandatory_variables.difference(set(name_to_use))) != set():
unknown_concepts = [sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body={"name": c}) for c in sorted(diff)]
@@ -139,7 +185,7 @@ class DefConceptEvaluator(OneReturnValueEvaluator):
# add the remaining properties
# They mainly come from BNF definition
for p in variables_found:
for p in certain_variables:
if p not in concept.values():
concept.def_var(p, None)
@@ -154,6 +200,10 @@ class DefConceptEvaluator(OneReturnValueEvaluator):
def_concept_node.definition_type == DEFINITION_TYPE_BNF:
concept.set_bnf(def_concept_node.definition.value.value)
# manage auto eval
if def_concept_node.auto_eval:
concept.add_prop(BuiltinConcepts.ISA, sheerka.new(BuiltinConcepts.AUTO_EVAL))
ret = sheerka.create_new_concept(context, concept)
if not ret.status:
error_cause = sheerka.objvalue(ret.body)
@@ -172,24 +222,38 @@ class DefConceptEvaluator(OneReturnValueEvaluator):
This function can only be a draft, as there may be tons of different situations
I guess that it can only be complete when will we have access to Sheerka memory
"""
def get_inner_concept(parsing_result):
if not isinstance(parsing_result, ParserResultConcept):
return None
if isinstance(parsing_result.body, Concept):
return parsing_result.body
# manage other cases (conceptNode) later
return None
debugger = context.get_debugger(DefConceptEvaluator.NAME, "get_variables")
#
# Case of NameNode
#
if isinstance(ret_value, NameNode):
names = [str(t.value) for t in ret_value.tokens if t.type in (
TokenKind.IDENTIFIER, TokenKind.STRING, TokenKind.KEYWORD)]
names = [str(t.value) for t in ret_value.tokens if t.type in (TokenKind.IDENTIFIER,
TokenKind.STRING,
TokenKind.KEYWORD)]
possible_vars = filter(lambda x: x in concept_name and context.sheerka.is_not_a_variable(x), names)
debugger.debug_var("names", names, hint="from NameNode")
return set(filter(lambda x: x in concept_name and context.sheerka.is_not_a_variable(x), names))
debugger.debug_var("possible_vars", possible_vars, hint="from NameNode")
return [PossibleVariable(v) for v in possible_vars]
#
# case of BNF
#
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, ParsingExpression):
visitor = ConceptOrRuleNameVisitor()
visitor = ConceptOrRuleVariableVisitor()
visitor.visit(ret_value.value.value)
debugger.debug_var("names", visitor.names, hint="from BNF")
return set(visitor.names)
debugger.debug_var("names", visitor.variables, hint="from BNF")
return visitor.variables
#
# Case of python code
@@ -198,31 +262,43 @@ class DefConceptEvaluator(OneReturnValueEvaluator):
if len(concept_name) > 1:
visitor = UnreferencedVariablesVisitor(context)
names = visitor.get_names(python_node.ast_)
possible_vars = filter(lambda x: x in concept_name and context.sheerka.is_not_a_variable(x), names)
debugger.debug_var("names", names, hint="from python node")
return set(filter(lambda x: x in concept_name and context.sheerka.is_not_a_variable(x), names))
debugger.debug_var("possible_vars", possible_vars, hint="from python node")
return [PossibleVariable(v) for v in possible_vars]
else:
return set()
return []
#
# Concept
# Case of Concept
#
if (concept := get_inner_concept(ret_value.value)) is not None and len(concept_name) > 1:
# use the variables of the concept is any
names = [var_value or var_name for var_name, var_value in concept.get_metadata().variables]
possible_vars = filter(lambda x: context.sheerka.is_not_a_variable(x), names)
debugger.debug_var("names", names, hint="from concept")
debugger.debug_var("possible_vars", possible_vars, hint="from concept")
return [PossibleVariable(v) for v in possible_vars]
#
# Other cases
#
if isinstance(ret_value.value, ParserResultConcept) and len(concept_name) > 1:
variables = set()
source = ret_value.value.source.as_text() if isinstance(ret_value.value.source,
ParserInput) else ret_value.value.source
source = ret_value.value.source.as_text() if isinstance(ret_value.value.source, ParserInput) else \
ret_value.value.source
tokens = ret_value.value.tokens or list(Tokenizer(source, yield_eof=False))
possible_vars = set()
names = []
for t in tokens:
if t.type == TokenKind.RULE:
for v in [v for v in t.value if v is not None]:
possible_vars.add(v)
names.append(v)
else:
possible_vars.add(t.str_value)
names.append(t.str_value)
for identifier in [i for i in concept_name if str(i).isalnum()]:
if identifier in possible_vars:
variables.add(identifier)
debugger.debug_var("names", variables, hint="from concept")
return variables
possible_vars = filter(lambda x: context.sheerka.is_not_a_variable(x), names)
debugger.debug_var("names", names, hint="from source")
debugger.debug_var("possible_vars", possible_vars, hint="from source")
return [PossibleVariable(v) for v in possible_vars]
return set()
return []