diff --git a/.gitignore b/.gitignore index 13d1490..e987c98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -# ---> Python # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -48,7 +47,6 @@ htmlcov/ nosetests.xml coverage.xml *.cover -*.py,cover .hypothesis/ .pytest_cache/ @@ -60,7 +58,6 @@ coverage.xml *.log local_settings.py db.sqlite3 -db.sqlite3-journal # Flask stuff: instance/ @@ -85,19 +82,8 @@ ipython_config.py # pyenv .python-version -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff +# celery beat schedule file celerybeat-schedule -celerybeat.pid # SageMath parsed files *.sage.py @@ -129,3 +115,27 @@ dmypy.json # Pyre type checker .pyre/ +wap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +#VSCODE +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/Ft_op.py b/Ft_op.py new file mode 100644 index 0000000..275d640 --- /dev/null +++ b/Ft_op.py @@ -0,0 +1,14 @@ +class Ft_op: + + # def __init__ (self): + def ft_and(a, b): + return a & b + + def ft_or(a, b): + return a | b + + def ft_xor(a, b): + return a ^ b + + def ft_not(a, b): + return not a diff --git a/InferenceEngine.py b/InferenceEngine.py new file mode 100644 index 0000000..faa4bca --- /dev/null +++ b/InferenceEngine.py @@ -0,0 +1,60 @@ +from fact import Fact, check_fact +from user import * +from Ft_op import Ft_op +from config import * +import config + +@v_args(inline=True) # Affects the signatures of the methods +class InferenceEngine(Transformer): + + def iter_subtree(self, fact_tree): + for fact in fact_tree.children: + self.set_fact(str(fact)) + + def set_fact(self, *string): + for letter in string: + self.assign_var(letter, True) + + def print_state(self, string): + for letter in string: + print(letter, '=', self.var(letter)) + + def apply_func(self, tree): + if (isinstance(tree, Tree) == False): + return self.var(str(tree)) + else: + return getattr(Ft_op, tree.data)(self.apply_func(tree.children[0]), + self.apply_func(tree.children[1] if len(tree.children) > 1 else 0)) + + def assign_var(self, name, state): + config.fact_dict[name] = Fact(state=state, key=name, isset=True) + return state + + def var(self, name): + if (config.glob != True): + return name + if (config.fact_dict.get(name) == None): + config.fact_dict[name] = Fact(key=name) + return config.fact_dict[name].get_state(self) + + + def set_state(self, tree, op_tree, is_not=False, is_iff=False): + if (isinstance(tree, Tree) == False): + check_fact(str(tree)) + config.fact_dict[str(tree)].trees.append((op_tree, is_not, is_iff)) + elif (tree.data == 'ft_and'): + self.set_state(tree.children[0], op_tree, is_not, is_iff) + self.set_state(tree.children[1], op_tree, is_not, is_iff) + elif (tree.data == 'ft_not'): + self.set_state(tree.children[0], op_tree, not is_not) + elif (tree.data == 'ft_or'): + choice = ask_or(tree) + if (choice is 2): + self.set_state(tree.children[0], op_tree, is_not, is_iff) + self.set_state(tree.children[1], op_tree, is_not, is_iff) + else: + self.set_state(tree.children[choice], op_tree, is_not, is_iff) + elif (tree.data == 'ft_xor'): + choice = ask_xor(tree) + self.set_state(tree.children[choice], op_tree, False, is_iff) + self.set_state(tree.children[not choice], op_tree, True, is_iff) diff --git a/auteur b/auteur new file mode 100644 index 0000000..c978531 --- /dev/null +++ b/auteur @@ -0,0 +1,2 @@ +ygarrot +gbrochar diff --git a/bonus b/bonus new file mode 100644 index 0000000..8ca62ef --- /dev/null +++ b/bonus @@ -0,0 +1,9 @@ +or +xor +iff +interactive +reset +reset --hard +pretty graph + + diff --git a/config.py b/config.py new file mode 100644 index 0000000..1f0ab53 --- /dev/null +++ b/config.py @@ -0,0 +1,8 @@ +from lark import Lark, Transformer, v_args, Tree +from InferenceEngine import InferenceEngine +computer = InferenceEngine() +pstr ="\033[1;32m--->\033[0m" +skip = False +glob = False +fact_dict = {} + diff --git a/fact.py b/fact.py new file mode 100644 index 0000000..6aed5db --- /dev/null +++ b/fact.py @@ -0,0 +1,66 @@ +import sys +from user import * +from lark import Tree +import config + +def check_fact(key, state=False): + + if (config.fact_dict.get(key) == None): + config.fact_dict[key] = Fact(state, key) + +class Fact: + + def __init__(self, state = False, key = 0, isset=False): + self.trees = list() + self.isset = isset + self.mutex_lock = 0 + self.key = key + self.state = state + + def check_fact(self,fact): + if (self.key == str(fact)): + return 1 + + def remove_tree(self, tree, idx, computer): + if (config.skip): + sys.exit('Conflict between rules') + choice = 'w' + st = ("<=>" if tree[2] else "=>") + ('!' if tree[1] else '') + self.key + while choice not in ['y', 'n']: + choice = input(str("There is an error in operation, would you like to remove this one ? {" + + set_choices(tree[0]) + st+ "} y/n?\n")) + if (choice == 'n'): + sys.exit('Conflict between rules') + self.trees.pop(idx) + return self.get_state(computer) + + def check_imply(self,tree): + ret = 0 + if (isinstance(tree, Tree)): + fact = tree.find_data("query") + for truc in fact: + ret = self.check_fact(fact) + else: + ret = self.check_fact(tree) + return ret + + def get_state(self, computer): + if (self.mutex_lock >= 2): + return self.state + self.mutex_lock += 1 + config.glob = True + idx =0 + for tree in self.trees: + is_set = self.isset is True or idx > 0 + if (self.check_imply(tree[0]) and tree[1] is True): + return self.remove_tree(tree, idx, computer) + new_state = computer.apply_func(tree[0]) + if new_state is False : + continue + new_state = False if tree[1] and new_state is True else True + if (tree[2] is False and is_set and new_state != self.state): + return self.remove_tree(tree, idx, computer) + self.state |= new_state + idx+=1 + self.mutex_lock -= 1 + return self.state diff --git a/interactive_m.py b/interactive_m.py new file mode 100644 index 0000000..b3c7b80 --- /dev/null +++ b/interactive_m.py @@ -0,0 +1,71 @@ +import config +from config import * +from parse import * + +interactive_grammar = r""" + ?start: imply + | initial_fact | query + + ?imply: xor "=>" xor -> imply + | xor "<=>" xor -> iff + ?xor: or + | xor "^" or -> ft_xor + ?or: and + | or "|" and -> ft_or + ?and: atom + | and "+" atom -> ft_and + ?atom: UCASE_LETTER -> var + | "!" atom -> ft_not + | "(" xor ")" + + ?initial_fact: "=" UCASE_LETTER* -> set_fact + ?query: "?" UCASE_LETTER+ ->query + + _LI: (_COMMENT | LF+) + _COMMENT: /#[^\n].*\n/ + + %import common.UCASE_LETTER + %import common.NUMBER + %import common.WS_INLINE + %import common.LF + %ignore WS_INLINE + %ignore _COMMENT +""" + + +def reset_states(): + for idx in fact_dict: + fact_dict[idx].state = False + fact_dict[idx].is_set = False + +def interactive(): + print('Welcome to the best expert system in the world wide web') + calc_parser = Lark(interactive_grammar, parser='lalr', + debug=True, transformer=computer) + while True: + try: + s = input('> ') + except EOFError: + break + if (s == 'exit'): + exit('Good bye') + continue + elif (s == 'reset'): + reset_states() + continue + elif (s == 'reset --hard'): + config.fact_dict = {} + continue + + try: + tree = calc_parser.parse(s) + except UnexpectedInput as e: + print(e) + continue + if (tree == None): + continue + if (tree.data == "imply" or tree.data == "iff"): + set_trees(tree) + else: + query(tree) + diff --git a/parse.py b/parse.py new file mode 100644 index 0000000..377af07 --- /dev/null +++ b/parse.py @@ -0,0 +1,103 @@ +import argparse +from lark import Lark, Transformer, v_args, Tree, UnexpectedInput +import traceback +import logging +from config import * +import config +import interactive_m + +try: + input = raw_input # For Python2 compatibility +except NameError: + pass + +calc_grammar = r""" + ?start: (imply _LI)+ initial_fact _LI query _LI + + ?imply: xor "=>" xor -> imply + | xor "<=>" xor -> iff + ?xor: or + | xor "^" or -> ft_xor + ?or: and + | or "|" and -> ft_or + ?and: atom + | and "+" atom -> ft_and + ?atom: UCASE_LETTER -> var + | "!" atom -> ft_not + | "(" xor ")" + + ?initial_fact: "=" UCASE_LETTER* -> set_fact + ?query: "?" UCASE_LETTER+ ->query + + _LI: (_COMMENT | LF+) + _COMMENT: /#[^\n].*\n/ + + %import common.UCASE_LETTER + %import common.NUMBER + %import common.WS_INLINE + %import common.LF + %ignore WS_INLINE + %ignore _COMMENT +""" + +def set_fact(tree): + ifact = tree.find_data("initial_fact") + for fact in ifact: + computer.iter_subtree(fact) + +def set_trees(tree): + implies = tree.find_data("imply") + for imply in list(implies): + new_tree = imply.children[0] + computer.set_state(imply.children[1], new_tree) + iffs = tree.find_data("iff") + for iff in list(iffs): + new_tree1 = iff.children[0] + new_tree2 = iff.children[1] + computer.set_state(iff.children[1], new_tree1, is_iff=True) + computer.set_state(iff.children[0], new_tree2, is_iff=True) + +def query(tree): + config.glob = True + queries = list(tree.find_data("query")) + if (len(queries) <= 0): + return + st = str() + for token in queries[0].children: + st += str(token) + computer.print_state(st) + +def test(args): + if (args.interactive == True): + interactive_m.interactive() + return + calc_parser = Lark(calc_grammar, parser='lalr', + debug=True, transformer=computer) + with open(args.path, 'r') as myfile: + string=myfile.read() + print(string) + try: + tree = calc_parser.parse(string) + except UnexpectedInput as e: + print(e) + return + if (args.pretty): + print(tree.pretty(pstr)) + set_trees(tree) + query(tree) + +def main(): + parser = argparse.ArgumentParser(description='Smarter expert system you have ever seen') + parser.add_argument("-i", "--interactive", default=False, action="store_true", + help="interactive expert system") + parser.add_argument("-b", "--batch", default=False, action="store_true", + help="run install in batch mode (without manual intervention)") + parser.add_argument("-p", "--pretty", default=False, action="store_true", + help="print pretty trees") + parser.add_argument("path", type=str, default="none", help="input file name") + args = parser.parse_args() + config.skip = args.batch + test(args) + +if __name__ == '__main__': + main() diff --git a/test/basic1 b/test/basic1 new file mode 100644 index 0000000..5b931fb --- /dev/null +++ b/test/basic1 @@ -0,0 +1,13 @@ +B => A +D + E => B +G + H => F +I + J => G +G => H +L + M => K +O + P => L + N +N => M + +=DEIJOP +?AFKP + +#AFKP is true diff --git a/test/basic2 b/test/basic2 new file mode 100644 index 0000000..fd23c9d --- /dev/null +++ b/test/basic2 @@ -0,0 +1,13 @@ +B => A +D + E => B +G + H => F +I + J => G +G => H +L + M => K +O + P => L + N +N => M + +=DEIJP +?AFKP + +#AFP is true, K is false diff --git a/test/iff1 b/test/iff1 new file mode 100644 index 0000000..a0788d8 --- /dev/null +++ b/test/iff1 @@ -0,0 +1,8 @@ + A <=> B + C <=> D + C => A + =D + + ?ABCD + +# ABCD should be true diff --git a/test/neg1 b/test/neg1 new file mode 100644 index 0000000..9e1aa4c --- /dev/null +++ b/test/neg1 @@ -0,0 +1,6 @@ +B + !C => A + += +?A + +# A should be false diff --git a/test/neg2 b/test/neg2 new file mode 100644 index 0000000..8d68af5 --- /dev/null +++ b/test/neg2 @@ -0,0 +1,6 @@ +B + !C => A + +=B +?A + +# A should be true diff --git a/test/neg3 b/test/neg3 new file mode 100644 index 0000000..8df39ad --- /dev/null +++ b/test/neg3 @@ -0,0 +1,6 @@ +B + !C => A + +=C +?A + +# A should be false diff --git a/test/neg4 b/test/neg4 new file mode 100644 index 0000000..b921d9e --- /dev/null +++ b/test/neg4 @@ -0,0 +1,6 @@ +B + !C => A + +=BC +?A + +# A should be false diff --git a/test/not_conclu1 b/test/not_conclu1 new file mode 100644 index 0000000..a280588 --- /dev/null +++ b/test/not_conclu1 @@ -0,0 +1,5 @@ +A => C +B => !C + += +?ABC diff --git a/test/not_conclu2 b/test/not_conclu2 new file mode 100644 index 0000000..ebeca92 --- /dev/null +++ b/test/not_conclu2 @@ -0,0 +1,5 @@ +A => C +B => !C + +=A +?ABC diff --git a/test/not_conclu3 b/test/not_conclu3 new file mode 100644 index 0000000..d6229f9 --- /dev/null +++ b/test/not_conclu3 @@ -0,0 +1,5 @@ +A => C +B => !C + +=B +?ABC diff --git a/test/not_conclu4 b/test/not_conclu4 new file mode 100644 index 0000000..c4a70bc --- /dev/null +++ b/test/not_conclu4 @@ -0,0 +1,5 @@ +A => C +B => !C + +=AB +?ABC diff --git a/test/not_conclu5 b/test/not_conclu5 new file mode 100644 index 0000000..64835f5 --- /dev/null +++ b/test/not_conclu5 @@ -0,0 +1,5 @@ +B => !C +A => C + +=ABC +?ABC diff --git a/test/or1 b/test/or1 new file mode 100644 index 0000000..310dbb3 --- /dev/null +++ b/test/or1 @@ -0,0 +1,8 @@ +B + C => A +D | E => B +B => C + += +?A + +# A should be false diff --git a/test/or2 b/test/or2 new file mode 100644 index 0000000..4f8e318 --- /dev/null +++ b/test/or2 @@ -0,0 +1,8 @@ +B + C => A +D | E => B +B => C + +=D +?A + +# A should be true diff --git a/test/or3 b/test/or3 new file mode 100644 index 0000000..5b7f081 --- /dev/null +++ b/test/or3 @@ -0,0 +1,8 @@ +B + C => A +D | E => B +B => C + +=E +?A + +# A should be true diff --git a/test/or4 b/test/or4 new file mode 100644 index 0000000..4041b2f --- /dev/null +++ b/test/or4 @@ -0,0 +1,8 @@ +B + C => A +D | E => B +B => C + +=DE +?A + +# A should be true diff --git a/test/or_conclu1 b/test/or_conclu1 new file mode 100644 index 0000000..a7e6348 --- /dev/null +++ b/test/or_conclu1 @@ -0,0 +1,6 @@ +A => B | C +B => D +C => E | B + +=A +?ABCDE diff --git a/test/par1 b/test/par1 new file mode 100644 index 0000000..6abe467 --- /dev/null +++ b/test/par1 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + += +?E + +# E should be false diff --git a/test/par10 b/test/par10 new file mode 100644 index 0000000..e74c4f5 --- /dev/null +++ b/test/par10 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=FH +?E + +# E should be true diff --git a/test/par11 b/test/par11 new file mode 100644 index 0000000..df5b0e4 --- /dev/null +++ b/test/par11 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=GH +?E + +# E should be true diff --git a/test/par2 b/test/par2 new file mode 100644 index 0000000..17af46c --- /dev/null +++ b/test/par2 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=A +?E + +# E should be true diff --git a/test/par3 b/test/par3 new file mode 100644 index 0000000..25cb4a6 --- /dev/null +++ b/test/par3 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=B +?E + +# E should be false diff --git a/test/par4 b/test/par4 new file mode 100644 index 0000000..6eed071 --- /dev/null +++ b/test/par4 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=C +?E + +# E should be false diff --git a/test/par5 b/test/par5 new file mode 100644 index 0000000..9ba4150 --- /dev/null +++ b/test/par5 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=AC +?E + +# E should be true diff --git a/test/par6 b/test/par6 new file mode 100644 index 0000000..c74ce5e --- /dev/null +++ b/test/par6 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=BC +?E + +# E should be true diff --git a/test/par7 b/test/par7 new file mode 100644 index 0000000..277a486 --- /dev/null +++ b/test/par7 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=F +?E + +# E should be false diff --git a/test/par8 b/test/par8 new file mode 100644 index 0000000..d8ae1cc --- /dev/null +++ b/test/par8 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=G +?E + +# E should be false diff --git a/test/par9 b/test/par9 new file mode 100644 index 0000000..c65b9d6 --- /dev/null +++ b/test/par9 @@ -0,0 +1,7 @@ +A | B + C => E +(F | G) + H => E + +=H +?E + +# E should be false diff --git a/test/same_conclu1 b/test/same_conclu1 new file mode 100644 index 0000000..41c6068 --- /dev/null +++ b/test/same_conclu1 @@ -0,0 +1,7 @@ +B => A +C => A + += +?A + +# A should be false diff --git a/test/same_conclu2 b/test/same_conclu2 new file mode 100644 index 0000000..be8aab1 --- /dev/null +++ b/test/same_conclu2 @@ -0,0 +1,7 @@ +B => A +C => A + +=B +?A + +# A should be true diff --git a/test/same_conclu3 b/test/same_conclu3 new file mode 100644 index 0000000..c95ba96 --- /dev/null +++ b/test/same_conclu3 @@ -0,0 +1,7 @@ +B => A +C => A + +=C +?A + +# A should be true diff --git a/test/same_conclu4 b/test/same_conclu4 new file mode 100644 index 0000000..183f171 --- /dev/null +++ b/test/same_conclu4 @@ -0,0 +1,7 @@ +B => A +C => A + +=BC +?A + +# A should be true diff --git a/test/xor1 b/test/xor1 new file mode 100644 index 0000000..e9d3f59 --- /dev/null +++ b/test/xor1 @@ -0,0 +1,8 @@ +B + C => A +D ^ E => B +B => C + += +?A + +# A should be false diff --git a/test/xor2 b/test/xor2 new file mode 100644 index 0000000..1da20b7 --- /dev/null +++ b/test/xor2 @@ -0,0 +1,8 @@ +B + C => A +D ^ E => B +B => C + +=D +?A + +# A should be true diff --git a/test/xor3 b/test/xor3 new file mode 100644 index 0000000..840e8af --- /dev/null +++ b/test/xor3 @@ -0,0 +1,8 @@ +B + C => A +D ^ E => B +B => C + +=E +?A + +# A should be true diff --git a/test/xor4 b/test/xor4 new file mode 100644 index 0000000..3ad51a1 --- /dev/null +++ b/test/xor4 @@ -0,0 +1,8 @@ +B + C => A +D ^ E => B +B => C + +=DE +?A + +# A should be false diff --git a/test/xor_conclu1 b/test/xor_conclu1 new file mode 100644 index 0000000..654ff74 --- /dev/null +++ b/test/xor_conclu1 @@ -0,0 +1,6 @@ +A => B ^ C +B => D +C => E ^ B + +=A +?ABCDE diff --git a/unit.sh b/unit.sh new file mode 100644 index 0000000..f246693 --- /dev/null +++ b/unit.sh @@ -0,0 +1,6 @@ +for file in test/* +do + python3 parse.py $file + echo "\n\n\033[32mPress ENTER\033[0m\n" + read +done diff --git a/user.py b/user.py new file mode 100644 index 0000000..e801419 --- /dev/null +++ b/user.py @@ -0,0 +1,25 @@ +import config +from config import * +def set_choices(tree): + if (isinstance(tree, Tree) == False): + return str(tree) + else: + return tree.pretty().replace('\n', '') + +def ask_xor(tree): + if (config.skip is True): + return 0 + choices = [set_choices(tree.children[0]), set_choices(tree.children[1])] + choice = 0 + while choice not in [ '0', '1']: + choice = input(str('Would you like to choose 0:{'+ choices[0]+ '} or 1:{'+ choices[1]+ '}\n')) + return int(choice) + +def ask_or(tree): + if (config.skip is True): + return 0 + choices = [set_choices(tree.children[0]), set_choices(tree.children[1])] + choice = 0 + while choice not in [ '0', '1', '2']: + choice = input(str('Would you like to choose 0:{'+ choices[0]+ '} or 1:{'+ choices[1]+ '} or 2: for both\n')) + return int(choice)