Skip to content

Commit a1ac974

Browse files
committed
Add Unifier interfaces and tests
1 parent 98d334d commit a1ac974

File tree

9 files changed

+1842
-0
lines changed

9 files changed

+1842
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package net.pelsmaeker.unifiers
2+
3+
/**
4+
* An exception that is thrown when a cyclic term is detected during unification.
5+
*
6+
* @property term The term that caused the cyclic term exception.
7+
* @property variable The variable that caused the cyclic term exception.
8+
* @param message The message to include in the exception; or `null` to use the default message.
9+
* @param cause The cause of the exception; or `null` if there is no cause.
10+
*/
11+
class CyclicTermException(
12+
val term: Any,
13+
val variable: Any,
14+
message: String? = null,
15+
cause: Throwable? = null,
16+
): Exception(message ?: "Occurs check failed for $term and $variable", cause)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package net.pelsmaeker.unifiers
2+
3+
4+
/**
5+
* A mutable unifier.
6+
*/
7+
interface MutableUnifier<T, V: T>: Unifier<T, V> {
8+
9+
/**
10+
* Changes this unifier to be the composition of this unifier with another unifier.
11+
*
12+
* @param other The other unifier to compose with.
13+
* @return `true` if the composition was successful; otherwise, `false`.
14+
* @throws CyclicTermException If the composition created a cyclic term.
15+
*/
16+
fun composeWith(other: Unifier<T, V>): Boolean
17+
18+
/**
19+
* Changes this unifier to include the given substitution.
20+
*
21+
* @param v The variable to add.
22+
* @param term The term to add.
23+
* @return `true` if the addition was successful; otherwise, `false`.
24+
* @throws CyclicTermException If the addition created a cyclic term.
25+
*/
26+
fun add(v: V, term: T): Boolean
27+
28+
/**
29+
* Changes this unifier to include the given substitutions.
30+
*
31+
* @param map The map of substitutions to add.
32+
* @return `true` if the additions were successful; otherwise, `false`.
33+
* @throws CyclicTermException If the addition created a cyclic term.
34+
*/
35+
fun addAll(map: Map<V, T>): Boolean
36+
37+
/**
38+
* Changes this unifier such that the given terms are equal.
39+
*
40+
* @param term1 The first term to unify.
41+
* @param term2 The second term to unify.
42+
* @return `true` if the unification was successful; otherwise, `false`.
43+
* @throws CyclicTermException If the unification created a cyclic term.
44+
*/
45+
fun unify(term1: T, term2: T): Boolean
46+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package net.pelsmaeker.unifiers
2+
3+
4+
/**
5+
* A persistent unifier.
6+
*/
7+
interface PersistentUnifier<T, V: T>: Unifier<T, V> {
8+
9+
/**
10+
* Returns a new unifier that is the composition of this unifier with another unifier.
11+
*
12+
* @param other The other unifier to compose with.
13+
* @return The new unifier if the composition was successful; otherwise, `null`.
14+
* @throws CyclicTermException If the composition created a cyclic term.
15+
*/
16+
fun composeWith(other: Unifier<T, V>): PersistentUnifier<T, V>?
17+
= mutate { composeWith(other) }
18+
19+
/**
20+
* Returns a new unifier that includes the given substitution.
21+
*
22+
* @param v The variable to add.
23+
* @param term The term to add.
24+
* @return The new unifier if the addition was successful; otherwise, `null`.
25+
* @throws CyclicTermException If the addition created a cyclic term.
26+
*/
27+
fun add(v: V, term: T): PersistentUnifier<T, V>?
28+
= mutate { add(v, term) }
29+
30+
/**
31+
* Returns a new unifier that includes the given substitutions.
32+
*
33+
* @param map The map of substitutions to add.
34+
* @return The new unifier if the additions were successful; otherwise, `null`.
35+
* @throws CyclicTermException If the addition created a cyclic term.
36+
*/
37+
fun addAll(map: Map<V, T>): PersistentUnifier<T, V>?
38+
= mutate { addAll(map) }
39+
40+
/**
41+
* Returns a new unifier such that the given terms are equal.
42+
*
43+
* @param term1 The first term to unify.
44+
* @param term2 The second term to unify.
45+
* @return The new unifier if the unification was successful; otherwise, `false`.
46+
* @throws CyclicTermException If the unification created a cyclic term.
47+
*/
48+
fun unify(term1: T, term2: T): Unifier<T, V>?
49+
= mutate { unify(term1, term2) }
50+
51+
/**
52+
* Obtain a builder with the same contents as this unifier.
53+
*
54+
* The builder can be used to efficiently perform multiple modification operations.
55+
* Call the builder's [build] method to create a new unifier with the modifications.
56+
*
57+
* @return A new builder with the same contents as this unifier.
58+
*/
59+
fun builder(): Builder<T, V>
60+
61+
/**
62+
* A generic builder of a persistent unifier.
63+
* The builder exposes its modification operations through the [MutableUnifier] interface.
64+
*
65+
* Builders are reusable, that is, its [build] method can be called multiple times with modifications
66+
* between these calls. However, applied modifications do not affect previously built instances.
67+
*/
68+
interface Builder<T, V: T>: MutableUnifier<T, V> {
69+
fun build(): PersistentUnifier<T, V>?
70+
}
71+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package net.pelsmaeker.unifiers
2+
3+
4+
/**
5+
* A unifier is the substitution that makes terms equal.
6+
*
7+
* @param T The type of terms.
8+
* @param V The type of variables.
9+
*/
10+
interface Unifier<T, V: T> {
11+
12+
// /** The number of entries in this unifier. */
13+
// val size: Int
14+
15+
/**
16+
* Determines whether this unifier is empty.
17+
*
18+
* @return `true` when this unifier is empty; otherwise, `false`.
19+
*/
20+
fun isEmpty(): Boolean
21+
22+
/**
23+
* Determines whether this unifier is not empty.
24+
*
25+
* @return `true` when this unifier is not empty; otherwise, `false`.
26+
*/
27+
fun isNotEmpty(): Boolean = !isEmpty()
28+
29+
// /** The entries of this unifier. */
30+
// val entries: Map<V, T>
31+
32+
/**
33+
* Find the representative variable for the given variable.
34+
*
35+
* @param v The variable to find the representative for.
36+
* @return The representative variable; or `null` when the variable is not in the unifier.
37+
*/
38+
fun find(v: V): V?
39+
40+
/**
41+
* Find the representative term for the given term.
42+
*
43+
* @param term The term to find the representative for.
44+
* @return The representative term if [term] is a term variable and occurs in this unifier;
45+
* otherwise, [term] itself.
46+
*/
47+
operator fun get(term: T): T
48+
49+
/**
50+
* Determines whether the unifier contains the specified variable.
51+
*
52+
* @param v The variable to check.
53+
* @return `true` when the unifier contains the specified variable;
54+
* otherwise, `false`.
55+
*/
56+
operator fun contains(v: V): Boolean = find(v) != null
57+
58+
/**
59+
* Checks whether the given term is equal to another term.
60+
*
61+
* This method checks whether the two terms are equal after applying the unifier.
62+
*
63+
* @param term1 The first term to check.
64+
* @param term2 The second term to check.
65+
* @return `true` if the terms are equal after applying the unifier; otherwise, `false`.
66+
*/
67+
fun same(term1: T, term2: T): Boolean
68+
69+
/**
70+
* Recursively instantiate the given term using this unifier.
71+
*
72+
* This method replaces all variables in the term with their corresponding terms
73+
* from the unifier.
74+
*
75+
* @param term The term to instantiate.
76+
* @return The recursively instantiated term.
77+
* @throws UnsupportedOperationException If the term is cyclic.
78+
*/
79+
fun instantiate(term: T): T
80+
81+
/**
82+
* Gets the set of free variables in the given term relative to this unifier.
83+
*
84+
* This method returns the set of variables that are not fully instantiated
85+
* by this unifier.
86+
*
87+
* @param term The term to check.
88+
* @return The set of variables in the term.
89+
*/
90+
fun getFreeVars(term: T): Set<V>
91+
92+
/**
93+
* Checks whether the given term is a ground term relative to this unifier.
94+
*
95+
* A ground term is a term that contains no variables.
96+
*
97+
* @param term The term to check.
98+
* @return `true` if the term is a ground term; otherwise, `false`.
99+
*/
100+
fun isGround(term: T): Boolean
101+
102+
/**
103+
* Checks whether the given term is cyclic relative to this unifier.
104+
*
105+
* A cyclic term is a term that contains itself as a subterm.
106+
*
107+
* @param term The term to check.
108+
* @return `true` if the term is cyclic; otherwise, `false`.
109+
*/
110+
fun isCyclic(term: T): Boolean
111+
112+
// TODO: Add empty() unifier
113+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package net.pelsmaeker.unifiers
2+
3+
4+
/**
5+
* Returns the result of applying the provided modifications on this unifier.
6+
*
7+
* The mutable unifier passed to the [mutator] closure has the same contents as this persistent unifier.
8+
*
9+
* @receiver The persistent unifier to mutate.
10+
* @param mutator A function that mutates the unifier.
11+
* @return A new persistent unifier with the provided modifications applied if the modifications were successful;
12+
* otherwise this instance if no modifications were made in the result of this operation;
13+
* otherwise, `null`.
14+
*/
15+
inline fun <T, V: T> PersistentUnifier<T, V>.mutate(mutator: (MutableUnifier<T, V>) -> Unit): PersistentUnifier<T, V>?
16+
= builder().apply(mutator).build()

0 commit comments

Comments
 (0)