├── gradle.properties ├── docs └── Compiler Documentation.docx ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── test │ └── resources │ │ └── test_mod.funk └── main │ ├── java │ └── net │ │ └── earthcomputer │ │ └── minefunk │ │ ├── parser │ │ ├── VoidDef.java │ │ ├── BoolDef.java │ │ ├── IntDef.java │ │ ├── StringDef.java │ │ ├── UserDataKey.java │ │ ├── Keys.java │ │ ├── CommandListVisitor.java │ │ ├── StatementParser.java │ │ ├── IndexerVisitor.java │ │ ├── Modifiers.java │ │ ├── IndexVisitor.java │ │ ├── CyclicReferencesFinderVisitor.java │ │ ├── PreIndexVisitor.java │ │ ├── Type.java │ │ ├── ASTNodeValue.java │ │ ├── PostIndexVisitor.java │ │ ├── ASTProcessor.java │ │ ├── CallGraphVisitor.java │ │ ├── ExpressionParser.java │ │ ├── CommandParser.java │ │ ├── Frame.java │ │ ├── ASTUtil.java │ │ └── Index.java │ │ ├── FileMatcher.java │ │ ├── CallGraphAnalyzer.java │ │ ├── CommandLineOptions.java │ │ ├── Util.java │ │ └── Main.java │ ├── resources │ └── stdlib │ │ └── lang.funk │ └── jjtree │ └── net │ └── earthcomputer │ └── minefunk │ └── parser │ └── MinefunkParser.jjt ├── README.md ├── gradlew.bat └── gradlew /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION = 0.1 2 | -------------------------------------------------------------------------------- /docs/Compiler Documentation.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Earthcomputer/Minefunk/HEAD/docs/Compiler Documentation.docx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .classpath 3 | .project 4 | .settings/ 5 | /bin/ 6 | build/ 7 | docs/~$mpiler Documentation.docx 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Earthcomputer/Minefunk/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/test_mod.funk: -------------------------------------------------------------------------------- 1 | 2 | namespace test_mod { 3 | 4 | void main() { 5 | std::echo(true); 6 | std::print("Hello World!"); 7 | std::removeCommandChainLimit(); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 10 20:56:31 BST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip 7 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/VoidDef.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | /** 4 | * The type definition of void 5 | * 6 | * @author Earthcomputer 7 | */ 8 | public class VoidDef extends ASTTypeDef { 9 | 10 | public static final VoidDef INSTANCE = new VoidDef(); 11 | 12 | private VoidDef() { 13 | super(MinefunkParserTreeConstants.JJTTYPEDEF); 14 | value = new ASTNodeValue(0, 0, 0, 0); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/BoolDef.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | /** 4 | * The type definition of a boolean 5 | * 6 | * @author Earthcomputer 7 | */ 8 | public class BoolDef extends ASTTypeDef { 9 | 10 | public static final BoolDef INSTANCE = new BoolDef(); 11 | 12 | private BoolDef() { 13 | super(MinefunkParserTreeConstants.JJTTYPEDEF); 14 | value = new ASTNodeValue(0, 0, 0, 0); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/IntDef.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | /** 4 | * The type definition of an integer 5 | * 6 | * @author Earthcomputer 7 | */ 8 | public class IntDef extends ASTTypeDef { 9 | 10 | public static final IntDef INSTANCE = new IntDef(); 11 | 12 | private IntDef() { 13 | super(MinefunkParserTreeConstants.JJTTYPEDEF); 14 | value = new ASTNodeValue(0, 0, 0, 0); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/StringDef.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | /** 4 | * The type definition of a string 5 | * 6 | * @author Earthcomputer 7 | */ 8 | public class StringDef extends ASTTypeDef { 9 | 10 | public static final StringDef INSTANCE = new StringDef(); 11 | 12 | private StringDef() { 13 | super(MinefunkParserTreeConstants.JJTTYPEDEF); 14 | value = new ASTNodeValue(0, 0, 0, 0); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/stdlib/lang.funk: -------------------------------------------------------------------------------- 1 | 2 | namespace std { 3 | 4 | const int MAX_INT = 2147483647; 5 | const int MIN_INT = -2147483648; 6 | 7 | /** 8 | * Removes, for all intents an purposes, the maxCommandChainLength limit. 9 | */ 10 | inline void removeCommandChainLimit() { 11 | $gamerule maxCommandChainLength %MAX_INT% 12 | } 13 | 14 | /** 15 | * Turns command output on/off 16 | */ 17 | inline void echo(const bool on) { 18 | $gamerule commandBlockOutput %on% 19 | $gamerule logAdminCommands %on% 20 | } 21 | 22 | inline void print(const string str) { 23 | $tellraw @a %str% 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/UserDataKey.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | /** 4 | * A user data key 5 | * 6 | * @author Earthcomputer 7 | * 8 | * @param 9 | * The type of the data stored 10 | */ 11 | public class UserDataKey { 12 | 13 | private Class clazz; 14 | 15 | /** 16 | * Creates a user data key with the type of the data stored. We need this 17 | * argument to cast to the right class when the user data is obtained. 18 | * 19 | * @param clazz 20 | */ 21 | public UserDataKey(Class clazz) { 22 | this.clazz = clazz; 23 | } 24 | 25 | /** 26 | * Gets the type of the data stored. 27 | * 28 | * @return The type of the data stored 29 | */ 30 | public Class getClazz() { 31 | return clazz; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/Keys.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * A class which contains all the custom data keys for AST nodes 7 | * 8 | * @author Earthcomputer 9 | */ 10 | @SuppressWarnings({ "unchecked" }) 11 | public class Keys { 12 | 13 | private Keys() { 14 | } 15 | 16 | public static final UserDataKey ID = new UserDataKey<>(Integer.class); 17 | public static final UserDataKey TYPE_ID = new UserDataKey<>(Integer.class); 18 | public static final UserDataKey> NAMESPACES = (UserDataKey>) (UserDataKey) new UserDataKey<>( 19 | List.class); 20 | public static final UserDataKey CONST_VALUE = new UserDataKey<>(Object.class); 21 | public static final UserDataKey REFERENCED = new UserDataKey<>(Boolean.class); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/CommandListVisitor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | /** 8 | * The AST visitor used to generate Minecraft commands 9 | * 10 | * @author Earthcomputer 11 | */ 12 | public class CommandListVisitor extends IndexVisitor { 13 | 14 | @Override 15 | public Object visit(ASTFunction node, Object data) { 16 | if ((ASTUtil.getModifiers(node) & Modifiers.INLINE) == 0) { 17 | String funcId = ((Data) data).index.getFunctionId(node); 18 | List commands = new ArrayList<>(); 19 | StatementParser.toCommandList(ASTUtil.getBody(node), ((Data) data).index, commands, 20 | ((Data) data).exceptions); 21 | ((Data) data).commandLists.put(funcId, commands); 22 | } 23 | return data; 24 | } 25 | 26 | public static class Data implements IIndexVisitorData { 27 | private Index index; 28 | private Map> commandLists; 29 | private List exceptions; 30 | 31 | public Data(Index index, Map> commandLists, List exceptions) { 32 | this.index = index; 33 | this.commandLists = commandLists; 34 | this.exceptions = exceptions; 35 | } 36 | 37 | @Override 38 | public Index getIndex() { 39 | return index; 40 | } 41 | 42 | @Override 43 | public List getExceptions() { 44 | return exceptions; 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/StatementParser.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import static net.earthcomputer.minefunk.parser.MinefunkParserTreeConstants.*; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Utility class for performing operations on statements 9 | * 10 | * @author Earthcomputer 11 | */ 12 | public class StatementParser { 13 | 14 | /** 15 | * Compiles the given statement to a raw Minecraft command list 16 | * 17 | * @param stmt 18 | * - the statement 19 | * @param index 20 | * - the index 21 | * @param commands 22 | * - the list of commands to add to 23 | * @param exceptions 24 | * - the list of compiler errors to add to 25 | */ 26 | public static void toCommandList(Node stmt, Index index, List commands, List exceptions) { 27 | switch (stmt.getId()) { 28 | case JJTBLOCKSTMT: 29 | index.getFrame().pushBlock(); 30 | for (Node child : ASTUtil.getChildren((ASTBlockStmt) stmt)) { 31 | toCommandList(child, index, commands, exceptions); 32 | } 33 | index.getFrame().popBlock(); 34 | break; 35 | case JJTCOMMANDSTMT: 36 | String rawCommand; 37 | try { 38 | rawCommand = CommandParser.makeRawCommand((ASTCommandStmt) stmt, index); 39 | } catch (ParseException e) { 40 | exceptions.add(e); 41 | return; 42 | } 43 | commands.add(rawCommand); 44 | break; 45 | case JJTEXPRESSIONSTMT: 46 | ExpressionParser.toCommandList(ASTUtil.getExpression((ASTExpressionStmt) stmt), index, commands, 47 | exceptions); 48 | break; 49 | case JJTVARDECLSTMT: 50 | index.getFrame().addLocalVariableDeclaration((ASTVarDeclStmt) stmt, exceptions); 51 | break; 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/IndexerVisitor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * The AST visitor which adds members to the index 7 | * 8 | * @author Earthcomputer 9 | */ 10 | public class IndexerVisitor extends IndexVisitor { 11 | 12 | @Override 13 | public Object visit(ASTTypeDef node, Object data) { 14 | ((Data) data).index.addTypeDefinition(node, ((Data) data).exceptions); 15 | ((Data) data).index.defineTypeId(node); 16 | return super.visit(node, data); 17 | } 18 | 19 | @Override 20 | public Object visit(ASTFunction node, Object data) { 21 | ((Data) data).index.addFunctionDefinition(node, ((Data) data).exceptions); 22 | ((Data) data).index.defineFunctionId(node); 23 | ASTUtil.getNodeValue(node).setUserData(Keys.REFERENCED, false); 24 | super.visit(node, data); 25 | return data; 26 | } 27 | 28 | @Override 29 | public Object visit(ASTVarDeclStmt node, Object data) { 30 | if (!((Data) data).index.getFrame().isInBlock()) { 31 | ((Data) data).index.addFieldDefinition(node, ((Data) data).exceptions); 32 | } 33 | ((Data) data).index.defineVariableId(node); 34 | ASTUtil.getNodeValue(node).setUserData(Keys.REFERENCED, false); 35 | return super.visit(node, data); 36 | } 37 | 38 | public static class Data implements IIndexVisitorData { 39 | public final Index index; 40 | public final List exceptions; 41 | 42 | public Data(Index index, List exceptions) { 43 | this.index = index; 44 | this.exceptions = exceptions; 45 | } 46 | 47 | @Override 48 | public Index getIndex() { 49 | return index; 50 | } 51 | 52 | @Override 53 | public List getExceptions() { 54 | return exceptions; 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minefunk 2 | A powerful compiler for Minecraft functions. 3 | 4 | ## What's happened to this project? 5 | With 1.13 on its way, a version where pretty much every command has changed, I thought it would make a good opportunity to redesign some features of Minefunk, and perhaps make better use of some of the upcoming features with the new execute command. A complete rewrite is on its way. 6 | 7 | ## Example 8 | ### my_mod.funk: 9 | ``` 10 | namespace my_mod { 11 | void install() { 12 | std::print("Installing a test mod"); 13 | std::echo(false); 14 | std::removeCommandChainLimit(); 15 | std::print("Installed a test mod"); 16 | } 17 | 18 | void tick() { 19 | killAllInRange("creeper"); 20 | keepAllAtOneDiamond(); 21 | } 22 | 23 | const int RANGE_TO_KILL = 20; 24 | inline void killAllInRange(const string mobType) { 25 | $execute @a ~ ~ ~ kill @e[type=%mobType%,r=%RANGE_TO_KILL%] 26 | } 27 | 28 | void keepAllAtOneDiamond() { 29 | $clear @a diamond 30 | $give @a diamond 31 | } 32 | } 33 | ``` 34 | 35 | ### Compiles to: 36 | #### my_mod/install.mcfunction: 37 | ``` 38 | tellraw @a Installing a test mod 39 | gamerule commandBlockOutput false 40 | gamerule logAdminCommands false 41 | gamerule maxCommandChainLength 2147483647 42 | tellraw @a Installed a test mod 43 | ``` 44 | #### my_mod/tick.mcfunction 45 | ``` 46 | execute @a ~ ~ ~ kill @e[type=creeper,r=20] 47 | function my_mod:keepAllAtOneDiamond 48 | ``` 49 | #### my_mod/keepAllAtOneDiamond.mcfunction 50 | ``` 51 | clear @a diamond 52 | give @a diamond 53 | ``` 54 | 55 | ## Downloads 56 | Downloads can be found at [the bintray page](https://bintray.com/earthcomputer/util/minefunk). More detailed download instructions can be found on [the wiki page](https://github.com/Earthcomputer/Minefunk/wiki/Downloading). 57 | 58 | ## Usage 59 | The Minefunk compiler is meant for use on the command line. Again, details are to be found on the [wiki](https://github.com/Earthcomputer/Minefunk/wiki) 60 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/FileMatcher.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk; 2 | 3 | import java.io.File; 4 | import java.io.FileFilter; 5 | import java.nio.file.Path; 6 | import java.util.ArrayList; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.regex.Pattern; 10 | 11 | /** 12 | * Matches files based on the input pattern 13 | * 14 | * @author Earthcomputer 15 | */ 16 | public class FileMatcher implements FileFilter { 17 | 18 | private Path directory; 19 | private Pattern pattern; 20 | 21 | public FileMatcher(Path directory, String... patterns) { 22 | this.directory = directory; 23 | this.pattern = compilePattern(patterns); 24 | if (this.pattern == null) { 25 | throw new IllegalArgumentException("Invalid pattern: " + pattern); 26 | } 27 | } 28 | 29 | @Override 30 | public boolean accept(File file) { 31 | return pattern.matcher(directory.relativize(file.toPath()).toString()).matches(); 32 | } 33 | 34 | private static Pattern compilePattern(String... patterns) { 35 | List patternsList = new ArrayList<>(); 36 | for (String pattern : patterns) { 37 | pattern = pattern.replace("/", File.separator); 38 | pattern = pattern.replace(":", File.pathSeparator); 39 | if (pattern.contains("***")) { 40 | return null; 41 | } 42 | if (pattern.contains("?")) { 43 | return null; 44 | } 45 | pattern = pattern.replace("\\", "\\\\"); 46 | pattern = pattern.replace(".", "\\."); 47 | pattern = pattern.replace("**", ".?"); 48 | pattern = pattern.replace("*", "[^" + File.separator.replace("\\", "\\\\") + "]*"); 49 | pattern = pattern.replace("?", "*"); 50 | String[] parts = pattern.split(File.pathSeparator); 51 | for (String part : parts) { 52 | patternsList.add(part); 53 | } 54 | } 55 | 56 | StringBuilder pattern = new StringBuilder(); 57 | Iterator patternItr = patternsList.iterator(); 58 | pattern.append("(").append(patternItr.next()).append(")"); 59 | while (patternItr.hasNext()) { 60 | pattern.append("|(").append(patternItr.next()).append(")"); 61 | } 62 | return Pattern.compile(pattern.toString()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/Modifiers.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * A class containing constants for the modifiers of types, variables and 8 | * functions 9 | * 10 | * @author Earthcomputer 11 | */ 12 | public class Modifiers { 13 | 14 | private Modifiers() { 15 | } 16 | 17 | /** 18 | * The names of all the modifiers 19 | */ 20 | private static final Map modifierNames = new HashMap<>(); 21 | 22 | /** 23 | * This pseudo-modifier represents when something has no modifiers 24 | */ 25 | public static final int NONE = 0; 26 | /** 27 | * This modifier goes on functions. If present, the function will not be 28 | * generated as a separate function file but will be inlined into the 29 | * caller. 30 | */ 31 | public static final int INLINE = 1; 32 | /** 33 | * This modifier goes on variables. If present, the variable must have an 34 | * initializer and cannot be modified afterwards. 35 | */ 36 | public static final int CONST = 2; 37 | 38 | /** 39 | * Flags containing all modifiers allowed on type definitions 40 | */ 41 | public static final int ALLOWED_TYPE_MODIFIERS = NONE; 42 | /** 43 | * Flags containing all modifiers allowed on field definitions 44 | */ 45 | public static final int ALLOWED_VARIABLE_MODIFIERS = CONST; 46 | /** 47 | * Flags containing all modifiers allowed on function definitions 48 | */ 49 | public static final int ALLOWED_FUNCTION_MODIFIERS = INLINE; 50 | 51 | static { 52 | modifierNames.put(INLINE, "inline"); 53 | modifierNames.put(CONST, "const"); 54 | } 55 | 56 | /** 57 | * Converts the given modifier flags to a string 58 | * 59 | * @param modifiers 60 | * - the modifier flags 61 | * @return A string representation 62 | */ 63 | public static String toString(int modifiers) { 64 | if (modifiers == NONE) { 65 | return "none"; 66 | } 67 | StringBuilder str = new StringBuilder(); 68 | modifierNames.forEach((mask, name) -> { 69 | if ((modifiers & mask) != 0) { 70 | if (str.length() != 0) { 71 | str.append(" "); 72 | } 73 | str.append(name); 74 | } 75 | }); 76 | return str.toString(); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/IndexVisitor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Any visitor which walks through an AST tree with the purpose of performing 7 | * operations with the index. This class overrides appropriate methods to 8 | * automatically push and pop namespaces etc. 9 | * 10 | * @author Earthcomputer 11 | */ 12 | public abstract class IndexVisitor extends MinefunkParserDefaultVisitor { 13 | 14 | @Override 15 | public Object visit(ASTNamespace node, Object data) { 16 | getIndex(data).getFrame().pushNamespace(node); 17 | super.visit(node, data); 18 | getIndex(data).getFrame().popNamespace(); 19 | return data; 20 | } 21 | 22 | @Override 23 | public Object visit(ASTFunction node, Object data) { 24 | getIndex(data).getFrame().pushBlock(); 25 | super.visit(node, data); 26 | getIndex(data).getFrame().popBlock(); 27 | return data; 28 | } 29 | 30 | @Override 31 | public Object visit(ASTBlockStmt node, Object data) { 32 | getIndex(data).getFrame().pushBlock(); 33 | super.visit(node, data); 34 | getIndex(data).getFrame().popBlock(); 35 | return data; 36 | } 37 | 38 | @Override 39 | public Object visit(ASTVarDeclStmt node, Object data) { 40 | if (getIndex(data).getFrame().isInBlock()) { 41 | getIndex(data).getFrame().addLocalVariableDeclaration(node, getExceptions(data)); 42 | } 43 | return super.visit(node, data); 44 | } 45 | 46 | private static Index getIndex(Object data) { 47 | return ((IIndexVisitorData) data).getIndex(); 48 | } 49 | 50 | private static List getExceptions(Object data) { 51 | return ((IIndexVisitorData) data).getExceptions(); 52 | } 53 | 54 | /** 55 | * All subclasses of IndexVisitor must work on data which are 56 | * instances of this interface 57 | * 58 | * @author Earthcomputer 59 | */ 60 | public static interface IIndexVisitorData { 61 | /** 62 | * Gets the index we are working with 63 | * 64 | * @return The index we are working with 65 | */ 66 | Index getIndex(); 67 | 68 | /** 69 | * Gets the list of compiler errors to add to 70 | * 71 | * @return The list of compiler errors to add to 72 | */ 73 | List getExceptions(); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/CyclicReferencesFinderVisitor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.List; 4 | 5 | import net.earthcomputer.minefunk.CallGraphAnalyzer; 6 | import net.earthcomputer.minefunk.Util; 7 | 8 | /** 9 | * Finds cycles in a call graph. Uses an AST tree so we know what files the 10 | * errors occur in. 11 | * 12 | * @author Earthcomputer 13 | */ 14 | public class CyclicReferencesFinderVisitor extends MinefunkParserDefaultVisitor { 15 | 16 | @Override 17 | public Object visit(ASTFunction node, Object data) { 18 | if ((ASTUtil.getModifiers(node) & Modifiers.INLINE) != 0) { 19 | if (((Data) data).cycleSearchResults.isConnectedComponent( 20 | new CallGraphVisitor.CallGraphNode(ASTUtil.getNodeValue(node).getUserData(Keys.ID), 21 | CallGraphVisitor.CallGraphNode.EnumType.FUNCTION))) { 22 | ((Data) data).exceptions.add(cycRefFound(node)); 23 | } 24 | } 25 | return super.visit(node, data); 26 | } 27 | 28 | @Override 29 | public Object visit(ASTVarDeclStmt node, Object data) { 30 | if (((Data) data).cycleSearchResults.isConnectedComponent(new CallGraphVisitor.CallGraphNode( 31 | ASTUtil.getNodeValue(node).getUserData(Keys.ID), CallGraphVisitor.CallGraphNode.EnumType.VARIABLE))) { 32 | ((Data) data).exceptions.add(cycRefFound(node)); 33 | } else { 34 | if (ASTUtil.getNodeValue(node).getUserData(Keys.REFERENCED)) { 35 | Object constValue = null; 36 | if ((ASTUtil.getModifiers(node) & Modifiers.CONST) != 0) { 37 | Node initializer = ASTUtil.getInitializer(node); 38 | if (initializer != null) { 39 | try { 40 | constValue = ExpressionParser.staticEvaluateExpression(initializer, ((Data) data).index); 41 | } catch (ParseException e) { 42 | // Stays as null 43 | } 44 | } 45 | } 46 | ASTUtil.getNodeValue(node).setUserData(Keys.CONST_VALUE, constValue); 47 | } 48 | } 49 | return super.visit(node, data); 50 | } 51 | 52 | private static ParseException cycRefFound(Node node) { 53 | return Util.createParseException("Cyclic variable/inline function referneces detected", node); 54 | } 55 | 56 | public static class Data { 57 | public Index index; 58 | public List exceptions; 59 | public CallGraphAnalyzer.StronglyConnectedComponentsFinder.Result cycleSearchResults; 60 | 61 | public Data(Index index, List exceptions, 62 | CallGraphAnalyzer.StronglyConnectedComponentsFinder.Result cycleSearchResults) { 63 | this.index = index; 64 | this.exceptions = exceptions; 65 | this.cycleSearchResults = cycleSearchResults; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/PreIndexVisitor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import static net.earthcomputer.minefunk.parser.ASTUtil.*; 4 | 5 | import java.util.List; 6 | 7 | import net.earthcomputer.minefunk.Util; 8 | 9 | /** 10 | * This is the AST visitor which performs pre-index checking. This includes all 11 | * the checks which we do not need an index to perform, such as verifying the 12 | * correct modifiers are on the correct things. 13 | * 14 | * @author Earthcomputer 15 | */ 16 | public class PreIndexVisitor extends MinefunkParserDefaultVisitor implements MinefunkParserTreeConstants { 17 | 18 | @Override 19 | public Object visit(ASTCommandStmt node, Object data) { 20 | if (getCommand(node).endsWith(";")) { 21 | addException(data, Util.createParseException("Command statements should not end with a ; semicolon", node)); 22 | } 23 | return super.visit(node, data); 24 | } 25 | 26 | @Override 27 | public Object visit(ASTExpressionStmt node, Object data) { 28 | Node expr = getExpression(node); 29 | switch (expr.getId()) { 30 | case JJTFUNCTIONCALLEXPR: 31 | break; 32 | default: 33 | addException(data, 34 | Util.createParseException("You cannot use that type of expression as a statement", node)); 35 | } 36 | return super.visit(node, data); 37 | } 38 | 39 | @Override 40 | public Object visit(ASTFunction node, Object data) { 41 | int modifiers = getModifiers(node); 42 | if ((modifiers & Modifiers.INLINE) == 0) { 43 | if (getParameters(node).length != 0) { 44 | addException(data, 45 | Util.createParseException("Non-inline functions with parameters are not supported yet", node)); 46 | } 47 | } 48 | if (!getReturnType(node).isVoid()) { 49 | addException(data, Util.createParseException("Non-void functions are not supported yet", 50 | ASTUtil.getReturnTypeNode(node))); 51 | } 52 | modifiers &= ~Modifiers.ALLOWED_FUNCTION_MODIFIERS; 53 | if (modifiers != Modifiers.NONE) { 54 | addException(data, 55 | Util.createParseException("Invalid modifiers on function", ASTUtil.getModifiersNode(node))); 56 | } 57 | return super.visit(node, data); 58 | } 59 | 60 | @Override 61 | public Object visit(ASTVarDeclStmt node, Object data) { 62 | int modifiers = getModifiers(node); 63 | if ((modifiers & Modifiers.CONST) == 0) { 64 | addException(data, Util.createParseException("Non-const variables not supported yet", node)); 65 | } 66 | modifiers &= ~Modifiers.ALLOWED_VARIABLE_MODIFIERS; 67 | if (modifiers != Modifiers.NONE) { 68 | addException(data, 69 | Util.createParseException("Invalid modifiers on function \"" + Modifiers.toString(modifiers) + "\"", 70 | ASTUtil.getModifiersNode(node))); 71 | } 72 | return super.visit(node, data); 73 | } 74 | 75 | @SuppressWarnings("unchecked") 76 | private static void addException(Object data, ParseException e) { 77 | ((List) data).add(e); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/Type.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | /** 7 | * Represents a name within namespaces, e.g. a type, variable or function name, 8 | * or unresolved references of each of these 9 | * 10 | * @author Earthcomputer 11 | */ 12 | public class Type { 13 | 14 | /** 15 | * The built-in boolean type 16 | */ 17 | public static final Type BOOL = new Type("bool"); 18 | /** 19 | * The built-in integer type 20 | */ 21 | public static final Type INT = new Type("int"); 22 | /** 23 | * The built-in string type 24 | */ 25 | public static final Type STRING = new Type("string"); 26 | /** 27 | * The built-in void type 28 | */ 29 | public static final Type VOID = new Type("void"); 30 | 31 | private List namespaces; 32 | private String typeName; 33 | 34 | /** 35 | * Creates a type with no namespaces. Unless this is a built-in type, this 36 | * will be unqualified. 37 | * 38 | * @param typeName 39 | * - the type name 40 | */ 41 | public Type(String typeName) { 42 | this(Collections.emptyList(), typeName); 43 | } 44 | 45 | /** 46 | * Creates a type with the given namespaces and the given name. 47 | * 48 | * @param namespaces 49 | * - the namespaces 50 | * @param typeName 51 | * - the name 52 | */ 53 | public Type(List namespaces, String typeName) { 54 | this.namespaces = namespaces; 55 | this.typeName = typeName; 56 | } 57 | 58 | /** 59 | * Gets the namespaces of this type 60 | * 61 | * @return The namespaces of this type 62 | */ 63 | public List getNamespaces() { 64 | return namespaces; 65 | } 66 | 67 | /** 68 | * Gets the name of this type 69 | * 70 | * @return The name of this type 71 | */ 72 | public String getTypeName() { 73 | return typeName; 74 | } 75 | 76 | /** 77 | * Gets whether this type represents the built-in boolean type 78 | * 79 | * @return Whether this type is "bool" 80 | */ 81 | public boolean isBool() { 82 | return namespaces.isEmpty() && "bool".equals(typeName); 83 | } 84 | 85 | /** 86 | * Gets whether this type represents the built-in integer type 87 | * 88 | * @return Whether this type is "int" 89 | */ 90 | public boolean isInt() { 91 | return namespaces.isEmpty() && "int".equals(typeName); 92 | } 93 | 94 | /** 95 | * Gets whether this type represents the built-in string type 96 | * 97 | * @return Whether this type is "string" 98 | */ 99 | public boolean isString() { 100 | return namespaces.isEmpty() && "string".equals(typeName); 101 | } 102 | 103 | /** 104 | * Gets whether this type represents the built-in void type 105 | * 106 | * @return Whether this type is "void" 107 | */ 108 | public boolean isVoid() { 109 | return namespaces.isEmpty() && "void".equals(typeName); 110 | } 111 | 112 | @Override 113 | public int hashCode() { 114 | return typeName.hashCode() + 31 * namespaces.hashCode(); 115 | } 116 | 117 | @Override 118 | public boolean equals(Object other) { 119 | if (other == this) { 120 | return true; 121 | } else if (other == null) { 122 | return false; 123 | } else if (other.getClass() != Type.class) { 124 | return false; 125 | } else { 126 | Type type = (Type) other; 127 | return typeName.equals(type.typeName) && namespaces.equals(type.namespaces); 128 | } 129 | } 130 | 131 | @Override 132 | public String toString() { 133 | StringBuilder sb = new StringBuilder(); 134 | namespaces.forEach(ns -> sb.append(ns).append("::")); 135 | return sb.append(typeName).toString(); 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/ASTNodeValue.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.IdentityHashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * The value in an AST node. All AST nodes value field must contain an instance 8 | * of this class. This object contains information such as the position of the 9 | * AST node in the original source file, and any custom value. 10 | * 11 | * @author Earthcomputer 12 | * @see SimpleNode#value 13 | */ 14 | public class ASTNodeValue { 15 | 16 | private int startLine; 17 | private int startCol; 18 | private int endLine; 19 | private int endCol; 20 | private Object value; 21 | private Map, Object> userData = new IdentityHashMap<>(); 22 | 23 | /** 24 | * Creates an ASTNodeValue with the given range in the source code 25 | * with no custom value 26 | * 27 | * @param startLine 28 | * - the start line in the source code 29 | * @param startCol 30 | * - the start column in the source code 31 | * @param endLine 32 | * - the end line in the source code 33 | * @param endCol 34 | * - the end column in the source code 35 | */ 36 | public ASTNodeValue(int startLine, int startCol, int endLine, int endCol) { 37 | this(startLine, startCol, endLine, endCol, null); 38 | } 39 | 40 | /** 41 | * Creates an ASTNodeValue with the given range in the source code 42 | * with the given custom value 43 | * 44 | * @param startLine 45 | * - the start line in the source code 46 | * @param startCol 47 | * - the start column in the source code 48 | * @param endLine 49 | * - the end line in the source code 50 | * @param endCol 51 | * - the end column in the source code 52 | * @param value 53 | * - the custom value 54 | */ 55 | public ASTNodeValue(int startLine, int startCol, int endLine, int endCol, Object value) { 56 | this.startLine = startLine; 57 | this.startCol = startCol; 58 | this.endLine = endLine; 59 | this.endCol = endCol; 60 | this.value = value; 61 | } 62 | 63 | /** 64 | * Gets the start line in the source code 65 | * 66 | * @return The start line in the source code 67 | */ 68 | public int getStartLine() { 69 | return startLine; 70 | } 71 | 72 | /** 73 | * Gets the start column in the source code 74 | * 75 | * @return The start column in the source code 76 | */ 77 | public int getStartColumn() { 78 | return startCol; 79 | } 80 | 81 | /** 82 | * Gets the end line in the source code 83 | * 84 | * @return The end line in the source code 85 | */ 86 | public int getEndLine() { 87 | return endLine; 88 | } 89 | 90 | /** 91 | * Gets the end column in the source code 92 | * 93 | * @return The end column in the source code 94 | */ 95 | public int getEndColumn() { 96 | return endCol; 97 | } 98 | 99 | /** 100 | * Gets the custom value, or null if none was set 101 | * 102 | * @return The custom value 103 | */ 104 | public Object getValue() { 105 | return value; 106 | } 107 | 108 | /** 109 | * Sets custom data for this AST node 110 | * 111 | * @param key 112 | * - the key to get the custom data 113 | * @param value 114 | * - the custom data value 115 | */ 116 | public void setUserData(UserDataKey key, T value) { 117 | userData.put(key, value); 118 | } 119 | 120 | /** 121 | * Gets custom data for this AST node 122 | * 123 | * @param key 124 | * - the key to get the custom data 125 | * @return The value of the custom data, or null if it does not 126 | * exist 127 | */ 128 | public T getUserData(UserDataKey key) { 129 | return key.getClazz().cast(userData.get(key)); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/PostIndexVisitor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import net.earthcomputer.minefunk.Util; 4 | import net.earthcomputer.minefunk.parser.Index.FunctionId; 5 | import net.earthcomputer.minefunk.parser.IndexerVisitor.Data; 6 | 7 | /** 8 | * This is the AST visitor which performs post-index checks (things which we can 9 | * only know whether they are correct once we have finished the indexing phase), 10 | * such as type-checking 11 | * 12 | * @author Earthcomputer 13 | */ 14 | public class PostIndexVisitor extends IndexVisitor { 15 | 16 | @Override 17 | public Object visit(ASTCommandStmt node, Object data) { 18 | CommandParser.checkWildcardsAgainstIndex(node, ((Data) data).index, ((Data) data).exceptions); 19 | return super.visit(node, data); 20 | } 21 | 22 | @Override 23 | public Object visit(ASTFunctionCallExpr node, Object data) { 24 | // Visit children first to validate parameters 25 | super.visit(node, data); 26 | Node[] arguments = ASTUtil.getArguments(node); 27 | Type[] paramTypes = new Type[arguments.length]; 28 | for (int i = 0; i < arguments.length; i++) { 29 | paramTypes[i] = ExpressionParser.getExpressionType(arguments[i], ((Data) data).index); 30 | if (paramTypes[i] == null) { 31 | // Possible if sub-function-call is invalid 32 | // Exit before we cause problems 33 | return data; 34 | } 35 | if (paramTypes[i].isVoid()) { 36 | ((Data) data).exceptions 37 | .add(Util.createParseException("You cannot pass void to a function", arguments[i])); 38 | return data; 39 | } 40 | } 41 | Type resolvedFunctionName = ((Data) data).index.getFrame() 42 | .resolveFunction(new FunctionId(ASTUtil.getFunctionName(node), paramTypes)); 43 | if (resolvedFunctionName == null) { 44 | ((Data) data).exceptions.add(Util.createParseException("Undefined function", node)); 45 | return data; 46 | } 47 | ASTFunction function = ((Data) data).index.getFunctionDefinition(resolvedFunctionName, paramTypes); 48 | ASTUtil.getNodeValue(function).setUserData(Keys.REFERENCED, true); 49 | ASTUtil.getNodeValue(node).setUserData(Keys.ID, ASTUtil.getNodeValue(function).getUserData(Keys.ID)); 50 | return data; 51 | } 52 | 53 | @Override 54 | public Object visit(ASTFunction node, Object data) { 55 | Type returnType = ((Data) data).index.getFrame().resolveType(ASTUtil.getReturnType(node)); 56 | if (returnType == null) { 57 | ((Data) data).exceptions.add(Util.createParseException("Undefined type", ASTUtil.getReturnTypeNode(node))); 58 | } else { 59 | ASTTypeDef returnTypeDef = ((Data) data).index.getTypeDefinition(returnType); 60 | ASTUtil.getNodeValue(node).setUserData(Keys.TYPE_ID, 61 | ASTUtil.getNodeValue(returnTypeDef).getUserData(Keys.ID)); 62 | } 63 | super.visit(node, data); 64 | return data; 65 | } 66 | 67 | @Override 68 | public Object visit(ASTVarAccessExpr node, Object data) { 69 | ASTVarDeclStmt varDecl = ((Data) data).index.getFrame().resolveVariableReference(ASTUtil.getVariable(node)); 70 | if (varDecl == null) { 71 | ((Data) data).exceptions.add(Util.createParseException("Undefined variable", node)); 72 | } else { 73 | ASTUtil.getNodeValue(node).setUserData(Keys.ID, ASTUtil.getNodeValue(varDecl).getUserData(Keys.ID)); 74 | ASTUtil.getNodeValue(varDecl).setUserData(Keys.REFERENCED, true); 75 | } 76 | return super.visit(node, data); 77 | } 78 | 79 | @Override 80 | public Object visit(ASTVarDeclStmt node, Object data) { 81 | Type varType = ((Data) data).index.getFrame().resolveType(ASTUtil.getType(node)); 82 | if (varType == null) { 83 | ((Data) data).exceptions.add(Util.createParseException("Undefined type", ASTUtil.getTypeNode(node))); 84 | } else { 85 | ASTTypeDef typeDef = ((Data) data).index.getTypeDefinition(varType); 86 | ASTUtil.getNodeValue(node).setUserData(Keys.TYPE_ID, ASTUtil.getNodeValue(typeDef).getUserData(Keys.ID)); 87 | } 88 | return super.visit(node, data); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/CallGraphAnalyzer.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.Deque; 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.Map; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * A class which contains utilities for analyzing call graphs 13 | * 14 | * @author Earthcomputer 15 | */ 16 | public class CallGraphAnalyzer { 17 | 18 | private CallGraphAnalyzer() { 19 | } 20 | 21 | /** 22 | * Finds groups of strongly connected components in a call graph. If a group 23 | * of strongly connected components contains more than one node, then a 24 | * cycle is present. 25 | * 26 | * @author Earthcomputer 27 | * 28 | * @param 29 | * - the type of a call graph node 30 | */ 31 | // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm 32 | public static class StronglyConnectedComponentsFinder { 33 | 34 | private Map> graph; 35 | private int index; 36 | private Map indexes = new HashMap<>(); 37 | private Map lowlinks = new HashMap<>(); 38 | private Set onStack = new HashSet<>(); 39 | private Deque stack = new ArrayDeque<>(); 40 | private Result result; 41 | 42 | public StronglyConnectedComponentsFinder(Map> graph) { 43 | this.graph = graph; 44 | } 45 | 46 | public Result findStronglyConnectedComponents() { 47 | indexes.clear(); 48 | lowlinks.clear(); 49 | onStack.clear(); 50 | result = new Result<>(); 51 | 52 | index = 0; 53 | stack.clear(); 54 | graph.keySet().forEach(vertex -> { 55 | if (!indexes.containsKey(vertex)) { 56 | strongConnect(vertex); 57 | } 58 | }); 59 | 60 | return result; 61 | } 62 | 63 | private void strongConnect(T vertex) { 64 | indexes.put(vertex, index); 65 | lowlinks.put(vertex, index); 66 | index++; 67 | stack.push(vertex); 68 | onStack.add(vertex); 69 | 70 | graph.get(vertex).forEach(nextVertex -> { 71 | if (nextVertex.equals(vertex)) { 72 | result.singleNodeCycles.add(vertex); 73 | } else if (!indexes.containsKey(nextVertex)) { 74 | strongConnect(nextVertex); 75 | int nextVertexLowlink = lowlinks.get(nextVertex); 76 | if (nextVertexLowlink < lowlinks.get(vertex)) { 77 | lowlinks.put(vertex, nextVertexLowlink); 78 | } 79 | } else if (onStack.contains(nextVertex)) { 80 | int nextVertexIndex = indexes.get(nextVertex); 81 | if (nextVertexIndex < lowlinks.get(vertex)) { 82 | lowlinks.put(vertex, nextVertexIndex); 83 | } 84 | } 85 | }); 86 | 87 | // Boxed integers will compare identity if not unboxed 88 | if (lowlinks.get(vertex).intValue() == indexes.get(vertex).intValue()) { 89 | Set group = new HashSet<>(); 90 | T node; 91 | do { 92 | node = stack.pop(); 93 | onStack.remove(node); 94 | group.add(node); 95 | result.groups.put(node, group); 96 | } while (!node.equals(vertex)); 97 | } 98 | } 99 | 100 | /** 101 | * A container class containing the result of this algorithm 102 | * 103 | * @author Earthcomputer 104 | * 105 | * @param 106 | * - the type of a call graph node 107 | */ 108 | public static class Result { 109 | private Set singleNodeCycles = new HashSet<>(); 110 | private Map> groups = new HashMap<>(); 111 | 112 | private Result() { 113 | } 114 | 115 | public Set getConnectedComponents() { 116 | return groups.keySet().stream().filter(this::isConnectedComponent).collect(Collectors.toSet()); 117 | } 118 | 119 | public boolean isConnectedComponent(T node) { 120 | return groups.get(node).size() > 1 || singleNodeCycles.contains(node); 121 | } 122 | 123 | public Set> getConnectedGroups() { 124 | return groups.values().stream().distinct().collect(Collectors.toSet()); 125 | } 126 | } 127 | 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/ASTProcessor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | import net.earthcomputer.minefunk.CallGraphAnalyzer; 8 | 9 | /** 10 | * Helper class for performing each stage of compilation on an AST tree 11 | * 12 | * @author Earthcomputer 13 | */ 14 | public class ASTProcessor { 15 | 16 | /** 17 | * Performs a pre-index check on an AST tree. 18 | * 19 | * @param root 20 | * - the AST tree 21 | * @param exceptions 22 | * - a list of compiler errors to add to 23 | */ 24 | public static void preIndexCheck(ASTRoot root, List exceptions) { 25 | root.jjtAccept(new PreIndexVisitor(), exceptions); 26 | } 27 | 28 | /** 29 | * Indexes an AST tree. 30 | * 31 | * @param root 32 | * - the AST tree 33 | * @param index 34 | * - the index to add to 35 | * @param exceptions 36 | * - a list of compiler errors to add to 37 | */ 38 | public static void index(ASTRoot root, Index index, List exceptions) { 39 | root.jjtAccept(new IndexerVisitor(), new IndexerVisitor.Data(index, exceptions)); 40 | } 41 | 42 | /** 43 | * Performs a post-index check on an AST tree 44 | * 45 | * @param root 46 | * - the AST tree 47 | * @param index 48 | * - the index 49 | * @param exceptions 50 | * - a list of compiler errors to add to 51 | */ 52 | public static void postIndexCheck(ASTRoot root, Index index, List exceptions) { 53 | root.jjtAccept(new PostIndexVisitor(), new IndexerVisitor.Data(index, exceptions)); 54 | } 55 | 56 | /** 57 | * Adds to a call graph from an AST tree 58 | * 59 | * @param callGraph 60 | * - the call graph 61 | * @param root 62 | * - the AST tree 63 | * @param index 64 | * - the index 65 | * @param exceptions 66 | * - a list of compiler errors to add to 67 | */ 68 | public static void addToCallGraph( 69 | Map> callGraph, ASTRoot root, 70 | Index index, List exceptions) { 71 | CallGraphVisitor visitor = new CallGraphVisitor(); 72 | root.jjtAccept(visitor, new CallGraphVisitor.Data(index, exceptions, callGraph)); 73 | } 74 | 75 | /** 76 | * Checks an AST tree for cyclic variable and inline function references, 77 | * using a call graph having already been obtained using 78 | * {@link #addToCallGraph(Map, ASTRoot, Index, List)} 79 | * 80 | * @param cycleSearchResults 81 | * - the call graph cycle analysis results (see 82 | * {@link CallGraphAnalyzer} 83 | * @param root 84 | * - the AST tree 85 | * @param index 86 | * - the index 87 | * @param exceptions 88 | * - a list of compiler errors to add to 89 | */ 90 | public static void checkForCyclicReferences( 91 | CallGraphAnalyzer.StronglyConnectedComponentsFinder.Result cycleSearchResults, 92 | ASTRoot root, Index index, List exceptions) { 93 | root.jjtAccept(new CyclicReferencesFinderVisitor(), 94 | new CyclicReferencesFinderVisitor.Data(index, exceptions, cycleSearchResults)); 95 | } 96 | 97 | /** 98 | * Performs code generation for each function in the AST tree 99 | * 100 | * @param root 101 | * - the AST tree 102 | * @param index 103 | * - the index 104 | * @param commandLists 105 | * - a map from the output function name to a list of Minecraft 106 | * commands that the output function will contain 107 | * @param exceptions 108 | * - a list of compiler errors to add to 109 | */ 110 | public static void generateCommandLists(ASTRoot root, Index index, Map> commandLists, 111 | List exceptions) { 112 | root.jjtAccept(new CommandListVisitor(), new CommandListVisitor.Data(index, commandLists, exceptions)); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/CallGraphVisitor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.Deque; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | /** 11 | * This AST visitor adds from an AST tree to a call graph 12 | * 13 | * @author Earthcomputer 14 | * 15 | */ 16 | public class CallGraphVisitor extends IndexVisitor { 17 | 18 | private Deque currentNodeStack = new ArrayDeque<>(); 19 | 20 | @Override 21 | public Object visit(ASTVarDeclStmt node, Object data) { 22 | currentNodeStack.push( 23 | new CallGraphNode(ASTUtil.getNodeValue(node).getUserData(Keys.ID), CallGraphNode.EnumType.VARIABLE)); 24 | ((Data) data).callGraph.put(currentNodeStack.peek(), new HashSet<>()); 25 | super.visit(node, data); 26 | currentNodeStack.pop(); 27 | return data; 28 | } 29 | 30 | @Override 31 | public Object visit(ASTVarAccessExpr node, Object data) { 32 | ((Data) data).callGraph.get(currentNodeStack.peek()).add( 33 | new CallGraphNode(ASTUtil.getNodeValue(node).getUserData(Keys.ID), CallGraphNode.EnumType.VARIABLE)); 34 | return super.visit(node, data); 35 | } 36 | 37 | @Override 38 | public Object visit(ASTCommandStmt node, Object data) { 39 | try { 40 | CommandParser.getWildcardIndexes(node).forEach(wildcardIndex -> { 41 | try { 42 | Type type = CommandParser.wildcardToType(node, wildcardIndex); 43 | ASTVarDeclStmt varRef = ((Data) data).index.getFrame().resolveVariableReference(type); 44 | ((Data) data).callGraph.get(currentNodeStack.peek()).add(new CallGraphNode( 45 | ASTUtil.getNodeValue(varRef).getUserData(Keys.ID), CallGraphNode.EnumType.VARIABLE)); 46 | } catch (ParseException e) { 47 | throw new Error(e); 48 | } 49 | }); 50 | } catch (ParseException e) { 51 | throw new Error(e); 52 | } 53 | return super.visit(node, data); 54 | } 55 | 56 | @Override 57 | public Object visit(ASTFunction node, Object data) { 58 | currentNodeStack.push( 59 | new CallGraphNode(ASTUtil.getNodeValue(node).getUserData(Keys.ID), CallGraphNode.EnumType.FUNCTION)); 60 | ((Data) data).callGraph.put(currentNodeStack.peek(), new HashSet<>()); 61 | super.visit(node, data); 62 | currentNodeStack.pop(); 63 | return data; 64 | } 65 | 66 | @Override 67 | public Object visit(ASTFunctionCallExpr node, Object data) { 68 | ((Data) data).callGraph.get(currentNodeStack.peek()).add( 69 | new CallGraphNode(ASTUtil.getNodeValue(node).getUserData(Keys.ID), CallGraphNode.EnumType.FUNCTION)); 70 | return super.visit(node, data); 71 | } 72 | 73 | public static class Data implements IIndexVisitorData { 74 | public Index index; 75 | public List exceptions; 76 | public Map> callGraph; 77 | 78 | public Data(Index index, List exceptions, Map> callGraph) { 79 | this.index = index; 80 | this.exceptions = exceptions; 81 | this.callGraph = callGraph; 82 | } 83 | 84 | @Override 85 | public Index getIndex() { 86 | return index; 87 | } 88 | 89 | @Override 90 | public List getExceptions() { 91 | return exceptions; 92 | } 93 | } 94 | 95 | /** 96 | * A call graph node. Contains the variable/function ID and the type (i.e. 97 | * whether a node is a variable or a function) 98 | * 99 | * @author Earthcomputer 100 | */ 101 | public static class CallGraphNode { 102 | private int id; 103 | private EnumType type; 104 | 105 | public CallGraphNode(int id, EnumType type) { 106 | this.id = id; 107 | this.type = type; 108 | } 109 | 110 | public int getId() { 111 | return id; 112 | } 113 | 114 | public EnumType getType() { 115 | return type; 116 | } 117 | 118 | @Override 119 | public int hashCode() { 120 | return id + 31 * type.hashCode(); 121 | } 122 | 123 | @Override 124 | public boolean equals(Object other) { 125 | if (other == this) { 126 | return true; 127 | } else if (!(other instanceof CallGraphNode)) { 128 | return false; 129 | } else { 130 | CallGraphNode otherNode = (CallGraphNode) other; 131 | return id == otherNode.id && type == otherNode.type; 132 | } 133 | } 134 | 135 | @Override 136 | public String toString() { 137 | return "(type=" + type + ",id=" + id + ")"; 138 | } 139 | 140 | public static enum EnumType { 141 | VARIABLE, FUNCTION 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/CommandLineOptions.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk; 2 | 3 | import java.io.File; 4 | import java.nio.file.Path; 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | 9 | /** 10 | * A class representing command line options 11 | * 12 | * @author Earthcomputer 13 | */ 14 | public class CommandLineOptions { 15 | 16 | // @formatter:off 17 | public static final String USAGE = "java -jar minefunk.jar [options ...]\n" 18 | + "Options are:\n" 19 | + "--output: The output directory\n" 20 | + "--stacktace: Whether to show the stacktrace of a compiler error (debug feature)\n"; 21 | // @formatter:on 22 | 23 | private FileMatcher inputFileMatcher; 24 | private File outputDirectory; 25 | private boolean showStacktrace; 26 | 27 | private CommandLineOptions() { 28 | } 29 | 30 | /** 31 | * Parses the command line options into an instance of this class 32 | * 33 | * @param workingDirectory 34 | * - the working directory 35 | * @param args 36 | * - the raw command line args 37 | * @return An instance of this class representing these command line 38 | * options, or null if an error occurred in parsing 39 | */ 40 | public static CommandLineOptions parse(Path workingDirectory, String[] args) { 41 | List argsList = new ArrayList<>(args.length); 42 | for (String arg : args) { 43 | argsList.add(arg); 44 | } 45 | return parse(workingDirectory, argsList); 46 | } 47 | 48 | /** 49 | * Same as {@link #parse(Path, String[])}, except uses a list which it 50 | * modifies 51 | * 52 | * @param workingDirectory 53 | * - the working directory 54 | * @param args 55 | * - the raw command line args 56 | * @return An instance of this class representing these command line options 57 | */ 58 | private static CommandLineOptions parse(Path workingDirectory, List args) { 59 | CommandLineOptions opts = new CommandLineOptions(); 60 | String opt; 61 | opts.showStacktrace = findFlag(args, "--stacktrace") | findFlag(args, "-s"); 62 | opt = findStringOption(args, "--output", "-o"); 63 | if (opt == null) { 64 | opts.outputDirectory = workingDirectory.toFile(); 65 | } else { 66 | opts.outputDirectory = new File(opt); 67 | } 68 | 69 | if (args.isEmpty()) { 70 | return null; 71 | } 72 | opts.inputFileMatcher = new FileMatcher(workingDirectory, args.toArray(new String[args.size()])); 73 | 74 | return opts; 75 | } 76 | 77 | /** 78 | * Removes all occurrences of the given flag in args and returns 79 | * whether any were found 80 | * 81 | * @param args 82 | * - the command line arguments 83 | * @param flag 84 | * - the flag to find 85 | * @return Whether the flag was found 86 | */ 87 | private static boolean findFlag(List args, String flag) { 88 | boolean found = false; 89 | Iterator argItr = args.iterator(); 90 | while (argItr.hasNext()) { 91 | if (flag.equals(argItr.next())) { 92 | found = true; 93 | argItr.remove(); 94 | } 95 | } 96 | return found; 97 | } 98 | 99 | /** 100 | * Removes all occurrences of the given string option in args and 101 | * returns the value found, or null if not found 102 | * 103 | * @param args 104 | * - the command line arguments 105 | * @param option 106 | * - the option to find 107 | * @param shorthand 108 | * - the shorthand alias of the option 109 | * @return The value of the option 110 | */ 111 | private static String findStringOption(List args, String option, String shorthand) { 112 | String value = null; 113 | Iterator argItr = args.iterator(); 114 | while (argItr.hasNext()) { 115 | String tmp = argItr.next(); 116 | if (option.equals(tmp) || shorthand.equals(tmp)) { 117 | argItr.remove(); 118 | if (argItr.hasNext()) { 119 | tmp = argItr.next(); 120 | if (value == null) { 121 | value = tmp; 122 | } 123 | argItr.remove(); 124 | } 125 | } 126 | } 127 | return value; 128 | } 129 | 130 | /** 131 | * Gets the input file matcher 132 | * 133 | * @return The input file matcher 134 | */ 135 | public FileMatcher getInputFileMatcher() { 136 | return inputFileMatcher; 137 | } 138 | 139 | /** 140 | * Gets the output directory 141 | * 142 | * @return The output directory 143 | */ 144 | public File getOutputDirectory() { 145 | return outputDirectory; 146 | } 147 | 148 | /** 149 | * Gets whether the command line flags indicate that we should show the 150 | * stack trace of a compiler error 151 | * 152 | * @return Whether the show stacktace flag was set 153 | */ 154 | public boolean showStacktrace() { 155 | return showStacktrace; 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/ExpressionParser.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import static net.earthcomputer.minefunk.parser.MinefunkParserTreeConstants.*; 4 | 5 | import java.util.List; 6 | 7 | import net.earthcomputer.minefunk.Util; 8 | import net.earthcomputer.minefunk.parser.Index.FunctionId; 9 | 10 | /** 11 | * Utility class for performing operations on expressions 12 | * 13 | * @author Earthcomputer 14 | */ 15 | public class ExpressionParser { 16 | 17 | private ExpressionParser() { 18 | } 19 | 20 | /** 21 | * Gets the type that the given expression will evaluate to. 22 | * 23 | * @param node 24 | * - the expression 25 | * @param index 26 | * - the index 27 | * @return The type that the given expression will evaluate to 28 | */ 29 | public static Type getExpressionType(Node node, Index index) { 30 | switch (node.getId()) { 31 | case JJTBOOLLITERALEXPR: 32 | return Type.BOOL; 33 | case JJTFUNCTIONCALLEXPR: 34 | ASTFunctionCallExpr funcCall = (ASTFunctionCallExpr) node; 35 | Node[] arguments = ASTUtil.getArguments(funcCall); 36 | Type[] paramTypes = new Type[arguments.length]; 37 | for (int i = 0; i < arguments.length; i++) { 38 | paramTypes[i] = getExpressionType(arguments[i], index); 39 | } 40 | ASTFunction func = index.getFunctionDefinition( 41 | index.getFrame().resolveFunction(new FunctionId(ASTUtil.getFunctionName(funcCall), paramTypes)), 42 | paramTypes); 43 | if (func == null) { 44 | // Possible if not validated yet 45 | return null; 46 | } 47 | index.pushFrame(Util.listToDeque(ASTUtil.getNodeValue(func).getUserData(Keys.NAMESPACES))); 48 | Type resolvedType = index.getFrame().resolveType(ASTUtil.getReturnType(func)); 49 | index.popFrame(); 50 | return resolvedType; 51 | case JJTINTLITERALEXPR: 52 | return Type.INT; 53 | case JJTSTRINGLITERALEXPR: 54 | return Type.STRING; 55 | case JJTVARACCESSEXPR: 56 | return index.getFrame().resolveType(ASTUtil 57 | .getType(index.getFrame().resolveVariableReference(ASTUtil.getVariable((ASTVarAccessExpr) node)))); 58 | default: 59 | throw new IllegalArgumentException("Unrecognized expression"); 60 | } 61 | } 62 | 63 | /** 64 | * Statically evaluates the given expression 65 | * 66 | * @param node 67 | * - the expression 68 | * @param index 69 | * - the index 70 | * @return The result of the static evaluation 71 | * @throws ParseException 72 | * if the expression cannot be statically evaluated 73 | */ 74 | public static Object staticEvaluateExpression(Node node, Index index) throws ParseException { 75 | switch (node.getId()) { 76 | case JJTBOOLLITERALEXPR: 77 | return ASTUtil.getNodeValue(node).getValue(); 78 | case JJTFUNCTIONCALLEXPR: 79 | throw cantStaticEvaluate(node); 80 | case JJTINTLITERALEXPR: 81 | return ASTUtil.getNodeValue(node).getValue(); 82 | case JJTSTRINGLITERALEXPR: 83 | return ASTUtil.getNodeValue(node).getValue(); 84 | case JJTVARACCESSEXPR: 85 | Object constValue = index.getFrame().staticEvaluateVariable(ASTUtil.getVariable((ASTVarAccessExpr) node)); 86 | if (constValue == null) { 87 | throw cantStaticEvaluate(node); 88 | } else { 89 | return constValue; 90 | } 91 | default: 92 | throw new IllegalArgumentException("Unrecognized expression"); 93 | } 94 | } 95 | 96 | /** 97 | * Converts an expression to a command list 98 | * 99 | * @param expr 100 | * - the expression 101 | * @param index 102 | * - the index 103 | * @param commands 104 | * - the command list to add to 105 | * @param exceptions 106 | * - the compiler errors to add to 107 | */ 108 | public static void toCommandList(Node expr, Index index, List commands, List exceptions) { 109 | switch (expr.getId()) { 110 | case JJTBOOLLITERALEXPR: 111 | return; 112 | case JJTFUNCTIONCALLEXPR: 113 | ASTFunctionCallExpr callExpr = (ASTFunctionCallExpr) expr; 114 | Node[] arguments = ASTUtil.getArguments(callExpr); 115 | Type[] paramTypes = new Type[arguments.length]; 116 | for (int i = 0; i < arguments.length; i++) { 117 | paramTypes[i] = getExpressionType(arguments[i], index); 118 | } 119 | ASTFunction func = index.getFunctionDefinition( 120 | index.getFrame().resolveFunction(new FunctionId(ASTUtil.getFunctionName(callExpr), paramTypes)), 121 | paramTypes); 122 | int modifiers = ASTUtil.getModifiers(func); 123 | if ((modifiers & Modifiers.INLINE) != 0) { 124 | ASTVarDeclStmt[] parameters = ASTUtil.getParameters(func); 125 | Object[] constValues = new Object[parameters.length]; 126 | for (int i = 0; i < arguments.length; i++) { 127 | try { 128 | constValues[i] = ExpressionParser.staticEvaluateExpression(arguments[i], index); 129 | } catch (ParseException e) { 130 | continue; 131 | } 132 | } 133 | index.pushFrame(Util.listToDeque(ASTUtil.getNodeValue(func).getUserData(Keys.NAMESPACES))); 134 | index.getFrame().pushBlock(); 135 | for (int i = 0; i < parameters.length; i++) { 136 | index.getFrame().addLocalVariableDeclaration(parameters[i], exceptions); 137 | if (constValues[i] != null) { 138 | index.getFrame().setConstLocalVariableValue(parameters[i], constValues[i]); 139 | } 140 | } 141 | StatementParser.toCommandList(ASTUtil.getBody(func), index, commands, exceptions); 142 | index.popFrame(); 143 | } else { 144 | commands.add("function " + index.getFunctionId(func)); 145 | } 146 | case JJTINTLITERALEXPR: 147 | return; 148 | case JJTSTRINGLITERALEXPR: 149 | return; 150 | case JJTVARACCESSEXPR: 151 | return; 152 | default: 153 | throw new IllegalArgumentException("Unknown expression type"); 154 | } 155 | } 156 | 157 | /** 158 | * Creates a compiler error with a message saying that the given expression 159 | * cannot be statically evaluated 160 | * 161 | * @param expr 162 | * - the expression 163 | * @return The compiler error 164 | */ 165 | public static ParseException cantStaticEvaluate(Node expr) { 166 | return Util.createParseException("Can't static evaluate that expression", expr); 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/Util.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.ArrayList; 5 | import java.util.Deque; 6 | import java.util.List; 7 | 8 | import net.earthcomputer.minefunk.parser.ASTNodeValue; 9 | import net.earthcomputer.minefunk.parser.ASTUtil; 10 | import net.earthcomputer.minefunk.parser.MinefunkParserConstants; 11 | import net.earthcomputer.minefunk.parser.Node; 12 | import net.earthcomputer.minefunk.parser.ParseException; 13 | import net.earthcomputer.minefunk.parser.Token; 14 | 15 | /** 16 | * General utilities 17 | * 18 | * @author Earthcomputer 19 | * 20 | */ 21 | public class Util { 22 | 23 | /** 24 | * Converts a deque to a new array list. 25 | * 26 | * @param deque 27 | * - the deque to convert 28 | * @return The converted list 29 | * @see #dequeToList(Deque, List) 30 | */ 31 | public static ArrayList dequeToList(Deque deque) { 32 | return dequeToList(deque, new ArrayList<>(deque.size())); 33 | } 34 | 35 | /** 36 | * Converts a deque to the given list. The head (top) of the deque will be 37 | * the first element in the list. 38 | * 39 | * @param deque 40 | * - the deque to convert 41 | * @param list 42 | * - the list to add elements to 43 | * @return The input list with elements having been added 44 | */ 45 | public static > T dequeToList(Deque deque, T list) { 46 | deque.descendingIterator().forEachRemaining(list::add); 47 | return list; 48 | } 49 | 50 | /** 51 | * Converts a list to a new array deque. 52 | * 53 | * @param list 54 | * - the list to convert 55 | * @return The converted deque 56 | * @see #listToDeque(List, Deque) 57 | */ 58 | public static ArrayDeque listToDeque(List list) { 59 | return listToDeque(list, new ArrayDeque<>(list.size())); 60 | } 61 | 62 | /** 63 | * Converts a list to the given deque. The first element in the list will be 64 | * the head (top) of the deque. 65 | * 66 | * @param list 67 | * - the list to convert 68 | * @param deque 69 | * - the deque to add elements to 70 | * @return The input deque with elements having been added 71 | */ 72 | public static > T listToDeque(List list, T deque) { 73 | list.forEach(deque::addLast); 74 | return deque; 75 | } 76 | 77 | /** 78 | * Creates a syntax error on the given node with the given message 79 | * 80 | * @param message 81 | * - the message 82 | * @param node 83 | * - the AST node 84 | * @return The newly created parse exception 85 | */ 86 | public static ParseException createParseException(String message, Node node) { 87 | return createParseException(message, createToken(node)); 88 | } 89 | 90 | /** 91 | * Creates a syntax error on the given token with the given message. Note 92 | * the token doesn't need an image, it can just be used to describe a range 93 | * in the source code. 94 | * 95 | * @param message 96 | * - the message 97 | * @param token 98 | * - the token (range in the source code) 99 | * @return The newly created parse exception 100 | */ 101 | public static ParseException createParseException(String message, Token token) { 102 | ParseException e = new ParseException(message); 103 | e.currentToken = token; 104 | return e; 105 | } 106 | 107 | /** 108 | * Creates a token, seeing as there is no easy constructor in the token 109 | * class 110 | * 111 | * @param image 112 | * - the token's image 113 | * @param startLine 114 | * - the token's begin line 115 | * @param startColumn 116 | * - the token's begin column 117 | * @param endLine 118 | * - the token's end line 119 | * @param endColumn 120 | * - the token's end column 121 | * @return The newly created token 122 | */ 123 | public static Token createToken(String image, int startLine, int startColumn, int endLine, int endColumn) { 124 | Token tok = new Token(MinefunkParserConstants.EOF, image); 125 | tok.beginLine = startLine; 126 | tok.beginColumn = startColumn; 127 | tok.endLine = endLine; 128 | tok.endColumn = endColumn; 129 | return tok; 130 | } 131 | 132 | /** 133 | * Creates a token (range in the source code) containing the given node. 134 | * This token will not have an image 135 | * 136 | * @param node 137 | * - the AST node 138 | * @return The newly created token 139 | */ 140 | public static Token createToken(Node node) { 141 | ASTNodeValue value = ASTUtil.getNodeValue(node); 142 | Token tok = new Token(MinefunkParserConstants.EOF); 143 | tok.beginLine = value.getStartLine(); 144 | tok.beginColumn = value.getStartColumn(); 145 | tok.endLine = value.getEndLine(); 146 | tok.endColumn = value.getEndColumn(); 147 | return tok; 148 | } 149 | 150 | /** 151 | * Finds a whitespace character with the same width as c. 152 | * Specifically, if c is a whitespace character it returns 153 | * c, and otherwise a space (' '). 154 | * 155 | * @param c 156 | * - the possibly non-whitespace character 157 | * @return The whitespace character 158 | */ 159 | public static char charToWhitespace(char c) { 160 | if (Character.isWhitespace(c)) { 161 | return c; 162 | } else { 163 | return ' '; 164 | } 165 | } 166 | 167 | /** 168 | * Calculates the date of Easter in the given year. This 170 | * algorithm is used. 171 | * 172 | * @param y 173 | * - the year 174 | * @return A two-element array containing the 0-indexed month followed by 175 | * the 1-indexed day of the month 176 | */ 177 | public static int[] calculateEaster(int y) { 178 | int a = y % 19; 179 | int b = y / 100; 180 | int c = y % 100; 181 | int d = b >> 2; // b / 4 182 | int e = b & 3; // b % 4 183 | int g = ((b << 3) + 13) / 25; // (8 * b + 13) / 25 184 | int h = (19 * a + b - d - g + 15) / 30; 185 | int j = c >> 2; // c / 4 186 | int k = c & 3; // c % 4 187 | int m = (a + 11 * h) / 319; 188 | // (2 * e + 2 * j - k - h + m + 32) % 7 189 | int r = ((e << 1) + (j << 1) - k - h + m + 32) % 7; 190 | int n = (h - m + r + 90) / 25; 191 | int p = h - m + r + n + 19 & 31; // (h - m + r + n + 19) % 32 192 | return new int[] { n - 1, p }; 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/CommandParser.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import net.earthcomputer.minefunk.Util; 7 | 8 | /** 9 | * Utilities for processing the raw string inside a command statement 10 | * 11 | * @author Earthcomputer 12 | */ 13 | public class CommandParser { 14 | 15 | private CommandParser() { 16 | } 17 | 18 | /** 19 | * Gets a list of positions in the command where wildcards appear 20 | * 21 | * @param commandStmt 22 | * - the command statement 23 | * @return The list of positions of wildcards 24 | * @throws ParseException 25 | */ 26 | public static List getWildcardIndexes(ASTCommandStmt commandStmt) throws ParseException { 27 | String command = ASTUtil.getCommand(commandStmt); 28 | List wildcardIndexes = new ArrayList<>(); 29 | boolean escaped = false; 30 | for (int i = 0; i < command.length() - 1; i++) { 31 | if (command.charAt(i) == '%') { 32 | escaped = !escaped; 33 | } else { 34 | if (escaped) { 35 | escaped = false; 36 | int start = i - 1; 37 | int end = -1; 38 | for (; i < command.length(); i++) { 39 | if (command.charAt(i) == '%') { 40 | end = i; 41 | i++; 42 | break; 43 | } 44 | } 45 | if (end == -1) { 46 | throw Util.createParseException("Unclosed variable reference", commandStmt); 47 | } 48 | wildcardIndexes.add(new WildcardIndex(start, end)); 49 | } 50 | } 51 | } 52 | return wildcardIndexes; 53 | } 54 | 55 | /** 56 | * Converts the wildcard at the given wildcard index to a type which may 57 | * refer to a variable 58 | * 59 | * @param commandStmt 60 | * - the command statement 61 | * @param wildcardIndex 62 | * - the wildcard index 63 | * @return The type representing a variable referred to 64 | * @throws ParseException 65 | */ 66 | public static Type wildcardToType(ASTCommandStmt commandStmt, WildcardIndex wildcardIndex) throws ParseException { 67 | String command = ASTUtil.getCommand(commandStmt); 68 | String wildcard = command.substring(wildcardIndex.startPercent + 1, wildcardIndex.endPercent); 69 | wildcard = wildcard.trim(); 70 | String[] parts = wildcard.split("\\s*::\\s*"); 71 | if (parts.length == 0) { 72 | throw createParseException("Invalid variable reference", commandStmt, wildcardIndex); 73 | } 74 | for (String part : parts) { 75 | if (part.isEmpty()) { 76 | throw createParseException("Invalid variable reference", commandStmt, wildcardIndex); 77 | } 78 | } 79 | List namespaces = new ArrayList<>(); 80 | for (int i = 0; i < parts.length - 1; i++) { 81 | namespaces.add(parts[i]); 82 | } 83 | return new Type(namespaces, parts[parts.length - 1]); 84 | } 85 | 86 | /** 87 | * Checks the wildcards that occur in the given command statement against 88 | * the index 89 | * 90 | * @param commandStmt 91 | * - the command statement 92 | * @param index 93 | * - the index 94 | * @param exceptions 95 | * - the list of compiler errors to add to 96 | */ 97 | public static void checkWildcardsAgainstIndex(ASTCommandStmt commandStmt, Index index, 98 | List exceptions) { 99 | List wildcardIndexes; 100 | try { 101 | wildcardIndexes = getWildcardIndexes(commandStmt); 102 | } catch (ParseException e) { 103 | exceptions.add(e); 104 | return; 105 | } 106 | wildcardIndexes.forEach(wildcardIndex -> { 107 | Type type; 108 | try { 109 | type = wildcardToType(commandStmt, wildcardIndex); 110 | } catch (ParseException e) { 111 | exceptions.add(e); 112 | return; 113 | } 114 | if (index.getFrame().resolveVariableReference(type) == null) { 115 | exceptions.add(createParseException("Unrecognized variable", commandStmt, wildcardIndex)); 116 | } 117 | }); 118 | } 119 | 120 | /** 121 | * Converts the given command statement into a raw Minecraft command by 122 | * static evaluating all the wildcards 123 | * 124 | * @param commandStmt 125 | * - the command statement 126 | * @param index 127 | * - the index 128 | * @return The raw Minecraft command 129 | * @throws ParseException 130 | */ 131 | public static String makeRawCommand(ASTCommandStmt commandStmt, Index index) throws ParseException { 132 | String command = ASTUtil.getCommand(commandStmt); 133 | StringBuilder newCommand = new StringBuilder(command); 134 | List wildcardIndices = getWildcardIndexes(commandStmt); 135 | for (int i = wildcardIndices.size() - 1; i >= 0; i--) { 136 | WildcardIndex wildcardIndex = wildcardIndices.get(i); 137 | Type type = wildcardToType(commandStmt, wildcardIndex); 138 | Object value = index.getFrame().staticEvaluateVariable(type); 139 | if (value == null) { 140 | throw createParseException("Cannot static evaluate that variable", commandStmt, wildcardIndex); 141 | } 142 | newCommand.replace(wildcardIndex.startPercent, wildcardIndex.endPercent + 1, value.toString()); 143 | } 144 | for (int i = 0; i < newCommand.length() - 1; i++) { 145 | if (newCommand.charAt(i) == '%' && newCommand.charAt(i + 1) == '%') { 146 | newCommand.deleteCharAt(i); 147 | } 148 | } 149 | return newCommand.toString(); 150 | } 151 | 152 | /** 153 | * Create a compiler error with the wildcard index as the range in the 154 | * source code 155 | * 156 | * @param message 157 | * - the message 158 | * @param commandStmt 159 | * - the command statement 160 | * @param idx 161 | * - the wildcard index 162 | * @return The parse exception 163 | */ 164 | private static ParseException createParseException(String message, ASTCommandStmt commandStmt, WildcardIndex idx) { 165 | ASTNodeValue value = ASTUtil.getNodeValue(commandStmt); 166 | return Util.createParseException(message, 167 | Util.createToken(ASTUtil.getCommand(commandStmt).substring(idx.startPercent + 1, idx.endPercent), 168 | value.getStartLine(), value.getStartColumn() + 1 + idx.startPercent, value.getEndLine(), 169 | value.getEndColumn() + 1 + idx.endPercent)); 170 | } 171 | 172 | /** 173 | * A wildcard index. Contains the index in the command string of the opening 174 | * % and the closing % of the wildcard 175 | * 176 | * @author Earthcomputer 177 | */ 178 | public static class WildcardIndex { 179 | public final int startPercent; 180 | public final int endPercent; 181 | 182 | public WildcardIndex(int startPercent, int endPercent) { 183 | this.startPercent = startPercent; 184 | this.endPercent = endPercent; 185 | } 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/Frame.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Deque; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.function.Predicate; 9 | 10 | import net.earthcomputer.minefunk.Util; 11 | import net.earthcomputer.minefunk.parser.Index.FunctionId; 12 | 13 | /** 14 | * A context through which to resolve types, variables and functions. Allows for 15 | * omission of namespaces. 16 | * 17 | * @author Earthcomputer 18 | */ 19 | public class Frame { 20 | 21 | private Index globalIndex; 22 | private Deque namespaces; 23 | private Deque> localVariablesDefined; 24 | 25 | public Frame(Index globalIndex, Deque namespaces, 26 | Deque> localVariablesDefined) { 27 | this.globalIndex = globalIndex; 28 | this.namespaces = namespaces; 29 | this.localVariablesDefined = localVariablesDefined; 30 | } 31 | 32 | /** 33 | * Gets the global index containing the fully qualified names of all types, 34 | * fields and functions 35 | * 36 | * @return The global index 37 | */ 38 | public Index getGlobalIndex() { 39 | return globalIndex; 40 | } 41 | 42 | /** 43 | * Gets the namespaces of the current context converted to a list. 44 | * 45 | * @return The namespaces of the current context 46 | * @see #getNamespaces() 47 | */ 48 | public List getNamespacesList() { 49 | return Util.dequeToList(namespaces); 50 | } 51 | 52 | /** 53 | * Gets the namespaces of the current context. Types, variables and 54 | * functions are resolved relative to these namespaces 55 | * 56 | * @return The namespaces of the current frame 57 | */ 58 | public Deque getNamespaces() { 59 | return namespaces; 60 | } 61 | 62 | /** 63 | * Gets a collection of all local variables defined. What is actually 64 | * returned is a deque of blocks, each of which can define local variables 65 | * which may have the same name as a different block. Each block contains a 66 | * map mapping the name of the variable to the variable declaration 67 | * statement where it was declared. 68 | * 69 | * @return The local variables defined 70 | */ 71 | public Deque> getLocalVariablesDefined() { 72 | return localVariablesDefined; 73 | } 74 | 75 | /** 76 | * Pushes a new namespace onto the deque of namespaces. 77 | * 78 | * @param namespace 79 | * - the namespace to push 80 | * @see #pushNamespace(String) 81 | */ 82 | public void pushNamespace(ASTNamespace namespace) { 83 | pushNamespace(ASTUtil.getName(namespace)); 84 | } 85 | 86 | /** 87 | * Pushes a new namespace with the given name onto the deque of namespaces. 88 | * Types, variables and functions will now be resolved relative to this 89 | * namespace. 90 | * 91 | * @param namespace 92 | * - the namespace to push 93 | */ 94 | public void pushNamespace(String namespace) { 95 | namespaces.push(namespace); 96 | } 97 | 98 | /** 99 | * Pops a namespace from the deque of namespaces. Types, variables and 100 | * functions will no longer be resolved relative to the top namespace. 101 | */ 102 | public void popNamespace() { 103 | namespaces.pop(); 104 | } 105 | 106 | /** 107 | * Pushes a block in which local variables can be declared 108 | */ 109 | public void pushBlock() { 110 | localVariablesDefined.push(new HashMap<>()); 111 | } 112 | 113 | /** 114 | * Pops a block in which local variables may have been declared 115 | */ 116 | public void popBlock() { 117 | localVariablesDefined.pop(); 118 | } 119 | 120 | /** 121 | * Gets whether we are inside a block and hence any variable declaration 122 | * would be a local variable, as opposed to a field 123 | * 124 | * @return Whether we are inside a block 125 | */ 126 | public boolean isInBlock() { 127 | return !localVariablesDefined.isEmpty(); 128 | } 129 | 130 | /** 131 | * Defines a local variable inside the current block 132 | * 133 | * @param varDecl 134 | * - the local variable declaration 135 | * @param exceptions 136 | * - the list of compiler errors to add to 137 | */ 138 | public void addLocalVariableDeclaration(ASTVarDeclStmt varDecl, List exceptions) { 139 | if (localVariablesDefined.peek().containsKey(ASTUtil.getName(varDecl))) { 140 | exceptions.add(Util.createParseException("Duplicate local variable defined", ASTUtil.getNameNode(varDecl))); 141 | return; 142 | } 143 | localVariablesDefined.peek().put(ASTUtil.getName(varDecl), varDecl); 144 | } 145 | 146 | /** 147 | * Sets a local variable's constant value to some custom value 148 | * 149 | * @param varDecl 150 | * - the variable whose constant value to change 151 | * @param value 152 | * - the value to change to 153 | */ 154 | public void setConstLocalVariableValue(ASTVarDeclStmt varDecl, Object value) { 155 | ASTUtil.getNodeValue(varDecl).setUserData(Keys.CONST_VALUE, value); 156 | } 157 | 158 | /** 159 | * Resolves a thing which has namespaces (e.g. a type, variable or 160 | * function), relative to the current namespaces. 161 | * 162 | * @param relativeType 163 | * - the thing to resolve 164 | * @param existenceTest 165 | * - a test for whether a fully qualified thing exists 166 | * @return The fully qualified thing resolved, or null if it 167 | * couldn't be resolved 168 | */ 169 | public Type resolve(Type relativeType, Predicate existenceTest) { 170 | List relativeNamespaces = relativeType.getNamespaces(); 171 | List resolvedNamespaces = new ArrayList<>(namespaces.size() + relativeType.getNamespaces().size()); 172 | Util.dequeToList(namespaces, resolvedNamespaces).addAll(relativeNamespaces); 173 | 174 | Type resolvedType = new Type(resolvedNamespaces, relativeType.getTypeName()); 175 | 176 | while (resolvedNamespaces.size() >= relativeNamespaces.size()) { 177 | if (existenceTest.test(resolvedType)) { 178 | return resolvedType; 179 | } 180 | resolvedNamespaces.remove(0); 181 | } 182 | 183 | return null; 184 | } 185 | 186 | /** 187 | * Resolves a type relative to the current namespaces 188 | * 189 | * @param relativeType 190 | * - the type to resolve 191 | * @return The resolved type, or null if it couldn't be resolved 192 | */ 193 | public Type resolveType(Type relativeType) { 194 | return resolve(relativeType, resolvedType -> globalIndex.getTypeDefinition(resolvedType) != null); 195 | } 196 | 197 | /** 198 | * Resolves a field relative to the current namespaces 199 | * 200 | * @param relativeField 201 | * - the field to resolve 202 | * @return The resolved field, or null if it couldn't be resolved 203 | */ 204 | public Type resolveField(Type relativeField) { 205 | return resolve(relativeField, resolvedField -> globalIndex.getFieldDefinition(resolvedField) != null); 206 | } 207 | 208 | /** 209 | * Resolves a function relative to the current namespaces 210 | * 211 | * @param relativeFunction 212 | * - the function to resolve 213 | * @return The name and namespaces of the resolved function, or 214 | * null if it couldn't be resolved 215 | */ 216 | public Type resolveFunction(FunctionId relativeFunction) { 217 | return resolve(relativeFunction.getName(), resolvedFunction -> globalIndex 218 | .getFunctionDefinition(resolvedFunction, relativeFunction.getParamTypes()) != null); 219 | } 220 | 221 | /** 222 | * Resolves a variable reference to find its variable declaration. The 223 | * variable reference may be resolved to a local variable or a field. 224 | * 225 | * @param varRef 226 | * - the variable reference to resolve 227 | * @return The declaration of the resolved variable, or null if it 228 | * couldn't be resolved 229 | */ 230 | public ASTVarDeclStmt resolveVariableReference(Type varRef) { 231 | if (varRef.getNamespaces().isEmpty()) { 232 | for (Map block : localVariablesDefined) { 233 | if (block.containsKey(varRef.getTypeName())) { 234 | return block.get(varRef.getTypeName()); 235 | } 236 | } 237 | } 238 | Type resolvedField = resolveField(varRef); 239 | if (resolvedField == null) { 240 | return null; 241 | } 242 | return globalIndex.getFieldDefinition(resolvedField); 243 | } 244 | 245 | /** 246 | * Statically evaluates the given variable reference 247 | * 248 | * @param varRef 249 | * - the variable reference to statically evaluate 250 | * @return The result of the evaluation, or null if it couldn't be 251 | * statically evaluated 252 | */ 253 | public Object staticEvaluateVariable(Type varRef) { 254 | ASTVarDeclStmt varDecl = resolveVariableReference(varRef); 255 | if (varDecl == null) { 256 | return null; 257 | } 258 | if (!globalIndex.isField(varDecl)) { 259 | return ASTUtil.getNodeValue(varDecl).getUserData(Keys.CONST_VALUE); 260 | } 261 | if ((ASTUtil.getModifiers(varDecl) & Modifiers.CONST) == 0) { 262 | return null; 263 | } 264 | Node initializer = ASTUtil.getInitializer(varDecl); 265 | if (initializer == null) { 266 | return null; 267 | } 268 | globalIndex.pushFrame(Util.listToDeque(ASTUtil.getNodeValue(varDecl).getUserData(Keys.NAMESPACES))); 269 | try { 270 | return ExpressionParser.staticEvaluateExpression(initializer, globalIndex); 271 | } catch (ParseException e) { 272 | return null; 273 | } finally { 274 | globalIndex.popFrame(); 275 | } 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/ASTUtil.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * A helper class to get fields from different types of AST nodes 7 | * 8 | * @author Earthcomputer 9 | */ 10 | public class ASTUtil { 11 | 12 | private ASTUtil() { 13 | } 14 | 15 | /** 16 | * Gets the ASTNodeValue of the given AST node 17 | * 18 | * @param node 19 | * - the AST node 20 | * @return The ASTNodeValue 21 | */ 22 | public static ASTNodeValue getNodeValue(Node node) { 23 | return (ASTNodeValue) ((SimpleNode) node).value; 24 | } 25 | 26 | /** 27 | * Gets the child statements of a block statement 28 | * 29 | * @param stmt 30 | * - the block statement 31 | * @return The child statements 32 | */ 33 | public static Node[] getChildren(ASTBlockStmt stmt) { 34 | return stmt.children == null ? new Node[0] : stmt.children; 35 | } 36 | 37 | /** 38 | * Gets the string contained in a command statement 39 | * 40 | * @param stmt 41 | * - the command statement 42 | * @return The command contained in the command statement (with wildcards 43 | * still present) 44 | */ 45 | public static String getCommand(ASTCommandStmt stmt) { 46 | return (String) getNodeValue(stmt).getValue(); 47 | } 48 | 49 | /** 50 | * Gets the expression contained in an expression statement 51 | * 52 | * @param stmt 53 | * - the expression statement 54 | * @return The contained expression 55 | */ 56 | public static Node getExpression(ASTExpressionStmt stmt) { 57 | return stmt.children[0]; 58 | } 59 | 60 | /** 61 | * Gets the ASTModifiers of a function 62 | * 63 | * @param function 64 | * - the function 65 | * @return The modifiers of the function 66 | */ 67 | public static ASTModifiers getModifiersNode(ASTFunction function) { 68 | return (ASTModifiers) function.children[0]; 69 | } 70 | 71 | /** 72 | * Gets the actual modifiers of a function 73 | * 74 | * @param function 75 | * - the function 76 | * @return The modifiers of the function 77 | */ 78 | public static int getModifiers(ASTFunction function) { 79 | return getModifiers(getModifiersNode(function)); 80 | } 81 | 82 | /** 83 | * Gets the return type of a function as an ASTType 84 | * 85 | * @param function 86 | * - the function 87 | * @return The return type of the function 88 | */ 89 | public static ASTType getReturnTypeNode(ASTFunction function) { 90 | return (ASTType) function.children[1]; 91 | } 92 | 93 | /** 94 | * Gets the actual return type of a function 95 | * 96 | * @param function 97 | * - the function 98 | * @return The return type of the function 99 | */ 100 | public static Type getReturnType(ASTFunction function) { 101 | return getType(getReturnTypeNode(function)); 102 | } 103 | 104 | /** 105 | * Gets the name of a function, as an ASTIdentifier 106 | * 107 | * @param function 108 | * - the function 109 | * @return The name of the function 110 | */ 111 | public static ASTIdentifier getNameNode(ASTFunction function) { 112 | return (ASTIdentifier) function.children[2]; 113 | } 114 | 115 | /** 116 | * Gets the actual name of a function 117 | * 118 | * @param function 119 | * - the function 120 | * @return The name of the function 121 | */ 122 | public static String getName(ASTFunction function) { 123 | return getValue(getNameNode(function)); 124 | } 125 | 126 | /** 127 | * Gets the parameters of a function 128 | * 129 | * @param function 130 | * - the function 131 | * @return The parameters of the function 132 | */ 133 | public static ASTVarDeclStmt[] getParameters(ASTFunction function) { 134 | Node[] nodes = ((ASTParamList) function.children[3]).children; 135 | if (nodes == null) { 136 | return new ASTVarDeclStmt[0]; 137 | } 138 | ASTVarDeclStmt[] params = Arrays.copyOf(nodes, nodes.length, ASTVarDeclStmt[].class); 139 | return params; 140 | } 141 | 142 | /** 143 | * Gets the body of a function 144 | * 145 | * @param function 146 | * - the function 147 | * @return The body of a function 148 | */ 149 | public static ASTBlockStmt getBody(ASTFunction function) { 150 | return (ASTBlockStmt) function.children[4]; 151 | } 152 | 153 | /** 154 | * Gets the function name in a function call expression 155 | * 156 | * @param expr 157 | * - the function call expression 158 | * @return The called function name 159 | */ 160 | public static Type getFunctionName(ASTFunctionCallExpr expr) { 161 | return (Type) getNodeValue(expr).getValue(); 162 | } 163 | 164 | /** 165 | * Gets the arguments in a function call expression 166 | * 167 | * @param expr 168 | * - the function call expression 169 | * @return An array of expressions containing the arguments the function is 170 | * called with 171 | */ 172 | public static Node[] getArguments(ASTFunctionCallExpr expr) { 173 | return expr.children == null ? new Node[0] : expr.children; 174 | } 175 | 176 | /** 177 | * Gets the string value of an ASTIdentifier 178 | * 179 | * @param id 180 | * - the identifier 181 | * @return The string value 182 | */ 183 | public static String getValue(ASTIdentifier id) { 184 | return (String) getNodeValue(id).getValue(); 185 | } 186 | 187 | /** 188 | * Gets the integer value of an ASTIntLiteralExpr 189 | * 190 | * @param expr 191 | * - the integer literal expression 192 | * @return The integer value 193 | */ 194 | public static int getValue(ASTIntLiteralExpr expr) { 195 | return (int) getNodeValue(expr).getValue(); 196 | } 197 | 198 | /** 199 | * Gets the integer value of an ASTModifiers node 200 | * 201 | * @param modifiers 202 | * - the modifiers 203 | * @return The integer value 204 | */ 205 | public static int getModifiers(ASTModifiers modifiers) { 206 | return (int) getNodeValue(modifiers).getValue(); 207 | } 208 | 209 | /** 210 | * Gets the name of a namespace 211 | * 212 | * @param namespace 213 | * - the namespace 214 | * @return The name of the namespace 215 | */ 216 | public static String getName(ASTNamespace namespace) { 217 | return getValue((ASTIdentifier) namespace.children[0]); 218 | } 219 | 220 | /** 221 | * Gets the members contained inside the namespace 222 | * 223 | * @param namespace 224 | * - the namespace 225 | * @return The members contained inside the namespace 226 | */ 227 | public static Node[] getMembers(ASTNamespace namespace) { 228 | return Arrays.copyOfRange(namespace.children, 1, namespace.children.length); 229 | } 230 | 231 | /** 232 | * Gets all the namespaces contained in an AST tree 233 | * 234 | * @param root 235 | * - the AST tree 236 | * @return All the namespaces contained in the AST tree 237 | */ 238 | public static ASTNamespace[] getNamespaces(ASTRoot root) { 239 | if (root.children == null) { 240 | return new ASTNamespace[0]; 241 | } 242 | return Arrays.copyOf(root.children, root.children.length, ASTNamespace[].class); 243 | } 244 | 245 | /** 246 | * Gets the string value of an ASTStringLiteralExpr 247 | * 248 | * @param expr 249 | * - the string literal expression 250 | * @return The string value 251 | */ 252 | public static String getValue(ASTStringLiteralExpr expr) { 253 | return (String) getNodeValue(expr).getValue(); 254 | } 255 | 256 | /** 257 | * Gets the type represented by an ASTType 258 | * 259 | * @param type 260 | * - the ASTType 261 | * @return The represented type 262 | */ 263 | public static Type getType(ASTType type) { 264 | return (Type) getNodeValue(type).getValue(); 265 | } 266 | 267 | /** 268 | * Gets the ASTIdentifier representing the name of a type 269 | * definition 270 | * 271 | * @param type 272 | * - the type definition 273 | * @return The name of the type defintion 274 | */ 275 | public static ASTIdentifier getNameNode(ASTTypeDef type) { 276 | return (ASTIdentifier) type.children[0]; 277 | } 278 | 279 | /** 280 | * Gets the name of a type definition 281 | * 282 | * @param type 283 | * - the type definition 284 | * @return The name of the type definition 285 | */ 286 | public static String getName(ASTTypeDef type) { 287 | return getValue(getNameNode(type)); 288 | } 289 | 290 | /** 291 | * Gets the ASTType representing the variable accessed by a 292 | * variable access expression 293 | * 294 | * @param expr 295 | * - the variable access expression 296 | * @return The variable accessed 297 | */ 298 | public static ASTType getVariableNode(ASTVarAccessExpr expr) { 299 | return (ASTType) expr.children[0]; 300 | } 301 | 302 | /** 303 | * Gets the variable accessed by a variable access expression 304 | * 305 | * @param expr 306 | * - the variable access expression 307 | * @return The variable accessed 308 | */ 309 | public static Type getVariable(ASTVarAccessExpr expr) { 310 | return getType(getVariableNode(expr)); 311 | } 312 | 313 | /** 314 | * Gets the ASTModifiers of a variable declaration statement 315 | * 316 | * @param stmt 317 | * - the variable declaration statement 318 | * @return The modifiers of the variable declaration statement 319 | */ 320 | public static ASTModifiers getModifiersNode(ASTVarDeclStmt stmt) { 321 | return (ASTModifiers) stmt.children[0]; 322 | } 323 | 324 | /** 325 | * Gets the actual modifiers of the variable declaration statement 326 | * 327 | * @param stmt 328 | * - the variable declaration statement 329 | * @return The modifiers of the variable declaration statement 330 | */ 331 | public static int getModifiers(ASTVarDeclStmt stmt) { 332 | return getModifiers(getModifiersNode(stmt)); 333 | } 334 | 335 | /** 336 | * Gets the ASTType representing the type of the variable declared 337 | * by a variable declaration statement 338 | * 339 | * @param stmt 340 | * - the variable declaration statement 341 | * @return The type of the variable declared 342 | */ 343 | public static ASTType getTypeNode(ASTVarDeclStmt stmt) { 344 | return (ASTType) stmt.children[1]; 345 | } 346 | 347 | /** 348 | * Gets the type of the variable declared by a variable declaration 349 | * statement 350 | * 351 | * @param stmt 352 | * - the variable declaration statement 353 | * @return The type of the variable declared 354 | */ 355 | public static Type getType(ASTVarDeclStmt stmt) { 356 | return getType(getTypeNode(stmt)); 357 | } 358 | 359 | /** 360 | * Gets the ASTIdentifier representing the name of the variable 361 | * declared by a variable declaration statement 362 | * 363 | * @param stmt 364 | * - the variable declaration statement 365 | * @return The type of the variable declared 366 | */ 367 | public static ASTIdentifier getNameNode(ASTVarDeclStmt stmt) { 368 | return (ASTIdentifier) stmt.children[2]; 369 | } 370 | 371 | /** 372 | * Gets the name of the variable declared by a variable declaration 373 | * statement 374 | * 375 | * @param stmt 376 | * - the variable declaration statement 377 | * @return The type of the variable declared 378 | */ 379 | public static String getName(ASTVarDeclStmt stmt) { 380 | return getValue(getNameNode(stmt)); 381 | } 382 | 383 | /** 384 | * Gets the initialization expression used to initialize the variable 385 | * declared by a variable declaration statement 386 | * 387 | * @param stmt 388 | * - the variable declaration statement 389 | * @return The expression used to initialize the variable 390 | */ 391 | public static Node getInitializer(ASTVarDeclStmt stmt) { 392 | if (stmt.children.length <= 3) { 393 | return null; 394 | } else { 395 | return stmt.children[3]; 396 | } 397 | } 398 | 399 | } 400 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/parser/Index.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk.parser; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.Arrays; 5 | import java.util.Deque; 6 | import java.util.HashMap; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import net.earthcomputer.minefunk.Util; 12 | 13 | /** 14 | * Contains the definitions of all types, fields and functions. This structure 15 | * is shared globally, so contains the fully qualified names of each 16 | * 17 | * @author Earthcomputer 18 | */ 19 | public class Index { 20 | 21 | private Map types = new HashMap<>(); 22 | private Map fields = new HashMap<>(); 23 | private Map functions = new HashMap<>(); 24 | private Map functionsToResolve = new HashMap<>(); 25 | private Deque frames = new ArrayDeque<>(); 26 | private Map typesById = new HashMap<>(); 27 | private Map variablesById = new HashMap<>(); 28 | private Map functionsById = new HashMap<>(); 29 | private int nextTypeId = 0; 30 | private int nextVariableId = 0; 31 | private int nextFunctionId = 0; 32 | 33 | public Index() { 34 | addBuiltinTypes(); 35 | // Root frame 36 | pushFrame(new ArrayDeque<>()); 37 | } 38 | 39 | private void addBuiltinTypes() { 40 | types.put(Type.BOOL, BoolDef.INSTANCE); 41 | types.put(Type.INT, IntDef.INSTANCE); 42 | types.put(Type.STRING, StringDef.INSTANCE); 43 | types.put(Type.VOID, VoidDef.INSTANCE); 44 | defineTypeId(BoolDef.INSTANCE); 45 | defineTypeId(IntDef.INSTANCE); 46 | defineTypeId(StringDef.INSTANCE); 47 | defineTypeId(VoidDef.INSTANCE); 48 | } 49 | 50 | /** 51 | * Defines a type 52 | * 53 | * @param typeDef 54 | * - the type to define 55 | * @param exceptions 56 | * - the list of compiler errors to add to 57 | */ 58 | public void addTypeDefinition(ASTTypeDef typeDef, List exceptions) { 59 | Type type = new Type(frames.peek().getNamespacesList(), ASTUtil.getName(typeDef)); 60 | if (types.containsKey(type)) { 61 | exceptions.add(Util.createParseException("Duplicate type declared: " + type, ASTUtil.getNameNode(typeDef))); 62 | } else { 63 | types.put(type, typeDef); 64 | } 65 | } 66 | 67 | /** 68 | * Defines a field 69 | * 70 | * @param fieldDecl 71 | * - the field to define 72 | * @param exceptions 73 | * - the list of compiler errors to add to 74 | */ 75 | public void addFieldDefinition(ASTVarDeclStmt fieldDecl, List exceptions) { 76 | Type field = new Type(frames.peek().getNamespacesList(), ASTUtil.getName(fieldDecl)); 77 | if (fields.containsKey(field)) { 78 | exceptions.add( 79 | Util.createParseException("Duplicate field declared: " + field, ASTUtil.getNameNode(fieldDecl))); 80 | } else { 81 | fields.put(field, fieldDecl); 82 | ASTUtil.getNodeValue(fieldDecl).setUserData(Keys.NAMESPACES, field.getNamespaces()); 83 | } 84 | } 85 | 86 | /** 87 | * Defines a function. Note that the function is not defined immediately 88 | * because the parameter types may not have been defined yet. After 89 | * indexing, {@link #resolvePendingFunctions(List)} must be called to ensure 90 | * the functions are fully defined. 91 | * 92 | * @param func 93 | * - the function to define 94 | * @param exceptions 95 | * - the list of compiler errors to add to 96 | */ 97 | public void addFunctionDefinition(ASTFunction func, List exceptions) { 98 | ASTVarDeclStmt[] rawParams = ASTUtil.getParameters(func); 99 | Type[] params = new Type[rawParams.length]; 100 | for (int i = 0; i < rawParams.length; i++) { 101 | params[i] = ASTUtil.getType(rawParams[i]); 102 | } 103 | FunctionId funcId = new FunctionId(new Type(frames.peek().getNamespacesList(), ASTUtil.getName(func)), params); 104 | if (functionsToResolve.containsKey(funcId)) { 105 | exceptions.add( 106 | Util.createParseException("Duplicate function declared: " + funcId, ASTUtil.getNameNode(func))); 107 | } else { 108 | functionsToResolve.put(funcId, func); 109 | } 110 | } 111 | 112 | /** 113 | * Actually defines the functions after everything has been indexed 114 | * 115 | * @param exceptions 116 | * - the list of compiler errors to add to 117 | */ 118 | public void resolvePendingFunctions(List exceptions) { 119 | functionsToResolve.forEach((funcId, func) -> { 120 | pushFrame(Util.listToDeque(funcId.name.getNamespaces())); 121 | Type[] resolvedParams = new Type[funcId.paramTypes.length]; 122 | boolean errored = false; 123 | for (int i = 0; i < resolvedParams.length; i++) { 124 | Type resolvedParam = getFrame().resolveType(funcId.paramTypes[i]); 125 | if (resolvedParam == null) { 126 | errored = true; 127 | exceptions.add(Util.createParseException("Unknown type", 128 | ASTUtil.getTypeNode(ASTUtil.getParameters(func)[i]))); 129 | } else { 130 | resolvedParams[i] = resolvedParam; 131 | } 132 | } 133 | if (!errored) { 134 | functions.put(new FunctionId(funcId.name, resolvedParams), func); 135 | ASTUtil.getNodeValue(func).setUserData(Keys.NAMESPACES, funcId.name.getNamespaces()); 136 | } 137 | popFrame(); 138 | }); 139 | functionsToResolve.clear(); 140 | } 141 | 142 | /** 143 | * Pushes a new frame to the deque of frames 144 | * 145 | * @param namespaces 146 | * - the namespaces of the new frame 147 | */ 148 | public void pushFrame(Deque namespaces) { 149 | frames.push(new Frame(this, namespaces, new ArrayDeque<>())); 150 | } 151 | 152 | /** 153 | * Gets the top frame on the deque of frames 154 | * 155 | * @return The top frame 156 | */ 157 | public Frame getFrame() { 158 | return frames.peek(); 159 | } 160 | 161 | /** 162 | * Pops the top frame from the deque of frames 163 | */ 164 | public void popFrame() { 165 | frames.pop(); 166 | } 167 | 168 | /** 169 | * Gets a type definition, given a fully qualified type name 170 | * 171 | * @param typeName 172 | * - the fully qualified type name 173 | * @return The type definition, or null if the type was not defined 174 | */ 175 | public ASTTypeDef getTypeDefinition(Type typeName) { 176 | return types.get(typeName); 177 | } 178 | 179 | /** 180 | * Gets a field definition, given a fully qualified field name 181 | * 182 | * @param fieldName 183 | * - the fully qualified field name 184 | * @return The field definition, or null if the field was not 185 | * defined 186 | */ 187 | public ASTVarDeclStmt getFieldDefinition(Type fieldName) { 188 | return fields.get(fieldName); 189 | } 190 | 191 | /** 192 | * Gets a function definition, given a fully qualified function name and 193 | * parameters 194 | * 195 | * @param funcId 196 | * - the fully qualified function ID 197 | * @return The function definition, or null if the function was not 198 | * resolved 199 | */ 200 | public ASTFunction getFunctionDefinition(FunctionId funcId) { 201 | return functions.get(funcId); 202 | } 203 | 204 | /** 205 | * Gets a function definition, given a fully qualified function name and 206 | * parameters 207 | * 208 | * @param type 209 | * - the fully qualified name 210 | * @param paramTypes 211 | * - the fully qualified names of the parameter types 212 | * @return The function definition, or null if the function was not 213 | * resolved 214 | */ 215 | public ASTFunction getFunctionDefinition(Type type, Type... paramTypes) { 216 | return functions.get(new FunctionId(type, paramTypes)); 217 | } 218 | 219 | /** 220 | * Returns whether the given variable declaration declares a field 221 | * 222 | * @param varDecl 223 | * - the variable declaration 224 | * @return Whether the given variable is a field 225 | */ 226 | public boolean isField(ASTVarDeclStmt varDecl) { 227 | return fields.containsValue(varDecl); 228 | } 229 | 230 | /** 231 | * Gets the final function name of the given function. This should be used 232 | * instead of {@link ExtFunctionData#getId()}, as it creates a function ID 233 | * if none was assigned yet. 234 | * 235 | * @param function 236 | * - the function 237 | * @return The final function name of the given function 238 | */ 239 | public String getFunctionId(ASTFunction function) { 240 | StringBuilder newName = new StringBuilder(); 241 | Iterator nsItr = ASTUtil.getNodeValue(function).getUserData(Keys.NAMESPACES).iterator(); 242 | if (nsItr.hasNext()) { 243 | newName.append(nsItr.next()).append(":"); 244 | while (nsItr.hasNext()) { 245 | newName.append(nsItr.next()).append("/"); 246 | } 247 | } 248 | boolean noarg = ASTUtil.getParameters(function).length == 0; 249 | if (noarg) { 250 | newName.append(ASTUtil.getName(function)); 251 | } else { 252 | newName.append("0funk").append(ASTUtil.getNodeValue(function).getUserData(Keys.ID)); 253 | } 254 | return newName.toString(); 255 | 256 | } 257 | 258 | public void defineTypeId(ASTTypeDef typeDef) { 259 | typesById.put(nextTypeId, typeDef); 260 | ASTUtil.getNodeValue(typeDef).setUserData(Keys.ID, nextTypeId++); 261 | } 262 | 263 | /** 264 | * Gives a variable a unique ID 265 | * 266 | * @param variable 267 | * - the variable to give an ID 268 | */ 269 | public void defineVariableId(ASTVarDeclStmt variable) { 270 | variablesById.put(nextVariableId, variable); 271 | ASTUtil.getNodeValue(variable).setUserData(Keys.ID, nextVariableId++); 272 | } 273 | 274 | /** 275 | * Gives a function an unique ID 276 | * 277 | * @param function 278 | * - the function to give an ID 279 | */ 280 | public void defineFunctionId(ASTFunction function) { 281 | functionsById.put(nextFunctionId, function); 282 | ASTUtil.getNodeValue(function).setUserData(Keys.ID, nextFunctionId++); 283 | } 284 | 285 | /** 286 | * Returns the type assigned to the given ID 287 | * 288 | * @param id 289 | * - the ID 290 | * @return The type 291 | */ 292 | public ASTTypeDef getTypeById(int id) { 293 | return typesById.get(id); 294 | } 295 | 296 | /** 297 | * Returns the variable declaration assigned to the given variable ID 298 | * 299 | * @param id 300 | * - the ID 301 | * @return The variable declaration statement 302 | */ 303 | public ASTVarDeclStmt getVariableById(int id) { 304 | return variablesById.get(id); 305 | } 306 | 307 | /** 308 | * Returns the function declaration assigned to the given function ID 309 | * 310 | * @param id 311 | * - the ID 312 | * @return The function declaration 313 | */ 314 | public ASTFunction getFunctionById(int id) { 315 | return functionsById.get(id); 316 | } 317 | 318 | /** 319 | * A class which stores the name and parameter types of a function, both of 320 | * which are used to identify functions. 321 | * 322 | * @author Earthcomputer 323 | */ 324 | public static class FunctionId { 325 | private Type name; 326 | private Type[] paramTypes; 327 | 328 | public FunctionId(Type name, Type[] paramTypes) { 329 | this.name = name; 330 | this.paramTypes = paramTypes; 331 | } 332 | 333 | /** 334 | * Gets the function name 335 | * 336 | * @return The function name 337 | */ 338 | public Type getName() { 339 | return name; 340 | } 341 | 342 | /** 343 | * Gets the type names of the parameters 344 | * 345 | * @return The parameter types 346 | */ 347 | public Type[] getParamTypes() { 348 | return paramTypes; 349 | } 350 | 351 | @Override 352 | public int hashCode() { 353 | return Arrays.hashCode(paramTypes) + 31 * name.hashCode(); 354 | } 355 | 356 | @Override 357 | public boolean equals(Object other) { 358 | if (other == this) { 359 | return true; 360 | } else if (other == null) { 361 | return false; 362 | } else if (other.getClass() != FunctionId.class) { 363 | return false; 364 | } else { 365 | FunctionId funcId = (FunctionId) other; 366 | return name.equals(funcId.name) && Arrays.equals(paramTypes, funcId.paramTypes); 367 | } 368 | } 369 | 370 | @Override 371 | public String toString() { 372 | StringBuilder sb = new StringBuilder(name.toString()); 373 | sb.append('('); 374 | boolean first = true; 375 | for (Type paramType : paramTypes) { 376 | if (!first) { 377 | sb.append(", "); 378 | } 379 | sb.append(paramType); 380 | first = false; 381 | } 382 | sb.append(')'); 383 | return sb.toString(); 384 | } 385 | } 386 | 387 | } 388 | -------------------------------------------------------------------------------- /src/main/jjtree/net/earthcomputer/minefunk/parser/MinefunkParser.jjt: -------------------------------------------------------------------------------- 1 | options 2 | { 3 | STATIC = false; 4 | NODE_DEFAULT_VOID = true; 5 | MULTI = true; 6 | VISITOR = true; 7 | } 8 | 9 | PARSER_BEGIN(MinefunkParser) 10 | package net.earthcomputer.minefunk.parser; 11 | import java.util.*; 12 | import net.earthcomputer.minefunk.*; 13 | 14 | public class MinefunkParser 15 | { 16 | } 17 | 18 | PARSER_END(MinefunkParser) 19 | 20 | TOKEN : 21 | { 22 | < NAMESPACE : "namespace" > 23 | | < TYPEDEF : "typedef" > 24 | | < INLINE : "inline" > 25 | | < CONST : "const" > 26 | | < TRUE : "true" > 27 | | < FALSE : "false" > 28 | | < INTLITERAL : 29 | ( 30 | < PLUS > 31 | | < MINUS > 32 | )? 33 | (< DIGIT >)+ > 34 | | < STRING_START : "\"" > : STATE_STRING_LITERAL 35 | | < WORD : 36 | ( 37 | "_" 38 | | < LETTER > 39 | ) 40 | ( 41 | "_" 42 | | < ALPHANUM > 43 | )* > 44 | | < PLUS : "+" > 45 | | < MINUS : "-" > 46 | | < MULTIPLY : "*" > 47 | | < DIVIDE : "/" > 48 | | < MOD : "%" > 49 | | < OPEN_BRACE : "{" > 50 | | < CLOSE_BRACE : "}" > 51 | | < DOUBLE_COLON : "::" > 52 | | < COMMA : "," > 53 | | < OPEN_PARENTHESIS : "(" > 54 | | < CLOSE_PARENTHESIS : ")" > 55 | | < SEMICOLON : ";" > 56 | | < ASSIGN : "=" > 57 | | < ASSIGN_PLUS : "+=" > 58 | | < ASSIGN_MINUS : "-=" > 59 | | < ASSIGN_MULTIPLY : "*=" > 60 | | < ASSIGN_DIVIDE : "/=" > 61 | | < ASSIGN_MOD : "%=" > 62 | | < ASSIGN_SWAP : "><" > 63 | | < START_COMMAND : "$" > : STATE_COMMAND 64 | } 65 | 66 | TOKEN : 67 | { 68 | < #DIGIT : [ "0"-"9" ] > 69 | | < #LETTER : [ "a"-"z", "A"-"Z" ] > 70 | | < #ALPHANUM : 71 | < DIGIT > 72 | | < LETTER > > 73 | } 74 | 75 | < STATE_STRING_LITERAL > 76 | TOKEN : 77 | { 78 | < STRING_BODY : 79 | ( 80 | ~[ "\"", "\\" ] 81 | | "\\\\" 82 | | "\\\"" 83 | )+ > 84 | | < STRING_END : "\"" > : DEFAULT 85 | } 86 | 87 | < STATE_COMMAND > 88 | TOKEN : 89 | { 90 | < COMMAND_BODY : 91 | ( 92 | ~[ "\r", "\n" ] 93 | | ("\r" ~[ "\n" ]) 94 | )+ > 95 | | < END_COMMAND : 96 | ( 97 | "\r\n" 98 | | "\n" 99 | ) > 100 | : DEFAULT 101 | } 102 | 103 | SKIP : 104 | { 105 | " " 106 | | "\t" 107 | | "\r\n" 108 | | "\n" 109 | | < LINE_COMMENT : "//" > : STATE_LINE_COMMENT 110 | | < MULTILINE_COMMENT : "/*" > : STATE_MULTILINE_COMMENT 111 | } 112 | 113 | < STATE_LINE_COMMENT > 114 | SKIP : 115 | { 116 | < LINE_COMMENT_BODY : 117 | ( 118 | ~[ "\r", "\n" ] 119 | | ("\r" ~[ "\n" ]) 120 | )+ > 121 | | < END_LINE_COMMENT : 122 | ( 123 | "\r\n" 124 | | "\n" 125 | ) > 126 | : DEFAULT 127 | } 128 | 129 | < STATE_MULTILINE_COMMENT > 130 | SKIP : 131 | { 132 | < MULTILINE_COMMENT_BODY : 133 | ( 134 | ~[ "*" ] 135 | | ("*" ~[ "/" ]) 136 | )+ > 137 | | < END_MULTILINE_COMMENT : "*/" > : DEFAULT 138 | } 139 | 140 | public ASTRoot parse() #Root : 141 | { 142 | } 143 | { 144 | ( 145 | namespace() 146 | )* 147 | < EOF > 148 | { 149 | ASTNamespace [ ] namespaces = ASTUtil.getNamespaces(jjtThis); 150 | if (namespaces.length == 0) 151 | { 152 | jjtThis.value = new ASTNodeValue(1, 0, 1, 0); 153 | } 154 | else 155 | { 156 | ASTNodeValue firstValue = (ASTNodeValue) namespaces [ 0 ].value; 157 | ASTNodeValue lastValue = (ASTNodeValue) namespaces [ namespaces.length - 1 ].value; 158 | jjtThis.value = new ASTNodeValue(firstValue.getStartLine(), firstValue.getStartColumn(), lastValue.getEndLine(), lastValue.getEndColumn()); 159 | } 160 | return jjtThis; 161 | } 162 | } 163 | 164 | public void namespace() #Namespace : 165 | { 166 | Token firstToken; 167 | Token lastToken; 168 | } 169 | { 170 | firstToken = < NAMESPACE > 171 | identifier() 172 | < OPEN_BRACE > 173 | ( 174 | member() 175 | )* 176 | lastToken = < CLOSE_BRACE > 177 | { 178 | jjtThis.value = new ASTNodeValue(firstToken.beginLine, firstToken.beginColumn, lastToken.endLine, lastToken.endColumn); 179 | } 180 | } 181 | 182 | public void member() : 183 | { 184 | } 185 | { 186 | ( 187 | LOOKAHEAD(function()) 188 | function() 189 | | varDeclStatement() 190 | ) 191 | } 192 | 193 | public void typedef() #TypeDef : 194 | { 195 | Token firstToken; 196 | } 197 | { 198 | firstToken = < TYPEDEF > 199 | identifier() 200 | < SEMICOLON > 201 | { 202 | ASTNodeValue lastValue = (ASTNodeValue) ASTUtil.getNameNode(jjtThis).value; 203 | jjtThis.value = new ASTNodeValue(firstToken.beginLine, firstToken.beginColumn, lastValue.getEndLine(), lastValue.getEndColumn()); 204 | } 205 | } 206 | 207 | public void function() #Function : 208 | { 209 | } 210 | { 211 | modifiers() 212 | type() 213 | identifier() 214 | < OPEN_PARENTHESIS > 215 | paramList() 216 | < CLOSE_PARENTHESIS > 217 | blockStatement() 218 | { 219 | ASTNodeValue firstValue = (ASTNodeValue) ASTUtil.getModifiersNode(jjtThis).value; 220 | ASTNodeValue lastValue = (ASTNodeValue) ASTUtil.getBody(jjtThis).value; 221 | jjtThis.value = new ASTNodeValue(firstValue.getStartLine(), firstValue.getStartColumn(), lastValue.getEndLine(), lastValue.getEndColumn()); 222 | } 223 | } 224 | 225 | public void paramList() #ParamList : 226 | { 227 | } 228 | { 229 | ( 230 | varDecl() 231 | ( 232 | < COMMA > 233 | varDecl() 234 | )* 235 | )? 236 | { 237 | if (jjtThis.children == null || jjtThis.children.length == 0) 238 | { 239 | jjtThis.value = new ASTNodeValue(1, 0, 1, 0); 240 | } 241 | else 242 | { 243 | ASTNodeValue firstValue = (ASTNodeValue) ((ASTVarDeclStmt) jjtThis.children [ 0 ]).value; 244 | ASTNodeValue lastValue = (ASTNodeValue) ((ASTVarDeclStmt) jjtThis.children [ jjtThis.children.length - 1 ]).value; 245 | jjtThis.value = new ASTNodeValue(firstValue.getStartLine(), firstValue.getStartColumn(), lastValue.getEndLine(), lastValue.getEndColumn()); 246 | } 247 | } 248 | } 249 | 250 | public void statement() : 251 | { 252 | } 253 | { 254 | ( 255 | commandStatement() 256 | | blockStatement() 257 | | 258 | ( 259 | LOOKAHEAD(varDeclStatement()) 260 | varDeclStatement() 261 | | expressionStatement() 262 | ) 263 | ) 264 | } 265 | 266 | public void blockStatement() #BlockStmt : 267 | { 268 | Token firstToken; 269 | Token lastToken; 270 | } 271 | { 272 | firstToken = < OPEN_BRACE > 273 | ( 274 | statement() 275 | )* 276 | lastToken = < CLOSE_BRACE > 277 | { 278 | jjtThis.value = new ASTNodeValue(firstToken.beginLine, firstToken.beginColumn, lastToken.endLine, lastToken.endColumn); 279 | } 280 | } 281 | 282 | public void commandStatement() #CommandStmt : 283 | { 284 | Token t; 285 | Token firstToken; 286 | Token lastToken; 287 | } 288 | { 289 | firstToken = < START_COMMAND > 290 | t = < COMMAND_BODY > 291 | lastToken = < END_COMMAND > 292 | { 293 | jjtThis.value = new ASTNodeValue(firstToken.beginLine, firstToken.beginColumn, lastToken.endLine, lastToken.endColumn, t.image); 294 | } 295 | } 296 | 297 | public void varDeclStatement() : 298 | { 299 | } 300 | { 301 | varDecl() 302 | < SEMICOLON > 303 | } 304 | 305 | public void varDecl() #VarDeclStmt : 306 | { 307 | } 308 | { 309 | modifiers() 310 | type() 311 | identifier() 312 | ( 313 | < ASSIGN > 314 | expression() 315 | )? 316 | { 317 | ASTNodeValue firstValue = (ASTNodeValue) ASTUtil.getModifiersNode(jjtThis).value; 318 | Node initializer = ASTUtil.getInitializer(jjtThis); 319 | ASTNodeValue lastValue; 320 | if (initializer == null) 321 | { 322 | lastValue = (ASTNodeValue) ASTUtil.getNameNode(jjtThis).value; 323 | } 324 | else 325 | { 326 | lastValue = ASTUtil.getNodeValue(initializer); 327 | } 328 | jjtThis.value = new ASTNodeValue(firstValue.getStartLine(), firstValue.getStartColumn(), lastValue.getEndLine(), lastValue.getEndColumn()); 329 | } 330 | } 331 | 332 | public void expressionStatement() #ExpressionStmt : 333 | { 334 | Token lastToken; 335 | } 336 | { 337 | expression() 338 | lastToken = < SEMICOLON > 339 | { 340 | ASTNodeValue firstValue = (ASTNodeValue) ((SimpleNode) ASTUtil.getExpression(jjtThis)).value; 341 | jjtThis.value = new ASTNodeValue(firstValue.getStartLine(), firstValue.getStartColumn(), lastToken.endLine, lastToken.endColumn); 342 | } 343 | } 344 | 345 | public void expression() : 346 | { 347 | } 348 | { 349 | ( 350 | LOOKAHEAD(functionCallExpression()) 351 | functionCallExpression() 352 | | variableAccessExpression() 353 | ) 354 | | booleanLiteralExpression() 355 | | integerLiteralExpression() 356 | | stringLiteralExpression() 357 | } 358 | 359 | public void functionCallExpression() #FunctionCallExpr : 360 | { 361 | Token firstToken = null; 362 | Token lastToken; 363 | Token t; 364 | List < String > namespaces = new ArrayList < String > (); 365 | Type value; 366 | } 367 | { 368 | ( 369 | LOOKAHEAD(2) 370 | t = < WORD > 371 | { 372 | if (firstToken == null) firstToken = t; 373 | namespaces.add(t.image); 374 | } 375 | < DOUBLE_COLON > 376 | )* 377 | t = < WORD > 378 | { 379 | if (firstToken == null) firstToken = t; 380 | value = new Type(namespaces, t.image); 381 | } 382 | < OPEN_PARENTHESIS > 383 | ( 384 | expression() 385 | ( 386 | < COMMA > 387 | expression() 388 | )* 389 | )? 390 | lastToken = < CLOSE_PARENTHESIS > 391 | { 392 | jjtThis.value = new ASTNodeValue(firstToken.beginLine, firstToken.beginColumn, lastToken.endLine, lastToken.endColumn, value); 393 | } 394 | } 395 | 396 | public void variableAccessExpression() #VarAccessExpr : 397 | { 398 | } 399 | { 400 | type() 401 | { 402 | ASTNodeValue firstValue = (ASTNodeValue) ASTUtil.getVariableNode(jjtThis).value; 403 | ASTNodeValue lastValue = firstValue; 404 | jjtThis.value = new ASTNodeValue(firstValue.getStartLine(), firstValue.getStartColumn(), lastValue.getEndLine(), lastValue.getEndColumn()); 405 | } 406 | } 407 | 408 | public void booleanLiteralExpression() #BoolLiteralExpr : 409 | { 410 | Token t; 411 | Boolean value; 412 | } 413 | { 414 | ( 415 | t = < TRUE > 416 | { 417 | value = Boolean.TRUE; 418 | } 419 | | t = < FALSE > 420 | { 421 | value = Boolean.FALSE; 422 | } 423 | ) 424 | { 425 | jjtThis.value = new ASTNodeValue(t.beginLine, t.beginColumn, t.endLine, t.endColumn, value); 426 | } 427 | } 428 | 429 | public void integerLiteralExpression() #IntLiteralExpr : 430 | { 431 | Token t; 432 | } 433 | { 434 | t = < INTLITERAL > 435 | { 436 | try 437 | { 438 | jjtThis.value = new ASTNodeValue(t.beginLine, t.beginColumn, t.endLine, t.endColumn, Integer.parseInt(t.image)); 439 | } 440 | catch (NumberFormatException e) 441 | { 442 | // Possible if > Integer.MAX_VALUE or < Integer.MIN_VALUE 443 | throw Util.createParseException("Integer is out of bounds", t); 444 | } 445 | } 446 | } 447 | 448 | public void stringLiteralExpression() #StringLiteralExpr : 449 | { 450 | Token firstToken; 451 | Token lastToken; 452 | Token t = null; 453 | String value; 454 | } 455 | { 456 | firstToken = < STRING_START > 457 | ( 458 | t = < STRING_BODY > 459 | )? 460 | { 461 | value = t == null ? "" : t.image; 462 | } 463 | lastToken = < STRING_END > 464 | { 465 | jjtThis.value = new ASTNodeValue(firstToken.beginLine, firstToken.beginColumn, lastToken.endLine, lastToken.endColumn, value); 466 | } 467 | } 468 | 469 | public void identifier() #Identifier : 470 | { 471 | Token t; 472 | } 473 | { 474 | t = < WORD > 475 | { 476 | jjtThis.value = new ASTNodeValue(t.beginLine, t.beginColumn, t.endLine, t.endColumn, t.image); 477 | } 478 | } 479 | 480 | public void modifiers() #Modifiers : 481 | { 482 | Token firstToken = null; 483 | Token lastToken = null; 484 | int modifiers = Modifiers.NONE; 485 | } 486 | { 487 | ( 488 | ( 489 | lastToken = < INLINE > 490 | { 491 | if ((modifiers & Modifiers.INLINE) != 0) 492 | { 493 | throw Util.createParseException("Duplicate modifier \"inline\"", lastToken); 494 | } 495 | modifiers |= Modifiers.INLINE; 496 | } 497 | | lastToken = < CONST > 498 | { 499 | if ((modifiers & Modifiers.CONST) != 0) 500 | { 501 | throw Util.createParseException("Duplicate modifier \"const\"", lastToken); 502 | } 503 | modifiers |= Modifiers.CONST; 504 | } 505 | ) 506 | { 507 | if (firstToken == null) firstToken = lastToken; 508 | } 509 | )* 510 | { 511 | if (firstToken == null) 512 | { 513 | jjtThis.value = new ASTNodeValue(1, 0, 1, 0, 0); 514 | } 515 | else 516 | { 517 | jjtThis.value = new ASTNodeValue(firstToken.beginLine, firstToken.beginColumn, lastToken.endLine, lastToken.endColumn, modifiers); 518 | } 519 | } 520 | } 521 | 522 | public void type() #Type : 523 | { 524 | Token firstToken = null; 525 | Token lastToken; 526 | Token t; 527 | List < String > namespaces = new ArrayList < String > (); 528 | } 529 | { 530 | ( 531 | LOOKAHEAD(2) 532 | t = < WORD > 533 | { 534 | if (firstToken == null) firstToken = t; 535 | namespaces.add(t.image); 536 | } 537 | < DOUBLE_COLON > 538 | )* 539 | t = < WORD > 540 | { 541 | if (firstToken == null) firstToken = t; 542 | lastToken = t; 543 | Type value = new Type(namespaces, t.image); 544 | jjtThis.value = new ASTNodeValue(firstToken.beginLine, firstToken.beginColumn, lastToken.endLine, lastToken.endColumn, value); 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/minefunk/Main.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.minefunk; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.URISyntaxException; 9 | import java.net.URL; 10 | import java.nio.file.FileVisitResult; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import java.nio.file.SimpleFileVisitor; 15 | import java.nio.file.StandardOpenOption; 16 | import java.nio.file.attribute.BasicFileAttributes; 17 | import java.util.ArrayList; 18 | import java.util.Calendar; 19 | import java.util.Collections; 20 | import java.util.Enumeration; 21 | import java.util.HashMap; 22 | import java.util.LinkedHashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Set; 26 | import java.util.jar.JarEntry; 27 | import java.util.jar.JarFile; 28 | 29 | import net.earthcomputer.minefunk.parser.ASTProcessor; 30 | import net.earthcomputer.minefunk.parser.ASTRoot; 31 | import net.earthcomputer.minefunk.parser.CallGraphVisitor; 32 | import net.earthcomputer.minefunk.parser.Index; 33 | import net.earthcomputer.minefunk.parser.MinefunkParser; 34 | import net.earthcomputer.minefunk.parser.MinefunkParserConstants; 35 | import net.earthcomputer.minefunk.parser.ParseException; 36 | import net.earthcomputer.minefunk.parser.Token; 37 | 38 | /** 39 | * The main class of the compiler 40 | * 41 | * @author Earthcomputer 42 | */ 43 | public class Main { 44 | 45 | /** 46 | * The directory that the program is being run from 47 | */ 48 | private static Path workingDirectory = new File(".").getAbsoluteFile().toPath(); 49 | /** 50 | * The command line options 51 | */ 52 | private static CommandLineOptions cmdLineOptions; 53 | 54 | public static void main(String[] args) throws IOException { 55 | // Parse command line options 56 | cmdLineOptions = CommandLineOptions.parse(workingDirectory, args); 57 | if (cmdLineOptions == null) { 58 | System.err.println(CommandLineOptions.USAGE); 59 | return; 60 | } 61 | 62 | // Get matching files 63 | List inputFiles = new ArrayList<>(); 64 | Files.walkFileTree(workingDirectory, new SimpleFileVisitor() { 65 | @Override 66 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 67 | if (cmdLineOptions.getInputFileMatcher().accept(file.toFile())) { 68 | inputFiles.add(file.toFile()); 69 | } 70 | return FileVisitResult.CONTINUE; 71 | } 72 | 73 | @Override 74 | public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException { 75 | System.err.println("Failed to read file " + file); 76 | e.printStackTrace(); 77 | return FileVisitResult.CONTINUE; 78 | } 79 | }); 80 | 81 | Map asts = new LinkedHashMap<>(inputFiles.size()); 82 | Index index = new Index(); 83 | Map> exceptions = new LinkedHashMap<>(); 84 | 85 | // Parse asts from all input files 86 | for (File inputFile : inputFiles) { 87 | String filename = workingDirectory.relativize(inputFile.toPath()).toString(); 88 | exceptions.put(filename, new ArrayList<>()); 89 | InputStream in = new BufferedInputStream(new FileInputStream(inputFile)); 90 | ASTRoot root; 91 | try { 92 | root = new MinefunkParser(in).parse(); 93 | } catch (ParseException e) { 94 | exceptions.get(filename).add(e); 95 | continue; 96 | } finally { 97 | in.close(); 98 | } 99 | asts.put(filename, root); 100 | } 101 | addStdLib(asts, exceptions); 102 | if (handleExceptions("parsing", exceptions)) { 103 | return; 104 | } 105 | 106 | // Pre-index check 107 | asts.forEach((filename, root) -> { 108 | ASTProcessor.preIndexCheck(root, exceptions.get(filename)); 109 | }); 110 | if (handleExceptions("pre-index check", exceptions)) { 111 | return; 112 | } 113 | 114 | // Indexing 115 | asts.forEach((filename, root) -> { 116 | ASTProcessor.index(root, index, exceptions.get(filename)); 117 | }); 118 | if (handleExceptions("indexing", exceptions)) { 119 | return; 120 | } 121 | 122 | // Resolve functions after indexing 123 | List globalExceptions = new ArrayList<>(); 124 | index.resolvePendingFunctions(globalExceptions); 125 | if (handleExceptions("resolve functions", Collections.singletonMap("global", globalExceptions))) { 126 | return; 127 | } 128 | 129 | // Post-index check 130 | asts.forEach((filename, root) -> { 131 | ASTProcessor.postIndexCheck(root, index, exceptions.get(filename)); 132 | }); 133 | if (handleExceptions("post-index check", exceptions)) { 134 | return; 135 | } 136 | 137 | // Check circular references 138 | Map> callGraph = new HashMap<>(); 139 | asts.forEach((filename, root) -> { 140 | ASTProcessor.addToCallGraph(callGraph, root, index, exceptions.get(filename)); 141 | }); 142 | CallGraphAnalyzer.StronglyConnectedComponentsFinder.Result cycleSearchResults = new CallGraphAnalyzer.StronglyConnectedComponentsFinder<>( 143 | callGraph).findStronglyConnectedComponents(); 144 | asts.forEach((filename, root) -> { 145 | ASTProcessor.checkForCyclicReferences(cycleSearchResults, root, index, exceptions.get(filename)); 146 | }); 147 | if (handleExceptions("circular references check", exceptions)) { 148 | return; 149 | } 150 | 151 | // Command generation 152 | Map> commandLists = new HashMap<>(); 153 | asts.forEach((filename, root) -> { 154 | ASTProcessor.generateCommandLists(root, index, commandLists, exceptions.get(filename)); 155 | }); 156 | if (handleExceptions("command generation", exceptions)) { 157 | return; 158 | } 159 | 160 | // Output commands generated 161 | commandLists.forEach((funcId, commands) -> { 162 | File outputFile = new File(cmdLineOptions.getOutputDirectory(), funcId.replace(':', '/') + ".mcfunction"); 163 | outputFile.getParentFile().mkdirs(); 164 | try { 165 | Files.write(outputFile.toPath(), commands, StandardOpenOption.CREATE); 166 | } catch (IOException e) { 167 | System.err.println("Failed to write function " + funcId + ", " + e); 168 | } 169 | }); 170 | } 171 | 172 | /** 173 | * Adds the standard library to the list of ASTs 174 | * 175 | * @param asts 176 | * - the list of ASTs 177 | * @param exceptions 178 | * - the compiler errors to add to 179 | */ 180 | private static void addStdLib(Map asts, Map> exceptions) { 181 | try { 182 | URL jarLocation = Main.class.getResource("/" + Main.class.getName().replace('.', '/') + ".class"); 183 | if (jarLocation.getProtocol().contains("jar")) { 184 | File jarFile = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI()); 185 | JarFile jar = new JarFile(jarFile); 186 | Enumeration entries = jar.entries(); 187 | while (entries.hasMoreElements()) { 188 | JarEntry entry = entries.nextElement(); 189 | if (entry.getName().startsWith("stdlib/") && entry.getName().endsWith(".funk")) { 190 | exceptions.put(entry.getName(), new ArrayList<>()); 191 | try { 192 | asts.put(entry.getName(), new MinefunkParser(jar.getInputStream(entry)).parse()); 193 | } catch (ParseException e) { 194 | exceptions.get(entry.getName()).add(e); 195 | } 196 | } 197 | } 198 | jar.close(); 199 | } else { 200 | Path path = Paths.get(Main.class.getResource("/stdlib").toURI()); 201 | Path parent = path.getParent(); 202 | Files.walkFileTree(path, new SimpleFileVisitor() { 203 | @Override 204 | public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { 205 | if (!path.toString().endsWith(".funk")) { 206 | return FileVisitResult.CONTINUE; 207 | } 208 | String filename = parent.relativize(path).toString(); 209 | exceptions.put(filename, new ArrayList<>()); 210 | try { 211 | asts.put(filename, new MinefunkParser(Files.newBufferedReader(path)).parse()); 212 | } catch (ParseException e) { 213 | exceptions.get(filename).add(e); 214 | } 215 | return FileVisitResult.CONTINUE; 216 | } 217 | 218 | @Override 219 | public FileVisitResult visitFileFailed(Path path, IOException e) throws IOException { 220 | e.printStackTrace(); 221 | return FileVisitResult.TERMINATE; 222 | } 223 | }); 224 | } 225 | } catch (IOException | URISyntaxException e) { 226 | System.err.println("Unable to read stdlib"); 227 | e.printStackTrace(); 228 | System.exit(1); 229 | } 230 | } 231 | 232 | /** 233 | * Handles any compiler errors that occur in each phase 234 | * 235 | * @param phase 236 | * - the compilation phase to output to the user 237 | * @param exceptions 238 | * - the map of parse exceptions that occurred for each file 239 | * @return Whether there were any compiler errors during this phase 240 | */ 241 | private static boolean handleExceptions(String phase, Map> exceptions) { 242 | // Check if there were any errors at all 243 | int errorCount = exceptions.values().stream().mapToInt(List::size).sum(); 244 | if (errorCount == 0) { 245 | return false; 246 | } 247 | 248 | // Summarizing message 249 | System.err.printf("Encountered %d errors during phase %s:\n", errorCount, phase); 250 | 251 | // Loop through the errors in each file 252 | exceptions.forEach((filename, errorsInFile) -> { 253 | // If there were no errors in this file, skip it. 254 | if (errorsInFile.isEmpty()) { 255 | return; 256 | } 257 | 258 | // Read lines from the file so we can helpfully echo them to the 259 | // user 260 | List fileLines; 261 | try { 262 | fileLines = Files.readAllLines(workingDirectory.resolve(filename)); 263 | } catch (IOException e) { 264 | fileLines = Collections.emptyList(); 265 | } 266 | final List fileLines_f = fileLines; 267 | 268 | errPrintDivider(); 269 | 270 | // Print the name of the file 271 | System.err.println("In file " + filename + ":"); 272 | // Print all the errors in the file 273 | errorsInFile.forEach(ex -> errOutputParseException(fileLines_f, ex)); 274 | }); 275 | 276 | errPrintDivider(); 277 | 278 | // Print a witty comment to brighten up the user's day 279 | errPrintWittyComment(); 280 | 281 | // Print stack traces 282 | if (cmdLineOptions.showStacktrace()) { 283 | errPrintDivider(); 284 | System.err.println("In case any of these were errors in the compiler itself,"); 285 | System.err.println("here are the stack traces:"); 286 | exceptions.forEach((filename, errorsInFile) -> { 287 | errorsInFile.forEach(ex -> { 288 | errPrintDivider(); 289 | ex.printStackTrace(); 290 | }); 291 | }); 292 | } 293 | 294 | return true; 295 | } 296 | 297 | /** 298 | * Outputs a compiler error in a user-friendly way 299 | * 300 | * @param fileLines 301 | * - the lines read from the errored file 302 | * @param ex 303 | * - the compiler error 304 | */ 305 | private static void errOutputParseException(List fileLines, ParseException ex) { 306 | if (ex.expectedTokenSequences != null) { 307 | errPrintSyntaxError(fileLines, ex); 308 | } else { 309 | errPrintNonSyntaxError(fileLines, ex); 310 | } 311 | System.err.println(); 312 | } 313 | 314 | /** 315 | * Outputs a syntax error in a user-friendly way 316 | * 317 | * @param fileLines 318 | * - the lines read from the errored file 319 | * @param ex 320 | * - the syntax error 321 | */ 322 | private static void errPrintSyntaxError(List fileLines, ParseException ex) { 323 | // Current token is the one that's OK, the errored token is the next one 324 | Token errTok = ex.currentToken.next; 325 | 326 | // Echo the errored token 327 | errCopyLineFromFile(fileLines, errTok.beginLine, errTok.beginColumn, errTok.endLine, errTok.endColumn); 328 | 329 | // An informative message as to why it's a syntax error 330 | System.err.printf("From %d:%d to %d:%d... Token \"%s\" encountered, but was not expected in this location.\n", 331 | errTok.beginLine, errTok.beginColumn, errTok.endLine, errTok.endColumn, errTok.image); 332 | StringBuilder expected = new StringBuilder("\tExpected one of the following sequences instead:\n"); 333 | for (int[] sequence : ex.expectedTokenSequences) { 334 | expected.append("\t\t|"); 335 | for (int tokKind : sequence) { 336 | expected.append(" "); 337 | expected.append(MinefunkParserConstants.tokenImage[tokKind]); 338 | } 339 | expected.append("\n"); 340 | } 341 | System.err.print(expected); 342 | } 343 | 344 | /** 345 | * Outputs a compiler error that's not a syntax error in a user-friendly way 346 | * 347 | * @param fileLines 348 | * - the lines read from the errored file 349 | * @param ex 350 | * - the compiler error 351 | */ 352 | private static void errPrintNonSyntaxError(List fileLines, ParseException ex) { 353 | Token tok = ex.currentToken; 354 | // Echo the errored code from the file 355 | errCopyLineFromFile(fileLines, tok.beginLine, tok.beginColumn, tok.endLine, tok.endColumn); 356 | // Description as to why it's an error 357 | System.err.printf("From %d:%d to %d:%d... %s\n", tok.beginLine, tok.beginColumn, tok.endLine, tok.endColumn, 358 | ex.getMessage()); 359 | } 360 | 361 | /** 362 | * Echos a region of a file to the console and highlights it 363 | * 364 | * @param fileLines 365 | * @param beginLine 366 | * @param beginColumn 367 | * @param endLine 368 | * @param endColumn 369 | */ 370 | private static void errCopyLineFromFile(List fileLines, int beginLine, int beginColumn, int endLine, 371 | int endColumn) { 372 | // Can't echo lines that aren't there 373 | if (endLine > fileLines.size()) { 374 | System.err.println("[Unable to read from file]"); 375 | return; 376 | } 377 | 378 | /* 379 | * The following code accounts for the following situations. Errored 380 | * code is denoted as capital letters. All other characters are 381 | * non-errors. 382 | */ 383 | // @formatter:off 384 | 385 | // 1. Single character 386 | // Input: hello World! 387 | // Output: 388 | // hello World! 389 | // ^ 390 | // 2. Single line 391 | // Input: hello WORLD! 392 | // Output: 393 | // hello WORLD! 394 | // ^---^ 395 | // 3. Two lines 396 | // Input: 397 | // heLLO 398 | // WORld! 399 | // Output: 400 | // heLLO 401 | // ^-- 402 | // WORld! 403 | // --^ 404 | // 4. Many lines 405 | // Input: 406 | // heLL 407 | // OWO 408 | // RLD! 409 | // Output: 410 | // heLL 411 | // ^- 412 | // [...] 413 | // RLD! 414 | // --^ 415 | // @formatter:on 416 | 417 | // Always print first line 418 | String line = fileLines.get(beginLine - 1); 419 | System.err.println(line); 420 | // Print whitespace up to the start of the error 421 | for (int i = 0; i < beginColumn - 1; i++) { 422 | System.err.print(Util.charToWhitespace(line.charAt(i))); 423 | } 424 | // If beginLine and endLine are equal, we have case 1 or 2. Otherwise, 425 | // we have case 3 or 4 426 | if (beginLine == endLine) { 427 | // Print a ^ at the start of the error 428 | System.err.print('^'); 429 | // Print -s between the start and end of the error 430 | for (int i = beginColumn + 1; i < endColumn; i++) { 431 | System.err.print('-'); 432 | } 433 | // In case 2, print a ^ at the end of the error. If this was done in 434 | // case 1, we'd have a repeat ^ 435 | if (beginColumn != endColumn) { 436 | System.err.print('^'); 437 | } 438 | System.err.println(); 439 | } else { 440 | // Print a ^ at the start of the error 441 | System.err.print('^'); 442 | // Print -s until the end of the line 443 | for (int i = beginColumn + 1; i < line.length(); i++) { 444 | System.err.print('-'); 445 | } 446 | System.err.println(); 447 | // In case 4, we print a [...] 448 | if (endLine > beginLine + 1) { 449 | System.err.println("[...]"); 450 | } 451 | // Echo the last line from the file 452 | line = fileLines.get(endLine - 1); 453 | System.err.println(line); 454 | // Print -s until the end of the error 455 | for (int i = 0; i < endColumn; i++) { 456 | System.err.print('-'); 457 | } 458 | // Print a ^ at the end of the error 459 | System.err.print('^'); 460 | System.err.println(); 461 | } 462 | } 463 | 464 | /** 465 | * Prints a witty comment to brighten up the user's day 466 | */ 467 | private static void errPrintWittyComment() { 468 | // Date-specific witty comments 469 | Calendar cal = Calendar.getInstance(); 470 | int year = cal.get(Calendar.YEAR); 471 | int month = cal.get(Calendar.MONTH); 472 | int day = cal.get(Calendar.DAY_OF_MONTH); 473 | int[] easter = Util.calculateEaster(year); 474 | 475 | if (month == Calendar.DECEMBER && day == 25) { 476 | // Christmas 477 | System.err.println("// Apart from this, I hope Christmas is going well!"); 478 | } else if (month == Calendar.OCTOBER && day == 31) { 479 | // Halloween 480 | System.err.println("// Put a pumpkin on your head, you'd look better with it"); 481 | } else if (month == Calendar.JANUARY && day == 1) { 482 | // New Year's Day 483 | System.err.println("// GREAT way to start a year!"); 484 | } else if (month == Calendar.APRIL && day == 1 && cal.get(Calendar.HOUR_OF_DAY) < 12) { 485 | // April Fools 486 | System.err.println("// April fools!"); 487 | } else if (month == Calendar.FEBRUARY && day == 29) { 488 | // Leap Day 489 | System.err.println("// I hope this isn't your birthday. Just sayin'"); 490 | } else if (month == Calendar.DECEMBER && day == 18) { 491 | // Earthcomputer's Birthday 492 | System.err.println("// A gift for Earthcomputer's birthday"); 493 | } else if (month == easter[0] && day == easter[1]) { 494 | // Easter 495 | System.err.println("// Your Easter egg has hatched!"); 496 | } 497 | 498 | // Print a random witty comment 499 | String[] wittyComments = { "You're a map-maker! Make maps then, not mistakes!", "Oops!", 500 | "It's not you, it's me...", "Now for the hard part: the fix...", 501 | "Maybe if you just stopped making mistakes", "Man 0-1 Machine", "Okay, whatever", 502 | "Perhaps it would be better if you just went back to playing Minecraft", "No! What happened?!", 503 | "Earthcomputer has nice hair", "Minefunk left the game", "My favourite block is the command block", 504 | "My favourite item is pufferfish", "My favourite mob is the vex", "kill @a[name=!Earthcomputer]", 505 | "Awkwardness is happening!", "Map-making is harder than circles in Minecraft" }; 506 | System.err.println("// " + wittyComments[Math.abs((int) System.nanoTime()) % wittyComments.length]); 507 | } 508 | 509 | /** 510 | * Prints a divider (lots of -s) 511 | */ 512 | private static void errPrintDivider() { 513 | System.err.println("--------------------------------"); 514 | } 515 | 516 | } 517 | --------------------------------------------------------------------------------