Skip to content

Commit 06a6f6e

Browse files
committed
2 parents d501f9f + 0b74087 commit 06a6f6e

File tree

8 files changed

+79
-57
lines changed

8 files changed

+79
-57
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
bin/
2+
build/
23
.vscode
34
*.o
45
*.tab.hpp

Makefile

+17-27
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
# Based on https://stackoverflow.com/a/52036564 which is well worth reading!
22

3-
CXXFLAGS += -std=c++20 -W -Wall -g -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -fsanitize=address -static-libasan -O0 -rdynamic -I include
3+
CXXFLAGS += -std=c++20 -W -Wall -g -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -fsanitize=address -static-libasan -O0 -rdynamic --coverage -I include
44

55
SOURCES := $(wildcard src/*.cpp)
6-
DEPENDENCIES := $(patsubst %.cpp,%.d,$(SOURCES))
6+
DEPENDENCIES := $(patsubst src/%.cpp,build/%.d,$(SOURCES))
77

8-
OBJECTS := $(patsubst %.cpp,%.o,$(SOURCES))
9-
OBJECTS += src/parser.tab.o src/lexer.yy.o
8+
OBJECTS := $(patsubst src/%.cpp,build/%.o,$(SOURCES))
9+
OBJECTS += build/parser.tab.o build/lexer.yy.o
1010

11-
12-
.PHONY: default clean with_coverage coverage
11+
.PHONY: default clean coverage
1312

1413
default: bin/c_compiler
1514

@@ -19,35 +18,26 @@ bin/c_compiler: $(OBJECTS)
1918

2019
-include $(DEPENDENCIES)
2120

22-
%.o: %.cpp Makefile
21+
build/%.o: src/%.cpp Makefile
22+
@mkdir -p $(@D)
2323
g++ $(CXXFLAGS) -MMD -MP -c $< -o $@
2424

25-
src/parser.tab.cpp src/parser.tab.hpp: src/parser.y
26-
bison -v -d src/parser.y -o src/parser.tab.cpp
27-
28-
src/lexer.yy.cpp : src/lexer.flex src/parser.tab.hpp
29-
flex -o src/lexer.yy.cpp src/lexer.flex
25+
build/parser.tab.cpp build/parser.tab.hpp: src/parser.y
26+
@mkdir -p build
27+
bison -v -d src/parser.y -o build/parser.tab.cpp
3028

31-
with_coverage : CXXFLAGS += --coverage
32-
with_coverage : bin/c_compiler
29+
build/lexer.yy.cpp: src/lexer.flex build/parser.tab.hpp
30+
@mkdir -p build
31+
flex -o build/lexer.yy.cpp src/lexer.flex
3332

34-
coverage : coverage/index.html
35-
36-
coverage/index.html :
33+
coverage:
34+
@rm -rf coverage/
3735
@mkdir -p coverage
38-
# somehow lexer and parser coverage info are available but not accurate. To exclude them use:
39-
# lcov -c --no-external --exclude "`pwd`/src/lexer.*" --exclude "`pwd`/src/parser.*" -d . -o coverage/cov.info
40-
lcov -c --no-external -d . -o coverage/cov.info
36+
lcov -c --no-external --exclude "`pwd`/src/lexer.*" --exclude "`pwd`/src/parser.*" --exclude "`pwd`/build/*" -d . -o coverage/cov.info
4137
genhtml coverage/cov.info -o coverage
4238
@find . -name "*.gcda" -delete
4339

4440
clean :
4541
@rm -rf coverage/
46-
@rm -rf src/*.o
47-
@rm -rf src/*.d
48-
@rm -rf src/*.gcno
42+
@rm -rf build/
4943
@rm -rf bin/
50-
@rm -f src/*.tab.hpp
51-
@rm -f src/*.tab.cpp
52-
@rm -f src/*.yy.cpp
53-
@rm -f src/*.output

docs/c_compiler.md

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ Here is a list of intermediate features that you might like to implement once th
111111
* the `enum` keyword
112112
* `switch` statements
113113
* the `break` and `continue` keywords
114+
* ternary operator (`x ? y : z`)
114115

115116
Here is a list of more advanced features like you might like to implement once the basic and intermediate features are working.
116117

docs/coverage.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Coverage information
22
====================
33

4-
If you want to know which part of your code is executed when running your compiler on a file you can build your compiler with `make with_coverage`, run your compiler on the file, then run `make coverage`.
4+
If you want to know which part of your code is executed when running your compiler on a file you can run your compiler on the file, then run `make coverage`.
55

66
This will generate a webpage `coverage/index.html` with a listing of all the source files and for each source file a listing of the number of times each line has been executed.
77

scripts/test.py

+50-19
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
This script will also generate a JUnit XML file, which can be used to integrate
88
with CI/CD pipelines.
99
10-
Usage: test.py [-h] [-m] [-s] [--version] [--no_clean | --coverage] [dir]
10+
Usage: test.py [-h] [-m] [-s] [--version] [--no_clean] [--coverage] [dir]
1111
1212
Example usage: scripts/test.py compiler_tests/_example
1313
@@ -32,6 +32,7 @@
3232
from pathlib import Path
3333
from concurrent.futures import ThreadPoolExecutor, as_completed
3434
from typing import List, Optional
35+
from http.server import HTTPServer, SimpleHTTPRequestHandler
3536

3637

3738
RED = "\033[31m"
@@ -321,29 +322,56 @@ def clean() -> bool:
321322
return False
322323
return True
323324

324-
def make(with_coverage: bool, silent: bool) -> bool:
325+
def make(silent: bool) -> bool:
325326
"""
326327
Wrapper for make bin/c_compiler.
327328
328329
Return True if successful, False otherwise
329330
"""
330331
print(GREEN + "Running make..." + RESET)
332+
return_code, error_msg, _ = run_subprocess(
333+
cmd=["make", "-C", PROJECT_LOCATION, "bin/c_compiler"], timeout=BUILD_TIMEOUT_SECONDS, silent=silent
334+
)
335+
if return_code != 0:
336+
print(RED + "Error when making:", error_msg + RESET)
337+
return False
331338

332-
cmd = ["make", "-C", PROJECT_LOCATION, "bin/c_compiler"]
333-
if with_coverage:
334-
# Run coverage if needed
335-
print(GREEN + "Making with coverage..." + RESET)
336-
shutil.rmtree(COVERAGE_FOLDER, ignore_errors=True)
337-
cmd = ["make", "-C", PROJECT_LOCATION, "with_coverage"]
339+
return True
338340

339-
return_code, error_msg, _ = run_subprocess(cmd=cmd, timeout=BUILD_TIMEOUT_SECONDS, silent=silent)
341+
def coverage() -> bool:
342+
"""
343+
Wrapper for make coverage.
340344
345+
Return True if successful, False otherwise
346+
"""
347+
print(GREEN + "Running make coverage..." + RESET)
348+
return_code, error_msg, _ = run_subprocess(
349+
cmd=["make", "-C", PROJECT_LOCATION, "coverage"], timeout=BUILD_TIMEOUT_SECONDS, silent=True
350+
)
341351
if return_code != 0:
342-
print(RED + "Error when making:", error_msg + RESET)
352+
print(RED + "Error when making coverage:", error_msg + RESET)
343353
return False
344-
345354
return True
346355

356+
def serve_coverage_forever(host: str, port: int):
357+
"""
358+
Starts a HTTP server which serves the coverage folder forever until Ctrl+C
359+
is pressed.
360+
"""
361+
class Handler(SimpleHTTPRequestHandler):
362+
def __init__(self, *args, directory=None, **kwargs):
363+
super().__init__(*args, directory=COVERAGE_FOLDER, **kwargs)
364+
365+
def log_message(self, format, *args):
366+
pass
367+
368+
httpd = HTTPServer((host, port), Handler)
369+
print(GREEN + "Serving coverage on" + RESET + f" http://{host}:{port}/ ... (Ctrl+C to exit)")
370+
try:
371+
httpd.serve_forever()
372+
except KeyboardInterrupt:
373+
print(RED + "\nServer has been stopped!" + RESET)
374+
347375
def process_result(
348376
result: Result,
349377
xml_file: JUnitXMLFile,
@@ -402,7 +430,7 @@ def run_tests(args, xml_file: JUnitXMLFile):
402430
print("\n>> Test Summary: " + GREEN + f"{passing} Passed, " + RED + f"{total-passing} Failed" + RESET)
403431

404432
def parse_args():
405-
""""
433+
"""
406434
Wrapper for argument parsing.
407435
"""
408436
parser = argparse.ArgumentParser()
@@ -433,16 +461,14 @@ def parse_args():
433461
action="version",
434462
version=f"BetterTesting {__version__}"
435463
)
436-
# Coverage cannot be perfomed without rebuilding the compiler
437-
group = parser.add_mutually_exclusive_group(required=False)
438-
group.add_argument(
439-
"--no_clean",
464+
parser.add_argument(
465+
'--no_clean',
440466
action="store_true",
441467
default=False,
442-
help="Do no clean the repository before testing. This will make it "
468+
help="Don't clean the repository before testing. This will make it "
443469
"faster but it can be safer to clean if you have any compilation issues."
444470
)
445-
group.add_argument(
471+
parser.add_argument(
446472
"--coverage",
447473
action="store_true",
448474
default=False,
@@ -461,12 +487,17 @@ def main():
461487
# Clean the repo if required and exit if this fails.
462488
exit(2)
463489

464-
if not make(with_coverage=args.coverage, silent=args.short):
490+
if not make(silent=args.short):
465491
exit(3)
466492

467493
with JUnitXMLFile(J_UNIT_OUTPUT_FILE) as xml_file:
468494
run_tests(args, xml_file)
469495

496+
if args.coverage:
497+
if not coverage():
498+
exit(4)
499+
serve_coverage_forever('0.0.0.0', 8000)
500+
470501
if __name__ == "__main__":
471502
try:
472503
main()

scripts/test.sh

+3-10
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,9 @@ if [ "${DONT_CLEAN:-}" != "1" ]; then
1010
make clean
1111
fi
1212

13-
if [ "${COVERAGE:-}" == "1" ]; then
14-
rm -rf coverage
15-
set -e
16-
make with_coverage
17-
set +e
18-
else
19-
set -e
20-
make bin/c_compiler
21-
set +e
22-
fi
13+
set -e
14+
make bin/c_compiler
15+
set +e
2316

2417
mkdir -p bin
2518
mkdir -p bin/output

src/parser.y

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
extern FILE *yyin;
1212
int yylex(void);
1313
void yyerror(const char *);
14+
int yylex_destroy(void);
1415
}
1516

1617
// Represents the value associated with any kind of AST node.
@@ -202,5 +203,7 @@ Node *ParseAST(std::string file_name)
202203
}
203204
g_root = nullptr;
204205
yyparse();
206+
fclose(yyin);
207+
yylex_destroy();
205208
return g_root;
206209
}

src/parser_full.y.example

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
extern FILE *yyin;
88
int yylex(void);
99
void yyerror(const char *);
10+
int yylex_destroy(void);
1011
}
1112

1213
// Represents the value associated with any kind of AST node.
@@ -468,5 +469,7 @@ Node *ParseAST(std::string file_name)
468469
}
469470
g_root = nullptr;
470471
yyparse();
472+
fclose(yyin);
473+
yylex_destroy();
471474
return g_root;
472475
}

0 commit comments

Comments
 (0)