Java tutorial
/* * NextFractal 1.3.0 * https://github.com/nextbreakpoint/nextfractal * * Copyright 2015-2016 Andrea Medeghini * * This file is part of NextFractal. * * NextFractal is an application for creating fractals and other graphics artifacts. * * NextFractal 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. * * NextFractal 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 NextFractal. If not, see <http://www.gnu.org/licenses/>. * */ package com.nextbreakpoint.nextfractal.mandelbrot.compiler.java; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import com.nextbreakpoint.nextfractal.mandelbrot.compiler.CompilerError; import com.nextbreakpoint.nextfractal.mandelbrot.compiler.CompilerErrorStrategy; import com.nextbreakpoint.nextfractal.mandelbrot.compiler.CompilerReport; import com.nextbreakpoint.nextfractal.mandelbrot.compiler.CompilerReport.Type; import com.nextbreakpoint.nextfractal.mandelbrot.compiler.CompilerVariable; import com.nextbreakpoint.nextfractal.mandelbrot.compiler.ExpressionContext; import com.nextbreakpoint.nextfractal.mandelbrot.core.Color; import com.nextbreakpoint.nextfractal.mandelbrot.core.Expression; import com.nextbreakpoint.nextfractal.mandelbrot.core.FastExpression; import com.nextbreakpoint.nextfractal.mandelbrot.core.MutableNumber; import com.nextbreakpoint.nextfractal.mandelbrot.core.Number; import com.nextbreakpoint.nextfractal.mandelbrot.core.Orbit; import com.nextbreakpoint.nextfractal.mandelbrot.core.Palette; import com.nextbreakpoint.nextfractal.mandelbrot.core.Scope; import com.nextbreakpoint.nextfractal.mandelbrot.core.Trap; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTBuilder; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTColor; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTException; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTFractal; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTOrbit; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTOrbitBegin; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTOrbitEnd; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTOrbitLoop; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTOrbitTrap; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTOrbitTrapOp; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTPalette; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTPaletteElement; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTRule; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.ASTStatement; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.MandelbrotLexer; import com.nextbreakpoint.nextfractal.mandelbrot.grammar.MandelbrotParser; public class JavaReportCompiler { private static final Logger logger = Logger.getLogger(JavaReportCompiler.class.getName()); private final String packageName; private final String className; public JavaReportCompiler(String packageName, String className) { this.packageName = packageName; this.className = className; } public CompilerReport generateReport(String source) throws IOException { List<CompilerError> errors = new ArrayList<>(); ASTFractal ast = parse(source, errors); if (errors.size() == 0) { ExpressionContext orbitContext = new ExpressionContext(); String orbitSource = buildOrbit(orbitContext, ast, errors); ExpressionContext colorContext = new ExpressionContext(); String colorSource = buildColor(colorContext, ast, errors); if (logger.isLoggable(Level.FINE)) { logger.fine(orbitSource); logger.fine(colorSource); } return new CompilerReport(ast, Type.JAVA, orbitSource, colorSource, errors); } return new CompilerReport(ast, Type.JAVA, "", "", errors); } private ASTFractal parse(String source, List<CompilerError> errors) throws IOException { try { ANTLRInputStream is = new ANTLRInputStream(new StringReader(source)); MandelbrotLexer lexer = new MandelbrotLexer(is); CommonTokenStream tokens = new CommonTokenStream(lexer); MandelbrotParser parser = new MandelbrotParser(tokens); parser.setErrorHandler(new CompilerErrorStrategy(errors)); ParseTree fractalTree = parser.fractal(); if (fractalTree != null) { ASTBuilder builder = parser.getBuilder(); ASTFractal fractal = builder.getFractal(); return fractal; } } catch (ASTException e) { CompilerError error = new CompilerError(CompilerError.ErrorType.M_COMPILER, e.getLocation().getLine(), e.getLocation().getCharPositionInLine(), e.getLocation().getStartIndex(), e.getLocation().getStopIndex() - e.getLocation().getStartIndex(), e.getMessage()); logger.log(Level.FINE, error.toString(), e); errors.add(error); } catch (Exception e) { CompilerError error = new CompilerError(CompilerError.ErrorType.M_COMPILER, 0L, 0L, 0L, 0L, e.getMessage()); logger.log(Level.FINE, error.toString(), e); errors.add(error); } return null; } private String buildOrbit(ExpressionContext context, ASTFractal fractal, List<CompilerError> errors) { try { StringBuilder builder = new StringBuilder(); Map<String, CompilerVariable> variables = new HashMap<>(); compileOrbit(context, builder, variables, fractal); return builder.toString(); } catch (ASTException e) { CompilerError error = new CompilerError(CompilerError.ErrorType.M_COMPILER, e.getLocation().getLine(), e.getLocation().getCharPositionInLine(), e.getLocation().getStartIndex(), e.getLocation().getStopIndex() - e.getLocation().getStartIndex(), e.getMessage()); logger.log(Level.FINE, error.toString(), e); errors.add(error); } return ""; } private String buildColor(ExpressionContext context, ASTFractal fractal, List<CompilerError> errors) { try { StringBuilder builder = new StringBuilder(); Map<String, CompilerVariable> variables = new HashMap<>(); compileColor(context, builder, variables, fractal); return builder.toString(); } catch (ASTException e) { CompilerError error = new CompilerError(CompilerError.ErrorType.M_COMPILER, e.getLocation().getLine(), e.getLocation().getCharPositionInLine(), e.getLocation().getStartIndex(), e.getLocation().getStopIndex() - e.getLocation().getStartIndex(), e.getMessage()); logger.log(Level.FINE, error.toString(), e); errors.add(error); } return ""; } private String compileOrbit(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTFractal fractal) { builder.append("package "); builder.append(packageName); builder.append(";\n"); builder.append("import static "); if (Boolean.getBoolean("mandelbrot.expression.fastmath")) { builder.append(FastExpression.class.getCanonicalName()); } else { builder.append(Expression.class.getCanonicalName()); } builder.append(".*;\n"); builder.append("import "); builder.append(Number.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(MutableNumber.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(Trap.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(Orbit.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(Scope.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(List.class.getCanonicalName()); builder.append(";\n"); builder.append("@SuppressWarnings(value=\"unused\")\n"); builder.append("public class "); builder.append(className); builder.append("Orbit extends Orbit {\n"); buildOrbit(context, builder, variables, fractal); builder.append("}\n"); return builder.toString(); } private String compileColor(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTFractal fractal) { builder.append("package "); builder.append(packageName); builder.append(";\n"); builder.append("import static "); if (Boolean.getBoolean("mandelbrot.expression.fastmath")) { builder.append(FastExpression.class.getCanonicalName()); } else { builder.append(Expression.class.getCanonicalName()); } builder.append(".*;\n"); builder.append("import "); builder.append(Number.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(MutableNumber.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(Palette.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(Color.class.getCanonicalName()); builder.append(";\n"); builder.append("import "); builder.append(Scope.class.getCanonicalName()); builder.append(";\n"); builder.append("@SuppressWarnings(value=\"unused\")\n"); builder.append("public class "); builder.append(className); builder.append("Color extends Color {\n"); buildColor(context, builder, variables, fractal); builder.append("}\n"); return builder.toString(); } private void buildOrbit(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> scope, ASTFractal fractal) { if (fractal != null) { for (CompilerVariable var : fractal.getOrbitVariables()) { scope.put(var.getName(), var); } for (CompilerVariable var : fractal.getStateVariables()) { scope.put(var.getName(), var); } compile(context, builder, scope, fractal.getStateVariables(), fractal.getOrbit()); } } private void buildColor(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> scope, ASTFractal fractal) { if (fractal != null) { for (CompilerVariable var : fractal.getColorVariables()) { scope.put(var.getName(), var); } for (CompilerVariable var : fractal.getStateVariables()) { scope.put(var.getName(), var); } compile(context, builder, scope, fractal.getStateVariables(), fractal.getColor()); } } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> scope, Collection<CompilerVariable> stateVariables, ASTOrbit orbit) { builder.append("public void init() {\n"); if (orbit != null) { builder.append("setInitialRegion("); builder.append("number("); builder.append(orbit.getRegion().getA()); builder.append("),number("); builder.append(orbit.getRegion().getB()); builder.append("));\n"); for (CompilerVariable var : stateVariables) { builder.append("addVariable("); builder.append(var.getName()); builder.append(");\n"); } for (ASTOrbitTrap trap : orbit.getTraps()) { builder.append("addTrap(trap"); builder.append(trap.getName().toUpperCase().substring(0, 1)); builder.append(trap.getName().substring(1)); builder.append(");\n"); } } builder.append("}\n"); for (CompilerVariable var : scope.values()) { scope.put(var.getName(), var); if (var.isCreate()) { if (var.isReal()) { builder.append("private double "); builder.append(var.getName()); builder.append(" = 0.0;\n"); } else { builder.append("private final MutableNumber "); builder.append(var.getName()); builder.append(" = getNumber("); builder.append(context.newNumberIndex()); builder.append(").set(0.0,0.0);\n"); } } } if (orbit != null) { for (ASTOrbitTrap trap : orbit.getTraps()) { compile(context, builder, scope, trap); } } builder.append("public void render(List<Number[]> states) {\n"); if (orbit != null) { compile(context, builder, scope, orbit.getBegin(), stateVariables); Map<String, CompilerVariable> vars = new HashMap<String, CompilerVariable>(scope); compile(context, builder, vars, orbit.getLoop(), stateVariables); compile(context, builder, scope, orbit.getEnd(), stateVariables); } int i = 0; for (CompilerVariable var : stateVariables) { builder.append("setVariable("); builder.append(i++); builder.append(","); builder.append(var.getName()); builder.append(");\n"); } builder.append("}\n"); builder.append("protected MutableNumber[] createNumbers() {\n"); builder.append("return new MutableNumber["); builder.append(context.getNumberCount()); builder.append("];\n"); builder.append("}\n"); } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> scope, Collection<CompilerVariable> stateVariables, ASTColor color) { builder.append("public void init() {\n"); if (color != null) { } builder.append("}\n"); for (CompilerVariable var : stateVariables) { scope.put(var.getName(), var); } if (color != null) { for (ASTPalette palette : color.getPalettes()) { compile(context, builder, scope, palette); } } builder.append("public void render() {\n"); int i = 0; for (CompilerVariable var : stateVariables) { if (var.isReal()) { builder.append("double "); builder.append(var.getName()); builder.append(" = getRealVariable("); builder.append(i++); builder.append(");\n"); } else { builder.append("final MutableNumber "); builder.append(var.getName()); builder.append(" = getVariable("); builder.append(i++); builder.append(");\n"); } } i = 0; for (CompilerVariable var : scope.values()) { if (!stateVariables.contains(var)) { if (var.isReal()) { builder.append("double "); builder.append(var.getName()); builder.append(" = 0;\n"); } else { builder.append("final MutableNumber "); builder.append(var.getName()); builder.append(" = getNumber("); builder.append(i++); builder.append(");\n"); } } } if (color != null) { builder.append("setColor(color("); builder.append(color.getArgb().getComponents()[0]); builder.append(","); builder.append(color.getArgb().getComponents()[1]); builder.append(","); builder.append(color.getArgb().getComponents()[2]); builder.append(","); builder.append(color.getArgb().getComponents()[3]); builder.append("));\n"); if (color.getInit() != null) { for (ASTStatement statement : color.getInit().getStatements()) { compile(context, builder, scope, statement); } } for (ASTRule rule : color.getRules()) { compile(context, builder, scope, rule); } } builder.append("}\n"); builder.append("protected MutableNumber[] createNumbers() {\n"); builder.append("return new MutableNumber["); builder.append(context.getNumberCount()); builder.append("];\n"); builder.append("}\n"); } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTRule rule) { builder.append("if ("); rule.getRuleExp().compile(new JavaASTCompiler(context, variables, builder)); builder.append(") {\n"); builder.append("addColor("); builder.append(rule.getOpacity()); builder.append(","); rule.getColorExp().compile(new JavaASTCompiler(context, variables, builder)); builder.append(");\n}\n"); } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTPalette palette) { builder.append("private Palette palette"); builder.append(palette.getName().toUpperCase().substring(0, 1)); builder.append(palette.getName().substring(1)); builder.append(" = palette()"); for (ASTPaletteElement element : palette.getElements()) { builder.append(".add("); compile(context, builder, variables, element); builder.append(")"); } builder.append(".build();\n"); } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTPaletteElement element) { builder.append("element("); builder.append(createArray(element.getBeginColor().getComponents())); builder.append(","); builder.append(createArray(element.getEndColor().getComponents())); builder.append(","); builder.append(element.getSteps()); builder.append(",(start, end, step) -> { return "); if (element.getExp() != null) { if (element.getExp().isReal()) { element.getExp().compile(new JavaASTCompiler(context, variables, builder)); } else { throw new ASTException("Expression type not valid: " + element.getLocation().getText(), element.getLocation()); } } else { builder.append("step / (end - start)"); } builder.append(";})"); } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTOrbitTrap trap) { builder.append("private Trap trap"); builder.append(trap.getName().toUpperCase().substring(0, 1)); builder.append(trap.getName().substring(1)); builder.append(" = trap(number("); builder.append(trap.getCenter()); builder.append("))"); for (ASTOrbitTrapOp operator : trap.getOperators()) { builder.append("."); switch (operator.getOp()) { case "MOVETO": builder.append("moveTo"); break; case "MOVETOREL": builder.append("moveToRel"); break; case "LINETO": builder.append("lineTo"); break; case "LINETOREL": builder.append("lineToRel"); break; case "ARCTO": builder.append("arcTo"); break; case "ARCTOREL": builder.append("arcToRel"); break; case "QUADTO": builder.append("quadTo"); break; case "QUADTOREL": builder.append("quadToRel"); break; case "CURVETO": builder.append("curveTo"); break; case "CURVETOREL": builder.append("curveToRel"); break; case "CLOSE": builder.append("close"); break; default: break; } builder.append("("); if (operator.getC1() != null) { if (operator.getC1().isReal()) { builder.append("number("); operator.getC1().compile(new JavaASTCompiler(context, variables, builder)); builder.append(")"); } else { operator.getC1().compile(new JavaASTCompiler(context, variables, builder)); } } if (operator.getC2() != null) { builder.append(","); if (operator.getC2().isReal()) { builder.append("number("); operator.getC2().compile(new JavaASTCompiler(context, variables, builder)); builder.append(")"); } else { operator.getC2().compile(new JavaASTCompiler(context, variables, builder)); } } if (operator.getC3() != null) { builder.append(","); if (operator.getC3().isReal()) { builder.append("number("); operator.getC3().compile(new JavaASTCompiler(context, variables, builder)); builder.append(")"); } else { operator.getC3().compile(new JavaASTCompiler(context, variables, builder)); } } builder.append(")"); } builder.append(";\n"); } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTOrbitBegin begin, Collection<CompilerVariable> stateVariables) { if (begin != null) { for (ASTStatement statement : begin.getStatements()) { compile(context, builder, variables, statement); } } } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTOrbitEnd end, Collection<CompilerVariable> stateVariables) { if (end != null) { for (ASTStatement statement : end.getStatements()) { compile(context, builder, variables, statement); } } } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTOrbitLoop loop, Collection<CompilerVariable> stateVariables) { if (loop != null) { builder.append("n = "); builder.append(loop.getBegin()); builder.append(";\n"); builder.append("if (states != null) {\n"); builder.append("states.add(new Number[] { "); int i = 0; for (CompilerVariable var : stateVariables) { if (i > 0) { builder.append(", "); } builder.append("number("); builder.append(var.getName()); builder.append(")"); i += 1; } builder.append(" });\n"); builder.append("}\n"); builder.append("for (int i = "); builder.append(loop.getBegin()); builder.append(" + 1; i <= "); builder.append(loop.getEnd()); builder.append("; i++) {\n"); for (ASTStatement statement : loop.getStatements()) { compile(context, builder, variables, statement); } builder.append("if ("); loop.getExpression().compile(new JavaASTCompiler(context, variables, builder)); builder.append(") { n = i; break; }\n"); builder.append("if (states != null) {\n"); builder.append("states.add(new Number[] { "); i = 0; for (CompilerVariable var : stateVariables) { if (i > 0) { builder.append(", "); } builder.append("number("); builder.append(var.getName()); builder.append(")"); i += 1; } builder.append(" });\n"); builder.append("}\n"); builder.append("}\n"); builder.append("if (states != null) {\n"); builder.append("states.add(new Number[] { "); i = 0; for (CompilerVariable var : stateVariables) { if (i > 0) { builder.append(", "); } builder.append("number("); builder.append(var.getName()); builder.append(")"); i += 1; } builder.append(" });\n"); builder.append("}\n"); } } private void compile(ExpressionContext context, StringBuilder builder, Map<String, CompilerVariable> variables, ASTStatement statement) { if (statement != null) { statement.compile(new JavaASTCompiler(context, variables, builder)); } } private String createArray(float[] components) { return "new float[] {" + components[0] + "f," + components[1] + "f," + components[2] + "f," + components[3] + "f}"; } }