| |
| package java_cup; |
| |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| |
| /** This class represents a production in the grammar. It contains |
| * a LHS non terminal, and an array of RHS symbols. As various |
| * transformations are done on the RHS of the production, it may shrink. |
| * As a result a separate length is always maintained to indicate how much |
| * of the RHS array is still valid.<p> |
| * |
| * I addition to construction and manipulation operations, productions provide |
| * methods for factoring out actions (see remove_embedded_actions()), for |
| * computing the nullability of the production (i.e., can it derive the empty |
| * string, see check_nullable()), and operations for computing its first |
| * set (i.e., the set of terminals that could appear at the beginning of some |
| * string derived from the production, see check_first_set()). |
| * |
| * @see java_cup.production_part |
| * @see java_cup.symbol_part |
| * @see java_cup.action_part |
| * @version last updated: 11/25/95 |
| * @author Scott Hudson |
| */ |
| |
| public class production { |
| |
| /*-----------------------------------------------------------*/ |
| /*--- Constructor(s) ----------------------------------------*/ |
| /*-----------------------------------------------------------*/ |
| |
| /** Full constructor. This constructor accepts a LHS non terminal, |
| * an array of RHS parts (including terminals, non terminals, and |
| * actions), and a string for a final reduce action. It does several |
| * manipulations in the process of creating a production object. |
| * After some validity checking it translates labels that appear in |
| * actions into code for accessing objects on the runtime parse stack. |
| * It them merges adjacent actions if they appear and moves any trailing |
| * action into the final reduce actions string. Next it removes any |
| * embedded actions by factoring them out with new action productions. |
| * Finally it assigns a unique index to the production.<p> |
| * |
| * Factoring out of actions is accomplished by creating new "hidden" |
| * non terminals. For example if the production was originally: <pre> |
| * A ::= B {action} C D |
| * </pre> |
| * then it is factored into two productions:<pre> |
| * A ::= B X C D |
| * X ::= {action} |
| * </pre> |
| * (where X is a unique new non terminal). This has the effect of placing |
| * all actions at the end where they can be handled as part of a reduce by |
| * the parser. |
| */ |
| public production( |
| non_terminal lhs_sym, |
| production_part rhs_parts[], |
| int rhs_l, |
| String action_str) |
| throws internal_error |
| { |
| int i; |
| action_part tail_action; |
| |
| /* remember the length */ |
| if (rhs_l >= 0) |
| _rhs_length = rhs_l; |
| else if (rhs_parts != null) |
| _rhs_length = rhs_parts.length; |
| else |
| _rhs_length = 0; |
| |
| /* make sure we have a valid left-hand-side */ |
| if (lhs_sym == null) |
| throw new internal_error( |
| "Attempt to construct a production with a null LHS"); |
| |
| /* translate labels appearing in action strings */ |
| action_str = translate_labels( |
| rhs_parts, rhs_l, action_str, lhs_sym.stack_type()); |
| |
| /* count use of lhs */ |
| lhs_sym.note_use(); |
| |
| /* create the part for left-hand-side */ |
| _lhs = new symbol_part(lhs_sym); |
| |
| /* merge adjacent actions (if any) */ |
| _rhs_length = merge_adjacent_actions(rhs_parts, _rhs_length); |
| |
| /* strip off any trailing action */ |
| tail_action = strip_trailing_action(rhs_parts, _rhs_length); |
| if (tail_action != null) _rhs_length--; |
| |
| /* allocate and copy over the right-hand-side */ |
| _rhs = new production_part[_rhs_length]; |
| for (i=0; i<_rhs_length; i++) |
| _rhs[i] = rhs_parts[i]; |
| |
| /* count use of each rhs symbol */ |
| for (i=0; i<_rhs_length; i++) |
| if (!_rhs[i].is_action()) |
| ((symbol_part)_rhs[i]).the_symbol().note_use(); |
| |
| /* merge any trailing action with action string parameter */ |
| if (action_str == null) action_str = ""; |
| if (tail_action != null && tail_action.code_string() != null) |
| action_str = tail_action.code_string() + action_str; |
| |
| /* stash the action */ |
| _action = new action_part(action_str); |
| |
| /* rewrite production to remove any embedded actions */ |
| remove_embedded_actions(); |
| |
| /* assign an index */ |
| _index = next_index++; |
| |
| /* put us in the global collection of productions */ |
| _all.put(new Integer(_index),this); |
| |
| /* put us in the production list of the lhs non terminal */ |
| lhs_sym.add_production(this); |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Constructor with no action string. */ |
| public production( |
| non_terminal lhs_sym, |
| production_part rhs_parts[], |
| int rhs_l) |
| throws internal_error |
| { |
| this(lhs_sym,rhs_parts,rhs_l,null); |
| } |
| |
| /*-----------------------------------------------------------*/ |
| /*--- (Access to) Static (Class) Variables ------------------*/ |
| /*-----------------------------------------------------------*/ |
| |
| /** Table of all productions. Elements are stored using their index as |
| * the key. |
| */ |
| protected static Hashtable _all = new Hashtable(); |
| |
| /** Access to all productions. */ |
| public static Enumeration all() {return _all.elements();}; |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Total number of productions. */ |
| public static int number() {return _all.size();}; |
| |
| /** Static counter for assigning unique index numbers. */ |
| protected static int next_index; |
| |
| /*-----------------------------------------------------------*/ |
| /*--- (Access to) Instance Variables ------------------------*/ |
| /*-----------------------------------------------------------*/ |
| |
| /** The left hand side non-terminal. */ |
| protected symbol_part _lhs; |
| |
| /** The left hand side non-terminal. */ |
| public symbol_part lhs() {return _lhs;} |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** A collection of parts for the right hand side. */ |
| protected production_part _rhs[]; |
| |
| /** Access to the collection of parts for the right hand side. */ |
| public production_part rhs(int indx) throws internal_error |
| { |
| if (indx >= 0 && indx < _rhs_length) |
| return _rhs[indx]; |
| else |
| throw new internal_error( |
| "Index out of range for right hand side of production"); |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** How much of the right hand side array we are presently using. */ |
| protected int _rhs_length; |
| |
| /** How much of the right hand side array we are presently using. */ |
| public int rhs_length() {return _rhs_length;} |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** An action_part containing code for the action to be performed when we |
| * reduce with this production. |
| */ |
| protected action_part _action; |
| |
| /** An action_part containing code for the action to be performed when we |
| * reduce with this production. |
| */ |
| public action_part action() {return _action;} |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Index number of the production. */ |
| protected int _index; |
| |
| /** Index number of the production. */ |
| public int index() {return _index;} |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Count of number of reductions using this production. */ |
| protected int _num_reductions = 0; |
| |
| /** Count of number of reductions using this production. */ |
| public int num_reductions() {return _num_reductions;} |
| |
| /** Increment the count of reductions with this non-terminal */ |
| public void note_reduction_use() {_num_reductions++;} |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Is the nullability of the production known or unknown? */ |
| protected boolean _nullable_known = false; |
| |
| /** Is the nullability of the production known or unknown? */ |
| public boolean nullable_known() {return _nullable_known;} |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Nullability of the production (can it derive the empty string). */ |
| protected boolean _nullable = false; |
| |
| /** Nullability of the production (can it derive the empty string). */ |
| public boolean nullable() {return _nullable;} |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** First set of the production. This is the set of terminals that |
| * could appear at the front of some string derived from this production. |
| */ |
| protected terminal_set _first_set = new terminal_set(); |
| |
| /** First set of the production. This is the set of terminals that |
| * could appear at the front of some string derived from this production. |
| */ |
| public terminal_set first_set() {return _first_set;} |
| |
| /*-----------------------------------------------------------*/ |
| /*--- Static Methods ----------------------------------------*/ |
| /*-----------------------------------------------------------*/ |
| |
| /** Determine if a given character can be a label id starter. |
| * @param c the character in question. |
| */ |
| protected static boolean is_id_start(char c) |
| { |
| return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_'); |
| |
| //later need to handle non-8-bit chars here |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Determine if a character can be in a label id. |
| * @param c the character in question. |
| */ |
| protected static boolean is_id_char(char c) |
| { |
| return is_id_start(c) || (c >= '0' && c <= '9'); |
| } |
| |
| /*-----------------------------------------------------------*/ |
| /*--- General Methods ---------------------------------------*/ |
| /*-----------------------------------------------------------*/ |
| |
| /** Determine the translation for one label id found within a code_string. |
| * Symbols appearing in the RHS correspond to objects found on the parse |
| * stack at runtime. The code to access them, becomes code to access an |
| * object at the appropriate offset from the top of the stack, and then |
| * cast that to the proper type. |
| * |
| * @param id_str the name of the id to be translated. |
| * @param act_pos the original position of the action it appears in. |
| * @param label_map a hash table mapping labels to positions in the RHS. |
| * @param type_map a hash table mapping labels to declared symbol types. |
| */ |
| protected String label_translate( |
| String id_str, /* the id string we are (possibly) translating */ |
| int act_pos, /* position of the action */ |
| Hashtable label_map, /* map from labels to positions in the RHS */ |
| Hashtable label_types)/* map from labels to stack types */ |
| { |
| Integer label_pos; |
| String label_type; |
| int offset; |
| |
| /* look up the id */ |
| label_pos = (Integer)label_map.get(id_str); |
| |
| /* if we don't find it, just return the id */ |
| if (label_pos == null) return id_str; |
| |
| /* extract the type of the labeled symbol */ |
| label_type = (String)label_types.get(id_str); |
| |
| /* is this for the LHS? */ |
| if (label_pos.intValue() == -1) |
| { |
| /* return the result object cast properly */ |
| return "((" + label_type + ")" + emit.pre("result") + ")"; |
| } |
| |
| /* its a RHS label */ |
| |
| /* if the label appears after the action, we have an error */ |
| if (label_pos.intValue() > act_pos) |
| { |
| /* emit an error message */ |
| System.err.println("*** Label \"" + id_str + |
| "\" appears in action before it appears in production"); |
| lexer.error_count++; |
| |
| // later need to print the production this is in |
| |
| /* just return the id unchanged */ |
| return id_str; |
| } |
| |
| /* calculate the stack offset as the difference in position from |
| label to action minus one */ |
| offset = (act_pos - label_pos.intValue())-1; |
| |
| /* translation is properly cast element at that offset from TOS */ |
| return "(/*"+id_str+"*/("+label_type+")" + |
| emit.pre("stack") + ".elementAt(" + emit.pre("top") +"-"+ offset + "))"; |
| |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Translate all the label names within an action string to appropriate code. |
| * @param act_string the string to be translated |
| * @param act_pos the position that the action originally held in the |
| * production. |
| * @param label_map a hash table mapping labels to positions in the RHS. |
| * @param type_map a hash table mapping labels to declared symbol types. |
| */ |
| protected String action_translate( |
| String act_string, /* the action string */ |
| int act_pos, /* the position of the action on the RHS */ |
| Hashtable label_map, /* map from labels to RHS positions */ |
| Hashtable label_types) /* map from labels to symbol stack types */ |
| { |
| int id_start; |
| int pos; |
| int len; |
| String id_str; |
| boolean in_id; |
| StringBuffer result; |
| char buffer[]; |
| |
| /* if we have no string we are done */ |
| if (act_string == null || act_string.length()== 0) return act_string; |
| |
| len = act_string.length(); |
| |
| /* set up a place to put the result */ |
| result = new StringBuffer(len + 50); |
| |
| /* extract string into array */ |
| buffer = new char[len + 1]; |
| act_string.getChars(0, len, buffer, 0); |
| |
| /* put terminator in buffer so we can look one past the end */ |
| buffer[len] = '\0'; |
| |
| /* walk down the input buffer looking for identifiers */ |
| in_id = false; |
| for (pos = id_start = 0; pos <= len; pos++) |
| { |
| /* are we currently working on an id? */ |
| if (in_id) |
| { |
| /* does this end the id? */ |
| if (!is_id_char(buffer[pos])) |
| { |
| /* extract the id string and translate it */ |
| id_str = new String(buffer, id_start, pos - id_start); |
| result.append( |
| label_translate(id_str, act_pos, label_map,label_types)); |
| |
| /* copy over the ending character */ |
| if (buffer[pos] != '\0') |
| result.append(buffer, pos, 1); |
| |
| /* and we are done with this id */ |
| in_id = false; |
| } |
| else |
| { |
| /* we are still in the id, so just keep going */ |
| } |
| } |
| else /* we are not inside an id */ |
| { |
| /* start a new id? */ |
| if (is_id_start(buffer[pos])) |
| { |
| /* start keeping these chars as an id */ |
| in_id = true; |
| id_start = pos; |
| } |
| else |
| { |
| /* just copy over the char */ |
| if (buffer[pos] != '\0') |
| result.append(buffer, pos, 1); |
| } |
| } |
| } |
| |
| /* return the accumulated result */ |
| return result.toString(); |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Translate label names to appropriate code within all action strings. |
| * @param rhs array of RHS parts. |
| * @param rhs_len how much of rhs to consider valid. |
| * @param final_action the final action string of the production. |
| * @param lhs_type the object type associated with the LHS symbol. |
| */ |
| protected String translate_labels( |
| production_part rhs[], |
| int rhs_len, |
| String final_action, |
| String lhs_type) |
| { |
| Hashtable label_map = new Hashtable(11); |
| Hashtable label_types = new Hashtable(11); |
| symbol_part part; |
| action_part act_part; |
| int pos; |
| |
| /* walk down the parts and extract the labels */ |
| for (pos = 0; pos < rhs_len; pos++) |
| { |
| if (!rhs[pos].is_action()) |
| { |
| part = (symbol_part)rhs[pos]; |
| |
| /* if it has a label enter it in the tables */ |
| if (part.label() != null) |
| { |
| label_map.put(part.label(), new Integer(pos)); |
| label_types.put(part.label(), part.the_symbol().stack_type()); |
| } |
| } |
| } |
| |
| /* add a label for the LHS */ |
| label_map.put("RESULT", new Integer(-1)); |
| label_types.put("RESULT", lhs_type); |
| |
| /* now walk across and do each action string */ |
| for (pos = 0; pos < rhs_len; pos++) |
| { |
| if (rhs[pos].is_action()) |
| { |
| act_part = (action_part)rhs[pos]; |
| act_part.set_code_string( |
| action_translate( |
| act_part.code_string(), pos, label_map, label_types)); |
| } |
| } |
| |
| /* now do the final action string at the position after the last */ |
| return action_translate(final_action, rhs_len, label_map, label_types); |
| |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Helper routine to merge adjacent actions in a set of RHS parts |
| * @param rhs_parts array of RHS parts. |
| * @param len amount of that array that is valid. |
| * @return remaining valid length. |
| */ |
| protected int merge_adjacent_actions(production_part rhs_parts[], int len) |
| { |
| int from_loc, to_loc, merge_cnt; |
| |
| /* bail out early if we have no work to do */ |
| if (rhs_parts == null || len == 0) return 0; |
| |
| merge_cnt = 0; |
| to_loc = -1; |
| for (from_loc=0; from_loc<len; from_loc++) |
| { |
| /* do we go in the current position or one further */ |
| if (to_loc < 0 || !rhs_parts[to_loc].is_action() |
| || !rhs_parts[from_loc].is_action()) |
| { |
| /* next one */ |
| to_loc++; |
| |
| /* clear the way for it */ |
| if (to_loc != from_loc) rhs_parts[to_loc] = null; |
| } |
| |
| /* if this is not trivial? */ |
| if (to_loc != from_loc) |
| { |
| /* do we merge or copy? */ |
| if (rhs_parts[to_loc] != null && rhs_parts[to_loc].is_action() && |
| rhs_parts[from_loc].is_action()) |
| { |
| /* merge */ |
| rhs_parts[to_loc] = new action_part( |
| ((action_part)rhs_parts[to_loc]).code_string() + |
| ((action_part)rhs_parts[from_loc]).code_string()); |
| merge_cnt++; |
| } |
| else |
| { |
| /* copy */ |
| rhs_parts[to_loc] = rhs_parts[from_loc]; |
| } |
| } |
| } |
| |
| /* return the used length */ |
| return len - merge_cnt; |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Helper routine to strip a trailing action off rhs and return it |
| * @param rhs_parts array of RHS parts. |
| * @param len how many of those are valid. |
| * @return the removed action part. |
| */ |
| protected action_part strip_trailing_action( |
| production_part rhs_parts[], |
| int len) |
| { |
| action_part result; |
| |
| /* bail out early if we have nothing to do */ |
| if (rhs_parts == null || len == 0) return null; |
| |
| /* see if we have a trailing action */ |
| if (rhs_parts[len-1].is_action()) |
| { |
| /* snip it out and return it */ |
| result = (action_part)rhs_parts[len-1]; |
| rhs_parts[len-1] = null; |
| return result; |
| } |
| else |
| return null; |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Remove all embedded actions from a production by factoring them |
| * out into individual action production using new non terminals. |
| * if the original production was: <pre> |
| * A ::= B {action1} C {action2} D |
| * </pre> |
| * then it will be factored into: <pre> |
| * A ::= B NT$1 C NT$2 D |
| * NT$1 ::= {action1} |
| * NT$2 ::= {action2} |
| * </pre> |
| * where NT$1 and NT$2 are new system created non terminals. |
| */ |
| protected void remove_embedded_actions() throws internal_error |
| { |
| non_terminal new_nt; |
| production new_prod; |
| |
| /* walk over the production and process each action */ |
| for (int act_loc = 0; act_loc < rhs_length(); act_loc++) |
| if (rhs(act_loc).is_action()) |
| { |
| /* create a new non terminal for the action production */ |
| new_nt = non_terminal.create_new(); |
| |
| /* create a new production with just the action */ |
| new_prod = new action_production(this, new_nt, null, 0, |
| ((action_part)rhs(act_loc)).code_string()); |
| |
| /* replace the action with the generated non terminal */ |
| _rhs[act_loc] = new symbol_part(new_nt); |
| } |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Check to see if the production (now) appears to be nullable. |
| * A production is nullable if its RHS could derive the empty string. |
| * This results when the RHS is empty or contains only non terminals |
| * which themselves are nullable. |
| */ |
| public boolean check_nullable() throws internal_error |
| { |
| production_part part; |
| symbol sym; |
| int pos; |
| |
| /* if we already know bail out early */ |
| if (nullable_known()) return nullable(); |
| |
| /* if we have a zero size RHS we are directly nullable */ |
| if (rhs_length() == 0) |
| { |
| /* stash and return the result */ |
| return set_nullable(true); |
| } |
| |
| /* otherwise we need to test all of our parts */ |
| for (pos=0; pos<rhs_length(); pos++) |
| { |
| part = rhs(pos); |
| |
| /* only look at non-actions */ |
| if (!part.is_action()) |
| { |
| sym = ((symbol_part)part).the_symbol(); |
| |
| /* if its a terminal we are definitely not nullable */ |
| if (!sym.is_non_term()) |
| return set_nullable(false); |
| /* its a non-term, is it marked nullable */ |
| else if (!((non_terminal)sym).nullable()) |
| /* this one not (yet) nullable, so we aren't */ |
| return false; |
| } |
| } |
| |
| /* if we make it here all parts are nullable */ |
| return set_nullable(true); |
| } |
| |
| /** set (and return) nullability */ |
| boolean set_nullable(boolean v) |
| { |
| _nullable_known = true; |
| _nullable = v; |
| return v; |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Update (and return) the first set based on current NT firsts. |
| * This assumes that nullability has already been computed for all non |
| * terminals and productions. |
| */ |
| public terminal_set check_first_set() throws internal_error |
| { |
| int part; |
| symbol sym; |
| |
| /* walk down the right hand side till we get past all nullables */ |
| for (part=0; part<rhs_length(); part++) |
| { |
| /* only look at non-actions */ |
| if (!rhs(part).is_action()) |
| { |
| sym = ((symbol_part)rhs(part)).the_symbol(); |
| |
| /* is it a non-terminal?*/ |
| if (sym.is_non_term()) |
| { |
| /* add in current firsts from that NT */ |
| _first_set.add(((non_terminal)sym).first_set()); |
| |
| /* if its not nullable, we are done */ |
| if (!((non_terminal)sym).nullable()) |
| break; |
| } |
| else |
| { |
| /* its a terminal -- add that to the set */ |
| _first_set.add((terminal)sym); |
| |
| /* we are done */ |
| break; |
| } |
| } |
| } |
| |
| /* return our updated first set */ |
| return first_set(); |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Equality comparison. */ |
| public boolean equals(production other) |
| { |
| if (other == null) return false; |
| return other._index == _index; |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Generic equality comparison. */ |
| public boolean equals(Object other) |
| { |
| if (!(other instanceof production)) |
| return false; |
| else |
| return equals((production)other); |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Produce a hash code. */ |
| public int hashCode() |
| { |
| /* just use a simple function of the index */ |
| return _index*13; |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Convert to a string. */ |
| public String toString() |
| { |
| String result; |
| |
| /* catch any internal errors */ |
| try { |
| result = "production [" + index() + "]: "; |
| result += ((lhs() != null) ? lhs().toString() : "$$NULL-LHS$$"); |
| result += " :: = "; |
| for (int i = 0; i<rhs_length(); i++) |
| result += rhs(i) + " "; |
| result += ";"; |
| if (action() != null && action().code_string() != null) |
| result += " {" + action().code_string() + "}"; |
| |
| if (nullable_known()) |
| if (nullable()) |
| result += "[NULLABLE]"; |
| else |
| result += "[NOT NULLABLE]"; |
| } catch (internal_error e) { |
| /* crash on internal error since we can't throw it from here (because |
| superclass does not throw anything. */ |
| e.crash(); |
| result = null; |
| } |
| |
| return result; |
| } |
| |
| /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ |
| |
| /** Convert to a simpler string. */ |
| public String to_simple_string() throws internal_error |
| { |
| String result; |
| |
| result = ((lhs() != null) ? lhs().the_symbol().name() : "NULL_LHS"); |
| result += " ::= "; |
| for (int i = 0; i < rhs_length(); i++) |
| if (!rhs(i).is_action()) |
| result += ((symbol_part)rhs(i)).the_symbol().name() + " "; |
| |
| return result; |
| } |
| |
| /*-----------------------------------------------------------*/ |
| |
| }; |