Added basic implentation for where

This commit is contained in:
2020-02-05 18:47:20 +01:00
parent a5a721094b
commit afc1e22949
35 changed files with 864 additions and 320 deletions
+2 -1
View File
@@ -3,4 +3,5 @@ venv
.idea .idea
__pycache__ __pycache__
build build
prof prof
tests/_concepts.txt
+1 -2
View File
@@ -39,5 +39,4 @@ seventeen isa number
eighteen isa number eighteen isa number
nineteen isa number nineteen isa number
twenty isa number twenty isa number
def concept twenties from bnf twenty number where number < 10 as twenty + number def concept twenties from bnf twenty number where number < 10 as twenty + number
+19
View File
@@ -50,6 +50,7 @@ class BuiltinConcepts(Enum):
CONCEPT_EVAL_REQUESTED = "concept eval requested" CONCEPT_EVAL_REQUESTED = "concept eval requested"
REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible
NOT_A_SET = "not a set" # the concept has no entry in sets NOT_A_SET = "not a set" # the concept has no entry in sets
WHERE_CLAUSE_FAILED = "where clause failed" # failed to validate where clause during evaluation
NODE = "node" NODE = "node"
GENERIC_NODE = "generic node" GENERIC_NODE = "generic node"
@@ -91,6 +92,7 @@ BuiltinErrors = [str(e) for e in {
BuiltinConcepts.CONCEPT_EVAL_ERROR, BuiltinConcepts.CONCEPT_EVAL_ERROR,
BuiltinConcepts.CONCEPT_ALREADY_IN_SET, BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
BuiltinConcepts.NOT_A_SET, BuiltinConcepts.NOT_A_SET,
BuiltinConcepts.WHERE_CLAUSE_FAILED
}] }]
""" """
@@ -385,3 +387,20 @@ class ConceptAlreadyInSet(Concept):
@property @property
def concept_set(self): def concept_set(self):
return self.props["concept_set"].value return self.props["concept_set"].value
class WhereClauseFailed(Concept):
def __init__(self, concept=None):
super().__init__(BuiltinConcepts.WHERE_CLAUSE_FAILED,
True,
False,
BuiltinConcepts.WHERE_CLAUSE_FAILED)
self.set_metadata_value(ConceptParts.BODY, concept)
self.metadata.is_evaluated = True
def __repr__(self):
return f"WhereClauseFailed(concept={self.concept})"
@property
def concept(self):
return self.body
+2 -1
View File
@@ -231,7 +231,8 @@ class Concept:
if token.value in variables: if token.value in variables:
key += VARIABLE_PREFIX + str(variables.index(token.value)) key += VARIABLE_PREFIX + str(variables.index(token.value))
else: else:
key += token.value[1:-1] if token.type == TokenKind.STRING else token.value value = token.value[1:-1] if token.type == TokenKind.STRING else token.value
key += value
first = False first = False
self.metadata.key = key self.metadata.key = key
@@ -45,7 +45,7 @@ class SheerkaCreateNewConcept:
# add the BNF if known # add the BNF if known
if concept.bnf: if concept.bnf:
concepts_definitions = self.sheerka.get_concept_definition() concepts_definitions = self.sheerka.get_concepts_definitions(context)
concepts_definitions[concept] = concept.bnf concepts_definitions[concept] = concept.bnf
# check if it's a valid BNF or whether it breaks the known rules # check if it's a valid BNF or whether it breaks the known rules
@@ -61,22 +61,26 @@ class SheerkaCreateNewConcept:
# save the new concept in sdp # save the new concept in sdp
try: try:
# TODO : needs to make these calls atomic (or at least one single call) # TODO : needs to make these calls atomic (or at least one single call)
# save the new concept
self.sheerka.sdp.add( self.sheerka.sdp.add(
context.event.get_digest(), context.event.get_digest(),
self.sheerka.CONCEPTS_ENTRY, self.sheerka.CONCEPTS_ENTRY,
concept, concept,
use_ref=True) use_ref=True)
# save it by id
self.sheerka.sdp.add( self.sheerka.sdp.add(
context.event.get_digest(), context.event.get_digest(),
self.sheerka.CONCEPTS_BY_ID_ENTRY, self.sheerka.CONCEPTS_BY_ID_ENTRY,
{concept.id: concept.get_digest()}, {concept.id: concept.get_digest()},
is_ref=True) is_ref=True)
# update the definition table
if concepts_definitions is not None: if concepts_definitions is not None:
self.sheerka.sdp.set( self.sheerka.sdp.set(
context.event.get_digest(), context.event.get_digest(),
self.sheerka.CONCEPTS_DEFINITIONS_ENTRY, self.sheerka.CONCEPTS_DEFINITIONS_ENTRY,
concepts_definitions, concept_lexer_parser.encode_grammar(init_ret_value.body),
use_ref=True) use_ref=True)
self.sheerka.concepts_definitions_cache = None # invalidate cache
except SheerkaDataProviderDuplicateKeyError as error: except SheerkaDataProviderDuplicateKeyError as error:
context.log_error(logger, "Failed to create a new concept.", who=self.logger_name) context.log_error(logger, "Failed to create a new concept.", who=self.logger_name)
return self.sheerka.ret( return self.sheerka.ret(
@@ -94,6 +98,3 @@ class SheerkaCreateNewConcept:
# process the return in needed # process the return in needed
ret = self.sheerka.ret(self.logger_name, True, self.sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept)) ret = self.sheerka.ret(self.logger_name, True, self.sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=concept))
return ret return ret
+14
View File
@@ -1,5 +1,14 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept from core.concept import Concept
from sdp.sheerkaDataProvider import SheerkaDataProvider
import pprint
import os
def get_pp():
rows, columns = os.popen('stty size', 'r').read().split()
pp = pprint.PrettyPrinter(width=columns, compact=True)
return pp
class SheerkaDump: class SheerkaDump:
@@ -71,3 +80,8 @@ class SheerkaDump:
break break
page_count += 1 page_count += 1
def dump_state(self):
snapshot = self.sheerka.sdp.get_snapshot(SheerkaDataProvider.HeadFile)
state = self.sheerka.sdp.load_state(snapshot)
self.sheerka.log.info(get_pp().pformat(state.data))
@@ -147,9 +147,6 @@ class SheerkaEvaluateConcept:
if concept.metadata.is_evaluated: if concept.metadata.is_evaluated:
return concept return concept
# WHERE condition should already be validated by the parser.
# It's a mandatory condition for the concept before it can be recognized
# #
# TODO : Validate the PRE condition # TODO : Validate the PRE condition
# #
@@ -157,8 +154,8 @@ class SheerkaEvaluateConcept:
self.initialize_concept_asts(context, concept, logger) self.initialize_concept_asts(context, concept, logger)
# to make sure of the order, it don't use ConceptParts.get_parts() # to make sure of the order, it don't use ConceptParts.get_parts()
# props must be evaluated first # props must be evaluated first, body must be evaluated before where
all_metadata_to_eval = ["props", "where", "pre", "post", "body"] all_metadata_to_eval = ["pre", "post", "props", "body", "where"]
for metadata_to_eval in all_metadata_to_eval: for metadata_to_eval in all_metadata_to_eval:
if metadata_to_eval == "props": if metadata_to_eval == "props":
@@ -186,6 +183,12 @@ class SheerkaEvaluateConcept:
else: else:
concept.values[part_key] = resolved concept.values[part_key] = resolved
# validate where clause
if concept.metadata.where is not None:
where_value = concept.values[ConceptParts.WHERE]
if not (where_value is None or self.sheerka.value(where_value) is True):
return self.sheerka.new(BuiltinConcepts.WHERE_CLAUSE_FAILED, body=concept)
# #
# TODO : Validate the POST condition # TODO : Validate the POST condition
# #
@@ -19,7 +19,7 @@ class History:
return msg return msg
def __repr__(self): def __repr__(self):
return f"event={self.event!r}, status={self.status}, result={self.result}" return f"History(event={self.event!r}, status={self.status}, result={self.result})"
def __eq__(self, other): def __eq__(self, other):
if id(self) == id(other): if id(self) == id(other):
@@ -38,6 +38,21 @@ class SheerkaSetsManager:
context.log_error(logger, "Failed to add to set.", who=self.logger_name) context.log_error(logger, "Failed to add to set.", who=self.logger_name)
return self.sheerka.ret(self.logger_name, False, ErrorConcept(error), error.args[0]) return self.sheerka.ret(self.logger_name, False, ErrorConcept(error), error.args[0])
def add_concepts_to_set(self, context, concepts, concept_set, logger=None):
"""Adding multiple concepts at the same time"""
logger = logger or self.sheerka.log
context.log(logger, f"Adding concepts {concepts} to set {concept_set}", who=self.logger_name)
previous = self.sheerka.sdp.get_safe(GROUP_PREFIX + concept_set.id)
new_ids = [c.id for c in concepts] if previous is None else previous + [c.id for c in concepts]
try:
self.sheerka.sdp.set(context.event.get_digest(), GROUP_PREFIX + concept_set.id, new_ids)
return self.sheerka.ret(self.logger_name, True, self.sheerka.new(BuiltinConcepts.SUCCESS))
except Exception as error:
context.log_error(logger, "Failed to add to set.", who=self.logger_name)
return self.sheerka.ret(self.logger_name, False, ErrorConcept(error), error.args[0])
def get_set_elements(self, concept): def get_set_elements(self, concept):
""" """
Concept is supposed to be a set Concept is supposed to be a set
+26 -9
View File
@@ -22,6 +22,7 @@ import logging
# BuiltinConcepts.AFTER_EVALUATION] # BuiltinConcepts.AFTER_EVALUATION]
CONCEPT_LEXER_PARSER_CLASS = "parsers.ConceptLexerParser.ConceptLexerParser" CONCEPT_LEXER_PARSER_CLASS = "parsers.ConceptLexerParser.ConceptLexerParser"
BNF_PARSER_CLASS = "parsers.BnfParser.BnfParser"
CONCEPTS_FILE = "_concepts.txt" CONCEPTS_FILE = "_concepts.txt"
@@ -52,7 +53,7 @@ class Sheerka(Concept):
# cache for concept definitions, # cache for concept definitions,
# Primarily used for unit test that does not have access to sdp # Primarily used for unit test that does not have access to sdp
self.concepts_definition_cache = {} self.concepts_definitions_cache = {}
# #
# cache for concepts grammars # cache for concepts grammars
@@ -187,7 +188,8 @@ class Sheerka(Concept):
def initialize_concepts_definitions(self, execution_context): def initialize_concepts_definitions(self, execution_context):
self.init_log.debug("Initializing concepts definitions") self.init_log.debug("Initializing concepts definitions")
definitions = self.sdp.get_safe(self.CONCEPTS_DEFINITIONS_ENTRY, load_origin=False) # definitions = self.sdp.get_safe(self.CONCEPTS_DEFINITIONS_ENTRY, load_origin=False)
definitions = self.get_concepts_definitions(execution_context)
if definitions is None: if definitions is None:
self.init_log.debug("No BNF defined") self.init_log.debug("No BNF defined")
@@ -389,14 +391,26 @@ class Sheerka(Concept):
return result or self._get_unknown(('id', concept_id)) return result or self._get_unknown(('id', concept_id))
def get_concept_definition(self): def get_concepts_definitions(self, context):
if self.concepts_definition_cache: if self.concepts_definitions_cache:
return self.concepts_definition_cache return self.concepts_definitions_cache
self.concepts_definition_cache = self.sdp.get_safe( encoded = self.sdp.get_safe(
self.CONCEPTS_DEFINITIONS_ENTRY, self.CONCEPTS_DEFINITIONS_ENTRY,
load_origin=False) or {} load_origin=False) or {}
return self.concepts_definition_cache
self.concepts_definitions_cache = {}
bnf_parser = self.parsers[BNF_PARSER_CLASS]()
for k, v in encoded.items():
key, id_ = core.utils.unstr_concept(k)
concept = self.new((key, id_))
rule_result = bnf_parser.parse(context, v)
if rule_result.status:
self.concepts_definitions_cache[concept] = rule_result.value.value
else:
self.log.error(f"Failed to load bnf rule for concept {key}")
return self.concepts_definitions_cache
def new(self, concept_key, **kwargs): def new(self, concept_key, **kwargs):
""" """
@@ -411,7 +425,7 @@ class Sheerka(Concept):
else: else:
concept_id = None concept_id = None
template = self.get(concept_key, concept_id) template = self.get_by_id(concept_id) if not concept_key else self.get(concept_key, concept_id)
# manage concept not found # manage concept not found
if self.isinstance(template, BuiltinConcepts.UNKNOWN_CONCEPT) and \ if self.isinstance(template, BuiltinConcepts.UNKNOWN_CONCEPT) and \
@@ -579,7 +593,10 @@ class Sheerka(Concept):
self.during_restore = True self.during_restore = True
with open(CONCEPTS_FILE, "r") as f: with open(CONCEPTS_FILE, "r") as f:
for line in f.readlines(): for line in f.readlines():
self.log.info(line.strip()) line = line.strip()
if line == "" or line.startswith("#"):
continue
self.log.info(line)
self.evaluate_user_input(line) self.evaluate_user_input(line)
self.during_restore = False self.during_restore = False
except IOError: except IOError:
+27 -17
View File
@@ -266,10 +266,10 @@ class Tokenizer:
self.column = 1 self.column = 1
self.line += 1 self.line += 1
elif c == "c" and self.i + 1 < self.text_len and self.text[self.i + 1] == ":": elif c == "c" and self.i + 1 < self.text_len and self.text[self.i + 1] == ":":
concept_name = self.eat_concept_name(self.i + 2, self.line, self.column) name, id, length = self.eat_concept(self.i + 2, self.line, self.column + 2)
yield Token(TokenKind.CONCEPT, concept_name, self.i, self.line, self.column) yield Token(TokenKind.CONCEPT, (name, id), self.i, self.line, self.column)
self.i += len(concept_name) + 3 self.i += length + 2
self.column += len(concept_name) + 3 self.column += length + 2
elif c.isalpha() or c == "_": elif c.isalpha() or c == "_":
identifier = self.eat_identifier(self.i) identifier = self.eat_identifier(self.i)
token_type = TokenKind.KEYWORD if identifier in self.KEYWORDS else TokenKind.IDENTIFIER token_type = TokenKind.KEYWORD if identifier in self.KEYWORDS else TokenKind.IDENTIFIER
@@ -297,31 +297,41 @@ class Tokenizer:
yield Token(TokenKind.EOF, "", self.i, self.line, self.column) yield Token(TokenKind.EOF, "", self.i, self.line, self.column)
def eat_concept_name(self, start, line, column): def eat_concept(self, start, line, column):
result = "" key, id, buffer = None, None, ""
i = start i = start
end_colon_found = False processing_key = True
while i < self.text_len: while i < self.text_len:
c = self.text[i]
c = self.text[i]
if c == "\n": if c == "\n":
raise LexerError(f"New line is forbidden in concept name", result, i, line, column + 2 + len(result)) raise LexerError(f"New line in concept name", self.text[start:i], i, line, column + i - start)
if c == ":": if c == ":":
end_colon_found = True if processing_key:
key = buffer if buffer else None
else:
id = buffer if buffer else None
i += 1 # eat the colon
break break
result += c if c == "|":
key = buffer if buffer else None
buffer = ""
processing_key = False
i += 1
continue
buffer += c
i += 1 i += 1
else:
raise LexerError(f"Missing ending colon", self.text[start:i], i, line, column + i - start)
if not end_colon_found: if (key, id) == (None, None):
raise LexerError(f"Missing ending colon", result, i, line, column + 2 + len(result)) raise LexerError(f"Concept identifiers not found", "", start, line, column)
if result == "": return key, id, i - start
raise LexerError(f"Concept name not found", result, start, line, column + 2 + len(result))
return result
def eat_whitespace(self, start): def eat_whitespace(self, start):
result = self.text[start] result = self.text[start]
+108 -37
View File
@@ -1,6 +1,7 @@
import importlib import importlib
import inspect import inspect
import pkgutil import pkgutil
import re
from core.tokenizer import TokenKind from core.tokenizer import TokenKind
@@ -239,43 +240,6 @@ def pp(items):
return " \n" + " \n".join(str(item) for item in items) return " \n" + " \n".join(str(item) for item in items)
def decode_concept(concept_repr):
"""
if concept_repr is like :c:key:id:
return the key and the id
:param concept_repr:
:return:
"""
if not (concept_repr and isinstance(concept_repr, str) and concept_repr.startswith(":c:")):
return None, None
i = 3
length = len(concept_repr)
key = ""
while i < length:
if concept_repr[i] == ":":
break
key += concept_repr[i]
i += 1
else:
return None, None
i += 1
if i >= length:
return key, None
id = ""
while i < length:
if concept_repr[i] == ":":
break
id += concept_repr[i]
i += 1
else:
return None, None
return key, id
def decode_enum(enum_repr: str): def decode_enum(enum_repr: str):
""" """
Tries to transform ClassName.Name into an enum Tries to transform ClassName.Name into an enum
@@ -300,3 +264,110 @@ def decode_enum(enum_repr: str):
except TypeError: except TypeError:
return None return None
def str_concept(t):
"""
The key,id identifiers of a concept are stored in a tuple
we want to return the key and the id, separated by a pipe
None value must be replaced by an empty string
>>> assert str_concept(("key", "id")) == "c:key|id:"
>>> assert str_concept((None, "id")) == "c:|id:"
>>> assert str_concept(("key", None)) == "c:key:"
>>> assert str_concept((None, None)) == ""
:param t:
:return:
"""
if isinstance(t, tuple):
key, id_ = t[0], t[1]
else:
key, id_ = t.key, t.id
if key is None and id_ is None:
return ""
result = 'c:' if key is None else "c:" + key
if id_:
result += "|" + id_
return result + ":"
def unstr_concept(concept_repr):
"""
if concept_repr is like :c:key:id:
return the key and the id
:param concept_repr:
:return:
"""
if not (concept_repr and isinstance(concept_repr, str) and concept_repr.startswith("c:")):
return None, None
i = 2
length = len(concept_repr)
key = ""
while i < length:
c = concept_repr[i]
if c in (":", "|"):
break
key += c
i += 1
else:
return None, None
if c == ":":
return key if key != "" else None, None
i += 1
id = ""
while i < length:
c = concept_repr[i]
if c == ":":
break
id += c
i += 1
else:
return None, None
return key if key != "" else None, id if id != "" else None
def encode_concept(t, use_concept=False):
"""
Given a tuple of concept id, concept id
Create a valid Python identifier that can be parsed back
>>> assert encode_concept(("key", "id")) == "__C__KEY_key__ID_id__C__"
>>> assert encode_concept((None, "id")) == "__C__KEY_00None00__ID_id__C__"
>>> assert encode_concept(("key", None)) == "__C__KEY_key__ID_00None00__C__"
>>> assert encode_concept(("key", "id"), True) == "__C__USE_CONCEPT__KEY_key__ID_id__C__"
:param t:
:param use_concept:
:return:
"""
key, id_ = (t[0], t[1]) if isinstance(t, tuple) else (t.key, t.id)
prefix = "__C__USE_CONCEPT" if use_concept else "__C"
sanitized_key = "".join(c if c.isalnum() else "0" for c in key) if key else "00None00"
return prefix + f"__KEY_{sanitized_key}__ID_{id_ or '00None00'}__C__"
decode_regex = re.compile(r"__KEY_(\w+)__ID_(\w+)__C__")
def decode_concept(text):
"""
Decode what was encoded by encode_concept_key_id
:param text:
:return:
"""
use_concept = text.startswith("__C__USE_CONCEPT")
m = decode_regex.search(text)
lookup = {"00None00": None}
if m:
key = lookup.get(m.group(1), m.group(1))
id_ = lookup.get(m.group(2), m.group(2))
return key, id_, use_concept
return None, None, None
+65 -51
View File
@@ -8,6 +8,7 @@ from evaluators.BaseEvaluator import OneReturnValueEvaluator
from parsers.PythonParser import PythonNode from parsers.PythonParser import PythonNode
import ast import ast
import core.ast.nodes import core.ast.nodes
import core.utils
class PythonEvaluator(OneReturnValueEvaluator): class PythonEvaluator(OneReturnValueEvaluator):
@@ -40,9 +41,11 @@ class PythonEvaluator(OneReturnValueEvaluator):
not_for_me = context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=node) not_for_me = context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=node)
return sheerka.ret(self.name, False, not_for_me, parents=[return_value]) return sheerka.ret(self.name, False, not_for_me, parents=[return_value])
# get locals
my_locals = self.get_locals(context, node) my_locals = self.get_locals(context, node)
context.log(self.verbose_log, f"locals={my_locals}", self.name) context.log(self.verbose_log, f"locals={my_locals}", self.name)
# eval
if isinstance(node.ast_, ast.Expression): if isinstance(node.ast_, ast.Expression):
context.log(self.verbose_log, "Evaluating using 'eval'.", self.name) context.log(self.verbose_log, "Evaluating using 'eval'.", self.name)
compiled = compile(node.ast_, "<string>", "eval") compiled = compile(node.ast_, "<string>", "eval")
@@ -53,6 +56,7 @@ class PythonEvaluator(OneReturnValueEvaluator):
context.log(self.verbose_log, f"{evaluated=}", self.name) context.log(self.verbose_log, f"{evaluated=}", self.name)
return sheerka.ret(self.name, True, evaluated, parents=[return_value]) return sheerka.ret(self.name, True, evaluated, parents=[return_value])
except Exception as error: except Exception as error:
context.log_error(self.verbose_log, error, self.name) context.log_error(self.verbose_log, error, self.name)
error = sheerka.new(BuiltinConcepts.ERROR, body=error) error = sheerka.new(BuiltinConcepts.ERROR, body=error)
@@ -65,16 +69,19 @@ class PythonEvaluator(OneReturnValueEvaluator):
"concepts": context.sheerka.dump_handler.dump_concepts, "concepts": context.sheerka.dump_handler.dump_concepts,
"definitions": context.sheerka.dump_handler.dump_definitions, "definitions": context.sheerka.dump_handler.dump_definitions,
"history": context.sheerka.dump_handler.dump_history, "history": context.sheerka.dump_handler.dump_history,
"state": context.sheerka.dump_handler.dump_state,
} }
if context.obj: if context.obj:
context.log(self.verbose_log, context.log(self.verbose_log,
f"Concept '{context.obj}' is in context. Adding its properties to locals if any.", self.name) f"Concept '{context.obj}' is in context. Adding it and its properties to locals.", self.name)
for prop_name, prop_value in context.obj.props.items(): for prop_name, prop_value in context.obj.props.items():
if not isinstance(prop_value.value, Concept): if isinstance(prop_value.value, Concept):
my_locals[prop_name] = prop_value.value
else:
my_locals[prop_name] = context.sheerka.value(prop_value.value) my_locals[prop_name] = context.sheerka.value(prop_value.value)
else:
my_locals[prop_name] = prop_value.value
my_locals["self"] = context.obj.body
node_concept = core.ast.nodes.python_to_concept(node.ast_) node_concept = core.ast.nodes.python_to_concept(node.ast_)
unreferenced_names_visitor = UnreferencedNamesVisitor(context.sheerka) unreferenced_names_visitor = UnreferencedNamesVisitor(context.sheerka)
@@ -89,16 +96,17 @@ class PythonEvaluator(OneReturnValueEvaluator):
return_concept = False return_concept = False
else: else:
concept_key, concept_id, return_concept = self.resolve_name(context, name) c_key, c_id, return_concept = self.resolve_name(name)
if concept_key in my_locals: if c_key in my_locals:
context.log(self.verbose_log, f"Using value from property.", self.name) context.log(self.verbose_log, f"Using value from property.", self.name)
continue continue
context.log(self.verbose_log, f"Instantiating new concept.", self.name) context.log(self.verbose_log, f"Instantiating new concept with {c_key=}, {c_id=}.", self.name)
concept = context.sheerka.new((concept_key, concept_id)) new = context.sheerka.new
concept = new((None, c_id)) if c_id else new(c_key)
if context.sheerka.isinstance(concept, BuiltinConcepts.UNKNOWN_CONCEPT): if context.sheerka.isinstance(concept, BuiltinConcepts.UNKNOWN_CONCEPT):
context.log(self.verbose_log, f"'{concept_key}' is not a concept. Skipping.", self.name) context.log(self.verbose_log, f"({c_key=}, {c_id=}) is not a concept. Skipping.", self.name)
continue continue
context.log(self.verbose_log, f"Evaluating '{concept}'", self.name) context.log(self.verbose_log, f"Evaluating '{concept}'", self.name)
@@ -110,62 +118,68 @@ class PythonEvaluator(OneReturnValueEvaluator):
if evaluated.key == concept.key: if evaluated.key == concept.key:
my_locals[name] = evaluated if return_concept else context.sheerka.value(evaluated) my_locals[name] = evaluated if return_concept else context.sheerka.value(evaluated)
if self.locals: if self.locals: # when exta values are given. Add them
my_locals.update(self.locals) my_locals.update(self.locals)
return my_locals return my_locals
def resolve_name(self, context, to_resolve): @staticmethod
def resolve_name(to_resolve):
""" """
Try to match Try to match
__C__concept_key__C__ __C__concept_key__C__
or or
__C__concept_key__concept_id__C__ __C__concept_key__concept_id__C__
:param context:
:param to_resolve: :param to_resolve:
:return: :return:
""" """
if not to_resolve.startswith("__C__"): key, id_, use_concept = core.utils.decode_concept(to_resolve)
return to_resolve, None, False if key or id_:
return key, id_, use_concept
context.log(self.verbose_log, f"Resolving name '{to_resolve}'.", self.name)
if len(to_resolve) >= 18 and to_resolve[:18] == "__C__USE_CONCEPT__":
use_concept = True
index = 18
else: else:
use_concept = False return to_resolve, None, False
index = 5 #
# if not to_resolve.startswith("__C__"):
try: # return to_resolve, None, False
next_index = to_resolve.index("__", index) #
if next_index == index: # context.log(self.verbose_log, f"Resolving name '{to_resolve}'.", self.name)
context.log(self.verbose_log, f"Error: no key between '__'.", self.name) #
return None # if len(to_resolve) >= 18 and to_resolve[:18] == "__C__USE_CONCEPT__":
concept_key = to_resolve[index: next_index] # use_concept = True
except ValueError: # index = 18
context.log(self.verbose_log, f"Error: Missing trailing '__'.", self.name) # else:
return None # use_concept = False
# index = 5
if next_index == len(to_resolve) - 5: #
context.log(self.verbose_log, f"Recognized concept '{concept_key}'", self.name) # try:
return concept_key, None, use_concept # next_index = to_resolve.index("__", index)
# if next_index == index:
index = next_index + 2 # context.log(self.verbose_log, f"Error: no key between '__'.", self.name)
try: # return None
next_index = to_resolve.index("__", index) # concept_key = to_resolve[index: next_index]
if next_index == index: # except ValueError:
context.log(self.verbose_log, f"Error: no id between '__'.", self.name) # context.log(self.verbose_log, f"Error: Missing trailing '__'.", self.name)
return None # return None
#
concept_id = to_resolve[index: next_index] # if next_index == len(to_resolve) - 5:
except ValueError: # context.log(self.verbose_log, f"Recognized concept '{concept_key}'", self.name)
context.log(self.verbose_log, f"Recognized concept '{concept_key}'.", self.name) # return concept_key, None, use_concept
return concept_key, None, use_concept #
# index = next_index + 2
context.log(self.verbose_log, f"Recognized concept '{concept_key}' (id='{concept_id}').", self.name) # try:
return concept_key, concept_id, use_concept # next_index = to_resolve.index("__", index)
# if next_index == index:
# context.log(self.verbose_log, f"Error: no id between '__'.", self.name)
# return None
#
# concept_id = to_resolve[index: next_index]
# except ValueError:
# context.log(self.verbose_log, f"Recognized concept '{concept_key}'.", self.name)
# return concept_key, None, use_concept
#
# context.log(self.verbose_log, f"Recognized concept '{concept_key}' (id='{concept_id}').", self.name)
# return concept_key, concept_id, use_concept
@staticmethod @staticmethod
def expr_to_expression(expr): def expr_to_expression(expr):
+29 -2
View File
@@ -2,8 +2,9 @@ from dataclasses import dataclass
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept from core.concept import Concept
from core.tokenizer import TokenKind, Keywords from core.tokenizer import TokenKind, Keywords, Token
from core.sheerka_logger import get_logger from core.sheerka_logger import get_logger
import core.utils
import logging import logging
@@ -35,8 +36,34 @@ class ErrorNode(Node):
@dataclass() @dataclass()
class UnexpectedTokenErrorNode(ErrorNode): class UnexpectedTokenErrorNode(ErrorNode):
message: str message: str
token: Token
expected_tokens: list expected_tokens: list
def __eq__(self, other):
if id(other) == id(self):
return True
if not isinstance(other, UnexpectedTokenErrorNode):
return False
if self.message != other.message:
return False
if self.token.type != other.token.type or self.token.value != other.token.value:
return False
if len(self.expected_tokens) != len(other.expected_tokens):
return False
for i, t in enumerate(self.expected_tokens):
if t != other.expected_tokens[i]:
return False
return True
def __hash__(self):
return hash((self.message, self.token, self.expected_tokens))
class BaseParser: class BaseParser:
PREFIX = "parsers." PREFIX = "parsers."
@@ -108,7 +135,7 @@ class BaseParser:
switcher = { switcher = {
TokenKind.KEYWORD: lambda t: Keywords(t.value).value, TokenKind.KEYWORD: lambda t: Keywords(t.value).value,
TokenKind.CONCEPT: lambda t: "c:" + t.value + ":", TokenKind.CONCEPT: lambda t: core.utils.str_concept(t.value),
} }
if custom_switcher: if custom_switcher:
+39 -24
View File
@@ -5,7 +5,8 @@ from core.builtin_concepts import BuiltinConcepts
from core.sheerka.Sheerka import ExecutionContext from core.sheerka.Sheerka import ExecutionContext
from core.tokenizer import Tokenizer, Token, TokenKind, LexerError from core.tokenizer import Tokenizer, Token, TokenKind, LexerError
from parsers.BaseParser import BaseParser, ErrorNode, UnexpectedTokenErrorNode from parsers.BaseParser import BaseParser, ErrorNode, UnexpectedTokenErrorNode
from parsers.ConceptLexerParser import OrderedChoice, Sequence, Optional, ZeroOrMore, OneOrMore, ConceptExpression, StrMatch from parsers.ConceptLexerParser import OrderedChoice, Sequence, Optional, ZeroOrMore, OneOrMore, ConceptExpression, \
StrMatch, ConceptGroupExpression
@dataclass() @dataclass()
@@ -119,11 +120,11 @@ class BnfParser(BaseParser):
tree = None tree = None
try: try:
self.reset_parser(context, text) self.reset_parser(context, text)
tree = self.parser_outer_rule_name() tree = self.parse_choice()
token = self.get_token() token = self.get_token()
if token and token.type != TokenKind.EOF: if token and token.type != TokenKind.EOF:
self.add_error(UnexpectedTokenErrorNode(f"Unexpected token '{token}'", [])) self.add_error(UnexpectedTokenErrorNode(f"Unexpected token '{token}'", token, []))
except LexerError as e: except LexerError as e:
self.add_error(e, False) self.add_error(e, False)
@@ -136,10 +137,11 @@ class BnfParser(BaseParser):
return ret return ret
def parser_outer_rule_name(self):
return self.parser_rule_name(self.parse_choice)
def parse_choice(self): def parse_choice(self):
"""
a | b | c
:return:
"""
sequence = self.parse_sequence() sequence = self.parse_sequence()
self.eat_white_space() self.eat_white_space()
@@ -159,9 +161,13 @@ class BnfParser(BaseParser):
sequence = self.parse_sequence() sequence = self.parse_sequence()
elements.append(sequence) elements.append(sequence)
return OrderedChoice(*elements) return self.eat_rule_name_if_needed(OrderedChoice(*elements))
def parse_sequence(self): def parse_sequence(self):
"""
a b c
:return:
"""
expr_and_modifier = self.parse_modifier() expr_and_modifier = self.parse_modifier()
token = self.get_token() token = self.get_token()
if token is None or \ if token is None or \
@@ -185,30 +191,31 @@ class BnfParser(BaseParser):
sequence = self.parse_modifier() sequence = self.parse_modifier()
elements.append(sequence) elements.append(sequence)
return Sequence(*elements) return self.eat_rule_name_if_needed(Sequence(*elements))
def parse_modifier(self): def parse_modifier(self):
expression = self.parser_inner_rule_name() """
a? | a* | a+
:return:
"""
expression = self.parse_expression()
token = self.get_token() token = self.get_token()
if token.type == TokenKind.QMARK: if token.type == TokenKind.QMARK:
self.next_token() self.next_token()
return Optional(expression) return self.eat_rule_name_if_needed(Optional(expression))
if token.type == TokenKind.STAR: if token.type == TokenKind.STAR:
self.next_token() self.next_token()
return ZeroOrMore(expression) return self.eat_rule_name_if_needed(ZeroOrMore(expression))
if token.type == TokenKind.PLUS: if token.type == TokenKind.PLUS:
self.next_token() self.next_token()
return OneOrMore(expression) return self.eat_rule_name_if_needed(OneOrMore(expression))
return expression return expression
def parser_inner_rule_name(self):
return self.parser_rule_name(self.parse_expression)
def parse_expression(self): def parse_expression(self):
token = self.get_token() token = self.get_token()
if token.type == TokenKind.EOF: if token.type == TokenKind.EOF:
@@ -216,15 +223,21 @@ class BnfParser(BaseParser):
if token.type == TokenKind.LPAR: if token.type == TokenKind.LPAR:
self.nb_open_par += 1 self.nb_open_par += 1
self.next_token() self.next_token()
expression = self.parse_choice() expr = self.parse_choice()
token = self.get_token() token = self.get_token()
if token.type == TokenKind.RPAR: if token.type == TokenKind.RPAR:
self.nb_open_par -= 1 self.nb_open_par -= 1
self.next_token() self.next_token()
return expression return self.eat_rule_name_if_needed(expr)
else: else:
self.add_error(UnexpectedTokenErrorNode(f"Unexpected token '{token}'", [TokenKind.RPAR])) self.add_error(UnexpectedTokenErrorNode(f"Unexpected token '{token}'", token, [TokenKind.RPAR]))
return expression return expr
if token.type == TokenKind.CONCEPT:
self.next_token()
concept = self.sheerka.new((token.value[0], token.value[1]))
expr = ConceptGroupExpression(concept) if self.sheerka.isaset(concept) else ConceptExpression(concept)
return self.eat_rule_name_if_needed(expr)
if token.type == TokenKind.IDENTIFIER: if token.type == TokenKind.IDENTIFIER:
self.next_token() self.next_token()
@@ -247,14 +260,15 @@ class BnfParser(BaseParser):
body=("key", concept_name))) body=("key", concept_name)))
return None return None
else: else:
return concept expr = ConceptGroupExpression(concept) if self.sheerka.isaset(concept) else ConceptExpression(concept)
expr.rule_name = concept.name
return expr
ret = StrMatch(core.utils.strip_quotes(token.value)) ret = StrMatch(core.utils.strip_quotes(token.value))
self.next_token() self.next_token()
return ret return self.eat_rule_name_if_needed(ret)
def parser_rule_name(self, next_to_parse): def eat_rule_name_if_needed(self, expression):
expression = next_to_parse()
token = self.get_token() token = self.get_token()
if token is None or token.type != TokenKind.EQUALS: if token is None or token.type != TokenKind.EQUALS:
return expression return expression
@@ -263,7 +277,8 @@ class BnfParser(BaseParser):
token = self.get_token() token = self.get_token()
if token is None or token.type != TokenKind.IDENTIFIER: if token is None or token.type != TokenKind.IDENTIFIER:
return self.add_error(UnexpectedTokenErrorNode(f"Unexpected token '{token}'", [TokenKind.IDENTIFIER])) return self.add_error(
UnexpectedTokenErrorNode(f"Unexpected token '{token}'", token, [TokenKind.IDENTIFIER]))
expression.rule_name = token.value expression.rule_name = token.value
self.next_token() self.next_token()
+58 -10
View File
@@ -243,6 +243,9 @@ class ParsingExpression:
def parse(self, parser): def parse(self, parser):
return self._parse(parser) return self._parse(parser)
def add_rule_name_if_needed(self, text):
return text + "=" + self.rule_name if self.rule_name else text
class ConceptExpression(ParsingExpression): class ConceptExpression(ParsingExpression):
""" """
@@ -257,7 +260,7 @@ class ConceptExpression(ParsingExpression):
self.concept = concept self.concept = concept
def __repr__(self): def __repr__(self):
return f"{self.concept}" return self.add_rule_name_if_needed(f"{self.concept}")
def __eq__(self, other): def __eq__(self, other):
if not super().__eq__(other): if not super().__eq__(other):
@@ -352,7 +355,7 @@ class Sequence(ParsingExpression):
def __repr__(self): def __repr__(self):
to_str = ", ".join(repr(n) for n in self.elements) to_str = ", ".join(repr(n) for n in self.elements)
return f"({to_str})" return self.add_rule_name_if_needed(f"({to_str})")
class OrderedChoice(ParsingExpression): class OrderedChoice(ParsingExpression):
@@ -375,7 +378,7 @@ class OrderedChoice(ParsingExpression):
def __repr__(self): def __repr__(self):
to_str = "| ".join(repr(n) for n in self.elements) to_str = "| ".join(repr(n) for n in self.elements)
return f"({to_str})" return self.add_rule_name_if_needed(f"({to_str})")
class Optional(ParsingExpression): class Optional(ParsingExpression):
@@ -413,7 +416,7 @@ class Optional(ParsingExpression):
return f"{self.elements[0]}?" return f"{self.elements[0]}?"
else: else:
to_str = ", ".join(repr(n) for n in self.elements) to_str = ", ".join(repr(n) for n in self.elements)
return f"({to_str})?" return self.add_rule_name_if_needed(f"({to_str})?")
class Repetition(ParsingExpression): class Repetition(ParsingExpression):
@@ -467,7 +470,7 @@ class ZeroOrMore(Repetition):
def __repr__(self): def __repr__(self):
to_str = ", ".join(repr(n) for n in self.elements) to_str = ", ".join(repr(n) for n in self.elements)
return f"({to_str})*" return self.add_rule_name_if_needed(f"({to_str})*")
class OneOrMore(Repetition): class OneOrMore(Repetition):
@@ -507,7 +510,7 @@ class OneOrMore(Repetition):
def __repr__(self): def __repr__(self):
to_str = ", ".join(repr(n) for n in self.elements) to_str = ", ".join(repr(n) for n in self.elements)
return f"({to_str})+" return self.add_rule_name_if_needed(f"({to_str})+")
class UnorderedGroup(Repetition): class UnorderedGroup(Repetition):
@@ -541,13 +544,13 @@ class StrMatch(Match):
Matches a literal Matches a literal
""" """
def __init__(self, to_match, rule_name="", root=False, ignore_case=True): def __init__(self, to_match, rule_name="", ignore_case=True):
super(Match, self).__init__(rule_name=rule_name, root=root) super(Match, self).__init__(rule_name=rule_name)
self.to_match = to_match self.to_match = to_match
self.ignore_case = ignore_case self.ignore_case = ignore_case
def __repr__(self): def __repr__(self):
return f"'{self.to_match}'" return self.add_rule_name_if_needed(f"'{self.to_match}'")
def __eq__(self, other): def __eq__(self, other):
if not super().__eq__(other): if not super().__eq__(other):
@@ -699,10 +702,14 @@ class ConceptLexerParser(BaseParser):
else: else:
ret = ConceptExpression(expression, rule_name=expression.name) ret = ConceptExpression(expression, rule_name=expression.name)
concepts_to_resolve.add(expression) concepts_to_resolve.add(expression)
elif isinstance(expression, ConceptExpression): elif isinstance(expression, ConceptExpression): # it includes ConceptGroupExpression
if expression.rule_name is None or expression.rule_name == "": if expression.rule_name is None or expression.rule_name == "":
expression.rule_name = expression.concept.name if isinstance(expression.concept, Concept) \ expression.rule_name = expression.concept.name if isinstance(expression.concept, Concept) \
else expression.concept else expression.concept
if isinstance(expression.concept, str):
concept = self.get_concept(expression.concept)
if self.sheerka.is_known(concept):
expression.concept = concept
concepts_to_resolve.add(expression.concept) concepts_to_resolve.add(expression.concept)
ret = expression ret = expression
elif isinstance(expression, str): elif isinstance(expression, str):
@@ -955,6 +962,47 @@ class ConceptLexerParser(BaseParser):
return concept return concept
def encode_grammar(self, grammar):
"""
Transform the grammar into something that can easily can be serialized
:param grammar:
:return:
"""
def _encode(expression):
if isinstance(expression, StrMatch):
res = f"'{expression.to_match}'"
elif isinstance(expression, ConceptExpression):
res = core.utils.str_concept(expression.concept)
elif isinstance(expression, Sequence):
res = "(" + " ".join(_encode(c) for c in expression.nodes) + ")"
elif isinstance(expression, OrderedChoice):
res = "(" + "|".join(_encode(c) for c in expression.nodes) + ")"
elif isinstance(expression, Optional):
res = _encode(expression.nodes[0]) + "?"
elif isinstance(expression, ZeroOrMore):
res = _encode(expression.nodes[0]) + "*"
elif isinstance(expression, OneOrMore):
res = _encode(expression.nodes[0]) + "+"
if expression.rule_name:
res += "=" + expression.rule_name
return res
result = {}
for k, v in grammar.items():
key = core.utils.str_concept(k)
value = _encode(v)
result[key] = value
return result
@staticmethod @staticmethod
def get_bests(results): def get_bests(results):
""" """
+4 -2
View File
@@ -1,9 +1,10 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.tokenizer import Tokenizer, LexerError, TokenKind from core.tokenizer import Tokenizer, LexerError, TokenKind
from parsers.BaseParser import BaseParser, Node, ErrorNode from parsers.BaseParser import BaseParser, Node, ErrorNode
from dataclasses import dataclass, field from dataclasses import dataclass
import ast import ast
import logging import logging
import core.utils
from parsers.ConceptLexerParser import ConceptNode from parsers.ConceptLexerParser import ConceptNode
@@ -71,7 +72,7 @@ class PythonParser(BaseParser):
tree = None tree = None
python_switcher = { python_switcher = {
TokenKind.CONCEPT: lambda t: f"__C__USE_CONCEPT__{t.value}__C__" TokenKind.CONCEPT: lambda t: core.utils.encode_concept(t.value, True)
} }
try: try:
@@ -136,6 +137,7 @@ class PythonGetNamesVisitor(ast.NodeVisitor):
def visit_Name(self, node): def visit_Name(self, node):
self.names.add(node.id) self.names.add(node.id)
class LexerNodeParserHelperForPython: class LexerNodeParserHelperForPython:
"""Helper class to parse mix of concepts and Python""" """Helper class to parse mix of concepts and Python"""
+18 -8
View File
@@ -223,7 +223,7 @@ class PickleSerializer(BaseSerializer):
class StateSerializer(PickleSerializer): class StateSerializer(PickleSerializer):
def __init__(self, ): def __init__(self):
PickleSerializer.__init__( PickleSerializer.__init__(
self, self,
lambda obj: core.utils.get_full_qualified_name(obj) == "sdp.sheerkaDataProvider.State", lambda obj: core.utils.get_full_qualified_name(obj) == "sdp.sheerkaDataProvider.State",
@@ -239,13 +239,23 @@ class ConceptSerializer(JsonSerializer):
return isinstance(obj, Concept) return isinstance(obj, Concept)
class DictionarySerializer(PickleSerializer): class DictionarySerializer(BaseSerializer):
def __init__(self, ): def __init__(self):
PickleSerializer.__init__( super().__init__("D", 1)
self,
lambda obj: isinstance(obj, dict), def matches(self, obj):
"D", return isinstance(obj, dict)
1)
def dump(self, stream, obj, context):
stream.write(json.dumps(obj, default=json_default_converter).encode("utf-8"))
stream.seek(0)
return stream
def load(self, stream, context):
json_stream = stream.read().decode("utf-8")
obj = json.loads(json_stream)
return obj
class ExecutionContextSerializer(BaseSerializer): class ExecutionContextSerializer(BaseSerializer):
+1 -1
View File
@@ -70,7 +70,7 @@ class SheerkaPickler:
elif utils.is_enum(k): elif utils.is_enum(k):
k_str = core.utils.get_full_qualified_name(k) + "." + k.name k_str = core.utils.get_full_qualified_name(k) + "." + k.name
elif isinstance(k, Concept): elif isinstance(k, Concept):
k_str = f":c:{k.key}:{k.id}:" k_str = core.utils.str_concept(k)
else: else:
k_str = k k_str = k
+1 -1
View File
@@ -90,7 +90,7 @@ class SheerkaUnpickler:
if key == "null": if key == "null":
return None return None
concept_key, concept_id = core.utils.decode_concept(key) concept_key, concept_id = core.utils.unstr_concept(key)
if concept_key is not None: if concept_key is not None:
return self.sheerka.new((concept_key, concept_id)) if concept_id else self.sheerka.new(concept_key) return self.sheerka.new((concept_key, concept_id)) if concept_id else self.sheerka.new(concept_key)
-15
View File
@@ -1,15 +0,0 @@
def concept one as 1
def concept two as 2
def concept three as 3
def concept four as 4
def concept five as 5
def concept one as 1
def concept two as 2
def concept three as 3
def concept four as 4
def concept five as 5
def concept one as 1
def concept two as 2
def concept three as 3
def concept four as 4
def concept five as 5
+10
View File
@@ -8,3 +8,13 @@ def concept two as 2
def concept three as 3 def concept three as 3
def concept four as 4 def concept four as 4
def concept five as 5 def concept five as 5
def concept one as 1
def concept two as 2
def concept three as 3
def concept four as 4
def concept five as 5
def concept one as 1
def concept two as 2
def concept three as 3
def concept four as 4
def concept five as 5
+46 -7
View File
@@ -18,7 +18,7 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
("True", True), ("True", True),
("1 > 2", False), ("1 > 2", False),
]) ])
def test_i_can_evaluate_a_concept_with_simple_body(self,body, expected): def test_i_can_evaluate_a_concept_with_simple_body(self, body, expected):
sheerka = self.get_sheerka() sheerka = self.get_sheerka()
concept = Concept("foo", body=body) concept = Concept("foo", body=body)
@@ -43,9 +43,9 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
("True", True), ("True", True),
("1 > 2", False), ("1 > 2", False),
]) ])
def test_i_can_evaluate_the_other_metadata(self,expr, expected): def test_i_can_evaluate_the_other_metadata(self, expr, expected):
""" """
I only test WHERE, it's the same for the others I only test PRE, it's the same for the others
:param expr: :param expr:
:param expected: :param expected:
:return: :return:
@@ -53,15 +53,15 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
sheerka = self.get_sheerka() sheerka = self.get_sheerka()
concept = Concept("foo", where=expr) concept = Concept("foo", pre=expr)
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept) evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
assert evaluated.key == concept.key assert evaluated.key == concept.key
assert evaluated.metadata.body is None assert evaluated.metadata.body is None
assert evaluated.metadata.pre is None assert evaluated.metadata.pre == expr
assert evaluated.metadata.post is None assert evaluated.metadata.post is None
assert evaluated.metadata.where == expr assert evaluated.metadata.where is None
assert evaluated.get_metadata_value(ConceptParts.WHERE) == expected assert evaluated.get_metadata_value(ConceptParts.PRE) == expected
assert evaluated.props == {} assert evaluated.props == {}
assert evaluated.metadata.is_evaluated assert evaluated.metadata.is_evaluated
assert len(evaluated.values) == 0 if expr is None else 1 assert len(evaluated.values) == 0 if expr is None else 1
@@ -330,3 +330,42 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
assert evaluated.key == concept.init_key().key assert evaluated.key == concept.init_key().key
@pytest.mark.parametrize("where_clause, expected", [
("True", True),
("False", False),
("self < 10", False),
("self < 11", True),
("a < 20", False),
("a > 19", True),
("a + self > 20", True),
])
def test_i_can_evaluate_simple_where(self, where_clause, expected):
sheerka = self.get_sheerka()
concept = Concept("foo", body="10", where=where_clause).def_prop("a", "20")
sheerka.add_in_cache(concept)
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
if expected:
assert evaluated.key == concept.key
else:
assert sheerka.isinstance(evaluated, BuiltinConcepts.WHERE_CLAUSE_FAILED)
assert evaluated.body == concept
def test_i_can_evaluate_where_when_using_other_concept(self):
sheerka = self.get_sheerka()
foo_true = Concept("foo_true", body="True").init_key()
foo_false = Concept("foo_false", body="False").init_key()
sheerka.add_in_cache(foo_false)
sheerka.add_in_cache(foo_true)
concept = Concept("foo", where="foo_true").init_key()
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
assert evaluated.key == concept.key
concept = Concept("foo", where="foo_false")
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
assert sheerka.isinstance(evaluated, BuiltinConcepts.WHERE_CLAUSE_FAILED)
assert evaluated.body == concept
+44 -49
View File
@@ -1,8 +1,3 @@
import os
import shutil
from os import path
import pytest
from core.builtin_concepts import ConceptAlreadyInSet, BuiltinConcepts from core.builtin_concepts import ConceptAlreadyInSet, BuiltinConcepts
from core.concept import Concept from core.concept import Concept
@@ -11,42 +6,36 @@ from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka
class TestSheerkaSetsManager(TestUsingFileBasedSheerka): class TestSheerkaSetsManager(TestUsingFileBasedSheerka):
def init(self, use_dict, *concepts):
sheerka = self.get_sheerka(use_dict, True)
def test_i_can_add_concept_to_set(self): for c in concepts:
sheerka = self.get_sheerka(False, False) sheerka.set_id_if_needed(c, False)
sheerka.add_in_cache(c)
foo = Concept("foo")
sheerka.set_id_if_needed(foo, False)
all_foos = Concept("all_foos")
sheerka.set_id_if_needed(all_foos, False)
context = self.get_context(sheerka) context = self.get_context(sheerka)
return sheerka, context
def test_i_can_add_concept_to_set(self):
foo = Concept("foo")
all_foos = Concept("all_foos")
sheerka, context = self.init(False, foo, all_foos)
res = sheerka.add_concept_to_set(context, foo, all_foos) res = sheerka.add_concept_to_set(context, foo, all_foos)
assert res.status assert res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
all_entries = self.get_sheerka(False, False).sdp.get("All_" + all_foos.id, None, False) all_entries = self.get_sheerka(False, True).sdp.get("All_" + all_foos.id, None, False)
assert len(all_entries) == 1 assert len(all_entries) == 1
assert foo.id in all_entries assert foo.id in all_entries
def test_i_can_add_several_concepts_to_set(self): def test_i_can_add_several_concepts_to_set(self):
sheerka = self.get_sheerka(False, False)
foo1 = Concept("foo1") foo1 = Concept("foo1")
sheerka.set_id_if_needed(foo1, False) foo2 = Concept("foo2")
foo2 = Concept("foo1")
sheerka.set_id_if_needed(foo2, False)
all_foos = Concept("all_foos") all_foos = Concept("all_foos")
sheerka.set_id_if_needed(all_foos, False) sheerka, context = self.init(False, foo1, foo2, all_foos)
context = self.get_context(sheerka) res = sheerka.sets_handler.add_concepts_to_set(context, (foo1, foo2), all_foos)
sheerka.add_concept_to_set(context, foo1, all_foos)
res = sheerka.add_concept_to_set(context, foo2, all_foos)
assert res.status assert res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
@@ -56,16 +45,30 @@ class TestSheerkaSetsManager(TestUsingFileBasedSheerka):
assert foo1.id in all_entries assert foo1.id in all_entries
assert foo2.id in all_entries assert foo2.id in all_entries
# I can add another elements
foo3 = Concept("foo3")
foo4 = Concept("foo4")
for c in [foo3, foo4]:
sheerka.set_id_if_needed(c, False)
sheerka.add_in_cache(c)
res = sheerka.sets_handler.add_concepts_to_set(context, (foo3, foo4), all_foos)
assert res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
all_entries = self.get_sheerka(False, False).sdp.get("All_" + all_foos.id, None, False)
assert len(all_entries) == 4
assert foo1.id in all_entries
assert foo2.id in all_entries
assert foo3.id in all_entries
assert foo4.id in all_entries
def test_i_cannot_add_the_same_concept_twice_in_a_set(self): def test_i_cannot_add_the_same_concept_twice_in_a_set(self):
sheerka = self.get_sheerka()
foo = Concept("foo") foo = Concept("foo")
sheerka.set_id_if_needed(foo, False)
all_foos = Concept("all_foos") all_foos = Concept("all_foos")
sheerka.set_id_if_needed(all_foos, False) sheerka, context = self.init(True, foo, all_foos)
context = self.get_context(sheerka)
sheerka.add_concept_to_set(context, foo, all_foos) sheerka.add_concept_to_set(context, foo, all_foos)
res = sheerka.add_concept_to_set(context, foo, all_foos) res = sheerka.add_concept_to_set(context, foo, all_foos)
@@ -77,18 +80,12 @@ class TestSheerkaSetsManager(TestUsingFileBasedSheerka):
assert foo.id in all_entries assert foo.id in all_entries
def test_i_get_elements_from_a_set(self): def test_i_get_elements_from_a_set(self):
sheerka = self.get_sheerka()
one = Concept("one") one = Concept("one")
two = Concept("two") two = Concept("two")
three = Concept("three") three = Concept("three")
number = Concept("number") number = Concept("number")
sheerka, context = self.init(True, one, two, three, number)
for c in [one, two, three, number]:
sheerka.set_id_if_needed(c, False)
sheerka.add_in_cache(c)
context = self.get_context(sheerka)
for c in [one, two, three]: for c in [one, two, three]:
sheerka.add_concept_to_set(context, c, number) sheerka.add_concept_to_set(context, c, number)
@@ -97,10 +94,8 @@ class TestSheerkaSetsManager(TestUsingFileBasedSheerka):
assert set(elements) == {one, two, three} assert set(elements) == {one, two, three}
def test_i_cannot_get_elements_if_not_a_set(self): def test_i_cannot_get_elements_if_not_a_set(self):
sheerka = self.get_sheerka()
one = Concept("one") one = Concept("one")
sheerka.set_id_if_needed(one, False) sheerka, context = self.init(True, one)
sheerka.add_in_cache(one)
error = sheerka.get_set_elements(one) error = sheerka.get_set_elements(one)
@@ -108,17 +103,17 @@ class TestSheerkaSetsManager(TestUsingFileBasedSheerka):
assert error.body == one assert error.body == one
def test_isa_and_isa_group(self): def test_isa_and_isa_group(self):
sheerka = self.get_sheerka() group = Concept("group")
foo = Concept("foo")
sheerka, context = self.init(True, group, foo)
group = Concept("group").init_key()
group.metadata.id = "1001"
assert not sheerka.isaset(group) assert not sheerka.isaset(group)
foo = Concept("foo").init_key()
foo.metadata.id = "1002"
assert not sheerka.isa(foo, group) assert not sheerka.isa(foo, group)
context = self.get_context(sheerka) context = self.get_context(sheerka)
sheerka.add_concept_to_set(context, foo, group) sheerka.add_concept_to_set(context, foo, group)
assert sheerka.isaset(group) assert sheerka.isaset(group)
assert sheerka.isa(foo, group) assert sheerka.isa(foo, group)
def test_i_can_a_multiples_concepts(self):
pass
+17 -3
View File
@@ -40,7 +40,7 @@ def test_i_can_tokenize():
assert tokens[31] == Token(TokenKind.AMPER, '&', 78, 6, 20) assert tokens[31] == Token(TokenKind.AMPER, '&', 78, 6, 20)
assert tokens[32] == Token(TokenKind.LESS, '<', 79, 6, 21) assert tokens[32] == Token(TokenKind.LESS, '<', 79, 6, 21)
assert tokens[33] == Token(TokenKind.GREATER, '>', 80, 6, 22) assert tokens[33] == Token(TokenKind.GREATER, '>', 80, 6, 22)
assert tokens[34] == Token(TokenKind.CONCEPT, 'name', 81, 6, 23) assert tokens[34] == Token(TokenKind.CONCEPT, ('name', None), 81, 6, 23)
assert tokens[35] == Token(TokenKind.DOLLAR, '$', 88, 6, 30) assert tokens[35] == Token(TokenKind.DOLLAR, '$', 88, 6, 30)
assert tokens[36] == Token(TokenKind.STERLING, '£', 89, 6, 31) assert tokens[36] == Token(TokenKind.STERLING, '£', 89, 6, 31)
assert tokens[37] == Token(TokenKind.EURO, '', 90, 6, 32) assert tokens[37] == Token(TokenKind.EURO, '', 90, 6, 32)
@@ -79,8 +79,8 @@ def test_i_can_tokenize_identifiers(text, expected):
('"string', "Missing Trailing quote", '"string', 7, 1, 8), ('"string', "Missing Trailing quote", '"string', 7, 1, 8),
('"a" + "string', "Missing Trailing quote", '"string', 13, 1, 14), ('"a" + "string', "Missing Trailing quote", '"string', 13, 1, 14),
('"a"\n\n"string', "Missing Trailing quote", '"string', 12, 3, 8), ('"a"\n\n"string', "Missing Trailing quote", '"string', 12, 3, 8),
("c::", "Concept name not found", "", 2, 1, 3), ("c::", "Concept identifiers not found", "", 2, 1, 3),
("c:foo\nbar:", "New line is forbidden in concept name", "foo", 5, 1, 6), ("c:foo\nbar:", "New line in concept name", "foo", 5, 1, 6),
("c:foo", "Missing ending colon", "foo", 5, 1, 6) ("c:foo", "Missing ending colon", "foo", 5, 1, 6)
]) ])
def test_i_can_detect_tokenizer_errors(text, message, error_text, index, line, column): def test_i_can_detect_tokenizer_errors(text, message, error_text, index, line, column):
@@ -139,3 +139,17 @@ def test_i_can_recognize_keywords(text, expected):
tokens = list(Tokenizer(text)) tokens = list(Tokenizer(text))
assert tokens[0].type == TokenKind.KEYWORD assert tokens[0].type == TokenKind.KEYWORD
assert tokens[0].value == expected assert tokens[0].value == expected
@pytest.mark.parametrize("text, expected", [
("c:key:", ("key", None)),
("c:key|id:", ("key", "id")),
("c:key|:", ("key", None)),
("c:|id:", (None, "id")),
("c:125:", ("125", None)),
])
def test_i_can_parse_concept_token(text, expected):
tokens = list(Tokenizer(text))
assert tokens[0].type == TokenKind.CONCEPT
assert tokens[0].value == expected
+56 -20
View File
@@ -1,10 +1,25 @@
import core.utils import core.utils
import pytest import pytest
from core.concept import ConceptParts from core.concept import ConceptParts, Concept
from core.tokenizer import Token, TokenKind from core.tokenizer import Token, TokenKind
def get_tokens(lst):
res = []
for e in lst:
if e == " ":
res.append(Token(TokenKind.WHITESPACE, " ", 0, 0, 0))
elif e == "\n":
res.append(Token(TokenKind.NEWLINE, "\n", 0, 0, 0))
elif e == "<EOF>":
res.append(Token(TokenKind.EOF, "\n", 0, 0, 0))
else:
res.append(Token(TokenKind.IDENTIFIER, e, 0, 0, 0))
return res
@pytest.mark.parametrize("lst, as_string", [ @pytest.mark.parametrize("lst, as_string", [
(None, "",), (None, "",),
([], ""), ([], ""),
@@ -136,18 +151,33 @@ def test_i_can_escape():
(10, None, None), (10, None, None),
("", None, None), ("", None, None),
("xxx", None, None), ("xxx", None, None),
(":c:", None, None), ("c:", None, None),
(":c:key", None, None), ("c:key", None, None),
(":c:key:", "key", None), ("c:key:", "key", None),
(":c:key:id", None, None), ("c:key|id", None, None),
(":c:key:id:", "key", "id"), ("c:key|id:", "key", "id"),
("c:|id:", None, "id"),
("c:key|:", "key", None),
]) ])
def test_i_can_decode_concept_repr(text, expected_key, expected_id): def test_i_can_unstr_concept(text, expected_key, expected_id):
k, i = core.utils.decode_concept(text) k, i = core.utils.unstr_concept(text)
assert k == expected_key assert k == expected_key
assert i == expected_id assert i == expected_id
def test_i_can_str_concept():
assert core.utils.str_concept(("key", "id")) == "c:key|id:"
assert core.utils.str_concept((None, "id")) == "c:|id:"
assert core.utils.str_concept(("key", None)) == "c:key:"
assert core.utils.str_concept((None, None)) == ""
concept = Concept("foo").init_key()
assert core.utils.str_concept(concept) == "c:foo:"
concept.metadata.id = "1001"
assert core.utils.str_concept(concept) == "c:foo|1001:"
@pytest.mark.parametrize("text, expected", [ @pytest.mark.parametrize("text, expected", [
(None, None), (None, None),
(10, None), (10, None),
@@ -162,16 +192,22 @@ def test_i_can_decode_enum(text, expected):
assert actual == expected assert actual == expected
def get_tokens(lst): def test_encode_concept_key_id():
res = [] assert core.utils.encode_concept(("key", "id")) == "__C__KEY_key__ID_id__C__"
for e in lst: assert core.utils.encode_concept((None, "id")) == "__C__KEY_00None00__ID_id__C__"
if e == " ": assert core.utils.encode_concept(("key", None)) == "__C__KEY_key__ID_00None00__C__"
res.append(Token(TokenKind.WHITESPACE, " ", 0, 0, 0)) assert core.utils.encode_concept(("key", "id"), True) == "__C__USE_CONCEPT__KEY_key__ID_id__C__"
elif e == "\n": assert core.utils.encode_concept(("k + y", "id")) == "__C__KEY_k000y__ID_id__C__"
res.append(Token(TokenKind.NEWLINE, "\n", 0, 0, 0))
elif e == "<EOF>":
res.append(Token(TokenKind.EOF, "\n", 0, 0, 0))
else:
res.append(Token(TokenKind.IDENTIFIER, e, 0, 0, 0))
return res concept = Concept("foo").init_key()
assert core.utils.encode_concept(concept) == "__C__KEY_foo__ID_00None00__C__"
concept.metadata.id = "1001"
assert core.utils.encode_concept(concept) == "__C__KEY_foo__ID_1001__C__"
def test_decode_concept_key_id():
assert core.utils.decode_concept("__C__KEY_key__ID_id__C__") == ("key", "id", False)
assert core.utils.decode_concept("__C__KEY_00None00__ID_id__C__") == (None, "id", False)
assert core.utils.decode_concept("__C__KEY_key__ID_00None00__C__") == ("key", None, False)
assert core.utils.decode_concept("__C__USE_CONCEPT__KEY_key__ID_id__C__") == ("key", "id", True)
+4 -4
View File
@@ -21,7 +21,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
def test_i_can_evaluate_concept(self): def test_i_can_evaluate_concept(self):
context = self.get_context() context = self.get_context()
concept = Concept(name="foo", concept = Concept(name="foo",
where="1", where="True",
pre="2", pre="2",
post="3").def_prop("a", "4").def_prop("b", "5") post="3").def_prop("a", "4").def_prop("b", "5")
@@ -32,7 +32,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
assert result.who == evaluator.name assert result.who == evaluator.name
assert result.status assert result.status
assert result.value.name == "foo" assert result.value.name == "foo"
assert result.value.get_metadata_value(ConceptParts.WHERE) == 1 assert result.value.get_metadata_value(ConceptParts.WHERE) == True
assert result.value.get_metadata_value(ConceptParts.PRE) == 2 assert result.value.get_metadata_value(ConceptParts.PRE) == 2
assert result.value.get_metadata_value(ConceptParts.POST) == 3 assert result.value.get_metadata_value(ConceptParts.POST) == 3
assert result.value.get_prop("a") == 4 assert result.value.get_prop("a") == 4
@@ -44,7 +44,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
context = self.get_context() context = self.get_context()
concept = Concept(name="foo", concept = Concept(name="foo",
body="'I have a value'", body="'I have a value'",
where="1", where="True",
pre="2", pre="2",
post="3").set_prop("a", "4").set_prop("b", "5") post="3").set_prop("a", "4").set_prop("b", "5")
@@ -61,7 +61,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
context = self.get_context() context = self.get_context()
concept = Concept(name="foo", concept = Concept(name="foo",
body="'I have a value'", body="'I have a value'",
where="1", where="True",
pre="2", pre="2",
post="3").set_prop("a", "4").set_prop("b", "5") post="3").set_prop("a", "4").set_prop("b", "5")
+20 -20
View File
@@ -116,23 +116,23 @@ class TestPythonEvaluator(TestUsingMemoryBasedSheerka):
assert not evaluated.status assert not evaluated.status
assert evaluated.body.body.args[0] == "'int' object has no attribute 'name'" assert evaluated.body.body.args[0] == "'int' object has no attribute 'name'"
@pytest.mark.parametrize("text, concept_key, concept_id, use_concept", [ # @pytest.mark.parametrize("text, concept_key, concept_id, use_concept", [
("__C__key__C__", "key", None, False), # ("__C__key__C__", "key", None, False),
("__C__key__id__C__", "key", "id", False), # ("__C__key__id__C__", "key", "id", False),
("__C__USE_CONCEPT__key__id__C__", "key", "id", True), # ("__C__USE_CONCEPT__key__id__C__", "key", "id", True),
("__C__USE_CONCEPT__key__id__C__", "key", "id", True), # ("__C__USE_CONCEPT__key__id__C__", "key", "id", True),
]) # ])
def test_i_can_resolve_name(self, text, concept_key, concept_id, use_concept): # def test_i_can_resolve_name(self, text, concept_key, concept_id, use_concept):
context = self.get_context() # context = self.get_context()
assert PythonEvaluator().resolve_name(context, text) == (concept_key, concept_id, use_concept) # assert PythonEvaluator().resolve_name(context, text) == (concept_key, concept_id, use_concept)
#
@pytest.mark.parametrize("text", [ # @pytest.mark.parametrize("text", [
"__C__", # "__C__",
"__C__key", # "__C__key",
"__C__key____", # "__C__key____",
"__C____", # "__C____",
"__C__USE_CONCEPT__", # "__C__USE_CONCEPT__",
]) # ])
def test_i_cannot_resolve_name(self, text): # def test_i_cannot_resolve_name(self, text):
context = self.get_context() # context = self.get_context()
assert PythonEvaluator().resolve_name(context, text) is None # assert PythonEvaluator().resolve_name(context, text) is None
+50 -2
View File
@@ -273,8 +273,9 @@ as:
saved_definitions = sheerka.sdp.get_safe(sheerka.CONCEPTS_DEFINITIONS_ENTRY) saved_definitions = sheerka.sdp.get_safe(sheerka.CONCEPTS_DEFINITIONS_ENTRY)
expected_bnf = Sequence( expected_bnf = Sequence(
a, Optional(Sequence(StrMatch("plus"), ConceptExpression("plus", rule_name="plus")))) ConceptExpression(a, rule_name="a"),
assert saved_definitions[saved_concept] == expected_bnf Optional(Sequence(StrMatch("plus"), ConceptExpression(saved_concept, rule_name="plus"))))
assert saved_definitions["c:plus|1001:"] == "(c:a:=a ('plus' c:plus|1001:=plus)?)"
new_concept = res[0].value.body new_concept = res[0].value.body
assert new_concept.metadata.name == "plus" assert new_concept.metadata.name == "plus"
@@ -456,6 +457,26 @@ as:
assert res[0].status assert res[0].status
assert res[0].body == 23 assert res[0].body == 23
def test_i_can_mix_bnf_and_isa_when_concept_other_case(self):
sheerka = self.get_sheerka()
init = [
"def concept one as 1",
"def concept twenty as 20",
"def concept number",
"one isa number",
"twenty isa number",
"def concept twenties from bnf twenty number as twenty + number"
]
for exp in init:
sheerka.evaluate_user_input(exp)
res = sheerka.evaluate_user_input("twenty one")
assert len(res) == 1
assert res[0].status
assert res[0].body == simplec("twenties", 21)
def test_i_can_mix_concept_of_concept(self): def test_i_can_mix_concept_of_concept(self):
sheerka = self.get_sheerka() sheerka = self.get_sheerka()
@@ -623,3 +644,30 @@ as:
assert len(res) == 1 assert len(res) == 1
assert res[0].status assert res[0].status
assert res[0].body == 21 assert res[0].body == 21
def test_i_can_use_where_in_bnf(self):
sheerka = self.get_sheerka()
init = [
"def concept one as 1",
"def concept two as 2",
"def concept three as 3",
"def concept twenty as 20",
"def concept number",
"one isa number",
"two isa number",
"three isa number",
"def concept twenties from bnf twenty number where number <= 2 as twenty + number"
]
for exp in init:
sheerka.evaluate_user_input(exp)
res = sheerka.evaluate_user_input("eval twenty one")
assert len(res) == 1 and res[0].status and res[0].body == 21
res = sheerka.evaluate_user_input("eval twenty two")
assert len(res) == 1 and res[0].status and res[0].body == 22
res = sheerka.evaluate_user_input("eval twenty three")
assert len(res) > 1
+1 -1
View File
@@ -18,7 +18,7 @@ def test_i_can_get_text_from_tokens(text, expected_text):
@pytest.mark.parametrize("text, custom, expected_text", [ @pytest.mark.parametrize("text, custom, expected_text", [
("execute(c:concept_name:)", {TokenKind.CONCEPT: lambda t: f"__C__{t.value}"}, "execute(__C__concept_name)") ("execute(c:concept_name:)", {TokenKind.CONCEPT: lambda t: f"__C__{t.value[0]}"}, "execute(__C__concept_name)")
]) ])
def test_i_can_get_text_from_tokens_with_custom_switcher(text, custom, expected_text): def test_i_can_get_text_from_tokens_with_custom_switcher(text, custom, expected_text):
tokens = list(Tokenizer(text)) tokens = list(Tokenizer(text))
+28 -12
View File
@@ -2,7 +2,7 @@ import pytest
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept from core.concept import Concept
from core.tokenizer import Tokenizer, TokenKind, LexerError from core.tokenizer import Tokenizer, TokenKind, LexerError, Token
from parsers.BaseParser import UnexpectedTokenErrorNode from parsers.BaseParser import UnexpectedTokenErrorNode
from parsers.BnfParser import BnfParser, UnexpectedEndOfFileError from parsers.BnfParser import BnfParser, UnexpectedEndOfFileError
from parsers.ConceptLexerParser import StrMatch, Optional, ZeroOrMore, OrderedChoice, Sequence, OneOrMore, \ from parsers.ConceptLexerParser import StrMatch, Optional, ZeroOrMore, OrderedChoice, Sequence, OneOrMore, \
@@ -16,6 +16,14 @@ class ClassWithName:
self.name = name self.name = name
def c(name):
concept = Concept(name).init_key()
return ConceptExpression(concept, rule_name=name)
eof_token = Token(TokenKind.EOF, "", 0, 0, 0)
class TestBnfParser(TestUsingMemoryBasedSheerka): class TestBnfParser(TestUsingMemoryBasedSheerka):
@pytest.mark.parametrize("expression, expected", [ @pytest.mark.parametrize("expression, expected", [
@@ -33,9 +41,10 @@ class TestBnfParser(TestUsingMemoryBasedSheerka):
("1 2 | 3 4+", OrderedChoice( ("1 2 | 3 4+", OrderedChoice(
Sequence(StrMatch("1"), StrMatch("2")), Sequence(StrMatch("1"), StrMatch("2")),
Sequence(StrMatch("3"), OneOrMore(StrMatch("4"))))), Sequence(StrMatch("3"), OneOrMore(StrMatch("4"))))),
( ("1 (2 | 3) 4+", Sequence(
"1 (2 | 3) 4+", StrMatch("1"),
Sequence(StrMatch("1"), OrderedChoice(StrMatch("2"), StrMatch("3")), OneOrMore(StrMatch("4")))), OrderedChoice(StrMatch("2"), StrMatch("3")),
OneOrMore(StrMatch("4")))),
("(1|2)+", OneOrMore(OrderedChoice(StrMatch("1"), StrMatch("2")))), ("(1|2)+", OneOrMore(OrderedChoice(StrMatch("1"), StrMatch("2")))),
("(1 2)+", OneOrMore(Sequence(StrMatch("1"), StrMatch("2")))), ("(1 2)+", OneOrMore(Sequence(StrMatch("1"), StrMatch("2")))),
("1 *", Sequence(StrMatch("1"), StrMatch("*"))), ("1 *", Sequence(StrMatch("1"), StrMatch("*"))),
@@ -61,6 +70,13 @@ class TestBnfParser(TestUsingMemoryBasedSheerka):
("(1 2)=var", Sequence(StrMatch("1"), StrMatch("2"), rule_name="var")), ("(1 2)=var", Sequence(StrMatch("1"), StrMatch("2"), rule_name="var")),
("(1 2)+=var", OneOrMore(Sequence(StrMatch("1"), StrMatch("2")), rule_name="var")), ("(1 2)+=var", OneOrMore(Sequence(StrMatch("1"), StrMatch("2")), rule_name="var")),
("(1 2)=var+", OneOrMore(Sequence(StrMatch("1"), StrMatch("2"), rule_name="var"))), ("(1 2)=var+", OneOrMore(Sequence(StrMatch("1"), StrMatch("2"), rule_name="var"))),
("(1=a 2=b)=c", Sequence(StrMatch("1", rule_name="a"), StrMatch("2", rule_name="b"), rule_name="c")),
("(1*=a)", ZeroOrMore(StrMatch("1"), rule_name="a")),
("'a'* 'b'+", Sequence(ZeroOrMore(StrMatch("a")), OneOrMore(StrMatch("b")))),
("('a'* 'b'+)", Sequence(ZeroOrMore(StrMatch("a")), OneOrMore(StrMatch("b")))),
("('a'*=x 'b'+=y)=z", Sequence(
ZeroOrMore(StrMatch("a"), rule_name="x"),
OneOrMore(StrMatch("b"), rule_name="y"), rule_name="z")),
]) ])
def test_i_can_parse_regex(self, expression, expected): def test_i_can_parse_regex(self, expression, expected):
parser = BnfParser() parser = BnfParser()
@@ -72,12 +88,12 @@ class TestBnfParser(TestUsingMemoryBasedSheerka):
assert res.value.source == expression assert res.value.source == expression
@pytest.mark.parametrize("expression, expected", [ @pytest.mark.parametrize("expression, expected", [
("foo", Concept("foo").init_key()), ("foo", c("foo")),
("foo*", ZeroOrMore(Concept("foo").init_key())), ("foo*", ZeroOrMore(c("foo"))),
("foo 'and' bar+", Sequence(Concept("foo").init_key(), StrMatch("and"), OneOrMore(Concept("bar").init_key()))), ("foo 'and' bar+", Sequence(c("foo"), StrMatch("and"), OneOrMore(c("bar")))),
("foo | bar?", OrderedChoice(Concept("foo").init_key(), Optional(Concept("bar").init_key()))), ("foo | bar?", OrderedChoice(c("foo"), Optional(c("bar")))),
("'str' = var", Sequence(StrMatch("str"), StrMatch("="), Concept("var").init_key())), ("'str' = var", Sequence(StrMatch("str"), StrMatch("="), c("var"))),
("'str''='var", Sequence(StrMatch("str"), StrMatch("="), Concept("var").init_key())), ("'str''='var", Sequence(StrMatch("str"), StrMatch("="), c("var"))),
]) ])
def test_i_can_parse_regex_with_concept(self, expression, expected): def test_i_can_parse_regex_with_concept(self, expression, expected):
foo = Concept("foo") foo = Concept("foo")
@@ -113,8 +129,8 @@ class TestBnfParser(TestUsingMemoryBasedSheerka):
@pytest.mark.parametrize("expression, error", [ @pytest.mark.parametrize("expression, error", [
("1 ", UnexpectedEndOfFileError()), ("1 ", UnexpectedEndOfFileError()),
("1|", UnexpectedEndOfFileError()), ("1|", UnexpectedEndOfFileError()),
("(1|)", UnexpectedTokenErrorNode("Unexpected token 'Token(<EOF>)'", [TokenKind.RPAR])), ("(1|)", UnexpectedTokenErrorNode("Unexpected token 'Token(<EOF>)'", eof_token, [TokenKind.RPAR])),
("1=", UnexpectedTokenErrorNode("Unexpected token 'Token(<EOF>)'", [TokenKind.IDENTIFIER])), ("1=", UnexpectedTokenErrorNode("Unexpected token 'Token(<EOF>)'", eof_token, [TokenKind.IDENTIFIER])),
("'name", LexerError("Missing Trailing quote", "'name", 5, 1, 6)) ("'name", LexerError("Missing Trailing quote", "'name", 5, 1, 6))
]) ])
def test_i_can_detect_errors(self, expression, error): def test_i_can_detect_errors(self, expression, error):
+134 -1
View File
@@ -1,10 +1,13 @@
from ast import Str
import pytest import pytest
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept, ConceptParts, DoNotResolve from core.concept import Concept, ConceptParts, DoNotResolve
from core.tokenizer import Tokenizer, TokenKind, Token from core.tokenizer import Tokenizer, TokenKind, Token
from parsers.BnfParser import BnfParser
from parsers.ConceptLexerParser import ConceptLexerParser, ConceptNode, Sequence, StrMatch, OrderedChoice, Optional, \ from parsers.ConceptLexerParser import ConceptLexerParser, ConceptNode, Sequence, StrMatch, OrderedChoice, Optional, \
ParsingExpressionVisitor, TerminalNode, NonTerminalNode, ZeroOrMore, OneOrMore, \ ParsingExpressionVisitor, TerminalNode, NonTerminalNode, ZeroOrMore, OneOrMore, \
UnrecognizedTokensNode, cnode, short_cnode UnrecognizedTokensNode, cnode, short_cnode, ConceptExpression, ConceptGroupExpression
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -75,6 +78,7 @@ class TestConceptLexerParser(TestUsingMemoryBasedSheerka):
context = self.get_context() context = self.get_context()
for c in concepts: for c in concepts:
context.sheerka.add_in_cache(c) context.sheerka.add_in_cache(c)
context.sheerka.set_id_if_needed(c, False)
parser = ConceptLexerParser() parser = ConceptLexerParser()
parser.initialize(context, grammar) parser.initialize(context, grammar)
@@ -586,6 +590,32 @@ class TestConceptLexerParser(TestUsingMemoryBasedSheerka):
assert res.status assert res.status
assert res.value.body == [cnode("foo", 0, 2, "twenty one")] assert res.value.body == [cnode("foo", 0, 2, "twenty one")]
def test_i_can_initialize_when_cyclic_reference(self):
foo = Concept(name="foo")
grammar = {foo: Optional("one", ConceptExpression("foo"))}
context, parser = self.init([foo], grammar)
assert parser.concepts_grammars[foo] == Optional("one", ConceptExpression(foo, rule_name="foo"))
def test_i_cannot_initialize_when_cyclic_reference_when_concept_is_under_construction_and_not_known(self):
foo = Concept(name="foo").init_key()
grammar = {foo: Optional("one", ConceptExpression("foo"))}
context = self.get_context()
parser = ConceptLexerParser()
parser.initialize(context, grammar)
assert parser.concepts_grammars[foo] == Optional("one", ConceptExpression("foo", rule_name="foo"))
def test_i_can_initialize_when_cyclic_reference_when_concept_is_under_construction_and_known(self):
foo = Concept(name="foo").init_key()
grammar = {foo: Optional("one", ConceptExpression("foo"))}
context = self.get_context()
context.concepts["foo"] = foo
parser = ConceptLexerParser()
parser.initialize(context, grammar)
assert parser.concepts_grammars[foo] == Optional("one", ConceptExpression(foo, rule_name="foo"))
def test_i_can_parse_concept_reference_that_is_group(self): def test_i_can_parse_concept_reference_that_is_group(self):
""" """
if one is number, then number is a 'group' if one is number, then number is a 'group'
@@ -1092,6 +1122,109 @@ class TestConceptLexerParser(TestUsingMemoryBasedSheerka):
assert cprop(concept_found, "seq")[1] == DoNotResolve("un ok") assert cprop(concept_found, "seq")[1] == DoNotResolve("un ok")
assert cprop(concept_found, "seq")[2] == DoNotResolve("uno ok") assert cprop(concept_found, "seq")[2] == DoNotResolve("uno ok")
@pytest.mark.parametrize("rule, expected", [
(StrMatch("string"), "'string'"),
(StrMatch("string", rule_name="rule_name"), "'string'=rule_name"),
(Sequence(StrMatch("foo"), StrMatch("bar")), "('foo' 'bar')"),
(Sequence(StrMatch("foo"), StrMatch("bar"), rule_name="rule_name"), "('foo' 'bar')=rule_name"),
(OrderedChoice(StrMatch("foo"), StrMatch("bar")), "('foo'|'bar')"),
(OrderedChoice(StrMatch("foo"), StrMatch("bar"), rule_name="rule_name"), "('foo'|'bar')=rule_name"),
(Optional(StrMatch("foo")), "'foo'?"),
(Optional(StrMatch("foo"), rule_name="rule_name"), "'foo'?=rule_name"),
(ZeroOrMore(StrMatch("foo")), "'foo'*"),
(ZeroOrMore(StrMatch("foo"), rule_name="rule_name"), "'foo'*=rule_name"),
(OneOrMore(StrMatch("foo")), "'foo'+"),
(OneOrMore(StrMatch("foo"), rule_name="rule_name"), "'foo'+=rule_name"),
(Sequence(
Optional(StrMatch("foo"), rule_name="a"),
ZeroOrMore(StrMatch("bar"), rule_name="b"),
OneOrMore(StrMatch("baz"), rule_name="c"),
rule_name="d"), "('foo'?=a 'bar'*=b 'baz'+=c)=d"),
(OrderedChoice(
Optional(StrMatch("foo"), rule_name="a"),
ZeroOrMore(StrMatch("bar"), rule_name="b"),
OneOrMore(StrMatch("baz"), rule_name="c"),
rule_name="d"), "('foo'?=a|'bar'*=b|'baz'+=c)=d"),
(Sequence(
OrderedChoice(StrMatch("foo"), StrMatch("bar"), rule_name="a"),
OrderedChoice(StrMatch("x"), StrMatch("y"), rule_name="b"),
rule_name="c"), "(('foo'|'bar')=a ('x'|'y')=b)=c")
])
def test_i_can_encode_grammar(self, rule, expected):
foo = Concept(name="foo")
grammar = {foo: rule}
context, parser = self.init([foo], grammar)
encoded = parser.encode_grammar(parser.concepts_grammars)
assert encoded["c:foo|1001:"] == expected
bnf_parser = BnfParser()
parse_res = bnf_parser.parse(context, encoded["c:foo|1001:"])
assert parse_res.status
assert parse_res.value.value == rule
def test_i_can_encode_grammar_when_concept_simple(self):
foo = Concept(name="foo")
bar = Concept(name="bar")
grammar = {foo: ConceptExpression(bar)}
context, parser = self.init([foo, bar], grammar)
encoded = parser.encode_grammar(parser.concepts_grammars)
assert encoded["c:foo|1001:"] == "c:bar|1002:=bar"
bnf_parser = BnfParser()
parse_res = bnf_parser.parse(context, encoded["c:foo|1001:"])
assert parse_res.status
assert parse_res.value.value == grammar[foo]
def test_i_can_encode_grammar_when_concepts(self):
foo = Concept(name="foo")
bar = Concept(name="bar")
baz = Concept(name="baz")
grammar = {foo: Sequence(
StrMatch("a"),
OrderedChoice(ConceptExpression(bar),
OneOrMore(ConceptExpression(baz)), rule_name="oc"), rule_name="s")}
context, parser = self.init([foo, bar, baz], grammar)
encoded = parser.encode_grammar(parser.concepts_grammars)
assert encoded["c:foo|1001:"] == "('a' (c:bar|1002:=bar|c:baz|1003:=baz+)=oc)=s"
bnf_parser = BnfParser()
parse_res = bnf_parser.parse(context, encoded["c:foo|1001:"])
assert parse_res.status
assert parse_res.value.value == grammar[foo]
def test_i_can_encode_grammar_when_set_concepts(self):
foo = Concept(name="foo")
bar = Concept(name="bar")
baz = Concept(name="baz")
grammar = {foo: Sequence(
StrMatch("a"),
OrderedChoice(bar,
OneOrMore(ConceptExpression(baz)), rule_name="oc"), rule_name="s")}
context = self.get_context()
for c in [foo, bar, baz]:
context.sheerka.add_in_cache(c)
context.sheerka.set_id_if_needed(c, False)
context.sheerka.add_concept_to_set(context, baz, bar)
parser = ConceptLexerParser()
parser.initialize(context, grammar)
encoded = parser.encode_grammar(parser.concepts_grammars)
assert encoded["c:foo|1001:"] == "('a' (c:bar|1002:=bar|c:baz|1003:=baz+)=oc)=s"
bnf_parser = BnfParser()
parse_res = bnf_parser.parse(context, encoded["c:foo|1001:"])
assert parse_res.status
expected = Sequence(
StrMatch("a"),
OrderedChoice(ConceptGroupExpression(bar, rule_name="bar"),
OneOrMore(ConceptExpression(baz, rule_name="baz")), rule_name="oc"), rule_name="s")
assert parse_res.value.value == expected
# #
# def test_i_can_parse_basic_arithmetic_operations_and_resolve_properties(self): # def test_i_can_parse_basic_arithmetic_operations_and_resolve_properties(self):
# context = self.get_context() # context = self.get_context()
+3 -3
View File
@@ -3,7 +3,7 @@ import ast
from core.builtin_concepts import ParserResultConcept, BuiltinConcepts, ReturnValueConcept from core.builtin_concepts import ParserResultConcept, BuiltinConcepts, ReturnValueConcept
from core.concept import Concept from core.concept import Concept
from parsers.ConceptLexerParser import OrderedChoice, StrMatch from parsers.ConceptLexerParser import OrderedChoice, StrMatch, ConceptExpression
from parsers.PythonParser import PythonParser, PythonNode from parsers.PythonParser import PythonParser, PythonNode
from core.tokenizer import Keywords, Tokenizer, LexerError from core.tokenizer import Keywords, Tokenizer, LexerError
from parsers.DefaultParser import DefaultParser, NameNode, SyntaxErrorNode, CannotHandleErrorNode, IsaConceptNode from parsers.DefaultParser import DefaultParser, NameNode, SyntaxErrorNode, CannotHandleErrorNode, IsaConceptNode
@@ -246,7 +246,7 @@ def concept add one to a as
parser = DefaultParser() parser = DefaultParser()
res = parser.parse(context, text) res = parser.parse(context, text)
node = res.value.value node = res.value.value
definition = OrderedChoice(a_concept, StrMatch("a_string")) definition = OrderedChoice(ConceptExpression(a_concept, rule_name="a_concept"), StrMatch("a_string"))
parser_result = ParserResultConcept(BnfParser(), "a_concept | 'a_string'", definition, definition) parser_result = ParserResultConcept(BnfParser(), "a_concept | 'a_string'", definition, definition)
expected = get_def_concept(name="name", body="__definition[0]", definition=parser_result) expected = get_def_concept(name="name", body="__definition[0]", definition=parser_result)
@@ -321,7 +321,7 @@ def concept add one to a as
("def concept 'name", "Missing Trailing quote", "'name"), ("def concept 'name", "Missing Trailing quote", "'name"),
("def concept name as 'body", "Missing Trailing quote", "'body"), ("def concept name as 'body", "Missing Trailing quote", "'body"),
("def concept name from bnf 'expression", "Missing Trailing quote", "'expression"), ("def concept name from bnf 'expression", "Missing Trailing quote", "'expression"),
("def concept c::", "Concept name not found", ""), ("def concept c::", "Concept identifiers not found", ""),
]) ])
def test_i_cannot_parse_when_tokenizer_fails(self, text, error_msg, error_text): def test_i_cannot_parse_when_tokenizer_fails(self, text, error_msg, error_text):
parser = DefaultParser() parser = DefaultParser()
+6 -5
View File
@@ -3,6 +3,7 @@ import pytest
from core.builtin_concepts import ParserResultConcept from core.builtin_concepts import ParserResultConcept
from core.tokenizer import Tokenizer, LexerError from core.tokenizer import Tokenizer, LexerError
from parsers.PythonParser import PythonNode, PythonParser, PythonErrorNode from parsers.PythonParser import PythonNode, PythonParser, PythonErrorNode
import core.utils
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -52,8 +53,8 @@ class TestPythonParser(TestUsingMemoryBasedSheerka):
assert isinstance(res.value.value[0].exception, SyntaxError) assert isinstance(res.value.value[0].exception, SyntaxError)
@pytest.mark.parametrize("text, error_msg, error_text", [ @pytest.mark.parametrize("text, error_msg, error_text", [
("c::", "Concept name not found", ""), ("c::", "Concept identifiers not found", ""),
("c:: + 1", "Concept name not found", ""), ("c:: + 1", "Concept identifiers not found", ""),
]) ])
def test_i_can_detect_lexer_errors(self, text, error_msg, error_text): def test_i_can_detect_lexer_errors(self, text, error_msg, error_text):
parser = PythonParser() parser = PythonParser()
@@ -66,12 +67,12 @@ class TestPythonParser(TestUsingMemoryBasedSheerka):
assert res.body.body[0].text == error_text assert res.body.body[0].text == error_text
def test_i_can_parse_a_concept(self): def test_i_can_parse_a_concept(self):
text = "c:concept_name: + 1" text = "c:name|key: + 1"
parser = PythonParser() parser = PythonParser()
res = parser.parse(self.get_context(), text) res = parser.parse(self.get_context(), text)
assert res assert res
assert res.value.value == PythonNode( assert res.value.value == PythonNode(
"c:concept_name: + 1", "c:name|key: + 1",
ast.parse("__C__USE_CONCEPT__concept_name__C__+1", mode="eval")) ast.parse(core.utils.encode_concept(("name", "key"), True) + "+1", mode="eval"))
+1 -1
View File
@@ -127,7 +127,7 @@ class TestSheerkaPickler(TestUsingFileBasedSheerka):
sheerka.add_in_cache(concept) sheerka.add_in_cache(concept)
obj = {concept: "a"} obj = {concept: "a"}
flatten = SheerkaPickler(sheerka).flatten(obj) flatten = SheerkaPickler(sheerka).flatten(obj)
assert flatten == {':c:foo:1001:': 'a'} assert flatten == {'c:foo|1001:': 'a'}
decoded = SheerkaUnpickler(sheerka).restore(flatten) decoded = SheerkaUnpickler(sheerka).restore(flatten)
assert decoded == obj assert decoded == obj