From e0792909cbd398f7824a9a27f56c37831ef9a69a Mon Sep 17 00:00:00 2001 From: Warren Toomey Date: Wed, 6 Nov 2019 15:32:28 +1000 Subject: [PATCH] I've added better regression testing. --- 27_Testing_Errors/Makefile | 22 + 27_Testing_Errors/Readme.md | 291 +++++++++++ 27_Testing_Errors/cg.c | 647 +++++++++++++++++++++++++ 27_Testing_Errors/cg_arm.c | 388 +++++++++++++++ 27_Testing_Errors/data.h | 19 + 27_Testing_Errors/decl.c | 263 ++++++++++ 27_Testing_Errors/decl.h | 110 +++++ 27_Testing_Errors/defs.h | 108 +++++ 27_Testing_Errors/expr.c | 438 +++++++++++++++++ 27_Testing_Errors/gen.c | 295 +++++++++++ 27_Testing_Errors/lib/printint.c | 8 + 27_Testing_Errors/main.c | 74 +++ 27_Testing_Errors/misc.c | 68 +++ 27_Testing_Errors/scan.c | 366 ++++++++++++++ 27_Testing_Errors/stmt.c | 237 +++++++++ 27_Testing_Errors/sym.c | 147 ++++++ 27_Testing_Errors/tests/err.input31.c | 1 + 27_Testing_Errors/tests/err.input32.c | 1 + 27_Testing_Errors/tests/err.input33.c | 1 + 27_Testing_Errors/tests/err.input34.c | 1 + 27_Testing_Errors/tests/err.input35.c | 1 + 27_Testing_Errors/tests/err.input36.c | 1 + 27_Testing_Errors/tests/err.input37.c | 1 + 27_Testing_Errors/tests/err.input38.c | 1 + 27_Testing_Errors/tests/err.input39.c | 1 + 27_Testing_Errors/tests/err.input40.c | 1 + 27_Testing_Errors/tests/err.input41.c | 1 + 27_Testing_Errors/tests/err.input42.c | 1 + 27_Testing_Errors/tests/err.input43.c | 1 + 27_Testing_Errors/tests/err.input44.c | 1 + 27_Testing_Errors/tests/err.input45.c | 1 + 27_Testing_Errors/tests/err.input46.c | 1 + 27_Testing_Errors/tests/err.input47.c | 1 + 27_Testing_Errors/tests/err.input48.c | 1 + 27_Testing_Errors/tests/err.input49.c | 1 + 27_Testing_Errors/tests/err.input50.c | 1 + 27_Testing_Errors/tests/err.input51.c | 1 + 27_Testing_Errors/tests/err.input52.c | 1 + 27_Testing_Errors/tests/input01.c | 5 + 27_Testing_Errors/tests/input02.c | 8 + 27_Testing_Errors/tests/input03.c | 9 + 27_Testing_Errors/tests/input04.c | 13 + 27_Testing_Errors/tests/input05.c | 10 + 27_Testing_Errors/tests/input06.c | 8 + 27_Testing_Errors/tests/input07.c | 7 + 27_Testing_Errors/tests/input08.c | 7 + 27_Testing_Errors/tests/input09.c | 14 + 27_Testing_Errors/tests/input10.c | 10 + 27_Testing_Errors/tests/input11.c | 15 + 27_Testing_Errors/tests/input12.c | 9 + 27_Testing_Errors/tests/input13.c | 11 + 27_Testing_Errors/tests/input14.c | 12 + 27_Testing_Errors/tests/input15.c | 16 + 27_Testing_Errors/tests/input16.c | 10 + 27_Testing_Errors/tests/input17.c | 10 + 27_Testing_Errors/tests/input18.c | 9 + 27_Testing_Errors/tests/input18a.c | 11 + 27_Testing_Errors/tests/input19.c | 12 + 27_Testing_Errors/tests/input20.c | 9 + 27_Testing_Errors/tests/input21.c | 11 + 27_Testing_Errors/tests/input22.c | 20 + 27_Testing_Errors/tests/input23.c | 22 + 27_Testing_Errors/tests/input24.c | 12 + 27_Testing_Errors/tests/input25.c | 15 + 27_Testing_Errors/tests/input26.c | 16 + 27_Testing_Errors/tests/input27.c | 32 ++ 27_Testing_Errors/tests/input28.c | 16 + 27_Testing_Errors/tests/input29.c | 20 + 27_Testing_Errors/tests/input30.c | 22 + 27_Testing_Errors/tests/input31.c | 4 + 27_Testing_Errors/tests/input32.c | 3 + 27_Testing_Errors/tests/input33.c | 3 + 27_Testing_Errors/tests/input34.c | 4 + 27_Testing_Errors/tests/input35.c | 4 + 27_Testing_Errors/tests/input36.c | 2 + 27_Testing_Errors/tests/input37.c | 1 + 27_Testing_Errors/tests/input38.c | 2 + 27_Testing_Errors/tests/input39.c | 1 + 27_Testing_Errors/tests/input40.c | 1 + 27_Testing_Errors/tests/input41.c | 1 + 27_Testing_Errors/tests/input42.c | 1 + 27_Testing_Errors/tests/input43.c | 1 + 27_Testing_Errors/tests/input44.c | 1 + 27_Testing_Errors/tests/input45.c | 1 + 27_Testing_Errors/tests/input46.c | 1 + 27_Testing_Errors/tests/input47.c | 1 + 27_Testing_Errors/tests/input48.c | 1 + 27_Testing_Errors/tests/input49.c | 5 + 27_Testing_Errors/tests/input50.c | 5 + 27_Testing_Errors/tests/input51.c | 3 + 27_Testing_Errors/tests/input52.c | 4 + 27_Testing_Errors/tests/input53.c | 7 + 27_Testing_Errors/tests/input54.c | 10 + 27_Testing_Errors/tests/mktests | 22 + 27_Testing_Errors/tests/out.input01.c | 3 + 27_Testing_Errors/tests/out.input02.c | 1 + 27_Testing_Errors/tests/out.input03.c | 5 + 27_Testing_Errors/tests/out.input04.c | 9 + 27_Testing_Errors/tests/out.input05.c | 1 + 27_Testing_Errors/tests/out.input06.c | 10 + 27_Testing_Errors/tests/out.input07.c | 10 + 27_Testing_Errors/tests/out.input08.c | 10 + 27_Testing_Errors/tests/out.input09.c | 10 + 27_Testing_Errors/tests/out.input10.c | 12 + 27_Testing_Errors/tests/out.input11.c | 20 + 27_Testing_Errors/tests/out.input12.c | 1 + 27_Testing_Errors/tests/out.input13.c | 2 + 27_Testing_Errors/tests/out.input14.c | 3 + 27_Testing_Errors/tests/out.input15.c | 4 + 27_Testing_Errors/tests/out.input16.c | 2 + 27_Testing_Errors/tests/out.input17.c | 2 + 27_Testing_Errors/tests/out.input18.c | 2 + 27_Testing_Errors/tests/out.input18a.c | 2 + 27_Testing_Errors/tests/out.input19.c | 1 + 27_Testing_Errors/tests/out.input20.c | 1 + 27_Testing_Errors/tests/out.input21.c | 2 + 27_Testing_Errors/tests/out.input22.c | 12 + 27_Testing_Errors/tests/out.input23.c | 9 + 27_Testing_Errors/tests/out.input24.c | 5 + 27_Testing_Errors/tests/out.input25.c | 6 + 27_Testing_Errors/tests/out.input26.c | 11 + 27_Testing_Errors/tests/out.input27.c | 23 + 27_Testing_Errors/tests/out.input28.c | 9 + 27_Testing_Errors/tests/out.input29.c | 9 + 27_Testing_Errors/tests/out.input30.c | 22 + 27_Testing_Errors/tests/out.input53.c | 1 + 27_Testing_Errors/tests/out.input54.c | 20 + 27_Testing_Errors/tests/runtests | 64 +++ 27_Testing_Errors/tree.c | 186 +++++++ 27_Testing_Errors/types.c | 122 +++++ Readme.md | 1 + 131 files changed, 4606 insertions(+) create mode 100644 27_Testing_Errors/Makefile create mode 100644 27_Testing_Errors/Readme.md create mode 100644 27_Testing_Errors/cg.c create mode 100644 27_Testing_Errors/cg_arm.c create mode 100644 27_Testing_Errors/data.h create mode 100644 27_Testing_Errors/decl.c create mode 100644 27_Testing_Errors/decl.h create mode 100644 27_Testing_Errors/defs.h create mode 100644 27_Testing_Errors/expr.c create mode 100644 27_Testing_Errors/gen.c create mode 100644 27_Testing_Errors/lib/printint.c create mode 100644 27_Testing_Errors/main.c create mode 100644 27_Testing_Errors/misc.c create mode 100644 27_Testing_Errors/scan.c create mode 100644 27_Testing_Errors/stmt.c create mode 100644 27_Testing_Errors/sym.c create mode 100644 27_Testing_Errors/tests/err.input31.c create mode 100644 27_Testing_Errors/tests/err.input32.c create mode 100644 27_Testing_Errors/tests/err.input33.c create mode 100644 27_Testing_Errors/tests/err.input34.c create mode 100644 27_Testing_Errors/tests/err.input35.c create mode 100644 27_Testing_Errors/tests/err.input36.c create mode 100644 27_Testing_Errors/tests/err.input37.c create mode 100644 27_Testing_Errors/tests/err.input38.c create mode 100644 27_Testing_Errors/tests/err.input39.c create mode 100644 27_Testing_Errors/tests/err.input40.c create mode 100644 27_Testing_Errors/tests/err.input41.c create mode 100644 27_Testing_Errors/tests/err.input42.c create mode 100644 27_Testing_Errors/tests/err.input43.c create mode 100644 27_Testing_Errors/tests/err.input44.c create mode 100644 27_Testing_Errors/tests/err.input45.c create mode 100644 27_Testing_Errors/tests/err.input46.c create mode 100644 27_Testing_Errors/tests/err.input47.c create mode 100644 27_Testing_Errors/tests/err.input48.c create mode 100644 27_Testing_Errors/tests/err.input49.c create mode 100644 27_Testing_Errors/tests/err.input50.c create mode 100644 27_Testing_Errors/tests/err.input51.c create mode 100644 27_Testing_Errors/tests/err.input52.c create mode 100644 27_Testing_Errors/tests/input01.c create mode 100644 27_Testing_Errors/tests/input02.c create mode 100644 27_Testing_Errors/tests/input03.c create mode 100644 27_Testing_Errors/tests/input04.c create mode 100644 27_Testing_Errors/tests/input05.c create mode 100644 27_Testing_Errors/tests/input06.c create mode 100644 27_Testing_Errors/tests/input07.c create mode 100644 27_Testing_Errors/tests/input08.c create mode 100644 27_Testing_Errors/tests/input09.c create mode 100644 27_Testing_Errors/tests/input10.c create mode 100644 27_Testing_Errors/tests/input11.c create mode 100644 27_Testing_Errors/tests/input12.c create mode 100644 27_Testing_Errors/tests/input13.c create mode 100644 27_Testing_Errors/tests/input14.c create mode 100644 27_Testing_Errors/tests/input15.c create mode 100644 27_Testing_Errors/tests/input16.c create mode 100644 27_Testing_Errors/tests/input17.c create mode 100644 27_Testing_Errors/tests/input18.c create mode 100644 27_Testing_Errors/tests/input18a.c create mode 100644 27_Testing_Errors/tests/input19.c create mode 100644 27_Testing_Errors/tests/input20.c create mode 100644 27_Testing_Errors/tests/input21.c create mode 100644 27_Testing_Errors/tests/input22.c create mode 100644 27_Testing_Errors/tests/input23.c create mode 100644 27_Testing_Errors/tests/input24.c create mode 100644 27_Testing_Errors/tests/input25.c create mode 100644 27_Testing_Errors/tests/input26.c create mode 100644 27_Testing_Errors/tests/input27.c create mode 100644 27_Testing_Errors/tests/input28.c create mode 100644 27_Testing_Errors/tests/input29.c create mode 100644 27_Testing_Errors/tests/input30.c create mode 100644 27_Testing_Errors/tests/input31.c create mode 100644 27_Testing_Errors/tests/input32.c create mode 100644 27_Testing_Errors/tests/input33.c create mode 100644 27_Testing_Errors/tests/input34.c create mode 100644 27_Testing_Errors/tests/input35.c create mode 100644 27_Testing_Errors/tests/input36.c create mode 100644 27_Testing_Errors/tests/input37.c create mode 100644 27_Testing_Errors/tests/input38.c create mode 100644 27_Testing_Errors/tests/input39.c create mode 100644 27_Testing_Errors/tests/input40.c create mode 100644 27_Testing_Errors/tests/input41.c create mode 100644 27_Testing_Errors/tests/input42.c create mode 100644 27_Testing_Errors/tests/input43.c create mode 100644 27_Testing_Errors/tests/input44.c create mode 100644 27_Testing_Errors/tests/input45.c create mode 100644 27_Testing_Errors/tests/input46.c create mode 100644 27_Testing_Errors/tests/input47.c create mode 100644 27_Testing_Errors/tests/input48.c create mode 100644 27_Testing_Errors/tests/input49.c create mode 100644 27_Testing_Errors/tests/input50.c create mode 100644 27_Testing_Errors/tests/input51.c create mode 100644 27_Testing_Errors/tests/input52.c create mode 100644 27_Testing_Errors/tests/input53.c create mode 100644 27_Testing_Errors/tests/input54.c create mode 100755 27_Testing_Errors/tests/mktests create mode 100644 27_Testing_Errors/tests/out.input01.c create mode 100644 27_Testing_Errors/tests/out.input02.c create mode 100644 27_Testing_Errors/tests/out.input03.c create mode 100644 27_Testing_Errors/tests/out.input04.c create mode 100644 27_Testing_Errors/tests/out.input05.c create mode 100644 27_Testing_Errors/tests/out.input06.c create mode 100644 27_Testing_Errors/tests/out.input07.c create mode 100644 27_Testing_Errors/tests/out.input08.c create mode 100644 27_Testing_Errors/tests/out.input09.c create mode 100644 27_Testing_Errors/tests/out.input10.c create mode 100644 27_Testing_Errors/tests/out.input11.c create mode 100644 27_Testing_Errors/tests/out.input12.c create mode 100644 27_Testing_Errors/tests/out.input13.c create mode 100644 27_Testing_Errors/tests/out.input14.c create mode 100644 27_Testing_Errors/tests/out.input15.c create mode 100644 27_Testing_Errors/tests/out.input16.c create mode 100644 27_Testing_Errors/tests/out.input17.c create mode 100644 27_Testing_Errors/tests/out.input18.c create mode 100644 27_Testing_Errors/tests/out.input18a.c create mode 100644 27_Testing_Errors/tests/out.input19.c create mode 100644 27_Testing_Errors/tests/out.input20.c create mode 100644 27_Testing_Errors/tests/out.input21.c create mode 100644 27_Testing_Errors/tests/out.input22.c create mode 100644 27_Testing_Errors/tests/out.input23.c create mode 100644 27_Testing_Errors/tests/out.input24.c create mode 100644 27_Testing_Errors/tests/out.input25.c create mode 100644 27_Testing_Errors/tests/out.input26.c create mode 100644 27_Testing_Errors/tests/out.input27.c create mode 100644 27_Testing_Errors/tests/out.input28.c create mode 100644 27_Testing_Errors/tests/out.input29.c create mode 100644 27_Testing_Errors/tests/out.input30.c create mode 100644 27_Testing_Errors/tests/out.input53.c create mode 100644 27_Testing_Errors/tests/out.input54.c create mode 100755 27_Testing_Errors/tests/runtests create mode 100644 27_Testing_Errors/tree.c create mode 100644 27_Testing_Errors/types.c diff --git a/27_Testing_Errors/Makefile b/27_Testing_Errors/Makefile new file mode 100644 index 0000000..07b80b4 --- /dev/null +++ b/27_Testing_Errors/Makefile @@ -0,0 +1,22 @@ +HSRCS= data.h decl.h defs.h +SRCS= cg.c decl.c expr.c gen.c main.c misc.c \ + scan.c stmt.c sym.c tree.c types.c + +ARMSRCS= cg.c decl.c expr.c gen.c main.c misc.c \ + scan.c stmt.c sym.c tree.c types.c + +comp1: $(SRCS) $(HSRCS) + cc -o comp1 -g -Wall $(SRCS) + +comp1arm: $(ARMSRCS) $(HSRCS) + cc -o comp1arm -g -Wall $(ARMSRCS) + cp comp1arm comp1 + +clean: + rm -f comp1 comp1arm *.o *.s out + +test: comp1 tests/runtests + (cd tests; chmod +x runtests; ./runtests) + +armtest: comp1arm tests/runtests + (cd tests; chmod +x runtests; ./runtests) diff --git a/27_Testing_Errors/Readme.md b/27_Testing_Errors/Readme.md new file mode 100644 index 0000000..ece16e5 --- /dev/null +++ b/27_Testing_Errors/Readme.md @@ -0,0 +1,291 @@ +# Part 27: Regression Testing and a Nice Surprise + +We've had a few large-ish steps recently in our +compiler writing journey, so I thought we should have +a bit of a breather in this step. We can slow down a +bit and review our progress so far. + +In the last step I noticed that we didn't have a way to +confirm that our syntax and semantic error checking was +working correctly. So I've just rewritten the scripts +in the `tests/` folder to do this. + +I've been using Unix since the late 1980s, so my go-to +automation tools are shell scripts and Makefiles or, +if I need more complex tools, scripts written in Python +or Perl (yes, I'm that old). + +So let's quickly look at the `runtest` script in the +`tests/` directory. Even though I said I'd been using +Unix scripts forever, I'm definitely not an uber +script writer. + +## The `runtest` Script + +The job of this script is to take a set of input programs, +get our compiler to compile them, run the executable +and compare its output against known-good output. If they +match, the test is a success. If not, it's a failure. + +I've just extended it so that, if there is an "error" +file associated with an input, we run our compiler +and capture its error output. If this error output +matches the expected error output, the test is a +success as the compiler correctly detected the bad input. + +So let's look at the sections of the `runtest` script in stages. + +``` +# Build our compiler if needed +if [ ! -f ../comp1 ] +then (cd ..; make) +fi +``` + +I'm using the '( ... )' syntax here to create a *sub-shell*. This +can change its working directory without affecting the original +shell, so we can move up a directory and rebuild our compiler. + + +``` +# Try to use each input source file +for i in input* +# We can't do anything if there's no file to test against +do if [ ! -f "out.$i" -a ! -f "err.$i" ] + then echo "Can't run test on $i, no output file!" +``` + +The '[' thing is actually the external Unix tool, *test(1)*. +Oh, if you've never seen this syntax before, *test(1)* means +the manual page for *test* is in Section One of the man pages, +and you can do: + +``` +$ man 1 test +``` + +to read the manual for *test* in Section One of the man pages. +The `/usr/bin/[` executable is usually linked to `/usr/bin/test`, +so that when you use '[' in a shell script, it's the same as running +the *test* command. + +We can read the line `[ ! -f "out.$i" -a ! -f "err.$i" ]` as saying: +test if there is no file "out.$i" and no file "err.$i". If both +don't exist, we can give the error message. + +``` + # Output file: compile the source, run it and + # capture the output, and compare it against + # the known-good output + else if [ -f "out.$i" ] + then + # Print the test name, compile it + # with our compiler + echo -n $i + ../comp1 $i + + # Assemble the output, run it + # and get the output in trial.$i + cc -o out out.s ../lib/printint.c + ./out > trial.$i + + # Compare this agains the correct output + cmp -s "out.$i" "trial.$i" + + # If different, announce failure + # and print out the difference + if [ "$?" -eq "1" ] + then echo ": failed" + diff -c "out.$i" "trial.$i" + echo + + # No failure, so announce success + else echo ": OK" + fi +``` + +This is the bulk of the script. I think the comments +explain what is going on, but perhaps there are some +subtleties to flesh out. `cmp -s` compares two +text files; the `-s` flag means produce no output +but set the exit value that `cmp` gives when it exits to: + +> 0 if inputs are the same, 1 if different, 2 if + trouble. (from the man page) + +The line `if [ "$?" -eq "1" ]` says: if the exit value +of the last command is equal to the number 1. So, if +the compiler's output is different to the known-good +output, we announce this and use the `diff` tool to +show the differences between the two files. + +``` + # Error file: compile the source and + # capture the error messages. Compare + # against the known-bad output. Same + # mechanism as before + else if [ -f "err.$i" ] + then + echo -n $i + ../comp1 $i 2> "trial.$i" + cmp -s "err.$i" "trial.$i" + ... +``` + +This section gets executed when there is an error +document, "err.$i". This time, we use the shell +syntax `2>` to capture our compilers standard +error output to the file "trial.$i" and compare +that against the correct error output. The logic +after this is the same as before. + +## What We Are Doing: Regression Testing + +I haven't talked much before about testing, but now's the +time. I've taught software development in the past so it +would be remiss of me not to cover testing at some point. + +What we are doing here is [**regression testing**](https://en.wikipedia.org/wiki/Regression_testing). +Wikipedia gives this definition: + +> Regression testing is the action of re-running functional and non-functional tests +> to ensure that previously developed and tested software still performs after a change. + +As our compiler is changing at each step, we have to ensure that each new change doesn't +break the functionality (and the error checking) of the previous steps. So each time I +introduce a change, I add one or more tests to a) prove that it works and b) re-run +this test on future changes. As long as all the tests pass, I'm sure that the new +code hasn't broken the old code. + +### Functional Tests + +The `runtests` script looks for files with the `out` prefix to do the +functional testing. Right now, we have: + +``` +tests/out.input01.c tests/out.input12.c tests/out.input22.c +tests/out.input02.c tests/out.input13.c tests/out.input23.c +tests/out.input03.c tests/out.input14.c tests/out.input24.c +tests/out.input04.c tests/out.input15.c tests/out.input25.c +tests/out.input05.c tests/out.input16.c tests/out.input26.c +tests/out.input06.c tests/out.input17.c tests/out.input27.c +tests/out.input07.c tests/out.input18a.c tests/out.input28.c +tests/out.input08.c tests/out.input18.c tests/out.input29.c +tests/out.input09.c tests/out.input19.c tests/out.input30.c +tests/out.input10.c tests/out.input20.c tests/out.input53.c +tests/out.input11.c tests/out.input21.c tests/out.input54.c +``` + +That's 33 separate tests of the compiler's functionality. Right now, +I know for a fact that our compiler is a bit fragile. None of these +tests really stress the compiler in any way: they are simple tests +of a few lines each. Later on, we will start to add some nasty stress +tests to help strengthen the compiler and make it more resilient. + +### Non-Functional Tests + +The `runtests` script looks for files with the `err` prefix to do the +functional testing. Right now, we have: + +``` +tests/err.input31.c tests/err.input39.c tests/err.input47.c +tests/err.input32.c tests/err.input40.c tests/err.input48.c +tests/err.input33.c tests/err.input41.c tests/err.input49.c +tests/err.input34.c tests/err.input42.c tests/err.input50.c +tests/err.input35.c tests/err.input43.c tests/err.input51.c +tests/err.input36.c tests/err.input44.c tests/err.input52.c +tests/err.input37.c tests/err.input45.c +tests/err.input38.c tests/err.input46.c +``` + +I created these 22 tests of the compiler's error checking in this +step of our journey by looking for `fatal()` calls in the compiler. +For each one, I've tried to write a small input file which would +trigger it. Have a read of the matching source files and see if +you can work out what syntax or semantic error each one triggers. + +## Other Forms of Testing + +This isn't a course on software development methodologies, so I won't give +too much more coverage on testing. But I'll give you links to a few +more thing that I would highly recommend that you look at: + + + [Unit testing](https://en.wikipedia.org/wiki/Unit_testing) + + [Test-driven development](https://en.wikipedia.org/wiki/Test-driven_development) + + [Continuous integration](https://en.wikipedia.org/wiki/Continuous_integration) + + [Version control](https://en.wikipedia.org/wiki/Version_control) + +I haven't done any unit testing with our compiler. The main reason +here is that the code is very fluid in terms of the APIs for the +functions. I'm not using a traditional waterfall model of development, +so I'd be spending too much time rewriting my unit tests to match +the latest APIs of all the functions. So, in some sense I am living +dangerously here: there will be a number of latent bugs in the code +which we haven't detected yet. + +However, there are guaranteed to be *many* more bugs where the +compiler looks like it accepts the C language, but of course this isn't +true. The compiler is failing the +[principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment). We will need to spend some time adding in +functionality that a "normal" C programmer expects to see. + +## And a Nice Surprise + +Finally, we have a nice functional surprise with the compiler as it +stands. A while back, I purposefully left out the code to test +that the number and type of arguments to a function call matches +the function's prototype (in `expr.c`): + +``` + // XXX Check type of each argument against the function's prototype +``` + +I left this as I didn't want to add too much new code in one of our steps. + +Now that we have prototypes, I've wanted to finally add support for +`printf()` so that we can ditch our homegrown `printint()` and +`printchar()` function. But we can't do this just yet, because +`printf()` is a [variadic function](https://en.wikipedia.org/wiki/Variadic_function): +it can accept a variable number of parameters. And, right now, our +compiler only allows a function declaration with a fixed number of +parameters. + +*However* (and this is the nice surprise), because we don't check +the number of arguments in a function call, we can pass *any* number +of arguments to `printf()` as long as we have given it an existing +prototype. So, at present, this code (`tests/input53.c`) works: + +``` +int printf(char *fmt); + +int main() +{ + printf("Hello world, %d\n", 23); + return(0); +} +``` + +And that's a nice thing! + +There is a gotcha. With the given `printf()` prototype, the cleanup code +in `cgcall()` won't adjust the stack pointer when the function returns, +as there are less than six parameters in the prototype. But we could +call `printf()` with ten arguments: we'd push four of them on the stack, +but `cgcall()` wouldn't clean up these four arguments when `printf()` +returns. + +## Conclusion and What's Next + +There is no new code in this step, but we are now testing the +error checking capability of the compiler, and we now have 54 +regression tests to help ensure we don't break the compiler when +we add new functionality. And, fortuitously, we can now use +`printf()` as well as the other external fixed parameter count functions. + +In the next part of our compiler writing journey, I think I'll +try to: + + + add support for an external pre-processor + + allow the compiler to compile multiple files named on the command line + + add the `-o`, `-c` and `-S` flags to the compiler to make it feel + more like a "normal" C compiler diff --git a/27_Testing_Errors/cg.c b/27_Testing_Errors/cg.c new file mode 100644 index 0000000..7966beb --- /dev/null +++ b/27_Testing_Errors/cg.c @@ -0,0 +1,647 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Code generator for x86-64 +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Flag to say which section were are outputting in to +enum { no_seg, text_seg, data_seg } currSeg = no_seg; + +void cgtextseg() { + if (currSeg != text_seg) { + fputs("\t.text\n", Outfile); + currSeg = text_seg; + } +} + +void cgdataseg() { + if (currSeg != data_seg) { + fputs("\t.data\n", Outfile); + currSeg = data_seg; + } +} + +// Position of next local variable relative to stack base pointer. +// We store the offset as positive to make aligning the stack pointer easier +static int localOffset; +static int stackOffset; + +// Create the position of a new local variable. +static int newlocaloffset(int type) { + // Decrement the offset by a minimum of 4 bytes + // and allocate on the stack + localOffset += (cgprimsize(type) > 4) ? cgprimsize(type) : 4; + return (-localOffset); +} + +// List of available registers and their names. +// We need a list of byte and doubleword registers, too +// The list also includes the registers used to +// hold function parameters +#define NUMFREEREGS 4 +#define FIRSTPARAMREG 9 // Position of first parameter register +static int freereg[NUMFREEREGS]; +static char *reglist[] = + { "%r10", "%r11", "%r12", "%r13", "%r9", "%r8", "%rcx", "%rdx", "%rsi", + "%rdi" +}; + +static char *breglist[] = + { "%r10b", "%r11b", "%r12b", "%r13b", "%r9b", "%r8b", "%cl", "%dl", "%sil", + "%dil" +}; + +static char *dreglist[] = + { "%r10d", "%r11d", "%r12d", "%r13d", "%r9d", "%r8d", "%ecx", "%edx", + "%esi", "%edi" +}; + + +// Set all registers as available +void freeall_registers(void) { + freereg[0] = freereg[1] = freereg[2] = freereg[3] = 1; +} + +// Allocate a free register. Return the number of +// the register. Die if no available registers. +static int alloc_register(void) { + for (int i = 0; i < NUMFREEREGS; i++) { + if (freereg[i]) { + freereg[i] = 0; + return (i); + } + } + fatal("Out of registers"); + return (NOREG); // Keep -Wall happy +} + +// Return a register to the list of available registers. +// Check to see if it's not already there. +static void free_register(int reg) { + if (freereg[reg] != 0) + fatald("Error trying to free register", reg); + freereg[reg] = 1; +} + +// Print out the assembly preamble +void cgpreamble() { + freeall_registers(); +} + +// Nothing to do +void cgpostamble() { +} + +// Print out a function preamble +void cgfuncpreamble(int id) { + char *name = Symtable[id].name; + int i; + int paramOffset = 16; // Any pushed params start at this stack offset + int paramReg = FIRSTPARAMREG; // Index to the first param register in above reg lists + + // Output in the text segment, reset local offset + cgtextseg(); + localOffset = 0; + + // Output the function start, save the %rsp and %rsp + fprintf(Outfile, + "\t.globl\t%s\n" + "\t.type\t%s, @function\n" + "%s:\n" "\tpushq\t%%rbp\n" + "\tmovq\t%%rsp, %%rbp\n", name, name, name); + + // Copy any in-register parameters to the stack + // Stop after no more than six parameter registers + for (i = NSYMBOLS - 1; i > Locls; i--) { + if (Symtable[i].class != C_PARAM) + break; + if (i < NSYMBOLS - 6) + break; + Symtable[i].posn = newlocaloffset(Symtable[i].type); + cgstorlocal(paramReg--, i); + } + + // For the remainder, if they are a parameter then they are + // already on the stack. If only a local, make a stack position. + for (; i > Locls; i--) { + if (Symtable[i].class == C_PARAM) { + Symtable[i].posn = paramOffset; + paramOffset += 8; + } else { + Symtable[i].posn = newlocaloffset(Symtable[i].type); + } + } + + // Align the stack pointer to be a multiple of 16 + // less than its previous value + stackOffset = (localOffset + 15) & ~15; + fprintf(Outfile, "\taddq\t$%d,%%rsp\n", -stackOffset); +} + +// Print out a function postamble +void cgfuncpostamble(int id) { + cglabel(Symtable[id].endlabel); + fprintf(Outfile, "\taddq\t$%d,%%rsp\n", stackOffset); + fputs("\tpopq %rbp\n" "\tret\n", Outfile); +} + +// Load an integer literal value into a register. +// Return the number of the register. +// For x86-64, we don't need to worry about the type. +int cgloadint(int value, int type) { + // Get a new register + int r = alloc_register(); + + fprintf(Outfile, "\tmovq\t$%d, %s\n", value, reglist[r]); + return (r); +} + +// Load a value from a variable into a register. +// Return the number of the register. If the +// operation is pre- or post-increment/decrement, +// also perform this action. +int cgloadglob(int id, int op) { + // Get a new register + int r = alloc_register(); + + // Print out the code to initialise it + switch (Symtable[id].type) { + case P_CHAR: + if (op == A_PREINC) + fprintf(Outfile, "\tincb\t%s(%%rip)\n", Symtable[id].name); + if (op == A_PREDEC) + fprintf(Outfile, "\tdecb\t%s(%%rip)\n", Symtable[id].name); + fprintf(Outfile, "\tmovzbq\t%s(%%rip), %s\n", Symtable[id].name, + reglist[r]); + if (op == A_POSTINC) + fprintf(Outfile, "\tincb\t%s(%%rip)\n", Symtable[id].name); + if (op == A_POSTDEC) + fprintf(Outfile, "\tdecb\t%s(%%rip)\n", Symtable[id].name); + break; + case P_INT: + if (op == A_PREINC) + fprintf(Outfile, "\tincl\t%s(%%rip)\n", Symtable[id].name); + if (op == A_PREDEC) + fprintf(Outfile, "\tdecl\t%s(%%rip)\n", Symtable[id].name); + fprintf(Outfile, "\tmovslq\t%s(%%rip), %s\n", Symtable[id].name, + reglist[r]); + if (op == A_POSTINC) + fprintf(Outfile, "\tincl\t%s(%%rip)\n", Symtable[id].name); + if (op == A_POSTDEC) + fprintf(Outfile, "\tdecl\t%s(%%rip)\n", Symtable[id].name); + break; + case P_LONG: + case P_CHARPTR: + case P_INTPTR: + case P_LONGPTR: + if (op == A_PREINC) + fprintf(Outfile, "\tincq\t%s(%%rip)\n", Symtable[id].name); + if (op == A_PREDEC) + fprintf(Outfile, "\tdecq\t%s(%%rip)\n", Symtable[id].name); + fprintf(Outfile, "\tmovq\t%s(%%rip), %s\n", Symtable[id].name, + reglist[r]); + if (op == A_POSTINC) + fprintf(Outfile, "\tincq\t%s(%%rip)\n", Symtable[id].name); + if (op == A_POSTDEC) + fprintf(Outfile, "\tdecq\t%s(%%rip)\n", Symtable[id].name); + break; + default: + fatald("Bad type in cgloadglob:", Symtable[id].type); + } + return (r); +} + +// Load a value from a local variable into a register. +// Return the number of the register. If the +// operation is pre- or post-increment/decrement, +// also perform this action. +int cgloadlocal(int id, int op) { + // Get a new register + int r = alloc_register(); + + // Print out the code to initialise it + switch (Symtable[id].type) { + case P_CHAR: + if (op == A_PREINC) + fprintf(Outfile, "\tincb\t%d(%%rbp)\n", Symtable[id].posn); + if (op == A_PREDEC) + fprintf(Outfile, "\tdecb\t%d(%%rbp)\n", Symtable[id].posn); + fprintf(Outfile, "\tmovzbq\t%d(%%rbp), %s\n", Symtable[id].posn, + reglist[r]); + if (op == A_POSTINC) + fprintf(Outfile, "\tincb\t%d(%%rbp)\n", Symtable[id].posn); + if (op == A_POSTDEC) + fprintf(Outfile, "\tdecb\t%d(%%rbp)\n", Symtable[id].posn); + break; + case P_INT: + if (op == A_PREINC) + fprintf(Outfile, "\tincl\t%d(%%rbp)\n", Symtable[id].posn); + if (op == A_PREDEC) + fprintf(Outfile, "\tdecl\t%d(%%rbp)\n", Symtable[id].posn); + fprintf(Outfile, "\tmovslq\t%d(%%rbp), %s\n", Symtable[id].posn, + reglist[r]); + if (op == A_POSTINC) + fprintf(Outfile, "\tincl\t%d(%%rbp)\n", Symtable[id].posn); + if (op == A_POSTDEC) + fprintf(Outfile, "\tdecl\t%d(%%rbp)\n", Symtable[id].posn); + break; + case P_LONG: + case P_CHARPTR: + case P_INTPTR: + case P_LONGPTR: + if (op == A_PREINC) + fprintf(Outfile, "\tincq\t%d(%%rbp)\n", Symtable[id].posn); + if (op == A_PREDEC) + fprintf(Outfile, "\tdecq\t%d(%%rbp)\n", Symtable[id].posn); + fprintf(Outfile, "\tmovq\t%d(%%rbp), %s\n", Symtable[id].posn, + reglist[r]); + if (op == A_POSTINC) + fprintf(Outfile, "\tincq\t%d(%%rbp)\n", Symtable[id].posn); + if (op == A_POSTDEC) + fprintf(Outfile, "\tdecq\t%d(%%rbp)\n", Symtable[id].posn); + break; + default: + fatald("Bad type in cgloadlocal:", Symtable[id].type); + } + return (r); +} + +// Given the label number of a global string, +// load its address into a new register +int cgloadglobstr(int id) { + // Get a new register + int r = alloc_register(); + fprintf(Outfile, "\tleaq\tL%d(%%rip), %s\n", id, reglist[r]); + return (r); +} + +// Add two registers together and return +// the number of the register with the result +int cgadd(int r1, int r2) { + fprintf(Outfile, "\taddq\t%s, %s\n", reglist[r1], reglist[r2]); + free_register(r1); + return (r2); +} + +// Subtract the second register from the first and +// return the number of the register with the result +int cgsub(int r1, int r2) { + fprintf(Outfile, "\tsubq\t%s, %s\n", reglist[r2], reglist[r1]); + free_register(r2); + return (r1); +} + +// Multiply two registers together and return +// the number of the register with the result +int cgmul(int r1, int r2) { + fprintf(Outfile, "\timulq\t%s, %s\n", reglist[r1], reglist[r2]); + free_register(r1); + return (r2); +} + +// Divide the first register by the second and +// return the number of the register with the result +int cgdiv(int r1, int r2) { + fprintf(Outfile, "\tmovq\t%s,%%rax\n", reglist[r1]); + fprintf(Outfile, "\tcqo\n"); + fprintf(Outfile, "\tidivq\t%s\n", reglist[r2]); + fprintf(Outfile, "\tmovq\t%%rax,%s\n", reglist[r1]); + free_register(r2); + return (r1); +} + +int cgand(int r1, int r2) { + fprintf(Outfile, "\tandq\t%s, %s\n", reglist[r1], reglist[r2]); + free_register(r1); + return (r2); +} + +int cgor(int r1, int r2) { + fprintf(Outfile, "\torq\t%s, %s\n", reglist[r1], reglist[r2]); + free_register(r1); + return (r2); +} + +int cgxor(int r1, int r2) { + fprintf(Outfile, "\txorq\t%s, %s\n", reglist[r1], reglist[r2]); + free_register(r1); + return (r2); +} + +int cgshl(int r1, int r2) { + fprintf(Outfile, "\tmovb\t%s, %%cl\n", breglist[r2]); + fprintf(Outfile, "\tshlq\t%%cl, %s\n", reglist[r1]); + free_register(r2); + return (r1); +} + +int cgshr(int r1, int r2) { + fprintf(Outfile, "\tmovb\t%s, %%cl\n", breglist[r2]); + fprintf(Outfile, "\tshrq\t%%cl, %s\n", reglist[r1]); + free_register(r2); + return (r1); +} + +// Negate a register's value +int cgnegate(int r) { + fprintf(Outfile, "\tnegq\t%s\n", reglist[r]); + return (r); +} + +// Invert a register's value +int cginvert(int r) { + fprintf(Outfile, "\tnotq\t%s\n", reglist[r]); + return (r); +} + +// Logically negate a register's value +int cglognot(int r) { + fprintf(Outfile, "\ttest\t%s, %s\n", reglist[r], reglist[r]); + fprintf(Outfile, "\tsete\t%s\n", breglist[r]); + fprintf(Outfile, "\tmovzbq\t%s, %s\n", breglist[r], reglist[r]); + return (r); +} + +// Convert an integer value to a boolean value. Jump if +// it's an IF or WHILE operation +int cgboolean(int r, int op, int label) { + fprintf(Outfile, "\ttest\t%s, %s\n", reglist[r], reglist[r]); + if (op == A_IF || op == A_WHILE) + fprintf(Outfile, "\tje\tL%d\n", label); + else { + fprintf(Outfile, "\tsetnz\t%s\n", breglist[r]); + fprintf(Outfile, "\tmovzbq\t%s, %s\n", breglist[r], reglist[r]); + } + return (r); +} + +// Call a function with the given symbol id +// Pop off any arguments pushed on the stack +// Return the register with the result +int cgcall(int id, int numargs) { + // Get a new register + int outr = alloc_register(); + // Call the function + fprintf(Outfile, "\tcall\t%s@PLT\n", Symtable[id].name); + // Remove any arguments pushed on the stack + if (numargs > 6) + fprintf(Outfile, "\taddq\t$%d, %%rsp\n", 8 * (numargs - 6)); + // and copy the return value into our register + fprintf(Outfile, "\tmovq\t%%rax, %s\n", reglist[outr]); + return (outr); +} + +// Given a register with an argument value, +// copy this argument into the argposn'th +// parameter in preparation for a future function +// call. Note that argposn is 1, 2, 3, 4, ..., never zero. +void cgcopyarg(int r, int argposn) { + + // If this is above the sixth argument, simply push the + // register on the stack. We rely on being called with + // successive arguments in the correct order for x86-64 + if (argposn > 6) { + fprintf(Outfile, "\tpushq\t%s\n", reglist[r]); + } else { + // Otherwise, copy the value into one of the six registers + // used to hold parameter values + fprintf(Outfile, "\tmovq\t%s, %s\n", reglist[r], + reglist[FIRSTPARAMREG - argposn + 1]); + } +} + +// Shift a register left by a constant +int cgshlconst(int r, int val) { + fprintf(Outfile, "\tsalq\t$%d, %s\n", val, reglist[r]); + return (r); +} + +// Store a register's value into a variable +int cgstorglob(int r, int id) { + switch (Symtable[id].type) { + case P_CHAR: + fprintf(Outfile, "\tmovb\t%s, %s(%%rip)\n", breglist[r], + Symtable[id].name); + break; + case P_INT: + fprintf(Outfile, "\tmovl\t%s, %s(%%rip)\n", dreglist[r], + Symtable[id].name); + break; + case P_LONG: + case P_CHARPTR: + case P_INTPTR: + case P_LONGPTR: + fprintf(Outfile, "\tmovq\t%s, %s(%%rip)\n", reglist[r], + Symtable[id].name); + break; + default: + fatald("Bad type in cgstorglob:", Symtable[id].type); + } + return (r); +} + +// Store a register's value into a local variable +int cgstorlocal(int r, int id) { + switch (Symtable[id].type) { + case P_CHAR: + fprintf(Outfile, "\tmovb\t%s, %d(%%rbp)\n", breglist[r], + Symtable[id].posn); + break; + case P_INT: + fprintf(Outfile, "\tmovl\t%s, %d(%%rbp)\n", dreglist[r], + Symtable[id].posn); + break; + case P_LONG: + case P_CHARPTR: + case P_INTPTR: + case P_LONGPTR: + fprintf(Outfile, "\tmovq\t%s, %d(%%rbp)\n", reglist[r], + Symtable[id].posn); + break; + default: + fatald("Bad type in cgstorlocal:", Symtable[id].type); + } + return (r); +} + +// Array of type sizes in P_XXX order. +// 0 means no size. +static int psize[] = { 0, 0, 1, 4, 8, 8, 8, 8, 8 }; + +// Given a P_XXX type value, return the +// size of a primitive type in bytes. +int cgprimsize(int type) { + // Check the type is valid + if (type < P_NONE || type > P_LONGPTR) + fatal("Bad type in cgprimsize()"); + return (psize[type]); +} + +// Generate a global symbol but not functions +void cgglobsym(int id) { + int typesize; + + if (Symtable[id].stype == S_FUNCTION) + return; + + // Get the size of the type + typesize = cgprimsize(Symtable[id].type); + + // Generate the global identity and the label + cgdataseg(); + fprintf(Outfile, "\t.globl\t%s\n", Symtable[id].name); + fprintf(Outfile, "%s:", Symtable[id].name); + + // Generate the space + for (int i = 0; i < Symtable[id].size; i++) { + switch (typesize) { + case 1: + fprintf(Outfile, "\t.byte\t0\n"); + break; + case 4: + fprintf(Outfile, "\t.long\t0\n"); + break; + case 8: + fprintf(Outfile, "\t.quad\t0\n"); + break; + default: + fatald("Unknown typesize in cgglobsym: ", typesize); + } + } +} + +// Generate a global string and its start label +void cgglobstr(int l, char *strvalue) { + char *cptr; + cglabel(l); + for (cptr = strvalue; *cptr; cptr++) { + fprintf(Outfile, "\t.byte\t%d\n", *cptr); + } + fprintf(Outfile, "\t.byte\t0\n"); +} + +// List of comparison instructions, +// in AST order: A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE +static char *cmplist[] = + { "sete", "setne", "setl", "setg", "setle", "setge" }; + +// Compare two registers and set if true. +int cgcompare_and_set(int ASTop, int r1, int r2) { + + // Check the range of the AST operation + if (ASTop < A_EQ || ASTop > A_GE) + fatal("Bad ASTop in cgcompare_and_set()"); + + fprintf(Outfile, "\tcmpq\t%s, %s\n", reglist[r2], reglist[r1]); + fprintf(Outfile, "\t%s\t%s\n", cmplist[ASTop - A_EQ], breglist[r2]); + fprintf(Outfile, "\tmovzbq\t%s, %s\n", breglist[r2], reglist[r2]); + free_register(r1); + return (r2); +} + +// Generate a label +void cglabel(int l) { + fprintf(Outfile, "L%d:\n", l); +} + +// Generate a jump to a label +void cgjump(int l) { + fprintf(Outfile, "\tjmp\tL%d\n", l); +} + +// List of inverted jump instructions, +// in AST order: A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE +static char *invcmplist[] = { "jne", "je", "jge", "jle", "jg", "jl" }; + +// Compare two registers and jump if false. +int cgcompare_and_jump(int ASTop, int r1, int r2, int label) { + + // Check the range of the AST operation + if (ASTop < A_EQ || ASTop > A_GE) + fatal("Bad ASTop in cgcompare_and_set()"); + + fprintf(Outfile, "\tcmpq\t%s, %s\n", reglist[r2], reglist[r1]); + fprintf(Outfile, "\t%s\tL%d\n", invcmplist[ASTop - A_EQ], label); + freeall_registers(); + return (NOREG); +} + +// Widen the value in the register from the old +// to the new type, and return a register with +// this new value +int cgwiden(int r, int oldtype, int newtype) { + // Nothing to do + return (r); +} + +// Generate code to return a value from a function +void cgreturn(int reg, int id) { + // Generate code depending on the function's type + switch (Symtable[id].type) { + case P_CHAR: + fprintf(Outfile, "\tmovzbl\t%s, %%eax\n", breglist[reg]); + break; + case P_INT: + fprintf(Outfile, "\tmovl\t%s, %%eax\n", dreglist[reg]); + break; + case P_LONG: + fprintf(Outfile, "\tmovq\t%s, %%rax\n", reglist[reg]); + break; + default: + fatald("Bad function type in cgreturn:", Symtable[id].type); + } + cgjump(Symtable[id].endlabel); +} + + +// Generate code to load the address of an +// identifier into a variable. Return a new register +int cgaddress(int id) { + int r = alloc_register(); + + if (Symtable[id].class == C_LOCAL) + fprintf(Outfile, "\tleaq\t%d(%%rbp), %s\n", Symtable[id].posn, + reglist[r]); + else + fprintf(Outfile, "\tleaq\t%s(%%rip), %s\n", Symtable[id].name, + reglist[r]); + return (r); +} + +// Dereference a pointer to get the value it +// pointing at into the same register +int cgderef(int r, int type) { + switch (type) { + case P_CHARPTR: + fprintf(Outfile, "\tmovzbq\t(%s), %s\n", reglist[r], reglist[r]); + break; + case P_INTPTR: + fprintf(Outfile, "\tmovslq\t(%s), %s\n", reglist[r], reglist[r]); + break; + case P_LONGPTR: + fprintf(Outfile, "\tmovq\t(%s), %s\n", reglist[r], reglist[r]); + break; + default: + fatald("Can't cgderef on type:", type); + } + return (r); +} + +// Store through a dereferenced pointer +int cgstorderef(int r1, int r2, int type) { + switch (type) { + case P_CHAR: + fprintf(Outfile, "\tmovb\t%s, (%s)\n", breglist[r1], reglist[r2]); + break; + case P_INT: + fprintf(Outfile, "\tmovq\t%s, (%s)\n", reglist[r1], reglist[r2]); + break; + case P_LONG: + fprintf(Outfile, "\tmovq\t%s, (%s)\n", reglist[r1], reglist[r2]); + break; + default: + fatald("Can't cgstoderef on type:", type); + } + return (r1); +} diff --git a/27_Testing_Errors/cg_arm.c b/27_Testing_Errors/cg_arm.c new file mode 100644 index 0000000..178e668 --- /dev/null +++ b/27_Testing_Errors/cg_arm.c @@ -0,0 +1,388 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Code generator for ARMv6 on Raspberry Pi +// Copyright (c) 2019 Warren Toomey, GPL3 + + +// List of available registers and their names. +static int freereg[4]; +static char *reglist[4] = { "r4", "r5", "r6", "r7" }; + +// Set all registers as available +void freeall_registers(void) { + freereg[0] = freereg[1] = freereg[2] = freereg[3] = 1; +} + +// Allocate a free register. Return the number of +// the register. Die if no available registers. +static int alloc_register(void) { + for (int i = 0; i < 4; i++) { + if (freereg[i]) { + freereg[i] = 0; + return (i); + } + } + fatal("Out of registers"); + return (NOREG); // Keep -Wall happy +} + +// Return a register to the list of available registers. +// Check to see if it's not already there. +static void free_register(int reg) { + if (freereg[reg] != 0) + fatald("Error trying to free register", reg); + freereg[reg] = 1; +} + +// We have to store large integer literal values in memory. +// Keep a list of them which will be output in the postamble +#define MAXINTS 1024 +int Intlist[MAXINTS]; +static int Intslot = 0; + +// Determine the offset of a large integer +// literal from the .L3 label. If the integer +// isn't in the list, add it. +static void set_int_offset(int val) { + int offset = -1; + + // See if it is already there + for (int i = 0; i < Intslot; i++) { + if (Intlist[i] == val) { + offset = 4 * i; + break; + } + } + + // Not in the list, so add it + if (offset == -1) { + offset = 4 * Intslot; + if (Intslot == MAXINTS) + fatal("Out of int slots in set_int_offset()"); + Intlist[Intslot++] = val; + } + // Load r3 with this offset + fprintf(Outfile, "\tldr\tr3, .L3+%d\n", offset); +} + +// Print out the assembly preamble +void cgpreamble() { + freeall_registers(); + fputs("\t.text\n", Outfile); +} + +// Print out the assembly postamble +void cgpostamble() { + + // Print out the global variables + fprintf(Outfile, ".L2:\n"); + for (int i = 0; i < Globs; i++) { + if (Symtable[i].stype == S_VARIABLE) + fprintf(Outfile, "\t.word %s\n", Symtable[i].name); + } + + // Print out the integer literals + fprintf(Outfile, ".L3:\n"); + for (int i = 0; i < Intslot; i++) { + fprintf(Outfile, "\t.word %d\n", Intlist[i]); + } +} + +// Print out a function preamble +void cgfuncpreamble(int id) { + char *name = Symtable[id].name; + fprintf(Outfile, + "\t.text\n" + "\t.globl\t%s\n" + "\t.type\t%s, \%%function\n" + "%s:\n" "\tpush\t{fp, lr}\n" + "\tadd\tfp, sp, #4\n" + "\tsub\tsp, sp, #8\n" "\tstr\tr0, [fp, #-8]\n", name, name, name); +} + +// Print out a function postamble +void cgfuncpostamble(int id) { + cglabel(Symtable[id].endlabel); + fputs("\tsub\tsp, fp, #4\n" "\tpop\t{fp, pc}\n" "\t.align\t2\n", Outfile); +} + +// Load an integer literal value into a register. +// Return the number of the register. +int cgloadint(int value, int type) { + // Get a new register + int r = alloc_register(); + + // If the literal value is small, do it with one instruction + if (value <= 1000) + fprintf(Outfile, "\tmov\t%s, #%d\n", reglist[r], value); + else { + set_int_offset(value); + fprintf(Outfile, "\tldr\t%s, [r3]\n", reglist[r]); + } + return (r); +} + +// Determine the offset of a variable from the .L2 +// label. Yes, this is inefficient code. +static void set_var_offset(int id) { + int offset = 0; + // Walk the symbol table up to id. + // Find S_VARIABLEs and add on 4 until + // we get to our variable + + for (int i = 0; i < id; i++) { + if (Symtable[i].stype == S_VARIABLE) + offset += 4; + } + // Load r3 with this offset + fprintf(Outfile, "\tldr\tr3, .L2+%d\n", offset); +} + + +// Load a value from a variable into a register. +// Return the number of the register +int cgloadglob(int id) { + // Get a new register + int r = alloc_register(); + + // Get the offset to the variable + set_var_offset(id); + + switch (Symtable[id].type) { + case P_CHAR: + fprintf(Outfile, "\tldrb\t%s, [r3]\n", reglist[r]); + break; + case P_INT: + case P_LONG: + case P_CHARPTR: + case P_INTPTR: + case P_LONGPTR: + fprintf(Outfile, "\tldr\t%s, [r3]\n", reglist[r]); + break; + default: + fatald("Bad type in cgloadglob:", Symtable[id].type); + } + return (r); +} + +// Add two registers together and return +// the number of the register with the result +int cgadd(int r1, int r2) { + fprintf(Outfile, "\tadd\t%s, %s, %s\n", reglist[r2], reglist[r1], + reglist[r2]); + free_register(r1); + return (r2); +} + +// Subtract the second register from the first and +// return the number of the register with the result +int cgsub(int r1, int r2) { + fprintf(Outfile, "\tsub\t%s, %s, %s\n", reglist[r1], reglist[r1], + reglist[r2]); + free_register(r2); + return (r1); +} + +// Multiply two registers together and return +// the number of the register with the result +int cgmul(int r1, int r2) { + fprintf(Outfile, "\tmul\t%s, %s, %s\n", reglist[r2], reglist[r1], + reglist[r2]); + free_register(r1); + return (r2); +} + +// Divide the first register by the second and +// return the number of the register with the result +int cgdiv(int r1, int r2) { + + // To do a divide: r0 holds the dividend, r1 holds the divisor. + // The quotient is in r0. + fprintf(Outfile, "\tmov\tr0, %s\n", reglist[r1]); + fprintf(Outfile, "\tmov\tr1, %s\n", reglist[r2]); + fprintf(Outfile, "\tbl\t__aeabi_idiv\n"); + fprintf(Outfile, "\tmov\t%s, r0\n", reglist[r1]); + free_register(r2); + return (r1); +} + +// Call a function with one argument from the given register +// Return the register with the result +int cgcall(int r, int id) { + fprintf(Outfile, "\tmov\tr0, %s\n", reglist[r]); + fprintf(Outfile, "\tbl\t%s\n", Symtable[id].name); + fprintf(Outfile, "\tmov\t%s, r0\n", reglist[r]); + return (r); +} + +// Shift a register left by a constant +int cgshlconst(int r, int val) { + fprintf(Outfile, "\tlsl\t%s, %s, #%d\n", reglist[r], reglist[r], val); + return (r); +} + +// Store a register's value into a variable +int cgstorglob(int r, int id) { + + // Get the offset to the variable + set_var_offset(id); + + switch (Symtable[id].type) { + case P_CHAR: + fprintf(Outfile, "\tstrb\t%s, [r3]\n", reglist[r]); + break; + case P_INT: + case P_LONG: + case P_CHARPTR: + case P_INTPTR: + case P_LONGPTR: + fprintf(Outfile, "\tstr\t%s, [r3]\n", reglist[r]); + break; + default: + fatald("Bad type in cgstorglob:", Symtable[id].type); + } + return (r); +} + +// Array of type sizes in P_XXX order. +// 0 means no size. +static int psize[] = { 0, 0, 1, 4, 4, 4, 4, 4 }; + +// Given a P_XXX type value, return the +// size of a primitive type in bytes. +int cgprimsize(int type) { + // Check the type is valid + if (type < P_NONE || type > P_LONGPTR) + fatal("Bad type in cgprimsize()"); + return (psize[type]); +} + +// Generate a global symbol +void cgglobsym(int id) { + int typesize; + // Get the size of the type + typesize = cgprimsize(Symtable[id].type); + + fprintf(Outfile, "\t.data\n" "\t.globl\t%s\n", Symtable[id].name); + switch (typesize) { + case 1: + fprintf(Outfile, "%s:\t.byte\t0\n", Symtable[id].name); + break; + case 4: + fprintf(Outfile, "%s:\t.long\t0\n", Symtable[id].name); + break; + default: + fatald("Unknown typesize in cgglobsym: ", typesize); + } +} + +// List of comparison instructions, +// in AST order: A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE +static char *cmplist[] = + { "moveq", "movne", "movlt", "movgt", "movle", "movge" }; + +// List of inverted jump instructions, +// in AST order: A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE +static char *invcmplist[] = + { "movne", "moveq", "movge", "movle", "movgt", "movlt" }; + +// Compare two registers and set if true. +int cgcompare_and_set(int ASTop, int r1, int r2) { + + // Check the range of the AST operation + if (ASTop < A_EQ || ASTop > A_GE) + fatal("Bad ASTop in cgcompare_and_set()"); + + fprintf(Outfile, "\tcmp\t%s, %s\n", reglist[r1], reglist[r2]); + fprintf(Outfile, "\t%s\t%s, #1\n", cmplist[ASTop - A_EQ], reglist[r2]); + fprintf(Outfile, "\t%s\t%s, #0\n", invcmplist[ASTop - A_EQ], reglist[r2]); + fprintf(Outfile, "\tuxtb\t%s, %s\n", reglist[r2], reglist[r2]); + free_register(r1); + return (r2); +} + +// Generate a label +void cglabel(int l) { + fprintf(Outfile, "L%d:\n", l); +} + +// Generate a jump to a label +void cgjump(int l) { + fprintf(Outfile, "\tb\tL%d\n", l); +} + +// List of inverted branch instructions, +// in AST order: A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE +static char *brlist[] = { "bne", "beq", "bge", "ble", "bgt", "blt" }; + +// Compare two registers and jump if false. +int cgcompare_and_jump(int ASTop, int r1, int r2, int label) { + + // Check the range of the AST operation + if (ASTop < A_EQ || ASTop > A_GE) + fatal("Bad ASTop in cgcompare_and_set()"); + + fprintf(Outfile, "\tcmp\t%s, %s\n", reglist[r1], reglist[r2]); + fprintf(Outfile, "\t%s\tL%d\n", brlist[ASTop - A_EQ], label); + freeall_registers(); + return (NOREG); +} + +// Widen the value in the register from the old +// to the new type, and return a register with +// this new value +int cgwiden(int r, int oldtype, int newtype) { + // Nothing to do + return (r); +} + +// Generate code to return a value from a function +void cgreturn(int reg, int id) { + fprintf(Outfile, "\tmov\tr0, %s\n", reglist[reg]); + cgjump(Symtable[id].endlabel); +} + +// Generate code to load the address of a global +// identifier into a variable. Return a new register +int cgaddress(int id) { + // Get a new register + int r = alloc_register(); + + // Get the offset to the variable + set_var_offset(id); + fprintf(Outfile, "\tmov\t%s, r3\n", reglist[r]); + return (r); +} + +// Dereference a pointer to get the value it +// pointing at into the same register +int cgderef(int r, int type) { + switch (type) { + case P_CHARPTR: + fprintf(Outfile, "\tldrb\t%s, [%s]\n", reglist[r], reglist[r]); + break; + case P_INTPTR: + case P_LONGPTR: + fprintf(Outfile, "\tldr\t%s, [%s]\n", reglist[r], reglist[r]); + break; + } + return (r); +} + +// Store through a dereferenced pointer +int cgstorderef(int r1, int r2, int type) { + switch (type) { + case P_CHAR: + fprintf(Outfile, "\tstrb\t%s, [%s]\n", reglist[r1], reglist[r2]); + break; + case P_INT: + case P_LONG: + fprintf(Outfile, "\tstr\t%s, [%s]\n", reglist[r1], reglist[r2]); + break; + default: + fatald("Can't cgstoderef on type:", type); + } + return (r1); +} diff --git a/27_Testing_Errors/data.h b/27_Testing_Errors/data.h new file mode 100644 index 0000000..22746c1 --- /dev/null +++ b/27_Testing_Errors/data.h @@ -0,0 +1,19 @@ +#ifndef extern_ +#define extern_ extern +#endif + +// Global variables +// Copyright (c) 2019 Warren Toomey, GPL3 + +extern_ int Line; // Current line number +extern_ int Putback; // Character put back by scanner +extern_ int Functionid; // Symbol id of the current function +extern_ int Globs; // Position of next free global symbol slot +extern_ int Locls; // Position of next free local symbol slot +extern_ FILE *Infile; // Input and output files +extern_ FILE *Outfile; +extern_ struct token Token; // Last token scanned +extern_ char Text[TEXTLEN + 1]; // Last identifier scanned +extern_ struct symtable Symtable[NSYMBOLS]; // Global symbol table + +extern_ int O_dumpAST; diff --git a/27_Testing_Errors/decl.c b/27_Testing_Errors/decl.c new file mode 100644 index 0000000..65b409d --- /dev/null +++ b/27_Testing_Errors/decl.c @@ -0,0 +1,263 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Parsing of declarations +// Copyright (c) 2019 Warren Toomey, GPL3 + + +// Parse the current token and return +// a primitive type enum value. Also +// scan in the next token +int parse_type(void) { + int type; + switch (Token.token) { + case T_VOID: + type = P_VOID; + break; + case T_CHAR: + type = P_CHAR; + break; + case T_INT: + type = P_INT; + break; + case T_LONG: + type = P_LONG; + break; + default: + fatald("Illegal type, token", Token.token); + } + + // Scan in one or more further '*' tokens + // and determine the correct pointer type + while (1) { + scan(&Token); + if (Token.token != T_STAR) + break; + type = pointer_to(type); + } + + // We leave with the next token already scanned + return (type); +} + +// variable_declaration: type identifier ';' +// | type identifier '[' INTLIT ']' ';' +// ; +// +// Parse the declaration of a scalar variable or an array +// with a given size. +// The identifier has been scanned & we have the type. +// class is the variable's class +void var_declaration(int type, int class) { + + // Text now has the identifier's name. + // If the next token is a '[' + if (Token.token == T_LBRACKET) { + // Skip past the '[' + scan(&Token); + + // Check we have an array size + if (Token.token == T_INTLIT) { + // Add this as a known array and generate its space in assembly. + // We treat the array as a pointer to its elements' type + if (class == C_LOCAL) { + fatal("For now, declaration of local arrays is not implemented"); + } else { + addglob(Text, pointer_to(type), S_ARRAY, class, 0, Token.intvalue); + } + } + // Ensure we have a following ']' + scan(&Token); + match(T_RBRACKET, "]"); + } else { + // Add this as a known scalar + // and generate its space in assembly + if (class == C_LOCAL) { + if (addlocl(Text, type, S_VARIABLE, class, 1) == -1) + fatals("Duplicate local variable declaration", Text); + } else { + addglob(Text, type, S_VARIABLE, class, 0, 1); + } + } +} + +// param_declaration: +// | variable_declaration +// | variable_declaration ',' param_declaration +// +// Parse the parameters in parentheses after the function name. +// Add them as symbols to the symbol table and return the number +// of parameters. If id is not -1, there is an existing function +// prototype, and the function has this symbol slot number. +static int param_declaration(int id) { + int type, param_id; + int orig_paramcnt; + int paramcnt = 0; + + // Add 1 to id so that it's either zero (no prototype), or + // it's the position of the zeroth existing parameter in + // the symbol table + param_id = id + 1; + + // Get any existing prototype parameter count + if (param_id) + orig_paramcnt = Symtable[id].nelems; + + // Loop until the final right parentheses + while (Token.token != T_RPAREN) { + // Get the type and identifier + // and add it to the symbol table + type = parse_type(); + ident(); + + // We have an existing prototype. + // Check that this type matches the prototype. + if (param_id) { + if (type != Symtable[id].type) + fatald("Type doesn't match prototype for parameter", paramcnt + 1); + param_id++; + } else { + // Add a new parameter to the new prototype + var_declaration(type, C_PARAM); + } + paramcnt++; + + // Must have a ',' or ')' at this point + switch (Token.token) { + case T_COMMA: + scan(&Token); + break; + case T_RPAREN: + break; + default: + fatald("Unexpected token in parameter list", Token.token); + } + } + + // Check that the number of parameters in this list matches + // any existing prototype + if ((id != -1) && (paramcnt != orig_paramcnt)) + fatals("Parameter count mismatch for function", Symtable[id].name); + + // Return the count of parameters + return (paramcnt); +} + +// +// function_declaration: type identifier '(' parameter_list ')' ; +// | type identifier '(' parameter_list ')' compound_statement ; +// +// Parse the declaration of function. +// The identifier has been scanned & we have the type. +struct ASTnode *function_declaration(int type) { + struct ASTnode *tree, *finalstmt; + int id; + int nameslot, endlabel, paramcnt; + + // Text has the identifier's name. If this exists and is a + // function, get the id. Otherwise, set id to -1 + if ((id = findsymbol(Text)) != -1) + if (Symtable[id].stype != S_FUNCTION) + id = -1; + + // If this is a new function declaration, get a + // label-id for the end label, and add the function + // to the symbol table, + if (id == -1) { + endlabel = genlabel(); + nameslot = addglob(Text, type, S_FUNCTION, C_GLOBAL, endlabel, 0); + } + // Scan in the '(', any parameters and the ')'. + // Pass in any existing function prototype symbol slot number + lparen(); + paramcnt = param_declaration(id); + rparen(); + + // If this is a new function declaration, update the + // function symbol entry with the number of parameters + if (id == -1) + Symtable[nameslot].nelems = paramcnt; + + // Declaration ends in a semicolon, only a prototype. + if (Token.token == T_SEMI) { + scan(&Token); + return (NULL); + } + // This is not just a prototype. + // Copy the global parameters to be local parameters + if (id == -1) + id = nameslot; + copyfuncparams(id); + + // Set the Functionid global to the function's symbol-id + Functionid = id; + + // Get the AST tree for the compound statement + tree = compound_statement(); + + // If the function type isn't P_VOID .. + if (type != P_VOID) { + + // Error if no statements in the function + if (tree == NULL) + fatal("No statements in function with non-void type"); + + // Check that the last AST operation in the + // compound statement was a return statement + finalstmt = (tree->op == A_GLUE) ? tree->right : tree; + if (finalstmt == NULL || finalstmt->op != A_RETURN) + fatal("No return for function with non-void type"); + } + // Return an A_FUNCTION node which has the function's id + // and the compound statement sub-tree + return (mkuastunary(A_FUNCTION, type, tree, id)); +} + + +// Parse one or more global declarations, either +// variables or functions +void global_declarations(void) { + struct ASTnode *tree; + int type; + + while (1) { + + // We have to read past the type and identifier + // to see either a '(' for a function declaration + // or a ',' or ';' for a variable declaration. + // Text is filled in by the ident() call. + type = parse_type(); + ident(); + if (Token.token == T_LPAREN) { + + // Parse the function declaration + tree = function_declaration(type); + + // Only a function prototype, no code + if (tree == NULL) + continue; + + // A real function, generate the assembly code for it + if (O_dumpAST) { + dumpAST(tree, NOLABEL, 0); + fprintf(stdout, "\n\n"); + } + genAST(tree, NOLABEL, 0); + + // Now free the symbols associated + // with this function + freeloclsyms(); + } else { + + // Parse the global variable declaration + // and skip past the trailing semicolon + var_declaration(type, C_GLOBAL); + semi(); + } + + // Stop when we have reached EOF + if (Token.token == T_EOF) + break; + } +} diff --git a/27_Testing_Errors/decl.h b/27_Testing_Errors/decl.h new file mode 100644 index 0000000..f82d5d1 --- /dev/null +++ b/27_Testing_Errors/decl.h @@ -0,0 +1,110 @@ +// Function prototypes for all compiler files +// Copyright (c) 2019 Warren Toomey, GPL3 + +// scan.c +void reject_token(struct token *t); +int scan(struct token *t); + +// tree.c +struct ASTnode *mkastnode(int op, int type, + struct ASTnode *left, + struct ASTnode *mid, + struct ASTnode *right, int intvalue); +struct ASTnode *mkastleaf(int op, int type, int intvalue); +struct ASTnode *mkuastunary(int op, int type, + struct ASTnode *left, int intvalue); +void dumpAST(struct ASTnode *n, int label, int parentASTop); + +// gen.c +int genlabel(void); +int genAST(struct ASTnode *n, int reg, int parentASTop); +void genpreamble(); +void genpostamble(); +void genfreeregs(); +void genglobsym(int id); +int genglobstr(char *strvalue); +int genprimsize(int type); +void genreturn(int reg, int id); + +// cg.c +void cgtextseg(); +void cgdataseg(); +void freeall_registers(void); +void cgpreamble(); +void cgpostamble(); +void cgfuncpreamble(int id); +void cgfuncpostamble(int id); +int cgloadint(int value, int type); +int cgloadglob(int id, int op); +int cgloadlocal(int id, int op); +int cgloadglobstr(int id); +int cgadd(int r1, int r2); +int cgsub(int r1, int r2); +int cgmul(int r1, int r2); +int cgdiv(int r1, int r2); +int cgshlconst(int r, int val); +int cgcall(int id, int numargs); +void cgcopyarg(int r, int argposn); +int cgstorglob(int r, int id); +int cgstorlocal(int r, int id); +void cgglobsym(int id); +void cgglobstr(int l, char *strvalue); +int cgcompare_and_set(int ASTop, int r1, int r2); +int cgcompare_and_jump(int ASTop, int r1, int r2, int label); +void cglabel(int l); +void cgjump(int l); +int cgwiden(int r, int oldtype, int newtype); +int cgprimsize(int type); +void cgreturn(int reg, int id); +int cgaddress(int id); +int cgderef(int r, int type); +int cgstorderef(int r1, int r2, int type); +int cgnegate(int r); +int cginvert(int r); +int cglognot(int r); +int cgboolean(int r, int op, int label); +int cgand(int r1, int r2); +int cgor(int r1, int r2); +int cgxor(int r1, int r2); +int cgshl(int r1, int r2); +int cgshr(int r1, int r2); + +// expr.c +struct ASTnode *binexpr(int ptp); + +// stmt.c +struct ASTnode *compound_statement(void); + +// misc.c +void match(int t, char *what); +void semi(void); +void lbrace(void); +void rbrace(void); +void lparen(void); +void rparen(void); +void ident(void); +void fatal(char *s); +void fatals(char *s1, char *s2); +void fatald(char *s, int d); +void fatalc(char *s, int c); + +// sym.c +int findglob(char *s); +int findlocl(char *s); +int findsymbol(char *s); +int addglob(char *name, int type, int stype, int class, int endlabel, int size); +int addlocl(char *name, int type, int stype, int class, int size); +void copyfuncparams(int slot); +void freeloclsyms(void); + +// decl.c +void var_declaration(int type, int class); +struct ASTnode *function_declaration(int type); +void global_declarations(void); + +// types.c +int inttype(int type); +int parse_type(void); +int pointer_to(int type); +int value_at(int type); +struct ASTnode *modify_type(struct ASTnode *tree, int rtype, int op); diff --git a/27_Testing_Errors/defs.h b/27_Testing_Errors/defs.h new file mode 100644 index 0000000..5366a37 --- /dev/null +++ b/27_Testing_Errors/defs.h @@ -0,0 +1,108 @@ +#include +#include +#include +#include + +// Structure and enum definitions +// Copyright (c) 2019 Warren Toomey, GPL3 + +#define TEXTLEN 512 // Length of symbols in input +#define NSYMBOLS 1024 // Number of symbol table entries + +// Token types +enum { + T_EOF, + + // Binary operators + T_ASSIGN, T_LOGOR, T_LOGAND, + T_OR, T_XOR, T_AMPER, + T_EQ, T_NE, + T_LT, T_GT, T_LE, T_GE, + T_LSHIFT, T_RSHIFT, + T_PLUS, T_MINUS, T_STAR, T_SLASH, + + // Other operators + T_INC, T_DEC, T_INVERT, T_LOGNOT, + + // Type keywords + T_VOID, T_CHAR, T_INT, T_LONG, + + // Other keywords + T_IF, T_ELSE, T_WHILE, T_FOR, T_RETURN, + + // Structural tokens + T_INTLIT, T_STRLIT, T_SEMI, T_IDENT, + T_LBRACE, T_RBRACE, T_LPAREN, T_RPAREN, + T_LBRACKET, T_RBRACKET, T_COMMA +}; + +// Token structure +struct token { + int token; // Token type, from the enum list above + int intvalue; // For T_INTLIT, the integer value +}; + +// AST node types. The first few line up +// with the related tokens +enum { + A_ASSIGN= 1, A_LOGOR, A_LOGAND, A_OR, A_XOR, A_AND, + A_EQ, A_NE, A_LT, A_GT, A_LE, A_GE, A_LSHIFT, A_RSHIFT, + A_ADD, A_SUBTRACT, A_MULTIPLY, A_DIVIDE, + A_INTLIT, A_STRLIT, A_IDENT, A_GLUE, + A_IF, A_WHILE, A_FUNCTION, A_WIDEN, A_RETURN, + A_FUNCCALL, A_DEREF, A_ADDR, A_SCALE, + A_PREINC, A_PREDEC, A_POSTINC, A_POSTDEC, + A_NEGATE, A_INVERT, A_LOGNOT, A_TOBOOL +}; + +// Primitive types +enum { + P_NONE, P_VOID, P_CHAR, P_INT, P_LONG, + P_VOIDPTR, P_CHARPTR, P_INTPTR, P_LONGPTR +}; + +// Abstract Syntax Tree structure +struct ASTnode { + int op; // "Operation" to be performed on this tree + int type; // Type of any expression this tree generates + int rvalue; // True if the node is an rvalue + struct ASTnode *left; // Left, middle and right child trees + struct ASTnode *mid; + struct ASTnode *right; + union { // For A_INTLIT, the integer value + int intvalue; // For A_IDENT, the symbol slot number + int id; // For A_FUNCTION, the symbol slot number + int size; // For A_SCALE, the size to scale by + } v; // For A_FUNCCALL, the symbol slot number +}; + +#define NOREG -1 // Use NOREG when the AST generation + // functions have no register to return +#define NOLABEL 0 // Use NOLABEL when we have no label to + // pass to genAST() + +// Structural types +enum { + S_VARIABLE, S_FUNCTION, S_ARRAY +}; + +// Storage classes +enum { + C_GLOBAL = 1, // Globally visible symbol + C_LOCAL, // Locally visible symbol + C_PARAM // Locally visible function parameter +}; + +// Symbol table structure +struct symtable { + char *name; // Name of a symbol + int type; // Primitive type for the symbol + int stype; // Structural type for the symbol + int class; // Storage class for the symbol + int endlabel; // For S_FUNCTIONs, the end label + int size; // Number of elements in the symbol + int posn; // For locals, either the negative offset + // from stack base pointer, or register id +#define nelems posn // For functions, # of params + // For structs, # of fields +}; diff --git a/27_Testing_Errors/expr.c b/27_Testing_Errors/expr.c new file mode 100644 index 0000000..ede3c67 --- /dev/null +++ b/27_Testing_Errors/expr.c @@ -0,0 +1,438 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Parsing of expressions +// Copyright (c) 2019 Warren Toomey, GPL3 + +// expression_list: +// | expression +// | expression ',' expression_list +// ; + +// Parse a list of zero or more comma-separated expressions and +// return an AST composed of A_GLUE nodes with the left-hand child +// being the sub-tree of previous expressions (or NULL) and the right-hand +// child being the next expression. Each A_GLUE node will have size field +// set to the number of expressions in the tree at this point. If no +// expressions are parsed, NULL is returned +static struct ASTnode *expression_list(void) { + struct ASTnode *tree = NULL; + struct ASTnode *child = NULL; + int exprcount = 0; + + // Loop until the final right parentheses + while (Token.token != T_RPAREN) { + + // Parse the next expression and increment the expression count + child = binexpr(0); + exprcount++; + + // Build an A_GLUE AST node with the previous tree as the left child + // and the new expression as the right child. Store the expression count. + tree = mkastnode(A_GLUE, P_NONE, tree, NULL, child, exprcount); + + // Must have a ',' or ')' at this point + switch (Token.token) { + case T_COMMA: + scan(&Token); + break; + case T_RPAREN: + break; + default: + fatald("Unexpected token in expression list", Token.token); + } + } + + // Return the tree of expressions + return (tree); +} + +// Parse a function call and return its AST +static struct ASTnode *funccall(void) { + struct ASTnode *tree; + int id; + + // Check that the identifier has been defined as a function, + // then make a leaf node for it. + if ((id = findsymbol(Text)) == -1 || Symtable[id].stype != S_FUNCTION) { + fatals("Undeclared function", Text); + } + // Get the '(' + lparen(); + + // Parse the argument expression list + tree = expression_list(); + + // XXX Check type of each argument against the function's prototype + + // Build the function call AST node. Store the + // function's return type as this node's type. + // Also record the function's symbol-id + tree = mkuastunary(A_FUNCCALL, Symtable[id].type, tree, id); + + // Get the ')' + rparen(); + return (tree); +} + +// Parse the index into an array and return an AST tree for it +static struct ASTnode *array_access(void) { + struct ASTnode *left, *right; + int id; + + // Check that the identifier has been defined as an array + // then make a leaf node for it that points at the base + if ((id = findsymbol(Text)) == -1 || Symtable[id].stype != S_ARRAY) { + fatals("Undeclared array", Text); + } + left = mkastleaf(A_ADDR, Symtable[id].type, id); + + // Get the '[' + scan(&Token); + + // Parse the following expression + right = binexpr(0); + + // Get the ']' + match(T_RBRACKET, "]"); + + // Ensure that this is of int type + if (!inttype(right->type)) + fatal("Array index is not of integer type"); + + // Scale the index by the size of the element's type + right = modify_type(right, left->type, A_ADD); + + // Return an AST tree where the array's base has the offset + // added to it, and dereference the element. Still an lvalue + // at this point. + left = mkastnode(A_ADD, Symtable[id].type, left, NULL, right, 0); + left = mkuastunary(A_DEREF, value_at(left->type), left, 0); + return (left); +} + +// Parse a postfix expression and return +// an AST node representing it. The +// identifier is already in Text. +static struct ASTnode *postfix(void) { + struct ASTnode *n; + int id; + + // Scan in the next token to see if we have a postfix expression + scan(&Token); + + // Function call + if (Token.token == T_LPAREN) + return (funccall()); + + // An array reference + if (Token.token == T_LBRACKET) + return (array_access()); + + // A variable. Check that the variable exists. + id = findsymbol(Text); + if (id == -1 || Symtable[id].stype != S_VARIABLE) + fatals("Unknown variable", Text); + + switch (Token.token) { + // Post-increment: skip over the token + case T_INC: + scan(&Token); + n = mkastleaf(A_POSTINC, Symtable[id].type, id); + break; + + // Post-decrement: skip over the token + case T_DEC: + scan(&Token); + n = mkastleaf(A_POSTDEC, Symtable[id].type, id); + break; + + // Just a variable reference + default: + n = mkastleaf(A_IDENT, Symtable[id].type, id); + } + return (n); +} + +// Parse a primary factor and return an +// AST node representing it. +static struct ASTnode *primary(void) { + struct ASTnode *n; + int id; + + switch (Token.token) { + case T_INTLIT: + // For an INTLIT token, make a leaf AST node for it. + // Make it a P_CHAR if it's within the P_CHAR range + if ((Token.intvalue) >= 0 && (Token.intvalue < 256)) + n = mkastleaf(A_INTLIT, P_CHAR, Token.intvalue); + else + n = mkastleaf(A_INTLIT, P_INT, Token.intvalue); + break; + + case T_STRLIT: + // For a STRLIT token, generate the assembly for it. + // Then make a leaf AST node for it. id is the string's label. + id = genglobstr(Text); + n = mkastleaf(A_STRLIT, P_CHARPTR, id); + break; + + case T_IDENT: + return (postfix()); + + case T_LPAREN: + // Beginning of a parenthesised expression, skip the '('. + // Scan in the expression and the right parenthesis + scan(&Token); + n = binexpr(0); + rparen(); + return (n); + + default: + fatald("Expecting a primary expression, got token", Token.token); + } + + // Scan in the next token and return the leaf node + scan(&Token); + return (n); +} + +// Convert a binary operator token into a binary AST operation. +// We rely on a 1:1 mapping from token to AST operation +static int binastop(int tokentype) { + if (tokentype > T_EOF && tokentype <= T_SLASH) + return (tokentype); + fatald("Syntax error, token", tokentype); + return (0); // Keep -Wall happy +} + +// Return true if a token is right-associative, +// false otherwise. +static int rightassoc(int tokentype) { + if (tokentype == T_ASSIGN) + return (1); + return (0); +} + +// Operator precedence for each token. Must +// match up with the order of tokens in defs.h +static int OpPrec[] = { + 0, 10, 20, 30, // T_EOF, T_ASSIGN, T_LOGOR, T_LOGAND + 40, 50, 60, // T_OR, T_XOR, T_AMPER + 70, 70, // T_EQ, T_NE + 80, 80, 80, 80, // T_LT, T_GT, T_LE, T_GE + 90, 90, // T_LSHIFT, T_RSHIFT + 100, 100, // T_PLUS, T_MINUS + 110, 110 // T_STAR, T_SLASH +}; + +// Check that we have a binary operator and +// return its precedence. +static int op_precedence(int tokentype) { + int prec; + if (tokentype > T_SLASH) + fatald("Token with no precedence in op_precedence:", tokentype); + prec = OpPrec[tokentype]; + if (prec == 0) + fatald("Syntax error, token", tokentype); + return (prec); +} + +// prefix_expression: primary +// | '*' prefix_expression +// | '&' prefix_expression +// | '-' prefix_expression +// | '++' prefix_expression +// | '--' prefix_expression +// ; + +// Parse a prefix expression and return +// a sub-tree representing it. +struct ASTnode *prefix(void) { + struct ASTnode *tree; + switch (Token.token) { + case T_AMPER: + // Get the next token and parse it + // recursively as a prefix expression + scan(&Token); + tree = prefix(); + + // Ensure that it's an identifier + if (tree->op != A_IDENT) + fatal("& operator must be followed by an identifier"); + + // Now change the operator to A_ADDR and the type to + // a pointer to the original type + tree->op = A_ADDR; + tree->type = pointer_to(tree->type); + break; + case T_STAR: + // Get the next token and parse it + // recursively as a prefix expression + scan(&Token); + tree = prefix(); + + // For now, ensure it's either another deref or an + // identifier + if (tree->op != A_IDENT && tree->op != A_DEREF) + fatal("* operator must be followed by an identifier or *"); + + // Prepend an A_DEREF operation to the tree + tree = mkuastunary(A_DEREF, value_at(tree->type), tree, 0); + break; + case T_MINUS: + // Get the next token and parse it + // recursively as a prefix expression + scan(&Token); + tree = prefix(); + + // Prepend a A_NEGATE operation to the tree and + // make the child an rvalue. Because chars are unsigned, + // also widen this to int so that it's signed + tree->rvalue = 1; + tree = modify_type(tree, P_INT, 0); + tree = mkuastunary(A_NEGATE, tree->type, tree, 0); + break; + case T_INVERT: + // Get the next token and parse it + // recursively as a prefix expression + scan(&Token); + tree = prefix(); + + // Prepend a A_INVERT operation to the tree and + // make the child an rvalue. + tree->rvalue = 1; + tree = mkuastunary(A_INVERT, tree->type, tree, 0); + break; + case T_LOGNOT: + // Get the next token and parse it + // recursively as a prefix expression + scan(&Token); + tree = prefix(); + + // Prepend a A_LOGNOT operation to the tree and + // make the child an rvalue. + tree->rvalue = 1; + tree = mkuastunary(A_LOGNOT, tree->type, tree, 0); + break; + case T_INC: + // Get the next token and parse it + // recursively as a prefix expression + scan(&Token); + tree = prefix(); + + // For now, ensure it's an identifier + if (tree->op != A_IDENT) + fatal("++ operator must be followed by an identifier"); + + // Prepend an A_PREINC operation to the tree + tree = mkuastunary(A_PREINC, tree->type, tree, 0); + break; + case T_DEC: + // Get the next token and parse it + // recursively as a prefix expression + scan(&Token); + tree = prefix(); + + // For now, ensure it's an identifier + if (tree->op != A_IDENT) + fatal("-- operator must be followed by an identifier"); + + // Prepend an A_PREDEC operation to the tree + tree = mkuastunary(A_PREDEC, tree->type, tree, 0); + break; + default: + tree = primary(); + } + return (tree); +} + +// Return an AST tree whose root is a binary operator. +// Parameter ptp is the previous token's precedence. +struct ASTnode *binexpr(int ptp) { + struct ASTnode *left, *right; + struct ASTnode *ltemp, *rtemp; + int ASTop; + int tokentype; + + // Get the tree on the left. + // Fetch the next token at the same time. + left = prefix(); + + // If we hit one of several terminating tokens, return just the left node + tokentype = Token.token; + if (tokentype == T_SEMI || tokentype == T_RPAREN || + tokentype == T_RBRACKET || tokentype == T_COMMA) { + left->rvalue = 1; + return (left); + } + // While the precedence of this token is more than that of the + // previous token precedence, or it's right associative and + // equal to the previous token's precedence + while ((op_precedence(tokentype) > ptp) || + (rightassoc(tokentype) && op_precedence(tokentype) == ptp)) { + // Fetch in the next integer literal + scan(&Token); + + // Recursively call binexpr() with the + // precedence of our token to build a sub-tree + right = binexpr(OpPrec[tokentype]); + + // Determine the operation to be performed on the sub-trees + ASTop = binastop(tokentype); + + if (ASTop == A_ASSIGN) { + // Assignment + // Make the right tree into an rvalue + right->rvalue = 1; + + // Ensure the right's type matches the left + right = modify_type(right, left->type, 0); + if (right == NULL) + fatal("Incompatible expression in assignment"); + + // Make an assignment AST tree. However, switch + // left and right around, so that the right expression's + // code will be generated before the left expression + ltemp = left; + left = right; + right = ltemp; + } else { + + // We are not doing an assignment, so both trees should be rvalues + // Convert both trees into rvalue if they are lvalue trees + left->rvalue = 1; + right->rvalue = 1; + + // Ensure the two types are compatible by trying + // to modify each tree to match the other's type. + ltemp = modify_type(left, right->type, ASTop); + rtemp = modify_type(right, left->type, ASTop); + if (ltemp == NULL && rtemp == NULL) + fatal("Incompatible types in binary expression"); + if (ltemp != NULL) + left = ltemp; + if (rtemp != NULL) + right = rtemp; + } + + // Join that sub-tree with ours. Convert the token + // into an AST operation at the same time. + left = mkastnode(binastop(tokentype), left->type, left, NULL, right, 0); + + // Update the details of the current token. + // If we hit a terminating token, return just the left node + tokentype = Token.token; + if (tokentype == T_SEMI || tokentype == T_RPAREN || + tokentype == T_RBRACKET || tokentype == T_COMMA) { + left->rvalue = 1; + return (left); + } + } + + // Return the tree we have when the precedence + // is the same or lower + left->rvalue = 1; + return (left); +} diff --git a/27_Testing_Errors/gen.c b/27_Testing_Errors/gen.c new file mode 100644 index 0000000..636ebd2 --- /dev/null +++ b/27_Testing_Errors/gen.c @@ -0,0 +1,295 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Generic code generator +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Generate and return a new label number +int genlabel(void) { + static int id = 1; + return (id++); +} + +// Generate the code for an IF statement +// and an optional ELSE clause +static int genIF(struct ASTnode *n) { + int Lfalse, Lend; + + // Generate two labels: one for the + // false compound statement, and one + // for the end of the overall IF statement. + // When there is no ELSE clause, Lfalse _is_ + // the ending label! + Lfalse = genlabel(); + if (n->right) + Lend = genlabel(); + + // Generate the condition code followed + // by a jump to the false label. + genAST(n->left, Lfalse, n->op); + genfreeregs(); + + // Generate the true compound statement + genAST(n->mid, NOLABEL, n->op); + genfreeregs(); + + // If there is an optional ELSE clause, + // generate the jump to skip to the end + if (n->right) + cgjump(Lend); + + // Now the false label + cglabel(Lfalse); + + // Optional ELSE clause: generate the + // false compound statement and the + // end label + if (n->right) { + genAST(n->right, NOLABEL, n->op); + genfreeregs(); + cglabel(Lend); + } + + return (NOREG); +} + +// Generate the code for a WHILE statement +static int genWHILE(struct ASTnode *n) { + int Lstart, Lend; + + // Generate the start and end labels + // and output the start label + Lstart = genlabel(); + Lend = genlabel(); + cglabel(Lstart); + + // Generate the condition code followed + // by a jump to the end label. + genAST(n->left, Lend, n->op); + genfreeregs(); + + // Generate the compound statement for the body + genAST(n->right, NOLABEL, n->op); + genfreeregs(); + + // Finally output the jump back to the condition, + // and the end label + cgjump(Lstart); + cglabel(Lend); + return (NOREG); +} + +// Generate the code to copy the arguments of a +// function call to its parameters, then call the +// function itself. Return the register that holds +// the function's return value. +static int gen_funccall(struct ASTnode *n) { + struct ASTnode *gluetree = n->left; + int reg; + int numargs = 0; + + // If there is a list of arguments, walk this list + // from the last argument (right-hand child) to the + // first + while (gluetree) { + // Calculate the expression's value + reg = genAST(gluetree->right, NOLABEL, gluetree->op); + // Copy this into the n'th function parameter: size is 1, 2, 3, ... + cgcopyarg(reg, gluetree->v.size); + // Keep the first (highest) number of arguments + if (numargs == 0) + numargs = gluetree->v.size; + genfreeregs(); + gluetree = gluetree->left; + } + + // Call the function, clean up the stack (based on numargs), + // and return its result + return (cgcall(n->v.id, numargs)); +} + +// Given an AST, an optional label, and the AST op +// of the parent, generate assembly code recursively. +// Return the register id with the tree's final value. +int genAST(struct ASTnode *n, int label, int parentASTop) { + int leftreg, rightreg; + + // We have some specific AST node handling at the top + // so that we don't evaluate the child sub-trees immediately + switch (n->op) { + case A_IF: + return (genIF(n)); + case A_WHILE: + return (genWHILE(n)); + case A_FUNCCALL: + return (gen_funccall(n)); + case A_GLUE: + // Do each child statement, and free the + // registers after each child + genAST(n->left, NOLABEL, n->op); + genfreeregs(); + genAST(n->right, NOLABEL, n->op); + genfreeregs(); + return (NOREG); + case A_FUNCTION: + // Generate the function's preamble before the code + // in the child sub-tree + cgfuncpreamble(n->v.id); + genAST(n->left, NOLABEL, n->op); + cgfuncpostamble(n->v.id); + return (NOREG); + } + + // General AST node handling below + + // Get the left and right sub-tree values + if (n->left) + leftreg = genAST(n->left, NOLABEL, n->op); + if (n->right) + rightreg = genAST(n->right, NOLABEL, n->op); + + switch (n->op) { + case A_ADD: + return (cgadd(leftreg, rightreg)); + case A_SUBTRACT: + return (cgsub(leftreg, rightreg)); + case A_MULTIPLY: + return (cgmul(leftreg, rightreg)); + case A_DIVIDE: + return (cgdiv(leftreg, rightreg)); + case A_AND: + return (cgand(leftreg, rightreg)); + case A_OR: + return (cgor(leftreg, rightreg)); + case A_XOR: + return (cgxor(leftreg, rightreg)); + case A_LSHIFT: + return (cgshl(leftreg, rightreg)); + case A_RSHIFT: + return (cgshr(leftreg, rightreg)); + case A_EQ: + case A_NE: + case A_LT: + case A_GT: + case A_LE: + case A_GE: + // If the parent AST node is an A_IF or A_WHILE, generate + // a compare followed by a jump. Otherwise, compare registers + // and set one to 1 or 0 based on the comparison. + if (parentASTop == A_IF || parentASTop == A_WHILE) + return (cgcompare_and_jump(n->op, leftreg, rightreg, label)); + else + return (cgcompare_and_set(n->op, leftreg, rightreg)); + case A_INTLIT: + return (cgloadint(n->v.intvalue, n->type)); + case A_STRLIT: + return (cgloadglobstr(n->v.id)); + case A_IDENT: + // Load our value if we are an rvalue + // or we are being dereferenced + if (n->rvalue || parentASTop == A_DEREF) { + if (Symtable[n->v.id].class == C_GLOBAL) { + return (cgloadglob(n->v.id, n->op)); + } else { + return (cgloadlocal(n->v.id, n->op)); + } + } else + return (NOREG); + case A_ASSIGN: + // Are we assigning to an identifier or through a pointer? + switch (n->right->op) { + case A_IDENT: + if (Symtable[n->right->v.id].class == C_GLOBAL) + return (cgstorglob(leftreg, n->right->v.id)); + else + return (cgstorlocal(leftreg, n->right->v.id)); + case A_DEREF: + return (cgstorderef(leftreg, rightreg, n->right->type)); + default: + fatald("Can't A_ASSIGN in genAST(), op", n->op); + } + case A_WIDEN: + // Widen the child's type to the parent's type + return (cgwiden(leftreg, n->left->type, n->type)); + case A_RETURN: + cgreturn(leftreg, Functionid); + return (NOREG); + case A_ADDR: + return (cgaddress(n->v.id)); + case A_DEREF: + // If we are an rvalue, dereference to get the value we point at, + // otherwise leave it for A_ASSIGN to store through the pointer + if (n->rvalue) + return (cgderef(leftreg, n->left->type)); + else + return (leftreg); + case A_SCALE: + // Small optimisation: use shift if the + // scale value is a known power of two + switch (n->v.size) { + case 2: + return (cgshlconst(leftreg, 1)); + case 4: + return (cgshlconst(leftreg, 2)); + case 8: + return (cgshlconst(leftreg, 3)); + default: + // Load a register with the size and + // multiply the leftreg by this size + rightreg = cgloadint(n->v.size, P_INT); + return (cgmul(leftreg, rightreg)); + } + case A_POSTINC: + case A_POSTDEC: + // Load and decrement the variable's value into a register + // and post increment/decrement it + if (Symtable[n->v.id].class == C_GLOBAL) + return (cgloadglob(n->v.id, n->op)); + else + return (cgloadlocal(n->v.id, n->op)); + case A_PREINC: + case A_PREDEC: + // Load and decrement the variable's value into a register + // and pre increment/decrement it + if (Symtable[n->left->v.id].class == C_GLOBAL) + return (cgloadglob(n->left->v.id, n->op)); + else + return (cgloadlocal(n->left->v.id, n->op)); + case A_NEGATE: + return (cgnegate(leftreg)); + case A_INVERT: + return (cginvert(leftreg)); + case A_LOGNOT: + return (cglognot(leftreg)); + case A_TOBOOL: + // If the parent AST node is an A_IF or A_WHILE, generate + // a compare followed by a jump. Otherwise, set the register + // to 0 or 1 based on it's zeroeness or non-zeroeness + return (cgboolean(leftreg, parentASTop, label)); + default: + fatald("Unknown AST operator", n->op); + } + return (NOREG); // Keep -Wall happy +} + +void genpreamble() { + cgpreamble(); +} +void genpostamble() { + cgpostamble(); +} +void genfreeregs() { + freeall_registers(); +} +void genglobsym(int id) { + cgglobsym(id); +} +int genglobstr(char *strvalue) { + int l = genlabel(); + cgglobstr(l, strvalue); + return (l); +} +int genprimsize(int type) { + return (cgprimsize(type)); +} diff --git a/27_Testing_Errors/lib/printint.c b/27_Testing_Errors/lib/printint.c new file mode 100644 index 0000000..9cf8e78 --- /dev/null +++ b/27_Testing_Errors/lib/printint.c @@ -0,0 +1,8 @@ +#include +void printint(long x) { + printf("%ld\n", x); +} + +void printchar(long x) { + putc((char)(x & 0x7f), stdout); +} diff --git a/27_Testing_Errors/main.c b/27_Testing_Errors/main.c new file mode 100644 index 0000000..d552dd5 --- /dev/null +++ b/27_Testing_Errors/main.c @@ -0,0 +1,74 @@ +#include "defs.h" +#define extern_ +#include "data.h" +#undef extern_ +#include "decl.h" +#include + +// Compiler setup and top-level execution +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Initialise global variables +static void init() { + Line = 1; + Putback = '\n'; + Globs = 0; + Locls = NSYMBOLS - 1; + O_dumpAST = 0; +} + +// Print out a usage if started incorrectly +static void usage(char *prog) { + fprintf(stderr, "Usage: %s [-T] infile\n", prog); + exit(1); +} + +// Main program: check arguments and print a usage +// if we don't have an argument. Open up the input +// file and call scanfile() to scan the tokens in it. +int main(int argc, char *argv[]) { + int i; + + // Initialise the globals + init(); + + // Scan for command-line options + for (i = 1; i < argc; i++) { + if (*argv[i] != '-') + break; + for (int j = 1; argv[i][j]; j++) { + switch (argv[i][j]) { + case 'T': + O_dumpAST = 1; + break; + default: + usage(argv[0]); + } + } + } + + // Ensure we have an input file argument + if (i >= argc) + usage(argv[0]); + + // Open up the input file + if ((Infile = fopen(argv[i], "r")) == NULL) { + fprintf(stderr, "Unable to open %s: %s\n", argv[i], strerror(errno)); + exit(1); + } + // Create the output file + if ((Outfile = fopen("out.s", "w")) == NULL) { + fprintf(stderr, "Unable to create out.s: %s\n", strerror(errno)); + exit(1); + } + // For now, ensure that printint() and printchar() are defined + addglob("printint", P_INT, S_FUNCTION, C_GLOBAL, 0, 0); + addglob("printchar", P_VOID, S_FUNCTION, C_GLOBAL, 0, 0); + + scan(&Token); // Get the first token from the input + genpreamble(); // Output the preamble + global_declarations(); // Parse the global declarations + genpostamble(); // Output the postamble + fclose(Outfile); // Close the output file and exit + return (0); +} diff --git a/27_Testing_Errors/misc.c b/27_Testing_Errors/misc.c new file mode 100644 index 0000000..08ab712 --- /dev/null +++ b/27_Testing_Errors/misc.c @@ -0,0 +1,68 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Miscellaneous functions +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Ensure that the current token is t, +// and fetch the next token. Otherwise +// throw an error +void match(int t, char *what) { + if (Token.token == t) { + scan(&Token); + } else { + fatals("Expected", what); + } +} + +// Match a semicolon and fetch the next token +void semi(void) { + match(T_SEMI, ";"); +} + +// Match a left brace and fetch the next token +void lbrace(void) { + match(T_LBRACE, "{"); +} + +// Match a right brace and fetch the next token +void rbrace(void) { + match(T_RBRACE, "}"); +} + +// Match a left parenthesis and fetch the next token +void lparen(void) { + match(T_LPAREN, "("); +} + +// Match a right parenthesis and fetch the next token +void rparen(void) { + match(T_RPAREN, ")"); +} + +// Match an identifer and fetch the next token +void ident(void) { + match(T_IDENT, "identifier"); +} + +// Print out fatal messages +void fatal(char *s) { + fprintf(stderr, "%s on line %d\n", s, Line); + exit(1); +} + +void fatals(char *s1, char *s2) { + fprintf(stderr, "%s:%s on line %d\n", s1, s2, Line); + exit(1); +} + +void fatald(char *s, int d) { + fprintf(stderr, "%s:%d on line %d\n", s, d, Line); + exit(1); +} + +void fatalc(char *s, int c) { + fprintf(stderr, "%s:%c on line %d\n", s, c, Line); + exit(1); +} diff --git a/27_Testing_Errors/scan.c b/27_Testing_Errors/scan.c new file mode 100644 index 0000000..b97646b --- /dev/null +++ b/27_Testing_Errors/scan.c @@ -0,0 +1,366 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Lexical scanning +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Return the position of character c +// in string s, or -1 if c not found +static int chrpos(char *s, int c) { + char *p; + + p = strchr(s, c); + return (p ? p - s : -1); +} + +// Get the next character from the input file. +static int next(void) { + int c; + + if (Putback) { // Use the character put + c = Putback; // back if there is one + Putback = 0; + return (c); + } + + c = fgetc(Infile); // Read from input file + if ('\n' == c) + Line++; // Increment line count + return (c); +} + +// Put back an unwanted character +static void putback(int c) { + Putback = c; +} + +// Skip past input that we don't need to deal with, +// i.e. whitespace, newlines. Return the first +// character we do need to deal with. +static int skip(void) { + int c; + + c = next(); + while (' ' == c || '\t' == c || '\n' == c || '\r' == c || '\f' == c) { + c = next(); + } + return (c); +} + +// Return the next character from a character +// or string literal +static int scanch(void) { + int c; + + // Get the next input character and interpret + // metacharacters that start with a backslash + c = next(); + if (c == '\\') { + switch (c = next()) { + case 'a': + return '\a'; + case 'b': + return '\b'; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'v': + return '\v'; + case '\\': + return '\\'; + case '"': + return '"'; + case '\'': + return '\''; + default: + fatalc("unknown escape sequence", c); + } + } + return (c); // Just an ordinary old character! +} + +// Scan and return an integer literal +// value from the input file. +static int scanint(int c) { + int k, val = 0; + + // Convert each character into an int value + while ((k = chrpos("0123456789", c)) >= 0) { + val = val * 10 + k; + c = next(); + } + + // We hit a non-integer character, put it back. + putback(c); + return (val); +} + +// Scan in a string literal from the input file, +// and store it in buf[]. Return the length of +// the string. +static int scanstr(char *buf) { + int i, c; + + // Loop while we have enough buffer space + for (i = 0; i < TEXTLEN - 1; i++) { + // Get the next char and append to buf + // Return when we hit the ending double quote + if ((c = scanch()) == '"') { + buf[i] = 0; + return (i); + } + buf[i] = c; + } + // Ran out of buf[] space + fatal("String literal too long"); + return (0); +} + +// Scan an identifier from the input file and +// store it in buf[]. Return the identifier's length +static int scanident(int c, char *buf, int lim) { + int i = 0; + + // Allow digits, alpha and underscores + while (isalpha(c) || isdigit(c) || '_' == c) { + // Error if we hit the identifier length limit, + // else append to buf[] and get next character + if (lim - 1 == i) { + fatal("Identifier too long"); + } else if (i < lim - 1) { + buf[i++] = c; + } + c = next(); + } + // We hit a non-valid character, put it back. + // NUL-terminate the buf[] and return the length + putback(c); + buf[i] = '\0'; + return (i); +} + +// Given a word from the input, return the matching +// keyword token number or 0 if it's not a keyword. +// Switch on the first letter so that we don't have +// to waste time strcmp()ing against all the keywords. +static int keyword(char *s) { + switch (*s) { + case 'c': + if (!strcmp(s, "char")) + return (T_CHAR); + break; + case 'e': + if (!strcmp(s, "else")) + return (T_ELSE); + break; + case 'f': + if (!strcmp(s, "for")) + return (T_FOR); + break; + case 'i': + if (!strcmp(s, "if")) + return (T_IF); + if (!strcmp(s, "int")) + return (T_INT); + break; + case 'l': + if (!strcmp(s, "long")) + return (T_LONG); + break; + case 'r': + if (!strcmp(s, "return")) + return (T_RETURN); + break; + case 'w': + if (!strcmp(s, "while")) + return (T_WHILE); + break; + case 'v': + if (!strcmp(s, "void")) + return (T_VOID); + break; + } + return (0); +} + +// A pointer to a rejected token +static struct token *Rejtoken = NULL; + +// Reject the token that we just scanned +void reject_token(struct token *t) { + if (Rejtoken != NULL) + fatal("Can't reject token twice"); + Rejtoken = t; +} + +// Scan and return the next token found in the input. +// Return 1 if token valid, 0 if no tokens left. +int scan(struct token *t) { + int c, tokentype; + + // If we have any rejected token, return it + if (Rejtoken != NULL) { + t = Rejtoken; + Rejtoken = NULL; + return (1); + } + // Skip whitespace + c = skip(); + + // Determine the token based on + // the input character + switch (c) { + case EOF: + t->token = T_EOF; + return (0); + case '+': + if ((c = next()) == '+') { + t->token = T_INC; + } else { + putback(c); + t->token = T_PLUS; + } + break; + case '-': + if ((c = next()) == '-') { + t->token = T_DEC; + } else { + putback(c); + t->token = T_MINUS; + } + break; + case '*': + t->token = T_STAR; + break; + case '/': + t->token = T_SLASH; + break; + case ';': + t->token = T_SEMI; + break; + case '{': + t->token = T_LBRACE; + break; + case '}': + t->token = T_RBRACE; + break; + case '(': + t->token = T_LPAREN; + break; + case ')': + t->token = T_RPAREN; + break; + case '[': + t->token = T_LBRACKET; + break; + case ']': + t->token = T_RBRACKET; + break; + case '~': + t->token = T_INVERT; + break; + case '^': + t->token = T_XOR; + break; + case ',': + t->token = T_COMMA; + break; + case '=': + if ((c = next()) == '=') { + t->token = T_EQ; + } else { + putback(c); + t->token = T_ASSIGN; + } + break; + case '!': + if ((c = next()) == '=') { + t->token = T_NE; + } else { + putback(c); + t->token = T_LOGNOT; + } + break; + case '<': + if ((c = next()) == '=') { + t->token = T_LE; + } else if (c == '<') { + t->token = T_LSHIFT; + } else { + putback(c); + t->token = T_LT; + } + break; + case '>': + if ((c = next()) == '=') { + t->token = T_GE; + } else if (c == '>') { + t->token = T_RSHIFT; + } else { + putback(c); + t->token = T_GT; + } + break; + case '&': + if ((c = next()) == '&') { + t->token = T_LOGAND; + } else { + putback(c); + t->token = T_AMPER; + } + break; + case '|': + if ((c = next()) == '|') { + t->token = T_LOGOR; + } else { + putback(c); + t->token = T_OR; + } + break; + case '\'': + // If it's a quote, scan in the + // literal character value and + // the trailing quote + t->intvalue = scanch(); + t->token = T_INTLIT; + if (next() != '\'') + fatal("Expected '\\'' at end of char literal"); + break; + case '"': + // Scan in a literal string + scanstr(Text); + t->token = T_STRLIT; + break; + default: + // If it's a digit, scan the + // literal integer value in + if (isdigit(c)) { + t->intvalue = scanint(c); + t->token = T_INTLIT; + break; + } else if (isalpha(c) || '_' == c) { + // Read in a keyword or identifier + scanident(c, Text, TEXTLEN); + + // If it's a recognised keyword, return that token + if ((tokentype = keyword(Text)) != 0) { + t->token = tokentype; + break; + } + // Not a recognised keyword, so it must be an identifier + t->token = T_IDENT; + break; + } + // The character isn't part of any recognised token, error + fatalc("Unrecognised character", c); + } + + // We found a token + return (1); +} diff --git a/27_Testing_Errors/stmt.c b/27_Testing_Errors/stmt.c new file mode 100644 index 0000000..187d7f6 --- /dev/null +++ b/27_Testing_Errors/stmt.c @@ -0,0 +1,237 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Parsing of statements +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Prototypes +static struct ASTnode *single_statement(void); + +// compound_statement: // empty, i.e. no statement +// | statement +// | statement statements +// ; +// +// statement: declaration +// | expression_statement +// | function_call +// | if_statement +// | while_statement +// | for_statement +// | return_statement +// ; + + +// if_statement: if_head +// | if_head 'else' compound_statement +// ; +// +// if_head: 'if' '(' true_false_expression ')' compound_statement ; +// +// Parse an IF statement including any +// optional ELSE clause and return its AST +static struct ASTnode *if_statement(void) { + struct ASTnode *condAST, *trueAST, *falseAST = NULL; + + // Ensure we have 'if' '(' + match(T_IF, "if"); + lparen(); + + // Parse the following expression + // and the ')' following. Force a + // non-comparison to be boolean + // the tree's operation is a comparison. + condAST = binexpr(0); + if (condAST->op < A_EQ || condAST->op > A_GE) + condAST = mkuastunary(A_TOBOOL, condAST->type, condAST, 0); + rparen(); + + // Get the AST for the compound statement + trueAST = compound_statement(); + + // If we have an 'else', skip it + // and get the AST for the compound statement + if (Token.token == T_ELSE) { + scan(&Token); + falseAST = compound_statement(); + } + // Build and return the AST for this statement + return (mkastnode(A_IF, P_NONE, condAST, trueAST, falseAST, 0)); +} + + +// while_statement: 'while' '(' true_false_expression ')' compound_statement ; +// +// Parse a WHILE statement and return its AST +static struct ASTnode *while_statement(void) { + struct ASTnode *condAST, *bodyAST; + + // Ensure we have 'while' '(' + match(T_WHILE, "while"); + lparen(); + + // Parse the following expression + // and the ')' following. Force a + // non-comparison to be boolean + // the tree's operation is a comparison. + condAST = binexpr(0); + if (condAST->op < A_EQ || condAST->op > A_GE) + condAST = mkuastunary(A_TOBOOL, condAST->type, condAST, 0); + rparen(); + + // Get the AST for the compound statement + bodyAST = compound_statement(); + + // Build and return the AST for this statement + return (mkastnode(A_WHILE, P_NONE, condAST, NULL, bodyAST, 0)); +} + +// for_statement: 'for' '(' preop_statement ';' +// true_false_expression ';' +// postop_statement ')' compound_statement ; +// +// preop_statement: statement (for now) +// postop_statement: statement (for now) +// +// Parse a FOR statement and return its AST +static struct ASTnode *for_statement(void) { + struct ASTnode *condAST, *bodyAST; + struct ASTnode *preopAST, *postopAST; + struct ASTnode *tree; + + // Ensure we have 'for' '(' + match(T_FOR, "for"); + lparen(); + + // Get the pre_op statement and the ';' + preopAST = single_statement(); + semi(); + + // Get the condition and the ';'. + // Force a non-comparison to be boolean + // the tree's operation is a comparison. + condAST = binexpr(0); + if (condAST->op < A_EQ || condAST->op > A_GE) + condAST = mkuastunary(A_TOBOOL, condAST->type, condAST, 0); + semi(); + + // Get the post_op statement and the ')' + postopAST = single_statement(); + rparen(); + + // Get the compound statement which is the body + bodyAST = compound_statement(); + + // For now, all four sub-trees have to be non-NULL. + // Later on, we'll change the semantics for when some are missing + + // Glue the compound statement and the postop tree + tree = mkastnode(A_GLUE, P_NONE, bodyAST, NULL, postopAST, 0); + + // Make a WHILE loop with the condition and this new body + tree = mkastnode(A_WHILE, P_NONE, condAST, NULL, tree, 0); + + // And glue the preop tree to the A_WHILE tree + return (mkastnode(A_GLUE, P_NONE, preopAST, NULL, tree, 0)); +} + +// return_statement: 'return' '(' expression ')' ; +// +// Parse a return statement and return its AST +static struct ASTnode *return_statement(void) { + struct ASTnode *tree; + + // Can't return a value if function returns P_VOID + if (Symtable[Functionid].type == P_VOID) + fatal("Can't return from a void function"); + + // Ensure we have 'return' '(' + match(T_RETURN, "return"); + lparen(); + + // Parse the following expression + tree = binexpr(0); + + // Ensure this is compatible with the function's type + tree = modify_type(tree, Symtable[Functionid].type, 0); + if (tree == NULL) + fatal("Incompatible type to return"); + + // Add on the A_RETURN node + tree = mkuastunary(A_RETURN, P_NONE, tree, 0); + + // Get the ')' + rparen(); + return (tree); +} + +// Parse a single statement and return its AST +static struct ASTnode *single_statement(void) { + int type; + + switch (Token.token) { + case T_CHAR: + case T_INT: + case T_LONG: + + // The beginning of a variable declaration. + // Parse the type and get the identifier. + // Then parse the rest of the declaration + // and skip over the semicolon + type = parse_type(); + ident(); + var_declaration(type, C_LOCAL); + semi(); + return (NULL); // No AST generated here + case T_IF: + return (if_statement()); + case T_WHILE: + return (while_statement()); + case T_FOR: + return (for_statement()); + case T_RETURN: + return (return_statement()); + default: + // For now, see if this is an expression. + // This catches assignment statements. + return (binexpr(0)); + } + return (NULL); // Keep -Wall happy +} + +// Parse a compound statement +// and return its AST +struct ASTnode *compound_statement(void) { + struct ASTnode *left = NULL; + struct ASTnode *tree; + + // Require a left curly bracket + lbrace(); + + while (1) { + // Parse a single statement + tree = single_statement(); + + // Some statements must be followed by a semicolon + if (tree != NULL && (tree->op == A_ASSIGN || + tree->op == A_RETURN || tree->op == A_FUNCCALL)) + semi(); + + // For each new tree, either save it in left + // if left is empty, or glue the left and the + // new tree together + if (tree != NULL) { + if (left == NULL) + left = tree; + else + left = mkastnode(A_GLUE, P_NONE, left, NULL, tree, 0); + } + // When we hit a right curly bracket, + // skip past it and return the AST + if (Token.token == T_RBRACE) { + rbrace(); + return (left); + } + } +} diff --git a/27_Testing_Errors/sym.c b/27_Testing_Errors/sym.c new file mode 100644 index 0000000..fef93ef --- /dev/null +++ b/27_Testing_Errors/sym.c @@ -0,0 +1,147 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Symbol table functions +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Determine if the symbol s is in the global symbol table. +// Return its slot position or -1 if not found. +// Skip C_PARAM entries +int findglob(char *s) { + int i; + + for (i = 0; i < Globs; i++) { + if (Symtable[i].class == C_PARAM) + continue; + if (*s == *Symtable[i].name && !strcmp(s, Symtable[i].name)) + return (i); + } + return (-1); +} + +// Get the position of a new global symbol slot, or die +// if we've run out of positions. +static int newglob(void) { + int p; + + if ((p = Globs++) >= Locls) + fatal("Too many global symbols"); + return (p); +} + +// Determine if the symbol s is in the local symbol table. +// Return its slot position or -1 if not found. +int findlocl(char *s) { + int i; + + for (i = Locls + 1; i < NSYMBOLS; i++) { + if (*s == *Symtable[i].name && !strcmp(s, Symtable[i].name)) + return (i); + } + return (-1); +} + +// Get the position of a new local symbol slot, or die +// if we've run out of positions. +static int newlocl(void) { + int p; + + if ((p = Locls--) <= Globs) + fatal("Too many local symbols"); + return (p); +} + +// Clear all the entries in the +// local symbol table +void freeloclsyms(void) { + Locls = NSYMBOLS - 1; +} + +// Update a symbol at the given slot number in the symbol table. Set up its: +// + type: char, int etc. +// + structural type: var, function, array etc. +// + size: number of elements +// + endlabel: if this is a function +// + posn: Position information for local symbols +static void updatesym(int slot, char *name, int type, int stype, + int class, int endlabel, int size, int posn) { + if (slot < 0 || slot >= NSYMBOLS) + fatal("Invalid symbol slot number in updatesym()"); + Symtable[slot].name = strdup(name); + Symtable[slot].type = type; + Symtable[slot].stype = stype; + Symtable[slot].class = class; + Symtable[slot].endlabel = endlabel; + Symtable[slot].size = size; + Symtable[slot].posn = posn; +} + +// Add a global symbol to the symbol table. Set up its: +// + type: char, int etc. +// + structural type: var, function, array etc. +// + class of the symbol +// + size: number of elements +// + endlabel: if this is a function +// Return the slot number in the symbol table +int addglob(char *name, int type, int stype, int class, int endlabel, + int size) { + int slot; + + // If this is already in the symbol table, return the existing slot + if ((slot = findglob(name)) != -1) + return (slot); + + // Otherwise get a new slot and fill it in + slot = newglob(); + updatesym(slot, name, type, stype, class, endlabel, size, 0); + // Generate the assembly for the symbol if it's global + if (class == C_GLOBAL) + genglobsym(slot); + // Return the slot number + return (slot); +} + +// Add a local symbol to the symbol table. Set up its: +// + type: char, int etc. +// + structural type: var, function, array etc. +// + size: number of elements +// Return the slot number in the symbol table, -1 if a duplicate entry +int addlocl(char *name, int type, int stype, int class, int size) { + int localslot; + + // If this is already in the symbol table, return an error + if ((localslot = findlocl(name)) != -1) + return (-1); + + // Otherwise get a new symbol slot and a position for this local. + // Update the local symbol table entry. + localslot = newlocl(); + updatesym(localslot, name, type, stype, class, 0, size, 0); + + // Return the local symbol's slot + return (localslot); +} + +// Given a function's slot number, copy the global parameters +// from its prototype to be local parameters +void copyfuncparams(int slot) { + int i, id = slot + 1; + + for (i = 0; i < Symtable[slot].nelems; i++, id++) { + addlocl(Symtable[id].name, Symtable[id].type, Symtable[id].stype, + Symtable[id].class, Symtable[id].size); + } +} + + +// Determine if the symbol s is in the symbol table. +// Return its slot position or -1 if not found. +int findsymbol(char *s) { + int slot; + + slot = findlocl(s); + if (slot == -1) + slot = findglob(s); + return (slot); +} diff --git a/27_Testing_Errors/tests/err.input31.c b/27_Testing_Errors/tests/err.input31.c new file mode 100644 index 0000000..1eb7824 --- /dev/null +++ b/27_Testing_Errors/tests/err.input31.c @@ -0,0 +1 @@ +Expecting a primary expression, got token:15 on line 3 diff --git a/27_Testing_Errors/tests/err.input32.c b/27_Testing_Errors/tests/err.input32.c new file mode 100644 index 0000000..809a970 --- /dev/null +++ b/27_Testing_Errors/tests/err.input32.c @@ -0,0 +1 @@ +Unknown variable:cow on line 2 diff --git a/27_Testing_Errors/tests/err.input33.c b/27_Testing_Errors/tests/err.input33.c new file mode 100644 index 0000000..189ecfc --- /dev/null +++ b/27_Testing_Errors/tests/err.input33.c @@ -0,0 +1 @@ +Incompatible type to return on line 2 diff --git a/27_Testing_Errors/tests/err.input34.c b/27_Testing_Errors/tests/err.input34.c new file mode 100644 index 0000000..48ea035 --- /dev/null +++ b/27_Testing_Errors/tests/err.input34.c @@ -0,0 +1 @@ +For now, declaration of local arrays is not implemented on line 2 diff --git a/27_Testing_Errors/tests/err.input35.c b/27_Testing_Errors/tests/err.input35.c new file mode 100644 index 0000000..4a1ec36 --- /dev/null +++ b/27_Testing_Errors/tests/err.input35.c @@ -0,0 +1 @@ +Duplicate local variable declaration:a on line 2 diff --git a/27_Testing_Errors/tests/err.input36.c b/27_Testing_Errors/tests/err.input36.c new file mode 100644 index 0000000..31e7d0a --- /dev/null +++ b/27_Testing_Errors/tests/err.input36.c @@ -0,0 +1 @@ +Type doesn't match prototype for parameter:3 on line 2 diff --git a/27_Testing_Errors/tests/err.input37.c b/27_Testing_Errors/tests/err.input37.c new file mode 100644 index 0000000..ac08bf5 --- /dev/null +++ b/27_Testing_Errors/tests/err.input37.c @@ -0,0 +1 @@ +Unexpected token in parameter list:15 on line 1 diff --git a/27_Testing_Errors/tests/err.input38.c b/27_Testing_Errors/tests/err.input38.c new file mode 100644 index 0000000..31e7d0a --- /dev/null +++ b/27_Testing_Errors/tests/err.input38.c @@ -0,0 +1 @@ +Type doesn't match prototype for parameter:3 on line 2 diff --git a/27_Testing_Errors/tests/err.input39.c b/27_Testing_Errors/tests/err.input39.c new file mode 100644 index 0000000..5c7140b --- /dev/null +++ b/27_Testing_Errors/tests/err.input39.c @@ -0,0 +1 @@ +No statements in function with non-void type on line 2 diff --git a/27_Testing_Errors/tests/err.input40.c b/27_Testing_Errors/tests/err.input40.c new file mode 100644 index 0000000..993db8f --- /dev/null +++ b/27_Testing_Errors/tests/err.input40.c @@ -0,0 +1 @@ +No return for function with non-void type on line 2 diff --git a/27_Testing_Errors/tests/err.input41.c b/27_Testing_Errors/tests/err.input41.c new file mode 100644 index 0000000..44c9337 --- /dev/null +++ b/27_Testing_Errors/tests/err.input41.c @@ -0,0 +1 @@ +Can't return from a void function on line 1 diff --git a/27_Testing_Errors/tests/err.input42.c b/27_Testing_Errors/tests/err.input42.c new file mode 100644 index 0000000..2e82c45 --- /dev/null +++ b/27_Testing_Errors/tests/err.input42.c @@ -0,0 +1 @@ +Undeclared function:fred on line 1 diff --git a/27_Testing_Errors/tests/err.input43.c b/27_Testing_Errors/tests/err.input43.c new file mode 100644 index 0000000..c194bec --- /dev/null +++ b/27_Testing_Errors/tests/err.input43.c @@ -0,0 +1 @@ +Undeclared array:b on line 1 diff --git a/27_Testing_Errors/tests/err.input44.c b/27_Testing_Errors/tests/err.input44.c new file mode 100644 index 0000000..9ae1e8d --- /dev/null +++ b/27_Testing_Errors/tests/err.input44.c @@ -0,0 +1 @@ +Unknown variable:z on line 1 diff --git a/27_Testing_Errors/tests/err.input45.c b/27_Testing_Errors/tests/err.input45.c new file mode 100644 index 0000000..104d9a4 --- /dev/null +++ b/27_Testing_Errors/tests/err.input45.c @@ -0,0 +1 @@ +& operator must be followed by an identifier on line 1 diff --git a/27_Testing_Errors/tests/err.input46.c b/27_Testing_Errors/tests/err.input46.c new file mode 100644 index 0000000..d0e20fc --- /dev/null +++ b/27_Testing_Errors/tests/err.input46.c @@ -0,0 +1 @@ +* operator must be followed by an identifier or * on line 1 diff --git a/27_Testing_Errors/tests/err.input47.c b/27_Testing_Errors/tests/err.input47.c new file mode 100644 index 0000000..c33e31b --- /dev/null +++ b/27_Testing_Errors/tests/err.input47.c @@ -0,0 +1 @@ +++ operator must be followed by an identifier on line 1 diff --git a/27_Testing_Errors/tests/err.input48.c b/27_Testing_Errors/tests/err.input48.c new file mode 100644 index 0000000..eb4ac9c --- /dev/null +++ b/27_Testing_Errors/tests/err.input48.c @@ -0,0 +1 @@ +-- operator must be followed by an identifier on line 1 diff --git a/27_Testing_Errors/tests/err.input49.c b/27_Testing_Errors/tests/err.input49.c new file mode 100644 index 0000000..bdc8586 --- /dev/null +++ b/27_Testing_Errors/tests/err.input49.c @@ -0,0 +1 @@ +Incompatible expression in assignment on line 4 diff --git a/27_Testing_Errors/tests/err.input50.c b/27_Testing_Errors/tests/err.input50.c new file mode 100644 index 0000000..140ae45 --- /dev/null +++ b/27_Testing_Errors/tests/err.input50.c @@ -0,0 +1 @@ +Incompatible types in binary expression on line 4 diff --git a/27_Testing_Errors/tests/err.input51.c b/27_Testing_Errors/tests/err.input51.c new file mode 100644 index 0000000..200a89c --- /dev/null +++ b/27_Testing_Errors/tests/err.input51.c @@ -0,0 +1 @@ +Expected '\'' at end of char literal on line 2 diff --git a/27_Testing_Errors/tests/err.input52.c b/27_Testing_Errors/tests/err.input52.c new file mode 100644 index 0000000..c056c74 --- /dev/null +++ b/27_Testing_Errors/tests/err.input52.c @@ -0,0 +1 @@ +Unrecognised character:$ on line 3 diff --git a/27_Testing_Errors/tests/input01.c b/27_Testing_Errors/tests/input01.c new file mode 100644 index 0000000..acbfa83 --- /dev/null +++ b/27_Testing_Errors/tests/input01.c @@ -0,0 +1,5 @@ +void main() +{ printint(12 * 3); + printint(18 - 2 * 4); + printint(1 + 2 + 9 - 5/2 + 3*5); +} diff --git a/27_Testing_Errors/tests/input02.c b/27_Testing_Errors/tests/input02.c new file mode 100644 index 0000000..7c7572c --- /dev/null +++ b/27_Testing_Errors/tests/input02.c @@ -0,0 +1,8 @@ +void main() +{ + int fred; + int jim; + fred= 5; + jim= 12; + printint(fred + jim); +} diff --git a/27_Testing_Errors/tests/input03.c b/27_Testing_Errors/tests/input03.c new file mode 100644 index 0000000..0e9e1cc --- /dev/null +++ b/27_Testing_Errors/tests/input03.c @@ -0,0 +1,9 @@ +void main() +{ + int x; + x= 1; printint(x); + x= x + 1; printint(x); + x= x + 1; printint(x); + x= x + 1; printint(x); + x= x + 1; printint(x); +} diff --git a/27_Testing_Errors/tests/input04.c b/27_Testing_Errors/tests/input04.c new file mode 100644 index 0000000..27526f2 --- /dev/null +++ b/27_Testing_Errors/tests/input04.c @@ -0,0 +1,13 @@ +void main() +{ + int x; + x= 7 < 9; printint(x); + x= 7 <= 9; printint(x); + x= 7 != 9; printint(x); + x= 7 == 7; printint(x); + x= 7 >= 7; printint(x); + x= 7 <= 7; printint(x); + x= 9 > 7; printint(x); + x= 9 >= 7; printint(x); + x= 9 != 7; printint(x); +} diff --git a/27_Testing_Errors/tests/input05.c b/27_Testing_Errors/tests/input05.c new file mode 100644 index 0000000..1d055af --- /dev/null +++ b/27_Testing_Errors/tests/input05.c @@ -0,0 +1,10 @@ +void main() +{ + int i; int j; + i=6; j=12; + if (i < j) { + printint(i); + } else { + printint(j); + } +} diff --git a/27_Testing_Errors/tests/input06.c b/27_Testing_Errors/tests/input06.c new file mode 100644 index 0000000..4c2c8d8 --- /dev/null +++ b/27_Testing_Errors/tests/input06.c @@ -0,0 +1,8 @@ +void main() +{ int i; + i=1; + while (i <= 10) { + printint(i); + i= i + 1; + } +} diff --git a/27_Testing_Errors/tests/input07.c b/27_Testing_Errors/tests/input07.c new file mode 100644 index 0000000..f9ce2e1 --- /dev/null +++ b/27_Testing_Errors/tests/input07.c @@ -0,0 +1,7 @@ +void main() +{ + int i; + for (i= 1; i <= 10; i= i + 1) { + printint(i); + } +} diff --git a/27_Testing_Errors/tests/input08.c b/27_Testing_Errors/tests/input08.c new file mode 100644 index 0000000..f9ce2e1 --- /dev/null +++ b/27_Testing_Errors/tests/input08.c @@ -0,0 +1,7 @@ +void main() +{ + int i; + for (i= 1; i <= 10; i= i + 1) { + printint(i); + } +} diff --git a/27_Testing_Errors/tests/input09.c b/27_Testing_Errors/tests/input09.c new file mode 100644 index 0000000..2bb7d31 --- /dev/null +++ b/27_Testing_Errors/tests/input09.c @@ -0,0 +1,14 @@ +void main() +{ + int i; + for (i= 1; i <= 10; i= i + 1) { + printint(i); + } +} + +void fred() +{ + int a; int b; + a= 12; b= 3 * a; + if (a >= b) { printint(2 * b - a); } +} diff --git a/27_Testing_Errors/tests/input10.c b/27_Testing_Errors/tests/input10.c new file mode 100644 index 0000000..57d50e6 --- /dev/null +++ b/27_Testing_Errors/tests/input10.c @@ -0,0 +1,10 @@ +void main() +{ + int i; char j; + + j= 20; printint(j); + i= 10; printint(i); + + for (i= 1; i <= 5; i= i + 1) { printint(i); } + for (j= 253; j != 2; j= j + 1) { printint(j); } +} diff --git a/27_Testing_Errors/tests/input11.c b/27_Testing_Errors/tests/input11.c new file mode 100644 index 0000000..7d6f630 --- /dev/null +++ b/27_Testing_Errors/tests/input11.c @@ -0,0 +1,15 @@ +int main() +{ + int i; char j; long k; + + i= 10; printint(i); + j= 20; printint(j); + k= 30; printint(k); + + for (i= 1; i <= 5; i= i + 1) { printint(i); } + for (j= 253; j != 4; j= j + 1) { printint(j); } + for (k= 1; k <= 5; k= k + 1) { printint(k); } + return(i); + printint(12345); + return(3); +} diff --git a/27_Testing_Errors/tests/input12.c b/27_Testing_Errors/tests/input12.c new file mode 100644 index 0000000..a91f079 --- /dev/null +++ b/27_Testing_Errors/tests/input12.c @@ -0,0 +1,9 @@ +int fred() { + return(5); +} + +void main() { + int x; + x= fred(2); + printint(x); +} diff --git a/27_Testing_Errors/tests/input13.c b/27_Testing_Errors/tests/input13.c new file mode 100644 index 0000000..8251bd1 --- /dev/null +++ b/27_Testing_Errors/tests/input13.c @@ -0,0 +1,11 @@ +int fred() { + return(56); +} + +void main() { + int dummy; + int result; + dummy= printint(23); + result= fred(10); + dummy= printint(result); +} diff --git a/27_Testing_Errors/tests/input14.c b/27_Testing_Errors/tests/input14.c new file mode 100644 index 0000000..b2b840d --- /dev/null +++ b/27_Testing_Errors/tests/input14.c @@ -0,0 +1,12 @@ +int fred() { + return(20); +} + +int main() { + int result; + printint(10); + result= fred(15); + printint(result); + printint(fred(15)+10); + return(0); +} diff --git a/27_Testing_Errors/tests/input15.c b/27_Testing_Errors/tests/input15.c new file mode 100644 index 0000000..5ed4636 --- /dev/null +++ b/27_Testing_Errors/tests/input15.c @@ -0,0 +1,16 @@ +int main() { + char a; + char *b; + char c; + + int d; + int *e; + int f; + + a= 18; printint(a); + b= &a; c= *b; printint(c); + + d= 12; printint(d); + e= &d; f= *e; printint(f); + return(0); +} diff --git a/27_Testing_Errors/tests/input16.c b/27_Testing_Errors/tests/input16.c new file mode 100644 index 0000000..4392e6b --- /dev/null +++ b/27_Testing_Errors/tests/input16.c @@ -0,0 +1,10 @@ +int c; +int d; +int *e; +int f; + +int main() { + c= 12; d=18; printint(c); + e= &c + 1; f= *e; printint(f); + return(0); +} diff --git a/27_Testing_Errors/tests/input17.c b/27_Testing_Errors/tests/input17.c new file mode 100644 index 0000000..172475c --- /dev/null +++ b/27_Testing_Errors/tests/input17.c @@ -0,0 +1,10 @@ +int main() { + char a; + char *b; + int d; + int *e; + + b= &a; *b= 19; printint(a); + e= &d; *e= 12; printint(d); + return(0); +} diff --git a/27_Testing_Errors/tests/input18.c b/27_Testing_Errors/tests/input18.c new file mode 100644 index 0000000..e0a8b67 --- /dev/null +++ b/27_Testing_Errors/tests/input18.c @@ -0,0 +1,9 @@ +int main() +{ + int a; + int b; + a= b= 34; + printint(a); + printint(b); + return(0); +} diff --git a/27_Testing_Errors/tests/input18a.c b/27_Testing_Errors/tests/input18a.c new file mode 100644 index 0000000..f3c9e55 --- /dev/null +++ b/27_Testing_Errors/tests/input18a.c @@ -0,0 +1,11 @@ +int a; +int *b; +char c; +char *d; + +int main() +{ + b= &a; *b= 15; printint(a); + d= &c; *d= 16; printint(c); + return(0); +} diff --git a/27_Testing_Errors/tests/input19.c b/27_Testing_Errors/tests/input19.c new file mode 100644 index 0000000..854032d --- /dev/null +++ b/27_Testing_Errors/tests/input19.c @@ -0,0 +1,12 @@ +int a; +int b; +int c; +int d; +int e; +int main() +{ + a= 2; b= 4; c= 3; d= 2; + e= (a+b) * (c+d); + printint(e); + return(0); +} diff --git a/27_Testing_Errors/tests/input20.c b/27_Testing_Errors/tests/input20.c new file mode 100644 index 0000000..6f273d8 --- /dev/null +++ b/27_Testing_Errors/tests/input20.c @@ -0,0 +1,9 @@ +int a; +int b[25]; + +int main() { + b[3]= 12; + a= b[3]; + printint(a); + return(0); +} diff --git a/27_Testing_Errors/tests/input21.c b/27_Testing_Errors/tests/input21.c new file mode 100644 index 0000000..6993180 --- /dev/null +++ b/27_Testing_Errors/tests/input21.c @@ -0,0 +1,11 @@ +char c; +char *str; + +int main() { + c= '\n'; printint(c); + + for (str= "Hello world\n"; *str != 0; str= str + 1) { + printchar(*str); + } + return(0); +} diff --git a/27_Testing_Errors/tests/input22.c b/27_Testing_Errors/tests/input22.c new file mode 100644 index 0000000..d342905 --- /dev/null +++ b/27_Testing_Errors/tests/input22.c @@ -0,0 +1,20 @@ +char a; char b; char c; +int d; int e; int f; +long g; long h; long i; + + +int main() { + b= 5; c= 7; a= b + c++; printint(a); + e= 5; f= 7; d= e + f++; printint(d); + h= 5; i= 7; g= h + i++; printint(g); + a= b-- + c; printint(a); + d= e-- + f; printint(d); + g= h-- + i; printint(g); + a= ++b + c; printint(a); + d= ++e + f; printint(d); + g= ++h + i; printint(g); + a= b * --c; printint(a); + d= e * --f; printint(d); + g= h * --i; printint(g); + return(0); +} diff --git a/27_Testing_Errors/tests/input23.c b/27_Testing_Errors/tests/input23.c new file mode 100644 index 0000000..e2f2d09 --- /dev/null +++ b/27_Testing_Errors/tests/input23.c @@ -0,0 +1,22 @@ +char *str; +int x; + +int main() { + x= -23; printint(x); + printint(-10 * -10); + + x= 1; x= ~x; printint(x); + + x= 2 > 5; printint(x); + x= !x; printint(x); + x= !x; printint(x); + + x= 13; if (x) { printint(13); } + x= 0; if (!x) { printint(14); } + + for (str= "Hello world\n"; *str; str++) { + printchar(*str); + } + + return(0); +} diff --git a/27_Testing_Errors/tests/input24.c b/27_Testing_Errors/tests/input24.c new file mode 100644 index 0000000..cbdc3d5 --- /dev/null +++ b/27_Testing_Errors/tests/input24.c @@ -0,0 +1,12 @@ +int a; +int b; +int c; +int main() { + a= 42; b= 19; + printint(a & b); + printint(a | b); + printint(a ^ b); + printint(1 << 3); + printint(63 >> 3); + return(0); +} diff --git a/27_Testing_Errors/tests/input25.c b/27_Testing_Errors/tests/input25.c new file mode 100644 index 0000000..dd359ec --- /dev/null +++ b/27_Testing_Errors/tests/input25.c @@ -0,0 +1,15 @@ +int a; +int b; +int c; + +int main() +{ + char z; + int y; + int x; + x= 10; y= 20; z= 30; + printint(x); printint(y); printint(z); + a= 5; b= 15; c= 25; + printint(a); printint(b); printint(c); + return(0); +} diff --git a/27_Testing_Errors/tests/input26.c b/27_Testing_Errors/tests/input26.c new file mode 100644 index 0000000..b5c2674 --- /dev/null +++ b/27_Testing_Errors/tests/input26.c @@ -0,0 +1,16 @@ +int main(int a, char b, long c, int d, int e, int f, int g, int h) { + int i; int j; int k; + + a= 13; printint(a); + b= 23; printint(b); + c= 34; printint(c); + d= 44; printint(d); + e= 54; printint(e); + f= 64; printint(f); + g= 74; printint(g); + h= 84; printint(h); + i= 94; printint(i); + j= 95; printint(j); + k= 96; printint(k); + return(0); +} diff --git a/27_Testing_Errors/tests/input27.c b/27_Testing_Errors/tests/input27.c new file mode 100644 index 0000000..ff43e37 --- /dev/null +++ b/27_Testing_Errors/tests/input27.c @@ -0,0 +1,32 @@ +int param8(int a, int b, int c, int d, int e, int f, int g, int h) { + printint(a); printint(b); printint(c); printint(d); + printint(e); printint(f); printint(g); printint(h); + return(0); +} + +int param5(int a, int b, int c, int d, int e) { + printint(a); printint(b); printint(c); printint(d); printint(e); + return(0); +} + +int param2(int a, int b) { + int c; int d; int e; + c= 3; d= 4; e= 5; + printint(a); printint(b); printint(c); printint(d); printint(e); + return(0); +} + +int param0() { + int a; int b; int c; int d; int e; + a= 1; b= 2; c= 3; d= 4; e= 5; + printint(a); printint(b); printint(c); printint(d); printint(e); + return(0); +} + +int main() { + param8(1,2,3,4,5,6,7,8); + param5(1,2,3,4,5); + param2(1,2); + param0(); + return(0); +} diff --git a/27_Testing_Errors/tests/input28.c b/27_Testing_Errors/tests/input28.c new file mode 100644 index 0000000..53f605a --- /dev/null +++ b/27_Testing_Errors/tests/input28.c @@ -0,0 +1,16 @@ +int param8(int a, int b, int c, int d, int e, int f, int g, int h) { + printint(a); printint(b); printint(c); printint(d); + printint(e); printint(f); printint(g); printint(h); + return(0); +} + +int fred(int a, int b, int c) { + return(a+b+c); +} + +int main() { + int x; + param8(1, 2, 3, 5, 8, 13, 21, 34); + x= fred(2, 3, 4); printint(x); + return(0); +} diff --git a/27_Testing_Errors/tests/input29.c b/27_Testing_Errors/tests/input29.c new file mode 100644 index 0000000..11e2c59 --- /dev/null +++ b/27_Testing_Errors/tests/input29.c @@ -0,0 +1,20 @@ +int param8(int a, int b, int c, int d, int e, int f, int g, int h); +int fred(int a, int b, int c); +int main(); + +int param8(int a, int b, int c, int d, int e, int f, int g, int h) { + printint(a); printint(b); printint(c); printint(d); + printint(e); printint(f); printint(g); printint(h); + return(0); +} + +int fred(int a, int b, int c) { + return(a+b+c); +} + +int main() { + int x; + param8(1, 2, 3, 5, 8, 13, 21, 34); + x= fred(2, 3, 4); printint(x); + return(0); +} diff --git a/27_Testing_Errors/tests/input30.c b/27_Testing_Errors/tests/input30.c new file mode 100644 index 0000000..1d921f3 --- /dev/null +++ b/27_Testing_Errors/tests/input30.c @@ -0,0 +1,22 @@ +int open(char *pathname, int flags); +int read(int fd, char *buf, int count); +int write(int fd, void *buf, int count); +int close(int fd); + +char *buf; + +int main() { + int zin; + int cnt; + + buf= " "; + zin = open("input30.c", 0); + if (zin == -1) { + return (1); + } + while ((cnt = read(zin, buf, 60)) > 0) { + write(1, buf, cnt); + } + close(zin); + return (0); +} diff --git a/27_Testing_Errors/tests/input31.c b/27_Testing_Errors/tests/input31.c new file mode 100644 index 0000000..0ec1218 --- /dev/null +++ b/27_Testing_Errors/tests/input31.c @@ -0,0 +1,4 @@ +int main() { + int x; + x= 2 + + 3 - * / ; +} diff --git a/27_Testing_Errors/tests/input32.c b/27_Testing_Errors/tests/input32.c new file mode 100644 index 0000000..90cafac --- /dev/null +++ b/27_Testing_Errors/tests/input32.c @@ -0,0 +1,3 @@ +int main() { + pizza cow llama sausage; +} diff --git a/27_Testing_Errors/tests/input33.c b/27_Testing_Errors/tests/input33.c new file mode 100644 index 0000000..1a67c69 --- /dev/null +++ b/27_Testing_Errors/tests/input33.c @@ -0,0 +1,3 @@ +int main() { + char *z; return(z); +} diff --git a/27_Testing_Errors/tests/input34.c b/27_Testing_Errors/tests/input34.c new file mode 100644 index 0000000..a83e5b0 --- /dev/null +++ b/27_Testing_Errors/tests/input34.c @@ -0,0 +1,4 @@ +int main() { + int a[12]; + return(0); +} diff --git a/27_Testing_Errors/tests/input35.c b/27_Testing_Errors/tests/input35.c new file mode 100644 index 0000000..3f2d642 --- /dev/null +++ b/27_Testing_Errors/tests/input35.c @@ -0,0 +1,4 @@ +int fred(int a, int b) { + int a; + return(a); +} diff --git a/27_Testing_Errors/tests/input36.c b/27_Testing_Errors/tests/input36.c new file mode 100644 index 0000000..88d5a9b --- /dev/null +++ b/27_Testing_Errors/tests/input36.c @@ -0,0 +1,2 @@ +int fred(int a, char b, int c); +int fred(int a, int b, char c); diff --git a/27_Testing_Errors/tests/input37.c b/27_Testing_Errors/tests/input37.c new file mode 100644 index 0000000..6da34c4 --- /dev/null +++ b/27_Testing_Errors/tests/input37.c @@ -0,0 +1 @@ +int fred(int a, char b +, int z); diff --git a/27_Testing_Errors/tests/input38.c b/27_Testing_Errors/tests/input38.c new file mode 100644 index 0000000..19fb885 --- /dev/null +++ b/27_Testing_Errors/tests/input38.c @@ -0,0 +1,2 @@ +int fred(int a, char b, int c); +int fred(int a, int b, char c, int g); diff --git a/27_Testing_Errors/tests/input39.c b/27_Testing_Errors/tests/input39.c new file mode 100644 index 0000000..04d8d7b --- /dev/null +++ b/27_Testing_Errors/tests/input39.c @@ -0,0 +1 @@ +int main() { int a; } diff --git a/27_Testing_Errors/tests/input40.c b/27_Testing_Errors/tests/input40.c new file mode 100644 index 0000000..e7796b1 --- /dev/null +++ b/27_Testing_Errors/tests/input40.c @@ -0,0 +1 @@ +int main() { int a; a= 5; } diff --git a/27_Testing_Errors/tests/input41.c b/27_Testing_Errors/tests/input41.c new file mode 100644 index 0000000..47c2c0e --- /dev/null +++ b/27_Testing_Errors/tests/input41.c @@ -0,0 +1 @@ +void fred() { return(5); } diff --git a/27_Testing_Errors/tests/input42.c b/27_Testing_Errors/tests/input42.c new file mode 100644 index 0000000..65da2eb --- /dev/null +++ b/27_Testing_Errors/tests/input42.c @@ -0,0 +1 @@ +int main() { fred(5); } diff --git a/27_Testing_Errors/tests/input43.c b/27_Testing_Errors/tests/input43.c new file mode 100644 index 0000000..544e5c0 --- /dev/null +++ b/27_Testing_Errors/tests/input43.c @@ -0,0 +1 @@ +int main() { int a; a= b[4]; } diff --git a/27_Testing_Errors/tests/input44.c b/27_Testing_Errors/tests/input44.c new file mode 100644 index 0000000..63daf47 --- /dev/null +++ b/27_Testing_Errors/tests/input44.c @@ -0,0 +1 @@ +int main() { int a; a= z; } diff --git a/27_Testing_Errors/tests/input45.c b/27_Testing_Errors/tests/input45.c new file mode 100644 index 0000000..356d63d --- /dev/null +++ b/27_Testing_Errors/tests/input45.c @@ -0,0 +1 @@ +int main() { int a; a= &5; } diff --git a/27_Testing_Errors/tests/input46.c b/27_Testing_Errors/tests/input46.c new file mode 100644 index 0000000..9513f95 --- /dev/null +++ b/27_Testing_Errors/tests/input46.c @@ -0,0 +1 @@ +int main() { int a; a= *5; } diff --git a/27_Testing_Errors/tests/input47.c b/27_Testing_Errors/tests/input47.c new file mode 100644 index 0000000..e019f13 --- /dev/null +++ b/27_Testing_Errors/tests/input47.c @@ -0,0 +1 @@ +int main() { int a; a= ++5; } diff --git a/27_Testing_Errors/tests/input48.c b/27_Testing_Errors/tests/input48.c new file mode 100644 index 0000000..64234a3 --- /dev/null +++ b/27_Testing_Errors/tests/input48.c @@ -0,0 +1 @@ +int main() { int a; a= --5; } diff --git a/27_Testing_Errors/tests/input49.c b/27_Testing_Errors/tests/input49.c new file mode 100644 index 0000000..66ddc5b --- /dev/null +++ b/27_Testing_Errors/tests/input49.c @@ -0,0 +1,5 @@ +int main() { + int x; + char y; + y= x; +} diff --git a/27_Testing_Errors/tests/input50.c b/27_Testing_Errors/tests/input50.c new file mode 100644 index 0000000..ccb762b --- /dev/null +++ b/27_Testing_Errors/tests/input50.c @@ -0,0 +1,5 @@ +int main() { + char *a; + char *b; + a= a + b; +} diff --git a/27_Testing_Errors/tests/input51.c b/27_Testing_Errors/tests/input51.c new file mode 100644 index 0000000..9b91f52 --- /dev/null +++ b/27_Testing_Errors/tests/input51.c @@ -0,0 +1,3 @@ +int main() { + char a; a= 'fred'; +} diff --git a/27_Testing_Errors/tests/input52.c b/27_Testing_Errors/tests/input52.c new file mode 100644 index 0000000..ce418dc --- /dev/null +++ b/27_Testing_Errors/tests/input52.c @@ -0,0 +1,4 @@ +int main() { + int a; + a= $5.00; +} diff --git a/27_Testing_Errors/tests/input53.c b/27_Testing_Errors/tests/input53.c new file mode 100644 index 0000000..3eae26e --- /dev/null +++ b/27_Testing_Errors/tests/input53.c @@ -0,0 +1,7 @@ +int printf(char *fmt); + +int main() +{ + printf("Hello world, %d\n", 23); + return(0); +} diff --git a/27_Testing_Errors/tests/input54.c b/27_Testing_Errors/tests/input54.c new file mode 100644 index 0000000..57d8a03 --- /dev/null +++ b/27_Testing_Errors/tests/input54.c @@ -0,0 +1,10 @@ +int printf(char *fmt); + +int main() +{ + int i; + for (i=0; i < 20; i++) { + printf("Hello world, %d\n", i); + } + return(0); +} diff --git a/27_Testing_Errors/tests/mktests b/27_Testing_Errors/tests/mktests new file mode 100755 index 0000000..957938c --- /dev/null +++ b/27_Testing_Errors/tests/mktests @@ -0,0 +1,22 @@ +#!/bin/sh +# Make the output files for each test + +# Build our compiler if needed +if [ ! -f ../comp1 ] +then (cd ..; make) +fi + +for i in input*c +do if [ ! -f "out.$i" -a ! -f "err.$i" ] + then + ../comp1 $i 2> "err.$i" + # If the err file is empty + if [ ! -s "err.$i" ] + then + rm -f "err.$i" + cc -o out $i ../lib/printint.c + ./out > "out.$i" + fi + fi + rm -f out out.s +done diff --git a/27_Testing_Errors/tests/out.input01.c b/27_Testing_Errors/tests/out.input01.c new file mode 100644 index 0000000..4502630 --- /dev/null +++ b/27_Testing_Errors/tests/out.input01.c @@ -0,0 +1,3 @@ +36 +10 +25 diff --git a/27_Testing_Errors/tests/out.input02.c b/27_Testing_Errors/tests/out.input02.c new file mode 100644 index 0000000..98d9bcb --- /dev/null +++ b/27_Testing_Errors/tests/out.input02.c @@ -0,0 +1 @@ +17 diff --git a/27_Testing_Errors/tests/out.input03.c b/27_Testing_Errors/tests/out.input03.c new file mode 100644 index 0000000..8a1218a --- /dev/null +++ b/27_Testing_Errors/tests/out.input03.c @@ -0,0 +1,5 @@ +1 +2 +3 +4 +5 diff --git a/27_Testing_Errors/tests/out.input04.c b/27_Testing_Errors/tests/out.input04.c new file mode 100644 index 0000000..bb08505 --- /dev/null +++ b/27_Testing_Errors/tests/out.input04.c @@ -0,0 +1,9 @@ +1 +1 +1 +1 +1 +1 +1 +1 +1 diff --git a/27_Testing_Errors/tests/out.input05.c b/27_Testing_Errors/tests/out.input05.c new file mode 100644 index 0000000..1e8b314 --- /dev/null +++ b/27_Testing_Errors/tests/out.input05.c @@ -0,0 +1 @@ +6 diff --git a/27_Testing_Errors/tests/out.input06.c b/27_Testing_Errors/tests/out.input06.c new file mode 100644 index 0000000..f00c965 --- /dev/null +++ b/27_Testing_Errors/tests/out.input06.c @@ -0,0 +1,10 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff --git a/27_Testing_Errors/tests/out.input07.c b/27_Testing_Errors/tests/out.input07.c new file mode 100644 index 0000000..f00c965 --- /dev/null +++ b/27_Testing_Errors/tests/out.input07.c @@ -0,0 +1,10 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff --git a/27_Testing_Errors/tests/out.input08.c b/27_Testing_Errors/tests/out.input08.c new file mode 100644 index 0000000..f00c965 --- /dev/null +++ b/27_Testing_Errors/tests/out.input08.c @@ -0,0 +1,10 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff --git a/27_Testing_Errors/tests/out.input09.c b/27_Testing_Errors/tests/out.input09.c new file mode 100644 index 0000000..f00c965 --- /dev/null +++ b/27_Testing_Errors/tests/out.input09.c @@ -0,0 +1,10 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 diff --git a/27_Testing_Errors/tests/out.input10.c b/27_Testing_Errors/tests/out.input10.c new file mode 100644 index 0000000..8215f2b --- /dev/null +++ b/27_Testing_Errors/tests/out.input10.c @@ -0,0 +1,12 @@ +20 +10 +1 +2 +3 +4 +5 +253 +254 +255 +0 +1 diff --git a/27_Testing_Errors/tests/out.input11.c b/27_Testing_Errors/tests/out.input11.c new file mode 100644 index 0000000..07233a7 --- /dev/null +++ b/27_Testing_Errors/tests/out.input11.c @@ -0,0 +1,20 @@ +10 +20 +30 +1 +2 +3 +4 +5 +253 +254 +255 +0 +1 +2 +3 +1 +2 +3 +4 +5 diff --git a/27_Testing_Errors/tests/out.input12.c b/27_Testing_Errors/tests/out.input12.c new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/27_Testing_Errors/tests/out.input12.c @@ -0,0 +1 @@ +5 diff --git a/27_Testing_Errors/tests/out.input13.c b/27_Testing_Errors/tests/out.input13.c new file mode 100644 index 0000000..c4e0c9a --- /dev/null +++ b/27_Testing_Errors/tests/out.input13.c @@ -0,0 +1,2 @@ +23 +56 diff --git a/27_Testing_Errors/tests/out.input14.c b/27_Testing_Errors/tests/out.input14.c new file mode 100644 index 0000000..300ed6f --- /dev/null +++ b/27_Testing_Errors/tests/out.input14.c @@ -0,0 +1,3 @@ +10 +20 +30 diff --git a/27_Testing_Errors/tests/out.input15.c b/27_Testing_Errors/tests/out.input15.c new file mode 100644 index 0000000..e923c86 --- /dev/null +++ b/27_Testing_Errors/tests/out.input15.c @@ -0,0 +1,4 @@ +18 +18 +12 +12 diff --git a/27_Testing_Errors/tests/out.input16.c b/27_Testing_Errors/tests/out.input16.c new file mode 100644 index 0000000..7a951c4 --- /dev/null +++ b/27_Testing_Errors/tests/out.input16.c @@ -0,0 +1,2 @@ +12 +18 diff --git a/27_Testing_Errors/tests/out.input17.c b/27_Testing_Errors/tests/out.input17.c new file mode 100644 index 0000000..984cd0d --- /dev/null +++ b/27_Testing_Errors/tests/out.input17.c @@ -0,0 +1,2 @@ +19 +12 diff --git a/27_Testing_Errors/tests/out.input18.c b/27_Testing_Errors/tests/out.input18.c new file mode 100644 index 0000000..3e66c60 --- /dev/null +++ b/27_Testing_Errors/tests/out.input18.c @@ -0,0 +1,2 @@ +34 +34 diff --git a/27_Testing_Errors/tests/out.input18a.c b/27_Testing_Errors/tests/out.input18a.c new file mode 100644 index 0000000..1946125 --- /dev/null +++ b/27_Testing_Errors/tests/out.input18a.c @@ -0,0 +1,2 @@ +15 +16 diff --git a/27_Testing_Errors/tests/out.input19.c b/27_Testing_Errors/tests/out.input19.c new file mode 100644 index 0000000..64bb6b7 --- /dev/null +++ b/27_Testing_Errors/tests/out.input19.c @@ -0,0 +1 @@ +30 diff --git a/27_Testing_Errors/tests/out.input20.c b/27_Testing_Errors/tests/out.input20.c new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/27_Testing_Errors/tests/out.input20.c @@ -0,0 +1 @@ +12 diff --git a/27_Testing_Errors/tests/out.input21.c b/27_Testing_Errors/tests/out.input21.c new file mode 100644 index 0000000..7b83769 --- /dev/null +++ b/27_Testing_Errors/tests/out.input21.c @@ -0,0 +1,2 @@ +10 +Hello world diff --git a/27_Testing_Errors/tests/out.input22.c b/27_Testing_Errors/tests/out.input22.c new file mode 100644 index 0000000..6086532 --- /dev/null +++ b/27_Testing_Errors/tests/out.input22.c @@ -0,0 +1,12 @@ +12 +12 +12 +13 +13 +13 +13 +13 +13 +35 +35 +35 diff --git a/27_Testing_Errors/tests/out.input23.c b/27_Testing_Errors/tests/out.input23.c new file mode 100644 index 0000000..90ae7ff --- /dev/null +++ b/27_Testing_Errors/tests/out.input23.c @@ -0,0 +1,9 @@ +-23 +100 +-2 +0 +1 +0 +13 +14 +Hello world diff --git a/27_Testing_Errors/tests/out.input24.c b/27_Testing_Errors/tests/out.input24.c new file mode 100644 index 0000000..993b6df --- /dev/null +++ b/27_Testing_Errors/tests/out.input24.c @@ -0,0 +1,5 @@ +2 +59 +57 +8 +7 diff --git a/27_Testing_Errors/tests/out.input25.c b/27_Testing_Errors/tests/out.input25.c new file mode 100644 index 0000000..6104da8 --- /dev/null +++ b/27_Testing_Errors/tests/out.input25.c @@ -0,0 +1,6 @@ +10 +20 +30 +5 +15 +25 diff --git a/27_Testing_Errors/tests/out.input26.c b/27_Testing_Errors/tests/out.input26.c new file mode 100644 index 0000000..819bea4 --- /dev/null +++ b/27_Testing_Errors/tests/out.input26.c @@ -0,0 +1,11 @@ +13 +23 +34 +44 +54 +64 +74 +84 +94 +95 +96 diff --git a/27_Testing_Errors/tests/out.input27.c b/27_Testing_Errors/tests/out.input27.c new file mode 100644 index 0000000..58b007c --- /dev/null +++ b/27_Testing_Errors/tests/out.input27.c @@ -0,0 +1,23 @@ +1 +2 +3 +4 +5 +6 +7 +8 +1 +2 +3 +4 +5 +1 +2 +3 +4 +5 +1 +2 +3 +4 +5 diff --git a/27_Testing_Errors/tests/out.input28.c b/27_Testing_Errors/tests/out.input28.c new file mode 100644 index 0000000..adff135 --- /dev/null +++ b/27_Testing_Errors/tests/out.input28.c @@ -0,0 +1,9 @@ +1 +2 +3 +5 +8 +13 +21 +34 +9 diff --git a/27_Testing_Errors/tests/out.input29.c b/27_Testing_Errors/tests/out.input29.c new file mode 100644 index 0000000..adff135 --- /dev/null +++ b/27_Testing_Errors/tests/out.input29.c @@ -0,0 +1,9 @@ +1 +2 +3 +5 +8 +13 +21 +34 +9 diff --git a/27_Testing_Errors/tests/out.input30.c b/27_Testing_Errors/tests/out.input30.c new file mode 100644 index 0000000..1d921f3 --- /dev/null +++ b/27_Testing_Errors/tests/out.input30.c @@ -0,0 +1,22 @@ +int open(char *pathname, int flags); +int read(int fd, char *buf, int count); +int write(int fd, void *buf, int count); +int close(int fd); + +char *buf; + +int main() { + int zin; + int cnt; + + buf= " "; + zin = open("input30.c", 0); + if (zin == -1) { + return (1); + } + while ((cnt = read(zin, buf, 60)) > 0) { + write(1, buf, cnt); + } + close(zin); + return (0); +} diff --git a/27_Testing_Errors/tests/out.input53.c b/27_Testing_Errors/tests/out.input53.c new file mode 100644 index 0000000..bf0caa2 --- /dev/null +++ b/27_Testing_Errors/tests/out.input53.c @@ -0,0 +1 @@ +Hello world, 23 diff --git a/27_Testing_Errors/tests/out.input54.c b/27_Testing_Errors/tests/out.input54.c new file mode 100644 index 0000000..176f146 --- /dev/null +++ b/27_Testing_Errors/tests/out.input54.c @@ -0,0 +1,20 @@ +Hello world, 0 +Hello world, 1 +Hello world, 2 +Hello world, 3 +Hello world, 4 +Hello world, 5 +Hello world, 6 +Hello world, 7 +Hello world, 8 +Hello world, 9 +Hello world, 10 +Hello world, 11 +Hello world, 12 +Hello world, 13 +Hello world, 14 +Hello world, 15 +Hello world, 16 +Hello world, 17 +Hello world, 18 +Hello world, 19 diff --git a/27_Testing_Errors/tests/runtests b/27_Testing_Errors/tests/runtests new file mode 100755 index 0000000..bda62a3 --- /dev/null +++ b/27_Testing_Errors/tests/runtests @@ -0,0 +1,64 @@ +#!/bin/sh +# Run each test and compare +# against known good output + +# Build our compiler if needed +if [ ! -f ../comp1 ] +then (cd ..; make) +fi + +# Try to use each input source file +for i in input* +# We can't do anything if there's no file to test against +do if [ ! -f "out.$i" -a ! -f "err.$i" ] + then echo "Can't run test on $i, no output file!" + + # Output file: compile the source, run it and + # capture the output, and compare it against + # the known-good output + else if [ -f "out.$i" ] + then + # Print the test name, compile it + # with our compiler + echo -n $i + ../comp1 $i + + # Assemble the output, run it + # and get the output in trial.$i + cc -o out out.s ../lib/printint.c + ./out > trial.$i + + # Compare this agains the correct output + cmp -s "out.$i" "trial.$i" + + # If different, announce failure + # and print out the difference + if [ "$?" -eq "1" ] + then echo ": failed" + diff -c "out.$i" "trial.$i" + echo + + # No failure, so announce success + else echo ": OK" + fi + + # Error file: compile the source and + # capture the error messages. Compare + # against the known-bad output. Same + # mechanism as before + else if [ -f "err.$i" ] + then + echo -n $i + ../comp1 $i 2> "trial.$i" + cmp -s "err.$i" "trial.$i" + if [ "$?" -eq "1" ] + then echo ": failed" + diff -c "err.$i" "trial.$i" + echo + else echo ": OK" + fi + fi + fi + fi + rm -f out out.s "trial.$i" +done diff --git a/27_Testing_Errors/tree.c b/27_Testing_Errors/tree.c new file mode 100644 index 0000000..44f4f19 --- /dev/null +++ b/27_Testing_Errors/tree.c @@ -0,0 +1,186 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// AST tree functions +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Build and return a generic AST node +struct ASTnode *mkastnode(int op, int type, + struct ASTnode *left, + struct ASTnode *mid, + struct ASTnode *right, int intvalue) { + struct ASTnode *n; + + // Malloc a new ASTnode + n = (struct ASTnode *) malloc(sizeof(struct ASTnode)); + if (n == NULL) + fatal("Unable to malloc in mkastnode()"); + + // Copy in the field values and return it + n->op = op; + n->type = type; + n->left = left; + n->mid = mid; + n->right = right; + n->v.intvalue = intvalue; + return (n); +} + + +// Make an AST leaf node +struct ASTnode *mkastleaf(int op, int type, int intvalue) { + return (mkastnode(op, type, NULL, NULL, NULL, intvalue)); +} + +// Make a unary AST node: only one child +struct ASTnode *mkuastunary(int op, int type, struct ASTnode *left, + int intvalue) { + return (mkastnode(op, type, left, NULL, NULL, intvalue)); +} + +// Generate and return a new label number +// just for AST dumping purposes +static int gendumplabel(void) { + static int id = 1; + return (id++); +} + +// Given an AST tree, print it out and follow the +// traversal of the tree that genAST() follows +void dumpAST(struct ASTnode *n, int label, int level) { + int Lfalse, Lstart, Lend; + + + switch (n->op) { + case A_IF: + Lfalse = gendumplabel(); + for (int i = 0; i < level; i++) + fprintf(stdout, " "); + fprintf(stdout, "A_IF"); + if (n->right) { + Lend = gendumplabel(); + fprintf(stdout, ", end L%d", Lend); + } + fprintf(stdout, "\n"); + dumpAST(n->left, Lfalse, level + 2); + dumpAST(n->mid, NOLABEL, level + 2); + if (n->right) + dumpAST(n->right, NOLABEL, level + 2); + return; + case A_WHILE: + Lstart = gendumplabel(); + for (int i = 0; i < level; i++) + fprintf(stdout, " "); + fprintf(stdout, "A_WHILE, start L%d\n", Lstart); + Lend = gendumplabel(); + dumpAST(n->left, Lend, level + 2); + dumpAST(n->right, NOLABEL, level + 2); + return; + } + + // Reset level to -2 for A_GLUE + if (n->op == A_GLUE) + level = -2; + + // General AST node handling + if (n->left) + dumpAST(n->left, NOLABEL, level + 2); + if (n->right) + dumpAST(n->right, NOLABEL, level + 2); + + + for (int i = 0; i < level; i++) + fprintf(stdout, " "); + switch (n->op) { + case A_GLUE: + fprintf(stdout, "\n\n"); + return; + case A_FUNCTION: + fprintf(stdout, "A_FUNCTION %s\n", Symtable[n->v.id].name); + return; + case A_ADD: + fprintf(stdout, "A_ADD\n"); + return; + case A_SUBTRACT: + fprintf(stdout, "A_SUBTRACT\n"); + return; + case A_MULTIPLY: + fprintf(stdout, "A_MULTIPLY\n"); + return; + case A_DIVIDE: + fprintf(stdout, "A_DIVIDE\n"); + return; + case A_EQ: + fprintf(stdout, "A_EQ\n"); + return; + case A_NE: + fprintf(stdout, "A_NE\n"); + return; + case A_LT: + fprintf(stdout, "A_LE\n"); + return; + case A_GT: + fprintf(stdout, "A_GT\n"); + return; + case A_LE: + fprintf(stdout, "A_LE\n"); + return; + case A_GE: + fprintf(stdout, "A_GE\n"); + return; + case A_INTLIT: + fprintf(stdout, "A_INTLIT %d\n", n->v.intvalue); + return; + case A_STRLIT: + fprintf(stdout, "A_STRLIT rval label L%d\n", n->v.id); + return; + case A_IDENT: + if (n->rvalue) + fprintf(stdout, "A_IDENT rval %s\n", Symtable[n->v.id].name); + else + fprintf(stdout, "A_IDENT %s\n", Symtable[n->v.id].name); + return; + case A_ASSIGN: + fprintf(stdout, "A_ASSIGN\n"); + return; + case A_WIDEN: + fprintf(stdout, "A_WIDEN\n"); + return; + case A_RETURN: + fprintf(stdout, "A_RETURN\n"); + return; + case A_FUNCCALL: + fprintf(stdout, "A_FUNCCALL %s\n", Symtable[n->v.id].name); + return; + case A_ADDR: + fprintf(stdout, "A_ADDR %s\n", Symtable[n->v.id].name); + return; + case A_DEREF: + if (n->rvalue) + fprintf(stdout, "A_DEREF rval\n"); + else + fprintf(stdout, "A_DEREF\n"); + return; + case A_SCALE: + fprintf(stdout, "A_SCALE %d\n", n->v.size); + return; + case A_PREINC: + fprintf(stdout, "A_PREINC %s\n", Symtable[n->v.id].name); + return; + case A_PREDEC: + fprintf(stdout, "A_PREDEC %s\n", Symtable[n->v.id].name); + return; + case A_POSTINC: + fprintf(stdout, "A_POSTINC\n"); + return; + case A_POSTDEC: + fprintf(stdout, "A_POSTDEC\n"); + return; + case A_NEGATE: + fprintf(stdout, "A_NEGATE\n"); + return; + default: + fatald("Unknown dumpAST operator", n->op); + } +} diff --git a/27_Testing_Errors/types.c b/27_Testing_Errors/types.c new file mode 100644 index 0000000..16e3f18 --- /dev/null +++ b/27_Testing_Errors/types.c @@ -0,0 +1,122 @@ +#include "defs.h" +#include "data.h" +#include "decl.h" + +// Types and type handling +// Copyright (c) 2019 Warren Toomey, GPL3 + +// Return true if a type is an int type +// of any size, false otherwise +int inttype(int type) { + if (type == P_CHAR || type == P_INT || type == P_LONG) + return (1); + return (0); +} + +// Return true if a type is of pointer type +int ptrtype(int type) { + if (type == P_VOIDPTR || type == P_CHARPTR || + type == P_INTPTR || type == P_LONGPTR) + return (1); + return (0); +} + +// Given a primitive type, return +// the type which is a pointer to it +int pointer_to(int type) { + int newtype; + switch (type) { + case P_VOID: + newtype = P_VOIDPTR; + break; + case P_CHAR: + newtype = P_CHARPTR; + break; + case P_INT: + newtype = P_INTPTR; + break; + case P_LONG: + newtype = P_LONGPTR; + break; + default: + fatald("Unrecognised in pointer_to: type", type); + } + return (newtype); +} + +// Given a primitive pointer type, return +// the type which it points to +int value_at(int type) { + int newtype; + switch (type) { + case P_VOIDPTR: + newtype = P_VOID; + break; + case P_CHARPTR: + newtype = P_CHAR; + break; + case P_INTPTR: + newtype = P_INT; + break; + case P_LONGPTR: + newtype = P_LONG; + break; + default: + fatald("Unrecognised in value_at: type", type); + } + return (newtype); +} + +// Given an AST tree and a type which we want it to become, +// possibly modify the tree by widening or scaling so that +// it is compatible with this type. Return the original tree +// if no changes occurred, a modified tree, or NULL if the +// tree is not compatible with the given type. +// If this will be part of a binary operation, the AST op is not zero. +struct ASTnode *modify_type(struct ASTnode *tree, int rtype, int op) { + int ltype; + int lsize, rsize; + + ltype = tree->type; + + // Compare scalar int types + if (inttype(ltype) && inttype(rtype)) { + + // Both types same, nothing to do + if (ltype == rtype) + return (tree); + + // Get the sizes for each type + lsize = genprimsize(ltype); + rsize = genprimsize(rtype); + + // Tree's size is too big + if (lsize > rsize) + return (NULL); + + // Widen to the right + if (rsize > lsize) + return (mkuastunary(A_WIDEN, rtype, tree, 0)); + } + // For pointers on the left + if (ptrtype(ltype)) { + // OK is same type on right and not doing a binary op + if (op == 0 && ltype == rtype) + return (tree); + } + // We can scale only on A_ADD or A_SUBTRACT operation + if (op == A_ADD || op == A_SUBTRACT) { + + // Left is int type, right is pointer type and the size + // of the original type is >1: scale the left + if (inttype(ltype) && ptrtype(rtype)) { + rsize = genprimsize(value_at(rtype)); + if (rsize > 1) + return (mkuastunary(A_SCALE, rtype, tree, rsize)); + else + return (tree); // Size 1, no need to scale + } + } + // If we get here, the types are not compatible + return (NULL); +} diff --git a/Readme.md b/Readme.md index 6842d36..9ecfd6c 100644 --- a/Readme.md +++ b/Readme.md @@ -36,6 +36,7 @@ Here are the steps I've taken so far: + [Part 24](24_Function_Params/Readme.md): Function Parameters + [Part 25](25_Function_Arguments/Readme.md): Function Calls and Arguments + [Part 26](26_Prototypes/Readme.md): Function Prototypes + + [Part 27](27_Testing_Errors/Readme.md): Regression Testing and a Nice Surprise There isn't a schedule or timeline for the future parts, so just keep checking back here to see if I've written any more.