All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.adrodoc55.minecraft.mpl.compilation.MplCompiler Maven / Gradle / Ivy

/*
 * Minecraft Programming Language (MPL): A language for easy development of command block
 * applications including an IDE.
 *
 * © Copyright (C) 2016 Adrodoc55
 *
 * This file is part of MPL.
 *
 * MPL is free software: you can redistribute it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MPL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with MPL. If not, see
 * .
 *
 *
 *
 * Minecraft Programming Language (MPL): Eine Sprache für die einfache Entwicklung von Commandoblock
 * Anwendungen, inklusive einer IDE.
 *
 * © Copyright (C) 2016 Adrodoc55
 *
 * Diese Datei ist Teil von MPL.
 *
 * MPL ist freie Software: Sie können diese unter den Bedingungen der GNU General Public License,
 * wie von der Free Software Foundation, Version 3 der Lizenz oder (nach Ihrer Wahl) jeder späteren
 * veröffentlichten Version, weiterverbreiten und/oder modifizieren.
 *
 * MPL wird in der Hoffnung, dass es nützlich sein wird, aber OHNE JEDE GEWÄHRLEISTUNG,
 * bereitgestellt; sogar ohne die implizite Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN
 * BESTIMMTEN ZWECK. Siehe die GNU General Public License für weitere Details.
 *
 * Sie sollten eine Kopie der GNU General Public License zusammen mit MPL erhalten haben. Wenn
 * nicht, siehe .
 */
package de.adrodoc55.minecraft.mpl.compilation;

import static de.adrodoc55.minecraft.mpl.compilation.CompilerOptions.CompilerOption.DEBUG;
import static java.util.stream.Collectors.toList;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;

import de.adrodoc55.minecraft.coordinate.Coordinate3D;
import de.adrodoc55.minecraft.coordinate.Orientation3D;
import de.adrodoc55.minecraft.mpl.antlr.MplBaseListener;
import de.adrodoc55.minecraft.mpl.ast.MplAstVisitor;
import de.adrodoc55.minecraft.mpl.ast.MplAstVisitorImpl;
import de.adrodoc55.minecraft.mpl.ast.chainparts.program.MplProcess;
import de.adrodoc55.minecraft.mpl.ast.chainparts.program.MplProgram;
import de.adrodoc55.minecraft.mpl.blocks.CommandBlock;
import de.adrodoc55.minecraft.mpl.blocks.MplBlock;
import de.adrodoc55.minecraft.mpl.blocks.Transmitter;
import de.adrodoc55.minecraft.mpl.chain.ChainContainer;
import de.adrodoc55.minecraft.mpl.chain.CommandBlockChain;
import de.adrodoc55.minecraft.mpl.commands.chainlinks.Command;
import de.adrodoc55.minecraft.mpl.commands.chainlinks.InternalCommand;
import de.adrodoc55.minecraft.mpl.commands.chainlinks.NoOperationCommand;
import de.adrodoc55.minecraft.mpl.interpretation.Include;
import de.adrodoc55.minecraft.mpl.interpretation.MplInterpreter;
import de.adrodoc55.minecraft.mpl.placement.MplDebugProgramPlacer;
import de.adrodoc55.minecraft.mpl.placement.MplProgramPlacer;
import de.adrodoc55.minecraft.mpl.placement.NotEnoughSpaceException;


/**
 * @author Adrodoc55
 */
public class MplCompiler extends MplBaseListener {

  public static MplCompilationResult compile(File programFile, CompilerOptions options)
      throws IOException, CompilationFailedException {
    // Assembly
    MplProgram program = assembleProgram(programFile);

    // Materializing
    ChainContainer container = materialize(program, options);

    // Placement
    List chains = place(container, options);

    Orientation3D orientation = program.getOrientation();

    // Inserts
    insertRelativeCoordinates(orientation, chains);

    // Result
    return toResult(orientation, chains);
  }

  public static MplCompilationResult toResult(Orientation3D orientation,
      List chains) {
    List blocks = chains.stream()//
        .flatMap(c -> c.getBlocks().stream())//
        .collect(toList());

    ImmutableMap result = Maps.uniqueIndex(blocks, b -> b.getCoordinate());
    return new MplCompilationResult(orientation, result);
  }

  public static void insertRelativeCoordinates(Orientation3D orientation,
      Iterable chains) {
    for (CommandBlockChain chain : chains) {
      insertRelativeCoordinates(orientation, chain.getBlocks());
    }
  }

