/* * This file is part of LaTeXDraw. * Copyright (c) 2005-2017 Arnaud BLOUIN * LaTeXDraw 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 2 of the License, or (at your option) any later version. * LaTeXDraw is distributed 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. */ package net.sf.latexdraw.parsers.ps; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; /** * A postscript function parser. * @author Arnaud Blouin */ public class PSFunctionParser { /** The postscript function. */ private final String function; private final List<PSArithemticCommand> commands; private static final Map<String, Supplier<PSArithemticCommand>> factoryMap; static { factoryMap = new HashMap<>(); factoryMap.put("add", () -> new PSAddCommand()); factoryMap.put("mul", () -> new PSMulCommand()); factoryMap.put("sub", () -> new PSSubCommand()); factoryMap.put("sin", () -> new PSSinCommand()); factoryMap.put("cos", () -> new PSCosCommand()); factoryMap.put("div", () -> new PSDivCommand()); factoryMap.put("idiv", () -> new PSIDivCommand()); factoryMap.put("mod", () -> new PSModCommand()); factoryMap.put("neg", () -> new PSNegCommand()); factoryMap.put("exch", () -> new PSExchCommand()); factoryMap.put("clear", () -> new PSClearCommand()); factoryMap.put("dup", () -> new PSDupCommand()); factoryMap.put("pop", () -> new PSPopCommand()); factoryMap.put("roll", () -> null); factoryMap.put("sqrt", () -> null); factoryMap.put("exp", () -> new PSExpCommand()); factoryMap.put("abs", () -> new PSAbsCommand()); factoryMap.put("floor", () -> new PSFloorCommand()); factoryMap.put("ceiling", () -> new PSCeilingCommand()); factoryMap.put("count", () -> new PSCountCommand()); factoryMap.put("x", () -> new PSPlotXVariable()); factoryMap.put("log", () -> new PSLogCommand()); } /** * Creates and parser from postscript functions. * @param fct The function to parse. * @throws InvalidFormatPSFunctionException If the function format is not valid. * @since 3.0 */ public PSFunctionParser(final String fct) throws InvalidFormatPSFunctionException { super(); if(fct == null || fct.isEmpty()) throw new IllegalArgumentException(); commands = new ArrayList<>(); function = fct; parseFunction(); } /** * Checks whether the given equation is a valid post-fixed PS equation. * @param eq The equation to check. * @param min The X-min of the plotting. * @param max The X-max of the plotting. * @param nbPts The number of points to plot. * @return True if the given equation is a valid post-fixed PS equation. * @since 3.3 */ public static boolean isValidPostFixEquation(final String eq, final double min, final double max, final double nbPts) { try { final PSFunctionParser fct = new PSFunctionParser(eq); final double gap = (max - min) / (nbPts - 1); for(double x = min; x < max; x += gap) { fct.getY(x); } return true; }catch(final NumberFormatException | ArithmeticException ex) { return false; } } /** * @param x The X-coordinate used to compute the Y using the function. * @return The y value corresponding to the given X value. Or Double.NaN is an arithmetic error occurs. * @throws InvalidFormatPSFunctionException If the function is not correct. * @throws ArithmeticException If an error occurs during the computation of the points (e.g. division by 0). */ public double getY(final double x) throws InvalidFormatPSFunctionException { final Deque<Double> stack = new ArrayDeque<>(); commands.forEach(cmd -> cmd.execute(stack, x)); if(stack.isEmpty()) throw new InvalidFormatPSFunctionException(); return stack.pop(); } /** * Parses the function. * @throws InvalidFormatPSFunctionException If the function is not correct. * @throws NumberFormatException If the function is not correct. */ protected void parseFunction() throws NumberFormatException { int i = 0; final int lgth = function.length(); final StringBuilder cmd = new StringBuilder(); while(i < lgth) { cmd.delete(0, cmd.length()); while(i < lgth && function.charAt(i) == ' ') { i++; } while(i < lgth && function.charAt(i) != ' ') { cmd.append(function.charAt(i++)); } if(cmd.length() > 0) { commands.add(identifyCommand(cmd.toString())); } } } /** * @param cmd The arithmetic command to analyse. * @return The arithmetic instance corresponding to the given command. * @throws InvalidFormatPSFunctionException If the function is not correct. * @throws NumberFormatException If the function is not correct. * @since 3.0 */ protected PSArithemticCommand identifyCommand(final String cmd) throws NumberFormatException { if(cmd == null || cmd.isEmpty()) throw new InvalidFormatPSFunctionException(); try { return factoryMap.getOrDefault(cmd, () -> new PSValue(Double.parseDouble(cmd))).get(); }catch(final NumberFormatException ex) { throw new InvalidFormatPSFunctionException("Cannot parse: " + cmd); //$NON-NLS-1$ } } }