//4 empty lines to get replaced by
//the contents of preamble_{compile,csh}.yy
//files depending on whether we build parsers
//for compiling files or for color sytnax highlight (csh)

/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/

%require "3.8.2"
%expect 353
%locations
%define api.pure full
%define api.location.type {SourceLocationRange<C_S_H>}
%param{sv_reader<C_S_H> &input}
%initial-action { @$.set(input.current_pos()); };

//Goes to the parser.h files
%code requires{
    #include "gvcsh.h"
    #include "graphchart.h"
    #include "graph_lexer.h"

    //To allow including both graph_parse_{compile,csh}.h
    #undef C_S_H
    #undef CHAR_IF_CSH
    #undef FIRST_IF_CSH

    #ifdef C_S_H_IS_COMPILED
        #define C_S_H true
        #define CHAR_IF_CSH(A) char
        #define FIRST_IF_CSH(A,B) A
    #else
        #define C_S_H false
        #define CHAR_IF_CSH(A) A
        #define FIRST_IF_CSH(A,B) B
    #endif

    using namespace graph;
}

//Goes to the parser.cpp files
%code {
    #include "graph_tokenizer.h"
    #include "graph_lexer.h"

    #ifdef C_S_H_IS_COMPILED
        void graph_csh_error(SourceLocationRange<C_S_H>* pos, Csh &csh, graph::gv_parser_helper_csh &, sv_reader<C_S_H>&, const char *msg) {
            csh.AddCSH_Error(*pos, std::string(msg));
        }
        int graph_csh_lex(GRAPH_CSH_STYPE *lvalp, SourceLocationRange<C_S_H> *llocp, graph::GraphCsh& csh,
                          graph::gv_parser_helper_csh &helper, sv_reader<C_S_H>& input) {
            return get_token<graph_csh_tokentype>(input, csh,
                                                  graph::basic::readandclassify<sv_reader<C_S_H>>,
                                                  graph::html_string::readandclassify<sv_reader<C_S_H>>,
                                                  *lvalp, *llocp, helper);
        }
    #else
        void graph_compile_error(SourceLocationRange<C_S_H>* pos, Chart &chart, graph::gv_parser_helper_compile &, sv_reader<C_S_H>&, const char *msg) {
            chart.Error.Error(*pos, msg);
        }
        int graph_compile_lex(GRAPH_COMPILE_STYPE *lvalp, SourceLocationRange<C_S_H> *llocp, GraphChart& chart,
                              graph::gv_parser_helper_compile &helper, sv_reader<C_S_H>& input) {
            return get_token<graph_compile_tokentype>(input, chart,
                                                      graph::basic::readandclassify<sv_reader<C_S_H>>,
                                                      graph::html_string::readandclassify<sv_reader<C_S_H>>,
                                                      *lvalp, *llocp, helper);
        }
    #endif
}

%token TOK_STRICT TOK_GRAPH TOK_DIGRAPH TOK_SUBGRAPH TOK_NODE TOK_EDGE
       TOK_EDGEOP TOK_EQUAL TOK_CLUSTER
       TOK_COLON_STRING TOK_COLON_QUOTED_STRING
       TOK_STRING TOK_QSTRING TOK_OSBRACKET TOK_CSBRACKET TOK_COLON
       TOK_TILDE TOK_SEMICOLON TOK_PARAM_NAME TOK_OPARENTHESIS TOK_CPARENTHESIS
       TOK_COMMAND_DEFPROC TOK_COMMAND_REPLAY TOK_COMMAND_SET TOK_BYE
       TOK_IF TOK_THEN TOK_ELSE TOK_COMMAND_INCLUDE
       TOK_OCBRACKET TOK_CCBRACKET TOK_COMMA
       TOK_DEFDESIGN TOK_USEDESIGN TOK_DEFSTYLE TOK_EOF 0
       TOK_UNRECOGNIZED_CHAR
       TOK_WHITESPACE TOK_NEWLINE TOK_COMMENT

%union	{
    str_view                                                   str;
    bool                                                       boolean;
    int                                                        i;
    GraphEdgeType                                              edgetype;
    CHAR_IF_CSH(gsl::owner<Attribute*>)                        attr;
    CHAR_IF_CSH(gsl::owner<GraphStyle*>)                       attrlist;
    CHAR_IF_CSH(gsl::owner<GraphNodePort*>)                    node;
    CHAR_IF_CSH(gsl::owner<GraphNodePortList*>)                nodelist;
    FIRST_IF_CSH(int, gsl::owner<GraphEdgeList*>)              edgelist;
    CHAR_IF_CSH(EdgeAttrlist)                                  edgelist_attrlist;
    gsl::owner<std::vector<std::string>*>                      stringlist;
    const char*                                                input_text_ptr;
    int                                                        condition; //0:false, 1:true, 2:had_error
    ECompareOperator                                           compare_op;
    multi_segment_string                                       multi_str;
    CHAR_IF_CSH(gsl::owner<Procedure*>)                        procedure;
    CHAR_IF_CSH(const Procedure)*                              cprocedure;
    gsl::owner<CHAR_IF_CSH(ProcParamDef)*>                     procparamdef;      //the CSH version holds an owning string of param value, null if none
    CHAR_IF_CSH(gsl::owner<ProcParamDefList*>)                 procparamdeflist;
    CHAR_IF_CSH(gsl::owner<ProcParamInvocation*>)              procparaminvoc;
    CHAR_IF_CSH(gsl::owner<ProcParamInvocationList*>)          procparaminvoclist;
    CHAR_IF_CSH(gsl::owner<ProcDefParseHelper<GraphStyle>*>)   procdefhelper;
}

%type <i> attrtype
%type <boolean> graphtype TOK_GRAPH TOK_DIGRAPH
%type <edgetype> TOK_EDGEOP edgeop
%type <str> TOK_STRING reserved_word TOK_PARAM_NAME
%type <multi_str>
    string_single string_single_or_reserved_word string string_or_reserved_word
    multi_string_continuation tok_param_name_as_multi string_or_param
    string_or_reserved_word_or_param stylename designdefhdr include
    TOK_QSTRING TOK_COLON_STRING TOK_COLON_QUOTED_STRING
%type <attr> graphattrdef attritem attrdef
%type <attrlist> attrdefs attrlist full_attrlist full_attrlist_with_label
%type <node> node0 node1 node2 node
%type <nodelist> subgraph nodelist nodes close_scope body
%type <edgelist> chain
%type <edgelist_attrlist> chain_with_attrs
%type <stringlist> stylenamelist
%type <condition> condition ifthen_condition else
%type <compare_op> comp
%type <input_text_ptr> TOK_OCBRACKET TOK_CCBRACKET scope_open_proc_body scope_close_proc_body
%type <cprocedure> proc_invocation
%type <procedure> procedure_body
%type <procparamdeflist> proc_def_param_list proc_def_arglist proc_def_arglist_tested
%type <procparamdef> proc_def_param
%type <procparaminvoclist> proc_param_list proc_invoc_param_list
%type <procparaminvoc> proc_invoc_param
%type <procdefhelper> defprochelp1 defprochelp2 defprochelp3 defprochelp4

%destructor {
  #ifndef C_S_H_IS_COMPILED
    delete $$;
  #endif
} <attr> <attrlist> <node> <nodelist> <edgelist>
%destructor {
  #ifndef C_S_H_IS_COMPILED
    delete $$.edgelist;
    delete $$.style;
  #endif
} <edgelist_attrlist>
%destructor { } <str>
%destructor {delete $$;} <stringlist>
%destructor {
  #ifndef C_S_H_IS_COMPILED
    delete $$;
  #endif
} <procparamdeflist> <procparaminvoc> <procparaminvoclist> <procdefhelper> <procedure>
%destructor { if constexpr (C_S_H) free($$); else delete $$; } <procparamdef>
%destructor { $$.destroy(); } <multi_str>
%destructor {} <cprocedure> <input_text_ptr> <condition> <compare_op>
%destructor {
  #ifdef C_S_H_IS_COMPILED
    csh.PopContext();
  #else
    chart.PopContext();
  #endif
} ifthen_condition else

%%

main: list
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckLineStartHintAfter(@1) ||
        csh.CheckLineStartHintBefore(@1)) {
        csh.AddLineBeginToHintsOutsideGraph(true, true);
        csh.hintStatus = HINT_READY;
    }
  #endif
}
      |  /* empty */
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddLineBeginToHintsOutsideGraph(true, true);
    csh.hintStatus = HINT_READY;
    csh.hintSource = EHintSourceType::LINE_START;
    csh.hintsForcedOnly = true;
  #endif
};

top_level_command: designdef | usedesign | styledef | graph | option | defproc
      | completed_proc_invocation
      | string
{
  #ifdef C_S_H_IS_COMPILED
    if (!$1.had_error)
        csh.AddCSH_LineBeginOutsideGraph(@1, $1);
    if (csh.CheckHintAt(@1, EHintSourceType::LINE_START)) {
        csh.AddLineBeginToHintsOutsideGraph(true,true);
        csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@1, "Missing keyword or chart option.");
  #endif
    $1.destroy();
};

top_level_command_with_semi: top_level_command opt_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstruction(@$);
  #endif
}
      | include opt_semi
{
  #ifdef C_S_H_IS_COMPILED
  #else
    if (!$1.had_error && !$1.empty()) {
        auto text = chart.Include($1, @1);
        if (text.first && text.first->length() && text.second.IsValid())
            input.push(*text.first, EInclusionReason::INCLUDE, text.second, @$);
    }
  #endif
    $1.destroy();
}
      | TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_SEMICOLON);
  #endif
};


list: top_level_command_with_semi
{
  #ifdef C_S_H_IS_COMPILED
    parse_helper.last_top_level_command_pos = @$;
  #endif
}
      | list top_level_command_with_semi
{
  #ifdef C_S_H_IS_COMPILED
    parse_helper.last_top_level_command_pos = @2;
  #endif
}
      | list error top_level_command_with_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "I am not sure what this is.");
    parse_helper.last_top_level_command_pos = @3;
  #endif
};

option: attritem
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE)) {
        csh.AddOptionsValuesToHints();
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME)) {
        csh.AddOptionsToHints();
        csh.hintStatus = HINT_READY;
    }
  #else
	if ($1) {
        chart.AddChartOption(*$1);
		delete $1;
	}
  #endif
};

usedesign: TOK_USEDESIGN string
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    if (!$2.had_error)
        csh.AddCSH(@2, COLOR_ATTRVALUE);
    if (csh.CheckHintBetweenAndAt(@1, @2, EHintSourceType::ATTR_VALUE)) {
        csh.AddDesignsToHints(true);
        csh.AddDesignsToHints(false);
        csh.hintStatus = HINT_READY; //dont add linebegin hints later
    }
  #else
    if (!$2.had_error) {
        auto i = chart.Designs.find($2.view());
        if (i==chart.Designs.end())
            chart.Error.Error(@2, "Unknown design. Ignoring it.");
        else
            chart.MyCurrentContext().ApplyContextContent(i->second);
    }
  #endif
    $2.destroy();
}
      | TOK_USEDESIGN
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@1, "Missing design name to apply.");
    if (csh.CheckHintAfter(@1, EHintSourceType::ATTR_VALUE)) {
        csh.AddDesignsToHints(true);
        csh.AddDesignsToHints(false);
        csh.hintStatus = HINT_READY; //dont add linebegin hints later
    }
  #else
     chart.Error.Error(@1.after(), "Missing design name to apply. Ignoring statement.");
  #endif
};


designdef:  designdefhdr empty_open_scope attrinstrlist TOK_CCBRACKET
{
  #ifdef C_S_H_IS_COMPILED
	csh.BracePairs.push_back(@2+@4);
    csh.AddCSH(@4, COLOR_BRACE);
    csh.BracePairs.push_back(@2+@4);
    if (!csh.SkipContent()) {
        auto &d = csh.CurrentContext().IsFull() ? csh.FullDesigns : csh.PartialDesigns;
        auto i = d.find($1.view());
        if (i == d.end())
            d.emplace($1, csh.Contexts.back());
        else
            i->second += csh.Contexts.back();
    }
    csh.PopContext();
  #else
    if (!chart.SkipContent()) {
        //'scope_open_empty' pushed an empty color & style set onto the stack
        //then designelementlist added color & style definitions, procedures, etc., now we harvest those.
        //This is either a lookup if the design exists or creates a new empty design of this name
        auto i = chart.Designs.emplace(std::piecewise_construct,
                                       std::forward_as_tuple($1.str()),
                                       std::forward_as_tuple(chart.MyCurrentContext().IsFull(),
                                                             EContextParse::NORMAL,
                                                             EContextCreate::EMPTY,
                                                             @2));
        //we apply the content from current context all the same if inserted or existing
        i.first->second.ApplyContextContent(std::move(chart.MyCurrentContext())); //we can move, will pop below
    }
    auto ret = chart.PopContext().release();
    chart.FinalizeGraph(false);
    delete ret;
  #endif
    $1.destroy();
    (void) $4;
};

designdefhdr: TOK_DEFDESIGN string_or_reserved_word
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.SkipContent())
        csh.AddCSH_Error(@1, "Cannot define designs inside a procedure.");
    else
        csh.AddCSH(@1, COLOR_KEYWORD);
    if (!$2.had_error)
        csh.AddCSH(@2, COLOR_ATTRVALUE);
  #else
    if (chart.SkipContent())
        chart.Error.Error(@1, "Cannot define designs inside a procedure.");
    chart.NewGraph(true, false); //parameters do not matter much
    chart.lst_scope_is_graph = true; //so that we do not add a subgraph for the design context
  #endif
    $$ = $2;
};

stylename: string_or_reserved_word
{
  #ifdef C_S_H_IS_COMPILED
    if (!$1.had_error)
        csh.AddCSH(@1, COLOR_STYLENAME);
  #endif
    $$ = $1;
}
      | edgeop { $$.set($1.AsText()); };

stylenamelist: stylename
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintAt(@1, EHintSourceType::ATTR_VALUE)) {
        csh.AddStylesToHints(true, true);
        csh.hintStatus = HINT_READY;
    }
  #endif
    $$ = new std::vector<std::string>;
    if (!$1.had_error && !$1.empty())
        $$->emplace_back($1.str());
    $1.destroy();
}
      | stylenamelist TOK_COMMA
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_COMMA);
    csh.AddCSH_ErrorAfter(@2, "Missing another style name to define.");
    if (csh.CheckHintAfter(@2, EHintSourceType::ATTR_VALUE)) {
        csh.AddStylesToHints(true, true);
        csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@2.after(), "Missing another style name to define.");
  #endif
    $$ = $1;
}
      | stylenamelist TOK_COMMA stylename
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_COMMA);
    if (csh.CheckHintBetweenAndAt(@2, @3, EHintSourceType::ATTR_VALUE)) {
        csh.AddStylesToHints(true, true);
        csh.hintStatus = HINT_READY;
    }
  #endif
    $$ = $1;
    if (!$3.had_error && !$3.empty())
        $$->emplace_back($3.str());
    $3.destroy();
};

styledef: TOK_DEFSTYLE stylenamelist full_attrlist
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    if (!csh.SkipContent())
        for (auto &str : *($2))
            if (csh.ForbiddenStyles.find(str) == csh.ForbiddenStyles.end())
                csh.CurrentContext().StyleNames.insert(str);
    if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE)) {
        csh.AttributeValues(GraphStyle::ANY);
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME)) {
        csh.AttributeNames(GraphStyle::ANY);
        csh.hintStatus = HINT_READY;
    }
  #else
    if (chart.SkipContent())
        delete $3;
    else
	    chart.AddAttributeListToStyleList($3, $2); //deletes $3, as well
  #endif
    delete $2;
}
      | TOK_DEFSTYLE full_attrlist
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE)) {
        csh.AttributeValues(GraphStyle::ANY);
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME)) {
        csh.AttributeNames(GraphStyle::ANY);
        csh.hintStatus = HINT_READY;
    }
    csh.AddCSH_Error(@2, "Missing style name to define.");
  #else
    chart.Error.Error(@2, "Missing style name to define. Ignoring statement.");
    if ($2) delete $2;
  #endif
}
      | TOK_DEFSTYLE error full_attrlist
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_Error(@2, "Expecting a style name to define.");
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::ATTR_VALUE)) {
        csh.AddStylesToHints(true, true);
        csh.hintStatus = HINT_READY;
    }
    if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE)) {
        csh.AttributeValues(GraphStyle::ANY);
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME)) {
        csh.AttributeNames(GraphStyle::ANY);
        csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@2, "Missing style name to define. Ignoring statement.");
    if ($3) delete $3;
  #endif
}
      | TOK_DEFSTYLE stylenamelist
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@2, "Missing attributes enclosed in between '[' and ']'.");
  #else
    chart.Error.Error(@2.after(), "Missing attributes enclosed in between '[' and ']'. Statement has no effect.");
  #endif
    delete $2;
}
      | TOK_DEFSTYLE
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@1, "Expecting a style name (or a comma separated list of styles) to define.");
    if (csh.CheckHintAfter(@1, EHintSourceType::ATTR_VALUE)) {
        csh.AddStylesToHints(true, true);
        csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@1.after(), "Expecting a style name (or a comma separated list of styles) to define. Statement has no effect.");
  #endif
};

defproc: defprochelp1
{
  #ifdef C_S_H_IS_COMPILED
  #else
    if ($1) {
        if (chart.SkipContent()) {
            chart.Error.Error(@$, "Cannot define procedures inside a procedure.");
        } else if ($1->name.had_error) {
            //do nothing, error already reported
        } else if ($1->name.empty()) {
            chart.Error.Error($1->linenum_name, "Missing a procedure name to (re-)define. Ignoring this.");
        } else if (!$1->had_error && $1->body) {
            if (chart.MyCurrentContext().num_error != chart.Error.GetErrorNum(true, false)) {
                chart.Error.Error(@$, "There are warnings or errors inside the procedure definition. Ignoring it.");
                auto &proc = chart.MyCurrentContext().Procedures[$1->name.str()];
                proc.name = $1->name.str();
                proc.status = EDefProcResult::PROBLEM;
                proc.file_pos = $1->linenum_body;
            } else if ($1->body->status==EDefProcResult::OK || $1->body->status==EDefProcResult::EMPTY) {
                if ($1->parameters) {
                    auto &p = chart.MyCurrentContext().Procedures[$1->name.str()] = *$1->body;
                    p.name = $1->name.str();
                    p.parameters = std::move(*$1->parameters);
                    if ($1->attrs) for (auto &a : $1->attrs->attributes)
                        p.AddAttribute(a.second.GenerateAttribute(a.first.c_str(), false), chart);
                    if ($1->body->status==EDefProcResult::EMPTY)
                        chart.Error.Warning($1->linenum_body, "Empty procedure. Is this what you want?");
                } else {
                     chart.Error.Error(@$, "Ill-formed procedure parameter list. Ignoring this procedure definition.");
                }
            } else {
                 chart.Error.Error(@$, "Ill-formed procedure body. Ignoring this procedure definition.");
            }
        }
        delete $1;
    }
  #endif
}


defprochelp1: TOK_COMMAND_DEFPROC
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.SkipContent()) {
        csh.AddCSH_Error(@1, "Cannot define procedures inside a procedure.");
    } else {
        csh.AddCSH(@1, COLOR_KEYWORD);
        csh.AddCSH_ErrorAfter(@$, "Missing procedure name to (re-)define.");
    }
    if (csh.CheckLineStartHintAt(@1)) {
        csh.AddLineBeginToHints();
        csh.hintStatus = HINT_READY;
    }
  #else
    $$ = new ProcDefParseHelper<GraphStyle>;
    $$->linenum_name = @$.after();
  #endif
}
      | TOK_COMMAND_DEFPROC defprochelp2
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.SkipContent()) {
        csh.AddCSH_Error(@1, "Cannot define procedures inside a procedure.");
    } else {
        csh.AddCSH(@1, COLOR_KEYWORD);
    }
    if (csh.CheckLineStartHintAt(@1)) {
        csh.AddLineBeginToHints();
        csh.hintStatus = HINT_READY;
    }
  #else
    $$ = $2;
  #endif
};

defprochelp2: string_or_reserved_word
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PROCNAME);
    csh.AddCSH_ErrorAfter(@1, "Missing a procedure definition starting with '{'.");
    $1.destroy();
  #else
    $$ = new ProcDefParseHelper<GraphStyle>;
    $$->name = $1;
    $$->linenum_name = @1;
  #endif
}
      | string_or_reserved_word defprochelp3
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PROCNAME);
    $1.destroy();
  #else
    $$ = $2;
    $$->name = $1;
    $$->linenum_name = @1;
  #endif
}
      | defprochelp3
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(CshPos(@1.first_pos, @1.first_pos), "Missing procedure name.");
  #else
    $$ = $1;
    $$->linenum_name = @1;
  #endif
};

defprochelp3: proc_def_arglist_tested
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@1, "Missing a procedure definition starting with '{'.");
  #else
    $$ = new ProcDefParseHelper<GraphStyle>;
    $$->parameters = $1;
  #endif
}
      | proc_def_arglist_tested defprochelp4
{
  #ifdef C_S_H_IS_COMPILED
  #else
    $$ = $2;
    $$->parameters = $1;
  #endif
}
      | defprochelp4
{
  #ifdef C_S_H_IS_COMPILED
  #else
    $$ = $1;
    $$->parameters = new ProcParamDefList;
  #endif
};

defprochelp4: attrlist
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@1, "Missing a procedure definition starting with '{'.");
    if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME, @1))
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "export",
            "Set if styles and colors defined in the procedure remain valid after calling it.",
            EHintType::ATTR_NAME));
    else if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE, @1))
        csh.AddYesNoToHints();
  #else
    $$ = new ProcDefParseHelper<GraphStyle>;
    $$->attrs = $1;
  #endif
}
      | attrlist procedure_body
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME, @1))
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_ATTRNAME) + "export",
            "Set if styles and colors defined in the procedure remain valid after calling it.",
            EHintType::ATTR_NAME));
    else if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE, @1))
        csh.AddYesNoToHints();
  #else
    $$ = new ProcDefParseHelper<GraphStyle>;
    $$->body = $2;
    $$->linenum_body = @2;
    $$->attrs = $1;
  #endif
}
      | procedure_body
{
  #ifdef C_S_H_IS_COMPILED
  #else
    $$ = new ProcDefParseHelper<GraphStyle>;
    $$->body = $1;
    $$->linenum_body = @1;
  #endif
};


scope_open_proc_body: TOK_OCBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACE);
    csh.PushContext(true, EContextParse::SKIP_CONTENT);
  #else
    chart.MyCurrentContext().num_error = chart.Error.GetErrorNum(true, false);
    chart.NewGraph(true, false); //parameters do not matter much
    chart.lst_scope_is_graph = true; //so that we do not add a subgraph for the procedure definition context below in PushContext
    chart.PushContext(@1, EContextParse::SKIP_CONTENT, EContextCreate::EMPTY);
    chart.MyCurrentContext().parameters = std::move(parse_helper.last_procedure_params);
    chart.MyCurrentContext().starts_procedure = true;
    _ASSERT(parse_helper.open_context_mode == EScopeOpenMode::NORMAL);
    parse_helper.open_context_mode = EScopeOpenMode::NORMAL;
  #endif
    $$ = $1;
};

scope_close_proc_body: TOK_CCBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.PopContext();
    csh.AddCSH(@1, COLOR_BRACE);
  #else
    chart.PopContext();
    chart.FinalizeGraph(false);
  #endif
    $$ = $1;
};

proc_def_arglist_tested: proc_def_arglist
{
  #ifdef C_S_H_IS_COMPILED
  #else
    if ($1) {
        auto pair = Procedure::AreAllParameterNamesUnique(*$1);
        if (pair.first) {
            chart.Error.Error(pair.second->linenum_name, "This parameter name is already used.");
            chart.Error.Error(pair.first->linenum_name, pair.second->linenum_name, "This parameter name is already used.");
            delete $1;
            $$ = nullptr;
        } else {
            //Also copy to parse_helper.last_procedure_params and set open_context_mode
            auto &store = parse_helper.last_procedure_params;
            store.clear();
            for (const auto &p : *$1)
                store.emplace(p->name, ProcParamResolved(std::string(), FileLineCol(), true));
            $$ = $1;
        }
    } else
        $$ = nullptr;
  #endif
};

proc_def_arglist: TOK_OPARENTHESIS TOK_CPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH(@2, COLOR_PARENTHESIS);
  #else
    $$ = new ProcParamDefList;
  #endif
}
      | TOK_OPARENTHESIS error TOK_CPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH_Error(@2, "Invalid parameter definitions.");
    csh.AddCSH(@3, COLOR_PARENTHESIS);
  #else
    chart.Error.Error(@2, "Invalid parameter definitions.", "Say something like '($first, $second=default)'.");
    $$ = nullptr;
  #endif
}
      | TOK_OPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH_ErrorAfter(@1, "Missing parameter list closed by a parenthesis ')'.");
  #else
    chart.Error.Error(@1.after(), "Missing parameter list closed by a parenthesis ')'.");
    $$ = nullptr;
  #endif
}
      | TOK_OPARENTHESIS proc_def_param_list error TOK_CPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH_Error(@3, "Invalid parameter definitions.");
    csh.AddCSH(@4, COLOR_PARENTHESIS);
  #else
    chart.Error.Error(@3, "Invalid parameter definitions.");
    delete $2;
    $$ = nullptr;
  #endif
}
      | TOK_OPARENTHESIS proc_def_param_list
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH_ErrorAfter(@2, "Missing closing parenthesis ')'.");
  #else
    chart.Error.Error(@2.after(), "Missing closing parenthesis ')'.");
    delete $2;
    $$ = nullptr;
  #endif
}
      | TOK_OPARENTHESIS proc_def_param_list TOK_CPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH(@3, COLOR_PARENTHESIS);
  #else
    $$ = $2;
  #endif
};

proc_def_param_list: proc_def_param
{
  #ifdef C_S_H_IS_COMPILED
    free($1);
  #else
    if ($1) {
        $$ = new ProcParamDefList;
        ($$)->Append($1);
    } else
        $$= nullptr;
  #endif
}
      | proc_def_param_list TOK_COMMA
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_COMMA);
    csh.AddCSH_ErrorAfter(@2, "Missing parameter after the comma.");
  #else
    chart.Error.Error(@2.after(), "Missing parameter after the comma.");
    delete $1;
    $$= nullptr;
  #endif
}
      | proc_def_param_list TOK_COMMA proc_def_param
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_COMMA);
    free($3);
  #else
    if ($1 && $3) {
        ($1)->Append($3);
        $$ = $1;
    } else {
        delete $1;
        delete $3;
        $$= nullptr;
    }
  #endif
};

proc_def_param: TOK_PARAM_NAME
{
  #ifdef C_S_H_IS_COMPILED
    if ($1 && $1[0]=='$' && $1[1])
        csh.AddCSH(@1, COLOR_PARAMNAME);
    else
        csh.AddCSH_Error(@1, "Need name after the '$' sign.");
    $$ = nullptr; //no value
  #else
    if ($1 && $1[0]=='$' && $1[1]) {
        $$ = new ProcParamDef($1, @1);
    } else {
        chart.Error.Error(@1, "Need name after the '$' sign.");
        $$ = nullptr;
    }
  #endif
}
      | TOK_PARAM_NAME TOK_EQUAL
{
  #ifdef C_S_H_IS_COMPILED
    if ($1 && $1[0]=='$' && $1[1])
        csh.AddCSH(@1, COLOR_PARAMNAME);
    else
        csh.AddCSH_Error(@1, "Need name after the '$' sign.");
    csh.AddCSH(@2, COLOR_EQUAL);
    $$ = nullptr; //no value
  #else
    if ($1 && $1[0]=='$' && $1[1]) {
        $$ = new ProcParamDef($1, @1);
    } else {
        chart.Error.Error(@1, "Need name after the '$' sign.");
        $$ = nullptr;
    }
  #endif
}
      | TOK_PARAM_NAME TOK_EQUAL string
{
  #ifdef C_S_H_IS_COMPILED
    if ($1 && $1[0]=='$' && $1[1])
        csh.AddCSH(@1, COLOR_PARAMNAME);
    else
        csh.AddCSH_Error(@1, "Need name after the '$' sign.");
    csh.AddCSH(@2, COLOR_EQUAL);
    csh.AddCSH_ParamOrCond(@3, $3);
    $$ = $3.extract_owning();
  #else
    if ($3.had_error) {
        $$ = nullptr;
    } else if ($1 && $1[0]=='$' && $1[1]) {
        $$ = new ProcParamDef($1, @1, $3, @3);
    } else {
        chart.Error.Error(@1, "Need name after the '$' sign.");
        $$ = nullptr;
    }
    $3.destroy();
  #endif
};

procedure_body: scope_open_proc_body instrlist scope_close_proc_body
{
  #ifdef C_S_H_IS_COMPILED
    csh.BracePairs.push_back(@$);
  #else
    auto tmp = new Procedure;
    tmp->status = EDefProcResult::OK;
    tmp->text = std::string(($1), ($3)+1)+";";
    tmp->file_pos = @$;
    $$ = tmp;
  #endif
}
      | scope_open_proc_body scope_close_proc_body
{
  #ifdef C_S_H_IS_COMPILED
    csh.BracePairs.push_back(@$);
  #else
    auto tmp = new Procedure;
    tmp->status = EDefProcResult::EMPTY;
    tmp->file_pos = @$;
    $$ = tmp;
  #endif
  (void) $1;
  (void) $2;
}
      | scope_open_proc_body instrlist error scope_close_proc_body
{
  #ifdef C_S_H_IS_COMPILED
    csh.BracePairs.push_back(@$);
    csh.AddCSH_Error(@3, "Could not recognize this as a valid line.");
  #else
    auto tmp = new Procedure;
    tmp->status = EDefProcResult::PROBLEM;
    tmp->file_pos = @$;
    $$ = tmp;
    chart.Error.Error(@3, "syntax error.");
  #endif
    yyerrok;
  (void) $1;
  (void) $4;
}
      | scope_open_proc_body instrlist error TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddOpenBracePair(@$);
    csh.AddCSH_Error(@3, "Could not recognize this as a valid line.");
    csh.PopContext();
  #else
    auto tmp = new Procedure;
    tmp->status = EDefProcResult::PROBLEM;
    tmp->file_pos = @$;
    $$ = tmp;
    chart.PopContext();
    chart.Error.Error(@3, "Missing '}'.");
    chart.Error.Error(@1, @3, "Here is the corresponding '{'.");
  #endif
  (void) $1;
}
      | scope_open_proc_body instrlist TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddOpenBracePair(@$);
    csh.AddCSH_ErrorAfter(@2, "Missing a closing brace ('}').");
    csh.PopContext();
  #else
    auto tmp = new Procedure;
    tmp->status = EDefProcResult::PROBLEM;
    tmp->file_pos = @$;
    $$ = tmp;
    chart.PopContext();
    chart.Error.Error(@2.after(), "Missing '}'.");
    chart.Error.Error(@1, @2.after(), "Here is the corresponding '{'.");
  #endif
  (void) $1;
}
      | scope_open_proc_body TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddOpenBracePair(@$);
    csh.AddCSH_ErrorAfter(@1, "Missing a closing brace ('}').");
    csh.PopContext();
  #else
    auto tmp = new Procedure;
    tmp->status = EDefProcResult::PROBLEM;
    tmp->file_pos = @$;
    $$ = tmp;
    chart.PopContext();
    chart.Error.Error(@1.after(), "Missing a corresponding '}'.");
  #endif
  (void) $1;
}
      | scope_open_proc_body instrlist TOK_BYE
{
  #ifdef C_S_H_IS_COMPILED
    csh.BracePairs.push_back(@$);
    csh.AddCSH_Error(@3, "The command 'bye' can only be used at the top level.");
    csh.PopContext();
  #else
    auto tmp = new Procedure;
    tmp->status = EDefProcResult::PROBLEM;
    tmp->file_pos = @$;
    $$ = tmp;
    chart.PopContext();
    chart.Error.Error(@3, "The command 'bye' can not be used between curly braces '{' and '}'.");
    chart.Error.Error(@1, @3, "Here is the opening '{'.");
  #endif
    (void) $1;
}
      | scope_open_proc_body TOK_BYE
{
  #ifdef C_S_H_IS_COMPILED
    csh.BracePairs.push_back(@$);
    csh.AddCSH_Error(@2, "The command 'bye' can only be used at the top level and not inside curly braces '{' and '}'.");
    csh.PopContext();
  #else
    auto tmp = new Procedure;
    tmp->status = EDefProcResult::PROBLEM;
    tmp->file_pos = @$;
    $$ = tmp;
    chart.PopContext();
    chart.Error.Error(@2, "The command 'bye' can not be used between curly braces '{' and '}'.");
  #endif
    (void) $1;
};

set: TOK_COMMAND_SET proc_def_param
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    if ($2)
        free($2);
    else
        csh.AddCSH_ErrorAfter(@2, "Missing value.");
  #else
    if (!chart.SkipContent())
        chart.SetVariable($2, @$);
    else
        delete $2;
  #endif
}
      | TOK_COMMAND_SET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@1, "Missing variable or parameter name to set.");
  #else
    chart.Error.Error(@1.after(), "Missing variable or parameter name to set.");
  #endif
};


opt_semi: TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@$, COLOR_SEMICOLON);
  #endif
}
      | /*empty*/;

 /*This is used only in design defs, and not as an 'instruction', so we mark instructions here*/
attrinst_opt_semi:  styledef opt_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstruction(@$);
  #endif
}
      | attrinst opt_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstruction(@$);
    if (csh.CheckLineStartHintAfter(@2)) {
        csh.AddLineBeginToHintsInsideDesignDef();
        csh.hintStatus = HINT_READY;
    }
  #endif
};


attrinstrlist: attrinst_opt_semi | attrinstrlist attrinst_opt_semi | /*empty*/;

graph:  hdr body
{
  #ifdef C_S_H_IS_COMPILED
  #else
    delete $2; //discard returned references to nodes - nodes stored in graph
    chart.FinalizeGraph(true);
  #endif
}
			|  error body
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@1, "Syntax error, chart must start with either of the 'graph' or 'digraph' keywords.");
    csh.EntityNames.clear();
    csh.RefNames.clear();
  #else
    //Warning, here we have a 'body', which will call chart.GetCurrentGraph, without a call to
    //chart.NewGraph(), which happens in 'hdr'
    //GraphChart must survive this
    chart.Error.Error(@1, "Syntax error, chart must start with either of the 'graph' or 'digraph' keywords.");
    delete $2;
    chart.FinalizeGraph(false);
  #endif
}
			|  error TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@1, "Syntax error, chart must start with either of the 'graph' or 'digraph' keywords.");
  #else
    chart.Error.Error(@1, "Syntax error, chart must start with either of the 'graph' or 'digraph' keywords.");
  #endif
}
			|  hdr error body
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error, opening brace ('{') expected.");
    csh.EntityNames.clear();
    csh.RefNames.clear();
  #else
    chart.Error.Error(@2, "Syntax error, opening brace ('{') expected.");
    delete $3;
    chart.FinalizeGraph(true);
  #endif
}
			|  hdr error
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error, opening brace ('{') expected.");
    csh.EntityNames.clear();
    csh.RefNames.clear();
  #else
    chart.Error.Error(@2, "Syntax error, opening brace ('{') expected.");
    chart.FinalizeGraph(false);
  #endif
}
			|  hdr
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@1, "Syntax error, opening brace ('{') expected.");
    csh.EntityNames.clear();
    csh.RefNames.clear();
  #else
    chart.Error.Error(@1.after(), "Syntax error, opening brace ('{') expected.");
    chart.FinalizeGraph(false);
  #endif
};


body:         open_scope instrlist close_scope
{
  #ifdef C_S_H_IS_COMPILED
	csh.BracePairs.push_back(@$);
    if (csh.CheckLineStartHintBetween(@2, @3)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #else
    $$ = $3;
  #endif
}
      | open_scope instrlist error close_scope
{
  #ifdef C_S_H_IS_COMPILED
	csh.BracePairs.push_back(@$);
    csh.AddCSH_Error(@3, "Syntax error here.");
    if (csh.CheckLineStartHintBetween(@2, @3)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@3, "Syntax error here.");
    $$ = $4;
  #endif
}
      |open_scope close_scope
{
  #ifdef C_S_H_IS_COMPILED
	csh.BracePairs.push_back(@$);
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::LINE_START)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #else
    $$ = nullptr;
    delete $2;
  #endif
}
      |open_scope error close_scope
{
  #ifdef C_S_H_IS_COMPILED
	csh.BracePairs.push_back(@$);
    csh.AddCSH_Error(@2, "Syntax error here.");
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::LINE_START)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #else
    $$ = nullptr;
    chart.Error.Error(@2, "Syntax error here.");
    delete $3;
  #endif
}

      | open_scope instrlist TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddOpenBracePair(@$);
    csh.AddCSH_ErrorAfter(@$, "Missing a closing brace ('}').");
    if (csh.CheckLineStartHintAfter(@2)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
    csh.PopContext();
  #else
    chart.Error.Error(@2.after(), "Missing '}'.");
    if (@2.start().line != @2.end().line)
        chart.Error.Error(@1, @2.after(), "Here is the corresponding open_scope.");
    //close context ourselves
	$$ = chart.PopContext().release();
  #endif
}
      | open_scope instrlist error TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddOpenBracePair(@$);
    csh.AddCSH_Error(@3, "Syntax error here.");
    csh.AddCSH_ErrorAfter(@$, "Missing a closing brace ('}').");
    if (csh.CheckLineStartHintBetween(@2, @3)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
    csh.PopContext();
  #else
    chart.Error.Error(@3, "Syntax error here.");
    chart.Error.Error(@3.after(), "Missing '}'.");
    if (@3.start().line != @3.end().line)
        chart.Error.Error(@1, @3.after(), "Here is the corresponding open_scope.");
    //close context ourselves
	$$ = chart.PopContext().release();
  #endif
}
      | open_scope TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddOpenBracePair(@$);
    csh.AddCSH_ErrorAfter(@$, "Missing a closing brace ('}').");
    if (csh.CheckLineStartHintAfter(@1)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
    csh.PopContext();
  #else
    chart.Error.Error(@1.after(), "Missing '}'.");
    //close context ourselves
    $$ = chart.PopContext().release(); //will be empty list of nodes anyway.
  #endif
}
      | open_scope error TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddOpenBracePair(@$);
    csh.AddCSH_Error(@2, "Syntax error here.");
    csh.AddCSH_ErrorAfter(@2, "Missing a closing brace ('}').");
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::LINE_START)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
    csh.PopContext();
  #else
    chart.Error.Error(@2, "Syntax error here.");
    chart.Error.Error(@2.after(), "Missing '}'.");
    if (@2.start().line != @2.end().line)
        chart.Error.Error(@1, @2.after(), "Here is the corresponding '{'.");
    //close context ourselves
    $$ = chart.PopContext().release(); //will be empty list of nodes anyway.
  #endif
};


empty_open_scope: TOK_OCBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACE);
    csh.PushContext(false, EContextParse::NORMAL);
  #else
    //This is only called for design definitions
    chart.PushContext(@1, EContextParse::NORMAL, EContextCreate::CLEAR);
  #endif
    (void) $1; //suppress
};

open_scope: TOK_OCBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACE);
    csh.PushContext();
  #else
    if (parse_helper.open_context_mode == EScopeOpenMode::PROC_REPLAY) {
        //Open a scope to replay a procedure. The text of the procedure has
        //already been placed to the lex buffer stack (in fact the '{' already comes
        //from there), and the parameters are in YYEXTRA.
        parse_helper.open_context_mode = EScopeOpenMode::NORMAL;
        chart.PushContext(@1, EContextParse::REPARSING);
        chart.MyCurrentContext().starts_procedure = true;
        chart.MyCurrentContext().parameters = std::move(parse_helper.last_procedure_params);
        chart.MyCurrentContext().export_colors = parse_helper.last_procedure->export_colors;
        chart.MyCurrentContext().export_styles = parse_helper.last_procedure->export_styles;
        parse_helper.last_procedure = nullptr;
    } else {
        //Just open a regular scope
        chart.PushContext(@1);
        //if the scope is preceeded by a subgraph header, we have set the
        //file_pos of the subgraph created in PushContext(). If there were
        //no such header and this is an unnamed subgraph, we set it below.
        //SetLineEnd() will not change an already finalized file_pos, so
        //we can call it anyway. Howwever, if this scope is that of a graph
        //(and there are no subgraphs in the parse stack, we of course omit such a call).
        if (chart.parse_stack.size())
            chart.parse_stack.back()->SetLineEnd(@$);
    }
  #endif
    (void) $1; //suppress
};

close_scope: TOK_CCBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACE);
    csh.PopContext();
  #else
    $$ = chart.PopContext().release();
  #endif
    (void) $1; //suppress
};



hdr:	TOK_STRICT graphtype string_or_reserved_word
{
  #ifdef C_S_H_IS_COMPILED
    //Clean up after a potentially preceeding graph
    csh.EntityNames.clear();
    csh.RefNames.clear();
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH(@2, COLOR_KEYWORD);
    if (!$3.had_error)
        csh.AddCSH(@3, COLOR_ATTRVALUE);
    parse_helper.directed = $2;
  #else
    if ($3.had_error)
        chart.NewGraph($2, true);
    else
        chart.NewGraph($2, true, $3);
    parse_helper.directed = $2;
  #endif
    $3.destroy();
}
      | graphtype string_or_reserved_word
{
  #ifdef C_S_H_IS_COMPILED
    //Clean up after a potentially preceeding graph
    csh.EntityNames.clear();
    csh.RefNames.clear();
    csh.AddCSH(@1, COLOR_KEYWORD);
    if (!$2.had_error)
        csh.AddCSH(@2, COLOR_ATTRVALUE);
    if (csh.CheckLineStartHintBetween(parse_helper.last_top_level_command_pos, @1)) {
        csh.AddLineBeginToHintsOutsideGraph(true, false);
        csh.hintStatus = HINT_READY;
    }
    parse_helper.directed = $1;
  #else
    if ($2.had_error)
        chart.NewGraph($1, true);
    else
	chart.NewGraph($1, false, $2);
    parse_helper.directed = $1;
  #endif
    $2.destroy();
}
      | graphtype
{
  #ifdef C_S_H_IS_COMPILED
    //Clean up after a potentially preceeding graph
    csh.EntityNames.clear();
    csh.RefNames.clear();
	csh.AddCSH(@1, COLOR_KEYWORD);
    parse_helper.directed = $1;
    if (csh.CheckLineStartHintBetween(parse_helper.last_top_level_command_pos, @1)) {
        csh.AddLineBeginToHintsOutsideGraph(true, false);
        csh.hintStatus = HINT_READY;
    }
  #else
	chart.NewGraph($1, false);
    parse_helper.directed = $1;
  #endif
}
      | TOK_STRICT graphtype
{
  #ifdef C_S_H_IS_COMPILED
    //Clean up after a potentially preceeding graph
    csh.EntityNames.clear();
    csh.RefNames.clear();
	csh.AddCSH(@1, COLOR_KEYWORD);
	csh.AddCSH(@2, COLOR_KEYWORD);
    parse_helper.directed = $2;
  #else
	chart.NewGraph($2, true);
    parse_helper.directed = $2;
  #endif
}
      | TOK_STRICT
{
  #ifdef C_S_H_IS_COMPILED
    //Clean up after a potentially preceeding graph
    csh.EntityNames.clear();
    csh.RefNames.clear();
	csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@1, "Missing 'graph' or 'digraph' keywords.");
    if (csh.CheckHintAfter(@1, EHintSourceType::KEYWORD)) {
        csh.AddLineBeginToHintsOutsideGraph(false, true);
        csh.hintStatus = HINT_READY;
    }
    parse_helper.directed = true;
  #else
	chart.NewGraph(true, true);
    parse_helper.directed =true;
    chart.Error.Error(@1.after(), "Missing 'graph' or 'digraph' keywords.");
  #endif
};



graphtype	:	TOK_GRAPH | TOK_DIGRAPH;

instrlist:	instruction
      | instrlist instruction
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckLineStartHintBetween(@1, @2)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #endif
}
      | instrlist error instruction
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error.");
    if (csh.CheckLineStartHintBetween(@1, @2)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #endif
}
      | instrlist error
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error.");
    if (csh.CheckLineStartHintBetween(@1, @2)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #endif
};



instruction: instruction_no_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstruction(@$);
  #endif
}
      |instruction_no_semi TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstruction(@$);
	csh.AddCSH(@2, COLOR_SEMICOLON);
  #endif
}
      |instruction_no_semi error TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstruction(@$);
    csh.AddCSH_Error(@2, "Syntax error.");
	csh.AddCSH(@3, COLOR_SEMICOLON);
    if (csh.CheckLineStartHintBetween(@1, @2)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@2, "Syntax error.");
  #endif
}
      | include TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_SEMICOLON);
  #else
    if (!$1.had_error && !$1.empty()) {
        auto text = chart.Include($1, @1);
        if (text.first && text.first->length() && text.second.IsValid())
            input.push(*text.first, EInclusionReason::INCLUDE, text.second, @$);
    }
  #endif
    $1.destroy();
}
      | include
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@1, "Missing semicolon.");
  #else
    if (!$1.had_error && !$1.empty()) {
        auto text = chart.Include($1, @1);
        if (text.first && text.first->length() && text.second.IsValid())
            input.push(*text.first, EInclusionReason::INCLUDE, text.second, @$);
    }
    chart.Error.Error(@1.after(), "Missing a semicolon ';'.");
    chart.Error.Error(@1, @1.after(), "Here is the beginning of the command as I understood it.");
  #endif
    $1.destroy();
}
      | include error TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@3, COLOR_SEMICOLON);
    csh.AddCSH_Error(@2, "I am not sure what is coming here.");
    if (csh.CheckLineStartHintAfter(@3)) {
       csh.AddLineBeginToHints();
       csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@1.after(), "Missing a semicolon ';'.");
    chart.Error.Error(@1, @1.after(), "Here is the beginning of the command as I understood it.");
    if (!$1.had_error && !$1.empty()) {
        auto text = chart.Include($1, @1);
        if (text.first && text.first->length() && text.second.IsValid())
            input.push(*text.first, EInclusionReason::INCLUDE, text.second, @$);
    }
  #endif
    $1.destroy();
}
      | TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_SEMICOLON);
  #endif
}
      | completed_proc_invocation;


completed_proc_invocation: proc_invocation TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_SEMICOLON);
  #else
    if ($1)
        if (auto ctx = $1->MatchParameters(nullptr, @1.end(), &chart)) {
            input.push($1->text, EInclusionReason::PROCEDURE, $1->file_pos, @$);
            parse_helper.last_procedure = $1;
            parse_helper.last_procedure_params = std::move(*ctx);
            parse_helper.open_context_mode = EScopeOpenMode::PROC_REPLAY;
        }
  #endif
}
      | proc_invocation error TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@3, COLOR_SEMICOLON);
    csh.AddCSH_Error(@2, "I am not sure what is coming here.");
    if (csh.CheckLineStartHintAfter(@3)) {
       csh.AddLineBeginToHints();
       csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@1.after(), "Missing a semicolon TOK_SEMICOLON.");
    chart.Error.Error(@1, @1.after(), "Here is the beginning of the command as I understood it.");
    if ($1)
        if (auto ctx = $1->MatchParameters(nullptr, @1.end(), &chart)) {
            input.push($1->text, EInclusionReason::PROCEDURE, $1->file_pos, @$);
            parse_helper.last_procedure = $1;
            parse_helper.last_procedure_params = std::move(*ctx);
            parse_helper.open_context_mode = EScopeOpenMode::PROC_REPLAY;
        }
  #endif
}
      | proc_invocation
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@1, "Missing semicolon.");
  #else
    chart.Error.Error(@1.after(), "Missing a semicolon TOK_SEMICOLON.");
    chart.Error.Error(@1, @1.after(), "Here is the beginning of the command as I understood it.");
    if ($1)
        if (auto ctx = $1->MatchParameters(nullptr, @1.end(), &chart)) {
            input.push($1->text, EInclusionReason::PROCEDURE, $1->file_pos, @$);
            parse_helper.last_procedure = $1;
            parse_helper.last_procedure_params = std::move(*ctx);
            parse_helper.open_context_mode = EScopeOpenMode::PROC_REPLAY;
        }
  #endif
}
      | proc_invocation proc_param_list TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@3, COLOR_SEMICOLON);
  #else
    if ($1 && $2) {
        if (auto ctx = $1->MatchParameters($2, @2.end(), &chart)) {
            input.push($1->text, EInclusionReason::PROCEDURE, $1->file_pos, @$);
            parse_helper.last_procedure = $1;
            parse_helper.last_procedure_params = std::move(*ctx);
            parse_helper.open_context_mode = EScopeOpenMode::PROC_REPLAY;
        }
    } else
        delete $2;
  #endif
}
      | proc_invocation proc_param_list error TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@4, COLOR_SEMICOLON);
    csh.AddCSH_Error(@3, "I am not sure what is coming here.");
    if (csh.CheckLineStartHintAfter(@4)) {
       csh.AddLineBeginToHints();
       csh.hintStatus = HINT_READY;
    }
  #else
    chart.Error.Error(@2.after(), "Missing a semicolon TOK_SEMICOLON.");
    chart.Error.Error(@1, @2.after(), "Here is the beginning of the command as I understood it.");
    if ($1 && $2) {
        if (auto ctx = $1->MatchParameters($2, @2.end(), &chart)) {
            input.push($1->text, EInclusionReason::PROCEDURE, $1->file_pos, @$);
            parse_helper.last_procedure = $1;
            parse_helper.last_procedure_params = std::move(*ctx);
            parse_helper.open_context_mode = EScopeOpenMode::PROC_REPLAY;
        }
    } else
        delete $2;
  #endif
}
      | proc_invocation proc_param_list
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@2, "Missing semicolon.");
  #else
    chart.Error.Error(@2.after(), "Missing a semicolon TOK_SEMICOLON.");
    chart.Error.Error(@1, @2.after(), "Here is the beginning of the command as I understood it.");
    if ($1 && $2) {
        if (auto ctx = $1->MatchParameters($2, @2.end(), &chart)) {
            input.push($1->text, EInclusionReason::PROCEDURE, $1->file_pos, @$);
            parse_helper.last_procedure = $1;
            parse_helper.last_procedure_params = std::move(*ctx);
            parse_helper.open_context_mode = EScopeOpenMode::PROC_REPLAY;
        }
    } else
        delete $2;
  #endif
};

proc_invocation: TOK_COMMAND_REPLAY
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@1, "Missing procedure name.");
  #else
    chart.Error.Error(@1.after(), "Missing procedure name.");
    $$ = nullptr;
  #endif
}
      | TOK_COMMAND_REPLAY string_or_reserved_word
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH(@2, COLOR_PROCNAME);
  #else
    $$ = nullptr;
    if (!$2.had_error) {
        auto proc = chart.GetProcedure($2);
        if (proc==nullptr)
            chart.Error.Error(@2, "Undefined procedure. Ignoring procedure call.");
        else if (proc->status==EDefProcResult::PROBLEM)
            chart.Error.Error(@2, "Ill-formed procedure. Ignoring procedure call.");
        else if (proc->status==EDefProcResult::OK) {
            //Only return the procedure if we are not inside a procedure definition
            if (!chart.SkipContent())
                $$ = proc;
            //else just move on parsing - we do not reparse procedure replays during
            //the definition of an outer procedure.
        }
        //else return null, emit no error for EMPTY
    }
  #endif
    $2.destroy();
};

proc_param_list: TOK_OPARENTHESIS TOK_CPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH(@2, COLOR_PARENTHESIS);
  #else
    $$ = new ProcParamInvocationList;
  #endif
}
      | TOK_OPARENTHESIS error TOK_CPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH_Error(@2, "Invalid parameter syntax.");
    csh.AddCSH(@3, COLOR_PARENTHESIS);
  #else
    chart.Error.Error(@2, "Invalid parameter syntax. Ignoring procedure call.");
    $$ = nullptr;
  #endif
}
      | TOK_OPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH_ErrorAfter(@1, "Missing parameter list closed by a parenthesis ')'.");
  #else
    chart.Error.Error(@1.after(), "Missing parameter list closed by a parenthesis ')'. Ignoring procedure call.");
    $$ = nullptr;
  #endif
}
      | TOK_OPARENTHESIS proc_invoc_param_list error TOK_CPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH_Error(@3, "Invalid parameter syntax.");
    csh.AddCSH(@4, COLOR_PARENTHESIS);
  #else
    chart.Error.Error(@3, "Invalid parameter syntax. Ignoring procedure call.");
    delete $2;
    $$ = nullptr;
  #endif
}
      | TOK_OPARENTHESIS proc_invoc_param_list
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH_ErrorAfter(@2, "Missing closing parenthesis ')'.");
  #else
    chart.Error.Error(@2.after(), "Missing closing parenthesis ')'. Ignoring procedure call.");
    delete $2;
    $$ = nullptr;
  #endif
}
      | TOK_OPARENTHESIS proc_invoc_param_list TOK_CPARENTHESIS
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_PARENTHESIS);
    csh.AddCSH(@3, COLOR_PARENTHESIS);
  #else
    $$ = $2;
  #endif
};

proc_invoc_param_list: proc_invoc_param
{
  #ifdef C_S_H_IS_COMPILED
  #else
    if ($1) {
        $$ = new ProcParamInvocationList;
        ($$)->Append($1);
    } else
        $$= nullptr;
  #endif
}
      | TOK_COMMA
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_COMMA);
  #else
    $$ = new ProcParamInvocationList;
    ($$)->Append(new ProcParamInvocation(@1));
  #endif
}
      | TOK_COMMA proc_invoc_param
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_COMMA);
  #else
    if ($2) {
        $$ = new ProcParamInvocationList;
        ($$)->Append(std::make_unique<ProcParamInvocation>(@1));
        ($$)->Append($2);
    } else
        $$= nullptr;
  #endif
}
      | proc_invoc_param_list TOK_COMMA
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_COMMA);
  #else
    if ($1)
        ($1)->Append(std::make_unique<ProcParamInvocation>(@2.after()));
    $$ = $1;
  #endif
}
      | proc_invoc_param_list TOK_COMMA proc_invoc_param
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_COMMA);
  #else
    if ($1 && $3) {
        ($1)->Append($3);
        $$ = $1;
    } else {
        delete $1;
        delete $3;
        $$= nullptr;
    }
  #endif
};

proc_invoc_param: string
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ParamOrCond(@1, $1);
  #else
    if ($1.had_error)
        $$ = nullptr;
    else
        $$ = new ProcParamInvocation($1, @1);
  #endif
    $1.destroy();
};

include: TOK_COMMAND_INCLUDE
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@1, "Missing a file name to include. You must use quotation marks ('\"').");
    if (csh.CheckHintAfter(@1, EHintSourceType::ATTR_VALUE))
        csh.AddIncludeFilesToHints();
  #else
    chart.Error.Error(@1.after(), "Missing a file name to include. You must use quotation marks ('\"').");
  #endif
    $$.init();
}
      | TOK_COMMAND_INCLUDE TOK_QSTRING
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH(@2, COLOR_INCLUDEFILE);
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::ATTR_VALUE))
        csh.AddIncludeFilesToHints();
    else if(csh.CheckHintAt(@2, EHintSourceType::ATTR_VALUE))
        csh.AddIncludeFilesToHints($2, @2);
  #endif
    $$ = $2;
};


instruction_no_semi:  attrinst
      | styledef
      | defproc
      | usedesign
      | set
      | ifthen
{
  #ifdef C_S_H_IS_COMPILED
    csh.IfThenElses.push_back(@$);
  #endif
}
      | chain_with_attrs
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE)) {
        csh.AttributeValues($1 ? GraphStyle::EDGE : GraphStyle::NODE);
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME)) {
        csh.AttributeNames($1 ? GraphStyle::EDGE : GraphStyle::NODE);
        csh.hintStatus = HINT_READY;
    }
  #else
	if ($1.edgelist && $1.edgelist->size() && !chart.SkipContent()) {
		if ($1.edgelist->size()==1) {
			//This is just a list of nodes - no edges
			//Just apply the attributes to all of them - ignore ports
            if ($1.style) {
			    for (auto &pNodePort : $1.edgelist->front().nodes)
			        pNodePort.node->AddAttributeList($1.style, &chart);
                chart.GetCurrentGraph().AddAttrName(GraphStyle::EGraphElementType::NODE, *$1.style);
            }
		} else {
			//There is a list of edges, create all of them, and add
			//all attributes, as well.
            //First take out any key attribute
            std::string key;
            if ($1.style)
                for (auto i = $1.style->attributes.begin(); i!=$1.style->attributes.end(); i++)
                    if (i->first == "key") {
                        key = i->second.value;
                        $1.style->attributes.erase(i); //is becomes invalid
                        break;
                    }
			for (auto i = $1.edgelist->begin(), j = ++$1.edgelist->begin(); j!=$1.edgelist->end(); i++, j++)
				for (auto &pNodePort1 : i->nodes)
					for (auto &pNodePort2 : j->nodes) {
						auto pEdge = chart.CreateEdge(pNodePort1.node, pNodePort2.node, pNodePort1.port, pNodePort2.port, pNodePort1.port_file_pos, pNodePort2.port_file_pos);
                        pEdge->style = chart.MyCurrentContext().styles["edge"];
						pEdge->SetType(j->type, chart.MyCurrentContext().styles);
                        if ($1.style)
						    pEdge->AddAttributeList($1.style, &chart);
                        pEdge->name = key;
                        pEdge->SetLineEnd(j->pos);
					}
            if ($1.style)
                chart.GetCurrentGraph().AddAttrName(GraphStyle::EGraphElementType::EDGE, *$1.style);
		}
	}
	delete $1.edgelist;
	delete $1.style;
  #endif
};

chain_with_attrs: chain
{
  #ifndef C_S_H_IS_COMPILED
    $$.edgelist  = $1;
    $$.style = nullptr;
  #endif
}
      | chain full_attrlist_with_label
{
  #ifndef C_S_H_IS_COMPILED
    $$.edgelist  = $1;
    $$.style = $2;
  #endif
};

edgeop: TOK_EDGEOP
{
  #ifdef C_S_H_IS_COMPILED
//    if (($1.dir==GraphEdgeType::NO_ARROW) == parse_helper.directed) {
//        if (parse_helper.directed)
//	        csh.AddCSH_Error(@1, "Directed graphs expect directed edges. Use '->', '=>', '>', '>>' or bidirectional variants.");
//        else
//            csh.AddCSH_Error(@1, "Undirected graphs expect non-directed edges. Use '--', '==', '..' or '++'.");
//    } else
        csh.AddCSH(@1, COLOR_SYMBOL);
  #else
    //If we are defining a proc (and the context is empty, hence no pedantic chart option set)
    //we can assume pedantic==false and ignore any errors from mixed dir/undir edges. When we
    //replay the procedure, pedantic will be set as in the caller's context and these errors will
    //be emitted (if needed). If the context is empty for any other reason (not selected if/then/else
    //branch, defining a design), we better not emit an error, so assuming pedantic==false is OK overall..
    if (chart.MyCurrentContext().pedantic.value_or(false) && (($1.dir==GraphEdgeType::NO_ARROW) == parse_helper.directed)) {
        std::string msg;
        if (parse_helper.directed) {
	        msg = "Directed graphs expect directed edges. Assuming '";
            $1.dir = GraphEdgeType::FWD;
        } else {
	        msg = "Undirected graphs expect undirected edges. Assuming '";
            $1.dir = GraphEdgeType::NO_ARROW;
        }
        chart.Error.Warning(@1, msg << $1.AsText() << "'.", "Use 'pedantic=false' to turn off these warnings.");
    }
  #endif
  $$ = $1;
};

chain: nodes
{
  #ifdef C_S_H_IS_COMPILED
    $$ = 0; //how many set of nodes
    /* We located hints of entity (or line_start) */
    if (csh.CheckHintLocated(EHintSourceType::ENTITY, @1)) {
        csh.AddEntitiesToHints();
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::LINE_START, @1)) {
        if (csh.IsCursorAtLineBegin()) {
            csh.AddLineBeginToHintsInsideGraph();
            csh.hintStatus = HINT_READY;
        } else {
            csh.hintStatus = HINT_NONE;
        }
    }
  #else
	$$ = new GraphEdgeList();
	if ($1) {
		$$->emplace_back();
        $$->back().type = {GraphEdgeType::SOLID, GraphEdgeType::NO_ARROW};
        $$->back().nodes = std::move(*$1);
		delete $1;
	}
  #endif
}
      | chain edgeop nodes
{
  #ifdef C_S_H_IS_COMPILED
    $$ = ++$1;
    if (csh.CheckHintLocated(EHintSourceType::ENTITY, @3) ||
        csh.CheckHintLocated(EHintSourceType::LINE_START, @3)) {
        csh.AddEntitiesToHints();
        csh.hintSource = EHintSourceType::ENTITY;
        csh.hintStatus = HINT_READY;
    }
  #else
	if ($3) {
		$1->emplace_back();
        $1->back().type = $2;
        $1->back().pos = @2;
        $1->back().nodes = std::move(*$3);
		delete $3;
	}
	$$ = $1;
  #endif
}
      | chain error nodes
{
  #ifdef C_S_H_IS_COMPILED
    if (parse_helper.directed)
        csh.AddCSH_Error(@2, "Missing edge, e.g., '->'.");
    else
        csh.AddCSH_Error(@2, "Missing edge, e.g., '--'.");
    $$ = ++$1;
    if (csh.CheckHintLocated(EHintSourceType::ENTITY, @3) ||
        csh.CheckHintLocated(EHintSourceType::LINE_START, @3)) {
        csh.AddEntitiesToHints();
        csh.hintSource = EHintSourceType::ENTITY;
        csh.hintStatus = HINT_READY;
    }
  #else
	if ($3) {
		$1->emplace_back();
        $1->back().type = {GraphEdgeType::SOLID, parse_helper.directed ? GraphEdgeType::FWD : GraphEdgeType::NO_ARROW};
        $1->back().pos = @3;
        $1->back().nodes = std::move(*$3);
		delete $3;
	}
	$$ = $1;
    if (parse_helper.directed)
        chart.Error.Error(@2, "Missing edge symbol (e.g., '->').");
    else
        chart.Error.Error(@2, "Missing edge symbol (e.g., '--').");
  #endif
}
      | chain edgeop
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@2, "Missing a node, a set of nodes or a subgraph here.");
    if (csh.CheckHintAfter(@2, EHintSourceType::ENTITY)) {
        csh.AddEntitiesToHints();
        csh.hintStatus = HINT_READY;
    }
  #else
	$$ = $1;
    chart.Error.Error(@2.after(), "Missing a node, a set of nodes or a subgraph here.");
  #endif
};

