/* * 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 java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Stack; import org.antlr.v4.runtime.ANTLRFileStream; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Token; public class Builder { private static Builder currentBuilder; private CFDG cfdg = new CFDG(); private Rand64 seed; private Stack<ASTRepContainer> containerStack = new Stack<ASTRepContainer>(); private ASTRepContainer paramDecls = new ASTRepContainer(); private Map<String, Integer> flagNames = new HashMap<String, Integer>(); private List<StackRule> longLivedParams = new ArrayList<StackRule>(); private Stack<String> fileNames = new Stack<String>(); private Stack<String> filesToLoad = new Stack<String>(); private Stack<CharStream> streamsToLoad = new Stack<CharStream>(); private Stack<Boolean> includeNamespace = new Stack<Boolean>(); private Stack<ASTSwitch> switchStack = new Stack<ASTSwitch>(); private String currentNameSpace = ""; private String currentPath; private String maybeVersion; private int currentShape; private int pathCount; private int includeDepth; private int localStackDepth; private double maxNatual; private boolean allowOverlap; private boolean inPathContainer; static List<String> globals = new ArrayList<String>(); { globals.add("CIRCLE"); globals.add("FILL"); globals.add("SQUARE"); globals.add("TRIANGLE"); } public Builder() { containerStack.add(new ASTRepContainer()); } protected void warning(String message, Token location) { System.out.println("[" + location.getLine() + ":" + location.getCharPositionInLine() + "] : " + message); } protected void error(String message, Token location) { System.err.println("[" + location.getLine() + ":" + location.getCharPositionInLine() + "] : " + message); } protected boolean isPrimeShape(int nameIndex) { return nameIndex < 4; } public int stringToShape(String name, boolean colonsAllowed, Token location) { checkName(name, colonsAllowed, location); if (currentNameSpace.length() == 0) { return cfdg.encodeShapeName(name); } int index = Collections.binarySearch(globals, name); String n = currentNameSpace + name; if (index != -1 && cfdg.tryEncodeShapeName(n) == -1) { return cfdg.encodeShapeName(name); } else { return cfdg.encodeShapeName(n); } } public String shapeToString(int shape) { return cfdg.decodeShapeName(shape); } public void checkName(String name, boolean colonsAllowed, Token location) { int pos = name.indexOf(":"); if (pos == -1) { return; } if (!colonsAllowed) { error("namespace specification not allowed in this context", location); return; } if (pos == 0) { error("improper namespace specification", location); return; } for (;;) { if (pos == name.length() - 1 || name.charAt(pos + 1) != ':') break; int next = name.indexOf(":", pos + 2); if (next == -1) return; if (next == pos + 2) break; pos = next; } error("improper namespace specification", location); } public void includeFile(String fileName, Token location) { try { String path = relativeFilePath(currentPath, fileName); ANTLRFileStream is = new ANTLRFileStream(path); fileNames.push(path); currentPath = path; filesToLoad.push(currentPath); streamsToLoad.push(is); includeNamespace.push(Boolean.FALSE); pathCount++; includeDepth++; currentShape = -1; setShape(null, false, location); warning("Reading rules file " + path, location); } catch (Exception e) { error(e.getMessage(), location); } } public boolean endInclude(Token location) { try { boolean endOfInput = includeDepth == 0; setShape(null, false, location); includeDepth--; if (filesToLoad.isEmpty()) { return endOfInput; } if (includeNamespace.peek()) { popNameSpace(); } streamsToLoad.pop(); filesToLoad.pop(); includeNamespace.pop(); currentPath = filesToLoad.isEmpty() ? null : filesToLoad.peek(); return endOfInput; } catch (Exception e) { error(e.getMessage(), location); return false; } } public void setShape(String name, Token location) { setShape(name, false, location); } public void setShape(String name, boolean isPath, Token location) { if (name == null) { currentShape = -1; return; } currentShape = stringToShape(name, false, location); ASTDefine def = cfdg.findFunction(currentShape); if (def != null) { error("There is a function with the same name as this shape: " + def.getLocation(), location); return; } String err = cfdg.setShapeParams(currentShape, paramDecls, paramDecls.getStackCount(), isPath); if (err != null) { error("cannot set shape params: " + err, location); } localStackDepth -= paramDecls.getStackCount(); paramDecls.getParameters().clear(); paramDecls.setStackCount(0); } public void addRule(ASTRule rule) { boolean isShapeItem = rule.getNameIndex() == -1; if (isShapeItem) { rule.setNameIndex(currentShape); } else { currentShape = -1; } if (rule.getNameIndex() == -1) { error("Shape rules/paths must follow a shape declaration", rule.getLocation()); } EShapeType type = cfdg.getShapeType(rule.getNameIndex()); if ((rule.isPath() && type == EShapeType.RuleType) || (!rule.isPath() && type == EShapeType.PathType)) { error("Cannot mix rules and shapes with the same name", rule.getLocation()); } boolean matchesShape = cfdg.addRuleShape(rule); if (!isShapeItem && matchesShape) { error("Rule/path name matches existing shape name", rule.getLocation()); } } public void nextParameterDecl(String type, String name, Token location) { int nameIndex = stringToShape(name, false, location); checkVariableName(nameIndex, true); paramDecls.addParameter(type, nameIndex, location); ASTParameter param = paramDecls.getParameters().get(paramDecls.getParameters().size() - 1); param.setStackIndex(localStackDepth); paramDecls.setStackCount(paramDecls.getStackCount() + param.getTupleSize()); localStackDepth += param.getTupleSize(); } public ASTDefine makeDefinition(String name, boolean isFunction, Token location) { if (name.startsWith("CF::")) { if (isFunction) { error("Configuration parameters cannot be functions", location); return null; } if (containerStack.lastElement().isGlobal()) { error("Configuration parameters must be at global scope", location); return null; } ASTDefine def = new ASTDefine(name, location); def.setConfigDepth(includeDepth); def.setDefineType(EDefineType.ConfigDefine); return def; } if (EFuncType.getFuncTypeByName(name) != EFuncType.NotAFunction) { error("Internal function names are reserved", location); return null; } int nameIndex = stringToShape(name, false, location); ASTDefine def = cfdg.findFunction(nameIndex); if (def != null) { error("Definition with same name as user function: " + def.getLocation(), location); return null; } checkVariableName(nameIndex, false); def = new ASTDefine(name, location); def.getShapeSpecifier().setShapeType(nameIndex); if (isFunction) { for (ASTParameter param : paramDecls.getParameters()) { param.setLocality(ELocality.PureNonlocal); } def.getParameters().clear(); def.getParameters().addAll(paramDecls.getParameters()); def.setStackCount(paramDecls.getStackCount()); def.setDefineType(EDefineType.FunctionDefine); localStackDepth -= paramDecls.getStackCount(); paramDecls.setStackCount(0); cfdg.declareFunction(nameIndex, def); } else { containerStack.lastElement().addDefParameter(nameIndex, def, location); } return def; } public void makeConfig(ASTDefine cfg) { if (cfg.getName().equals("CF::Impure")) { double[] v = new double[] { 0.0 }; if (cfg.getExp() != null || cfg.getExp().isConstant() || cfg.getExp().evaluate(v, 1, null) != 1) { error("CF::Impure requires a constant numeric expression", cfg.getLocation()); } else { ASTParameter.Impure = v[0] != 0.0; } } if (cfg.getName().equals("CF::AllowOverlap")) { double[] v = new double[] { 0.0 }; if (cfg.getExp() != null || cfg.getExp().isConstant() || cfg.getExp().evaluate(v, 1, null) != 1) { error("CF::AllowOverlap requires a constant numeric expression", cfg.getLocation()); } else { allowOverlap = v[0] != 0.0; } } if (cfg.getName().equals("CF::StartShape") && cfg.getExp() != null && (cfg.getExp() instanceof ASTStartSpecifier)) { ASTRuleSpecifier rule = null; ASTModification mod = null; List<ASTExpression> specAndMod = extract(cfg.getExp()); switch (specAndMod.size()) { case 2: if (!(specAndMod.get(1) instanceof ASTModification)) { error("CF::StartShape second term must be a modification", cfg.getLocation()); } else { mod = (ASTModification) specAndMod.get(1); } break; case 1: if (!(specAndMod.get(0) instanceof ASTRuleSpecifier)) { error("CF::StartShape must start with a shape specification", cfg.getLocation()); } else { rule = (ASTRuleSpecifier) specAndMod.get(0); } break; default: error("CF::StartShape expression must have the form shape_spec or shape_spec, modification", cfg.getLocation()); break; } if (mod == null) { mod = new ASTModification(cfg.getLocation()); } cfg.setExp(new ASTStartSpecifier(rule, mod, cfg.getLocation())); } ASTExpression current = cfg.getExp(); if (!cfdg.addParameter(cfg.getName(), cfg.getExp(), cfg.getConfigDepth())) { error("Unknown configuration parameter", cfg.getLocation()); } if (cfg.getName().equals("CF::MaxNatural")) { ASTExpression max = cfdg.hasParameter(ECFGParam.MaxNatural); if (max != current) { return; } double[] v = new double[] { -1.0 }; if (max == null || !max.isConstant() || max.getType() != EExpType.NumericType || max.evaluate(v, 1, null) != 1) { error("CF::MaxNatural requires a constant numeric expression", cfg.getLocation()); } else if (v[0] < 1.0 || v[0] > 9007199254740992.0) { error(v[0] < 1.0 ? "CF::MaxNatural must be >= 1" : "CF::MaxNatural must be < 9007199254740992", cfg.getLocation()); } else { maxNatual = v[0]; } } } private List<ASTExpression> extract(ASTExpression exp) { if (exp instanceof ASTCons) { return ((ASTCons)exp).getChildren(); } else { List<ASTExpression> ret = new ArrayList<ASTExpression>(); ret.add(exp); return ret; } } public ASTExpression makeVariable(String name, Token location) { Integer flagItem = flagNames.get(name); if (flagItem != null) { ASTReal flag = new ASTReal(flagItem, location); flag.setType(EExpType.FlagType); return flag; } if (name.startsWith("CF::")) { error("Configuration parameter names are reserved", location); return new ASTExpression(location); } if (EFuncType.getFuncTypeByName(name) != EFuncType.NotAFunction) { error("Internal function names are reserved", location); return new ASTExpression(location); } int varNum = stringToShape(name, true, location); boolean isGlobal = false; ASTParameter bound = findExpression(varNum, isGlobal); if (bound == null) { return new ASTRuleSpecifier(varNum, name, null, cfdg.getShapeParams(currentShape), location); } return new ASTVariable(varNum, name, location); } public ASTExpression makeArray(String name, ASTExpression args, Token location) { if (name.startsWith("CF::")) { error("Configuration parameter names are reserved", location); return args; } int varNum = stringToShape(name, true, location); boolean isGlobal = false; ASTParameter bound = findExpression(varNum, isGlobal); if (bound == null) { return args; } return new ASTArray(varNum, args, "", location); } public ASTExpression makeLet(ASTRepContainer vars, ASTExpression exp, Token location) { int nameIndex = stringToShape("let", false, location); ASTDefine def = new ASTDefine("let", location); def.getShapeSpecifier().setShapeType(nameIndex); def.setExp(exp); def.setDefineType(EDefineType.LetDefine); return new ASTLet(vars, def, location); } public ASTRuleSpecifier makeRuleSpec(String name, ASTExpression args, Token location) { return makeRuleSpec(name, args, null, false, location); } public ASTRuleSpecifier makeRuleSpec(String name, ASTExpression args, ASTModification mod, boolean makeStart, Token location) { if (name.equals("if") || name.equals("let") || name.equals("select")) { if (name.equals("select")) { args = new ASTSelect(args, false, location); } if (makeStart) { return new ASTStartSpecifier(args, mod, location); } else { return new ASTRuleSpecifier(args, location); } } int nameIndex = stringToShape(name, true, location); boolean isGlobal = false; ASTParameter bound = findExpression(nameIndex, isGlobal); if (bound != null && args != null && args.getType() == EExpType.ReuseType && !makeStart && isGlobal && nameIndex == currentShape) { error("Shape name binds to global variable and current shape, using current shape", location); } if (bound != null && bound.isParameter() && bound.getType() == EExpType.RuleType) { return new ASTRuleSpecifier(nameIndex, name, location); } ASTRuleSpecifier ret = null; cfdg.setShapeHasNoParam(nameIndex, args); if (makeStart) { ret = new ASTStartSpecifier(nameIndex, name, args, mod, location); } else { ret = new ASTRuleSpecifier(nameIndex, name, args, cfdg.getShapeParams(currentShape), location); } if (ret.getArguments() != null && ret.getArguments().getType() == EExpType.ReuseType) { if (makeStart) { error("Startshape cannot reuse parameters", location); } else if (nameIndex == currentShape) { ret.setArgSouce(EArgSource.SimpleArgs); ret.setTypeSignature(ret.getTypeSignature()); } } return ret; } public void makeModTerm(ASTModification dest, ASTModTerm t, Token location) { if (t == null) { return; } if (t.getModType() == EModType.time) { timeWise(); } if (t.getModType() == EModType.sat || t.getModType() == EModType.satTarg) { inColor(); } dest.concat(t); } public ASTReplacement makeElement(String s, ASTModification mods, ASTExpression params, boolean subPath, Token location) { if (inPathContainer && !subPath && (s.equals("FILL") || s.equals("STROKE"))) { return new ASTPathCommand(s, mods, params, location); } ASTRuleSpecifier r = makeRuleSpec(s, params, null, false, location); ERepElemType t = ERepElemType.replacement; if (inPathContainer) { boolean isGlobal = false; ASTParameter bound = findExpression(r.getShapeType(), isGlobal); if (!subPath) { error("Replacements are not allowed in paths", location); } else if (r.getArgSource() == EArgSource.StackArgs || r.getArgSource() == EArgSource.ShapeArgs) { // Parameter subpaths must be all ops, but we must check at runtime t = ERepElemType.op; } else if (cfdg.getShapeType(r.getShapeType()) == EShapeType.PathType) { ASTRule rule = cfdg.findRule(r.getShapeType()); if (rule != null) { t = ERepElemType.typeByOrdinal(rule.getRuleBody().getRepType()); } else { error("Subpath references must be to previously declared paths", location); } } else if (bound != null) { // Variable subpaths must be all ops, but we must check at runtime t = ERepElemType.op; } else if (isPrimeShape(r.getShapeType())) { t = ERepElemType.op; } else { error("Subpath references must be to previously declared paths", location); } } return new ASTReplacement(r, mods, t, location); } public ASTExpression makeFunction(String name, ASTExpression args, Token location) { return makeFunction(name, args, false, location); } public ASTExpression makeFunction(String name, ASTExpression args, boolean consAllowed, Token location) { int nameIndex = stringToShape(name, true, location); boolean isGlobal = false; ASTParameter bound = findExpression(nameIndex, isGlobal); if (bound != null) { if (!consAllowed) { error("Cannot bind expression to variable/parameter", location); } return makeVariable(name, location).append(args); } if (name.equals("select") || name.equals("if")) { return new ASTSelect(args, name.equals("if"), location); } EFuncType t = EFuncType.getFuncTypeByName(name); if (t == EFuncType.NotAFunction) { return new ASTFunction(name, args, seed, location); } if (args != null && args.getType() == EExpType.ReuseType) { return makeRuleSpec(name, args, null, false, location); } return new ASTUserFunction(nameIndex, args, null, location); } public ASTModification makeModification(ASTModification mod, boolean canonial, Token location) { mod.setIsConstant(mod.getModExp().isEmpty()); mod.setCanonical(canonial); mod.setLocation(location); return mod; } public String getTypeInfo(int nameIndex, ASTDefine[] func, List<ASTParameter>[] p) { func[0] = cfdg.findFunction(nameIndex); p[0] = cfdg.getShapeParams(nameIndex); return cfdg.decodeShapeName(nameIndex); } public ASTRule getRule(int nameIndex) { return cfdg.findRule(nameIndex); } public void pushRepContainer(ASTRepContainer c) { containerStack.push(c); processRepContainer(c); } private void processRepContainer(ASTRepContainer c) { c.setStackCount(0); for (ASTParameter param : c.getParameters()) { if (param.isParameter() || param.isLoopIndex()) { param.setStackIndex(localStackDepth); c.setStackCount(c.getStackCount() + param.getTupleSize()); localStackDepth += param.getTupleSize(); } else { break; // the parameters are all in front } } } public void popRepContainer(ASTReplacement r) { ASTRepContainer lastContainer = containerStack.lastElement(); localStackDepth -= lastContainer.getStackCount(); if (r != null) { r.setRepType(ERepElemType.typeByOrdinal(r.getRepType().ordinal() | lastContainer.getRepType())); if (r.getPathOp() == EPathOp.UNKNOWN) { r.setPathOp(lastContainer.getPathOp()); } } containerStack.pop(); } private boolean badContainer(int containerType) { return (containerType & (ERepElemType.op.ordinal() | ERepElemType.replacement.ordinal())) == (ERepElemType.op.ordinal() | ERepElemType.replacement.ordinal()); } public void pushRep(ASTReplacement r, boolean global) { if (r == null) { return; } ASTRepContainer container = containerStack.lastElement(); if (container.getBody().size() > 0) { container.getBody().remove(container.getBody().size() - 1); } container.getBody().add(r); if (container.getPathOp() == EPathOp.UNKNOWN) { container.setPathOp(r.getPathOp()); } int oldType = container.getRepType(); container.setRepType(oldType | r.getRepType().ordinal()); if (badContainer(container.getRepType()) && !badContainer(oldType) && !global) { error("Cannot mix path elements and replacements in the same container", r.getLocation()); } } public ASTParameter findExpression(int nameIndex, boolean isGlobal) { for (ListIterator<ASTRepContainer> i = containerStack.listIterator(); i.hasPrevious();) { ASTRepContainer repCont = i.previous(); for (ListIterator<ASTParameter> p = repCont.getParameters().listIterator(); i.hasPrevious();) { ASTParameter param = p.previous(); if (param.getNameIndex() == nameIndex) { isGlobal = repCont.isGlobal(); return param; } } } return null; } protected void checkVariableName(int nameIndex, boolean param) { if (allowOverlap && !param) { return; } ASTRepContainer repCont = param ? paramDecls : containerStack.lastElement(); for (ListIterator<ASTParameter> i = repCont.getParameters().listIterator(); i.hasPrevious();) { ASTParameter p = i.previous(); if (p.getNameIndex() == nameIndex) { error("Scope of name overlaps variable/parameter with same name", p.getLocation()); } } } protected String relativeFilePath(String base, String rel) { int i = base.lastIndexOf("/"); if (i == -1) { return rel; } return base.substring(0, i) + rel; } protected void popNameSpace() { currentNameSpace = currentNameSpace.substring(0, currentNameSpace.length() - 2); int end = currentNameSpace.lastIndexOf(":"); if (end == -1) { currentNameSpace = ""; } else { currentNameSpace.substring(0, end + 1); } } protected void pushNameSpace(String n, Token location) { if (n.equals("CF")) { error("CF namespace is reserved", location); return; } if (n.length() == 0) { error("zero-length namespace", location); return; } checkName(n, false, location); includeNamespace.pop(); includeNamespace.push(Boolean.TRUE); currentNameSpace = currentNameSpace + n + "::"; } public void inColor() { cfdg.addParameter(EParam.Color); } public void timeWise() { cfdg.addParameter(EParam.Time); } public void storeParams(StackRule p) { p.setRefCount(Integer.MAX_VALUE); longLivedParams.add(p); } public String getMaybeVersion() { return maybeVersion; } public void setMaybeVersion(String maybeVersion) { this.maybeVersion = maybeVersion; } public EExpType decodeType(String typeName, int[] tupleSize, boolean[] isNatural, Token location) { EExpType type; tupleSize[0] = 1; isNatural[0] = false; if (typeName.equals("number")) { type = EExpType.NumericType; } else if (typeName.equals("natural")) { type = EExpType.NumericType; isNatural[0] = true; } else if (typeName == "adjustment") { type = EExpType.ModType; tupleSize[0] = 6; } else if (typeName == "shape") { type = EExpType.RuleType; } else if (typeName.startsWith("vector")) { type = EExpType.NumericType; if (typeName.matches("vector[0-9]+")) { tupleSize[0] = Integer.parseInt(typeName.substring(6)); if (tupleSize[0] <= 1 || tupleSize[0] > 99) { error("Illegal vector size (<=1 or >99)", location); } } else { error("Illegal vector type specification", location); } } else { type = EExpType.NoType; error("Unrecognized type name", location); } return type; } public void setInPathContainer(boolean inPathContainer) { this.inPathContainer = inPathContainer; } public Stack<ASTSwitch> getSwitchStack() { return switchStack; } public void incSwitchStack() { localStackDepth--; } public void decSwitchStack() { localStackDepth++; } public Rand64 getSeed() { return seed; } public ASTRepContainer getParamDecls() { return paramDecls; } public static Builder currentBuilder() { return currentBuilder; } public int getLocalStackDepth() { return localStackDepth; } public boolean isInPathContainer() { return this.inPathContainer ; } public Stack<ASTRepContainer> getContainerStack() { return containerStack; } public void setLocalStackDepth(int localStackDepth) { this.localStackDepth = localStackDepth; } }