Initial commit
This commit is contained in:
commit
5dbf58b460
28
compiler/CodeGenVisitor.cpp
Normal file
28
compiler/CodeGenVisitor.cpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#include "CodeGenVisitor.h"
|
||||||
|
|
||||||
|
antlrcpp::Any CodeGenVisitor::visitProg(ifccParser::ProgContext *ctx)
|
||||||
|
{
|
||||||
|
#ifdef __APPLE__
|
||||||
|
std::cout<< ".globl _main\n" ;
|
||||||
|
std::cout<< " _main: \n" ;
|
||||||
|
#else
|
||||||
|
std::cout<< ".globl main\n" ;
|
||||||
|
std::cout<< " main: \n" ;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->visit( ctx->return_stmt() );
|
||||||
|
|
||||||
|
std::cout << " ret\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
antlrcpp::Any CodeGenVisitor::visitReturn_stmt(ifccParser::Return_stmtContext *ctx)
|
||||||
|
{
|
||||||
|
int retval = stoi(ctx->CONST()->getText());
|
||||||
|
|
||||||
|
std::cout << " movl $"<<retval<<", %eax\n" ;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
13
compiler/CodeGenVisitor.h
Normal file
13
compiler/CodeGenVisitor.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "antlr4-runtime.h"
|
||||||
|
#include "generated/ifccBaseVisitor.h"
|
||||||
|
|
||||||
|
|
||||||
|
class CodeGenVisitor : public ifccBaseVisitor {
|
||||||
|
public:
|
||||||
|
virtual antlrcpp::Any visitProg(ifccParser::ProgContext *ctx) override ;
|
||||||
|
virtual antlrcpp::Any visitReturn_stmt(ifccParser::Return_stmtContext *ctx) override;
|
||||||
|
};
|
||||||
|
|
||||||
71
compiler/Makefile
Normal file
71
compiler/Makefile
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# config.mk contains the paths to antlr4 etc.
|
||||||
|
# Each student should have a config.mk corresponding to her system.
|
||||||
|
# Examples are ubuntu.mk, DI.mk, fedora.mk
|
||||||
|
# Then config.mk should be in the .gitignore of your project
|
||||||
|
include config.mk
|
||||||
|
|
||||||
|
CC=g++
|
||||||
|
CCFLAGS=-g -c -std=c++17 -I$(ANTLRINC) -Wno-attributes # -Wno-defaulted-function-deleted -Wno-unknown-warning-option
|
||||||
|
LDFLAGS=-g
|
||||||
|
|
||||||
|
default: all
|
||||||
|
all: ifcc
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
# link together all pieces of our compiler
|
||||||
|
OBJECTS=build/ifccBaseVisitor.o \
|
||||||
|
build/ifccLexer.o \
|
||||||
|
build/ifccVisitor.o \
|
||||||
|
build/ifccParser.o \
|
||||||
|
build/main.o \
|
||||||
|
build/CodeGenVisitor.o
|
||||||
|
|
||||||
|
ifcc: $(OBJECTS)
|
||||||
|
@mkdir -p build
|
||||||
|
$(CC) $(LDFLAGS) build/*.o $(ANTLRLIB) -o ifcc
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
# compile our hand-writen C++ code: main(), CodeGenVisitor, etc.
|
||||||
|
build/%.o: %.cpp generated/ifccParser.cpp
|
||||||
|
@mkdir -p build
|
||||||
|
$(CC) $(CCFLAGS) -MMD -o $@ $<
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
# compile all the antlr-generated C++
|
||||||
|
build/%.o: generated/%.cpp
|
||||||
|
@mkdir -p build
|
||||||
|
$(CC) $(CCFLAGS) -MMD -o $@ $<
|
||||||
|
|
||||||
|
# automagic dependency management: `gcc -MMD` generates all the .d files for us
|
||||||
|
-include build/*.d
|
||||||
|
build/%.d:
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
# generate the C++ implementation of our Lexer/Parser/Visitor
|
||||||
|
generated/ifccLexer.cpp: generated/ifccParser.cpp
|
||||||
|
generated/ifccVisitor.cpp: generated/ifccParser.cpp
|
||||||
|
generated/ifccBaseVisitor.cpp: generated/ifccParser.cpp
|
||||||
|
generated/ifccParser.cpp: ifcc.g4
|
||||||
|
@mkdir -p generated
|
||||||
|
java -jar $(ANTLRJAR) -visitor -no-listener -Dlanguage=Cpp -o generated ifcc.g4
|
||||||
|
|
||||||
|
# prevent automatic cleanup of "intermediate" files like ifccLexer.cpp etc
|
||||||
|
.PRECIOUS: generated/ifcc%.cpp
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
# view the parse tree in a graphical window
|
||||||
|
|
||||||
|
# Usage: `make gui FILE=path/to/your/file.c`
|
||||||
|
FILE ?= ../tests/testfiles/1_return42.c
|
||||||
|
|
||||||
|
gui:
|
||||||
|
@mkdir -p generated build
|
||||||
|
java -jar $(ANTLRJAR) -Dlanguage=Java -o generated ifcc.g4
|
||||||
|
javac -cp $(ANTLRJAR) -d build generated/*.java
|
||||||
|
java -cp $(ANTLRJAR):build org.antlr.v4.gui.TestRig ifcc axiom -gui $(FILE)
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
# delete all machine-generated files
|
||||||
|
clean:
|
||||||
|
rm -rf build generated
|
||||||
|
rm -f ifcc
|
||||||
4
compiler/config-IF501.mk
Normal file
4
compiler/config-IF501.mk
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# these values work with the "install-antlr.sh" script provided for the PLD
|
||||||
|
ANTLRJAR=../antlr/jar/antlr-4.9.2-complete.jar
|
||||||
|
ANTLRINC=../antlr/include
|
||||||
|
ANTLRLIB=../antlr/lib/libantlr4-runtime.a
|
||||||
3
compiler/config-wsl-2025.mk
Normal file
3
compiler/config-wsl-2025.mk
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ANTLRJAR=/home/$(USER)/antlr4-install/antlr-4.13.2-complete.jar
|
||||||
|
ANTLRINC=/usr/local/include/antlr4-runtime/
|
||||||
|
ANTLRLIB=/usr/local/lib/libantlr4-runtime.a
|
||||||
3
compiler/config.mk
Normal file
3
compiler/config.mk
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ANTLRJAR=/home/$(USER)/antlr4-install/antlr-4.13.2-complete.jar
|
||||||
|
ANTLRINC=/usr/local/include/antlr4-runtime/
|
||||||
|
ANTLRLIB=/usr/local/lib/libantlr4-runtime.a
|
||||||
13
compiler/ifcc.g4
Normal file
13
compiler/ifcc.g4
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
grammar ifcc;
|
||||||
|
|
||||||
|
axiom : prog EOF ;
|
||||||
|
|
||||||
|
prog : 'int' 'main' '(' ')' '{' return_stmt '}' ;
|
||||||
|
|
||||||
|
return_stmt: RETURN CONST ';' ;
|
||||||
|
|
||||||
|
RETURN : 'return' ;
|
||||||
|
CONST : [0-9]+ ;
|
||||||
|
COMMENT : '/*' .*? '*/' -> skip ;
|
||||||
|
DIRECTIVE : '#' .*? '\n' -> skip ;
|
||||||
|
WS : [ \t\r\n] -> channel(HIDDEN);
|
||||||
56
compiler/main.cpp
Normal file
56
compiler/main.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "antlr4-runtime.h"
|
||||||
|
#include "generated/ifccLexer.h"
|
||||||
|
#include "generated/ifccParser.h"
|
||||||
|
#include "generated/ifccBaseVisitor.h"
|
||||||
|
|
||||||
|
#include "CodeGenVisitor.h"
|
||||||
|
|
||||||
|
using namespace antlr4;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
int main(int argn, const char **argv)
|
||||||
|
{
|
||||||
|
stringstream in;
|
||||||
|
if (argn==2)
|
||||||
|
{
|
||||||
|
ifstream lecture(argv[1]);
|
||||||
|
if( !lecture.good() )
|
||||||
|
{
|
||||||
|
cerr<<"error: cannot read file: " << argv[1] << endl ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
in << lecture.rdbuf();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cerr << "usage: ifcc path/to/file.c" << endl ;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ANTLRInputStream input(in.str());
|
||||||
|
|
||||||
|
ifccLexer lexer(&input);
|
||||||
|
CommonTokenStream tokens(&lexer);
|
||||||
|
|
||||||
|
tokens.fill();
|
||||||
|
|
||||||
|
ifccParser parser(&tokens);
|
||||||
|
tree::ParseTree* tree = parser.axiom();
|
||||||
|
|
||||||
|
if(parser.getNumberOfSyntaxErrors() != 0)
|
||||||
|
{
|
||||||
|
cerr << "error: syntax error during parsing" << endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CodeGenVisitor v;
|
||||||
|
v.visit(tree);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
333
ifcc-test.py
Executable file
333
ifcc-test.py
Executable file
@ -0,0 +1,333 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# In "multiple-files mode" (by default), this script runs both GCC and
|
||||||
|
# IFCC on each test-case provided and compares the results.
|
||||||
|
#
|
||||||
|
# In "single-file mode", we mimic the CLI behaviour of GCC i.e. we
|
||||||
|
# interpret the '-o', '-S', and '-c' options.
|
||||||
|
#
|
||||||
|
# Run "python3 ifcc-test.py --help" for more info.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def run_command(string, logfile=None, toscreen=False):
|
||||||
|
""" execute `string` as a shell command. Maybe write stdout+stderr to `logfile` and/or to the toscreen.
|
||||||
|
return the exit status"""
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
print("ifcc-test.py: "+string)
|
||||||
|
|
||||||
|
process=subprocess.Popen(string,shell=True,
|
||||||
|
stderr=subprocess.STDOUT,stdout=subprocess.PIPE,
|
||||||
|
text=True,bufsize=0)
|
||||||
|
if logfile:
|
||||||
|
logfile=open(logfile,'w')
|
||||||
|
|
||||||
|
while True:
|
||||||
|
output = process.stdout.readline()
|
||||||
|
if len(output) == 0: # only happens when 'process' has terminated
|
||||||
|
break
|
||||||
|
if logfile: logfile.write(output)
|
||||||
|
if toscreen: sys.stdout.write(output)
|
||||||
|
process.wait() # collect child exit status
|
||||||
|
assert process.returncode != None # sanity check (I was using poll() instead of wait() previously, and did see some unsanity)
|
||||||
|
if logfile:
|
||||||
|
logfile.write(f'\nexit status: {process.returncode}\n')
|
||||||
|
return process.returncode
|
||||||
|
|
||||||
|
def dumpfile(name,quiet=False):
|
||||||
|
data=open(name,"rb").read().decode('utf-8',errors='ignore')
|
||||||
|
if not quiet:
|
||||||
|
print(data,end='')
|
||||||
|
return data
|
||||||
|
|
||||||
|
######################################################################################
|
||||||
|
## ARGPARSE step: make sense of our command-line arguments
|
||||||
|
|
||||||
|
# This is where we decide between multiple-files
|
||||||
|
# mode and single-file mode
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
import shutil
|
||||||
|
width = shutil.get_terminal_size().columns-2
|
||||||
|
twf=lambda text: textwrap.fill(text,width,initial_indent=' '*4,subsequent_indent=' '*6)
|
||||||
|
|
||||||
|
argparser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
description = "Testing script for the ifcc compiler. operates in one of two modes:\n\n"
|
||||||
|
+twf("- Multiple-files mode (by default): Compile several programs with both GCC and IFCC, run them, and compare the results.",)+"\n\n"
|
||||||
|
+twf("- Single-file mode (with options -o,-c and/or -S): Compile and/or assemble and/or link a single program."),
|
||||||
|
epilog="examples:\n\n"
|
||||||
|
+twf("python3 ifcc-test.py testfiles")+'\n'
|
||||||
|
+twf("python3 ifcc-test.py path/to/some/dir/*.c path/to/some/other/dir")+'\n'
|
||||||
|
+'\n'
|
||||||
|
+twf("python3 ifcc-test.py -o ./myprog path/to/some/source.c")+'\n'
|
||||||
|
+twf("python3 ifcc-test.py -S -o truc.s truc.c")+'\n'
|
||||||
|
,
|
||||||
|
)
|
||||||
|
|
||||||
|
argparser.add_argument('input',metavar='PATH',nargs='+',help='For each path given:'
|
||||||
|
+' if it\'s a file, use this file;'
|
||||||
|
+' if it\'s a directory, use all *.c files under this subtree')
|
||||||
|
|
||||||
|
argparser.add_argument('-v','--verbose',action="count",default=0,
|
||||||
|
help='increase verbosity level. You can use this option multiple times.')
|
||||||
|
argparser.add_argument('-d','--debug',action="count",default=0,
|
||||||
|
help='increase quantity of debugging messages (only useful to debug the test script itself)')
|
||||||
|
argparser.add_argument('-S',action = "store_true", help='single-file mode: compile from C to assembly, but do not assemble')
|
||||||
|
argparser.add_argument('-c',action = "store_true", help='single-file mode: compile/assemble to machine code, but do not link')
|
||||||
|
argparser.add_argument('-o','--output',metavar = 'OUTPUTNAME', help='single-file mode: write output to that file')
|
||||||
|
|
||||||
|
args=argparser.parse_args()
|
||||||
|
|
||||||
|
if args.debug >=2:
|
||||||
|
print('debug: command-line arguments '+str(args))
|
||||||
|
|
||||||
|
orig_cwd=os.getcwd()
|
||||||
|
if "ifcc-test-output" in orig_cwd:
|
||||||
|
print('error: cannot run ifcc-test.py from within its own output directory')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
pld_base_dir=os.path.normpath(os.path.dirname(__file__))
|
||||||
|
if args.debug:
|
||||||
|
print("ifcc-test.py: "+os.path.dirname(__file__))
|
||||||
|
|
||||||
|
# cleanup stale output directory
|
||||||
|
if os.path.isdir(f'{pld_base_dir}/ifcc-test-output'):
|
||||||
|
run_command(f'rm -rf {pld_base_dir}/ifcc-test-output')
|
||||||
|
|
||||||
|
# Ensure that the `ifcc` executable itself is up-to-date
|
||||||
|
makestatus=run_command(f'cd {pld_base_dir}/compiler; make --question ifcc')
|
||||||
|
if makestatus: # updates are needed
|
||||||
|
makestatus=run_command(f'cd {pld_base_dir}/compiler; make ifcc',toscreen=True) # this time we run `make` for real
|
||||||
|
if makestatus: # if `make` failed, we fail too
|
||||||
|
if os.path.exists("ifcc"): # and we remove any out-of-date compiler (to reduce chance of confusion)
|
||||||
|
os.unlink("ifcc")
|
||||||
|
exit(makestatus)
|
||||||
|
|
||||||
|
##########################################
|
||||||
|
## single-file mode aka "let's act just like GCC (almost)"
|
||||||
|
|
||||||
|
if args.S or args.c or args.output:
|
||||||
|
if args.S and args.c:
|
||||||
|
print("error: options -S and -c are not compatible")
|
||||||
|
exit(1)
|
||||||
|
if len(args.input)>1:
|
||||||
|
print("error: this mode only supports a single input file")
|
||||||
|
exit(1)
|
||||||
|
inputfilename=args.input[0]
|
||||||
|
|
||||||
|
if inputfilename[-2:] != ".c":
|
||||||
|
print("error: incorrect filename suffix (should be '.c'): "+inputfilename)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
open(inputfilename,"r").close()
|
||||||
|
except Exception as e:
|
||||||
|
print("error: "+e.args[1]+": "+inputfilename)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if (args.S or args.c) and not args.output:
|
||||||
|
print("error: option '-o filename' is required in this mode")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if args.S: # produce assembly
|
||||||
|
if args.output[-2:] != ".s":
|
||||||
|
print("error: output file name must end with '.s'")
|
||||||
|
exit(1)
|
||||||
|
ifccstatus=run_command(f'{pld_base_dir}/compiler/ifcc {inputfilename} > {args.output}')
|
||||||
|
if ifccstatus: # let's show error messages on screen
|
||||||
|
exit(run_command(f'{pld_base_dir}/compiler/ifcc {inputfilename}',toscreen=True))
|
||||||
|
else:
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
elif args.c: # produce machine code
|
||||||
|
if args.output[-2:] != ".o":
|
||||||
|
print("error: output file name must end with '.o'")
|
||||||
|
exit(1)
|
||||||
|
asmname=args.output[:-2]+".s"
|
||||||
|
ifccstatus=run_command(f'{pld_base_dir}/compiler/ifcc {inputfilename} > {asmname}')
|
||||||
|
if ifccstatus: # let's show error messages on screen
|
||||||
|
exit(run_command(f'{pld_base_dir}/compiler/ifcc {inputfilename}',toscreen=True))
|
||||||
|
exit(run_command(f'gcc -c -o {args.output} {asmname}',toscreen=True))
|
||||||
|
|
||||||
|
else: # produce an executable
|
||||||
|
if args.output[-2:] in [".o",".c",".s"]:
|
||||||
|
print("error: incorrect name for an executable: "+args.output)
|
||||||
|
exit(1)
|
||||||
|
asmname=args.output+".s"
|
||||||
|
ifccstatus=run_command(f'{pld_base_dir}/compiler/ifcc {inputfilename} > {asmname}')
|
||||||
|
if ifccstatus:
|
||||||
|
exit(run_command(f'{pld_base_dir}/compiler/ifcc {inputfilename}', toscreen=True))
|
||||||
|
exit(run_command(f'gcc -o {args.output} {asmname}'))
|
||||||
|
|
||||||
|
# we should never end up here
|
||||||
|
print("unexpected error. please report this bug.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# if we were not in single-file mode, then it means we are in
|
||||||
|
# multiple-files mode.
|
||||||
|
|
||||||
|
######################################################################################
|
||||||
|
## PREPARE step: find and copy all test-cases under ifcc-test-output
|
||||||
|
|
||||||
|
## Process each cli argument as a filename or subtree
|
||||||
|
os.chdir(orig_cwd)
|
||||||
|
inputfilenames=[]
|
||||||
|
for path in args.input:
|
||||||
|
path=os.path.normpath(path) # collapse redundant slashes etc.
|
||||||
|
if os.path.isfile(path):
|
||||||
|
if path[-2:] == '.c':
|
||||||
|
inputfilenames.append(path)
|
||||||
|
else:
|
||||||
|
print("error: incorrect filename suffix (should be '.c'): "+path)
|
||||||
|
exit(1)
|
||||||
|
elif os.path.isdir(path):
|
||||||
|
for dirpath,dirnames,filenames in os.walk(path):
|
||||||
|
inputfilenames+=[dirpath+'/'+name for name in filenames if name[-2:]=='.c']
|
||||||
|
else:
|
||||||
|
print("error: cannot read input path `"+path+"'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
inputfilenames=sorted(inputfilenames)
|
||||||
|
## debug: after treewalk
|
||||||
|
if args.debug:
|
||||||
|
print("debug: list of files after tree walk:"," ".join(inputfilenames))
|
||||||
|
|
||||||
|
## sanity check
|
||||||
|
if len(inputfilenames) == 0:
|
||||||
|
print("error: found no test-case in: "+" ".join(args.input))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
## Check that we actually can read these files. Our goal is to
|
||||||
|
## fail as early as possible when the CLI arguments are wrong.
|
||||||
|
for inputfilename in inputfilenames:
|
||||||
|
try:
|
||||||
|
f=open(inputfilename,"r")
|
||||||
|
f.close()
|
||||||
|
except Exception as e:
|
||||||
|
print("error: "+e.args[1]+": "+inputfilename)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
## We're going to copy every test-case in its own subdir of ifcc-test-output
|
||||||
|
os.mkdir(pld_base_dir+'/ifcc-test-output')
|
||||||
|
|
||||||
|
jobs=[]
|
||||||
|
|
||||||
|
for inputfilename in inputfilenames:
|
||||||
|
if args.debug>=2:
|
||||||
|
print("debug: PREPARING "+inputfilename)
|
||||||
|
|
||||||
|
if 'ifcc-test-output' in os.path.realpath(inputfilename):
|
||||||
|
print('error: input filename is within output directory: '+inputfilename)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
## each test-case gets copied and processed in its own subdirectory:
|
||||||
|
## ../somedir/subdir/file.c becomes ifcc-test-output/--somedir-subdir-file/input.c
|
||||||
|
subdirname=inputfilename[:-2] # remove the '.c' suffix
|
||||||
|
if pld_base_dir in subdirname: # hide "absolute" part of path when not meaningful
|
||||||
|
subdirname=subdirname[len(pld_base_dir):]
|
||||||
|
subdirname=subdirname.replace('..','-') # keep some punctuation to discern "bla.c" from "../bla.c"
|
||||||
|
subdirname=subdirname.replace('./','') # remove meaningless part of name
|
||||||
|
subdirname=subdirname.replace('/','-') # flatten path to single subdir
|
||||||
|
if args.debug>=2:
|
||||||
|
print("debug: subdir="+subdirname)
|
||||||
|
|
||||||
|
os.mkdir(pld_base_dir+'/ifcc-test-output/'+subdirname)
|
||||||
|
shutil.copyfile(inputfilename,pld_base_dir+'/ifcc-test-output/'+subdirname+'/input.c')
|
||||||
|
jobs.append(subdirname)
|
||||||
|
|
||||||
|
## eliminate duplicate paths from the 'jobs' list
|
||||||
|
unique_jobs=[]
|
||||||
|
for j in jobs:
|
||||||
|
for d in unique_jobs:
|
||||||
|
if os.path.samefile(pld_base_dir+'/ifcc-test-output/'+j,pld_base_dir+'/ifcc-test-output/'+d):
|
||||||
|
break # and skip the 'else' branch
|
||||||
|
else:
|
||||||
|
unique_jobs.append(j)
|
||||||
|
jobs=sorted(unique_jobs)
|
||||||
|
|
||||||
|
# debug: after deduplication
|
||||||
|
if args.debug:
|
||||||
|
print("debug: list of test-cases after PREPARE step:"," ".join(jobs))
|
||||||
|
|
||||||
|
######################################################################################
|
||||||
|
## TEST step: actually compile/link/run each test-case with both compilers.
|
||||||
|
##
|
||||||
|
## if both toolchains agree, this test-case is passed.
|
||||||
|
## otherwise, this is a fail.
|
||||||
|
|
||||||
|
all_ok=True
|
||||||
|
|
||||||
|
for jobname in jobs:
|
||||||
|
os.chdir(f'{pld_base_dir}/ifcc-test-output')
|
||||||
|
|
||||||
|
print('TEST-CASE: '+jobname)
|
||||||
|
os.chdir(jobname)
|
||||||
|
|
||||||
|
## Reference compiler = GCC
|
||||||
|
gccstatus=run_command("gcc -S -o asm-gcc.s input.c", "gcc-compile.txt")
|
||||||
|
if gccstatus == 0:
|
||||||
|
# test-case is a valid program. we should run it
|
||||||
|
gccstatus=run_command("gcc -o exe-gcc asm-gcc.s", "gcc-link.txt")
|
||||||
|
if gccstatus == 0: # then both compile and link stage went well
|
||||||
|
exegccstatus=run_command("./exe-gcc", "gcc-execute.txt")
|
||||||
|
if args.verbose >=2:
|
||||||
|
dumpfile("gcc-execute.txt")
|
||||||
|
|
||||||
|
## IFCC compiler
|
||||||
|
ifccstatus=run_command(f'{pld_base_dir}/compiler/ifcc input.c > asm-ifcc.s', 'ifcc-compile.txt')
|
||||||
|
|
||||||
|
if gccstatus != 0 and ifccstatus != 0:
|
||||||
|
## ifcc correctly rejects invalid program -> test-case ok
|
||||||
|
print("TEST OK")
|
||||||
|
continue
|
||||||
|
elif gccstatus != 0 and ifccstatus == 0:
|
||||||
|
## ifcc wrongly accepts invalid program -> error
|
||||||
|
print("TEST FAIL (your compiler accepts an invalid program)")
|
||||||
|
all_ok=False
|
||||||
|
continue
|
||||||
|
elif gccstatus == 0 and ifccstatus != 0:
|
||||||
|
## ifcc wrongly rejects valid program -> error
|
||||||
|
print("TEST FAIL (your compiler rejects a valid program)")
|
||||||
|
all_ok=False
|
||||||
|
if args.verbose:
|
||||||
|
dumpfile("asm-ifcc.s") # stdout of ifcc
|
||||||
|
dumpfile("ifcc-compile.txt") # stderr of ifcc
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
## ifcc accepts to compile valid program -> let's link it
|
||||||
|
ldstatus=run_command("gcc -o exe-ifcc asm-ifcc.s", "ifcc-link.txt")
|
||||||
|
if ldstatus:
|
||||||
|
print("TEST FAIL (your compiler produces incorrect assembly)")
|
||||||
|
all_ok=False
|
||||||
|
if args.verbose:
|
||||||
|
dumpfile("asm-ifcc.s")
|
||||||
|
dumpfile("ifcc-link.txt")
|
||||||
|
continue
|
||||||
|
|
||||||
|
## both compilers did produce an executable, so now we run both
|
||||||
|
## these executables and compare the results.
|
||||||
|
|
||||||
|
run_command("./exe-ifcc", "ifcc-execute.txt")
|
||||||
|
if open("gcc-execute.txt").read() != open("ifcc-execute.txt").read() :
|
||||||
|
print("TEST FAIL (different results at execution)")
|
||||||
|
all_ok=False
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
print("GCC:")
|
||||||
|
dumpfile("gcc-execute.txt")
|
||||||
|
print("you:")
|
||||||
|
dumpfile("ifcc-execute.txt")
|
||||||
|
continue
|
||||||
|
|
||||||
|
## last but not least
|
||||||
|
print("TEST OK")
|
||||||
|
|
||||||
|
if not (all_ok or args.verbose):
|
||||||
|
print("Some test-cases failed. Run ifcc-test.py with option '--verbose' for more detailed feedback.")
|
||||||
3
testfiles/1_return42.c
Normal file
3
testfiles/1_return42.c
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
int main() {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
1
testfiles/2_invalid_program.c
Normal file
1
testfiles/2_invalid_program.c
Normal file
@ -0,0 +1 @@
|
|||||||
|
Si ça c'est du C, moi je suis prof de manga.
|
||||||
5
testfiles/3_return_var.c
Normal file
5
testfiles/3_return_var.c
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
int main() {
|
||||||
|
int x;
|
||||||
|
x=8;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user