Beispiel #1
0
    def _parse_plot(self, lineno, linesplit_space):
        """
        @plot expr {mpl_prop}
        """
        cmd = linesplit_space[0]
        if len(linesplit_space) == 1:  # @plot
            raise ParseException(lineno, f"Invalid {cmd} command.")

        expr0, mpl_prop = ParseUtil.get_ending_dict(linesplit_space[1])
        if mpl_prop is None:
            mpl_prop = dict()

        expr = expr0
        all_stimulus_elements = self.parameters.get(kw.STIMULUS_ELEMENTS)
        all_behaviors = self.parameters.get(kw.BEHAVIORS)
        err = None
        if cmd == kw.VPLOT:
            expr, err = ParseUtil.parse_element_behavior(
                expr0, all_stimulus_elements, all_behaviors)
        elif cmd == kw.VSSPLOT:
            expr, err = ParseUtil.parse_element_element(
                expr0, all_stimulus_elements)
        elif cmd == kw.PPLOT:
            stimulus, behavior, err = ParseUtil.parse_stimulus_behavior(
                expr0, all_stimulus_elements, all_behaviors, self.variables)
            expr = (stimulus, behavior)
        elif cmd == kw.NPLOT:
            expr, err = ParseUtil.parse_chain(expr0, all_stimulus_elements,
                                              all_behaviors)
        if err:
            raise ParseException(lineno, err)
        return expr, mpl_prop, expr0
Beispiel #2
0
    def _parse_behavior_cost(self, behavior_cost_str, variables,
                             to_be_continued, is_appending):
        if not self.val[kw.BEHAVIORS]:
            return f"The parameter 'behaviors' must be assigned before the parameter '{kw.BEHAVIOR_COST}'."

        # Create and populate the struct with None values
        if not is_appending:
            self.val[kw.BEHAVIOR_COST] = dict()
            for e in self.val[kw.BEHAVIORS]:
                self.val[kw.BEHAVIOR_COST][e] = None
            self.val[kw.BEHAVIOR_COST][kw.DEFAULT] = None

        single_c, _ = ParseUtil.evaluate(behavior_cost_str, variables)
        if single_c is not None:
            if is_appending:
                return "A single value for '{}' cannot follow other values.".format(
                    kw.BEHAVIOR_COST)
            elif to_be_continued:
                return "A single value for '{}' cannot be followed by other values.".format(
                    kw.BEHAVIOR_COST)
            else:
                for key in self.val[kw.BEHAVIOR_COST]:
                    self.val[kw.BEHAVIOR_COST][key] = single_c
                self.val[kw.BEHAVIOR_COST].pop(kw.DEFAULT)
        else:
            cs = ParseUtil.comma_split(behavior_cost_str)
            cs = [x.strip() for x in cs]
            for bc_str in cs:  # bc_str is 'e:value' or 'default:value'
                if bc_str.count(':') != 1:
                    return "Expected 'element:value' or 'default:value' in '{}', got '{}'.".format(
                        kw.BEHAVIOR_COST, bc_str)
                b, c_str = bc_str.split(':')
                b = b.strip()
                c_str = c_str.strip()
                c, err = ParseUtil.evaluate(c_str, variables)
                if err:
                    return f"Invalid value '{c_str}' for '{b}' in parameter '{kw.BEHAVIOR_COST}'."

                if b == kw.DEFAULT:
                    if self.val[kw.BEHAVIOR_COST][kw.DEFAULT] is not None:
                        return "Default value for '{}' can only be stated once.".format(
                            kw.BEHAVIOR_COST)
                elif b not in self.val[kw.BEHAVIORS]:
                    return f"Error in parameter '{kw.BEHAVIOR_COST}': '{b}' is an invalid behavior name."
                if self.val[kw.BEHAVIOR_COST][b] is not None:
                    return "Duplicate of {} in '{}'.".format(
                        b, kw.BEHAVIOR_COST)
                self.val[kw.BEHAVIOR_COST][b] = c

            if not to_be_continued:
                # Set the default value for non-set behaviors
                err = self._set_default_values(kw.BEHAVIOR_COST)
                if err:
                    return err

        return None  # No error
