// OpenSTA, Static Timing Analyzer
// Copyright (c) 2026, Parallax Software, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program 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 General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// 
// The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software.
// 
// Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 
// This notice may not be removed or altered from any source distribution.

%{
#include <cctype>
#include <string>
#include <utility>

#include "sdf/SdfReaderPvt.hh"
#include "sdf/SdfScanner.hh"

#undef yylex
#define yylex scanner->lex

// warning: variable 'yynerrs_' set but not used
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"

void
sta::SdfParse::error(const location_type &loc,
                     const std::string &msg)
{
  reader->report()->fileError(170, reader->filename(), loc.begin.line,"{}",msg);
}
%}

%code requires {
#include <string>
#include <vector>
#include "StringUtil.hh"

namespace sta {
class SdfScanner;
class SdfReader;
class Transition;
class SdfPortSpec;
class SdfTriple;

using SdfTripleSeq = std::vector<SdfTriple*>;
}
}

%require  "3.2"
%skeleton "lalr1.cc"
%debug
%define api.namespace {sta}
%locations
%define api.location.file "SdfLocation.hh"
%define parse.assert
%parse-param { SdfScanner *scanner }
%parse-param { SdfReader *reader }
%define api.parser.class {SdfParse}
%define api.value.type variant

// expected shift/reduce conflicts
%expect 4

%token <std::string> QSTRING ID EXPR_OPEN_IOPATH EXPR_OPEN EXPR_ID_CLOSE
%token <float> FNUMBER
%token <int> DNUMBER

%token DELAYFILE SDFVERSION DESIGN DATE VENDOR PROGRAM PVERSION
%token DIVIDER VOLTAGE PROCESS TEMPERATURE TIMESCALE
%token CELL CELLTYPE INSTANCE DELAY ABSOLUTE INCREMENTAL
%token INTERCONNECT PORT DEVICE RETAIN
%token IOPATH TIMINGCHECK
%token SETUP HOLD SETUPHOLD RECOVERY REMOVAL RECREM WIDTH PERIOD SKEW NOCHANGE
%token POSEDGE NEGEDGE COND CONDELSE

%type <float> NUMBER
%type <float*> number_opt
%type <sta::SdfTriple *> value triple
%type <sta::SdfTripleSeq *> delval_list
%type <std::string> path port port_instance
%type <sta::SdfPortSpec *> port_spec port_tchk
%type <const sta::Transition *> port_transition
%type <char> hchar

%start file

%%

file:
	'(' DELAYFILE  header cells ')' {}
;

header:
	header_stmt
|	header header_stmt
;

// technically the ordering of these statements is fixed by the spec
header_stmt:
	'(' SDFVERSION QSTRING ')'
|	'(' DESIGN QSTRING ')'
|	'(' DATE QSTRING ')'
|	'(' VENDOR QSTRING ')'
|	'(' PROGRAM QSTRING ')'
|	'(' PVERSION QSTRING ')'
|	'(' DIVIDER hchar ')' { reader->setDivider($3); }
|	'(' VOLTAGE triple ')' { reader->deleteTriple($3); }
|	'(' VOLTAGE NUMBER ')'
|	'(' VOLTAGE ')'  // Illegal SDF (from OC).
|	'(' PROCESS QSTRING ')'
|	'(' PROCESS ')'  // Illegal SDF (from OC).
|	'(' TEMPERATURE NUMBER ')'
|	'(' TEMPERATURE triple ')' { reader->deleteTriple($3); }
|	'(' TEMPERATURE ')' // Illegal SDF (from OC).
|	'(' TIMESCALE NUMBER ID ')' { reader->setTimescale($3, $4); }
;

hchar:
	'/'
	{ $$ = '/'; }
|	'.'
	{ $$ = '.'; }
;

number_opt: { $$ = nullptr; }
|	NUMBER	{ $$ = new float($1); }
;

cells:
	cell
|	cells cell
;

cell:
	'(' CELL celltype cell_instance timing_specs ')'
	{ reader->cellFinish(); }
;

celltype:
	'(' CELLTYPE QSTRING ')'
	{ reader->setCell($3); }
;

cell_instance:
	'(' INSTANCE ')'
        { reader->setInstance(); }
|	'(' INSTANCE '*' ')'
        { reader->setInstanceWildcard(); }
|	'(' INSTANCE path ')'
	{ reader->setInstance($3); }
;

timing_specs:
	/* empty */
|	timing_specs timing_spec
;

timing_spec:
	'(' DELAY deltypes ')'
|	'(' TIMINGCHECK tchk_defs ')'
;

deltypes:
|       deltypes deltype
;

deltype:
	'(' ABSOLUTE
	{ reader->setInIncremental(false); }
	del_defs ')'
|	'(' INCREMENTAL
	{ reader->setInIncremental(true); }
	del_defs ')'
;

del_defs:
|	del_defs del_def
;

path:
	ID
        { $$ = reader->unescaped($1); }
|	path hchar ID
        { $$ = reader->makePath($1, reader->unescaped($3)); }
;

del_def:
	'(' IOPATH port_spec port_instance retains delval_list ')'
	{ reader->iopath($3, $4, $6, "", false); }
|	'(' CONDELSE '(' IOPATH port_spec port_instance
            retains delval_list ')' ')'
	{ reader->iopath($5, $6, $8, "", true); }
