diff --git a/src/block.py b/src/block.py index 8fc620bced3c5c3e86ad6f2d1ba78cf359c44210..c58d05d32044bf4af66e7458be540a9a2925fe17 100644 --- a/src/block.py +++ b/src/block.py @@ -13,14 +13,67 @@ class Block(Root): def get_type(self): return "Root" - def get_channels(self): - return self.channels+self.origin.get_channels() + def same_condition(self, block): + return self.condition.same_condition(block.condition) + + def get_blocks_with_same_conditions(self, condition): + tab = self.origin.get_blocks_with_same_conditions(self) + return tab + + #def get_channels(self): + # blocks_with_same_condition = self.get_blocks_with_same_conditions(self.condition) + # channels_in_other_blocks = [] + # for b in blocks_with_same_condition: + # channels_in_other_blocks+=b.channels + # return self.channels+self.origin.get_channels()+channels_in_other_blocks + #This method returns all the executors of a block and the block above it + #As well as the executors with the same condition on the same level def get_executors(self): - return self.executors+self.origin.get_executors() + blocks_with_same_condition = self.get_blocks_with_same_conditions(self.condition) + executors_in_other_blocks = [] + for b in blocks_with_same_condition: + executors_in_other_blocks+=b.executors + return self.executors+self.origin.get_executors()+executors_in_other_blocks def get_structure(self, dico): return super().get_structure(dico) + + + #This method returns all the executors inside a block + def get_above_executors_rec(self, dico = {}): + for e in self.executors: + dico[e] = '' + self.origin.get_above_executors_rec(dico) + + def get_above_executors(self, dico = {}): + self.origin.get_above_executors_rec(dico) + return list(dico.keys()) + + + def get_calls_above_level(self): + tab = [] + for e in self.get_above_executors(): + if(e.get_type()=="Call"): + tab.append(e) + elif(e.get_type()=="Operation"): + for o in e.get_origins(): + if(o.get_type()=="Call"): + tab.append(o) + return tab + + ############# + # CHANNELS + ############# + def get_channels_above_level_rec(self, dico = {}): + for c in self.channels: + dico[c] = '' + self.origin.get_channel_from_name_above_level_rec(dico) + + def get_channels_above_level(self, dico = {}): + dico = {} + self.origin.get_channels_above_level_rec(dico) + return list(dico.keys()) #def check_in_channels(self, channel): # for c in self.get_channels(): diff --git a/src/call.py b/src/call.py index abdd3ba96047409224e5ad09047c3105d224862e..7d47860ae35b3d1a19d5e1b62d3c40f583a64a89 100644 --- a/src/call.py +++ b/src/call.py @@ -26,6 +26,17 @@ class Call(Executor): #It's important this is last #self.condition = Condition(self) + #This method returns all the calls inside a call eg p1(p2(), p3()) returns [p1(p2(), p3()), p2(), p3()] + def get_all_calls(self): + tab = [] + tab.append(self) + for param in self.parameters: + if(param.get_type()=="Operation"): + for o in param.get_origins(): + if(o.get_type()=="Call"): + tab+=o.get_all_calls() + return tab + def add_to_emits(self, emitted): self.emits.append(emitted) @@ -138,16 +149,26 @@ class Call(Executor): #Case it's a channel if(re.fullmatch(constant.WORD, param) and not analysed_param): #if(re.fullmatch(constant.WORD, param) and not analysed_param or param in ['[]'] or param[:7]=="params."): - from .channel import Channel - channel = Channel(name=param, origin=self.origin) - if(not self.origin.check_in_channels(channel)): + + channels = self.origin.get_channels_from_name_same_level(param) + if(channels==[]): + channels = self.origin.get_channels_from_name_inside_level(param) + if(channels==[]): + channels = self.origin.get_channels_from_name_above_level(param) + if(channels==[]): + from .channel import Channel + channel = Channel(name=param, origin=self.origin) self.origin.add_channel(channel) - else: - channel = self.origin.get_channel_from_name(param) - #TODO -> check this - channel.add_sink(self) - self.parameters.append(channel) + channels = [channel] + from .operation import Operation + ope = Operation(param, self) + for channel in channels: + channel.add_sink(self) + ope.add_element_origins(channel) + self.parameters.append(ope) analysed_param = True + + else: from .executor import Executor executor = Executor(param, self) @@ -399,7 +420,7 @@ class Call(Executor): if(self.get_duplicate_status()): temp = subworkflow subworkflow = subworkflow.copy() - subworkflow.set_alias(subworkflow.get_alias()) + subworkflow.set_alias(temp.get_alias()) subworkflow.initialise() self.first_element_called = subworkflow self.origin.add_element_to_elements_being_called(subworkflow) @@ -429,7 +450,7 @@ class Call(Executor): file.write(" "*(tab+1)+"* Called "+str(self.get_called())+"\n") file.write(" "*(tab+1)+"* Code : "+ str(self.get_code())+"\n") file.write(" "*(tab+1)+"* Parameters"+"\n") - for p in self.parameters: + for p in self.parameters: file.write(" "*(tab+3)+p.get_code()+f" '{p.get_type()}'"+"\n") file.write("\n") diff --git a/src/condition.py b/src/condition.py index f081c0126b02a6ed0e4a5466b27643e3971a70fb..243cca04633c97c2e6e176f338d427d9d0e5453d 100644 --- a/src/condition.py +++ b/src/condition.py @@ -4,6 +4,9 @@ from .outils import extract_conditions class Condition: def __init__(self, origin, condition): self.origin = origin - self.condition = condition + self.value = condition #self.initialise() + def same_condition(self, condition): + return self.value == condition.value + diff --git a/src/executor.py b/src/executor.py index 845b5ca76b48968d6dc3547512a7e14771d59ccf..c7ada07e9a0559092b85cc8e97fd3012193a1af0 100644 --- a/src/executor.py +++ b/src/executor.py @@ -204,22 +204,35 @@ class Executor(Nextflow_Building_Blocks): return Call(self.get_code(), self.origin) - #Method which returns the call which calls the element called - def get_call_by_name(self, name): - #This is an old comment: - #We get the calls that have already been analysed or which are currently being analysed - #for example "p(a.out)" -> the call for 'a' may not have been analysed yet - #In that case when calling "get_calls" -> we don't want to reanalyse the "p(a.out)" - + #Method which returns the calls which call the element called + def get_calls_by_name(self, name): + tab = [] if(self.origin.get_type() in ['Root', 'Block']): - for c in self.origin.get_calls(): - #c.initialise()#Don't need to analyse the call cause the element called is already analysed when the call is created - if(c.first_element_called.get_alias()==name): - return c - return None - + for call in self.origin.get_calls_same_level(): + #call.initialise() + for c in call.get_all_calls(): + if(c.first_element_called.get_alias()==name): + tab.append(c) + #if(c.first_element_called.get_alias()==name): + # tab.append(c) + #Here it is important that BioFlow-Insight is not a Nextflow verificator + #Here i'm checking the call inside the block + if(len(tab)==0): + for call in self.origin.get_calls_inside_level(): + #call.initialise() + for c in call.get_all_calls(): + if(c.first_element_called.get_alias()==name): + tab.append(c) + if(len(tab)==0): + for call in self.origin.get_calls_above_level(): + #call.initialise() + for c in call.get_all_calls(): + if(c.first_element_called.get_alias()==name): + tab.append(c) + return tab + else: - return self.origin.get_call_by_name(name) + return self.origin.get_calls_by_name(name) diff --git a/src/operation.py b/src/operation.py index b5add767c455b1d9abb70e2687d485072d7db7ac..6bdd6d3ba7347b0ac01c8588f783cf1ff7c89f86 100644 --- a/src/operation.py +++ b/src/operation.py @@ -85,17 +85,31 @@ class Operation(Executor): #Check that the name is not the list of illegal words #and Check that the thing extarcted is not WorkflowNameFile like 'WorkflowHgtseq' in nf-core/hgtseq if(name not in constant.ERROR_WORDS_ORIGINS):# and name.lower()!=f"workflow{self.get_name_file().lower()}"): - channel = Channel(name=name, origin=self.origin) + #channel = Channel(name=name, origin=self.origin) if(self.origin.get_type()!="Subworkflow"): - if(not self.origin.check_in_channels(channel)): - self.origin.add_channel(channel) - else: - channel = self.origin.get_channel_from_name(name) + #First check that the channel is not defined at the same level + channels = self.origin.get_channels_from_name_same_level(name) + #Then check that the channel is defined in the below level + if(channels==[]): + channels = self.origin.get_channels_from_name_inside_level(name) + #Finally check if the channels is defined above + if(channels==[]): + channels = self.origin.get_channels_from_name_above_level(name) + #If it still doesn't exist -> we create it + if(channels==[]): + channel = Channel(name=name, origin=self.origin) + self.origin.add_channel(channel) + channels = [channel] + else: + channel = Channel(name=name, origin=self.origin) self.origin.takes_channels.append(channel) - self.origins.append(channel) - #channel.initialise() - channel.add_sink(self) + channels = [channel] + + for channel in channels: + self.origins.append(channel) + #channel.initialise() + channel.add_sink(self) @@ -117,37 +131,21 @@ class Operation(Executor): if( splited[-1] in constant.LIST_OPERATORS): full_code = '.'.join(splited[:-1]) if(name_called not in IGNORE_NAMES): - call = self.get_call_by_name(name_called) - #process = self.get_process_from_name(name_called) - #subworkflow = self.get_subworkflow_from_name(name_called) - # - #if(process!=None and subworkflow!=None): - # raise Exception(f"Problem in get_element -> {name_called} exists as process and subworkflow") - ##Case subworkflow - #if(process==None and subworkflow!=None): - # emitted = Emitted(name=full_code, origin=self.origin, emitted_by=subworkflow) - # emitted.set_emits(name_emitted) - ##Case Process - #if(process!=None and subworkflow==None): - # emitted = Emitted(name=full_code, origin=self.origin, emitted_by=process) - # #TODO -> analyse the outputs of the process - # - #if(process==None and subworkflow==None): - # if(name_called[:5]=="Call_"): - # name_called = self.calls[name_called].get_code() - # raise BioFlowInsightError(f"The call for '{name_called}' coudn't be found, before its use in the operation '{self.get_code(get_OG=True)}'{self.get_string_line(self.get_code(get_OG=True))}. Either because the call wasn't made before the operation or that the element it is calling doesn't exist.", num =8, origin=self) - - if(call!=None): - emitted = Emitted(name=full_code, origin=self.origin, emitted_by=call) - emitted.set_emits(name_emitted) + calls = self.get_calls_by_name(name_called) + + if(calls!=[]): + for call in calls: + emitted = Emitted(name=full_code, origin=self.origin, emitted_by=call) + emitted.set_emits(name_emitted) + emitted.add_sink(self) + self.origins.append(emitted) else: if(name_called[:5]=="Call_"): name_called = self.calls[name_called].get_code() - raise BioFlowInsightError(f"The call for '{name_called}' coudn't be found, before its use in the operation '{self.get_code(get_OG=True)}'{self.get_string_line(self.get_code(get_OG=True))}. Either because the call wasn't made before the operation or that the element it is calling doesn't exist.", num =8, origin=self) + raise BioFlowInsightError(f"The call for '{name_called}' coudn't be found, before its use in the operation '{self.get_code(get_OG=True)}'{self.get_string_line(self.get_code(get_OG=True))}. Either because the call wasn't made before the operation or that the element it is calling doesn't exist.", num =8, origin=self) - emitted.add_sink(self) - self.origins.append(emitted) + #This methods checks if the input is an emit and adds it if it's the case, it also returns T/F if it's an emit def check_is_emit(self, name): @@ -195,17 +193,28 @@ class Operation(Executor): raise Exception("This shoudn't happen! -> a call is taking a value") else: - print("here") - channel = Channel(name=name, origin=self.origin) - print(self.get_code(), name, self.get_file_address()) - if(not self.origin.check_in_channels(channel)): - self.origin.add_channel(channel) + + origin = None + #Case the emited thing is an operation -> of type 'ch = existing_channel' + if(self.origin.get_type()=="Subworkflow"): + origin = self.origin.root else: - channel = self.origin.get_channel_from_name(name) - - self.gives.append(channel) - #channel.initialise() - channel.add_source(self) + origin = self.origin + + channels = origin.get_channels_from_name_same_level(name) + if(channels==[]): + channels = origin.get_channels_from_name_inside_level(name) + if(channels==[]): + channels = origin.get_channels_from_name_above_level(name) + if(channels==[]): + channel = Channel(name=name, origin=self.origin) + origin.add_channel(channel) + channels = [channel] + + for channel in channels: + self.gives.append(channel) + #channel.initialise() + channel.add_source(self) def add_origin(self, name): name = name.strip() diff --git a/src/outils.py b/src/outils.py index 84da8804f8eb0ece59bb8ab29f83048940ff76fc..f1bb04fc59135aa441e2536ffc6bfe4aa2282d36 100644 --- a/src/outils.py +++ b/src/outils.py @@ -922,6 +922,7 @@ def is_git_directory(path = '.'): def extract_conditions(code): conditions_dico = {} + index_condition = 0 start = 0 @@ -997,7 +998,8 @@ def extract_conditions(code): conditions.append(condition) end = extract_curly(code, match.span(0)[1]+start)#Here we nedd to add the start index since we're only working on a subpart of code start_inside, end_inside = match.span(0)[1]+start, end-1 - conditions_dico[condition] = (start_inside, end_inside) + conditions_dico[f"{condition}$$__$${index_condition}"] = (start_inside, end_inside) + index_condition+=1 #conditions_dico = adding_inside(conditions_dico, code, start_inside, end_inside) break searching_for_else = True @@ -1016,7 +1018,8 @@ def extract_conditions(code): start_else+=end end_else = extract_curly(code, end_else+end) start_inside, end_inside = match.span(0)[1]+end, end_else-1 - conditions_dico[condition] = (start_inside, end_inside) + conditions_dico[f"{condition}$$__$${index_condition}"] = (start_inside, end_inside) + index_condition+=1 #conditions_dico = adding_inside(conditions_dico, code, start_inside, end_inside) break #CASE of "else" @@ -1026,7 +1029,9 @@ def extract_conditions(code): start_else+=end end_else = extract_curly(code, end_else+end) start_inside, end_inside = match.span(0)[1]+end, end_else-1 - conditions_dico[' && '.join(["!({})".format(v) for v in conditions])] = (start_inside, end_inside) + condition = ' && '.join(["!({})".format(v) for v in conditions]) + conditions_dico[f"{condition}$$__$${index_condition}"] = (start_inside, end_inside) + index_condition+=1 #conditions_dico = adding_inside(conditions_dico, code, start_inside, end_inside) break diff --git a/src/root.py b/src/root.py index 24f7a310d6e3b2b7c64083c0abdd8e77f489fd91..2e585d7be6bfd4e5ac6af1f2c4f10bcd71ed88c6 100644 --- a/src/root.py +++ b/src/root.py @@ -19,43 +19,179 @@ class Root(Nextflow_Building_Blocks): self.elements_being_called = [] self.channels = subworkflow_inputs + + ############# + # GENERAL + ############# def get_type(self): return "Root" + def get_blocks(self): + return self.blocks + def add_element_to_elements_being_called(self, element): self.elements_being_called.append(element) - def check_in_channels(self, channel): - for c in self.get_channels(): - if(c.equal(channel)): - return True - return False + def get_blocks_with_same_conditions(self, searching_block): + tab = [] + for block in self.blocks: + if(block != searching_block): + if(block.same_condition(searching_block)): + tab.append(block) + return tab + + + ############# + # CHANNELS + ############# + + def get_channels_from_name_same_level(self, name): + tab = [] + for c in self.channels: + if(c.get_name()==name): + tab.append(c) + return tab + + def get_channels_above_level(self): + return [] - def get_channels(self): - return self.channels - - def add_channel(self, channel): - if(not self.check_in_channels(channel)): - self.channels.append(channel) - else: - raise Exception("This shoudn't happen!") + def get_channels_above_level_rec(self, dico = {}): + for c in self.channels: + dico[c] = '' + + def get_channels_from_name_above_level(self, name): + tab = [] + for c in self.get_channels_above_level(): + if(c.get_name()==name): + tab.append(c) + return tab - def get_channel_from_name(self, name): - for c in self.get_channels(): - if(name == c.get_name()): - return c - #raise Exception(f"{name} is not in the list of channels") - return None + def get_channels_inside_level_rec(self, dico = {}): + for c in self.channels: + dico[c] = '' + for b in self.blocks: + b.get_channels_inside_level_rec(dico) + + def get_channels_inside_level(self): + dico = {} + for b in self.blocks: + b.get_channels_inside_level_rec(dico) + return list(dico.keys()) + + def get_channels_from_name_inside_level(self, name): + tab = [] + for c in self.get_channels_inside_level(): + if(c.get_name()==name): + tab.append(c) + return tab + + + + + + #def check_in_channels(self, channel): + # for c in self.get_channels(): + # if(c.equal(channel)): + # return True + # for b in self.blocks: + # if(b.check_in_channels(channel)): + # return True + # return False - def get_executors(self): + + def add_channel(self, channel): + self.channels.append(channel) + + + #def get_channel_from_name(self, name): + # for c in self.get_channels(): + # if(name == c.get_name()): + # return c + # return None + # #tab = [] + # #for b in self.blocks: + # # channels = b.get_channel_from_name(name) + # # tab+=channels + # #raise Exception(f"{name} is not in the list of channels") + + + + ############# + # EXECUTORS + ############# + def get_executors_same_level(self): return self.executors + + def get_above_executors(self): + return [] + + def get_above_executors_rec(self, dico = {}): + for e in self.executors: + dico[e] = '' + + #This method returns all the executors inside a block + def get_inside_executors_rec(self, dico = {}): + for e in self.executors: + dico[e] = '' + for b in self.blocks: + b.get_inside_executors_rec(dico) + + def get_inside_executors(self): + dico = {} + for b in self.blocks: + b.get_inside_executors_rec(dico) + return list(dico.keys()) + + #def get_calls(self): + # tab = [] + # for c in self.get_executors(): + # if(c.get_type()=="Call"): + # tab.append(c) + # elif(c.get_type()=="Operation"): + # for o in c.get_origins(): + # if(o.get_type()=="Call"): + # tab.append(o) + # return tab + + ############# + # CALLS + ############# - def get_calls(self): + def get_calls_same_level(self): tab = [] - for c in self.get_executors(): + for c in self.executors: if(c.get_type()=="Call"): tab.append(c) + elif(c.get_type()=="Operation"): + for o in c.get_origins(): + if(o.get_type()=="Call"): + tab.append(o) + return tab + + def get_calls_above_level(self): + return [] + + #This method returns all the calls inside a block + def get_calls_inside_level(self): + tab = [] + executors = self.get_inside_executors() + for e in executors: + if(e.get_type()=="Call"): + tab.append(e) + elif(e.get_type()=="Operation"): + for o in e.get_origins(): + if(o.get_type()=="Call"): + tab.append(o) return tab + + + + + + + + + def initialise(self): @@ -69,18 +205,49 @@ class Root(Nextflow_Building_Blocks): for c in conditions: from .block import Block body = code[conditions[c][0]:conditions[c][1]] - block = Block(code=body, origin=self, condition=c, modules_defined=self.modules_defined, existing_channels = self.channels) + c = c.split("$$__$$")[0] + import copy + block = Block(code=body, origin=self, condition=c, modules_defined=self.modules_defined, existing_channels = copy.copy(self.channels)) self.blocks.append(block) self.extract_executors() + #TODO i need to sort the execution order out + position_2_thing_2_analyse = {} for block in self.blocks: - block.initialise() - - #Analyse Executors + pos = code.find(block.get_code()) + print(block.get_code()) + print() + if(pos!=-1): + position_2_thing_2_analyse[pos] = block + code = code.replace(block.get_code(), "a"*len(block.get_code()), 1) + else: + raise Exception("This shouldn't happen") for e in self.executors: - e.initialise() + pos = code.find(e.get_code()) + if(pos!=-1): + position_2_thing_2_analyse[pos] = e + code = code.replace(e.get_code(), "a"*len(e.get_code())) + else: + raise Exception("This shouldn't happen") + + sorted_position_2_thing_2_analyse = dict(sorted(position_2_thing_2_analyse.items())) + print(sorted_position_2_thing_2_analyse) + for key in sorted_position_2_thing_2_analyse: + element = sorted_position_2_thing_2_analyse[key] + element.initialise() + + + #for block in self.blocks: + # print("block",code.find(block.get_code())) + # #TODO -> this would be the place you put the verification of the conditions + # block.initialise() + # + ##Analyse Executors + #for e in self.executors: + # print(code.find(e.get_code())) + # e.initialise() #Initialise each subworkflow being called #for sub in self.elements_being_called: diff --git a/src/subworkflow.py b/src/subworkflow.py index 07fb61edfa798639fae924dd489dc685fb1744bd..432719d7204ff7fee2a1232480e4cebfbb5b8f10 100644 --- a/src/subworkflow.py +++ b/src/subworkflow.py @@ -43,12 +43,25 @@ class Subworkflow(Main): sub.called_by = [] return sub - def get_call_by_name(self, name): - for c in self.root.get_calls(): - #c.initialise()#Don't need to analyse the call cause the element called is already analysed when the call is created - if(c.first_element_called.get_alias()==name): - return c - return None + #TODO make sure this is uptodate + def get_calls_by_name(self, name): + tab = [] + for call in self.root.get_calls_same_level(): + #call.initialise() + for c in call.get_all_calls(): + if(c.first_element_called.get_alias()==name): + tab.append(c) + + + #Here it is important that BioFlow-Insight is not a Nextflow verificator + #Here i'm checking the call inside the block + if(len(tab)!=0): + for c in self.root.get_calls_inside_level(): + #call.initialise() + for c in call.get_all_calls(): + if(c.first_element_called.get_alias()==name): + tab.append(c) + return tab def add_to_emits(self, emit): @@ -193,12 +206,15 @@ class Subworkflow(Main): for i in range(len(code)): code[i] = code[i].strip() if(code[i]!=""): - channel = self.root.get_channel_from_name(code[i]) - if(channel!=None): + channels = self.root.get_channels_from_name_same_level(code[i]) + if(channels==[]): + channels = self.root.get_channels_from_name_inside_level(code[i]) + if(channels!=[]): ope = Operation(code=f"emit: {code[i]}", origin=self) ope.set_as_artificial() - ope.add_element_origins(channel) - channel.add_sink(ope) + for channel in channels: + ope.add_element_origins(channel) + channel.add_sink(ope) tab.append(ope) else: