This commit is contained in:
2026-01-16 09:03:49 +01:00
committed by Fredrik Robertsen
commit 3ed3c3de12
24 changed files with 529 additions and 0 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

69
.gitignore vendored Normal file
View File

@@ -0,0 +1,69 @@
# ---> CMake
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
CMakeUserPresets.json
# ---> C
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
build

4
CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.10)
project(PS1)
add_executable(ps1 src/driver.c)

18
LICENSE Normal file
View File

@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 frero-uni
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# TDT4205
compiler construction

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1769461804,
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

23
flake.nix Normal file
View File

@@ -0,0 +1,23 @@
{
description = "DevShell with flex, bison, gdb, cmake";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs =
{ self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
buildInputs = with pkgs; [
flex
bison
gdb
cmake
libgcc
];
};
};
}

89
src/driver.c Normal file
View File

@@ -0,0 +1,89 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "table.c"
static_assert(START >= 0 && START < NSTATES, "START must be a valid state");
static_assert(ACCEPT >= 0 && ACCEPT < NSTATES, "ACCEPT must be a valid state");
static_assert(ERROR >= 0 && ERROR < NSTATES, "ERROR must be a valid state");
// This program takes in lines from stdin and validates them line by line.
// If a line is valid, it is ignored.
// Otherwise, it is printed in full,
// followed by a line indicating at which character the error occurs.
int main() {
// Fill the table first. This function does no input processing only table creation.
fillTable();
printf("Table filled!\n");
// Lines may not be more than 512 lines long
char line[512];
int lineNum = 0;
// For each line in input
while (fgets(line, sizeof(line), stdin)) {
lineNum++;
int state = START;
int pos = 0;
bool fatalError = false;
while (true) {
unsigned char c = line[pos];
// If the line ended without a newline, patch it in
if (c == '\0')
c = '\n';
// Use the symbol to go to the next state
state = table[state][c];
// Uncomment this for debugging:
/* printf("State %i got c=%c\n", state, c); */
if (state < 0 || state >= NSTATES) {
printf("fatal error: DFS entered an invalid state: %d\n", state);
fatalError = true;
}
else if (state == ACCEPT) {
// Make sure statement is accepted at new line
if (c == '\n') {
printf("line %4d: accepted: %.*s\n", lineNum, pos, line);
// Break inner loop
break;
}
printf("fatal error: ACCEPT state reached before end of line:\n");
fatalError = true;
}
else if (c == '\n' && state != ERROR) {
printf("fatal error: Line ended without reaching either ACCEPT or ERROR state: %d\n", state);
fatalError = true;
}
// Provide the position we were at in the string when the DFS reached the ERROR state,
// or when a fatal error occured. The latter can only happen if the DFS is broken.
if (state == ERROR || fatalError) {
printf("line %4d: error: %s", lineNum, line);
printf("~~~~~~~~~~~~~~~~~~");
for(int i = 0; i < pos; i++)
putc('~', stdout);
printf("^\n");
break;
}
pos++;
}
// Fatal errors means there is something wrong with the DFS table. Stop processing lines!
if (fatalError) {
return 1; // return code 1 means error
}
}
return 0;
}

40
src/table.c Normal file
View File

@@ -0,0 +1,40 @@
// The number of states in your table
#define NSTATES 14
// The starting state, at the beginning of each line
#define START 0
// The state to go to after a valid line
// All lines end with the newline character '\n'
#define ACCEPT 12
// The state to jump to as soon as a line is invalid
#define ERROR 13
int table[NSTATES][256];
void fillTable() {
// Make all states lead to ERROR by default
for (int i = 0; i < NSTATES; i++) {
for (int c = 0; c < 256; c++) {
table[i][c] = ERROR;
}
}
// Skip whitespace
table[START][' '] = START;
// If we reach a newline, and are not in the middle of a statement, accept
table[START]['\n'] = ACCEPT;
// Accept the statement "go"
table[START]['g'] = 1;
table[1]['o'] = 2;
table[2]['\n'] = ACCEPT;
// TODO Expand the table to pass (and fail) the described syntax
// table[...][...] = ...
}

11
testing/expected/0-go.txt Normal file
View File