|	'(' COND EXPR_OPEN_IOPATH port_spec port_instance
            retains delval_list ')' ')'
	{ reader->iopath($4, $5, $7, $3, false); }
|	'(' INTERCONNECT port_instance port_instance delval_list ')'
	{ reader->interconnect($3, $4, $5); }
|	'(' PORT port_instance delval_list ')'
	{ reader->port($3, $4); }
|	'(' DEVICE delval_list ')'
	{ reader->device($3); }
|	'(' DEVICE port_instance delval_list ')'
	{ reader->device($3, $4); }
;

retains:
	/* empty */
|	retains retain
;

retain:
	'(' RETAIN delval_list ')'
	{ reader->deleteTripleSeq($3); }
;

delval_list:
	value
	{ $$ = reader->makeTripleSeq(); $$->push_back($1); }
|  	delval_list value
	{ $1->push_back($2); $$ = $1; }
;

tchk_defs:
|	tchk_defs tchk_def
;

tchk_def:
	'(' SETUP { reader->setInTimingCheck(true); }
	port_tchk port_tchk value ')'
	{ reader->timingCheck(sta::TimingRole::setup(), $4, $5, $6);
	  reader->setInTimingCheck(false);
	}
|	'(' HOLD { reader->setInTimingCheck(true); }
	port_tchk port_tchk value ')'
	{ reader->timingCheck(sta::TimingRole::hold(), $4, $5, $6);
	  reader->setInTimingCheck(false);
	}
|	'(' SETUPHOLD { reader->setInTimingCheck(true); }
	port_tchk port_tchk value value ')'
	{ reader->timingCheckSetupHold($4, $5, $6, $7);
	  reader->setInTimingCheck(false);
	}
|	'(' RECOVERY { reader->setInTimingCheck(true); }
	port_tchk port_tchk value ')'
	{ reader->timingCheck(sta::TimingRole::recovery(),$4,$5,$6);
	  reader->setInTimingCheck(false);
	}
|	'(' REMOVAL { reader->setInTimingCheck(true); }
	port_tchk port_tchk value ')'
	{ reader->timingCheck(sta::TimingRole::removal(),$4,$5,$6);
	  reader->setInTimingCheck(false);
	}
|	'(' RECREM { reader->setInTimingCheck(true); }
	port_tchk port_tchk value value ')'
	{ reader->timingCheckRecRem($4, $5, $6, $7);
	  reader->setInTimingCheck(false);
	}
|	'(' SKEW { reader->setInTimingCheck(true); }
	port_tchk port_tchk value ')'
	// Sdf skew clk/ref are reversed from liberty.
	{ reader->timingCheck(sta::TimingRole::skew(),$5,$4,$6);
	  reader->setInTimingCheck(false);
	}
|	'(' WIDTH { reader->setInTimingCheck(true); }
	port_tchk value ')'
	{ reader->timingCheckWidth($4, $5);
	  reader->setInTimingCheck(false);
	}
|	'(' PERIOD { reader->setInTimingCheck(true); }
	port_tchk value ')'	
	{ reader->timingCheckPeriod($4, $5);
	  reader->setInTimingCheck(false);
	}
|	'(' NOCHANGE { reader->setInTimingCheck(true); }
	port_tchk port_tchk value value ')'
	{ reader->timingCheckNochange($4, $5, $6, $7);
	  reader->setInTimingCheck(false);
	}
;

port:
	ID
        { $$ = reader->unescaped($1); }
        | ID '[' DNUMBER ']'
        { $$ = reader->makeBusName($1, $3); }
;

port_instance:
	port
	{ $$ = $1; }
|	path hchar port
        { $$ = reader->makePath($1, $3); }
;

port_spec:
	port_instance
	{ $$ = reader->makePortSpec(sta::Transition::riseFall(), $1); }
|	'(' port_transition port_instance ')'
	{ $$ = reader->makePortSpec($2, $3); }
;

port_transition:
	POSEDGE	{ $$ = sta::Transition::rise(); }
|	NEGEDGE	{ $$ = sta::Transition::fall(); }
;

port_tchk:
	port_spec
	{ $$ = $1; }
|	'(' COND EXPR_ID_CLOSE
	{ $$ = reader->makeCondPortSpec($3); }
|	'(' COND EXPR_OPEN port_transition port_instance ')' ')'
        { $$ = reader->makePortSpec($4, $5, $3); }
;

value:
	'(' ')'
	{
	  $$ = reader->makeTriple();
	}
|	'(' NUMBER ')'
	{
	  $$ = reader->makeTriple($2);
	}
|	'(' triple ')' { $$ = $2; }
;

triple:
	NUMBER ':' number_opt ':' number_opt
	{
	  float *fp = new float($1);
	  $$ = reader->makeTriple(fp, $3, $5);
	}
|	number_opt ':' NUMBER ':' number_opt
	{
	  float *fp = new float($3);
	  $$ = reader->makeTriple($1, fp, $5);
	}
|	number_opt ':' number_opt ':' NUMBER
	{
	  float *fp = new float($5);
	  $$ = reader->makeTriple($1, $3, fp);
	}
;

NUMBER:
        FNUMBER
	{ $$ = $1; }
|       DNUMBER
        { $$ = static_cast<float>($1); }
|       '-' DNUMBER
        { $$ = static_cast<float>(-$2); }
;

%%
