Compare commits
39 Commits
8529e24a37
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| bcc9f29ff5 | |||
| 259ac49981 | |||
| 2aff9b3d0d | |||
| a7e93f4f01 | |||
| 5f0e84198a | |||
| aef2c84fdc | |||
| 4219f93021 | |||
| 7965c89a60 | |||
| 26eff47057 | |||
|
|
53976615e1 | ||
| c124587983 | |||
| 07e5a338a4 | |||
| 76419d86bb | |||
| f55f2661de | |||
| 06609ae899 | |||
| c5c01041e4 | |||
| 9751a1da2f | |||
| 534b507f7a | |||
| cce58b6e38 | |||
| 8b17ced533 | |||
| bacc40d844 | |||
| f38bd3d69e | |||
| 441d0122f8 | |||
| e835bd0f06 | |||
| ea1c04ae0a | |||
| 198bd74a47 | |||
| 0594542167 | |||
| 77fe360ffa | |||
| 55a5b8f54a | |||
| 2768b4429c | |||
| 30dfbbbbba | |||
| f77d6a002d | |||
| 22634c9652 | |||
| 6fd3f5a2e6 | |||
| 3b928d621b | |||
| 9a58afb550 | |||
| 6e4431652c | |||
| 7af815042b | |||
| 7c40a50196 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,3 +17,4 @@ src/main/antlr4/de/hsrm/compiler/Klang/.antlr
|
|||||||
# build output
|
# build output
|
||||||
out
|
out
|
||||||
src/test/test
|
src/test/test
|
||||||
|
/.idea/uiDesigner.xml
|
||||||
|
|||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
16
.idea/compiler.xml
generated
Normal 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
8
.idea/encodings.xml
generated
Normal 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
20
.idea/jarRepositories.xml
generated
Normal 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>
|
||||||
13
.idea/libraries/Maven__org_antlr_antlr4_runtime_4_7_2.xml
generated
Normal file
13
.idea/libraries/Maven__org_antlr_antlr4_runtime_4_7_2.xml
generated
Normal 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>
|
||||||
13
.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_0.xml
generated
Normal file
13
.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_0.xml
generated
Normal 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>
|
||||||
13
.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_6_0.xml
generated
Normal file
13
.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_6_0.xml
generated
Normal 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>
|
||||||
13
.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_6_0.xml
generated
Normal file
13
.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_6_0.xml
generated
Normal 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>
|
||||||
13
.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_6_0.xml
generated
Normal file
13
.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_6_0.xml
generated
Normal 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>
|
||||||
13
.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_6_0.xml
generated
Normal file
13
.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_6_0.xml
generated
Normal 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>
|
||||||
13
.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml
generated
Normal file
13
.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml
generated
Normal 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
28
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
22
klang.iml
Normal 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>
|
||||||
7
pom.xml
7
pom.xml
@@ -40,7 +40,7 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.8.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>11</release>
|
<release>17</release>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<!-- Plugin to compile the g4 files ahead of the java files
|
<!-- Plugin to compile the g4 files ahead of the java files
|
||||||
@@ -72,6 +72,11 @@
|
|||||||
<descriptorRefs>
|
<descriptorRefs>
|
||||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
</descriptorRefs>
|
</descriptorRefs>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>de.hsrm.compiler.Klang.Klang</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ parse
|
|||||||
;
|
;
|
||||||
|
|
||||||
program
|
program
|
||||||
: (functionDef | structDef)* expression SCOL
|
: (functionDef | structDef | enumDef)* expression SCOL
|
||||||
|
;
|
||||||
|
|
||||||
|
enumDef
|
||||||
|
: ENUM enumName=IDENT OBRK (IDENT (COMMA IDENT)*)+ CBRK
|
||||||
;
|
;
|
||||||
|
|
||||||
structDef
|
structDef
|
||||||
@@ -29,7 +33,7 @@ parameter
|
|||||||
;
|
;
|
||||||
|
|
||||||
braced_block
|
braced_block
|
||||||
: OBRK statement+ CBRK
|
: OBRK (statement | functionCall SCOL)+ CBRK
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
@@ -69,7 +73,7 @@ field_assignment
|
|||||||
;
|
;
|
||||||
|
|
||||||
return_statement
|
return_statement
|
||||||
: RETURN expression SCOL
|
: RETURN expression? SCOL
|
||||||
;
|
;
|
||||||
|
|
||||||
destroy_statement
|
destroy_statement
|
||||||
@@ -78,7 +82,7 @@ destroy_statement
|
|||||||
|
|
||||||
expression
|
expression
|
||||||
: atom #atomExpression
|
: atom #atomExpression
|
||||||
| IDENT (PERIOD IDENT)+ #structFieldAccessExpression
|
| IDENT (PERIOD IDENT)+ #memberAccessExpression
|
||||||
| OPAR expression CPAR #parenthesisExpression
|
| OPAR expression CPAR #parenthesisExpression
|
||||||
| lhs=expression MUL rhs=expression #multiplicationExpression
|
| lhs=expression MUL rhs=expression #multiplicationExpression
|
||||||
| lhs=expression DIV rhs=expression #divisionExpression
|
| lhs=expression DIV rhs=expression #divisionExpression
|
||||||
@@ -103,6 +107,7 @@ atom
|
|||||||
: INTEGER_LITERAL #intAtom
|
: INTEGER_LITERAL #intAtom
|
||||||
| BOOLEAN_LITERAL #boolAtom
|
| BOOLEAN_LITERAL #boolAtom
|
||||||
| FLOAT_LITERAL #floatAtom
|
| FLOAT_LITERAL #floatAtom
|
||||||
|
| CHAR_LITERAL #charAtom
|
||||||
| NULL # nullAtom
|
| NULL # nullAtom
|
||||||
| IDENT #variable
|
| IDENT #variable
|
||||||
;
|
;
|
||||||
@@ -115,7 +120,9 @@ type
|
|||||||
: INTEGER
|
: INTEGER
|
||||||
| BOOLEAN
|
| BOOLEAN
|
||||||
| FLOAT
|
| FLOAT
|
||||||
|
| CHAR
|
||||||
| IDENT
|
| IDENT
|
||||||
|
| VOID
|
||||||
;
|
;
|
||||||
|
|
||||||
functionCall
|
functionCall
|
||||||
@@ -143,6 +150,7 @@ forLoop
|
|||||||
IF: 'if';
|
IF: 'if';
|
||||||
ELSE: 'else';
|
ELSE: 'else';
|
||||||
FUNC: 'function';
|
FUNC: 'function';
|
||||||
|
ENUM: 'enum';
|
||||||
STRUCT: 'struct';
|
STRUCT: 'struct';
|
||||||
RETURN: 'return';
|
RETURN: 'return';
|
||||||
LET: 'let';
|
LET: 'let';
|
||||||
@@ -178,9 +186,13 @@ SUB: '-';
|
|||||||
MOD: '%';
|
MOD: '%';
|
||||||
DIV: '/';
|
DIV: '/';
|
||||||
|
|
||||||
|
SQUOT: '\'';
|
||||||
|
|
||||||
BOOLEAN: 'bool';
|
BOOLEAN: 'bool';
|
||||||
INTEGER: 'int';
|
INTEGER: 'int';
|
||||||
FLOAT: 'float';
|
FLOAT: 'float';
|
||||||
|
CHAR: 'char';
|
||||||
|
VOID: 'void';
|
||||||
|
|
||||||
INTEGER_LITERAL
|
INTEGER_LITERAL
|
||||||
: [0-9]+
|
: [0-9]+
|
||||||
@@ -195,6 +207,10 @@ BOOLEAN_LITERAL
|
|||||||
| 'false'
|
| 'false'
|
||||||
;
|
;
|
||||||
|
|
||||||
|
CHAR_LITERAL
|
||||||
|
: SQUOT [ -~] SQUOT
|
||||||
|
;
|
||||||
|
|
||||||
IDENT
|
IDENT
|
||||||
: [a-zA-Z][a-zA-Z0-9]*
|
: [a-zA-Z][a-zA-Z0-9]*
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
package de.hsrm.compiler.Klang;
|
package de.hsrm.compiler.Klang;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import de.hsrm.compiler.Klang.helper.FunctionInformation;
|
|
||||||
import de.hsrm.compiler.Klang.helper.Helper;
|
import de.hsrm.compiler.Klang.helper.Helper;
|
||||||
import de.hsrm.compiler.Klang.nodes.*;
|
import de.hsrm.compiler.Klang.nodes.*;
|
||||||
import de.hsrm.compiler.Klang.nodes.expressions.*;
|
import de.hsrm.compiler.Klang.nodes.expressions.*;
|
||||||
@@ -12,11 +8,16 @@ import de.hsrm.compiler.Klang.nodes.loops.ForLoop;
|
|||||||
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
|
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
|
||||||
import de.hsrm.compiler.Klang.nodes.statements.*;
|
import de.hsrm.compiler.Klang.nodes.statements.*;
|
||||||
import de.hsrm.compiler.Klang.types.Type;
|
import de.hsrm.compiler.Klang.types.Type;
|
||||||
|
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||||
|
Map<String, VariableDeclaration> params = new HashMap<>();
|
||||||
Map<String, VariableDeclaration> vars = new HashMap<>();
|
Map<String, VariableDeclaration> vars = new HashMap<>();
|
||||||
Map<String, FunctionInformation> funcs;
|
Map<String, FunctionDefinition> functionDefs;
|
||||||
Map<String, StructDefinition> structs;
|
Map<String, StructDefinition> structDefs;
|
||||||
|
Map<String, EnumDefinition> enumDefs;
|
||||||
Type currentDeclaredReturnType;
|
Type currentDeclaredReturnType;
|
||||||
String currentFunctionDefinitionName;
|
String currentFunctionDefinitionName;
|
||||||
|
|
||||||
@@ -27,25 +28,35 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContextAnalysis(Map<String, FunctionInformation> funcs, Map<String, StructDefinition> structs) {
|
public ContextAnalysis(
|
||||||
this.funcs = funcs;
|
Map<String, FunctionDefinition> functionDefs,
|
||||||
this.structs = structs;
|
Map<String, StructDefinition> structDefs,
|
||||||
|
Map<String, EnumDefinition> enumDefs
|
||||||
|
) {
|
||||||
|
this.functionDefs = functionDefs;
|
||||||
|
this.structDefs = structDefs;
|
||||||
|
this.enumDefs = enumDefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitProgram(KlangParser.ProgramContext ctx) {
|
public Node visitProgram(KlangParser.ProgramContext ctx) {
|
||||||
FunctionDefinition[] funcs = new FunctionDefinition[ctx.functionDef().size()];
|
var typeCheckedFunctionDefs = new FunctionDefinition[ctx.functionDef().size()];
|
||||||
|
var typeCheckedStructDefs = new HashMap<String, StructDefinition>();
|
||||||
|
|
||||||
for (int i = 0; i < ctx.functionDef().size(); i++) {
|
for (int i = 0; i < ctx.functionDef().size(); i++) {
|
||||||
funcs[i] = (FunctionDefinition) this.visit(ctx.functionDef(i));
|
typeCheckedFunctionDefs[i] = (FunctionDefinition) visit(ctx.functionDef(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression expression = (Expression) this.visit(ctx.expression());
|
for (int i = 0; i < ctx.structDef().size(); i++) {
|
||||||
Program result = new Program(funcs, this.structs, expression);
|
typeCheckedStructDefs.put(ctx.structDef(i).structName.getText(), (StructDefinition) visit(ctx.structDef(i)));
|
||||||
result.type = expression.type;
|
}
|
||||||
result.line = ctx.start.getLine();
|
|
||||||
result.col = ctx.start.getCharPositionInLine();
|
var expression = (Expression) visit(ctx.expression());
|
||||||
return result;
|
var program = new Program(typeCheckedFunctionDefs, typeCheckedStructDefs, enumDefs, expression);
|
||||||
|
program.type = expression.type;
|
||||||
|
program.line = ctx.start.getLine();
|
||||||
|
program.col = ctx.start.getCharPositionInLine();
|
||||||
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,25 +68,32 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitBraced_block(KlangParser.Braced_blockContext ctx) {
|
public Node visitBraced_block(KlangParser.Braced_blockContext ctx) {
|
||||||
int actualStatementCount = 0;
|
var statementsOrFunctionCalls = new ArrayList<Node>();
|
||||||
int declaredStatementCount = ctx.statement().size();
|
var hasReturn = false;
|
||||||
boolean hasReturn = false;
|
|
||||||
Statement[] statements = new Statement[declaredStatementCount];
|
|
||||||
|
|
||||||
for (int i = 0; i < declaredStatementCount; i++) {
|
for (var child: ctx.children) {
|
||||||
Node currentStatement = this.visit(ctx.statement(i));
|
var statementOrFunctionCall = visit(child);
|
||||||
statements[i] = (Statement) currentStatement;
|
|
||||||
actualStatementCount += 1;
|
|
||||||
|
|
||||||
// We use the existance of a type to indicate that this statement returns
|
// The children array contains more than just the statements or function calls
|
||||||
// something for which the VariableDeclaration is an exception
|
// but everything else evaluates to null, so we can skip it.
|
||||||
if (currentStatement.type != null && !(currentStatement instanceof VariableDeclaration)) {
|
if (statementOrFunctionCall == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
statementsOrFunctionCalls.add(statementOrFunctionCall);
|
||||||
|
|
||||||
|
if (
|
||||||
|
statementOrFunctionCall.type != null
|
||||||
|
&& !(statementOrFunctionCall instanceof VariableDeclaration)
|
||||||
|
&& !statementOrFunctionCall.type.equals(Type.getVoidType())
|
||||||
|
) {
|
||||||
// check whether the type matches
|
// check whether the type matches
|
||||||
try {
|
try {
|
||||||
this.currentDeclaredReturnType.combine(currentStatement.type);
|
currentDeclaredReturnType.combine(statementOrFunctionCall.type);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(
|
var line = statementOrFunctionCall.line;
|
||||||
Helper.getErrorPrefix(currentStatement.line, currentStatement.col) + e.getMessage());
|
var col = statementOrFunctionCall.col;
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// since we have a return guaranteed, every statement
|
// since we have a return guaranteed, every statement
|
||||||
@@ -85,21 +103,12 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there was unreachable code in this block,
|
|
||||||
// create a shorter statements array and copy the statements to there
|
|
||||||
if (actualStatementCount < declaredStatementCount) {
|
|
||||||
Statement[] newStatements = new Statement[actualStatementCount];
|
|
||||||
for (int i = 0; i < actualStatementCount; i++) {
|
|
||||||
newStatements[i] = statements[i];
|
|
||||||
}
|
|
||||||
statements = newStatements;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this block contains at least one statement that guarantees a return value,
|
// if this block contains at least one statement that guarantees a return value,
|
||||||
// we indicate that this block guarantees a return value by setting result.type
|
// we indicate that this block guarantees a return value by setting result.type
|
||||||
Block result = new Block(statements);
|
var result = new Block(statementsOrFunctionCalls.toArray(new Node[0]));
|
||||||
|
|
||||||
if (hasReturn) {
|
if (hasReturn) {
|
||||||
result.type = this.currentDeclaredReturnType;
|
result.type = currentDeclaredReturnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.line = ctx.start.getLine();
|
result.line = ctx.start.getLine();
|
||||||
@@ -172,72 +181,89 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitVariable_declaration(KlangParser.Variable_declarationContext ctx) {
|
public Node visitVariable_declaration(KlangParser.Variable_declarationContext ctx) {
|
||||||
String name = ctx.IDENT().getText();
|
var variableName = ctx.IDENT().getText();
|
||||||
int line = ctx.start.getLine();
|
var declaredType = Type.getByName(ctx.type_annotation().type().getText());
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var line = ctx.start.getLine();
|
||||||
Type declaredType = Type.getByName(ctx.type_annotation().type().getText());
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
if (!declaredType.isPrimitiveType() && this.structs.get(declaredType.getName()) == null) {
|
if (declaredType.equals(Type.getVoidType())) {
|
||||||
String error = "Type " + declaredType.getName() + " not defined.";
|
var error = "Type " + declaredType.getName() + " can not be used to declare variables.";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.vars.get(name) != null) {
|
if (!declaredType.isPrimitiveType() && !structDefs.containsKey(declaredType.getName()) && !enumDefs.containsKey(declaredType.getName())) {
|
||||||
String error = "Redeclaration of variable with name \"" + name + "\".";
|
var error = "Type " + declaredType.getName() + " not defined.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (structDefs.containsKey(variableName)) {
|
||||||
|
var error = "Variable name " + variableName + " shadows a struct of the same name.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enumDefs.containsKey(variableName)) {
|
||||||
|
var error = "Variable name " + variableName + " shadows an enum of the same name.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vars.get(variableName) != null || params.get(variableName) != null) {
|
||||||
|
var error = "Redeclaration of variable or parameter with name \"" + variableName + "\".";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the appropriate instance
|
// Create the appropriate instance
|
||||||
VariableDeclaration result;
|
VariableDeclaration variableDeclaration;
|
||||||
if (ctx.expression() != null) {
|
if (ctx.expression() != null) {
|
||||||
Node expression = this.visit(ctx.expression());
|
var expression = visit(ctx.expression());
|
||||||
try {
|
try {
|
||||||
declaredType.combine(expression.type);
|
declaredType.combine(expression.type);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||||
}
|
}
|
||||||
result = new VariableDeclaration(name, (Expression) expression);
|
variableDeclaration = new VariableDeclaration(variableName, (Expression) expression);
|
||||||
result.initialized = true;
|
variableDeclaration.initialized = true;
|
||||||
} else {
|
} else {
|
||||||
result = new VariableDeclaration(name);
|
if (enumDefs.containsKey(declaredType.getName())) {
|
||||||
|
var error = "Variable " + variableName + " references an enum but is not initialized.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
variableDeclaration = new VariableDeclaration(variableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add it to the global map of variable declarations
|
// Add it to the global map of variable declarations
|
||||||
this.vars.put(name, result);
|
vars.put(variableName, variableDeclaration);
|
||||||
|
|
||||||
result.line = line;
|
variableDeclaration.line = line;
|
||||||
result.col = col;
|
variableDeclaration.col = col;
|
||||||
result.type = declaredType;
|
variableDeclaration.type = declaredType;
|
||||||
return result;
|
|
||||||
|
return variableDeclaration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitVariable_assignment(KlangParser.Variable_assignmentContext ctx) {
|
public Node visitVariable_assignment(KlangParser.Variable_assignmentContext ctx) {
|
||||||
String name = ctx.IDENT().getText();
|
var name = ctx.IDENT().getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
VariableDeclaration var = this.vars.get(name);
|
var variableOrParameter = getVariableOrParameter(name, line, col);
|
||||||
if (var == null) {
|
|
||||||
String error = "Variable with name \"" + name + "\" not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the expression
|
// Evaluate the expression
|
||||||
Expression expression = (Expression) this.visit(ctx.expression());
|
var expression = (Expression) visit(ctx.expression());
|
||||||
|
|
||||||
// Make sure expression can be assigned to the variable
|
// Make sure expression can be assigned to the variable
|
||||||
try {
|
try {
|
||||||
expression.type.combine(var.type);
|
expression.type.combine(variableOrParameter.type);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we assigned a value to this variable, we can consider it initialized
|
// Since we assigned a value to this variable, we can consider it initialized
|
||||||
var.initialized = true;
|
variableOrParameter.initialized = true;
|
||||||
|
|
||||||
// Create a new node and add the type of the expression to it
|
// Create a new node and add the type of the expression to it
|
||||||
Node result = new VariableAssignment(name, expression);
|
var result = new VariableAssignment(name, expression);
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
return result;
|
return result;
|
||||||
@@ -245,109 +271,102 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitReturn_statement(KlangParser.Return_statementContext ctx) {
|
public Node visitReturn_statement(KlangParser.Return_statementContext ctx) {
|
||||||
Expression expression = (Expression) this.visit(ctx.expression());
|
if (currentDeclaredReturnType.equals(Type.getVoidType())) {
|
||||||
ReturnStatement result = new ReturnStatement(expression);
|
var result = new ReturnStatement();
|
||||||
|
result.type = Type.getVoidType();
|
||||||
|
result.line = ctx.start.getLine();
|
||||||
|
result.col = ctx.start.getCharPositionInLine();
|
||||||
|
if (ctx.expression() != null) {
|
||||||
|
var error = "Cannot return an expression from a void function.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(result.line, result.col) + error);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expression = (Expression) visit(ctx.expression());
|
||||||
|
|
||||||
// Check if this expression is a tail recursion
|
// Check if this expression is a tail recursion
|
||||||
if (expression instanceof FunctionCall) {
|
if (expression instanceof FunctionCall funCall) {
|
||||||
var funCall = (FunctionCall) expression;
|
if (funCall.name.equals(currentFunctionDefinitionName)) {
|
||||||
if (funCall.name.equals(this.currentFunctionDefinitionName)) {
|
|
||||||
// Flag this function call
|
// Flag this function call
|
||||||
funCall.isTailRecursive = true;
|
funCall.isTailRecursive = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.type = expression.type;
|
var result = new ReturnStatement(expression);
|
||||||
result.line = ctx.start.getLine();
|
result.line = ctx.start.getLine();
|
||||||
|
result.type = expression.type;
|
||||||
result.col = ctx.start.getCharPositionInLine();
|
result.col = ctx.start.getCharPositionInLine();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitField_assignment(KlangParser.Field_assignmentContext ctx) {
|
public Node visitField_assignment(KlangParser.Field_assignmentContext ctx) {
|
||||||
String varName = ctx.IDENT(0).getText();
|
var varName = ctx.IDENT(0).getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
String[] path = new String[ctx.IDENT().size() - 1];
|
var path = createStructPath(ctx.IDENT());
|
||||||
|
|
||||||
for (int i = 1; i < ctx.IDENT().size(); i++) {
|
var variableOrParameter = getVariableOrParameter(varName, line, col);
|
||||||
path[i - 1] = ctx.IDENT(i).getText();
|
ensureReferencesStruct(variableOrParameter, line, col);
|
||||||
}
|
var fieldType = drillType(variableOrParameter, path, line, col);
|
||||||
|
|
||||||
// Get the referenced variable, make sure it is defined
|
// Visit the expression and make sure the type combines properly
|
||||||
var variableDef = this.vars.get(varName);
|
var expression = (Expression) visit(ctx.expression());
|
||||||
if (variableDef == null) {
|
|
||||||
String error = "Variable with name " + varName + " not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure it references a struct
|
|
||||||
if (variableDef.type.isPrimitiveType()) {
|
|
||||||
String error = "Variable must reference a struct but references " + variableDef.type.getName() + ".";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the type of the result of this expression
|
|
||||||
String structName = variableDef.type.getName();
|
|
||||||
Type fieldType;
|
|
||||||
try {
|
|
||||||
fieldType = Helper.drillType(this.structs, structName, path, 0);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the expression and make sure the type combines properly
|
|
||||||
Expression expression = (Expression) this.visit(ctx.expression());
|
|
||||||
try {
|
try {
|
||||||
fieldType.combine(expression.type);
|
fieldType.combine(expression.type);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
Node result = new FieldAssignment(varName, structName, path, expression);
|
var result = new FieldAssignment(varName, variableOrParameter.type.getName(), path, expression);
|
||||||
result.col = col;
|
result.col = col;
|
||||||
result.line = line;
|
result.line = line;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitStructFieldAccessExpression(KlangParser.StructFieldAccessExpressionContext ctx) {
|
public Node visitMemberAccessExpression(KlangParser.MemberAccessExpressionContext ctx) {
|
||||||
String varName = ctx.IDENT(0).getText();
|
var baseName = ctx.IDENT(0).getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
String[] path = new String[ctx.IDENT().size() - 1];
|
var path = createStructPath(ctx.IDENT());
|
||||||
|
|
||||||
for (int i = 1; i < ctx.IDENT().size(); i++) {
|
// Determine if the base name points to an enum or a variable
|
||||||
path[i - 1] = ctx.IDENT(i).getText();
|
var enumDef = enumDefs.get(baseName);
|
||||||
}
|
if (enumDef != null) {
|
||||||
|
if (path.length != 1) {
|
||||||
// Get the referenced variable, make sure it is defined
|
var error = "Illegal access to enum " + enumDef.name + ".";
|
||||||
var variableDef = this.vars.get(varName);
|
|
||||||
if (variableDef == null) {
|
|
||||||
String error = "Variable with name " + varName + " not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure it references a struct
|
var enumValueName = path[0];
|
||||||
if (variableDef.type.isPrimitiveType()) {
|
var enumValue = Arrays.stream(enumDef.enums)
|
||||||
String error = "Variable must reference a struct but references " + variableDef.type.getName() + ".";
|
.filter(e -> e.value.equals(enumValueName))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> {
|
||||||
|
var error = "Unknown enum value " + enumValueName + " of enum " + enumDef.name + ".";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
});
|
||||||
|
|
||||||
|
var enumAccessExpression = new EnumAccessExpression(baseName, enumValueName, enumValue);
|
||||||
|
enumAccessExpression.type = enumDef.type;
|
||||||
|
enumAccessExpression.line = line;
|
||||||
|
enumAccessExpression.col = col;
|
||||||
|
|
||||||
|
return enumAccessExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the type of the result of this expression
|
var variableOrParameter = getVariableOrParameter(baseName, line, col);
|
||||||
String structName = variableDef.type.getName();
|
ensureReferencesStruct(variableOrParameter, line, col);
|
||||||
Type resultType;
|
var resultType = drillType(variableOrParameter, path, line, col);
|
||||||
try {
|
|
||||||
resultType = Helper.drillType(this.structs, structName, path, 0);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
Node result = new StructFieldAccessExpression(varName, structName, path);
|
var memberAccessExpression = new MemberAccessExpression(baseName, variableOrParameter.type.getName(), path);
|
||||||
result.type = resultType;
|
memberAccessExpression.type = resultType;
|
||||||
result.line = line;
|
memberAccessExpression.line = line;
|
||||||
result.col = col;
|
memberAccessExpression.col = col;
|
||||||
return result;
|
|
||||||
|
return memberAccessExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -408,18 +427,21 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitEqualityExpression(KlangParser.EqualityExpressionContext ctx) {
|
public Node visitEqualityExpression(KlangParser.EqualityExpressionContext ctx) {
|
||||||
Node lhs = this.visit(ctx.lhs);
|
var lhs = visit(ctx.lhs);
|
||||||
Node rhs = this.visit(ctx.rhs);
|
var rhs = visit(ctx.rhs);
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
|
// Since there are countless combinations of types that are comparable
|
||||||
|
// to each other we use Type::combine to figure out if two types
|
||||||
|
// may be compared to each other
|
||||||
try {
|
try {
|
||||||
lhs.type.combine(rhs.type);
|
lhs.type.combine(rhs.type);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
EqualityExpression result = new EqualityExpression((Expression) lhs, (Expression) rhs);
|
var result = new EqualityExpression((Expression) lhs, (Expression) rhs);
|
||||||
result.type = Type.getBooleanType();
|
result.type = Type.getBooleanType();
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
@@ -428,18 +450,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitNotEqualityExpression(KlangParser.NotEqualityExpressionContext ctx) {
|
public Node visitNotEqualityExpression(KlangParser.NotEqualityExpressionContext ctx) {
|
||||||
Node lhs = this.visit(ctx.lhs);
|
var lhs = visit(ctx.lhs);
|
||||||
Node rhs = this.visit(ctx.rhs);
|
var rhs = visit(ctx.rhs);
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
try {
|
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||||
lhs.type.combine(rhs.type);
|
var error = "Can only compare primitives.";
|
||||||
} catch (Exception e) {
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NotEqualityExpression result = new NotEqualityExpression((Expression) lhs, (Expression) rhs);
|
var result = new NotEqualityExpression((Expression) lhs, (Expression) rhs);
|
||||||
result.type = Type.getBooleanType();
|
result.type = Type.getBooleanType();
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
@@ -448,20 +469,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitLessThanExpression(KlangParser.LessThanExpressionContext ctx) {
|
public Node visitLessThanExpression(KlangParser.LessThanExpressionContext ctx) {
|
||||||
Node lhs = this.visit(ctx.lhs);
|
var lhs = visit(ctx.lhs);
|
||||||
Node rhs = this.visit(ctx.rhs);
|
var rhs = visit(ctx.rhs);
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
try {
|
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||||
lhs.type.combine(rhs.type);
|
var error = "Can only compare primitives.";
|
||||||
} catch (Exception e) {
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNumeric(lhs, rhs, line, col);
|
var result = new LTExpression((Expression) lhs, (Expression) rhs);
|
||||||
|
|
||||||
LTExpression result = new LTExpression((Expression) lhs, (Expression) rhs);
|
|
||||||
result.type = Type.getBooleanType();
|
result.type = Type.getBooleanType();
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
@@ -470,23 +488,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitGreaterThanExpression(KlangParser.GreaterThanExpressionContext ctx) {
|
public Node visitGreaterThanExpression(KlangParser.GreaterThanExpressionContext ctx) {
|
||||||
Node lhs = this.visit(ctx.lhs);
|
var lhs = visit(ctx.lhs);
|
||||||
Node rhs = this.visit(ctx.rhs);
|
var rhs = visit(ctx.rhs);
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
try {
|
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||||
lhs.type.combine(rhs.type);
|
var error = "Can only compare primitives.";
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lhs.type.isNumericType() || !rhs.type.isNumericType()) {
|
|
||||||
String error = "Only numeric types are allowed for this expression.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
GTExpression result = new GTExpression((Expression) lhs, (Expression) rhs);
|
var result = new GTExpression((Expression) lhs, (Expression) rhs);
|
||||||
result.type = Type.getBooleanType();
|
result.type = Type.getBooleanType();
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
@@ -495,20 +507,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitLessThanOrEqualToExpression(KlangParser.LessThanOrEqualToExpressionContext ctx) {
|
public Node visitLessThanOrEqualToExpression(KlangParser.LessThanOrEqualToExpressionContext ctx) {
|
||||||
Node lhs = this.visit(ctx.lhs);
|
var lhs = visit(ctx.lhs);
|
||||||
Node rhs = this.visit(ctx.rhs);
|
var rhs = visit(ctx.rhs);
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
try {
|
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||||
lhs.type.combine(rhs.type);
|
var error = "Can only compare primitives.";
|
||||||
} catch (Exception e) {
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNumeric(lhs, rhs, line, col);
|
var result = new LTEExpression((Expression) lhs, (Expression) rhs);
|
||||||
|
|
||||||
LTEExpression result = new LTEExpression((Expression) lhs, (Expression) rhs);
|
|
||||||
result.type = Type.getBooleanType();
|
result.type = Type.getBooleanType();
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
@@ -517,20 +526,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitGreaterThanOrEqualToExpression(KlangParser.GreaterThanOrEqualToExpressionContext ctx) {
|
public Node visitGreaterThanOrEqualToExpression(KlangParser.GreaterThanOrEqualToExpressionContext ctx) {
|
||||||
Node lhs = this.visit(ctx.lhs);
|
var lhs = visit(ctx.lhs);
|
||||||
Node rhs = this.visit(ctx.rhs);
|
var rhs = visit(ctx.rhs);
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
try {
|
if (!lhs.type.isPrimitiveType() || !rhs.type.isPrimitiveType()) {
|
||||||
lhs.type.combine(rhs.type);
|
var error = "Can only compare primitives.";
|
||||||
} catch (Exception e) {
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNumeric(lhs, rhs, line, col);
|
var result = new GTEExpression((Expression) lhs, (Expression) rhs);
|
||||||
|
|
||||||
GTEExpression result = new GTEExpression((Expression) lhs, (Expression) rhs);
|
|
||||||
result.type = Type.getBooleanType();
|
result.type = Type.getBooleanType();
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
@@ -660,24 +666,20 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitVariable(KlangParser.VariableContext ctx) {
|
public Node visitVariable(KlangParser.VariableContext ctx) {
|
||||||
String name = ctx.IDENT().getText();
|
var variableName = ctx.IDENT().getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
VariableDeclaration var = this.vars.get(name);
|
var variableOrParameter = getVariableOrParameter(variableName, line, col);
|
||||||
if (var == null) {
|
|
||||||
String error = "Variable with name \"" + name + "\" not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the variable has been initialized before it can be used
|
// Make sure the variable has been initialized before it can be used
|
||||||
if (!var.initialized) {
|
if (!variableOrParameter.initialized) {
|
||||||
String error = "Variable with name \"" + name + "\" has not been initialized.";
|
var error = "Variable with name \"" + variableName + "\" has not been initialized.";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
Variable result = new Variable(ctx.IDENT().getText());
|
var result = new Variable(ctx.IDENT().getText());
|
||||||
result.type = var.type;
|
result.type = variableOrParameter.type;
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
return result;
|
return result;
|
||||||
@@ -708,13 +710,24 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitBoolAtom(KlangParser.BoolAtomContext ctx) {
|
public Node visitBoolAtom(KlangParser.BoolAtomContext ctx) {
|
||||||
Node n = new BooleanExpression(ctx.getText().equals("true") ? true : false);
|
Node n = new BooleanExpression(ctx.getText().equals("true"));
|
||||||
n.type = Type.getBooleanType();
|
n.type = Type.getBooleanType();
|
||||||
n.line = ctx.start.getLine();
|
n.line = ctx.start.getLine();
|
||||||
n.col = ctx.start.getCharPositionInLine();
|
n.col = ctx.start.getCharPositionInLine();
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node visitCharAtom(KlangParser.CharAtomContext ctx) {
|
||||||
|
// I had no idea how to design the grammar so that the quotes
|
||||||
|
// are separate from the char, so I cheat here...
|
||||||
|
var n = new CharExpression(ctx.CHAR_LITERAL().getText().charAt(1));
|
||||||
|
n.type = Type.getCharType();
|
||||||
|
n.line = ctx.start.getLine();
|
||||||
|
n.col = ctx.start.getCharPositionInLine();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitNullAtom(KlangParser.NullAtomContext ctx) {
|
public Node visitNullAtom(KlangParser.NullAtomContext ctx) {
|
||||||
Node n = new NullExpression();
|
Node n = new NullExpression();
|
||||||
@@ -725,67 +738,180 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) {
|
public Node visitStructDef(KlangParser.StructDefContext ctx) {
|
||||||
String name = ctx.funcName.getText();
|
var structName = ctx.structName.getText();
|
||||||
int line = ctx.start.getLine();
|
var structFieldCount = ctx.structField().size();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var structFields = new StructField[structFieldCount];
|
||||||
Type returnType = Type.getByName(ctx.returnType.type().getText());
|
var line = ctx.start.getLine();
|
||||||
this.currentDeclaredReturnType = returnType;
|
var col = ctx.start.getCharPositionInLine();
|
||||||
this.currentFunctionDefinitionName = name;
|
|
||||||
|
|
||||||
if (!returnType.isPrimitiveType() && this.structs.get(returnType.getName()) == null) {
|
for (int i = 0; i < structFieldCount; i++) {
|
||||||
String error = "Type " + returnType.getName() + " not defined.";
|
structFields[i] = (StructField) visit(ctx.structField(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
var structDef = new StructDefinition(structName, structFields);
|
||||||
|
structDef.type = Type.getByName(structName);
|
||||||
|
structDef.line = line;
|
||||||
|
structDef.col = col;
|
||||||
|
|
||||||
|
return structDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node visitStructField(KlangParser.StructFieldContext ctx) {
|
||||||
|
var structFieldName = ctx.IDENT().getText();
|
||||||
|
var structFieldType = Type.getByName(ctx.type_annotation().type().getText());
|
||||||
|
var line = ctx.start.getLine();
|
||||||
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
|
if (structFieldType.equals(Type.getVoidType())) {
|
||||||
|
var error = "Type void can not be used as a struct field type.";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new set for the variables of the current function
|
if (!structFieldType.isPrimitiveType() && !structDefs.containsKey(structFieldType.getName()) && !enumDefs.containsKey(structFieldType.getName())) {
|
||||||
|
var error = "Type " + structFieldType.getName() + " not defined.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (structDefs.containsKey(structFieldName)) {
|
||||||
|
var error = "Struct field name " + structFieldName + " shadows a struct of the same name.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enumDefs.containsKey(structFieldName)) {
|
||||||
|
var error = "Struct field name " + structFieldName + " shadows an enum of the same name.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
var structField = new StructField(structFieldName);
|
||||||
|
structField.type = structFieldType;
|
||||||
|
structField.line = line;
|
||||||
|
structField.col = col;
|
||||||
|
|
||||||
|
return structField;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node visitFunctionDef(KlangParser.FunctionDefContext ctx) {
|
||||||
|
var name = ctx.funcName.getText();
|
||||||
|
var returnType = Type.getByName(ctx.returnType.type().getText());
|
||||||
|
currentDeclaredReturnType = returnType;
|
||||||
|
currentFunctionDefinitionName = name;
|
||||||
|
|
||||||
|
var typeNotDefined = !returnType.isPrimitiveType() && !structDefs.containsKey(returnType.getName()) && !enumDefs.containsKey(returnType.getName());
|
||||||
|
if (!returnType.equals(Type.getVoidType()) && typeNotDefined) {
|
||||||
|
var line = ctx.returnType.start.getLine();
|
||||||
|
var col = ctx.returnType.start.getCharPositionInLine();
|
||||||
|
var error = "Type " + returnType.getName() + " not defined.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new set for the variables and parameters of the current function
|
||||||
// this will be filled in the variable declaration visitor as well
|
// this will be filled in the variable declaration visitor as well
|
||||||
this.vars = new HashMap<>();
|
vars = new HashMap<>();
|
||||||
|
params = new HashMap<>();
|
||||||
|
|
||||||
// Process the paremter list by visiting every paremter in it
|
|
||||||
int paramCount = ctx.params.parameter().size();
|
var paramCount = ctx.params.parameter().size();
|
||||||
Parameter[] params = new Parameter[paramCount];
|
var functionParameters = new Parameter[paramCount]; // the list of parameters that get passed to FunctionDefinition
|
||||||
for (int i = 0; i < paramCount; i++) {
|
for (int i = 0; i < paramCount; i++) {
|
||||||
// Add the parameter to the list of parameters
|
functionParameters[i] = (Parameter) visit(ctx.params.parameter(i));
|
||||||
Parameter param = (Parameter) this.visit(ctx.params.parameter(i));
|
|
||||||
params[i] = param;
|
|
||||||
|
|
||||||
// add the param as a variable
|
// add the param as a variable to the global parameter map so that
|
||||||
VariableDeclaration var = new VariableDeclaration(param.name);
|
// child nodes can access them.
|
||||||
var.initialized = true; // parameters can always be considered initialized
|
var param = new VariableDeclaration(functionParameters[i].name);
|
||||||
var.type = param.type;
|
param.initialized = true; // parameters can always be considered initialized
|
||||||
this.vars.put(param.name, var);
|
param.type = functionParameters[i].type;
|
||||||
|
params.put(param.name, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visit the block, make sure that a return value is guaranteed
|
// Visit the block, make sure that a return value is guaranteed
|
||||||
Node block = this.visit(ctx.braced_block());
|
var block = visit(ctx.braced_block());
|
||||||
if (block.type == null) {
|
if (block.type == null && !returnType.equals(Type.getVoidType())) {
|
||||||
String error = "Function " + name + " has to return something of type " + returnType.getName() + ".";
|
var line = ctx.braced_block().start.getLine();
|
||||||
|
var col = ctx.braced_block().start.getCharPositionInLine();
|
||||||
|
var error = "Function " + name + " has to return something of type " + returnType.getName() + ".";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionDefinition result = new FunctionDefinition(name, params, (Block) block);
|
var functionDef = new FunctionDefinition(name, functionParameters, vars.values().toArray(new VariableDeclaration[0]), (Block) block);
|
||||||
result.type = returnType;
|
functionDef.type = returnType;
|
||||||
|
functionDef.line = ctx.start.getLine();
|
||||||
|
functionDef.col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
result.line = ctx.start.getLine();
|
return functionDef;
|
||||||
result.col = ctx.start.getCharPositionInLine();
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitParameter(KlangParser.ParameterContext ctx) {
|
public Node visitParameter(KlangParser.ParameterContext ctx) {
|
||||||
String name = ctx.IDENT().getText();
|
var parameterName = ctx.IDENT().getText();
|
||||||
int line = ctx.start.getLine();
|
var parameterType = Type.getByName(ctx.type_annotation().type().getText());
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var line = ctx.start.getLine();
|
||||||
Type type = Type.getByName(ctx.type_annotation().type().getText());
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
if (!type.isPrimitiveType() && this.structs.get(type.getName()) == null) {
|
if (parameterType.equals(Type.getVoidType())) {
|
||||||
String error = "Type " + type.getName() + " not defined.";
|
var error = "Type " + parameterType.getName() + " cannot be used to declare a parameter.";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
Parameter result = new Parameter(name);
|
if (structDefs.containsKey(parameterName)) {
|
||||||
result.type = type;
|
var error = "Parameter name " + parameterName + " duplicates a struct of the same name.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enumDefs.containsKey(parameterName)) {
|
||||||
|
var error = "Parameter name " + parameterName + " duplicates an enum of the same name.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parameterType.isPrimitiveType() && !structDefs.containsKey(parameterType.getName()) && !enumDefs.containsKey(parameterType.getName())) {
|
||||||
|
var typeLine = ctx.type_annotation().start.getLine();
|
||||||
|
var typeCol = ctx.type_annotation().start.getCharPositionInLine();
|
||||||
|
var error = "Type " + parameterType.getName() + " not defined.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(typeLine, typeCol) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameter = new Parameter(parameterName);
|
||||||
|
parameter.type = parameterType;
|
||||||
|
parameter.line = ctx.start.getLine();
|
||||||
|
parameter.col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node visitFunctionCall(KlangParser.FunctionCallContext ctx) {
|
||||||
|
var name = ctx.IDENT().getText();
|
||||||
|
var line = ctx.start.getLine();
|
||||||
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
|
var functionDef = functionDefs.get(name);
|
||||||
|
if (functionDef == null) {
|
||||||
|
var error = "Function with name \"" + name + "\" not defined.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the number of arguments matches the number of parameters
|
||||||
|
var argCount = ctx.arguments().expression().size();
|
||||||
|
var paramCount = functionDef.parameters.length;
|
||||||
|
if (argCount != paramCount) {
|
||||||
|
var error = "Function \"" + name + "\" expects " + paramCount + " parameters, but got " + argCount + ".";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate every argument
|
||||||
|
var args = new Expression[argCount];
|
||||||
|
for (int i = 0; i < argCount; i++) {
|
||||||
|
var expression = (Expression) visit(ctx.arguments().expression(i));
|
||||||
|
if (!expression.type.equals(functionDef.parameters[i].type)) {
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + "argument " + i + " Expected " + functionDef.parameters[i].type.getName() + " but got: " + expression.type.getName());
|
||||||
|
}
|
||||||
|
args[i] = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new FunctionCall(name, args);
|
||||||
|
result.type = functionDef.type;
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
return result;
|
return result;
|
||||||
@@ -793,39 +919,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitFunctionCallExpression(KlangParser.FunctionCallExpressionContext ctx) {
|
public Node visitFunctionCallExpression(KlangParser.FunctionCallExpressionContext ctx) {
|
||||||
String name = ctx.functionCall().IDENT().getText();
|
return visit(ctx.functionCall());
|
||||||
int line = ctx.start.getLine();
|
|
||||||
int col = ctx.start.getCharPositionInLine();
|
|
||||||
|
|
||||||
FunctionInformation func = this.funcs.get(name);
|
|
||||||
if (func == null) {
|
|
||||||
String error = "Function with name \"" + name + "\" not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the number of arguments matches the number of parameters
|
|
||||||
int argCount = ctx.functionCall().arguments().expression().size();
|
|
||||||
int paramCount = func.parameters.size();
|
|
||||||
if (argCount != paramCount) {
|
|
||||||
String error = "Function \"" + name + "\" expects " + paramCount + " parameters, but got " + argCount + ".";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate every argument
|
|
||||||
Expression[] args = new Expression[argCount];
|
|
||||||
for (int i = 0; i < argCount; i++) {
|
|
||||||
Expression expression = (Expression) this.visit(ctx.functionCall().arguments().expression(i));
|
|
||||||
if (!expression.type.equals(func.signature[i])) {
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + "argument " + i + " Expected " + func.signature[i].getName() + " but got: " + expression.type.getName());
|
|
||||||
}
|
|
||||||
args[i] = expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
FunctionCall result = new FunctionCall(name, args);
|
|
||||||
result.type = func.returnType;
|
|
||||||
result.line = line;
|
|
||||||
result.col = col;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -835,7 +929,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
int col = ctx.start.getCharPositionInLine();
|
int col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
// Get the corresponding struct definition
|
// Get the corresponding struct definition
|
||||||
var struct = this.structs.get(name);
|
var struct = this.structDefs.get(name);
|
||||||
if (struct == null) {
|
if (struct == null) {
|
||||||
String error = "Struct with name \"" + name + "\" not defined.";
|
String error = "Struct with name \"" + name + "\" not defined.";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
@@ -870,19 +964,52 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitDestroy_statement(KlangParser.Destroy_statementContext ctx) {
|
public Node visitDestroy_statement(KlangParser.Destroy_statementContext ctx) {
|
||||||
String name = ctx.IDENT().getText();
|
var varName = ctx.IDENT().getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
VariableDeclaration var = this.vars.get(name);
|
var variableOrParameter = getVariableOrParameter(varName, line, col);
|
||||||
if (var == null) {
|
ensureReferencesStruct(variableOrParameter, line, col);
|
||||||
String error = "Variable with name \"" + name + "\" not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
Node result = new DestructorCall(name);
|
var result = new DestructorCall(varName);
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private VariableDeclaration getVariableOrParameter(String name, int line, int col) {
|
||||||
|
var variable = vars.get(name);
|
||||||
|
var parameter = params.get(name);
|
||||||
|
if (variable == null && parameter == null) {
|
||||||
|
var error = "Variable or parameter with name \"" + name + "\" not defined.";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return variable != null ? variable : parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureReferencesStruct(VariableDeclaration variableOrParameter, int line, int col) {
|
||||||
|
if (variableOrParameter.type.isPrimitiveType() || enumDefs.containsKey(variableOrParameter.type.getName())) {
|
||||||
|
var error = "Variable or parameter must reference a struct but references " + variableOrParameter.type.getName() + ".";
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type drillType(VariableDeclaration variableOrParameter, String[] path, int line, int col) {
|
||||||
|
try {
|
||||||
|
var structName = variableOrParameter.type.getName();
|
||||||
|
return Helper.drillType(structDefs, structName, path, 0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] createStructPath(List<TerminalNode> identifiers) {
|
||||||
|
// Create a list of member names. This excludes the first entry as it is the base name.
|
||||||
|
var path = new String[identifiers.size() - 1];
|
||||||
|
for (int i = 1; i < identifiers.size(); i++) {
|
||||||
|
path[i - 1] = identifiers.get(i).getText();
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
220
src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java
Normal file
220
src/main/java/de/hsrm/compiler/Klang/GetDefinitions.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
package de.hsrm.compiler.Klang;
|
package de.hsrm.compiler.Klang;
|
||||||
|
|
||||||
// import ANTLR's runtime libraries
|
import de.hsrm.compiler.Klang.helper.*;
|
||||||
import org.antlr.v4.runtime.*;
|
import de.hsrm.compiler.Klang.nodes.EnumDefinition;
|
||||||
import org.antlr.v4.runtime.tree.*;
|
import de.hsrm.compiler.Klang.nodes.FunctionDefinition;
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
import de.hsrm.compiler.Klang.nodes.Node;
|
import de.hsrm.compiler.Klang.nodes.Node;
|
||||||
import de.hsrm.compiler.Klang.nodes.StructDefinition;
|
import de.hsrm.compiler.Klang.nodes.StructDefinition;
|
||||||
import de.hsrm.compiler.Klang.visitors.*;
|
import de.hsrm.compiler.Klang.types.Type;
|
||||||
import de.hsrm.compiler.Klang.helper.*;
|
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 {
|
public class Klang {
|
||||||
|
|
||||||
@@ -87,22 +92,15 @@ public class Klang {
|
|||||||
|
|
||||||
// Context Analysis and DAST generation
|
// Context Analysis and DAST generation
|
||||||
Node root;
|
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 {
|
try {
|
||||||
// Extract information about all functions
|
// Extract information about all definitions
|
||||||
var functionDefinitions = new HashMap<String, FunctionInformation>();
|
new GetDefinitions(functionDefs, structDefs, enumDefs).visit(tree);
|
||||||
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);
|
|
||||||
|
|
||||||
// Create the DAST
|
// Create the DAST
|
||||||
ContextAnalysis ctxAnal = new ContextAnalysis(functionDefinitions, structs);
|
ContextAnalysis ctxAnal = new ContextAnalysis(functionDefs, structDefs, enumDefs);
|
||||||
root = ctxAnal.visit(tree);
|
root = ctxAnal.visit(tree);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(e.getMessage());
|
System.err.println(e.getMessage());
|
||||||
@@ -122,14 +120,18 @@ public class Klang {
|
|||||||
if (evaluate) {
|
if (evaluate) {
|
||||||
// Evaluate the sourcecode and print the result
|
// Evaluate the sourcecode and print the result
|
||||||
System.out.println("\nEvaluating the source code:");
|
System.out.println("\nEvaluating the source code:");
|
||||||
EvalVisitor evalVisitor = new EvalVisitor(structs);
|
EvalVisitor evalVisitor = new EvalVisitor(structDefs);
|
||||||
Value result = root.welcome(evalVisitor);
|
Value result = root.welcome(evalVisitor);
|
||||||
|
if (result.type.equals(Type.getVoidType())) {
|
||||||
|
generateOutput(out, "Result was void");
|
||||||
|
} else {
|
||||||
generateOutput(out, "Result was: " + result.asObject().toString());
|
generateOutput(out, "Result was: " + result.asObject().toString());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate assembler code
|
// Generate assembler code
|
||||||
GenASM genasm = new GenASM(mainName, structs);
|
GenASM genasm = new GenASM(mainName, structDefs);
|
||||||
root.welcome(genasm);
|
root.welcome(genasm);
|
||||||
generateOutput(out, genasm.toAsm());
|
generateOutput(out, genasm.toAsm());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import de.hsrm.compiler.Klang.types.Type;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class Value {
|
public class Value {
|
||||||
|
private final Object value;
|
||||||
public Type type;
|
public Type type;
|
||||||
private Object value;
|
|
||||||
|
|
||||||
public Value(Object value) {
|
public Value(Object value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@@ -17,19 +17,23 @@ public class Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Object asObject() {
|
public Object asObject() {
|
||||||
return this.value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int asInteger() {
|
public int asInteger() {
|
||||||
return (int) this.value;
|
return (int) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double asFloat() {
|
public double asFloat() {
|
||||||
return (double) this.value;
|
return (double) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean asBoolean() {
|
public boolean asBoolean() {
|
||||||
return (boolean) this.value;
|
return (boolean) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char asChar() {
|
||||||
|
return (char) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|||||||
@@ -172,6 +172,10 @@ public class ASM {
|
|||||||
mnemonics.add(new Cqto());
|
mnemonics.add(new Cqto());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void leave() {
|
||||||
|
mnemonics.add(new Leave());
|
||||||
|
}
|
||||||
|
|
||||||
public void ret() {
|
public void ret() {
|
||||||
mnemonics.add(new Ret());
|
mnemonics.add(new Ret());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package de.hsrm.compiler.Klang.asm.mnemonics;
|
||||||
|
|
||||||
|
public class Leave extends NoOperandMnemonic {
|
||||||
|
@Override
|
||||||
|
public String toAsm() {
|
||||||
|
return "leave";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
package de.hsrm.compiler.Klang.nodes;
|
package de.hsrm.compiler.Klang.nodes;
|
||||||
|
|
||||||
import de.hsrm.compiler.Klang.nodes.statements.Statement;
|
|
||||||
import de.hsrm.compiler.Klang.visitors.Visitor;
|
import de.hsrm.compiler.Klang.visitors.Visitor;
|
||||||
|
|
||||||
public class Block extends Node {
|
public class Block extends Node {
|
||||||
|
|
||||||
public Statement[] statements;
|
public Node[] statementsOrFunctionCalls;
|
||||||
|
|
||||||
public Block(Statement[] statements) {
|
public Block(Node[] statements) {
|
||||||
this.statements = statements;
|
this.statementsOrFunctionCalls = statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/java/de/hsrm/compiler/Klang/nodes/EnumValue.java
Normal file
18
src/main/java/de/hsrm/compiler/Klang/nodes/EnumValue.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
package de.hsrm.compiler.Klang.nodes;
|
package de.hsrm.compiler.Klang.nodes;
|
||||||
|
|
||||||
|
import de.hsrm.compiler.Klang.nodes.statements.VariableDeclaration;
|
||||||
import de.hsrm.compiler.Klang.visitors.Visitor;
|
import de.hsrm.compiler.Klang.visitors.Visitor;
|
||||||
|
|
||||||
public class FunctionDefinition extends Node {
|
public class FunctionDefinition extends Node {
|
||||||
|
|
||||||
public String name;
|
public String name;
|
||||||
public Parameter[] parameters;
|
public Parameter[] parameters;
|
||||||
|
public VariableDeclaration[] localVariables;
|
||||||
public Block block;
|
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.name = name;
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
this.block = block;
|
this.block = block;
|
||||||
|
this.localVariables = localVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -9,11 +9,18 @@ public class Program extends Node {
|
|||||||
|
|
||||||
public FunctionDefinition[] funcs;
|
public FunctionDefinition[] funcs;
|
||||||
public Map<String, StructDefinition> structs;
|
public Map<String, StructDefinition> structs;
|
||||||
|
public Map<String, EnumDefinition> enums;
|
||||||
public Expression expression;
|
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.funcs = funcs;
|
||||||
this.structs = structs;
|
this.structs = structs;
|
||||||
|
this.enums = enums;
|
||||||
this.expression = expression;
|
this.expression = expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,12 @@ package de.hsrm.compiler.Klang.nodes.expressions;
|
|||||||
|
|
||||||
import de.hsrm.compiler.Klang.visitors.Visitor;
|
import de.hsrm.compiler.Klang.visitors.Visitor;
|
||||||
|
|
||||||
public class StructFieldAccessExpression extends Expression {
|
public class MemberAccessExpression extends Expression {
|
||||||
public String varName;
|
public String varName;
|
||||||
public String structName;
|
public String structName;
|
||||||
public String[] path;
|
public String[] path;
|
||||||
|
|
||||||
public StructFieldAccessExpression(String varName, String structName, String[] path) {
|
public MemberAccessExpression(String varName, String structName, String[] path) {
|
||||||
this.varName = varName;
|
this.varName = varName;
|
||||||
this.structName = structName;
|
this.structName = structName;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@@ -11,6 +11,10 @@ public class ReturnStatement extends Statement {
|
|||||||
this.expression = expression;
|
this.expression = expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReturnStatement() {
|
||||||
|
this.expression = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <R> R welcome(Visitor<R> v) {
|
public <R> R welcome(Visitor<R> v) {
|
||||||
return v.visit(this);
|
return v.visit(this);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package de.hsrm.compiler.Klang.types;
|
package de.hsrm.compiler.Klang.types;
|
||||||
|
|
||||||
|
import de.hsrm.compiler.Klang.Value;
|
||||||
|
|
||||||
public class BooleanType extends PrimitiveType {
|
public class BooleanType extends PrimitiveType {
|
||||||
|
|
||||||
private static BooleanType instance = null;
|
private static BooleanType instance = null;
|
||||||
@@ -30,7 +32,11 @@ public class BooleanType extends PrimitiveType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Every remaining type will throw a RuntimeException
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
47
src/main/java/de/hsrm/compiler/Klang/types/CharType.java
Normal file
47
src/main/java/de/hsrm/compiler/Klang/types/CharType.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package de.hsrm.compiler.Klang.types;
|
package de.hsrm.compiler.Klang.types;
|
||||||
|
|
||||||
|
import de.hsrm.compiler.Klang.Value;
|
||||||
|
|
||||||
public class FloatType extends NumericType {
|
public class FloatType extends NumericType {
|
||||||
|
|
||||||
private static FloatType instance = null;
|
private static FloatType instance = null;
|
||||||
@@ -34,7 +36,11 @@ public class FloatType extends NumericType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Every remaining type will throw a RuntimeException
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package de.hsrm.compiler.Klang.types;
|
package de.hsrm.compiler.Klang.types;
|
||||||
|
|
||||||
|
import de.hsrm.compiler.Klang.Value;
|
||||||
|
|
||||||
public class IntegerType extends NumericType {
|
public class IntegerType extends NumericType {
|
||||||
|
|
||||||
private static IntegerType instance = null;
|
private static IntegerType instance = null;
|
||||||
@@ -33,8 +35,17 @@ public class IntegerType extends NumericType {
|
|||||||
return Type.getFloatType();
|
return Type.getFloatType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (that.equals(Type.getCharType())) {
|
||||||
|
return Type.getCharType();
|
||||||
|
}
|
||||||
|
|
||||||
// Every remaining type will throw a RuntimeException
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
53
src/main/java/de/hsrm/compiler/Klang/types/NamedType.java
Normal file
53
src/main/java/de/hsrm/compiler/Klang/types/NamedType.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package de.hsrm.compiler.Klang.types;
|
package de.hsrm.compiler.Klang.types;
|
||||||
|
|
||||||
|
import de.hsrm.compiler.Klang.Value;
|
||||||
|
|
||||||
public class NullType extends Type {
|
public class NullType extends Type {
|
||||||
|
|
||||||
private static NullType instance = null;
|
private static NullType instance = null;
|
||||||
@@ -21,13 +23,18 @@ public class NullType extends Type {
|
|||||||
public Type combine(Type that) {
|
public Type combine(Type that) {
|
||||||
// You can not combine null with a primitive type
|
// You can not combine null with a primitive type
|
||||||
if (that.isPrimitiveType()) {
|
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
|
// Everything else combines with null to the type it was before
|
||||||
return that;
|
return that;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean valuesEqual(Value a, Value b) {
|
||||||
|
return a.asObject() == b.asObject();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPrimitiveType() {
|
public boolean isPrimitiveType() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
package de.hsrm.compiler.Klang.types;
|
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 {
|
public abstract class Type {
|
||||||
|
|
||||||
// Returns an instance of IntegerType
|
// Returns an instance of IntegerType
|
||||||
@@ -16,22 +23,33 @@ public abstract class Type {
|
|||||||
return FloatType.getType();
|
return FloatType.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static CharType getCharType() {
|
||||||
|
return CharType.getType();
|
||||||
|
}
|
||||||
|
|
||||||
public static NullType getNullType() {
|
public static NullType getNullType() {
|
||||||
return NullType.getType();
|
return NullType.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static VoidType getVoidType() {
|
||||||
|
return VoidType.getType();
|
||||||
|
}
|
||||||
|
|
||||||
public static Type getByName(String name) {
|
public static Type getByName(String name) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "bool": return getBooleanType();
|
case "bool": return getBooleanType();
|
||||||
case "int": return getIntegerType();
|
case "int": return getIntegerType();
|
||||||
case "float": return getFloatType();
|
case "float": return getFloatType();
|
||||||
|
case "char": return getCharType();
|
||||||
case "null": return getNullType();
|
case "null": return getNullType();
|
||||||
default: return new StructType(name);
|
case "void": return getVoidType();
|
||||||
|
default: return new NamedType(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String getName();
|
public abstract String getName();
|
||||||
public abstract Type combine(Type that);
|
public abstract Type combine(Type that);
|
||||||
|
public abstract boolean valuesEqual(Value a, Value b);
|
||||||
public abstract boolean isPrimitiveType();
|
public abstract boolean isPrimitiveType();
|
||||||
public abstract boolean isNumericType();
|
public abstract boolean isNumericType();
|
||||||
}
|
}
|
||||||
45
src/main/java/de/hsrm/compiler/Klang/types/VoidType.java
Normal file
45
src/main/java/de/hsrm/compiler/Klang/types/VoidType.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,17 +4,13 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import de.hsrm.compiler.Klang.Value;
|
import de.hsrm.compiler.Klang.Value;
|
||||||
import de.hsrm.compiler.Klang.nodes.Block;
|
import de.hsrm.compiler.Klang.nodes.*;
|
||||||
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.expressions.*;
|
import de.hsrm.compiler.Klang.nodes.expressions.*;
|
||||||
import de.hsrm.compiler.Klang.nodes.loops.DoWhileLoop;
|
import de.hsrm.compiler.Klang.nodes.loops.DoWhileLoop;
|
||||||
import de.hsrm.compiler.Klang.nodes.loops.ForLoop;
|
import de.hsrm.compiler.Klang.nodes.loops.ForLoop;
|
||||||
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
|
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
|
||||||
import de.hsrm.compiler.Klang.nodes.statements.*;
|
import de.hsrm.compiler.Klang.nodes.statements.*;
|
||||||
|
import de.hsrm.compiler.Klang.types.NullType;
|
||||||
import de.hsrm.compiler.Klang.types.Type;
|
import de.hsrm.compiler.Klang.types.Type;
|
||||||
|
|
||||||
public class EvalVisitor implements Visitor<Value> {
|
public class EvalVisitor implements Visitor<Value> {
|
||||||
@@ -50,26 +46,17 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Value visit(EqualityExpression e) {
|
public Value visit(CharExpression e) {
|
||||||
Value lhs = e.lhs.welcome(this);
|
return new Value(e.c, Type.getCharType());
|
||||||
Value rhs = e.rhs.welcome(this);
|
}
|
||||||
Type resultType = Type.getBooleanType();
|
|
||||||
Type combineType = lhs.type.combine(rhs.type);
|
|
||||||
|
|
||||||
switch(combineType.getName()) {
|
@Override
|
||||||
case "bool": {
|
public Value visit(EqualityExpression e) {
|
||||||
return new Value(lhs.asBoolean() == rhs.asBoolean(), resultType);
|
var lhs = e.lhs.welcome(this);
|
||||||
}
|
var rhs = e.rhs.welcome(this);
|
||||||
case "int": {
|
var combinedType = lhs.type.combine(rhs.type);
|
||||||
return new Value(lhs.asInteger() == rhs.asInteger(), resultType);
|
|
||||||
}
|
return new Value(combinedType.valuesEqual(lhs, rhs), Type.getBooleanType());
|
||||||
case "float": {
|
|
||||||
return new Value(lhs.asFloat() == rhs.asFloat(), resultType);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return new Value(lhs.asObject() == rhs.asObject(), resultType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -413,13 +400,16 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Value visit(ReturnStatement e) {
|
public Value visit(ReturnStatement e) {
|
||||||
|
if (e.expression == null) {
|
||||||
|
return new Value(null, Type.getVoidType());
|
||||||
|
}
|
||||||
return e.expression.welcome(this);
|
return e.expression.welcome(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Value visit(Block e) {
|
public Value visit(Block e) {
|
||||||
for (var stmt : e.statements) {
|
for (var stmt : e.statementsOrFunctionCalls) {
|
||||||
Value result = stmt.welcome(this);
|
var result = stmt.welcome(this);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -447,7 +437,7 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
this.env = newEnv;
|
this.env = newEnv;
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
Value result = func.block.welcome(this);
|
var result = func.block.welcome(this);
|
||||||
|
|
||||||
// Das alte env wiederherstellen
|
// Das alte env wiederherstellen
|
||||||
this.env = oldEnv;
|
this.env = oldEnv;
|
||||||
@@ -471,6 +461,16 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Value visit(EnumDefinition e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Value visit(EnumValue e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Value visit(StructDefinition e) {
|
public Value visit(StructDefinition e) {
|
||||||
// We get these from a previous visitor
|
// We get these from a previous visitor
|
||||||
@@ -484,7 +484,7 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Value visit(StructFieldAccessExpression e) {
|
public Value visit(MemberAccessExpression e) {
|
||||||
Value var = this.env.get(e.varName);
|
Value var = this.env.get(e.varName);
|
||||||
Map<String, Value> struct = var.asStruct();
|
Map<String, Value> struct = var.asStruct();
|
||||||
|
|
||||||
@@ -496,6 +496,11 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
return currentValue;
|
return currentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Value visit(EnumAccessExpression e) {
|
||||||
|
return new Value(e.enumValueName, e.type);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Value visit(ConstructorCall e) {
|
public Value visit(ConstructorCall e) {
|
||||||
StructDefinition structDef = this.structs.get(e.structName);
|
StructDefinition structDef = this.structs.get(e.structName);
|
||||||
@@ -506,12 +511,17 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
struct.put(structDef.fields[i].name, arg);
|
struct.put(structDef.fields[i].name, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Value(struct);
|
var structValue = new Value(struct);
|
||||||
|
structValue.type = structDef.type;
|
||||||
|
|
||||||
|
return structValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Value visit(NullExpression e) {
|
public Value visit(NullExpression e) {
|
||||||
return null;
|
var nullValue = new Value(null);
|
||||||
|
nullValue.type = new NullType();
|
||||||
|
return nullValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
package de.hsrm.compiler.Klang.visitors;
|
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.asm.ASM;
|
||||||
import de.hsrm.compiler.Klang.helper.Helper;
|
import de.hsrm.compiler.Klang.helper.Helper;
|
||||||
import de.hsrm.compiler.Klang.nodes.*;
|
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.nodes.statements.*;
|
||||||
import de.hsrm.compiler.Klang.types.Type;
|
import de.hsrm.compiler.Klang.types.Type;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class GenASM implements Visitor<Void> {
|
public class GenASM implements Visitor<Void> {
|
||||||
private class FloatWriter {
|
private static class FloatWriter {
|
||||||
private StringBuilder sb = new StringBuilder();
|
private final StringBuilder sb = new StringBuilder();
|
||||||
private int id = -1;
|
private int id = -1;
|
||||||
|
|
||||||
public String getFloat(double d) {
|
public String getFloat(double d) {
|
||||||
Long longBits = Double.doubleToRawLongBits(d);
|
long longBits = Double.doubleToRawLongBits(d);
|
||||||
String binary = Long.toBinaryString(longBits);
|
StringBuilder binary = new StringBuilder(Long.toBinaryString(longBits));
|
||||||
int padCount = 64 - binary.length();
|
int padCount = 64 - binary.length();
|
||||||
while (padCount > 0) {
|
while (padCount > 0) {
|
||||||
binary = "0" + binary;
|
binary.insert(0, "0");
|
||||||
padCount--;
|
padCount--;
|
||||||
}
|
}
|
||||||
String upper = binary.substring(0, 32);
|
String upper = binary.substring(0, 32);
|
||||||
@@ -63,49 +59,19 @@ public class GenASM implements Visitor<Void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ASM asm;
|
private final ASM asm;
|
||||||
private FloatWriter fw = new FloatWriter();
|
private final FloatWriter fw = new FloatWriter();
|
||||||
private String mainName;
|
private final String mainName;
|
||||||
|
|
||||||
Map<String, Integer> env = new HashMap<>();
|
Map<String, Integer> env = new HashMap<>();
|
||||||
Map<String, StructDefinition> structs;
|
Map<String, StructDefinition> structs;
|
||||||
Set<String> vars;
|
|
||||||
String[] registers = { "%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9" };
|
String[] registers = { "%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9" };
|
||||||
String[] floatRegisters = { "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" };
|
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) {
|
private int lCount = 0;
|
||||||
boolean lhsIsFloat = lhs.type.equals(Type.getFloatType());
|
private int currentFunctionStartLabel = 0;
|
||||||
boolean rhsIsFloat = rhs.type.equals(Type.getFloatType());
|
private long bytesToClearFromTheStack = 0;
|
||||||
if (lhsIsFloat && rhsIsFloat) {
|
private FunctionDefinition currentFunctionDef;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public GenASM(String mainName, Map<String, StructDefinition> structs) {
|
public GenASM(String mainName, Map<String, StructDefinition> structs) {
|
||||||
this.mainName = mainName;
|
this.mainName = mainName;
|
||||||
@@ -113,10 +79,6 @@ public class GenASM implements Visitor<Void> {
|
|||||||
this.asm = new ASM();
|
this.asm = new ASM();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenASM(Map<String, StructDefinition> structs) {
|
|
||||||
this("main", structs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toAsm() {
|
public String toAsm() {
|
||||||
return asm.toAsm();
|
return asm.toAsm();
|
||||||
}
|
}
|
||||||
@@ -140,6 +102,12 @@ public class GenASM implements Visitor<Void> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visit(CharExpression e) {
|
||||||
|
asm.mov("q", "$" + ((int) e.c), "%rax");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(Variable e) {
|
public Void visit(Variable e) {
|
||||||
if (e.type.equals(Type.getFloatType())) {
|
if (e.type.equals(Type.getFloatType())) {
|
||||||
@@ -330,10 +298,19 @@ public class GenASM implements Visitor<Void> {
|
|||||||
@Override
|
@Override
|
||||||
public Void visit(ModuloExpression e) {
|
public Void visit(ModuloExpression e) {
|
||||||
e.lhs.welcome(this);
|
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");
|
asm.push("q", "%rax");
|
||||||
e.rhs.welcome(this);
|
e.rhs.welcome(this);
|
||||||
asm.mov("q", "%rax", "%rbx");
|
asm.mov("q", "%rax", "%rbx");
|
||||||
asm.pop("q", "%rax");
|
asm.pop("q", "%rax");
|
||||||
|
|
||||||
|
// Which we remove afterwards
|
||||||
|
asm.add("q", "$8", "%rsp");
|
||||||
|
|
||||||
asm.cqto();
|
asm.cqto();
|
||||||
asm.idiv("q", "%rbx");
|
asm.idiv("q", "%rbx");
|
||||||
asm.mov("q", "%rdx", "%rax");
|
asm.mov("q", "%rdx", "%rax");
|
||||||
@@ -361,7 +338,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
// Werte LHS aus
|
// Werte LHS aus
|
||||||
// Wenn LHS != 0 bedeutet das true
|
// 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);
|
e.lhs.welcome(this);
|
||||||
asm.cmp("q", 0, "%rax");
|
asm.cmp("q", 0, "%rax");
|
||||||
asm.jne(lblTrue);
|
asm.jne(lblTrue);
|
||||||
@@ -379,11 +356,11 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.mov("q", 1, "%rax");
|
asm.mov("q", 1, "%rax");
|
||||||
asm.jmp(lblEnd);
|
asm.jmp(lblEnd);
|
||||||
|
|
||||||
// Die Expressoin wertet zu false aus
|
// Die Expression wertet zu false aus
|
||||||
asm.label(lblFalse);
|
asm.label(lblFalse);
|
||||||
asm.mov("q", 0, "%rax");
|
asm.mov("q", 0, "%rax");
|
||||||
|
|
||||||
// Das hier ist das ende
|
// Das hier ist das Ende
|
||||||
asm.label(lblEnd);
|
asm.label(lblEnd);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -396,7 +373,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
// Werte LHS aus
|
// Werte LHS aus
|
||||||
// Wenn LHS == 0, bedeutet das false
|
// 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);
|
e.lhs.welcome(this);
|
||||||
asm.cmp("q", 0, "%rax");
|
asm.cmp("q", 0, "%rax");
|
||||||
asm.je(lblFalse);
|
asm.je(lblFalse);
|
||||||
@@ -414,11 +391,11 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.mov("q", 1, "%rax");
|
asm.mov("q", 1, "%rax");
|
||||||
asm.jmp(lblEnd);
|
asm.jmp(lblEnd);
|
||||||
|
|
||||||
// Die Expressoin wertet zu false aus
|
// Die Expression wertet zu false aus
|
||||||
asm.label(lblFalse);
|
asm.label(lblFalse);
|
||||||
asm.mov("q", 0, "%rax");
|
asm.mov("q", 0, "%rax");
|
||||||
|
|
||||||
// Das hier ist das ende
|
// Das hier ist das Ende
|
||||||
asm.label(lblEnd);
|
asm.label(lblEnd);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -428,9 +405,9 @@ public class GenASM implements Visitor<Void> {
|
|||||||
int lblFalse = ++lCount;
|
int lblFalse = ++lCount;
|
||||||
int lblEnd = ++lCount;
|
int lblEnd = ++lCount;
|
||||||
|
|
||||||
// Werte LHS aus
|
// Werte LHS aus.
|
||||||
// Wenn LHS != 0 bedeutet das true, also jumpe zum false Teil
|
// Wenn LHS != 0 bedeutet das true, also springe zum false Teil.
|
||||||
// Wenn nicht, falle durch zum true Teil
|
// Wenn nicht, falle durch zum true Teil.
|
||||||
e.lhs.welcome(this);
|
e.lhs.welcome(this);
|
||||||
asm.cmp("q", 0, "%rax");
|
asm.cmp("q", 0, "%rax");
|
||||||
asm.jne(lblFalse);
|
asm.jne(lblFalse);
|
||||||
@@ -554,49 +531,76 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(ReturnStatement e) {
|
public Void visit(ReturnStatement e) {
|
||||||
|
if (e.expression != null) {
|
||||||
e.expression.welcome(this);
|
e.expression.welcome(this);
|
||||||
asm.mov("q", "%rbp", "%rsp");
|
}
|
||||||
asm.pop("q", "%rbp");
|
|
||||||
|
// 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();
|
asm.ret();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(Block e) {
|
public Void visit(Block e) {
|
||||||
for (var statement : e.statements) {
|
for (var statementOrFunctionCall : e.statementsOrFunctionCalls) {
|
||||||
statement.welcome(this);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(FunctionDefinition e) {
|
public Void visit(FunctionDefinition e) {
|
||||||
int lblStart = ++lCount;
|
// If the user chooses "main" as one of his function names then
|
||||||
this.currentFunctionStartLabel = lblStart;
|
// rename it and hope that they didn't use the renamed function name
|
||||||
this.currentFunctionParams = e.parameters;
|
// 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.functionHead(e.name);
|
||||||
asm.push("q", "%rbp");
|
asm.push("q", "%rbp");
|
||||||
asm.mov("q", "%rsp", "%rbp");
|
asm.mov("q", "%rsp", "%rbp");
|
||||||
asm.label(lblStart);
|
asm.label(lblStart);
|
||||||
|
|
||||||
// hole die anzahl der lokalen variablen
|
// Create a new environment
|
||||||
this.vars = new TreeSet<String>();
|
env = new HashMap<>();
|
||||||
HashMap<String, Type> types = new HashMap<String, Type>();
|
|
||||||
GetVars getvars = new GetVars(vars, types);
|
|
||||||
getvars.visit(e);
|
|
||||||
|
|
||||||
// Erzeuge ein environment
|
// Remember the offsets of the arguments that were placed on the stack.
|
||||||
this.env = new HashMap<String, Integer>();
|
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
|
// Add arguments that were passed via the stack to the environment
|
||||||
int offset = 16; // Per Stack übergebene Parameter liegen über unserem BSP
|
|
||||||
int ri = 0;
|
|
||||||
int fi = 0;
|
|
||||||
// Per stack übergebene variablen in env registrieren
|
|
||||||
var registerParameters = new ArrayList<Parameter>();
|
var registerParameters = new ArrayList<Parameter>();
|
||||||
for (int i = 0; i < e.parameters.length; i++) {
|
for (int i = 0; i < e.parameters.length; i++) {
|
||||||
if (e.parameters[i].type.equals(Type.getFloatType())) {
|
if (e.parameters[i].type.equals(Type.getFloatType())) {
|
||||||
if (fi >= this.floatRegisters.length) {
|
if (fi >= floatRegisters.length) {
|
||||||
// parameter is on stack already
|
// parameter is on stack already
|
||||||
env.put(e.parameters[i].name, offset);
|
env.put(e.parameters[i].name, offset);
|
||||||
offset += 8;
|
offset += 8;
|
||||||
@@ -606,7 +610,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
fi++;
|
fi++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ri >= this.registers.length) {
|
if (ri >= registers.length) {
|
||||||
// parameter is on stack already
|
// parameter is on stack already
|
||||||
env.put(e.parameters[i].name, offset);
|
env.put(e.parameters[i].name, offset);
|
||||||
offset += 8;
|
offset += 8;
|
||||||
@@ -621,29 +625,58 @@ public class GenASM implements Visitor<Void> {
|
|||||||
offset = 0;
|
offset = 0;
|
||||||
ri = 0;
|
ri = 0;
|
||||||
fi = 0;
|
fi = 0;
|
||||||
|
|
||||||
|
// Push arguments that were passed via registers onto the stack
|
||||||
|
// and add them to the environment.
|
||||||
for (var param: registerParameters) {
|
for (var param: registerParameters) {
|
||||||
if (param.type.equals(Type.getFloatType())) {
|
if (param.type.equals(Type.getFloatType())) {
|
||||||
asm.mov("q", this.floatRegisters[fi], "%rax");
|
asm.mov("q", floatRegisters[fi], "%rax");
|
||||||
asm.push("q", "%rax");
|
asm.push("q", "%rax");
|
||||||
offset -= 8;
|
offset -= 8;
|
||||||
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
|
env.put(param.name, offset); // negative, liegt unter aktuellem BP
|
||||||
fi++;
|
fi++;
|
||||||
} else {
|
} else {
|
||||||
asm.push("q", this.registers[ri]);
|
asm.push("q", registers[ri]);
|
||||||
offset -= 8;
|
offset -= 8;
|
||||||
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
|
env.put(param.name, offset); // negative, liegt unter aktuellem BP
|
||||||
ri++;
|
ri++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserviere Platz auf dem Stack für jede lokale variable
|
// Reserve memory on the stack for the local variables.
|
||||||
for (String lok_var : vars) {
|
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;
|
offset -= 8;
|
||||||
asm.push("q", 0);
|
env.put(localVariable.name, offset);
|
||||||
this.env.put(lok_var, 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);
|
e.block.welcome(this);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,13 +697,16 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
// push args into local var locations, last arg is on top 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--) {
|
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;
|
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) {
|
if (e.arguments.length > 0) {
|
||||||
// Mapping arguments index -> xmm registers index
|
// Mapping arguments index -> xmm registers index
|
||||||
@@ -681,9 +717,6 @@ public class GenASM implements Visitor<Void> {
|
|||||||
int[] rIdxs = new int[this.registers.length];
|
int[] rIdxs = new int[this.registers.length];
|
||||||
int ri = -1;
|
int ri = -1;
|
||||||
|
|
||||||
// Mapping arguments index -> stack
|
|
||||||
ArrayList<Integer> stackIdxs = new ArrayList<Integer>();
|
|
||||||
|
|
||||||
// Go through arguments
|
// Go through arguments
|
||||||
// sort them into the memory regions they go when being passed to functions
|
// sort them into the memory regions they go when being passed to functions
|
||||||
for (int i = 0; i < e.arguments.length; i++) {
|
for (int i = 0; i < e.arguments.length; i++) {
|
||||||
@@ -695,7 +728,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
xmmIdxs[fi] = i;
|
xmmIdxs[fi] = i;
|
||||||
} else {
|
} else {
|
||||||
// Float onto stack
|
// Float onto stack
|
||||||
stackIdxs.add(i);
|
stackIndices.add(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ri < this.registers.length - 1) {
|
if (ri < this.registers.length - 1) {
|
||||||
@@ -704,14 +737,39 @@ public class GenASM implements Visitor<Void> {
|
|||||||
rIdxs[ri] = i;
|
rIdxs[ri] = i;
|
||||||
} else {
|
} else {
|
||||||
// bool/int onto stack
|
// bool/int onto stack
|
||||||
stackIdxs.add(i);
|
stackIndices.add(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Welcome the arguments in order, push everything onto the stack
|
// Make sure that the stack is aligned after all arguments
|
||||||
for (var arg : e.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);
|
arg.welcome(this);
|
||||||
|
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
asm.add("q", "$8", "%rsp");
|
||||||
|
}
|
||||||
|
|
||||||
if (arg.type.equals(Type.getFloatType())) {
|
if (arg.type.equals(Type.getFloatType())) {
|
||||||
asm.mov("q", "%xmm0", "%rax");
|
asm.mov("q", "%xmm0", "%rax");
|
||||||
asm.push("q", "%rax");
|
asm.push("q", "%rax");
|
||||||
@@ -734,21 +792,38 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.mov("q", rspOffset, "%rsp", this.registers[i]);
|
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);
|
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;
|
stackStartOffset -= 8;
|
||||||
int indexInArguments = stackIdxs.get(i);
|
int indexInArguments = stackIndices.get(i);
|
||||||
int rspOffset = (((e.arguments.length - indexInArguments) - 1) * 8);
|
int rspOffset = (((e.arguments.length - indexInArguments) - 1) * 8);
|
||||||
asm.mov("q", rspOffset, "%rsp", "%rax");
|
asm.mov("q", rspOffset, "%rsp", "%rax");
|
||||||
asm.mov("q", "%rax", stackStartOffset, "%rsp");
|
asm.mov("q", "%rax", stackStartOffset, "%rsp");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rescue 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");
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -773,11 +848,21 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(Parameter e) {
|
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
|
// in the function definition visitor
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visit(EnumDefinition e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visit(EnumValue e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(StructDefinition e) {
|
public Void visit(StructDefinition e) {
|
||||||
// We get these from a previous visitor
|
// We get these from a previous visitor
|
||||||
@@ -791,7 +876,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(StructFieldAccessExpression e) {
|
public Void visit(MemberAccessExpression e) {
|
||||||
var structDef = this.structs.get(e.structName);
|
var structDef = this.structs.get(e.structName);
|
||||||
int offset = this.env.get(e.varName);
|
int offset = this.env.get(e.varName);
|
||||||
|
|
||||||
@@ -816,12 +901,39 @@ public class GenASM implements Visitor<Void> {
|
|||||||
return null;
|
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
|
@Override
|
||||||
public Void visit(ConstructorCall e) {
|
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
|
// 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);
|
arg.welcome(this);
|
||||||
|
|
||||||
|
if (i % 2 != 0) {
|
||||||
|
asm.add("q", "$8", "%rsp");
|
||||||
|
}
|
||||||
|
|
||||||
// move float values from xmm0 to rax first
|
// move float values from xmm0 to rax first
|
||||||
if (arg.type.equals(Type.getFloatType())) {
|
if (arg.type.equals(Type.getFloatType())) {
|
||||||
asm.mov("q", "%xmm0", "%rax");
|
asm.mov("q", "%xmm0", "%rax");
|
||||||
@@ -840,6 +952,11 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.pop("q", Helper.getFieldOffset(structDef, i) + "(%rax)");
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,7 +981,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
e.expression.welcome(this);
|
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())) {
|
if (e.expression.type.equals(Type.getFloatType())) {
|
||||||
asm.mov("q", "%xmm0", "%rax");
|
asm.mov("q", "%xmm0", "%rax");
|
||||||
}
|
}
|
||||||
@@ -889,4 +1006,42 @@ public class GenASM implements Visitor<Void> {
|
|||||||
return null;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -68,6 +68,12 @@ public class PrettyPrintVisitor implements Visitor<Void> {
|
|||||||
ex.nl();
|
ex.nl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (var enumDef: e.enums.values()) {
|
||||||
|
enumDef.welcome(this);
|
||||||
|
ex.nl();
|
||||||
|
ex.nl();
|
||||||
|
}
|
||||||
|
|
||||||
e.expression.welcome(this);
|
e.expression.welcome(this);
|
||||||
ex.write(";");
|
ex.write(";");
|
||||||
return null;
|
return null;
|
||||||
@@ -91,6 +97,14 @@ public class PrettyPrintVisitor implements Visitor<Void> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visit(CharExpression e) {
|
||||||
|
ex.write("'");
|
||||||
|
ex.write(e.c);
|
||||||
|
ex.write("'");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(EqualityExpression e) {
|
public Void visit(EqualityExpression e) {
|
||||||
ex.write("(");
|
ex.write("(");
|
||||||
@@ -305,7 +319,10 @@ public class PrettyPrintVisitor implements Visitor<Void> {
|
|||||||
@Override
|
@Override
|
||||||
public Void visit(ReturnStatement e) {
|
public Void visit(ReturnStatement e) {
|
||||||
ex.write("return");
|
ex.write("return");
|
||||||
|
if (e.expression != null) {
|
||||||
|
ex.write(" ");
|
||||||
e.expression.welcome(this);
|
e.expression.welcome(this);
|
||||||
|
}
|
||||||
ex.write(";");
|
ex.write(";");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -314,7 +331,7 @@ public class PrettyPrintVisitor implements Visitor<Void> {
|
|||||||
public Void visit(Block e) {
|
public Void visit(Block e) {
|
||||||
ex.write("{");
|
ex.write("{");
|
||||||
ex.addIndent();
|
ex.addIndent();
|
||||||
for (Statement stmt : e.statements) {
|
for (var stmt : e.statementsOrFunctionCalls) {
|
||||||
ex.nl();
|
ex.nl();
|
||||||
stmt.welcome(this);
|
stmt.welcome(this);
|
||||||
if (stmt.getClass() == VariableAssignment.class || stmt.getClass() == VariableDeclaration.class) {
|
if (stmt.getClass() == VariableAssignment.class || stmt.getClass() == VariableDeclaration.class) {
|
||||||
@@ -377,6 +394,28 @@ public class PrettyPrintVisitor implements Visitor<Void> {
|
|||||||
return null;
|
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
|
@Override
|
||||||
public Void visit(StructDefinition e) {
|
public Void visit(StructDefinition e) {
|
||||||
ex.write("struct " + e.name + " {");
|
ex.write("struct " + e.name + " {");
|
||||||
@@ -398,7 +437,7 @@ public class PrettyPrintVisitor implements Visitor<Void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(StructFieldAccessExpression e) {
|
public Void visit(MemberAccessExpression e) {
|
||||||
ex.write(e.varName);
|
ex.write(e.varName);
|
||||||
for (int i = 0; i < e.path.length; i++) {
|
for (int i = 0; i < e.path.length; i++) {
|
||||||
ex.write(".");
|
ex.write(".");
|
||||||
@@ -407,6 +446,15 @@ public class PrettyPrintVisitor implements Visitor<Void> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visit(EnumAccessExpression e) {
|
||||||
|
ex.write(e.enumName);
|
||||||
|
ex.write(".");
|
||||||
|
ex.write(e.enumValueName);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(ConstructorCall e) {
|
public Void visit(ConstructorCall e) {
|
||||||
ex.write("create " + e.structName + "(");
|
ex.write("create " + e.structName + "(");
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
package de.hsrm.compiler.Klang.visitors;
|
package de.hsrm.compiler.Klang.visitors;
|
||||||
|
|
||||||
import de.hsrm.compiler.Klang.nodes.Block;
|
import de.hsrm.compiler.Klang.nodes.*;
|
||||||
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.expressions.*;
|
import de.hsrm.compiler.Klang.nodes.expressions.*;
|
||||||
import de.hsrm.compiler.Klang.nodes.loops.*;
|
import de.hsrm.compiler.Klang.nodes.loops.*;
|
||||||
import de.hsrm.compiler.Klang.nodes.statements.*;
|
import de.hsrm.compiler.Klang.nodes.statements.*;
|
||||||
@@ -17,6 +12,7 @@ public interface Visitor<R> {
|
|||||||
R visit(IntegerExpression e);
|
R visit(IntegerExpression e);
|
||||||
R visit(FloatExpression e);
|
R visit(FloatExpression e);
|
||||||
R visit(BooleanExpression e);
|
R visit(BooleanExpression e);
|
||||||
|
R visit(CharExpression e);
|
||||||
R visit(Variable e);
|
R visit(Variable e);
|
||||||
R visit(AdditionExpression e);
|
R visit(AdditionExpression e);
|
||||||
R visit(EqualityExpression e);
|
R visit(EqualityExpression e);
|
||||||
@@ -42,9 +38,12 @@ public interface Visitor<R> {
|
|||||||
R visit(FunctionCall e);
|
R visit(FunctionCall e);
|
||||||
R visit(Program e);
|
R visit(Program e);
|
||||||
R visit(Parameter e);
|
R visit(Parameter e);
|
||||||
|
R visit(EnumDefinition e);
|
||||||
|
R visit(EnumValue e);
|
||||||
R visit(StructDefinition e);
|
R visit(StructDefinition e);
|
||||||
R visit(StructField e);
|
R visit(StructField e);
|
||||||
R visit(StructFieldAccessExpression e);
|
R visit(MemberAccessExpression e);
|
||||||
|
R visit(EnumAccessExpression e);
|
||||||
R visit(ConstructorCall e);
|
R visit(ConstructorCall e);
|
||||||
R visit(NullExpression e);
|
R visit(NullExpression e);
|
||||||
R visit(DestructorCall e);
|
R visit(DestructorCall e);
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ public class AndTest {
|
|||||||
ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 && 2; } foo();");
|
ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 && 2; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||||
assertEquals("Error in line 1:30 && is only defined for bool.", e.getMessage());
|
assertEquals("Error in line 1:30 && is only defined for bool.", e.getMessage());
|
||||||
|
|||||||
164
src/test/java/CharTest.java
Normal file
164
src/test/java/CharTest.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,18 +13,20 @@ public class ConstructorCallTest {
|
|||||||
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create schwurbel(1); } foo();");
|
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create schwurbel(1); } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||||
assertEquals("Error in line 1:52 Struct with name \"schwurbel\" not defined.", e.getMessage());
|
assertEquals("Error in line 1:52 Struct with name \"schwurbel\" not defined.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void numConstructorParameterMissmatch() {
|
void numConstructorParametermismatch() {
|
||||||
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(1, false); } foo();");
|
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(1, false); } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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());
|
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();");
|
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): bar { return create bar(false); } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,9 +13,10 @@ public class DestroyStatementTest {
|
|||||||
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { destroy x; return 1; } foo();");
|
ParseTree tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { destroy x; return 1; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,10 +13,11 @@ public class FieldAssignmentTest {
|
|||||||
ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { str.a = 1; return 1; } foo();");
|
ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { str.a = 1; return 1; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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
|
@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();");
|
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 funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,8 @@ public class FunctionCallTest {
|
|||||||
ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } bar();");
|
ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } bar();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||||
assertEquals("Error in line 1:34 Function with name \"bar\" not defined.", e.getMessage());
|
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);");
|
ParseTree tree = Helper.prepareParser("function foo(): int { return 1; } foo(5);");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||||
assertEquals("Error in line 1:34 Function \"foo\" expects 0 parameters, but got 1.", e.getMessage());
|
assertEquals("Error in line 1:34 Function \"foo\" expects 0 parameters, but got 1.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parameterTypeMissmatch() {
|
void parameterTypeMismatch() {
|
||||||
ParseTree tree = Helper.prepareParser("function foo(x: int): int { return x; } foo(false);");
|
ParseTree tree = Helper.prepareParser("function foo(x: int): int { return x; } foo(false);");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||||
assertEquals("Error in line 1:40 argument 0 Expected int but got: bool", e.getMessage());
|
assertEquals("Error in line 1:40 argument 0 Expected int but got: bool", e.getMessage());
|
||||||
|
|||||||
@@ -1,32 +1,119 @@
|
|||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import de.hsrm.compiler.Klang.ContextAnalysis;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import org.antlr.v4.runtime.tree.ParseTree;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import de.hsrm.compiler.Klang.ContextAnalysis;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class FunctionDefinitionTest {
|
public class FunctionDefinitionTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void typeNotDefined() {
|
void shouldNotThrowIfReturnTypeIsReferringToAnEnum() {
|
||||||
ParseTree tree = Helper.prepareParser("function foo(): schwurbel { return 1; } foo();");
|
// given
|
||||||
var funcs = Helper.getFuncs(tree);
|
var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: int): bar { return bar.A; } foo(1);");
|
||||||
var structs = Helper.getStructs(tree);
|
var ctxAnal = new ContextAnalysis(
|
||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
|
Helper.getFuncs(tree),
|
||||||
|
Helper.getStructs(tree),
|
||||||
|
Helper.getEnums(tree)
|
||||||
|
);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
// when / then
|
||||||
assertEquals("Error in line 1:0 Type schwurbel not defined.", e.getMessage());
|
assertDoesNotThrow(() -> ctxAnal.visit(tree));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void noReturnExpression() {
|
void shouldNotThrowIfParameterTypeIsReferringToAnEnum() {
|
||||||
ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; x = 0; } foo();");
|
// given
|
||||||
var funcs = Helper.getFuncs(tree);
|
var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: bar): int { return 1; } foo(bar.A);");
|
||||||
var structs = Helper.getStructs(tree);
|
var ctxAnal = new ContextAnalysis(
|
||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
|
Helper.getFuncs(tree),
|
||||||
|
Helper.getStructs(tree),
|
||||||
|
Helper.getEnums(tree)
|
||||||
|
);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
// when / then
|
||||||
assertEquals("Error in line 1:0 Function foo has to return something of type int.", e.getMessage());
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,21 +16,21 @@ public class Helper {
|
|||||||
return parser.parse();
|
return parser.parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, FunctionInformation> getFuncs(ParseTree tree) {
|
public static Map<String, FunctionDefinition> getFuncs(ParseTree tree) {
|
||||||
var functionDefinitions = new HashMap<String, FunctionInformation>();
|
var functionDefinitions = new HashMap<String, FunctionDefinition>();
|
||||||
new GetFunctions(functionDefinitions).visit(tree);
|
new GetDefinitions(functionDefinitions, new HashMap<>(), new HashMap<>()).visit(tree);
|
||||||
return functionDefinitions;
|
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) {
|
public static Map<String, StructDefinition> getStructs(ParseTree tree) {
|
||||||
var structs = new HashMap<String, StructDefinition>();
|
var structs = new HashMap<String, StructDefinition>();
|
||||||
new GetStructs(getStructNames(tree), structs).visit(tree);
|
new GetDefinitions(new HashMap<>(), structs, new HashMap<>()).visit(tree);
|
||||||
return structs;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,8 @@ public class ModuloTest {
|
|||||||
ParseTree tree = Helper.prepareParser("function foo(): float { return 1.0 % 2.3; } foo();");
|
ParseTree tree = Helper.prepareParser("function foo(): float { return 1.0 % 2.3; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||||
assertEquals("Error in line 1:31 Only integers are allowed for modulo.", e.getMessage());
|
assertEquals("Error in line 1:31 Only integers are allowed for modulo.", e.getMessage());
|
||||||
|
|||||||
72
src/test/java/NaughtTest.java
Normal file
72
src/test/java/NaughtTest.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,8 @@ public class OrTest {
|
|||||||
ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 || 2; } foo();");
|
ParseTree tree = Helper.prepareParser("function foo(): bool { return 1 || 2; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||||
assertEquals("Error in line 1:30 || is only defined for bool.", e.getMessage());
|
assertEquals("Error in line 1:30 || is only defined for bool.", e.getMessage());
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
94
src/test/java/StructDefinitionTest.java
Normal file
94
src/test/java/StructDefinitionTest.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,10 +12,11 @@ public class StructFieldAccessTest {
|
|||||||
ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { return str.a; } foo();");
|
ParseTree tree = Helper.prepareParser("struct test { a: int; } function foo(): int { return str.a; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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
|
@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();");
|
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 funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,9 +13,10 @@ public class VariableAssignmentTest {
|
|||||||
ParseTree tree = Helper.prepareParser("function foo(): int { x = 1; return 1; } foo();");
|
ParseTree tree = Helper.prepareParser("function foo(): int { x = 1; return 1; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,109 @@
|
|||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import de.hsrm.compiler.Klang.ContextAnalysis;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import org.antlr.v4.runtime.tree.ParseTree;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import de.hsrm.compiler.Klang.ContextAnalysis;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class VariableDeclarationTest {
|
public class VariableDeclarationTest {
|
||||||
@Test
|
@Test
|
||||||
void typeNotDefined() {
|
void shouldNotThrowIfDeclaredTypeIsAStruct() {
|
||||||
ParseTree tree = Helper.prepareParser("function foo(): int { let X: unk; return 1; } foo();");
|
// given
|
||||||
var funcs = Helper.getFuncs(tree);
|
var tree = Helper.prepareParser("struct bar { a: int; } function foo(): int { let a: bar; return 1; } foo();");
|
||||||
var structs = Helper.getStructs(tree);
|
var ctxAnal = new ContextAnalysis(
|
||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
|
Helper.getFuncs(tree),
|
||||||
|
Helper.getStructs(tree),
|
||||||
|
Helper.getEnums(tree)
|
||||||
|
);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(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());
|
assertEquals("Error in line 1:22 Type unk not defined.", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void variableRedeclaration()
|
void shouldThrowExceptionIfVariableIsRedeclared() {
|
||||||
{
|
// given
|
||||||
ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; return 1; } foo();");
|
var tree = Helper.prepareParser("function foo(): int { let x: int; let x: bool; return 1; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var ctxAnal = new ContextAnalysis(
|
||||||
var structs = Helper.getStructs(tree);
|
Helper.getFuncs(tree),
|
||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs);
|
Helper.getStructs(tree),
|
||||||
|
Helper.getEnums(tree)
|
||||||
|
);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
// when / then
|
||||||
assertEquals("Error in line 1:34 Redeclaration of variable with name \"x\".", e.getMessage());
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,10 +13,11 @@ public class VariableTest {
|
|||||||
ParseTree tree = Helper.prepareParser("function foo(): int { return x; } foo();");
|
ParseTree tree = Helper.prepareParser("function foo(): int { return x; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
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
|
@Test
|
||||||
@@ -24,7 +25,8 @@ public class VariableTest {
|
|||||||
ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; return x; } foo();");
|
ParseTree tree = Helper.prepareParser("function foo(): int { let x: int; return x; } foo();");
|
||||||
var funcs = Helper.getFuncs(tree);
|
var funcs = Helper.getFuncs(tree);
|
||||||
var structs = Helper.getStructs(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));
|
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
||||||
assertEquals("Error in line 1:41 Variable with name \"x\" has not been initialized.", e.getMessage());
|
assertEquals("Error in line 1:41 Variable with name \"x\" has not been initialized.", e.getMessage());
|
||||||
|
|||||||
101
src/test/java/VoidTest.java
Normal file
101
src/test/java/VoidTest.java
Normal 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -94,6 +94,11 @@ int testStructCreation() {
|
|||||||
struct_testExpected_l("init inner field a", 20, resultRec->b->a);
|
struct_testExpected_l("init inner field a", 20, resultRec->b->a);
|
||||||
struct_testExpected_s("init inner field b", NULL, resultRec->b->b);
|
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(resultRec);
|
||||||
free(innerStruct);
|
free(innerStruct);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,3 +29,5 @@ struct testStructRec *getStructFieldRecB(struct testStructRec *t);
|
|||||||
|
|
||||||
struct testStructRec *setStructFieldRecA(struct testStructRec *t, long a);
|
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();
|
||||||
@@ -555,6 +555,72 @@ function getTestStructRec(a: int, b: testStructRec): testStructRec {
|
|||||||
return create testStructRec(a, b);
|
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 {
|
function getStructFieldA(t: testStruct): int {
|
||||||
return t.a;
|
return t.a;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user