nodes:	nodelist | subgraph | body;

 /* We locate hints of entity (or line_start) */
nodelist	: node
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintLocated(EHintSourceType::ENTITY, @1)) {
        csh.hintStatus = HINT_LOCATED;
        csh.hintSource = EHintSourceType::LINE_START;
    }
  #else
    $$ = new GraphNodePortList;
    if ($1) {
	$$->push_back(std::move(*$1));
	delete $1;
    }
  #endif
}
      | nodelist TOK_COMMA node
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddCSH(@2, COLOR_COMMA);
    csh.CheckHintBetweenAndAt(@2, @3, EHintSourceType::ENTITY);
  #else
    if ($3) {
	$1->push_back(std::move(*$3));
	delete $3;
    }
    $$ = $1;
  #endif
}
      | nodelist TOK_COMMA
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_COMMA);
    csh.CheckHintAfter(@2, EHintSourceType::ENTITY);
  #else
    $$ = $1;
  #endif
}
      | nodelist error TOK_COMMA
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error");
    csh.AddCSH(@3, COLOR_COMMA);
    csh.CheckHintAfter(@3, EHintSourceType::ENTITY);
  #else
    $$ = $1;
  #endif
};

node  : node0 | node1 | node2;

node0		: string
{
  #ifdef C_S_H_IS_COMPILED
    csh.CheckHintAt(@1, EHintSourceType::ENTITY);
    if (!$1.had_error)
        csh.AddCSH_KeywordOrEntity(@1, $1);   //Do it after AddLineBeginToHints so if this is a newly defined entity it one is not included among the hints
  #else
    if ($1.had_error) {
        $$ = nullptr;
    } else {
        if (chart.SkipContent()) {
            $$ = nullptr;
        } else {
            $$ = new GraphNodePort;
            //If node already exists we just fetch pointer
            if (chart.parse_stack.size())
                $$->node = chart.CreateNode(*chart.parse_stack.back(), $1); //add to current subgraph (will add to main graph, too)
            else
                $$->node = chart.CreateNode(chart.GetCurrentGraph(), $1); //no subgraph: add to main graph
            $$->node->SetLineEnd(@$); //If node already exists, it will not override previously set lineend
            $$->file_pos = @$;
        }
    }
  #endif
    $1.destroy();
};

node1        : node0 TOK_COLON string
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_SYMBOL);
    if (!$3.had_error)
        csh.AddCSH(@3, COLOR_ATTRVALUE);
  #else
    if ($1 && !$3.had_error) {
       $1->port = $3;
       $1->port_file_pos = @3;
    }
    $$ = $1;
  #endif
    $3.destroy();
}
      | node0 TOK_COLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_SYMBOL);
    csh.AddCSH_ErrorAfter(@2, "Missing port name.");
  #else
    chart.Error.Error(@2.after(), "Missing port name.");
    $$ = $1;
  #endif
};

node2        : node1 TOK_COLON string
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_SYMBOL);
    if (!$3.had_error)
        csh.AddCSH_Compass(@3, $3);
    if (csh.CheckHintBetweenAndAt(@2, @3, EHintSourceType::ATTR_VALUE, "headport"))
        csh.AttributeValues(GraphStyle::EDGE);
  #else
    if ($1 && !$3.had_error) {
        $1->port += ':';
        $1->port += $3;
        $1->compass_file_pos = @3;
    }
    $$ = $1;
  #endif
    $3.destroy();
}
      | node1 ':'
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@2, COLOR_SYMBOL);
    csh.AddCSH_ErrorAfter(@2, "Missing compass point.");
    if (csh.CheckHintAfter(@2, EHintSourceType::ATTR_VALUE, "headport"))
        csh.AttributeValues(GraphStyle::EDGE);
  #else
    chart.Error.Error(@2.after(), "Missing compass point.");
    $$ = $1;
  #endif
};


attrinst:  attrtype attrlist
{
    GraphStyle::EGraphElementType element_type;
    switch ($1) {
		case TOK_GRAPH: element_type = GraphStyle::GRAPH; break;
		case TOK_EDGE: element_type = GraphStyle::EDGE; break;
		case TOK_NODE: element_type = GraphStyle::NODE; break;
        default: element_type = GraphStyle::ANY; break;
    }
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE)) {
        csh.AttributeValues(element_type);
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME)) {
        csh.AttributeNames(element_type);
        csh.hintStatus = HINT_READY;
    }
  #else
    chart.DoAttributeInstruction(element_type, $2);
  #endif
}
			|  graphattrdef
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE)) {
        csh.AttributeValues(GraphStyle::CLUSTER);
        csh.AttributeValues(GraphStyle::GRAPH);
        csh.AttributeValues(GraphStyle::SUBGRAPH);
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME)) {
        csh.AttributeNames(GraphStyle::CLUSTER);
        csh.AttributeNames(GraphStyle::GRAPH);
        csh.AttributeNames(GraphStyle::SUBGRAPH);
        csh.hintStatus = HINT_READY;
    }
  #else
	if ($1) {
        chart.AddChartOption(*$1);
		delete $1;
	}
  #endif
};

attrtype :	TOK_GRAPH
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddCSH(@$, COLOR_KEYWORD);
  #endif
  $$ = TOK_GRAPH;
}
      | TOK_NODE
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddCSH(@$, COLOR_KEYWORD);
  #endif
  $$ = TOK_NODE;
}
      | TOK_EDGE
{
  #ifdef C_S_H_IS_COMPILED
	csh.AddCSH(@$, COLOR_KEYWORD);
  #endif
  $$ = TOK_EDGE;
};

 /*Should never return nullptr*/
full_attrlist_with_label: TOK_COLON_STRING
{
  #ifdef C_S_H_IS_COMPILED
  #else
    $$ = new GraphStyle(EStyleType::STYLE);
    $$->AddAttribute(Attribute("::", $1, @$, @$.IncStartCol()), &chart);
  #endif
    $1.destroy();
}
      | TOK_COLON_STRING full_attrlist
{
  #ifdef C_S_H_IS_COMPILED
  #else
    if ($2) {
        ($2)->AddAttribute(Attribute("::", $1, @1, @1.IncStartCol()), &chart);
        $$ = $2;
    } else {
        $$ = new GraphStyle(EStyleType::STYLE);
        $$->AddAttribute(Attribute("::", $1, @1, @1.IncStartCol()), &chart);
    }
  #endif
    $1.destroy();
}
      | full_attrlist TOK_COLON_STRING full_attrlist
{
  #ifdef C_S_H_IS_COMPILED
  #else
    if ($1) {
        ($1)->AddAttribute(Attribute("::", $2, @2, @2.IncStartCol()), &chart);
        if ($3) {
            *($1) += *($3);
            delete ($3);
        }
        $$ = $1;
    } else if ($3) {
        ($3)->AddAttribute(Attribute("::", $2, @2, @2.IncStartCol()), &chart);
        $$ = $3;
    } else {
        $$ = new GraphStyle(EStyleType::STYLE);
        $$->AddAttribute(Attribute("::", $2, @2, @2.IncStartCol()), &chart);
    }
  #endif
    $2.destroy();
}
      | full_attrlist TOK_COLON_STRING
{
  #ifdef C_S_H_IS_COMPILED
  #else
    if ($1) {
        ($1)->AddAttribute(Attribute("::", $2, @2, @2.IncStartCol()), &chart);
        $$ = $1;
    } else {
        $$ = new GraphStyle(EStyleType::STYLE);
        $$->AddAttribute(Attribute("::", $2, @2, @2.IncStartCol()), &chart);
    }
  #endif
    $2.destroy();
}
      | full_attrlist
{
  #ifdef C_S_H_IS_COMPILED
  #else
    if ($1) {
        $$ = $1;
    } else {
        $$ = new GraphStyle(EStyleType::STYLE);
    }
  #endif
};


full_attrlist : attrlist
      | full_attrlist attrlist
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::LINE_START)) {
        csh.AddLineBeginToHintsInsideGraph();
        csh.hintStatus = HINT_READY;
    }
  #else
    if ($1) {
        if ($2) {
            *$1 += *$2;
            $$ = $1;
            delete $2;
        } else {
            $$ = $1;
        }
    } else
        $$ = $2;
  #endif
}
      | full_attrlist error attrlist
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error.");
    csh.CheckHintBetween(@1, @2, EHintSourceType::LINE_START);
  #else
    chart.Error.Error(@2, "Syntax error.");
    if ($1) {
        if ($3) {
            *$1 += *$3;
            $$ = $1;
            delete $3;
        } else {
            $$ = $1;
        }
    } else
        $$ = $3;
  #endif
}


attrlist: TOK_OSBRACKET attrdefs TOK_CSBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    csh.AddCSH(@3, COLOR_BRACKET);
    csh.CheckHintBetween(@1, @2, EHintSourceType::ATTR_NAME);
    csh.CheckHintBetween(@2, @3, EHintSourceType::ATTR_NAME);
	csh.SqBracketPairs.push_back(@1 + @3);
  #else
	$$ = $2;
  #endif
}
      | TOK_OSBRACKET attrdefs error TOK_CSBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    csh.AddCSH(@4, COLOR_BRACKET);
    csh.CheckHintBetween(@1, @2, EHintSourceType::ATTR_NAME);
    csh.CheckHintBetween(@2, @3, EHintSourceType::ATTR_NAME);
	csh.SqBracketPairs.push_back(@1 + @4);
    csh.AddCSH_Error(@3, "Missing an attribute here ('name = value').");
  #else
	$$ = $2;
    chart.Error.Error(@3, "Missing an attribute here ('name = value').");
  #endif
}
      | TOK_OSBRACKET attrdefs
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    csh.CheckHintBetween(@1, @2, EHintSourceType::ATTR_NAME);
    csh.CheckHintBetween(@2, yylloc, EHintSourceType::ATTR_NAME);
	csh.SqBracketPairs.push_back(@1 + yylloc);
    csh.AddCSH_ErrorAfter(@2, "Missing a closing bracket (']').");
  #else
	$$ = $2;
    chart.Error.Error(@2.after(), "Missing ']'.");
  #endif
}
      | TOK_OSBRACKET stylename
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    if (csh.CheckHintBetweenAndAt(@1, @2, EHintSourceType::ATTR_NAME) && !csh.SkipContent())
        csh.AddStylesToHints(false, false); //leave in HINT_LOCATED to continue adding attr names
	csh.SqBracketPairs.push_back(@1 + yylloc);
    csh.AddCSH_ErrorAfter(@2, "Missing a closing bracket (']').");
  #else
    $$ = new GraphStyle(EStyleType::STYLE);
    if (!chart.SkipContent()) {
        auto i = chart.MyCurrentContext().styles.find($2.view());
        if (i!=chart.MyCurrentContext().styles.end())
            *$$ += i->second.read();
        else
            chart.Error.Error(@2, "Unknown style. Ignoring.");
    }
    chart.Error.Error(@2.after(), "Missing ']'.");
  #endif
	$2.destroy();
}
      | TOK_OSBRACKET stylename TOK_CSBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    csh.AddCSH(@3, COLOR_BRACKET);
    if (csh.CheckHintBetweenAndAt(@1, @2, EHintSourceType::ATTR_NAME) && !csh.SkipContent())
        csh.AddStylesToHints(false, false); //leave in HINT_LOCATED to continue adding attr names
	csh.SqBracketPairs.push_back(@1 + @3);
  #else
    $$ = new GraphStyle(EStyleType::STYLE);
    if (!chart.SkipContent()) {
        auto i = chart.MyCurrentContext().styles.find($2.view());
        if (i!=chart.MyCurrentContext().styles.end())
            *$$ += i->second.read();
        else
            chart.Error.Error(@2, "Unknown style. Ignoring.");
    }
  #endif
	$2.destroy();
}
      | TOK_OSBRACKET TOK_EOF
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::ATTR_NAME))
        csh.AddStylesToHints(false, false); //leave in HINT_LOCATED to continue adding attr names
	csh.SqBracketPairs.push_back(@1+@2);
    csh.AddCSH_ErrorAfter(@1, "Missing a closing bracket (']').");
  #else
	$$ = nullptr;
    chart.Error.Error(@1.after(), "Missing ']'.");
  #endif
}
      | TOK_OSBRACKET TOK_CSBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    csh.AddCSH(@2, COLOR_BRACKET);
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::ATTR_NAME))
        csh.AddStylesToHints(false, false); //leave in HINT_LOCATED to continue adding attr names
	csh.SqBracketPairs.push_back(@1+@2);
  #else
	$$ = nullptr;
  #endif
}
      | TOK_OSBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    if (csh.CheckHintAfter(@1, EHintSourceType::ATTR_NAME))
        csh.AddStylesToHints(false, false); //leave in HINT_LOCATED to continue adding attr names
	csh.SqBracketPairs.push_back(@1+yylloc);
  #else
	$$ = nullptr;
  #endif
}
      | TOK_OSBRACKET error TOK_CSBRACKET
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_BRACKET);
    csh.AddCSH(@3, COLOR_BRACKET);
    if (csh.CheckHintBetween(@1, @2, EHintSourceType::ATTR_NAME))
        csh.AddStylesToHints(false, false); //leave in HINT_LOCATED to continue adding attr names
	csh.SqBracketPairs.push_back(@1+@3);
    csh.AddCSH_Error(@2, "Missing an attribute here ('name = value').");
  #else
	$$ = nullptr;
    chart.Error.Error(@2, "Missing an attribute here ('name = value').");
  #endif
};

