Sign in Join
Wiki copytemporary

Plex and Pyacc: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11Free Pascal comes with substitutes for the GNU projects Lex and YACC. They are called Plex and Pyacc and they can be used to generate compilers and regular expression analyzers in Pascal instead of C.
22
33## Library contents
44
55TP Lex and Yacc can be found here: <http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/utils/tply/>
66
77## Documentation
88
99Download the manual here: <http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/utils/tply/tply.doc?revision=1>
1010
1111## Simple example application
1212
1313This is a very simple calculator, which shows how to use plex and pyacc. The files in this simple project are:
1414
1515- build.sh
1616- lexer.l
1717- parser.y
1818
1919WARNING: The code below compiles but currently doesn't work **\<-- Because it is simply wrong. See below and learn why !**
2020
2121### build.sh
2222
2323``` pascal
2424plex lexer.l
2525pyacc -d parser.y
2626mv parser.pas calculadora.pas
2727fpc calculadora.pas
2828```
2929
3030### lexer.l
3131
3232``` pascal
3333{ Analisador léxico da calculadora para a disciplina de PCS
3434
3535 Aluno: Felipe Monteiro de Carvalho }
3636
3737%{
3838%}
3939%%
4040
4141[0-9]+[dD]? begin yylval.yyInteger := StrToInt(yytext); yyruleno := NUMBER; end;
4242
4343[0-9a-fA-F]+[hH] begin yylval.yyInteger := StrToInt(yytext); yyruleno := NUMBER; end;
4444
4545[ \t] begin end; { ignorar espaços em branco }
4646
4747[-] begin yyruleno := MENOS; end;
4848
4949[+*/()] begin yyruleno := Integer(yytext[0]); end;
5050
5151[=][hH] begin yyruleno := IGUALH; end;
5252
5353[=][dD]? begin yyruleno := IGUALD; end;
5454
5555\n begin yyruleno := Integer(yytext[0]); end;
5656
5757. begin yyerror('Caracter inexperado'); end;
5858
5959%%
6060function meuyywrap (): Integer;
6161begin
6262 Result := 1;
6363end;
6464```
6565
6666### parser.y
6767
6868``` pascal
6969/* Parser
7070
7171 Aluno: Felipe Monteiro de Carvalho */
7272%{
7373program calculadora;
7474
7575{$mode delphi}
7676
7777uses SysUtils, yacclib, lexlib;
7878
7979%}
8080
8181%start entrada
8282%token <Integer> NUMBER IGUALD IGUALH MENOS
8383%type <Integer> expressao termo fator
8484
8585%%
8686
8787entrada
8888 : /* linha vazia */ { }
8989 | entrada linha { }
9090 ;
9191linha
9292 : expressao IGUALD '\n' { WriteLn(Format('Resultado: %d', [$1])); }
9393 | expressao IGUALH '\n' { WriteLn(Format('Resultado: %H', [$1])); }
9494 ;
9595expressao
9696 : expressao '+' termo { $$ := $1 + $3; }
9797 | expressao MENOS termo { $$ := $1 - $3; }
9898 | termo { $$ := $1; }
9999 ;
100100termo
101101 : termo '*' fator { $$ := $1 * $3; }
102102 | termo '/' fator { if ($3 = 0) then
103103 yyerror('Divisao por zero!')
104104 else
105105 $$ := $1 div $3; }
106106 | fator { $$ := $1; }
107107 ;
108108fator
109109 : NUMBER { $$ := $1; }
110110 | MENOS NUMBER { $$ := -1 * $2; }
111111 | '(' expressao ')' { $$ := $2; }
112112 ;
113113
114114%%
115115
116116procedure meuyyerror(s: PChar); // Called by yyparse on error
117117begin
118118 WriteLn(Format('Erro: %s', [s]));
119119end;
120120
121121{$include lexer.pas}
122122
123123begin
124124 yywrap := @meuyywrap;
125125// yyerror := @meuyyerror;
126126 yyparse ();
127127end.
128128```
129129
130130### How to use it
131131
132132Type for example: 5+3=
133133
134134And it will answer with: 8
135135
136136You can also try 3*8=H to request an answer in Hexadecimal instead of decimal
137137
138138# How to do it the right way
139139
140140**Sorry that I have to do this, but this example is wrong !**
141141
142142First of all I recommend you to read the manual and try to understand it.\
143143Secondly take a look at the examples provided with original tply package by Albert Graef distributed at: <http://www.musikwissenschaft.uni-mainz.de/~ag/tply>\
144144There you will find a more sophisticated calculator example named 'expr'. If you take a look at it you will find out that this is a basic example for Lex/Yacc generated parsers and it works.\
145145
146146**`Now I will explain you why you can NOT do things that way !`**
147147
148-<u>We start with Lex:</u>\
148+We start with Lex:\
149149\# Lex returns a token to the parser which is always an integer value. The token is defined as an constant value with the **%token** command. Tokens can only be returned to Yacc through two commands, **return** and **returnc**.
150150
1511511. 1. **return** : This is the normal return of a token = integer
152152 2. **returnc**: With this it is possible to return one character, it is needed for the literals in Yacc rules
153153
154154Now we correct the return of a token from example above:
155155
156156``` pascal
157157[0-9]+[dD]? begin yylval.yyInteger := StrToInt(yytext); yyruleno := NUMBER; end; <- nothing, because yyruleno ends up in nirvana
158158[0-9]+[dD]? begin yylval.yyInteger := StrToInt(yytext); return(NUMBER); end; <- good
159159```
160160
161161And the litteral return :
162162
163163``` pascal
164164[+*/()] begin yyruleno := Integer(yytext[0]); end; <- ouch !
165165[+*/()] returnc(yytext[1]); <- returns single characters we want
166166```
167167
168168Can you see the difference ?\
169169Why the hell one should use index NULL ? And no need for begins and ends.\
170170Last but not least the strange function:
171171
172172``` pascal
173173function meuyywrap (): Integer; <- ??? what is this for ??? this no overload for yywrap !
174174begin
175175 Result := 1;
176176end;
177177```
178178
179179The yywrap function is for loading multiple files, do we need this if we read from stdin and write to stdout ? No, we don't !\
180-\
180+
181181The working Lex file:
182182
183183``` pascal
184184{ Analisador léxico da calculadora para a disciplina de PCS
185185
186186 Aluno: Felipe Monteiro de Carvalho }
187187
188188%{
189189%}
190190%%
191191
192192[0-9]+[dD]? begin yylval.yyInteger := StrToInt(yytext); return(NUMBER); end;
193193
194194[0-9a-fA-F]+[hH] begin yylval.yyInteger := StrToInt(yytext); return(NUMBER); end;
195195
196196[ \t] ; { ignorar espaços em branco }
197197
198198[-] return(MENOS);
199199
200200[+*/()] returnc(yytext[1]);
201201
202202[=][hH] return(IGUALH);
203203
204204[=][dD]? return(IGUALD);
205205
206206\n returnc(yytext[1]);
207207
208208. yyerror('Caracter inexperado');
209209
210210%%
211211```
212212
213-\
214-<u>Now we go on with Yacc:</u>\
213+Now we go on with Yacc:\
215214The main block:\
216215If we want to read a file we have to assign and open it before we can parse it. If we want to read more files we have to use yywrap.\
217216With a parser like this we need only stdio. So we need to do nothing because Lex assigns and opens the 'files' known as stdin and stdout.\
218217With this we can forget all but the yyparse() call.\
219-\
218+
220219Additional functionality:\
221220As a feature we want to write user defined error messages. For this we simply overload the yyerror procedure.\
222-\
221+
223222The working Yacc file:
224223
225224``` pascal
226225/* Parser
227226
228227 Aluno: Felipe Monteiro de Carvalho */
229228%{
230229program calculadora;
231230
232231{$mode delphi}
233232
234233uses SysUtils, yacclib, lexlib;
235234
236235procedure yyerror(s: string); // Called by yyparse on error <- overload !
237236begin
238237 WriteLn(Format('Erro: %s', [s]));
239238end;
240239
241240%}
242241
243242%start entrada
244243%token <Integer> NUMBER IGUALD IGUALH MENOS
245244%type <Integer> expressao termo fator
246245
247246%%
248247
249248entrada
250249 : /* linha vazia */ { }
251250 | entrada linha { }
252251 | entrada linha '\n' { writeln('DONE.'); yyaccept; } /* <- nice end with RETURN */
253252 ;
254253linha
255254 : expressao IGUALD '\n' { WriteLn(Format('Resultado: %d', [$1])); }
256255 | expressao IGUALH '\n' { WriteLn(Format('Resultado: %Xh', [$1])); } /* <- hex = X , add 'h' to identify this result as a hex number */
257256 ;
258257expressao
259258 : expressao '+' termo { $$ := $1 + $3; }
260259 | expressao MENOS termo { $$ := $1 - $3; }
261260 | termo { $$ := $1; }
262261 ;
263262termo
264263 : termo '*' fator { $$ := $1 * $3; }
265264 | termo '/' fator { if ($3 = 0) then
266265 yyerror('Divisao por zero!')
267266 else
268267 $$ := $1 div $3; }
269268 | fator { $$ := $1; }
270269 ;
271270fator
272271 : NUMBER { $$ := $1; }
273272 | MENOS NUMBER { $$ := -1 * $2; }
274273 | '(' expressao ')' { $$ := $2; }
275274 ;
276275
277276%%
278277
279278
280279{$include lexer.pas}
281280
282281begin
283282 yyparse ();
284283end.
285284```
286285
287286Hint: One does not need to type tokens as done in this example as the default yylval is always integer. If you do type tokens pyacc will generate a corresponding YYStype for you.\
288287**WOW. The calculator works as expected !**\
289288GVS 00:09, 9 March 2014 (CET)\
290289=Additional Information= If you are a beginner you may have more questions than answers after reading the above. Thus I will try to show you things to make live easier.\
291290We start with Lex and its regular expressions. I don't explain how to build them, read the documentation to find this out.\
292291I will try explain some implementation details.\
293-\
292+
294293A lexer can use multiple rules that start with the same character or set of characters. This is not recommended because it can cause a problem. The main problem is that the lexer uses the rule with the longest match and returns that token. Now if you return different tokens with these rules you may not be able to match the yacc rule you expected. This is the common error if you write wrong lex rules and wonder why yacc returns syntax errors. If you use such rules in a way they can be identified correctly this can be a benefit. But with such rules you should take care if you need the 'loop' as in (a\|b)* which can consist of a unknown number of 'a' or 'b' or even be empty. This is not a good start or end for your rule if the 'constant' characters do not cause a significant match.\
295294One common use is the detection of numbers where you have two rules, one for integers and one for reals. The rule for integer is then the beginning of the rule for real and there is no problem. There is also no problem if these rules return the same token. Then the only question is if you can find a better rule that includes both single rules in one rule.\
296295Example: Integer and Real numbers without sign
297296
298297``` pascal
299298[0-9]+ ReturnVerb(INUM,1)
300299'.'[0-9]+ ReturnVerb(RNUM,2)
301300[0-9]+"."[0-9]+ ReturnVerb(RNUM,3)
302301```
303302
304303Now you ask: Why should I parse numbers without sign ? This is a yacc problem, because the sign can be in an expression as a operator e.g. 'a+b' or as a prefix sign e.g. '-1'. Then you may have a token defined for MINUS and you don't need to know if the *sign* is a prefix sign or an operator, the yacc rules will handle it.\
305304Taking a look to the code you will see that I returned the token via the 'ReturnVerb' procedure. This is for debugging the lexer to see the token at the time it is matched. The procedure writes the token number and rule index and then calls the normal return procedure. This is useful because I see the token and the text representation of the matching rule. The text can be read from yytext which is always the current match.\
306305One issue you should keep in mind: The lexer is a function that is called from the parser and the token is returned at the point of time the function exits. The parser uses yylval only if he needs this value and one can not find out at which time this will be. The only valid way to return a value to your parser is to use yylval AND to set this value right before the call of return or returnc, otherwise the value is lost or you run into trouble. For details see below at the returnc explanation.\
307-\
306+
308307You can combine rules with predefined rule parts standing before the rule part in your file. This makes it easier to write longer rules and you can use them in multiple rules.\
309308Example: Rules consisting of predefined parts
310309
311310``` pascal
312311L [A-Za-z]
313312D [0-9]
314313
315314%%
316315
317316{D}+(\.{D}+)?([Ee][+-]?{D}+)? return(RNUM);
318317
319318{L} return(CH);
320319
321320" " ;
322321
323322. |
324323\n returnc(yytext[1]);
325324```
326325
327326One important thing you can see in this example is that one always has to catch all characters of input. Even if you don't need them like the SPACE. Typically in programming languages the so called WHITE SPACE is ignored at all. This is the most difficult thing to match all the unused characters to empty rules without breaking the rules for tokens (difficult because they do not appear in your output where wrong parsed and returned tokens can be found, things you don't see are hard to find). Empty rules do not call return or returnc. If you can not find a rule you can use built in functions to to read the characters yourself or write additional characters to input. This is commonly used with comments. After a initial match of a short rule to match the start of a comment the input is read until the end of the comment or EOF is found.\
328327Example: Line Comment
329328
330329`%{`\
331330`procedure CommentEOL;`\
332331` var c:char;`\
333332`begin`\
334333`  c:= get_char;`\
335334`  while (c <> #0)and(c <> #10) do  c:= get_char;`\
336335`end; `\
337336`%}`\
338337`"//"         CommentEOL;`
339338
340339With this all the characters the get_char function reads from input are lost. If one is reading a multi line comment the ending maker may have to be written back. This is done with unget_char. This characters can than be read in next call of yylex to be evaluated.\
341340Example: A language with '/' as an ID, '//' as a unused sequence until EOL or \#26
342341
343342``` pascal
344343"/" begin
345344 c := get_char;
346345 if c <> '/' then
347346 begin
348347 unget_char(c);
349348 ReturnC(yytext[1]);
350349 end
351350 else
352351 begin (* line comment *)
353352 while ((c<>#10)and(c<>#13)and(c<>#26)) do c := get_char;
354353 if c=#26 then unget_char(c);
355354 end;
356355 end;
357356```
358357
359-\
360358Lets talk about returnc.\
361359As I said above (at the correction part) that tokens are always integer but returnc gets an character as an argument you might say that is not true. But it is, returnc uses ORD to convert the character to integer. Now you will understand why your token constants (have you look at yacc generated pasacl code) are always higher than 256; its the full ASCII range for litteral tokens so other tokens are higher.\
362360If you use returnc you can deal with literals in yacc rules but you have to make sure to return ALL the characters you use in yacc. Yacc does not care about not returned characters and your rules will never match.\
363-\
361+
364362The short way to debug lex is to overload the return procedures.\
365363Example: Overload lex return
366364
367365``` pascal
368366procedure return ( n : Integer );
369367begin
370368 writeln('return >',yytext,'< = ',n);
371369 (* now do the 'inherited' code return would do *)
372370 yyretval := n;
373371 yydone := true;
374372end(*return*);
375373
376374procedure returnc ( c : Char );
377375begin
378376 writeln('returnc >',yytext,'< c = ',c,' , n = ',ord(c));
379377 (* now do the 'inherited' code returnc would do *)
380378 yyretval := ord(c);
381379 yydone := true;
382380end(*returnc*);
383381```
384382
385383The result of this is shown next for calculating 4*5 as a hex value with the calculator above:
386384
387385``` pascal
3883864*5=h
389387return >4< = 257
390388returnc >*< c = * , n = 42
391389return >5< = 257
392390return >=h< = 259
393391returnc >
394392< c =
395393 , n = 10
396394Resultado: 14h
397395```
398396
399397Remark: You see the NEWLINE from input causing the same on output (n = 10).\
400-\
398+
401399Now its time for Yacc.\
402-\
400+
403401Benefits of literals in rules are that you can use them with less trouble. Another thing is that the rules are more readable. The trouble with non literal tokens is that you easily generate conflicts in yacc rules. This is a problem with tokens that appear very often in your rules and thus the parser has to do different things with them. It is always a good idea to write rules in such a way that their beginning is significant. If you can not write such rules you might have to change your lexer.\
404402Using EMPTY with rules cause trouble too if you use them in combination with loops. In loops you can have only one rule that produces empty. The other rules have to have a significant beginning/ending or part (this needs to be unique in this loop of rules). It is also a problem if you have different rules that end with the same token and the loop separator is this token. Then the parser can not decide what to do if this token is found. Typically the use of SEMICOLON can cause this in a pascal parser (semicolons are not always allowed or close different rules that are not significant itself).\
405403Example: FPC Parser snippet to parse CLASS\
406404
407405``` pascal
408406Class_Typed_Type : _CLASSFORWARD /* 'Class ;' */
409407 | LClass_PRE _OF { LDisable(co_Class); } id
410408/* Class Type */
411409 | LClass_PRE Class_Component_List { LDisable(co_Class); }
412410 | LClass_PRE '(' Class_Implemented_Interface ')'
413411 Class_Component_List { LDisable(co_Class); }
414412 ;
415413LClass_PRE : _CLASS { LEnable(co_Class); } ;
416414Class_Implemented_Interface :
417415 id
418416 | id ',' Class_Interface_Identifiers
419417 ;
420418Class_Interface_Identifiers : Y_Id_Comma_List ;
421419Class_Component_List : OPT_Class_Components _END ;
422420OPT_Class_Components :
423421 /* EMPTY */
424422 | Class_Component_LOOP
425423 ;
426424Class_Component_LOOP : /* LOOP */
427425 Class_Component
428426 | Class_Component_LOOP Class_Component
429427 ;
430428Class_Component :
431429 Class_Visibility_Specifier
432430 | Class_Field_Definition
433431 | Class_Method_Definition { LClassModifiers(false); }
434432 | Class_Property_Definition
435433 ;
436434Class_Visibility_Specifier : /* 'private' | 'public' | 'published' | 'protected' | 'strict' 'private' ; */
437435 CVI_PRIVATE
438436 | CVI_PROTECTED
439437 | NKW_PUBLIC
440438 | CVI_PUBLISHED
441439 | CVI_STRICT CVI_PRIVATE
442440 ;
443441
444442Class_Field_Definition : /* all decl starting with id as a field */
445443 /* Const
446444 type */
447445 Class_Variable_Declaration
448446 | Class_ClassVariable_Declaration
449447 ;
450448Class_Variable_Declaration :
451449 Y_Id_Comma_List ':' { LEnable(co_ClassVarStatic); LHints(true); } RClass_Variable_Declaration ;
452450RClass_Variable_Declaration :
453451 Result_Type OPT_Hint ';' OPT_Static { LDisable(co_ClassVarStatic); LHints(false); }
454452 ;
455453OPT_Static :
456454 /* EMTY */
457455 | NKW_STATIC ';'
458456 ;
459457Class_ClassVariable_Declaration : _CLASS Class_Variable_Declaration ;
460458Class_Method_Definition :
461459 Class_PuF_Definition
462460 | Class_CuD_Definition
463461 | _CLASS Class_PuF_Definition
464462 ;
465463
466464Class_PuF_Definition : PuF_Definition ;
467465Class_CuD_Definition :
468466 _CONSTRUCTOR PLike_Definition
469467 | _DESTRUCTOR PLike_Definition
470468 ;
471469```
472470
473471This are context sensitive rules to switch between the different cases CLASS can be used in a class type declaration. The LEnable and LDisable switches the lexer to a different mode. To do this the line 'LClass_PRE : _CLASS { LEnable(co_Class); } ;' is a own rule that has to be completed first to give the lexer time to switch the modes (the next lookahead character of the parser triggers the rule end; the parser has to reduce the rule to get to the next state before the next token is evaluated which we want to switch to a different token).\
474472Remark: This example parser switches between different sets of KEYWORDS because not all tokens that are used are real RESERVED words of the language. This means that e.g. 'public' is not a keyword and is only enabled on context as 'NKW_PUBLIC'. The '_CLASSFORWARD' is a typical conflict where the string 'Class ;' terminates with SEMICOLON. Thus the lexer does a lookahead if a semicolon is found behind 'Class' and then returns the token '_CLASSFORWARD' instead of '_CLASS' and the problem is solved.\
475-\
473+
476474The rule in between pitfall:\
477475Example: Hello in between
478476
479477``` pascal
480478rule1 : ID rule2 { writeln($2); }
481479
482480rule2 : ID1 { $$:='Hello'; } ID3 { $$:= $2; }
483481```
484482
485483'Hello' can only be assigned with the last statement '{ \$\$:= \$2; }' as yacc generates a new state for the rule in between. The assignment must be at the end of this rule.\
486-\
484+
487485Additional explanation to this last two examples:\
488486This are different cases ! The first example is for switching the lexer and the second example is for parsers internal value handling to return the right value to the next rule.\
489-\
490487
491488# Useful Links
492489
493490[[Make your own compiler, interpreter, parser, or expression analyzer]]
494491
495492GVS 04:48, 9 March 2014 (CET)
496493
497494---
498495*From the Free Pascal wiki, page "Plex and Pyacc", as it stood on 2020-02-23 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
499496Category: Code
500497Category: Compiler design
501498Category: Utilities

Back to history