Search code examples
umlumlet

What is the markup language specification for umlet?


I have been looking all over the web site, Internet, and of course SO and can't seem to find a description or specification for the markup language being used in umlet.

In the example sequence diagram for example:

title: sample
_alpha:A~id1_|_beta:B~id2_|_gamma:G~id3_
id1->>id2:id1,id2
id2-/>id1:async Msg.
id3->>>id1:id1,id3
id1.>id3:id1,id3:async return Msg
id1->id1:id1:self
iframe{:interaction frame
id2->id3:id1,id3:async Msg.
iframe}

The ->, -->, etc are fairly obvious, but what the colons do?

Why are underlines needed, etc. Inquiring minds would like to know as this looks like a useful tool for sketching.


Solution

  • Found an annotated script (that doesn't seem valid as of 14.2):

    //UML2 style titles are optional.
    title:sequence diagram
    //the participating objects are separated using the pipe symbol "|". Their names may be underlined using underscores.
    _alpha:A_|_beta:B_|_gamma:G_
    //The following line describes a (synchonuous) message originating from the first object sent to the second object, while both objects are active.
    1->>2:1,2
    //This message is asynchronous being sent from the second object to the first object. The messages is named.
    2-/>1:async Msg.
    //A message from the third object to the first object; both objects are active.
    3->>>1:1,3
    //A named asynchronous return message in UML2 style (using a stick arrow head).
    1.>3:1,3:async return Msg
    //This is a self call of the first object.
    1->1:1:self
    //Interaction frames may be used to show optional or conditional blocks within sequence diagrams.
    iframe{:interaction frame
    2->3:2,3:async Msg.
    iframe}
    

    RE: BNF, there's umlet-elements/src/main/javacc/SequenceAllInOneParser.jj in the source:

    options {
      static              = false;
      IGNORE_CASE         = false;
      JAVA_UNICODE_ESCAPE = true;
      JDK_VERSION        = "1.6";
      // Not yet detected as a valid option by the Eclipse plugin, but works nonetheless. This is essential for GWT compatible generated code.
      JAVA_TEMPLATE_TYPE = "modern";  
    }
    
    
    PARSER_BEGIN(SequenceAllInOneParser)
    package com.baselet.element.facet.specific.sequence_aio.gen;
    
    import org.slf4j.Logger;import org.slf4j.LoggerFactory;
    
    import com.baselet.control.enums.LineType;
    import com.baselet.element.facet.specific.sequence_aio.Lifeline;
    import com.baselet.element.facet.specific.sequence_aio.Message.ArrowType;
    import com.baselet.element.facet.specific.sequence_aio.SequenceDiagramBuilder;
    import com.baselet.element.facet.specific.sequence_aio.SequenceDiagramException;
    
    public class SequenceAllInOneParser {
        private static final Logger log = LoggerFactory.getLogger(SequenceAllInOneParser.class);
    
        private boolean autoTick;
    
        /**
        * Replaces "\\\\" with "\\" in the output so that any further specified match replace pair can't match the replaced "\\".
        * @param matchReplacePairs
        * @return the string with the replacements
        */
        public static String backslashReplace(String input, String... matchReplacePairs) {
            if (matchReplacePairs.length % 2 == 1) {
                throw new IllegalArgumentException("matchReplacePairs must have an even number of elements.");
            }
            String split = "\\\\";
            StringBuilder strBuilder = new StringBuilder(input.length());
            int firstIndex = 0;
            // use the indexOf function instead of a split, because split uses regex and regex is not 100% supported by GWT
            int foundIndex = input.indexOf(split, firstIndex);
            while (firstIndex < input.length()) {
                int lastIndex = foundIndex == -1 ? input.length() : foundIndex;
                String tmp = input.substring(firstIndex, lastIndex);
                for (int j = 0; j < matchReplacePairs.length - 1; j += 2) {
                    tmp = tmp.replace(matchReplacePairs[j], matchReplacePairs[j + 1]);
                }
                strBuilder.append(tmp);
                if (foundIndex != -1) {
                    strBuilder.append('\\');
                }
                firstIndex = lastIndex + split.length();
                foundIndex = input.indexOf(split, firstIndex);
            }
            return strBuilder.toString();
        }
    
        /**
         * Small data container to pass all informations.
         */
        private class MessageArrowInfo {
            boolean fromLeftToRight;
            LineType lineType;
            ArrowType arrowType;
        }
    
        private class InteractionConstraint {
            private String lifelineId = null;
            private String text = "";
        }
    
        private class LifelineInterval {
            private String startId;
            private String endId;
        }
    }
    PARSER_END(SequenceAllInOneParser)
    
    /* defines input to be ignored */
    < * > SKIP:
    {
        " "
        | "\t"
    }
    
    < DIAGRAM_DEF_TEXT > SKIP:
    {
        "\n" : DEFAULT
        | "\r\n" : DEFAULT
        | "\r" : DEFAULT
    }
    
    < DEFAULT > TOKEN:
    {
        < DIAGRAM_OPTION_OVERRIDE_ID :"overrideIds=" >
        | < DIAGRAM_OPTION_AUTO_TICK: "autoTick=" >
        | < TRUE: "true" >
        | < FALSE: "false" >
        | < DIAGRAM_TITLE: "title=" > : DIAGRAM_DEF_TEXT
        | < DIAGRAM_DESC: "desc=" > : DIAGRAM_DEF_TEXT
        | < LIFELINE_DEFINITIONS: "obj=" > : LIFELINE_DEF
    }
    
    < DIAGRAM_DEF_TEXT > TOKEN:
    {
        < DIAGRAM_DEFINITION_TEXT:
            (
                ~ ["\\","\r","\n"]
                | ("\\" ["\\", "n"] )
            )+> : DIAGRAM_DEF_TEXT
    }
    
    < LIFELINE_DEF > TOKEN :
    {
        < LL_DEF_NEW_LINE: "\n" | "\r\n" | "\r" > : DEFAULT
        | < LIFELINE_DEF_DELIMITER: "|" >
        /*| < LIFELINE_TITLE_DELIMITER: "~" > integrated in the title */
        | < LIFELINE_ACTOR: "ACTOR" >
        | < LIFELINE_ACTIVE: "ACTIVE" >
        | < LIFELINE_CREATED_LATER :"CREATED_LATER" >
        | < LIFELINE_EXEC_SPEC_FROM_START: "EXECUTION" >
        | < LIFELINE_TITLE: /* since | and ~ are special characters which delimit the title they need to be escaped */
            ( 
                ~["~","\\","\n","\r","|"] 
                | ("\\" ["\\","~","|","n"])
            )+
            "~" >
    }
    
    < DEFAULT > TOKEN :
    {
        < TEXT_DELIMITER: ":" > : DIAGRAM_SEQ_TEXT
        | < LIST_DELIMITER: "," >
        | < OPEN_CURLY_BRACKET: "{" >
        | < CLOSE_CURLY_BRACKET: "}" >
        | < COMMAND_DELIMITER: ";" >
        | < T_DASH: "-" >
        | < T_DOT: "." >
        | < T_DDOT: ".." >
        | < T_EQ: "=" >
        | < MESSAGE_ARROW_LEFT_OPEN: "<" >
        | < MESSAGE_ARROW_LEFT_FILLED: "<<<" >
        | < MESSAGE_ARROW_RIGHT_OPEN: ">" >
        | < MESSAGE_ARROW_RIGHT_FILLED: ">>>" >
        | < MESSAGE_DURATION_INC: "+" >
        | < LOST: "lost" >
        | < FOUND: "found" >
        | < GATE: "gate" >
        | < START_COREGION: "coregionStart=" >
        | < END_COREGION: "coregionEnd=" >
        | < INVARIANT: "invariant=" >
        | < STATE_INVARIANT: "stateInvariant=" >
        | < EXEC_SPEC_START: "on=" >
        | < EXEC_SPEC_END: "off=" >
        | < LL_DESTROY: "destroy=" >
        | < REF: "ref=" >
        | < CONTINUATION: "continuation=" >
        | < TEXT_ON_LIFELINE: "text=" >
        | < TICK: "tick=" >
        | < COMBINED_FRAGMENT: "combinedFragment=" > : CF_OPERATOR
        | < INTERACTION_CONSTRAINT: "constraint=" >
        | < UNSIGNED_INT_CONSTANT: ( ["0" - "9"] ) + >
        | < DEFAULT_NEW_LINE: "\n" | "\r\n" | "\r" >
    }
    
    < LIFELINE_DEF, DEFAULT > TOKEN :
    {
        < LIFELINE_ID: ["a"-"z","A"-"Z"] (["a"-"z","A"-"Z","0"-"9"])* >
    }
    
    < DIAGRAM_SEQ_TEXT > TOKEN :
    {
        < TEXT_UNTIL_NEXT_COMMAND : ( /* since ; is special characters which delimit the text it must be escaped */ 
                ~["\\","\n","\r",";"] 
                | ("\\" ["\\",";","n"])
            )+ > : DEFAULT
    }
    
    < CF_OPERATOR > TOKEN :
    {
        < COMBINED_FRAGMENT_OPERATOR: /* since ; and ~ are special characters which delimit the operator they need to be escaped */
            ( 
                ~["~","\\","\n","\r",";"] 
                | ("\\" ["\\","~","|","n",";"])
            )+
            "~" > : DEFAULT
    }
    
    /* general Tokens */
    
    /* skip comments */
    < * > SKIP :
    {
        <"//"(~["\n","\r"])*>
    }
    
    < * > TOKEN :
    {
        <LAST_LINE_COMMENT: "//"(~["\n","\r"])*>
    }
    
    
    /**
     * The main function which parses the whole diagram.
     * Line comments are skipped (see Tokens)
     */
    SequenceDiagramBuilder start() :
    {
        String titleText = "";
        String descText = "";
        SequenceDiagramBuilder diagram  = new SequenceDiagramBuilder();
        autoTick = true;
    }
    {
        (
            (
                titleText=DiagramTitle() /* NL skip and change state to DEFAULT */
                | descText = DiagramDescription() /* NL skip and change state to DEFAULT */
                | Option(diagram) < DEFAULT_NEW_LINE >
                | LifelineDefinitions(diagram) /* NL part of the nonterminal */
    
            )+//diagram definition as new NT followed by NL alternating with sequence so that the sequence= can be removed
            Sequence(diagram)
        )
    
        (< LAST_LINE_COMMENT >)?
        <EOF>
        {
            diagram.setTitle(titleText);
            diagram.setText(descText);
            return diagram;
        }
    }
    
    String DiagramTitle() :
    {
        String text = "";
    }
    {
        < DIAGRAM_TITLE >
        (
            < DIAGRAM_DEFINITION_TEXT > { text = backslashReplace(token.image, "\\n","\n");}
        )?
        {return text;}
    }
    
    String DiagramDescription() :
    {
        String desc = "";
    }
    {
        < DIAGRAM_DESC >
        < DIAGRAM_DEFINITION_TEXT > { desc = backslashReplace(token.image, "\\n","\n");}
        {return desc;}
    }
    
    /**
     * Options for the whole diagram
     */
    void Option(SequenceDiagramBuilder diagram) :
    {
        boolean overrideIds;
    }
    {
        < DIAGRAM_OPTION_OVERRIDE_ID > overrideIds = booleanConstant()
        {
            diagram.setOverrideDefaultIds(overrideIds);
        }
        | < DIAGRAM_OPTION_AUTO_TICK > autoTick = booleanConstant()
    }
    
    /**
     * Defines all Lifelines
     */
    void LifelineDefinitions(SequenceDiagramBuilder diagram) :
    {}
    {
        < LIFELINE_DEFINITIONS > LifelineDef(diagram) (< LIFELINE_DEF_DELIMITER > LifelineDef(diagram))* < LL_DEF_NEW_LINE >
    }
    
    /**
     * Defines one Lifeline, the id can't be LIFELINE_ACTOR, LIFELINE_ACTIVE
     * or LIFELINE_CREATED_LATER because these are keywords.
     */
    void LifelineDef(SequenceDiagramBuilder diagram) :
    {
        String name = "";
        String id = null;
        boolean createdOnStart = true;
        Lifeline.LifelineHeadType headType = Lifeline.LifelineHeadType.STANDARD;
        boolean execSpecFromStart = false;
    }
    {
        name = LifelineDefTitleText() (id=LifelineId())?
        (
            < LIFELINE_ACTOR > { headType = Lifeline.LifelineHeadType.ACTOR; }
            | < LIFELINE_ACTIVE > { headType = Lifeline.LifelineHeadType.ACTIVE_CLASS; }
            | < LIFELINE_CREATED_LATER > { createdOnStart = false; }
            | < LIFELINE_EXEC_SPEC_FROM_START > { execSpecFromStart = true; }
        ) *
        {
            if("lost".equals(id) || "found".equals(id)) {
                throw new SequenceDiagramException("'lost' and 'found' are keywords and can not be used as lifeline identifiers.");
            }
            diagram.addLiveline(name, id, headType, createdOnStart, execSpecFromStart);
        }
    }
    
    /** can could be multiple lines */
    String LifelineDefTitleText() :
    {}
    {
        < LIFELINE_TITLE >
        {
            /* remove trailing ~ and handle the escaping of \, |, n and ~ */
            return backslashReplace(token.image.substring(0, token.image.length() - 1), "\\n", "\n", "\\~", "~", "\\|", "|");
        }
    }
    
    /**
     * Can't be one of the following, because these are keywords in the lifeline definition!
     * < LIFELINE_ACTOR: "ACTOR" >
     * < LIFELINE_ACTIVE: "ACTIVE" >
     * < LIFELINE_CREATED_LATER :"CREATED_LATER" >
     */
    String LifelineId() :
    {}
    {
        < LIFELINE_ID >
        {
            return token.image;
        }
    }
    
    void Sequence(SequenceDiagramBuilder diagram) :
    {}
    {
        (
            SequenceTick(diagram) < DEFAULT_NEW_LINE >
            | (
                SequenceElement(diagram)
                ( LOOKAHEAD(2) < COMMAND_DELIMITER > SequenceElement(diagram))*
                (< COMMAND_DELIMITER >)?
                < DEFAULT_NEW_LINE >
                { if(autoTick) { diagram.tick(); } }
            )
            | < DEFAULT_NEW_LINE >
        )*
    }
    
    void SequenceTick(SequenceDiagramBuilder diagram) :
    {
        int tickCount = 1;
    }
    {
        < TICK > (tickCount = unsignedIntConstant())?
        { diagram.tick(tickCount); }
    }
    
    void SequenceElement(SequenceDiagramBuilder diagram) :
    {}
    {
        (
            MessageOrGeneralOrderingOrText(diagram) /* Message, GeneralOrdering have a common prefix for more clarity a new nonterminal was created*/
            | Coregion(diagram)
            | DestroyLL(diagram)
            | ExecutionSpecification(diagram)
            | StateInvariant(diagram)
            //| (diagram) //general ordering, TimeConstraint TimeObservation and DurationConstraint Duration Observation missing
            | InteractionUse(diagram)
            | Continuation(diagram)
            | CombinedFragment(diagram)
        )
    }
    
    void MessageOrGeneralOrderingOrText(SequenceDiagramBuilder diagram) :
    {}
    {
        /*
         * (Message and GeneralOrdering) A common prefix is: <LIFELINE_ID> "." <LIFELINE_ID> "<"
         * All three have <LIFELINE_ID > as common prefix
         * therefore use LOOKAHEAD(2) if it is a text, if not use a LOOKAHEAD(5) to determine if it is a message or a general ordering
         */
        LOOKAHEAD(2) TextOnLifeline(diagram)
        | LOOKAHEAD(5) Message(diagram)
        | GeneralOrdering(diagram)
    }
    
    void CombinedFragment(SequenceDiagramBuilder diagram) :
    {
        LifelineInterval interval = new LifelineInterval();
        String operator = "";
        String id = null;
    }
    {
        (
            < COMBINED_FRAGMENT >
            (
                < COMBINED_FRAGMENT_OPERATOR >
                { operator = backslashReplace(token.image.substring(0, token.image.length() - 1), "\\n", "\n", "\\;", ";", "\\~", "~"); }
            )
            [
                id = LifelineId() 
                [interval = LifelineInterval()]
            ]
            { diagram.beginCombinedFragment(interval.startId, interval.endId, id, operator); }
        )
        | (
            < T_DASH > < T_DASH > (< T_EQ > id=LifelineId())?
            { diagram.endCombinedFragment(id); }
        )
        | (
            < T_DDOT > (< T_EQ > id=LifelineId())?
            { diagram.endAndBeginOperand(id); }
        )
    }
    
    void Message(SequenceDiagramBuilder diagram) :
    {
        String leftLifelineId = null;
        String rightLifelineId = null;
        String leftLifelineLocalId = null;
        String rightLifelineLocalId = null;
        MessageArrowInfo messageArrowInfo;
        String msgText = "";
        int lostCount = 0;
        int foundCount = 0;
        int gateCount = 0;
        int duration = 0;
    }
    {
        (
            (
                leftLifelineId = LifelineId() [LOOKAHEAD(2)< T_DOT > leftLifelineLocalId = LifelineId()]
                | < LOST > { leftLifelineId = "lost"; lostCount++; }
                | < FOUND > { leftLifelineId = "found"; foundCount++; }
                | < GATE > { leftLifelineId = "gate"; gateCount++; }
            )
            messageArrowInfo =  MessageArrow()
            (
                rightLifelineId = LifelineId() [< T_DOT > rightLifelineLocalId = LifelineId()]
                | < LOST > { rightLifelineId = "lost"; lostCount++; }
                | < FOUND > { rightLifelineId = "found"; foundCount++; }
                | < GATE > { rightLifelineId = "gate"; gateCount++; }
            )
            (duration = MessageDuration())?
            (msgText = TextUntilNewLine())?
        )
        {
            if(lostCount + foundCount + gateCount > 1) {
                throw new SequenceDiagramException("Error: 'lost', 'found' and 'gate' can only occur once per message.");
            }
            String send;
            String receive;
            String sendLocalId;
            String receiveLocalId;
            if(messageArrowInfo.fromLeftToRight) {
                send = leftLifelineId;
                receive = rightLifelineId;
                sendLocalId = leftLifelineLocalId;
                receiveLocalId = rightLifelineLocalId;
            }
            else {
                send = rightLifelineId;
                receive = leftLifelineId;
                sendLocalId = rightLifelineLocalId;
                receiveLocalId = leftLifelineLocalId;
            }
            if(gateCount > 0) {
                if(duration != 0) {
                    throw new SequenceDiagramException("Error: a messages with a gate can only have a duration of 0, but the duration was " + duration + ".");
                }
                if(send.equals("gate")) {
                    diagram.addSendGateMessage(receive, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, receiveLocalId);
                }
                else {
                    diagram.addReceiveGateMessage(send, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId);
                }
            }
            else if(send.equals("lost")) {
                throw new SequenceDiagramException("Error: 'lost' can only be on the receiving end of a message.");
            }
            else if(send.equals("found")) {
                if(duration != 0) {
                    throw new SequenceDiagramException("Error: 'lost' and 'found' messages can only have a duration of 0, but the duration was " + duration + ".");
                }
                diagram.addFoundMessage(receive, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, receiveLocalId);
            }
            else
            {
                if(receive.equals("lost")) {
                    if(duration != 0) {
                        throw new SequenceDiagramException("Error: 'lost' and 'found' messages can only have a duration of 0, but the duration was " + duration + ".");
                    }
                    diagram.addLostMessage(send, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId);
                }
                else if(receive.equals("found")) {
                    throw new SequenceDiagramException("Error: 'found' can only be on the sending end of a message.");
                }
                else {
                    diagram.addMessage(send, receive, duration, msgText, messageArrowInfo.lineType, messageArrowInfo.arrowType, sendLocalId, receiveLocalId);
                }
            }
        }
    }
    
    MessageArrowInfo MessageArrow():
    {
        MessageArrowInfo messageArrowInfo = new MessageArrowInfo();
    }
    {
            (
                (
                    messageArrowInfo.lineType = MessageArrowLineType()
                    (
                        < MESSAGE_ARROW_RIGHT_OPEN > { messageArrowInfo.arrowType = ArrowType.OPEN; }
                        | < MESSAGE_ARROW_RIGHT_FILLED >  { messageArrowInfo.arrowType = ArrowType.FILLED; }
                    )
                )
                {
                    messageArrowInfo.fromLeftToRight = true;
                }
                | (
                    (
                        < MESSAGE_ARROW_LEFT_OPEN > { messageArrowInfo.arrowType = ArrowType.OPEN; }
                        | < MESSAGE_ARROW_LEFT_FILLED >  { messageArrowInfo.arrowType = ArrowType.FILLED; }
                    )
                    messageArrowInfo.lineType = MessageArrowLineType()
                )
                {
                    messageArrowInfo.fromLeftToRight = false;
                }
            )
            { return messageArrowInfo; }
    }
    
    LineType MessageArrowLineType() :
    {
        LineType lineType;
    }
    {
        (
            < T_DASH > { lineType = LineType.SOLID; }
            | < T_DOT >  { lineType = LineType.DASHED; }
        )
        {
            return lineType;
        }
    }
    
    int MessageDuration() :
    {
        int duration;
    }
    {
        (
            (
                < MESSAGE_DURATION_INC > { duration = 1; }
                (
                    duration = unsignedIntConstant() 
                    | (< MESSAGE_DURATION_INC >  { duration++; })*
                )
            )
            | (
                < T_DASH > { duration = -1; }
                (
                    duration = unsignedIntConstant() { duration = -duration; }
                    | (< T_DASH >  { duration--; })*
                )
            )
        )
        {
            return duration;
        }
    }
    
    void GeneralOrdering(SequenceDiagramBuilder diagram):
    {
        String leftLifelineId = null;
        String rightLifelineId = null;
        String leftLifelineLocalId = null;
        String rightLifelineLocalId = null;
        boolean leftEarlier;
    }
    {
        leftLifelineId = LifelineId()
        < T_DOT >
        leftLifelineLocalId = LifelineId()
        (
            < MESSAGE_ARROW_RIGHT_OPEN > { leftEarlier = true; }
            | < MESSAGE_ARROW_LEFT_OPEN > { leftEarlier = false; }
        )
        rightLifelineId = LifelineId()
        < T_DOT >
        rightLifelineLocalId = LifelineId()
        {
            if(leftEarlier) {
                diagram.addGeneralOrdering(leftLifelineId, leftLifelineLocalId, rightLifelineId, rightLifelineLocalId);
            }
            else {
                diagram.addGeneralOrdering(rightLifelineId, rightLifelineLocalId, leftLifelineId, leftLifelineLocalId);
            }
        }
    }
    
    void Coregion(SequenceDiagramBuilder diagram) :
    {
        String lifelineId;
        boolean start;
    }
    {
        (
            < START_COREGION > { start = true; }
            | < END_COREGION > { start = false; }
        )
        lifelineId = LifelineId()
        {
            diagram.addCoregion(lifelineId, start);
        }
    }
    
    void DestroyLL(SequenceDiagramBuilder diagram) :
    {
        String lifelineId;
    }
    {
        < LL_DESTROY > 
        lifelineId = LifelineId()
        {
            diagram.destroyLifeline(lifelineId);
        }
    }
    
    void ExecutionSpecification(SequenceDiagramBuilder diagram) :
    {
        String lifelineId;
        boolean on;
    }
    {
        (
            < EXEC_SPEC_START > { on = true; }
            | < EXEC_SPEC_END > { on = false; }
        )
        lifelineId = LifelineId()
        {
            diagram.changeExecutionSpecification(lifelineId, on);
        }
        ( (< LIST_DELIMITER >)? lifelineId = LifelineId()
            {
                diagram.changeExecutionSpecification(lifelineId, on);
            }
        )*
    }
    
    void StateInvariant(SequenceDiagramBuilder diagram) :
    {
        String lifelineId;
        String text = "";
        boolean stateStyle;
    }
    {
        (
            < INVARIANT > { stateStyle = false; }
            | < STATE_INVARIANT > { stateStyle = true; }
        ) 
        lifelineId = LifelineId()
        (text = TextUntilNewLine())?
        {
            diagram.addStateInvariant(lifelineId, text, stateStyle);
        }
    }
    
    void TextOnLifeline(SequenceDiagramBuilder diagram) :
    {
        String lifelineId;
        String text = "";
    }
    {
        [< TEXT_ON_LIFELINE >]
        lifelineId = LifelineId()
        text = TextUntilNewLine() /* text is mandatory, if not lookahead with message and general ordering would not work*/ 
        {
            diagram.addTextOnLifeline(lifelineId, text);
        }
    }
    
    String TextUntilNewLine() :
    {
    }
    {
        < TEXT_DELIMITER >
        < TEXT_UNTIL_NEXT_COMMAND >
        {
            return backslashReplace(token.image, "\\n", "\n", "\\;", ";");
        }
    }
    
    void InteractionUse(SequenceDiagramBuilder diagram):
    {
        LifelineInterval interval;
        String text = "";
    }
    {
        (
            < REF >
            interval = LifelineInterval()
            (text = TextUntilNewLine())?
        )
        {
            diagram.addInteractionUse(interval.startId, interval.endId, text);
        }
    }
    
    void Continuation(SequenceDiagramBuilder diagram):
    {
        LifelineInterval interval;
        String text = "";
    }
    {
        (
            < CONTINUATION >
            interval = LifelineInterval()
            (text = TextUntilNewLine())?
        )
        {
            diagram.addContinuation(interval.startId, interval.endId, text);
        }
    }
    
    /**
     * @return the start and end of the interval.
     */
    LifelineInterval LifelineInterval():
    {
        LifelineInterval interval = new LifelineInterval();
    }
    {
        interval.startId = LifelineId()
        (< LIST_DELIMITER >)?
        interval.endId = LifelineId()
        {
            return interval;
        }
    }
    
    boolean booleanConstant() :
    { boolean value;}
    {
        (
            <FALSE>  { value = false;}
            | <TRUE> { value = true;}
        ) {return value; }
    }
    
    int unsignedIntConstant() :
    {}
    {
        <UNSIGNED_INT_CONSTANT> {
            int value;
            try {
                value = Integer.parseInt(token.image);
            } catch(NumberFormatException e) {
                // only digits are accepted by the gramer, so the only reason for a NumberFormatException should be that the number is too big for int
                throw (ParseException) new ParseException("Error: The string '" + token.image + "' couldn't be parsed as integer. The most probable reason is that the number is too big.").initCause(e);
            }
            return value;
        }
    }