def _parse_export(self, lineno, linesplit_space): """ @export expr @export expr filename @hexport @hexport filename """ cmd = linesplit_space[0] filename_param = self.parameters.get(kw.FILENAME) if cmd == kw.HEXPORT: if len(linesplit_space) == 1: # @hexport if len(filename_param) == 0: raise ParseException(lineno, f"Invalid {cmd} command.") else: filename = filename_param else: # @hexport filename filename = linesplit_space[1] return None, filename, None if len(linesplit_space) == 1: # @export raise ParseException(lineno, f"Invalid {cmd} command.") args = linesplit_space[1] expr0, filename = ParseUtil.split1_strip(args) expr = expr0 if filename is None: if len(filename_param) == 0: raise ParseException(lineno, f"No filename given to {cmd}.") else: filename = filename_param all_stimulus_elements = self.parameters.get(kw.STIMULUS_ELEMENTS) all_behaviors = self.parameters.get(kw.BEHAVIORS) err = None if cmd == kw.VEXPORT: expr, err = ParseUtil.parse_element_behavior( expr0, all_stimulus_elements, all_behaviors) elif cmd == kw.VSSEXPORT: expr, err = ParseUtil.parse_element_element( expr0, all_stimulus_elements) elif cmd == kw.PEXPORT: stimulus, behavior, err = ParseUtil.parse_stimulus_behavior( expr0, all_stimulus_elements, all_behaviors, self.variables) expr = (stimulus, behavior) elif cmd == kw.NEXPORT: expr, err = ParseUtil.parse_chain(expr0, all_stimulus_elements, all_behaviors) if err: raise ParseException(lineno, err) return expr, filename, expr0
def parse(self, parameters, global_variables): self.parameters = parameters self.global_variables = global_variables stimulus_elements = parameters.get(STIMULUS_ELEMENTS) behaviors = parameters.get(BEHAVIORS) phase_lines_afterlabel = list() linenos = list() # First iteration through lines: Create list of lines (and labels) for line_lineno in self.lines: line, lineno = line_lineno label, afterlabel = ParseUtil.split1_strip(line) if afterlabel is None: raise ParseException(lineno, "Phase line contains only label.") coincide_err = f"The phase line label '{label}' coincides with the name of a " if label in stimulus_elements: raise ParseException(lineno, coincide_err + "stimulus element.") elif label in behaviors: raise ParseException(lineno, coincide_err + "behavior.") if label in self.linelabels and not self.is_inherited: raise ParseException(lineno, f"Duplicate of phase line label '{label}'.") self.linelabels.append(label) phase_lines_afterlabel.append(afterlabel) linenos.append(lineno) if self.first_label is None: # Set self.first_label to the label of the first line self.first_label = label # Second iteration: Create PhaseLine objects and put in the dict self.phase_lines for label, after_label, lineno in zip(self.linelabels, phase_lines_afterlabel, linenos): self.phase_lines[label] = PhaseLine(self, lineno, label, after_label, self.linelabels, self.parameters, self.global_variables) if label == "new_trial": # Change self.first_label to the new_trial line self.first_label = label self.initialize_local_variables() self.event_counter = PhaseEventCounter(self.linelabels, self.parameters) self.subject_reset() self.is_parsed = True
def check_action(action, parameters, global_variables, lineno, all_linelabels): if action.count(':') == 1 or action.count('=') == 1: if action.count('=') == 1: sep = '=' else: sep = ':' var_name, _ = ParseUtil.split1_strip(action, sep=sep) var_err = is_valid_name(var_name, parameters, kw) if var_err is not None: raise ParseException(lineno, var_err) if global_variables.contains(var_name): raise ParseException(lineno, "Cannot modify global variable inside a phase.") elif action.startswith("count_reset(") and action.endswith(")"): behaviors = parameters.get(BEHAVIORS) stimulus_elements = parameters.get(STIMULUS_ELEMENTS) event = action[12:-1] if event not in stimulus_elements and event not in behaviors and event not in all_linelabels: raise ParseException(lineno, f"Unknown event '{event}' in count_reset.") elif action == "@omit_learn": pass else: raise ParseException(lineno, f"Unknown action '{action}'.")
def __init__(self, phase_obj, lineno, label, after_label, all_linelabels, parameters, global_variables): self.lineno = lineno self.label = label self.parameters = parameters self.all_linelabels = all_linelabels self.is_help_line = False self.stimulus = None # A dict with an intensity for each element in stimulus_elememts self.action = None self.action, logic = ParseUtil.split1_strip(after_label, sep=PHASEDIV) if logic is None: raise ParseException(lineno, f"Missing separator '{PHASEDIV}' on phase line.") action_list = ParseUtil.comma_split_strip(self.action) first_element, _, _ = ParseUtil.parse_element_and_intensity(action_list[0], variables=None, safe_intensity_eval=True) self.is_help_line = (len(action_list) == 1) and (first_element not in parameters.get(STIMULUS_ELEMENTS)) if self.is_help_line: self.stimulus = None if action_list[0] != '': check_action(action_list[0], parameters, global_variables, lineno, all_linelabels) else: self.stimulus, err = ParseUtil.parse_elements_and_intensities(self.action, global_variables, safe_intensity_eval=True) if err: raise ParseException(lineno, err) for element in self.stimulus: if element not in parameters.get(STIMULUS_ELEMENTS): raise ParseException(lineno, f"Expected a stimulus element, got '{element}'.") self.action = None if len(logic) == 0: raise ParseException(lineno, f"Line with label '{label}' has no conditions.") self.conditions = PhaseLineConditions(phase_obj, lineno, self.is_help_line, logic, parameters, all_linelabels, global_variables)
def _perform_action(self, lineno, action): """ Sets a variable (x:3) or count_reset(event). """ omit_learn = False # No action to perform if len(action) == 0: pass # Setting a variable elif action.count(':') == 1 or action.count('=') == 1: if action.count('=') == 1: sep = '=' else: sep = ':' var_name, value_str = ParseUtil.split1_strip(action, sep=sep) variables_join = Variables.join(self.global_variables, self.local_variables) value, err = ParseUtil.evaluate(value_str, variables_join) if err: raise ParseException(lineno, err) err = self.local_variables.set(var_name, value, self.parameters) if err: raise ParseException(lineno, err) # count_reset elif action.startswith("count_reset(") and action.endswith(")"): event = action[12:-1] self.event_counter.reset_count(event) # omit_learn elif action == "@omit_learn": omit_learn = True else: raise ParseException(lineno, "Internal error.") # Should have been caught during Pase.parse() return omit_learn
def _parse_subplot(self, lineno, linesplit_space): """ @subplot @subplot subplotspec @subplot subplotspec title @subplot subplotspec {mpl_prop} @subplot subplotspec title {mpl_prop} """ title_param = self.parameters.get(kw.SUBPLOTTITLE) if len(linesplit_space) == 1: # @subplot subplotspec = '111' title = title_param mpl_prop = dict() elif len(linesplit_space) == 2: # @subplot ... args, mpl_prop = ParseUtil.get_ending_dict(linesplit_space[1]) if mpl_prop is None: mpl_prop = dict() subplotspec, title_line = ParseUtil.split1_strip(args) if title_line is None: # @subplot subplotspec title = title_param else: title = title_line return subplotspec, title, mpl_prop
def parse(self): if len(self.lines) == 0: raise ParseException(1, "Script is empty.") prop = None curr_phase_label = None in_prop = False in_variables = False in_phase = False for lineno0, line_orig in enumerate(self.lines): line = line_orig lineno = lineno0 + 1 # Handle empty line if len(line) == 0: continue line_endswith_comma = line.endswith(',') if line_endswith_comma: line = line[:-1] line_parser = LineParser(line, self.variables) line_parser.parse() linesplit_colon = line_parser.linesplit_colon linesplit_equal = line_parser.linesplit_equal linesplit_space = line_parser.linesplit_space if in_prop or in_variables or in_phase: parse_this_line_done = True err = None if in_prop: in_prop = line_endswith_comma err = self.parameters.str_append(prop, line, self.variables, self.phases, self.all_run_labels, line_endswith_comma) elif in_variables: in_variables = line_endswith_comma err = self.variables.add_cs_varvals(line, self.parameters) elif in_phase: in_phase = line_parser.line_type is None if in_phase: self.phases.append_line(curr_phase_label, line, lineno) else: parse_this_line_done = False # Phase with label curr_phase_label is complete, parse it self.phases.parse_phase(curr_phase_label, self.parameters, self.variables) if err: raise ParseException(lineno, err) if parse_this_line_done: continue if line_parser.line_type == LineParser.PARAMETER: # Handle line that sets a parameter (e.g. "prop : val") prop = line_parser.param if len(linesplit_colon) == 1 and len(linesplit_equal) == 1: raise ParseException( lineno, f"Parameter '{prop}' is not specified.") first_colon_index = line_orig.find(':') first_equal_index = line_orig.find('=') if first_equal_index > 0 and first_colon_index > 0: if first_colon_index < first_equal_index: # u : a=1, b=2 possible_val = linesplit_colon[1].strip() else: # u = a:1, b:2 possible_val = linesplit_equal[1].strip() elif first_equal_index > 0 and first_colon_index < 0: # u = 2 possible_val = linesplit_equal[1].strip() elif first_colon_index > 0 and first_equal_index < 0: # u : 2 possible_val = linesplit_colon[1].strip() if len(possible_val) == 0: raise ParseException( lineno, f"Parameter '{prop}' is not specified.") if line_endswith_comma: if not self.parameters.may_end_with_comma(prop): raise ParseException( lineno, "Value for {} may not end by comma.".format(prop)) in_prop = self.parameters.is_csv(prop) err = self.parameters.str_set(prop, possible_val, self.variables, self.phases, self.all_run_labels, line_endswith_comma) if err: raise ParseException(lineno, err) continue elif line_parser.line_type == LineParser.VARIABLES: if len(linesplit_space) == 1: raise ParseException(lineno, "@VARIABLES not specified.") in_variables = line_endswith_comma cs_varvals = linesplit_space[1].strip() err = self.variables.add_cs_varvals(cs_varvals, self.parameters) if err: raise ParseException(lineno, err) else: continue elif line_parser.line_type == LineParser.PREV_DEFINED_VARIABLE: err = self.variables.add_cs_varvals(line.strip(), self.parameters) if err: raise ParseException(lineno, err) else: continue elif line_parser.line_type == LineParser.RUN: if len(linesplit_space) == 1: raise ParseException( lineno, "@RUN line must have the form '@RUN phases [runlabel:label]." ) after_run = linesplit_space[1].strip() run_label, run_phase_labels = self._parse_run( after_run, lineno) world = self.phases.make_world(run_phase_labels) run_parameters = copy.deepcopy( self.parameters) # Params may change betweeen runs mechanism_obj, err = run_parameters.make_mechanism_obj() if err: raise ParseException(lineno, err) n_subjects = run_parameters.get(kw.N_SUBJECTS) bind_trials = run_parameters.get(kw.BIND_TRIALS) is_ok, err, err_lineno = mechanism_obj.check_compatibility_with_world( world) if err: raise ParseException(err_lineno, err) run = Run(run_label, world, mechanism_obj, n_subjects, bind_trials) self.runs.add(run, run_label, lineno) continue elif line_parser.line_type == LineParser.PHASE: gen_err = "@PHASE line must have the form '@PHASE label stop:condition'." if len(linesplit_space) == 1: raise ParseException(lineno, gen_err) lbl_and_stopcond = linesplit_space[1].strip() curr_phase_label, stop_condition = ParseUtil.split1( lbl_and_stopcond) inherited_from = None if '(' in curr_phase_label and curr_phase_label.endswith(')'): lind = curr_phase_label.index('(') inherited_from = curr_phase_label[(lind + 1):-1] curr_phase_label = curr_phase_label[0:lind] if not self.phases.contains(inherited_from): raise ParseException( lineno, f"Invalid phase label '{inherited_from}'.") if self.phases.contains(curr_phase_label): raise ParseException( lineno, f"Redefinition of phase '{curr_phase_label}'.") if not curr_phase_label.isidentifier(): raise ParseException( lineno, f"Phase label '{curr_phase_label}' is not a valid identifier." ) if stop_condition is None: raise ParseException(lineno, gen_err) stop, condition = ParseUtil.split1_strip(stop_condition, ':') if stop != "stop" or condition is None or len(condition) == 0: raise ParseException( lineno, "Phase stop condition must have the form 'stop:condition'." ) in_phase = True if inherited_from: self.phases.inherit_from(inherited_from, curr_phase_label, condition, lineno) else: self.phases.add_phase(curr_phase_label, condition, lineno) continue elif line_parser.line_type == LineParser.FIGURE: figure_title, mpl_prop = self._parse_figure( lineno, linesplit_space) figure_cmd = FigureCmd(figure_title, mpl_prop) self.postcmds.add(figure_cmd) elif line_parser.line_type == LineParser.SUBPLOT: subplotspec, subplot_title, mpl_prop = self._parse_subplot( lineno, linesplit_space) subplot_cmd = SubplotCmd(subplotspec, subplot_title, mpl_prop) self.postcmds.add(subplot_cmd) elif line_parser.line_type == LineParser.PLOT: expr, mpl_prop, expr0 = self._parse_plot( lineno, linesplit_space) cmd = linesplit_space[0].lower() plot_parameters = copy.deepcopy( self.parameters) # Params may change betweeen plot self._evalparse(lineno, plot_parameters) plot_cmd = PlotCmd(cmd, expr, expr0, plot_parameters, mpl_prop) self.postcmds.add(plot_cmd) elif line_parser.line_type == LineParser.LEGEND: mpl_prop = self._parse_legend(lineno, linesplit_space) legend_cmd = LegendCmd(mpl_prop) self.postcmds.add(legend_cmd) elif line_parser.line_type == LineParser.EXPORT: expr, filename, expr0 = self._parse_export( lineno, linesplit_space) cmd = linesplit_space[0].lower() export_parameters = copy.deepcopy( self.parameters) # Params may change betweeen exports self._evalparse(lineno, export_parameters) export_parameters.val[ kw. FILENAME] = filename # If filename only given on export line export_cmd = ExportCmd(lineno, cmd, expr, expr0, export_parameters) self.postcmds.add(export_cmd) else: raise ParseException(lineno, f"Invalid expression '{line}'.")
def _parse(self, lineno, is_help_line, condition_and_actions, logicpart_index, n_logicparts, parameters, all_linelabels, global_variables): ''' Args: condition_and_actions (str): Examples are "b=5: x:2, y=2, ROWLBL", "x:2, y:2, ROWLBL", "@break" "x=1: @break" "x:1" "@break, x:1" ''' self.condition = None ca_list = ParseUtil.comma_split_strip(condition_and_actions) found_condition = False any_rowlbl_prob = False found_rowlbl = False goto_list = list() goto_list_index = list() for i, ca in enumerate(ca_list): err = f"Invalid statement '{ca}'." n_colons = ca.count(':') if n_colons == 0: contains_condition = False action = ca elif n_colons == 1: before_colon, after_colon = ParseUtil.split1_strip(ca, ':') contains_condition = self._is_condition(before_colon, parameters) if contains_condition: if self.condition is not None: # Cannot have multiple conditions raise ParseException(lineno, f"Multiple conditions ('{self.condition}' and '{before_colon}') found in '{condition_and_actions}'.") self.condition = before_colon action = after_colon else: action = ca elif n_colons == 2: colon_inds = [m.start() for m in re.finditer(':', ca)] if colon_inds[1] - colon_inds[0] == 1: raise ParseException(lineno, err) before_first_colon, after_first_colon = ParseUtil.split1_strip(ca, ':') if not self._is_condition(before_first_colon, parameters): raise ParseException(lineno, err) if self.condition is not None: # Cannot have multiple conditions raise ParseException(lineno, f"Multiple conditions ('{self.condition}' and '{before_first_colon}') found in '{condition_and_actions}'.") contains_condition = True self.condition = before_first_colon action = after_first_colon else: raise ParseException(lineno, err) if contains_condition and found_rowlbl: raise ParseException(lineno, f"Found condition '{self.condition}' after row label '{','.join(goto_list)}'.") found_condition = found_condition or contains_condition is_rowlbl, is_rowlbl_prob = self._is_rowlbl(action, all_linelabels) any_rowlbl_prob = (any_rowlbl_prob or is_rowlbl_prob) if is_rowlbl: found_rowlbl = True goto_list.append(action) goto_list_index.append(i) else: check_action(action, parameters, global_variables, lineno, all_linelabels) if found_rowlbl: err = f"Row label(s) must be the last action(s). Found '{action}' after row-label." raise ParseException(lineno, err) if found_condition: # self.condition is not None: self.conditional_actions.append(action) else: self.unconditional_actions.append(action) is_last_action = (i == len(ca_list) - 1) if is_last_action and not is_rowlbl: raise ParseException(lineno, f"Last action must be a row label, found '{action}'.") if found_condition: self.condition_is_behavior = (self.condition in parameters.get(BEHAVIORS)) cond_depends_on_behavior, err = ParseUtil.depends_on(self.condition, parameters.get(BEHAVIORS)) if err is not None: raise ParseException(lineno, err) if cond_depends_on_behavior and is_help_line: raise ParseException(lineno, "Condition on help line cannot depend on response.") goto_str = ','.join(goto_list) # A deterministic ROWLBL cannot have elif/else continuation if (not found_condition) and found_rowlbl and not any_rowlbl_prob: if logicpart_index < n_logicparts - 1: err = f"The unconditional goto row label '{goto_str}' cannot be continued." raise ParseException(lineno, err) if len(goto_list) > 0: self._parse_goto(goto_str, lineno, all_linelabels, global_variables)