/* * JAME 6.2.1 * http://jame.sourceforge.net * * Copyright 2001, 2016 Andrea Medeghini * * This file is part of JAME. * * JAME is an application for creating fractals and other graphics artifacts. * * JAME 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. * * JAME 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 JAME. If not, see <http://www.gnu.org/licenses/>. * */ package net.sf.jame.contextfree.parser; import org.antlr.v4.runtime.Token; class ASTFunction extends ASTExpression { private ASTExpression arguments; private EFuncType funcType; private Rand64 random; public ASTFunction(String name, ASTExpression arguments, Token location) { this(name, arguments, null, location); } public ASTFunction(String name, ASTExpression arguments, Rand64 seed, Token location) { super(arguments != null ? arguments.isConstant() : true, false, EExpType.NumericType, location); this.funcType = EFuncType.NotAFunction; this.arguments = arguments; if (name == null || name.trim().length() == 0) { throw new RuntimeException("Invalid function name"); } int argcount = arguments != null ? arguments.evaluate((double[])null, 0, null) : 0; funcType = EFuncType.getFuncTypeByName(name); if (funcType == EFuncType.NotAFunction) { throw new RuntimeException("Unknown function"); } if (funcType == EFuncType.Infinity && argcount == 0) { arguments = new ASTReal(1.0f, location); } if (funcType.ordinal() >= EFuncType.Rand_Static.ordinal() && funcType.ordinal() <= EFuncType.RandInt.ordinal()) { if (funcType == EFuncType.Rand_Static) { random = seed; } else { isConstant = false; } switch (argcount) { case 0: arguments = new ASTCons(location, new ASTReal(0.0f, location), new ASTReal(1.0f, location)); break; case 1: arguments = new ASTCons(location, new ASTReal(0.0f, location), arguments); break; case 2: break; default: throw new RuntimeException("Illegal argument(s) for random function"); } if (!isConstant && funcType == EFuncType.Rand_Static) { throw new RuntimeException("Argument(s) for rand_static() must be constant"); } this.arguments = arguments; } else { if (funcType.ordinal() < EFuncType.Atan2.ordinal()) { if (argcount != 1) { throw new RuntimeException(funcType == EFuncType.Infinity ? "Function takes zero or one arguments" : "Function takes one argument"); } } else { if (argcount != 2) { throw new RuntimeException("Function takes two arguments"); } } this.arguments = arguments; } } public ASTExpression getArguments() { return arguments; } public EFuncType getFuncType() { return funcType; } @Override public int evaluate(double[] result, int length, RTI rti) { if (type != EExpType.NumericType) { throw new RuntimeException("Non-numeric expression in a numeric context"); } if (result != null && length < 1) return -1; if (result == null) return 1; double[] a = new double[2]; int count = arguments.evaluate(a, 2, rti); // no need to check the argument count, the constructor already checked it // But check it anyway to make valgrind happy if (count < 0) return 1; switch (funcType) { case Cos: result[0] = Math.cos(a[0] * 0.0174532925199); break; case Sin: result[0] = Math.sin(a[0] * 0.0174532925199); break; case Tan: result[0] = Math.tan(a[0] * 0.0174532925199); break; case Cot: result[0] = 1.0 / Math.tan(a[0] * 0.0174532925199); break; case Acos: result[0] = Math.acos(a[0]) * 57.29577951308; break; case Asin: result[0] = Math.asin(a[0]) * 57.29577951308; break; case Atan: result[0] = Math.atan(a[0]) * 57.29577951308; break; case Acot: result[0] = Math.atan(1.0 / a[0]) * 57.29577951308; break; case Cosh: result[0] = Math.cosh(a[0]); break; case Sinh: result[0] = Math.sinh(a[0]); break; case Tanh: result[0] = Math.tanh(a[0]); break; case Acosh: result[0] = Math.log(a[0] + Math.sqrt(a[0] * a[0] - 1)); break; case Asinh: result[0] = Math.log(a[0] + Math.sqrt(a[0] * a[0] + 1)); break; case Atanh: result[0] = Math.log((1 / a[0] + 1) / (1 / a[0] - 1)) / 2; break; case Log: result[0] = Math.log(a[0]); break; case Log10: result[0] = Math.log10(a[0]); break; case Sqrt: result[0] = Math.sqrt(a[0]); break; case Exp: result[0] = Math.exp(a[0]); break; case Abs: if (count == 1) { result[0] = Math.abs(a[0]); } else { result[0] = Math.abs(a[0] - a[1]); } break; case Infinity: result[0] = a[0] < 0.0 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; break; case Sg: result[0] = a[0] == 0.0 ? 0.0 : 1.0; break; case IsNatural: result[0] = evalIsNatural(rti, a[0]) ? 1 : 0; break; case BitNot: result[0] = (double)((~((long)a[0])) & 0xFFFFFFFF); break; case BitOr: result[0] = (double)((((long)a[0]) | ((long)a[1])) & 0xFFFFFFFF); break; case BitAnd: result[0] = (double)((((long)a[0]) & ((long)a[1])) & 0xFFFFFFFF); break; case BitXOR: result[0] = (double)((((long)a[0]) ^ ((long)a[1])) & 0xFFFFFFFF); break; case BitLeft: result[0] = (double)((((long)a[0]) << ((long)a[1])) & 0xFFFFFFFF); break; case BitRight: result[0] = (double)((((long)a[0]) >> ((long)a[1])) & 0xFFFFFFFF); break; case Atan2: result[0] = Math.atan2(a[0], a[1]) * 57.29577951308; break; case Mod: if (arguments.isNatural()) { result[0] = (double)(((long)a[0]) % ((long)a[1])); } else { result[0] = Math.IEEEremainder(a[0], a[1]); } break; case Divides: result[0] = (double)((((long)a[0]) % ((long)a[1])) == 0 ? 1.0 : 0.0); break; case Div: result[0] = (double)(((long)a[0]) / ((long)a[1])); break; case Floor: if (rti == null) throw new DeferUntilRuntimeException(); result[0] = Math.floor(a[0]); break; case Ftime: if (rti == null) throw new DeferUntilRuntimeException(); result[0] = rti.getCurrentTime(); break; case Frame: if (rti == null) throw new DeferUntilRuntimeException(); result[0] = rti.getCurrentFrame(); break; case Rand_Static: result[0] = random.getDouble() * Math.abs(a[1] - a[0]) + Math.min(a[0], a[1]); break; case Rand: if (rti == null) throw new DeferUntilRuntimeException(); rti.setRandUsed(true); result[0] = rti.getCurrentSeed().getDouble() * Math.abs(a[1] - a[0]) + Math.min(a[0], a[1]); break; case Rand2: if (rti == null) throw new DeferUntilRuntimeException(); rti.setRandUsed(true); result[0] = (rti.getCurrentSeed().getDouble() * 2.0 - 1.0) * a[1] + a[0]; break; case RandInt: if (rti == null) throw new DeferUntilRuntimeException(); rti.setRandUsed(true); result[0] = Math.floor(rti.getCurrentSeed().getDouble() * Math.abs(a[1] - a[0]) + Math.min(a[0], a[1])); break; case NotAFunction: case Min: case Max: return -1; default: break; } return 1; } private boolean evalIsNatural(RTI rti, double n) { return n >= 0 && n <= (rti != null ? rti.getMaxNatural() : Integer.MAX_VALUE) && n == Math.floor(n); } @Override public void entropy(StringBuilder e) { arguments.entropy(e); e.append(funcType.getEntropy()); } @Override public ASTExpression compile(ECompilePhase ph) { if (arguments != null) { arguments = arguments.compile(ph); } switch (ph) { case TypeCheck: { isConstant = true; locality = ELocality.PureLocal; int argcount = 0; if (arguments != null) { isConstant = arguments.isConstant(); locality = arguments.getLocality(); if (locality == ELocality.PureNonlocal) { locality = ELocality.ImpureNonlocal; } if (arguments.getType() == EExpType.NumericType) { argcount = arguments.evaluate((double[])null, 0); } else { error("function arguments must be numeric"); } } if (funcType == EFuncType.Infinity && argcount == 0) { arguments = new ASTReal(1.0, location); return null; } if (funcType == EFuncType.Ftime) { if (arguments != null) { error("ftime() function takes no arguments"); } isConstant = false; arguments = new ASTReal(1.0, location); } if (funcType == EFuncType.Frame) { if (arguments != null) { error("time() function takes no arguments"); } isConstant = false; arguments = new ASTReal(1.0, location); } if (funcType.ordinal() >= EFuncType.Rand_Static.ordinal() && funcType.ordinal() <= EFuncType.RandInt.ordinal()) { if (funcType != EFuncType.Rand_Static) { isConstant = false; } switch (argcount) { case 0: arguments = new ASTCons(location, new ASTReal(0.0, location), new ASTReal(funcType == EFuncType.RandInt ? 2.0 : 1.0, location)); break; case 1: arguments = new ASTCons(location, new ASTReal(0.0, location), arguments); break; case 2: break; default: error("Illegal argument(s) for random function"); break; } if (!isConstant && funcType == EFuncType.Rand_Static) { error("Argument(s) for rand_static() must be constant"); } if (funcType == EFuncType.RandInt && arguments != null) { isNatural = arguments.isNatural(); } return null; } if (funcType == EFuncType.Abs) { if (argcount < 1 || argcount > 2) { error("function takes one or two arguments"); } } else if (funcType.ordinal() < EFuncType.BitOr.ordinal()) { if (argcount != 1) { if (funcType == EFuncType.Infinity) { error("function takes zero or one arguments"); } else { error("function takes one argument"); } } } else if (funcType.ordinal() < EFuncType.Min.ordinal()) { if (argcount != 2) { error("function takes two arguments"); } } else if (funcType.ordinal() < EFuncType.BitOr.ordinal()) { if (argcount < 2) { error("function takes at least two arguments"); } } if (funcType == EFuncType.Mod || funcType == EFuncType.Abs || funcType == EFuncType.Min || funcType == EFuncType.Max || (funcType.ordinal() >= EFuncType.BitNot.ordinal() && funcType.ordinal() <= EFuncType.BitRight.ordinal())) { isNatural = arguments == null || arguments.isNatural(); } if (funcType == EFuncType.Factorial || funcType == EFuncType.Sg || funcType == EFuncType.IsNatural || funcType == EFuncType.Div || funcType == EFuncType.Divides) { if (arguments != null && !arguments.isNatural()) { error("function is defined over natural numbers only"); } isNatural = true; } } break; case Simplify: break; default: break; } return null; } @Override public ASTExpression simplify() { if (isConstant) { double[] result = new double[1]; if (evaluate(result, 1, null) != 1) { return this; } ASTReal r = new ASTReal(result[0], location); r.setIsNatural(isNatural); return r; } else { if (arguments != null) { arguments = arguments.simplify(); } } return this; } }