Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • tddd55/tddd55-lab
  • yunli455/tddd55-lab
  • dimpi105/tddd55-lab
  • aleen361/tddd55-lab
4 results
Show changes
Commits on Source (13)
*.o
lab1/scanner
lab1/scanner.cc
lab3-4/scanner.cc
lab3-4/parser.cc
lab3-4/parser.hh
lab3-4/compiler
\ No newline at end of file
......@@ -41,7 +41,6 @@ release = ''
extensions = [
'sphinx.ext.imgmath',
'sphinx.ext.todo',
'sphinxcontrib.bibtex',
'breathe',
# Currently, graphviz has no option to deal with large figures
'modifiedgraphviz',
......
......@@ -132,3 +132,34 @@ The file ``main.cc`` contains a sample main program.
You may have to modify it depending on how you choose to report errors from your parser.
If the scanner encounters an error it will throw an object of type :class`ScannerError`.
Your main program should catch this exception (the sample main program does), print an error message (you can print a :class:`ScannerError` object using stream operators) and then perform error recovery.
Testing your solution
---------------------
A test script is available called test.py with an auxilary file called test.txt.
You may test your solution by using the following command::
python3 test.py test.txt
The output of running this test program should be something like this::
=============================================================================
| 0: 1+2*3^-4 | Expected: 1.02469 | Got: 1.02469 | PASS |
| 1: 1+2/3 | Expected: 1.66667 | Got: 1.66667 | PASS |
| 2: (1+2)/3 | Expected: 1 | Got: 1 | PASS |
| 3: 2*3+5 | Expected: 11 | Got: 11 | PASS |
| 4: 2*(3+5) | Expected: 16 | Got: 16 | PASS |
| 5: 2^2^3 | Expected: 256 | Got: 256 | PASS |
| 6: 2*2^2*2 | Expected: 16 | Got: 16 | PASS |
| 7: (2*2)^(2*2) | Expected: 256 | Got: 256 | PASS |
| 8: 2^2*10 | Expected: 40 | Got: 40 | PASS |
| 9: 1--1 | Expected: 2 | Got: 2 | PASS |
| 10: 1*2^2+3/4^-1 | Expected: 16 | Got: 16 | PASS |
| 11: (-4*-4+4^2) | Expected: 32 | Got: 32 | PASS |
| 12: ((-4*-4+4^2)-32+1) | Expected: 1 | Got: 1 | PASS |
| 13: -(-1--(-(-10--100))) | Expected: 91 | Got: 91 | PASS |
| 14: e | Expected: 2.71828 | Got: 2.71828 | PASS |
| 15: pi | Expected: 3.14159 | Got: 3.14159 | PASS |
| 16: e^pi | Expected: 23.1407 | Got: 23.1407 | PASS |
| 17: log10(10) | Expected: 1 | Got: 1 | PASS |
| 18: sin(pi)+cos(pi) | Expected: -1 | Got: -1 | PASS |
| 19: log(e^e) | Expected: 2.71828 | Got: 2.71828 | PASS |
| 20: sin(pi/2)+cos(0)+tan(pi/4) | Expected: 3 | Got: 3 | PASS |
=============================================================================
......@@ -42,3 +42,26 @@ Hand in the following
Demonstrate your solution to your lab assistant during a laboratory session.
Send an e-mail (one e-mail per group) with your modified code to the same assistant, put TDDD55, assignment number and your LiU logins in the e-mail subject line.
Getting Started
---------------
In this lab you will be working with a quite large codebase.
Documentation concerning this codebase is available under skeleton at the left side in the tab tab "The Skeleton".
It is recommended that you start by implementing the different rules for expressions.
A test program is available at `./test/expression_test.prog`.
However, it might be useful to write your own program and test simple expressions on a step by step basis.
To run the compiler on your own program, named for instance myProgram.prog execute::
./compiler ./test/myProgram.prog
If you want to run the program with debugging information you may run::
./compiler -d ./test/myProgram.prog
See the different files in the test folders for examples on how to write programs in this language.
Troubleshooting
---------------
Make sure that you have checked all places in parser.y with the phrase "Your Code Here".
Once you have done so you can execute the following sequence of commands to check your compiler::
./compiler ./test/test.prog > output.txt
diff -y ./output.txt ../traces/trace-lab3.txt
Using this commands you will get a side by side comparision of your own output and a trace.
Take note that the memory addresses might differ for some entries, this is ok.
......@@ -26,3 +26,15 @@ Questions
For example, assigning a constant to a variable causes two quads to be generated, where one would have been enough.
There are a number of other situations where equally bad code is generated.
Suggest at least one way of eliminating most of the bad code that is generated.
Testing your solution
---------------------
To test your solution you may run the code-test.sh script located in the lab3-4 folder.
You can run this script using the following command::
bash ./codegen-test.sh
However, it might be wise to employ a step by step strategy.
Hence, similar to Lab III you may write your own test program and test it as you add new features to the backend.
Assuming you have written your own program you may test this program by invoking the compiler in the following way::
./compiler -c ./test/myProgram.prog > myProgram.c
gcc -g -o myProgram myProgram.c -lm
./myProgram
scanner.o: scanner.cc scanner.h
main.o: main.cc scanner.h
......@@ -10,7 +10,7 @@ using namespace std;
typedef struct
{
int token;
char *name;
const char *name;
} tTokenName;
......@@ -75,7 +75,7 @@ int main(int argc, char **argv)
int token;
extern FILE *yyin;
extern int yylex();
/*
* Open the input file, if any
*/
......@@ -92,7 +92,7 @@ int main(int argc, char **argv)
perror(argv[1]);
exit(1);
}
break;
break;
default:
cerr << "Usage: " << argv[0] << " [ filename ]\n";
exit(1);
......
#define FUNCTION 258
#define ID 259
#define DECLARE 260
#define ARRAY 261
#define INTEGER 262
#define OF 263
#define REAL 264
#define XBEGIN 265
#define XEND 266
#define IF 267
#define THEN 268
#define ELSE 269
#define WHILE 270
#define DO 271
#define ASSIGN 272
#define RETURN 273
#define GE 274
#define LE 275
#define EQ 276
#define NE 277
#define TRUE 278
#define FALSE 279
#define PROGRAM 280
#define ELSEIF 281
#define NOT 282
#define AND 283
#define OR 284
#define UMINUS 285
#define FUNCTION 258
#define ID 259
#define DECLARE 260
#define ARRAY 261
#define INTEGER 262
#define OF 263
#define REAL 264
#define XBEGIN 265
#define XEND 266
#define IF 267
#define THEN 268
#define ELSE 269
#define WHILE 270
#define DO 271
#define ASSIGN 272
#define RETURN 273
#define GE 274
#define LE 275
#define EQ 276
#define NE 277
#define TRUE 278
#define FALSE 279
#define PROGRAM 280
#define ELSEIF 281
#define NOT 282
#define AND 283
#define OR 284
#define UMINUS 285
#define true 286
#define false 287
......@@ -52,7 +52,8 @@ not return NOT;
"<>" return NE;
array return ARRAY;
of return OF;
true return TRUE;
false return FALSE;
......
/*
This test checks the fibonacci function
Author: John Tinnerholm
*/
declare
a : integer;
function fib (x : integer) : integer
begin
if x == 0 then
begin
return 0;
end
elseif x == 1 then
begin
return 1;
end
else
begin
return fib(x - 1) + fib(x-2);
end
if;
end;
begin
a := 0;
while a < 10 do
begin
putint(fib(a));
putline();
a := a + 1;
end
while;
end;
CC = g++
CCFLAGS = -g
CFLAGS = -g
LDFLAGS =
lab2 : lex.o main.o lab2.o
......
# Instructions on how to run the test script
This folder contains the code for lab 2. A test script exists to test your implementation. To run this test script, you must execute the following command in the terminal on the IDA Lab Computers.
```bash
python3 test.py test.txt --program ./lab2
```
This assumes that you have successfully compiled the project by executing the makefile and *that the program has been configured to read successfully from stdin*.
Furthermore, the test script assumes that the lab has been completed according to the instructions, mistakes might cause the test script to malfunction.
To make the project make sure that you execute:
```
make
````
in the command line interface.
## Notes (Important)
* The test script assumes that you have Python 3 installed and that you run on the IDA Lab Systems. It is has not been tested on other systems such as MacOS or GNU/Linux.
* Initially the program will just enter an infinite loop. Hence, you will need to modify the program and get the basic scaffolding in place before you can run the test script. Thus, it might be a good idea to test of some of the arthimatic expressions in the terminal before proceeding with the test script.
To avoid inifnite loops when using files from stdin the following code snippet could be helpful.
Here we specifiy that if we encounter the end of file token we will throw an `ParserEndOfFile` error.
````c++
double Parser::<your name of the start expression>() {
//Note almost correct C++ but you will need to modify this code in order to make it run corretly. Do not simply just copy paste.
currentToken = scanner.Scan();
if (currentToken.type == kEndMark) {
throw ParserEndOfFile();
}
return <name of your expression method>();
}
````
This will be cought by the main loop which is configured in the following way:
```c++
int main(void)
{
Parser parser;
double val;
while (true)
{
try
{
cout << "Expression: " << flush;
/* Implement the parser.Parse method */
val = parser.Parse();
cout << "Result: " << val << '\n' << flush;
}
catch (ScannerError& e)
{
cerr << e << '\n' << flush;
parser.Recover();
}
catch (ParserError)
{
parser.Recover();
}
catch (ParserEndOfFile)
{
cerr << "End of file\n" << flush;
exit(0);
}
}
}
```
Note that the program will terminate if the end of file token is encountered.
......@@ -25,7 +25,7 @@ double Parser::Parse(void)
* Parse the input using recursive descent parsing. You'll need
* an object of type Scanner to get tokens from the input. Call
* the Scan method of this object to get the next token in the
* input stream.
* input stream.
*/
/* --- End your code --- */
......@@ -37,17 +37,17 @@ void Parser::Recover(void)
{
cerr << "Error recovery.\n" << flush;
/* --- Your code here ---
*
* Error recovery routine
*
*
* Unless you come up with something better, this function should
* scan tokens until it sees the end of line or end of file.
* Parsing may be resumed at that point. This means that if an end
* of line token caused the error, this routine doesn't need to do
* anything.
*
*
* Be sure not to scan more tokens than necessary, or it won't be
* possible to resume parsing.
*/
......
......@@ -17,6 +17,7 @@ using namespace std;
class Parser
{
public:
Scanner scanner;
void Recover(void); // Error recovery routine
double Parse(void); // Main entry point
};
......@@ -24,10 +25,10 @@ public:
class Trace
{
static int indent;
char *name;
const char *name;
public:
Trace(char *s)
Trace(const char *s)
{
name = s;
cerr.width(indent);
......
......@@ -8,7 +8,7 @@
// Human-readable representations of token types
//
static char *kTokenTypeNames[] =
static const char *kTokenTypeNames[] =
{
"uninitialized",
"number",
......@@ -81,12 +81,12 @@ void Scanner::PutbackChar(unsigned char c)
Token Scanner::Scan(void)
{
int c;
Reset();
while (1)
{
c = GetChar();
switch (state)
{
case 0: // Initial state
......@@ -238,7 +238,7 @@ Token Scanner::Scan(void)
// multiple-character token.
//
void Scanner::Accumulate(char c)
void Scanner::Accumulate(const char c)
{
if (position >= kMaxTokenLength)
{
......@@ -311,13 +311,12 @@ ostream& operator<<(ostream& s, ScannerError& e)
// Lookup just returns the textual representation of a token type. The
// no argument version returns the type of the token.
//
char *Token::Lookup(void)
const char *Token::Lookup(void)
{
return kTokenTypeNames[type];
}
char *Token::Lookup(TokenType t)
const char *Token::Lookup(TokenType t)
{
return kTokenTypeNames[t];
}
......@@ -30,11 +30,11 @@ class ScannerError
{
public:
char errorCharacter;
char *message;
const char *message;
int state;
ScannerError(char c, int s) : errorCharacter(c), state(s) {};
ScannerError(char *s) : message(s) {};
ScannerError(const char *s) : message(s) {};
ScannerError() : errorCharacter(0) {};
};
......@@ -86,8 +86,8 @@ public:
double numberValue;
char *symbolValue;
char *Lookup(TokenType);
char *Lookup(void);
const char *Lookup(TokenType);
const char *Lookup(void);
Token() : type(kUninitialized) {};
Token(TokenType t) : type(t) {};
Token(TokenType t, double x) : type(t), numberValue(x) {};
......
......@@ -9,11 +9,12 @@ int main(void)
Parser parser;
double val;
while (1)
while (true)
{
try
{
cout << "Expression: " << flush;
/* Implement the parser.Parse method */
val = parser.Parse();
cout << "Result: " << val << '\n' << flush;
}
......
#!/usr/bin/python3
'''
Written by Martin Olivesten in 2023 for TDDD55.
Full permission to use in anyway with no restrictions. :)
Run: `python3 test.py -h` for usage.
'''
from subprocess import Popen, PIPE, STDOUT
from re import search
from argparse import ArgumentParser as ArgParse
from dataclasses import dataclass
from random import randrange
from io import TextIOWrapper
from typing import List, Tuple
GREEN = '\x1b[32m'
YELLOW = '\x1b[33m'
RED = '\x1b[31m'
RESET = '\x1b[39m'
PASS = f'{GREEN}PASS{RESET}'
FAIL = f'{RED}FAIL{RESET}'
MAYBE = f'{YELLOW}MAYBE{RESET}'
REAL_REGEX = r'-?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][+-]?[0-9]+)?'
dump = ''
@dataclass
class Result():
expr: str
expected: str
got: str
outcome: str
def parse_tests(file: TextIOWrapper) -> List[Tuple[str, str]]:
tests = file.readlines()
tests = filter(lambda t: t[0] != '#' and t.strip(), tests)
tests = map(lambda t: t.split('='), tests)
tests = list(tests)
for t in tests:
if len(t) != 2:
print(f'ERR: Invalid test format: `{", ".join(t).strip()}`.')
exit(1)
tests = map(lambda p: list(map(str.strip, p)), tests)
return list(tests)
def run_tests(tests: List[Tuple[str, str]], prog: str) -> (List[Result], int):
global dump
res = []
n_err = 0
for i, test in enumerate(tests):
expr, expected = test
p = Popen([prog], stdin=PIPE, stdout=PIPE, stderr=STDOUT)
p_out = p.communicate(f'{expr}\n'.encode())[0].decode()
m = search(fr'Result:\s*{REAL_REGEX}', p_out)
if m is None:
res.append(Result(expr, expected, 'ERR', FAIL))
n_err += 1
dump += 'ERR: Could not match for result in output.\n'
dump += f'INPUT: {expr}\n'
dump += f'OUTPUT:\n{p_out}\n\n'
continue
got = search(REAL_REGEX, m[0])[0]
outcome = ''
s_ans = got if len(got) <= len(expected) else expected
l_ans = got if len(got) > len(expected) else expected
if got == expected:
outcome = PASS
elif (len(s_ans) > 1
and s_ans.find('.') != -1
and (search(s_ans, l_ans)
or search(s_ans[:-1], l_ans[:-1]))):
outcome = MAYBE
else:
outcome = FAIL
res.append(Result(expr, expected, got, outcome))
return (res, n_err)
def main(file: TextIOWrapper, prog: str) -> None:
global dump
tests = parse_tests(open(file, 'r'))
if len(tests) == 0:
print("ERR: No tests to run")
exit(1)
res, n_err = run_tests(tests, prog)
expr_len = max(map(lambda r: len(r.expr), res))
expect_len = max(map(lambda r: len(r.expected), res))
got_len = max(map(lambda r: len(r.got), res))
nr_len = len(str(len(res)))
bar_len = 35 + expr_len + expect_len + got_len + nr_len
print('='*bar_len)
for i, r in enumerate(res):
print(f'| {i:>{nr_len}}: {r.expr:>{expr_len}} | Expected: {r.expected:>{expect_len}} | Got: {r.got:>{got_len}} | {r.outcome:>{5 + len(RED) + len(RESET)}} |') # noqa: E501
print('='*bar_len)
if n_err:
rand_str = ''.join([str(randrange(0, 10)) for _ in range(8)])
dump_file = f'./test-{rand_str}-dump.txt'
print(f'Writing error outputs to dump file: `{dump_file}`')
with open(dump_file, 'w') as f:
f.write(dump)
if __name__ == '__main__':
ap = ArgParse()
ap.add_argument(
'file',
help='Test file to use.'
)
ap.add_argument(
'-p', '--program',
default='./lab2',
help='Executable to run tests on. Defaults to `./lab2`.'
)
args = ap.parse_args()
main(args.file, args.program)
# Format:
# expression = expected answer
# '#' and empty lines ignored
# Precedence and associativity
1+2*3^-4 = 1.02469
1+2/3 = 1.66667
(1+2)/3 = 1
2*3+5 = 11
2*(3+5) = 16
2^2^3 = 256
2*2^2*2 = 16
(2*2)^(2*2) = 256
2^2*10 = 40
1--1 = 2
1*2^2+3/4^-1 = 16
(-4*-4+4^2) = 32
((-4*-4+4^2)-32+1) = 1
-(-1--(-(-10--100))) = 91
# Symbolic constants
e = 2.71828
pi = 3.14159
e^pi = 23.1407
# Symbolic functions
log10(10) = 1
sin(pi)+cos(pi) = -1
log(e^e) = 2.71828
sin(pi/2)+cos(0)+tan(pi/4) = 3
......@@ -15,6 +15,8 @@ OUTFILE = compiler
FLEX = flex
BISON = bison
TESTFILES=expression_test.c factorial_test.c fibonacci_test.c sort_test.c simple_array_test.c
TESTEXECS=expression_test factorial_test fibonacci_test sort_test simple_array_test
DPFILE = Makefile.dependencies
......@@ -39,10 +41,9 @@ parser.o : parser.cc
$(CC) $(CFLAGS) -c $<
clean :
rm -f $(OBJECTS) core *~ scanner.cc parser.cc parser.hh $(DPFILE) $(OUTFILE) parser.cc.output
rm -f $(OBJECTS) core *~ scanner.cc parser.cc parser.hh $(DPFILE) $(OUTFILE) parser.cc.output $(TESTFILES) $(TESTEXECS)
touch $(DPFILE)
$(DPFILE) depend : $(BASESRC) $(HEADERS)
$(CC) $(DPFLAGS) $(CFLAGS) $(BASESRC) > $(DPFILE)
......