/*
* matiec - a compiler for the programming languages defined in IEC 61131-3
*
* Copyright (C) 2003-2011 Mario de Sousa (msousa@fe.up.pt)
*
* 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 thest_whitespaceLicense, 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 <http://www.gnu.org/licenses/>.
*
*
* This code is made available on the understanding that it will not be
* used in safety-critical situations without a full and competent review.
*/
/*
* An IEC 61131-3 compiler.
*
* Based on the
* FINAL DRAFT - IEC 61131-3, 2nd Ed. (2001-12-10)
*
*/
/*
* Stage 1
* =======
*
* This file contains the lexical tokens definitions, from which
* the flex utility will generate a lexical parser function.
*/
/*****************************/
/* Lexical Parser Options... */
/*****************************/
/* The lexical analyser will never work in interactive mode,
* i.e., it will only process programs saved to files, and never
* programs being written inter-actively by the user.
* This option saves the resulting parser from calling the
* isatty() function, that seems to be generating some compile
* errors under some (older?) versions of flex.
*/
%option never-interactive
/* Have the lexical analyser use a 'char *yytext' instead of an
* array of char 'char yytext[??]' to store the lexical token.
*/
%pointer
/* Have the lexical analyser ignore the case of letters.
* This will occur for all the tokens and keywords, but
* the resulting text handed up to the syntax parser
* will not be changed, and keep the original case
* of the letters in the input file.
*/
%option case-insensitive
/* Have the generated lexical analyser keep track of the
* line number it is currently analysing.
* This is used to pass up to the syntax parser
* the number of the line on which the current
* token was found. It will enable the syntax parser
* to generate more informatve error messages...
*/
%option yylineno
/* required for the use of the yy_pop_state() and
* yy_push_state() functions
*/
%option stack
/* The '%option stack' also requests the inclusion of
* the yy_top_state(), however this function is not
* currently being used. This means that the compiler
* is complaining about the existance of this function.
* The following option removes the yy_top_state()
* function from the resulting c code, so the compiler
* no longer complains.
*/
%option noyy_top_state
/* We will be using unput() in our flex code, so we cannot set the following option!... */
/*
%option nounput
*/
/**************************************************/
/* External Variable and Function declarations... */
/**************************************************/
%{
/* Define TEST_MAIN to include a main() function.
* Useful for testing the parser generated by flex.
*/
/*
#define TEST_MAIN
*/
/* If lexical parser is compiled by itself, we need to define the following
* constant to some string. Under normal circumstances LIBDIRECTORY is set
* in the syntax parser header file...
*/
#ifdef TEST_MAIN
#define DEFAULT_LIBDIR "just_testing"
#endif
/* Required for strdup() */
#include <string.h>
/* Required only for the declaration of abstract syntax classes
* (class symbol_c; class token_c; class list_c;)
* These will not be used in flex, but the token type union defined
* in iec_bison.hh contains pointers to these classes, so we must include
* it here.
*/
#include "../absyntax/absyntax.hh"
/* iec_bison.hh is generated by bison.
* Contains the definition of the token constants, and the
* token value type YYSTYPE (in our case, a 'const char *')
*/
#include "iec_bison.hh"
#include "stage1_2_priv.hh"
/* Variable defined by the bison parser,
* where the value of the tokens will be stored
*/
extern YYSTYPE yylval;
/* The name of the file currently being parsed...
* Note that flex accesses and updates this global variable
* apropriately whenever it comes across an (*#include <filename> *) directive...
*/
const char *current_filename = NULL;
/* We will not be using unput() in our flex code... */
/* NOTE: it seems that this #define is no longer needed, It has been
* replaced by %option nounput.
* Should we simply delete it?
* For now leave it in, in case someone is using an old version of flex.
* In any case, the most harm that can result in a warning message
* when compiling iec.flex.c:
* warning: ‘void yyunput(int, char*)’ defined but not used
*/
#define YY_NO_UNPUT
/* Variable defined by the bison parser.
* It must be initialised with the location
* of the token being parsed.
* This is only needed if we want to keep
* track of the locations, in order to give
* more meaningful error messages!
*/
/*
*extern YYLTYPE yylloc;
b*/
#define YY_INPUT(buf,result,max_size) {\
result = GetNextChar(buf, max_size);\
if ( result <= 0 )\
result = YY_NULL;\
}
/* Macro that is executed for every action.
* We use it to pass the location of the token
* back to the bison parser...
*/
#define YY_USER_ACTION {\
yylloc.first_line = current_tracking->lineNumber; \
yylloc.first_column = current_tracking->currentTokenStart; \
yylloc.first_file = current_filename; \
yylloc.first_order = current_order; \
yylloc.last_line = current_tracking->lineNumber; \
yylloc.last_column = current_tracking->currentChar - 1; \
yylloc.last_file = current_filename; \
yylloc.last_order = current_order; \
current_tracking->currentTokenStart = current_tracking->currentChar; \
current_order++; \
}
/* Since this lexical parser we defined only works in ASCII based
* systems, we might as well make sure it is being compiled on
* one...
* Lets check a few random characters...
*/
#if (('a' != 0x61) || ('A' != 0x41) || ('z' != 0x7A) || ('Z' != 0x5A) || \
('0' != 0x30) || ('9' != 0x39) || ('(' != 0x28) || ('[' != 0x5B))
#error This lexical analyser is not portable to a non ASCII based system.
#endif
/* Function only called from within flex, but defined
* in iec.y!
* We declare it here...
*
* Search for a symbol in either of the two symbol tables
* and return the token id of the first symbol found.
* Searches first in the variables, and only if not found
* does it continue searching in the library elements
*/
//token_id_t get_identifier_token(const char *identifier_str);
int get_identifier_token(const char *identifier_str);
%}
/***************************************************/
/* Forward Declaration of functions defined later. */
/***************************************************/
%{
/* return all the text in the current token back to the input stream. */
void unput_text(unsigned int n);
/* return all the text in the current token back to the input stream,
* but first return to the stream an additional character to mark the end of the token.
*/
void unput_and_mark(const char c);
void include_file(const char *include_filename);
int GetNextChar(char *b, int maxBuffer);
%}
/****************************/
/* Lexical Parser States... */
/****************************/
/* NOTE: Our psrser can parse st or il code, intermixed
* within the same file.
* With IL we come across the issue of the EOL (end of line) token.
* ST, and the declaration parts of IL do not use this token!
* If the lexical analyser were to issue this token during ST
* language parsing, or during the declaration of data types,
* function headers, etc. in IL, the syntax parser would crash.
*
* We can solve this issue using one of three methods:
* (1) Augment all the syntax that does not accept the EOL
* token to simply ignore it. This makes the syntax
* definition (in iec.y) very cluttered!
* (2) Let the lexical parser figure out which language
* it is parsing, and decide whether or not to issue
* the EOL token. This requires the lexical parser
* to have knowledge of the syntax!, making for a poor
* overall organisation of the code. It would also make it
* very difficult to understand the lexical parser as it
* would use several states, and a state machine to transition
* between the states. The state transitions would be
* intermingled with the lexical parser defintion!
* (3) Use a mixture of (1) and (2). The lexical analyser
* merely distinguishes between function headers and function
* bodies, but no longer makes a distinction between il and
* st language bodies. When parsing a body, it will return
* the EOL token. In other states '\n' will be ignored as
* whitespace.
* The ST language syntax has been augmented in the syntax
* parser configuration to ignore any EOL tokens that it may
* come across!
* This option has both drawbacks of option (1) and (2), but
* much less intensely.
* The syntax that gets cluttered is limited to the ST statements
* (which is rather limited, compared to the function headers and
* data type declarations, etc...), while the state machine in
* the lexical parser becomes very simple. All state transitions
* can be handled within the lexical parser by itself, and can be
* easily identified. Thus knowledge of the syntax required by
* the lexical parser is very limited!
*
* Amazingly enough, I (Mario) got to implement option (3)
* at first, requiring two basic states, decl and body.
* The lexical parser will enter the body state when
* it is parsing the body of a function/program/function block. The
* state transition is done when we find a VAR_END that is not followed
* by a VAR! This is the syntax knowledge that gets included in the
* lexical analyser with this option!
* Unfortunately, getting the st syntax parser to ignore EOL anywhere
* where they might appear leads to conflicts. This is due to the fact
* that the syntax parser uses the single look-ahead token to remove
* possible conflicts. When we insert a possible EOL, the single
* look ahead token becomes the EOL, which means the potential conflicts
* could no longer be resolved.
* Removing these conflicts would make the st syntax parser very convoluted,
* and adding the extraneous EOL would make it very cluttered.
* This option was therefore dropped in favour of another!
*
* I ended up implementing (2). Unfortunately the lexical analyser can
* not easily distinguish between il and st code, since function
* calls in il are very similar to function block calls in st.
* We therefore use an extra 'body' state. When the lexical parser
* finds that last END_VAR, it enters the body state. This state
* must figure out what language is being parsed from the first few
* tokens, and switch to the correct state (st, il or sfc) according to the
* language. This means that we insert quite a bit of knowledge of the
* syntax of the languages into the lexical parser. This is ugly, but it
* works, and at least it is possible to keep all the state changes together
* to make it easier to remove them later on if need be.
* Once the language being parsed has been identified,
* the body state returns any matched text back to the buffer with unput(),
* to be later matched correctly by the apropriate language parser (st, il or sfc).
*
* Aditionally, in sfc state it may further recursively enter the body state
* once again. This is because an sfc body may contain ACTIONS, which are then
* written in one of the three languages (ST, IL or SFC), so once again we need
* to figure out which language the ACTION in the SFC was written in. We already
* ahve all that done in the body state, so we recursively transition to the body
* state once again.
* Note that in this case, when coming out of the st/il state (whichever language
* the action was written in) the sfc state will become active again. This is done by
* pushing and poping the previously active state!
*
* The sfc_qualifier_state is required because when parsing actions within an
* sfc, we will be expecting action qualifiers (N, P, R, S, DS, SD, ...). In order
* to bison to work correctly, these qualifiers must be returned as tokens. However,
* these tokens are not reserved keywords, which means it should be possible to
* define variables/functions/FBs with any of these names (including
* S and R which are special because they are also IL operators). So, when we are not
* expecting any action qualifiers, flex does not return these tokens, and is free
* to interpret them as previously defined variables/functions/... as the case may be.
*
* The time_literal_state is required because TIME# literals are decomposed into
* portions, and wewant to send these portions one by one to bison. Each poertion will
* represent the value in days/hours/minutes/seconds/ms.
* Unfortunately, some of these portions may also be lexically analysed as an identifier. So,
* we need to disable lexical identification of identifiers while parsing TIME# literals!
* e.g.: TIME#55d_4h_56m
* We would like to return to bison the tokens 'TIME' '#' '55d' '_' '4h' '_' '56m'
* Unfortunately, flex will join '_' and '4h' to create a legal {identifier} '_4h',
* and return that identifier instead! So, we added this state!
*
* There is a main state machine...
*
* +---> INITIAL <-------> config
* | \
* | V
* | header_state
* | |
* | V
* vardecl_list_state <------> var_decl
* ^ |
* | | [using push()]
* | |
* | V
* | body,
* | |
* | |
* | -------------------
* | | | |
* | v v v
* | st il sfc
* | | | | [using pop() when leaving st/il/sfc => goes to vardecl_list_state]
* | | | |
* -----------------------
*
* NOTE:- When inside sfc, and an action or transition in ST/IL is found, then
* we also push() to the body state. This means that sometimes, when pop()ing
* from st and il, the state machine may return to the sfc state!
* - The transitions form sfc to body will be decided by bison, which will
* tell flex to do the transition by calling cmd_goto_body_state().
*
*
* Possible state changes are:
* INITIAL -> goto(config_state)
* (when a CONFIGURATION is found)
*
* INITIAL -> goto(header_state)
* (when a FUNCTION, FUNCTION_BLOCK, or PROGRAM is found)
* header_state -> goto(vardecl_list_state)
* (When the first VAR token is found, i.e. at begining of first VAR .. END_VAR declaration)
*
* vardecl_list_state -> push current state (vardecl_list_state), and goto(vardecl_state)
* (when a VAR token is found)
* vardecl_state -> pop() to (vardecl_list_state)
* (when a END_VAR token is found)
*
* vardecl_list_state -> push current state (vardecl_list_state), and goto(body_state)
* (when the last END_VAR is found!)
*
* body_state -> goto(sfc_state)
* (when it figures out it is parsing sfc language)
* body_state -> goto(st_state)
* (when it figures out it is parsing st language)
* body_state -> goto(il_state)
* (when it figures out it is parsing il language)
* st_state -> pop() to vardecl_list_state
* (when a END_FUNCTION, END_FUNCTION_BLOCK, END_PROGRAM,
* END_ACTION or END_TRANSITION is found)
* il_state -> pop() to vardecl_list_state
* (when a END_FUNCTION, END_FUNCTION_BLOCK, END_PROGRAM,
* END_ACTION or END_TRANSITION is found)
* sfc_state -> pop() to vardecl_list_state
* (when a END_FUNCTION, END_FUNCTION_BLOCK, or END_PROGRAM is found)
*
* vardecl_list_state -> goto(INITIAL)
* (when a END_FUNCTION, END_FUNCTION_BLOCK, or END_PROGRAM is found)
* config_state -> goto(INITIAL)
* (when a END_CONFIGURATION is found)
*
*
* sfc_state -> push current state(sfc_state); goto(body_state)
* (when parsing an action. This transition is requested by bison)
* sfc_state -> push current state(sfc_state); goto(sfc_qualifier_state)
* (when expecting an action qualifier. This transition is requested by bison)
* sfc_qualifier_state -> pop() to sfc_state
* (when no longer expecting an action qualifier. This transition is requested by bison)
*
* config_state -> push(config_state); goto(task_init_state)
* (when parsing a task initialisation. This transition is requested by bison)
* task_init_state -> pop()
* (when no longer parsing task initialisation parameters. This transition is requested by bison)
*
*
* There is another secondary state machine for parsing comments, another for file_includes,
* and yet another for time literals.
*/
/* we are parsing a configuration. */
%s config_state
/* Inside a configuration, we are parsing a task initialisation parameters */
/* This means that PRIORITY, SINGLE and INTERVAL must be handled as
* tokens, and not as possible identifiers. Note that the above words
* are not keywords.
*/
%s task_init_state
/* we are looking for the first VAR inside a function's, program's or function block's declaration */
/* This is not exclusive (%x) as we must be able to parse the identifier and data types of a function/FB */
%s header_state
/* we are parsing a function, program or function block sequence of VAR..END_VAR delcarations */
%x vardecl_list_state
/* a substate of the vardecl_list_state: we are inside a specific VAR .. END_VAR */
%s vardecl_state
/* we will be parsing a function body/action/transition. Whether il/st/sfc remains to be determined */
%x body_state
/* we are parsing il code -> flex must return the EOL tokens! */
%s il_state
/* we are parsing st code -> flex must not return the EOL tokens! */
%s st_state
/* we are parsing sfc code -> flex must not return the EOL tokens! */
%s sfc_state
/* we are parsing sfc code, and expecting an action qualifier. */
%s sfc_qualifier_state
/* we are parsing sfc code, and expecting the priority token. */
%s sfc_priority_state
/* we are parsing a TIME# literal. We must not return any {identifier} tokens. */
%x time_literal_state
/* we are parsing a comment. */
%x comment_state
/*******************/
/* File #include's */
/*******************/
/* We extend the IEC 61131-3 standard syntax to allow inclusion
* of other files, using the IEC 61131-3 pragma directive...
* The accepted syntax is:
* {#include "<filename>"}
*/
/* the "include" states are used for picking up the name of an include file */
%x include_beg
%x include_filename
%x include_end
file_include_pragma_filename [^\"]*
file_include_pragma_beg "{#include"{st_whitespace}\"
file_include_pragma_end \"{st_whitespace}"}"
file_include_pragma {file_include_pragma_beg}{file_include_pragma_filename}{file_include_pragma_end}
%{
/* A counter to track the order by which each token is processed.
* NOTE: This counter is not exactly linear (i.e., it does not get incremented by 1 for each token).
* i.e.. it may get incremented by more than one between two consecutive tokens.
* This is due to the fact that the counter gets incremented every 'user action' in flex,
* however not every user action will result in a token being passed to bison.
* Nevertheless this is still OK, as we are only interested in the relative
* ordering of tokens...
*/
static long int current_order = 0;
typedef struct {
int eof;
int lineNumber;
int currentChar;
int lineLength;
int currentTokenStart;
char *buffer;
FILE *in_file;
} tracking_t;
/* A forward declaration of a function defined at the end of this file. */
void FreeTracking(tracking_t *tracking);
#define MAX_INCLUDE_DEPTH 16
typedef struct {
YY_BUFFER_STATE buffer_state;
tracking_t *env;
const char *filename;
} include_stack_t;
tracking_t *current_tracking = NULL;
include_stack_t include_stack[MAX_INCLUDE_DEPTH];
int include_stack_ptr = 0;
const char *INCLUDE_DIRECTORIES[] = {
DEFAULT_LIBDIR,
".",
"/lib",
"/usr/lib",
"/usr/lib/iec",
NULL /* must end with NULL!! */
};
%}
/*****************************/
/* Prelimenary constructs... */
/*****************************/
/* PRAGMAS */
/* ======= */
/* In order to allow the declaration of POU prototypes (Function, FB, Program, ...),
* especially the prototypes of Functions and FBs defined in the standard
* (i.e. standard functions and FBs), we extend the IEC 61131-3 standard syntax
* with two pragmas to indicate that the code is to be parsed (going through the
* lexical, syntactical, and semantic analysers), but no code is to be generated.
*
* The accepted syntax is:
* {no_code_generation begin}
* ... prototypes ...
* {no_code_generation end}
*
* When parsing these prototypes the abstract syntax tree will be populated as usual,
* allowing the semantic analyser to correctly analyse the semantics of calls to these
* functions/FBs. However, stage4 will simply ignore all IEC61131-3 code
* between the above two pragmas.
*/
disable_code_generation_pragma "{disable code generation}"
enable_code_generation_pragma "{enable code generation}"
/* Any other pragma... */
pragma ("{"[^}]*"}")|("{{"([^}]|"}"[^}])*"}}")
/* COMMENTS */
/* ======== */
/* In order to allow nested comments, comments are handled by a specific comment_state state */
/* Whenever a "(*" is found, we push the current state onto the stack, and enter a new instance of the comment_state state.
* Whenever a "*)" is found, we pop a state off the stack
*/
/* comments... */
comment_beg "(*"
comment_end "*)"
/* However, bison has a shift/reduce conflict in bison, when parsing formal function/FB
* invocations with the 'NOT <variable_name> =>' syntax (which needs two look ahead
* tokens to be parsed correctly - and bison being LALR(1) only supports one).
* The current work around requires flex to completely parse the '<variable_name> =>'
* sequence. This sequence includes whitespace and/or comments between the
* <variable_name> and the "=>" token.
*
* This flex rule (sendto_identifier_token) uses the whitespace/comment as trailing context,
* which means we can not use the comment_state method of specifying/finding and ignoring
* comments.
*
* For this reason only, we must also define what a complete comment looks like, so
* it may be used in this rule. Since the rule uses the whitespace_or_comment
* construct as trailing context, this definition of comment must not use any
* trailing context either.
*
* Aditionally, it is not possible to define nested comments in flex without the use of
* states, so for this particular location, we do NOT support nested comments.
*/
/* NOTE: this seemingly unnecessary complex definition is required
* to be able to eat up comments such as:
* '(* Testing... ! ***** ******)'
* without using the trailing context command in flex (/{context})
* since {comment} itself will later be used with
* trailing context ({comment}/{context})
*/
not_asterisk [^*]
not_close_parenthesis_nor_asterisk [^*)]
asterisk "*"
comment_text ({not_asterisk})|(({asterisk}+){not_close_parenthesis_nor_asterisk})
comment "(*"({comment_text}*)({asterisk}+)")"
/* 3.1 Whitespace */
/* ============== */
/*
* Whitespace is clearly defined (see IEC 61131-3 v2, section 2.1.4)
*
* Whitespace definition includes the newline character.
*
* However, the standard is inconsistent in that in IL the newline character
* is considered a token (EOL - end of line).
* In our implementation we therefore have two definitions of whitespace
* - one for ST, that includes the newline character
* - one for IL without the newline character.
* Additionally, when parsing IL, the newline character is treated as the EOL token.
* This requires the use of a state machine in the lexical parser that needs at least
* some knowledge of the syntax itself.
*
* NOTE: Our definition of whitespace will only work in ASCII!
*
* NOTE: we cannot use
* st_whitespace [:space:]*
* since we use {st_whitespace} as trailing context. In our case
* this would not constitute "dangerous trailing context", but the
* lexical generator (i.e. flex) does not know this (since it does
* not know which characters belong to the set [:space:]), and will
* generate a "dangerous trailing context" warning!
* We use this alternative just to stop the flex utility from
* generating the invalid (in this case) warning...
*/
st_whitespace [ \f\n\r\t\v]*
il_whitespace [ \f\r\t\v]*
st_whitespace_or_pragma_or_commentX ({st_whitespace})|({pragma})|({comment})
il_whitespace_or_pragma_or_commentX ({il_whitespace})|({pragma})|({comment})
st_whitespace_or_pragma_or_comment {st_whitespace_or_pragma_or_commentX}*
il_whitespace_or_pragma_or_comment {il_whitespace_or_pragma_or_commentX}*
qualified_identifier {identifier}(\.{identifier})+
/*****************************************/
/* B.1.1 Letters, digits and identifiers */
/*****************************************/
/* NOTE: The following definitions only work if the host computer
* is using the ASCII maping. For e.g., with EBCDIC [A-Z]
* contains non-alphabetic characters!
* The correct way of doing it would be to use
* the [:upper:] etc... definitions.
*
* Unfortunately, further on we need all printable
* characters (i.e. [:print:]), but excluding '$'.
* Flex does not allow sets to be composed by excluding
* elements. Sets may only be constructed by adding new
* elements, which means that we have to revert to
* [\x20\x21\x23\x25\x26\x28-x7E] for the definition
* of the printable characters with the required exceptions.
* The above also implies the use of ASCII, but now we have
* no way to work around it|
*
* The conclusion is that our parser is limited to ASCII
* based host computers!!
*/
letter [A-Za-z]
digit [0-9]
octal_digit [0-7]
hex_digit {digit}|[A-F]
identifier ({letter}|(_({letter}|{digit})))((_?({letter}|{digit}))*)
/*******************/
/* B.1.2 Constants */
/*******************/
/******************************/
/* B.1.2.1 Numeric literals */
/******************************/
integer {digit}((_?{digit})*)
/* Some helper symbols for parsing TIME literals... */
integer_0_59 (0(_?))*([0-5](_?))?{digit}
integer_0_19 (0(_?))*([0-1](_?))?{digit}
integer_20_23 (0(_?))*2(_?)[0-3]
integer_0_23 {integer_0_19}|{integer_20_23}
integer_0_999 {digit}((_?{digit})?)((_?{digit})?)
binary_integer 2#{bit}((_?{bit})*)
bit [0-1]
octal_integer 8#{octal_digit}((_?{octal_digit})*)
hex_integer 16#{hex_digit}((_?{hex_digit})*)
exponent [Ee]([+-]?){integer}
/* The correct definition for real would be:
* real {integer}\.{integer}({exponent}?)
*
* Unfortunately, the spec also defines fixed_point (B 1.2.3.1) as:
* fixed_point {integer}\.{integer}
*
* This means that {integer}\.{integer} could be interpreted
* as either a fixed_point or a real.
* I have opted to interpret {integer}\.{integer} as a fixed_point.
* In order to do this, the definition of real has been changed to:
* real {integer}\.{integer}{exponent}
*
* This means that the syntax parser now needs to define a real to be
* either a real_token or a fixed_point_token!
*/
real {integer}\.{integer}{exponent}
/*******************************/
/* B.1.2.2 Character Strings */
/*******************************/
/*
common_character_representation :=
<any printable character except '$', '"' or "'">
|'$$'
|'$L'|'$N'|'$P'|'$R'|'$T'
|'$l'|'$n'|'$p'|'$r'|'$t'
NOTE: $ = 0x24
" = 0x22
' = 0x27
printable chars in ASCII: 0x20-0x7E
*/
esc_char_u $L|$N|$P|$R|$T
esc_char_l $l|$n|$p|$r|$t
esc_char $$|{esc_char_u}|{esc_char_l}
double_byte_char (${hex_digit}{hex_digit}{hex_digit}{hex_digit})
single_byte_char (${hex_digit}{hex_digit})
/* WARNING:
* This definition is only valid in ASCII...
*
* Flex includes the function print_char() that defines
* all printable characters portably (i.e. whatever character
* encoding is currently being used , ASCII, EBCDIC, etc...)
* Unfortunately, we cannot generate the definition of
* common_character_representation portably, since flex
* does not allow definition of sets by subtracting
* elements in one set from another set.
* This means we must build up the defintion of
* common_character_representation using only set addition,
* which leaves us with the only choice of defining the
* characters non-portably...
*/
common_character_representation [\x20\x21\x23\x25\x26\x28-\x7E]|{esc_char}
double_byte_character_representation $\"|'|{double_byte_char}|{common_character_representation}
single_byte_character_representation $'|\"|{single_byte_char}|{common_character_representation}
double_byte_character_string \"({double_byte_character_representation}*)\"
single_byte_character_string '({single_byte_character_representation}*)'
/************************/
/* B 1.2.3.1 - Duration */
/************************/
fixed_point {integer}\.{integer}
/* NOTE: The IEC 61131-3 v2 standard has an incorrect formal syntax definition of duration,
* as its definition does not match the standard's text.
* IEC 61131-3 v3 (committee draft) seems to have this fixed, so we use that
* definition instead!
*
* duration::= ('T' | 'TIME') '#' ['+'|'-'] interval
* interval::= days | hours | minutes | seconds | milliseconds
* fixed_point ::= integer [ '.' integer]
* days ::= fixed_point 'd' | integer 'd' ['_'] [ hours ]
* hours ::= fixed_point 'h' | integer 'h' ['_'] [ minutes ]
* minutes ::= fixed_point 'm' | integer 'm' ['_'] [ seconds ]
* seconds ::= fixed_point 's' | integer 's' ['_'] [ milliseconds ]
* milliseconds ::= fixed_point 'ms'
*
*
* The original IEC 61131-3 v2 definition is:
* duration ::= ('T' | 'TIME') '#' ['-'] interval
* interval ::= days | hours | minutes | seconds | milliseconds
* fixed_point ::= integer [ '.' integer]
* days ::= fixed_point 'd' | integer 'd' ['_'] hours
* hours ::= fixed_point 'h' | integer 'h' ['_'] minutes
* minutes ::= fixed_point 'm' | integer 'm' ['_'] seconds
* seconds ::= fixed_point 's' | integer 's' ['_'] milliseconds
* milliseconds ::= fixed_point 'ms'
*/
interval_ms_X ({integer_0_999}(\.{integer})?)ms
interval_s_X {integer_0_59}s(_?{interval_ms_X})?|({integer_0_59}(\.{integer})?s)
interval_m_X {integer_0_59}m(_?{interval_s_X})?|({integer_0_59}(\.{integer})?m)
interval_h_X {integer_0_23}h(_?{interval_m_X})?|({integer_0_23}(\.{integer})?h)
interval_ms {integer}ms|({fixed_point}ms)
interval_s {integer}s(_?{interval_ms_X})?|({fixed_point}s)
interval_m {integer}m(_?{interval_s_X})?|({fixed_point}m)
interval_h {integer}h(_?{interval_m_X})?|({fixed_point}h)
interval_d {integer}d(_?{interval_h_X})?|({fixed_point}d)
interval {interval_ms}|{interval_s}|{interval_m}|{interval_h}|{interval_d}
/* to help provide nice error messages, we also parse an incorrect but plausible interval... */
/* NOTE that this erroneous interval will be parsed outside the time_literal_state, so must not
* be able to parse any other legal lexcial construct (besides a legal interval, but that
* is OK as this rule will appear _after_ the rule to parse legal intervals!).
*/
fixed_point_or_integer {fixed_point}|{integer}
erroneous_interval ({fixed_point_or_integer}d_?)?({fixed_point_or_integer}h_?)?({fixed_point_or_integer}m_?)?({fixed_point_or_integer}s_?)?({fixed_point_or_integer}ms)?
/********************************************/
/* B.1.4.1 Directly Represented Variables */
/********************************************/
/* The correct definition, if the standard were to be followed... */
location_prefix [IQM]
size_prefix [XBWDL]
direct_variable_standard %{location_prefix}({size_prefix}?){integer}((.{integer})*)
/* For the MatPLC, we will accept %<identifier>
* as a direct variable, this being mapped onto the MatPLC point
* named <identifier>
*/
/* TODO: we should not restrict it to only the accepted syntax
* of <identifier> as specified by the standard. MatPLC point names
* have a more permissive syntax.
*
* e.g. "P__234"
* Is a valid MatPLC point name, but not a valid <identifier> !!
* The same happens with names such as "333", "349+23", etc...
* How can we handle these more expressive names in our case?
* Remember that some direct variable may remain anonymous, with
* declarations such as:
* VAR
* AT %I3 : BYTE := 255;
* END_VAR
* in which case we are currently using "%I3" as the variable
* name.
*/
/* direct_variable_matplc %{identifier} */
/* direct_variable {direct_variable_standard}|{direct_variable_matplc} */
direct_variable {direct_variable_standard}
/******************************************/
/* B 1.4.3 - Declaration & Initialisation */
/******************************************/
incompl_location %[IQM]\*
%%
/* fprintf(stderr, "flex: state %d\n", YY_START); */
/*****************************************************/
/*****************************************************/
/*****************************************************/
/***** *****/
/***** *****/
/***** F I R S T T H I N G S F I R S T *****/
/***** *****/
/***** *****/
/*****************************************************/
/*****************************************************/
/*****************************************************/
/***********************************************************/
/* Handle requests sent by bison for flex to change state. */
/***********************************************************/
if (get_goto_body_state()) {
yy_push_state(body_state);
rst_goto_body_state();
}
if (get_goto_sfc_qualifier_state()) {
yy_push_state(sfc_qualifier_state);
rst_goto_sfc_qualifier_state();
}
if (get_goto_sfc_priority_state()) {
yy_push_state(sfc_priority_state);
rst_goto_sfc_priority_state();
}
if (get_goto_task_init_state()) {
yy_push_state(task_init_state);
rst_goto_task_init_state();
}
if (get_pop_state()) {
yy_pop_state();
rst_pop_state();
}
/***************************/
/* Handle the pragmas! */
/***************************/
/* We start off by searching for the pragmas we handle in the lexical parser. */
<INITIAL>{file_include_pragma} unput_text(0); yy_push_state(include_beg);
/* Pragmas sent to syntax analyser (bison) */
{disable_code_generation_pragma} return disable_code_generation_pragma_token;
{enable_code_generation_pragma} return enable_code_generation_pragma_token;
<body_state,vardecl_list_state>{disable_code_generation_pragma} return disable_code_generation_pragma_token;
<body_state,vardecl_list_state>{enable_code_generation_pragma} return enable_code_generation_pragma_token;
/* Any other pragma we find, we just pass it up to the syntax parser... */
/* Note that the <body_state> state is exclusive, so we have to include it here too. */
{pragma} {/* return the pragmma without the enclosing '{' and '}' */
int cut = yytext[1]=='{'?2:1;
yytext[strlen(yytext)-cut] = '\0';
yylval.ID=strdup(yytext+cut);
return pragma_token;
}
<body_state,vardecl_list_state>{pragma} {/* return the pragmma without the enclosing '{' and '}' */
int cut = yytext[1]=='{'?2:1;
yytext[strlen(yytext)-cut] = '\0';
yylval.ID=strdup(yytext+cut);
return pragma_token;
}
/*********************************/
/* Handle the file includes! */
/*********************************/
<include_beg>{file_include_pragma_beg} BEGIN(include_filename);
<include_filename>{file_include_pragma_filename} {
/* set the internal state variables of lexical analyser to process a new include file */
include_file(yytext);
/* switch to whatever state was active before the include file */
yy_pop_state();
/* now process the new file... */
}
<<EOF>> { /* NOTE: Currently bison is incorrectly using END_OF_INPUT in many rules
* when checking for syntax errors in the input source code.
* This means that in reality flex will be asked to carry on reading the input
* even after it has reached the end of all (including the main) input files.
* In other owrds, we will be called to return more tokens, even after we have
* already returned an END_OF_INPUT token. In this case, we must carry on returning
* more END_OF_INPUT tokens.
*
* However, in the above case we will be asked to carry on reading more tokens
* from the main input file, after we have reached the end. For this to work
* correctly, we cannot close the main input file!
*
* This is why we WILL be called with include_stack_ptr == 0 multiple times,
* and why we must handle it as a special case
* that leaves the include_stack_ptr unchanged, and returns END_OF_INPUT once again.
*
* As a corollory, flex can never safely close the main input file, and we must ask
* bison to close it!
*/
if (include_stack_ptr == 0) {
// fclose(yyin); // Must not do this!!
// FreeTracking(current_tracking); // Must not do this!!
/* yyterminate() terminates the scanner and returns a 0 to the
* scanner's caller, indicating "all done".
*
* Our syntax parser (written with bison) has the token
* END_OF_INPUT associated to the value 0, so even though
* we don't explicitly return the token END_OF_INPUT
* calling yyterminate() is equivalent to doing that.
*/
yyterminate();
} else {
fclose(yyin);
FreeTracking(current_tracking);
--include_stack_ptr;
yy_delete_buffer(YY_CURRENT_BUFFER);
yy_switch_to_buffer((include_stack[include_stack_ptr]).buffer_state);
current_tracking = include_stack[include_stack_ptr].env;
/* removing constness of char *. This is safe actually,
* since the only real const char * that is stored on the stack is
* the first one (i.e. the one that gets stored in include_stack[0],
* which is never free'd!
*/
/* NOTE: We do __NOT__ free the malloc()'d memory since
* pointers to this filename will be kept by many objects
* in the abstract syntax tree.
* This will later be used to provide correct error
* messages during semantic analysis (stage 3)
*/
/* free((char *)current_filename); */
current_filename = include_stack[include_stack_ptr].filename;
yy_push_state(include_end);
}
}
<include_end>{file_include_pragma_end} yy_pop_state();
/* handle the artificial file includes created by include_string(), which do not end with a '}' */
<include_end>. unput_text(0); yy_pop_state();
/*********************************/
/* Handle all the state changes! */
/*********************************/
/* INITIAL -> header_state */
<INITIAL>{
/* NOTE: how about functions that do not declare variables, and go directly to the body_state???
* - According to Section 2.5.1.3 (Function Declaration), item 2 in the list, a FUNCTION
* must have at least one input argument, so a correct declaration will have at least
* one VAR_INPUT ... VAR_END construct!
* - According to Section 2.5.2.2 (Function Block Declaration), a FUNCTION_BLOCK
* must have at least one input argument, so a correct declaration will have at least
* one VAR_INPUT ... VAR_END construct!
* - According to Section 2.5.3 (Programs), a PROGRAM must have at least one input
* argument, so a correct declaration will have at least one VAR_INPUT ... VAR_END
* construct!
*
* All the above means that we needn't worry about PROGRAMs, FUNCTIONs or
* FUNCTION_BLOCKs that do not have at least one VAR_END before the body_state.
* If the code has an error, and no VAR_END before the body, we will simply
* continue in the <vardecl_state> state, untill the end of the FUNCTION, FUNCTION_BLOCK
* or PROGAM.
*/
FUNCTION yy_push_state(header_state); return FUNCTION;
FUNCTION_BLOCK yy_push_state(header_state); return FUNCTION_BLOCK;
PROGRAM yy_push_state(header_state); return PROGRAM;
CONFIGURATION BEGIN(config_state); return CONFIGURATION;
}
/* INITIAL -> body_state */
/* required if the function, program, etc.. has no VAR block! */
/* We comment it out since the standard does not allow this. */
/* NOTE: Even if we were to include the following code, it */
/* would have no effect whatsoever since the above */
/* rules will take precendence! */
/*
<INITIAL>{
FUNCTION BEGIN(body_state); return FUNCTION;
FUNCTION_BLOCK BEGIN(body_state); return FUNCTION_BLOCK;
PROGRAM BEGIN(body_state); return PROGRAM;
}
*/
/* header_state -> (vardecl_list_state) */
<header_state>{
VAR | /* execute the next rule's action, i.e. fall-through! */
VAR_INPUT |
VAR_OUTPUT |
VAR_IN_OUT |
VAR_EXTERNAL |
VAR_GLOBAL |
VAR_TEMP |
VAR_CONFIG |
VAR_ACCESS unput_text(0); BEGIN(vardecl_list_state);
}
/* vardecl_list_state -> (vardecl_state | body_state | INITIAL) */
<vardecl_list_state>{
VAR_INPUT | /* execute the next rule's action, i.e. fall-through! */
VAR_OUTPUT |
VAR_IN_OUT |
VAR_EXTERNAL |
VAR_GLOBAL |
VAR_TEMP |
VAR_CONFIG |
VAR_ACCESS |
VAR unput_text(0); yy_push_state(vardecl_state);
END_FUNCTION unput_text(0); BEGIN(INITIAL);
END_FUNCTION_BLOCK unput_text(0); BEGIN(INITIAL);
END_PROGRAM unput_text(0); BEGIN(INITIAL);
. unput_text(0); yy_push_state(body_state); /* anything else, just change to body_state! */
}
/* vardecl_list_state -> pop to $previous_state (vardecl_list_state) */
<vardecl_state>{
END_VAR yy_pop_state(); return END_VAR; /* pop back to header_state */
}
/* body_state -> (il_state | st_state | sfc_state) */
<body_state>{
INITIAL_STEP unput_text(0); BEGIN(sfc_state);
{qualified_identifier} unput_text(0); BEGIN(st_state); /* will always be followed by '[' for an array access, or ':=' as the left hand of an assignment statement */
{direct_variable_standard} unput_text(0); BEGIN(st_state); /* will always be followed by ':=' as the left hand of an assignment statement */
RETURN unput_text(0); BEGIN(st_state);
IF unput_text(0); BEGIN(st_state);
CASE unput_text(0); BEGIN(st_state);
FOR unput_text(0); BEGIN(st_state);
WHILE unput_text(0); BEGIN(st_state);
EXIT unput_text(0); BEGIN(st_state);
REPEAT unput_text(0); BEGIN(st_state);
/* ':=' occurs only in transitions, and not Function or FB bodies! */
:= unput_text(0); BEGIN(st_state);
{identifier} {int token = get_identifier_token(yytext);
if ((token == prev_declared_fb_name_token) || (token == prev_declared_variable_name_token)) {
/* the code has a call to a function block OR has an assingment with a variable as the lvalue */
unput_text(0); BEGIN(st_state);
} else
if (token == prev_declared_derived_function_name_token) {
/* the code has a call to a function - must be IL */
unput_text(0); BEGIN(il_state);
} else {
/* Might be a lable in IL, or a bug in ST/IL code. We jump to IL */
unput_text(0); BEGIN(il_state);
}
}
. unput_text(0); BEGIN(il_state); /* Don't know what it could be. This is most likely a bug. Let's just to a random state... */
} /* end of body_state lexical parser */
/* (il_state | st_state) -> pop to $previous_state (vardecl_list_state or sfc_state) */
<il_state,st_state>{
END_FUNCTION yy_pop_state(); unput_text(0);
END_FUNCTION_BLOCK yy_pop_state(); unput_text(0);
END_PROGRAM yy_pop_state(); unput_text(0);
END_TRANSITION yy_pop_state(); unput_text(0);
END_ACTION yy_pop_state(); unput_text(0);
}
/* sfc_state -> pop to $previous_state (vardecl_list_state or sfc_state) */
<sfc_state>{
END_FUNCTION yy_pop_state(); unput_text(0);
END_FUNCTION_BLOCK yy_pop_state(); unput_text(0);
END_PROGRAM yy_pop_state(); unput_text(0);
}
/* config -> INITIAL */
END_CONFIGURATION BEGIN(INITIAL); return END_CONFIGURATION;
/***************************************/
/* Next is to to remove all whitespace */
/***************************************/
/* NOTE: pragmas are handled right at the beginning... */
/* The whitespace */
<INITIAL,header_state,config_state,body_state,vardecl_list_state,vardecl_state,st_state,sfc_state,task_init_state,sfc_qualifier_state>{st_whitespace} /* Eat any whitespace */
<il_state>{il_whitespace} /* Eat any whitespace */
/* The comments */
<body_state,vardecl_list_state>{comment_beg} yy_push_state(comment_state);
{comment_beg} yy_push_state(comment_state);
<comment_state>{
{comment_beg} {if (get_opt_nested_comments()) yy_push_state(comment_state);}
{comment_end} yy_pop_state();
. /* Ignore text inside comment! */
\n /* Ignore text inside comment! */
}
/*****************************************/
/* B.1.1 Letters, digits and identifiers */
/*****************************************/
/* NOTE: 'R1', 'IN', etc... are IL operators, and therefore tokens
* On the other hand, the spec does not define them as keywords,
* which means they may be re-used for variable names, etc...!
* The syntax parser already caters for the possibility of these
* tokens being used for variable names in their declarations.
* When they are declared, they will be added to the variable symbol table!
* Further appearances of these tokens must no longer be parsed
* as R1_tokens etc..., but rather as variable_name_tokens!
*
* That is why the first thing we do with identifiers, even before
* checking whether they may be a 'keyword', is to check whether
* they have been previously declared as a variable name,
*
* However, we have a dilema! Should we here also check for
* prev_declared_derived_function_name_token?
* If we do, then the 'MOD' default library function (defined in
* the standard) will always be returned as a function name, and
* it will therefore not be possible to use it as an operator as
* in the following ST expression 'X := Y MOD Z;' !
* If we don't, then even it will not be possible to use 'MOD'
* as a funtion as in 'X := MOD(Y, Z);'
* We solve this by NOT testing for function names here, and
* handling this function and keyword clash in bison!
*/
/*
{identifier} {int token = get_identifier_token(yytext);
// fprintf(stderr, "flex: analysing identifier '%s'...", yytext);
if ((token == prev_declared_variable_name_token) ||
// (token == prev_declared_derived_function_name_token) || // DO NOT add this condition!
(token == prev_declared_fb_name_token)) {
// if (token != identifier_token)
// * NOTE: if we replace the above uncommented conditions with
* the simple test of (token != identifier_token), then
* 'MOD' et al must be removed from the
* library_symbol_table as a default function name!
* //
yylval.ID=strdup(yytext);
// fprintf(stderr, "returning token %d\n", token);
return token;
}
// otherwise, leave it for the other lexical parser rules...
// fprintf(stderr, "rejecting\n");
REJECT;
}
*/
/******************************************************/
/******************************************************/
/******************************************************/
/***** *****/
/***** *****/
/***** N O W D O T H E K E Y W O R D S *****/
/***** *****/
/***** *****/
/******************************************************/
/******************************************************/
/******************************************************/
REF {if (get_opt_ref_operator()) return REF; else{REJECT;}} /* Keyword in IEC 61131-3 v3 */
REF_TO {if (get_opt_ref_operator()) return REF_TO; else{REJECT;}} /* Keyword in IEC 61131-3 v3 */
NULL {if (get_opt_ref_operator()) return NULL_token; else{REJECT;}} /* Keyword in IEC 61131-3 v3 */
EN return EN; /* Keyword */
ENO return ENO; /* Keyword */
/******************************/
/* B 1.2.1 - Numeric Literals */
/******************************/
TRUE return TRUE; /* Keyword */
BOOL#1 return boolean_true_literal_token;
BOOL#TRUE return boolean_true_literal_token;
SAFEBOOL#1 {if (get_opt_safe_extensions()) {return safeboolean_true_literal_token;} else{REJECT;}} /* Keyword (Data Type) */
SAFEBOOL#TRUE {if (get_opt_safe_extensions()) {return safeboolean_true_literal_token;} else{REJECT;}} /* Keyword (Data Type) */
FALSE return FALSE; /* Keyword */
BOOL#0 return boolean_false_literal_token;
BOOL#FALSE return boolean_false_literal_token;
SAFEBOOL#0 {if (get_opt_safe_extensions()) {return safeboolean_false_literal_token;} else{REJECT;}} /* Keyword (Data Type) */
SAFEBOOL#FALSE {if (get_opt_safe_extensions()) {return safeboolean_false_literal_token;} else{REJECT;}} /* Keyword (Data Type) */
/************************/
/* B 1.2.3.1 - Duration */
/************************/
t# return T_SHARP; /* Delimiter */
T# return T_SHARP; /* Delimiter */
TIME return TIME; /* Keyword (Data Type) */
/************************************/
/* B 1.2.3.2 - Time of day and Date */
/************************************/
TIME_OF_DAY return TIME_OF_DAY; /* Keyword (Data Type) */
TOD return TIME_OF_DAY; /* Keyword (Data Type) */
DATE return DATE; /* Keyword (Data Type) */
d# return D_SHARP; /* Delimiter */
D# return D_SHARP; /* Delimiter */
DATE_AND_TIME return DATE_AND_TIME; /* Keyword (Data Type) */
DT return DATE_AND_TIME; /* Keyword (Data Type) */
/***********************************/
/* B 1.3.1 - Elementary Data Types */
/***********************************/
BOOL return BOOL; /* Keyword (Data Type) */
BYTE return BYTE; /* Keyword (Data Type) */
WORD return WORD; /* Keyword (Data Type) */
DWORD return DWORD; /* Keyword (Data Type) */
LWORD return LWORD; /* Keyword (Data Type) */
SINT return SINT; /* Keyword (Data Type) */
INT return INT; /* Keyword (Data Type) */
DINT return DINT; /* Keyword (Data Type) */
LINT return LINT; /* Keyword (Data Type) */
USINT return USINT; /* Keyword (Data Type) */
UINT return UINT; /* Keyword (Data Type) */
UDINT return UDINT; /* Keyword (Data Type) */
ULINT return ULINT; /* Keyword (Data Type) */
REAL return REAL; /* Keyword (Data Type) */
LREAL return LREAL; /* Keyword (Data Type) */
WSTRING return WSTRING; /* Keyword (Data Type) */
STRING return STRING; /* Keyword (Data Type) */
TIME return TIME; /* Keyword (Data Type) */
DATE return DATE; /* Keyword (Data Type) */
DT return DT; /* Keyword (Data Type) */
TOD return TOD; /* Keyword (Data Type) */
DATE_AND_TIME return DATE_AND_TIME; /* Keyword (Data Type) */
TIME_OF_DAY return TIME_OF_DAY; /* Keyword (Data Type) */
/*****************************************************************/
/* Keywords defined in "Safety Software Technical Specification" */
/*****************************************************************/
/*
* NOTE: The following keywords are define in
* "Safety Software Technical Specification,
* Part 1: Concepts and Function Blocks,
* Version 1.0 – Official Release"
* written by PLCopen - Technical Committee 5
*
* We only support these extensions and keywords
* if the apropriate command line option is given.
*/
SAFEBOOL {if (get_opt_safe_extensions()) {return SAFEBOOL;} else {REJECT;}}
SAFEBYTE {if (get_opt_safe_extensions()) {return SAFEBYTE;} else {REJECT;}}
SAFEWORD {if (get_opt_safe_extensions()) {return SAFEWORD;} else {REJECT;}}
SAFEDWORD {if (get_opt_safe_extensions()) {return SAFEDWORD;} else{REJECT;}}
SAFELWORD {if (get_opt_safe_extensions()) {return SAFELWORD;} else{REJECT;}}
SAFEREAL {if (get_opt_safe_extensions()) {return SAFESINT;} else{REJECT;}}
SAFELREAL {if (get_opt_safe_extensions()) {return SAFELREAL;} else{REJECT;}}
SAFESINT {if (get_opt_safe_extensions()) {return SAFESINT;} else{REJECT;}}
SAFEINT {if (get_opt_safe_extensions()) {return SAFEINT;} else{REJECT;}}
SAFEDINT {if (get_opt_safe_extensions()) {return SAFEDINT;} else{REJECT;}}
SAFELINT {if (get_opt_safe_extensions()) {return SAFELINT;} else{REJECT;}}
SAFEUSINT {if (get_opt_safe_extensions()) {return SAFEUSINT;} else{REJECT;}}
SAFEUINT {if (get_opt_safe_extensions()) {return SAFEUINT;} else{REJECT;}}
SAFEUDINT {if (get_opt_safe_extensions()) {return SAFEUDINT;} else{REJECT;}}
SAFEULINT {if (get_opt_safe_extensions()) {return SAFEULINT;} else{REJECT;}}
/* SAFESTRING and SAFEWSTRING are not yet supported, i.e. checked correctly, in the semantic analyser (stage 3) */
/* so it is best not to support them at all... */
/*
SAFEWSTRING {if (get_opt_safe_extensions()) {return SAFEWSTRING;} else{REJECT;}}
SAFESTRING {if (get_opt_safe_extensions()) {return SAFESTRING;} else{REJECT;}}
*/
SAFETIME {if (get_opt_safe_extensions()) {return SAFETIME;} else{REJECT;}}
SAFEDATE {if (get_opt_safe_extensions()) {return SAFEDATE;} else{REJECT;}}
SAFEDT {if (get_opt_safe_extensions()) {return SAFEDT;} else{REJECT;}}
SAFETOD {if (get_opt_safe_extensions()) {return SAFETOD;} else{REJECT;}}
SAFEDATE_AND_TIME {if (get_opt_safe_extensions()) {return SAFEDATE_AND_TIME;} else{REJECT;}}
SAFETIME_OF_DAY {if (get_opt_safe_extensions()) {return SAFETIME_OF_DAY;} else{REJECT;}}
/********************************/
/* B 1.3.2 - Generic data types */
/********************************/
/* Strangely, the following symbols do not seem to be required! */
/* But we include them so they become reserved words, and do not
* get passed up to bison as an identifier...
*/
ANY return ANY; /* Keyword (Data Type) */
ANY_DERIVED return ANY_DERIVED; /* Keyword (Data Type) */
ANY_ELEMENTARY return ANY_ELEMENTARY; /* Keyword (Data Type) */
ANY_MAGNITUDE return ANY_MAGNITUDE; /* Keyword (Data Type) */
ANY_NUM return ANY_NUM; /* Keyword (Data Type) */
ANY_REAL return ANY_REAL; /* Keyword (Data Type) */
ANY_INT return ANY_INT; /* Keyword (Data Type) */
ANY_BIT return ANY_BIT; /* Keyword (Data Type) */
ANY_STRING return ANY_STRING; /* Keyword (Data Type) */
ANY_DATE return ANY_DATE; /* Keyword (Data Type) */
/********************************/
/* B 1.3.3 - Derived data types */
/********************************/
":=" return ASSIGN; /* Delimiter */
".." return DOTDOT; /* Delimiter */
TYPE return TYPE; /* Keyword */
END_TYPE return END_TYPE; /* Keyword */
ARRAY return ARRAY; /* Keyword */
OF return OF; /* Keyword */
STRUCT return STRUCT; /* Keyword */
END_STRUCT return END_STRUCT; /* Keyword */
/*********************/
/* B 1.4 - Variables */
/*********************/
/******************************************/
/* B 1.4.3 - Declaration & Initialisation */
/******************************************/
VAR_INPUT return VAR_INPUT; /* Keyword */
VAR_OUTPUT return VAR_OUTPUT; /* Keyword */
VAR_IN_OUT return VAR_IN_OUT; /* Keyword */
VAR_EXTERNAL return VAR_EXTERNAL; /* Keyword */
VAR_GLOBAL return VAR_GLOBAL; /* Keyword */
END_VAR return END_VAR; /* Keyword */
RETAIN return RETAIN; /* Keyword */
NON_RETAIN return NON_RETAIN; /* Keyword */
R_EDGE return R_EDGE; /* Keyword */
F_EDGE return F_EDGE; /* Keyword */
AT return AT; /* Keyword */
/***********************/
/* B 1.5.1 - Functions */
/***********************/
FUNCTION return FUNCTION; /* Keyword */
END_FUNCTION return END_FUNCTION; /* Keyword */
VAR return VAR; /* Keyword */
CONSTANT return CONSTANT; /* Keyword */
/*****************************/
/* B 1.5.2 - Function Blocks */
/*****************************/
FUNCTION_BLOCK return FUNCTION_BLOCK; /* Keyword */
END_FUNCTION_BLOCK return END_FUNCTION_BLOCK; /* Keyword */
VAR_TEMP return VAR_TEMP; /* Keyword */
VAR return VAR; /* Keyword */
NON_RETAIN return NON_RETAIN; /* Keyword */
END_VAR return END_VAR; /* Keyword */
/**********************/
/* B 1.5.3 - Programs */
/**********************/
PROGRAM return PROGRAM; /* Keyword */
END_PROGRAM return END_PROGRAM; /* Keyword */
/********************************************/
/* B 1.6 Sequential Function Chart elements */
/********************************************/
/* NOTE: the following identifiers/tokens clash with the R and S IL operators, as well
.* as other identifiers that may be used as variable names inside IL and ST programs.
* They will have to be handled when we include parsing of SFC... For now, simply
* ignore them!
*/
ACTION return ACTION; /* Keyword */
END_ACTION return END_ACTION; /* Keyword */
TRANSITION return TRANSITION; /* Keyword */
END_TRANSITION return END_TRANSITION; /* Keyword */
FROM return FROM; /* Keyword */
TO return TO; /* Keyword */
INITIAL_STEP return INITIAL_STEP; /* Keyword */
STEP return STEP; /* Keyword */
END_STEP return END_STEP; /* Keyword */
/* PRIORITY is not a keyword, so we only return it when
* it is explicitly required and we are not expecting any identifiers
* that could also use the same letter sequence (i.e. an identifier: piority)
*/
<sfc_priority_state>PRIORITY return PRIORITY;
<sfc_qualifier_state>{
L return L;
D return D;
SD return SD;
DS return DS;
SL return SL;
N return N;
P return P;
P0 return P0;
P1 return P1;
R return R;
S return S;
}
/********************************/
/* B 1.7 Configuration elements */
/********************************/
CONFIGURATION return CONFIGURATION; /* Keyword */
END_CONFIGURATION return END_CONFIGURATION; /* Keyword */
TASK return TASK; /* Keyword */
RESOURCE return RESOURCE; /* Keyword */
ON return ON; /* Keyword */
END_RESOURCE return END_RESOURCE; /* Keyword */
VAR_CONFIG return VAR_CONFIG; /* Keyword */
VAR_ACCESS return VAR_ACCESS; /* Keyword */
END_VAR return END_VAR; /* Keyword */
WITH return WITH; /* Keyword */
PROGRAM return PROGRAM; /* Keyword */
RETAIN return RETAIN; /* Keyword */
NON_RETAIN return NON_RETAIN; /* Keyword */
READ_WRITE return READ_WRITE; /* Keyword */
READ_ONLY return READ_ONLY; /* Keyword */
/* PRIORITY, SINGLE and INTERVAL are not a keywords, so we only return them when
* it is explicitly required and we are not expecting any identifiers
* that could also use the same letter sequence (i.e. an identifier: piority, ...)
*/
<task_init_state>{
PRIORITY return PRIORITY;
SINGLE return SINGLE;
INTERVAL return INTERVAL;
}
/***********************************/
/* B 2.1 Instructions and Operands */
/***********************************/
<il_state>\n return EOL;
/*******************/
/* B 2.2 Operators */
/*******************/
/* NOTE: we can't have flex return the same token for
* ANDN and &N, neither for AND and &, since
* AND and ANDN are considered valid variable
* function or functionblock type names!
* This means that the parser may decide that the
* AND or ANDN strings found in the source code
* are being used as variable names
* and not as operators, and will therefore transform
* these tokens into indentifier tokens!
* We can't have the parser thinking that the source
* code contained the string AND (which may be interpreted
* as a vairable name) when in reality the source code
* merely contained the character &, so we use two
* different tokens for & and AND (and similarly
* ANDN and &N)!
*/
/* The following tokens clash with ST expression operators and Standard Functions */
/* They are also keywords! */
AND return AND; /* Keyword */
MOD return MOD; /* Keyword */
OR return OR; /* Keyword */
XOR return XOR; /* Keyword */
NOT return NOT; /* Keyword */
/* The following tokens clash with Standard Functions */
/* They are keywords because they are a function name */
<il_state>{
ADD return ADD; /* Keyword (Standard Function) */
DIV return DIV; /* Keyword (Standard Function) */
EQ return EQ; /* Keyword (Standard Function) */
GE return GE; /* Keyword (Standard Function) */
GT return GT; /* Keyword (Standard Function) */
LE return LE; /* Keyword (Standard Function) */
LT return LT; /* Keyword (Standard Function) */
MUL return MUL; /* Keyword (Standard Function) */
NE return NE; /* Keyword (Standard Function) */
SUB return SUB; /* Keyword (Standard Function) */
}
/* The following tokens clash with SFC action qualifiers */
/* They are not keywords! */
<il_state>{
S return S;
R return R;
}
/* The following tokens clash with ST expression operators */
& return AND2; /* NOT a Delimiter! */
/* The following tokens have no clashes */
/* They are not keywords! */
<il_state>{
LD return LD;
LDN return LDN;
ST return ST;
STN return STN;
S1 return S1;
R1 return R1;
CLK return CLK;
CU return CU;
CD return CD;
PV return PV;
IN return IN;
PT return PT;
ANDN return ANDN;
&N return ANDN2;
ORN return ORN;
XORN return XORN;
CAL return CAL;
CALC return CALC;
CALCN return CALCN;
RET return RET;
RETC return RETC;
RETCN return RETCN;
JMP return JMP;
JMPC return JMPC;
JMPCN return JMPCN;
}
/***********************/
/* B 3.1 - Expressions */
/***********************/
"**" return OPER_EXP; /* NOT a Delimiter! */
"<>" return OPER_NE; /* NOT a Delimiter! */
">=" return OPER_GE; /* NOT a Delimiter! */
"<=" return OPER_LE; /* NOT a Delimiter! */
& return AND2; /* NOT a Delimiter! */
AND return AND; /* Keyword */
XOR return XOR; /* Keyword */
OR return OR; /* Keyword */
NOT return NOT; /* Keyword */
MOD return MOD; /* Keyword */
/*****************************************/
/* B 3.2.2 Subprogram Control Statements */
/*****************************************/
:= return ASSIGN; /* Delimiter */
=> return SENDTO; /* Delimiter */
RETURN return RETURN; /* Keyword */
/********************************/
/* B 3.2.3 Selection Statements */
/********************************/
IF return IF; /* Keyword */
THEN return THEN; /* Keyword */
ELSIF return ELSIF; /* Keyword */
ELSE return ELSE; /* Keyword */
END_IF return END_IF; /* Keyword */
CASE return CASE; /* Keyword */
OF return OF; /* Keyword */
ELSE return ELSE; /* Keyword */
END_CASE return END_CASE; /* Keyword */
/********************************/
/* B 3.2.4 Iteration Statements */
/********************************/
FOR return FOR; /* Keyword */
TO return TO; /* Keyword */
BY return BY; /* Keyword */
DO return DO; /* Keyword */
END_FOR return END_FOR; /* Keyword */
WHILE return WHILE; /* Keyword */
DO return DO; /* Keyword */
END_WHILE return END_WHILE; /* Keyword */
REPEAT return REPEAT; /* Keyword */
UNTIL return UNTIL; /* Keyword */
END_REPEAT return END_REPEAT; /* Keyword */
EXIT return EXIT; /* Keyword */
/********************************************************/
/********************************************************/
/********************************************************/
/***** *****/
/***** *****/
/***** N O W W O R K W I T H V A L U E S *****/
/***** *****/
/***** *****/
/********************************************************/
/********************************************************/
/********************************************************/
/********************************************/
/* B.1.4.1 Directly Represented Variables */
/********************************************/
{direct_variable} {yylval.ID=strdup(yytext); return get_direct_variable_token(yytext);}
/******************************************/
/* B 1.4.3 - Declaration & Initialisation */
/******************************************/
{incompl_location} {yylval.ID=strdup(yytext); return incompl_location_token;}
/************************/
/* B 1.2.3.1 - Duration */
/************************/
{fixed_point} {yylval.ID=strdup(yytext); return fixed_point_token;}
{interval} {/*fprintf(stderr, "entering time_literal_state ##%s##\n", yytext);*/ unput_and_mark('#'); yy_push_state(time_literal_state);}
{erroneous_interval} {return erroneous_interval_token;}
<time_literal_state>{
{integer}d {yylval.ID=strdup(yytext); yylval.ID[yyleng-1] = '\0'; return integer_d_token;}
{integer}h {yylval.ID=strdup(yytext); yylval.ID[yyleng-1] = '\0'; return integer_h_token;}
{integer}m {yylval.ID=strdup(yytext); yylval.ID[yyleng-1] = '\0'; return integer_m_token;}
{integer}s {yylval.ID=strdup(yytext); yylval.ID[yyleng-1] = '\0'; return integer_s_token;}
{integer}ms {yylval.ID=strdup(yytext); yylval.ID[yyleng-2] = '\0'; return integer_ms_token;}
{fixed_point}d {yylval.ID=strdup(yytext); yylval.ID[yyleng-1] = '\0'; return fixed_point_d_token;}
{fixed_point}h {yylval.ID=strdup(yytext); yylval.ID[yyleng-1] = '\0'; return fixed_point_h_token;}
{fixed_point}m {yylval.ID=strdup(yytext); yylval.ID[yyleng-1] = '\0'; return fixed_point_m_token;}
{fixed_point}s {yylval.ID=strdup(yytext); yylval.ID[yyleng-1] = '\0'; return fixed_point_s_token;}
{fixed_point}ms {yylval.ID=strdup(yytext); yylval.ID[yyleng-2] = '\0'; return fixed_point_ms_token;}
_ /* do nothing - eat it up!*/
\# {/*fprintf(stderr, "popping from time_literal_state (###)\n");*/ yy_pop_state(); return end_interval_token;}
. {/*fprintf(stderr, "time_literal_state: found invalid character '%s'. Aborting!\n", yytext);*/ ERROR;}
\n {ERROR;}
}
/*******************************/
/* B.1.2.2 Character Strings */
/*******************************/
{double_byte_character_string} {yylval.ID=strdup(yytext); return double_byte_character_string_token;}
{single_byte_character_string} {yylval.ID=strdup(yytext); return single_byte_character_string_token;}
/******************************/
/* B.1.2.1 Numeric literals */
/******************************/
{integer} {yylval.ID=strdup(yytext); return integer_token;}
{real} {yylval.ID=strdup(yytext); return real_token;}
{binary_integer} {yylval.ID=strdup(yytext); return binary_integer_token;}
{octal_integer} {yylval.ID=strdup(yytext); return octal_integer_token;}
{hex_integer} {yylval.ID=strdup(yytext); return hex_integer_token;}
/*****************************************/
/* B.1.1 Letters, digits and identifiers */
/*****************************************/
<st_state>{identifier}/({st_whitespace_or_pragma_or_comment})"=>" {yylval.ID=strdup(yytext); return sendto_identifier_token;}
<il_state>{identifier}/({il_whitespace_or_pragma_or_comment})"=>" {yylval.ID=strdup(yytext); return sendto_identifier_token;}
{identifier} {yylval.ID=strdup(yytext);
// printf("returning identifier...: %s, %d\n", yytext, get_identifier_token(yytext));
return get_identifier_token(yytext);}
/************************************************/
/************************************************/
/************************************************/
/***** *****/
/***** *****/
/***** T H E L E F T O V E R S . . . *****/
/***** *****/
/***** *****/
/************************************************/
/************************************************/
/************************************************/
/* do the single character tokens...
*
* e.g.: ':' '(' ')' '+' '*' ...
*/
. {return yytext[0];}
%%
/*************************/
/* Tracking Functions... */
/*************************/
#define MAX_LINE_LENGTH 1024
tracking_t *GetNewTracking(FILE* in_file) {
tracking_t* new_env = new tracking_t;
new_env->eof = 0;
new_env->lineNumber = 0;
new_env->currentChar = 0;
new_env->lineLength = 0;
new_env->currentTokenStart = 0;
new_env->buffer = (char*)malloc(MAX_LINE_LENGTH);
new_env->in_file = in_file;
return new_env;
}
void FreeTracking(tracking_t *tracking) {
free(tracking->buffer);
delete tracking;
}
/* GetNextChar: reads a character from input */
int GetNextChar(char *b, int maxBuffer) {
char *p;
if ( current_tracking->eof )
return 0;
while ( current_tracking->currentChar >= current_tracking->lineLength ) {
current_tracking->currentChar = 0;
current_tracking->currentTokenStart = 1;
current_tracking->eof = false;
p = fgets(current_tracking->buffer, MAX_LINE_LENGTH, current_tracking->in_file);
if ( p == NULL ) {
if ( ferror(current_tracking->in_file) )
return 0;
current_tracking->eof = true;
return 0;
}
current_tracking->lineLength = strlen(current_tracking->buffer);
/* only increment line number if the buffer was big enough to read the whole line! */
char last_char = current_tracking->buffer[current_tracking->lineLength - 1];
if (('\n' == last_char) || ('\r' == last_char)) // '\r' ---> CR, '\n' ---> LF
current_tracking->lineNumber++;
}
b[0] = current_tracking->buffer[current_tracking->currentChar];
if (b[0] == ' ' || b[0] == '\t')
current_tracking->currentTokenStart++;
current_tracking->currentChar++;
return b[0]==0?0:1;
}
/***********************************/
/* Utility function definitions... */
/***********************************/
/* print the include file stack to stderr... */
void print_include_stack(void) {
int i;
if ((include_stack_ptr - 1) >= 0)
fprintf (stderr, "in file ");
for (i = include_stack_ptr - 1; i >= 0; i--)
fprintf (stderr, "included from file %s:%d\n", include_stack[i].filename, include_stack[i].env->lineNumber);
}
/* set the internal state variables of lexical analyser to process a new include file */
void handle_include_file_(FILE *filehandle, const char *filename) {
if (include_stack_ptr >= MAX_INCLUDE_DEPTH) {
fprintf(stderr, "Includes nested too deeply\n");
exit( 1 );
}
yyin = filehandle;
include_stack[include_stack_ptr].buffer_state = YY_CURRENT_BUFFER;
include_stack[include_stack_ptr].env = current_tracking;
include_stack[include_stack_ptr].filename = current_filename;
current_filename = strdup(filename);
current_tracking = GetNewTracking(yyin);
include_stack_ptr++;
/* switch input buffer to new file... */
yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
}
/* insert the code (in <source_code>) into the source code we are parsing.
* This is done by creating an artificial file with that new source code, and then 'including' the file
*/
void include_string_(const char *source_code) {
FILE *tmp_file = tmpfile();
if(tmp_file == NULL) {
perror("Error creating temp file.");
exit(EXIT_FAILURE);
}
fwrite((void *)source_code, 1, strlen(source_code), tmp_file);
rewind(tmp_file);
/* now parse the tmp file, by asking flex to handle it as if it had been included with the (*#include ... *) pragma... */
handle_include_file_(tmp_file, "");
//fclose(tmp_file); /* do NOT close file. It must only be closed when we finish reading from it! */
}
/* Open an include file, and set the internal state variables of lexical analyser to process a new include file */
void include_file(const char *filename) {
FILE *filehandle = NULL;
for (int i = 0; (INCLUDE_DIRECTORIES[i] != NULL) && (filehandle == NULL); i++) {
char *full_name;
full_name = strdup3(INCLUDE_DIRECTORIES[i], "/", filename);
if (full_name == NULL) {
fprintf(stderr, "Out of memory!\n");
exit( 1 );
}
filehandle = fopen(full_name, "r");
free(full_name);
}
if (NULL == filehandle) {
fprintf(stderr, "Error opening included file %s\n", filename);
exit( 1 );
}
/* now process the new file... */
handle_include_file_(filehandle, filename);
}
/* return all the text in the current token back to the input stream, except the first n chars. */
void unput_text(unsigned int n) {
/* it seems that flex has a bug in that it will not correctly count the line numbers
* if we return newlines back to the input stream. These newlines will be re-counted
* a second time when they are processed again by flex.
* We therefore determine how many newlines are in the text we are returning,
* and decrement the line counter acordingly...
*/
/*
unsigned int i;
for (i = n; i < strlen(yytext); i++)
if (yytext[i] == '\n')
current_tracking->lineNumber--;
*/
/* now return all the text back to the input stream... */
yyless(n);
}
/* return all the text in the current token back to the input stream,
* but first return to the stream an additional character to mark the end of the token.
*/
void unput_and_mark(const char c) {
char *yycopy = strdup( yytext ); /* unput() destroys yytext, so we copy it first */
unput(c);
for (int i = yyleng-1; i >= 0; i--)
unput(yycopy[i]);
free(yycopy);
}
/* Called by flex when it reaches the end-of-file */
int yywrap(void)
{
/* We reached the end of the input file... */
/* Should we continue with another file? */
/* If so:
* open the new file...
* return 0;
*/
/* to stop processing...
* return 1;
*/
return 1; /* Stop scanning at end of input file. */
}
/*******************************/
/* Public Interface for Bison. */
/*******************************/
/* The following functions will be called from inside bison code! */
void include_string(const char *source_code) {include_string_(source_code);}
/* Tell flex which file to parse. This function will not imediately start parsing the file.
* To parse the file, you then need to call yyparse()
*
* Returns NULL on error opening the file (and a valid errno), or 0 on success.
* Caller must close the file!
*/
FILE *parse_file(const char *filename) {
FILE *filehandle = NULL;
if((filehandle = fopen(filename, "r")) != NULL) {
yyin = filehandle;
current_filename = strdup(filename);
current_tracking = GetNewTracking(yyin);
}
return filehandle;
}
/*************************************/
/* Include a main() function to test */
/* the token parsing by flex.... */
/*************************************/
#ifdef TEST_MAIN
#include "../util/symtable.hh"
yystype yylval;
YYLTYPE yylloc;
int get_identifier_token(const char *identifier_str) {return 0;}
int get_direct_variable_token(const char *direct_variable_str) {return 0;}
int main(int argc, char **argv) {
FILE *in_file;
int res;
if (argc == 1) {
/* Work as an interactive (command line) parser... */
while((res=yylex()))
fprintf(stderr, "(line %d)token: %d\n", yylineno, res);
} else {
/* Work as non-interactive (file) parser... */
if((in_file = fopen(argv[1], "r")) == NULL) {
char *errmsg = strdup2("Error opening main file ", argv[1]);
perror(errmsg);
free(errmsg);
return -1;
}
/* parse the file... */
yyin = in_file;
current_filename = argv[1];
while(1) {
res=yylex();
fprintf(stderr, "(line %d)token: %d (%s)\n", yylineno, res, yylval.ID);
}
}
return 0;
}
#endif