Refactored to allow ConceptEvaluator
This commit is contained in:
@@ -0,0 +1,207 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from core.concept import Concept
|
||||||
|
|
||||||
|
|
||||||
|
class BuiltinConcepts(Enum):
|
||||||
|
"""
|
||||||
|
List of builtin concepts that do no need any specific implementation
|
||||||
|
"""
|
||||||
|
SHEERKA = 1
|
||||||
|
SUCCESS = 2
|
||||||
|
ERROR = 3
|
||||||
|
UNKNOWN_CONCEPT = 4 # the request concept is not recognized
|
||||||
|
RETURN_VALUE = 5 # a value is returned
|
||||||
|
CONCEPT_TOO_LONG = 6 # concept cannot be processed by exactConcept parser
|
||||||
|
NEW_CONCEPT = 7 # when a new concept is added
|
||||||
|
UNKNOWN_PROPERTY = 8 # when requesting for a unknown property
|
||||||
|
PARSER_RESULT = 9
|
||||||
|
TOO_MANY_SUCCESS = 10 # when expecting a limited number of successful return value
|
||||||
|
TOO_MANY_ERRORS = 11 # when expecting a limited number of successful return value
|
||||||
|
NOT_FOR_ME = 12 # a parser recognize that the entry is not meant for it
|
||||||
|
IS_EMPTY = 13 # when a set is empty
|
||||||
|
INVALID_RETURN_VALUE = 14 # the return value of an evaluator is not correct
|
||||||
|
BEFORE_PARSING = 15 # activated before evaluation by the parsers
|
||||||
|
PARSING = 16 # activated during the parsing. It contains the text to parse
|
||||||
|
AFTER_PARSING = 17 # activated when the parsing process seems to be finished
|
||||||
|
CONCEPT_ALREADY_DEFINED = 18 # when you try to add the same concept twice
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Some concepts have a specific implementation
|
||||||
|
It's mainly to a have proper __repr__ implementation, or redefine the is_unique attribut
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class SuccessConcept(Concept):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(BuiltinConcepts.SUCCESS, True, True, BuiltinConcepts.SUCCESS)
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorConcept(Concept):
|
||||||
|
def __init__(self, error=None):
|
||||||
|
super().__init__(BuiltinConcepts.ERROR, True, False, BuiltinConcepts.ERROR, body=error)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"({self.id}){self.name}: {self.body}"
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnValueConcept(Concept):
|
||||||
|
"""
|
||||||
|
This class represents the result of a data flow processing
|
||||||
|
It's the main input for the evaluators
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, who=None, status=None, value=None, message=None, parents=None):
|
||||||
|
super().__init__(BuiltinConcepts.RETURN_VALUE, True, False, BuiltinConcepts.RETURN_VALUE)
|
||||||
|
self.set_prop("who", who)
|
||||||
|
self.set_prop("status", status)
|
||||||
|
self.body = value
|
||||||
|
self.set_prop("message", message)
|
||||||
|
self.set_prop("parents", parents)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def who(self):
|
||||||
|
return self.props["who"].value
|
||||||
|
|
||||||
|
@who.setter
|
||||||
|
def who(self, value):
|
||||||
|
self.set_prop("who", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
return self.props["status"].value
|
||||||
|
|
||||||
|
@status.setter
|
||||||
|
def status(self, value):
|
||||||
|
self.set_prop("status", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.body
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
self.body = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
return self.props["message"].value
|
||||||
|
|
||||||
|
@message.setter
|
||||||
|
def message(self, value):
|
||||||
|
self.set_prop("message", value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parents(self):
|
||||||
|
return self.props["parents"].value
|
||||||
|
|
||||||
|
@parents.setter
|
||||||
|
def parents(self, value):
|
||||||
|
self.set_prop("parents", value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"ReturnValue(who={self.who}, status={self.status}, value={self.value}, message={self.message})"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, ReturnValueConcept):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.who == other.who and \
|
||||||
|
self.status == other.status and \
|
||||||
|
self.value == other.value and \
|
||||||
|
self.message == other.message
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.who, self.status, self.value))
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownPropertyConcept(Concept):
|
||||||
|
"""
|
||||||
|
This error is raised when, during sheerka.new(), an unknown property is asked
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, property_name=None, concept=None):
|
||||||
|
super().__init__(BuiltinConcepts.UNKNOWN_PROPERTY, True, False, BuiltinConcepts.UNKNOWN_PROPERTY)
|
||||||
|
self.set_prop("concept", concept)
|
||||||
|
self.body = property_name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"UnknownProperty(property={self.property_name}, concept={self.concept})"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def concept(self):
|
||||||
|
return self.props["concept"].value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def property_name(self):
|
||||||
|
return self.body
|
||||||
|
|
||||||
|
|
||||||
|
class ParserResultConcept(Concept):
|
||||||
|
"""
|
||||||
|
Result of a parsing
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parser=None, source=None, value=None, try_parsed=None):
|
||||||
|
super().__init__(BuiltinConcepts.PARSER_RESULT, True, False, BuiltinConcepts.PARSER_RESULT)
|
||||||
|
self.set_prop("parser", parser)
|
||||||
|
self.set_prop("source", source)
|
||||||
|
self.set_prop("try_parsed", try_parsed) # in case of error, what was found before the error
|
||||||
|
self.body = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"ParserResult({self.body})"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, ParserResultConcept):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.source == other.source and \
|
||||||
|
self.parser == other.parser and \
|
||||||
|
self.body == other.body and \
|
||||||
|
self.try_parsed == other.try_parsed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.body
|
||||||
|
|
||||||
|
@property
|
||||||
|
def try_parsed(self):
|
||||||
|
return self.props["try_parsed"].value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def source(self):
|
||||||
|
return self.props["source"].value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parser(self):
|
||||||
|
return self.props["parser"].value
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidReturnValueConcept(Concept):
|
||||||
|
"""
|
||||||
|
Error returned when an evaluator is not correctly coded
|
||||||
|
The accepted return value are
|
||||||
|
ReturnValueConcept, list of ReturnValueConcept or None
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, return_value=None, evaluator=None):
|
||||||
|
super().__init__(BuiltinConcepts.INVALID_RETURN_VALUE, True, False, BuiltinConcepts.INVALID_RETURN_VALUE)
|
||||||
|
self.set_prop("evaluator", evaluator)
|
||||||
|
self.body = return_value
|
||||||
|
|
||||||
|
|
||||||
|
class BeforeParsingConcept(Concept):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(BuiltinConcepts.BEFORE_PARSING, True, True, BuiltinConcepts.BEFORE_PARSING)
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingConcept(Concept):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(BuiltinConcepts.PARSING, True, True, BuiltinConcepts.PARSING)
|
||||||
|
|
||||||
|
|
||||||
|
class AfterParsingConcept(Concept):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(BuiltinConcepts.AFTER_PARSING, True, True, BuiltinConcepts.AFTER_PARSING)
|
||||||
+67
-48
@@ -8,6 +8,10 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class ConceptParts(Enum):
|
class ConceptParts(Enum):
|
||||||
|
"""
|
||||||
|
Helper class, Note quite sure that is it that useful
|
||||||
|
I guess, I was learning nums with Python...
|
||||||
|
"""
|
||||||
WHERE = "where"
|
WHERE = "where"
|
||||||
PRE = "pre"
|
PRE = "pre"
|
||||||
POST = "post"
|
POST = "post"
|
||||||
@@ -20,21 +24,36 @@ class Concept:
|
|||||||
A concept is a the base object of our universe
|
A concept is a the base object of our universe
|
||||||
Everything is a concept
|
Everything is a concept
|
||||||
"""
|
"""
|
||||||
props_to_serialize = ("id", "is_builtin", "name", "where", "pre", "post", "body", "desc")
|
props_to_serialize = ("id", "is_builtin", "key", "name", "where", "pre", "post", "body", "desc", "obj")
|
||||||
|
props_for_digest = ("is_builtin", "key", "name", "where", "pre", "post", "body", "desc")
|
||||||
|
concept_parts = set(item.value for item in ConceptParts)
|
||||||
|
|
||||||
PROPERTY_PREFIX = "__var__"
|
PROPERTY_PREFIX = "__var__"
|
||||||
|
|
||||||
def __init__(self, name=None, is_builtin=False, where=None, pre=None, post=None, body=None, desc=None, key=None):
|
def __init__(self, name=None,
|
||||||
self.name = name
|
is_builtin=False,
|
||||||
|
is_unique=False,
|
||||||
|
key=None,
|
||||||
|
where=None,
|
||||||
|
pre=None,
|
||||||
|
post=None,
|
||||||
|
body=None,
|
||||||
|
desc=None,
|
||||||
|
obj=None):
|
||||||
|
|
||||||
|
self.name = str(name) if name else None
|
||||||
self.is_builtin = is_builtin
|
self.is_builtin = is_builtin
|
||||||
|
self.is_unique = is_unique
|
||||||
|
self.key = str(key) if key else None # name od the concept, where prop are replaced. to ease search
|
||||||
|
|
||||||
self.where = where # condition to recognize variables in name
|
self.where = where # condition to recognize variables in name
|
||||||
self.pre = pre # list of pre conditions before calling the main function
|
self.pre = pre # list of pre conditions before calling the main function
|
||||||
self.post = post # list of post conditions after calling the main function
|
self.post = post # list of post conditions after calling the main function
|
||||||
self.body = body # main method, can also be the value of the concept
|
self.body = body # main method, can also be the value of the concept
|
||||||
self.desc = desc
|
self.desc = desc
|
||||||
self.id = None
|
self.id = None # unique identifier for a concept. The id will never be modified
|
||||||
self.key = key
|
|
||||||
|
|
||||||
|
self.obj = obj # main of principal property of the concept
|
||||||
self.props = {} # list of Property for this concept
|
self.props = {} # list of Property for this concept
|
||||||
self.functions = {} # list of helper functions
|
self.functions = {} # list of helper functions
|
||||||
|
|
||||||
@@ -46,11 +65,19 @@ class Concept:
|
|||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, Concept):
|
if not isinstance(other, Concept):
|
||||||
return False
|
return False
|
||||||
return self.name == other.name and \
|
|
||||||
self.where == other.where and \
|
# check the attributes
|
||||||
self.pre == other.pre and \
|
for prop in self.props_to_serialize:
|
||||||
self.post == other.post and \
|
if getattr(self, prop) != getattr(other, prop):
|
||||||
self.body == other.body
|
print(prop)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# check the props (Concept variables)
|
||||||
|
for var_name, p in self.props.items():
|
||||||
|
if p != other.props[var_name]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.name)
|
return hash(self.name)
|
||||||
@@ -68,7 +95,7 @@ class Concept:
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if self.key is not None:
|
if self.key is not None:
|
||||||
return self.key
|
return self
|
||||||
|
|
||||||
if tokens is None:
|
if tokens is None:
|
||||||
tokens = iter(Tokenizer(self.name))
|
tokens = iter(Tokenizer(self.name))
|
||||||
@@ -100,10 +127,11 @@ class Concept:
|
|||||||
So the values are kept in cache.
|
So the values are kept in cache.
|
||||||
|
|
||||||
For concepts loaded from sdp, these ASTs must be created again
|
For concepts loaded from sdp, these ASTs must be created again
|
||||||
|
TODO : Seems to be a service method. Can be put somewhere else
|
||||||
:param codes:
|
:param codes:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
possibles_codes = set(item.value for item in ConceptParts)
|
possibles_codes = self.concept_parts
|
||||||
if codes is None:
|
if codes is None:
|
||||||
return
|
return
|
||||||
for key in codes:
|
for key in codes:
|
||||||
@@ -117,14 +145,17 @@ class Concept:
|
|||||||
Returns the digest of the event
|
Returns the digest of the event
|
||||||
:return: hexa form of the sha256
|
:return: hexa form of the sha256
|
||||||
"""
|
"""
|
||||||
return hashlib.sha256(f"Concept:{self.name}{self.pre}{self.post}{self.body}".encode("utf-8")).hexdigest()
|
return hashlib.sha256(f"Concept:{self.to_dict(self.props_for_digest)}".encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self, props_to_use=None):
|
||||||
"""
|
"""
|
||||||
Returns a dict representing 'self'
|
Returns a dict representing 'self'
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
props_as_dict = dict((prop, getattr(self, prop)) for prop in self.props_to_serialize)
|
|
||||||
|
props_to_use = props_to_use or self.props_to_serialize
|
||||||
|
|
||||||
|
props_as_dict = dict((prop, getattr(self, prop)) for prop in props_to_use)
|
||||||
props_as_dict["props"] = [(p, self.props[p].value) for p in self.props]
|
props_as_dict["props"] = [(p, self.props[p].value) for p in self.props]
|
||||||
return props_as_dict
|
return props_as_dict
|
||||||
|
|
||||||
@@ -150,51 +181,30 @@ class Concept:
|
|||||||
:param other:
|
:param other:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
for prop in self.props_to_serialize:
|
if other is None:
|
||||||
setattr(self, prop, getattr(other, prop))
|
return self
|
||||||
|
|
||||||
|
self.from_dict(other.to_dict())
|
||||||
|
# for prop in self.props_to_serialize:
|
||||||
|
# setattr(self, prop, getattr(other, prop))
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def set_prop(self, prop_name, prop_value):
|
def set_prop(self, prop_name, prop_value=None):
|
||||||
self.props[prop_name] = Property(prop_name, prop_value)
|
self.props[prop_name] = Property(prop_name, prop_value)
|
||||||
|
return self
|
||||||
|
|
||||||
def set_prop_by_index(self, index, prop_value):
|
def set_prop_by_index(self, index, prop_value):
|
||||||
prop_name = list(self.props.keys())[index]
|
prop_name = list(self.props.keys())[index]
|
||||||
self.props[prop_name] = Property(prop_name, prop_value)
|
self.props[prop_name] = Property(prop_name, prop_value)
|
||||||
|
return self
|
||||||
class ErrorConcept(Concept):
|
|
||||||
NAME = "Error"
|
|
||||||
|
|
||||||
def __init__(self, where=None, pre=None, post=None, body=None, desc=None):
|
|
||||||
Concept.__init__(self, self.NAME, is_builtin=True, where=where, pre=pre, post=post, body=body, desc=desc)
|
|
||||||
self.key = self.NAME
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"({self.id}){self.name}: {self.body}"
|
|
||||||
|
|
||||||
|
|
||||||
class TooManySuccessConcept(Concept):
|
|
||||||
NAME = "Too many successful items"
|
|
||||||
|
|
||||||
def __init__(self, items=None):
|
|
||||||
super().__init__(self.NAME, body=items)
|
|
||||||
self.key = self.NAME
|
|
||||||
|
|
||||||
|
|
||||||
class ReturnValueConcept(Concept):
|
|
||||||
NAME = "Return Value"
|
|
||||||
|
|
||||||
def __init__(self, return_value=None):
|
|
||||||
super().__init__(self.NAME, body=return_value)
|
|
||||||
self.key = self.NAME
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"({self.id}){self.name}: {self.body}"
|
|
||||||
|
|
||||||
|
|
||||||
class Property:
|
class Property:
|
||||||
"""
|
"""
|
||||||
Defines a behaviour of Concept
|
Defines the variables of a concept
|
||||||
|
It as its specific class, because from experience,
|
||||||
|
property management is more complex than a key/value pair
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
@@ -203,3 +213,12 @@ class Property:
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.name}={self.value}"
|
return f"{self.name}={self.value}"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, Property):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.name == other.name and self.value == other.value
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.name, self.value))
|
||||||
|
|||||||
+266
-140
@@ -1,9 +1,9 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from core.builtin_concepts import BuiltinConcepts, ErrorConcept, ReturnValueConcept
|
||||||
from core.concept import Concept, ErrorConcept, Property, TooManySuccessConcept, ReturnValueConcept
|
from core.concept import Concept, ConceptParts
|
||||||
from parsers.PythonParser import PythonGetNamesVisitor, PythonNode
|
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||||
|
from parsers.BaseParser import BaseParser
|
||||||
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event, SheerkaDataProviderDuplicateKeyError
|
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event, SheerkaDataProviderDuplicateKeyError
|
||||||
from parsers.DefaultParser import DefConceptNode, DefaultParser
|
|
||||||
import core.utils
|
import core.utils
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -11,60 +11,28 @@ import logging
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Singleton(type):
|
|
||||||
_instances = {}
|
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs):
|
|
||||||
if cls not in cls._instances:
|
|
||||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
|
||||||
return cls._instances[cls]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ReturnValue:
|
|
||||||
"""
|
|
||||||
Class that handle the return of a concept
|
|
||||||
To avoid using the try/except pattern for each and every call
|
|
||||||
To give context (ie return message) even when the call is successful
|
|
||||||
"""
|
|
||||||
who: object
|
|
||||||
status: bool
|
|
||||||
value: Concept
|
|
||||||
message: str = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ExecutionContext:
|
|
||||||
"""
|
|
||||||
To keep track of the execution of a request
|
|
||||||
"""
|
|
||||||
sheerka: object
|
|
||||||
event_digest: str
|
|
||||||
|
|
||||||
|
|
||||||
class Sheerka(Concept):
|
class Sheerka(Concept):
|
||||||
"""
|
"""
|
||||||
Main controller for the project
|
Main controller for the project
|
||||||
"""
|
"""
|
||||||
|
|
||||||
NAME = "Sheerka"
|
|
||||||
UNKNOWN_CONCEPT_NAME = "Unknown Concept"
|
|
||||||
SUCCESS_CONCEPT_NAME = "Success"
|
|
||||||
CONCEPT_TOO_LONG_CONCEPT_NAME = "Concept too long"
|
|
||||||
|
|
||||||
CONCEPTS_ENTRY = "All_Concepts"
|
CONCEPTS_ENTRY = "All_Concepts"
|
||||||
BUILTIN_CONCEPTS_KEYS = "Builtins_Concepts"
|
BUILTIN_CONCEPTS_KEYS = "Builtins_Concepts"
|
||||||
USER_CONCEPTS_KEYS = "User_Concepts"
|
USER_CONCEPTS_KEYS = "User_Concepts"
|
||||||
|
|
||||||
def __init__(self, debug=False):
|
def __init__(self, debug=False):
|
||||||
log.debug("Starting Sheerka.")
|
log.debug("Starting Sheerka.")
|
||||||
super().__init__(Sheerka.NAME)
|
super().__init__(BuiltinConcepts.SHEERKA, True, True, BuiltinConcepts.SHEERKA)
|
||||||
|
|
||||||
# cache of the most used concepts
|
# cache of the most used concepts
|
||||||
# Note that these are only templates
|
# Note that these are only templates
|
||||||
# They are used as a footprint for instantiation
|
# They are used as a footprint for instantiation
|
||||||
self.concepts_cache = {}
|
self.concepts_cache = {}
|
||||||
|
|
||||||
|
# cache for builtin types.
|
||||||
|
# It allow instantiation of a builtin clas
|
||||||
|
self.builtin_cache = {}
|
||||||
|
|
||||||
# a concept can be instantiated
|
# a concept can be instantiated
|
||||||
# ex: File is a concept, but File('foo.txt') is an instance
|
# ex: File is a concept, but File('foo.txt') is an instance
|
||||||
# TODO: manage contexts
|
# TODO: manage contexts
|
||||||
@@ -78,7 +46,6 @@ class Sheerka(Concept):
|
|||||||
self.parsers = []
|
self.parsers = []
|
||||||
self.evaluators = []
|
self.evaluators = []
|
||||||
|
|
||||||
self.key = self.NAME
|
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
|
|
||||||
def initialize(self, root_folder=None):
|
def initialize(self, root_folder=None):
|
||||||
@@ -86,7 +53,6 @@ class Sheerka(Concept):
|
|||||||
Starting Sheerka
|
Starting Sheerka
|
||||||
Loads the current configuration
|
Loads the current configuration
|
||||||
Notes that when it's the first time, it also create the needed working folders
|
Notes that when it's the first time, it also create the needed working folders
|
||||||
:param debug:
|
|
||||||
:param root_folder: root configuration folder
|
:param root_folder: root configuration folder
|
||||||
:return: ReturnValue(Success or Error)
|
:return: ReturnValue(Success or Error)
|
||||||
"""
|
"""
|
||||||
@@ -94,22 +60,27 @@ class Sheerka(Concept):
|
|||||||
try:
|
try:
|
||||||
self.init_logging()
|
self.init_logging()
|
||||||
self.sdp = SheerkaDataProvider(root_folder)
|
self.sdp = SheerkaDataProvider(root_folder)
|
||||||
self.parsers.append(core.utils.get_class("parsers.DefaultParser.DefaultParser"))
|
|
||||||
self.parsers.append(core.utils.get_class("parsers.PythonParser.PythonParser"))
|
|
||||||
#self.parsers.append(core.utils.get_class("parsers.ExactConceptParser.ExactConceptParser"))
|
|
||||||
|
|
||||||
self.evaluators.append(core.utils.get_object("evaluators.DefaultEvaluator.DefaultEvaluator"))
|
|
||||||
self.evaluators.append(core.utils.get_object("evaluators.AddConceptEvaluator.AddConceptEvaluator"))
|
|
||||||
self.evaluators.append(core.utils.get_object("evaluators.PythonEvaluator.PythonEvaluator"))
|
|
||||||
|
|
||||||
if self.sdp.first_time:
|
if self.sdp.first_time:
|
||||||
self.sdp.set_key(self.USER_CONCEPTS_KEYS, 1000)
|
self.sdp.set_key(self.USER_CONCEPTS_KEYS, 1000)
|
||||||
|
|
||||||
self.create_builtin_concepts()
|
self.initialize_builtin_concepts()
|
||||||
except IOError as e:
|
|
||||||
return ReturnValue(self, False, self.get(ErrorConcept.NAME), e)
|
|
||||||
|
|
||||||
return ReturnValue(self, True, self.get(Sheerka.SUCCESS_CONCEPT_NAME))
|
self.parsers.append(core.utils.get_class("parsers.DefaultParser.DefaultParser"))
|
||||||
|
self.parsers.append(core.utils.get_class("parsers.PythonParser.PythonParser"))
|
||||||
|
self.parsers.append(core.utils.get_class("parsers.ExactConceptParser.ExactConceptParser"))
|
||||||
|
|
||||||
|
self.evaluators.append(core.utils.new_object("evaluators.ParsersEvaluator.ParsersEvaluator"))
|
||||||
|
self.evaluators.append(core.utils.new_object("evaluators.AddConceptEvaluator.AddConceptEvaluator"))
|
||||||
|
self.evaluators.append(core.utils.new_object("evaluators.PythonEvaluator.PythonEvaluator"))
|
||||||
|
self.evaluators.append(core.utils.new_object("evaluators.ConceptEvaluator.ConceptEvaluator"))
|
||||||
|
self.evaluators.append(
|
||||||
|
core.utils.new_object("evaluators.DuplicateConceptEvaluator.DuplicateConceptEvaluator"))
|
||||||
|
|
||||||
|
except IOError as e:
|
||||||
|
return ReturnValueConcept(self, False, self.get(BuiltinConcepts.ERROR), e)
|
||||||
|
|
||||||
|
return ReturnValueConcept(self, True, self)
|
||||||
|
|
||||||
def set_id_if_needed(self, obj, is_builtin):
|
def set_id_if_needed(self, obj, is_builtin):
|
||||||
"""
|
"""
|
||||||
@@ -123,34 +94,35 @@ class Sheerka(Concept):
|
|||||||
obj.id = self.sdp.get_next_key(self.BUILTIN_CONCEPTS_KEYS if is_builtin else self.USER_CONCEPTS_KEYS)
|
obj.id = self.sdp.get_next_key(self.BUILTIN_CONCEPTS_KEYS if is_builtin else self.USER_CONCEPTS_KEYS)
|
||||||
log.debug(f"Setting id '{obj.id}' to concept '{obj.name}'.")
|
log.debug(f"Setting id '{obj.id}' to concept '{obj.name}'.")
|
||||||
|
|
||||||
def create_builtin_concepts(self):
|
def initialize_builtin_concepts(self):
|
||||||
"""
|
"""
|
||||||
Initializes the builtin concepts
|
Initializes the builtin concepts
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
log.debug("Initializing builtin concepts")
|
log.debug("Initializing builtin concepts")
|
||||||
builtins = [
|
builtins_classes = self.get_builtins_classes_as_dict()
|
||||||
self,
|
|
||||||
Concept(Sheerka.UNKNOWN_CONCEPT_NAME, key=Sheerka.UNKNOWN_CONCEPT_NAME),
|
|
||||||
Concept(Sheerka.SUCCESS_CONCEPT_NAME, key=Sheerka.SUCCESS_CONCEPT_NAME),
|
|
||||||
Concept(Sheerka.CONCEPT_TOO_LONG_CONCEPT_NAME, key=Sheerka.CONCEPT_TOO_LONG_CONCEPT_NAME),
|
|
||||||
ErrorConcept(),
|
|
||||||
TooManySuccessConcept(),
|
|
||||||
ReturnValueConcept(),
|
|
||||||
]
|
|
||||||
|
|
||||||
for concept in builtins:
|
# this all initialization of the builtins seems to be little bit complicated
|
||||||
self.add_in_cache(concept)
|
# why do we need to update it from DB ?
|
||||||
|
for key in BuiltinConcepts:
|
||||||
|
concept = self if key == BuiltinConcepts.SHEERKA \
|
||||||
|
else builtins_classes[str(key)]() if str(key) in builtins_classes \
|
||||||
|
else Concept(key, True, False, key)
|
||||||
|
|
||||||
|
if not concept.is_unique and str(key) in builtins_classes:
|
||||||
|
self.builtin_cache[key] = builtins_classes[str(key)]
|
||||||
|
|
||||||
from_db = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.key)
|
from_db = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept.key)
|
||||||
if from_db is None:
|
if from_db is None:
|
||||||
log.debug(f"'{concept.name}' concept is not found. Adding.")
|
log.debug(f"'{concept.name}' concept is not found in db. Adding.")
|
||||||
self.set_id_if_needed(concept, True)
|
self.set_id_if_needed(concept, True)
|
||||||
self.sdp.add("init", self.CONCEPTS_ENTRY, concept, use_ref=True)
|
self.sdp.add("init", self.CONCEPTS_ENTRY, concept, use_ref=True)
|
||||||
else:
|
else:
|
||||||
log.debug(f"Found concept '{from_db}'. Updating.")
|
log.debug(f"Found concept '{from_db}' in db. Updating.")
|
||||||
concept.update_from(from_db)
|
concept.update_from(from_db)
|
||||||
|
|
||||||
|
self.add_in_cache(concept)
|
||||||
|
|
||||||
def init_logging(self):
|
def init_logging(self):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
log_format = "%(asctime)s %(name)s [%(levelname)s] %(message)s"
|
log_format = "%(asctime)s %(name)s [%(levelname)s] %(message)s"
|
||||||
@@ -163,37 +135,75 @@ class Sheerka(Concept):
|
|||||||
|
|
||||||
def eval(self, text):
|
def eval(self, text):
|
||||||
evt_digest = self.sdp.save_event(Event(text))
|
evt_digest = self.sdp.save_event(Event(text))
|
||||||
exec_context = ExecutionContext(self, evt_digest)
|
exec_context = ExecutionContext(self.key, evt_digest, self)
|
||||||
return_values = self.try_parse(exec_context, text)
|
|
||||||
return_values = self.try_eval(exec_context, return_values)
|
|
||||||
|
|
||||||
# return_values = []
|
before_parsing = self.ret(self.eval.__name__, True, self.new(BuiltinConcepts.BEFORE_PARSING))
|
||||||
# for parser_name, status, node in result:
|
return_values = self.process(exec_context, [], [before_parsing])
|
||||||
# if not status:
|
return_values = core.utils.remove_from_list(return_values, [before_parsing])
|
||||||
# return_values.append(ReturnValue(False, ErrorConcept(body=node)))
|
|
||||||
# elif status and isinstance(node, DefConceptNode):
|
parsing_results = self.parse(exec_context, text)
|
||||||
# return_values.append(self.add_concept(exec_context, node))
|
return_values.extend(parsing_results)
|
||||||
# else:
|
processing_parsing = self.ret(self.eval.__name__, True, self.new(BuiltinConcepts.PARSING))
|
||||||
# return_values.append(ReturnValue(True, node))
|
return_values = self.process(exec_context, return_values, [processing_parsing])
|
||||||
|
return_values = core.utils.remove_from_list(return_values, [processing_parsing])
|
||||||
|
|
||||||
|
after_parsing = self.ret(self.eval.__name__, True, self.new(BuiltinConcepts.AFTER_PARSING))
|
||||||
|
return_values = self.process(exec_context, return_values, [after_parsing])
|
||||||
|
return_values = core.utils.remove_from_list(return_values, [after_parsing])
|
||||||
|
|
||||||
return return_values
|
return return_values
|
||||||
|
|
||||||
def try_parse(self, context, text):
|
def expect_one(self, context, items):
|
||||||
|
|
||||||
|
if not isinstance(items, list):
|
||||||
|
items = [items]
|
||||||
|
|
||||||
|
if len(items) == 0:
|
||||||
|
return self.ret(context.who, False, self.new(BuiltinConcepts.IS_EMPTY, obj=items))
|
||||||
|
|
||||||
|
successful_results = [item for item in items if item.status]
|
||||||
|
number_of_successful = len(successful_results)
|
||||||
|
total_items = len(items)
|
||||||
|
|
||||||
|
# remove errors when a winner is found
|
||||||
|
if number_of_successful == 1:
|
||||||
|
# log.debug(f"1 / {total_items} good item found.")
|
||||||
|
return successful_results[0]
|
||||||
|
|
||||||
|
# too many winners, which one to choose ?
|
||||||
|
if number_of_successful > 1:
|
||||||
|
log.debug(f"{number_of_successful} / {total_items} good items. Too many success")
|
||||||
|
return self.ret(context.who, False, self.new(BuiltinConcepts.TOO_MANY_SUCCESS, obj=successful_results))
|
||||||
|
|
||||||
|
# only errors, i cannot help you
|
||||||
|
log.debug(f"{total_items} items. Only errors")
|
||||||
|
return self.ret(context.who, False, self.new(BuiltinConcepts.TOO_MANY_ERRORS, obj=items))
|
||||||
|
|
||||||
|
def parse(self, context, text):
|
||||||
result = []
|
result = []
|
||||||
log.debug(f"Parsing '{text}'")
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
|
debug_text = "'" + text + "'" if isinstance(text, str) \
|
||||||
|
else "'" + BaseParser.get_text_from_tokens(text) + "' as tokens"
|
||||||
|
log.debug(f"Parsing {debug_text}")
|
||||||
for parser in self.parsers:
|
for parser in self.parsers:
|
||||||
p = parser()
|
p = parser()
|
||||||
# try:
|
res = p.parse(context, text)
|
||||||
# tree = p.parse()
|
if isinstance(res, list):
|
||||||
# result.append((p.name, tree))
|
result.extend(res)
|
||||||
# except Exception as e:
|
else:
|
||||||
# result.append((p.name, e))
|
result.append(res)
|
||||||
tree = p.parse(context, text)
|
|
||||||
result.append(ReturnValue(p.name, not p.has_error, p.error_sink if p.has_error else tree))
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def try_eval(self, context, items):
|
def process(self, context, return_values, contextual_concepts=None):
|
||||||
log.debug("Evaluating parsing result.")
|
log.debug("Evaluating parsing result.")
|
||||||
|
|
||||||
|
# init
|
||||||
|
if not isinstance(return_values, list):
|
||||||
|
return_values = [return_values]
|
||||||
|
|
||||||
|
if contextual_concepts:
|
||||||
|
return_values.extend(contextual_concepts)
|
||||||
|
|
||||||
# group the evaluators by priority and sort them
|
# group the evaluators by priority and sort them
|
||||||
# The first one to be applied will be the one with the highest priority
|
# The first one to be applied will be the one with the highest priority
|
||||||
grouped_evaluators = {}
|
grouped_evaluators = {}
|
||||||
@@ -201,60 +211,102 @@ class Sheerka(Concept):
|
|||||||
grouped_evaluators.setdefault(item.priority, []).append(item)
|
grouped_evaluators.setdefault(item.priority, []).append(item)
|
||||||
sorted_priorities = sorted(grouped_evaluators.keys(), reverse=True)
|
sorted_priorities = sorted(grouped_evaluators.keys(), reverse=True)
|
||||||
|
|
||||||
for priority in sorted_priorities:
|
# process
|
||||||
log.debug("Processing priority " + str(priority))
|
while True:
|
||||||
for item in items:
|
simple_digest = return_values[:] # set(id(r) for r in return_values)
|
||||||
log.debug(item)
|
|
||||||
original_items = items[:]
|
for priority in sorted_priorities:
|
||||||
evaluated_items = []
|
# log.debug("Processing priority " + str(priority))
|
||||||
for evaluator in grouped_evaluators[priority]:
|
# for item in return_values:
|
||||||
if evaluator.matches(context, original_items):
|
# log.debug(item)
|
||||||
result = evaluator.eval(context, original_items)
|
original_items = return_values[:]
|
||||||
if isinstance(result, list):
|
evaluated_items = []
|
||||||
evaluated_items.extend(result)
|
to_delete = []
|
||||||
|
for evaluator in grouped_evaluators[priority]:
|
||||||
|
|
||||||
|
# process evaluators that work on return value
|
||||||
|
if isinstance(evaluator, OneReturnValueEvaluator):
|
||||||
|
for item in original_items:
|
||||||
|
if evaluator.matches(context, item):
|
||||||
|
result = evaluator.eval(context, item)
|
||||||
|
if result is None:
|
||||||
|
continue
|
||||||
|
elif isinstance(result, list):
|
||||||
|
evaluated_items.extend(result)
|
||||||
|
to_delete.append(item)
|
||||||
|
elif isinstance(result, ReturnValueConcept):
|
||||||
|
evaluated_items.append(result)
|
||||||
|
to_delete.append(item)
|
||||||
|
else:
|
||||||
|
error = self.new(BuiltinConcepts.INVALID_RETURN_VALUE, body=result,
|
||||||
|
evaluator=evaluator)
|
||||||
|
evaluated_items.append(self.ret("sheerka.process", False, error, parents=[item]))
|
||||||
|
to_delete.append(item)
|
||||||
|
# process evaluators that work on all return values
|
||||||
else:
|
else:
|
||||||
evaluated_items.append(result)
|
if evaluator.matches(context, original_items):
|
||||||
|
results = evaluator.eval(context, original_items)
|
||||||
|
if not isinstance(results, list):
|
||||||
|
results = [results]
|
||||||
|
for result in results:
|
||||||
|
evaluated_items.append(result)
|
||||||
|
to_delete.extend(result.parents)
|
||||||
|
|
||||||
# what was computed by this group will be the input of the following group
|
return_values = evaluated_items
|
||||||
items = evaluated_items if len(evaluated_items) > 0 else original_items
|
return_values.extend([item for item in original_items if item not in to_delete])
|
||||||
|
|
||||||
return items
|
# have we done something ?
|
||||||
|
to_compare = return_values[:] # set(id(r) for r in return_values)
|
||||||
|
if simple_digest == to_compare:
|
||||||
|
break
|
||||||
|
|
||||||
def add_concept(self, exec_context, def_concept_node: DefConceptNode):
|
return return_values
|
||||||
|
|
||||||
|
def create_new_concept(self, context, concept):
|
||||||
"""
|
"""
|
||||||
Adds a new concept to the system
|
Adds a new concept to the system
|
||||||
:param exec_context:
|
:param context:
|
||||||
:param def_concept_node: DefConceptNode
|
:param concept: DefConceptNode
|
||||||
:return: digest of the new concept
|
:return: digest of the new concept
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# validate the node
|
concept.init_key()
|
||||||
get_names_visitor = PythonGetNamesVisitor()
|
|
||||||
|
|
||||||
concept = Concept(def_concept_node.name)
|
# checks for duplicate concepts
|
||||||
for prop in ("where", "pre", "post", "body"):
|
if self.sdp.exists(self.CONCEPTS_ENTRY, concept.key, concept.get_digest()):
|
||||||
# put back the sources
|
error = SheerkaDataProviderDuplicateKeyError(self.CONCEPTS_ENTRY + "." + concept.key, concept)
|
||||||
concept_part_node = getattr(def_concept_node, prop)
|
return self.ret(self.create_new_concept.__name__, False, ErrorConcept(error), error.args[0])
|
||||||
if isinstance(concept_part_node, PythonNode):
|
|
||||||
get_names_visitor.visit(concept_part_node.ast)
|
|
||||||
source = concept_part_node.source if hasattr(concept_part_node, "source") else ""
|
|
||||||
setattr(concept, prop, source)
|
|
||||||
|
|
||||||
# try to find variables (eg props)
|
# set id before saving in db
|
||||||
# Note that with this method, the variables will be created in the order of appearance
|
|
||||||
for token in def_concept_node.tokens["name"]:
|
|
||||||
if token.value in get_names_visitor.names:
|
|
||||||
concept.set_prop(token.value, None)
|
|
||||||
|
|
||||||
concept.init_key(def_concept_node.tokens["name"])
|
|
||||||
concept.add_codes(def_concept_node.get_codes())
|
|
||||||
self.set_id_if_needed(concept, False)
|
self.set_id_if_needed(concept, False)
|
||||||
|
|
||||||
|
# save the new context in sdp
|
||||||
try:
|
try:
|
||||||
self.sdp.add(exec_context.event_digest, self.CONCEPTS_ENTRY, concept, use_ref=True)
|
self.sdp.add(context.event_digest, self.CONCEPTS_ENTRY, concept, use_ref=True)
|
||||||
except SheerkaDataProviderDuplicateKeyError as error:
|
except SheerkaDataProviderDuplicateKeyError as error:
|
||||||
return ReturnValue(self.add_concept.__name__, False, ErrorConcept(body=error), error.args[0])
|
return self.ret(self.create_new_concept.__name__, False, ErrorConcept(error), error.args[0])
|
||||||
return ReturnValue(self.add_concept.__name__, True, concept)
|
|
||||||
|
# add in cache for quick further reference
|
||||||
|
self.concepts_cache[concept.key] = concept
|
||||||
|
|
||||||
|
# process the return in needed
|
||||||
|
ret = self.ret(self.create_new_concept.__name__, True, self.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def add_codes_to_concept(self, context, concept):
|
||||||
|
"""
|
||||||
|
Updates the codes of the newly created concept
|
||||||
|
Basically, it runs the parsers on all parts
|
||||||
|
:param concept:
|
||||||
|
:param context:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for part_key in ConceptParts:
|
||||||
|
source = getattr(concept, part_key.value)
|
||||||
|
if source is None or source == "":
|
||||||
|
continue
|
||||||
|
ret_val = self.expect_one(context, self.parse(context, source))
|
||||||
|
concept.codes[part_key] = ret_val
|
||||||
|
|
||||||
def add_in_cache(self, concept):
|
def add_in_cache(self, concept):
|
||||||
"""
|
"""
|
||||||
@@ -268,36 +320,85 @@ class Sheerka(Concept):
|
|||||||
def get(self, concept_key):
|
def get(self, concept_key):
|
||||||
"""
|
"""
|
||||||
Tries to find a concept
|
Tries to find a concept
|
||||||
TODO: how to manage single vs multiple instances
|
What is return must be used a template for another concept.
|
||||||
|
You must not modify the returned concept
|
||||||
:param concept_key:
|
:param concept_key:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if isinstance(concept_key, BuiltinConcepts):
|
||||||
|
concept_key = str(concept_key)
|
||||||
|
|
||||||
# first search in cache
|
# first search in cache
|
||||||
if concept_key in self.concepts_cache:
|
if concept_key in self.concepts_cache:
|
||||||
return self.concepts_cache[concept_key]
|
return self.concepts_cache[concept_key]
|
||||||
|
|
||||||
return self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key) or \
|
# else look in sdp
|
||||||
self.new(self.UNKNOWN_CONCEPT_NAME, body=concept_key)
|
from_db = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
|
||||||
|
if from_db is not None:
|
||||||
|
return from_db
|
||||||
|
|
||||||
def new(self, concept, **kwargs):
|
# else return new Unknown concept
|
||||||
|
# Note that I don't call the new() method, as it use get() -> cyclic call
|
||||||
|
unknown_concept = Concept()
|
||||||
|
template = self.concepts_cache[str(BuiltinConcepts.UNKNOWN_CONCEPT)]
|
||||||
|
unknown_concept.update_from(template)
|
||||||
|
unknown_concept.body = concept_key
|
||||||
|
return unknown_concept
|
||||||
|
|
||||||
|
def new(self, concept_key, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns an instance of a new concept
|
Returns an instance of a new concept
|
||||||
TODO: Checks if the concept is supposed to be unique (ex Sheerka, or the number 'one' for example)
|
When the concept is supposed to be unique, returns the same instance
|
||||||
:param concept:
|
:param concept_key:
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
template = self.get(concept_key)
|
||||||
|
|
||||||
if isinstance(concept, str):
|
# manage concept not found
|
||||||
concept = self.get(concept)
|
if self.isinstance(template, BuiltinConcepts.UNKNOWN_CONCEPT) and \
|
||||||
|
concept_key != BuiltinConcepts.UNKNOWN_CONCEPT:
|
||||||
|
return template
|
||||||
|
|
||||||
|
# manage singleton
|
||||||
|
if template.is_unique:
|
||||||
|
return template
|
||||||
|
|
||||||
|
# otherwise, create another instance
|
||||||
|
concept = self.builtin_cache[concept_key]() if concept_key in self.builtin_cache else Concept()
|
||||||
|
concept.update_from(template)
|
||||||
|
|
||||||
|
# update the properties
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
if hasattr(concept, k):
|
if k in concept.props:
|
||||||
|
concept.set_prop(k, v)
|
||||||
|
elif hasattr(concept, k):
|
||||||
setattr(concept, k, v)
|
setattr(concept, k, v)
|
||||||
|
else:
|
||||||
|
return self.new(BuiltinConcepts.UNKNOWN_PROPERTY, body=k, concept=concept)
|
||||||
|
|
||||||
|
# TODO : add the concept to the list of known concepts (self.instances)
|
||||||
return concept
|
return concept
|
||||||
|
|
||||||
|
def ret(self, who, status, value, message=None, parents=None):
|
||||||
|
"""
|
||||||
|
Creates and returns a ReturnValue concept
|
||||||
|
:param who:
|
||||||
|
:param status:
|
||||||
|
:param value:
|
||||||
|
:param message:
|
||||||
|
:param parents:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return self.new(
|
||||||
|
BuiltinConcepts.RETURN_VALUE,
|
||||||
|
who=who,
|
||||||
|
status=status,
|
||||||
|
value=value,
|
||||||
|
message=message,
|
||||||
|
parents=parents)
|
||||||
|
|
||||||
def isinstance(self, a, b):
|
def isinstance(self, a, b):
|
||||||
"""
|
"""
|
||||||
return true if the concept a is an instance of the concept b
|
return true if the concept a is an instance of the concept b
|
||||||
@@ -306,15 +407,40 @@ class Sheerka(Concept):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(a, Concept):
|
if isinstance(a, BuiltinConcepts): # common KSI error ;-)
|
||||||
raise SyntaxError("The first parameter of isinstance MUST be a concept")
|
raise SyntaxError("Remember that the first parameter of isinstance MUST be a concept")
|
||||||
|
|
||||||
b_key = b if isinstance(b, str) else b.key
|
if not isinstance(a, Concept):
|
||||||
|
return False
|
||||||
|
|
||||||
|
b_key = b.key if isinstance(b, Concept) else str(b)
|
||||||
|
|
||||||
# TODO : manage when a is the list of all possible b
|
# TODO : manage when a is the list of all possible b
|
||||||
# for example, if a is a color, it will be found the entry 'All_Colors'
|
# for example, if a is a color, it will be found the entry 'All_Colors'
|
||||||
return a.key == b_key
|
return a.key == b_key
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_builtins_classes_as_dict():
|
||||||
|
res = {}
|
||||||
|
for c in core.utils.get_classes("core.builtin_concepts"):
|
||||||
|
if issubclass(c, Concept) and c != Concept:
|
||||||
|
res[c().key] = c
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def test():
|
def test():
|
||||||
return "I have access to Sheerka !"
|
return "I have access to Sheerka !"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExecutionContext:
|
||||||
|
"""
|
||||||
|
To keep track of the execution of a request
|
||||||
|
"""
|
||||||
|
who: object
|
||||||
|
event_digest: str
|
||||||
|
sheerka: Sheerka
|
||||||
|
|
||||||
|
def push(self, who):
|
||||||
|
return ExecutionContext(who, self.event_digest, self.sheerka)
|
||||||
|
|||||||
+23
-13
@@ -32,19 +32,17 @@ class TokenKind(Enum):
|
|||||||
AMPER = "amper"
|
AMPER = "amper"
|
||||||
EQUALS = "="
|
EQUALS = "="
|
||||||
AT = "at"
|
AT = "at"
|
||||||
BACK_QUOTE = "bquote" # `
|
BACK_QUOTE = "bquote" # `
|
||||||
BACK_SLASH = "bslash" # \
|
BACK_SLASH = "bslash" # \
|
||||||
CARAT = "carat" # ^
|
CARAT = "carat" # ^
|
||||||
DOLLAR = "dollar" # $
|
DOLLAR = "dollar" # $
|
||||||
EMARK = "emark" # !
|
EMARK = "emark" # !
|
||||||
GREATER = "greater" # >
|
GREATER = "greater" # >
|
||||||
LESS = "less" # <
|
LESS = "less" # <
|
||||||
HASH = "HASH" # #
|
HASH = "HASH" # #
|
||||||
TILDE = "tilde" # ~
|
TILDE = "tilde" # ~
|
||||||
UNDERSCORE = "underscore" # _
|
UNDERSCORE = "underscore" # _
|
||||||
DEGREE = "degree" # °
|
DEGREE = "degree" # °
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
@@ -55,6 +53,18 @@ class Token:
|
|||||||
line: int
|
line: int
|
||||||
column: int
|
column: int
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if type == TokenKind.IDENTIFIER:
|
||||||
|
value = "ident:" + str(self.value)
|
||||||
|
elif type == TokenKind.WHITESPACE:
|
||||||
|
value = " "
|
||||||
|
elif type == TokenKind.NEWLINE:
|
||||||
|
value = r"\n"
|
||||||
|
else:
|
||||||
|
value = self.value
|
||||||
|
|
||||||
|
return f"Token({value})"
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class LexerError(Exception):
|
class LexerError(Exception):
|
||||||
|
|||||||
+45
-5
@@ -1,3 +1,7 @@
|
|||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def sysarg_to_string(argv):
|
def sysarg_to_string(argv):
|
||||||
"""
|
"""
|
||||||
Transform a list of strings into a single string
|
Transform a list of strings into a single string
|
||||||
@@ -16,24 +20,38 @@ def sysarg_to_string(argv):
|
|||||||
result += '"' + s + '"' if " " in s else s
|
result += '"' + s + '"' if " " in s else s
|
||||||
first = False
|
first = False
|
||||||
|
|
||||||
|
if result[0] in ('"', "'"):
|
||||||
|
result = result[1:-1] # strip quotes
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_class(kls):
|
def get_class(qname):
|
||||||
"""
|
"""
|
||||||
Loads a class from its string full qualified name
|
Loads a class from its full qualified name
|
||||||
:param kls:
|
:param qname:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
parts = kls.split('.')
|
parts = qname.split('.')
|
||||||
module = ".".join(parts[:-1])
|
module = ".".join(parts[:-1])
|
||||||
m = __import__(module)
|
m = __import__(module)
|
||||||
for comp in parts[1:]:
|
for comp in parts[1:]:
|
||||||
m = getattr(m, comp)
|
m = getattr(m, comp)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
def get_module(qname):
|
||||||
|
"""
|
||||||
|
Loads a module from its full qualified name
|
||||||
|
:param qname:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
parts = qname.split('.')
|
||||||
|
m = __import__(qname)
|
||||||
|
for comp in parts[1:]:
|
||||||
|
m = getattr(m, comp)
|
||||||
|
return m
|
||||||
|
|
||||||
def get_object(kls, *args, **kwargs):
|
|
||||||
|
def new_object(kls, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
New instance of an object
|
New instance of an object
|
||||||
:param kls:
|
:param kls:
|
||||||
@@ -56,3 +74,25 @@ def get_full_qualified_name(obj):
|
|||||||
return obj.__class__.__name__ # Avoid reporting __builtin__
|
return obj.__class__.__name__ # Avoid reporting __builtin__
|
||||||
else:
|
else:
|
||||||
return module + '.' + obj.__class__.__name__
|
return module + '.' + obj.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def get_classes(module_name):
|
||||||
|
mod = get_module(module_name)
|
||||||
|
for name in dir(mod):
|
||||||
|
obj = getattr(mod, name)
|
||||||
|
if inspect.isclass(obj):
|
||||||
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
|
def remove_from_list(lst, to_remove):
|
||||||
|
"""
|
||||||
|
Removes elements from a list if they exist
|
||||||
|
:param lst:
|
||||||
|
:param to_remove:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for item in to_remove:
|
||||||
|
if item in lst:
|
||||||
|
lst.remove(item)
|
||||||
|
|
||||||
|
return lst
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
class Concept:
|
||||||
|
* name
|
||||||
|
|
||||||
|
human reading name of the concept. It is how the concept will be referenced
|
||||||
|
* is_builtin
|
||||||
|
|
||||||
|
Simple flag to make the difference between user defined concept and builtins
|
||||||
|
|
||||||
|
* is_unique
|
||||||
|
|
||||||
|
To manage singleton. There should be only one instance of the concept if True
|
||||||
|
|
||||||
|
* key
|
||||||
|
|
||||||
|
It is the name of the concept, where all variables, or the obj are replaced by
|
||||||
|
__var__x (x is the number of the variable). It's use to quickly find concepts
|
||||||
|
with variables
|
||||||
|
|
||||||
|
* id
|
||||||
|
|
||||||
|
The key is the unique identifier for the class of concept. The concept may be
|
||||||
|
modified along its life, but its key will never change
|
||||||
|
|
||||||
|
* where
|
||||||
|
|
||||||
|
preconditions for the variables of the concept. Body will be executed only
|
||||||
|
if the where conditions on the properties are True
|
||||||
|
|
||||||
|
* pre
|
||||||
|
|
||||||
|
Execution context to verified before executing the context
|
||||||
|
|
||||||
|
* post
|
||||||
|
|
||||||
|
Post conditions to verify, once the context is executed
|
||||||
|
|
||||||
|
* obj
|
||||||
|
|
||||||
|
main or principal subject of the concept
|
||||||
|
|
||||||
|
* body
|
||||||
|
|
||||||
|
Code or value of the concept
|
||||||
|
|
||||||
|
* desc
|
||||||
|
|
||||||
|
explanation of the concept
|
||||||
|
|
||||||
|
* digest
|
||||||
|
|
||||||
|
SHA 256 of all properties. It allows
|
||||||
|
|
||||||
|
* props
|
||||||
|
|
||||||
|
other properties of the concept (obj is the first one)
|
||||||
|
|
||||||
+15
-1
@@ -382,4 +382,18 @@ so I can type
|
|||||||
1 + 1
|
1 + 1
|
||||||
sheerka.test()
|
sheerka.test()
|
||||||
|
|
||||||
I will now work on how to call an already defined concept.
|
I will now work on how to call an already defined concept.
|
||||||
|
|
||||||
|
|
||||||
|
2019-11-11
|
||||||
|
**********
|
||||||
|
|
||||||
|
Maintaining the blog
|
||||||
|
""""""""""""""""""""
|
||||||
|
It's not very easy to maintain this blog. Every time I have some time to work on **Sheerka**,
|
||||||
|
I must choose between expressing my ideas in this blog and coding.
|
||||||
|
|
||||||
|
I have plenty of ideas that I would like to express, sometimes just to put the idea down,
|
||||||
|
but I lack of time. It would be great if I can find a tool that will allow me to just to
|
||||||
|
dictate my words. I know that there are plenty out there, I need to spend some time to test
|
||||||
|
them and choose one.
|
||||||
+15
-4
@@ -1,6 +1,10 @@
|
|||||||
|
In this example, I try to think of how I can deal with errors
|
||||||
```
|
```
|
||||||
> "hello
|
> "hello
|
||||||
-> unfinished quote "
|
-> unfinished quote "
|
||||||
|
|
||||||
|
create a concept to recognize the error
|
||||||
|
|
||||||
> def concept unfinished quote q
|
> def concept unfinished quote q
|
||||||
... where:
|
... where:
|
||||||
...... q in ('"', '"')
|
...... q in ('"', '"')
|
||||||
@@ -9,9 +13,16 @@
|
|||||||
... input = sheerka.last_input
|
... input = sheerka.last_input
|
||||||
|
|
||||||
|
|
||||||
|
Create the rule to manage the error
|
||||||
|
|
||||||
> when unfinished quote q as c:
|
> when unfinished quote q as c:
|
||||||
... add rule as:
|
... add rule r as:
|
||||||
...... if q in sheerka.input:
|
...... if q is last of sheerka.input:
|
||||||
......... sheerka.resume(c, c.input + input)
|
......... sheerka.resume(c, c.input + input)
|
||||||
......... remove rule
|
......... remove r
|
||||||
```
|
|
||||||
|
def concept a is last of b as:
|
||||||
|
where b is list
|
||||||
|
as b[-1] == a
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
from evaluators.BaseEvaluator import BaseEvaluator
|
from core.builtin_concepts import ParserResultConcept, ReturnValueConcept
|
||||||
|
from core.concept import Concept
|
||||||
|
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||||
from parsers.DefaultParser import DefConceptNode
|
from parsers.DefaultParser import DefConceptNode
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from parsers.PythonParser import PythonGetNamesVisitor, PythonNode
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AddConceptEvaluator(BaseEvaluator):
|
class AddConceptEvaluator(OneReturnValueEvaluator):
|
||||||
"""
|
"""
|
||||||
Used to add a new concept
|
Used to add a new concept
|
||||||
"""
|
"""
|
||||||
@@ -13,11 +17,60 @@ class AddConceptEvaluator(BaseEvaluator):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Add new Concept", 50)
|
super().__init__("Add new Concept", 50)
|
||||||
|
|
||||||
def matches(self, context, items):
|
def matches(self, context, return_value):
|
||||||
return len(items) == 1 and items[0].status and isinstance(items[0].value, DefConceptNode)
|
return return_value.status and \
|
||||||
|
isinstance(return_value.value, ParserResultConcept) and \
|
||||||
|
isinstance(return_value.value.value, DefConceptNode)
|
||||||
|
|
||||||
def eval(self, context, items):
|
def eval(self, context, return_value):
|
||||||
log.debug("Adding a new concept")
|
log.debug("Adding a new concept")
|
||||||
node = items[0].value
|
def_concept_node = return_value.value.value
|
||||||
sheerka = context.sheerka
|
sheerka = context.sheerka
|
||||||
return sheerka.add_concept(context, node)
|
|
||||||
|
# validate the node
|
||||||
|
props_found = set()
|
||||||
|
|
||||||
|
concept = Concept(def_concept_node.name)
|
||||||
|
for prop in ("where", "pre", "post", "body"):
|
||||||
|
# put back the sources
|
||||||
|
part_ret_val = getattr(def_concept_node, prop)
|
||||||
|
if not isinstance(part_ret_val, ReturnValueConcept) or not part_ret_val.status:
|
||||||
|
continue # not quite sure that it's possible
|
||||||
|
|
||||||
|
# update the parts
|
||||||
|
source = self.get_source(part_ret_val)
|
||||||
|
setattr(concept, prop, source)
|
||||||
|
|
||||||
|
# try to find what can be a property
|
||||||
|
for p in self.get_props(part_ret_val):
|
||||||
|
props_found.add(p)
|
||||||
|
|
||||||
|
# Auto discovered properties must be referenced in the name
|
||||||
|
# Note that with this method, the variables will be created in the order of appearance
|
||||||
|
for token in def_concept_node.name.tokens:
|
||||||
|
if token.value in props_found:
|
||||||
|
concept.set_prop(token.value, None)
|
||||||
|
|
||||||
|
# finish initialisation
|
||||||
|
concept.init_key(def_concept_node.name.tokens)
|
||||||
|
concept.add_codes(def_concept_node.get_codes())
|
||||||
|
|
||||||
|
ret = sheerka.create_new_concept(context, concept)
|
||||||
|
return sheerka.ret(self.name, ret.status, ret.value, parents=[return_value])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_source(ret_value):
|
||||||
|
return ret_value.value.source if isinstance(ret_value.value, ParserResultConcept) \
|
||||||
|
else ret_value.value.name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_props(ret_value):
|
||||||
|
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, PythonNode):
|
||||||
|
get_names_visitor = PythonGetNamesVisitor()
|
||||||
|
get_names_visitor.visit(ret_value.value.value.ast_)
|
||||||
|
return get_names_visitor.names
|
||||||
|
|
||||||
|
if isinstance(ret_value.value, Concept):
|
||||||
|
return list(ret_value.value.props.keys())
|
||||||
|
|
||||||
|
return []
|
||||||
|
|||||||
@@ -1,14 +1,34 @@
|
|||||||
class BaseEvaluator:
|
class BaseEvaluator:
|
||||||
"""
|
"""
|
||||||
base class to evaluate concepts or nodes
|
Base class to evaluate ReturnValues
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
PREFIX = "Evaluators:"
|
||||||
|
|
||||||
def __init__(self, name, priority: int):
|
def __init__(self, name, priority: int):
|
||||||
self.name = name
|
self.name = self.PREFIX + name
|
||||||
self.priority = priority
|
self.priority = priority
|
||||||
|
|
||||||
def matches(self, context, items):
|
|
||||||
|
class OneReturnValueEvaluator(BaseEvaluator):
|
||||||
|
"""
|
||||||
|
Evaluate one specific return value
|
||||||
|
"""
|
||||||
|
|
||||||
|
def matches(self, context, return_value):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def eval(self, context, items):
|
def eval(self, context, return_value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AllReturnValuesEvaluator(BaseEvaluator):
|
||||||
|
"""
|
||||||
|
Evaluates the groups of ReturnValues
|
||||||
|
"""
|
||||||
|
|
||||||
|
def matches(self, context, return_values):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def eval(self, context, return_values):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from core.concept import Concept, ConceptParts
|
||||||
|
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConceptEvaluator(OneReturnValueEvaluator):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("Concept Evaluator", 50)
|
||||||
|
|
||||||
|
def matches(self, context, return_value):
|
||||||
|
return return_value.status and \
|
||||||
|
return_value.who == "Parsers:ConceptParser" and \
|
||||||
|
isinstance(return_value.value, Concept)
|
||||||
|
|
||||||
|
def eval(self, context, return_value):
|
||||||
|
sheerka = context.sheerka
|
||||||
|
concept = return_value.value
|
||||||
|
|
||||||
|
# pre condition should already be validated by the parser.
|
||||||
|
# It's a mandatory condition for the concept before it can be recognized
|
||||||
|
|
||||||
|
if len(concept.codes) == 0:
|
||||||
|
sheerka.add_codes_to_concept(context, concept)
|
||||||
|
|
||||||
|
# TODO; check pre
|
||||||
|
# if pre is not true, return Concept with a false value
|
||||||
|
|
||||||
|
body = concept.codes[ConceptParts.BODY]
|
||||||
|
if body is None:
|
||||||
|
return None # nothing to do
|
||||||
|
|
||||||
|
return sheerka.ret(self.name, True, body.value, parents=[return_value])
|
||||||
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
from core.concept import TooManySuccessConcept
|
|
||||||
from core.sheerka import ReturnValue
|
|
||||||
from evaluators.BaseEvaluator import BaseEvaluator
|
|
||||||
import logging
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultEvaluator(BaseEvaluator):
|
|
||||||
"""
|
|
||||||
Used to filter the responses of the parsers
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("Default Evaluator", 90)
|
|
||||||
|
|
||||||
def matches(self, context, items):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def eval(self, context, items):
|
|
||||||
successful_results = [item for item in items if item.status]
|
|
||||||
number_of_successful = len(successful_results)
|
|
||||||
total_items = len(items)
|
|
||||||
|
|
||||||
# remove errors when a winner is found
|
|
||||||
if number_of_successful == 1:
|
|
||||||
log.debug(f"1 / {total_items} good item found.")
|
|
||||||
return successful_results
|
|
||||||
|
|
||||||
# too many winners, which one to choose ?
|
|
||||||
if number_of_successful > 1:
|
|
||||||
log.debug(f"{number_of_successful} / {total_items} good items. Too many success")
|
|
||||||
return ReturnValue(self.name,
|
|
||||||
False,
|
|
||||||
context.sheerka.new(TooManySuccessConcept.NAME, body=items))
|
|
||||||
|
|
||||||
# only errors, i cannot help you
|
|
||||||
log.debug(f"{total_items} items. Only errors")
|
|
||||||
return items
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from core.builtin_concepts import BuiltinConcepts
|
||||||
|
from evaluators.BaseEvaluator import AllReturnValuesEvaluator
|
||||||
|
from parsers.BaseParser import BaseParser
|
||||||
|
|
||||||
|
|
||||||
|
class DuplicateConceptEvaluator(AllReturnValuesEvaluator):
|
||||||
|
"""
|
||||||
|
Use to recognize when we tried to add the same concept twice
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("Duplicate Concept Evaluator", 10)
|
||||||
|
self.already_defined = None
|
||||||
|
|
||||||
|
def matches(self, context, return_values):
|
||||||
|
sheerka = context.sheerka
|
||||||
|
parsing = False
|
||||||
|
add_concept_in_error = False
|
||||||
|
only_parsers = True
|
||||||
|
|
||||||
|
for ret in return_values:
|
||||||
|
if sheerka.isinstance(ret.value, BuiltinConcepts.PARSING):
|
||||||
|
if ret.status:
|
||||||
|
parsing = True
|
||||||
|
elif ret.who == "Evaluators:Add new Concept":
|
||||||
|
if not ret.status and ret.value.body.args[0] == "Duplicate object.":
|
||||||
|
add_concept_in_error = True
|
||||||
|
self.already_defined = ret.value.body.obj
|
||||||
|
else:
|
||||||
|
if not ret.who.startswith(BaseParser.PREFIX):
|
||||||
|
only_parsers = False
|
||||||
|
|
||||||
|
return parsing and add_concept_in_error and only_parsers
|
||||||
|
|
||||||
|
def eval(self, context, return_values):
|
||||||
|
sheerka = context.sheerka
|
||||||
|
return sheerka.ret(
|
||||||
|
self.name,
|
||||||
|
False,
|
||||||
|
sheerka.new(BuiltinConcepts.CONCEPT_ALREADY_DEFINED, obj=self.already_defined),
|
||||||
|
parents=return_values)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from core.builtin_concepts import BuiltinConcepts
|
||||||
|
from core.concept import Concept
|
||||||
|
from evaluators.BaseEvaluator import AllReturnValuesEvaluator
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from parsers.BaseParser import BaseParser
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ParsersEvaluator(AllReturnValuesEvaluator):
|
||||||
|
"""
|
||||||
|
Used to filter the responses
|
||||||
|
It has a low priority to let other evaluators try to resolve the errors
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("Parsers Evaluator", 10)
|
||||||
|
self.successful_return_value = None
|
||||||
|
|
||||||
|
def matches(self, context, return_values):
|
||||||
|
sheerka = context.sheerka
|
||||||
|
after_parsing = False
|
||||||
|
nb_successful_evaluators = 0
|
||||||
|
only_parsers = True
|
||||||
|
for ret in return_values:
|
||||||
|
if sheerka.isinstance(ret.value, BuiltinConcepts.AFTER_PARSING):
|
||||||
|
if ret.status:
|
||||||
|
after_parsing = True
|
||||||
|
elif ret.who.startswith(self.PREFIX):
|
||||||
|
if ret.status:
|
||||||
|
nb_successful_evaluators += 1
|
||||||
|
self.successful_return_value = ret
|
||||||
|
else:
|
||||||
|
if not ret.who.startswith(BaseParser.PREFIX):
|
||||||
|
only_parsers = False
|
||||||
|
|
||||||
|
return after_parsing and nb_successful_evaluators == 1 and only_parsers
|
||||||
|
|
||||||
|
def eval(self, context, return_values):
|
||||||
|
sheerka = context.sheerka
|
||||||
|
return sheerka.ret(self.name, True, self.successful_return_value.value, parents=return_values)
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
from core.concept import ReturnValueConcept, ErrorConcept
|
from core.builtin_concepts import BuiltinConcepts, ParserResultConcept
|
||||||
from evaluators.BaseEvaluator import BaseEvaluator
|
from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||||
from parsers.PythonParser import PythonNode
|
from parsers.PythonParser import PythonNode
|
||||||
import ast
|
import ast
|
||||||
from core.sheerka import ReturnValue, Sheerka
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PythonEvaluator(BaseEvaluator):
|
class PythonEvaluator(OneReturnValueEvaluator):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Python Evaluator", 50)
|
super().__init__("Python Evaluator", 50)
|
||||||
|
|
||||||
def matches(self, context, items):
|
def matches(self, context, return_value):
|
||||||
return len(items) == 1 and isinstance(items[0].value, PythonNode)
|
return return_value.status and \
|
||||||
|
isinstance(return_value.value, ParserResultConcept) and \
|
||||||
|
isinstance(return_value.value.value, PythonNode)
|
||||||
|
|
||||||
def eval(self, context, items):
|
def eval(self, context, return_value):
|
||||||
sheerka = context.sheerka
|
sheerka = context.sheerka
|
||||||
node = items[0].value
|
node = return_value.value.value
|
||||||
if isinstance(node.ast, ast.Expression):
|
if isinstance(node.ast_, ast.Expression):
|
||||||
try:
|
try:
|
||||||
log.debug("Evaluating python expression")
|
log.debug(f"Evaluating python node {node}")
|
||||||
compiled = compile(node.ast, "<string>", "eval")
|
compiled = compile(node.ast_, "<string>", "eval")
|
||||||
evaluated = eval(compiled, {}, {"sheerka": context.sheerka})
|
evaluated = eval(compiled, {}, {"sheerka": context.sheerka})
|
||||||
concept = sheerka.new(ReturnValueConcept.NAME, body=evaluated)
|
return sheerka.ret(self.name, True, evaluated, parents=[return_value])
|
||||||
return ReturnValue(self.name, True, concept)
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
error = sheerka.new(ErrorConcept.NAME, body=error)
|
error = sheerka.new(BuiltinConcepts.ERROR, body=error)
|
||||||
return ReturnValue(self.name, False, error)
|
return sheerka.ret(self.name, False, error, parents=[return_value])
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import getopt
|
import getopt
|
||||||
import sys
|
import sys
|
||||||
from core.sheerka import Sheerka
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from core.sheerka import Sheerka
|
||||||
|
import core.utils
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print("Sheerka v0.1\n")
|
print("Sheerka v0.1\n")
|
||||||
@@ -10,40 +12,6 @@ def usage():
|
|||||||
print(sys.argv[0] + "[-hd] command ")
|
print(sys.argv[0] + "[-hd] command ")
|
||||||
|
|
||||||
|
|
||||||
def sysarg_to_string(argv):
|
|
||||||
"""
|
|
||||||
Transform a list of strings into a single string
|
|
||||||
Add quotes if needed
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if argv is None or not argv:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
result = ""
|
|
||||||
first = True
|
|
||||||
for s in argv:
|
|
||||||
if not first:
|
|
||||||
result += " "
|
|
||||||
|
|
||||||
result += '"' + s + '"' if " " in s else s
|
|
||||||
first = False
|
|
||||||
|
|
||||||
if result[0] in ('"', "'"):
|
|
||||||
result = result[1:-1] # strip quotes
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def init_logging(debug):
|
|
||||||
if debug:
|
|
||||||
log_format = "%(asctime)s %(name)s [%(levelname)s] %(message)s"
|
|
||||||
log_level = logging.DEBUG
|
|
||||||
else:
|
|
||||||
log_format = "%(message)s"
|
|
||||||
log_level = logging.INFO
|
|
||||||
|
|
||||||
logging.basicConfig(format=log_format, level=log_level)
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv, "hd", ["help", "debug"])
|
opts, args = getopt.getopt(argv, "hd", ["help", "debug"])
|
||||||
@@ -55,11 +23,10 @@ def main(argv):
|
|||||||
if o in ('-d', "--debug"):
|
if o in ('-d', "--debug"):
|
||||||
debug = True
|
debug = True
|
||||||
|
|
||||||
# init_logging(debug)
|
|
||||||
sheerka = Sheerka(debug=debug)
|
sheerka = Sheerka(debug=debug)
|
||||||
sheerka.initialize()
|
sheerka.initialize()
|
||||||
|
|
||||||
_in = sysarg_to_string(args)
|
_in = core.utils.sysarg_to_string(args)
|
||||||
result = sheerka.eval(_in)
|
result = sheerka.eval(_in)
|
||||||
|
|
||||||
for res in result:
|
for res in result:
|
||||||
|
|||||||
+25
-1
@@ -15,17 +15,34 @@ class NopNode(Node):
|
|||||||
return "nop"
|
return "nop"
|
||||||
|
|
||||||
|
|
||||||
|
class NotInitializedNode(Node):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "**N/A**"
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class ErrorNode(Node):
|
class ErrorNode(Node):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BaseParser:
|
class BaseParser:
|
||||||
|
PREFIX = "Parsers:"
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = self.PREFIX + name
|
||||||
self.has_error = False
|
self.has_error = False
|
||||||
self.error_sink = []
|
self.error_sink = []
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return False
|
||||||
|
return self.name == other.name
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.name)
|
||||||
|
|
||||||
def parse(self, context, text):
|
def parse(self, context, text):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -38,3 +55,10 @@ class BaseParser:
|
|||||||
value = Keywords(token.value).value if token.type == TokenKind.KEYWORD else token.value
|
value = Keywords(token.value).value if token.type == TokenKind.KEYWORD else token.value
|
||||||
res += value
|
res += value
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def log_result(log, text, ret):
|
||||||
|
if ret.status:
|
||||||
|
log.debug(f"Recognized '{text}' as {ret.value}")
|
||||||
|
else:
|
||||||
|
log.debug(f"Failed to recognize '{text}'")
|
||||||
|
|||||||
+303
-230
@@ -1,4 +1,6 @@
|
|||||||
from parsers.BaseParser import BaseParser, Node, NopNode, ErrorNode
|
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||||
|
from core.concept import ConceptParts
|
||||||
|
from parsers.BaseParser import BaseParser, Node, NopNode, ErrorNode, NotInitializedNode
|
||||||
from core.tokenizer import Tokenizer, TokenKind, Token, Keywords
|
from core.tokenizer import Tokenizer, TokenKind, Token, Keywords
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
import logging
|
import logging
|
||||||
@@ -8,16 +10,10 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class DefaultParserNode(Node):
|
class DefaultParserNode(Node):
|
||||||
tokens: list = field(compare=False)
|
"""
|
||||||
|
Base node for all default parser nodes
|
||||||
def is_same(self, other):
|
"""
|
||||||
if type(self) != type(other):
|
tokens: list = field(compare=False, repr=False)
|
||||||
return False
|
|
||||||
|
|
||||||
if hasattr(self, "value") and self.value != other.value:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
@@ -30,8 +26,8 @@ class UnexpectedTokenErrorNode(DefaultParserErrorNode):
|
|||||||
message: str
|
message: str
|
||||||
expected_tokens: list
|
expected_tokens: list
|
||||||
|
|
||||||
def __post_init__(self):
|
# def __post_init__(self):
|
||||||
log.debug("-> UnexpectedTokenErrorNode: " + self.message)
|
# log.debug("-> UnexpectedTokenErrorNode: " + self.message)
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
@@ -41,8 +37,8 @@ class SyntaxErrorNode(DefaultParserErrorNode):
|
|||||||
"""
|
"""
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
def __post_init__(self):
|
# def __post_init__(self):
|
||||||
log.debug("-> SyntaxErrorNode: " + self.message)
|
# log.debug("-> SyntaxErrorNode: " + self.message)
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
@@ -52,117 +48,209 @@ class CannotHandleErrorNode(DefaultParserErrorNode):
|
|||||||
"""
|
"""
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
def __post_init__(self):
|
# def __post_init__(self):
|
||||||
log.debug("-> CannotHandleErrorNode: " + self.text)
|
# log.debug("-> CannotHandleErrorNode: " + self.text)
|
||||||
|
|
||||||
|
#
|
||||||
|
# @dataclass()
|
||||||
|
# class NumberNode(DefaultParserNode):
|
||||||
|
# value: object
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return str(self.value)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @dataclass()
|
||||||
|
# class StringNode(DefaultParserNode):
|
||||||
|
# value: str
|
||||||
|
# quote: str
|
||||||
|
#
|
||||||
|
# def is_same(self, other):
|
||||||
|
# if not super(StringNode, self).is_same(other):
|
||||||
|
# return False
|
||||||
|
# return self.quote == other.quote
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return self.quote + self.value + self.quote
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @dataclass()
|
||||||
|
# class VariableNode(DefaultParserNode):
|
||||||
|
# value: str
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return self.value
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @dataclass()
|
||||||
|
# class TrueNode(DefaultParserNode):
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return "true"
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @dataclass()
|
||||||
|
# class FalseNode(DefaultParserNode):
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return "false"
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @dataclass()
|
||||||
|
# class NullNode(DefaultParserNode):
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return "null"
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @dataclass()
|
||||||
|
# class BinaryNode(DefaultParserNode):
|
||||||
|
# operator: TokenKind
|
||||||
|
# left: Node
|
||||||
|
# right: Node
|
||||||
|
#
|
||||||
|
# def is_same(self, other):
|
||||||
|
# if not super(BinaryNode, self).is_same(other):
|
||||||
|
# return False
|
||||||
|
# if self.operator != other.operator:
|
||||||
|
# return False
|
||||||
|
# if not self.left.is_same(other.left):
|
||||||
|
# return False
|
||||||
|
# return self.right.is_same(other.right)
|
||||||
|
#
|
||||||
|
# def __repr__(self):
|
||||||
|
# return f"({self.left} {self.operator} {self.right})"
|
||||||
|
#
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_concept_key(tokens, variables=None):
|
||||||
|
key = ""
|
||||||
|
first = True
|
||||||
|
for token in tokens:
|
||||||
|
if token.type == TokenKind.EOF:
|
||||||
|
break
|
||||||
|
if token.type == TokenKind.WHITESPACE:
|
||||||
|
continue
|
||||||
|
if not first:
|
||||||
|
key += " "
|
||||||
|
if variables is not None and token.value in variables:
|
||||||
|
key += "__var__" + str(variables.index(token.value))
|
||||||
|
else:
|
||||||
|
key += token.value[1:-1] if token.type == TokenKind.STRING else token.value
|
||||||
|
first = False
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass()
|
||||||
|
class NameNode(DefaultParserNode):
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
name = ""
|
||||||
|
first = True
|
||||||
|
for token in self.tokens:
|
||||||
|
if token.type == TokenKind.EOF:
|
||||||
|
break
|
||||||
|
if token.type == TokenKind.WHITESPACE:
|
||||||
|
continue
|
||||||
|
if not first:
|
||||||
|
name += " "
|
||||||
|
|
||||||
|
name += token.value[1:-1] if token.type == TokenKind.STRING else token.value
|
||||||
|
first = False
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.get_name()
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, NameNode):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.get_name() == other.get_name()
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.get_name())
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class DefConceptNode(DefaultParserNode):
|
class DefConceptNode(DefaultParserNode):
|
||||||
name: str
|
|
||||||
where: Node = None
|
name: NameNode = NotInitializedNode()
|
||||||
pre: Node = None
|
where: ReturnValueConcept = NotInitializedNode()
|
||||||
post: Node = None
|
pre: ReturnValueConcept = NotInitializedNode()
|
||||||
body: Node = NopNode
|
post: ReturnValueConcept = NotInitializedNode()
|
||||||
|
body: ReturnValueConcept = NotInitializedNode()
|
||||||
|
|
||||||
def get_codes(self):
|
def get_codes(self):
|
||||||
codes = {}
|
codes = {}
|
||||||
for prop in ["where", "pre", "post", "body"]:
|
for part_key in ConceptParts:
|
||||||
prop_value = getattr(self, prop)
|
prop_value = getattr(self, part_key.value)
|
||||||
if hasattr(prop_value, "ast"):
|
if hasattr(prop_value, "ast_"):
|
||||||
codes[prop] = prop_value.ast
|
codes[part_key] = prop_value.ast_
|
||||||
return codes
|
return codes
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
|
||||||
class NumberNode(DefaultParserNode):
|
|
||||||
value: object
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self.value)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
|
||||||
class StringNode(DefaultParserNode):
|
|
||||||
value: str
|
|
||||||
quote: str
|
|
||||||
|
|
||||||
def is_same(self, other):
|
|
||||||
if not super(StringNode, self).is_same(other):
|
|
||||||
return False
|
|
||||||
return self.quote == other.quote
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.quote + self.value + self.quote
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
|
||||||
class VariableNode(DefaultParserNode):
|
|
||||||
value: str
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
|
||||||
class TrueNode(DefaultParserNode):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "true"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
|
||||||
class FalseNode(DefaultParserNode):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "false"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
|
||||||
class NullNode(DefaultParserNode):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "null"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
|
||||||
class BinaryNode(DefaultParserNode):
|
|
||||||
operator: TokenKind
|
|
||||||
left: Node
|
|
||||||
right: Node
|
|
||||||
|
|
||||||
def is_same(self, other):
|
|
||||||
if not super(BinaryNode, self).is_same(other):
|
|
||||||
return False
|
|
||||||
if self.operator != other.operator:
|
|
||||||
return False
|
|
||||||
if not self.left.is_same(other.left):
|
|
||||||
return False
|
|
||||||
return self.right.is_same(other.right)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"({self.left} {self.operator} {self.right})"
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultParser(BaseParser):
|
class DefaultParser(BaseParser):
|
||||||
"""
|
"""
|
||||||
Parse sheerka specific grammar (like def concept)
|
Parse sheerka specific grammar (like def concept)
|
||||||
"""
|
"""
|
||||||
def __init__(self, sub_parser=None):
|
|
||||||
|
def __init__(self):
|
||||||
BaseParser.__init__(self, "DefaultParser")
|
BaseParser.__init__(self, "DefaultParser")
|
||||||
self.sub_parser = sub_parser
|
|
||||||
self.lexer_iter = None
|
self.lexer_iter = None
|
||||||
self._current = None
|
self._current = None
|
||||||
self.context = None
|
self.context = None
|
||||||
self.text = None
|
self.text = None
|
||||||
|
self.sheerka = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fix_indentation(tokens):
|
||||||
|
"""
|
||||||
|
In the following example
|
||||||
|
def concept add one to a as:
|
||||||
|
def func(x):
|
||||||
|
return x+1
|
||||||
|
func(a)
|
||||||
|
indentations in front of 'def func(x)', 'return x+1' and 'func(a)' must be fixed to avoid a python syntax error
|
||||||
|
:param tokens:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if tokens[0].type != TokenKind.COLON:
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
if len(tokens) < 3:
|
||||||
|
return UnexpectedTokenErrorNode(tokens[0:2], "Unexpected end of file", [TokenKind.NEWLINE])
|
||||||
|
|
||||||
|
if tokens[1].type != TokenKind.NEWLINE:
|
||||||
|
return UnexpectedTokenErrorNode([tokens[1]], "Unexpected token after colon", [TokenKind.NEWLINE])
|
||||||
|
|
||||||
|
if tokens[2].type != TokenKind.WHITESPACE:
|
||||||
|
return SyntaxErrorNode([tokens[2]], "Indentation not found.")
|
||||||
|
indent_size = len(tokens[2].value)
|
||||||
|
|
||||||
|
# now fix the other indentations
|
||||||
|
i = 3
|
||||||
|
while i < len(tokens) - 1:
|
||||||
|
if tokens[i].type == TokenKind.NEWLINE:
|
||||||
|
if tokens[i + 1].type != TokenKind.WHITESPACE:
|
||||||
|
return UnexpectedTokenErrorNode([tokens[i + 1]], "Unexpected token", [TokenKind.WHITESPACE])
|
||||||
|
|
||||||
|
if len(tokens[i + 1].value) < indent_size:
|
||||||
|
return SyntaxErrorNode([tokens[i + 1]], "Invalid indentation.")
|
||||||
|
|
||||||
|
tokens[i + 1].value = " " * (len(tokens[i + 1].value) - indent_size)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return tokens[3:]
|
||||||
|
|
||||||
def reset_parser(self, context, text):
|
def reset_parser(self, context, text):
|
||||||
self.context = context
|
self.context = context
|
||||||
# hack before implementing all the sub parsers
|
self.sheerka = context.sheerka
|
||||||
if context:
|
|
||||||
self.sub_parser = context.sheerka.parsers[1]
|
|
||||||
|
|
||||||
self.text = text
|
self.text = text
|
||||||
self.lexer_iter = iter(Tokenizer(text))
|
self.lexer_iter = iter(Tokenizer(text))
|
||||||
@@ -190,167 +278,152 @@ class DefaultParser(BaseParser):
|
|||||||
self._current = None
|
self._current = None
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_concept_key(tokens, variables=None):
|
|
||||||
key = ""
|
|
||||||
first = True
|
|
||||||
for token in tokens:
|
|
||||||
if token.type == TokenKind.EOF:
|
|
||||||
break
|
|
||||||
if token.type == TokenKind.WHITESPACE:
|
|
||||||
continue
|
|
||||||
if not first:
|
|
||||||
key += " "
|
|
||||||
if variables is not None and token.value in variables:
|
|
||||||
key += "__var__" + str(variables.index(token.value))
|
|
||||||
else:
|
|
||||||
key += token.value[1:-1] if token.type == TokenKind.STRING else token.value
|
|
||||||
first = False
|
|
||||||
|
|
||||||
return key
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def fix_indentation(tokens):
|
|
||||||
"""
|
|
||||||
In the following example
|
|
||||||
def concept add one to a as:
|
|
||||||
def func(x):
|
|
||||||
return x+1
|
|
||||||
func(a)
|
|
||||||
indentations in front of 'def func(x)', 'return x+1' and 'func(a)' must be fixed to avoid a python syntax error
|
|
||||||
:param tokens:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
if tokens[1].type != TokenKind.COLON:
|
|
||||||
return tokens[1:]
|
|
||||||
|
|
||||||
if len(tokens) < 3:
|
|
||||||
return UnexpectedTokenErrorNode(tokens[0:2], "Unexpected end of file", [TokenKind.NEWLINE])
|
|
||||||
|
|
||||||
if tokens[2].type != TokenKind.NEWLINE:
|
|
||||||
return UnexpectedTokenErrorNode([tokens[2]], "Unexpected token after colon", [TokenKind.NEWLINE])
|
|
||||||
|
|
||||||
if tokens[3].type != TokenKind.WHITESPACE:
|
|
||||||
return SyntaxErrorNode([tokens[3]], "Indentation not found")
|
|
||||||
indent_size = len(tokens[3].value)
|
|
||||||
|
|
||||||
# now fix the other indentations
|
|
||||||
i = 4
|
|
||||||
while i < len(tokens) - 1:
|
|
||||||
if tokens[i].type == TokenKind.NEWLINE:
|
|
||||||
if tokens[i + 1].type != TokenKind.WHITESPACE:
|
|
||||||
return UnexpectedTokenErrorNode([tokens[i + 1]], "Unexpected token", [TokenKind.WHITESPACE])
|
|
||||||
|
|
||||||
if len(tokens[i + 1].value) < indent_size:
|
|
||||||
return SyntaxErrorNode([tokens[i + 1]], "Invalid indentation.")
|
|
||||||
|
|
||||||
tokens[i + 1].value = " " * (len(tokens[i + 1].value) - indent_size)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
return tokens[4:]
|
|
||||||
|
|
||||||
def parse(self, context, text):
|
def parse(self, context, text):
|
||||||
|
# default parser can only manage string text
|
||||||
|
if not isinstance(text, str):
|
||||||
|
log.debug(f"Failed to recognize '{text}'")
|
||||||
|
return context.sheerka.ret(
|
||||||
|
self.name,
|
||||||
|
False,
|
||||||
|
context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=text))
|
||||||
|
|
||||||
self.reset_parser(context, text)
|
self.reset_parser(context, text)
|
||||||
return self.parse_statement()
|
tree = self.parse_statement()
|
||||||
|
|
||||||
|
# If a error is found it must be sent to error_sink
|
||||||
|
# tree must contain what was recognized
|
||||||
|
|
||||||
|
ret = self.sheerka.ret(
|
||||||
|
self.name,
|
||||||
|
not self.has_error,
|
||||||
|
self.sheerka.new(
|
||||||
|
BuiltinConcepts.PARSER_RESULT,
|
||||||
|
parser=self,
|
||||||
|
source=text,
|
||||||
|
body=self.error_sink if self.has_error else tree,
|
||||||
|
try_parsed=tree))
|
||||||
|
|
||||||
|
self.log_result(log, text, ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
def parse_statement(self):
|
def parse_statement(self):
|
||||||
token = self.get_token()
|
token = self.get_token()
|
||||||
if token.value == Keywords.DEF:
|
if token.value == Keywords.DEF:
|
||||||
self.next_token()
|
self.next_token()
|
||||||
return self.parse_def_concept()
|
return self.parse_def_concept(token)
|
||||||
else:
|
else:
|
||||||
return self.add_error(CannotHandleErrorNode([], self.text))
|
return self.add_error(CannotHandleErrorNode([], self.text))
|
||||||
|
|
||||||
def parse_def_concept(self):
|
def parse_def_concept(self, def_token):
|
||||||
"""
|
"""
|
||||||
def concept name [where xxx] [pre xxx] [post xxx] [as xxx]
|
def concept name [where xxx] [pre xxx] [post xxx] [as xxx]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def_concept_parts = [Keywords.AS, Keywords.WHERE, Keywords.PRE, Keywords.POST]
|
# init
|
||||||
|
log.debug("It may be a definition of a concept")
|
||||||
|
concept_special_tokens = [def_token]
|
||||||
|
concept_found = DefConceptNode(concept_special_tokens)
|
||||||
|
|
||||||
tokens_found = {} # Node token is supposed to be a list, but here, it will be a dict
|
# the definition of a concept consists of several parts
|
||||||
|
# Keywords.CONCEPT to get the name of the concept
|
||||||
|
# Keywords.AS to get the body
|
||||||
|
# Keywords.WHERE to get the conditions to recognize for the variables
|
||||||
|
# Keywords.PRE to know if the conditions to evaluate the concept
|
||||||
|
# Keywords.POST to apply or verify once the concept is executed
|
||||||
|
def_concept_parts = [Keywords.CONCEPT, Keywords.AS, Keywords.WHERE, Keywords.PRE, Keywords.POST]
|
||||||
|
|
||||||
token = self.get_token()
|
# tokens found, when trying to recognize the parts
|
||||||
if token.value != Keywords.CONCEPT:
|
tokens_found_by_parts = {
|
||||||
return self.add_error(UnexpectedTokenErrorNode([token], "Syntax error.", [Keywords.CONCEPT]))
|
Keywords.CONCEPT: [],
|
||||||
|
|
||||||
self.next_token()
|
|
||||||
token = self.get_token()
|
|
||||||
|
|
||||||
if token.value in (Keywords.AS, Keywords.WHERE, Keywords.PRE, Keywords.POST):
|
|
||||||
return self.add_error(UnexpectedTokenErrorNode([token], "Concept name is missing.", ["<name>"]))
|
|
||||||
|
|
||||||
name_as_tokens = []
|
|
||||||
while token.type != TokenKind.EOF and token.value not in def_concept_parts:
|
|
||||||
name_as_tokens.append(token)
|
|
||||||
self.next_token()
|
|
||||||
token = self.get_token()
|
|
||||||
name = self.get_concept_key(name_as_tokens)
|
|
||||||
tokens_found["name"] = name_as_tokens
|
|
||||||
|
|
||||||
# try to parse as, where, pre and post declarations
|
|
||||||
tokens = {
|
|
||||||
Keywords.AS: None,
|
Keywords.AS: None,
|
||||||
Keywords.WHERE: None,
|
Keywords.WHERE: None,
|
||||||
Keywords.PRE: None,
|
Keywords.PRE: None,
|
||||||
Keywords.POST: None,
|
Keywords.POST: None,
|
||||||
}
|
}
|
||||||
current_part = None
|
current_part = Keywords.CONCEPT
|
||||||
|
token = self.get_token()
|
||||||
|
first_token = token
|
||||||
|
|
||||||
|
# loop thru the tokens, and put them in the correct tokens_found_by_parts entry
|
||||||
while token.type != TokenKind.EOF:
|
while token.type != TokenKind.EOF:
|
||||||
if token.value in def_concept_parts:
|
if token.value in def_concept_parts:
|
||||||
|
concept_special_tokens.append(token) # keep track of the keywords
|
||||||
keyword = token.value
|
keyword = token.value
|
||||||
if tokens[keyword]:
|
if tokens_found_by_parts[keyword]:
|
||||||
return self.add_error(SyntaxErrorNode([token], f"Too many '{keyword.value}' declarations."))
|
# a part is defined more than once
|
||||||
tokens[keyword] = [token] # first element of the list is the keyword
|
self.add_error(SyntaxErrorNode([token], f"Too many '{keyword.value}' declarations."))
|
||||||
|
tokens_found_by_parts[current_part].append(token) # adds the token again
|
||||||
|
else:
|
||||||
|
tokens_found_by_parts[keyword] = [token]
|
||||||
current_part = keyword
|
current_part = keyword
|
||||||
self.next_token()
|
self.next_token()
|
||||||
else:
|
else:
|
||||||
if current_part is None:
|
tokens_found_by_parts[current_part].append(token)
|
||||||
return self.add_error(UnexpectedTokenErrorNode([token], "Unexpected token", def_concept_parts))
|
self.next_token(False)
|
||||||
else:
|
|
||||||
tokens[current_part].append(token)
|
|
||||||
self.next_token(False)
|
|
||||||
|
|
||||||
token = self.get_token()
|
token = self.get_token()
|
||||||
for t in tokens:
|
|
||||||
tokens_found[t.value] = tokens[t]
|
|
||||||
|
|
||||||
asts = {
|
# semantic checks
|
||||||
Keywords.AS: NopNode(),
|
name_first_token_index = 1
|
||||||
Keywords.WHERE: NopNode(),
|
if first_token.value != Keywords.CONCEPT:
|
||||||
Keywords.PRE: NopNode(),
|
self.add_error(UnexpectedTokenErrorNode([token], "Syntax error.", [Keywords.CONCEPT]))
|
||||||
Keywords.POST: NopNode(),
|
name_first_token_index = 0
|
||||||
|
|
||||||
|
# Manage the name
|
||||||
|
name_tokens = tokens_found_by_parts[Keywords.CONCEPT]
|
||||||
|
if len(name_tokens) == name_first_token_index:
|
||||||
|
self.add_error(SyntaxErrorNode([], "Name is mandatory"))
|
||||||
|
|
||||||
|
if name_tokens[-1].type == TokenKind.NEWLINE:
|
||||||
|
name_tokens = name_tokens[:-1] # strip trailing newlines
|
||||||
|
|
||||||
|
if TokenKind.NEWLINE in [t.type for t in name_tokens]:
|
||||||
|
self.add_error(SyntaxErrorNode(tokens_found_by_parts[Keywords.CONCEPT], "Newline are not allowed in name."))
|
||||||
|
|
||||||
|
concept_found.name = NameNode(name_tokens[name_first_token_index:]) # skip the first token
|
||||||
|
|
||||||
|
asts_found_by_parts = {
|
||||||
|
Keywords.AS: NotInitializedNode(),
|
||||||
|
Keywords.WHERE: NotInitializedNode(),
|
||||||
|
Keywords.PRE: NotInitializedNode(),
|
||||||
|
Keywords.POST: NotInitializedNode(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# check for empty declarations
|
for keyword in tokens_found_by_parts:
|
||||||
for keyword in tokens:
|
if keyword == Keywords.CONCEPT:
|
||||||
current_tokens = tokens[keyword]
|
continue # already done
|
||||||
if current_tokens is not None:
|
|
||||||
if len(current_tokens) == 0: # only one element means empty decl
|
|
||||||
return self.add_error(SyntaxErrorNode([current_tokens[0]], "Empty declaration"), False)
|
|
||||||
else:
|
|
||||||
current_tokens = self.fix_indentation(current_tokens)
|
|
||||||
if isinstance(current_tokens, ErrorNode):
|
|
||||||
self.add_error(current_tokens)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# start = current_tokens[0].index
|
log.debug("Processing part '" + keyword.name + "'")
|
||||||
# end = current_tokens[-1].index + len(current_tokens[-1].value)
|
|
||||||
sub_parser = self.sub_parser(source=keyword.value)
|
|
||||||
sub_tree = sub_parser.parse(self.context, current_tokens)
|
|
||||||
if isinstance(sub_tree, ErrorNode):
|
|
||||||
self.add_error(sub_tree, False)
|
|
||||||
asts[keyword] = sub_tree
|
|
||||||
|
|
||||||
def_concept_node = DefConceptNode(tokens_found, # dict instead of list is wanted.
|
tokens = tokens_found_by_parts[keyword]
|
||||||
name,
|
if tokens is None:
|
||||||
asts[Keywords.WHERE],
|
continue # nothing to do
|
||||||
asts[Keywords.PRE],
|
|
||||||
asts[Keywords.POST],
|
|
||||||
asts[Keywords.AS])
|
|
||||||
|
|
||||||
log.debug(f"Found DefConcept node '{def_concept_node}'")
|
if len(tokens) == 1: # check for empty declarations
|
||||||
return def_concept_node
|
self.add_error(SyntaxErrorNode([tokens[0]], "Empty declaration"), False)
|
||||||
|
continue
|
||||||
|
|
||||||
|
tokens = self.fix_indentation(tokens[1:]) # manage multi-lines declarations
|
||||||
|
if isinstance(tokens, ErrorNode):
|
||||||
|
self.add_error(tokens)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ask the other parsers if they recognize the tokens
|
||||||
|
new_context = self.context.push(self)
|
||||||
|
parsing_result = self.sheerka.expect_one(new_context, self.sheerka.parse(new_context, tokens))
|
||||||
|
if not parsing_result.status:
|
||||||
|
self.add_error(parsing_result.value)
|
||||||
|
continue
|
||||||
|
|
||||||
|
asts_found_by_parts[keyword] = parsing_result
|
||||||
|
|
||||||
|
concept_found.where = asts_found_by_parts[Keywords.WHERE]
|
||||||
|
concept_found.pre = asts_found_by_parts[Keywords.PRE]
|
||||||
|
concept_found.post = asts_found_by_parts[Keywords.POST]
|
||||||
|
concept_found.body = asts_found_by_parts[Keywords.AS]
|
||||||
|
|
||||||
|
log.debug(f"Found DefConcept node '{concept_found}'")
|
||||||
|
return concept_found
|
||||||
|
|
||||||
# def parse_expression(self):
|
# def parse_expression(self):
|
||||||
# return self.parse_addition()
|
# return self.parse_addition()
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from core.sheerka import ReturnValue
|
from core.builtin_concepts import ReturnValueConcept, BuiltinConcepts
|
||||||
from parsers.BaseParser import BaseParser
|
from parsers.BaseParser import BaseParser
|
||||||
from core.tokenizer import Tokenizer, Keywords, TokenKind
|
from core.tokenizer import Tokenizer, Keywords, TokenKind
|
||||||
from core.concept import Concept
|
from core.concept import Concept
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ExactConceptParser(BaseParser):
|
class ExactConceptParser(BaseParser):
|
||||||
@@ -15,11 +18,17 @@ class ExactConceptParser(BaseParser):
|
|||||||
BaseParser.__init__(self, "ConceptParser")
|
BaseParser.__init__(self, "ConceptParser")
|
||||||
|
|
||||||
def parse(self, context, text):
|
def parse(self, context, text):
|
||||||
|
"""
|
||||||
|
text can be string, but text can also be an list of tokens
|
||||||
|
:param context:
|
||||||
|
:param text:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
res = []
|
res = []
|
||||||
sheerka = context.sheerka
|
sheerka = context.sheerka
|
||||||
words = self.get_words(text)
|
words = self.get_words(text)
|
||||||
if len(words) > self.MAX_WORDS_SIZE:
|
if len(words) > self.MAX_WORDS_SIZE:
|
||||||
return ReturnValue(self.name, False, sheerka.new(sheerka.CONCEPT_TOO_LONG_CONCEPT_NAME))
|
return ReturnValueConcept(self.name, False, sheerka.new(BuiltinConcepts.CONCEPT_TOO_LONG, obj=text))
|
||||||
|
|
||||||
recognized = False
|
recognized = False
|
||||||
for combination in self.combinations(words):
|
for combination in self.combinations(words):
|
||||||
@@ -30,24 +39,27 @@ class ExactConceptParser(BaseParser):
|
|||||||
# That will depend on the context
|
# That will depend on the context
|
||||||
# Let's return a new one for now and see if it works
|
# Let's return a new one for now and see if it works
|
||||||
concept = sheerka.new(concept_key)
|
concept = sheerka.new(concept_key)
|
||||||
if not sheerka.isinstance(concept, sheerka.UNKNOWN_CONCEPT_NAME):
|
if not sheerka.isinstance(concept, BuiltinConcepts.UNKNOWN_CONCEPT):
|
||||||
# update the properties if needed
|
# update the properties if needed
|
||||||
for i, token in enumerate(combination):
|
for i, token in enumerate(combination):
|
||||||
if token.startswith(Concept.PROPERTY_PREFIX):
|
if token.startswith(Concept.PROPERTY_PREFIX):
|
||||||
index = int(token[len(Concept.PROPERTY_PREFIX):])
|
index = int(token[len(Concept.PROPERTY_PREFIX):])
|
||||||
concept.set_prop_by_index(index, words[i])
|
concept.set_prop_by_index(index, words[i])
|
||||||
res.append(ReturnValue(self.name, True, concept))
|
res.append(ReturnValueConcept(self.name, True, concept))
|
||||||
|
log.debug(f"Recognized '{text}' as '{concept}'")
|
||||||
recognized = True
|
recognized = True
|
||||||
|
|
||||||
if recognized:
|
if recognized:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
return ReturnValue(self.name, False, sheerka.new(sheerka.UNKNOWN_CONCEPT_NAME, body=text))
|
log.debug(f"Failed to recognize {words}")
|
||||||
|
return ReturnValueConcept(self.name, False, sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, obj=text))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_words(text):
|
def get_words(text):
|
||||||
|
tokens = iter(Tokenizer(text)) if isinstance(text, str) else text
|
||||||
res = []
|
res = []
|
||||||
for t in iter(Tokenizer(text)):
|
for t in tokens:
|
||||||
if t.type == TokenKind.EOF:
|
if t.type == TokenKind.EOF:
|
||||||
break
|
break
|
||||||
if t.type == TokenKind.NEWLINE or t.type == TokenKind.WHITESPACE:
|
if t.type == TokenKind.NEWLINE or t.type == TokenKind.WHITESPACE:
|
||||||
|
|||||||
+48
-9
@@ -1,3 +1,4 @@
|
|||||||
|
from core.builtin_concepts import BuiltinConcepts
|
||||||
from parsers.BaseParser import BaseParser, Node, ErrorNode
|
from parsers.BaseParser import BaseParser, Node, ErrorNode
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import ast
|
import ast
|
||||||
@@ -12,23 +13,50 @@ class PythonErrorNode(ErrorNode):
|
|||||||
source: str
|
source: str
|
||||||
exception: Exception
|
exception: Exception
|
||||||
|
|
||||||
def __post_init__(self):
|
# def __post_init__(self):
|
||||||
log.debug("-> PythonErrorNode: " + str(self.exception))
|
# log.debug("-> PythonErrorNode: " + str(self.exception))
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class PythonNode(Node):
|
class PythonNode(Node):
|
||||||
source: str
|
source: str
|
||||||
ast: ast.AST
|
ast_: ast.AST
|
||||||
|
|
||||||
|
# def __repr__(self):
|
||||||
|
# return "PythonNode(source='" + self.source + "', ast=" + self.get_dump(self.ast_) + ")"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "PythonNode(" + ast.dump(self.ast) + ")"
|
ast_type = "expr" if isinstance(self.ast_, ast.Expression) else "module"
|
||||||
# return "PythonNode(" + self.source + ")"
|
return "PythonNode(" + ast_type + "='" + self.source + "')"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, PythonNode):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.source != other.source:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self_dump = self.get_dump(self.ast_)
|
||||||
|
other_dump = self.get_dump(other.ast_)
|
||||||
|
|
||||||
|
return self_dump == other_dump
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.source, self.ast_.hash))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_dump(ast_):
|
||||||
|
dump = ast.dump(ast_)
|
||||||
|
for to_remove in [", ctx=Load()", ", kind=None", ", type_ignores=[]"]:
|
||||||
|
dump = dump.replace(to_remove, "")
|
||||||
|
return dump
|
||||||
|
|
||||||
|
|
||||||
class PythonParser(BaseParser):
|
class PythonParser(BaseParser):
|
||||||
"""
|
"""
|
||||||
Parse Python scripts
|
Parse Python scripts
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, source="<undef>"):
|
def __init__(self, source="<undef>"):
|
||||||
|
|
||||||
BaseParser.__init__(self, "PythonParser")
|
BaseParser.__init__(self, "PythonParser")
|
||||||
@@ -38,6 +66,8 @@ class PythonParser(BaseParser):
|
|||||||
text = text if isinstance(text, str) else self.get_text_from_tokens(text)
|
text = text if isinstance(text, str) else self.get_text_from_tokens(text)
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
|
|
||||||
|
sheerka = context.sheerka
|
||||||
|
|
||||||
# first, try to parse an expression
|
# first, try to parse an expression
|
||||||
res, tree, error = self.try_parse_expression(text)
|
res, tree, error = self.try_parse_expression(text)
|
||||||
if not res:
|
if not res:
|
||||||
@@ -47,10 +77,19 @@ class PythonParser(BaseParser):
|
|||||||
self.has_error = True
|
self.has_error = True
|
||||||
error_node = PythonErrorNode(text, error)
|
error_node = PythonErrorNode(text, error)
|
||||||
self.error_sink.append(error_node)
|
self.error_sink.append(error_node)
|
||||||
return error_node
|
|
||||||
|
|
||||||
log.debug("Recognized python code.")
|
ret = sheerka.ret(
|
||||||
return PythonNode(text, tree)
|
self.name,
|
||||||
|
not self.has_error,
|
||||||
|
sheerka.new(
|
||||||
|
BuiltinConcepts.PARSER_RESULT,
|
||||||
|
parser=self,
|
||||||
|
source=text,
|
||||||
|
body=self.error_sink if self.has_error else PythonNode(text, tree),
|
||||||
|
try_parsed=None))
|
||||||
|
|
||||||
|
self.log_result(log, text, ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
def try_parse_expression(self, text):
|
def try_parse_expression(self, text):
|
||||||
try:
|
try:
|
||||||
@@ -91,6 +130,7 @@ class PythonGetNamesVisitor(ast.NodeVisitor):
|
|||||||
"""
|
"""
|
||||||
This visitor will find all the name declared in the ast
|
This visitor will find all the name declared in the ast
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.names = set()
|
self.names = set()
|
||||||
log.debug("Searching for names.")
|
log.debug("Searching for names.")
|
||||||
@@ -98,4 +138,3 @@ class PythonGetNamesVisitor(ast.NodeVisitor):
|
|||||||
def visit_Name(self, node):
|
def visit_Name(self, node):
|
||||||
log.debug(f"Found name : {node.id}")
|
log.debug(f"Found name : {node.id}")
|
||||||
self.names.add(node.id)
|
self.names.add(node.id)
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class State:
|
|||||||
for item in items:
|
for item in items:
|
||||||
item_digest = SheerkaDataProvider.get_obj_digest(item)
|
item_digest = SheerkaDataProvider.get_obj_digest(item)
|
||||||
if item_digest == digest:
|
if item_digest == digest:
|
||||||
raise SheerkaDataProviderDuplicateKeyError("duplicate key", key, obj.obj)
|
raise SheerkaDataProviderDuplicateKeyError(key, obj.obj)
|
||||||
|
|
||||||
def update(self, entry, obj: ObjToUpdate, append=True):
|
def update(self, entry, obj: ObjToUpdate, append=True):
|
||||||
"""
|
"""
|
||||||
@@ -258,8 +258,8 @@ class SheerkaDataProviderError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class SheerkaDataProviderDuplicateKeyError(Exception):
|
class SheerkaDataProviderDuplicateKeyError(Exception):
|
||||||
def __init__(self, message, key, obj):
|
def __init__(self, key, obj):
|
||||||
Exception.__init__(self, message)
|
Exception.__init__(self, "Duplicate object.")
|
||||||
self.key = key
|
self.key = key
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
|
||||||
@@ -371,7 +371,6 @@ class SheerkaDataProvider:
|
|||||||
state.parents = [] if snapshot is None else [snapshot]
|
state.parents = [] if snapshot is None else [snapshot]
|
||||||
state.events = [event_digest]
|
state.events = [event_digest]
|
||||||
state.date = datetime.now()
|
state.date = datetime.now()
|
||||||
log.debug(state.data)
|
|
||||||
|
|
||||||
if use_ref:
|
if use_ref:
|
||||||
obj.set_digest(self.save_obj(obj.obj))
|
obj.set_digest(self.save_obj(obj.obj))
|
||||||
@@ -579,15 +578,36 @@ class SheerkaDataProvider:
|
|||||||
|
|
||||||
return self.load_ref_if_needed(state.data[entry] if key is None else state.data[entry][key])[0]
|
return self.load_ref_if_needed(state.data[entry] if key is None else state.data[entry][key])[0]
|
||||||
|
|
||||||
def exists(self, entry):
|
def exists(self, entry, key=None, digest=None):
|
||||||
"""
|
"""
|
||||||
Returns true if the entry is defined
|
Returns true if the entry is defined
|
||||||
|
:param digest:
|
||||||
|
:param key:
|
||||||
:param entry:
|
:param entry:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
snapshot = self.get_snapshot()
|
snapshot = self.get_snapshot()
|
||||||
state = self.load_state(snapshot)
|
state = self.load_state(snapshot)
|
||||||
return entry in state.data
|
exist = entry in state.data
|
||||||
|
|
||||||
|
if not exist or key is None:
|
||||||
|
return exist
|
||||||
|
|
||||||
|
items = state.data[entry]
|
||||||
|
exist = key in items
|
||||||
|
if not exist or digest is None:
|
||||||
|
return exist
|
||||||
|
|
||||||
|
items = items[key]
|
||||||
|
if not isinstance(items, list):
|
||||||
|
items = [items]
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
item_digest = SheerkaDataProvider.get_obj_digest(item)
|
||||||
|
if item_digest == digest:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def save_event(self, event: Event):
|
def save_event(self, event: Event):
|
||||||
"""
|
"""
|
||||||
@@ -657,7 +677,7 @@ class SheerkaDataProvider:
|
|||||||
with open(target_path, "wb") as f:
|
with open(target_path, "wb") as f:
|
||||||
f.write(stream.read())
|
f.write(stream.read())
|
||||||
|
|
||||||
log.debug(f"...digest is {digest}.")
|
log.debug(f"...digest={digest}.")
|
||||||
return digest
|
return digest
|
||||||
|
|
||||||
def load_obj(self, digest):
|
def load_obj(self, digest):
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import struct
|
|||||||
import io
|
import io
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
import core.utils
|
import core.utils
|
||||||
|
|
||||||
from core.concept import Concept
|
from core.concept import Concept
|
||||||
@@ -23,6 +25,9 @@ def json_default_converter(o):
|
|||||||
if isinstance(o, (datetime.date, datetime.datetime)):
|
if isinstance(o, (datetime.date, datetime.datetime)):
|
||||||
return o.isoformat()
|
return o.isoformat()
|
||||||
|
|
||||||
|
if isinstance(o, Enum):
|
||||||
|
return o.key
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class SerializerContext:
|
class SerializerContext:
|
||||||
|
|||||||
@@ -0,0 +1,350 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from os import path
|
||||||
|
import ast
|
||||||
|
|
||||||
|
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts, ReturnValueConcept
|
||||||
|
from core.sheerka import Sheerka, ExecutionContext
|
||||||
|
from parsers.BaseParser import BaseParser
|
||||||
|
from parsers.PythonParser import PythonParser, PythonNode, PythonErrorNode
|
||||||
|
from core.tokenizer import Keywords, Tokenizer
|
||||||
|
from parsers.DefaultParser import DefaultParser, NameNode, SyntaxErrorNode
|
||||||
|
# from parsers.DefaultParser import NumberNode, StringNode, VariableNode, TrueNode, FalseNode, NullNode, BinaryNode
|
||||||
|
from parsers.DefaultParser import UnexpectedTokenErrorNode, DefConceptNode, NopNode
|
||||||
|
|
||||||
|
tests_root = path.abspath("../build/tests")
|
||||||
|
root_folder = "init_folder"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def init_test():
|
||||||
|
if path.exists(tests_root):
|
||||||
|
shutil.rmtree(tests_root)
|
||||||
|
|
||||||
|
if not path.exists(tests_root):
|
||||||
|
os.makedirs(tests_root)
|
||||||
|
current_pwd = os.getcwd()
|
||||||
|
os.chdir(tests_root)
|
||||||
|
|
||||||
|
yield None
|
||||||
|
os.chdir(current_pwd)
|
||||||
|
|
||||||
|
|
||||||
|
# def nop():
|
||||||
|
# return NopNode()
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def n(number):
|
||||||
|
# return NumberNode([], number)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def s(string, quote="'"):
|
||||||
|
# return StringNode([], string, quote)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def v(name):
|
||||||
|
# return VariableNode([], name)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def t():
|
||||||
|
# return TrueNode([])
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def f():
|
||||||
|
# return FalseNode([])
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def null():
|
||||||
|
# return NullNode([])
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def b(operator, left, right):
|
||||||
|
# return BinaryNode([], operator, left, right)
|
||||||
|
|
||||||
|
#
|
||||||
|
# def compare_ast(left, right):
|
||||||
|
# left_as_string = ast.dump(left)
|
||||||
|
# left_as_string = left_as_string.replace(", ctx=Load()", "")
|
||||||
|
# left_as_string = left_as_string.replace(", kind=None", "")
|
||||||
|
#
|
||||||
|
# right_as_string = right if isinstance(right, str) else ast.dump(right)
|
||||||
|
# right_as_string = right_as_string.replace(", ctx=Load()", "")
|
||||||
|
# right_as_string = right_as_string.replace(", kind=None", "")
|
||||||
|
#
|
||||||
|
# return left_as_string == right_as_string
|
||||||
|
#
|
||||||
|
|
||||||
|
def get_concept(name, where=None, pre=None, post=None, body=None):
|
||||||
|
concept = DefConceptNode([], name=NameNode(list(Tokenizer(name))))
|
||||||
|
|
||||||
|
if body:
|
||||||
|
concept.body = get_concept_part(body)
|
||||||
|
if where:
|
||||||
|
concept.where = get_concept_part(where)
|
||||||
|
if pre:
|
||||||
|
concept.pre = get_concept_part(pre)
|
||||||
|
if post:
|
||||||
|
concept.post = get_concept_part(post)
|
||||||
|
return concept
|
||||||
|
|
||||||
|
|
||||||
|
def get_context():
|
||||||
|
sheerka = Sheerka()
|
||||||
|
sheerka.initialize(root_folder)
|
||||||
|
return ExecutionContext("test", "xxx", sheerka)
|
||||||
|
|
||||||
|
|
||||||
|
def get_concept_part(part):
|
||||||
|
if isinstance(part, str):
|
||||||
|
node = PythonNode(part, ast.parse(part, mode="eval"))
|
||||||
|
return ReturnValueConcept(
|
||||||
|
who="Parsers:PythonParser",
|
||||||
|
status=True,
|
||||||
|
value=ParserResultConcept(
|
||||||
|
source=part,
|
||||||
|
parser=PythonParser(),
|
||||||
|
value=node))
|
||||||
|
|
||||||
|
if isinstance(part, PythonNode):
|
||||||
|
return ReturnValueConcept(
|
||||||
|
who="Parsers:PythonParser",
|
||||||
|
status=True,
|
||||||
|
value=ParserResultConcept(
|
||||||
|
source=part.source,
|
||||||
|
parser=PythonParser(),
|
||||||
|
value=part))
|
||||||
|
|
||||||
|
if isinstance(part, ReturnValueConcept):
|
||||||
|
return part
|
||||||
|
|
||||||
|
# @pytest.mark.parametrize("text, expected", [
|
||||||
|
# ("1", n(1)),
|
||||||
|
# ("+1", n(1)),
|
||||||
|
# ("-1", n(-1)),
|
||||||
|
# ("'foo'", s("foo")),
|
||||||
|
# ("identifier", v("identifier")),
|
||||||
|
# ("true", t()),
|
||||||
|
# ("false", f()),
|
||||||
|
# ("null", null()),
|
||||||
|
# ("1 * 2", b(TokenKind.STAR, n(1), n(2))),
|
||||||
|
# ("1 * 2/3", b(TokenKind.STAR, n(1), b(TokenKind.SLASH, n(2), n(3)))),
|
||||||
|
# ("1 + 2", b(TokenKind.PLUS, n(1), n(2))),
|
||||||
|
# ("1 + 2 - 3", b(TokenKind.PLUS, n(1), b(TokenKind.MINUS, n(2), n(3)))),
|
||||||
|
# ("1 + 2-3", b(TokenKind.PLUS, n(1), b(TokenKind.PLUS, n(2), n(-3)))),
|
||||||
|
# ("1 + 2 +-3", b(TokenKind.PLUS, n(1), b(TokenKind.PLUS, n(2), n(-3)))),
|
||||||
|
# ("1 + 2 * 3", b(TokenKind.PLUS, n(1), b(TokenKind.STAR, n(2), n(3)))),
|
||||||
|
# ("1 * 2 + 3", b(TokenKind.PLUS, b(TokenKind.STAR, n(1), n(2)), n(3))),
|
||||||
|
# ("(1 + 2) * 3", b(TokenKind.STAR, b(TokenKind.PLUS, n(1), n(2)), n(3))),
|
||||||
|
# ("1 * (2 + 3)", b(TokenKind.STAR, n(1), b(TokenKind.PLUS, n(2), n(3)))),
|
||||||
|
# ])
|
||||||
|
# def test_i_can_parse_simple_expression(text, expected):
|
||||||
|
# parser = DefaultParser(text, None)
|
||||||
|
# ast = parser.parse()
|
||||||
|
# assert ast.is_same(expected)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @pytest.mark.parametrize("text, token_found, expected_tokens", [
|
||||||
|
# ("1+", TokenKind.EOF,
|
||||||
|
# [TokenKind.NUMBER, TokenKind.STRING, TokenKind.IDENTIFIER, 'true', 'false', 'null', TokenKind.LPAR]),
|
||||||
|
# ("(1+1", TokenKind.EOF, [TokenKind.RPAR])
|
||||||
|
# ])
|
||||||
|
# def test_i_can_detect_unexpected_end_of_code(text, token_found, expected_tokens):
|
||||||
|
# parser = DefaultParser(text, None)
|
||||||
|
# parser.parse()
|
||||||
|
#
|
||||||
|
# assert parser.has_error
|
||||||
|
# assert parser.error_sink[0].tokens[0].type == token_found
|
||||||
|
# assert parser.error_sink[0].expected_tokens == expected_tokens
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, expected", [
|
||||||
|
("def concept hello", get_concept(name="hello")),
|
||||||
|
("def concept hello ", get_concept(name="hello")),
|
||||||
|
("def concept a + b", get_concept(name="a + b")),
|
||||||
|
("def concept a+b", get_concept(name="a + b")),
|
||||||
|
("def concept 'a+b'+c", get_concept(name="'a+b' + c")),
|
||||||
|
("def concept 'as if'", get_concept(name="'as if'")),
|
||||||
|
("def concept 'as' if", get_concept(name="'as if'")),
|
||||||
|
("def concept hello as 'hello'", get_concept(name="hello", body="'hello'")),
|
||||||
|
("def concept hello as 1", get_concept(name="hello", body="1")),
|
||||||
|
("def concept hello as 1 + 1", get_concept(name="hello", body="1 + 1")),
|
||||||
|
])
|
||||||
|
def test_i_can_parse_def_concept(text, expected):
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
node = res.value.value
|
||||||
|
|
||||||
|
assert res.status
|
||||||
|
assert res.who == parser.name
|
||||||
|
assert res.value.source == text
|
||||||
|
assert isinstance(res.value, ParserResultConcept)
|
||||||
|
assert node == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_parse_complex_def_concept_statement():
|
||||||
|
text = """def concept a plus b
|
||||||
|
where a,b
|
||||||
|
pre isinstance(a, int) and isinstance(b, float)
|
||||||
|
post isinstance(res, int)
|
||||||
|
as res = a + b
|
||||||
|
"""
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
return_value = res.value
|
||||||
|
expected_concept = get_concept(
|
||||||
|
name="a plus b",
|
||||||
|
where="a,b",
|
||||||
|
pre="isinstance(a, int) and isinstance(b, float)",
|
||||||
|
post="isinstance(res, int)",
|
||||||
|
body=PythonNode("res = a + b", ast.parse("res = a + b", mode="exec"))
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res.status
|
||||||
|
assert isinstance(return_value, ParserResultConcept)
|
||||||
|
assert return_value.value == expected_concept
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_have_mutilines_declarations():
|
||||||
|
text = """
|
||||||
|
def concept add one to a as
|
||||||
|
def func(x):
|
||||||
|
return x+1
|
||||||
|
func(a)
|
||||||
|
"""
|
||||||
|
|
||||||
|
expected_concept = get_concept(
|
||||||
|
name="add one to a ",
|
||||||
|
body=PythonNode(
|
||||||
|
"def func(x):\n return x+1\nfunc(a)",
|
||||||
|
ast.parse("def func(x):\n return x+1\nfunc(a)", mode="exec"))
|
||||||
|
)
|
||||||
|
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
return_value = res.value
|
||||||
|
|
||||||
|
assert res.status
|
||||||
|
assert isinstance(return_value, ParserResultConcept)
|
||||||
|
assert return_value.value == expected_concept
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_colon_to_use_indentation():
|
||||||
|
text = """
|
||||||
|
def concept add one to a as:
|
||||||
|
def func(x):
|
||||||
|
return x+1
|
||||||
|
func(a)
|
||||||
|
"""
|
||||||
|
|
||||||
|
expected_concept = get_concept(
|
||||||
|
name="add one to a ",
|
||||||
|
body=PythonNode(
|
||||||
|
"def func(x):\n return x+1\nfunc(a)",
|
||||||
|
ast.parse("def func(x):\n return x+1\nfunc(a)", mode="exec"))
|
||||||
|
)
|
||||||
|
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
return_value = res.value
|
||||||
|
|
||||||
|
assert res.status
|
||||||
|
assert isinstance(return_value, ParserResultConcept)
|
||||||
|
assert return_value.value == expected_concept
|
||||||
|
|
||||||
|
|
||||||
|
def test_indentation_is_mandatory_after_a_colon():
|
||||||
|
text = """
|
||||||
|
def concept add one to a as:
|
||||||
|
def func(x):
|
||||||
|
return x+1
|
||||||
|
func(a)
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
return_value = res.value
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert isinstance(return_value, ParserResultConcept)
|
||||||
|
assert isinstance(return_value.value[0], SyntaxErrorNode)
|
||||||
|
assert return_value.value[0].message == "Indentation not found."
|
||||||
|
|
||||||
|
|
||||||
|
def test_indentation_is_not_allowed_if_the_colon_is_missing():
|
||||||
|
text = """
|
||||||
|
def concept add one to a as
|
||||||
|
def func(x):
|
||||||
|
return x+1
|
||||||
|
func(a)
|
||||||
|
"""
|
||||||
|
context = get_context()
|
||||||
|
sheerka = context.sheerka
|
||||||
|
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(context, text)
|
||||||
|
return_value = res.value
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert isinstance(return_value, ParserResultConcept)
|
||||||
|
assert sheerka.isinstance(return_value.value[0], BuiltinConcepts.TOO_MANY_ERRORS)
|
||||||
|
|
||||||
|
|
||||||
|
def test_name_is_mandatory():
|
||||||
|
text = "def concept as 'hello'"
|
||||||
|
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
return_value = res.value
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert isinstance(return_value, ParserResultConcept)
|
||||||
|
assert isinstance(return_value.value[0], SyntaxErrorNode)
|
||||||
|
assert return_value.value[0].message == "Name is mandatory"
|
||||||
|
|
||||||
|
|
||||||
|
def test_concept_keyword_is_mandatory_but_the_concept_is_recognized():
|
||||||
|
text = "def hello as a where b pre c post d"
|
||||||
|
|
||||||
|
expected_concept = get_concept(name="hello", body="a", where="b", pre="c", post="d")
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
return_value = res.value
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert isinstance(return_value, ParserResultConcept)
|
||||||
|
assert isinstance(return_value.value[0], UnexpectedTokenErrorNode)
|
||||||
|
assert return_value.value[0].message == "Syntax error."
|
||||||
|
assert return_value.value[0].expected_tokens == [Keywords.CONCEPT]
|
||||||
|
assert return_value.try_parsed == expected_concept
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text", [
|
||||||
|
"def concept hello where 1+",
|
||||||
|
"def concept hello pre 1+",
|
||||||
|
"def concept hello post 1+",
|
||||||
|
"def concept hello as 1+"
|
||||||
|
])
|
||||||
|
def test_i_can_detect_error_in_declaration(text):
|
||||||
|
context = get_context()
|
||||||
|
sheerka = context.sheerka
|
||||||
|
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(context, text)
|
||||||
|
return_value = res.value
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert isinstance(return_value, ParserResultConcept)
|
||||||
|
assert sheerka.isinstance(return_value.value[0], BuiltinConcepts.TOO_MANY_ERRORS)
|
||||||
|
|
||||||
|
|
||||||
|
def test_new_line_is_not_allowed_in_the_name():
|
||||||
|
text = "def concept hello \n my friend as 'hello'"
|
||||||
|
|
||||||
|
parser = DefaultParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
return_value = res.value
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert return_value.value == [SyntaxErrorNode([], "Newline are not allowed in name.")]
|
||||||
@@ -3,8 +3,10 @@ from os import path
|
|||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts
|
||||||
from core.concept import Concept, Property
|
from core.concept import Concept, Property
|
||||||
from core.sheerka import Sheerka, ExecutionContext
|
from core.sheerka import Sheerka, ExecutionContext
|
||||||
|
from core.tokenizer import Tokenizer
|
||||||
from parsers.DefaultParser import DefaultParser
|
from parsers.DefaultParser import DefaultParser
|
||||||
from parsers.ExactConceptParser import ExactConceptParser
|
from parsers.ExactConceptParser import ExactConceptParser
|
||||||
|
|
||||||
@@ -54,29 +56,28 @@ def test_i_can_compute_combinations_with_duplicates():
|
|||||||
|
|
||||||
|
|
||||||
def test_i_can_recognize_a_simple_concept():
|
def test_i_can_recognize_a_simple_concept():
|
||||||
sheerka = get_sheerka()
|
context = get_context()
|
||||||
concept = get_concept("hello world", [])
|
concept = get_concept("hello world", [])
|
||||||
sheerka.add_in_cache(concept)
|
context.sheerka.add_in_cache(concept)
|
||||||
|
|
||||||
source = "hello world"
|
source = "hello world"
|
||||||
context = ExecutionContext(sheerka, "xxxx")
|
|
||||||
results = ExactConceptParser().parse(context, source)
|
results = ExactConceptParser().parse(context, source)
|
||||||
|
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
assert results[0].status
|
assert results[0].status
|
||||||
assert results[0].value.key == concept.key
|
assert results[0].value == concept
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_recognize_concepts_defined_several_times():
|
def test_i_can_recognize_concepts_defined_several_times():
|
||||||
sheerka = get_sheerka()
|
context = get_context()
|
||||||
sheerka.add_in_cache(get_concept("hello world", []))
|
context.sheerka.add_in_cache(get_concept("hello world", []))
|
||||||
sheerka.add_in_cache(get_concept("hello a", ["a"]))
|
context.sheerka.add_in_cache(get_concept("hello a", ["a"]))
|
||||||
|
|
||||||
source = "hello world"
|
source = "hello world"
|
||||||
context = ExecutionContext(sheerka, "xxxx")
|
|
||||||
results = ExactConceptParser().parse(context, source)
|
results = ExactConceptParser().parse(context, source)
|
||||||
|
|
||||||
assert len(results) == 2
|
assert len(results) == 2
|
||||||
results = sorted(results, key=lambda x: x.value.name) # because of the usage of sets
|
results = sorted(results, key=lambda x: x.value.name) # because of the usage of sets
|
||||||
|
|
||||||
assert results[0].status
|
assert results[0].status
|
||||||
assert results[0].value.name == "hello a"
|
assert results[0].value.name == "hello a"
|
||||||
@@ -87,11 +88,10 @@ def test_i_can_recognize_concepts_defined_several_times():
|
|||||||
|
|
||||||
|
|
||||||
def test_i_can_recognize_a_concept_with_variables():
|
def test_i_can_recognize_a_concept_with_variables():
|
||||||
sheerka = get_sheerka()
|
context = get_context()
|
||||||
concept = get_concept("a + b", ["a", "b"])
|
concept = get_concept("a + b", ["a", "b"])
|
||||||
sheerka.concepts_cache[concept.key] = concept
|
context.sheerka.add_in_cache(concept)
|
||||||
source = "10 + 5"
|
source = "10 + 5"
|
||||||
context = ExecutionContext(sheerka, "xxxx")
|
|
||||||
results = ExactConceptParser().parse(context, source)
|
results = ExactConceptParser().parse(context, source)
|
||||||
|
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
@@ -102,11 +102,10 @@ def test_i_can_recognize_a_concept_with_variables():
|
|||||||
|
|
||||||
|
|
||||||
def test_i_can_recognize_a_concept_with_duplicate_variables():
|
def test_i_can_recognize_a_concept_with_duplicate_variables():
|
||||||
sheerka = get_sheerka()
|
context = get_context()
|
||||||
concept = get_concept("a + b + a", ["a", "b"])
|
concept = get_concept("a + b + a", ["a", "b"])
|
||||||
sheerka.concepts_cache[concept.key] = concept
|
context.sheerka.concepts_cache[concept.key] = concept
|
||||||
source = "10 + 5 + 10"
|
source = "10 + 5 + 10"
|
||||||
context = ExecutionContext(sheerka, "xxxx")
|
|
||||||
results = ExactConceptParser().parse(context, source)
|
results = ExactConceptParser().parse(context, source)
|
||||||
|
|
||||||
assert len(results) == 1
|
assert len(results) == 1
|
||||||
@@ -117,23 +116,43 @@ def test_i_can_recognize_a_concept_with_duplicate_variables():
|
|||||||
|
|
||||||
|
|
||||||
def test_i_can_manage_unknown_concept():
|
def test_i_can_manage_unknown_concept():
|
||||||
sheerka = get_sheerka()
|
context = get_context()
|
||||||
source = "def concept hello world" # this is not a concept by itself
|
source = "def concept hello world" # this is not a concept by itself
|
||||||
context = ExecutionContext(sheerka, "xxxx")
|
|
||||||
res = ExactConceptParser().parse(context, source)
|
res = ExactConceptParser().parse(context, source)
|
||||||
|
|
||||||
assert not res.status
|
assert not res.status
|
||||||
assert sheerka.isinstance(res.value, Sheerka.UNKNOWN_CONCEPT_NAME)
|
assert context.sheerka.isinstance(res.value, BuiltinConcepts.UNKNOWN_CONCEPT)
|
||||||
|
assert res.value.obj == "def concept hello world"
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_detect_concepts_too_long():
|
def test_i_can_detect_concepts_too_long():
|
||||||
sheerka = get_sheerka()
|
context = get_context()
|
||||||
source = "a very very long concept that cannot be an unique one"
|
source = "a very very long concept that cannot be an unique one"
|
||||||
context = ExecutionContext(sheerka, "xxxx")
|
|
||||||
res = ExactConceptParser().parse(context, source)
|
res = ExactConceptParser().parse(context, source)
|
||||||
|
|
||||||
assert not res.status
|
assert not res.status
|
||||||
assert sheerka.isinstance(res.value, Sheerka.CONCEPT_TOO_LONG_CONCEPT_NAME)
|
assert context.sheerka.isinstance(res.value, BuiltinConcepts.CONCEPT_TOO_LONG)
|
||||||
|
assert res.value.obj == "a very very long concept that cannot be an unique one"
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_detect_concept_from_tokens():
|
||||||
|
context = get_context()
|
||||||
|
concept = get_concept("hello world", [])
|
||||||
|
context.sheerka.add_in_cache(concept)
|
||||||
|
|
||||||
|
source = "hello world"
|
||||||
|
results = ExactConceptParser().parse(context, list(Tokenizer(source)))
|
||||||
|
|
||||||
|
assert len(results) == 1
|
||||||
|
assert results[0].status
|
||||||
|
assert results[0].value == concept
|
||||||
|
|
||||||
|
|
||||||
|
def get_context():
|
||||||
|
sheerka = Sheerka()
|
||||||
|
sheerka.initialize(root_folder)
|
||||||
|
|
||||||
|
return ExecutionContext("sheerka", "xxxx", sheerka)
|
||||||
|
|
||||||
|
|
||||||
def get_concept(name, variables):
|
def get_concept(name, variables):
|
||||||
@@ -143,10 +162,3 @@ def get_concept(name, variables):
|
|||||||
c.props[v] = Property(v, None)
|
c.props[v] = Property(v, None)
|
||||||
c.init_key()
|
c.init_key()
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
def get_sheerka():
|
|
||||||
sheerka = Sheerka()
|
|
||||||
sheerka.initialize(root_folder)
|
|
||||||
|
|
||||||
return sheerka
|
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import ast
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.builtin_concepts import ParserResultConcept
|
||||||
|
from core.sheerka import Sheerka, ExecutionContext
|
||||||
|
from core.tokenizer import Tokenizer
|
||||||
|
from parsers.BaseParser import BaseParser
|
||||||
|
from parsers.PythonParser import PythonNode, PythonParser, PythonErrorNode
|
||||||
|
|
||||||
|
tests_root = path.abspath("../build/tests")
|
||||||
|
root_folder = "init_folder"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def init_test():
|
||||||
|
if path.exists(tests_root):
|
||||||
|
shutil.rmtree(tests_root)
|
||||||
|
|
||||||
|
if not path.exists(tests_root):
|
||||||
|
os.makedirs(tests_root)
|
||||||
|
current_pwd = os.getcwd()
|
||||||
|
os.chdir(tests_root)
|
||||||
|
|
||||||
|
yield None
|
||||||
|
os.chdir(current_pwd)
|
||||||
|
|
||||||
|
|
||||||
|
def get_context():
|
||||||
|
sheerka = Sheerka()
|
||||||
|
sheerka.initialize(root_folder)
|
||||||
|
return ExecutionContext("test", "xxx", sheerka)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, expected", [
|
||||||
|
("1+1", PythonNode("1+1", ast.parse("1+1", mode="eval"))),
|
||||||
|
("a=10", PythonNode("a=10", ast.parse("a=10", mode="exec"))),
|
||||||
|
])
|
||||||
|
def test_i_can_parse_a_simple_expression(text, expected):
|
||||||
|
parser = PythonParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
|
||||||
|
assert res.status
|
||||||
|
assert res.who == parser.name
|
||||||
|
assert isinstance(res.value, ParserResultConcept)
|
||||||
|
assert res.value.value == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, expected", [
|
||||||
|
("1+1", PythonNode("1+1", ast.parse("1+1", mode="eval"))),
|
||||||
|
("a=10", PythonNode("a=10", ast.parse("a=10", mode="exec"))),
|
||||||
|
])
|
||||||
|
def test_i_can_parse_from_tokens(text, expected):
|
||||||
|
parser = PythonParser()
|
||||||
|
tokens = list(Tokenizer(text))
|
||||||
|
res = parser.parse(get_context(), tokens)
|
||||||
|
|
||||||
|
assert res.status
|
||||||
|
assert res.who == parser.name
|
||||||
|
assert isinstance(res.value, ParserResultConcept)
|
||||||
|
assert res.value.value == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_detect_error():
|
||||||
|
text = "1+"
|
||||||
|
|
||||||
|
parser = PythonParser()
|
||||||
|
res = parser.parse(get_context(), text)
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert res.who == parser.name
|
||||||
|
assert isinstance(res.value, ParserResultConcept)
|
||||||
|
assert isinstance(res.value.value[0], PythonErrorNode)
|
||||||
|
assert isinstance(res.value.value[0].exception, SyntaxError)
|
||||||
@@ -1,346 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from parsers.ExactConceptParser import ExactConceptParser
|
|
||||||
from parsers.PythonParser import PythonParser, PythonNode, PythonErrorNode
|
|
||||||
from core.tokenizer import Tokenizer, Token, TokenKind, Keywords, LexerError
|
|
||||||
from parsers.DefaultParser import DefaultParser
|
|
||||||
from parsers.DefaultParser import NumberNode, StringNode, VariableNode, TrueNode, FalseNode, NullNode, BinaryNode
|
|
||||||
from parsers.DefaultParser import UnexpectedTokenErrorNode, DefConceptNode, NopNode
|
|
||||||
import ast
|
|
||||||
|
|
||||||
|
|
||||||
def nop():
|
|
||||||
return NopNode()
|
|
||||||
|
|
||||||
|
|
||||||
def n(number):
|
|
||||||
return NumberNode([], number)
|
|
||||||
|
|
||||||
|
|
||||||
def s(string, quote="'"):
|
|
||||||
return StringNode([], string, quote)
|
|
||||||
|
|
||||||
|
|
||||||
def v(name):
|
|
||||||
return VariableNode([], name)
|
|
||||||
|
|
||||||
|
|
||||||
def t():
|
|
||||||
return TrueNode([])
|
|
||||||
|
|
||||||
|
|
||||||
def f():
|
|
||||||
return FalseNode([])
|
|
||||||
|
|
||||||
|
|
||||||
def null():
|
|
||||||
return NullNode([])
|
|
||||||
|
|
||||||
|
|
||||||
def b(operator, left, right):
|
|
||||||
return BinaryNode([], operator, left, right)
|
|
||||||
|
|
||||||
|
|
||||||
def compare_ast(left, right):
|
|
||||||
left_as_string = ast.dump(left)
|
|
||||||
left_as_string = left_as_string.replace(", ctx=Load()", "")
|
|
||||||
left_as_string = left_as_string.replace(", kind=None", "")
|
|
||||||
|
|
||||||
right_as_string = right if isinstance(right, str) else ast.dump(right)
|
|
||||||
right_as_string = right_as_string.replace(", ctx=Load()", "")
|
|
||||||
right_as_string = right_as_string.replace(", kind=None", "")
|
|
||||||
|
|
||||||
return left_as_string == right_as_string
|
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_tokenize():
|
|
||||||
source = "+*-/{}[]() ,;:.?\n\n\r\r\r\nidentifier_0\t \t10.15 10 'string\n' \"another string\"=|&"
|
|
||||||
tokens = list(Tokenizer(source))
|
|
||||||
assert tokens[0] == Token(TokenKind.PLUS, "+", 0, 1, 1)
|
|
||||||
assert tokens[1] == Token(TokenKind.STAR, "*", 1, 1, 2)
|
|
||||||
assert tokens[2] == Token(TokenKind.MINUS, "-", 2, 1, 3)
|
|
||||||
assert tokens[3] == Token(TokenKind.SLASH, "/", 3, 1, 4)
|
|
||||||
assert tokens[4] == Token(TokenKind.LBRACE, "{", 4, 1, 5)
|
|
||||||
assert tokens[5] == Token(TokenKind.RBRACE, "}", 5, 1, 6)
|
|
||||||
assert tokens[6] == Token(TokenKind.LBRACKET, "[", 6, 1, 7)
|
|
||||||
assert tokens[7] == Token(TokenKind.RBRACKET, "]", 7, 1, 8)
|
|
||||||
assert tokens[8] == Token(TokenKind.LPAR, "(", 8, 1, 9)
|
|
||||||
assert tokens[9] == Token(TokenKind.RPAR, ")", 9, 1, 10)
|
|
||||||
assert tokens[10] == Token(TokenKind.WHITESPACE, " ", 10, 1, 11)
|
|
||||||
assert tokens[11] == Token(TokenKind.COMMA, ",", 14, 1, 15)
|
|
||||||
assert tokens[12] == Token(TokenKind.SEMICOLON, ";", 15, 1, 16)
|
|
||||||
assert tokens[13] == Token(TokenKind.COLON, ":", 16, 1, 17)
|
|
||||||
assert tokens[14] == Token(TokenKind.DOT, ".", 17, 1, 18)
|
|
||||||
assert tokens[15] == Token(TokenKind.QMARK, "?", 18, 1, 19)
|
|
||||||
assert tokens[16] == Token(TokenKind.NEWLINE, "\n", 19, 1, 20)
|
|
||||||
assert tokens[17] == Token(TokenKind.NEWLINE, "\n\r", 20, 2, 1)
|
|
||||||
assert tokens[18] == Token(TokenKind.NEWLINE, "\r", 22, 3, 1)
|
|
||||||
assert tokens[19] == Token(TokenKind.NEWLINE, "\r\n", 23, 4, 1)
|
|
||||||
assert tokens[20] == Token(TokenKind.IDENTIFIER, "identifier_0", 25, 5, 1)
|
|
||||||
assert tokens[21] == Token(TokenKind.WHITESPACE, "\t \t", 37, 5, 13)
|
|
||||||
assert tokens[22] == Token(TokenKind.NUMBER, "10.15", 41, 5, 17)
|
|
||||||
assert tokens[23] == Token(TokenKind.WHITESPACE, " ", 46, 5, 22)
|
|
||||||
assert tokens[24] == Token(TokenKind.NUMBER, "10", 47, 5, 23)
|
|
||||||
assert tokens[25] == Token(TokenKind.WHITESPACE, " ", 49, 5, 25)
|
|
||||||
assert tokens[26] == Token(TokenKind.STRING, "'string\n'", 50, 5, 26)
|
|
||||||
assert tokens[27] == Token(TokenKind.WHITESPACE, " ", 59, 6, 1)
|
|
||||||
assert tokens[28] == Token(TokenKind.STRING, '"another string"', 60, 6, 2)
|
|
||||||
assert tokens[29] == Token(TokenKind.EQUALS, '=', 76, 6, 18)
|
|
||||||
assert tokens[30] == Token(TokenKind.VBAR, '|', 77, 6, 19)
|
|
||||||
assert tokens[31] == Token(TokenKind.AMPER, '&', 78, 6, 20)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text, expected", [
|
|
||||||
("_ident", True),
|
|
||||||
("ident", True),
|
|
||||||
("ident123", True),
|
|
||||||
("ident_123", True),
|
|
||||||
("ident-like-this", True),
|
|
||||||
("àèùéû", True),
|
|
||||||
("011254", False),
|
|
||||||
("0abcd", False),
|
|
||||||
("-abcd", False)
|
|
||||||
])
|
|
||||||
def test_i_can_tokenize_identifiers(text, expected):
|
|
||||||
tokens = list(Tokenizer(text))
|
|
||||||
comparison = tokens[0].type == TokenKind.IDENTIFIER
|
|
||||||
assert comparison == expected
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text, error_text, index, line, column", [
|
|
||||||
("'string", "'string", 7, 1, 8),
|
|
||||||
('"string', '"string', 7, 1, 8),
|
|
||||||
('"a" + "string', '"string', 13, 1, 14),
|
|
||||||
('"a"\n\n"string', '"string', 12, 3, 8),
|
|
||||||
])
|
|
||||||
def test_i_can_detect_unfinished_strings(text, error_text, index, line, column):
|
|
||||||
with pytest.raises(LexerError) as e:
|
|
||||||
list(Tokenizer(text))
|
|
||||||
assert e.value.text == error_text
|
|
||||||
assert e.value.index == index
|
|
||||||
assert e.value.line == line
|
|
||||||
assert e.value.column == column
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text, expected_text, expected_newlines", [
|
|
||||||
("'foo'", "'foo'", 0),
|
|
||||||
('"foo"', '"foo"', 0),
|
|
||||||
("'foo\rbar'", "'foo\rbar'", 1),
|
|
||||||
("'foo\nbar'", "'foo\nbar'", 1),
|
|
||||||
("'foo\n\rbar'", "'foo\n\rbar'", 1),
|
|
||||||
("'foo\r\nbar'", "'foo\r\nbar'", 1),
|
|
||||||
("'foo\r\rbar'", "'foo\r\rbar'", 2),
|
|
||||||
("'foo\n\nbar'", "'foo\n\nbar'", 2),
|
|
||||||
("'foo\r\n\n\rbar'", "'foo\r\n\n\rbar'", 2),
|
|
||||||
("'\rfoo\rbar\r'", "'\rfoo\rbar\r'", 3),
|
|
||||||
("'\nfoo\nbar\n'", "'\nfoo\nbar\n'", 3),
|
|
||||||
("'\n\rfoo\r\n'", "'\n\rfoo\r\n'", 2),
|
|
||||||
(r"'foo\'bar'", r"'foo\'bar'", 0),
|
|
||||||
(r'"foo\"bar"', r'"foo\"bar"', 0),
|
|
||||||
('"foo"bar"', '"foo"', 0),
|
|
||||||
("'foo'bar'", "'foo'", 0),
|
|
||||||
])
|
|
||||||
def test_i_can_parse_strings(text, expected_text, expected_newlines):
|
|
||||||
lexer = Tokenizer(text)
|
|
||||||
text_found, nb_of_newlines = lexer.eat_string(0, 1, 1)
|
|
||||||
|
|
||||||
assert nb_of_newlines == expected_newlines
|
|
||||||
assert text_found == expected_text
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text", [
|
|
||||||
"1", "3.1415", "0.5", "01", "-5", "-5.10"
|
|
||||||
])
|
|
||||||
def test_i_can_parse_numbers(text):
|
|
||||||
tokens = list(Tokenizer(text))
|
|
||||||
assert tokens[0].type == TokenKind.NUMBER
|
|
||||||
assert tokens[0].value == text
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text, expected", [
|
|
||||||
("def", Keywords.DEF),
|
|
||||||
("concept", Keywords.CONCEPT),
|
|
||||||
("as", Keywords.AS),
|
|
||||||
("pre", Keywords.PRE),
|
|
||||||
("post", Keywords.POST)
|
|
||||||
])
|
|
||||||
def test_i_can_recognize_keywords(text, expected):
|
|
||||||
tokens = list(Tokenizer(text))
|
|
||||||
assert tokens[0].type == TokenKind.KEYWORD
|
|
||||||
assert tokens[0].value == expected
|
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.parametrize("text, expected", [
|
|
||||||
# ("1", n(1)),
|
|
||||||
# ("+1", n(1)),
|
|
||||||
# ("-1", n(-1)),
|
|
||||||
# ("'foo'", s("foo")),
|
|
||||||
# ("identifier", v("identifier")),
|
|
||||||
# ("true", t()),
|
|
||||||
# ("false", f()),
|
|
||||||
# ("null", null()),
|
|
||||||
# ("1 * 2", b(TokenKind.STAR, n(1), n(2))),
|
|
||||||
# ("1 * 2/3", b(TokenKind.STAR, n(1), b(TokenKind.SLASH, n(2), n(3)))),
|
|
||||||
# ("1 + 2", b(TokenKind.PLUS, n(1), n(2))),
|
|
||||||
# ("1 + 2 - 3", b(TokenKind.PLUS, n(1), b(TokenKind.MINUS, n(2), n(3)))),
|
|
||||||
# ("1 + 2-3", b(TokenKind.PLUS, n(1), b(TokenKind.PLUS, n(2), n(-3)))),
|
|
||||||
# ("1 + 2 +-3", b(TokenKind.PLUS, n(1), b(TokenKind.PLUS, n(2), n(-3)))),
|
|
||||||
# ("1 + 2 * 3", b(TokenKind.PLUS, n(1), b(TokenKind.STAR, n(2), n(3)))),
|
|
||||||
# ("1 * 2 + 3", b(TokenKind.PLUS, b(TokenKind.STAR, n(1), n(2)), n(3))),
|
|
||||||
# ("(1 + 2) * 3", b(TokenKind.STAR, b(TokenKind.PLUS, n(1), n(2)), n(3))),
|
|
||||||
# ("1 * (2 + 3)", b(TokenKind.STAR, n(1), b(TokenKind.PLUS, n(2), n(3)))),
|
|
||||||
# ])
|
|
||||||
# def test_i_can_parse_simple_expression(text, expected):
|
|
||||||
# parser = DefaultParser(text, None)
|
|
||||||
# ast = parser.parse()
|
|
||||||
# assert ast.is_same(expected)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# @pytest.mark.parametrize("text, token_found, expected_tokens", [
|
|
||||||
# ("1+", TokenKind.EOF,
|
|
||||||
# [TokenKind.NUMBER, TokenKind.STRING, TokenKind.IDENTIFIER, 'true', 'false', 'null', TokenKind.LPAR]),
|
|
||||||
# ("(1+1", TokenKind.EOF, [TokenKind.RPAR])
|
|
||||||
# ])
|
|
||||||
# def test_i_can_detect_unexpected_end_of_code(text, token_found, expected_tokens):
|
|
||||||
# parser = DefaultParser(text, None)
|
|
||||||
# parser.parse()
|
|
||||||
#
|
|
||||||
# assert parser.has_error
|
|
||||||
# assert parser.error_sink[0].tokens[0].type == token_found
|
|
||||||
# assert parser.error_sink[0].expected_tokens == expected_tokens
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text, expected_name, expected_expr", [
|
|
||||||
("def concept hello", "hello", nop()),
|
|
||||||
("def concept hello ", "hello", nop()),
|
|
||||||
("def concept a+b", "a + b", nop()),
|
|
||||||
("def concept 'a+b'", "a+b", nop()),
|
|
||||||
("def concept 'a+b'+c", "a+b + c", nop()),
|
|
||||||
("def concept 'as if'", "as if", nop()),
|
|
||||||
("def concept 'as' if", "as if", nop()),
|
|
||||||
("def concept hello as 'hello'", "hello", ast.Expression(body=ast.Str(s='hello'))),
|
|
||||||
("def concept hello as 1", "hello", ast.Expression(body=ast.Num(n=1))),
|
|
||||||
("def concept h as 1 + 1", "h", ast.Expression(ast.BinOp(left=ast.Num(n=1), op=ast.Add(), right=ast.Num(n=1)))),
|
|
||||||
])
|
|
||||||
def test_i_can_parse_def_concept(text, expected_name, expected_expr):
|
|
||||||
parser = DefaultParser(PythonParser)
|
|
||||||
tree = parser.parse(None, text)
|
|
||||||
assert isinstance(tree, DefConceptNode)
|
|
||||||
assert tree.name == expected_name
|
|
||||||
if isinstance(tree.body, PythonNode):
|
|
||||||
assert compare_ast(tree.body.ast, expected_expr)
|
|
||||||
else:
|
|
||||||
assert tree.body == expected_expr
|
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_parse_complex_def_concept_statement():
|
|
||||||
text = """def concept a plus b
|
|
||||||
where a,b
|
|
||||||
pre isinstance(a, int) and isinstance(b, float)
|
|
||||||
post isinstance(res, int)
|
|
||||||
as res = a + b
|
|
||||||
"""
|
|
||||||
parser = DefaultParser(PythonParser)
|
|
||||||
tree = parser.parse(None, text)
|
|
||||||
assert not parser.has_error
|
|
||||||
assert isinstance(tree, DefConceptNode)
|
|
||||||
assert tree.name == "a plus b"
|
|
||||||
assert tree.where.source == "a,b"
|
|
||||||
assert isinstance(tree.where.ast, ast.Expression)
|
|
||||||
assert tree.pre.source == "isinstance(a, int) and isinstance(b, float)"
|
|
||||||
assert isinstance(tree.pre.ast, ast.Expression)
|
|
||||||
assert tree.post.source == "isinstance(res, int)"
|
|
||||||
assert isinstance(tree.post.ast, ast.Expression)
|
|
||||||
assert tree.body.source == "res = a + b"
|
|
||||||
assert isinstance(tree.body.ast, ast.Module)
|
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_use_colon_to_declare_indentation():
|
|
||||||
text = """
|
|
||||||
def concept add one to a as:
|
|
||||||
def func(x):
|
|
||||||
return x+1
|
|
||||||
func(a)
|
|
||||||
"""
|
|
||||||
parser = DefaultParser(PythonParser)
|
|
||||||
tree = parser.parse(None, text)
|
|
||||||
assert not parser.has_error
|
|
||||||
assert isinstance(tree, DefConceptNode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_use_colon_to_declare_indentation2():
|
|
||||||
text = """
|
|
||||||
def concept add one to a as:
|
|
||||||
def func(x):
|
|
||||||
return x+1
|
|
||||||
"""
|
|
||||||
parser = DefaultParser(PythonParser)
|
|
||||||
tree = parser.parse(None, text)
|
|
||||||
assert not parser.has_error
|
|
||||||
assert isinstance(tree, DefConceptNode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_without_colon_i_get_an_indent_error():
|
|
||||||
text = """
|
|
||||||
def concept add one to a as
|
|
||||||
def func(x):
|
|
||||||
return x+1
|
|
||||||
func(a)
|
|
||||||
"""
|
|
||||||
parser = DefaultParser(PythonParser)
|
|
||||||
tree = parser.parse(None, text)
|
|
||||||
assert parser.has_error
|
|
||||||
assert isinstance(tree, DefConceptNode)
|
|
||||||
assert isinstance(parser.error_sink[0].exception, IndentationError)
|
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_detect_error():
|
|
||||||
"""
|
|
||||||
In this test, func(b) is not correctly indented while colon is specified after the 'as' keyword
|
|
||||||
"""
|
|
||||||
|
|
||||||
text = """
|
|
||||||
def concept add one to a as:
|
|
||||||
def func(x):
|
|
||||||
return x+1
|
|
||||||
func(a)
|
|
||||||
func(b)
|
|
||||||
"""
|
|
||||||
parser = DefaultParser(PythonParser)
|
|
||||||
tree = parser.parse(None, text)
|
|
||||||
assert parser.has_error
|
|
||||||
assert isinstance(tree, DefConceptNode)
|
|
||||||
assert isinstance(parser.error_sink[0], UnexpectedTokenErrorNode)
|
|
||||||
# check that the error is caused by 'func(b)'
|
|
||||||
assert parser.error_sink[0].tokens[0].line == 6
|
|
||||||
assert parser.error_sink[0].tokens[0].column == 1
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text, token_found, expected_tokens", [
|
|
||||||
("def hello as 'hello'", "hello", [Keywords.CONCEPT]),
|
|
||||||
("def concept as", Keywords.AS, ["<name>"]),
|
|
||||||
])
|
|
||||||
def test_i_can_detect_unexpected_token_error_in_def_concept(text, token_found, expected_tokens):
|
|
||||||
parser = DefaultParser(PythonParser)
|
|
||||||
parser.parse(None, text)
|
|
||||||
|
|
||||||
assert parser.has_error
|
|
||||||
assert isinstance(parser.error_sink[0], UnexpectedTokenErrorNode)
|
|
||||||
assert parser.error_sink[0].tokens[0].value == token_found
|
|
||||||
assert parser.error_sink[0].expected_tokens == expected_tokens
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text", [
|
|
||||||
"def concept hello where 1+",
|
|
||||||
"def concept hello pre 1+",
|
|
||||||
"def concept hello post 1+",
|
|
||||||
"def concept hello as 1+"
|
|
||||||
])
|
|
||||||
def test_i_can_detect_error_in_declaration(text):
|
|
||||||
parser = DefaultParser(PythonParser)
|
|
||||||
parser.parse(None, text)
|
|
||||||
assert parser.has_error
|
|
||||||
assert isinstance(parser.error_sink[0], PythonErrorNode)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+400
-59
@@ -5,11 +5,13 @@ import os
|
|||||||
from os import path
|
from os import path
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from core.concept import Concept, ConceptParts, ReturnValueConcept
|
from core import utils
|
||||||
|
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept
|
||||||
|
from core.concept import Concept, ConceptParts
|
||||||
from core.sheerka import Sheerka, ExecutionContext
|
from core.sheerka import Sheerka, ExecutionContext
|
||||||
from parsers.DefaultParser import DefConceptNode, DefaultParser
|
from parsers.DefaultParser import DefaultParser
|
||||||
from parsers.PythonParser import PythonParser
|
from parsers.PythonParser import PythonParser
|
||||||
from sdp.sheerkaDataProvider import SheerkaDataProvider
|
from sdp.sheerkaDataProvider import SheerkaDataProvider, SheerkaDataProviderDuplicateKeyError
|
||||||
|
|
||||||
tests_root = path.abspath("../build/tests")
|
tests_root = path.abspath("../build/tests")
|
||||||
root_folder = "init_folder"
|
root_folder = "init_folder"
|
||||||
@@ -36,68 +38,55 @@ def test_root_folder_is_created_after_initialization():
|
|||||||
assert os.path.exists(root_folder), "init folder should be created"
|
assert os.path.exists(root_folder), "init folder should be created"
|
||||||
|
|
||||||
|
|
||||||
def test_lists_of_concepts_is_initialized():
|
def test_i_can_list_builtin_concepts():
|
||||||
sheerka = get_sheerka()
|
sheerka = get_sheerka()
|
||||||
assert len(sheerka.concepts_cache) > 1
|
builtins = list(sheerka.get_builtins_classes_as_dict())
|
||||||
|
|
||||||
|
assert str(BuiltinConcepts.ERROR) in builtins
|
||||||
|
assert str(BuiltinConcepts.RETURN_VALUE) in builtins
|
||||||
|
|
||||||
|
|
||||||
def get_concept():
|
def test_builtin_concepts_are_initialized():
|
||||||
text = """
|
sheerka = get_sheerka()
|
||||||
def concept a+b
|
assert len(sheerka.concepts_cache) == len(BuiltinConcepts)
|
||||||
where isinstance(a, int) and isinstance(b, int)
|
for concept_name in BuiltinConcepts:
|
||||||
pre isinstance(a, int) and isinstance(b, int)
|
assert str(concept_name) in sheerka.concepts_cache
|
||||||
post isinstance(res, int)
|
assert sheerka.sdp.get_safe(sheerka.CONCEPTS_ENTRY, str(concept_name)) is not None
|
||||||
as:
|
|
||||||
def func(x,y):
|
for key, concept_class in sheerka.get_builtins_classes_as_dict().items():
|
||||||
return x+y
|
assert isinstance(sheerka.concepts_cache[key], concept_class)
|
||||||
func(a,b)
|
|
||||||
"""
|
|
||||||
parser = DefaultParser(PythonParser)
|
def test_builtin_concepts_can_be_updated():
|
||||||
return parser.parse(None, text)
|
sheerka = get_sheerka()
|
||||||
|
loaded_sheerka = sheerka.get(BuiltinConcepts.SHEERKA)
|
||||||
|
loaded_sheerka.desc = "I have a description"
|
||||||
|
sheerka.sdp.modify("Test", sheerka.CONCEPTS_ENTRY, loaded_sheerka.key, loaded_sheerka)
|
||||||
|
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
loaded_sheerka = sheerka.get(BuiltinConcepts.SHEERKA)
|
||||||
|
|
||||||
|
assert loaded_sheerka.desc == "I have a description"
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_add_a_concept():
|
def test_i_can_add_a_concept():
|
||||||
sheerka = get_sheerka()
|
sheerka = get_sheerka()
|
||||||
concept = get_concept()
|
concept = get_default_concept()
|
||||||
res = sheerka.add_concept(ExecutionContext(sheerka, "xxx"), concept)
|
|
||||||
concept_found = res.value
|
res = sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
|
||||||
assert res.status
|
assert res.status
|
||||||
assert concept_found == Concept(
|
assert sheerka.isinstance(res.value, BuiltinConcepts.NEW_CONCEPT)
|
||||||
name="a + b",
|
|
||||||
where="isinstance(a, int) and isinstance(b, int)",
|
|
||||||
pre="isinstance(a, int) and isinstance(b, int)",
|
|
||||||
post="isinstance(res, int)",
|
|
||||||
body="def func(x,y):\n return x+y\nfunc(a,b)")
|
|
||||||
assert isinstance(concept_found.codes[ConceptParts.WHERE], ast.Expression)
|
|
||||||
assert isinstance(concept_found.codes[ConceptParts.PRE], ast.Expression)
|
|
||||||
assert isinstance(concept_found.codes[ConceptParts.POST], ast.Expression)
|
|
||||||
assert isinstance(concept_found.codes[ConceptParts.BODY], ast.Module)
|
|
||||||
|
|
||||||
all_props = list(concept_found.props.keys())
|
concept_found = res.value.body
|
||||||
assert all_props == ["a", "b"]
|
for prop in Concept.props_to_serialize:
|
||||||
|
assert getattr(concept_found, prop) == getattr(concept, prop)
|
||||||
|
|
||||||
assert concept_found.key == "__var__0 + __var__1"
|
assert concept_found.key == "__var__0 + __var__1"
|
||||||
assert concept_found.id == "1001"
|
assert concept_found.id == "1001"
|
||||||
|
|
||||||
assert path.exists(sheerka.sdp.get_obj_path(SheerkaDataProvider.ObjectsFolder,
|
assert concept.key in sheerka.concepts_cache
|
||||||
"4f249487410db35d8bcbcf4521acb3dd8354978804cd99bbc4de17a323b2f237"))
|
assert path.exists(sheerka.sdp.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_found.get_digest()))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("text, expected", [
|
|
||||||
("1 + 1", 2),
|
|
||||||
("sheerka.test()", 'I have access to Sheerka !')
|
|
||||||
])
|
|
||||||
def test_i_can_eval_simple_python_expressions(text, expected):
|
|
||||||
sheerka = Sheerka(debug=True)
|
|
||||||
sheerka.initialize(root_folder)
|
|
||||||
|
|
||||||
res = sheerka.eval(text)
|
|
||||||
|
|
||||||
assert len(res) == 1
|
|
||||||
assert res[0].status
|
|
||||||
assert res[0].value.body == expected
|
|
||||||
assert sheerka.isinstance(res[0].value, ReturnValueConcept())
|
|
||||||
|
|
||||||
|
|
||||||
def test_i_cannot_add_the_same_concept_twice():
|
def test_i_cannot_add_the_same_concept_twice():
|
||||||
@@ -105,30 +94,382 @@ def test_i_cannot_add_the_same_concept_twice():
|
|||||||
Checks that duplicated concepts are managed by sheerka, not by sheerka.sdp
|
Checks that duplicated concepts are managed by sheerka, not by sheerka.sdp
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
pass
|
sheerka = get_sheerka()
|
||||||
|
concept = get_default_concept()
|
||||||
|
|
||||||
|
sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
res = sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert sheerka.isinstance(res.value, BuiltinConcepts.ERROR)
|
||||||
|
assert res.value.body.args[0] == "Duplicate object."
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_get_a_concept():
|
def test_i_can_get_a_builtin_concept_by_their_enum_or_the_string():
|
||||||
"""
|
"""
|
||||||
Checks that a concept can be found its name
|
Checks that a concept can be found its name
|
||||||
even when there are variables in the name (ex 'hello + a' or 'a + b' )
|
even when there are variables in the name (ex 'hello + a' or 'a + b' )
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
pass
|
sheerka = get_sheerka()
|
||||||
|
for key in sheerka.get_builtins_classes_as_dict():
|
||||||
|
assert sheerka.get(key) is not None
|
||||||
|
assert sheerka.get(str(key)) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_get_new_concept():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
concept = get_default_concept()
|
||||||
|
|
||||||
|
sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
|
||||||
|
from_cache = sheerka.get(concept.key)
|
||||||
|
assert from_cache is not None
|
||||||
|
assert from_cache.key == concept.key
|
||||||
|
assert from_cache == concept
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_first_look_in_local_cache():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
concept = get_default_concept()
|
||||||
|
|
||||||
|
sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
sheerka.concepts_cache[concept.key].pre = "I have modified the concept in cache"
|
||||||
|
|
||||||
|
from_cache = sheerka.get(concept.key)
|
||||||
|
assert from_cache is not None
|
||||||
|
assert from_cache.key == concept.key
|
||||||
|
assert from_cache.pre == "I have modified the concept in cache"
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_get_a_known_concept_when_not_in_cache():
|
||||||
|
"""
|
||||||
|
When not in cache, uses sdp
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
concept = get_default_concept()
|
||||||
|
sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
|
||||||
|
sheerka.concepts_cache = {} # reset the cache
|
||||||
|
loaded = sheerka.get(concept.key)
|
||||||
|
|
||||||
|
assert loaded is not None
|
||||||
|
assert loaded == concept
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_concept_is_return_when_the_concept_is_not_found():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
loaded = sheerka.get("fake_key")
|
||||||
|
|
||||||
|
assert loaded is not None
|
||||||
|
assert sheerka.isinstance(loaded, BuiltinConcepts.UNKNOWN_CONCEPT)
|
||||||
|
assert loaded.body == "fake_key"
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_instantiate_a_builtin_concept_when_it_has_its_own_class():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
ret = sheerka.new(BuiltinConcepts.RETURN_VALUE, who="who", status="status", value="value", message="message")
|
||||||
|
|
||||||
|
assert isinstance(ret, ReturnValueConcept)
|
||||||
|
assert ret.key == str(BuiltinConcepts.RETURN_VALUE)
|
||||||
|
assert ret.who == "who"
|
||||||
|
assert ret.status == "status"
|
||||||
|
assert ret.value == "value"
|
||||||
|
assert ret.message == "message"
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_instantiate_a_builtin_concept_when_no_specific_class():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
ret = sheerka.new(BuiltinConcepts.UNKNOWN_CONCEPT, body="fake_concept")
|
||||||
|
|
||||||
|
assert isinstance(ret, Concept)
|
||||||
|
assert ret.key == str(BuiltinConcepts.UNKNOWN_CONCEPT)
|
||||||
|
assert ret.body == "fake_concept"
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_instantiate_a_concept():
|
def test_i_can_instantiate_a_concept():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
concept = get_default_concept()
|
||||||
|
sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
|
||||||
|
new = sheerka.new(concept.key, a=10, b="value")
|
||||||
|
|
||||||
|
assert sheerka.isinstance(new, concept)
|
||||||
|
for prop in Concept.props_to_serialize:
|
||||||
|
assert getattr(new, prop) == getattr(concept, prop)
|
||||||
|
|
||||||
|
assert new.props["a"].value == 10
|
||||||
|
assert new.props["b"].value == "value"
|
||||||
|
|
||||||
|
|
||||||
|
def test_instances_are_different_when_asking_for_new():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
concept = get_default_concept()
|
||||||
|
sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
|
||||||
|
new1 = sheerka.new(concept.key, a=10, b="value")
|
||||||
|
new2 = sheerka.new(concept.key, a=10, b="value")
|
||||||
|
|
||||||
|
assert new1 == new2
|
||||||
|
assert id(new1) != id(new2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_get_the_same_instance_when_is_unique_is_true():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
concept = get_unique_concept()
|
||||||
|
sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
|
||||||
|
new1 = sheerka.new(concept.key, a=10, b="value")
|
||||||
|
new2 = sheerka.new(concept.key, a=10, b="value")
|
||||||
|
|
||||||
|
assert new1 == new2
|
||||||
|
assert id(new1) == id(new2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_cannot_instantiate_an_unknown_concept():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
new = sheerka.new("fake_concept")
|
||||||
|
|
||||||
|
assert sheerka.isinstance(new, BuiltinConcepts.UNKNOWN_CONCEPT)
|
||||||
|
assert new.body == "fake_concept"
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_cannot_instantiate_when_properties_are_not_recognized():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
concept = get_default_concept()
|
||||||
|
sheerka.create_new_concept(get_context(sheerka), concept)
|
||||||
|
|
||||||
|
new = sheerka.new(concept.key, a=10, c="value")
|
||||||
|
|
||||||
|
assert sheerka.isinstance(new, BuiltinConcepts.UNKNOWN_PROPERTY)
|
||||||
|
assert new.property_name == "c"
|
||||||
|
assert sheerka.isinstance(new.concept, concept)
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_expect_one_when_empty():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
res = sheerka.expect_one(get_context(sheerka), [])
|
||||||
|
assert not res.status
|
||||||
|
assert sheerka.isinstance(res.value, BuiltinConcepts.IS_EMPTY)
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_expect_one_when_too_many_success():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
items = [
|
||||||
|
ReturnValueConcept("who", True, None),
|
||||||
|
ReturnValueConcept("who", True, None),
|
||||||
|
]
|
||||||
|
res = sheerka.expect_one(get_context(sheerka), items)
|
||||||
|
assert not res.status
|
||||||
|
assert sheerka.isinstance(res.value, BuiltinConcepts.TOO_MANY_SUCCESS)
|
||||||
|
assert res.value.obj == items
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_expect_when_only_errors_1():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
items = [
|
||||||
|
ReturnValueConcept("who", False, None),
|
||||||
|
]
|
||||||
|
res = sheerka.expect_one(get_context(sheerka), items)
|
||||||
|
assert not res.status
|
||||||
|
assert sheerka.isinstance(res.value, BuiltinConcepts.TOO_MANY_ERRORS)
|
||||||
|
assert res.value.obj == items
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_expect_when_only_errors_2():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
items = [
|
||||||
|
ReturnValueConcept("who", False, None),
|
||||||
|
ReturnValueConcept("who", False, None),
|
||||||
|
]
|
||||||
|
res = sheerka.expect_one(get_context(sheerka), items)
|
||||||
|
assert not res.status
|
||||||
|
assert sheerka.isinstance(res.value, BuiltinConcepts.TOO_MANY_ERRORS)
|
||||||
|
assert res.value.obj == items
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_expect_one_when_one_success_1():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
items = [
|
||||||
|
ReturnValueConcept("who", True, None),
|
||||||
|
]
|
||||||
|
res = sheerka.expect_one(get_context(sheerka), items)
|
||||||
|
assert res.status
|
||||||
|
assert res == items[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_expect_one_when_one_success_2():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
items = [
|
||||||
|
ReturnValueConcept("who", False, None),
|
||||||
|
ReturnValueConcept("who", True, None),
|
||||||
|
ReturnValueConcept("who", False, None),
|
||||||
|
]
|
||||||
|
res = sheerka.expect_one(get_context(sheerka), items)
|
||||||
|
assert res.status
|
||||||
|
assert res == items[1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_expect_one_when_not_a_list_true():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
res = sheerka.expect_one(get_context(sheerka), ReturnValueConcept("who", True, None))
|
||||||
|
assert res.status
|
||||||
|
assert res == ReturnValueConcept("who", True, None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_use_expect_one_when_not_a_list_false():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
res = sheerka.expect_one(get_context(sheerka), ReturnValueConcept("who", False, None))
|
||||||
|
|
||||||
|
assert not res.status
|
||||||
|
assert sheerka.isinstance(res.value, BuiltinConcepts.TOO_MANY_ERRORS)
|
||||||
|
assert res.value.obj == [ReturnValueConcept("who", False, None)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, expected", [
|
||||||
|
("1 + 1", 2),
|
||||||
|
("sheerka.test()", 'I have access to Sheerka !')
|
||||||
|
])
|
||||||
|
def test_i_can_eval_simple_python_expressions(text, expected):
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
res = sheerka.eval(text)
|
||||||
|
|
||||||
|
assert len(res) == 1
|
||||||
|
assert res[0].status
|
||||||
|
assert res[0].value == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_eval_simple_concept():
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
concept = Concept(name="one", body="1").init_key()
|
||||||
|
sheerka.add_in_cache(concept)
|
||||||
|
|
||||||
|
text = "one"
|
||||||
|
res = sheerka.eval(text)
|
||||||
|
assert len(res) == 1
|
||||||
|
assert res[0].status
|
||||||
|
assert res[0].value == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_eval_def_concept_request():
|
||||||
|
text = """
|
||||||
|
def concept a + b
|
||||||
|
where isinstance(a, int) and isinstance(b, int)
|
||||||
|
pre isinstance(a, int) and isinstance(b, int)
|
||||||
|
post isinstance(res, int)
|
||||||
|
as:
|
||||||
|
def func(x,y):
|
||||||
|
return x+y
|
||||||
|
func(a,b)
|
||||||
"""
|
"""
|
||||||
Test the new() functionnality
|
|
||||||
make sure that some Concept are singleton (ex Sheerka, True, False)
|
expected = get_default_concept()
|
||||||
otherwise, make sure that new() returns a **new** instance
|
expected.id = "1001"
|
||||||
|
expected.desc = None
|
||||||
|
expected.init_key()
|
||||||
|
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
res = sheerka.eval(text)
|
||||||
|
|
||||||
|
assert len(res) == 1
|
||||||
|
assert res[0].status
|
||||||
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
|
||||||
|
|
||||||
|
concept_saved = res[0].value.body
|
||||||
|
|
||||||
|
for prop in Concept.props_to_serialize:
|
||||||
|
assert getattr(concept_saved, prop) == getattr(expected, prop)
|
||||||
|
|
||||||
|
assert concept_saved.key in sheerka.concepts_cache
|
||||||
|
assert path.exists(sheerka.sdp.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_saved.get_digest()))
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_eval_def_concept_part_when_one_part_is_a_ref_of_another_concept():
|
||||||
|
"""
|
||||||
|
In this test, we test that the properties of 'concept a xx b' (which are 'a' and 'b')
|
||||||
|
are correctly detected, because of the concept 'a plus b' in its body
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
pass
|
sheerka = get_sheerka()
|
||||||
|
|
||||||
|
# concept 'a plus b' is known
|
||||||
|
concept_a_plus_b = Concept(name="a plus b").set_prop("a").set_prop("b").init_key()
|
||||||
|
sheerka.add_in_cache(concept_a_plus_b)
|
||||||
|
|
||||||
|
res = sheerka.eval("def concept a xx b as a plus b")
|
||||||
|
expected = Concept(name="a xx b", body="a plus b").set_prop("a").set_prop("b").init_key()
|
||||||
|
expected.id = "1001"
|
||||||
|
|
||||||
|
assert len(res) == 1
|
||||||
|
assert res[0].status
|
||||||
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
|
||||||
|
|
||||||
|
concept_saved = res[0].value.body
|
||||||
|
|
||||||
|
for prop in Concept.props_to_serialize:
|
||||||
|
assert getattr(concept_saved, prop) == getattr(expected, prop)
|
||||||
|
|
||||||
|
assert concept_saved.key in sheerka.concepts_cache
|
||||||
|
assert path.exists(sheerka.sdp.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_saved.get_digest()))
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_cannot_eval_the_same_def_concept_twice():
|
||||||
|
text = """
|
||||||
|
def concept a + b
|
||||||
|
where isinstance(a, int) and isinstance(b, int)
|
||||||
|
pre isinstance(a, int) and isinstance(b, int)
|
||||||
|
post isinstance(res, int)
|
||||||
|
as:
|
||||||
|
def func(x,y):
|
||||||
|
return x+y
|
||||||
|
func(a,b)
|
||||||
|
"""
|
||||||
|
|
||||||
|
sheerka = get_sheerka()
|
||||||
|
sheerka.eval(text)
|
||||||
|
res = sheerka.eval(text)
|
||||||
|
|
||||||
|
assert len(res) == 1
|
||||||
|
assert not res[0].status
|
||||||
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED)
|
||||||
|
|
||||||
|
|
||||||
def get_sheerka():
|
def get_sheerka():
|
||||||
sheerka = Sheerka()
|
sheerka = Sheerka()
|
||||||
sheerka.initialize(root_folder)
|
sheerka.initialize(root_folder)
|
||||||
|
|
||||||
return sheerka
|
return sheerka
|
||||||
|
|
||||||
|
|
||||||
|
def get_context(sheerka):
|
||||||
|
return ExecutionContext("test", "xxx", sheerka)
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_concept():
|
||||||
|
concept = Concept(
|
||||||
|
name="a + b",
|
||||||
|
where="isinstance(a, int) and isinstance(b, int)",
|
||||||
|
pre="isinstance(a, int) and isinstance(b, int)",
|
||||||
|
post="isinstance(res, int)",
|
||||||
|
body="def func(x,y):\n return x+y\nfunc(a,b)",
|
||||||
|
desc="specific description")
|
||||||
|
concept.set_prop("a", "value1")
|
||||||
|
concept.set_prop("b", "value2")
|
||||||
|
|
||||||
|
return concept
|
||||||
|
|
||||||
|
|
||||||
|
def get_unique_concept():
|
||||||
|
return Concept(name="unique", is_unique=True)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class ObjWithDigestNoKey:
|
|||||||
self.b == obj.b
|
self.b == obj.b
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"ObjNoKey({self.a}, {self.b})"
|
return f"ObjWithDigestNoKey({self.a}, {self.b})"
|
||||||
|
|
||||||
def get_digest(self):
|
def get_digest(self):
|
||||||
return str(self.a) + str(self.b)
|
return str(self.a) + str(self.b)
|
||||||
@@ -142,7 +142,7 @@ class ObjWithDigestWithKey:
|
|||||||
self.b == obj.b
|
self.b == obj.b
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"ObjNoKey({self.a}, {self.b})"
|
return f"ObjWithDigestWithKey({self.a}, {self.b})"
|
||||||
|
|
||||||
def get_key(self):
|
def get_key(self):
|
||||||
return self.a
|
return self.a
|
||||||
@@ -466,7 +466,7 @@ def test_i_cannot_add_the_same_digest_twice_in_the_same_entry():
|
|||||||
|
|
||||||
assert error.value.obj.get_digest() == ObjWithDigestNoKey("a", "b").get_digest()
|
assert error.value.obj.get_digest() == ObjWithDigestNoKey("a", "b").get_digest()
|
||||||
assert error.value.key == "entry"
|
assert error.value.key == "entry"
|
||||||
assert error.value.args[0] == "duplicate key"
|
assert error.value.args[0] == "Duplicate object."
|
||||||
|
|
||||||
|
|
||||||
def test_i_cannot_add_the_same_digest_twice_in_the_same_entry2():
|
def test_i_cannot_add_the_same_digest_twice_in_the_same_entry2():
|
||||||
@@ -483,7 +483,7 @@ def test_i_cannot_add_the_same_digest_twice_in_the_same_entry2():
|
|||||||
|
|
||||||
assert error.value.obj.get_digest() == ObjWithDigestNoKey("a", "b").get_digest()
|
assert error.value.obj.get_digest() == ObjWithDigestNoKey("a", "b").get_digest()
|
||||||
assert error.value.key == "entry"
|
assert error.value.key == "entry"
|
||||||
assert error.value.args[0] == "duplicate key"
|
assert error.value.args[0] == "Duplicate object."
|
||||||
|
|
||||||
|
|
||||||
def test_i_cannot_add_the_same_digest_twice_in_the_same_entry3():
|
def test_i_cannot_add_the_same_digest_twice_in_the_same_entry3():
|
||||||
@@ -499,7 +499,7 @@ def test_i_cannot_add_the_same_digest_twice_in_the_same_entry3():
|
|||||||
|
|
||||||
assert error.value.obj.get_digest() == ObjWithDigestWithKey("a", "b").get_digest()
|
assert error.value.obj.get_digest() == ObjWithDigestWithKey("a", "b").get_digest()
|
||||||
assert error.value.key == "entry.a"
|
assert error.value.key == "entry.a"
|
||||||
assert error.value.args[0] == "duplicate key"
|
assert error.value.args[0] == "Duplicate object."
|
||||||
|
|
||||||
|
|
||||||
def test_i_cannot_add_the_same_digest_twice_in_the_same_entry4():
|
def test_i_cannot_add_the_same_digest_twice_in_the_same_entry4():
|
||||||
@@ -516,7 +516,7 @@ def test_i_cannot_add_the_same_digest_twice_in_the_same_entry4():
|
|||||||
|
|
||||||
assert error.value.obj.get_digest() == ObjWithDigestWithKey("a", "b").get_digest()
|
assert error.value.obj.get_digest() == ObjWithDigestWithKey("a", "b").get_digest()
|
||||||
assert error.value.key == "entry.a"
|
assert error.value.key == "entry.a"
|
||||||
assert error.value.args[0] == "duplicate key"
|
assert error.value.args[0] == "Duplicate object."
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_get_and_set_key():
|
def test_i_can_get_and_set_key():
|
||||||
@@ -1198,7 +1198,7 @@ def test_i_can_remove_from_cache():
|
|||||||
assert not sdp.in_cache(category, key)
|
assert not sdp.in_cache(category, key)
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_test_than_an_entry_exits():
|
def test_i_can_test_than_an_entry_exists():
|
||||||
sdp = SheerkaDataProvider(".sheerka")
|
sdp = SheerkaDataProvider(".sheerka")
|
||||||
|
|
||||||
assert not sdp.exists("entry")
|
assert not sdp.exists("entry")
|
||||||
@@ -1206,6 +1206,67 @@ def test_i_can_test_than_an_entry_exits():
|
|||||||
assert sdp.exists("entry")
|
assert sdp.exists("entry")
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_test_if_a_key_exists():
|
||||||
|
sdp = SheerkaDataProvider(".sheerka")
|
||||||
|
obj = ObjWithDigestWithKey("key", "value")
|
||||||
|
|
||||||
|
assert not sdp.exists("entry")
|
||||||
|
assert not sdp.exists("entry", obj.get_key())
|
||||||
|
|
||||||
|
sdp.add(evt_digest, "entry", obj)
|
||||||
|
assert not sdp.exists("entry", "wrong_key")
|
||||||
|
assert sdp.exists("entry", obj.get_key())
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_test_that_the_object_exists():
|
||||||
|
sdp = SheerkaDataProvider(".sheerka")
|
||||||
|
obj = ObjWithDigestWithKey("key", "value")
|
||||||
|
|
||||||
|
assert not sdp.exists("entry")
|
||||||
|
assert not sdp.exists("entry", obj.get_key())
|
||||||
|
assert not sdp.exists("entry", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
# test for a single item under the key
|
||||||
|
sdp.add(evt_digest, "entry", obj)
|
||||||
|
assert not sdp.exists("entry", obj.get_key(), "wrong_digest")
|
||||||
|
assert sdp.exists("entry", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
# test for a list item under the key
|
||||||
|
sdp.add(evt_digest, "entry2", ObjWithDigestWithKey("key", "value2"))
|
||||||
|
assert not sdp.exists("entry2", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
sdp.add(evt_digest, "entry2", ObjWithDigestWithKey("key", "value3"))
|
||||||
|
assert not sdp.exists("entry2", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
sdp.add(evt_digest, "entry2", obj)
|
||||||
|
assert sdp.exists("entry2", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_test_than_the_object_exists_when_using_references():
|
||||||
|
sdp = SheerkaDataProvider(".sheerka")
|
||||||
|
sdp.serializer.register(PickleSerializer(lambda o: isinstance(o, ObjWithDigestWithKey)))
|
||||||
|
obj = ObjWithDigestWithKey("key", "value")
|
||||||
|
|
||||||
|
assert not sdp.exists("entry")
|
||||||
|
assert not sdp.exists("entry", obj.get_key())
|
||||||
|
assert not sdp.exists("entry", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
# test for a single item under the key
|
||||||
|
sdp.add(evt_digest, "entry", obj, use_ref=True)
|
||||||
|
assert not sdp.exists("entry", obj.get_key(), "wrong_digest")
|
||||||
|
assert sdp.exists("entry", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
# test for a list item under the key
|
||||||
|
sdp.add(evt_digest, "entry2", ObjWithDigestWithKey("key", "value2"), use_ref=True)
|
||||||
|
assert not sdp.exists("entry2", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
sdp.add(evt_digest, "entry2", ObjWithDigestWithKey("key", "value3"), use_ref=True)
|
||||||
|
assert not sdp.exists("entry2", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
sdp.add(evt_digest, "entry2", obj, use_ref=True)
|
||||||
|
assert sdp.exists("entry2", obj.get_key(), obj.get_digest())
|
||||||
|
|
||||||
|
|
||||||
def test_i_can_save_and_load_object_ref_with_history():
|
def test_i_can_save_and_load_object_ref_with_history():
|
||||||
sdp = SheerkaDataProvider(".sheerka")
|
sdp = SheerkaDataProvider(".sheerka")
|
||||||
obj = ObjDumpJson("my_key", "value1")
|
obj = ObjDumpJson("my_key", "value1")
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import pytest
|
||||||
|
from core.tokenizer import Tokenizer, Token, TokenKind, LexerError, Keywords
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_tokenize():
|
||||||
|
source = "+*-/{}[]() ,;:.?\n\n\r\r\r\nidentifier_0\t \t10.15 10 'string\n' \"another string\"=|&"
|
||||||
|
tokens = list(Tokenizer(source))
|
||||||
|
assert tokens[0] == Token(TokenKind.PLUS, "+", 0, 1, 1)
|
||||||
|
assert tokens[1] == Token(TokenKind.STAR, "*", 1, 1, 2)
|
||||||
|
assert tokens[2] == Token(TokenKind.MINUS, "-", 2, 1, 3)
|
||||||
|
assert tokens[3] == Token(TokenKind.SLASH, "/", 3, 1, 4)
|
||||||
|
assert tokens[4] == Token(TokenKind.LBRACE, "{", 4, 1, 5)
|
||||||
|
assert tokens[5] == Token(TokenKind.RBRACE, "}", 5, 1, 6)
|
||||||
|
assert tokens[6] == Token(TokenKind.LBRACKET, "[", 6, 1, 7)
|
||||||
|
assert tokens[7] == Token(TokenKind.RBRACKET, "]", 7, 1, 8)
|
||||||
|
assert tokens[8] == Token(TokenKind.LPAR, "(", 8, 1, 9)
|
||||||
|
assert tokens[9] == Token(TokenKind.RPAR, ")", 9, 1, 10)
|
||||||
|
assert tokens[10] == Token(TokenKind.WHITESPACE, " ", 10, 1, 11)
|
||||||
|
assert tokens[11] == Token(TokenKind.COMMA, ",", 14, 1, 15)
|
||||||
|
assert tokens[12] == Token(TokenKind.SEMICOLON, ";", 15, 1, 16)
|
||||||
|
assert tokens[13] == Token(TokenKind.COLON, ":", 16, 1, 17)
|
||||||
|
assert tokens[14] == Token(TokenKind.DOT, ".", 17, 1, 18)
|
||||||
|
assert tokens[15] == Token(TokenKind.QMARK, "?", 18, 1, 19)
|
||||||
|
assert tokens[16] == Token(TokenKind.NEWLINE, "\n", 19, 1, 20)
|
||||||
|
assert tokens[17] == Token(TokenKind.NEWLINE, "\n\r", 20, 2, 1)
|
||||||
|
assert tokens[18] == Token(TokenKind.NEWLINE, "\r", 22, 3, 1)
|
||||||
|
assert tokens[19] == Token(TokenKind.NEWLINE, "\r\n", 23, 4, 1)
|
||||||
|
assert tokens[20] == Token(TokenKind.IDENTIFIER, "identifier_0", 25, 5, 1)
|
||||||
|
assert tokens[21] == Token(TokenKind.WHITESPACE, "\t \t", 37, 5, 13)
|
||||||
|
assert tokens[22] == Token(TokenKind.NUMBER, "10.15", 41, 5, 17)
|
||||||
|
assert tokens[23] == Token(TokenKind.WHITESPACE, " ", 46, 5, 22)
|
||||||
|
assert tokens[24] == Token(TokenKind.NUMBER, "10", 47, 5, 23)
|
||||||
|
assert tokens[25] == Token(TokenKind.WHITESPACE, " ", 49, 5, 25)
|
||||||
|
assert tokens[26] == Token(TokenKind.STRING, "'string\n'", 50, 5, 26)
|
||||||
|
assert tokens[27] == Token(TokenKind.WHITESPACE, " ", 59, 6, 1)
|
||||||
|
assert tokens[28] == Token(TokenKind.STRING, '"another string"', 60, 6, 2)
|
||||||
|
assert tokens[29] == Token(TokenKind.EQUALS, '=', 76, 6, 18)
|
||||||
|
assert tokens[30] == Token(TokenKind.VBAR, '|', 77, 6, 19)
|
||||||
|
assert tokens[31] == Token(TokenKind.AMPER, '&', 78, 6, 20)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, expected", [
|
||||||
|
("_ident", True),
|
||||||
|
("ident", True),
|
||||||
|
("ident123", True),
|
||||||
|
("ident_123", True),
|
||||||
|
("ident-like-this", True),
|
||||||
|
("àèùéû", True),
|
||||||
|
("011254", False),
|
||||||
|
("0abcd", False),
|
||||||
|
("-abcd", False)
|
||||||
|
])
|
||||||
|
def test_i_can_tokenize_identifiers(text, expected):
|
||||||
|
tokens = list(Tokenizer(text))
|
||||||
|
comparison = tokens[0].type == TokenKind.IDENTIFIER
|
||||||
|
assert comparison == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, error_text, index, line, column", [
|
||||||
|
("'string", "'string", 7, 1, 8),
|
||||||
|
('"string', '"string', 7, 1, 8),
|
||||||
|
('"a" + "string', '"string', 13, 1, 14),
|
||||||
|
('"a"\n\n"string', '"string', 12, 3, 8),
|
||||||
|
])
|
||||||
|
def test_i_can_detect_unfinished_strings(text, error_text, index, line, column):
|
||||||
|
with pytest.raises(LexerError) as e:
|
||||||
|
list(Tokenizer(text))
|
||||||
|
assert e.value.text == error_text
|
||||||
|
assert e.value.index == index
|
||||||
|
assert e.value.line == line
|
||||||
|
assert e.value.column == column
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, expected_text, expected_newlines", [
|
||||||
|
("'foo'", "'foo'", 0),
|
||||||
|
('"foo"', '"foo"', 0),
|
||||||
|
("'foo\rbar'", "'foo\rbar'", 1),
|
||||||
|
("'foo\nbar'", "'foo\nbar'", 1),
|
||||||
|
("'foo\n\rbar'", "'foo\n\rbar'", 1),
|
||||||
|
("'foo\r\nbar'", "'foo\r\nbar'", 1),
|
||||||
|
("'foo\r\rbar'", "'foo\r\rbar'", 2),
|
||||||
|
("'foo\n\nbar'", "'foo\n\nbar'", 2),
|
||||||
|
("'foo\r\n\n\rbar'", "'foo\r\n\n\rbar'", 2),
|
||||||
|
("'\rfoo\rbar\r'", "'\rfoo\rbar\r'", 3),
|
||||||
|
("'\nfoo\nbar\n'", "'\nfoo\nbar\n'", 3),
|
||||||
|
("'\n\rfoo\r\n'", "'\n\rfoo\r\n'", 2),
|
||||||
|
(r"'foo\'bar'", r"'foo\'bar'", 0),
|
||||||
|
(r'"foo\"bar"', r'"foo\"bar"', 0),
|
||||||
|
('"foo"bar"', '"foo"', 0),
|
||||||
|
("'foo'bar'", "'foo'", 0),
|
||||||
|
])
|
||||||
|
def test_i_can_parse_strings(text, expected_text, expected_newlines):
|
||||||
|
lexer = Tokenizer(text)
|
||||||
|
text_found, nb_of_newlines = lexer.eat_string(0, 1, 1)
|
||||||
|
|
||||||
|
assert nb_of_newlines == expected_newlines
|
||||||
|
assert text_found == expected_text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text", [
|
||||||
|
"1", "3.1415", "0.5", "01", "-5", "-5.10"
|
||||||
|
])
|
||||||
|
def test_i_can_parse_numbers(text):
|
||||||
|
tokens = list(Tokenizer(text))
|
||||||
|
assert tokens[0].type == TokenKind.NUMBER
|
||||||
|
assert tokens[0].value == text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("text, expected", [
|
||||||
|
("def", Keywords.DEF),
|
||||||
|
("concept", Keywords.CONCEPT),
|
||||||
|
("as", Keywords.AS),
|
||||||
|
("pre", Keywords.PRE),
|
||||||
|
("post", Keywords.POST)
|
||||||
|
])
|
||||||
|
def test_i_can_recognize_keywords(text, expected):
|
||||||
|
tokens = list(Tokenizer(text))
|
||||||
|
assert tokens[0].type == TokenKind.KEYWORD
|
||||||
|
assert tokens[0].value == expected
|
||||||
+11
-1
@@ -6,7 +6,17 @@ import pytest
|
|||||||
(None, "",),
|
(None, "",),
|
||||||
([], ""),
|
([], ""),
|
||||||
(["hello", "world"], "hello world"),
|
(["hello", "world"], "hello world"),
|
||||||
(["hello world", "my friend"], '"hello world" "my friend"')
|
# (["hello world", "my friend"], '"hello world" "my friend"')
|
||||||
])
|
])
|
||||||
def test_i_can_create_string_from_a_list(lst, as_string):
|
def test_i_can_create_string_from_a_list(lst, as_string):
|
||||||
assert core.utils.sysarg_to_string(lst) == as_string
|
assert core.utils.sysarg_to_string(lst) == as_string
|
||||||
|
|
||||||
|
|
||||||
|
def test_i_can_get_classes():
|
||||||
|
classes = list(core.utils.get_classes("core.builtin_concepts"))
|
||||||
|
error_concept = core.utils.get_class("core.builtin_concepts.ErrorConcept")
|
||||||
|
return_value_concept = core.utils.get_class("core.builtin_concepts.ReturnValueConcept")
|
||||||
|
|
||||||
|
assert len(classes) > 2
|
||||||
|
assert error_concept in classes
|
||||||
|
assert return_value_concept in classes
|
||||||
|
|||||||
Reference in New Issue
Block a user