diff --git a/src/core/concept.py b/src/core/concept.py index b89361c..98fcc5f 100644 --- a/src/core/concept.py +++ b/src/core/concept.py @@ -13,7 +13,7 @@ PROPERTIES_FOR_DIGEST = ("name", "key", "definition", "definition_type", "is_builtin", "is_unique", "where", "pre", "post", "body", "ret", - "desc", "props", "variables") + "desc", "props", "variables", "parameters") PROPERTIES_TO_SERIALIZE = PROPERTIES_FOR_DIGEST + tuple(["id"]) PROPERTIES_FOR_NEW = ("where", "pre", "post", "body", "desc") VARIABLE_PREFIX = "__var__" @@ -73,6 +73,7 @@ class ConceptMetadata: id: str # unique identifier for a concept. The id will never be modified (but the key can) props: dict # hashmap of properties, values variables: list # list of concept variables(tuple), with their default values + parameters: list # list of variables that are part of the name of the concept full_serialization: bool = False # If True, the full object will be serialized, rather than just the diff all_attributes: List[str] = None # list of instance attributes @@ -163,7 +164,8 @@ class Concept: desc, id, props or {}, - variables or [] + variables or [], + [] ) self._metadata = metadata @@ -241,8 +243,8 @@ class Concept: # It's just to control what I put in the default value of properties # You can allow more type if it's REALLY needed. # - str are for standard definition - # - list of concepts is used by ISA - assert default_value is None or isinstance(default_value, str) + # - Concept when properly set + assert default_value is None or isinstance(default_value, (str, Concept)) self._metadata.variables.append((var_name, default_value)) @@ -330,7 +332,6 @@ class Concept: if token.value in variables: key += VARIABLE_PREFIX + str(variables.index(token.value)) else: - # value = token.value[1:-1] if token.type == TokenKind.STRING else token.value key += token.value.value if token.type == TokenKind.KEYWORD else token.value first = False @@ -421,6 +422,8 @@ class Concept: self.def_var(name, value) elif prop == "props": self._metadata.props = core.utils.sheerka_deepcopy(other.get_metadata().props) + elif prop == "parameters": + self._metadata.parameters = other.get_metadata().parameters.copy() else: setattr(self._metadata, prop, getattr(other.get_metadata(), prop)) @@ -522,11 +525,7 @@ class Concept: return NotInit def values(self): - try: - values = {k: getattr(self, k) for k in get_concept_attrs(self)} - except AttributeError as err: - print(f"{err}, {self=}") - raise err + values = {k: getattr(self, k) if hasattr(self, k) else NotInit for k in get_concept_attrs(self)} for prop_name in AllConceptParts: try: diff --git a/src/core/sheerka/Sheerka.py b/src/core/sheerka/Sheerka.py index fa4837b..325c1c0 100644 --- a/src/core/sheerka/Sheerka.py +++ b/src/core/sheerka/Sheerka.py @@ -21,6 +21,9 @@ from sdp.sheerkaDataProvider import Event BASE_NODE_PARSER_CLASS = "parsers.BaseNodeParser.BaseNodeParser" EXIT_COMMANDS = ("quit", "exit", "bye") +CONTEXT_HINT_COMMANDS = (("global_truth(", "global_truth"), + ("question(", "question"), + ("eval", "eval")) EXECUTE_STEPS = [ BuiltinConcepts.BEFORE_PARSING, BuiltinConcepts.PARSING, @@ -493,7 +496,7 @@ class Sheerka(Concept): c.get_hints().recognized_by = rec_by c.get_hints().is_instance = is_inst if is_eval is None: - c.get_hints().is_evaluated = len(c.get_metadata().variables) > 0 + c.get_hints().is_evaluated = len(c.get_metadata().parameters) > 0 else: c.get_hints().is_evaluated = is_eval return c @@ -581,7 +584,12 @@ class Sheerka(Concept): # otherwise, create another instance concept = self.builtin_cache[key]() if key in self.builtin_cache else Concept() concept.update_from(template, update_value=False) - # concept.freeze_definition_hash() + + # set the default values is needed + for var_name, var_value in concept.get_metadata().variables: + if isinstance(var_value, Concept): + concept.set_value(var_name, var_value) + # Note that the concept is not considered as evaluated. The body may need to be computed if len(kwargs) == 0: return concept diff --git a/src/core/sheerka/services/SheerkaConceptManager.py b/src/core/sheerka/services/SheerkaConceptManager.py index 0133624..4ddf427 100644 --- a/src/core/sheerka/services/SheerkaConceptManager.py +++ b/src/core/sheerka/services/SheerkaConceptManager.py @@ -295,7 +295,7 @@ class SheerkaConceptManager(BaseService): # 'props' : {: } of properties to add/update, # 'variables': {: } of variables to add/update, # } - # if the already exists, the entry is updated, otherwise a new value is created + # for variables, if the already exists, the entry is updated, otherwise a new value is created # for props, if the already exists, a new entry is added to the set # # to_remove = { @@ -391,9 +391,10 @@ class SheerkaConceptManager(BaseService): sheerka.publish(context, EVENT_CONCEPT_DELETED, concept) return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.SUCCESS)) - def set_attr(self, concept, attribute, value): + def set_attr(self, context, concept, attribute, value): """ Modifies an attribute of a concept (concept.values) + :param context: :param concept: :param attribute: :param value: @@ -402,8 +403,16 @@ class SheerkaConceptManager(BaseService): ensure_concept(concept) attr = attribute.str_id if isinstance(attribute, Concept) else attribute + old_value = concept.get_value(attr) - if (old_value := concept.get_value(attr)) is not NotInit: + if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): + old_value = self.sheerka.get_by_id(concept.id).get_value(attr) + else: + old_value = concept.get_value(attr) + + # Caution, creating a list when a set_attr is called for the second time is not always + # what is expected + if old_value is not NotInit: if old_value == value: return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS)) @@ -413,8 +422,15 @@ class SheerkaConceptManager(BaseService): else: value = [old_value, value] - concept.set_value(attr, value) - return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS)) + if context.in_context(BuiltinConcepts.EVAL_GLOBAL_TRUTH_REQUESTED): + to_add = {"variables": {attr: value}} + res = self.sheerka.modify_concept(context, concept, to_add, modify_source=True) + concept.set_value(attr, value) + return res + else: + + concept.set_value(attr, value) + return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS)) def get_attr(self, concept, attribute): """ @@ -857,6 +873,7 @@ class SheerkaConceptManager(BaseService): concept.set_bnf(None) ensure_bnf(context, concept) + self.recompute_concept_parameters(context, concept) concept.init_key() return @@ -1252,3 +1269,27 @@ class SheerkaConceptManager(BaseService): # update the compiled regex self.compiled_concepts_by_regex.clear() self.compiled_concepts_by_regex.extend(compiled_by_first_regex) + + @staticmethod + def recompute_concept_parameters(context, concept): + concept.get_metadata().parameters.clear() + + if concept.get_metadata().definition_type == DEFINITION_TYPE_DEF: + tokens = list(Tokenizer(concept.get_metadata().definition, yield_eof=False)) + else: + tokens = list(Tokenizer(concept.get_metadata().name, yield_eof=False)) + + # all variables that appear in the name are concept parameters + if len(tokens) > 0: + variables = [p[0] for p in concept.get_metadata().variables] + for token in [t for t in tokens if t.value in variables]: + concept.get_metadata().parameters.append(token.value) + + # for bnf concept, use the visitor to extract parameters + if concept.get_bnf(): + from evaluators.DefConceptEvaluator import ConceptOrRuleVariableVisitor, ParameterVariable + visitor = ConceptOrRuleVariableVisitor(context) + visitor.visit(concept.get_bnf()) + for variable in [v for v in visitor.variables if isinstance(v, ParameterVariable)]: + if variable.name not in concept.get_metadata().parameters: + concept.get_metadata().parameters.append(variable.name) diff --git a/src/core/sheerka/services/SheerkaEvaluateConcept.py b/src/core/sheerka/services/SheerkaEvaluateConcept.py index 0ec76c3..76bc99e 100644 --- a/src/core/sheerka/services/SheerkaEvaluateConcept.py +++ b/src/core/sheerka/services/SheerkaEvaluateConcept.py @@ -432,15 +432,18 @@ class SheerkaEvaluateConcept(BaseService): if default_value is None: continue - if not isinstance(default_value, str): - raise Exception("Invalid concept init. variable metadata must be a string") + if not isinstance(default_value, (str, Concept)): + raise Exception("Invalid concept init. variable metadata must be a string (or a concept)") - concept.get_compiled()[var_name] = self.get_asts(context, - self.NAME, - default_value, - concept, - var_name, - possible_variables) + if isinstance(default_value, Concept): + concept.get_compiled()[var_name] = default_value.copy() + else: + concept.get_compiled()[var_name] = self.get_asts(context, + self.NAME, + default_value, + concept, + var_name, + possible_variables) def resolve(self, context, @@ -740,8 +743,6 @@ class SheerkaEvaluateConcept(BaseService): # TODO : Validate the POST condition # - concept.init_key() # Necessary for old unit tests. To remove someday - if ConceptParts.BODY in all_metadata_to_eval and not failed_to_evaluate_body: concept.get_hints().is_evaluated = True diff --git a/src/evaluators/DefConceptEvaluator.py b/src/evaluators/DefConceptEvaluator.py index b6b221b..f7d97c5 100644 --- a/src/evaluators/DefConceptEvaluator.py +++ b/src/evaluators/DefConceptEvaluator.py @@ -74,27 +74,63 @@ class CertainVariable(ConceptEvaluatorVariable): return hash(("PossibleVariable", self.name)) +@dataclass(eq=True, frozen=True) +class ParameterVariable(ConceptEvaluatorVariable): + """ + A parameter variable (for BNF concepts) are variables that are mandatory for the resolution of the concept + + example: + def concept plus from bnf number=n1 plus number=n2 # n1 and n2 are parameters + def concept plus from bnf number plus number # number is a parameter + def concept inc from bnf (one|two)=n1 plus 1 # n1 is a parameter + def concept inc from bnf (one|two) plus 1 # (one|two) is a parameter (but with no associated variable name) + """ + is_a_variable: bool # True is the parameter has an associated variable name + + def __hash__(self): + return hash(("ParameterVariable", self.name, self.is_a_variable)) + + class ConceptOrRuleVariableVisitor(ParsingExpressionVisitor): """ Gets the concepts referenced by BNF If a rule_name is given, it will also be considered as a potential property """ - def __init__(self): + def __init__(self, context): super().__init__() + self.context = context self.variables = [] + self.counter = 1 + + def _get_parameter_name(self, node, prefix): + if not node.rule_name: + name = f"{prefix}{self.counter}" + self.counter += 1 + is_a_variable = False + else: + name = node.rule_name + is_a_variable = True + return name, is_a_variable def visit_ConceptExpression(self, node): - if node.rule_name: - self.variables.append(CertainVariable(node.rule_name)) - elif isinstance(node.concept, Concept): - self.variables.append(CertainVariable(node.concept.name)) + if isinstance(node.concept, Concept): + if self.context.sheerka.isaset(self.context, node.concept): + parameter_name, is_a_variable = self._get_parameter_name(node, "__ConceptExpression__") + self.variables.append(ParameterVariable(parameter_name, is_a_variable)) + else: + self.variables.append(CertainVariable(node.rule_name or node.concept.name)) else: - self.variables.append(CertainVariable(node.concept)) + self.variables.append(CertainVariable(node.rule_name or node.concept)) def visit_VariableExpression(self, node): self.variables.append(MandatoryVariable(node.rule_name)) + def visit_OrderedChoice(self, node): + parameter_name, is_a_variable = self._get_parameter_name(node, "__OrderedChoice__") + self.variables.append(ParameterVariable(parameter_name, is_a_variable)) + self.visit_children(node) + def visit_all(self, node): if node.rule_name: self.variables.append(CertainVariable(node.rule_name)) @@ -127,7 +163,8 @@ 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 = [] + parameter_variables = [] # BNF variables known as concept parameter. Use a list to keep the order + certain_variables = [] # names that we know for sure that there are variables skip_variables_resolution = False concept = Concept(str(def_concept_node.name)) @@ -164,16 +201,23 @@ class DefConceptEvaluator(OneReturnValueEvaluator): # 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): - mandatory_variables.add(p.name) - elif isinstance(p, CertainVariable): - certain_variables.append(p.name) + if isinstance(p, ParameterVariable): + parameter_variables.append(p.name) + if p.is_a_variable: + variables_found.add(p.name) + certain_variables.append(p.name) + else: + variables_found.add(p.name) + if isinstance(p, MandatoryVariable): + mandatory_variables.add(p.name) + elif isinstance(p, CertainVariable): + certain_variables.append(p.name) # 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) + concept.get_metadata().parameters.append(name_part) # check that all mandatory variables are defined in the name # KSI: 2021-02-17 @@ -190,6 +234,10 @@ class DefConceptEvaluator(OneReturnValueEvaluator): if p not in concept.values(): concept.def_var(p, None) + for p in parameter_variables: + if p not in concept.get_metadata().parameters: + concept.get_metadata().parameters.append(p) + # initialize the key key_source = def_concept_node.definition.tokens if \ def_concept_node.definition_type == DEFINITION_TYPE_DEF else \ @@ -251,7 +299,7 @@ class DefConceptEvaluator(OneReturnValueEvaluator): # case of BNF # if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, ParsingExpression): - visitor = ConceptOrRuleVariableVisitor() + visitor = ConceptOrRuleVariableVisitor(context) visitor.visit(ret_value.value.value) debugger.debug_var("names", visitor.variables, hint="from BNF") return visitor.variables diff --git a/src/parsers/BnfDefinitionParser.py b/src/parsers/BnfDefinitionParser.py index f521727..ef71a5e 100644 --- a/src/parsers/BnfDefinitionParser.py +++ b/src/parsers/BnfDefinitionParser.py @@ -11,10 +11,10 @@ class BnfDefinitionParser(BaseParser): """ Parser used to transform literal into ParsingExpression example : - a | b, c -> Sequence(OrderedChoice(a, b) ,c) + a | b c -> Sequence(OrderedChoice(a, b), c) '|' (pipe) is used for OrderedChoice - ',' (comma) is used for Sequence + ' ' space is used for Sequence '?' (question mark) is used for Optional '*' (star) is used for ZeroOrMore '+' (plus) is used for OneOrMore diff --git a/src/parsers/BnfNodeParser.py b/src/parsers/BnfNodeParser.py index 3bce7c0..a39879b 100644 --- a/src/parsers/BnfNodeParser.py +++ b/src/parsers/BnfNodeParser.py @@ -1165,6 +1165,9 @@ class ParsingExpressionVisitor: if hasattr(self, "visit_all"): self.visit_all(parsing_expression) + self.visit_children(parsing_expression) + + def visit_children(self, parsing_expression): for node in self.get_nodes(parsing_expression): if isinstance(node, Concept): res = self.visit(ConceptExpression(node.key or node.name)) diff --git a/src/parsers/SequenceNodeParser.py b/src/parsers/SequenceNodeParser.py index f1c12e6..091de7c 100644 --- a/src/parsers/SequenceNodeParser.py +++ b/src/parsers/SequenceNodeParser.py @@ -231,7 +231,7 @@ class SequenceNodeParser(BaseNodeParser): :param concept: :return: """ - return len(concept.get_metadata().variables) == 0 \ + return len(concept.get_metadata().parameters) == 0 \ and concept.get_metadata().definition_type != DEFINITION_TYPE_BNF def get_concepts(self, token, to_keep, custom=None, to_map=None, strip_quotes=False): diff --git a/src/parsers/SyaNodeParser.py b/src/parsers/SyaNodeParser.py index 350c80d..fbb39d7 100644 --- a/src/parsers/SyaNodeParser.py +++ b/src/parsers/SyaNodeParser.py @@ -199,7 +199,7 @@ class SyaConceptParserHelper: self.expected = temp self.eat_token(first_keyword_found) # remove the first token - self.tokens.append(first_keyword_found) + self.tokens.append(first_keyword_found) # and add it to the list of tokens eaten def is_matched(self): return len(self.expected) == 0 @@ -554,7 +554,7 @@ class InFixToPostFix: def nb_expected_parameters(expected): """ - Count the number of successive variables that are expected + Count the number of successive variables that are still expected :param expected: :return: """ @@ -1228,10 +1228,10 @@ class SyaNodeParser(BaseNodeParser): :param concept: :return: """ - # We only concepts that has parameter (refuse atoms) + # We only considers concepts that has parameter variables (refuse atoms) # Bnf definitions are not supposed to be managed by this parser either - return len( - concept.get_metadata().variables) > 0 and concept.get_metadata().definition_type != DEFINITION_TYPE_BNF + return (concept.get_metadata().definition_type != DEFINITION_TYPE_BNF and + len(concept.get_metadata().parameters) > 0) def infix_to_postfix(self, context, parser_input: ParserInput): """ @@ -1368,6 +1368,14 @@ class SyaNodeParser(BaseNodeParser): concept = sheerka.new_from_template(item.concept, item.concept.key) concept_metadata = [] for param_index in reversed(range(len(concept.get_metadata().variables))): + param_name = concept.get_metadata().variables[param_index][0] + + if param_name not in concept.get_metadata().parameters: + # This is not a real concept parameter, but a concept variable + # just copy its default value + concept_metadata.append(concept.get_metadata().variables[param_index]) + continue + inner_item = self.postfix_to_item(sheerka, postfixed) if inner_item.start < start: start = inner_item.start @@ -1376,7 +1384,6 @@ class SyaNodeParser(BaseNodeParser): has_unrecognized |= isinstance(inner_item, (UnrecognizedTokensNode, SourceCodeWithConceptNode)) or \ hasattr(inner_item, "has_unrecognized") and inner_item.has_unrecognized - param_name = concept.get_metadata().variables[param_index][0] param_value = inner_item.concept if hasattr(inner_item, "concept") else \ [inner_item.return_value] if isinstance(inner_item, SourceCodeNode) else \ inner_item diff --git a/src/repl/SheerkaPromptCompleter.py b/src/repl/SheerkaPromptCompleter.py index b0492b8..44a10fe 100644 --- a/src/repl/SheerkaPromptCompleter.py +++ b/src/repl/SheerkaPromptCompleter.py @@ -7,7 +7,7 @@ from dataclasses import dataclass from prompt_toolkit.completion import Completer, Completion from core.concept import Concept -from core.sheerka.Sheerka import EXIT_COMMANDS +from core.sheerka.Sheerka import EXIT_COMMANDS, CONTEXT_HINT_COMMANDS from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager from core.sheerka.services.SheerkaFunctionsParametersHistory import SheerkaFunctionsParametersHistory from core.tokenizer import Tokenizer, TokenKind, LexerError @@ -154,6 +154,9 @@ class SheerkaPromptCompleter(Completer): "builtin", ["context"])) + for name, display in CONTEXT_HINT_COMMANDS: + self.builtins.append(CompletionDesc(name, display, "context")) + self.exit_commands = [CompletionDesc(c, c, "command") for c in EXIT_COMMANDS] self.globals = {k: v.method for k, v in self.sheerka.sheerka_methods.items()} @@ -313,11 +316,12 @@ class SheerkaPromptCompleter(Completer): function_name = name + "(" signature = inspect.signature(function) params_count = len([p for p in signature.parameters if p not in skip_params]) - if params_count == 0: function_name += ")" + return CompletionDesc(function_name, name, meta_display) + @staticmethod def inside_function(text, pos): bracket_count = 0 diff --git a/tests/BaseTest.py b/tests/BaseTest.py index 40b73a7..c739525 100644 --- a/tests/BaseTest.py +++ b/tests/BaseTest.py @@ -6,6 +6,7 @@ from core.concept import Concept, DEFINITION_TYPE_BNF, DEFINITION_TYPE_DEF, free from core.rule import Rule, ACTION_TYPE_PRINT, ACTION_TYPE_EXEC from core.sheerka.ExecutionContext import ExecutionContext from core.sheerka.Sheerka import Sheerka +from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager from parsers.BnfDefinitionParser import BnfDefinitionParser from parsers.BnfNodeParser import StrMatch @@ -47,6 +48,7 @@ class InitTestHelper: raise Exception(f"Error in bnf definition '{c.get_metadata().definition}'", self.sheerka.get_errors(self.context, res)) + self._update_concept_parameters(c) if create_new: self.sheerka.create_new_concept(self.context, c) else: @@ -99,6 +101,12 @@ class InitTestHelper: return self + def _update_concept_parameters(self, concept): + if concept.get_metadata().parameters: + return + + SheerkaConceptManager.recompute_concept_parameters(self.context, concept) + class BaseTest: def get_sheerka(self, **kwargs) -> Sheerka: diff --git a/tests/core/test_SheerkaConceptManager.py b/tests/core/test_SheerkaConceptManager.py index 286ec2a..23a22d4 100644 --- a/tests/core/test_SheerkaConceptManager.py +++ b/tests/core/test_SheerkaConceptManager.py @@ -4,7 +4,7 @@ from cache.CacheManager import ConceptNotFound from core.builtin_concepts import BuiltinConcepts from core.builtin_helpers import ensure_bnf from core.concept import PROPERTIES_TO_SERIALIZE, Concept, DEFINITION_TYPE_DEF, get_concept_attrs, \ - DEFINITION_TYPE_BNF + DEFINITION_TYPE_BNF, ConceptParts from core.global_symbols import NotInit, NotFound, SyaAssociativity, CONCEPT_COMPARISON_CONTEXT from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager, NoModificationFound, ForbiddenAttribute, \ UnknownAttribute, CannotRemoveMeta, ValueNotFound, ConceptIsReferenced, NoFirstTokenError @@ -494,13 +494,13 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): to_add = {"meta": {"name": "b bar c d"}, "variables": {"c": None, "d": None}} - to_remove = {"variables": ["a"]} - - res = sheerka.modify_concept(context, foo, to_add, to_remove) + res = sheerka.modify_concept(context, foo, to_add) new_concept = res.body.body assert res.status - assert new_concept.key == "__var__0 bar __var__1 __var__2" + assert new_concept.key == "__var__1 bar __var__2 __var__3" + assert new_concept.get_metadata().parameters == ["b", "c", "d"] + assert new_concept.get_metadata().variables == [("a", None), ("b", None), ("c", None), ("d", None)] def test_key_is_modified_when_modifying_the_definition(self): sheerka, context, foo = self.init_concepts( @@ -790,13 +790,101 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): prop = Concept("property") bar = Concept("bar") - res = sheerka.set_attr(foo, prop, bar) + res = sheerka.set_attr(context, foo, prop, bar) assert res.status assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.get_attr(foo, prop) == bar - def test_i_setting_twice_the_same_property_creates_a_list(self): + def test_i_can_get_and_set_attribute_when_global_truth_is_set(self): + sheerka, context, foo, adjective, color, red, brightness, dark = self.init_test(cache_only=False).with_concepts( + "foo", + "adjective", + "color", + "red", + "brightness", + "dark", + create_new=True).unpack() + service = sheerka.services[SheerkaConceptManager.NAME] + global_truth_context = self.get_context(sheerka, global_truth=True) + + color_instance = sheerka.new("color", body=sheerka.new("red")) + sheerka.set_attr(context, color_instance, brightness, sheerka.new("dark")) + sheerka.set_isa(context, color_instance, sheerka.new(adjective)) + + adjective_instance = sheerka.new("adjective") + adjective_instance.set_value(ConceptParts.BODY, color_instance) + + foo_instance = sheerka.new("foo") + res = sheerka.set_attr(global_truth_context, foo_instance, sheerka.new("adjective"), adjective_instance) + assert res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.NEW_CONCEPT) + + # foo.adjective is modified + resolved_adjective = sheerka.get_attr(foo_instance, sheerka.new("adjective")) + assert sheerka.isinstance(resolved_adjective, "adjective") + resolved_color = resolved_adjective.body + assert sheerka.isinstance(resolved_color, "color") + assert sheerka.isa(resolved_color, adjective) + assert sheerka.get_attr(resolved_color, brightness) == dark + assert resolved_color.body == red + + # foo default variable value is modified + adjective_from_default_var = foo_instance.get_metadata().variables[0][1] + assert sheerka.isinstance(adjective_from_default_var, "adjective") + color_from_default_var = resolved_adjective.body + assert sheerka.isinstance(color_from_default_var, "color") + assert sheerka.isa(color_from_default_var, adjective) + assert sheerka.get_attr(color_from_default_var, brightness) == dark + assert color_from_default_var.body == red + + # new instance are update + new_instance = sheerka.new("foo") + resolved_adjective = sheerka.get_attr(new_instance, sheerka.new("adjective")) + assert sheerka.isinstance(resolved_adjective, "adjective") + resolved_color = resolved_adjective.body + assert sheerka.isinstance(resolved_color, "color") + assert sheerka.isa(resolved_color, adjective) + assert sheerka.get_attr(resolved_color, brightness) == dark + assert resolved_color.body == red + # No need to check the default variable value since the attribute is updated + + # make sure it's persisted in DB + sheerka.om.commit(context) + from_db = sheerka.om.current_sdp().get(service.CONCEPTS_BY_ID_ENTRY, foo.id) + # concepts values are never persisted in DB. + # only check the default value of the variable + adjective_from_default_var = from_db.get_metadata().variables[0][1] + assert sheerka.isinstance(adjective_from_default_var, "adjective") + color_from_default_var = resolved_adjective.body + assert sheerka.isinstance(color_from_default_var, "color") + assert sheerka.isa(color_from_default_var, adjective) + assert sheerka.get_attr(color_from_default_var, brightness) == dark + assert color_from_default_var.body == red + + # check that we can access the values + assert foo_instance.values() == {'c:adjective|1002:': adjective_instance} + assert new_instance.values() == {'c:adjective|1002:': adjective_instance} + assert from_db.values() == {'c:adjective|1002:': NotInit} + assert foo.values() == {'c:adjective|1002:': NotInit} + + def test_i_can_set_attr_twice_is_the_second_time_global_truth_is_true(self): + sheerka, context, foo, prop, value = self.init_test(cache_only=False).with_concepts( + "foo", + "prop", + "value", + create_new=True).unpack() + global_truth_context = self.get_context(sheerka, global_truth=True) + + foo_instance = sheerka.new("foo") + sheerka.set_attr(context, foo_instance, prop, value) + + sheerka.set_attr(global_truth_context, foo_instance, prop, value) + + new_instance = sheerka.new("foo") + assert sheerka.get_attr(new_instance, prop) == value + + def test_setting_twice_the_same_property_creates_a_list(self): sheerka, context = self.init_concepts() foo = Concept("foo") prop = Concept("property") @@ -804,22 +892,22 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): baz = Concept("baz") qux = Concept("qux") - res = sheerka.set_attr(foo, prop, bar) + res = sheerka.set_attr(context, foo, prop, bar) assert res.status assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.get_attr(foo, prop) == bar - res = sheerka.set_attr(foo, prop, bar) # again, same value as no effect + res = sheerka.set_attr(context, foo, prop, bar) # again, same value as no effect assert res.status assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.get_attr(foo, prop) == bar - res = sheerka.set_attr(foo, prop, baz) + res = sheerka.set_attr(context, foo, prop, baz) assert res.status assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.get_attr(foo, prop) == [bar, baz] - res = sheerka.set_attr(foo, prop, qux) + res = sheerka.set_attr(context, foo, prop, qux) assert res.status assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.get_attr(foo, prop) == [bar, baz, qux] @@ -1241,7 +1329,7 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): prop = Concept("property") bar = Concept("bar") - sheerka.set_attr(foo, prop, bar) + sheerka.set_attr(context, foo, prop, bar) assert sheerka.smart_get_attr(foo, prop) == bar def test_i_can_smart_get_attr_when_simple_isa(self): @@ -1256,7 +1344,7 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): color_instance = sheerka.new(color, body=red) adjective_instance = sheerka.new(adjective, body=color_instance) table_instance = sheerka.new(table) - sheerka.set_attr(table_instance, adjective, adjective_instance) + sheerka.set_attr(context, table_instance, adjective, adjective_instance) assert sheerka.smart_get_attr(table_instance, color) == color_instance assert sheerka.objvalue(sheerka.smart_get_attr(table_instance, color)) == red @@ -1276,7 +1364,7 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): color_instance = sheerka.new(color, body=reddish_instance) adjective_instance = sheerka.new(adjective, body=color_instance) table_instance = sheerka.new(table) - sheerka.set_attr(table_instance, adjective, adjective_instance) + sheerka.set_attr(context, table_instance, adjective, adjective_instance) assert sheerka.smart_get_attr(table_instance, reddish_instance) == reddish_instance assert sheerka.objvalue(sheerka.smart_get_attr(table_instance, color)) == red @@ -1296,12 +1384,12 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): # add red color red_color_instance = sheerka.new(color, body=red) red_adjective_instance = sheerka.new(adjective, body=red_color_instance) - sheerka.set_attr(table_instance, adjective, red_adjective_instance) + sheerka.set_attr(context, table_instance, adjective, red_adjective_instance) # add blue color blue_color_instance = sheerka.new(color, body=blue) blue_adjective_instance = sheerka.new(adjective, body=blue_color_instance) - sheerka.set_attr(table_instance, adjective, blue_adjective_instance) + sheerka.set_attr(context, table_instance, adjective, blue_adjective_instance) res = sheerka.smart_get_attr(table_instance, color) assert res == [red_color_instance, blue_color_instance] @@ -1317,7 +1405,7 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): color_instance = sheerka.new(color, body=red) table_instance = sheerka.new(table) - sheerka.set_attr(table_instance, color, color_instance) + sheerka.set_attr(context, table_instance, color, color_instance) assert sheerka.smart_get_attr(table_instance, adjective) == color_instance @@ -1336,9 +1424,9 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): table_instance = sheerka.new(table) color_instance = sheerka.new(color, body=red) - sheerka.set_attr(table_instance, color, color_instance) + sheerka.set_attr(context, table_instance, color, color_instance) size_instance = sheerka.new(size, body=large) - sheerka.set_attr(table_instance, size, size_instance) + sheerka.set_attr(context, table_instance, size, size_instance) assert sheerka.smart_get_attr(table_instance, adjective) == [color_instance, size_instance] @@ -1355,8 +1443,8 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): color_instance = sheerka.new(color, body=red) adjective_instance = sheerka.new(adjective, body=color_instance) table_instance = sheerka.new(table) - sheerka.set_attr(table_instance, adjective, adjective_instance) - sheerka.set_attr(table_instance, color, blue) # set direct color value + sheerka.set_attr(context, table_instance, adjective, adjective_instance) + sheerka.set_attr(context, table_instance, color, blue) # set direct color value assert sheerka.smart_get_attr(table_instance, color) == blue @@ -1372,8 +1460,8 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): color_instance = sheerka.new(color, body=red) table_instance = sheerka.new(table) - sheerka.set_attr(table_instance, color, color_instance) - sheerka.set_attr(table_instance, adjective, blue) # set direct color value + sheerka.set_attr(context, table_instance, color, color_instance) + sheerka.set_attr(context, table_instance, adjective, blue) # set direct color value assert sheerka.smart_get_attr(table_instance, adjective) == blue @@ -1397,7 +1485,7 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): color_instance = sheerka.new(color, body=red) adjective_instance = sheerka.new(adjective, body=color_instance) table_instance = sheerka.new(table) - sheerka.set_attr(table_instance, adjective, adjective_instance) + sheerka.set_attr(context, table_instance, adjective, adjective_instance) res = sheerka.smart_get_attr(table_instance, color) assert sheerka.isinstance(res, BuiltinConcepts.NOT_FOUND) @@ -1417,7 +1505,7 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): table_instance = sheerka.new(table) color_instance = sheerka.new(color, body=red) adjective_instance = sheerka.new(adjective, body=color_instance) - sheerka.set_attr(table_instance, adjective, adjective_instance) + sheerka.set_attr(context, table_instance, adjective, adjective_instance) res = sheerka.smart_get_attr(table_instance, size) assert sheerka.isinstance(res, BuiltinConcepts.NOT_FOUND) diff --git a/tests/core/test_SheerkaDebugManager.py b/tests/core/test_SheerkaDebugManager.py index d52276a..717e264 100644 --- a/tests/core/test_SheerkaDebugManager.py +++ b/tests/core/test_SheerkaDebugManager.py @@ -533,7 +533,7 @@ class TestSheerkaDebugManager(TestUsingMemoryBasedSheerka): def test_i_can_inspect_object_with_concept(self): sheerka, context, foo = self.init_concepts("foo") foo.values() # freeze known attributes - sheerka.set_attr(foo, "new_var", "var_value") + sheerka.set_attr(context, foo, "new_var", "var_value") dummy = DummyObj(foo, "value") res = sheerka.inspect(context, dummy) @@ -542,7 +542,7 @@ class TestSheerkaDebugManager(TestUsingMemoryBasedSheerka): def test_i_can_inspect_object_with_concept_when_as_bag(self): sheerka, context, foo = self.init_concepts("foo") foo.values() # freeze known attributes - sheerka.set_attr(foo, "new_var", "var_value") + sheerka.set_attr(context, foo, "new_var", "var_value") dummy = DummyObj(foo, "value") res = sheerka.inspect(context, dummy, as_bag=True) @@ -557,7 +557,7 @@ class TestSheerkaDebugManager(TestUsingMemoryBasedSheerka): def test_i_can_inspect_object_with_concept_when_values(self): sheerka, context, foo = self.init_concepts("foo") foo.values() # freeze known attributes - sheerka.set_attr(foo, "new_var", "var_value") + sheerka.set_attr(context, foo, "new_var", "var_value") dummy = DummyObj(foo, "value") res = sheerka.inspect(context, dummy, values=True) @@ -629,7 +629,7 @@ class TestSheerkaDebugManager(TestUsingMemoryBasedSheerka): def test_i_can_inspect_when_a_property_does_not_exist(self): sheerka, context, foo = self.init_concepts("foo") foo.values() # freeze known attributes - sheerka.set_attr(foo, "new_var", "var_value") + sheerka.set_attr(context, foo, "new_var", "var_value") dummy = DummyObj(foo, "value") res = sheerka.inspect(context, dummy, "#type#", "fake", "a", "b") @@ -641,7 +641,7 @@ class TestSheerkaDebugManager(TestUsingMemoryBasedSheerka): def test_i_can_inspect_when_properties_are_specified_several_times(self): sheerka, context, foo = self.init_concepts("foo") foo.values() # freeze known attributes - sheerka.set_attr(foo, "new_var", "var_value") + sheerka.set_attr(context, foo, "new_var", "var_value") dummy = DummyObj(foo, "value") res = sheerka.inspect(context, dummy, "#type#", "a", "b", "a") diff --git a/tests/core/test_SheerkaEvaluateConcept.py b/tests/core/test_SheerkaEvaluateConcept.py index f5367d4..0e9c2ef 100644 --- a/tests/core/test_SheerkaEvaluateConcept.py +++ b/tests/core/test_SheerkaEvaluateConcept.py @@ -314,7 +314,7 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka): def test_i_can_evaluate_when_variable_asts_is_a_list(self): sheerka = self.get_sheerka() - foo = Concept("foo", body="1") + foo = Concept("foo", body="1").init_key() concept = Concept("to_eval").def_var("prop") concept.get_compiled()["prop"] = [foo, DoNotResolve("1")] @@ -467,14 +467,6 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka): evaluated = sheerka.evaluate_concept(self.get_context(sheerka, True), concept) assert sheerka.isinstance(evaluated, BuiltinConcepts.CONCEPT_EVAL_ERROR) - def test_key_is_initialized_by_evaluation(self): - sheerka = self.get_sheerka() - - concept = Concept("foo") - evaluated = sheerka.evaluate_concept(self.get_context(sheerka, True), concept) - - assert evaluated.key == concept.init_key().key - @pytest.mark.parametrize("where_clause, expected, expected_prop, expected_body", [ ("True", True, None, NotInit), ("False", False, ConceptParts.WHERE, NotInit), diff --git a/tests/core/test_SheerkaMemory.py b/tests/core/test_SheerkaMemory.py index b06f146..82a1d81 100644 --- a/tests/core/test_SheerkaMemory.py +++ b/tests/core/test_SheerkaMemory.py @@ -253,7 +253,7 @@ class TestSheerkaMemory(TestUsingMemoryBasedSheerka): instantiated_foo = sheerka.new(foo, x="value1") instantiated_one = ensure_evaluated(context, sheerka.new(inner_value)) instantiated_value = sheerka.new(value, x=instantiated_one) - sheerka.set_attr(instantiated_foo, prop, instantiated_value) + sheerka.set_attr(context, instantiated_foo, prop, instantiated_value) sheerka.add_to_memory(context, "foo", instantiated_foo) sheerka.om.commit(context) diff --git a/tests/core/test_concept.py b/tests/core/test_concept.py index 7655221..8e587f3 100644 --- a/tests/core/test_concept.py +++ b/tests/core/test_concept.py @@ -64,10 +64,10 @@ def test_i_can_serialize(): :return: """ concept = Concept( - name="concept_name", + name="concept_name a", is_builtin=True, is_unique=True, - key="concept_key", + key=None, body="definition of the body", where="definition of the where", pre="definition of the pre", @@ -78,6 +78,8 @@ def test_i_can_serialize(): desc="this this the desc", id="123456" ).def_var("a", "10").def_var("b", None) + concept.get_metadata().parameters = ["a"] + concept.init_key() to_dict = concept.to_dict() assert to_dict == { @@ -88,14 +90,15 @@ def test_i_can_serialize(): 'id': '123456', 'is_builtin': True, 'is_unique': True, - 'key': 'concept_key', - 'name': 'concept_name', + 'key': 'concept_name __var__0', + 'name': 'concept_name a', 'post': 'definition of the post', 'pre': 'definition of the pre', 'ret': "concept to return", 'props': {}, 'variables': [('a', "10"), ('b', None)], - 'where': 'definition of the where' + 'where': 'definition of the where', + 'parameters': ['a'], } @@ -302,3 +305,33 @@ def test_i_can_manage_instance_attributes(): assert foo.values() == {"x": "value for x", "y": "value for y"} assert foo.get_all_attributes() == ["x", "y"] assert ALL_ATTRIBUTES == {"foo_id": ["x"]} + + +def test_i_can_init_key_and_compute_parameters(): + concept = Concept("foo x").def_var("x").init_key() + assert concept.key == "foo __var__0" + + concept = Concept("foo").def_var("x").init_key() + assert concept.key == "foo" + + concept = Concept("foo a b").def_var("a").def_var("b").init_key() + assert concept.key == "foo __var__0 __var__1" + + concept = Concept("foo a b").def_var("b").def_var("a").init_key() + assert concept.key == "foo __var__1 __var__0" + + concept = Concept("foo a b").def_var("a").init_key() + assert concept.key == "foo __var__0 b" + + concept = Concept("foo a b").def_var("b").init_key() + assert concept.key == "foo a __var__0" + + concept = Concept("foo a").def_var("a").def_var("b").init_key() + assert concept.key == "foo __var__0" + + concept = Concept("foo b").def_var("a").def_var("b").init_key() + assert concept.key == "foo __var__1" + + concept = Concept("plus", definition_type=DEFINITION_TYPE_DEF, definition="a plus b").def_var("a").def_var("b") + concept.init_key() + assert concept.key == "__var__0 plus __var__1" diff --git a/tests/evaluators/test_ConceptEvaluator.py b/tests/evaluators/test_ConceptEvaluator.py index 2cccce6..ce716e2 100644 --- a/tests/evaluators/test_ConceptEvaluator.py +++ b/tests/evaluators/test_ConceptEvaluator.py @@ -38,7 +38,6 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka): assert result.value.get_value(ConceptParts.POST) == 4 assert result.value.get_value("a") == 5 assert result.value.get_value("b") == 6 - assert result.value.key == "foo" assert result.parents == [item] def test_body_is_returned_when_defined_and_requested(self): diff --git a/tests/evaluators/test_DefConceptEvaluator.py b/tests/evaluators/test_DefConceptEvaluator.py index 166388c..1559945 100644 --- a/tests/evaluators/test_DefConceptEvaluator.py +++ b/tests/evaluators/test_DefConceptEvaluator.py @@ -7,7 +7,7 @@ from core.concept import VARIABLE_PREFIX, Concept, DEFINITION_TYPE_BNF, DEFINITI from core.sheerka.services.SheerkaConceptManager import NoFirstTokenError from core.sheerka.services.SheerkaExecute import ParserInput from core.tokenizer import Tokenizer -from evaluators.DefConceptEvaluator import DefConceptEvaluator, PossibleVariable, CertainVariable +from evaluators.DefConceptEvaluator import DefConceptEvaluator, PossibleVariable, CertainVariable, ParameterVariable from parsers.BaseParser import BaseParser from parsers.BnfDefinitionParser import BnfDefinitionParser from parsers.BnfNodeParser import Sequence, StrMatch, ZeroOrMore, ConceptExpression, VariableExpression @@ -200,7 +200,7 @@ class TestDefConceptEvaluator(TestUsingMemoryBasedSheerka): def_ret_val = DefConceptParser().parse(context, ParserInput(text)) concept_definition = def_ret_val.value.body.definition - expected = [CertainVariable("unit"), CertainVariable("one"), CertainVariable("two")] + expected = [ParameterVariable("unit", True), CertainVariable("one"), CertainVariable("two")] assert DefConceptEvaluator.get_variables(context, concept_definition, []) == expected def test_i_can_recognize_variables_when_referencing_other_concepts(self): @@ -326,6 +326,84 @@ class TestDefConceptEvaluator(TestUsingMemoryBasedSheerka): assert evaluated.body.body.key == "foo2 __var__0" assert evaluated.body.body.get_metadata().variables == [("x", None)] + def test_i_can_eval_when_bnf_with_implicit_variables_from_named_ordered_choice(self): + sheerka, context, one, two = self.init_concepts("one", "two", create_new=True) + text = "def concept plus from bnf (one|two)=n1 'plus' (one|two)=n2 as n1 + n2" + def_ret_val = DefConceptParser().parse(context, ParserInput(text)) + evaluated = DefConceptEvaluator().eval(context, def_ret_val) + + assert evaluated.status + assert context.sheerka.isinstance(evaluated.body, BuiltinConcepts.NEW_CONCEPT) + + created_concept = evaluated.body.body + assert created_concept.get_metadata().name == "plus" + assert created_concept.get_metadata().key == "plus" + assert created_concept.get_metadata().definition == "(one|two)=n1 'plus' (one|two)=n2" + assert created_concept.get_metadata().definition_type == "bnf" + assert created_concept.get_metadata().variables == [('n1', None), ('one', None), ('two', None), ('n2', None)] + assert created_concept.get_metadata().parameters == ['n1', 'n2'] + + def test_i_can_eval_when_bnf_with_implicit_variables_from_unamed_ordered_choice(self): + sheerka, context, one, two, three = self.init_concepts("one", "two", "three", create_new=True) + text = "def concept choice from bnf (one|two) 'or' (two|three) 'or' (two|three)=c3" + def_ret_val = DefConceptParser().parse(context, ParserInput(text)) + evaluated = DefConceptEvaluator().eval(context, def_ret_val) + + assert evaluated.status + assert context.sheerka.isinstance(evaluated.body, BuiltinConcepts.NEW_CONCEPT) + + created_concept = evaluated.body.body + assert created_concept.get_metadata().name == "choice" + assert created_concept.get_metadata().key == "choice" + assert created_concept.get_metadata().definition == "(one|two) 'or' (two|three) 'or' (two|three)=c3" + assert created_concept.get_metadata().definition_type == "bnf" + assert created_concept.get_metadata().variables == [('one', None), ('two', None), ('three', None), ('c3', None)] + assert created_concept.get_metadata().parameters == ['__OrderedChoice__1', '__OrderedChoice__2', "c3"] + + def test_i_can_eval_when_bnf_with_implicit_variables_from_unordered_choice(self): + # as it's not possible to directly defined UnorderedChoice, we test isa concept + sheerka, context, one, two, number = self.init_concepts("one", "two", "number", create_new=True) + global_truth_context = self.get_context(global_truth=True) + sheerka.set_isa(global_truth_context, one, number) + sheerka.set_isa(global_truth_context, two, number) + + text = "def concept plus from bnf number=n1 'plus' number=n2 as n1 + n2" + def_ret_val = DefConceptParser().parse(context, ParserInput(text)) + evaluated = DefConceptEvaluator().eval(context, def_ret_val) + + assert evaluated.status + assert context.sheerka.isinstance(evaluated.body, BuiltinConcepts.NEW_CONCEPT) + + created_concept = evaluated.body.body + assert created_concept.get_metadata().name == "plus" + assert created_concept.get_metadata().key == "plus" + assert created_concept.get_metadata().definition == "number=n1 'plus' number=n2" + assert created_concept.get_metadata().definition_type == "bnf" + assert created_concept.get_metadata().variables == [('n1', None), ('n2', None)] + assert created_concept.get_metadata().parameters == ['n1', 'n2'] + + def test_i_can_eval_when_bnf_with_implicit_variables_from_unamed_unordered_choice(self): + # as it's not possible to directly defined UnorderedChoice, we test isa concept + sheerka, context, one, two, number = self.init_concepts("one", "two", "number", create_new=True) + global_truth_context = self.get_context(global_truth=True) + sheerka.set_isa(global_truth_context, one, number) + sheerka.set_isa(global_truth_context, two, number) + + text = "def concept plus from bnf number 'plus' number as number[0] + number[1]" + def_ret_val = DefConceptParser().parse(context, ParserInput(text)) + evaluated = DefConceptEvaluator().eval(context, def_ret_val) + + assert evaluated.status + assert context.sheerka.isinstance(evaluated.body, BuiltinConcepts.NEW_CONCEPT) + + created_concept = evaluated.body.body + assert created_concept.get_metadata().name == "plus" + assert created_concept.get_metadata().key == "plus" + assert created_concept.get_metadata().definition == "number 'plus' number" + assert created_concept.get_metadata().definition_type == "bnf" + assert created_concept.get_metadata().variables == [('number', None)] + assert created_concept.get_metadata().parameters == ['number'] + def test_i_can_eval_when_bnf_concept_with_regex(self): context = self.get_context() def_ret_val = DefConceptParser().parse(context, ParserInput("def concept hello a from bnf r'[a-z]+'=a 'hello'")) @@ -339,6 +417,8 @@ class TestDefConceptEvaluator(TestUsingMemoryBasedSheerka): assert created_concept.get_metadata().key == "hello __var__0" assert created_concept.get_metadata().definition == "r'[a-z]+'=a 'hello'" assert created_concept.get_metadata().definition_type == "bnf" + assert created_concept.get_metadata().variables == [('a', None)] + assert created_concept.get_metadata().parameters == ['a'] def test_i_can_eval_when_bnf_concept_with_variable(self): context = self.get_context() @@ -355,6 +435,8 @@ class TestDefConceptEvaluator(TestUsingMemoryBasedSheerka): assert created_concept.get_metadata().definition_type == "bnf" assert created_concept.get_metadata().variables == [("x", None)] assert created_concept._bnf == Sequence(StrMatch("hello"), VariableExpression("x")) + assert created_concept.get_metadata().variables == [('x', None)] + assert created_concept.get_metadata().parameters == ['x'] def test_i_can_eval_when_auto_eval_is_true(self): sheerka, context = self.init_test().unpack() diff --git a/tests/non_reg/test_sheerka_non_reg.py b/tests/non_reg/test_sheerka_non_reg.py index 2b6bd86..36a86a7 100644 --- a/tests/non_reg/test_sheerka_non_reg.py +++ b/tests/non_reg/test_sheerka_non_reg.py @@ -94,6 +94,7 @@ as: expected.get_metadata().id = "1001" expected.get_metadata().desc = None expected.get_metadata().variables = [("a", None), ("b", None)] + expected.get_metadata().parameters = ["a", "b"] expected.init_key() sheerka = self.get_sheerka(cache_only=False) @@ -134,6 +135,7 @@ as: res = sheerka.evaluate_user_input("def concept a xx b as a plus b") expected = Concept(name="a xx b", body="a plus b").def_var("a").def_var("b").init_key() expected.get_metadata().id = "1001" + expected.get_metadata().parameters = ["a", "b"] assert len(res) == 1 assert res[0].status @@ -1392,17 +1394,32 @@ as: "set_isa(color, adjective)", "def concept what is the x of y pre is_question() as smart_get_attr(y, x)", "def concept qualify x from bnf adjective x as set_attr(x, c:adjective:, adjective) ret x", - "eval a red short", ] sheerka = self.init_scenario(init, global_truth=True) + res = sheerka.evaluate_user_input("eval a red short") res = sheerka.evaluate_user_input("what is the color of the short ?") assert len(res) == 1 assert res[0].status assert res[0].value == "red" - res = sheerka.evaluate_user_input("eval a blue short") + sheerka.evaluate_user_input("eval a blue short") res = sheerka.evaluate_user_input("what is the color of the short ?") assert len(res) == 1 assert res[0].status assert res[0].value == "blue" + + def test_i_can_access_a_concept_after_a_global_set_attr(self): + init = [ + "def concept foo", + "def concept color", + "def concept red", + "global_truth(set_attr(foo, color, red))" + ] + + sheerka = self.init_scenario(init) + res = sheerka.evaluate_user_input("inspect(foo)") + + assert len(res) == 1 + assert res[0].status + diff --git a/tests/non_reg/test_sheerka_non_reg_out.py b/tests/non_reg/test_sheerka_non_reg_out.py index edb3f07..cc9c407 100644 --- a/tests/non_reg/test_sheerka_non_reg_out.py +++ b/tests/non_reg/test_sheerka_non_reg_out.py @@ -32,7 +32,7 @@ name : foo key : foo definition: None type : None -hash : 16f7fbb8bc509b8c652edaf3d0c0457d15a37f0a862fbe03fa357b0c77249c46 +hash : 632c9ac7f3a08777918aeee4bb53712caedc634a863ae39a641f69055ae2e238 body : 1 where : None pre : None @@ -45,7 +45,7 @@ name : foo key : foo definition: None type : None -hash : 7036cd5ffa9294d2e1dc9bf9c9bbe2783ace5cf7f423bfce9b28c8d33c0d1d0c +hash : 23a1a8bc5966982471aa19b8452e22d414a55d704431866e198570913d47fd7d body : 2 where : None pre : None @@ -71,7 +71,7 @@ name : foo key : foo definition: None type : None -hash : 16f7fbb8bc509b8c652edaf3d0c0457d15a37f0a862fbe03fa357b0c77249c46 +hash : 632c9ac7f3a08777918aeee4bb53712caedc634a863ae39a641f69055ae2e238 body : 1 where : None pre : None @@ -84,7 +84,7 @@ name : foo key : foo definition: None type : None -hash : e8dd1af1b6bc0eca0fb4a87a6fabb16655caa4b7a6ea9dbbd1f887757e6caf89 +hash : 60e442c59940a2616a3783d2e34f428f4a5ae456a88539bba9a5e87cf77060a6 body : 2 where : True pre : None diff --git a/tests/parsers/test_SequenceNodeParser.py b/tests/parsers/test_SequenceNodeParser.py index 96ee2e5..5e27934 100644 --- a/tests/parsers/test_SequenceNodeParser.py +++ b/tests/parsers/test_SequenceNodeParser.py @@ -5,7 +5,7 @@ from core.concept import Concept, DEFINITION_TYPE_DEF from core.sheerka.services.SheerkaExecute import ParserInput from parsers.SequenceNodeParser import SequenceNodeParser from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka -from tests.parsers.parsers_utils import compute_expected_array, CN, CNC, SCN, get_test_obj, compare_with_test_object, \ +from tests.parsers.parsers_utils import compute_expected_array, CN, SCN, get_test_obj, compare_with_test_object, \ UTN @@ -275,6 +275,26 @@ class TestSequenceNodeParser(TestUsingMemoryBasedSheerka): assert concept_found.get_hints().is_evaluated assert not concept_found.get_hints().is_instance + def test_i_can_parse_when_variable_is_not_a_parameter(self): + concepts_map = { + "foo": Concept("foo").def_var("a"), # 'a' is not a parameter (but a property like its color for ex) + } + + text, expected = "foo", ["foo"] + sheerka, context, parser = self.init_parser(concepts_map, create_new=True, use_sheerka=True) + res = parser.parse(context, ParserInput(text)) + wrapper = res.body + lexer_nodes = res.body.body + + assert res.status + + expected_array = compute_expected_array(concepts_map, text, expected) + assert sheerka.isinstance(wrapper, BuiltinConcepts.PARSER_RESULT) + compare_with_test_object(lexer_nodes, expected_array) + assert not lexer_nodes[0].concept.get_hints().is_evaluated + assert lexer_nodes[0].concept.get_hints().use_copy + assert lexer_nodes[0].concept.get_hints().is_instance + @pytest.mark.parametrize("text", [ "foo", f"foo one", diff --git a/tests/parsers/test_SyaNodeParser.py b/tests/parsers/test_SyaNodeParser.py index ce7de9a..10c5931 100644 --- a/tests/parsers/test_SyaNodeParser.py +++ b/tests/parsers/test_SyaNodeParser.py @@ -1114,6 +1114,33 @@ class TestSyaNodeParser(TestUsingMemoryBasedSheerka): # check metadata assert expected_concept.get_metadata().variables == [("a", "twenty one")] + def test_i_can_parse_when_all_variables_are_not_parameters(self): + my_map = { + "foo": Concept("foo a").def_var("a", "'default_a'").def_var("b", "'default_b'"), # 'b' is not a parameter + "bar": Concept("bar b").def_var("a", "'default_a'").def_var("b", "'default_b'"), # 'a' is not a parameter + "baz": Concept("baz") + } + sheerka, context, parser = self.init_parser(my_map) + + text = "foo baz" + res = parser.parse(context, ParserInput(text)) + assert res.status + assert len(res.body.body) == 1 + + expected_concept = res.body.body[0].concept + assert sheerka.isinstance(expected_concept.get_compiled()["a"], "baz") + assert "b" not in expected_concept.get_compiled() + assert expected_concept.get_metadata().variables == [("a", "baz"), ("b", "'default_b'")] + + text = "bar baz" + res = parser.parse(context, ParserInput(text)) + assert res.status + assert len(res.body.body) == 1 + expected_concept = res.body.body[0].concept + assert sheerka.isinstance(expected_concept.get_compiled()["b"], "baz") + assert "a" not in expected_concept.get_compiled() + assert expected_concept.get_metadata().variables == [("a", "'default_a'"), ("b", "baz")] + def test_i_can_parse_sequences(self): sheerka, context, parser = self.init_parser() @@ -1335,6 +1362,19 @@ class TestSyaNodeParser(TestUsingMemoryBasedSheerka): assert not res.status assert sheerka.isinstance(res.body, BuiltinConcepts.IS_EMPTY) + def test_i_cannot_parse_concept_when_variable_is_not_a_parameter(self): + my_map = { + "foo": Concept("foo").def_var("a") # 'a' is not a parameter (but a property like its color for ex) + } + sheerka, context, parser = self.init_parser(my_map) + + text = "foo" + res = parser.parse(context, ParserInput(text)) + + assert not res.status + assert sheerka.isinstance(res.body, BuiltinConcepts.NOT_FOR_ME) + assert res.body.body == text + @pytest.mark.parametrize("expression, expected", [ ("function(", ([], "function(")), ("before the function(", (["before the "], "function(")),