@@ -0,0 +1,11 @@
Table filled!
line 1: accepted: go
line 2: error: apple
~~~~~~~~~~~~~~~~~~^
line 3: accepted: go
line 4: accepted:
line 5: error: banana
~~~~~~~~~~~~~~~~~~^
line 6: accepted: go
line 7: error: orange
~~~~~~~~~~~~~~~~~~^

View File

@@ -0,0 +1,9 @@
Table filled!
line 1: accepted: go
line 2: accepted:
line 3: accepted: go go
line 4: error: go gogo
~~~~~~~~~~~~~~~~~~~~~~~^
line 5: accepted: go go go go go go
line 6: error: gogo gogo
~~~~~~~~~~~~~~~~~~~~^

View File

@@ -0,0 +1,21 @@
Table filled!
line 1: accepted: dx=2
line 2: accepted: dy=3
line 3: accepted: dx=9
line 4: accepted: dx=55
line 5: accepted: dy=09
line 6: accepted: dx=5 dx=1 go
line 7: error: da=1
~~~~~~~~~~~~~~~~~~~^
line 8: error: indy=1
~~~~~~~~~~~~~~~~~~^
line 9: accepted: dx=999
line 10: accepted: dy=-10
line 11: error: dx=--5
~~~~~~~~~~~~~~~~~~~~~~^
line 12: error: dx=-
~~~~~~~~~~~~~~~~~~~~~~^
line 13: error: dy=
~~~~~~~~~~~~~~~~~~~~~^
line 14: accepted: dx=-0 dy=55 go dx=197 dy=-2
line 15: accepted: dx=20 dy=20

View File

@@ -0,0 +1,16 @@
Table filled!
line 1: accepted: 8: go
line 2: accepted: 10: dx=1
line 3: accepted: 5:go dx=2
line 4: accepted: 2:
line 5: error: -11: go go
~~~~~~~~~~~~~~~~~~^
line 6: accepted: 100: go dx=5
line 7: error: apple: go
~~~~~~~~~~~~~~~~~~^
line 8: error: go 4:
~~~~~~~~~~~~~~~~~~~~~^
line 9: error: 22: 22: go
~~~~~~~~~~~~~~~~~~~~~~^
line 10: error: : go
~~~~~~~~~~~~~~~~~~^

View File

@@ -0,0 +1,14 @@
Table filled!
line 1: accepted: // This file contains tests
line 2: accepted: //Spacesbeforecommentshouldnotmatter
line 3: accepted: go go go // This should be accepted
line 4: accepted: 10: dx=10 dy=-5 go // This should also be accepted
line 5: error: dx=-1 dy=5 go /* This should fail! */
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
line 6: accepted: go go // dx=-4.2 should be accepted
line 7: accepted: //
line 8: accepted: go// Comments can be right next to statements
line 9: accepted: dx=5// Here as well
line 10: accepted: 7://Labels too!
line 11: error: go / We need two slashes
~~~~~~~~~~~~~~~~~~~~~~^

View File

@@ -0,0 +1,9 @@
Table filled!
line 1: accepted: go go dx=1 go
line 2: accepted: go// OK
line 3: accepted: 9: dx=-1 //
line 4: accepted: 10: //
line 5: accepted: 11://
line 6: accepted: dy=-9 // Not dy=--9
line 7: accepted: 00: dx=-0001 go
line 8: accepted: 94124543535: dx=94124543535 go dy=-111 go // End

View File

@@ -0,0 +1,21 @@
Table filled!
line 1: error: dx= // Comment
~~~~~~~~~~~~~~~~~~~~~^
line 2: error: go go goooo
~~~~~~~~~~~~~~~~~~~~~~~~~~^
line 3: error: dy=11dx=11
~~~~~~~~~~~~~~~~~~~~~~~^
line 4: error: -5: go
~~~~~~~~~~~~~~~~~~^
line 5: error: : // Empty label?
~~~~~~~~~~~~~~~~~~^
line 6: error: 5:4:
~~~~~~~~~~~~~~~~~~~~^
line 7: error: go / Comment
~~~~~~~~~~~~~~~~~~~~~~^
line 8: error: 8: dy=42 dx=4.2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
line 9: error: I beheld the wretch—the miserable monster whom I had created
~~~~~~~~~~~~~~~~~~^
line 10: error: apple pie
~~~~~~~~~~~~~~~~~~^