Beispiel #3
0
    def _parse_stimulus_values(self, NAME, stimulus_values, variables,
                               to_be_continued, is_appending):
        if not self.val[kw.STIMULUS_ELEMENTS]:
            return f"The parameter 'stimulus_elements' must be assigned before the parameter '{NAME}'."

        # Create and populate the struct with None values
        if not is_appending:
            self.val[NAME] = dict()
            for e in self.val[kw.STIMULUS_ELEMENTS]:
                self.val[NAME][e] = None
            self.val[NAME][kw.DEFAULT] = None

        single_w, _ = ParseUtil.evaluate(stimulus_values, variables)
        if single_w is not None:
            if is_appending:
                return "A single value for '{}' cannot follow other values.".format(
                    NAME)
            elif to_be_continued:
                return "A single value for '{}' cannot be followed by other values.".format(
                    NAME)
            else:
                for key in self.val[NAME]:
                    self.val[NAME][key] = single_w
                self.val[NAME].pop(kw.DEFAULT)
        else:
            ws = ParseUtil.comma_split(stimulus_values)
            ws = [x.strip() for x in ws]
            for e_w_str in ws:  # eb_w_str is 'e:value' or 'default:value'
                if e_w_str.count(':') != 1:
                    return "Expected 'element:value' or 'default:value' in '{}', got '{}'.".format(
                        NAME, e_w_str)
                e, w_str = e_w_str.split(':')
                e = e.strip()
                w_str = w_str.strip()
                w, err = ParseUtil.evaluate(w_str, variables)
                if err:
                    return "Invalid value '{}' for '{}' in parameter '{}'.".format(
                        w_str, e, NAME)

                if e == kw.DEFAULT:
                    if self.val[NAME][kw.DEFAULT] is not None:
                        return "Default value for '{}' can only be stated once.".format(
                            NAME)
                elif e not in self.val[kw.STIMULUS_ELEMENTS]:
                    return f"Error in parameter '{NAME}': '{e}' is an invalid stimulus element."
                if self.val[NAME][e] is not None:
                    return "Duplicate of {} in '{}'.".format(e, NAME)
                self.val[NAME][e] = w

            if not to_be_continued:
                # Set the default value for non-set stimulus elements
                err = self._set_default_values(NAME)
                if err:
                    return err

        return None  # No error
Beispiel #4
0
    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
Beispiel #5
0
 def add_cs_varvals(self, cs_varvals, pv):
     """
     Adds to self.values the variables and values in the specified comma-separated
     variable:value pairs ("var1:val1, var2:val2, ...").
     """
     err = "A @VARIABLES line should have the form '@VARIABLES var1:val1, var2:val2, ...'."
     varvals = ParseUtil.comma_split(cs_varvals)
     for varval in varvals:
         if varval.count(':') == 1 or varval.count('=') == 1:
             if varval.count('=') == 1:
                 sep = '='
             else:
                 sep = ':'
             var, val_str = varval.split(sep)
             var = var.strip()
             val_str = val_str.strip()
             if var not in self.values:  # Otherwise overwrite previous value
                 var_err = is_valid_name(var, pv, kw)
                 if var_err:
                     return var_err
             val, val_err = self._is_valid_value(val_str)
             if val_err:
                 return val_err
             else:
                 self.values[var] = val
         else:
             return err
     return None
Beispiel #6
0
 def is_met(self, variables, event_counter):
     ismet, err = ParseUtil.evaluate(self.cond, variables, event_counter, ParseUtil.STOP_COND)
     if err:
         raise ParseException(self.lineno, err)
     if type(ismet) is not bool:
         raise ParseException(self.lineno, f"Condition '{self.cond}' is not a boolean expression.")
     return ismet
