Piszemy kalkulator dla odwrotnej notacji polskiej (wystarczy 4 działania + modulo).
Należy zwrócić uwagę na:
1 2 -3 ++ 1 2- 3 + 1 2--3-
Po ukończeniu kalkulatora można go przerobić tak, żeby zamiast obliczać budował drzewo wyrażenia i wypisywał je w notacji infiksowej (nietrywialne: nawiasy)
Poznajemy generator analizatorów leksykalnych dla naszego języka programowania. Może to obejmować Flex (C/C++), Alex (Haskell), ewentualnie JFlex (Java) i inne generatory.
W programie napisanym na poprzednim laboratorium, analizator leksykalny zamieniamy na wygenerowany przy pomocy odpowiedniego generatora.
Omawiamy przykład analizatora stworzonego przy uzyciu Flexa, np.
%{
// #include "exp.tab.h" /* Definiuje leksemy, np. NUM */
#define NUM '9'
int yylval;
%}
%option noyywrap
%%
[0-9]+ {
yylval = atoi( yytext ) ;
return (int)NUM;
}
. { return (int)(yytext[0]); }
\n { return (int)'\n'; }
%%
int main() { int lexem; while(lexem=yylex()) { ... }
return 0;
}Uruchamianie:
flex -o lexer.c lexer.l
Wygenerowany plik (tu: lexer.c) zawiera funkcję yylex(), której kolejne wywołania dają kolejne leksemy (0 dla EOF jeśli nie ma innych wytycznych)
Omawiamy przykład, np.
%%
%class Scanner
%type Symbol
%unicode
%line
%{
StringBuffer string = new StringBuffer();
// Leksemy są klasy Symbol, zdefiniowanej poza tym plikiem
private Symbol symbol(int type) {
return new Symbol(type, yyline, -1);
}
private Symbol symbol(int type, Object value) {
return new Symbol(type, yyline, -1, value);
}
%}
WhiteSpace = (LineTerminator | [ \t\f])
DecIntLiteral = 0 | [1-9][0-9]*
%%
(0|[1-9][0-9]*) { return symbol(sym.NUM, new Integer(yytext())); }
{WhiteSpace} { }
. { System.out.println("Unexpected character:"+yytext());}Z tej specyfikacji JFlex wygeneruje klasę (tu: Scanner) zawierającą kod leksera. Konstruktor tej klasy bierze jako argument klasy java.io.Reader reprezentujący strumień wejściowy. Kolejne wywołania metody yylex() z tej klasy dają kolejne leksemy (null dla EOF w tym wypadku)
Omawiamy przykład, np.
{
module CalcLex(alexScanTokens) where
import CalcToken(Token(..))
}
%wrapper "basic"
$digit = 0-9
tokens :-
$white+ ; -- whitespace
"--".* ; -- comment
$digit+ {\s -> Int (read s)}
[\=\+\-\*\/\(\)] {\s -> Sym (head s)}Przykład uzycia:
$ alex CalcLex.x $ ghci CalcLex.hs GHCi, version 6.12.1... Ok, modules loaded: CalcLex, CalcTokens. *CalcLex> alexScanTokens "let x = 1 in x +x" [Let,Var "x",Sym '=',Int 1,In,Var "x",Sym '+',Var "x"]
Na poprzednich zajęciach pisaliśmy kalkulator dla notacji postfiksowej (ONP). Tym razem piszemy kalkulator dla notacji infiksowej (czyli "zwykłej"). W sumie przeznaczamy na ten temat dwa zajęcia: na pierwszych tworzymy tylko parser, który sprawdza poprawność wejścia, na drugich rozszerzamy go o budowę i interpretację drzewa struktury.
Wychodzimy od gramatyki
\[ E \to E + E \mid E - E \mid E * E \mid E / E \mid (E) \mid n \]
Przekształcamy ją do gramatyki jednoznacznej, a potem dla postaci LL(1)
Piszemy parser metodą zejść rekurencyjnych.
Omawiamy przykład, np. kalkulator dla ONP
%{ #define YYDEBUG 1 #include <stdio.h> double atof(const char *s); int yylex(); void yyerror(const char *s); #define YYSTYPE double %} %token NUM %% input: /* empty */ | input line ; line: '\n' | exp '\n' { printf("\t%.10g\n", $1);} ; exp: NUM { $$ = $1; } | exp exp '+' { $$ = $1 + $2; } | exp exp '-' { $$ = $1 - $2; } | exp exp '*' { $$ = $1 * $2; } | exp exp '/' { $$ = $1 / $2; } %% void yyerror(const char * s) { puts(s); } int main() { yydebug=0; /* 1 for debugging */ yyparse(); }
Omwiamy przykład, np.
import java_cup.runtime.*; terminal Integer NUM; terminal SEMI, PLUS, STAR; non terminal prog; non terminal Integer exp; prog ::= exp:e {: System.out.println("value = " + e); :} SEMI ; exp ::= NUM:n {: RESULT = n; :} | exp:e1 exp:e2 PLUS {: RESULT = new Integer(e1.intValue() + e2.intValue()); :} | exp:e1 exp:e2 STAR {: RESULT = new Integer(e1.intValue() * e2.intValue()); :} ;
Omawiamy przykład np.
{
module Main where
import Data.Char
}
%name calc
%tokentype { Token }
%error { parseError }
%token
int { TokenInt $$ }
'+' { TokenPlus }
'*' { TokenTimes }
%%
Exp : Exp Exp '+' { $1 + $2 }
| Exp Exp '*' { $1 * $2 }
| int { $1 }
;
{
parseError :: [Token] -> a
parseError _ = error "Parse error"
data Token
= TokenInt Int
| TokenPlus
| TokenTimes
deriving Show
lexer :: String -> [Token]
lexer [] = []
lexer (c:cs)
| isSpace c = lexer cs
| isDigit c = lexNum (c:cs)
lexer ('+':cs) = TokenPlus : lexer cs
lexer ('*':cs) = TokenTimes : lexer cs
lexNum cs = TokenInt (read num) : lexer rest
where (num,rest) = span isDigit cs
main = getContents >>= print . calc . lexer
}E -> E + T | T ...
Omawiamy przykład priorytetów, n.p.
%token NUM %left '-' '+' %left '*' '/' %left NEG /* leksem-widmo: minus unarny */ %right '^' /* potęgowanie */ %% exp: NUM { $$ = $1; } | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; } | exp '/' exp { $$ = $1 / $3; } | '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = pow ($1,$3);} | '(' exp ')' { $$ = $2; } ; %%
Omawiamy przykład obsługi błędów, n.p.
exp: NUM { $$ = $1; } | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; } | exp '/' exp { $$ = $1 / $3; } | '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = pow ($1,$3);} | '(' exp ')' { $$ = $2; } | '(' error ')' { printf("Error in expression\n");$$ = 0;} ;
Studenci powinni zaimplementować powyzsze przykłady w swoich programach, poza tym piszą parser dla Javalette.
Priorytety:
precedence left PLUS, MINUS; precedence left TIMES, DIVIDE, MOD; precedence left UMINUS; expr ::= MINUS expr:e {: RESULT = new Integer(0 - e.intValue()); :} %prec UMINUS
Obsługa błędów:
stmt ::= expr SEMI | while_stmt SEMI | if_stmt SEMI | ... | error SEMI ;
Tak samo jak Bison, np.
%left '+' '-'
%left '*' '/'
%left NEG
%%
Exp :
| Exp '+' Exp { Plus $1 $3 }
| Exp '-' Exp { Minus $1 $3 }
| Exp '*' Exp { Times $1 $3 }
| Exp '/' Exp { Div $1 $3 }
| '(' Exp ')' { Brack $2 }
| '-' Exp %prec NEG { Negate $2 }
| int { Int $1 }Omawiamy przykład, np.
entrypoints Exp; EAdd. Exp ::= Exp "+" Exp1 ; EMul. Exp1 ::= Exp1 "*" Exp2 ; ENum. Exp2 ::= Integer ; coercions Exp 2;
Studenci powinni
Omawiamy przykład kodu JVM, np.
.class public Hello .super java/lang/Object ; standard initializer .method public <init>()V aload_0 invokespecial java/lang/Object/<init>()V return .end method .method public static main([Ljava/lang/String;)V .limit stack 2 getstatic java/lang/System/out Ljava/io/PrintStream; ldc "Hello" invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V return .end method
Kompilujemy prostą klasę np.
public class Inc { public static void main(String[] args) { System.out.println(test1(41)); } static int test1(int i) { return i+1; } }
Następnie analizujemy kod przy pomocy javap -c, po czym zapisujemy go w formacie asemblera Jasmin.
Rozszerzyć kalkulator z poprzednich laboratoriów o generowanie kodu Jasmin.
Low Level Virtual Machine, http://llvm.org/
%t2 = add i32 %t0, %t1
%t5 = add double %t4, %t3 store i32 %t2, i32* %loc_r
declare void @printInt(i32) ; w innym module
define i32 @main() {
%i1 = add i32 2, 2
call void @printInt(i32 %i1)
ret i32 0
}Narzędzia dla LLVM są w katalogu PUBLIC/MRJP/Llvm (w tym pliki runtime.{ll,bc}
$ llvm-as t2.ll $ llvm-link -o out.bc t2.bc runtime.bc $ lli out.bc 4
define i32 @fact(i32 %n) {
%c0 = icmp eq i32 %n, 0
br i1 %c0, label %L0, label %L1
L0:
ret i32 1
L1:
%i1 = sub i32 %n, 1
%i2 = call i32 @fact(i32 %i1)
%i3 = mul i32 %n, %i2
ret i32 %i3
}Uwaga:
declare void @printInt(i32)
define i32 @main() {
entry:
%i1=call i32 @fact(i32 5)
call void @printInt(i32 %i1)
ret i32 0
}
; r = 1
; i = n
; while (i > 1):
; r = r * i
; i = i -1
; return r
;;
define i32 @fact(i32 %n) {
entry:
; local variables:
%loc_r = alloca i32
%loc_i = alloca i32
; r = 1
store i32 1, i32* %loc_r
; i = n
store i32 %n, i32* %loc_i
br label %L1
; while i > 1:
L1:
%tmp_i1 = load i32* %loc_i
%c0 = icmp sle i32 %tmp_i1, 1
br i1 %c0, label %L3, label %L2
; loop body
L2:
; r = r * i
%tmp_i2 = load i32* %loc_r
%tmp_i3 = load i32* %loc_i
%tmp_i4 = mul i32 %tmp_i2, %tmp_i3
store i32 %tmp_i4, i32* %loc_r
; i = i-1
%tmp_i5 = load i32* %loc_i
%tmp_i6 = sub i32 %tmp_i5, 1
store i32 %tmp_i6, i32* %loc_i
br label %L1
L3:
%tmp_i8 = load i32* %loc_r
ret i32 %tmp_i8declare void @printInt(i32)
define i32 @main() {
entry:
%i1=call i32 @fact(i32 5)
call void @printInt(i32 %i1)
ret i32 0
}
; r = 1
; i = n
; while (i > 1):
; r = r * i
; i = i -1
define i32 @fact(i32 %n) {
entry: br label %L1
L1:
%i.1 = phi i32 [%n, %entry], [%i.2, %L2]
%r.1 = phi i32 [1, %entry], [%r.2, %L2]
%c0 = icmp sle i32 %i.1, 1
br i1 %c0, label %L3, label %L2
L2:
%r.2 = mul i32 %r.1, %i.1
%i.2 = sub i32 %i.1, 1
br label %L1
L3:
ret i32 %r.1
}Rozszerz kalkulator o generowanie kodu dla LLVM
@hellostr = internal constant [6 x i8] c"Hello\00"
declare void @printString(i8*)
define i32 @main() {
entry:
%t0 = bitcast [6 x i8]* @hellostr to i8* ; można też uzyć getelementptr
call void @printString(i8* %t0)
ret i32 0
}Kompilujemy i uruchamiamy programy napisane na ćwiczeniach.
Najprostsza metoda zbudowania programu zawierającego funkcję w asemblerze:
gcc -o f f.s main.c
gdzie f.s jest plikiem zawierającym kod funkcji w asemblerze
main.c plik zwierający program główny w C, np.
Uwaga: przy uruchamianiu na komputerze 32-bitowym potrzebna jeszcze będzie funkcja dostosowująca protokół wywołania i386 do omawianego na ćwiczeniach, np.
.globl fad fad: push %ebp mov %esp,%ebp // In 32 bit ABI %edi and %esi are callee-save push %edi push %esi mov 8(%ebp), %edi mov 12(%ebp), %esi mov 16(%ebp), %edx mov 20(%ebp), %ecx call f pop %esi pop %edi pop %ebp ret
Write a calculator for the Reverse Polish Notation (aka postfix notation).
Pay careful attention to:
1 2 -3 ++ 1 2- 3 + 1 2--3-
Learn a lexer generator for your programming language. This can include Flex (C/C++), Alex (Haskell), possibly also JFlex (Java) and other generators.
In the program written in the previous session, replace the handwritten lexer by a generated one.
Discuss an example of a Flex analyser, e.g.
%{
#include "exp.tab.h" /* Defines lexems, like NUM etc. */
%}
%option noyywrap
%%
[[:digit:]]+ {
yylval = atoi( yytext ) ;
return (int)NUM;
}
. { return (int)(yytext[0]); }
\n { return (int)'\n'; }
%%Running Flex:
flex -o lexer.c lexer.l
The generated file will contain a function yylex() which will return one lexem at a time.
Discuss an example, e.g.
%class Scanner
%unicode
%cup
%line
%{
StringBuffer string = new StringBuffer();
// Leksemy są klasy Symbol, zdefiniowanej poza tym plikiem
private Symbol symbol(int type) {
return new Symbol(type, yyline, -1);
}
private Symbol symbol(int type, Object value) {
return new Symbol(type, yyline, -1, value);
}
%}
WhiteSpace = (LineTerminator | [ \t\f])
DecIntLiteral = 0 | [1-9][0-9]*
%%
(0|[1-9][0-9]*) { return symbol(sym.NUM, new Integer(yytext())); }
[\r\n\t\f ] { }
. { System.out.println("Unexpected character:"+yytext());}From this specification JFlex generates a .java file with one class (for the above example: Scanner) that contains code for the scanner. The class will have a constructor taking a java.io.Reader from which the input is read. The class will also have a function yylex() returning the subsequent lexeme each time it is called.
Discuss an example, e.g.
{
module CalcLex(alexScanTokens) where
import CalcToken(Token(..))
}
%wrapper "basic"
$digit = 0-9
$alpha = [a-zA-Z]
tokens :-
$white+ ; -- whitespace
"--".* ; -- comment
let { \s -> Let }
in { \s -> In }
$digit+ {\s -> Int (read s)}
[\=\+\-\*\/\(\)] {\s -> Sym (head s)}
$alpha [$alpha $digit \_ \']* { \s -> Var s }Running Alex:
ben@sowa$ alex CalcLex.x ben@sowa$ ghci CalcLex.hs GHCi, version 6.12.1... Ok, modules loaded: CalcLex, CalcTokens. *CalcLex> alexScanTokens "let x = 1 in x +x" Loading package array-0.3.0.0 ... linking ... done. [Let,Var "x",Sym '=',Int 1,In,Var "x",Sym '+',Var "x"]
The previous lab involved writing a calculator for the postfix notation (RPN). This time we wrtie a calculator for the infix (i.e. ordinary) notation.
In the first sessions we write a correctness-checking parser, then extend it to build and iterpret a parse tree.
Starting with the expression grammar
\[ E \to E + E \mid E - E \mid E * E \mid E / E \mid (E) \mid n \]
Transform it first to unambiguous form, then to LL(1).
Then write a recursive descent parser.
Discuss an example, e.g. RPN calculator
%{ #define YYDEBUG 1 #include <stdio.h> double atof(const char *s); int yylex(); void yyerror(const char *s); #define YYSTYPE double %} %token NUM %% input: /* empty */ | input line ; line: '\n' | exp '\n' { printf("\t%.10g\n", $1);} ; exp: NUM { $$ = $1; } | exp exp '+' { $$ = $1 + $2; } | exp exp '-' { $$ = $1 - $2; } | exp exp '*' { $$ = $1 * $2; } | exp exp '/' { $$ = $1 / $2; } %% void yyerror(const char * s) { puts(s); } int main() { yydebug=0; /* 1 for debugging */ yyparse(); }
Discuss an example, e.g.
import java_cup.runtime.*; terminal Integer NUM; terminal SEMI, PLUS, STAR; non terminal prog; non terminal Integer exp; prog ::= exp:e {: System.out.println("value = " + e); :} SEMI ; exp ::= NUM:n {: RESULT = n; :} | exp:e1 exp:e2 PLUS {: RESULT = new Integer(e1.intValue() + e2.intValue()); :} | exp:e1 exp:e2 STAR {: RESULT = new Integer(e1.intValue() * e2.intValue()); :} ;
Discuss an example, e.g.
{
module Main where
import Data.Char
}
%name calc
%tokentype { Token }
%error { parseError }
%token
int { TokenInt $$ }
'+' { TokenPlus }
'*' { TokenTimes }
%%
Exp : Exp Exp '+' { $1 + $2 }
| Exp Exp '*' { $1 * $2 }
| int { $1 }
;
{
parseError :: [Token] -> a
parseError _ = error "Parse error"
data Token
= TokenInt Int
| TokenPlus
| TokenTimes
deriving Show
lexer :: String -> [Token]
lexer [] = []
lexer (c:cs)
| isSpace c = lexer cs
| isDigit c = lexNum (c:cs)
lexer ('+':cs) = TokenPlus : lexer cs
lexer ('*':cs) = TokenTimes : lexer cs
lexNum cs = TokenInt (read num) : lexer rest
where (num,rest) = span isDigit cs
main = getContents >>= print . calc . lexer
}E -> E + T | T ...
Discuss an example of handling operator priorities e.g.
%token NUM %left '-' '+' %left '*' '/' %left NEG /* phantom lexeme: unary minus */ %right '^' /* exponentiation */ %% exp: NUM { $$ = $1; } | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; } | exp '/' exp { $$ = $1 / $3; } | '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = pow ($1,$3);} | '(' exp ')' { $$ = $2; } ; %%
Discuss an example of error handling, e.g.
exp: NUM { $$ = $1; } | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; } | exp '/' exp { $$ = $1 / $3; } | '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = pow ($1,$3);} | '(' exp ')' { $$ = $2; } | '(' error ')' { printf("Error in expression\n");$$ = 0;} ;
The students should implement above examples in their code; apart from that thay write their Latte parsers.
Priorities:
precedence left PLUS, MINUS; precedence left TIMES, DIVIDE, MOD; precedence left UMINUS; expr ::= MINUS expr:e {: RESULT = new Integer(0 - e.intValue()); :} %prec UMINUS
Error handling:
stmt ::= expr SEMI | while_stmt SEMI | if_stmt SEMI | ... | error SEMI ;
Similar to Bison, e.g.
%left '+' '-'
%left '*' '/'
%left NEG
%%
Exp :
| Exp '+' Exp { Plus $1 $3 }
| Exp '-' Exp { Minus $1 $3 }
| Exp '*' Exp { Times $1 $3 }
| Exp '/' Exp { Div $1 $3 }
| '(' Exp ')' { Brack $2 }
| '-' Exp %prec NEG { Negate $2 }
| int { Int $1 }Discuss an example, e.g.
entrypoints Exp; EAdd. Exp ::= Exp "+" Exp1 ; EMul. Exp1 ::= Exp1 "*" Exp2 ; ENum. Exp2 ::= Integer ; coercions Exp 2;
Students should