87
testing/test.py Normal file
View File

@@ -0,0 +1,87 @@
import difflib
import pathlib
import os
import subprocess
import sys
def main():
ps1_root_dir = pathlib.Path(__file__).parent.parent.resolve()
build_dir = ps1_root_dir / "build"
print("Building and compiling...")
try:
subprocess.run(["cmake", str(ps1_root_dir), "-B", str(build_dir)], check=True)
subprocess.run(["cmake", "--build", str(build_dir)], check=True)
except subprocess.CalledProcessError as e:
print(f"An error occurred while running CMake: {e}", file=sys.stderr)
sys.exit(1)
print("Build complete")
testing_dir = ps1_root_dir / "testing"
tests_dir = testing_dir / "tests"
outputs_dir = testing_dir / "expected"
executable = build_dir / "ps1"
sorted_test_files : list[pathlib.Path] = []
for test_file in tests_dir.glob("*.txt"):
sorted_test_files.append(test_file)
sorted_test_files.sort(key= lambda p : p.name)
passed = 0
for test_file in sorted_test_files:
name = test_file.name
print(f"Testing {name}... ", end="")
output_file = outputs_dir / name
if not output_file.exists():
print("")
print(f"Error: Could not find {str(output_file)}", file=sys.stderr)
sys.exit(1)
try:
result : list[str] = []
with test_file.open("r") as test_input:
result_str : str = subprocess.run(
[executable],
stdin=test_input,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True, # Output must be text
check=True
).stdout
result = result_str.splitlines()
expected : list[str] = []
with output_file.open("r") as expected_output:
expected = expected_output.read().splitlines()
diff_list : list[str] = []
for diff in difflib.unified_diff(result, expected, fromfile='your output', tofile='expected_output', lineterm='', n=3):
diff_list.append(diff)
if not diff_list:
# List is empty; no diffs
passed += 1
print("OK")
continue
# If at least one diff, print
print("MISMATCH")
for diff in diff_list:
print(diff)
except subprocess.CalledProcessError as e:
print("")
print(f"Error: Test {name} failed to execute: {e}", file=sys.stderr)
print(f"{passed}/{len(sorted_test_files)} tests passed")
if __name__ == "__main__":
main()

7
testing/tests/0-go.txt Normal file
View File

@@ -0,0 +1,7 @@
go
apple
go
banana
go
orange

View File

@@ -0,0 +1,6 @@
go
go go
go gogo
go go go go go go
gogo gogo

15
testing/tests/2-dxdy.txt Normal file
View File

@@ -0,0 +1,15 @@
dx=2
dy=3
dx=9
dx=55
dy=09
dx=5 dx=1 go
da=1
indy=1
dx=999
dy=-10
dx=--5
dx=-
dy=
dx=-0 dy=55 go dx=197 dy=-2
dx=20 dy=20

View File

@@ -0,0 +1,10 @@
8: go
10: dx=1
5:go dx=2
2:
-11: go go
100: go dx=5
apple: go
go 4:
22: 22: go
: go

View File

@@ -0,0 +1,11 @@
// This file contains tests
//Spacesbeforecommentshouldnotmatter
go go go // This should be accepted
10: dx=10 dy=-5 go // This should also be accepted
dx=-1 dy=5 go /* This should fail! */
go go // dx=-4.2 should be accepted
//
go// Comments can be right next to statements
dx=5// Here as well
7://Labels too!
go / We need two slashes

View File

@@ -0,0 +1,8 @@
go go dx=1 go
go// OK
9: dx=-1 //
10: //
11://
dy=-9 // Not dy=--9
00: dx=-0001 go
94124543535: dx=94124543535 go dy=-111 go // End

View File

@@ -0,0 +1,10 @@
dx= // Comment
go go goooo
dy=11dx=11
-5: go
: // Empty label?
5:4:
go / Comment
8: dy=42 dx=4.2
I beheld the wretch—the miserable monster whom I had created
apple pie