attrdefs: attrdefs attrdef
{
  #ifndef C_S_H_IS_COMPILED
    if ($2) {
	($1)->AddAttribute(*$2, &chart);
	delete $2;
    }
    $$ = $1;
  #endif
}
      | attrdefs string
{
  #ifdef C_S_H_IS_COMPILED
    csh.CheckHintBetweenAndAt(@1, @2, EHintSourceType::ATTR_NAME);
    if (!$2.had_error)
        csh.AddCSH_AttrName(@2, $2, COLOR_ATTRNAME);
  #else
    chart.Error.Error(@2.after(), "Missing equal sign ('=') to continue an attribute definition.");
    $$ = $1;
  #endif
    $2.destroy();
}             | attrdefs error attrdef
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error.");
  #else
    chart.Error.Error(@2, "Syntax error.");
	if ($3) {
		($1)->AddAttribute(*$3, &chart);
		delete $3;
	}
	$$ = $1;
  #endif
}
			| attrdef
{
  #ifndef C_S_H_IS_COMPILED
    $$ = new GraphStyle(EStyleType::STYLE);
    if ($1) {
        ($$)->AddAttribute(*$1, &chart);
	    delete $1;
    }
  #endif
};

attrdef:  attritem optseparator
      |  attritem error optseparator
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error.");
  #else
    chart.Error.Error(@2, "Syntax error.");
    $$ = $1;
  #endif
};

attritem:  string TOK_EQUAL string
{
  #ifdef C_S_H_IS_COMPILED
    if (!$1.had_error)
        csh.AddCSH_AttrName(@1, $1, COLOR_ATTRNAME);
    csh.AddCSH(@2, COLOR_EQUAL);
    if (!$1.had_error && !$3.had_error)
        csh.AddCSH_AttrValue(@3, $1, $3, COLOR_ATTRVALUE);
    csh.CheckHintAt(@1, EHintSourceType::ATTR_NAME);
    if (!$1.had_error)
        csh.CheckHintBetweenAndAt(@2, @3, EHintSourceType::ATTR_VALUE, $1);
  #else
    if ($1.had_error || $3.had_error ||
        (($1.had_param || $3.had_param) && chart.SkipContent()))
        $$ = nullptr;
    else
        $$ = new Attribute($1, $3, @$, @3);
  #endif
    $1.destroy();
    $3.destroy();
}
      | string TOK_EQUAL
{
  #ifdef C_S_H_IS_COMPILED
    if (!$1.had_error)
        csh.AddCSH_AttrName(@1, $1, COLOR_ATTRNAME);
    csh.AddCSH(@2, COLOR_EQUAL);
    csh.AddCSH_ErrorAfter(@$, "Missing attribute value.");
    csh.CheckHintAt(@1, EHintSourceType::ATTR_NAME);
    if (!$1.had_error)
        csh.CheckHintAfter(@2, EHintSourceType::ATTR_VALUE, $1);
  #else
    chart.Error.Error(@2.after(), "Missing attribute value.");
    $$ = nullptr;
  #endif
    $1.destroy();
};

comp: TOK_EDGEOP
{
    if ($1.type==GraphEdgeType::DOUBLE && $1.dir==GraphEdgeType::NO_ARROW)
        $$ = ECompareOperator::EQUAL;
    else if ($1.type==GraphEdgeType::DOUBLE && $1.dir==GraphEdgeType::FWD)
        $$ = ECompareOperator::GREATER_OR_EQUAL;
    else if ($1.type==GraphEdgeType::DOUBLE && $1.dir==GraphEdgeType::BACK)
        $$ = ECompareOperator::SMALLER_OR_EQUAL;
    else if ($1.type==GraphEdgeType::DOTTED && $1.dir==GraphEdgeType::FWD)
        $$ = ECompareOperator::GREATER;
    else if ($1.type==GraphEdgeType::DOTTED && $1.dir==GraphEdgeType::BACK)
        $$ = ECompareOperator::SMALLER;
    else if ($1.type==GraphEdgeType::DOTTED && $1.dir==GraphEdgeType::BIDIR)
        $$ = ECompareOperator::NOT_EQUAL;
    else
        $$ = ECompareOperator::INVALID;
};

condition: string
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ParamOrCond(@1, $1);
  #endif
    $$ = $1.had_error ? 2 : !$1.empty();
    $1.destroy();
}
      | string comp
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ParamOrCond(@1, $1);
    csh.AddCSH(@2, COLOR_EQUAL);
    csh.AddCSH_ErrorAfter(@2, "Missing string to compare to.");
  #else
    chart.Error.Error(@2.after(), "Missing string to compare to.");
  #endif
    $$ = 2;
    $1.destroy();
    (void) $2; //to suppress
}
      | string comp string
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ParamOrCond(@1, $1);
    if ($2!=ECompareOperator::INVALID) {
        csh.AddCSH(@2, COLOR_EQUAL);
        $$ = $1.Compare($2, $3);
    } else {
        csh.AddCSH_Error(@2, "Bad comparison operator. Use one of '==', '<>', '<=', '=>', '<' or '>'.");
        $$ = 2;
    }
    csh.AddCSH_ParamOrCond(@3, $3);
  #else
    if ($2!=ECompareOperator::INVALID)
        $$ = $1.Compare($2, $3);
    else {
        chart.Error.Error(@2, "Bad comparison operator. Use one of '==', '<>', '<=', '=>', '<' or '>'.");
        $$ = 2;
    }
  #endif
    $1.destroy();
    $3.destroy();
}
      | string error string
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ParamOrCond(@1, $1);
    csh.AddCSH_Error(@2, "Bad comparison operator. Use one of '==', '<>', '<=', '=>', '<' or '>'.");
    csh.AddCSH_ParamOrCond(@3, $3);
  #else
     chart.Error.Error(@2, "Bad comparison operator. Use one of '==', '<>', '<=', '=>', '<' or '>'.");
  #endif
    $1.destroy();
    $3.destroy();
    $$ = 2;
};

ifthen_condition: TOK_IF condition TOK_THEN
{
  #ifdef C_S_H_IS_COMPILED
    $$ = csh.Contexts.back().if_condition = $2;
    const bool cond_true = $2==1;
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH(@3, COLOR_KEYWORD);
    if (csh.CheckLineStartHintAfter(@3)) {
        csh.AddLineBeginToHints();
        csh.hintStatus = HINT_READY;
    }
    if (cond_true)
        csh.PushContext();
    else
        csh.PushContext(true, EContextParse::SKIP_CONTENT);
  #else
    $$ = chart.MyCurrentContext().if_condition = $2;
    const bool cond_true = $2==1;
    if (cond_true)
        chart.PushContext(@1);
    else
        chart.PushContext(@1, EContextParse::SKIP_CONTENT);
    chart.MyCurrentContext().export_colors = cond_true;
    chart.MyCurrentContext().export_styles = cond_true;
  #endif
}
      | TOK_IF condition
{
  #ifdef C_S_H_IS_COMPILED
    $$ = csh.Contexts.back().if_condition = 2;
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@2, "Missing 'then' keyword.");
    if (csh.CheckHintAfter(@2, EHintSourceType::KEYWORD)) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_KEYWORD) + "then",
            "Continue the 'if' statement with 'then'.",
            EHintType::KEYWORD, true));
        csh.hintStatus = HINT_READY;
    }
    csh.PushContext(true, EContextParse::SKIP_CONTENT);
  #else
    $$ = chart.MyCurrentContext().if_condition = 2;
    chart.PushContext(@1, EContextParse::SKIP_CONTENT);
    chart.Error.Error(@2.after(), "Missing 'then' keyword.");
  #endif
    (void) $2; //to supress warnings
}
      | TOK_IF
{
  #ifdef C_S_H_IS_COMPILED
    $$ = csh.Contexts.back().if_condition = 2;
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_ErrorAfter(@1, "Missing condition.");
    csh.PushContext(true, EContextParse::SKIP_CONTENT);
  #else
    $$ = chart.MyCurrentContext().if_condition = 2;
    chart.PushContext(@1, EContextParse::SKIP_CONTENT);
    chart.Error.Error(@1.after(), "Missing condition after 'if'.");
  #endif
}
      | TOK_IF error
{
  #ifdef C_S_H_IS_COMPILED
    $$ = csh.Contexts.back().if_condition = 2;
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_Error(@2, "Missing condition instead of this.");
    csh.PushContext(true, EContextParse::SKIP_CONTENT);
  #else
    $$ = chart.MyCurrentContext().if_condition = 2;
    chart.PushContext(@1, EContextParse::SKIP_CONTENT);
    chart.Error.Error(@2, "Missing condition after 'if'.");
  #endif
}
      | TOK_IF error TOK_THEN
{
  #ifdef C_S_H_IS_COMPILED
    $$ = csh.Contexts.back().if_condition = 2;
    csh.AddCSH(@1, COLOR_KEYWORD);
    csh.AddCSH_Error(@2, "Missing condition instead of this.");
    csh.PushContext(true, EContextParse::SKIP_CONTENT);
    if (csh.CheckLineStartHintAfter(@3)) {
        csh.AddLineBeginToHints();
        csh.hintStatus = HINT_READY;
    }
  #else
    $$ = chart.MyCurrentContext().if_condition = 2;
    chart.PushContext(@1, EContextParse::SKIP_CONTENT);
    chart.Error.Error(@2, "Missing condition after 'if'.");
  #endif
};


else: TOK_ELSE
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    if (csh.CheckLineStartHintAfter(@1)) {
        csh.AddLineBeginToHints();
        csh.hintStatus = HINT_READY;
    }
    csh.PopContext();
    const bool cond_false = csh.Contexts.back().if_condition==0;
    //Return value not used, just there so that nonterminal 'else' can have destructor
    $$ = csh.Contexts.back().if_condition;
    if (cond_false)
        csh.PushContext();
    else
        csh.PushContext(true, EContextParse::SKIP_CONTENT);
  #else
    //kill previous context and open new one - set laterreparse if error or if condition was true
    //this will ignore everything in the else clause
    chart.PopContext();
    const bool cond_false = chart.MyCurrentContext().if_condition==0;
    //Return value not used, just there so that nonterminal 'else' can have destructor
    $$ = chart.MyCurrentContext().if_condition;
    if (cond_false)
        chart.PushContext(@1);
    else
        chart.PushContext(@1, EContextParse::SKIP_CONTENT);
    chart.MyCurrentContext().export_colors = cond_false;
    chart.MyCurrentContext().export_styles = cond_false;
  #endif
};

ifthen: ifthen_condition instruction_no_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstructionIfNotBrace(@2);
    csh.PopContext();
    if (csh.CheckHintAfter(@2, EHintSourceType::KEYWORD)) {
        csh.AddToHints(CshHint(csh.HintPrefix(COLOR_KEYWORD) + "else",
            "Continue the 'if/then' statement with 'else'.",
            EHintType::KEYWORD, true));
        csh.hintStatus = HINT_READY;
    }
  #else
    if ($1==1) {
    } else {
    }
    chart.PopContext();
  #endif
}
      | ifthen_condition
{
  #ifdef C_S_H_IS_COMPILED
    if ($1!=2)
        csh.AddCSH_ErrorAfter(@1, "Missing command after 'then'.");
    csh.PopContext();
  #else
    if ($1!=2)
        chart.Error.Error(@1.after(), "Missing a well-formed command after 'then'. Ignoring 'if' clause.");
    chart.PopContext();
  #endif
    (void) $1; //suppress
}
      | ifthen_condition error
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Missing command after 'then'.");
    csh.PopContext();
  #else
    chart.Error.Error(@2, "Missing a well-formed command. Ignoring 'if' clause.");
    chart.PopContext();
  #endif
    (void) $1; //suppress
}
      | ifthen_condition instruction_no_semi else
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstructionIfNotBrace(@2);
    csh.AddCSH_ErrorAfter(@3, "Missing command after 'else'.");
    csh.PopContext();
  #else
    chart.Error.Error(@3.after(), "Missing command after 'else'. Ignoring 'if' clause.");
    chart.PopContext();
  #endif
    (void) $1; (void) $3; //suppress
}
      | ifthen_condition instruction_no_semi error else
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstructionIfNotBrace(@2);
    csh.AddCSH_Error(@3, "I am not sure what is coming here.");
    csh.AddCSH_ErrorAfter(@4, "Missing command after 'else'.");
    csh.PopContext();
  #else
    chart.Error.Error(@3, "I am not sure what is coming here.");
    chart.Error.Error(@4.after(), "Missing command after 'else'. Ignoring 'if' clause.");
    chart.PopContext();
  #endif
    (void) $1; (void) $4; //suppress
}
      | ifthen_condition error else
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "I am not sure what is coming here.");
    csh.AddCSH_ErrorAfter(@3, "Missing command after 'else'.");
    csh.PopContext();
  #else
    chart.Error.Error(@2, "I am not sure what is coming here.");
    chart.Error.Error(@3.after(), "Missing command after 'else'. Ignoring 'if' clause.");
    chart.PopContext();
  #endif
    (void) $1; (void) $3; //suppress
}
      | ifthen_condition instruction_no_semi else instruction_no_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstructionIfNotBrace(@2);
    csh.AddInstructionIfNotBrace(@4);
    csh.PopContext();
  #else
    switch ($1) {
    case 1: //original condition was true
        break;
    case 0: //original condition was false
        break;
    default:
        _ASSERT(0);
        FALLTHROUGH;
    case 2: //we had an error, but has reported the error - delete both branches
        break;
    }
    chart.PopContext();
  #endif
    (void) $3; //suppress
}
      | ifthen_condition instruction_no_semi error else instruction_no_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstructionIfNotBrace(@2);
    csh.AddInstructionIfNotBrace(@5);
    csh.AddCSH_Error(@3, "I am not sure what is coming here.");
    csh.PopContext();
  #else
    chart.Error.Error(@3, "I am not sure what is coming here. Ignoring 'if' command.");
    chart.PopContext();
  #endif
    (void) $1; (void) $4; //suppress
}
      | ifthen_condition error else instruction_no_semi
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddInstructionIfNotBrace(@4);
    csh.AddCSH_Error(@2, "I am not sure what is coming here.");
    csh.PopContext();
  #else
    chart.Error.Error(@2, "I am not sure what is coming here. Ignoring 'if' command.");
    chart.PopContext();
  #endif
    (void) $1; (void) $3; //suppress
};



graphattrdef: attritem;

subgraph_attrs: full_attrlist_with_label
{
  #ifdef C_S_H_IS_COMPILED
    if (csh.CheckHintLocated(EHintSourceType::ATTR_VALUE)) {
        csh.AttributeValues(GraphStyle::CLUSTER);
        csh.hintStatus = HINT_READY;
    } else if (csh.CheckHintLocated(EHintSourceType::ATTR_NAME)) {
        csh.AttributeNames(GraphStyle::CLUSTER);
        csh.hintStatus = HINT_READY;
    }
  #else
    chart.last_subgraphattrs += *$1;
  #endif
}

subgraph:  subghdr body
{
  #ifndef C_S_H_IS_COMPILED
    $$ = $2;
  #endif
}
      | subghdr error body
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error.");
  #else
    chart.Error.Error(@2, "Syntax error.");
    $$ = $3;
  #endif
}
      | subghdr	subgraph_attrs body
{
  #ifndef C_S_H_IS_COMPILED
    $$ = $3;
  #endif
}
      | subghdr error subgraph_attrs body
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error.");
  #else
    chart.Error.Error(@2, "Syntax error.");
    $$ = $4;
  #endif
}
      | subghdr subgraph_attrs error body
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@3, "Syntax error.");
  #else
    chart.Error.Error(@3, "Syntax error.");
    $$ = $4;
  #endif
}
      | subghdr error subgraph_attrs error body
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@2, "Syntax error.");
    csh.AddCSH_Error(@4, "Syntax error.");
  #else
    chart.Error.Error(@2, "Syntax error.");
    chart.Error.Error(@4, "Syntax error.");
    $$ = $5;
  #endif
}
      | subghdr subgraph_attrs error
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@3, "Syntax error. Expecting a '{' to specify subgraph content.");
  #else
    chart.Error.Error(@3, "Syntax error. Expecting a '{' to specify subgraph content.");
    $$ = nullptr;
  #endif
}
      | subghdr error subgraph_attrs
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@1, "Syntax error.");
  #else
    chart.Error.Error(@3, "Syntax error.");
    $$ = nullptr;
  #endif
}
      | subghdr error subgraph_attrs error
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_Error(@1, "Syntax error.");
    csh.AddCSH_Error(@4, "Syntax error. Expecting a '{' to specify subgraph content.");
  #else
    chart.Error.Error(@3, "Syntax error.");
    chart.Error.Error(@4, "Syntax error. Expecting a '{' to specify subgraph content.");
    $$ = nullptr;
  #endif
};

subghdr	: TOK_SUBGRAPH string_or_reserved_word
{
  #ifdef C_S_H_IS_COMPILED
    if (!$2.had_error) {
        csh.AddCSH(@1, COLOR_KEYWORD);
        csh.AddCSH_Subgraphname(@2, $2);
    }
  #else
    if ($2.had_error)
	    chart.last_subgraphname.clear();
    else
        chart.last_subgraphname = $2;
    chart.last_subgraphspos = @$;
    chart.last_subgraphattrs.Empty();
  #endif
    $2.destroy();
}
			| TOK_SUBGRAPH
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
  #else
    chart.last_subgraphname.clear();
    chart.last_subgraphspos = @$;
    chart.last_subgraphattrs.Empty();
  #endif
}
			| TOK_CLUSTER
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
  #else
    chart.last_subgraphname = std::string("cluster_")+std::to_string(chart.cluser_counter++);
    chart.last_subgraphspos = @$;
    chart.last_subgraphattrs.Empty();
    chart.last_subgraphattrs.AddAttribute(Attribute("label", "", FileLineColRange(), FileLineColRange()), &chart);
  #endif
}
			| TOK_CLUSTER string_or_reserved_word
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@1, COLOR_KEYWORD);
    if (!$2.had_error)
        csh.AddCSH(@2, COLOR_ATTRVALUE_EMPH);
  #else
    chart.last_subgraphattrs.Empty();
    if ($2.had_error) {
        chart.last_subgraphname = "cluster_"+std::to_string(chart.cluser_counter++);
        chart.last_subgraphattrs.AddAttribute(Attribute("label", "", FileLineColRange(), FileLineColRange()), &chart);
    } else {
        chart.last_subgraphname = "cluster_"+$2.str();
        chart.last_subgraphattrs.AddAttribute(Attribute("label", $2, FileLineColRange(), @2), &chart);
    }
    chart.last_subgraphspos = @$;
  #endif
    $2.destroy();
};


optseparator :  TOK_SEMICOLON
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@$, COLOR_COMMA); //even semicolons colored like this in option list separators
  #endif
}
      | TOK_COMMA
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH(@$, COLOR_COMMA);
  #endif
}
			| /*empty*/ ;

string_single:  TOK_QSTRING
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddQuotedString(@1);
  #endif
  $$ = $1;
}
      | TOK_STRING { $$.set($1); } ;

reserved_word: TOK_NODE { $$.set("node"); }
      | TOK_EDGE { $$.set("edge"); }
      | TOK_STRICT { $$.set("strict"); }
      | TOK_SUBGRAPH { $$.set("subgraph"); }
      | TOK_DEFSTYLE { $$.set("defstyle"); }
      | TOK_DEFDESIGN { $$.set("defdesign"); }
      | TOK_USEDESIGN { $$.set("usedesign"); }
      | TOK_GRAPH { $$.set("graph"); }
      | TOK_DIGRAPH { $$.set("digraph"); }
      | TOK_CLUSTER { $$.set("cluster"); }
      | TOK_IF { $$.set("if"); }
      | TOK_THEN { $$.set("then"); }
      | TOK_ELSE { $$.set("else"); }
      | TOK_COMMAND_DEFPROC { $$.set("defproc"); }
      | TOK_COMMAND_REPLAY { $$.set("replay"); }
      | TOK_COMMAND_SET { $$.set("set"); }
      | TOK_BYE { $$.set("bye"); };

string_single_or_reserved_word: string_single | reserved_word { $$.set($1); };

tok_param_name_as_multi: TOK_PARAM_NAME
{
  #ifdef C_S_H_IS_COMPILED
    if ($1.empty() || $1[0]!='$' || $1.len<2) {
        csh.AddCSH_Error(@1, "Need name after the '$' sign.");
        $$.set_error();
    } else
        $$.init();
        $$.had_param = true;
        csh.StoreMulti($$, @$);
  #else
    if ($1.empty() || $1[0]!='$' || $1.len<2) {
        chart.Error.Error(@1, "Need name after the '$' sign.");
        $$.set_error();
    } else if (!chart.SkipContent()) {
        //When parsing a procedure we we accept all params
        //as they may be variables defined later
        auto p = chart.GetParameter($1);
        if (p==nullptr) {
            chart.Error.Error(@1, "Undefined parameter or variable name.");
            $$.set_error();
        } else {
            $$.set_owning(StringFormat::PushPosEscapes(p->value.c_str(), @1));
            $$.had_param = true;
        }
    } else {
        $$.init();
        $$.had_param = true;
    }
  #endif
};

string_or_reserved_word_or_param: string_single_or_reserved_word | tok_param_name_as_multi;

string_or_param: string_single | tok_param_name_as_multi;

multi_string_continuation: TOK_TILDE
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@1, "Missing string to concatenate after '~'.");
    $$.init(); //for CSH keep this valid to be colored
  #else
    chart.Error.Error(@1.after(), "Missing string to concatenate after '~'.");
    $$.set_error();
  #endif
}
      | TOK_TILDE string_or_reserved_word_or_param
{
  #ifdef C_S_H_IS_COMPILED
    csh.StoreMulti($2, @2);
  #endif
    $$ = $2;
    $$.multi = true;
}
      | multi_string_continuation TOK_TILDE
{
  #ifdef C_S_H_IS_COMPILED
    csh.AddCSH_ErrorAfter(@2, "Missing string to concatenate after '~'.");
  #else
    chart.Error.Error(@2.after(), "Missing string to concatenate after '~'.");
  #endif
    $1.destroy();
    $$.set_error();
}
      | multi_string_continuation TOK_TILDE string_or_reserved_word_or_param
{
  #ifdef C_S_H_IS_COMPILED
    csh.StoreMulti($3, @3);
  #endif
    $$.CombineThemToMe($1, $3);
};

string_or_reserved_word: reserved_word { $$.set($1); }
      | string
      | reserved_word multi_string_continuation
{
  #ifdef C_S_H_IS_COMPILED
    csh.StoreMulti($1, @1);
  #endif
    $$.CombineThemToMe($1, $2);
};

string: string_or_param
      | string_or_param multi_string_continuation
{
  #ifdef C_S_H_IS_COMPILED
    csh.StoreMulti($1, @1);
  #endif
    $$.CombineThemToMe($1, $2);
};

%%