  public static List place(ChainContainer container, CompilerOptions options)
      throws CompilationFailedException {
    try {
      if (options.hasOption(DEBUG)) {
        return new MplDebugProgramPlacer(container, options).place();
      } else {
        return new MplProgramPlacer(container, options).place();
      }
    } catch (NotEnoughSpaceException ex) {
      throw new CompilationFailedException(
          "The maximal coordinate is to small to place the entire program", ex);
    }
  }

  public static ChainContainer materialize(MplProgram program, CompilerOptions options) {
    MplAstVisitor visitor = new MplAstVisitorImpl(options);
    program.accept(visitor);
    ChainContainer container = visitor.getResult();
    return container;
  }

  public static MplProgram assembleProgram(File programFile)
      throws IOException, CompilationFailedException {
    MplCompiler compiler = new MplCompiler();
    MplProgram program = compiler.assemble(programFile);
    List exceptions = program.getExceptions();
    if (!exceptions.isEmpty()) {
      ImmutableListMultimap index =
          Multimaps.index(exceptions, ex -> ex.getSource().file);
      throw new CompilationFailedException(index);
    }
    return program;
  }

  private SetMultimap programContent = HashMultimap.create();
  private Set addedInterpreters = new HashSet<>();
  private LinkedList includeTodos;
  private MplProgram program;

  private Map interpreterCache = new HashMap<>();

  private MplInterpreter interpret(File file) throws IOException {
    MplInterpreter interpreter = interpreterCache.get(file);
    if (interpreter == null) {
      interpreter = MplInterpreter.interpret(file);
    }
    return interpreter;
  }

  private MplCompiler() {}

  private MplProgram assemble(File programFile) throws IOException {
    includeTodos = new LinkedList();
    MplInterpreter main = interpret(programFile);
    if (main.getProgram().isScript()) {
      return main.getProgram();
    }
    program = main.getProgram();

    // The main interpreter does not need to be added to itself
    addedInterpreters.add(programFile);

    // Skip includes that reference the same file
    List includes = main.getIncludes().values().stream()
        .filter(include -> !program.containsProcess(include.getProcessName()))
        .collect(Collectors.toList());
    includeTodos.addAll(includes);

    // Mark all processes of the main file
    program.getProcesses().stream().map(p -> p.getName()).forEach(name -> {
      programContent.put(programFile, name);
    });

    doIncludes();
    return program;
  }

  private void doIncludes() {
    while (!includeTodos.isEmpty()) {
      Include include = includeTodos.poll();
      String processName = include.getProcessName();
      if (processName == null) {
        massInclude(include);
      } else {
        processInclude(include);
      }
    }
  }

  private void processInclude(Include include) {
    String processName = include.getProcessName();
    FileException lastException = null;
    List found = new LinkedList<>();
    for (File file : include.getFiles()) {
      MplInterpreter interpreter = null;
      try {
        interpreter = interpret(file);
      } catch (Exception ex) {
        lastException = new FileException(ex, file);
        continue;
      }
      MplProgram program = interpreter.getProgram();
      if (program.containsProcess(processName)) {
        found.add(interpreter);
      }
    }

    if (found.isEmpty()) {
      CompilerException ex = new CompilerException(include.getSource(),
          "Could not resolve process " + processName, lastException);
      program.getExceptions().add(ex);
    } else if (found.size() > 1) {
      CompilerException ex = createAmbigiousProcessException(include, found);
      program.getExceptions().add(ex);
    } else {
      MplInterpreter interpreter = found.get(0);
      addInterpreter(interpreter);
      addProcess(interpreter, processName);
    }
  }

  public CompilerException createAmbigiousProcessException(Include include,
      List found) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < found.size() - 1; i++) {
      MplInterpreter interpreter = found.get(i);
      File programFile = interpreter.getProgramFile();
      sb.append(programFile);
      if (i + 1 < found.size() - 1) {
        sb.append(", ");
      }
    }
    sb.append(" and ");
    sb.append(found.get(found.size() - 1).getProgramFile());
    CompilerException ex = new CompilerException(include.getSource(),
        "Process " + include.getProcessName() + " is ambigious. It was found in '" + sb + "!");
    return ex;
  }

  public void addProcess(MplInterpreter interpreter, String processName) {
    MplProcess process = interpreter.getProgram().getProcess(processName);
    File file = interpreter.getProgramFile();
    Set processNames = programContent.get(file);
    if (!processNames.contains(process.getName())) {
      program.addProcess(process);
      includeTodos.addAll(interpreter.getIncludes().get(processName));
      programContent.put(file, processName);
    }
  }

  private void massInclude(Include include) {
    for (File file : include.getFiles()) {
      MplInterpreter interpreter = null;
      try {
        interpreter = interpret(file);
      } catch (IOException ex) {
        CompilerException compilerException =
            new CompilerException(include.getSource(), "Couldn't include '" + file + "'", ex);
        program.getExceptions().add(compilerException);
        return;
      }
      if (interpreter.getProgram().isScript()) {
        CompilerException compilerException = new CompilerException(include.getSource(),
            "Can't include script '" + file.getName() + "'. Scripts may not be included.");
        program.getExceptions().add(compilerException);
        return;
      }
      addInterpreter(interpreter);
      addAllProcesses(interpreter);
    }
  }

  public void addAllProcesses(MplInterpreter interpreter) {
    Collection processes = interpreter.getProgram().getProcesses();
    for (MplProcess process : processes) {
      addProcess(interpreter, process.getName());
    }
  }

  public void addInterpreter(MplInterpreter interpreter) {
    if (addedInterpreters.add(interpreter.getProgramFile())) {
      MplProgram program = interpreter.getProgram();
      this.program.getExceptions().addAll(program.getExceptions());
      this.program.getInstall().addAll(program.getInstall());
      this.program.getUninstall().addAll(program.getUninstall());
    }
  }

  private static final Pattern thisPattern =
      Pattern.compile("\\$\\{\\s*this\\s*([+-])\\s*(\\d+)\\s*\\}");

  private static final Pattern originPattern = Pattern.compile(
      "\\$\\{\\s*origin\\s*(?:\\+\\s*\\(\\s*(-?\\d+)\\s+(-?\\d+)\\s+(-?\\d+)\\s*\\)\\s*)?\\}");

  private static void insertRelativeCoordinates(Orientation3D orientation, List blocks) {
    for (int i = 0; i < blocks.size(); i++) {
      MplBlock currentBlock = blocks.get(i);
      if (!(currentBlock instanceof CommandBlock)) {
        continue;
      }
      CommandBlock current = (CommandBlock) currentBlock;
      Matcher thisMatcher = thisPattern.matcher(current.getCommand());
      StringBuffer thisSb = new StringBuffer();
      while (thisMatcher.find()) {
        boolean minus = thisMatcher.group(1).equals("-");
        int relative = Integer.parseInt(thisMatcher.group(2));
        int direction = 1;
        if (minus) {
          direction = -1;
        }
        int refIndex = i;
        for (int steps = relative; steps > 0; steps--) {
          refIndex += direction;
          if (refIndex < 0) {
            refIndex = 0;
            break;
          }
          MplBlock block = blocks.get(refIndex);
          if (isNop(block) || (isInternal(block) && !isInternal(current))) {
            steps++;
          }
        }
        Coordinate3D referenced;
        if (refIndex < 0) {
          referenced = blocks.get(0).getCoordinate().minus(orientation.getA().toCoordinate());
        } else {
          referenced = blocks.get(refIndex).getCoordinate();
        }
        Coordinate3D relativeCoordinate = referenced.minus(current.getCoordinate());
        thisMatcher.appendReplacement(thisSb, relativeCoordinate.toRelativeString());
      }
      thisMatcher.appendTail(thisSb);
      current.setCommand(thisSb.toString());

      Matcher originMatcher = originPattern.matcher(current.getCommand());
      StringBuffer originSb = new StringBuffer();
      while (originMatcher.find()) {
        Coordinate3D relativeCoordinate = current.getCoordinate().mult(-1);
        if (originMatcher.group(1) != null) {
          int x = Integer.parseInt(originMatcher.group(1));
          int y = Integer.parseInt(originMatcher.group(2));
          int z = Integer.parseInt(originMatcher.group(3));
          Coordinate3D referenced = new Coordinate3D(x, y, z);
          relativeCoordinate = relativeCoordinate.plus(referenced);
        }
        originMatcher.appendReplacement(originSb, relativeCoordinate.toRelativeString());
      }
      originMatcher.appendTail(originSb);
      current.setCommand(originSb.toString());
    }
  }

  private static boolean isInternal(MplBlock block) {
    if (block instanceof Transmitter) {
      Transmitter transmitter = (Transmitter) block;
      return transmitter.isInternal();
    }
    if (block instanceof CommandBlock) {
      CommandBlock commandBlock = (CommandBlock) block;
      Command command = commandBlock.toCommand();
      return command instanceof InternalCommand;
    }
    return false;
  }

  private static boolean isNop(MplBlock block) {
    if (block instanceof CommandBlock) {
      CommandBlock commandBlock = (CommandBlock) block;
      Command command = commandBlock.toCommand();
      return command instanceof NoOperationCommand;
    }
    return false;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy