|
The
Bartels User Language Compiler contains a preprocessor capable of processing special preprocessor statements. Preprocessor statements must start with a hash
(#) and they are delimited by the end of the corresponding source code line. Preprocessor statements can appear anywhere and have effect which lasts (independent of scope) until the end of the source code program file.
2.6.1 File Inclusion
Bartels User Language provides the
#include statement as known from C. The formal syntax of the
#include statement is
#include "filename" EOLN
where
filename must be the name of a
User Language source code file. This statement is terminated by end-of-line. An
#include statement causes the replacement of that statement by the entire contents of the specified
User Language source code file. Such inclusion files can contain general definitions frequently used for different programs. It is a good idea to use include files in order to reduce the expenditure of software maintenance.
#include statements can be nested, unless they do not refer identical file names (data recursion).
When including source code files one should consider, that not all of the therein contained definitions are really needed by a program; thus, it is recommended to run the
User Language Compiler with the optimizer to eliminate redundant parts of the program.
The following example shows the include file
baecall.ulh, which contains the function
call for activating
AutoEngineer menu functions:
// baecall.ulh -- BAE menu call facilities
void call(int menuitem) // Call a BAE menu function
{
// Perform the BAE menu call
if (bae_callmenu(menuitem))
{
// Error; print error message and exit from program
perror("BAE menu call fault!");
exit(-1);
}
}
The following example shows the source code of the
zoomall program for executing the
AutoEngineer
command; the program utilizes the
call function from the included
baecall.ulh source file:
// ZOOMALL -- Call the BAE Zoom All command
#include "baecall.ulh" // BAE menu call include
main()
{
// Call Zoom All
call(101);
}
2.6.2 Constant Definition
A preprocessor statement of the form
#define IDENT constexpr EOLN
causes the Compiler to replace subsequent instances of the
IDENT identifier with the value of the given constant expression (constexpr). This statement is terminated by end-of-line. The features introduced by the
#define statement are most valuable for definition of substantial constants.
A constant definition introduced by
#define is valid to the end of the program text unless deleted by a preprocessor statement of the form
#undef IDENT EOLN
A special
#define statement form is given by
#define IDENT EOLN
where just a name is defined. The existence or no-existence of such a name definition can be checked with the
#ifdef and
#ifndef preprocessor statements (see also
chapter 2.6.3).
The
#define statement can be applied as in the following examples:
#define DDBCLASSLAYOUT 100
#define mmtoinch 25.4
#define inchtomm 1.0/mmtoinch
#define REPABORT "Operation aborted."
#define ERRCLASS "Operation not allowed for this element!"
#define GERMAN 1
#define ENGLISH 0
#define LANGUAGE GERMAN
#define DEBUG
2.6.3 Conditional Compilation
A preprocessor statement of the form
#if constexpr EOLN
causes the Compiler to check, whether the specified constant expression is nonzero. A preprocessor statement of the form
#ifdef IDENT EOLN
causes the Compiler to check, whether the name specified with the identifier is defined (through a
#define statement). A preprocessor statement such as
#ifndef IDENT EOLN
causes the Compiler to check, whether the name specified with the identifier is undefined.
The source code lines following to
#if,
#ifdef or
#ifndef are only compiled if the checked condition is true. Otherwise, the the corresponding source code lines are merely checked for correct syntax.
An
#if preprocessor statement can optionally be followed by a preprocessor statement of the form
#else EOLN
A preprocessor statement of the form
#endif EOLN
terminates the if construct. If the checked condition is true then the source code lines between
#else and
#endif are not compiled. If the checked condition is false then the source code lines between if-statement and
#else (or
#endif on lacking
#else) are not be compiled. Such
#if preprocessor constructs can nest.
The following example illustrates the features of conditional compilation:
#define ENGLISH 0
#define GERMAN 1
#define LANGUAGE ENGLISH
#if LANGUAGE == GERMAN
#define MSGSTART "Programm gestartet."
#endif
#if LANGUAGE == ENGLISH
#define MSGSTART "Program started."
#endif
#define DEBUG
main()
{
#ifdef DEBUG
perror(MSGSTART);
#endif
:
}
2.6.4 BNF Precompiler
A BNF precompiler is integrated to the
Bartels User Language. This BNF precompiler with its corresponding scanner and parser functions can be utilized to implement programs for processing almost any foreign ASCII file data format.
Each
User Language program text can contain up to one preprocessor statement of the form
#bnf { ... }
which can cover an arbitrary number of source code lines. The
#bnf statement activates the BNF Precompiler of the
Bartels User Language. BNF (Backus Naur Form) is a formalism for describing the syntax of a language. The BNF definition (enclosed with the braces of the
#bnf statement) of a language consists of a sequence of rule defining the language grammar, i.e., the valid word and/or character sequences for building sentences in this language. A rule of the BNF notation consists of a grammar term (non-terminal symbol) and a sequence of one or more alternative formulations, which are assigned to the grammar term by the operator
: (to be read as "consists of"); alternative formulations are separated by the
| operator. A formulation consists of a sequence of grammar terms and input symbols (terminal symbols), whith empty formulations being also allowed. The grammar term of the first rule of a BNF definition is called start symbol. Grammar terms can be referenced recursively, thus allowing for the specification an infinite number of valid sentences of infinite length. Each rule definition must be terminated by the colon operator
(;).
The language's vocabulary is determined by the terminal symbols specified with the BNF definition. The keywords
IDENT (identifier),
NUMBER (numeric constants),
SQSTR (single quoted string),
DQSTR (double quoted string),
EOLN (end of line,
\n),
EOF (end of file and/or end of string scanning strings),
EOFINC (end of include file) and
UNKNOWN (special character sequence not explicitly defined) stand for generalized terminal symbols from in the corresponding word class. Special user-specific terminal symbols must be quoted. The BNF Precompiler applies automatic classification to assign these terminal symbols to either of the word classes keyword (reserved words consisting of three or more characters), single character operator (consisting of one special character) or double character operator (consisting of two special characters).
Keywords can be specified as in
"SECTION" 'ENDSEC' 'Inch' 'begin' "end" 'include'
Double character operators can be specified as in
'<=' '++' "&&" "+=" '::' ':='
Single character operators can be specified as in
'+' '-' "*" "/" "=" "[" "}" '#' '.'
Spacings (blanks, tabulator, newline) are not significant for defining a grammar; they serve just for separating adjacent symbols. Comments belong to the spacing token class as well. For comment type definitions, it is necessary to define comment delimiter operators. On default, the BNF Precompiler assigns the operators
/* (comment start recognition) and
*/ (comment end recognition) for comments which can span multiple lines. This assignment can be changed at the beginning of the BNF definition. The command for the default setting is:
COMMENT ("/*", "*/") ;
Omitting the second parameter from the
COMMENT statement as in
COMMENT ('//');
configures comments which extend to the end of the line. Please note that the
COMMENT default setting is reset if a
COMMENT statement is added to the BNF definition. I.e., the following statements must both be added at the beginning of the BNF definition to configure
/* and
*/ for multi-line comments and
// for comments to end-of-line:
COMMENT ('/*', '*/');
COMMENT ('//');
A special feature of the
Bartels User Language BNF Precompiler is the possibility of defining an action for each grammar term and/or input symbol of a formulation. An action is specified by appending the (parentheses-enclosed) name of a user function to the symbol. The parser automatically calls the referenced user function upon recognition of the corresponding input symbol. These user functions must be properly defined with data type
int; their return value must be (-1) on error or zero otherwise. Up to one action function parameter of type
int is allowed; this parameter must be specified as parentheses-enclosed integer constant after the function name in the BNF definition.
See the
Bartels User Language syntax description in
chapter 2.7 for a detailed description of the BNF precompiler syntax definition.
The BNF Precompiler compiles the BNF definition to
User Language machine code, which represents a state machine for processing text of the defined language. This state machine can be activated with either of the
Bartels User Language system functions
synparsefile and/or
synparsestring.
synparsefile activates a parser for processing a file name specified input file, whilst
synparsestring can be used to process strings rather than file contents; the referenced parser action functions are automatically called as required. The
synscanline and
synscanstring system functions can be utilized in these parser action functions for querying the current input scan line number and the input scan string. The current scan string can be subjected to semantic tests. The
synparsefile and/or
synparsestring functions are terminated if the end of the input file and/or the input string terminator is reached or if a syntax error (or a semantic error encountered by a parser action function) has occurred.
For completeness reasons the system functions
synscnaeoln,
synscanigncase and
synparseincfile are still to be mentioned. The
synscaneoln scan function is used to enable and/or disable the BNF parser's end-of-line recognition, which is disabled on default. The EOLN terminal symbol of a BNF definition can be recognized only if the EOLN recognition is activated with the
synscaneoln function. The
synscanigncase scan function is used to enable and/or disable the BNF parser's case-sensitivity when scanning keyords. The
synparseincfile function can be utilized for processing include files. The parser starts reading at the beginning of the name-specified include file when calling the
synparseincfile function. An
EOFINC terminal symbol is returned if the end of an include file is reached, and reading resumes where it was interrupted in the previously processed input file. The
EOFINC terminal symbol is obsolete if the
synparseincfile function is not used.
See
appendix C for a detailed description of the
Bartels User Language BNF scanner and parser system functions.
The usage of the BNF Precompiler is illustrated by the following
User Language example program. The purpose of this program is to read part placement data from an ASCII file and to perform the corresponding placement on the layout currently loaded to the
Bartels AutoEngineer
Layout Editor. The input placement data is supposed to be organized according to the following example:
// This is a comment @
LAYOUT # This is a comment extending to the end of line
UNITS {
LENGTH = ( 1.0 INCH ) ;
ANGLE = ( 1.0 DEGREE ) ;
}
PLACEMENT {
'ic1' : 'dil16' {
POSITION = (0.000,0.000) ;
ROTATION = 0.000 ;
MIRROR = 0 ;
}
'ic2' : 'dil16' {
POSITION = (2.250,0.100) ;
}
'ic3' : 'dil16' {
POSITION = (1.000,0.394) ;
ROTATION = 23.500 ;
}
'ic4' : 'so16' {
POSITION = (0.000,0.700) ;
ROTATION = 0.000 ;
MIRROR = 1 ;
}
}
END
The following listing shows a program which utilizes the BNF Precompiler and the corresponding scanner/parser functions in order to load placement data from external files according to the example above:
// READLPLC -- Read Layout Placement from ASCII File
//_________________________________________________________
// BNF input syntax definition
#bnf {
COMMENT ("//", "@") ;
COMMENT ("#") ;
placefile
: "LAYOUT" placeunits placedata "END"
;
placeunits
: "UNITS" "{"
"LENGTH" "=" "(" floatnum placelengthunit ")" ";"
"ANGLE" "=" "(" floatnum placeangleunit ")" ";"
"}"
;
placelengthunit
: "INCH" (p_unitl(1))
| "MM" (p_unitl(2))
| "MIL" (p_unitl(3))
;
placeangleunit
: "DEGREE" (p_unita(1))
| "RAD" (p_unita(2))
;
placedata
: "PLACEMENT" "{" placecommands "}"
;
placecommands
: placecommands placecommand
|
;
placecommand
: identifier (p_pname) ":" identifier (p_plname)
"{" placepos placerot placemirror "}" (p_storepart)
;
placepos
: "POSITION" "="
"(" floatnum (p_px) "," floatnum (p_py) ")" ";"
;
placerot
: "ROTATION" "=" floatnum (p_pa) ";"
|
;
placemirror
: "MIRROR" "=" NUMBER (p_pm) ";"
|
;
identifier
: SQSTR (p_ident)
;
floatnum
: NUMBER (p_fltnum(0))
| "-" NUMBER (p_fltnum(1))
;
}
//______________________________________________________________
// Globals
double plannx=bae_planwsnx(); // Element origin X coordinate
double planny=bae_planwsny(); // Element origin Y coordinate
double lenconv; // Length conversion factor
double angconv; // Angle conversion factor
string curpn; // Current part name
string curpln; // Current physical library name
double curx,cury; // Current coordinates
double cura = 0.0; // Current angle (default: 0.0)
int curm = 0; // Current mirror flag (default: 0)
string curid; // Current identifier
double curflt; // Current float value
struct partdes { // Part descriptor
string pn; // Part name
string pln; // Physical library name
double x,y; // Coordinates
double a; // Angle
int m; // Mirror flag
} pl[]; // Part list
int pn=0; // Part count
//______________________________________________________________
// Main program
main()
{
string fname; // Input file name
// Test if layout loaded
if (bae_planddbclass()!=100)
errormsg("Command not allowed for this element!","");
// Get and test the placement file name
if ((fname=askstr("Placement File : ",40))=="")
errormsg("Operation aborted.","");
// Parse the placement file
perror("Reading placement data...");
parseerr(synparsefile(fname),fname);
// Perform the placement
placement();
// Done
perror("Operation completed without errors.");
}
//______________________________________________________________
// Part list management and placement
void gcpart()
// Get or create some part list entry
{
index L_CPART cpart; // Part index
index L_NREF nref; // Named reference index
int slb=0; // Search lower boundary
int sub=pn-1; // Search upper boundary
int idx; // Search index
int compres; // Compare result
// Loop until search area empty
while (slb<=sub) {
// Get the search index
idx=(slb+sub)>>1;
// Get and test the compare result
if ((compres=strcmp(curpn,pl[idx].pn))==0)
errormsg("Multiple defined part '%s'!",curpn);
// Update the search area
if (compres<0)
sub=idx-1;
else
slb=idx+1;
}
// Check if part is placed already
forall (nref where curpn==nref.NAME)
// Part already placed; abort
return;
// Check net list consistence
forall (cpart where curpn==cpart.NAME) {
// Check the plname
if (curpln!=cpart.PLNAME)
// Netlist definition mismatch
errormsg("Wrong part macro name '%s'!",curpln);
// Done
break;
}
// Insert the new entry to the part list
pn++;
for (idx=pn-2;idx>=slb;idx--)
pl[idx+1]=pl[idx];
pl[slb].pn=curpn;
pl[slb].pln=curpln;
pl[slb].x=curx;
pl[slb].y=cury;
pl[slb].a=cura;
pl[slb].m=curm;
}
void placement()
// Perform the placement
{
int i; // Loop control variable
// Iterate part list
for (i=0;i<pn;i++) {
// Place the part
if (ged_storepart(pl[i].pn,pl[i].pln,
pl[i].x,pl[i].y,pl[i].a,pl[i].m))
errormsg("Error placing part '%s'!",pl[i].pn);
}
}
//______________________________________________________________
// Error handling
void parseerr(status,fn)
// Handle a syntax/parser error
int status; // Scan status
string fn; // File name
{
string msg; // Error message
// Evaluate the scan status
switch (status) {
case 0 : // No error
return;
case 1 :
msg="No BNF definition available!";
break;
case 2 :
msg="Parser already active!";
break;
case 3 :
sprintf(msg," Error opening file '%s'!",fn);
break;
case 4 :
msg="Too many open files!";
break;
case 5 :
sprintf(msg,"[%s/%d] Fatal read/write error!",
fn,synscanline());
break;
case 6 :
sprintf(msg,"[%s/%d] Scan item '%s' too long!",
fn,synscanline(),synscanstring());
break;
case 7 :
sprintf(msg,"[%s/%d] Syntax error at '%s'!",
fn,synscanline(),synscanstring());
break;
case 8 :
sprintf(msg,"[%s/%d] Unexpected end of file!",
fn,synscanline());
break;
case 9 :
sprintf(msg,"[%s/%d] Stack overflow (BNF too complex)!",
fn,synscanline());
break;
case 10 :
sprintf(msg,"[%s/%d] Stack underflow (BNF erroneous)!",
fn,synscanline());
break;
case 11 :
sprintf(msg,"[%s/%d] Error from parse action function!",
fn,synscanline());
break;
default :
sprintf(msg,"Unknown parser error code %d!",status);
break;
}
// Print the error message
errormsg(msg,"");
}
void errormsg(string errfmt,string erritem)
// Print an error message with error item and exit from program
{
string errmsg; // Error message string
// Build and print the error message string
sprintf(errmsg,errfmt,erritem);
perror(errmsg);
// Exit from program
exit(-1);
}
//______________________________________________________________
// Parser action routines
int p_unitl(code)
// Handle the length units definition request
// Returns : zero if done or (-1) on error
{
// Set the length conversion factor
switch (code) {
case 1 : lenconv=cvtlength(curflt,1,0); break; // Inch
case 2 : lenconv=cvtlength(curflt,2,0); break; // mm
case 3 : lenconv=cvtlength(curflt,3,0); break; // mil
default : return(-1); // Error
}
// Return without errors
return(0);
}
int p_unita(code)
// Handle the angle units definition request
// Returns : zero if done or (-1) on error
{
// Set the angle conversion factor
switch (code) {
case 1 : angconv=cvtangle(curflt,1,0); break; // Deg
case 2 : angconv=cvtangle(curflt,2,0); break; // Rad
default : return(-1); // Error
}
// Return without errors
return(0);
}
int p_storepart()
// Handle the store part request
// Returns : zero if done or (-1) on error
{
// Get or create the part list entry
gcpart();
// Re-init the current angle and mirror mode
cura=0.0;
curm=0;
// Return without errors
return(0);
}
int p_pname()
// Receive a part name
// Returns : zero if done or (-1) on error
{
// Store the current part name
strlower(curpn=curid);
// Return without errors
return(0);
}
int p_plname()
// Receive a physical library name
// Returns : zero if done or (-1) on error
{
// Store the current physical library name
strlower(curpln=curid);
// Return without errors
return(0);
}
int p_px()
// Receive a part X coordinate
// Returns : zero if done or (-1) on error
{
// Store the current part X coordinate
curx=curflt*lenconv+plannx;
// Return without errors
return(0);
}
int p_py()
// Receive a part Y coordinate
// Returns : zero if done or (-1) on error
{
// Store the current part Y coordinate
cury=curflt*lenconv+planny;
// Return without errors
return(0);
}
int p_pa()
// Receive a part angle
// Returns : zero if done or (-1) on error
{
// Store the current part angle
cura=curflt*angconv;
// Return without errors
return(0);
}
int p_pm()
// Receive a part mirror flag
// Returns : zero if done or (-1) on error
{
// Get and store the current part mirror flag
curm=atoi(synscanstring())==0?0:1;
// Return without errors
return(0);
}
int p_ident()
// Receive an identifier
// Returns : zero if done or (-1) on error
{
// Store the current string
curid=synscanstring();
// Return without errors
return(0);
}
int p_fltnum(negflag)
// Receive a float value
// Returns : zero if done or (-1) on error
int negflag; // Negative number flag
{
// Get the current float value
curflt=atof(synscanstring());
// Set negative on request
if (negflag)
curflt*=(-1);
// Return without errors
return(0);
}
//______________________________________________________________
// User Language program end
2.6.5 Program Caller Type and Undo Mechanism
Program Caller Type Setting
The
#pragma preprocessor statement can be used to set the caller type of the compiled
User Language program. This feature can be used to relax or restrict the compatibility of the
User Language program at compile time, no matter whether module-specific system functions and/or index variable types are refered or not. The following table lists the possible caller type specifications (see also
Appendix A.1.2).
| Caller Type |
Valid Interpreter Environment(s) |
|---|
ULCALLERSTD | all BAE program modules |
ULCALLERCAP | all Schematic Capture program modules |
ULCALLERSCM | Schematic Editor |
ULCALLERLAY | all Layout program modules |
ULCALLERGED | Layout Editor |
ULCALLERAR | Autorouter |
ULCALLERCAM | CAM Processor |
ULCALLERCV | CAM View |
ULCALLERICD | all IC Design program modules |
ULCALLERCED | Chip Editor |
The
#pragma ULCALLERSTD
preprocessor statement forces the compiled
User Language program caller type setting to standard (STD). The
Incompatible index/function reference(s)!
User Language Compiler error is suppressed.
User Language programs compiled with the above statement can be called in any
Bartels User Language Interpreter environment, even if the program code contains system functions or index variable types which are not compatible to that Interpreter environment. This allows for the implementation of programs with conditionally executed environment-specific program code. It is up to the program design to prevent from calling incompatible system functions or accessing invalid index variable types; otherwise the
Bartels User Language Interpreter quits the program with a
UL(Line): System function not available in this environment! runtime error message.
Configuring Undo Mechanism
On default, the execution of a
User Language program adds an undo step in the BAE system. The
#pragma ULCALLERNOUNDO
can be used to prevent the system from adding an undo step for the execution of the compiled program. I.e., by declaring
ULCALLERNOUNDO for programs which are not performing any operations relevant to the system's undo mechanism, it is possible to avoid redundant undo steps.
Preprocessor Statements © 1985-2025 Oliver Bartels F+E • Updated: 11 November 2009, 11:49 [UTC]
|