Fixed #32 : concept groups are not correctly updated

Fixed #35 : Refactor test helper class (CNC, CC, CIO)
Fixed #36 : Concept values are not used when declared with variable expression
Fixed #37 : Objects in memory lose their values are restart
Fixed #38 : func(a=b, c) (which is not allowed) raise an exception
This commit is contained in:
2021-03-05 11:16:19 +01:00
parent 646c428edb
commit 05577012f3
38 changed files with 1942 additions and 1463 deletions
+14 -2
View File
@@ -5,11 +5,11 @@ from cache.Cache import Cache
from core.ast_helpers import ast_to_props
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept, ConceptParts, DEFINITION_TYPE_BNF, concept_part_value
from core.global_symbols import NotInit, NotFound
from core.global_symbols import NotInit, NotFound, CURRENT_OBJ
from core.rule import Rule
from core.utils import as_bag
from parsers.BaseNodeParser import SourceCodeNode, ConceptNode, UnrecognizedTokensNode, SourceCodeWithConceptNode, \
RuleNode
RuleNode, VariableNode
from parsers.BaseParser import ParsingError
PARSE_STEPS = [BuiltinConcepts.BEFORE_PARSING, BuiltinConcepts.PARSING, BuiltinConcepts.AFTER_PARSING]
@@ -498,6 +498,18 @@ def get_lexer_nodes_from_unrecognized(context, unrecognized_tokens_node, parsers
:return:
"""
# first look into short term memory to see if the unrecognized is not a variable of the current object
if (current_obj := context.sheerka.get_from_short_term_memory(context, CURRENT_OBJ)) is not NotFound:
if isinstance(current_obj, Concept):
source = unrecognized_tokens_node.source
if source in current_obj.get_compiled() or source in current_obj.variables():
return [[VariableNode(current_obj,
source,
unrecognized_tokens_node.start,
unrecognized_tokens_node.end,
unrecognized_tokens_node.tokens,
unrecognized_tokens_node.source)]]
res = context.sheerka.parse_unrecognized(context, unrecognized_tokens_node.source, parsers)
res = only_parsers_results(context, res)
+22 -291
View File
@@ -1,9 +1,7 @@
import hashlib
from collections import namedtuple
from copy import deepcopy
from dataclasses import dataclass
from threading import RLock
from typing import Union
import core.utils
from core.builtin_concepts_ids import BuiltinDynamicAttrs
@@ -68,14 +66,21 @@ all_attributes_lock = RLock()
def get_concept_attrs(concept):
# look for instance attributes
if concept.get_all_attributes() is not None:
return concept.get_all_attributes()
# look for class attributes defined within the concept (for real classes that inherit from Concept)
if concept.ALL_ATTRIBUTES is not None:
return concept.ALL_ATTRIBUTES
try:
# class attributes defined globally
return ALL_ATTRIBUTES[concept.id]
except KeyError:
pass
# create a class attribute
with all_attributes_lock:
all_attributes = [k for k in concept.__dict__ if k[0] != "_" and k[0] != "#"]
if concept.id and concept.key not in BuiltinDynamicAttrs:
@@ -97,7 +102,8 @@ def copy_concepts_attrs():
def load_concepts_attrs(attrs):
global ALL_ATTRIBUTES
with all_attributes_lock:
ALL_ATTRIBUTES = attrs
ALL_ATTRIBUTES.clear()
ALL_ATTRIBUTES.update(attrs)
class Concept:
@@ -107,7 +113,7 @@ class Concept:
Everything is a concept
"""
ALL_ATTRIBUTES = None
ALL_ATTRIBUTES = None # class attributes (only use when for Concept subclasses where it id is not yet defined)
def __init__(self, name=None,
is_builtin=False,
@@ -151,22 +157,16 @@ class Concept:
self._original_definition_hash = None # concept hash before any alteration of the metadata
self._format = None # how to print the concept
self._hints = {} # extra processing information to help processing
self._all_attributes = None # instance attributes
def __repr__(self):
text = f"({self._metadata.id}){self._metadata.name}"
return text + " (" + self._metadata.pre + ")" if self._metadata.pre else text
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, simplec):
return self.name == other.name and self.body == other.body
if isinstance(other, (CC, CB, CV, CMV, CIO)):
return other == self
if not isinstance(other, Concept):
return False
@@ -208,14 +208,8 @@ class Concept:
def __hash__(self):
return hash(self._metadata.name)
# def __getattr__(self, item):
# # I have this complicated implementation because of the usage of Pickle
#
# if 'values' in vars(self) and item in self.values:
# return self.get_value(item)
#
# name = self.name if 'metadata' in vars(self) else 'Concept'
# raise AttributeError(f"'{name}' concept has no attribute '{item}'")
def get_all_attributes(self):
return self._all_attributes
def def_var(self, var_name, default_value=None):
"""
@@ -466,6 +460,15 @@ class Concept:
setattr(self, ConceptParts.BODY, value)
elif self._bound_body and name == ConceptParts.BODY:
setattr(self, self._bound_body, value)
# KSI 2021-03-04
# I am not sure how cost efficient it is to check for new attribute everytime
# Need to find a better way
if name not in get_concept_attrs(self):
if self._all_attributes is None:
self._all_attributes = get_concept_attrs(self).copy()
self._all_attributes.append(name)
except AttributeError:
print(f"Cannot set {name}")
return self
@@ -607,275 +610,3 @@ class InfiniteRecursionResolved:
def get_obj_value(self):
return self.value
# ################################
#
# Class created for tests purpose
#
# ################################
class CC:
"""
Concept class for test purpose
CC means concept for compiled (or concept with compiled)
It matches a concept if the compiles are equals
"""
# The only properties that are testes are concept_key and compiled
# The other properties (concept, source, start and end)
# are used in tests/parsers/parsers_utils.py to help creating helper objects
def __init__(self, concept, source=None, exclude_body=False, **kwargs):
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.compiled = kwargs
self.concept = concept if isinstance(concept, Concept) else None
self.source = source # to use when the key is different from the sub str to search when filling start and stop
self.start = None # for debug purpose, indicate where the concept starts
self.end = None # for debug purpose, indicate where the concept ends
self.exclude_body = exclude_body
if "body" in self.compiled:
self.compiled[ConceptParts.BODY] = self.compiled["body"]
del self.compiled["body"]
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, Concept):
if other.key != self.concept_key:
return False
if self.exclude_body:
to_compare = {k: v for k, v in other.get_compiled().items() if k != ConceptParts.BODY}
else:
to_compare = other.get_compiled()
if self.compiled == to_compare:
return True
else:
return False
if not isinstance(other, CC):
return False
if self.concept_key != other.concept_key:
return False
return self.compiled == other.compiled
def __hash__(self):
if self.concept:
return hash(self.concept)
return hash(self.concept_key)
def __repr__(self):
if self.concept:
txt = f"CC(concept='{self.concept}'"
else:
txt = f"CC(concept_key='{self.concept_key}'"
for k, v in self.compiled.items():
txt += f", {k}='{v}'"
return txt + ")"
def fix_pos(self, node):
start = node.start if hasattr(node, "start") else \
node[0] if isinstance(node, tuple) else None
end = node.end if hasattr(node, "end") else \
node[1] if isinstance(node, tuple) else None
if start is not None:
if self.start is None or start < self.start:
self.start = start
if end is not None:
if self.end is None or end > self.end:
self.end = end
return self
def to_compare(self, other, to_compare_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param to_compare_delegate:
:return:
"""
if isinstance(other, CC):
return other
if isinstance(other, Concept):
if self.exclude_body:
compiled = {k: v for k, v in other.get_compiled().items() if k != ConceptParts.BODY}
else:
compiled = other.get_compiled()
self_compile_to_use = self.compiled or compiled
compiled = to_compare_delegate(self_compile_to_use, compiled, to_compare_delegate)
return CC(other,
self.source,
self.exclude_body,
**compiled)
raise NotImplementedError(f"CC, {other=}")
@dataclass()
class CB:
"""
Concept with body only
Test class that tests only the body of the concept
"""
concept: Union[str, Concept]
body: object
def __eq__(self, other):
if isinstance(other, Concept):
key = self.concept if isinstance(self.concept, str) else self.concept.key
return key == other.key and self.body == other.body
if not isinstance(other, CB):
return False
return self.concept == other.concept and self.body == other.body
def __hash__(self):
return hash((self.concept, self.body))
def __repr__(self):
return f"CB({self.body})"
class CV:
"""
Concept with all values
Test class that tests all the values (not the metadata, so not the properties) of a concept
"""
def __init__(self, concept, **kwargs):
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.concept = concept if isinstance(concept, Concept) else None
self.values = {}
for k, v in kwargs.items():
if f"#{k}#" in AllConceptParts:
self.values[f"#{k}#"] = v
else:
self.values[k] = v
def __eq__(self, other):
if isinstance(other, Concept):
if self.concept_key != other.key:
return False
for k, v in self.values.items():
if self.values[k] != other.get_value(k):
return False
return True
if not isinstance(other, CV):
return False
return self.concept_key == other.concept_key and self.values == other.values
def __hash__(self):
return hash((self.concept_key, self.values))
def __repr__(self):
return f"CV(key={self.concept_key}, values={self.values})"
class CMV:
"""
Concept with metadata variables
CMV stands for Concept Metadata Variables
Test class that only compare the key and the metadata variables
"""
def __init__(self, concept, **kwargs):
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.concept = concept if isinstance(concept, Concept) else None
self.variables = kwargs
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, Concept):
if other.key != self.concept_key:
return False
if len(other._metadata.variables) != len(self.variables):
return False
for name, value in other._metadata.variables:
if self.variables[name] != value:
return False
return True
if not isinstance(other, CMV):
return False
if self.concept_key != other.concept_key:
return False
return self.variables == other.variables
def __hash__(self):
if self.concept:
return hash(self.concept)
return hash(self.concept_key)
def __repr__(self):
if self.concept:
txt = f"CMV(concept='{self.concept}'"
else:
txt = f"CMV(concept_key='{self.concept_key}'"
for k, v in self.variables.items():
txt += f", {k}='{v}'"
return txt + ")"
class CIO:
"""
Concept id only
only test the id
"""
def __init__(self, concept, source=None):
if isinstance(concept, str):
self.concept_name = concept
self.concept_id = None
self.concept = None
elif isinstance(concept, Concept):
self.concept_id = concept.id
self.concept = concept
self.source = source
self.start = None
self.end = None
def set_concept(self, concept):
self.concept = concept
self.concept_id = concept.id
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, Concept):
return self.concept_id == other.id
if not isinstance(other, CIO):
return False
return self.concept_id == other.concept_id
def __hash__(self):
return hash(self.concept_id)
def __repr__(self):
return f"CIO(concept='{self.concept}')" if self.concept else f"CIO(name='{self.concept_name}')"
simplec = namedtuple("concept", "name body") # for simple concept (tests purposes only)
+6
View File
@@ -31,6 +31,9 @@ class CustomType:
def __eq__(self, other):
return isinstance(other, CustomType) and self.value == other.value
def __hash__(self):
return hash(self.value)
class NotInitType(CustomType):
def __init__(self):
@@ -63,3 +66,6 @@ class ErrorObj:
To indicate that somehow, the underlying object is (or has) an error
"""
pass
CURRENT_OBJ = "__obj"
+13 -1
View File
@@ -35,13 +35,15 @@ class SheerkaAdmin(BaseService):
self.sheerka.bind_service_method(self.ontologies, False)
self.sheerka.bind_service_method(self.in_memory, False)
self.sheerka.bind_service_method(self.admin_history, False, as_name="history")
self.sheerka.bind_service_method(self.admin_history, False, as_name="history")
self.sheerka.bind_service_method(self.sdp, False)
def caches_names(self):
"""
Returns the name of all the caches
:return:
"""
return list(self.sheerka.om.current_cache_manager().caches.keys())
return self.sheerka.new(BuiltinConcepts.TO_LIST, body=self.sheerka.om.current_cache_manager().caches.keys())
def cache(self, name, *keys):
"""
@@ -58,6 +60,16 @@ class SheerkaAdmin(BaseService):
return {key: self.sheerka.om.get(name, key) for key in keys}
def sdp(self, name=None):
if name:
for ontology in self.sheerka.om.ontologies:
if ontology.name == name:
return ontology.cache_manager.sdp
return self.sheerka.err(self.sheerka.new(BuiltinConcepts.NOT_FOUND, {"sdp_name", name}))
return self.sheerka.om.current_sdp()
def restore(self, concept_file=CONCEPTS_FILE_TO_USE):
"""
Restore the state with all previous valid concept definitions
@@ -237,36 +237,22 @@ class SheerkaConceptManager(BaseService):
except Exception as ex:
return sheerka.ret(self.NAME, False, ex.args[0])
# compute first token and/or first regex
init_ret_value = self.compute_concepts_by_first_item(context, [concept], True)
if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
by_first_keyword, by_first_regex = init_ret_value.body
# computes resolved concepts_by_first_keyword
init_ret_value = self.resolve_concepts_by_first_keyword(context, by_first_keyword)
if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
resolved_concepts_by_first_keyword = init_ret_value.body
# compile regex
compile_ret = self.compile_concepts_by_first_regex(context, by_first_regex)
if not compile_ret.status:
return sheerka.ret(self.NAME, False, ErrorConcept(compile_ret.value))
compiled_concepts_by_first_regex = compile_ret.body
# recompute concepts by first tokens and concept by first regex
update_items_res = self.recompute_first_items(context, None, [concept])
if not update_items_res.status:
return update_items_res
by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex = update_items_res.body
# if everything is fine
freeze_concept_attrs(concept)
concept.freeze_definition_hash()
om.add_concept(concept)
om.put(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, by_first_keyword)
om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
om.put(self.CONCEPTS_BY_REGEX_ENTRY, False, {k.serialize(): v for k, v in by_first_regex.items()})
# update the compiled regex
self.compiled_concepts_by_regex.clear()
self.compiled_concepts_by_regex.extend(compiled_concepts_by_first_regex)
self.update_first_items_caches(context,
by_first_keyword,
by_first_regex,
resolved_by_first_keyword,
compiled_by_first_regex)
if concept.get_metadata().definition_type == DEFINITION_TYPE_DEF and concept.get_metadata().definition != concept.name:
# allow search by definition when definition relevant
@@ -284,9 +270,7 @@ class SheerkaConceptManager(BaseService):
# publish the new concept
sheerka.publish(context, EVENT_CONCEPT_CREATED, concept)
# process the return if needed
ret = sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
return ret
return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
def modify_concept(self, context, concept, to_add=None, to_remove=None, modify_source=False):
"""
@@ -324,40 +308,17 @@ class SheerkaConceptManager(BaseService):
# modify the metadata. Almost all ConceptMetadata attributes except variables and props
new_concept = sheerka.new_from_template(concept, concept.key) # reload from cache or database ?
res = self._update_concept(context, new_concept, to_add, to_remove)
if res is not None:
return res
# To update concept by first keyword and first regex
# first remove old first token and first regex entries
concepts_by_first_keyword, concepts_by_regex = self._remove_concept_first_token_and_first_regex(concept)
# recompute concepts by first tokens and concept by first regex
update_items_res = self.recompute_first_items(context, concept, [new_concept])
if not update_items_res.status:
return update_items_res
by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex = update_items_res.body
# and then update
init_ret_value = self.compute_concepts_by_first_item(context,
[new_concept],
False,
concepts_by_first_keyword,
concepts_by_regex)
if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
concepts_by_first_keyword, concepts_by_regex = init_ret_value.body
# computes resolved concepts_by_first_keyword
init_ret_value = self.resolve_concepts_by_first_keyword(context,
concepts_by_first_keyword,
{new_concept.id: new_concept})
if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
resolved_concepts_by_first_keyword = init_ret_value.body
# compile new regex
compile_ret = self.compile_concepts_by_first_regex(context, concepts_by_regex)
if not compile_ret.status:
return sheerka.ret(self.NAME, False, ErrorConcept(compile_ret.value))
compiled_concepts_by_first_regex = compile_ret.body
# update concept that referenced the old concept and clear old references
# update concepts that referenced the old concept and clear old references
self.update_references(context, concept, new_concept, to_add)
for ref in self.compute_references(concept):
om.delete(self.CONCEPTS_REFERENCES_ENTRY, ref, concept.id)
@@ -368,13 +329,11 @@ class SheerkaConceptManager(BaseService):
# everything is ok, update the caches
om.update_concept(concept, new_concept)
om.put(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, concepts_by_first_keyword)
om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
om.put(self.CONCEPTS_BY_REGEX_ENTRY, False, {k.serialize(): v for k, v in concepts_by_regex.items()})
# update the compiled regex
self.compiled_concepts_by_regex.clear()
self.compiled_concepts_by_regex.extend(compiled_concepts_by_first_regex)
self.update_first_items_caches(context,
by_first_keyword,
by_first_regex,
resolved_by_first_keyword,
compiled_by_first_regex)
# everything seems to be fine. Update the list of attributes
# Caution. Must be done AFTER update_concept()
@@ -386,8 +345,7 @@ class SheerkaConceptManager(BaseService):
self._update_concept(context, concept, to_add, to_remove)
# KSI 2021-02-16 publish the modification of the concept only when someone needs it
ret = sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=new_concept))
return ret
return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=new_concept))
def remove_concept(self, context, concept):
"""
@@ -410,30 +368,19 @@ class SheerkaConceptManager(BaseService):
refs_instances = [sheerka.new_from_template(c, c.key) for c in [self.get_by_id(ref) for ref in refs]]
return sheerka.ret(self.NAME, False, sheerka.err(ConceptIsReferenced(refs_instances)))
concepts_by_first_keyword, concepts_by_regex = self._remove_concept_first_token_and_first_regex(concept)
# computes resolved concepts_by_first_keyword
init_ret_value = self.resolve_concepts_by_first_keyword(context, concepts_by_first_keyword)
if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
resolved_concepts_by_first_keyword = init_ret_value.body
# compile new regex
compile_ret = self.compile_concepts_by_first_regex(context, concepts_by_regex)
if not compile_ret.status:
return sheerka.ret(self.NAME, False, ErrorConcept(compile_ret.value))
compiled_concepts_by_first_regex = compile_ret.body
# recompute concepts by first tokens and concept by first regex
update_items_res = self.recompute_first_items(context, concept, None)
if not update_items_res.status:
return update_items_res
by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex = update_items_res.body
# everything seems fine. I can commit the modification and remove
om.remove_concept(concept)
om.put(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, concepts_by_first_keyword)
om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_concepts_by_first_keyword)
om.put(self.CONCEPTS_BY_REGEX_ENTRY, False, {k.serialize(): v for k, v in concepts_by_regex.items()})
# update the compiled regex
self.compiled_concepts_by_regex.clear()
self.compiled_concepts_by_regex.extend(compiled_concepts_by_first_regex)
self.update_first_items_caches(context,
by_first_keyword,
by_first_regex,
resolved_by_first_keyword,
compiled_by_first_regex)
sheerka.publish(context, EVENT_CONCEPT_DELETED, concept)
return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.SUCCESS))
@@ -568,9 +515,9 @@ class SheerkaConceptManager(BaseService):
"""
Updates all the concepts that reference concept
:param context:
:param concept:
:param modified_concept:
:param modifications:
:param concept: Old version of the concept
:param modified_concept: new version of the concept
:param modifications: what are the modification
:return:
"""
@@ -582,11 +529,12 @@ class SheerkaConceptManager(BaseService):
# remove the grammar entry so that it can be recreated
self.sheerka.om.delete(self.CONCEPTS_BNF_DEFINITIONS_ENTRY, concept_id)
to_update = self.get_by_id(concept_id)
metadata = to_update.get_metadata()
# reset the bnf definition if needed
if modified_concept:
if self.has_id(concept_id):
to_update = self.get_by_id(concept_id)
metadata = to_update.get_metadata()
if self.has_id(concept_id): # reset only it the bnf definition is in cache
if metadata.definition_type == DEFINITION_TYPE_BNF and self._name_has_changed(modifications):
tokens = list(Tokenizer(metadata.definition))
modified = False
@@ -601,6 +549,19 @@ class SheerkaConceptManager(BaseService):
to_update.get_metadata().definition = core.utils.get_text_from_tokens(tokens)
to_update.set_bnf(None)
# update concept_by_first_token
if metadata.definition_type == DEFINITION_TYPE_BNF:
# recompute concepts by first tokens and concept by first regex
res = self.recompute_first_items(context, concept, [concept])
if not res.status:
return res
by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex = res.body
self.update_first_items_caches(context,
by_first_keyword,
by_first_regex,
resolved_by_first_keyword,
compiled_by_first_regex)
def compute_references(self, concept):
"""
We need to keep a track of all concepts used by the current concept
@@ -723,6 +684,8 @@ class SheerkaConceptManager(BaseService):
else:
return sheerka.ret(self.NAME, False, sheerka.err(UnknownAttribute(k)))
core.utils.remove_list_from_list(concept.get_metadata().variables, variables_to_remove)
if concept.get_all_attributes():
core.utils.remove_list_from_list(concept.get_all_attributes(), [v[0] for v in variables_to_remove])
concept.get_metadata().key = None
if self._definition_has_changed(to_add) and concept.get_metadata().definition_type == DEFINITION_TYPE_BNF:
@@ -1046,3 +1009,78 @@ class SheerkaConceptManager(BaseService):
def get_concepts_bnf_definitions(self):
return self.sheerka.om.current_cache_manager().caches[self.CONCEPTS_BNF_DEFINITIONS_ENTRY].cache
def recompute_first_items(self, context, old_concept, new_concepts):
"""
Recompute
concepts fy first items
resolved concept by first items
concepts by first regex
compiled concepts by first regex
Do not update anything
:param context:
:param old_concept:
:param new_concepts:
:return:
"""
sheerka = context.sheerka
if old_concept and not new_concepts:
# remove
modified_concepts = None
by_first_keyword, by_first_regex = self._remove_concept_first_token_and_first_regex(old_concept)
elif old_concept and new_concepts:
# remove
by_first_keyword, by_first_regex = self._remove_concept_first_token_and_first_regex(old_concept)
# and then update
init_ret_value = self.compute_concepts_by_first_item(context,
new_concepts,
False,
by_first_keyword,
by_first_regex)
if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
by_first_keyword, by_first_regex = init_ret_value.body
modified_concepts = {new_concept.id: new_concept for new_concept in new_concepts}
elif not old_concept and new_concepts:
# only update
modified_concepts = None
init_ret_value = self.compute_concepts_by_first_item(context, new_concepts, True)
if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
by_first_keyword, by_first_regex = init_ret_value.body
# computes resolved concepts_by_first_keyword
init_ret_value = self.resolve_concepts_by_first_keyword(context, by_first_keyword, modified_concepts)
if not init_ret_value.status:
return sheerka.ret(self.NAME, False, ErrorConcept(init_ret_value.value))
resolved_by_first_keyword = init_ret_value.body
# compile new regex
compile_ret = self.compile_concepts_by_first_regex(context, by_first_regex)
if not compile_ret.status:
return sheerka.ret(self.NAME, False, ErrorConcept(compile_ret.value))
compiled_by_first_regex = compile_ret.body
return sheerka.ret(self.NAME,
True,
(by_first_keyword, by_first_regex, resolved_by_first_keyword, compiled_by_first_regex))
def update_first_items_caches(self,
context,
by_first_keyword,
by_first_regex,
resolved_by_first_keyword,
compiled_by_first_regex):
om = context.sheerka.om
om.put(self.CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, by_first_keyword)
om.put(self.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY, False, resolved_by_first_keyword)
om.put(self.CONCEPTS_BY_REGEX_ENTRY, False, {k.serialize(): v for k, v in by_first_regex.items()})
# update the compiled regex
self.compiled_concepts_by_regex.clear()
self.compiled_concepts_by_regex.extend(compiled_by_first_regex)
@@ -4,7 +4,7 @@ from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import expect_one, only_successful, evaluate, ensure_concept
from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved, AllConceptParts, \
concept_part_value
from core.global_symbols import NotInit
from core.global_symbols import NotInit, CURRENT_OBJ
from core.rule import Rule
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager
from core.sheerka.services.SheerkaExecute import ParserInput
@@ -535,6 +535,8 @@ class SheerkaEvaluateConcept(BaseService):
if context.sheerka.isa(concept, context.sheerka.new(BuiltinConcepts.AUTO_EVAL)):
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
sub_context.add_to_short_term_memory(CURRENT_OBJ, concept)
try:
self.initialize_concept_asts(sub_context, concept)
except ChickenAndEggException as ex:
+9 -10
View File
@@ -280,17 +280,16 @@ for x in xx__concepts__xx:
sub_context.protected_hints.add(BuiltinConcepts.EVAL_BODY_REQUESTED)
errors = []
for element_id in ids:
concept = self.sheerka.get_by_id(element_id)
if len(concept.get_metadata().variables) == 0:
# The concepts are directly taken from Sheerka.get_by_id, so variable cannot be filled
# It's the reason why we only evaluate concept with no variable
evaluated = self.sheerka.evaluate_concept(sub_context, concept)
if context.sheerka.is_success(evaluated):
result.append(evaluated)
concept = self.sheerka.fast_resolve((None, element_id))
if concept:
if len(concept.get_metadata().variables) == 0:
evaluated = self.sheerka.evaluate_concept(sub_context, concept)
if context.sheerka.is_success(evaluated):
result.append(evaluated)
else:
errors.append(evaluated)
else:
errors.append(evaluated)
else:
result.append(concept)
result.append(concept)
sub_context.add_values(return_value=result)
sub_context.add_values(errors=errors)
return result
+32 -4
View File
@@ -12,6 +12,15 @@ from core.sheerka.services.sheerka_service import BaseService, ServiceObj
class MemoryObject(ServiceObj):
obj: object
def __eq__(self, other):
if not isinstance(other, MemoryObject):
return False
return self.obj == other.obj and self.event_id == other.event_id
def __hash__(self):
return hash((self.event_id, self.obj))
class SheerkaMemory(BaseService):
NAME = "Memory"
@@ -26,6 +35,7 @@ class SheerkaMemory(BaseService):
def initialize(self):
self.sheerka.bind_service_method(self.get_from_short_term_memory, False, visible=False)
self.sheerka.bind_service_method(self.get_all_short_term_memory, False, visible=False)
self.sheerka.bind_service_method(self.add_to_short_term_memory, True, visible=False)
self.sheerka.bind_service_method(self.remove_context, True, as_name="clear_short_term_memory", visible=False)
self.sheerka.bind_service_method(self.add_to_memory, True, visible=False)
@@ -62,8 +72,26 @@ class SheerkaMemory(BaseService):
context = context.get_parent()
def get_all_short_term_memory(self, context):
return self.short_term_objects.get(context.id)
def get_all_short_term_memory(self, context, recursive=False):
id_to_use = context.id if context else self.GLOBAL
if not recursive:
return self.short_term_objects.get(id_to_use)
all_vars = {}
while True:
try:
all_vars.update(self.short_term_objects.cache[id_to_use])
except KeyError:
pass
if id_to_use == self.GLOBAL:
break
else:
context = context.get_parent()
id_to_use = context.id if context else self.GLOBAL
return all_vars
def add_to_short_term_memory(self, context, key, value):
if context:
@@ -99,12 +127,12 @@ class SheerkaMemory(BaseService):
if last is NotFound:
self.sheerka.om.put(SheerkaMemory.OBJECTS_ENTRY, key, MemoryObject(context.event.get_digest(), concept))
return
if not isinstance(last, list) and last.obj == concept:
self.sheerka.om.delete(SheerkaMemory.OBJECTS_ENTRY, key, last)
self.sheerka.om.put(SheerkaMemory.OBJECTS_ENTRY, key, MemoryObject(context.event.get_digest(), concept))
return
if isinstance(last, list) and last[-1].obj == concept:
self.sheerka.om.delete(SheerkaMemory.OBJECTS_ENTRY, key, last[-1])
self.sheerka.om.put(SheerkaMemory.OBJECTS_ENTRY, key, MemoryObject(context.event.get_digest(), concept))
+1 -1
View File
@@ -444,7 +444,7 @@ def str_concept(t, drop_name=None, prefix="c:"):
elif prefix == "r:":
name, id_ = t.metadata.name, t.id
else:
name, id_ = t.key, t.id
name, id_ = t.key or t.name, t.id
if name is None and id_ is None:
return ""
+32
View File
@@ -0,0 +1,32 @@
from core.concept import Concept
from core.rule import Rule
class VariableRef:
"""
Represents the reference to the property/attribute of an object
"""
def __init__(self, obj, prop):
self.obj = obj
self.prop = prop
def __eq__(self, other):
if not isinstance(other, VariableRef):
return False
return self.obj == other.obj and self.prop == other.prop
def __hash__(self):
return hash((self.obj, self.prop))
@property
def key(self):
return self.obj.key or self.obj.name if isinstance(self.obj, (Concept, Rule)) else type(self.obj).__name__
@property
def id(self):
if self.prop is None:
return ""
return self.prop
+5
View File
@@ -13,6 +13,7 @@ from core.rule import Rule
from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka.services.SheerkaMemory import SheerkaMemory
from core.tokenizer import Token, TokenKind
from core.var_ref import VariableRef
from evaluators.BaseEvaluator import OneReturnValueEvaluator
from parsers.PythonParser import PythonNode
@@ -332,6 +333,10 @@ class PythonEvaluator(OneReturnValueEvaluator):
:param name:
:return:
"""
if isinstance(name, VariableRef):
return getattr(name.obj, name.prop)
if isinstance(name, Rule):
return context.sheerka.resolve_rule(context, name)
+44 -458
View File
@@ -1,11 +1,9 @@
from collections import namedtuple
from dataclasses import dataclass
from enum import Enum
import core.utils
from core.concept import Concept, ConceptParts
from core.rule import Rule
from core.tokenizer import TokenKind, Token
from core.var_ref import VariableRef
from parsers.BaseParser import Node, BaseParser, ParsingError
DEBUG_COMPILED = True
@@ -117,14 +115,6 @@ class UnrecognizedTokensNode(LexerNode):
return self.tokens[-1].type
def __eq__(self, other):
if isinstance(other, utnode):
return self.start == other.start and \
self.end == other.end and \
self.source == other.source
if isinstance(other, UTN):
return other == self
if not isinstance(other, UnrecognizedTokensNode):
return False
@@ -158,9 +148,6 @@ class RuleNode(LexerNode):
if id(self) == id(other):
return True
if isinstance(other, RN):
return other == self
if not isinstance(other, RuleNode):
return False
@@ -198,18 +185,6 @@ class ConceptNode(LexerNode):
if id(self) == id(other):
return True
if isinstance(other, (CN, CNC)):
return other == self
if isinstance(other, cnode):
return self.concept.key == other.concept_key and \
self.start == other.start and \
self.end == other.end and \
self.source == other.source
if isinstance(other, short_cnode):
return self.concept.key == other.concept_key and self.source == other.source
if not isinstance(other, ConceptNode):
return False
@@ -255,7 +230,8 @@ class SourceCodeNode(LexerNode):
Returned when some source code (like Python source code is recognized)
"""
def __init__(self, start, end, tokens=None, source=None, python_node=None, return_value=None):
def __init__(self, start, end, tokens=None, source=None,
python_node=None, return_value=None, error_when_parsing=None):
"""
:param start: start position (index of the first token)
@@ -269,18 +245,12 @@ class SourceCodeNode(LexerNode):
You should have return_value.body.body == node
"""
super().__init__(start, end, tokens, source)
self.python_node = python_node # The PythonNode (or whatever language node) that is found
self.return_value = return_value # original result of the parsing
self.error_when_parsing = error_when_parsing # if python_node is still None after parsing, it explains why
def __eq__(self, other):
if isinstance(other, scnode):
return self.start == other.start and \
self.end == other.end and \
self.source == other.source
if isinstance(other, SCN):
return other == self
if not isinstance(other, SourceCodeNode):
return False
@@ -336,6 +306,7 @@ class SourceCodeWithConceptNode(LexerNode):
self.python_node = None # if the source code node is validated against a python parse, here is the PythonNode
self.return_value = None # return_value that produced the PythonNode
self.error_when_parsing = None # if python_node is still None after parsing, it explains why
def add_node(self, node):
self.nodes.append(node)
@@ -348,9 +319,6 @@ class SourceCodeWithConceptNode(LexerNode):
if id(self) == id(other):
return True
if isinstance(other, SCWC):
return other == self
if not isinstance(other, SourceCodeWithConceptNode):
return False
@@ -436,6 +404,44 @@ class SourceCodeWithConceptNode(LexerNode):
return self.python_node.source
class VariableNode(LexerNode):
"""
When trying to parser source code, a reference to a variable is recognized
Not sure yet if it has to be a lexer node
"""
def __init__(self, obj, prop, start, end, tokens=None, source=None):
super().__init__(start, end, tokens, source)
self.var_ref = VariableRef(obj, prop)
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, VariableNode):
return False
return self.var_ref == other.var_ref and \
self.start == other.start and \
self.end == other.end and \
self.source == other.source
def __hash__(self):
return hash((self.var_ref.obj, self.var_ref.prop, self.start, self.end, self.source))
def __repr__(self):
ret = f"VariableNode(obj={self.var_ref.obj}, prop={self.var_ref.prop}, "
ret += f"start={self.start}, end={self.end}, source='{self.source}')"
return ret
def to_short_str(self):
return f"VN({self.var_ref.obj})" if self.var_ref.prop is None else f"VN({self.var_ref.obj}.{self.var_ref.prop})"
def clone(self):
clone = VariableNode(self.var_ref.obj, self.var_ref.prop, self.start, self.end, self.tokens, self.source)
return clone
@dataclass()
class GrammarErrorNode(ParsingError):
message: str
@@ -455,426 +461,6 @@ class SyaAssociativity(Enum):
return self.value
cnode = namedtuple("ConceptNode", "concept_key start end source")
short_cnode = namedtuple("ConceptNode", "concept_key source")
utnode = namedtuple("utnode", "start end source")
scnode = namedtuple("scnode", "start end source")
class HelperWithPos:
def __init__(self, start=None, end=None):
self.start = start
self.end = end
self.start_is_fixed = start is not None
self.end_is_fixed = end is not None
def fix_pos(self, node):
if not self.start_is_fixed:
start = node.start if hasattr(node, "start") else \
node[0] if isinstance(node, tuple) else None
if start is not None and (self.start is None or start < self.start):
self.start = start
if not self.end_is_fixed:
end = node.end if hasattr(node, "end") else \
node[1] if isinstance(node, tuple) else None
if end is not None and (self.end is None or end > self.end):
self.end = end
return self
class SCN(HelperWithPos):
"""
SourceCodeNode tester class
It matches with SourceCodeNode but with less constraints
SCN == SourceCodeNode if source, start, end (start and end are not validated when None)
"""
def __init__(self, source, start=None, end=None):
super().__init__(start, end)
self.source = source
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, SourceCodeNode):
if self.source != other.source:
return False
if self.start is not None and self.start != other.start:
return False
if self.end is not None and self.end != other.end:
return False
return True
if not isinstance(other, CN):
return False
return self.source == other.source and \
self.start == other.start and \
self.end == other.end
def __hash__(self):
return hash((self.source, self.start, self.end))
def __repr__(self):
txt = f"SCN(source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
return txt + ")"
class SCWC(HelperWithPos):
"""
SourceNodeWithConcept tester class
It matches with a SourceNodeWithConcept
but it's easier to instantiate during the tests
"""
def __init__(self, first, last, *args):
super().__init__(None, None)
self.first = first
self.last = last
self.content = args
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, SourceCodeWithConceptNode):
if self.first != other.first:
return False
if self.last != other.last:
return False
if len(self.content) != len(other.nodes):
return False
for self_node, other_node in zip(self.content, other.nodes):
if self_node != other_node:
return False
# at last
return True
def __repr__(self):
txt = "SCWC("
if self.start is not None:
txt += f"start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
txt += f", source='{self.source}'"
return txt + ")"
@property
def source(self):
"""
this code is a copy and paste from SourceCodeWithConceptNode.pseudo_fix_source
TODO: create a common function or whatever...
:return:
"""
source = self.first.source if hasattr(self.first, "source") else self.first
for n in self.content:
source += " "
if hasattr(n, "source"):
source += n.source
elif hasattr(n, "concept"):
source += str(n.concept)
else:
source += " unknown"
source += self.last.source if hasattr(self.last, "source") else self.last
return source
class CN(HelperWithPos):
"""
ConceptNode tester class
It matches with ConceptNode but with less constraints
CN == ConceptNode if concept key, start, end and source are the same
"""
def __init__(self, concept, start=None, end=None, source=None):
"""
:param concept: Concept or concept_key (only the key is used anyway)
:param start:
:param end:
:param source:
"""
super().__init__(start, end)
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.source = source
self.concept = concept if isinstance(concept, Concept) else None
def fix_source(self, str_tokens):
self.source = "".join(str_tokens)
return self
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, ConceptNode):
if other.concept is None:
return False
if other.concept.key != self.concept_key:
return False
if self.start is not None and self.start != other.start:
return False
if self.end is not None and self.end != other.end:
return False
if self.source is not None and self.source != other.source:
return False
return True
if not isinstance(other, CN):
return False
return self.concept_key == other.concept_key and \
self.start == other.start and \
self.end == other.end and \
self.source == other.source
def __hash__(self):
return hash((self.concept_key, self.start, self.end, self.source))
def __repr__(self):
if self.concept:
txt = f"CN(concept='{self.concept}'"
else:
txt = f"CN(concept_key='{self.concept_key}'"
txt += f", source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
return txt + ")"
class CNC(CN):
"""
ConceptNode for Compiled tester class
It matches with ConceptNode
But focuses on the 'compiled' property of the concept
CNC == ConceptNode if CNC.get_compiled() == ConceptNode.concept.get_compiled()
"""
def __init__(self, concept_key, start=None, end=None, source=None, exclude_body=False, **kwargs):
super().__init__(concept_key, start, end, source)
self.compiled = kwargs
self.exclude_body = exclude_body
if "body" in self.compiled:
self.compiled[ConceptParts.BODY] = self.compiled["body"]
del self.compiled["body"]
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, ConceptNode):
if other.concept is None:
return False
if other.concept.key != self.concept_key:
return False
if self.start is not None and self.start != other.start:
return False
if self.end is not None and self.end != other.end:
return False
if self.source is not None and self.source != other.source:
return False
if self.exclude_body:
to_compare = {k: v for k, v in other.concept.get_compiled().items() if k != ConceptParts.BODY}
else:
to_compare = other.concept.get_compiled()
if self.compiled == to_compare: # expanded form to ease the debug
return True
else:
return False
if not isinstance(other, CNC):
return False
return self.concept_key == other.concept_key and \
self.start == other.start and \
self.end == other.end and \
self.source == other.source and \
self.compiled == other.compiled
def __repr__(self):
if self.concept:
txt = f"CNC(concept='{self.concept}'"
else:
txt = f"CNC(concept_key='{self.concept_key}'"
txt += f", source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
for k, v in self.compiled.items():
txt += f", {k}='{v}'"
return txt + ")"
def to_compare(self, other, to_compare_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param to_compare_delegate:
:return:
"""
if isinstance(other, CNC):
return other
if isinstance(other, ConceptNode):
if self.exclude_body:
compiled = {k: v for k, v in other.concept.get_compiled().items() if k != ConceptParts.BODY}
else:
compiled = other.concept.get_compiled()
self_compile_to_use = self.compiled or compiled
compiled = to_compare_delegate(self_compile_to_use, compiled, to_compare_delegate)
return CNC(other.concept,
other.start if self.start is not None else None,
other.end if self.end is not None else None,
other.source if self.source is not None else None,
self.exclude_body,
**compiled)
raise NotImplementedError("CNC")
class UTN(HelperWithPos):
"""
Tester class for UnrecognizedTokenNode
compare the source, and start, end if defined
"""
def __init__(self, source, start=None, end=None):
"""
:param source:
:param start:
:param end:
"""
super().__init__(start, end)
self.source = source
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, UnrecognizedTokensNode):
return self.start == other.start and \
self.end == other.end and \
self.source == other.source
if not isinstance(other, UTN):
return False
return self.start == other.start and \
self.end == other.end and \
self.source == other.source
def __hash__(self):
return hash((self.source, self.start, self.end))
def __repr__(self):
txt = f"UTN(source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
return txt + ")"
def to_compare(self, other, to_compare_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param to_compare_delegate:
:return:
"""
if isinstance(other, UTN):
return other
if isinstance(other, UnrecognizedTokensNode):
return UTN(other.source,
other.start,
other.end)
raise NotImplementedError("UTN")
class RN(HelperWithPos):
"""
Helper class to test RuleNode
"""
def __init__(self, rule, start=None, end=None, source=None):
"""
:param concept: Concept or concept_key (only the key is used anyway)
:param start:
:param end:
:param source:
"""
super().__init__(start, end)
self.rule_id = rule.id if isinstance(rule, Rule) else rule
self.source = source or core.utils.str_concept((None, self.rule_id), prefix="r:")
self.rule = rule if isinstance(rule, Rule) else None
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, RuleNode):
if other.rule is None:
return False
if other.rule.id != self.rule_id:
return False
if self.start is not None and self.start != other.start:
return False
if self.end is not None and self.end != other.end:
return False
if self.source is not None and self.source != other.source:
return False
return True
if not isinstance(other, RN):
return False
return self.rule_id == other.rule_id and \
self.start == other.start and \
self.end == other.end and \
self.source == other.source
def __hash__(self):
return hash((self.rule_id, self.start, self.end, self.source))
def __repr__(self):
if self.rule:
txt = f"RN(rule='{self.rule}'"
else:
txt = f"RN(rule_id='{self.rule_id}'"
txt += f", source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
return txt + ")"
class BaseNodeParser(BaseParser):
"""
Parser that return LexerNode
+14 -7
View File
@@ -168,15 +168,22 @@ class BaseParser:
if expected_parser and parser_input.parser != expected_parser:
return None
if len(parser_input.value) == 0:
return None
for node in parser_input.value:
from parsers.BaseNodeParser import LexerNode
if not isinstance(node, LexerNode):
from parsers.BaseNodeParser import LexerNode
if isinstance(parser_input.value, list):
if len(parser_input.value) == 0:
return None
return parser_input.value
for node in parser_input.value:
if not isinstance(node, LexerNode):
return None
return parser_input.value
else:
if not isinstance(parser_input.value, LexerNode):
return None
return [parser_input.value]
@staticmethod
def get_tokens_boundaries(tokens):
+43 -70
View File
@@ -53,59 +53,6 @@ class FunctionNode(FunctionParserNode):
parameters: list
class FN(FunctionNode):
"""
Test class only
It matches with FunctionNode but with less constraints
Thereby,
FN("first", "last", ["param1," ...]) can be compared to
FunctionNode(NameExprNode("first"), NameExprNode("second"), [FunctionParameter(NamesNodes("param1"), NamesNodes(", ")])
Note that FunctionParameter can easily be defined with a single string
* "param" -> FunctionParameter(NameExprNode("param"), None)
* "param, " -> FunctionParameter(NameExprNode("param"), NameExprNode(", "))
For more complicated situations, you can use a tuple (value, sep) to define the value part and the separator part
"""
def __init__(self, first, last, parameters):
self.first = first
self.last = last
self.parameters = []
for param in parameters:
if isinstance(param, tuple):
self.parameters.append(param)
elif isinstance(param, str) and (pos := param.find(",")) != -1:
self.parameters.append((param[:pos], param[pos:]))
else:
self.parameters.append((param, None))
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, FN):
return self.first == other.first and self.last == other.last and self.parameters == other.parameters
if isinstance(other, FunctionNode):
if self.first != other.first.value or self.last != other.last.value:
return False
if len(self.parameters) != len(other.parameters):
return False
for self_parameter, other_parameter in zip(self.parameters, other.parameters):
value = other_parameter.value.value if isinstance(self_parameter[0], str) else other_parameter.value
sep = other_parameter.separator.value if other_parameter.separator else None
if self_parameter[0] != value or self_parameter[1] != sep:
return False
return True
return False
def __hash__(self):
return hash((self.first, self.last, self.parameters))
class FunctionParser(BaseParser):
"""
The parser will be used to parse func(x, y, z)
@@ -126,6 +73,17 @@ class FunctionParser(BaseParser):
self.longest_concepts_only = longest_concepts_only
self.record_errors = True
def function_parser_get_return_value_body(self, source_code_node):
if source_code_node.error_when_parsing:
return self.sheerka.new(BuiltinConcepts.ERROR,
body=source_code_node.error_when_parsing)
return self.sheerka.new(BuiltinConcepts.PARSER_RESULT,
parser=self,
source=self.parser_input.as_text(),
body=source_code_node,
try_parsed=source_code_node)
def add_error(self, error, next_token=True):
if not self.record_errors:
return
@@ -166,6 +124,9 @@ class FunctionParser(BaseParser):
[TokenKind.EOF]))
if self.has_error:
if len(self.error_sink) == 1 and isinstance(self.error_sink[0], Concept):
return self.error_sink[0]
if node is None:
body = context.sheerka.new(BuiltinConcepts.NOT_FOR_ME,
body=parser_input.as_text(),
@@ -178,12 +139,8 @@ class FunctionParser(BaseParser):
res = []
for source_code_node in source_code_nodes:
value = self.get_return_value_body(context.sheerka,
self.parser_input.as_text(),
source_code_node,
source_code_node)
res.append(self.sheerka.ret(self.name, source_code_node.python_node is not None, value))
body = self.function_parser_get_return_value_body(source_code_node)
res.append(self.sheerka.ret(self.name, source_code_node.python_node is not None, body))
return res[0] if len(res) == 1 else res
@@ -288,6 +245,25 @@ class FunctionParser(BaseParser):
def to_source_code_node(self, function_node: FunctionNode):
python_parser = PythonWithConceptsParser()
def update_source_code_node(scn, nodes, sep):
if hasattr(nodes, "__iter__"):
for n in nodes:
scn.add_node(n)
else:
scn.add_node(nodes)
if sep:
scn.add_node(sep.to_unrecognized())
def get_errors_from_python_parsing(parsing_res):
if parsing_res.status:
return None
if self.sheerka.isinstance(parsing_res.body, BuiltinConcepts.NOT_FOR_ME):
return parsing_res.body.reason
else:
return parsing_res.body.body
if len(function_node.parameters) == 0:
# validate the source
nodes_to_parse = [function_node.first.to_unrecognized(), function_node.last.to_unrecognized()]
@@ -298,17 +274,8 @@ class FunctionParser(BaseParser):
end=function_node.last.end,
tokens=function_node.first.tokens + function_node.last.tokens,
python_node=python_node,
return_value=python_parsing_res)]
def update_source_code_node(scn, nodes, sep):
if hasattr(nodes, "__iter__"):
for n in nodes:
scn.add_node(n)
else:
scn.add_node(nodes)
if sep:
scn.add_node(sep.to_unrecognized())
return_value=python_parsing_res,
error_when_parsing=get_errors_from_python_parsing(python_parsing_res))]
res = [SourceCodeWithConceptNode(function_node.first.to_unrecognized(), function_node.last.to_unrecognized())]
@@ -363,6 +330,12 @@ class FunctionParser(BaseParser):
for c in [c for c in source_code_node.python_node.objects.values() if isinstance(c, Concept)]:
update_compiled(self.context, c, errors)
if errors:
source_code_node.error_when_parsing = errors
else:
source_code_node.error_when_parsing = get_errors_from_python_parsing(python_parsing_res)
return res
@staticmethod
+14 -1
View File
@@ -1,6 +1,6 @@
from core.builtin_concepts import BuiltinConcepts
from core.builtin_helpers import CreateObjectIdentifiers
from parsers.BaseNodeParser import ConceptNode, RuleNode
from parsers.BaseNodeParser import ConceptNode, RuleNode, VariableNode
from parsers.BaseNodeParser import SourceCodeWithConceptNode
from parsers.BaseParser import BaseParser
from parsers.UnrecognizedNodeParser import UnrecognizedNodeParser
@@ -64,6 +64,19 @@ class PythonWithConceptsParser(BaseParser):
python_ids_mappings[python_id] = rule
last_token_index = node.end
elif isinstance(node, VariableNode):
if node.start != last_token_index + 1 and source: # put back missing whitespace
source += " "
to_parse += " "
source += node.source
var_ref = node.var_ref
python_id = ids_manager.get_identifier(var_ref, "__V__")
to_parse += python_id
python_ids_mappings[python_id] = var_ref
last_token_index = node.end
else:
source += node.source
to_parse += node.get_source_to_parse()
+2 -2
View File
@@ -12,7 +12,7 @@ from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import Token, TokenKind, Tokenizer
from core.utils import get_n_clones, get_text_from_tokens, NextIdManager
from parsers.BaseNodeParser import UnrecognizedTokensNode, ConceptNode, SourceCodeNode, SyaAssociativity, \
SourceCodeWithConceptNode, BaseNodeParser
SourceCodeWithConceptNode, BaseNodeParser, VariableNode
from parsers.BaseParser import ParsingError
PARSERS = ["Sequence", "Bnf", "Python"]
@@ -1262,7 +1262,7 @@ class SyaNodeParser(BaseNodeParser):
def postfix_to_item(self, sheerka, postfixed):
item = postfixed.pop()
if isinstance(item, (UnrecognizedTokensNode, SourceCodeNode, ConceptNode)):
if isinstance(item, (UnrecognizedTokensNode, SourceCodeNode, ConceptNode, VariableNode)):
return item
if isinstance(item, SourceCodeWithConceptNode):
+10
View File
@@ -235,6 +235,16 @@ class SheerkaDataProvider:
def get_transaction(self, event) -> SheerkaDataProviderTransaction:
return SheerkaDataProviderTransaction(self, event)
def get_ref(self, entry, key):
self.log.debug(f"getting object ref_id {entry=}, {key=}")
if entry not in self.state.data:
return NotFound
if key not in self.state.data[entry]:
return NotFound
return self.state.data[entry][key]
def get(self, entry, key=None, default=NotFound, load_origin=True):
"""
Get an element