Initial commit
This commit is contained in:
commit
192146b99d
84 changed files with 3258 additions and 0 deletions
88
README.md
Normal file
88
README.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Starter Code: Java
|
||||
|
||||
This project contains starter code written in Java 24.
|
||||
It contains:
|
||||
|
||||
- A lexer for L1
|
||||
- A parser for L1
|
||||
- Semantic analysis for L1
|
||||
- SSA translation and IR
|
||||
- Code generation for an abstract assembly
|
||||
|
||||
Furthermore, the starter code also provides working `build.sh` and `run.sh` files.
|
||||
|
||||
## Code Overview
|
||||
|
||||
The starter code is meant to spare you some initial work on things that are not covered
|
||||
by the lecture at the time of the first lab.
|
||||
You will most likely need to touch large parts of the existing code sooner or later,
|
||||
so we recommend going through it for a basic understanding of what is going on.
|
||||
|
||||
Remember that you are free to modify any code.
|
||||
|
||||
### Lexer & Tokens
|
||||
|
||||
The lexer lazily produces tokens from an input string.
|
||||
Invalid input parts will generate `ErrorToken`s.
|
||||
|
||||
### Parser & AST
|
||||
|
||||
The parser is a handwritten, recursive-descent parser.
|
||||
You can choose other technologies (e.g., ANTLR), but expanding this parser as needed
|
||||
might be a good exercise to deepen your understanding.
|
||||
|
||||
The parser does not implement any kind of error recovery.
|
||||
Instead, it just throws an exception as soon as the first problem is encountered.
|
||||
You can implement error recovery, but it is not mandatory.
|
||||
|
||||
### Semantic Analysis
|
||||
|
||||
The semantic analysis in Lab 1 is just very basic.
|
||||
You will need to expand it in future labs.
|
||||
Similar to the parser, error handling is only very basic.
|
||||
|
||||
### SSA translation & IR
|
||||
|
||||
The SSA IR is inspired by [libFirm](https://libfirm.github.io/) and [Sea-of-Nodes](https://github.com/SeaOfNodes/).
|
||||
It might be helpful to study these to get a better understanding of what is going on.
|
||||
The implementation also showcases how SSA translation can directly apply optimizations.
|
||||
|
||||
In the first lab, you don't need to understand SSA in full detail.
|
||||
However, register allocation on chordal graphs depends on SSA.
|
||||
For Lab 1, register allocation can also be done just using the AST,
|
||||
but that means you'll likely have to rewrite more code in future labs.
|
||||
It can still make sense to start with simple, naive implementations to have something working early on.
|
||||
|
||||
### Code generation
|
||||
|
||||
This is more or less just a placeholder.
|
||||
You most likely just want to fully replace it with your register allocation and instruction selection.
|
||||
|
||||
## Debugging Utilities
|
||||
|
||||
There is a chance something won't work on the first try.
|
||||
To figure out the cause, we provide utilities that ease debugging.
|
||||
|
||||
- `edu.kit.kastel.vads.compiler.parser.Printer` allows printing the AST.
|
||||
As it inserts many parentheses, it can be helpful when debugging precedence problems.
|
||||
- `edu.kit.kastel.vads.compiler.ir.util.GraphVizPrinter` can generate output in the DOT format.
|
||||
There are online tools (e.g., https://magjac.com/graphviz-visual-editor/) that can visualize that output.
|
||||
It allows debugging anything related to the IR.
|
||||
|
||||
We also try to keep track of source positions as much as possible through the compiler.
|
||||
You can get rid of all that, but it can be helpful to track down where something comes from.
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
### Nullability
|
||||
|
||||
This project uses [jspecify](https://jspecify.dev/).
|
||||
The `module-info.java` is annotated with `@NullMarked`,
|
||||
meaning uses of `null` must be annotated, and not-null is assumed otherwise.
|
||||
|
||||
### Gradle
|
||||
|
||||
This project provides the wrapper for Gradle 8.14.
|
||||
Additionally, the `application` plugin is used to easily specify the main class and build ready-to-use executables.
|
||||
To ease setup ceremony,
|
||||
the `foojay-resolver-convention` is used to automatically download a JDK matching the toolchain configuration.
|
||||
30
build.gradle.kts
Normal file
30
build.gradle.kts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
plugins {
|
||||
id("java")
|
||||
application
|
||||
}
|
||||
|
||||
group = "edu.kit.kastel.logic"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
application {
|
||||
mainModule = "edu.kit.kastel.vads.compiler"
|
||||
mainClass = "edu.kit.kastel.vads.compiler.Main"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jspecify:jspecify:1.0.0")
|
||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(24)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
4
build.sh
Executable file
4
build.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
./gradlew --no-daemon installDist
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
251
gradlew
vendored
Executable file
251
gradlew
vendored
Executable file
|
|
@ -0,0 +1,251 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
gradlew.bat
vendored
Normal file
94
gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
3
run.sh
Executable file
3
run.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env sh
|
||||
BIN_DIR="$(dirname "$0")/build/install/compiler/bin"
|
||||
$BIN_DIR/compiler "$@"
|
||||
5
settings.gradle.kts
Normal file
5
settings.gradle.kts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
rootProject.name = "compiler" // in case you want to change the name: it is used by run.sh too
|
||||
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
|
||||
}
|
||||
61
src/main/java/edu/kit/kastel/vads/compiler/Main.java
Normal file
61
src/main/java/edu/kit/kastel/vads/compiler/Main.java
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package edu.kit.kastel.vads.compiler;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.backend.aasm.CodeGenerator;
|
||||
import edu.kit.kastel.vads.compiler.ir.IrGraph;
|
||||
import edu.kit.kastel.vads.compiler.ir.SsaTranslation;
|
||||
import edu.kit.kastel.vads.compiler.ir.optimize.LocalValueNumbering;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Lexer;
|
||||
import edu.kit.kastel.vads.compiler.parser.ParseException;
|
||||
import edu.kit.kastel.vads.compiler.parser.Parser;
|
||||
import edu.kit.kastel.vads.compiler.parser.TokenSource;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree;
|
||||
import edu.kit.kastel.vads.compiler.semantic.SemanticAnalysis;
|
||||
import edu.kit.kastel.vads.compiler.semantic.SemanticException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length != 2) {
|
||||
System.err.println("Invalid arguments: Expected one input file and one output file");
|
||||
System.exit(3);
|
||||
}
|
||||
Path input = Path.of(args[0]);
|
||||
Path output = Path.of(args[1]);
|
||||
ProgramTree program = lexAndParse(input);
|
||||
try {
|
||||
new SemanticAnalysis(program).analyze();
|
||||
} catch (SemanticException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(2);
|
||||
return;
|
||||
}
|
||||
List<IrGraph> graphs = new ArrayList<>();
|
||||
for (FunctionTree function : program.topLevelTrees()) {
|
||||
SsaTranslation translation = new SsaTranslation(function, new LocalValueNumbering());
|
||||
graphs.add(translation.translate());
|
||||
}
|
||||
|
||||
// TODO: generate assembly and invoke gcc instead of generating abstract assembly
|
||||
String s = new CodeGenerator().generateCode(graphs);
|
||||
Files.writeString(output, s);
|
||||
}
|
||||
|
||||
private static ProgramTree lexAndParse(Path input) throws IOException {
|
||||
try {
|
||||
Lexer lexer = Lexer.forString(Files.readString(input));
|
||||
TokenSource tokenSource = new TokenSource(lexer);
|
||||
Parser parser = new Parser(tokenSource);
|
||||
return parser.parseProgram();
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
throw new AssertionError("unreachable");
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/main/java/edu/kit/kastel/vads/compiler/Position.java
Normal file
13
src/main/java/edu/kit/kastel/vads/compiler/Position.java
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package edu.kit.kastel.vads.compiler;
|
||||
|
||||
public sealed interface Position {
|
||||
int line();
|
||||
int column();
|
||||
|
||||
record SimplePosition(int line, int column) implements Position {
|
||||
@Override
|
||||
public String toString() {
|
||||
return line() + ":" + column();
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/main/java/edu/kit/kastel/vads/compiler/Span.java
Normal file
20
src/main/java/edu/kit/kastel/vads/compiler/Span.java
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package edu.kit.kastel.vads.compiler;
|
||||
|
||||
public sealed interface Span {
|
||||
Position start();
|
||||
Position end();
|
||||
|
||||
Span merge(Span later);
|
||||
|
||||
record SimpleSpan(Position start, Position end) implements Span {
|
||||
@Override
|
||||
public Span merge(Span later) {
|
||||
return new SimpleSpan(start(), later.end());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + start() + "|" + end() + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package edu.kit.kastel.vads.compiler.backend.aasm;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.backend.regalloc.Register;
|
||||
import edu.kit.kastel.vads.compiler.backend.regalloc.RegisterAllocator;
|
||||
import edu.kit.kastel.vads.compiler.ir.IrGraph;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Block;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ProjNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ReturnNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.StartNode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class AasmRegisterAllocator implements RegisterAllocator {
|
||||
private int id;
|
||||
private final Map<Node, Register> registers = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Map<Node, Register> allocateRegisters(IrGraph graph) {
|
||||
Set<Node> visited = new HashSet<>();
|
||||
visited.add(graph.endBlock());
|
||||
scan(graph.endBlock(), visited);
|
||||
return Map.copyOf(this.registers);
|
||||
}
|
||||
|
||||
private void scan(Node node, Set<Node> visited) {
|
||||
for (Node predecessor : node.predecessors()) {
|
||||
if (visited.add(predecessor)) {
|
||||
scan(predecessor, visited);
|
||||
}
|
||||
}
|
||||
if (needsRegister(node)) {
|
||||
this.registers.put(node, new VirtualRegister(this.id++));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsRegister(Node node) {
|
||||
return !(node instanceof ProjNode || node instanceof StartNode || node instanceof Block || node instanceof ReturnNode);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package edu.kit.kastel.vads.compiler.backend.aasm;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.backend.regalloc.Register;
|
||||
import edu.kit.kastel.vads.compiler.ir.IrGraph;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.AddNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.BinaryOperationNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Block;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ConstIntNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.DivNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ModNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.MulNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Phi;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ProjNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ReturnNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.StartNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.SubNode;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static edu.kit.kastel.vads.compiler.ir.util.NodeSupport.predecessorSkipProj;
|
||||
|
||||
public class CodeGenerator {
|
||||
|
||||
public String generateCode(List<IrGraph> program) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (IrGraph graph : program) {
|
||||
AasmRegisterAllocator allocator = new AasmRegisterAllocator();
|
||||
Map<Node, Register> registers = allocator.allocateRegisters(graph);
|
||||
builder.append("function ")
|
||||
.append(graph.name())
|
||||
.append(" {\n");
|
||||
generateForGraph(graph, builder, registers);
|
||||
builder.append("}");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void generateForGraph(IrGraph graph, StringBuilder builder, Map<Node, Register> registers) {
|
||||
Set<Node> visited = new HashSet<>();
|
||||
scan(graph.endBlock(), visited, builder, registers);
|
||||
}
|
||||
|
||||
private void scan(Node node, Set<Node> visited, StringBuilder builder, Map<Node, Register> registers) {
|
||||
for (Node predecessor : node.predecessors()) {
|
||||
if (visited.add(predecessor)) {
|
||||
scan(predecessor, visited, builder, registers);
|
||||
}
|
||||
}
|
||||
|
||||
switch (node) {
|
||||
case AddNode add -> binary(builder, registers, add, "add");
|
||||
case SubNode sub -> binary(builder, registers, sub, "sub");
|
||||
case MulNode mul -> binary(builder, registers, mul, "mul");
|
||||
case DivNode div -> binary(builder, registers, div, "div");
|
||||
case ModNode mod -> binary(builder, registers, mod, "mod");
|
||||
case ReturnNode r -> builder.repeat(" ", 2).append("ret ")
|
||||
.append(registers.get(predecessorSkipProj(r, ReturnNode.RESULT)));
|
||||
case ConstIntNode c -> builder.repeat(" ", 2)
|
||||
.append(registers.get(c))
|
||||
.append(" = const ")
|
||||
.append(c.value());
|
||||
case Phi _ -> throw new UnsupportedOperationException("phi");
|
||||
case Block _, ProjNode _, StartNode _ -> {
|
||||
// do nothing, skip line break
|
||||
return;
|
||||
}
|
||||
}
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
private static void binary(
|
||||
StringBuilder builder,
|
||||
Map<Node, Register> registers,
|
||||
BinaryOperationNode node,
|
||||
String opcode
|
||||
) {
|
||||
builder.repeat(" ", 2).append(registers.get(node))
|
||||
.append(" = ")
|
||||
.append(opcode)
|
||||
.append(" ")
|
||||
.append(registers.get(predecessorSkipProj(node, BinaryOperationNode.LEFT)))
|
||||
.append(" ")
|
||||
.append(registers.get(predecessorSkipProj(node, BinaryOperationNode.RIGHT)));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package edu.kit.kastel.vads.compiler.backend.aasm;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.backend.regalloc.Register;
|
||||
|
||||
public record VirtualRegister(int id) implements Register {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "%" + id();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package edu.kit.kastel.vads.compiler.backend.regalloc;
|
||||
|
||||
public interface Register {
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package edu.kit.kastel.vads.compiler.backend.regalloc;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.IrGraph;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface RegisterAllocator {
|
||||
|
||||
Map<Node, Register> allocateRegisters(IrGraph graph);
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
package edu.kit.kastel.vads.compiler.ir;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.node.AddNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Block;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ConstIntNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.DivNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ModNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.MulNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Phi;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ProjNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ReturnNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.StartNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.SubNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.optimize.Optimizer;
|
||||
import edu.kit.kastel.vads.compiler.parser.symbol.Name;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
class GraphConstructor {
|
||||
|
||||
private final Optimizer optimizer;
|
||||
private final IrGraph graph;
|
||||
private final Map<Name, Map<Block, Node>> currentDef = new HashMap<>();
|
||||
private final Map<Block, Map<Name, Phi>> incompletePhis = new HashMap<>();
|
||||
private final Map<Block, Node> currentSideEffect = new HashMap<>();
|
||||
private final Map<Block, Phi> incompleteSideEffectPhis = new HashMap<>();
|
||||
private final Set<Block> sealedBlocks = new HashSet<>();
|
||||
private Block currentBlock;
|
||||
|
||||
public GraphConstructor(Optimizer optimizer, String name) {
|
||||
this.optimizer = optimizer;
|
||||
this.graph = new IrGraph(name);
|
||||
this.currentBlock = this.graph.startBlock();
|
||||
// the start block never gets any more predecessors
|
||||
sealBlock(this.currentBlock);
|
||||
}
|
||||
|
||||
public Node newStart() {
|
||||
assert currentBlock() == this.graph.startBlock() : "start must be in start block";
|
||||
return new StartNode(currentBlock());
|
||||
}
|
||||
|
||||
public Node newAdd(Node left, Node right) {
|
||||
return this.optimizer.transform(new AddNode(currentBlock(), left, right));
|
||||
}
|
||||
public Node newSub(Node left, Node right) {
|
||||
return this.optimizer.transform(new SubNode(currentBlock(), left, right));
|
||||
}
|
||||
|
||||
public Node newMul(Node left, Node right) {
|
||||
return this.optimizer.transform(new MulNode(currentBlock(), left, right));
|
||||
}
|
||||
|
||||
public Node newDiv(Node left, Node right) {
|
||||
return this.optimizer.transform(new DivNode(currentBlock(), left, right, readCurrentSideEffect()));
|
||||
}
|
||||
|
||||
public Node newMod(Node left, Node right) {
|
||||
return this.optimizer.transform(new ModNode(currentBlock(), left, right, readCurrentSideEffect()));
|
||||
}
|
||||
|
||||
public Node newReturn(Node result) {
|
||||
return new ReturnNode(currentBlock(), readCurrentSideEffect(), result);
|
||||
}
|
||||
|
||||
public Node newConstInt(int value) {
|
||||
// always move const into start block, this allows better deduplication
|
||||
// and resultingly in better value numbering
|
||||
return this.optimizer.transform(new ConstIntNode(this.graph.startBlock(), value));
|
||||
}
|
||||
|
||||
public Node newSideEffectProj(Node node) {
|
||||
return new ProjNode(currentBlock(), node, ProjNode.SimpleProjectionInfo.SIDE_EFFECT);
|
||||
}
|
||||
|
||||
public Node newResultProj(Node node) {
|
||||
return new ProjNode(currentBlock(), node, ProjNode.SimpleProjectionInfo.RESULT);
|
||||
}
|
||||
|
||||
public Block currentBlock() {
|
||||
return this.currentBlock;
|
||||
}
|
||||
|
||||
public Phi newPhi() {
|
||||
// don't transform phi directly, it is not ready yet
|
||||
return new Phi(currentBlock());
|
||||
}
|
||||
|
||||
public IrGraph graph() {
|
||||
return this.graph;
|
||||
}
|
||||
|
||||
void writeVariable(Name variable, Block block, Node value) {
|
||||
this.currentDef.computeIfAbsent(variable, _ -> new HashMap<>()).put(block, value);
|
||||
}
|
||||
|
||||
Node readVariable(Name variable, Block block) {
|
||||
Node node = this.currentDef.getOrDefault(variable, Map.of()).get(block);
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
return readVariableRecursive(variable, block);
|
||||
}
|
||||
|
||||
|
||||
private Node readVariableRecursive(Name variable, Block block) {
|
||||
Node val;
|
||||
if (!this.sealedBlocks.contains(block)) {
|
||||
val = newPhi();
|
||||
this.incompletePhis.computeIfAbsent(block, _ -> new HashMap<>()).put(variable, (Phi) val);
|
||||
} else if (block.predecessors().size() == 1) {
|
||||
val = readVariable(variable, block.predecessors().getFirst().block());
|
||||
} else {
|
||||
val = newPhi();
|
||||
writeVariable(variable, block, val);
|
||||
val = addPhiOperands(variable, (Phi) val);
|
||||
}
|
||||
writeVariable(variable, block, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
Node addPhiOperands(Name variable, Phi phi) {
|
||||
for (Node pred : phi.block().predecessors()) {
|
||||
phi.appendOperand(readVariable(variable, pred.block()));
|
||||
}
|
||||
return tryRemoveTrivialPhi(phi);
|
||||
}
|
||||
|
||||
Node tryRemoveTrivialPhi(Phi phi) {
|
||||
// TODO: the paper shows how to remove trivial phis.
|
||||
// as this is not a problem in Lab 1 and it is just
|
||||
// a simplification, we recommend to implement this
|
||||
// part yourself.
|
||||
return phi;
|
||||
}
|
||||
|
||||
void sealBlock(Block block) {
|
||||
for (Map.Entry<Name, Phi> entry : this.incompletePhis.getOrDefault(block, Map.of()).entrySet()) {
|
||||
addPhiOperands(entry.getKey(), entry.getValue());
|
||||
}
|
||||
this.sealedBlocks.add(block);
|
||||
}
|
||||
|
||||
public void writeCurrentSideEffect(Node node) {
|
||||
writeSideEffect(currentBlock(), node);
|
||||
}
|
||||
|
||||
private void writeSideEffect(Block block, Node node) {
|
||||
this.currentSideEffect.put(block, node);
|
||||
}
|
||||
|
||||
public Node readCurrentSideEffect() {
|
||||
return readSideEffect(currentBlock());
|
||||
}
|
||||
|
||||
private Node readSideEffect(Block block) {
|
||||
Node node = this.currentSideEffect.get(block);
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
return readSideEffectRecursive(block);
|
||||
}
|
||||
|
||||
private Node readSideEffectRecursive(Block block) {
|
||||
Node val;
|
||||
if (!this.sealedBlocks.contains(block)) {
|
||||
val = newPhi();
|
||||
Phi old = this.incompleteSideEffectPhis.put(block, (Phi) val);
|
||||
assert old == null : "double readSideEffectRecursive for " + block;
|
||||
} else if (block.predecessors().size() == 1) {
|
||||
val = readSideEffect(block.predecessors().getFirst().block());
|
||||
} else {
|
||||
val = newPhi();
|
||||
writeSideEffect(block, val);
|
||||
val = addPhiOperands((Phi) val);
|
||||
}
|
||||
writeSideEffect(block, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
Node addPhiOperands(Phi phi) {
|
||||
for (Node pred : phi.block().predecessors()) {
|
||||
phi.appendOperand(readSideEffect(pred.block()));
|
||||
}
|
||||
return tryRemoveTrivialPhi(phi);
|
||||
}
|
||||
|
||||
}
|
||||
53
src/main/java/edu/kit/kastel/vads/compiler/ir/IrGraph.java
Normal file
53
src/main/java/edu/kit/kastel/vads/compiler/ir/IrGraph.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package edu.kit.kastel.vads.compiler.ir;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Block;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.SequencedSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class IrGraph {
|
||||
private final Map<Node, SequencedSet<Node>> successors = new IdentityHashMap<>();
|
||||
private final Block startBlock;
|
||||
private final Block endBlock;
|
||||
private final String name;
|
||||
|
||||
public IrGraph(String name) {
|
||||
this.name = name;
|
||||
this.startBlock = new Block(this);
|
||||
this.endBlock = new Block(this);
|
||||
}
|
||||
|
||||
public void registerSuccessor(Node node, Node successor) {
|
||||
this.successors.computeIfAbsent(node, _ -> new LinkedHashSet<>()).add(successor);
|
||||
}
|
||||
|
||||
public void removeSuccessor(Node node, Node oldSuccessor) {
|
||||
this.successors.computeIfAbsent(node, _ -> new LinkedHashSet<>()).remove(oldSuccessor);
|
||||
}
|
||||
|
||||
/// {@return the set of nodes that have the given node as one of their inputs}
|
||||
public Set<Node> successors(Node node) {
|
||||
SequencedSet<Node> successors = this.successors.get(node);
|
||||
if (successors == null) {
|
||||
return Set.of();
|
||||
}
|
||||
return Set.copyOf(successors);
|
||||
}
|
||||
|
||||
public Block startBlock() {
|
||||
return this.startBlock;
|
||||
}
|
||||
|
||||
public Block endBlock() {
|
||||
return this.endBlock;
|
||||
}
|
||||
|
||||
/// {@return the name of this graph}
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
package edu.kit.kastel.vads.compiler.ir;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Block;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.DivNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ModNode;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
import edu.kit.kastel.vads.compiler.ir.optimize.Optimizer;
|
||||
import edu.kit.kastel.vads.compiler.ir.util.DebugInfo;
|
||||
import edu.kit.kastel.vads.compiler.ir.util.DebugInfoHelper;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.AssignmentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BinaryOperationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BlockTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.DeclarationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.IdentExpressionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LValueIdentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LiteralTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NameTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NegateTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ReturnTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.StatementTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.Tree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.TypeTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.symbol.Name;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
/// SSA translation as described in
|
||||
/// [`Simple and Efficient Construction of Static Single Assignment Form`](https://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf).
|
||||
///
|
||||
/// This implementation also tracks side effect edges that can be used to avoid reordering of operations that cannot be
|
||||
/// reordered.
|
||||
///
|
||||
/// We recommend to read the paper to better understand the mechanics implemented here.
|
||||
public class SsaTranslation {
|
||||
private final FunctionTree function;
|
||||
private final GraphConstructor constructor;
|
||||
|
||||
public SsaTranslation(FunctionTree function, Optimizer optimizer) {
|
||||
this.function = function;
|
||||
this.constructor = new GraphConstructor(optimizer, function.name().name().asString());
|
||||
}
|
||||
|
||||
public IrGraph translate() {
|
||||
var visitor = new SsaTranslationVisitor();
|
||||
this.function.accept(visitor, this);
|
||||
return this.constructor.graph();
|
||||
}
|
||||
|
||||
private void writeVariable(Name variable, Block block, Node value) {
|
||||
this.constructor.writeVariable(variable, block, value);
|
||||
}
|
||||
|
||||
private Node readVariable(Name variable, Block block) {
|
||||
return this.constructor.readVariable(variable, block);
|
||||
}
|
||||
|
||||
private Block currentBlock() {
|
||||
return this.constructor.currentBlock();
|
||||
}
|
||||
|
||||
private static class SsaTranslationVisitor implements Visitor<SsaTranslation, Optional<Node>> {
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private static final Optional<Node> NOT_AN_EXPRESSION = Optional.empty();
|
||||
|
||||
private final Deque<DebugInfo> debugStack = new ArrayDeque<>();
|
||||
|
||||
private void pushSpan(Tree tree) {
|
||||
this.debugStack.push(DebugInfoHelper.getDebugInfo());
|
||||
DebugInfoHelper.setDebugInfo(new DebugInfo.SourceInfo(tree.span()));
|
||||
}
|
||||
|
||||
private void popSpan() {
|
||||
DebugInfoHelper.setDebugInfo(this.debugStack.pop());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(AssignmentTree assignmentTree, SsaTranslation data) {
|
||||
pushSpan(assignmentTree);
|
||||
BinaryOperator<Node> desugar = switch (assignmentTree.operator().type()) {
|
||||
case ASSIGN_MINUS -> data.constructor::newSub;
|
||||
case ASSIGN_PLUS -> data.constructor::newAdd;
|
||||
case ASSIGN_MUL -> data.constructor::newMul;
|
||||
case ASSIGN_DIV -> (lhs, rhs) -> projResultDivMod(data, data.constructor.newDiv(lhs, rhs));
|
||||
case ASSIGN_MOD -> (lhs, rhs) -> projResultDivMod(data, data.constructor.newMod(lhs, rhs));
|
||||
case ASSIGN -> null;
|
||||
default ->
|
||||
throw new IllegalArgumentException("not an assignment operator " + assignmentTree.operator());
|
||||
};
|
||||
|
||||
switch (assignmentTree.lValue()) {
|
||||
case LValueIdentTree(var name) -> {
|
||||
Node rhs = assignmentTree.expression().accept(this, data).orElseThrow();
|
||||
if (desugar != null) {
|
||||
rhs = desugar.apply(data.readVariable(name.name(), data.currentBlock()), rhs);
|
||||
}
|
||||
data.writeVariable(name.name(), data.currentBlock(), rhs);
|
||||
}
|
||||
}
|
||||
popSpan();
|
||||
return NOT_AN_EXPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(BinaryOperationTree binaryOperationTree, SsaTranslation data) {
|
||||
pushSpan(binaryOperationTree);
|
||||
Node lhs = binaryOperationTree.lhs().accept(this, data).orElseThrow();
|
||||
Node rhs = binaryOperationTree.rhs().accept(this, data).orElseThrow();
|
||||
Node res = switch (binaryOperationTree.operatorType()) {
|
||||
case MINUS -> data.constructor.newSub(lhs, rhs);
|
||||
case PLUS -> data.constructor.newAdd(lhs, rhs);
|
||||
case MUL -> data.constructor.newMul(lhs, rhs);
|
||||
case DIV -> projResultDivMod(data, data.constructor.newDiv(lhs, rhs));
|
||||
case MOD -> projResultDivMod(data, data.constructor.newMod(lhs, rhs));
|
||||
default ->
|
||||
throw new IllegalArgumentException("not a binary expression operator " + binaryOperationTree.operatorType());
|
||||
};
|
||||
popSpan();
|
||||
return Optional.of(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(BlockTree blockTree, SsaTranslation data) {
|
||||
pushSpan(blockTree);
|
||||
for (StatementTree statement : blockTree.statements()) {
|
||||
statement.accept(this, data);
|
||||
}
|
||||
popSpan();
|
||||
return NOT_AN_EXPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(DeclarationTree declarationTree, SsaTranslation data) {
|
||||
pushSpan(declarationTree);
|
||||
if (declarationTree.initializer() != null) {
|
||||
Node rhs = declarationTree.initializer().accept(this, data).orElseThrow();
|
||||
data.writeVariable(declarationTree.name().name(), data.currentBlock(), rhs);
|
||||
}
|
||||
popSpan();
|
||||
return NOT_AN_EXPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(FunctionTree functionTree, SsaTranslation data) {
|
||||
pushSpan(functionTree);
|
||||
Node start = data.constructor.newStart();
|
||||
data.constructor.writeCurrentSideEffect(data.constructor.newSideEffectProj(start));
|
||||
functionTree.body().accept(this, data);
|
||||
popSpan();
|
||||
return NOT_AN_EXPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(IdentExpressionTree identExpressionTree, SsaTranslation data) {
|
||||
pushSpan(identExpressionTree);
|
||||
Node value = data.readVariable(identExpressionTree.name().name(), data.currentBlock());
|
||||
popSpan();
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(LiteralTree literalTree, SsaTranslation data) {
|
||||
pushSpan(literalTree);
|
||||
Node node = data.constructor.newConstInt((int) literalTree.value());
|
||||
popSpan();
|
||||
return Optional.of(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(LValueIdentTree lValueIdentTree, SsaTranslation data) {
|
||||
return NOT_AN_EXPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(NameTree nameTree, SsaTranslation data) {
|
||||
return NOT_AN_EXPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(NegateTree negateTree, SsaTranslation data) {
|
||||
pushSpan(negateTree);
|
||||
Node node = negateTree.expression().accept(this, data).orElseThrow();
|
||||
Node res = data.constructor.newSub(data.constructor.newConstInt(0), node);
|
||||
popSpan();
|
||||
return Optional.of(res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(ProgramTree programTree, SsaTranslation data) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(ReturnTree returnTree, SsaTranslation data) {
|
||||
pushSpan(returnTree);
|
||||
Node node = returnTree.expression().accept(this, data).orElseThrow();
|
||||
Node ret = data.constructor.newReturn(node);
|
||||
data.constructor.graph().endBlock().addPredecessor(ret);
|
||||
popSpan();
|
||||
return NOT_AN_EXPRESSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Node> visit(TypeTree typeTree, SsaTranslation data) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private Node projResultDivMod(SsaTranslation data, Node divMod) {
|
||||
// make sure we actually have a div or a mod, as optimizations could
|
||||
// have changed it to something else already
|
||||
if (!(divMod instanceof DivNode || divMod instanceof ModNode)) {
|
||||
return divMod;
|
||||
}
|
||||
Node projSideEffect = data.constructor.newSideEffectProj(divMod);
|
||||
data.constructor.writeCurrentSideEffect(projSideEffect);
|
||||
return data.constructor.newResultProj(divMod);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class AddNode extends BinaryOperationNode {
|
||||
|
||||
public AddNode(Block block, Node left, Node right) {
|
||||
super(block, left, right);
|
||||
}
|
||||
|
||||
@SuppressWarnings("EqualsDoesntCheckParameterClass") // we do, but not here
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return commutativeEquals(this, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return commutativeHashCode(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public sealed abstract class BinaryOperationNode extends Node permits AddNode, DivNode, ModNode, MulNode, SubNode {
|
||||
public static final int LEFT = 0;
|
||||
public static final int RIGHT = 1;
|
||||
|
||||
protected BinaryOperationNode(Block block, Node left, Node right) {
|
||||
super(block, left, right);
|
||||
}
|
||||
|
||||
protected BinaryOperationNode(Block block, Node left, Node right, Node sideEffect) {
|
||||
super(block, left, right, sideEffect);
|
||||
}
|
||||
|
||||
protected static int commutativeHashCode(BinaryOperationNode node) {
|
||||
int h = node.block().hashCode();
|
||||
// commutative operation: we want h(op(x, y)) == h(op(y, x))
|
||||
h += 31 * (node.predecessor(LEFT).hashCode() ^ node.predecessor(RIGHT).hashCode());
|
||||
return h;
|
||||
}
|
||||
|
||||
protected static boolean commutativeEquals(BinaryOperationNode a, Object bObj) {
|
||||
if (!(bObj instanceof BinaryOperationNode b)) {
|
||||
return false;
|
||||
}
|
||||
if (a.getClass() != b.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (a.predecessor(LEFT) == b.predecessor(LEFT) && a.predecessor(RIGHT) == b.predecessor(RIGHT)) {
|
||||
return true;
|
||||
}
|
||||
// commutative operation: op(x, y) == op(y, x)
|
||||
return a.predecessor(LEFT) == b.predecessor(RIGHT) && a.predecessor(RIGHT) == b.predecessor(LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof BinaryOperationNode binOp)) {
|
||||
return false;
|
||||
}
|
||||
return obj.getClass() == this.getClass()
|
||||
&& this.predecessor(LEFT) == binOp.predecessor(LEFT)
|
||||
&& this.predecessor(RIGHT) == binOp.predecessor(RIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.predecessor(LEFT).hashCode() * 31 + this.predecessor(RIGHT).hashCode()) ^ this.getClass().hashCode();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.IrGraph;
|
||||
|
||||
public final class Block extends Node {
|
||||
|
||||
public Block(IrGraph graph) {
|
||||
super(graph);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class ConstIntNode extends Node {
|
||||
private final int value;
|
||||
|
||||
public ConstIntNode(Block block, int value) {
|
||||
super(block);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int value() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ConstIntNode c) {
|
||||
return this.block() == c.block() && c.value == this.value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String info() {
|
||||
return "[" + this.value + "]";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class DivNode extends BinaryOperationNode {
|
||||
public static final int SIDE_EFFECT = 2;
|
||||
public DivNode(Block block, Node left, Node right, Node sideEffect) {
|
||||
super(block, left, right, sideEffect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// side effect, must be very careful with value numbering.
|
||||
// this is the most conservative approach
|
||||
return obj == this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class ModNode extends BinaryOperationNode {
|
||||
public static final int SIDE_EFFECT = 2;
|
||||
public ModNode(Block block, Node left, Node right, Node sideEffect) {
|
||||
super(block, left, right, sideEffect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// side effect, must be very careful with value numbering.
|
||||
// this is the most conservative approach
|
||||
return obj == this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class MulNode extends BinaryOperationNode {
|
||||
public MulNode(Block block, Node left, Node right) {
|
||||
super(block, left, right);
|
||||
}
|
||||
|
||||
@SuppressWarnings("EqualsDoesntCheckParameterClass") // we do, but not here
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return commutativeEquals(this, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return commutativeHashCode(this);
|
||||
}
|
||||
}
|
||||
73
src/main/java/edu/kit/kastel/vads/compiler/ir/node/Node.java
Normal file
73
src/main/java/edu/kit/kastel/vads/compiler/ir/node/Node.java
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.util.DebugInfo;
|
||||
import edu.kit.kastel.vads.compiler.ir.IrGraph;
|
||||
import edu.kit.kastel.vads.compiler.ir.util.DebugInfoHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/// The base class for all nodes.
|
||||
public sealed abstract class Node permits BinaryOperationNode, Block, ConstIntNode, Phi, ProjNode, ReturnNode, StartNode {
|
||||
private final IrGraph graph;
|
||||
private final Block block;
|
||||
private final List<Node> predecessors = new ArrayList<>();
|
||||
private final DebugInfo debugInfo;
|
||||
|
||||
protected Node(Block block, Node... predecessors) {
|
||||
this.graph = block.graph();
|
||||
this.block = block;
|
||||
this.predecessors.addAll(List.of(predecessors));
|
||||
for (Node predecessor : predecessors) {
|
||||
graph.registerSuccessor(predecessor, this);
|
||||
}
|
||||
this.debugInfo = DebugInfoHelper.getDebugInfo();
|
||||
}
|
||||
|
||||
protected Node(IrGraph graph) {
|
||||
assert this.getClass() == Block.class : "must be used by Block only";
|
||||
this.graph = graph;
|
||||
this.block = (Block) this;
|
||||
this.debugInfo = DebugInfo.NoInfo.INSTANCE;
|
||||
}
|
||||
|
||||
public final IrGraph graph() {
|
||||
return this.graph;
|
||||
}
|
||||
|
||||
public final Block block() {
|
||||
return this.block;
|
||||
}
|
||||
|
||||
public final List<? extends Node> predecessors() {
|
||||
return List.copyOf(this.predecessors);
|
||||
}
|
||||
|
||||
public final void setPredecessor(int idx, Node node) {
|
||||
this.graph.removeSuccessor(this.predecessors.get(idx), this);
|
||||
this.predecessors.set(idx, node);
|
||||
this.graph.registerSuccessor(node, this);
|
||||
}
|
||||
|
||||
public final void addPredecessor(Node node) {
|
||||
this.predecessors.add(node);
|
||||
this.graph.registerSuccessor(node, this);
|
||||
}
|
||||
|
||||
public final Node predecessor(int idx) {
|
||||
return this.predecessors.get(idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return (this.getClass().getSimpleName().replace("Node", "") + " " + info()).stripTrailing();
|
||||
}
|
||||
|
||||
protected String info() {
|
||||
return "";
|
||||
}
|
||||
|
||||
public DebugInfo debugInfo() {
|
||||
return debugInfo;
|
||||
}
|
||||
}
|
||||
11
src/main/java/edu/kit/kastel/vads/compiler/ir/node/Phi.java
Normal file
11
src/main/java/edu/kit/kastel/vads/compiler/ir/node/Phi.java
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class Phi extends Node {
|
||||
public Phi(Block block) {
|
||||
super(block);
|
||||
}
|
||||
|
||||
public void appendOperand(Node node) {
|
||||
addPredecessor(node);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class ProjNode extends Node {
|
||||
public static final int IN = 0;
|
||||
private final ProjectionInfo projectionInfo;
|
||||
|
||||
public ProjNode(Block block, Node in, ProjectionInfo projectionInfo) {
|
||||
super(block, in);
|
||||
this.projectionInfo = projectionInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String info() {
|
||||
return this.projectionInfo.toString();
|
||||
}
|
||||
|
||||
public sealed interface ProjectionInfo {
|
||||
|
||||
}
|
||||
|
||||
public enum SimpleProjectionInfo implements ProjectionInfo {
|
||||
RESULT, SIDE_EFFECT
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class ReturnNode extends Node {
|
||||
public static final int SIDE_EFFECT = 0;
|
||||
public static final int RESULT = 1;
|
||||
public ReturnNode(Block block, Node sideEffect, Node result) {
|
||||
super(block, sideEffect, result);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class StartNode extends Node {
|
||||
public StartNode(Block block) {
|
||||
super(block);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.node;
|
||||
|
||||
public final class SubNode extends BinaryOperationNode {
|
||||
public SubNode(Block block, Node left, Node right) {
|
||||
super(block, left, right);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.optimize;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/// This depends on [Node#equals(java.lang.Object)] and [Node#hashCode()] methods.
|
||||
/// As long as they take the block into account, it is only local, but replacement
|
||||
/// is extremely simple.
|
||||
/// When using classes like [HashMap] or [java.util.HashSet] without this optimization,
|
||||
/// the [Node#equals(java.lang.Object)] and [Node#hashCode()] methods must be adjusted.
|
||||
public class LocalValueNumbering implements Optimizer {
|
||||
private final Map<Node, Node> knownNodes = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Node transform(Node node) {
|
||||
return this.knownNodes.computeIfAbsent(node, n -> n);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.optimize;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
|
||||
/// An interface that allows replacing a node with a more optimal one.
|
||||
public interface Optimizer {
|
||||
|
||||
Node transform(Node node);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.util;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
|
||||
/// Provides information to ease debugging
|
||||
public sealed interface DebugInfo {
|
||||
enum NoInfo implements DebugInfo {
|
||||
INSTANCE
|
||||
}
|
||||
|
||||
record SourceInfo(Span span) implements DebugInfo {}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.util;
|
||||
|
||||
/// This is a dirty trick as we don't have Scoped Values.
|
||||
/// It allows tracking debug info without having to pass it
|
||||
/// down all the layers.
|
||||
public final class DebugInfoHelper {
|
||||
private static DebugInfo debugInfo = DebugInfo.NoInfo.INSTANCE;
|
||||
|
||||
public static void setDebugInfo(DebugInfo debugInfo) {
|
||||
DebugInfoHelper.debugInfo = debugInfo;
|
||||
}
|
||||
|
||||
public static DebugInfo getDebugInfo() {
|
||||
return debugInfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.util;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.ir.IrGraph;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Block;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/// Outputs a DOT format string to visualize an [IrGraph].
|
||||
public class GraphVizPrinter {
|
||||
private final Map<Block, Set<Node>> clusters = new HashMap<>();
|
||||
private final List<Edge> edges = new ArrayList<>();
|
||||
private final Map<Node, Integer> ids = new HashMap<>();
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
private final IrGraph graph;
|
||||
private int counter = 0;
|
||||
|
||||
public GraphVizPrinter(IrGraph graph) {
|
||||
this.graph = graph;
|
||||
}
|
||||
|
||||
public static String print(IrGraph graph) {
|
||||
GraphVizPrinter printer = new GraphVizPrinter(graph);
|
||||
printer.prepare(graph.endBlock(), new HashSet<>());
|
||||
printer.print();
|
||||
return printer.builder.toString();
|
||||
}
|
||||
|
||||
private void prepare(Node node, Set<Node> seen) {
|
||||
if (!seen.add(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(node instanceof Block)) {
|
||||
this.clusters.computeIfAbsent(node.block(), _ -> Collections.newSetFromMap(new IdentityHashMap<>()))
|
||||
.add(node);
|
||||
}
|
||||
int idx = 0;
|
||||
for (Node predecessor : node.predecessors()) {
|
||||
this.edges.add(new Edge(predecessor, node, idx++));
|
||||
prepare(predecessor, seen);
|
||||
}
|
||||
if (node == this.graph.endBlock()) {
|
||||
this.clusters.put(this.graph.endBlock(), Set.of());
|
||||
}
|
||||
}
|
||||
|
||||
private void print() {
|
||||
this.builder.append("digraph \"")
|
||||
.append(this.graph.name())
|
||||
.append("\"")
|
||||
.append("""
|
||||
{
|
||||
compound=true;
|
||||
layout=dot;
|
||||
node [shape=box];
|
||||
splines=ortho;
|
||||
overlap=false;
|
||||
|
||||
""");
|
||||
|
||||
this.clusters.forEach((block, nodes) -> {
|
||||
this.builder.append(" subgraph cluster_")
|
||||
.append(idFor(block))
|
||||
.append(" {\n")
|
||||
.repeat(" ", 8)
|
||||
.append("c_").append(idFor(block))
|
||||
.append(" [width=0, height=0, fixedsize=true, style=invis];\n");
|
||||
if (block == this.graph.endBlock()) {
|
||||
this.builder.repeat(" ", 8)
|
||||
.append("label=End;\n");
|
||||
}
|
||||
for (Node node : nodes) {
|
||||
this.builder.repeat(" ", 8)
|
||||
.append(idFor(node))
|
||||
.append(" [label=\"")
|
||||
.append(labelFor(node))
|
||||
.append("\"");
|
||||
if (node.debugInfo() instanceof DebugInfo.SourceInfo(Span span)) {
|
||||
this.builder.append(", tooltip=\"")
|
||||
.append("source span: ")
|
||||
.append(span)
|
||||
.append("\"");
|
||||
}
|
||||
this.builder.append("];\n");
|
||||
}
|
||||
this.builder.append(" }\n\n");
|
||||
});
|
||||
|
||||
for (Edge edge : this.edges) {
|
||||
this.builder.repeat(" ", 4)
|
||||
.append(nameFor(edge.from()))
|
||||
.append(" -> ")
|
||||
.append(nameFor(edge.to()))
|
||||
.append(" [")
|
||||
.append("label=")
|
||||
.append(edge.idx());
|
||||
|
||||
if (edge.from() instanceof Block b) {
|
||||
this.builder.append(", ")
|
||||
.append("ltail=")
|
||||
.append("cluster_")
|
||||
.append(idFor(b));
|
||||
}
|
||||
if (edge.to() instanceof Block b) {
|
||||
this.builder.append(", ")
|
||||
.append("lhead=")
|
||||
.append("cluster_")
|
||||
.append(idFor(b));
|
||||
}
|
||||
|
||||
this.builder.append("];\n");
|
||||
}
|
||||
|
||||
this.builder.append("}");
|
||||
|
||||
}
|
||||
|
||||
private int idFor(Node node) {
|
||||
return this.ids.computeIfAbsent(node, _ -> this.counter++);
|
||||
}
|
||||
|
||||
private String nameFor(Node node) {
|
||||
if (node instanceof Block) {
|
||||
return "c_" + idFor(node);
|
||||
}
|
||||
return String.valueOf(idFor(node));
|
||||
}
|
||||
|
||||
private String labelFor(Node node) {
|
||||
return node.toString();
|
||||
}
|
||||
|
||||
record Edge(Node from, Node to, int idx) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package edu.kit.kastel.vads.compiler.ir.util;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.ir.node.Node;
|
||||
import edu.kit.kastel.vads.compiler.ir.node.ProjNode;
|
||||
|
||||
public final class NodeSupport {
|
||||
private NodeSupport() {
|
||||
|
||||
}
|
||||
|
||||
public static Node predecessorSkipProj(Node node, int predIdx) {
|
||||
Node pred = node.predecessor(predIdx);
|
||||
if (pred instanceof ProjNode) {
|
||||
return pred.predecessor(ProjNode.IN);
|
||||
}
|
||||
return pred;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
|
||||
public record ErrorToken(String value, Span span) implements Token {
|
||||
@Override
|
||||
public String asString() {
|
||||
return value();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
|
||||
public record Identifier(String value, Span span) implements Token {
|
||||
@Override
|
||||
public String asString() {
|
||||
return value();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
|
||||
public record Keyword(KeywordType type, Span span) implements Token {
|
||||
@Override
|
||||
public boolean isKeyword(KeywordType keywordType) {
|
||||
return type() == keywordType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return type().keyword();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
public enum KeywordType {
|
||||
STRUCT("struct"),
|
||||
IF("if"),
|
||||
ELSE("else"),
|
||||
WHILE("while"),
|
||||
FOR("for"),
|
||||
CONTINUE("continue"),
|
||||
BREAK("break"),
|
||||
RETURN("return"),
|
||||
ASSERT("assert"),
|
||||
TRUE("true"),
|
||||
FALSE("false"),
|
||||
NULL("NULL"),
|
||||
PRINT("print"),
|
||||
READ("read"),
|
||||
ALLOC("alloc"),
|
||||
ALLOC_ARRAY("alloc_array"),
|
||||
INT("int"),
|
||||
BOOL("bool"),
|
||||
VOID("void"),
|
||||
CHAR("char"),
|
||||
STRING("string"),
|
||||
;
|
||||
|
||||
private final String keyword;
|
||||
|
||||
KeywordType(String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
|
||||
public String keyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return keyword();
|
||||
}
|
||||
}
|
||||
215
src/main/java/edu/kit/kastel/vads/compiler/lexer/Lexer.java
Normal file
215
src/main/java/edu/kit/kastel/vads/compiler/lexer/Lexer.java
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Position;
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Operator.OperatorType;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Separator.SeparatorType;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class Lexer {
|
||||
private final String source;
|
||||
private int pos;
|
||||
private int lineStart;
|
||||
private int line;
|
||||
|
||||
private Lexer(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public static Lexer forString(String source) {
|
||||
return new Lexer(source);
|
||||
}
|
||||
|
||||
public Optional<Token> nextToken() {
|
||||
ErrorToken error = skipWhitespace();
|
||||
if (error != null) {
|
||||
return Optional.of(error);
|
||||
}
|
||||
if (this.pos >= this.source.length()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Token t = switch (peek()) {
|
||||
case '(' -> separator(SeparatorType.PAREN_OPEN);
|
||||
case ')' -> separator(SeparatorType.PAREN_CLOSE);
|
||||
case '{' -> separator(SeparatorType.BRACE_OPEN);
|
||||
case '}' -> separator(SeparatorType.BRACE_CLOSE);
|
||||
case ';' -> separator(SeparatorType.SEMICOLON);
|
||||
case '-' -> singleOrAssign(OperatorType.MINUS, OperatorType.ASSIGN_MINUS);
|
||||
case '+' -> singleOrAssign(OperatorType.PLUS, OperatorType.ASSIGN_PLUS);
|
||||
case '*' -> singleOrAssign(OperatorType.MUL, OperatorType.ASSIGN_MUL);
|
||||
case '/' -> singleOrAssign(OperatorType.DIV, OperatorType.ASSIGN_DIV);
|
||||
case '%' -> singleOrAssign(OperatorType.MOD, OperatorType.ASSIGN_MOD);
|
||||
case '=' -> new Operator(OperatorType.ASSIGN, buildSpan(1));
|
||||
default -> {
|
||||
if (isIdentifierChar(peek())) {
|
||||
if (isNumeric(peek())) {
|
||||
yield lexNumber();
|
||||
}
|
||||
yield lexIdentifierOrKeyword();
|
||||
}
|
||||
yield new ErrorToken(String.valueOf(peek()), buildSpan(1));
|
||||
}
|
||||
};
|
||||
|
||||
return Optional.of(t);
|
||||
}
|
||||
|
||||
private @Nullable ErrorToken skipWhitespace() {
|
||||
enum CommentType {
|
||||
SINGLE_LINE,
|
||||
MULTI_LINE
|
||||
}
|
||||
CommentType currentCommentType = null;
|
||||
int multiLineCommentDepth = 0;
|
||||
int commentStart = -1;
|
||||
while (hasMore(0)) {
|
||||
switch (peek()) {
|
||||
case ' ', '\t' -> this.pos++;
|
||||
case '\n', '\r' -> {
|
||||
this.pos++;
|
||||
this.lineStart = this.pos;
|
||||
this.line++;
|
||||
if (currentCommentType == CommentType.SINGLE_LINE) {
|
||||
currentCommentType = null;
|
||||
}
|
||||
}
|
||||
case '/' -> {
|
||||
if (currentCommentType == CommentType.SINGLE_LINE) {
|
||||
this.pos++;
|
||||
continue;
|
||||
}
|
||||
if (hasMore(1)) {
|
||||
if (peek(1) == '/' && currentCommentType == null) {
|
||||
currentCommentType = CommentType.SINGLE_LINE;
|
||||
} else if (peek(1) == '*') {
|
||||
currentCommentType = CommentType.MULTI_LINE;
|
||||
multiLineCommentDepth++;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
commentStart = this.pos;
|
||||
this.pos += 2;
|
||||
continue;
|
||||
}
|
||||
// are we in a multi line comment of any depth?
|
||||
if (multiLineCommentDepth > 0) {
|
||||
this.pos++;
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
default -> {
|
||||
if (currentCommentType == CommentType.MULTI_LINE) {
|
||||
if (peek() == '*' && hasMore(1) && peek(1) == '/') {
|
||||
this.pos += 2;
|
||||
multiLineCommentDepth--;
|
||||
currentCommentType = multiLineCommentDepth == 0 ? null : CommentType.MULTI_LINE;
|
||||
} else {
|
||||
this.pos++;
|
||||
}
|
||||
continue;
|
||||
} else if (currentCommentType == CommentType.SINGLE_LINE) {
|
||||
this.pos++;
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasMore(0) && currentCommentType == CommentType.MULTI_LINE) {
|
||||
return new ErrorToken(this.source.substring(commentStart), buildSpan(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Separator separator(SeparatorType parenOpen) {
|
||||
return new Separator(parenOpen, buildSpan(1));
|
||||
}
|
||||
|
||||
private Token lexIdentifierOrKeyword() {
|
||||
int off = 1;
|
||||
while (hasMore(off) && isIdentifierChar(peek(off))) {
|
||||
off++;
|
||||
}
|
||||
String id = this.source.substring(this.pos, this.pos + off);
|
||||
// This is a naive solution. Using a better data structure (hashmap, trie) likely performs better.
|
||||
for (KeywordType value : KeywordType.values()) {
|
||||
if (value.keyword().equals(id)) {
|
||||
return new Keyword(value, buildSpan(off));
|
||||
}
|
||||
}
|
||||
return new Identifier(id, buildSpan(off));
|
||||
}
|
||||
|
||||
private Token lexNumber() {
|
||||
if (isHexPrefix()) {
|
||||
int off = 2;
|
||||
while (hasMore(off) && isHex(peek(off))) {
|
||||
off++;
|
||||
}
|
||||
if (off == 2) {
|
||||
// 0x without any further hex digits
|
||||
return new ErrorToken(this.source.substring(this.pos, this.pos + off), buildSpan(2));
|
||||
}
|
||||
return new NumberLiteral(this.source.substring(this.pos, this.pos + off), 16, buildSpan(off));
|
||||
}
|
||||
int off = 1;
|
||||
while (hasMore(off) && isNumeric(peek(off))) {
|
||||
off++;
|
||||
}
|
||||
if (peek() == '0' && off > 1) {
|
||||
// leading zero is not allowed
|
||||
return new ErrorToken(this.source.substring(this.pos, this.pos + off), buildSpan(off));
|
||||
}
|
||||
return new NumberLiteral(this.source.substring(this.pos, this.pos + off), 10, buildSpan(off));
|
||||
}
|
||||
|
||||
private boolean isHexPrefix() {
|
||||
return peek() == '0' && hasMore(1) && (peek(1) == 'x' || peek(1) == 'X');
|
||||
}
|
||||
|
||||
private boolean isIdentifierChar(char c) {
|
||||
return c == '_'
|
||||
|| c >= 'a' && c <= 'z'
|
||||
|| c >= 'A' && c <= 'Z'
|
||||
|| c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
private boolean isNumeric(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
private boolean isHex(char c) {
|
||||
return isNumeric(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
private Token singleOrAssign(OperatorType single, OperatorType assign) {
|
||||
if (hasMore(1) && peek(1) == '=') {
|
||||
return new Operator(assign, buildSpan(2));
|
||||
}
|
||||
return new Operator(single, buildSpan(1));
|
||||
}
|
||||
|
||||
private Span buildSpan(int proceed) {
|
||||
int start = this.pos;
|
||||
this.pos += proceed;
|
||||
Position.SimplePosition s = new Position.SimplePosition(this.line, start - this.lineStart);
|
||||
Position.SimplePosition e = new Position.SimplePosition(this.line, start - this.lineStart + proceed);
|
||||
return new Span.SimpleSpan(s, e);
|
||||
}
|
||||
|
||||
private char peek() {
|
||||
return this.source.charAt(this.pos);
|
||||
}
|
||||
|
||||
private boolean hasMore(int offset) {
|
||||
return this.pos + offset < this.source.length();
|
||||
}
|
||||
|
||||
private char peek(int offset) {
|
||||
return this.source.charAt(this.pos + offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
|
||||
public record NumberLiteral(String value, int base, Span span) implements Token {
|
||||
@Override
|
||||
public String asString() {
|
||||
return value();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
|
||||
public record Operator(OperatorType type, Span span) implements Token {
|
||||
|
||||
@Override
|
||||
public boolean isOperator(OperatorType operatorType) {
|
||||
return type() == operatorType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return type().toString();
|
||||
}
|
||||
|
||||
public enum OperatorType {
|
||||
ASSIGN_MINUS("-="),
|
||||
MINUS("-"),
|
||||
ASSIGN_PLUS("+="),
|
||||
PLUS("+"),
|
||||
MUL("*"),
|
||||
ASSIGN_MUL("*="),
|
||||
ASSIGN_DIV("/="),
|
||||
DIV("/"),
|
||||
ASSIGN_MOD("%="),
|
||||
MOD("%"),
|
||||
ASSIGN("="),
|
||||
;
|
||||
|
||||
private final String value;
|
||||
|
||||
OperatorType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
|
||||
public record Separator(SeparatorType type, Span span) implements Token {
|
||||
|
||||
@Override
|
||||
public boolean isSeparator(SeparatorType separatorType) {
|
||||
return type() == separatorType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return type().toString();
|
||||
}
|
||||
|
||||
public enum SeparatorType {
|
||||
PAREN_OPEN("("),
|
||||
PAREN_CLOSE(")"),
|
||||
BRACE_OPEN("{"),
|
||||
BRACE_CLOSE("}"),
|
||||
SEMICOLON(";");
|
||||
|
||||
private final String value;
|
||||
|
||||
SeparatorType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/main/java/edu/kit/kastel/vads/compiler/lexer/Token.java
Normal file
22
src/main/java/edu/kit/kastel/vads/compiler/lexer/Token.java
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package edu.kit.kastel.vads.compiler.lexer;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
|
||||
public sealed interface Token permits ErrorToken, Identifier, Keyword, NumberLiteral, Operator, Separator {
|
||||
|
||||
Span span();
|
||||
|
||||
default boolean isKeyword(KeywordType keywordType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean isOperator(Operator.OperatorType operatorType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean isSeparator(Separator.SeparatorType separatorType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String asString();
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package edu.kit.kastel.vads.compiler.parser;
|
||||
|
||||
public class ParseException extends RuntimeException {
|
||||
public ParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
201
src/main/java/edu/kit/kastel/vads/compiler/parser/Parser.java
Normal file
201
src/main/java/edu/kit/kastel/vads/compiler/parser/Parser.java
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
package edu.kit.kastel.vads.compiler.parser;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.lexer.Identifier;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Keyword;
|
||||
import edu.kit.kastel.vads.compiler.lexer.KeywordType;
|
||||
import edu.kit.kastel.vads.compiler.lexer.NumberLiteral;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Operator;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Operator.OperatorType;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Separator;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Separator.SeparatorType;
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Token;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.AssignmentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BinaryOperationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BlockTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.DeclarationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ExpressionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.IdentExpressionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LValueIdentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LValueTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LiteralTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NameTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NegateTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ReturnTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.StatementTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.TypeTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.symbol.Name;
|
||||
import edu.kit.kastel.vads.compiler.parser.type.BasicType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Parser {
|
||||
private final TokenSource tokenSource;
|
||||
|
||||
public Parser(TokenSource tokenSource) {
|
||||
this.tokenSource = tokenSource;
|
||||
}
|
||||
|
||||
public ProgramTree parseProgram() {
|
||||
return new ProgramTree(List.of(parseFunction()));
|
||||
}
|
||||
|
||||
private FunctionTree parseFunction() {
|
||||
Keyword returnType = this.tokenSource.expectKeyword(KeywordType.INT);
|
||||
Identifier identifier = this.tokenSource.expectIdentifier();
|
||||
this.tokenSource.expectSeparator(SeparatorType.PAREN_OPEN);
|
||||
this.tokenSource.expectSeparator(SeparatorType.PAREN_CLOSE);
|
||||
BlockTree body = parseBlock();
|
||||
return new FunctionTree(
|
||||
new TypeTree(BasicType.INT, returnType.span()),
|
||||
name(identifier),
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
private BlockTree parseBlock() {
|
||||
Separator bodyOpen = this.tokenSource.expectSeparator(SeparatorType.BRACE_OPEN);
|
||||
List<StatementTree> statements = new ArrayList<>();
|
||||
while (!(this.tokenSource.peek() instanceof Separator sep && sep.type() == SeparatorType.BRACE_CLOSE)) {
|
||||
statements.add(parseStatement());
|
||||
}
|
||||
Separator bodyClose = this.tokenSource.expectSeparator(SeparatorType.BRACE_CLOSE);
|
||||
return new BlockTree(statements, bodyOpen.span().merge(bodyClose.span()));
|
||||
}
|
||||
|
||||
private StatementTree parseStatement() {
|
||||
StatementTree statement;
|
||||
if (this.tokenSource.peek().isKeyword(KeywordType.INT)) {
|
||||
statement = parseDeclaration();
|
||||
} else if (this.tokenSource.peek().isKeyword(KeywordType.RETURN)) {
|
||||
statement = parseReturn();
|
||||
} else {
|
||||
statement = parseSimple();
|
||||
}
|
||||
this.tokenSource.expectSeparator(SeparatorType.SEMICOLON);
|
||||
return statement;
|
||||
}
|
||||
|
||||
private StatementTree parseDeclaration() {
|
||||
Keyword type = this.tokenSource.expectKeyword(KeywordType.INT);
|
||||
Identifier ident = this.tokenSource.expectIdentifier();
|
||||
ExpressionTree expr = null;
|
||||
if (this.tokenSource.peek().isOperator(OperatorType.ASSIGN)) {
|
||||
this.tokenSource.expectOperator(OperatorType.ASSIGN);
|
||||
expr = parseExpression();
|
||||
}
|
||||
return new DeclarationTree(new TypeTree(BasicType.INT, type.span()), name(ident), expr);
|
||||
}
|
||||
|
||||
private StatementTree parseSimple() {
|
||||
LValueTree lValue = parseLValue();
|
||||
Operator assignmentOperator = parseAssignmentOperator();
|
||||
ExpressionTree expression = parseExpression();
|
||||
return new AssignmentTree(lValue, assignmentOperator, expression);
|
||||
}
|
||||
|
||||
private Operator parseAssignmentOperator() {
|
||||
if (this.tokenSource.peek() instanceof Operator op) {
|
||||
return switch (op.type()) {
|
||||
case ASSIGN, ASSIGN_DIV, ASSIGN_MINUS, ASSIGN_MOD, ASSIGN_MUL, ASSIGN_PLUS -> {
|
||||
this.tokenSource.consume();
|
||||
yield op;
|
||||
}
|
||||
default -> throw new ParseException("expected assignment but got " + op.type());
|
||||
};
|
||||
}
|
||||
throw new ParseException("expected assignment but got " + this.tokenSource.peek());
|
||||
}
|
||||
|
||||
private LValueTree parseLValue() {
|
||||
if (this.tokenSource.peek().isSeparator(SeparatorType.PAREN_OPEN)) {
|
||||
this.tokenSource.expectSeparator(SeparatorType.PAREN_OPEN);
|
||||
LValueTree inner = parseLValue();
|
||||
this.tokenSource.expectSeparator(SeparatorType.PAREN_CLOSE);
|
||||
return inner;
|
||||
}
|
||||
Identifier identifier = this.tokenSource.expectIdentifier();
|
||||
return new LValueIdentTree(name(identifier));
|
||||
}
|
||||
|
||||
private StatementTree parseReturn() {
|
||||
Keyword ret = this.tokenSource.expectKeyword(KeywordType.RETURN);
|
||||
ExpressionTree expression = parseExpression();
|
||||
return new ReturnTree(expression, ret.span().start());
|
||||
}
|
||||
|
||||
private ExpressionTree parseExpression() {
|
||||
ExpressionTree lhs = parseTerm();
|
||||
while (true) {
|
||||
if (this.tokenSource.peek() instanceof Operator(var type, _)
|
||||
&& (type == OperatorType.PLUS || type == OperatorType.MINUS)) {
|
||||
this.tokenSource.consume();
|
||||
lhs = new BinaryOperationTree(lhs, parseTerm(), type);
|
||||
} else {
|
||||
return lhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ExpressionTree parseTerm() {
|
||||
ExpressionTree lhs = parseFactor();
|
||||
while (true) {
|
||||
if (this.tokenSource.peek() instanceof Operator(var type, _)
|
||||
&& (type == OperatorType.MUL || type == OperatorType.DIV || type == OperatorType.MOD)) {
|
||||
this.tokenSource.consume();
|
||||
lhs = new BinaryOperationTree(lhs, parseFactor(), type);
|
||||
} else {
|
||||
return lhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ExpressionTree parseFactor() {
|
||||
return switch (this.tokenSource.peek()) {
|
||||
case Separator(var type, _) when type == SeparatorType.PAREN_OPEN -> {
|
||||
this.tokenSource.consume();
|
||||
ExpressionTree expression = parseExpression();
|
||||
this.tokenSource.expectSeparator(SeparatorType.PAREN_CLOSE);
|
||||
yield expression;
|
||||
}
|
||||
case Operator(var type, _) when type == OperatorType.MINUS -> {
|
||||
Span span = this.tokenSource.consume().span();
|
||||
yield new NegateTree(parseFactor(), span);
|
||||
}
|
||||
case Identifier ident -> {
|
||||
this.tokenSource.consume();
|
||||
yield new IdentExpressionTree(name(ident));
|
||||
}
|
||||
case NumberLiteral(String value, int base, Span span) -> {
|
||||
this.tokenSource.consume();
|
||||
yield new LiteralTree(parseValue(value, base), span);
|
||||
}
|
||||
case Token t -> throw new ParseException("invalid factor " + t);
|
||||
};
|
||||
}
|
||||
|
||||
private static long parseValue(String value, int base) {
|
||||
int begin = 0;
|
||||
int end = value.length();
|
||||
if (base == 16) {
|
||||
begin = 2; // ignore 0x
|
||||
}
|
||||
long l;
|
||||
try {
|
||||
l = Long.parseLong(value, begin, end, base);
|
||||
} catch (NumberFormatException _) {
|
||||
throw new ParseException("invalid int literal " + value);
|
||||
}
|
||||
if (l < 0 || l > Integer.toUnsignedLong(Integer.MIN_VALUE)) {
|
||||
throw new ParseException("invalid int literal " + value);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
private static NameTree name(Identifier ident) {
|
||||
return new NameTree(Name.forIdentifier(ident), ident.span());
|
||||
}
|
||||
}
|
||||
138
src/main/java/edu/kit/kastel/vads/compiler/parser/Printer.java
Normal file
138
src/main/java/edu/kit/kastel/vads/compiler/parser/Printer.java
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package edu.kit.kastel.vads.compiler.parser;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.AssignmentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BinaryOperationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BlockTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.IdentExpressionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LValueIdentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LiteralTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NameTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NegateTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ReturnTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.Tree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.DeclarationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.StatementTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.TypeTree;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// This is a utility class to help with debugging the parser.
|
||||
public class Printer {
|
||||
|
||||
private final Tree ast;
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
private boolean requiresIndent;
|
||||
private int indentDepth;
|
||||
|
||||
public Printer(Tree ast) {
|
||||
this.ast = ast;
|
||||
}
|
||||
|
||||
public static String print(Tree ast) {
|
||||
Printer printer = new Printer(ast);
|
||||
printer.printRoot();
|
||||
return printer.builder.toString();
|
||||
}
|
||||
|
||||
private void printRoot() {
|
||||
printTree(this.ast);
|
||||
}
|
||||
|
||||
private void printTree(Tree tree) {
|
||||
switch (tree) {
|
||||
case BlockTree(List<StatementTree> statements, _) -> {
|
||||
print("{");
|
||||
lineBreak();
|
||||
this.indentDepth++;
|
||||
for (StatementTree statement : statements) {
|
||||
printTree(statement);
|
||||
}
|
||||
this.indentDepth--;
|
||||
print("}");
|
||||
}
|
||||
case FunctionTree(var returnType, var name, var body) -> {
|
||||
printTree(returnType);
|
||||
space();
|
||||
printTree(name);
|
||||
print("()");
|
||||
space();
|
||||
printTree(body);
|
||||
}
|
||||
case NameTree(var name, _) -> print(name.asString());
|
||||
case ProgramTree(var topLevelTrees) -> {
|
||||
for (FunctionTree function : topLevelTrees) {
|
||||
printTree(function);
|
||||
lineBreak();
|
||||
}
|
||||
}
|
||||
case TypeTree(var type, _) -> print(type.asString());
|
||||
case BinaryOperationTree(var lhs, var rhs, var op) -> {
|
||||
print("(");
|
||||
printTree(lhs);
|
||||
print(")");
|
||||
space();
|
||||
this.builder.append(op);
|
||||
space();
|
||||
print("(");
|
||||
printTree(rhs);
|
||||
print(")");
|
||||
}
|
||||
case LiteralTree(var value, _) -> this.builder.append(value);
|
||||
case NegateTree(var expression, _) -> {
|
||||
print("-(");
|
||||
printTree(expression);
|
||||
print(")");
|
||||
}
|
||||
case AssignmentTree(var lValue, var op, var expression) -> {
|
||||
printTree(lValue);
|
||||
space();
|
||||
this.builder.append(op);
|
||||
space();
|
||||
printTree(expression);
|
||||
semicolon();
|
||||
}
|
||||
case DeclarationTree(var type, var name, var initializer) -> {
|
||||
printTree(type);
|
||||
space();
|
||||
printTree(name);
|
||||
if (initializer != null) {
|
||||
print(" = ");
|
||||
printTree(initializer);
|
||||
}
|
||||
semicolon();
|
||||
}
|
||||
case ReturnTree(var expr, _) -> {
|
||||
print("return ");
|
||||
printTree(expr);
|
||||
semicolon();
|
||||
}
|
||||
case LValueIdentTree(var name) -> printTree(name);
|
||||
case IdentExpressionTree(var name) -> printTree(name);
|
||||
}
|
||||
}
|
||||
|
||||
private void print(String str) {
|
||||
if (this.requiresIndent) {
|
||||
this.requiresIndent = false;
|
||||
this.builder.append(" ".repeat(4 * this.indentDepth));
|
||||
}
|
||||
this.builder.append(str);
|
||||
}
|
||||
|
||||
private void lineBreak() {
|
||||
this.builder.append("\n");
|
||||
this.requiresIndent = true;
|
||||
}
|
||||
|
||||
private void semicolon() {
|
||||
this.builder.append(";");
|
||||
lineBreak();
|
||||
}
|
||||
|
||||
private void space() {
|
||||
this.builder.append(" ");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package edu.kit.kastel.vads.compiler.parser;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.lexer.Identifier;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Keyword;
|
||||
import edu.kit.kastel.vads.compiler.lexer.KeywordType;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Lexer;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Operator;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Operator.OperatorType;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Separator;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Separator.SeparatorType;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Token;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TokenSource {
|
||||
private final List<Token> tokens;
|
||||
private int idx;
|
||||
|
||||
public TokenSource(Lexer lexer) {
|
||||
this.tokens = Stream.generate(lexer::nextToken)
|
||||
.takeWhile(Optional::isPresent)
|
||||
.map(Optional::orElseThrow)
|
||||
.toList();
|
||||
}
|
||||
|
||||
TokenSource(List<Token> tokens) {
|
||||
this.tokens = List.copyOf(tokens);
|
||||
}
|
||||
|
||||
public Token peek() {
|
||||
expectHasMore();
|
||||
return this.tokens.get(this.idx);
|
||||
}
|
||||
|
||||
public Keyword expectKeyword(KeywordType type) {
|
||||
Token token = peek();
|
||||
if (!(token instanceof Keyword kw) || kw.type() != type) {
|
||||
throw new ParseException("expected keyword '" + type + "' but got " + token);
|
||||
}
|
||||
this.idx++;
|
||||
return kw;
|
||||
}
|
||||
|
||||
public Separator expectSeparator(SeparatorType type) {
|
||||
Token token = peek();
|
||||
if (!(token instanceof Separator sep) || sep.type() != type) {
|
||||
throw new ParseException("expected separator '" + type + "' but got " + token);
|
||||
}
|
||||
this.idx++;
|
||||
return sep;
|
||||
}
|
||||
|
||||
public Operator expectOperator(OperatorType type) {
|
||||
Token token = peek();
|
||||
if (!(token instanceof Operator op) || op.type() != type) {
|
||||
throw new ParseException("expected operator '" + type + "' but got " + token);
|
||||
}
|
||||
this.idx++;
|
||||
return op;
|
||||
}
|
||||
public Identifier expectIdentifier() {
|
||||
Token token = peek();
|
||||
if (!(token instanceof Identifier ident)) {
|
||||
throw new ParseException("expected identifier but got " + token);
|
||||
}
|
||||
this.idx++;
|
||||
return ident;
|
||||
}
|
||||
|
||||
public Token consume() {
|
||||
Token token = peek();
|
||||
this.idx++;
|
||||
return token;
|
||||
}
|
||||
|
||||
private void expectHasMore() {
|
||||
if (this.idx >= this.tokens.size()) {
|
||||
throw new ParseException("reached end of file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.lexer.Operator;
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record AssignmentTree(LValueTree lValue, Operator operator, ExpressionTree expression) implements StatementTree {
|
||||
@Override
|
||||
public Span span() {
|
||||
return lValue().span().merge(expression().span());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.lexer.Operator;
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record BinaryOperationTree(
|
||||
ExpressionTree lhs, ExpressionTree rhs, Operator.OperatorType operatorType
|
||||
) implements ExpressionTree {
|
||||
@Override
|
||||
public Span span() {
|
||||
return lhs().span().merge(rhs().span());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record BlockTree(List<StatementTree> statements, Span span) implements StatementTree {
|
||||
|
||||
public BlockTree {
|
||||
statements = List.copyOf(statements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public record DeclarationTree(TypeTree type, NameTree name, @Nullable ExpressionTree initializer) implements StatementTree {
|
||||
@Override
|
||||
public Span span() {
|
||||
if (initializer() != null) {
|
||||
return type().span().merge(initializer().span());
|
||||
}
|
||||
return type().span().merge(name().span());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
public sealed interface ExpressionTree extends Tree permits BinaryOperationTree, IdentExpressionTree, LiteralTree, NegateTree {
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record FunctionTree(TypeTree returnType, NameTree name, BlockTree body) implements Tree {
|
||||
@Override
|
||||
public Span span() {
|
||||
return new Span.SimpleSpan(returnType().span().start(), body().span().end());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record IdentExpressionTree(NameTree name) implements ExpressionTree {
|
||||
@Override
|
||||
public Span span() {
|
||||
return name().span();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record LValueIdentTree(NameTree name) implements LValueTree {
|
||||
@Override
|
||||
public Span span() {
|
||||
return name().span();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
public sealed interface LValueTree extends Tree permits LValueIdentTree {
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record LiteralTree(long value, Span span) implements ExpressionTree {
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.symbol.Name;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record NameTree(Name name, Span span) implements Tree {
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record NegateTree(ExpressionTree expression, Span minusPos) implements ExpressionTree {
|
||||
@Override
|
||||
public Span span() {
|
||||
return minusPos().merge(expression().span());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record ProgramTree(List<FunctionTree> topLevelTrees) implements Tree {
|
||||
public ProgramTree {
|
||||
assert !topLevelTrees.isEmpty() : "must be non-empty";
|
||||
topLevelTrees = List.copyOf(topLevelTrees);
|
||||
}
|
||||
@Override
|
||||
public Span span() {
|
||||
var first = topLevelTrees.getFirst();
|
||||
var last = topLevelTrees.getLast();
|
||||
return new Span.SimpleSpan(first.span().start(), last.span().end());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Position;
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record ReturnTree(ExpressionTree expression, Position start) implements StatementTree {
|
||||
@Override
|
||||
public Span span() {
|
||||
return new Span.SimpleSpan(start(), expression().span().end());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
public sealed interface StatementTree extends Tree permits AssignmentTree, BlockTree, DeclarationTree, ReturnTree {
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public sealed interface Tree permits ExpressionTree, FunctionTree, LValueTree, NameTree, ProgramTree, StatementTree, TypeTree {
|
||||
|
||||
Span span();
|
||||
|
||||
<T, R> R accept(Visitor<T, R> visitor, T data);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.ast;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.Span;
|
||||
import edu.kit.kastel.vads.compiler.parser.type.Type;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Visitor;
|
||||
|
||||
public record TypeTree(Type type, Span span) implements Tree {
|
||||
@Override
|
||||
public <T, R> R accept(Visitor<T, R> visitor, T data) {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.symbol;
|
||||
|
||||
record IdentName(String identifier) implements Name {
|
||||
@Override
|
||||
public String asString() {
|
||||
return identifier();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.symbol;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.lexer.KeywordType;
|
||||
|
||||
record KeywordName(KeywordType type) implements Name {
|
||||
@Override
|
||||
public String asString() {
|
||||
return type().keyword();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.symbol;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.lexer.Identifier;
|
||||
import edu.kit.kastel.vads.compiler.lexer.Keyword;
|
||||
|
||||
public sealed interface Name permits IdentName, KeywordName {
|
||||
|
||||
static Name forKeyword(Keyword keyword) {
|
||||
return new KeywordName(keyword.type());
|
||||
}
|
||||
|
||||
static Name forIdentifier(Identifier identifier) {
|
||||
return new IdentName(identifier.value());
|
||||
}
|
||||
|
||||
String asString();
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.type;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum BasicType implements Type {
|
||||
INT;
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.type;
|
||||
|
||||
public sealed interface Type permits BasicType {
|
||||
String asString();
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.visitor;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.AssignmentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BinaryOperationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BlockTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.DeclarationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.IdentExpressionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LValueIdentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LiteralTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NameTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NegateTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ReturnTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.TypeTree;
|
||||
|
||||
/// A visitor that does nothing and returns [Unit#INSTANCE] by default.
|
||||
/// This can be used to implement operations only for specific tree types.
|
||||
public interface NoOpVisitor<T> extends Visitor<T, Unit> {
|
||||
|
||||
@Override
|
||||
default Unit visit(AssignmentTree assignmentTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(BinaryOperationTree binaryOperationTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(BlockTree blockTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(DeclarationTree declarationTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(FunctionTree functionTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(IdentExpressionTree identExpressionTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(LiteralTree literalTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(LValueIdentTree lValueIdentTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(NameTree nameTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(NegateTree negateTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(ProgramTree programTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(ReturnTree returnTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default Unit visit(TypeTree typeTree, T data) {
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.visitor;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.AssignmentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BinaryOperationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BlockTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.DeclarationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.IdentExpressionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LValueIdentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LiteralTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NameTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NegateTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ReturnTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.StatementTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.TypeTree;
|
||||
|
||||
/// A visitor that traverses a tree in postorder
|
||||
/// @param <T> a type for additional data
|
||||
/// @param <R> a type for a return type
|
||||
public class RecursivePostorderVisitor<T, R> implements Visitor<T, R> {
|
||||
private final Visitor<T, R> visitor;
|
||||
|
||||
public RecursivePostorderVisitor(Visitor<T, R> visitor) {
|
||||
this.visitor = visitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(AssignmentTree assignmentTree, T data) {
|
||||
R r = assignmentTree.lValue().accept(this, data);
|
||||
r = assignmentTree.expression().accept(this, accumulate(data, r));
|
||||
r = this.visitor.visit(assignmentTree, accumulate(data, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(BinaryOperationTree binaryOperationTree, T data) {
|
||||
R r = binaryOperationTree.lhs().accept(this, data);
|
||||
r = binaryOperationTree.rhs().accept(this, accumulate(data, r));
|
||||
r = this.visitor.visit(binaryOperationTree, accumulate(data, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(BlockTree blockTree, T data) {
|
||||
R r;
|
||||
T d = data;
|
||||
for (StatementTree statement : blockTree.statements()) {
|
||||
r = statement.accept(this, d);
|
||||
d = accumulate(d, r);
|
||||
}
|
||||
r = this.visitor.visit(blockTree, d);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(DeclarationTree declarationTree, T data) {
|
||||
R r = declarationTree.type().accept(this, data);
|
||||
r = declarationTree.name().accept(this, accumulate(data, r));
|
||||
if (declarationTree.initializer() != null) {
|
||||
r = declarationTree.initializer().accept(this, accumulate(data, r));
|
||||
}
|
||||
r = this.visitor.visit(declarationTree, accumulate(data, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(FunctionTree functionTree, T data) {
|
||||
R r = functionTree.returnType().accept(this, data);
|
||||
r = functionTree.name().accept(this, accumulate(data, r));
|
||||
r = functionTree.body().accept(this, accumulate(data, r));
|
||||
r = this.visitor.visit(functionTree, accumulate(data, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(IdentExpressionTree identExpressionTree, T data) {
|
||||
R r = identExpressionTree.name().accept(this, data);
|
||||
r = this.visitor.visit(identExpressionTree, accumulate(data, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(LiteralTree literalTree, T data) {
|
||||
return this.visitor.visit(literalTree, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(LValueIdentTree lValueIdentTree, T data) {
|
||||
R r = lValueIdentTree.name().accept(this, data);
|
||||
r = this.visitor.visit(lValueIdentTree, accumulate(data, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(NameTree nameTree, T data) {
|
||||
return this.visitor.visit(nameTree, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(NegateTree negateTree, T data) {
|
||||
R r = negateTree.expression().accept(this, data);
|
||||
r = this.visitor.visit(negateTree, accumulate(data, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(ProgramTree programTree, T data) {
|
||||
R r;
|
||||
T d = data;
|
||||
for (FunctionTree tree : programTree.topLevelTrees()) {
|
||||
r = tree.accept(this, d);
|
||||
d = accumulate(data, r);
|
||||
}
|
||||
r = this.visitor.visit(programTree, d);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(ReturnTree returnTree, T data) {
|
||||
R r = returnTree.expression().accept(this, data);
|
||||
r = this.visitor.visit(returnTree, accumulate(data, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(TypeTree typeTree, T data) {
|
||||
return this.visitor.visit(typeTree, data);
|
||||
}
|
||||
|
||||
protected T accumulate(T data, R value) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.visitor;
|
||||
|
||||
public enum Unit {
|
||||
INSTANCE
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package edu.kit.kastel.vads.compiler.parser.visitor;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.AssignmentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BinaryOperationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.BlockTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.DeclarationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.IdentExpressionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LValueIdentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LiteralTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NameTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NegateTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ReturnTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.TypeTree;
|
||||
|
||||
public interface Visitor<T, R> {
|
||||
|
||||
R visit(AssignmentTree assignmentTree, T data);
|
||||
|
||||
R visit(BinaryOperationTree binaryOperationTree, T data);
|
||||
|
||||
R visit(BlockTree blockTree, T data);
|
||||
|
||||
R visit(DeclarationTree declarationTree, T data);
|
||||
|
||||
R visit(FunctionTree functionTree, T data);
|
||||
|
||||
R visit(IdentExpressionTree identExpressionTree, T data);
|
||||
|
||||
R visit(LiteralTree literalTree, T data);
|
||||
|
||||
R visit(LValueIdentTree lValueIdentTree, T data);
|
||||
|
||||
R visit(NameTree nameTree, T data);
|
||||
|
||||
R visit(NegateTree negateTree, T data);
|
||||
|
||||
R visit(ProgramTree programTree, T data);
|
||||
|
||||
R visit(ReturnTree returnTree, T data);
|
||||
|
||||
R visit(TypeTree typeTree, T data);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package edu.kit.kastel.vads.compiler.semantic;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NameTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.symbol.Name;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
public class Namespace<T> {
|
||||
|
||||
private final Map<Name, T> content;
|
||||
|
||||
public Namespace() {
|
||||
this.content = new HashMap<>();
|
||||
}
|
||||
|
||||
public void put(NameTree name, T value, BinaryOperator<T> merger) {
|
||||
this.content.merge(name.name(), value, merger);
|
||||
}
|
||||
|
||||
public @Nullable T get(NameTree name) {
|
||||
return this.content.get(name.name());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package edu.kit.kastel.vads.compiler.semantic;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.FunctionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ReturnTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.NoOpVisitor;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Unit;
|
||||
|
||||
/// Checks that functions return.
|
||||
/// Currently only works for straight-line code.
|
||||
class ReturnAnalysis implements NoOpVisitor<ReturnAnalysis.ReturnState> {
|
||||
|
||||
static class ReturnState {
|
||||
boolean returns = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unit visit(ReturnTree returnTree, ReturnState data) {
|
||||
data.returns = true;
|
||||
return NoOpVisitor.super.visit(returnTree, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unit visit(FunctionTree functionTree, ReturnState data) {
|
||||
if (!data.returns) {
|
||||
throw new SemanticException("function " + functionTree.name() + " does not return");
|
||||
}
|
||||
data.returns = false;
|
||||
return NoOpVisitor.super.visit(functionTree, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package edu.kit.kastel.vads.compiler.semantic;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.ProgramTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.RecursivePostorderVisitor;
|
||||
|
||||
public class SemanticAnalysis {
|
||||
|
||||
private final ProgramTree program;
|
||||
|
||||
public SemanticAnalysis(ProgramTree program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
public void analyze() {
|
||||
this.program.accept(new RecursivePostorderVisitor<>(new VariableStatusAnalysis()), new Namespace<>());
|
||||
this.program.accept(new RecursivePostorderVisitor<>(new ReturnAnalysis()), new ReturnAnalysis.ReturnState());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package edu.kit.kastel.vads.compiler.semantic;
|
||||
|
||||
public class SemanticException extends RuntimeException {
|
||||
public SemanticException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package edu.kit.kastel.vads.compiler.semantic;
|
||||
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.AssignmentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.DeclarationTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.IdentExpressionTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.LValueIdentTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.ast.NameTree;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.NoOpVisitor;
|
||||
import edu.kit.kastel.vads.compiler.parser.visitor.Unit;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/// Checks that variables are
|
||||
/// - declared before assignment
|
||||
/// - not declared twice
|
||||
/// - not initialized twice
|
||||
/// - assigned before referenced
|
||||
class VariableStatusAnalysis implements NoOpVisitor<Namespace<VariableStatusAnalysis.VariableStatus>> {
|
||||
|
||||
@Override
|
||||
public Unit visit(AssignmentTree assignmentTree, Namespace<VariableStatus> data) {
|
||||
switch (assignmentTree.lValue()) {
|
||||
case LValueIdentTree(var name) -> {
|
||||
VariableStatus status = data.get(name);
|
||||
checkInitialized(name, status);
|
||||
}
|
||||
}
|
||||
return NoOpVisitor.super.visit(assignmentTree, data);
|
||||
}
|
||||
|
||||
private static void checkInitialized(NameTree name, @Nullable VariableStatus status) {
|
||||
if (status == null) {
|
||||
throw new SemanticException("Variable " + name + " must be declared before assignment");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unit visit(DeclarationTree declarationTree, Namespace<VariableStatus> data) {
|
||||
VariableStatus status = declarationTree.initializer() == null
|
||||
? VariableStatus.DECLARED
|
||||
: VariableStatus.INITIALIZED;
|
||||
data.put(declarationTree.name(), status, (existing, replacement) -> {
|
||||
if (existing.ordinal() >= replacement.ordinal()) {
|
||||
throw new SemanticException("variable is already " + existing + ". Cannot be " + replacement + " here.");
|
||||
}
|
||||
return replacement;
|
||||
});
|
||||
return NoOpVisitor.super.visit(declarationTree, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unit visit(IdentExpressionTree identExpressionTree, Namespace<VariableStatus> data) {
|
||||
VariableStatus status = data.get(identExpressionTree.name());
|
||||
checkInitialized(identExpressionTree.name(), status);
|
||||
return NoOpVisitor.super.visit(identExpressionTree, data);
|
||||
}
|
||||
|
||||
enum VariableStatus {
|
||||
DECLARED,
|
||||
INITIALIZED;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/main/java/module-info.java
Normal file
7
src/main/java/module-info.java
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
module edu.kit.kastel.vads.compiler {
|
||||
requires org.jspecify;
|
||||
requires java.xml;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue