Compare commits

...

39 Commits

Author SHA1 Message Date
bcc9f29ff5 Merge pull request 'feature/char-support' (#3) from feature/char-support into master
Reviewed-on: #3
2023-03-24 17:03:23 +01:00
259ac49981 ContextAnalysis: Make comparing more generous
Previously we only allowed numeric types to be compared using >,<,<= and >=. Now we allow all primitives types to be compared using the operators mentioned above.

It is now also possible to compare and assign ints and chars to each other.
2023-03-24 00:17:38 +01:00
2aff9b3d0d Char: Add support for chars.
First try at implementing chars. There are some problems with the grammar where I was not able to separate the single quotes from the captured char, but I managed to hack it together in the ContextAnalysis.

It's currently not possible to do any 'math' with chars, but I will give it a try soon.
2023-03-23 23:04:09 +01:00
a7e93f4f01 GenASM: Set the bytesToClearFromStack variable earlier
If you set the variable after visiting the function body all children will see bytesToClearFromStack == 0.
2023-03-23 22:52:52 +01:00
5f0e84198a GenASM+Void: Generate an implicit return
Void functions are allowed to be completely free of return statements. The problem is that this does not work on its own in assembler. We have to generate a return statement at the end of the function body if there is no return statement already.
2023-03-23 22:49:36 +01:00
aef2c84fdc Void Type: Fix a problem where calls to void functions were ignored
The ContextAnalysis visitor for braced blocks was not updated during void support implementation which is why I do it now. Although we are re-using the functionCall production rule a different visitor is required for visiting function calls directly inside braced blocks. This is because the other place where functionCalls are used the functionCall is annotated with a label with wraps the functionCall context inside a label context. Fortunately this is in fact simple wrapping so we just implement a visitor for the wrapping class that unwraps the functionCall and passes it to the visitor that actually implements the functionCall.
2023-03-23 21:49:59 +01:00
4219f93021 Merge pull request 'Add support for void types' (#2) from 31-void-type into master
Reviewed-on: #2
2023-03-23 14:28:24 +01:00
7965c89a60 VoidType: Add tests and fix some bugs 2023-03-23 13:10:20 +01:00
26eff47057 Fix typos. 2023-03-23 13:09:52 +01:00
Marvin Kaiser
53976615e1 31: Add void type 2023-03-23 12:47:22 +01:00
c124587983 git: ignore idea designer file 2023-03-23 03:35:11 +01:00
07e5a338a4 GenASM: Fix segfaults when calling malloc
This was a tricky one. In order to call malloc the stack needs to be 16 byte aligned. GenASM up until this point was 8 byte aligned. This commit changes the code generation so that it is guaranteed that the stack is 16 byte aligned before visiting a child node. We need to do this before visiting *any* child node because we can not know whether somewhere downstream some child node calls malloc.

To make it possible for a visitor method to determine if it needs to align the stack it has to assume that the stack is currently aligned. This is the other reason we ensure that the stack is aligned before visiting any child node.

On top of that some visitor methods did not clean up after themselves. I am not sure why this did not result in segfaults already, but I changed the code so that each visitor method leaves the stack in the same state after completion.
2023-03-23 03:21:10 +01:00
76419d86bb GenASM: Remove a few warnings 2023-03-23 02:59:01 +01:00
f55f2661de Tests: Fix failing test because the error message changed. 2023-03-23 00:32:50 +01:00
06609ae899 GenASM: Get number of local variables from the "localVariables" attribute of FunctionDefinition. 2023-03-22 23:40:02 +01:00
c5c01041e4 ContextAnalysis: Track local variables and function parameters separately.
This enables us to add all local variable Definitions to the DAST so that downstream visitors don't need to compute the local variables of a function again.
2023-03-22 23:37:43 +01:00
9751a1da2f Eval: Add the corresponding named type to a struct Value. 2023-03-22 00:14:24 +01:00
534b507f7a Eval: Make a NullExpression evaluate to a Value with Type "NulLValue" instead of returning null. 2023-03-22 00:13:23 +01:00
cce58b6e38 ContextAnalysis: Make naught comparable to struct again. 2023-03-21 22:44:21 +01:00
8b17ced533 Build: Set Java to verison 17 2023-03-21 22:43:40 +01:00
bacc40d844 GenASM: Make sure the stack is 16 byte aligned before calling malloc or free.
The GenASM visitor only uses quad mnemonics so each manipulation to the stack is done in 8 byte chunks. This means our stack is always 8 byte aligned which is fine when only calling KLang code. libc functions like 'malloc' or 'free' require the stack to be 16 byte aligned which isn't automatically guaranteed when being 8 byte aligned. This is why we need to be careful when manipulating the stack (e.g. pushq, popq, subq x, %rsp). By assuming that we are stack aligned at the beginning of execution and by tracking the number of pushq operations we can deduce whether we misaligned the stack and align it again by adding another 8 byte to the stack.

This re-alignment of the stack is done directly before entering a function body or calling malloc. Since there are no stack manipulations involved when generating the code to calling 'free' there is reason to check for alignment there.
2023-03-21 22:21:14 +01:00
f38bd3d69e GenASM: Use leave instead of mov and pop for returning from a function. 2023-03-21 00:22:11 +01:00
441d0122f8 Merge pull request 'feature/add-enum-support' (#1) from feature/add-enum-support into master
Reviewed-on: #1
2023-03-20 21:19:53 +01:00
e835bd0f06 GenASM: Make GenASM quietly rewrite a user's function if it's called main.
We generate our own main function that executes the user's specified expression at the end of his file. This auto generated function has to be called "main" in order for it to be executed on startup. If the user chooses to call one of his own functions "main" our auto generated function would collide with the user's function. That's why we quietly rewrite the user's function to "main_by_user". This way we prevent a collision.

Note for the future: We should change our Syntax to allow for no expression as the last definition of a file. This way the user can choose if a particular source file needs to contain a main function or not (just like c does it). This is also one of the requirements for modules to work.
2023-03-20 21:05:24 +01:00
ea1c04ae0a Build: Add a main manifest attribute to the generated jar.
This makes it possible to directly execute the packaged jar.
2023-03-20 19:55:37 +01:00
198bd74a47 Enums: Make the EnumAccessExpression save a reference to the EnumValue it is referencing.
This can be used during assembler generation to easily find the correct EnumValue for a given EnumAccessExpression.
2023-03-20 19:54:48 +01:00
0594542167 Enums: Make EnumDefinition use EnumValues instead of Strings as children.
This allows us to store the index of the enum value along the name. The index can be used to compare two enum values in assembler.

Later on this might be used to enable users of KLang to set arbitrary values as the index of an enum value.
2023-03-20 19:30:07 +01:00
77fe360ffa Evaluate: Implement evaluation for enums. 2023-03-20 19:10:40 +01:00
55a5b8f54a Make sure that a variable that references an enum has to be initialized. 2023-03-16 00:01:31 +01:00
2768b4429c Check that a struct field name of a struct declaration does not shadow an enum or a struct definition. 2023-03-15 23:48:57 +01:00
30dfbbbbba Check that a variable name of variable declaration does not shadow an enum definition. 2023-03-15 23:33:12 +01:00
f77d6a002d Check that a parameter name of a function definition does not shadow an enum definition. 2023-03-15 23:17:43 +01:00
22634c9652 Use LinkedHashMaps and LinkedHashSets to preserve the order of parameters and struct fields. 2023-03-15 23:08:38 +01:00
6fd3f5a2e6 Make it possible to use an enum in an expression (i.e. selecting one of the enum values: Foo.A) 2023-03-15 19:14:04 +01:00
3b928d621b Refactor FunctionDefinition and Parameter context analysis and extend the type check to include enums. 2023-03-15 17:47:58 +01:00
9a58afb550 Implement StructField type checking in ContextAnalysis. 2023-03-15 17:21:35 +01:00
6e4431652c Remove FunctionInformation and replace it with FunctionDefinition. 2023-03-15 16:19:42 +01:00
7af815042b WIP: Add enum support 2023-03-15 15:56:42 +01:00
7c40a50196 add intellij config files 2023-03-15 05:23:09 +01:00
72 changed files with 2374 additions and 1117 deletions

3
.gitignore vendored
View File

@@ -16,4 +16,5 @@ src/main/antlr4/de/hsrm/compiler/Klang/.antlr
# build output
out
src/test/test
src/test/test
/.idea/uiDesigner.xml

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

16
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="klang" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="klang" target="17" />
</bytecodeTargetLevel>
</component>
</project>

8
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/target/generated-sources/antlr4" charset="UTF-8" />
</component>
</project>

20
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

View File

@@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="Maven: org.antlr:antlr4-runtime:4.7.2">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.2/antlr4-runtime-4.7.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.2/antlr4-runtime-4.7.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.7.2/antlr4-runtime-4.7.2-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="Maven: org.apiguardian:apiguardian-api:1.1.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="Maven: org.junit.jupiter:junit-jupiter-api:5.6.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.0/junit-jupiter-api-5.6.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.0/junit-jupiter-api-5.6.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.0/junit-jupiter-api-5.6.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="Maven: org.junit.jupiter:junit-jupiter-engine:5.6.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.6.0/junit-jupiter-engine-5.6.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.6.0/junit-jupiter-engine-5.6.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.6.0/junit-jupiter-engine-5.6.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="Maven: org.junit.platform:junit-platform-commons:1.6.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.0/junit-platform-commons-1.6.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.0/junit-platform-commons-1.6.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.0/junit-platform-commons-1.6.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="Maven: org.junit.platform:junit-platform-engine:1.6.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.0/junit-platform-engine-1.6.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -0,0 +1,13 @@
<component name="libraryTable">
<library name="Maven: org.opentest4j:opentest4j:1.2.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0-sources.jar!/" />
</SOURCES>
</library>
</component>

28
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ANTLRGenerationPreferences">
<option name="perGrammarGenerationSettings">
<list>
<PerGrammarGenerationSettings>
<option name="fileName" value="$PROJECT_DIR$/src/main/antlr4/de/hsrm/compiler/Klang/Klang.g4" />
<option name="outputDir" value="" />
<option name="libDir" value="" />
<option name="encoding" value="" />
<option name="pkg" value="" />
<option name="language" value="" />
<option name="generateVisitor" value="true" />
</PerGrammarGenerationSettings>
</list>
</option>
</component>
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/klang.iml" filepath="$PROJECT_DIR$/klang.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

22
klang.iml Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_17">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/antlr4" isTestSource="false" generated="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.antlr:antlr4-runtime:4.7.2" level="project" />
<orderEntry type="library" name="Maven: org.junit.jupiter:junit-jupiter-api:5.6.0" level="project" />
<orderEntry type="library" name="Maven: org.apiguardian:apiguardian-api:1.1.0" level="project" />
<orderEntry type="library" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.junit.platform:junit-platform-commons:1.6.0" level="project" />
<orderEntry type="library" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.6.0" level="project" />
<orderEntry type="library" name="Maven: org.junit.platform:junit-platform-engine:1.6.0" level="project" />
</component>
</module>

View File

@@ -40,7 +40,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>11</release>
<release>17</release>
</configuration>
</plugin>
<!-- Plugin to compile the g4 files ahead of the java files
@@ -72,6 +72,11 @@
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>de.hsrm.compiler.Klang.Klang</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>

View File

@@ -5,7 +5,11 @@ parse
;
program
: (functionDef | structDef)* expression SCOL
: (functionDef | structDef | enumDef)* expression SCOL
;
enumDef
: ENUM enumName=IDENT OBRK (IDENT (COMMA IDENT)*)+ CBRK
;
structDef
@@ -29,7 +33,7 @@ parameter
;
braced_block
: OBRK statement+ CBRK
: OBRK (statement | functionCall SCOL)+ CBRK
;
@@ -69,7 +73,7 @@ field_assignment
;
return_statement
: RETURN expression SCOL
: RETURN expression? SCOL
;
destroy_statement
@@ -78,7 +82,7 @@ destroy_statement
expression
: atom #atomExpression
| IDENT (PERIOD IDENT)+ #structFieldAccessExpression
| IDENT (PERIOD IDENT)+ #memberAccessExpression
| OPAR expression CPAR #parenthesisExpression
| lhs=expression MUL rhs=expression #multiplicationExpression
| lhs=expression DIV rhs=expression #divisionExpression
@@ -103,6 +107,7 @@ atom
: INTEGER_LITERAL #intAtom
| BOOLEAN_LITERAL #boolAtom
| FLOAT_LITERAL #floatAtom
| CHAR_LITERAL #charAtom
| NULL # nullAtom
| IDENT #variable
;
@@ -115,7 +120,9 @@ type
: INTEGER
| BOOLEAN
| FLOAT
| CHAR
| IDENT
| VOID
;
functionCall
@@ -143,6 +150,7 @@ forLoop
IF: 'if';
ELSE: 'else';
FUNC: 'function';
ENUM: 'enum';
STRUCT: 'struct';
RETURN: 'return';
LET: 'let';
@@ -178,9 +186,13 @@ SUB: '-';
MOD: '%';
DIV: '/';
SQUOT: '\'';
BOOLEAN: 'bool';
INTEGER: 'int';
FLOAT: 'float';
CHAR: 'char';
VOID: 'void';
INTEGER_LITERAL
: [0-9]+
@@ -195,6 +207,10 @@ BOOLEAN_LITERAL
| 'false'
;
CHAR_LITERAL
: SQUOT [ -~] SQUOT
;
IDENT
: [a-zA-Z][a-zA-Z0-9]*
;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,220 @@
package de.hsrm.compiler.Klang;
import de.hsrm.compiler.Klang.helper.Helper;
import de.hsrm.compiler.Klang.nodes.*;
import de.hsrm.compiler.Klang.types.NamedType;
import de.hsrm.compiler.Klang.types.Type;
import java.util.*;
public class GetDefinitions extends KlangBaseVisitor<Node> {
private final Map<String, FunctionDefinition> functionDefs;
private final Map<String, StructDefinition> structDefs;
private final Map<String, EnumDefinition> enumDefs;
private Set<String> functionNames;
private Set<String> structNames;
private Set<String> enumNames;
public GetDefinitions(
Map<String, FunctionDefinition> functionDefs,
Map<String, StructDefinition> structDefs,
Map<String, EnumDefinition> enumDefs
) {
this.functionDefs = functionDefs;
this.structDefs = structDefs;
this.enumDefs = enumDefs;
}
private Set<String> collectFunctionNames(KlangParser.ProgramContext ctx) {
var result = new HashSet<String>();
for (int i = 0; i < ctx.functionDef().size(); i++) {
var currentFunctionDef = ctx.functionDef(i);
var funcName = currentFunctionDef.funcName.getText();
if (result.contains(funcName)) {
var line = currentFunctionDef.funcName.getLine();
var col = currentFunctionDef.funcName.getCharPositionInLine();
var error = "Function " + funcName + " defined multiple times.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
result.add(funcName);
}
return result;
}
private Set<String> collectStructNames(KlangParser.ProgramContext ctx) {
var result = new HashSet<String>();
for (int i = 0; i < ctx.structDef().size(); i++) {
var currentStructDef = ctx.structDef(i);
var structName = currentStructDef.structName.getText();
if (result.contains(structName)) {
var line = currentStructDef.structName.getLine();
var col = currentStructDef.structName.getCharPositionInLine();
var error = "Struct " + structName + " defined multiple times.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
result.add(structName);
}
return result;
}
private Set<String> collectEnumNames(KlangParser.ProgramContext ctx) {
var result = new HashSet<String>();
for (int i = 0; i < ctx.enumDef().size(); i++) {
var currentEnumDef = ctx.enumDef(i);
var enumName = currentEnumDef.enumName.getText();
if (result.contains(enumName)) {
var line = currentEnumDef.enumName.getLine();
var col = currentEnumDef.enumName.getCharPositionInLine();
var error = "Enum " + enumName + " defined multiple times.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
result.add(enumName);
}
return result;
}
@Override
public Node visitProgram(KlangParser.ProgramContext ctx) {
functionNames = collectFunctionNames(ctx);
structNames = collectStructNames(ctx);
enumNames = collectEnumNames(ctx);
for (int i = 0; i < ctx.functionDef().size(); i++) {
visit(ctx.functionDef(i));
}
for (int i = 0; i < ctx.structDef().size(); i++) {
visit(ctx.structDef(i));
}
for (int i = 0; i < ctx.enumDef().size(); i++) {
visit(ctx.enumDef(i));
}
return null;
}
@Override
public Node visitEnumDef(KlangParser.EnumDefContext ctx) {
// Check that there isn't a function or struct with the same name
var enumName = ctx.enumName.getText();
if (functionNames.contains(enumName) || structNames.contains(enumName)) {
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
var error = "Duplicate use of name " + enumName + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
// IDENT() includes the enumName as the first entry, which we skip
var enumValues = new LinkedHashMap<String, EnumValue>();
for (int i = 1; i < ctx.IDENT().size(); i++) {
var currentEnumField = ctx.IDENT(i);
var currentEnumFieldName = currentEnumField.getText();
var line = currentEnumField.getSymbol().getLine();
var col = currentEnumField.getSymbol().getCharPositionInLine();
if (enumValues.containsKey(currentEnumFieldName)) {
var error = " Duplicate enum value " + currentEnumFieldName + " in enum " + enumName + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var enumValue = new EnumValue(currentEnumFieldName, i - 1);
enumValue.line = line;
enumValue.col = col;
enumValues.put(currentEnumFieldName, enumValue);
}
var enumDef = new EnumDefinition(enumName, enumValues.values().toArray(new EnumValue[0]));
enumDef.line = ctx.start.getLine();
enumDef.col = ctx.start.getCharPositionInLine();
enumDef.type = new NamedType(enumName);
enumDefs.put(enumName, enumDef);
return null;
}
@Override
public Node visitStructDef(KlangParser.StructDefContext ctx) {
var structName = ctx.structName.getText();
var structFieldCount = ctx.structField().size();
var structFields = new LinkedHashMap<String, StructField>();
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
// Check that there isn't a function or enum with the same name
if (functionNames.contains(structName) || enumNames.contains(structName)) {
var error = "Duplicate use of name " + structName + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
for (int i = 0; i < structFieldCount; i++) {
var currentStructField = ctx.structField(i);
var structFieldName = currentStructField.IDENT().getText();
var structFieldLine = currentStructField.start.getLine();
var structFieldCol = currentStructField.start.getCharPositionInLine();
if (structFields.containsKey(structFieldName)) {
var error = "Duplicate struct field " + structFieldName + " in struct " + structName + ".";
throw new RuntimeException(Helper.getErrorPrefix(structFieldLine, structFieldCol) + error);
}
var structField = new StructField(structFieldName);
structField.type = Type.getByName(currentStructField.type_annotation().type().getText());
structField.line = structFieldLine;
structField.col = structFieldCol;
structFields.put(structFieldName, structField);
}
var structDef = new StructDefinition(structName, structFields.values().toArray(new StructField[0]));
structDef.line = line;
structDef.col = col;
structDef.type = new NamedType(structName);
structDefs.put(structName, structDef);
return null;
}
@Override
public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) {
var funcName = ctx.funcName.getText();
var paramCount = ctx.params.parameter().size();
var parameters = new LinkedHashMap<String, Parameter>();
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
// Check that there isn't a struct or enum with the same name
if (structNames.contains(funcName) || enumNames.contains(funcName)) {
var error = "Duplicate use of name " + funcName + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
for (int i = 0; i < paramCount; i++) {
var currentParam = ctx.params.parameter(i);
var paramName = currentParam.IDENT().getText();
var paramLine = currentParam.start.getLine();
var paramCol = currentParam.start.getCharPositionInLine();
if (parameters.containsKey(paramName)) {
var error = "Duplicate parameter name " + paramName + " in function " + funcName + ".";
throw new RuntimeException(Helper.getErrorPrefix(paramLine, paramCol) + error);
}
var parameter = new Parameter(paramName);
parameter.type = Type.getByName(currentParam.type_annotation().type().getText());
parameter.line = paramLine;
parameter.col = paramCol;
parameters.put(paramName, parameter);
}
var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null, null);
functionDef.type = Type.getByName(ctx.returnType.type().getText());
functionDef.line = line;
functionDef.col = col;
functionDefs.put(funcName, functionDef);
return null;
}
}

View File

@@ -1,54 +0,0 @@
package de.hsrm.compiler.Klang;
import java.util.Map;
import java.util.TreeMap;
import de.hsrm.compiler.Klang.types.*;
import de.hsrm.compiler.Klang.helper.*;
public class GetFunctions extends KlangBaseVisitor<Void> {
private Map<String, FunctionInformation> funcs;
public GetFunctions(Map<String, FunctionInformation> funcs) {
this.funcs = funcs;
}
@Override
public Void visitProgram(KlangParser.ProgramContext ctx) {
for (int i = 0; i < ctx.functionDef().size(); i++) {
this.visit(ctx.functionDef(i));
}
return null;
}
@Override
public Void visitFunctionDef(KlangParser.FunctionDefContext ctx) {
String name = ctx.funcName.getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
if (this.funcs.containsKey(name)) {
String error = "Function " + name + " defined multiple times.";
throw new Error(Helper.getErrorPrefix(line, col) + error);
}
Type returnType = Type.getByName(ctx.returnType.type().getText());
TreeMap<String, Type> parameters = new TreeMap<String, Type>();
// Process the paremter list by visiting every paremter in it
int paramCount = ctx.params.parameter().size();
Type[] signature = new Type[paramCount];
for (int i = 0; i < paramCount; i++) {
Type paramType = Type.getByName(ctx.params.parameter(i).type_annotation().type().getText());
String paramName = ctx.params.parameter(i).IDENT().getText();
parameters.put(paramName, paramType);
signature[i] = paramType;
}
FunctionInformation information = new FunctionInformation(name, returnType, parameters, signature);
this.funcs.put(name, information);
return null;
}
}

View File

@@ -1,37 +0,0 @@
package de.hsrm.compiler.Klang;
import java.util.Set;
import de.hsrm.compiler.Klang.helper.Helper;
public class GetStructNames extends KlangBaseVisitor<Void> {
private Set<String> structNames;
public GetStructNames(Set<String> structNames) {
this.structNames = structNames;
}
@Override
public Void visitProgram(KlangParser.ProgramContext ctx) {
for (int i = 0; i < ctx.structDef().size(); i++) {
this.visit(ctx.structDef(i));
}
return null;
}
@Override
public Void visitStructDef(KlangParser.StructDefContext ctx) {
String name = ctx.structName.getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
if (this.structNames.contains(name)) {
String error = "Struct " + name + " defined multiple times.";
throw new Error(Helper.getErrorPrefix(line, col) + error);
}
this.structNames.add(name);
return null;
}
}

View File

@@ -1,70 +0,0 @@
package de.hsrm.compiler.Klang;
import java.util.Map;
import java.util.Set;
import de.hsrm.compiler.Klang.helper.Helper;
import de.hsrm.compiler.Klang.nodes.Node;
import de.hsrm.compiler.Klang.nodes.StructDefinition;
import de.hsrm.compiler.Klang.nodes.StructField;
import de.hsrm.compiler.Klang.types.StructType;
import de.hsrm.compiler.Klang.types.Type;
public class GetStructs extends KlangBaseVisitor<Node> {
private Set<String> structNames;
private Map<String, StructDefinition> structs;
public GetStructs(Set<String> structNames, Map<String, StructDefinition> structs) {
this.structs = structs;
this.structNames = structNames;
}
@Override
public Node visitProgram(KlangParser.ProgramContext ctx) {
for (int i = 0; i < ctx.structDef().size(); i++) {
this.visit(ctx.structDef(i));
}
return null;
}
@Override
public Node visitStructDef(KlangParser.StructDefContext ctx) {
String name = ctx.structName.getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
StructField[] fields = new StructField[ctx.structField().size()];
for (int i = 0; i < ctx.structField().size(); i++) {
StructField field = (StructField) this.visit(ctx.structField(i));
fields[i] = field;
}
StructDefinition result = new StructDefinition(name, fields);
result.line = line;
result.col = col;
result.type = new StructType(name);
this.structs.put(name, result);
return null;
}
@Override
public Node visitStructField(KlangParser.StructFieldContext ctx) {
String name = ctx.IDENT().getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
Type type = Type.getByName(ctx.type_annotation().type().getText());
if (!type.isPrimitiveType() && !this.structNames.contains(type.getName())) {
String error = "Type " + type.getName() + " not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
Node result = new StructField(name);
result.type = type;
result.line = line;
result.col = col;
return result;
}
}

View File

@@ -1,19 +1,24 @@
package de.hsrm.compiler.Klang;
// import ANTLR's runtime libraries
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import de.hsrm.compiler.Klang.helper.*;
import de.hsrm.compiler.Klang.nodes.EnumDefinition;
import de.hsrm.compiler.Klang.nodes.FunctionDefinition;
import de.hsrm.compiler.Klang.nodes.Node;
import de.hsrm.compiler.Klang.nodes.StructDefinition;
import de.hsrm.compiler.Klang.visitors.*;
import de.hsrm.compiler.Klang.helper.*;
import de.hsrm.compiler.Klang.types.Type;
import de.hsrm.compiler.Klang.visitors.EvalVisitor;
import de.hsrm.compiler.Klang.visitors.GenASM;
import de.hsrm.compiler.Klang.visitors.PrettyPrintVisitor;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.FileWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class Klang {
@@ -87,22 +92,15 @@ public class Klang {
// Context Analysis and DAST generation
Node root;
HashMap<String, StructDefinition> structs;
var functionDefs = new HashMap<String, FunctionDefinition>();
var structDefs = new HashMap<String, StructDefinition>();
var enumDefs = new HashMap<String, EnumDefinition>();
try {
// Extract information about all functions
var functionDefinitions = new HashMap<String, FunctionInformation>();
new GetFunctions(functionDefinitions).visit(tree);
// Extract names of all structs
var structNames = new HashSet<String>();
new GetStructNames(structNames).visit(tree);
// Extract information about all structs
structs = new HashMap<String, StructDefinition>();
new GetStructs(structNames, structs).visit(tree);
// Extract information about all definitions
new GetDefinitions(functionDefs, structDefs, enumDefs).visit(tree);
// Create the DAST
ContextAnalysis ctxAnal = new ContextAnalysis(functionDefinitions, structs);
ContextAnalysis ctxAnal = new ContextAnalysis(functionDefs, structDefs, enumDefs);
root = ctxAnal.visit(tree);
} catch (Exception e) {
System.err.println(e.getMessage());
@@ -122,14 +120,18 @@ public class Klang {
if (evaluate) {
// Evaluate the sourcecode and print the result
System.out.println("\nEvaluating the source code:");
EvalVisitor evalVisitor = new EvalVisitor(structs);
EvalVisitor evalVisitor = new EvalVisitor(structDefs);
Value result = root.welcome(evalVisitor);
generateOutput(out, "Result was: " + result.asObject().toString());
if (result.type.equals(Type.getVoidType())) {
generateOutput(out, "Result was void");
} else {
generateOutput(out, "Result was: " + result.asObject().toString());
}
return;
}
// Generate assembler code
GenASM genasm = new GenASM(mainName, structs);
GenASM genasm = new GenASM(mainName, structDefs);
root.welcome(genasm);
generateOutput(out, genasm.toAsm());
}

View File

@@ -4,8 +4,8 @@ import de.hsrm.compiler.Klang.types.Type;
import java.util.Map;
public class Value {
private final Object value;
public Type type;
private Object value;
public Value(Object value) {
this.value = value;
@@ -17,19 +17,23 @@ public class Value {
}
public Object asObject() {
return this.value;
return value;
}
public int asInteger() {
return (int) this.value;
return (int) value;
}
public double asFloat() {
return (double) this.value;
return (double) value;
}
public boolean asBoolean() {
return (boolean) this.value;
return (boolean) value;
}
public char asChar() {
return (char) value;
}
@SuppressWarnings("unchecked")

View File

@@ -172,6 +172,10 @@ public class ASM {
mnemonics.add(new Cqto());
}
public void leave() {
mnemonics.add(new Leave());
}
public void ret() {
mnemonics.add(new Ret());
}

View File

@@ -0,0 +1,8 @@
package de.hsrm.compiler.Klang.asm.mnemonics;
public class Leave extends NoOperandMnemonic {
@Override
public String toAsm() {
return "leave";
}
}

View File

@@ -1,19 +0,0 @@
package de.hsrm.compiler.Klang.helper;
import java.util.Map;
import de.hsrm.compiler.Klang.types.Type;
public class FunctionInformation {
public String name;
public Type returnType;
public Map<String, Type> parameters;
public Type[] signature;
public FunctionInformation(String name, Type returnType, Map<String,Type> parameters, Type[] signature) {
this.name = name;
this.returnType = returnType;
this.parameters = parameters;
this.signature = signature;
}
}

View File

@@ -1,14 +1,13 @@
package de.hsrm.compiler.Klang.nodes;
import de.hsrm.compiler.Klang.nodes.statements.Statement;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class Block extends Node {
public Statement[] statements;
public Node[] statementsOrFunctionCalls;
public Block(Statement[] statements) {
this.statements = statements;
public Block(Node[] statements) {
this.statementsOrFunctionCalls = statements;
}
@Override

View File

@@ -0,0 +1,19 @@
package de.hsrm.compiler.Klang.nodes;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class EnumDefinition extends Node {
public String name;
public EnumValue[] enums;
public EnumDefinition(String name, EnumValue[] enums) {
this.name = name;
this.enums = enums;
}
@Override
public <R> R welcome(Visitor<R> v) {
return v.visit(this);
}
}

View File

@@ -0,0 +1,18 @@
package de.hsrm.compiler.Klang.nodes;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class EnumValue extends Node {
public String value;
public int index;
public EnumValue(String value, int index) {
this.value = value;
this.index = index;
}
@Override
public <R> R welcome(Visitor<R> v) {
return v.visit(this);
}
}

View File

@@ -1,17 +1,20 @@
package de.hsrm.compiler.Klang.nodes;
import de.hsrm.compiler.Klang.nodes.statements.VariableDeclaration;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class FunctionDefinition extends Node {
public String name;
public Parameter[] parameters;
public VariableDeclaration[] localVariables;
public Block block;
public FunctionDefinition(String name, Parameter[] parameters, Block block) {
public FunctionDefinition(String name, Parameter[] parameters, VariableDeclaration[] localVariables, Block block) {
this.name = name;
this.parameters = parameters;
this.block = block;
this.localVariables = localVariables;
}
@Override

View File

@@ -9,11 +9,18 @@ public class Program extends Node {
public FunctionDefinition[] funcs;
public Map<String, StructDefinition> structs;
public Map<String, EnumDefinition> enums;
public Expression expression;
public Program(FunctionDefinition[] funcs, Map<String, StructDefinition> structs, Expression expression) {
public Program(
FunctionDefinition[] funcs,
Map<String, StructDefinition> structs,
Map<String, EnumDefinition> enums,
Expression expression
) {
this.funcs = funcs;
this.structs = structs;
this.enums = enums;
this.expression = expression;
}

View File

@@ -0,0 +1,16 @@
package de.hsrm.compiler.Klang.nodes.expressions;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class CharExpression extends Expression {
public char c;
public CharExpression(char c) {
this.c = c;
}
@Override
public <R> R welcome(Visitor<R> v) {
return v.visit(this);
}
}

View File

@@ -0,0 +1,26 @@
package de.hsrm.compiler.Klang.nodes.expressions;
import de.hsrm.compiler.Klang.nodes.EnumDefinition;
import de.hsrm.compiler.Klang.nodes.EnumValue;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class EnumAccessExpression extends Expression {
public String enumName;
public String enumValueName;
public EnumValue enumValue;
public EnumAccessExpression(
String enumName,
String enumValueName,
EnumValue enumValue
) {
this.enumName = enumName;
this.enumValueName = enumValueName;
this.enumValue = enumValue;
}
@Override
public <R> R welcome(Visitor<R> v) {
return v.visit(this);
}
}

View File

@@ -2,12 +2,12 @@ package de.hsrm.compiler.Klang.nodes.expressions;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class StructFieldAccessExpression extends Expression {
public class MemberAccessExpression extends Expression {
public String varName;
public String structName;
public String[] path;
public StructFieldAccessExpression(String varName, String structName, String[] path) {
public MemberAccessExpression(String varName, String structName, String[] path) {
this.varName = varName;
this.structName = structName;
this.path = path;

View File

@@ -11,6 +11,10 @@ public class ReturnStatement extends Statement {
this.expression = expression;
}
public ReturnStatement() {
this.expression = null;
}
@Override
public <R> R welcome(Visitor<R> v) {
return v.visit(this);

View File

@@ -1,5 +1,7 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
public class BooleanType extends PrimitiveType {
private static BooleanType instance = null;
@@ -30,7 +32,11 @@ public class BooleanType extends PrimitiveType {
}
// Every remaining type will throw a RuntimeException
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override
public boolean valuesEqual(Value a, Value b) {
return a.asBoolean() == b.asBoolean();
}
}

View File

@@ -0,0 +1,47 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
public class CharType extends PrimitiveType {
private static CharType instance = null;
public static CharType getType() {
if (instance != null) {
return instance;
}
instance = new CharType();
return instance;
}
@Override
public String getName() {
return "char";
}
@Override
public Type combine(Type that) {
if (this.equals(that)) {
return this;
}
if (that.equals(Type.getIntegerType())) {
return this;
}
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName());
}
@Override
public boolean valuesEqual(Value a, Value b) {
return ((int) a.asChar()) == ((int) b.asChar());
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
return that instanceof CharType;
}
}

View File

@@ -1,5 +1,7 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
public class FloatType extends NumericType {
private static FloatType instance = null;
@@ -34,7 +36,11 @@ public class FloatType extends NumericType {
}
// Every remaining type will throw a RuntimeException
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override
public boolean valuesEqual(Value a, Value b) {
return a.asFloat() == b.asFloat();
}
}

View File

@@ -1,5 +1,7 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
public class IntegerType extends NumericType {
private static IntegerType instance = null;
@@ -33,8 +35,17 @@ public class IntegerType extends NumericType {
return Type.getFloatType();
}
if (that.equals(Type.getCharType())) {
return Type.getCharType();
}
// Every remaining type will throw a RuntimeException
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override
public boolean valuesEqual(Value a, Value b) {
return a.asInteger() == b.asInteger();
}
}

View File

@@ -0,0 +1,53 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
public class NamedType extends Type {
public String name;
public NamedType(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public Type combine(Type that) {
if(this.equals(that) || (that instanceof NullType)) {
return this;
}
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName());
}
@Override
public boolean valuesEqual(Value a, Value b) {
return a.asObject().equals(b.asObject());
}
@Override
public boolean isPrimitiveType() {
return false;
}
@Override
public boolean isNumericType() {
return false;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that instanceof NamedType thatType) {
return getName().equals(thatType.getName());
}
return false;
}
}

View File

@@ -1,5 +1,7 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
public class NullType extends Type {
private static NullType instance = null;
@@ -21,13 +23,18 @@ public class NullType extends Type {
public Type combine(Type that) {
// You can not combine null with a primitive type
if (that.isPrimitiveType()) {
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
// Everything else combines with null to the type it was before
return that;
}
@Override
public boolean valuesEqual(Value a, Value b) {
return a.asObject() == b.asObject();
}
@Override
public boolean isPrimitiveType() {
return false;

View File

@@ -1,54 +0,0 @@
package de.hsrm.compiler.Klang.types;
public class StructType extends Type {
public String name;
public StructType(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public Type combine(Type that) {
if (that.equals(this)) {
return this;
}
// If you combine a null type with a struct type, you
// always get the struct type back.
if (that == NullType.getType()) {
return this;
}
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override
public boolean isPrimitiveType() {
return false;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that instanceof StructType) {
StructType thatType = (StructType) that;
return this.getName().equals(thatType.getName());
}
return false;
}
@Override
public boolean isNumericType() {
return false;
}
}

View File

@@ -1,5 +1,12 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
import de.hsrm.compiler.Klang.nodes.EnumDefinition;
import de.hsrm.compiler.Klang.nodes.FunctionDefinition;
import de.hsrm.compiler.Klang.nodes.StructDefinition;
import java.util.Map;
public abstract class Type {
// Returns an instance of IntegerType
@@ -16,22 +23,33 @@ public abstract class Type {
return FloatType.getType();
}
public static CharType getCharType() {
return CharType.getType();
}
public static NullType getNullType() {
return NullType.getType();
}
public static VoidType getVoidType() {
return VoidType.getType();
}
public static Type getByName(String name) {
switch (name) {
case "bool": return getBooleanType();
case "int": return getIntegerType();
case "float": return getFloatType();
case "char": return getCharType();
case "null": return getNullType();
default: return new StructType(name);
case "void": return getVoidType();
default: return new NamedType(name);
}
}
public abstract String getName();
public abstract Type combine(Type that);
public abstract boolean valuesEqual(Value a, Value b);
public abstract boolean isPrimitiveType();
public abstract boolean isNumericType();
}
}

View File

@@ -0,0 +1,45 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
public class VoidType extends Type {
private static VoidType instance;
public static VoidType getType() {
if (instance != null) {
return instance;
}
instance = new VoidType();
return instance;
}
@Override
public String getName() {
return "void";
}
@Override
public Type combine(Type that) {
if (that.equals(this)) {
return this;
}
throw new RuntimeException("Type mismatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override
public boolean valuesEqual(Value a, Value b) {
throw new RuntimeException("Can not compare void types.");
}
@Override
public boolean isPrimitiveType() {
return false;
}
@Override
public boolean isNumericType() {
return false;
}
}

View File

@@ -4,17 +4,13 @@ import java.util.HashMap;
import java.util.Map;
import de.hsrm.compiler.Klang.Value;
import de.hsrm.compiler.Klang.nodes.Block;
import de.hsrm.compiler.Klang.nodes.FunctionDefinition;
import de.hsrm.compiler.Klang.nodes.Parameter;
import de.hsrm.compiler.Klang.nodes.Program;
import de.hsrm.compiler.Klang.nodes.StructDefinition;
import de.hsrm.compiler.Klang.nodes.StructField;
import de.hsrm.compiler.Klang.nodes.*;
import de.hsrm.compiler.Klang.nodes.expressions.*;
import de.hsrm.compiler.Klang.nodes.loops.DoWhileLoop;
import de.hsrm.compiler.Klang.nodes.loops.ForLoop;
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
import de.hsrm.compiler.Klang.nodes.statements.*;
import de.hsrm.compiler.Klang.types.NullType;
import de.hsrm.compiler.Klang.types.Type;
public class EvalVisitor implements Visitor<Value> {
@@ -50,26 +46,17 @@ public class EvalVisitor implements Visitor<Value> {
}
@Override
public Value visit(EqualityExpression e) {
Value lhs = e.lhs.welcome(this);
Value rhs = e.rhs.welcome(this);
Type resultType = Type.getBooleanType();
Type combineType = lhs.type.combine(rhs.type);
public Value visit(CharExpression e) {
return new Value(e.c, Type.getCharType());
}
switch(combineType.getName()) {
case "bool": {
return new Value(lhs.asBoolean() == rhs.asBoolean(), resultType);
}
case "int": {
return new Value(lhs.asInteger() == rhs.asInteger(), resultType);
}
case "float": {
return new Value(lhs.asFloat() == rhs.asFloat(), resultType);
}
default: {
return new Value(lhs.asObject() == rhs.asObject(), resultType);
}
}
@Override
public Value visit(EqualityExpression e) {
var lhs = e.lhs.welcome(this);
var rhs = e.rhs.welcome(this);
var combinedType = lhs.type.combine(rhs.type);
return new Value(combinedType.valuesEqual(lhs, rhs), Type.getBooleanType());
}
@Override
@@ -413,13 +400,16 @@ public class EvalVisitor implements Visitor<Value> {
@Override
public Value visit(ReturnStatement e) {
if (e.expression == null) {
return new Value(null, Type.getVoidType());
}
return e.expression.welcome(this);
}
@Override
public Value visit(Block e) {
for (var stmt : e.statements) {
Value result = stmt.welcome(this);
for (var stmt : e.statementsOrFunctionCalls) {
var result = stmt.welcome(this);
if (result != null) {
return result;
}
@@ -447,7 +437,7 @@ public class EvalVisitor implements Visitor<Value> {
this.env = newEnv;
// Execute
Value result = func.block.welcome(this);
var result = func.block.welcome(this);
// Das alte env wiederherstellen
this.env = oldEnv;
@@ -471,6 +461,16 @@ public class EvalVisitor implements Visitor<Value> {
return null;
}
@Override
public Value visit(EnumDefinition e) {
return null;
}
@Override
public Value visit(EnumValue e) {
return null;
}
@Override
public Value visit(StructDefinition e) {
// We get these from a previous visitor
@@ -484,7 +484,7 @@ public class EvalVisitor implements Visitor<Value> {
}
@Override
public Value visit(StructFieldAccessExpression e) {
public Value visit(MemberAccessExpression e) {
Value var = this.env.get(e.varName);
Map<String, Value> struct = var.asStruct();
@@ -496,6 +496,11 @@ public class EvalVisitor implements Visitor<Value> {
return currentValue;
}
@Override
public Value visit(EnumAccessExpression e) {
return new Value(e.enumValueName, e.type);
}
@Override
public Value visit(ConstructorCall e) {
StructDefinition structDef = this.structs.get(e.structName);
@@ -506,12 +511,17 @@ public class EvalVisitor implements Visitor<Value> {
struct.put(structDef.fields[i].name, arg);
}
return new Value(struct);
var structValue = new Value(struct);
structValue.type = structDef.type;
return structValue;
}
@Override
public Value visit(NullExpression e) {
return null;
var nullValue = new Value(null);
nullValue.type = new NullType();
return nullValue;
}
@Override

View File

@@ -1,11 +1,5 @@
package de.hsrm.compiler.Klang.visitors;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import de.hsrm.compiler.Klang.asm.ASM;
import de.hsrm.compiler.Klang.helper.Helper;
import de.hsrm.compiler.Klang.nodes.*;
@@ -16,17 +10,19 @@ import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
import de.hsrm.compiler.Klang.nodes.statements.*;
import de.hsrm.compiler.Klang.types.Type;
import java.util.*;
public class GenASM implements Visitor<Void> {
private class FloatWriter {
private StringBuilder sb = new StringBuilder();
private static class FloatWriter {
private final StringBuilder sb = new StringBuilder();
private int id = -1;
public String getFloat(double d) {
Long longBits = Double.doubleToRawLongBits(d);
String binary = Long.toBinaryString(longBits);
long longBits = Double.doubleToRawLongBits(d);
StringBuilder binary = new StringBuilder(Long.toBinaryString(longBits));
int padCount = 64 - binary.length();
while (padCount > 0) {
binary = "0" + binary;
binary.insert(0, "0");
padCount--;
}
String upper = binary.substring(0, 32);
@@ -63,49 +59,19 @@ public class GenASM implements Visitor<Void> {
}
}
private ASM asm;
private FloatWriter fw = new FloatWriter();
private String mainName;
private final ASM asm;
private final FloatWriter fw = new FloatWriter();
private final String mainName;
Map<String, Integer> env = new HashMap<>();
Map<String, StructDefinition> structs;
Set<String> vars;
String[] registers = { "%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9" };
String[] floatRegisters = { "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" };
private int lCount = 0; // Invariant: lCount is used
private int currentFunctionStartLabel = 0;
private Parameter[] currentFunctionParams;
private boolean prepareRegisters(Expression lhs, Expression rhs) {
boolean lhsIsFloat = lhs.type.equals(Type.getFloatType());
boolean rhsIsFloat = rhs.type.equals(Type.getFloatType());
if (lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm2", "%xmm0");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else if (lhsIsFloat && !rhsIsFloat) {
lhs.welcome(this);
rhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm1");
return true;
} else if (!lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm1");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else {
lhs.welcome(this);
asm.push("q", "%rax");
rhs.welcome(this);
asm.mov("q", "%rax", "%rbx");
asm.pop("q", "%rax");
return false;
}
}
private int lCount = 0;
private int currentFunctionStartLabel = 0;
private long bytesToClearFromTheStack = 0;
private FunctionDefinition currentFunctionDef;
public GenASM(String mainName, Map<String, StructDefinition> structs) {
this.mainName = mainName;
@@ -113,10 +79,6 @@ public class GenASM implements Visitor<Void> {
this.asm = new ASM();
}
public GenASM(Map<String, StructDefinition> structs) {
this("main", structs);
}
public String toAsm() {
return asm.toAsm();
}
@@ -140,6 +102,12 @@ public class GenASM implements Visitor<Void> {
return null;
}
@Override
public Void visit(CharExpression e) {
asm.mov("q", "$" + ((int) e.c), "%rax");
return null;
}
@Override
public Void visit(Variable e) {
if (e.type.equals(Type.getFloatType())) {
@@ -330,10 +298,19 @@ public class GenASM implements Visitor<Void> {
@Override
public Void visit(ModuloExpression e) {
e.lhs.welcome(this);
// A single pushq brings the stack out of 16 byte alignment,
// so we need to increase the stack by another 8 bytes
asm.sub("q", "$8", "%rsp");
asm.push("q", "%rax");
e.rhs.welcome(this);
asm.mov("q", "%rax", "%rbx");
asm.pop("q", "%rax");
// Which we remove afterwards
asm.add("q", "$8", "%rsp");
asm.cqto();
asm.idiv("q", "%rbx");
asm.mov("q", "%rdx", "%rax");
@@ -361,7 +338,7 @@ public class GenASM implements Visitor<Void> {
// Werte LHS aus
// Wenn LHS != 0 bedeutet das true
// also können wir direkt sagen dass das Ergebnis true ist
// also können wir direkt sagen, dass das Ergebnis true ist
e.lhs.welcome(this);
asm.cmp("q", 0, "%rax");
asm.jne(lblTrue);
@@ -379,11 +356,11 @@ public class GenASM implements Visitor<Void> {
asm.mov("q", 1, "%rax");
asm.jmp(lblEnd);
// Die Expressoin wertet zu false aus
// Die Expression wertet zu false aus
asm.label(lblFalse);
asm.mov("q", 0, "%rax");
// Das hier ist das ende
// Das hier ist das Ende
asm.label(lblEnd);
return null;
}
@@ -396,7 +373,7 @@ public class GenASM implements Visitor<Void> {
// Werte LHS aus
// Wenn LHS == 0, bedeutet das false
// also können wir direkt sagen dass das Ergebnis false ist
// also können wir direkt sagen, dass das Ergebnis false ist
e.lhs.welcome(this);
asm.cmp("q", 0, "%rax");
asm.je(lblFalse);
@@ -414,11 +391,11 @@ public class GenASM implements Visitor<Void> {
asm.mov("q", 1, "%rax");
asm.jmp(lblEnd);
// Die Expressoin wertet zu false aus
// Die Expression wertet zu false aus
asm.label(lblFalse);
asm.mov("q", 0, "%rax");
// Das hier ist das ende
// Das hier ist das Ende
asm.label(lblEnd);
return null;
}
@@ -428,9 +405,9 @@ public class GenASM implements Visitor<Void> {
int lblFalse = ++lCount;
int lblEnd = ++lCount;
// Werte LHS aus
// Wenn LHS != 0 bedeutet das true, also jumpe zum false Teil
// Wenn nicht, falle durch zum true Teil
// Werte LHS aus.
// Wenn LHS != 0 bedeutet das true, also springe zum false Teil.
// Wenn nicht, falle durch zum true Teil.
e.lhs.welcome(this);
asm.cmp("q", 0, "%rax");
asm.jne(lblFalse);
@@ -554,49 +531,76 @@ public class GenASM implements Visitor<Void> {
@Override
public Void visit(ReturnStatement e) {
e.expression.welcome(this);
asm.mov("q", "%rbp", "%rsp");
asm.pop("q", "%rbp");
if (e.expression != null) {
e.expression.welcome(this);
}
// The ReturnStatement visitor is kindly removing the
// stack space that was allocated by the FunctionDefinition
// before executing the function body.
if (bytesToClearFromTheStack > 0) {
asm.add("q", "$" + bytesToClearFromTheStack, "%rsp");
}
asm.leave();
asm.ret();
return null;
}
@Override
public Void visit(Block e) {
for (var statement : e.statements) {
statement.welcome(this);
for (var statementOrFunctionCall : e.statementsOrFunctionCalls) {
statementOrFunctionCall.welcome(this);
}
// It's possible (and allowed -> void functions) that there is no return statement
// in the outermost block of a function body. In this case, no
// direct descendant of Block will be a return statement.
// This means we have to generate an implicit return, otherwise
// the code would fall through to the next function below :D
// We also have to clean up the stack which is the ReturnStatement's job.
// Check if we have to generate an implicit return
var lastStatementOrFunctionCall = e.statementsOrFunctionCalls[e.statementsOrFunctionCalls.length - 1];
if (currentFunctionDef.block == e && !(lastStatementOrFunctionCall instanceof ReturnStatement)) {
visit(new ReturnStatement());
}
return null;
}
@Override
public Void visit(FunctionDefinition e) {
int lblStart = ++lCount;
this.currentFunctionStartLabel = lblStart;
this.currentFunctionParams = e.parameters;
// If the user chooses "main" as one of his function names then
// rename it and hope that they didn't use the renamed function name
// as well :D
if (e.name.equals("main")) {
e.name = "main_by_user";
}
// Remember the current function definition so everyone below us knows where they belong to.
currentFunctionDef = e;
var lblStart = ++lCount;
currentFunctionStartLabel = lblStart;
asm.functionHead(e.name);
asm.push("q", "%rbp");
asm.mov("q", "%rsp", "%rbp");
asm.label(lblStart);
// hole die anzahl der lokalen variablen
this.vars = new TreeSet<String>();
HashMap<String, Type> types = new HashMap<String, Type>();
GetVars getvars = new GetVars(vars, types);
getvars.visit(e);
// Create a new environment
env = new HashMap<>();
// Erzeuge ein environment
this.env = new HashMap<String, Integer>();
// Remember the offsets of the arguments that were placed on the stack.
var offset = 16; // Variables that were passed via the stack are located above our BSP
var ri = 0;
var fi = 0;
// Merke dir die offsets der parameter, die direkt auf den stack gelegt wurden
int offset = 16; // Per Stack übergebene Parameter liegen über unserem BSP
int ri = 0;
int fi = 0;
// Per stack übergebene variablen in env registrieren
// Add arguments that were passed via the stack to the environment
var registerParameters = new ArrayList<Parameter>();
for (int i = 0; i < e.parameters.length; i++) {
if (e.parameters[i].type.equals(Type.getFloatType())) {
if (fi >= this.floatRegisters.length) {
if (fi >= floatRegisters.length) {
// parameter is on stack already
env.put(e.parameters[i].name, offset);
offset += 8;
@@ -606,7 +610,7 @@ public class GenASM implements Visitor<Void> {
fi++;
}
} else {
if (ri >= this.registers.length) {
if (ri >= registers.length) {
// parameter is on stack already
env.put(e.parameters[i].name, offset);
offset += 8;
@@ -621,29 +625,58 @@ public class GenASM implements Visitor<Void> {
offset = 0;
ri = 0;
fi = 0;
// Push arguments that were passed via registers onto the stack
// and add them to the environment.
for (var param: registerParameters) {
if (param.type.equals(Type.getFloatType())) {
asm.mov("q", this.floatRegisters[fi], "%rax");
asm.mov("q", floatRegisters[fi], "%rax");
asm.push("q", "%rax");
offset -= 8;
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
env.put(param.name, offset); // negative, liegt unter aktuellem BP
fi++;
} else {
asm.push("q", this.registers[ri]);
asm.push("q", registers[ri]);
offset -= 8;
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
env.put(param.name, offset); // negative, liegt unter aktuellem BP
ri++;
}
}
// Reserviere Platz auf dem Stack für jede lokale variable
for (String lok_var : vars) {
offset -= 8;
asm.push("q", 0);
this.env.put(lok_var, offset);
// Reserve memory on the stack for the local variables.
if (e.localVariables.length > 0) {
// Each variable is at most 8 bytes in size.
asm.sub("q", "$" + (8 * e.localVariables.length), "%rsp");
// Save the offsets (they are relative to rbp)
for (var localVariable : e.localVariables) {
offset -= 8;
env.put(localVariable.name, offset);
}
}
// Check the stack alignment and correct if necessary
// The padding needs to happen after the register parameters
// and local variables were pushed to the stack because otherwise
// the relative offsets to rbp will be wrong.
if ((registerParameters.size() + e.localVariables.length) % 2 != 0) {
// Since each variable is 8 bytes and the stack is 16 byte aligned
// we need to add 8 bytes to the stack to make it aligned
// if there is an odd number of parameters and local variables.
asm.sub("q", "$8", "%rsp");
}
// I need to clear the stack here so that the top most element is the old rbp that
// ret uses to return to the caller but code that gets generated after visiting the
// function body will never be reached since the block node is guaranteed to
// contain a return node that results in a ret command being executed before this
// code would be reached. As a workaround (and a dirty, dirty hack) I indicate
// to the return node visitor how many bytes need to be cleared from the stack.
var wasStackPadded = (registerParameters.size() + e.localVariables.length) % 2 != 0;
bytesToClearFromTheStack = 8L * (registerParameters.size() + e.localVariables.length + (wasStackPadded ? 1 : 0));
e.block.welcome(this);
return null;
}
@@ -662,15 +695,18 @@ public class GenASM implements Visitor<Void> {
asm.push("q", "%rax");
}
// push args into local var locations, last arg is ontop of the stack
// push args into local var locations, last arg is on top of the stack
for (int i = e.arguments.length - 1; i >= 0; i--) {
asm.pop("q", this.env.get(this.currentFunctionParams[i].name) + "(%rbp)");
asm.pop("q", env.get(currentFunctionDef.parameters[i].name) + "(%rbp)");
}
asm.jmp(this.currentFunctionStartLabel);
asm.jmp(currentFunctionStartLabel);
return null;
}
// This holds the indices into the arguments array for
// arguments that need to be passed to the callee via the stack
var stackIndices = new ArrayList<Integer>();
if (e.arguments.length > 0) {
// Mapping arguments index -> xmm registers index
@@ -681,9 +717,6 @@ public class GenASM implements Visitor<Void> {
int[] rIdxs = new int[this.registers.length];
int ri = -1;
// Mapping arguments index -> stack
ArrayList<Integer> stackIdxs = new ArrayList<Integer>();
// Go through arguments
// sort them into the memory regions they go when being passed to functions
for (int i = 0; i < e.arguments.length; i++) {
@@ -695,7 +728,7 @@ public class GenASM implements Visitor<Void> {
xmmIdxs[fi] = i;
} else {
// Float onto stack
stackIdxs.add(i);
stackIndices.add(i);
}
} else {
if (ri < this.registers.length - 1) {
@@ -704,14 +737,39 @@ public class GenASM implements Visitor<Void> {
rIdxs[ri] = i;
} else {
// bool/int onto stack
stackIdxs.add(i);
stackIndices.add(i);
}
}
}
// Welcome the arguments in order, push everything onto the stack
for (var arg : e.arguments) {
// Make sure that the stack is aligned after all arguments
// have been pushed onto the stack. Keep in mind that we
// may remove the top n elements from our stack so they form
// the base of the callee's stack.
if ((e.arguments.length - stackIndices.size()) % 2 != 0) {
asm.sub("q", "$8", "%rsp");
}
// Welcome the arguments in order, push everything onto the stack.
// We can assume that we are currently 16 byte aligned which holds
// true until after the result of the first argument is pushed onto
// the stack. After that we have to pad the stack for every argument
// to ensure that the stack is aligned before welcoming the argument.
// We get rid of the padding directly after welcome() to
// "tightly pack" the arguments on the stack. This way the usual
// 8 byte offsets are kept intact.
for (int i = 0; i < e.arguments.length; i++) {
if (i % 2 == 0) {
asm.sub("q", "$8", "%rsp");
}
var arg = e.arguments[i];
arg.welcome(this);
if (i % 2 == 0) {
asm.add("q", "$8", "%rsp");
}
if (arg.type.equals(Type.getFloatType())) {
asm.mov("q", "%xmm0", "%rax");
asm.push("q", "%rax");
@@ -734,21 +792,38 @@ public class GenASM implements Visitor<Void> {
asm.mov("q", rspOffset, "%rsp", this.registers[i]);
}
// Move everything else from a higher stack position to our stack frame start
// Reorder the remaining n arguments that did not fit in a register,
// so that the remaining n arguments occupy the last n spots on our current stack
// This does not manipulate the stack in any way. It just reorders it.
int stackStartOffset = ((e.arguments.length) * 8);
for (int i = stackIdxs.size() - 1; i >= 0; i--) {
for (int i = stackIndices.size() - 1; i >= 0; i--) {
stackStartOffset -= 8;
int indexInArguments = stackIdxs.get(i);
int indexInArguments = stackIndices.get(i);
int rspOffset = (((e.arguments.length - indexInArguments) - 1) * 8);
asm.mov("q", rspOffset, "%rsp", "%rax");
asm.mov("q", "%rax", stackStartOffset, "%rsp");
}
// Rescue RSP
asm.add("q", stackStartOffset, "%rsp");
// The top n elements of the stack prepared in the step earlier will now become
// the base of the callee's stack by shrinking the stack by the size of the
// n arguments that did not fit in registers.
// This must only be done if there were any arguments that did not fit in the registers.
if (!stackIndices.isEmpty()) {
asm.add("q", stackStartOffset, "%rsp");
}
}
// We rename a function name if it is "main"
var mainName = e.name.equals("main") ? "main_by_user": e.name;
asm.call(mainName);
// Remove the arguments that remained on the stack and the stack padding if there was any
var wasStackPadded = (e.arguments.length - stackIndices.size()) % 2 != 0;
var bytesToRemove = 8 * (e.arguments.length - stackIndices.size() + (wasStackPadded ? 1 : 0));
if (bytesToRemove > 0) {
asm.add("q", "$" + bytesToRemove, "%rsp");
}
asm.call(e.name);
return null;
}
@@ -773,11 +848,21 @@ public class GenASM implements Visitor<Void> {
@Override
public Void visit(Parameter e) {
// The work for a paremeter node is implement
// The work for a parameter node is implement
// in the function definition visitor
return null;
}
@Override
public Void visit(EnumDefinition e) {
return null;
}
@Override
public Void visit(EnumValue e) {
return null;
}
@Override
public Void visit(StructDefinition e) {
// We get these from a previous visitor
@@ -791,7 +876,7 @@ public class GenASM implements Visitor<Void> {
}
@Override
public Void visit(StructFieldAccessExpression e) {
public Void visit(MemberAccessExpression e) {
var structDef = this.structs.get(e.structName);
int offset = this.env.get(e.varName);
@@ -816,12 +901,39 @@ public class GenASM implements Visitor<Void> {
return null;
}
@Override
public Void visit(EnumAccessExpression e) {
// Since the access to an enum simply results in an integer (i.e. the index
// of the enum value), we can just let IntegerExpression handle the expected behaviour.
new IntegerExpression(e.enumValue.index).welcome(this);
return null;
}
@Override
public Void visit(ConstructorCall e) {
// Make sure the stack is aligned before calling malloc
if (e.args.length % 2 != 0) {
// an odd number of arguments means we called pushq an odd number of times
// which results in an unaligned stack. Subtract 8 from the stack pointer to
// re-align the stack
asm.sub("q", "$8", "%rsp");
}
// push arguments onto the stack
for (var arg: e.args) {
for (int i = 0; i < e.args.length; i++) {
// if you want to know why this is done go and read visit(FunctionCall)
if (i % 2 != 0) {
asm.sub("q", "$8", "%rsp");
}
var arg = e.args[i];
arg.welcome(this);
if (i % 2 != 0) {
asm.add("q", "$8", "%rsp");
}
// move float values from xmm0 to rax first
if (arg.type.equals(Type.getFloatType())) {
asm.mov("q", "%xmm0", "%rax");
@@ -835,11 +947,16 @@ public class GenASM implements Visitor<Void> {
asm.mov("l", Helper.getFieldSizeBytes(structDef), "%edi");
asm.call("malloc@PLT");
// push args into struct memory, last arg is ontop of the stack
// push args into struct memory, last arg is on top of the stack
for (int i = e.args.length - 1; i >= 0; i--) {
asm.pop("q", Helper.getFieldOffset(structDef, i) + "(%rax)");
}
// Get rid of the stack alignment if there was any
if (e.args.length % 2 != 0) {
asm.add("q", "$8", "%rsp");
}
return null;
}
@@ -864,7 +981,7 @@ public class GenASM implements Visitor<Void> {
e.expression.welcome(this);
// Move it from xmm0 rax if its a flaot
// Move it from xmm0 rax if it's a float
if (e.expression.type.equals(Type.getFloatType())) {
asm.mov("q", "%xmm0", "%rax");
}
@@ -882,11 +999,49 @@ public class GenASM implements Visitor<Void> {
asm.mov("q", Helper.getFieldOffset(structDef, e.path[i]), "%rax", "%rax");
}
// pop the expression that is ontop of the stack into the field of the struct that has to be updated
// pop the expression that is on top of the stack into the field of the struct that has to be updated
asm.pop("q", Helper.getFieldOffset(structDef, fieldNameToUpdate) + "(%rax)");
asm.mov("q", 0 , "%rax"); // clear rax since an assignment has no result
return null;
}
}
private boolean prepareRegisters(Expression lhs, Expression rhs) {
boolean lhsIsFloat = lhs.type.equals(Type.getFloatType());
boolean rhsIsFloat = rhs.type.equals(Type.getFloatType());
if (lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm2", "%xmm0");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else if (lhsIsFloat) {
lhs.welcome(this);
rhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm1");
return true;
} else if (rhsIsFloat) {
lhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm1");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else {
lhs.welcome(this);
// A single pushq brings the stack out of 16 byte alignment,
// so we need to increase the stack by another 8 bytes
asm.sub("q", "$8", "%rsp");
asm.push("q", "%rax");
rhs.welcome(this);
asm.mov("q", "%rax", "%rbx");
asm.pop("q", "%rax");
// Which we remove afterwards
asm.add("q", "$8", "%rsp");
return false;
}
}
}

View File

@@ -1,271 +0,0 @@
package de.hsrm.compiler.Klang.visitors;
import java.util.Map;
import java.util.Set;
import de.hsrm.compiler.Klang.nodes.*;
import de.hsrm.compiler.Klang.nodes.expressions.*;
import de.hsrm.compiler.Klang.nodes.loops.DoWhileLoop;
import de.hsrm.compiler.Klang.nodes.loops.ForLoop;
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
import de.hsrm.compiler.Klang.nodes.statements.*;
import de.hsrm.compiler.Klang.types.Type;
class GetVars implements Visitor<Void> {
public Set<String> vars;
public Map<String, Type> types;
public GetVars(Set<String> vars, Map<String, Type> types) {
this.vars = vars;
this.types = types;
}
@Override
public Void visit(IntegerExpression e) {
return null;
}
@Override
public Void visit(FloatExpression e) {
return null;
}
@Override
public Void visit(BooleanExpression e) {
return null;
}
@Override
public Void visit(Variable e) {
return null;
}
@Override
public Void visit(EqualityExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(NotEqualityExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(GTExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(GTEExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(LTExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(LTEExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(AdditionExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(SubstractionExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(MultiplicationExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(DivisionExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(ModuloExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(NegateExpression e) {
e.lhs.welcome(this);
return null;
}
@Override
public Void visit(OrExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(AndExpression e) {
e.lhs.welcome(this);
e.rhs.welcome(this);
return null;
}
@Override
public Void visit(NotExpression e) {
e.lhs.welcome(this);
return null;
}
@Override
public Void visit(IfStatement e) {
e.cond.welcome(this);
e.then.welcome(this);
if (e.alt != null) {
e.alt.welcome(this);
} else if (e.elif != null) {
e.elif.welcome(this);
}
return null;
}
@Override
public Void visit(WhileLoop e) {
e.cond.welcome(this);
e.block.welcome(this);
return null;
}
@Override
public Void visit(DoWhileLoop e) {
e.cond.welcome(this);
e.block.welcome(this);
return null;
}
@Override
public Void visit(ForLoop e) {
e.init.welcome(this);
e.condition.welcome(this);
e.step.welcome(this);
e.block.welcome(this);
return null;
}
@Override
public Void visit(VariableDeclaration e) {
vars.add(e.name);
types.put(e.name, e.type);
return null;
}
@Override
public Void visit(VariableAssignment e) {
e.expression.welcome(this);
return null;
}
@Override
public Void visit(ReturnStatement e) {
e.expression.welcome(this);
return null;
}
@Override
public Void visit(Block e) {
for (var statement : e.statements) {
statement.welcome(this);
}
return null;
}
@Override
public Void visit(FunctionDefinition e) {
e.block.welcome(this);
return null;
}
@Override
public Void visit(FunctionCall e) {
for (var expression : e.arguments) {
expression.welcome(this);
}
return null;
}
@Override
public Void visit(Program e) {
e.expression.welcome(this);
for (var func : e.funcs) {
func.welcome(this);
}
return null;
}
@Override
public Void visit(Parameter e) {
return null;
}
@Override
public Void visit(StructDefinition e) {
return null;
}
@Override
public Void visit(StructField e) {
return null;
}
@Override
public Void visit(StructFieldAccessExpression e) {
return null;
}
@Override
public Void visit(ConstructorCall e) {
return null;
}
@Override
public Void visit(NullExpression e) {
return null;
}
@Override
public Void visit(DestructorCall e) {
return null;
}
@Override
public Void visit(FieldAssignment e) {
return null;
}
}

View File

@@ -68,6 +68,12 @@ public class PrettyPrintVisitor implements Visitor<Void> {
ex.nl();
}
for (var enumDef: e.enums.values()) {
enumDef.welcome(this);
ex.nl();
ex.nl();
}
e.expression.welcome(this);
ex.write(";");
return null;
@@ -91,6 +97,14 @@ public class PrettyPrintVisitor implements Visitor<Void> {
return null;
}
@Override
public Void visit(CharExpression e) {
ex.write("'");
ex.write(e.c);
ex.write("'");
return null;
}
@Override
public Void visit(EqualityExpression e) {
ex.write("(");
@@ -304,8 +318,11 @@ public class PrettyPrintVisitor implements Visitor<Void> {
@Override
public Void visit(ReturnStatement e) {
ex.write("return ");
e.expression.welcome(this);
ex.write("return");
if (e.expression != null) {
ex.write(" ");
e.expression.welcome(this);
}
ex.write(";");
return null;
}
@@ -314,7 +331,7 @@ public class PrettyPrintVisitor implements Visitor<Void> {
public Void visit(Block e) {
ex.write("{");
ex.addIndent();
for (Statement stmt : e.statements) {
for (var stmt : e.statementsOrFunctionCalls) {
ex.nl();
stmt.welcome(this);
if (stmt.getClass() == VariableAssignment.class || stmt.getClass() == VariableDeclaration.class) {
@@ -377,6 +394,28 @@ public class PrettyPrintVisitor implements Visitor<Void> {
return null;
}
@Override
public Void visit(EnumDefinition e) {
ex.write("enum " + e.name + " { ");
var first = true;
for(var enumValue: e.enums) {
if (!first) {
ex.write(", ");
} else {
first = false;
}
enumValue.welcome(this);
}
ex.write(" }");
return null;
}
@Override
public Void visit(EnumValue e) {
ex.write(e.value);
return null;
}
@Override
public Void visit(StructDefinition e) {
ex.write("struct " + e.name + " {");
@@ -398,7 +437,7 @@ public class PrettyPrintVisitor implements Visitor<Void> {
}
@Override
public Void visit(StructFieldAccessExpression e) {
public Void visit(MemberAccessExpression e) {
ex.write(e.varName);
for (int i = 0; i < e.path.length; i++) {
ex.write(".");
@@ -407,6 +446,15 @@ public class PrettyPrintVisitor implements Visitor<Void> {
return null;
}
@Override
public Void visit(EnumAccessExpression e) {
ex.write(e.enumName);
ex.write(".");
ex.write(e.enumValueName);
return null;
}
@Override
public Void visit(ConstructorCall e) {
ex.write("create " + e.structName + "(");

View File

@@ -1,11 +1,6 @@
package de.hsrm.compiler.Klang.visitors;
import de.hsrm.compiler.Klang.nodes.Block;
import de.hsrm.compiler.Klang.nodes.FunctionDefinition;
import de.hsrm.compiler.Klang.nodes.Parameter;
import de.hsrm.compiler.Klang.nodes.Program;
import de.hsrm.compiler.Klang.nodes.StructDefinition;
import de.hsrm.compiler.Klang.nodes.StructField;
import de.hsrm.compiler.Klang.nodes.*;
import de.hsrm.compiler.Klang.nodes.expressions.*;
import de.hsrm.compiler.Klang.nodes.loops.*;
import de.hsrm.compiler.Klang.nodes.statements.*;
@@ -17,6 +12,7 @@ public interface Visitor<R> {
R visit(IntegerExpression e);
R visit(FloatExpression e);
R visit(BooleanExpression e);
R visit(CharExpression e);
R visit(Variable e);
R visit(AdditionExpression e);
R visit(EqualityExpression e);
@@ -42,9 +38,12 @@ public interface Visitor<R> {
R visit(FunctionCall e);
R visit(Program e);
R visit(Parameter e);
R visit(EnumDefinition e);
R visit(EnumValue e);
R visit(StructDefinition e);
R visit(StructField e);
R visit(StructFieldAccessExpression e);
R visit(MemberAccessExpression e);
R visit(EnumAccessExpression e);
R visit(ConstructorCall e);
R visit(NullExpression e);
R visit(DestructorCall e);

View File

@@ -12,7 +12,8 @@ public class AndTest {
ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 && 2; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:30 && is only defined for bool.", e.getMessage());

164
src/test/java/CharTest.java Normal file
View File

@@ -0,0 +1,164 @@
import de.hsrm.compiler.Klang.ContextAnalysis;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CharTest {
@Test
void shouldNotThrowIfComparingCharsEquality() {
// given
var tree = Helper.prepareParser("""
function bar(): int {
let b: char = 'b';
if ('a' == b) {
return 1;
}
return -1;
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfComparingCharsInequality() {
// given
var tree = Helper.prepareParser("""
function bar(): int {
let b: char = 'b';
if ('a' > b) {
return 1;
}
return -1;
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfCharLiteralIsAssignedToCharVariable() {
// given
var tree = Helper.prepareParser("""
function bar(): int {
let c: char = 'c';
return 1;
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfCharLiteralIsReturnedFromFunction() {
// given
var tree = Helper.prepareParser("""
function bar(): char {
return 'c';
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfCharIsUsedInStruct() {
// given
var tree = Helper.prepareParser("""
struct myChar { c: char; }
function bar(): myChar {
let x: myChar = create myChar('c');
return x;
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldThrowIfCharIsAssignedToOtherTypedVariable() {
// given
var tree = Helper.prepareParser("""
struct foo { f: float; }
function bar(): int {
let c: foo = 'c';
return 1;
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 3:4 Type mismatch: cannot combine foo and char", e.getMessage());
}
@Test
void shouldThrowWhenReturningCharFromOtherTypedFunction() {
// given
var tree = Helper.prepareParser("""
function bar(): float {
return 'c';
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 2:4 Type mismatch: cannot combine float and char", e.getMessage());
}
@Test
void shouldThrowPassingCharToOtherTypedFunctionParameter() {
// given
var tree = Helper.prepareParser("""
function foo(a: float): void {
let x: bool = false;
}
function bar(): void {
foo('c');
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 5:4 argument 0 Expected float but got: char", e.getMessage());
}
@Test
void shouldThrowWhenAssigningCharToOtherTypedStructField() {
// given
var tree = Helper.prepareParser("""
struct foo { a: bool; }
function bar(): void {
let x: foo = create foo(false);
x.a = 'c';
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 4:4 Type mismatch: cannot combine bool and char", e.getMessage());
}
}

View File

@@ -13,18 +13,20 @@ public class ConstructorCallTest {
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create schwurbel(1); } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:52 Struct with name \"schwurbel\" not defined.", e.getMessage());
}
@Test
void numConstructorParameterMissmatch() {
void numConstructorParametermismatch() {
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(1, false); } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:52 Struct \"bar\" defined 1 fields, but got 2 constructor parameters.", e.getMessage());
@@ -35,9 +37,10 @@ public class ConstructorCallTest {
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(false); } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:63 argument 0 Type missmatch: cannot combine bool and int", e.getMessage());
assertEquals("Error in line 1:63 argument 0 Type mismatch: cannot combine bool and int", e.getMessage());
}
}

View File

@@ -13,9 +13,10 @@ public class DestroyStatementTest {
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { destroy x; return 1; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:45 Variable with name \"x\" not defined.", e.getMessage());
assertEquals("Error in line 1:45 Variable or parameter with name \"x\" not defined.", e.getMessage());
}
}

View File

@@ -13,10 +13,11 @@ public class FieldAssignmentTest {
ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { str.a = 1; return 1; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:46 Variable with name str not defined.", e.getMessage());
assertEquals("Error in line 1:46 Variable or parameter with name \"str\" not defined.", e.getMessage());
}
@Test
@@ -24,9 +25,10 @@ public class FieldAssignmentTest {
ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { let x: int = 0; x.a = 0; return 1; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:62 Variable must reference a struct but references int.", e.getMessage());
assertEquals("Error in line 1:62 Variable or parameter must reference a struct but references int.", e.getMessage());
}
}

View File

@@ -13,7 +13,8 @@ public class FunctionCallTest {
ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } bar();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:34 Function with name \"bar\" not defined.", e.getMessage());
@@ -24,18 +25,20 @@ public class FunctionCallTest {
ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } foo(5);");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:34 Function \"foo\" expects 0 parameters, but got 1.", e.getMessage());
}
@Test
void parameterTypeMissmatch() {
void parameterTypeMismatch() {
ParseTree tree = Helper.prepareParser("function foo(x: int): int { return x; } foo(false);");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:40 argument 0 Expected int but got: bool", e.getMessage());

View File

@@ -1,32 +1,119 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.antlr.v4.runtime.tree.ParseTree;
import de.hsrm.compiler.Klang.ContextAnalysis;
import org.junit.jupiter.api.Test;
import de.hsrm.compiler.Klang.ContextAnalysis;
import static org.junit.jupiter.api.Assertions.*;
public class FunctionDefinitionTest {
@Test
void typeNotDefined() {
ParseTree tree = Helper.prepareParser("function foo(): schwurbel { return 1; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:0 Type schwurbel not defined.", e.getMessage());
void shouldNotThrowIfReturnTypeIsReferringToAnEnum() {
// given
var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: int): bar { return bar.A; } foo(1);");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void noReturnExpression() {
ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; x = 0; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:0 Function foo has to return something of type int.", e.getMessage());
void shouldNotThrowIfParameterTypeIsReferringToAnEnum() {
// given
var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: bar): int { return 1; } foo(bar.A);");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldThrowExceptionIfParameterTypeIsNotDefined() {
// given
var tree = Helper.prepareParser("function foo(a: schwurbel): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage());
}
@Test
void shouldThrowExceptionIfReturnTypeIsNotDefined() {
// given
var tree = Helper.prepareParser("function foo(): schwurbel { return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage());
}
@Test
void shouldThrowExceptionIfReturnStatementIsMissing() {
// given
var tree = Helper.prepareParser("function foo(): int { let x: int; x = 0; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:20 Function foo has to return something of type int.", e.getMessage());
}
@Test
void shouldThrowExceptionIfParameterNameMatchesEnumName() {
// given
var tree = Helper.prepareParser("enum Bar { A, B } function foo(Bar: int): int { return 1; } foo(1);");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:31 Parameter name Bar duplicates an enum of the same name.", e.getMessage());
}
@Test
void shouldResetVariablesAndParameterEnvironment() {
// given
var tree = Helper.prepareParser("""
function foo(x: int): int {
return 1;
}
function bar(): int {
let x: int = 0;
return x;
}
bar();
""");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
}

View File

@@ -16,21 +16,21 @@ public class Helper {
return parser.parse();
}
public static Map<String, FunctionInformation> getFuncs(ParseTree tree) {
var functionDefinitions = new HashMap<String, FunctionInformation>();
new GetFunctions(functionDefinitions).visit(tree);
public static Map<String, FunctionDefinition> getFuncs(ParseTree tree) {
var functionDefinitions = new HashMap<String, FunctionDefinition>();
new GetDefinitions(functionDefinitions, new HashMap<>(), new HashMap<>()).visit(tree);
return functionDefinitions;
}
public static Set<String> getStructNames(ParseTree tree) {
var structNames = new HashSet<String>();
new GetStructNames(structNames).visit(tree);
return structNames;
}
public static Map<String, StructDefinition> getStructs(ParseTree tree) {
var structs = new HashMap<String, StructDefinition>();
new GetStructs(getStructNames(tree), structs).visit(tree);
new GetDefinitions(new HashMap<>(), structs, new HashMap<>()).visit(tree);
return structs;
}
public static Map<String, EnumDefinition> getEnums(ParseTree tree) {
var enums = new HashMap<String, EnumDefinition>();
new GetDefinitions(new HashMap<>(), new HashMap<>(), enums).visit(tree);
return enums;
}
}

View File

@@ -12,7 +12,8 @@ public class ModuloTest {
ParseTree tree = Helper.prepareParser("function foo(): float { return 1.0 % 2.3; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:31 Only integers are allowed for modulo.", e.getMessage());

View File

@@ -0,0 +1,72 @@
import de.hsrm.compiler.Klang.ContextAnalysis;
import org.antlr.v4.runtime.tree.ParseTree;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class NaughtTest {
@Test
void shouldBeComparableToStruct() {
ParseTree tree = Helper.prepareParser("""
struct bar { a: int; }
function foo(): int {
let a: bar = create bar(1);
if (a == naught) {
return -1;
}
return 1;
}
foo();
""");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldBeAssignableToStruct() {
ParseTree tree = Helper.prepareParser("""
struct bar { a: int; }
function foo(): int {
let a: bar = naught;
return 1;
}
foo();
""");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldBeReturnable() {
ParseTree tree = Helper.prepareParser("""
struct bar { a: int; }
function foo(): bar {
return naught;
}
foo();
""");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
}

View File

@@ -13,7 +13,8 @@ public class OrTest {
ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 || 2; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:30 || is only defined for bool.", e.getMessage());

View File

@@ -1,14 +0,0 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.antlr.v4.runtime.tree.ParseTree;
import org.junit.jupiter.api.Test;
public class ParameterTest {
@Test
void typeNotDefined() {
ParseTree tree = Helper.prepareParser("struct test { a: schwurbel; } function foo(): int { return 1; } foo();");
Exception e = assertThrows(RuntimeException.class, () -> Helper.getStructs(tree));
assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage());
}
}

View File

@@ -0,0 +1,94 @@
import de.hsrm.compiler.Klang.ContextAnalysis;
import de.hsrm.compiler.Klang.GetDefinitions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import static org.junit.jupiter.api.Assertions.*;
public class StructDefinitionTest {
@Test
void shouldNotThrowIfStructIsWellDefined() {
// given
var tree = Helper.prepareParser("struct test { a: int; } function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfStructFieldTypeIsReferringToEnum() {
// given
var tree = Helper.prepareParser("struct test { a: bar; } enum bar {A,B,C} function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfStructFieldTypeIsReferringToStruct() {
// given
var tree = Helper.prepareParser("struct a { hello: int; } struct b { world: a; } function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldThrowExceptionIfStructFieldNameShadowsAStruct() {
// given
var tree = Helper.prepareParser("struct bar { a: int; } struct baz { bar: int; } function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:36 Struct field name bar shadows a struct of the same name.", e.getMessage());
}
@Test
void shouldThrowExceptionIfStructFieldNameShadowsAnEnum() {
// given
var tree = Helper.prepareParser("enum bar { A, B } struct baz { bar: int; } function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:31 Struct field name bar shadows an enum of the same name.", e.getMessage());
}
@Test
void shouldThrowExceptionIfStructFieldTypeIsNotDefined() {
// given
var tree = Helper.prepareParser("struct test { a: schwurbel; } function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:14 Type schwurbel not defined.", e.getMessage());
}
@Test
void shouldThrowExceptionIfStructFieldTypeIsReferringToAFunction() {
// given
var tree = Helper.prepareParser("struct test { a: foo; } function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:14 Type foo not defined.", e.getMessage());
}
@Test
void shouldThrowExceptionIfStructFieldNameIsDuplicated() {
// given
var tree = Helper.prepareParser("struct test { a: int; a: bool; } function foo(): int { return 1; } foo();");
var getDefs = new GetDefinitions(new HashMap<>(), new HashMap<>(), new HashMap<>());
// when / then
var e = assertThrows(RuntimeException.class, () -> getDefs.visit(tree));
assertEquals("Error in line 1:22 Duplicate struct field a in struct test.", e.getMessage());
}
}

View File

@@ -12,10 +12,11 @@ public class StructFieldAccessTest {
ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { return str.a; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:53 Variable with name str not defined.", e.getMessage());
assertEquals("Error in line 1:53 Variable or parameter with name \"str\" not defined.", e.getMessage());
}
@Test
@@ -23,9 +24,10 @@ public class StructFieldAccessTest {
ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { let x: int = 0; return x.a; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:69 Variable must reference a struct but references int.", e.getMessage());
assertEquals("Error in line 1:69 Variable or parameter must reference a struct but references int.", e.getMessage());
}
}

View File

@@ -13,9 +13,10 @@ public class VariableAssignmentTest {
ParseTree tree = Helper.prepareParser("function foo(): int { x = 1; return 1; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:22 Variable with name \"x\" not defined.", e.getMessage());
assertEquals("Error in line 1:22 Variable or parameter with name \"x\" not defined.", e.getMessage());
}
}

View File

@@ -1,32 +1,109 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.antlr.v4.runtime.tree.ParseTree;
import de.hsrm.compiler.Klang.ContextAnalysis;
import org.junit.jupiter.api.Test;
import de.hsrm.compiler.Klang.ContextAnalysis;
import static org.junit.jupiter.api.Assertions.*;
public class VariableDeclarationTest {
@Test
void typeNotDefined() {
ParseTree tree = Helper.prepareParser("function foo(): int { let X: unk; return 1; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
void shouldNotThrowIfDeclaredTypeIsAStruct() {
// given
var tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { let a: bar; return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfDeclaredTypeIsAnEnum() {
// given
var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let a: bar = bar.A; return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldThrowExceptionIfDeclaredNameShadowsEnumName() {
// given
var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let bar: int; return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:40 Variable name bar shadows an enum of the same name.", e.getMessage());
}
@Test
void shouldThrowExceptionIfDeclaredNameShadowsStruct() {
// given
var tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { let bar: int; return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:45 Variable name bar shadows a struct of the same name.", e.getMessage());
}
@Test
void shouldThrowExceptionIfDeclaredTypeIsNotDefined() {
// given
var tree = Helper.prepareParser("function foo(): int { let X: unk; return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:22 Type unk not defined.", e.getMessage());
}
@Test
void variableRedeclaration()
{
ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; return 1; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:34 Redeclaration of variable with name \"x\".", e.getMessage());
void shouldThrowExceptionIfVariableIsRedeclared() {
// given
var tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:34 Redeclaration of variable or parameter with name \"x\".", e.getMessage());
}
@Test
void shouldThrowExceptionIfVariableReferencesEnumButIsNotInitialized() {
// given
var tree = Helper.prepareParser("enum bar { A, B } function foo(): int { let x: bar; return 1; } foo();");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:40 Variable x references an enum but is not initialized.", e.getMessage());
}
}

View File

@@ -13,10 +13,11 @@ public class VariableTest {
ParseTree tree = Helper.prepareParser("function foo(): int { return x; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:29 Variable with name \"x\" not defined.", e.getMessage());
assertEquals("Error in line 1:29 Variable or parameter with name \"x\" not defined.", e.getMessage());
}
@Test
@@ -24,7 +25,8 @@ public class VariableTest {
ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; return x; } foo();");
var funcs = Helper.getFuncs(tree);
var structs = Helper.getStructs(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
var enums = Helper.getEnums(tree);
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:41 Variable with name \"x\" has not been initialized.", e.getMessage());

101
src/test/java/VoidTest.java Normal file
View File

@@ -0,0 +1,101 @@
import de.hsrm.compiler.Klang.ContextAnalysis;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class VoidTest {
@Test
void shouldNotThrowIfVoidIsUsedAsReturnType() {
// given
var tree = Helper.prepareParser("function foo(): void { let a: int = 0; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldNotThrowIfVoidFunctionIsCalled() {
// given
var tree = Helper.prepareParser("""
function foo(): void {
let a: int = 0;
}
function bar(): int {
foo();
return 1;
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldThrowIfVoidFunctionIsAssignedToVariable() {
// given
var tree = Helper.prepareParser("""
function foo(): void {
let a: int = 0;
}
function bar(): int {
let a: int = foo();
return 1;
}
bar();
""");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 5:4 Type mismatch: cannot combine int and void", e.getMessage());
}
@Test
void shouldThrowIfValueIsReturnedFromVoidFunction() {
// given
var tree = Helper.prepareParser("function foo(): void { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:23 Cannot return an expression from a void function.", e.getMessage());
}
@Test
void shouldThrowIfVoidIsUsedAsParameterType() {
// given
var tree = Helper.prepareParser("function foo(a: void): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:13 Type void cannot be used to declare a parameter.", e.getMessage());
}
@Test
void shouldThrowIfVoidIsUsedAsVariableType() {
// given
var tree = Helper.prepareParser("function foo(): int { let a: void; return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:22 Type void can not be used to declare variables.", e.getMessage());
}
@Test
void shouldThrowIfVoidIsUsedAsStructFieldType() {
// given
var tree = Helper.prepareParser("struct bar { a: void; } function foo(): int { return 1; } foo();");
var ctxAnal = new ContextAnalysis(Helper.getFuncs(tree), Helper.getStructs(tree), Helper.getEnums(tree));
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:13 Type void can not be used as a struct field type.", e.getMessage());
}
}

View File

@@ -94,6 +94,11 @@ int testStructCreation() {
struct_testExpected_l("init inner field a", 20, resultRec->b->a);
struct_testExpected_s("init inner field b", NULL, resultRec->b->b);
// The result of this test is always 1 because if the tests fail
// a segmentation fault occurs.
long alignmentResult = isStackAlignedBeforeFunctionCall();
struct_testExpected_l("stack alignment before malloc", 1, alignmentResult);
free(resultRec);
free(innerStruct);
}

View File

@@ -28,4 +28,6 @@ long getStructFieldRecA(struct testStructRec *t);
struct testStructRec *getStructFieldRecB(struct testStructRec *t);
struct testStructRec *setStructFieldRecA(struct testStructRec *t, long a);
struct testStructRec *setStructFieldRecB(struct testStructRec *t, struct testStructRec *b);
struct testStructRec *setStructFieldRecB(struct testStructRec *t, struct testStructRec *b);
long isStackAlignedBeforeFunctionCall();

View File

@@ -555,6 +555,72 @@ function getTestStructRec(a: int, b: testStructRec): testStructRec {
return create testStructRec(a, b);
}
struct evenFieldsStruct {
a: int;
b: int;
}
struct oddFieldsStruct {
a: int;
b: int;
c: int;
}
function stackAlignmentTestInternal1(): oddFieldsStruct {
// an odd amount of constructor parameters could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
return create oddFieldsStruct(1, 1, 1);
}
function stackAlignmentTestInternal2(): evenFieldsStruct {
// an odd amount of local variables could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
let a: evenFieldsStruct = create evenFieldsStruct(1, 1);
return a;
}
function stackAlignmentTestInternal3(a: int): evenFieldsStruct {
// an odd amount of function parameters could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal4(a: int): evenFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let b: int = 0;
let c: int = 0;
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal5(a: int, b: int): evenFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let c: int = 0;
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal6(a: int, b: int): oddFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let c: int = 0;
let d: int = 0;
return create oddFieldsStruct(1, 1, 1);
}
function isStackAlignedBeforeFunctionCall(): int {
let a: oddFieldsStruct = stackAlignmentTestInternal1();
let b: evenFieldsStruct = stackAlignmentTestInternal2();
let c: evenFieldsStruct = stackAlignmentTestInternal3(1);
let d: evenFieldsStruct = stackAlignmentTestInternal4(1);
let e: evenFieldsStruct = stackAlignmentTestInternal5(1, 1);
let f: oddFieldsStruct = stackAlignmentTestInternal6(1, 1);
return 1;
}
function getStructFieldA(t: testStruct): int {
return t.a;
}