/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.novelang.treemangling;
import com.google.common.base.Preconditions;
import static org.novelang.parser.NodeKind.*;
import org.novelang.common.SimpleTree;
import org.novelang.common.SyntacticTree;
import org.novelang.common.tree.TreeTools;
import org.novelang.common.tree.Treepath;
import org.novelang.common.tree.TreepathTools;
import org.novelang.parser.NodeKind;
import org.novelang.parser.NodeKindTools;
/**
* {@link NodeKind#LEVEL_INTRODUCER_} become {@link org.novelang.parser.NodeKind#_LEVEL}.
*
*
* @author Laurent Caillette
*/
public class LevelMangler {
private LevelMangler() {}
public static Treepath< SyntacticTree > rehierarchizeLevels(
final Treepath< SyntacticTree > treepathToRehierarchize
) {
Treepath< SyntacticTree > currentTreepath ;
boolean first = true ;
{
if( treepathToRehierarchize.getTreeAtEnd().getChildCount() > 0 ) {
currentTreepath = Treepath.create( treepathToRehierarchize, 0 ) ;
} else {
return treepathToRehierarchize ;
}
}
while( true ) {
// We scan children of treepathToRehierarchize.
// If there is one LEVEL_INTRODUCER_ then we do special stuff on it.
if( currentTreepath.getTreeAtEnd().isOneOf( LEVEL_INTRODUCER_ ) ) {
final int roof ;
if( first ) {
first = false ;
roof = getLevelIntroducerDepth( currentTreepath.getTreeAtEnd() ) ;
} else {
roof = 0 ;
}
currentTreepath = rehierarchizeThisLevel( currentTreepath, roof ) ;
}
if( TreepathTools.hasNextSibling( currentTreepath ) ) {
currentTreepath = TreepathTools.getNextSibling( currentTreepath ) ;
} else {
return currentTreepath.getPrevious() ;
}
}
}
/**
* Given a {@code Treepath} to a {@link NodeKind#LEVEL_INTRODUCER_}, returns
* another {@code Treepath} to a a {@link NodeKind#_LEVEL} node where
* the Level Introducer and all following nodes are collapsed into.
* <p>
* Here is how it works.
* The {@code levelIntroducer} "eats" following siblings that should be children of the
* level it represents. They get collapsed in the {@link NodeKind#_LEVEL} node.
* Because {@code Treepath} is an immutable structure, it's important to have only one
* instance at a time to perform changes on.
* Because the {@code Treepath} doesn't allow to keep references on trees, we use index
* when needed.
*
* @param levelIntroducer a non-null {@code Treepath} with a minimum depth of 2.
* @param roof the minimum depth, use 0 to ignore.
* @return a non-null object.
*/
private static Treepath< SyntacticTree > rehierarchizeThisLevel(
Treepath< SyntacticTree > levelIntroducer,
final int roof
) {
final int depth = getLevelIntroducerDepth( levelIntroducer.getTreeAtEnd() ) ;
final int introducerIndex = levelIntroducer.getIndexInPrevious() ;
final SyntacticTree levelIntroducerTree = levelIntroducer.getTreeAtEnd() ;
SyntacticTree levelTree = new SimpleTree( _LEVEL, levelIntroducerTree.getLocation() ) ;
while( true ) {
if( TreepathTools.hasNextSibling( levelIntroducer ) ) {
// Jump to sibling at the start of the loop because on first iteration,
// levelIntroducerTreepath refers to the introducer itself.
final Treepath< SyntacticTree > next = TreepathTools.getNextSibling( levelIntroducer ) ;
final SyntacticTree nextTree = next.getTreeAtEnd() ;
if( LEVEL_INTRODUCER_ == NodeKindTools.ofRoot( nextTree ) ) {
final int newDepth = getLevelIntroducerDepth( nextTree ) ;
if( newDepth < roof ) {
throw new IllegalArgumentException(
"Incorrect depth [" + newDepth + "] " +
"for level declaration " + nextTree.getLocation()
) ;
}
if( newDepth > depth ) { // An introducer of bigger depth is processed then added.
// We get a treepath to new sublevel, with collapsed subcontent.
final Treepath< SyntacticTree > plainLevel = rehierarchizeThisLevel( next, 0 ) ;
// Jump backward to our introducer, deleting the sublevel to avoid duplicates.
levelIntroducer = TreepathTools.getSiblingAt( plainLevel, introducerIndex ) ;
levelIntroducer = TreepathTools.removeNextSibling( levelIntroducer ) ;
// Anyway the sublevel wasn't lost!
levelTree = TreeTools.addLast( levelTree, plainLevel.getTreeAtEnd() ) ;
} else { // Same depth or less means we're done with this one.
return substitute( levelIntroducer, levelTree ) ;
}
} else {
// Just eat the next sibling, moving it to current level.
levelIntroducer = TreepathTools.removeNextSibling( levelIntroducer ) ;
levelTree = TreeTools.addLast( levelTree, nextTree ) ;
}
} else {
return substitute( levelIntroducer, levelTree ) ;
}
}
}
/**
* Replace the {@link NodeKind#LEVEL_INTRODUCER_} at the end of the treepath by a
* {@link NodeKind#_LEVEL}, while retaining all of introducer's children except
* {@link NodeKind#LEVEL_INTRODUCER_INDENT_}.
*/
private static Treepath< SyntacticTree > substitute(
final Treepath< SyntacticTree > levelIntroducer,
SyntacticTree levelTree
) {
for( final SyntacticTree child : levelIntroducer.getTreeAtEnd().getChildren() ) {
if( ! child.isOneOf( LEVEL_INTRODUCER_INDENT_ ) ) {
levelTree = TreeTools.addFirst( levelTree, child ) ;
}
}
final Treepath< SyntacticTree > treepathWithoutIntroducer =
TreepathTools.replaceTreepathEnd( levelIntroducer, levelTree ) ;
return treepathWithoutIntroducer ;
}
/**
* Returns the depth of a level given its indentation.
*
* @param tree a tree of {@link NodeKind#LEVEL_INTRODUCER_} kind.
* @return a number equal to or greater than 1
*/
private static int getLevelIntroducerDepth( final SyntacticTree tree ) {
Preconditions.checkArgument( tree.isOneOf( LEVEL_INTRODUCER_ ) ) ;
Preconditions.checkArgument( tree.getChildCount() > 0 ) ;
final SyntacticTree indentTree = tree.getChildAt( 0 ) ;
Preconditions.checkArgument( indentTree.isOneOf( LEVEL_INTRODUCER_INDENT_ ) ) ;
Preconditions.checkArgument( indentTree.getChildCount() == 1 ) ;
final String indent = indentTree.getChildAt( 0 ).getText() ;
Preconditions.checkArgument( indent.startsWith( "=" ) ) ;
Preconditions.checkArgument( indent.length() > 1 ) ;
return indent.length() - 1 ;
}
}