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:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 ""
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user