from dataclasses import dataclass import core.builtin_helpers import core.utils from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept from core.concept import DEFINITION_TYPE_BNF, DEFINITION_TYPE_DEF from core.global_symbols import NotInit from core.sheerka.services.SheerkaExecute import ParserInput, SheerkaExecute from core.tokenizer import TokenKind, Keywords from parsers.BaseCustomGrammarParser import BaseCustomGrammarParser, SyntaxErrorNode, NameNode, CustomGrammarParserNode from parsers.BaseParser import ParsingError, UnexpectedTokenParsingError from parsers.BnfDefinitionParser import BnfDefinitionParser class ParsingException(Exception): def __init__(self, error): self.error = error @dataclass() class DefConceptParsingError(CustomGrammarParserNode, ParsingError): pass @dataclass() class CannotHandleParsingError(DefConceptParsingError): """ The input is not recognized """ text: str @dataclass() class DefConceptNode(CustomGrammarParserNode): name: NameNode = NotInit where: ReturnValueConcept = NotInit pre: ReturnValueConcept = NotInit post: ReturnValueConcept = NotInit body: ReturnValueConcept = NotInit ret: ReturnValueConcept = NotInit definition: ReturnValueConcept = NotInit definition_type: str = None @dataclass() class IsaConceptNode(CustomGrammarParserNode): concept: NameNode = NotInit set: NameNode = NotInit class DefConceptParser(BaseCustomGrammarParser): """ Parse sheerka specific grammar (like def concept) """ KEYWORDS = [Keywords.CONCEPT, Keywords.FROM, Keywords.AS, Keywords.WHERE, Keywords.PRE, Keywords.POST, Keywords.RET] KEYWORDS_VALUES = [k.value for k in KEYWORDS] def __init__(self, **kwargs): BaseCustomGrammarParser.__init__(self, "DefConcept", 60) def parse(self, context, parser_input: ParserInput): # default parser can only manage string text if parser_input.from_tokens: ret = context.sheerka.ret( self.name, False, context.sheerka.new(BuiltinConcepts.NOT_FOR_ME, body=parser_input)) self.log_result(context, parser_input, ret) return ret context.log(f"Parsing '{parser_input}' with FunctionParser", self.name) sheerka = context.sheerka if parser_input.is_empty(): return sheerka.ret(self.name, False, sheerka.new(BuiltinConcepts.IS_EMPTY)) if not self.reset_parser(context, parser_input): return self.sheerka.ret(self.name, False, context.sheerka.new(BuiltinConcepts.ERROR, body=self.error_sink)) self.parser_input.next_token() node = self.parse_def_concept() body = self.get_return_value_body(sheerka, parser_input.as_text(), node, node, self.error_sink) ret = sheerka.ret(self.name, not self.has_error, body) self.log_result(context, parser_input.as_text(), ret) return ret def parse_def_concept(self): """ def concept name [where xxx] [pre xxx] [post xxx] [as xxx] """ token = self.parser_input.token if token.value != Keywords.DEF.value: self.add_error(UnexpectedTokenParsingError("'def' keyword not found.", token, [Keywords.DEF])) return None self.context.log("Keyword DEF found.", self.name) keywords_found = [token] self.parser_input.next_token() # ## the definition of a concept consists of several parts # Keywords.CONCEPT to get the name of the concept # Keywords.FROM [Keywords.BNF] | [Keywords.DEF] to get the definition 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 # Keywords.RET to transform the concept into another concept parts = self.get_parts(self.KEYWORDS_VALUES, expected_first_token=Keywords.CONCEPT) if parts is None: return None keywords_found.extend([t[0] for t in parts.values()]) # keep track of all keywords found node = DefConceptNode(keywords_found) # if first_token.type == TokenKind.EOF: # return self.add_error(UnexpectedTokenParsingError([first_token], "Unexpected end of file", [Keywords.CONCEPT])) # get the name node.name = self.get_concept_name(parts[Keywords.CONCEPT]) # get definition node.definition_type, node.definition = self.get_concept_definition(node, parts) # get the bodies node.body = self.get_ast(Keywords.AS, parts) node.where = self.get_ast(Keywords.WHERE, parts) node.pre = self.get_ast(Keywords.PRE, parts) node.post = self.get_ast(Keywords.POST, parts) node.ret = self.get_ast(Keywords.RET, parts) return node def get_concept_name(self, tokens): name_tokens = core.utils.strip_tokens(tokens[1:]) if len(name_tokens) == 0: self.add_error(SyntaxErrorNode([], "Name is mandatory")) return None for token in name_tokens: if token.type == TokenKind.NEWLINE: self.add_error(SyntaxErrorNode([token], "Newline are not allowed in name.")) return None name_node = NameNode(name_tokens) # skip the first token return name_node def get_concept_definition(self, current_concept_def, parts): if Keywords.FROM not in parts: return None, NotInit tokens = parts[Keywords.FROM] if len(tokens) == 1: self.add_error(SyntaxErrorNode([], f"Empty '{tokens[0].value}' declaration."), False) return None, NotInit if tokens[1].value == Keywords.BNF.value: return self.get_concept_bnf_definition(current_concept_def, core.utils.strip_tokens(tokens[2:])) return self.get_concept_simple_definition(core.utils.strip_tokens(tokens[0:])) def get_concept_bnf_definition(self, current_concept_def, tokens): if len(tokens) == 0: self.add_error(SyntaxErrorNode([], "Empty 'bnf' declaration"), False) return None, NotInit if tokens[0].type == TokenKind.COLON: tokens = self.get_body(tokens[1:]) bnf_regex_parser = BnfDefinitionParser() desc = f"Resolving BNF {current_concept_def.definition}" with self.context.push(BuiltinConcepts.INIT_BNF, current_concept_def, who=self.name, obj=current_concept_def, desc=desc) as sub_context: parsing_result = bnf_regex_parser.parse(sub_context, tokens) sub_context.add_values(return_values=parsing_result) if not parsing_result.status: self.add_error(parsing_result.value) return None, NotInit return DEFINITION_TYPE_BNF, parsing_result def get_concept_simple_definition(self, tokens): start = 2 if tokens[1].value == Keywords.DEF.value else 1 tokens = core.utils.strip_tokens(tokens[start:]) if len(tokens) == 0: self.add_error(SyntaxErrorNode([], f"Empty 'from' declaration."), False) return None, NotInit if tokens[0].type == TokenKind.COLON: tokens = self.get_body(tokens[1:]) return DEFINITION_TYPE_DEF, NameNode(tokens) def get_ast(self, keyword, parts): if keyword not in parts: return NotInit tokens = parts[keyword] if len(tokens) == 1: self.add_error(SyntaxErrorNode(tokens, f"Empty '{tokens[0].value}' declaration.")) return None source = self.sheerka.services[SheerkaExecute.NAME].get_parser_input(None, tokens[1:]) parsed = self.sheerka.parse_unrecognized(self.context, source, parsers="all", who=self.name, prop=keyword, filter_func=core.builtin_helpers.expect_one) if not parsed.status: self.add_error(parsed.value) return None return parsed