All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bitbucket.inkytonik.kiama.relation.Tree.scala Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of Kiama.
 *
 * Copyright (C) 2014-2021 Anthony M Sloane, Macquarie University.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package org.bitbucket.inkytonik.kiama
package relation

/**
 * Exception thrown if a tree relation operation tries to use a node that
 * is not in the tree to which the relation applies. `t` is the node that
 * was found not to be in the tree.
 *
 * For the time-being, the exception does not indicate which tree is involved
 * because it's not clear how to identify the tree briefly in a short message.
 * The stack trace that accompanies the exception should be sufficient.
 */
case class NodeNotInTreeException[T](t : T) extends Exception(s"node not in tree: $t")

/**
 * Exception thrown if `CheckTree` is specified as a tree's shape but the
 * provided structure is not a tree. `msg` describes the reason(s) why the
 * structure is not a tree by listing the nodes that have more than one
 * parent.
 */
case class StructureIsNotATreeException(msg : String) extends Exception(msg)

/**
 * A bridge node in a tree structure which connects to another structure.
 * Bridges are not traversed when determining the tree structure.
 */
case class Bridge[T](cross : T)

/**
 * Tree properties
 */
sealed abstract class TreeShape

/**
 * The incoming structure has an acceptable shape and should be left alone
 * by the tree processing.
 */
case object LeaveAlone extends TreeShape

/**
 * The incoming structure should be a tree (i.e., no sharing nor cycles) but
 * a runtime check should be performed to make sure.
 */
case object CheckTree extends TreeShape

/**
 * The incoming structure may not be a tree (e.g., may contain sharing) and
 * shared parts should be cloned to make it a tree before any further
 * processing is performed.
 */
case object EnsureTree extends TreeShape

/**
 * Relational representations of trees built out of hierarchical `Product`
 * instances. Typically, the nodes are instances of case classes.
 *
 * The type `T` is the common base type of the tree nodes. The type `R` is a
 * sub-type of `T` that is the type of the root node.
 *
 * The `originalRoot` value should be the root of a finite, acyclic structure
 * that contains only `Product` instances of type `T` (unless they are skipped,
 * see below).
 *
 * The `shape` argument governs how the structure referred to by `originalRoot`
 * is treated. If `shape` is `LeaveAlone` (the default) then the structure is
 * used without change. If `shape` is `CheckTree` then a check is run before
 * the structure is used to make sure that it is a tree (i.e., no node sharing
 * nor cycles). A runtime error occurs if this is not the case. If `shape` is
 * `EnsureTree` then shared parts of the structure are cloned to ensure that
 * it is a tree before it is used.
 *
 * If you are confident that your structure is a tree (e.g., it comes from a
 * parser) then the default shape should be fine. If you prefer a bit more
 * safety, then use `CheckTree` so that a non-tree structure cannot be missed.
 * Finally, use `EnsureTree` if you know that your structure may contain
 * sharing (e.g., it is produced by a term rewriting process).
 *
 * The `child` relation of a tree is defined to skip certain nodes.
 * Specifically, if a node of type `T` is wrapped in a `Some` of an option,
 * a `Left` or `Right` of an `Either`, a tuple up to size four, or a
 * `TraversableOnce`, then those wrappers are ignored. E.g., if `t1`
 * is `Some (t2)` where both `t1` and `t2` are of type `T`, then `t1`
 * will be `t2`'s parent, not the `Some` value.
 *
 * Thanks to Len Hamey for the idea to use lazy cloning to restore the tree
 * structure instead of requiring that the input trees contain no sharing.
 */
class Tree[T <: AnyRef with Product, +R <: T](val originalRoot : R, shape : TreeShape = LeaveAlone) {

    tree =>

    import org.bitbucket.inkytonik.kiama.relation.Relation.emptyImage
    import org.bitbucket.inkytonik.kiama.relation.TreeRelation
    import org.bitbucket.inkytonik.kiama.relation.TreeRelation.childFromTree
    import org.bitbucket.inkytonik.kiama.rewriting.Strategy
    import org.bitbucket.inkytonik.kiama.rewriting.Cloner.lazyclone
    import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{all, attempt, rule}
    import org.bitbucket.inkytonik.kiama.util.Comparison
    import org.bitbucket.inkytonik.kiama.util.Comparison.same

    /**
     * A version of `bottomup` that doesn't traverse bridges.
     */
    def bottomupNoBridges(s : Strategy) : Strategy =
        rule[Any] { case b : Bridge[_] => b } <+
            (all(bottomupNoBridges(s)) <* s)

    /**
     * A version of `everywherebu` that doesn't traverse bridges.
     */
    def everywherebuNoBridges(s : Strategy) : Strategy =
        bottomupNoBridges(attempt(s))

    /**
     * The root node of the tree. If this tree's `shape` argument is `EnsureTree`
     * the root node will be different from the original root if any nodes in
     * the original tree are shared, since they will be cloned as necessary
     * to yield a proper tree structure. If there is no sharing or `EnsureTree`
     * is not specified then `root` will be same as `originalRoot`.
     *
     * Bridges to other structures will not be traversed. This behaviour means
     * that you can have references to other structures while still processing
     * this structure as a tree in its own right.
     */
    lazy val root =
        if (shape == EnsureTree)
            lazyclone(originalRoot, everywherebuNoBridges)
        else
            originalRoot

    /**
     * The basic relations between a node and its children. All of the
     * other relations are derived from `child`. If this tree's shape
     * argument is `CheckTree` then a check will be performed that the
     * structure is actually a tree. A runtime error will be thrown
     * if it's not a tree.
     */
    lazy val child : TreeRelation[T] = {

        /*
         * The child relation for this tree.
         */
        val child = childFromTree(tree)

        // As a safety check, we make sure that values are not children
        // of more than one parent.
        if (shape == CheckTree) {
            val msgBuilder = new StringBuilder
            val parent = child.inverse

            def addAncestors(c : T, ps : Vector[T], level : Int = 1) : Unit = {
                for (p <- ps) {
                    val index = indexInVector(child(p), c)
                    val indent = "  " * level
                    msgBuilder ++= s"${indent}child $index of $p\n"
                    addAncestors(p, parent(p), level + 1)
                }
            }

            for (c <- parent.domain) {
                val ps = parent(c)
                if (ps.length > 1) {
                    msgBuilder ++= s"child $c has multiple parents:\n"
                    addAncestors(c, ps)
                }
            }
            if (!msgBuilder.isEmpty)
                throw (new StructureIsNotATreeException(msgBuilder.result()))
        }

        // All ok
        child

    }

    /**
     * The nodes that occur in this tree. Mostly useful if you want to
     * iterate to look at every node.
     */
    lazy val nodes : Vector[T] =
        child.domain

    /**
     * If the tree contains node `u` return `v`, otherwise throw a
     * `NodeNotInTreeException`. `v` is only evaluated if necessary.
     */
    def whenContains[V](t : T, v : => V) : V =
        if (same(t, root) || (parent.containsInDomain(t)))
            v
        else
            throw new NodeNotInTreeException(t)

    // Derived relations

    /**
     * Map the function `f` over the images of this tree's child relation and
     * use the resulting graph to make a new tree relation.
     */
    def mapChild(f : Vector[T] => Vector[T]) : TreeRelation[T] = {
        val relation = new TreeRelation(tree)
        for (t <- child.domain)
            relation.set(t, f(child(t)))
        relation
    }

    /**
     * A relation that relates a node to its first child.
     */
    lazy val firstChild : TreeRelation[T] =
        mapChild(_.take(1))

    /**
     * A relation that relates a node to its last child.
     */
    lazy val lastChild : TreeRelation[T] =
        mapChild(_.takeRight(1))

    /**
     * The parent relation for this tree (inverse of child relation).
     */
    lazy val parent : TreeRelation[T] =
        child.inverse

    /**
     * A relation that relates a node to its next sibling. Inverse of
     * the `prev` relation.
     */
    lazy val next : TreeRelation[T] = {
        val relation = new TreeRelation(tree)
        relation.set(root, emptyImage)
        for (t <- child.domain) {
            val children = child(t)
            if (children.length > 0) {
                for (i <- 0 until children.length - 1)
                    relation.set(children(i), Vector(children(i + 1)))
                relation.set(children(children.length - 1), emptyImage)
            }
        }
        relation
    }

    /**
     * A relation that relates a node to its previous sibling. Inverse of the
     * `next` relation.
     */
    lazy val prev : TreeRelation[T] =
        next.inverse

    /**
     * A relation that relates a node to its siblings, including itself.
     */
    lazy val sibling : TreeRelation[T] = {
        val relation = new TreeRelation(tree)
        relation.put(tree.root, tree.root)
        for (t <- parent.domain; p <- parent(t))
            relation.set(t, child(p))
        relation
    }

    // Predicates derived from the relations

    /**
     * Return the index of a child in a particular child vector.
     */
    def indexInVector(vector : Vector[T], t : T) : Int =
        Comparison.indexOf(vector, t)

    /**
     * Return the first index of `t` in the children of `t's` parent node.
     * Counts from zero. Is zero for root.
     */
    def index(t : T) : Int =
        parent(t) match {
            case Vector(p) =>
                indexInVector(child(p), t)
            case _ =>
                if (same(t, root))
                    0
                else
                    throw new NodeNotInTreeException(t)
        }

    /**
     * Return the last index of `t` in the children of `t's` parent node.
     * Counts from zero. Is zero for root.
     */
    def indexFromEnd(t : T) : Int =
        parent(t) match {
            case Vector(p) =>
                val c = child(p)
                c.length - Comparison.lastIndexOf(c, t) - 1
            case _ =>
                if (same(t, root))
                    0
                else
                    throw new NodeNotInTreeException(t)
        }

    /**
     * Return whether or not `t` is a first child. True for root.
     */
    def isFirst(t : T) : Boolean =
        prev(t).isEmpty

    /**
     * Return whether or not `t` is a last child. True for root.
     */
    def isLast(t : T) : Boolean =
        next(t).isEmpty

    /**
     * Return whether or not `t` is the root of this tree.
     */
    def isRoot(t : T) : Boolean =
        parent(t).isEmpty

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy