aspect ConcreteSyntax { /* public ASTNode ASTNode.transformation() { ASTNode res = (ASTNode)copy(); for(int i = 0; i < getNumChildNoTransform(); i++) { ASTNode node = getChildNoTransform(i); if(node != null) node = node.transformation(); res.setChild(node, i); } return res; } */ /* public ASTNode Block.transformation() { Block block = (Block)copy(); List list = new List(); for(int i = 0; i < getNumStmt(); i++) if(!(getStmt(i) instanceof EmptyStmt)) list.add((Stmt)getStmt(i).transformation()); block.setStmtList(list); return block; } */ /* public ASTNode WhileStmt.transformation() { return new ForStmt( new List(), new Opt(getCondition().transformation()), new List(), (Stmt)getStmt().transformation() ); }*/ /* public ASTNode WhileStmt.transformation() { return new IfStmt( (Expr)getCondition().transformation(), (Stmt)getStmt().transformation(), new Opt() ); }*/ // generate a string for a subtree where layout information is preserved // unless the nodes lack concrete syntax in which case a default printing strategy is used syn String ASTNode.layout() { if(concreteSyntax == null) concreteSyntax = createConcreteSyntax(); String[] strings = concreteSyntax(); ASTNode[] children = concreteChildren(); StringBuilder buf = new StringBuilder(); if(children != null) { for(int i = 0; i < getNumConcreteChild(); i++) { buf.append(strings[i]); if(children[i] != null) buf.append(children[i].layout()); } } else if(strings != null) { buf.append(strings[0]); } if(strings != null) buf.append(strings[strings.length-1]); return buf.toString(); } protected String[] ASTNode.concreteSyntax; protected String[] ASTNode.concreteSyntax() { if(concreteSyntax == null) { concreteSyntax = new String[getNumConcreteChild() == 0 ? 2 : (getNumConcreteChild() + 1)]; } return concreteSyntax; } public void CompilationUnit.addLayout() { addLayout(layoutBuffer()); } // populate layout information in the nodes from ranges given by the parser public void ASTNode.addLayout(ArrayList layoutBuffer) { String[] strings = concreteSyntax(); for(int i = 0; i < strings.length; i++) if(strings[i] == null) strings[i] = ""; ASTNode[] children = concreteChildren(); if(hasLayout()) { int first = -1; int last = -1; if(children != null) { for(int i = 0; i < getNumConcreteChild(); i++) { if(children[i] != null && children[i].hasLayout()) { if(first == -1) first = i; last = i; } } } if(first == -1) { int startLine = startLine(); int startColumn = startColumn(); int endLine = endLine(); int endColumn = endColumn(); String str = extractLayout(startLine, startColumn, endLine, endColumn, layoutBuffer); appendLayout(str, 0); } else { int startLine = startLine(); int startColumn = startColumn(); int endLine = children[first].startLine(); int endColumn = children[first].startColumn(); String str = extractLayout(startLine, startColumn, endLine, endColumn, layoutBuffer); appendLayout(str.substring(0, str.length()-1), 0); startLine = children[last].endLine(); startColumn = children[last].endColumn(); endLine = endLine(); endColumn = endColumn(); str = extractLayout(startLine, startColumn, endLine, endColumn, layoutBuffer); int pos = children == null ? 1 : getNumConcreteChild(); prependLayout(str.substring(1), pos); for(int i = first + 1; i <= last; i++) { ASTNode child = children[i]; if(child != null && child.hasLayout()) { startLine = children[first].endLine(); startColumn = children[first].endColumn(); endLine = child.startLine(); endColumn = child.startColumn(); str = extractLayout(startLine, startColumn, endLine, endColumn, layoutBuffer); appendLayout(str.substring(1, str.length()-1), first + 1); first = i; } } } } adjustLayout(strings, children); if(children != null) { for(int i = 0; i < getNumConcreteChild(); i++) if(children[i] != null) children[i].addLayout(layoutBuffer); } } public void ASTNode.appendLayout(String s, int index) { String[] strings = concreteSyntax(); if(strings[index] == null) strings[index] = s; else strings[index] += s; } public void ASTNode.prependLayout(String s, int index) { String[] strings = concreteSyntax(); if(strings[index] == null) strings[index] = s; else strings[index] = s + strings[index]; } // The default layout may need to be adjusted // this is a hook that can be overridden for particular langauge constructs public void ASTNode.adjustLayout(String[] strings, ASTNode[] children) { } public void WhileStmt.adjustLayout(String[] strings, ASTNode[] children) { // move whitespace and comments after '(' into the condition node String s = strings[0]; int index = s.indexOf('('); String lhs = s.substring(0, index+1); String rhs = s.substring(index+1); strings[0] = lhs; children[0].prependLayout(rhs, 0); } public void Block.adjustLayout(String[] strings, ASTNode[] children) { // move whitespace and comments that occur on another line than '{' into // the first statement in the block String s = strings[0]; int index = s.indexOf('{'); while(index != -1 && index < s.length() && s.charAt(index) != '\n') index++; if(index < s.length()) { String lhs = s.substring(0, index+1); String rhs = s.substring(index+1); strings[0] = lhs; children[0].prependLayout(rhs, 0); } } syn boolean List.noChildrenWithLayout() { ASTNode[] children = concreteChildren(); for(int i=0; i= 0 && (s.charAt(index) != '\"'))// || (index != 0 && s.charAt(index-1) != '\\'))) index--; String lhs; if(index == 0) lhs = ""; else lhs = s.substring(0, index); String rhs = s.substring(index); parentStrings[childIndex] = lhs; strings[0] = rhs + strings[0]; } } public void List.adjustLayout(String[] strings, ASTNode[] children) { if(strings[0] != null && !strings[0].equals("")) { String prefix = strings[0].substring(0, 1); if(prefix.equals("(") || prefix.equals(")") || prefix.equals("{") || prefix.equals("}")) { int childIndex = getParent().getConcreteIndexOfChild(this); String[] parentStrings = getParent().concreteSyntax(); if(childIndex != -1) { parentStrings[childIndex] += prefix; strings[0] = strings[0].substring(1); } } } if(isBodyDeclList() || isStmtList()) { if(noChildrenWithLayout()) { if(strings[0] != null && !strings[0].equals("")) { String s = removeComments(strings[0]); int index = s.lastIndexOf('}'); index--; while(index >= 0 && (Character.isWhitespace(s.charAt(index)) || s.charAt(index) == '#') && s.charAt(index) != '\n') index--; if(index >= 0) { s = strings[0]; String lhs = s.substring(0, index + 1); String rhs = s.substring(index + 1, s.length()); appendLayoutInParent(lhs); prependLayoutInParent(rhs); strings[0] = ""; } } } else { splitBeginOpenBrace(strings, children); splitBetween(strings, children); splitEndCloseBrace(strings, children); } } else { if(noChildrenWithLayout()) { } else { keepCommasOnly(strings, children); } } } private void List.keepCommasOnly(String[] strings, ASTNode[] children) { for(int i = 1; i < strings.length - 1; i++) { String str = removeComments(strings[i]); int index = str.indexOf(','); if(index != -1) { str = strings[i]; int leftIndex = index - 1; while(leftIndex >= 0 && Character.isWhitespace(str.charAt(leftIndex))) leftIndex--; String lhs = str.substring(0, leftIndex + 1); int rightIndex = index + 1; while(rightIndex < str.length() && Character.isWhitespace(str.charAt(rightIndex))) rightIndex++; String rhs = str.substring(rightIndex); String mid = str.substring(leftIndex + 1, rightIndex); children[i-1].appendLayout(lhs, children[i-1].getNumChild()); children[i].prependLayout(rhs, 0); strings[i] = mid; } } } private void List.splitBeginOpenBrace(String[] strings, ASTNode[] children) { // move whitespace and comments that occur on another line than '{' into // the first statement in the block String s = removeComments(strings[0]); int index = s.indexOf('{'); index++; while(index < s.length() && (Character.isWhitespace(s.charAt(index)) || s.charAt(index) == '#') && s.charAt(index) != '\n') index++; if(index < s.length()) { s = strings[0]; String lhs = s.substring(0, index+1); String rhs = s.substring(index+1); appendLayoutInParent(lhs); children[0].prependLayout(rhs, 0); strings[0] = ""; } else { children[0].prependLayout(strings[0], 0); strings[0] = ""; } } private void List.splitBetween(String[] strings, ASTNode[] children) { for(int i = 1; i < strings.length - 1; i++) { String str = strings[i]; ASTNode child = children[i-1]; String childPrefix = child.concreteSyntax()[child.getNumConcreteChild()]; if(childPrefix == null || !childPrefix.endsWith("\n")) { str = removeComments(str); int index = str.indexOf('\n'); str = strings[i]; String lhs = str.substring(0, index + 1); String rhs = str.substring(index + 1, str.length()); child.appendLayout(lhs, child.getNumConcreteChild()); children[i].prependLayout(rhs, 0); } else children[i].prependLayout(str, 0); strings[i] = ""; } } private void List.splitEndCloseBrace(String[] strings, ASTNode[] children) { String s = removeComments(strings[strings.length-1]); int index = s.indexOf('}'); index--; while(index >= 0 && (Character.isWhitespace(s.charAt(index)) || s.charAt(index) == '#') && s.charAt(index) != '\n') index--; if(index >= -1) { s = strings[strings.length-1]; String lhs = s.substring(0, index + 1); String rhs = s.substring(index + 1); int i = getNumChild() - 1; while(i > 0 && !children[i].hasLayout()) i--; ASTNode child = children[i]; if(child.hasLayout()) { prependLayoutInParent(rhs); child.appendLayout(lhs, child.getNumConcreteChild()); strings[strings.length-1] = ""; } } } // The AST may have another structure than the parse tree. // This attribute should return an array with the children // in lexical order. syn ASTNode[] ASTNode.concreteChildren() = children; // put type parameters into the right place eq GenericClassDecl.concreteChildren() = new ASTNode[] { getChild(0), getChild(4), getChild(1), getChild(2), getChild(3) }; eq GenericInterfaceDecl.concreteChildren() = new ASTNode[] { getChild(0), getChild(4), getChild(1), getChild(2), getChild(3) }; eq GenericConstructorDecl.concreteChildren() = new ASTNode[] { getChild(5), getChild(0), getChild(1), getChild(2), getChild(3), getChild(4) }; eq GenericMethodDecl.concreteChildren() = new ASTNode[] { getChild(5), getChild(0), getChild(1), getChild(2), getChild(3), getChild(4) }; // omit explicit constructor invocation eq ConstructorDecl.concreteChildren() = new ASTNode[] { getChild(0), getChild(1), getChild(2), getChild(4) }; // pull in explicit constructor invocation from grandparent eq List.concreteChildren() { ASTNode body[] = super.concreteChildren(); if(isConstructorBodyStmtList()) { ConstructorDecl gramps = (ConstructorDecl)(getParent().getParent()); if(gramps.hasConstructorInvocation()) { if(body == null) { body = new ASTNode[] { gramps.getConstructorInvocation() }; } else { ASTNode[] oldbody = body; body = new ASTNode[body.length+1]; body[0] = gramps.getConstructorInvocation(); System.arraycopy(oldbody, 0, body, 1, oldbody.length); } } } return body; } // the number of concrete children might not be the same as that of abstract children syn int ASTNode.getNumConcreteChild() = getNumChild(); // no explicit constructor invocation eq ConstructorDecl.getNumConcreteChild() = super.getNumConcreteChild()-1; // might have additional constructor invocation eq List.getNumConcreteChild() { if(isConstructorBodyStmtList() && ((ConstructorDecl)(getParent().getParent())).hasConstructorInvocation()) return super.getNumConcreteChild()+1; return super.getNumConcreteChild(); } public int ASTNode.getConcreteIndexOfChild(ASTNode child) { int index = getIndexOfChild(child); ASTNode[] concreteChildren = concreteChildren(); if(concreteChildren[index] == child) return index; for(int i = 0; i < concreteChildren.length; i++) if(concreteChildren[i] == child) return i; return -1; } // generate default concrete syntax for an AST node syn String[] ASTNode.createConcreteSyntax() { String[] strs = concreteSyntax(); for(int i = 0; i < strs.length; i++) { if(strs[i] == null) strs[i] = ""; } return strs; } eq List.createConcreteSyntax() { //if(isBodyDeclList()) // return createBodyDeclListConcreteSyntax(); return super.createConcreteSyntax(); } syn boolean List.isBodyDeclList() = getParent() instanceof ClassDecl && ((ClassDecl)getParent()).getBodyDeclList() == this; syn boolean List.isStmtList() = getParent() instanceof Block && ((Block)getParent()).getStmtList() == this; syn boolean List.isConstructorBodyStmtList() = isStmtList() && ((Block)getParent()).isConstructorBody(); syn boolean Block.isConstructorBody() = getParent() instanceof ConstructorDecl && ((ConstructorDecl)getParent()).getBlock() == this; syn String[] List.createBodyDeclListConcreteSyntax() { String[] strs = new String[getNumConcreteChild() + 1]; strs[0] = "{\n"; for(int i = 1; i < strs.length - 1; i++) strs[i] = ""; strs[strs.length - 1] = createIndent() + "}\n"; return strs; } eq MethodDecl.createConcreteSyntax() { String[] strs = new String[6]; strs[0] = createIndent(); strs[1] = ""; strs[2] = " " + name() + "("; strs[3] = ") " + (getNumException() > 0 ? "throws " : ""); strs[4] = (getNumException() > 0 ? " " : "") + (hasBlock() ? "" : ";\n"); strs[5] = ""; return strs; } eq ConstructorDecl.createConcreteSyntax() = new String[] { createIndent(), " "+name()+"(", ") " + (getNumException() > 0 ? "throws " : ""), getNumException() > 0 ? " " : "", "", "" }; eq FieldDeclaration.createConcreteSyntax() = new String[] { createIndent(), "", " "+name()+(hasInit() ? " = " : ""), ";\n" }; eq Block.createConcreteSyntax() = shouldHaveIndent() ? new String[] { createIndent() + "{\n", createIndent() + "}\n" } : new String[] { "{\n" , createIndent() + "}\n" }; eq ExprStmt.createConcreteSyntax() = new String[] { createIndent(), ";\n" }; eq ReturnStmt.createConcreteSyntax() = hasResult() ? new String[] { createIndent() + "return ", ";\n" } : new String[] { createIndent() + "return", ";\n" }; eq VarAccess.createConcreteSyntax() = new String[] { name(), "" }; eq ConstructorAccess.createConcreteSyntax() = new String[] { name()+"(", ")" }; eq ParConstructorAccess.createConcreteSyntax() = new String[] { "<", ">"+name()+"(", ")" }; eq ParSuperConstructorAccess.createConcreteSyntax() = new String[] { "<", ">"+name()+"(", ")" }; eq TypeAccess.createConcreteSyntax() = new String[] { decl().isPrimitive()? name() : nameWithPackage(), "" }; eq PrimitiveTypeAccess.createConcreteSyntax() = new String[] { name(), "" }; eq ArrayTypeAccess.createConcreteSyntax() = new String[] { "", "[]" }; eq Dot.createConcreteSyntax() = new String[] { "", ".", "" }; eq ForStmt.createConcreteSyntax() = new String[] { createIndent() + "for(", ";", ";", ") ", "" }; eq IfStmt.createConcreteSyntax() = hasElse() ? new String[] { createIndent() + "if(", ") ", " else ", "" } : new String[] { createIndent() + "if(", ") ", "", "" }; eq StringLiteral.createConcreteSyntax() = new String[] { "\"" + escape(getLITERAL()) + "\"", "" }; syn String ASTNode.createIndent() = ""; eq List.createIndent() = getParent().createIndent(); eq Opt.createIndent() = getParent().createIndent(); eq BodyDecl.createIndent() = extractIndent(); eq Stmt.createIndent() = extractIndent(); eq Block.createIndent() = shouldHaveIndent() ? extractIndent() : getParent().createIndent(); syn String ASTNode.extractIndent() { if(concreteSyntax != null && concreteSyntax[0] != null) { String s = concreteSyntax[0]; int index = s.lastIndexOf('\n'); s = s.substring(index+1); int i = 0; while(i < s.length() && Character.isWhitespace(s.charAt(i))) i++; return s.substring(0, i); } if(getParent() == null) return ""; int index = getParent().getConcreteIndexOfChild(this); if(index == -1) return ""; if(index == 0) { return getParent().createIndent() + " "; } return getParent().getChild(index - 1).createIndent(); } syn boolean Block.shouldHaveIndent() = getParent() instanceof List && getParent().getParent() instanceof Block; // character ranges for AST nodes public int ASTNode.startLine() { int startLine = getLine(start()); if(startLine == 0) { ASTNode[] children = concreteChildren(); for(int i = 0; startLine == 0 && i < getNumConcreteChild(); i++) if(children[i].hasLayout()) startLine = children[i].startLine(); } return startLine; } public int ASTNode.startColumn() { int startColumn = getColumn(start()); if(startColumn == 0) { ASTNode[] children = concreteChildren(); for(int i = 0; startColumn == 0 && i < getNumConcreteChild(); i++) if(children[i].hasLayout()) startColumn = children[i].startColumn(); } return startColumn; } public int ASTNode.endLine() { int endLine = getLine(end()); if(endLine == 0) { ASTNode[] children = concreteChildren(); for(int i = getNumConcreteChild()-1; endLine == 0 && i >= 0; i--) if(children[i].hasLayout()) endLine = children[i].endLine(); } return endLine; } public int ASTNode.endColumn() { int endColumn = getColumn(end()); if(endColumn == 0) { ASTNode[] children = concreteChildren(); for(int i = getNumConcreteChild()-1; endColumn == 0 && i >= 0; i--) if(children[i].hasLayout()) endColumn = children[i].endColumn(); } return endColumn; } public int Dot.startLine() { return getLeft().startLine(); } public int Dot.startColumn() { return getLeft().startColumn(); } public int Dot.endLine() { return getRight().endLine(); } public int Dot.endColumn() { return getRight().endColumn(); } public int ArrayTypeAccess.startLine() { return getAccess().startLine(); } public int ArrayTypeAccess.startColumn() { return getAccess().startColumn(); } public int CompilationUnit.startLine() { return 1; } public int CompilationUnit.startColumn() { return 1; } syn boolean ASTNode.hasLayout() = startLine() != 0; protected String ASTNode.extractLayout(int startLine, int startColumn, int endLine, int endColumn, ArrayList layoutBuffer) { try { if(startLine == endLine) return layoutBuffer.get(startLine-1).substring(startColumn-1, endColumn); StringBuilder buf = new StringBuilder(); buf.append(layoutBuffer.get(startLine-1).substring(startColumn-1)); for(int i = startLine + 1; i < endLine; i++) buf.append(layoutBuffer.get(i-1)); buf.append(layoutBuffer.get(endLine-1).substring(0, endColumn)); return buf.toString(); } catch(Exception e) { return ""; } } private ArrayList CompilationUnit.layoutBuffer() { String fileName = pathName(); if(fileName == null) throw new UnsupportedOperationException("Can only extract layout from compilation units with source attached"); ArrayList strings = new ArrayList(); try { java.io.FileReader r = new java.io.FileReader(fileName); int previous = 0; int current = r.read(); StringBuilder buf = new StringBuilder(); while(current != -1) { buf.append((char)current); if(current == 0x0a && previous != 0x0d // LF || current == 0x0d // CR || current == 0x85 // NEL || current == 0x0c // FF || current == 0x2028 // LS || current == 0x2029 // ps ) { strings.add(buf.toString()); buf = new StringBuilder(); } previous = current; current = r.read(); } strings.add(buf.toString()); r.close(); } catch(IOException e) { } return strings; } public String ASTNode.debugLayout() { StringBuffer s = new StringBuffer(); debugLayout(s, 0); return s.toString(); } public void ASTNode.debugLayout(StringBuffer s, int j) { for(int i = 0; i < j; i++) { s.append(" "); } s.append(debugString() + "\n"); for(int i = 0; i < getNumChild(); i++) getChild(i).debugLayout(s, j + 1); } syn String ASTNode.debugString() { StringBuilder res = new StringBuilder(); res.append(getClass().getName()); res.append("["); if(concreteSyntax != null) for(String s : concreteSyntax()) res.append(s + ", "); res.append("]"); return res.toString(); } }