Beispiel #7
0
    def _parse_goto(self, goto_str, lineno, all_linelabels, global_variables):
        self.goto = list()
        err = f"Invalid condition '{goto_str}'. "
        lbls_and_probs = goto_str.split(',')
        lbls_and_probs = [lbl_and_prob.strip() for lbl_and_prob in lbls_and_probs]
        for lbl_and_prob in lbls_and_probs:
            if lbl_and_prob in all_linelabels:
                if len(lbls_and_probs) > 1:
                    raise ParseException(lineno, f"Invalid condition '{goto_str}'.")
                self.goto.append([1, lbl_and_prob])
            else:
                if '(' and ')' in lbl_and_prob:
                    if lbl_and_prob.count('(') > 1 or lbl_and_prob.count(')') > 1:
                        raise ParseException(lineno, err + "Too many parentheses.")
                    lindex = lbl_and_prob.find('(')
                    rindex = lbl_and_prob.find(')')
                    if lindex > rindex:
                        raise ParseException(lineno, err + "Mismatched parentheses.")

                    lbl = lbl_and_prob[0:lindex]
                    if lbl not in all_linelabels:
                        raise ParseException(lineno, err + f"Unknown line label '{lbl}'.")
                    prob_str = lbl_and_prob[(lindex + 1): rindex]
                    isprob, prob = ParseUtil.is_prob(prob_str, global_variables)

                    if not isprob:
                        if prob is not None:
                            raise ParseException(lineno, err + f"Expected a probability, got '{prob_str}'.")
                        prob = prob_str
                    for prob_lbl in self.goto:
                        if prob_lbl[1] == lbl:
                            raise ParseException(lineno, err + f"Label '{lbl}' duplicated.")
                    self.goto.append([prob, lbl])
                else:
                    raise ParseException(lineno, err + f"Invalid line label '{goto_str}'.")
Beispiel #8
0
    def _goto_if_met(self, variables):
        goto_prob_cumsum = list()
        cumsum = 0
        for i in range(len(self.goto)):
            prob = self.goto[i][0]
            if type(prob) is str:
                self.goto[i][0], err = ParseUtil.evaluate(prob, variables=variables)
                if err:
                    raise ParseException(self.lineno, err)
            cumsum += self.goto[i][0]
            goto_prob_cumsum.append(cumsum)
        if cumsum > 1:
            raise ParseException(self.lineno, f"Sum of probabilities is {cumsum}>1.")

        ind = ParseUtil.weighted_choice(goto_prob_cumsum)
        if ind is None:
            return None
        else:
            return self.goto[ind][1]
Beispiel #9
0
 def _parse_legend(self, lineno, linesplit_space):
     if len(linesplit_space) == 1:  # @legend
         mpl_prop = dict()
     elif len(linesplit_space) == 2:  # @legend {mpl_prop}
         arg = linesplit_space[1]
         is_dict, mpl_prop = ParseUtil.is_dict(arg)
         if not is_dict:
             raise ParseException(lineno,
                                  f"Invalid @legend argument {arg}.")
     return mpl_prop
Beispiel #10
0
 def _parse_subject(self, v_str, variables):
     err = f"Parameter {kw.SUBJECT} must be 'average', 'all', or a positive integer."
     if v_str.lower() in ('average', 'all'):
         self.val[kw.SUBJECT] = v_str.lower()
     else:
         v, interr = ParseUtil.parse_posint(v_str, variables)
         if interr:  # Parsing error
             return err + " " + interr
         if v is None:  # Parsing worked, but negative integer
             return err
         self.val[kw.SUBJECT] = v - 1  # Zero-based index internally
     return None
Beispiel #11
0
    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
Beispiel #12
0
    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)
Beispiel #13
0
 def _parse_phases(self, v_str):
     if v_str == 'all':
         self.val[kw.PHASES] = v_str  # list(all_phase_labels)
     else:
         phase_labels = ParseUtil.comma_split_strip(v_str)
         for phase_label in phase_labels:
             if len(phase_label) == 0:
                 return "Expected comma-separated list of phase labels, found {}".format(
                     phase_labels)
             # else:
             #     if phase_label not in all_phase_labels:
             #         return "Undefined phase label '{}'.".format(phase_label)
         self.val[kw.PHASES] = phase_labels
     return None
Beispiel #14
0
 def _parse_figure(self, lineno, linesplit_space):
     """
     @figure
     @figure title
     @figure {mpl_prop}
     @figure title {mpl_prop}
     """
     title = self.parameters.get(kw.TITLE)
     if len(linesplit_space) == 1:  # @figure
         mpl_prop = dict()
     elif len(linesplit_space) == 2:  # @figure args
         title, mpl_prop = ParseUtil.get_ending_dict(linesplit_space[1])
         if mpl_prop is None:
             mpl_prop = dict()
     return title, mpl_prop
Beispiel #15
0
    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
Beispiel #16
0
    def _parse_xscale(self, xscale, phases):
        if not self.val[kw.STIMULUS_ELEMENTS]:
            return f"The parameter 'stimulus_elements' must be assigned before the parameter '{kw.XSCALE}'."
        if not self.val[kw.BEHAVIORS] and self.val[kw.MECHANISM_NAME] != mn.RW:
            return f"The parameter 'behaviors' must be assigned before the parameter '{kw.XSCALE}'."

        if phases.is_phase_label(xscale):
            pass
        elif xscale == 'all':
            pass
        else:
            xscale, err = ParseUtil.parse_chain(xscale,
                                                self.val[kw.STIMULUS_ELEMENTS],
                                                self.val[kw.BEHAVIORS])
            if err:
                return err

        self.val[kw.XSCALE] = xscale
        return None
Beispiel #17
0
    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
Beispiel #18
0
    def is_met(self, response, global_variables, local_variables, event_counter):
        variables_both = Variables.join(global_variables, local_variables)
        if self.condition is None:
            ismet = True
        elif self.condition_is_behavior:
            ismet = (self.condition == response)
        else:
            ismet, err = ParseUtil.evaluate(self.condition, variables_both, event_counter,
                                            ParseUtil.PHASE_LINE)
            if err:
                raise ParseException(self.lineno, err)
            if type(ismet) is not bool:
                raise ParseException(self.lineno, f"Condition '{self.condition}' is not a boolean expression.")

        if ismet:
            label = self._goto_if_met(variables_both)
            if label is None:  # In "ROW1(0.1),ROW2(0.3)", goto_if_met returns None with prob. 0.6
                ismet = False
        else:
            label = None
        return ismet, label
Beispiel #19
0
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}'.")
Beispiel #20
0
    def check_compatibility_with_world(self, world):
        behaviors = self.parameters.get(kw.BEHAVIORS)

        # Check that stop condition does not depend on behavior
        for phase in world.phases:
            expr_vars = ParseUtil.variables_in_expr(phase.stop_condition.cond)
            for behavior in behaviors:
                if behavior in expr_vars:
                    mech_name = self.parameters.get(kw.MECHANISM_NAME)
                    err = f"Stop condition cannot depend on behavior in mechanism '{mech_name}'."
                    lineno = phase.stop_condition.lineno
                    return False, err, lineno

        # Check that phase line logics do not depend on behavior
        for phase in world.phases:
            for _, phase_line in phase.phase_lines.items():
                for condition_obj in phase_line.conditions.conditions:
                    if condition_obj.condition_is_behavior:
                        mech_name = self.parameters.get(kw.MECHANISM_NAME)
                        err = f"Phase line logic cannot depend on behavior in mechanism '{mech_name}'."
                        lineno = condition_obj.lineno
                        return False, err, lineno

        return True, None, None
Beispiel #21
0
    def next_stimulus(self, response, ignore_response_increment=False, preceeding_help_lines=None, omit_learn=None):
        # if not self.is_parsed:
        #     raise Exception("Internal error: Cannot call Phase.next_stimulus" +
        #                     " before Phase.parse().")

        if preceeding_help_lines is None:
            preceeding_help_lines = list()

        if not ignore_response_increment:
            # if not self.is_first_line:
            if response is not None:
                self.event_counter.increment_count(response)
                self.event_counter.increment_count_line(response)
                self.event_counter.set_last_response(response)

        if self.first_stimulus_presented:
            variables_both = Variables.join(self.global_variables, self.local_variables)
            if self.stop_condition.is_met(variables_both, self.event_counter):
                return None, None, preceeding_help_lines, None

        if self.is_first_line:
            # assert(response is None)
            rowlbl = self.first_label
            self.is_first_line = False
            omit_learn = True  # Since response is None, there is no learning (updating of v-values) to do
        else:
            rowlbl, omit_learn = self.curr_lineobj.next_line(response, self.global_variables, self.local_variables,
                                                             self.event_counter)
            self.prev_linelabel = self.curr_lineobj.label
            self._make_current_line(rowlbl)

        stimulus = self.phase_lines[rowlbl].stimulus
        if stimulus is not None:
            for element, intensity in stimulus.items():
                if type(intensity) is str:  # element[var] where var is a (local) variable
                    variables_both = Variables.join(self.global_variables, self.local_variables)
                    stimulus[element], err = ParseUtil.evaluate(intensity, variables=variables_both)
                    if err:
                        raise ParseException(self.phase_lines[rowlbl].lineno, err)

        if rowlbl != self.prev_linelabel:
            self.event_counter.reset_count_line()
            self.event_counter.line_label = rowlbl

        self.event_counter.increment_count(rowlbl)
        self.event_counter.increment_count_line(rowlbl)

        if stimulus is None:  # Help line
            action = self.phase_lines[rowlbl].action
            lineno = self.phase_lines[rowlbl].lineno
            self._perform_action(lineno, action)
            preceeding_help_lines.append(rowlbl)
            next_stimulus_out = self.next_stimulus(response, ignore_response_increment=True,
                                                   preceeding_help_lines=preceeding_help_lines,
                                                   omit_learn=omit_learn)
            stimulus, rowlbl, preceeding_help_lines, omit_learn_help = next_stimulus_out

            omit_learn = (omit_learn or omit_learn_help)
        else:
            for stimulus_element in stimulus:
                self.event_counter.increment_count(stimulus_element)
                self.event_counter.increment_count_line(stimulus_element)
            self.first_stimulus_presented = True

        return stimulus, rowlbl, preceeding_help_lines, omit_learn
Beispiel #22
0
    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)
Beispiel #23
0
    def str_set(self,
                prop,
                v_str,
                variables,
                phases,
                all_run_labels,
                to_be_continued,
                is_appending=False):
        """
        Parses the specified value (as a string) of the specified parameter and sets the resulting
        value. The input variables is a Variables object.

        Returns error message if parsing failed.
        """
        err = check_is_parameter_name(prop)
        if err:
            return err

        # all_phase_labels = phases.labels_set()
        if prop == kw.BEHAVIORS:
            return self._parse_behaviors(v_str, variables, is_appending)

        elif prop == kw.STIMULUS_ELEMENTS:
            return self._parse_stimulus_elements(v_str, variables,
                                                 is_appending)

        elif prop == kw.MECHANISM_NAME:
            return self._parse_mechanism_name(v_str)

        elif prop in (kw.START_VSS, kw.ALPHA_VSS):
            return self._parse_alphastart_vss(prop, v_str, variables,
                                              to_be_continued, is_appending)

        elif prop in (kw.START_W, kw.ALPHA_W, kw.U, kw.LAMBDA):
            return self._parse_stimulus_values(prop, v_str, variables,
                                               to_be_continued, is_appending)

        elif prop in (kw.BETA, kw.MU, kw.START_V, kw.ALPHA_V):
            return self._parse_stimulus_response_values(
                prop, v_str, variables, to_be_continued, is_appending)

        # Float
        elif prop in (kw.DISCOUNT, kw.TRACE):
            v, err = ParseUtil.evaluate(v_str, variables)
            if err:
                return err
            if (v < 0) or (v > 1):
                return f"Parameter '{prop}' must be a number >=0 and <=1."
            self.val[prop] = v
            return None

        elif prop == kw.BEHAVIOR_COST:
            return self._parse_behavior_cost(v_str, variables, to_be_continued,
                                             is_appending)

        elif prop == kw.RESPONSE_REQUIREMENTS:
            return self._parse_response_requirements(v_str, to_be_continued,
                                                     is_appending)

        # 'on' or 'off'
        elif prop in (kw.BIND_TRIALS, kw.CUMULATIVE):
            v_str_lower = v_str.lower()
            if v_str_lower not in ('on', 'off'):
                return "Parameter '{}' must be 'on' or 'off'.".format(prop)
            self.val[prop] = v_str_lower
            return None

        # Positive integer
        elif prop == kw.N_SUBJECTS:
            v, err = ParseUtil.parse_posint(v_str, variables)
            if err:
                return err
            if not v:
                return "Parameter {} must be a positive integer.".format(
                    kw.N_SUBJECTS)
            self.val[kw.N_SUBJECTS] = v
            return None

        # Any nonempty (after strip) string
        elif prop in (kw.TITLE, kw.SUBPLOTTITLE):
            if to_be_continued:  # Add the removed comma
                v_str = v_str + ","
            self.val[prop] = v_str
            return None

        # 'average', 'all' or 1-based index
        elif prop == kw.SUBJECT:
            return self._parse_subject(v_str, variables)

        # 'all' or s1->b1->s2->..., s=se1,se2,...
        elif prop == kw.XSCALE:
            return self._parse_xscale(v_str, phases)

        # 'subset' or 'exact'
        elif prop in (kw.MATCH, kw.XSCALE_MATCH):
            if v_str.lower() not in ('subset', 'exact'):
                return "Parameter {} must be 'subset' or 'exact'.".format(prop)
            self.val[prop] = v_str
            return None

        # 'all' or cs-list of phase labels
        elif prop == kw.PHASES:
            return self._parse_phases(v_str)  # , all_phase_labels)

        # String (@run-labels) (for postprocessing)
        elif prop == kw.RUNLABEL:
            if v_str not in all_run_labels:
                return "Invalid @RUN-label {}".format(v_str)
            self.val[kw.RUNLABEL] = v_str
            return None

        # Valid path to writable file
        elif prop == kw.FILENAME:
            filename = v_str
            try:
                file = open(filename, 'w', newline='')
            except Exception as ex:
                return str(ex)
            finally:
                file.close()
                try:
                    os.remove(filename)
                except FileNotFoundError:
                    pass
            self.val[kw.FILENAME] = filename
            return None
Beispiel #24
0
 def _is_valid_value(self, value_str):
     return ParseUtil.evaluate(value_str, self)
Beispiel #25
0
    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}'.")
Beispiel #26
0
    def _parse_response_requirements(self, v_str, to_be_continued,
                                     is_appending):
        if not self.val[kw.STIMULUS_ELEMENTS]:
            return f"The parameter 'stimulus_elements' must be assigned before the parameter '{kw.RESPONSE_REQUIREMENTS}'."
        if not self.val[kw.BEHAVIORS]:
            return f"The parameter 'behaviors' must be assigned before the parameter '{kw.RESPONSE_REQUIREMENTS}'."

        if not is_appending:
            self.val[kw.RESPONSE_REQUIREMENTS] = dict()
            for b in self.val[kw.BEHAVIORS]:
                self.val[kw.RESPONSE_REQUIREMENTS][b] = None

        rrs = ParseUtil.comma_split_sq(v_str)
        for rr in rrs:
            if rr.count(':') != 1:
                return "Expected 'behavior:stimulus_element', got '{}'.".format(
                    rr)
            b, s = rr.split(':')
            b = b.strip()
            s = s.strip()
            if len(b) == 0 or len(s) == 0:
                return "Expected 'behavior:stimulus_element', got '{}'.".format(
                    rr)
            if b not in self.val[kw.BEHAVIORS]:
                return "Unknown behavior name '{}'.".format(b)
            if self.val[kw.RESPONSE_REQUIREMENTS][b] is not None:
                return "Duplication of behavior '{}' in {}.".format(
                    b, kw.RESPONSE_REQUIREMENTS)
            if '[' in s or ']' in s:
                if s.count('[') != 1 or s.count(
                        ']') != 1 or s[0] != '[' or s[-1] != ']':
                    return "Malformed expression '{}'.".format(s)
                s = s[1:-1]  # Strip the '['and the ']'
                es = s.split(',')
                for e in es:
                    e = e.strip()
                    if e not in self.val[kw.STIMULUS_ELEMENTS]:
                        return "Unknown stimulus element '{}'.".format(e)
                    self._response_requirements_add_element(b, e)
            else:
                if s not in self.val[kw.STIMULUS_ELEMENTS]:
                    return "Unknown stimulus element '{}'.".format(s)
                self._response_requirements_add_element(b, s)

        if not to_be_continued:
            # For the unrestricted behaviors, add all stimulus elements
            for b in self.val[kw.RESPONSE_REQUIREMENTS]:
                if self.val[kw.RESPONSE_REQUIREMENTS][b] is None:
                    self.val[kw.RESPONSE_REQUIREMENTS][b] = set(
                        self.val[kw.STIMULUS_ELEMENTS])

            # Check that each stimulus element has at least one feasible response
            stimulus_elements_in_rr = []
            for stimulus_list in self.val[kw.RESPONSE_REQUIREMENTS].values():
                stimulus_elements_in_rr.extend(stimulus_list)
            if set(stimulus_elements_in_rr) != set(
                    self.val[kw.STIMULUS_ELEMENTS]):
                elements_without_response = set(self.val[
                    kw.STIMULUS_ELEMENTS]) - set(stimulus_elements_in_rr)
                elements_without_response = list(elements_without_response)
                elements_without_response.sort(
                )  # To make error message testable
                elements_without_response_str = make_readable_list_of_strings(
                    elements_without_response)
                err = f"Invalid {kw.RESPONSE_REQUIREMENTS}: "
                if len(elements_without_response) == 1:
                    return err + f"Stimulus element {elements_without_response_str} has no possible responses."
                else:
                    return err + f"Stimulus elements {elements_without_response_str} have no possible responses."

        return None  # No error
Beispiel #27
0
    def _parse_alphastart_vss(self, NAME, vss_str, variables, to_be_continued,
                              is_appending):
        """
        Parse the string vss_str with a start_vss/alpha_vss specification.

        Example: "S1->S2: 1.23, S2->S1:3.45, default:1" sets the parameter to
                 {('S1','S2'):1.23, ('S2','S1'):3.45, ('S1','S1'):1, ('S2','S2'):1}
                 under the assumption that stimulus_elements = {'S1', 'S2'}
        """
        if not self.val[kw.STIMULUS_ELEMENTS]:
            return f"The parameter 'stimulus_elements' must be assigned before the parameter '{NAME}'."

        # Create and populate the struct with None values
        if not is_appending:
            self.val[NAME] = dict()
            for e1 in self.val[kw.STIMULUS_ELEMENTS]:
                for e2 in self.val[kw.STIMULUS_ELEMENTS]:
                    self.val[NAME][(e1, e2)] = None
            self.val[NAME][kw.DEFAULT] = None

        single_vss, _ = ParseUtil.evaluate(vss_str, variables)
        if single_vss is not None:
            if is_appending:
                return f"A single value for '{NAME}' cannot follow other values."
            elif to_be_continued:
                return f"A single value for '{NAME}' cannot be followed by other values."
            else:
                for key in self.val[NAME]:
                    self.val[NAME][key] = single_vss
                self.val[NAME].pop(kw.DEFAULT)
        else:
            vs = ParseUtil.comma_split(vss_str)
            vs = [x.strip() for x in vs]
            for ee_str in vs:  # eb_v_str is 'e1->e2:value' or 'default:value'
                if ee_str.count(':') != 1:
                    return f"Expected 'x->y:value' or 'default:value' in '{NAME}', got '{ee_str}'."
                ee, v_str = ee_str.split(':')
                ee = ee.strip()
                v_str = v_str.strip()
                v, err = ParseUtil.evaluate(v_str, variables)
                if err:
                    return f"Invalid value '{v_str}' for '{ee}' in parameter '{NAME}'."

                if ee == kw.DEFAULT:
                    if self.val[NAME][kw.DEFAULT] is not None:
                        return f"Default value for '{NAME}' can only be stated once."
                    self.val[NAME][kw.DEFAULT] = v
                elif ee.count('->') == 1:
                    e1, e2 = ee.split('->')
                    if e1 not in self.val[kw.STIMULUS_ELEMENTS]:
                        return f"Error in parameter '{NAME}': '{e1}' is an invalid stimulus element."
                    if e2 not in self.val[kw.STIMULUS_ELEMENTS]:
                        return f"Error in parameter '{NAME}': '{e2}' is an invalid stimulus element."
                    if self.val[NAME][(e1, e2)] is not None:
                        return f"Duplicate of {e1}->{e2} in '{NAME}'."
                    self.val[NAME][(e1, e2)] = v
                else:
                    return f"Invalid string '{ee}' in parameter '{NAME}'."

            if not to_be_continued:
                # Set the default value for non-set stimulus-stimulus pairs
                err = self._set_default_values(NAME)
                if err:
                    return err

        return None  # No error
Beispiel #28
0
    def _parse_stimulus_response_values(self, NAME, sr_str, variables,
                                        to_be_continued, is_appending):
        """
        Parse the string sr_str with a value for stimulus-response pairs.

        Example: "S1->R1: 1.23, S2->R1:3.45, default:1" sets the parameter to
                 {('S1','R1'):1.23, ('S1','R2'):1, ('S2','R1'):3.45, ('S2','R2'):1}
                 under the assumption that
                 behaviors = {'R1', 'R2'} and
                 stimulus_elements = {'S1', 'S2'}
        """
        if not self.val[kw.STIMULUS_ELEMENTS]:
            return f"The parameter 'stimulus_elements' must be assigned before the parameter '{NAME}'."
        if not self.val[kw.BEHAVIORS]:
            return f"The parameter 'behaviors' must be assigned before the parameter '{NAME}'."

        # Create and populate the struct with None values
        if not is_appending:
            self.val[NAME] = dict()
            for e in self.val[kw.STIMULUS_ELEMENTS]:
                for b in self.val[kw.BEHAVIORS]:
                    self.val[NAME][(e, b)] = None
            self.val[NAME][kw.DEFAULT] = None

        single_v, _ = ParseUtil.evaluate(sr_str, variables)
        if single_v is not None:
            if is_appending:
                return f"A single value for '{NAME}' cannot follow other values."
            elif to_be_continued:
                return f"A single value for '{NAME}' cannot be followed by other values."
            else:
                for key in self.val[NAME]:
                    self.val[NAME][key] = single_v
                self.val[NAME].pop(kw.DEFAULT)
        else:
            vs = ParseUtil.comma_split(sr_str)
            vs = [x.strip() for x in vs]
            for eb_v_str in vs:  # eb_v_str is 'e->b:value' or 'default:value'
                if eb_v_str.count(':') != 1:
                    return f"Expected 'x->y:value' or 'default:value' in '{NAME}', got '{eb_v_str}'."
                eb, v_str = eb_v_str.split(':')
                eb = eb.strip()
                v_str = v_str.strip()
                v, err = ParseUtil.evaluate(v_str, variables)
                if err:
                    return f"Invalid value '{v_str}' for '{eb}' in parameter '{NAME}'."

                if eb == kw.DEFAULT:
                    if self.val[NAME][kw.DEFAULT] is not None:
                        return f"Default value for '{NAME}' can only be stated once."
                    self.val[NAME][kw.DEFAULT] = v
                elif eb.count('->') == 1:
                    e, b = eb.split('->')
                    if e not in self.val[kw.STIMULUS_ELEMENTS]:
                        return f"Error in parameter '{NAME}': '{e}' is an invalid stimulus element."
                    if b not in self.val[kw.BEHAVIORS]:
                        return f"Error in parameter '{NAME}': '{b}' is an invalid behavior name."
                    if self.val[NAME][(e, b)] is not None:
                        return f"Duplicate of {e}->{b} in '{NAME}'."
                    self.val[NAME][(e, b)] = v
                else:
                    return f"Invalid string '{eb}' in parameter '{NAME}'."

            if not to_be_continued:
                # Set the default value for non-set stimulus-behavior pairs
                err = self._set_default_values(NAME)
                if err:
                    return err

        return None  # No error