Skip to content

Commit cad0fba

Browse files
committed
feat: implement disjunction of goals
1 parent d863401 commit cad0fba

File tree

7 files changed

+60
-37
lines changed

7 files changed

+60
-37
lines changed

src/main/scala/io/github/kelvindev15/prolog/CompoundGoal.scala

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.github.kelvindev15.prolog
2+
3+
import io.github.kelvindev15.prolog.Constant.Atom
4+
import io.github.kelvindev15.prolog.Prolog.Functors
5+
import io.github.kelvindev15.prolog.RecursiveStruct.BinaryRecursiveStruct
6+
import io.github.kelvindev15.prolog.utils.TermVisitor
7+
8+
object Goals:
9+
trait Conjunction extends BinaryRecursiveStruct:
10+
override val functor: Constant.Atom = Functors.GOAL_CONJUNCTION
11+
override def accept[T](visitor: TermVisitor[T]): T = visitor.visit(this)
12+
13+
object Conjunction:
14+
def wrapIfNecessary(args: Term*): Term = BinaryRecursiveStruct.wrapIfNecessary(Conjunction.apply)(args*)
15+
def apply(args: Term*): Conjunction = BinaryRecursiveStruct.fold(ConjunctionImpl.apply)(args*)
16+
def unapply(conjunction: Conjunction): Option[(Term, Term)] = Option((conjunction.first, conjunction.second))
17+
18+
private case class ConjunctionImpl(left: Term, right: Term) extends Conjunction
19+
20+
trait Disjunction extends BinaryRecursiveStruct:
21+
override val functor: Constant.Atom = Atom(";")
22+
override def accept[T](visitor: TermVisitor[T]): T = visitor.visit(this)
23+
24+
object Disjunction:
25+
def wrapIfNecessary(args: Term*): Term = BinaryRecursiveStruct.wrapIfNecessary(Disjunction.apply)(args *)
26+
def apply(args: Term*): Conjunction = BinaryRecursiveStruct.fold(DisjunctionImpl.apply)(args *)
27+
def unapply(disjunction: Disjunction): Option[(Term, Term)] = Option((disjunction.first, disjunction.second))
28+
private case class DisjunctionImpl(left: Term, right: Term) extends Conjunction
29+
30+

src/main/scala/io/github/kelvindev15/prolog/Prolog.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ object Prolog:
1010
val VariableRegex: Regex = "[A-Z_][a-zA-Z_0-9]*".r
1111

1212
object Functors:
13-
val COMPOUND_GOAL: Atom = Atom(",")
13+
val GOAL_CONJUNCTION: Atom = Atom(",")
14+
val GOAL_DISJUNCTION: Atom = Atom(";")
1415
val CLAUSE: Atom = Atom(":-")
1516
val EMPTY_LIST: Atom = Atom("[]")
1617
val CONS: Atom = Atom(".")

src/main/scala/io/github/kelvindev15/prolog/RecursiveStruct.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ object RecursiveStruct:
1616
override def linearizedArguments: Iterable[Term] = accept(BinaryToFlatVisitor())
1717

1818
object BinaryRecursiveStruct:
19+
def wrapIfNecessary(struct: Seq[Term] => BinaryRecursiveStruct)(args: Term*): Term = args.size match
20+
case 0 => throw IllegalArgumentException("Cannot create a goal from an empty sequence")
21+
case 1 => args.head
22+
case _ => struct(args)
23+
1924
object Tuple:
2025
def unapply(tuple: BinaryRecursiveStruct): Option[(Term, Term)] =
2126
Option((tuple.left, tuple.right))

src/main/scala/io/github/kelvindev15/prolog/Struct.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.kelvindev15.prolog
22

33
import io.github.kelvindev15.prolog.Constant.Atom
4+
import io.github.kelvindev15.prolog.Goals.Conjunction
45
import io.github.kelvindev15.prolog.Prolog.Functors.CLAUSE
56
import io.github.kelvindev15.prolog.Prolog.Syntax
67
import io.github.kelvindev15.prolog.utils.TermVisitor
@@ -40,7 +41,7 @@ object Struct:
4041
final override def variables: Iterable[Variable] = head.variables ++ body.variables
4142

4243
object Rule:
43-
def apply(head: Struct, args: Term*): Rule = RuleImpl(head, CompoundGoal.ifNecessary(args*))
44+
def apply(head: Struct, args: Term*): Rule = RuleImpl(head, Conjunction.wrapIfNecessary(args*))
4445
private case class RuleImpl(head: Struct, body: Term) extends Rule
4546

4647
trait Fact extends Rule:

src/main/scala/io/github/kelvindev15/prolog/utils/BinaryToFlatVisitor.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.github.kelvindev15.prolog.utils
22

33
import io.github.kelvindev15.prolog.RecursiveStruct.BinaryRecursiveStruct
4-
import io.github.kelvindev15.prolog.{CompoundGoal, Term}
4+
import io.github.kelvindev15.prolog.Term
55
import io.github.kelvindev15.prolog.RecursiveStruct.BinaryRecursiveStruct.Tuple
66

77
class BinaryToFlatVisitor extends TermVisitor[Iterable[Term]]:

src/test/scala/io/github/kelvindev15/prolog/TestRecursiveStructs.scala

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,45 @@
11
package io.github.kelvindev15.prolog
22

33
import io.github.kelvindev15.prolog.Constant.Atom
4+
import io.github.kelvindev15.prolog.Goals.{Conjunction, Disjunction}
45
import io.github.kelvindev15.prolog.PrologList.{Cons, Nil}
56
import org.scalatest.funsuite.AnyFunSuite
67
import org.scalatest.matchers.should.Matchers
78

89
class TestRecursiveStructs extends AnyFunSuite with Matchers:
9-
private val cmpGoal = CompoundGoal(Atom("X"), Atom("Y"), Atom("Z"))
10+
private val cmpGoal = Conjunction(Atom("X"), Atom("Y"), Atom("Z"))
1011

11-
test("Cannot create a compound goal from no term") {
12+
test("Cannot create a conjunction of goals from no terms") {
1213
assertThrows[IllegalArgumentException] {
13-
CompoundGoal.ifNecessary()
14+
Conjunction.wrapIfNecessary()
1415
}
1516
}
1617

17-
test("A Compound goal is a recursive predicate with arity 2 and with ',' as a functor") {
18+
test("A conjunction of goal is a recursive predicate with arity 2 and with ',' as a functor") {
1819
cmpGoal shouldBe a [Term]
1920
cmpGoal.arity shouldBe 2
2021
cmpGoal.functor shouldBe Atom(",")
2122
}
2223

23-
test("Compound goal arguments") {
24+
test("Goal conjunction arguments") {
2425
cmpGoal.first shouldBe Atom("X")
25-
cmpGoal.second shouldBe CompoundGoal(Atom("Y"), Atom("Z"))
26+
cmpGoal.second shouldBe Conjunction(Atom("Y"), Atom("Z"))
2627
}
28+
private val goals = Seq(
29+
Atom("X"),
30+
Conjunction(Atom("X"), Variable("O")),
31+
Variable("Z"),
32+
Conjunction(Constant(1), Constant(2)))
33+
private val linearizationExpectation = goals.take(3) ++ Seq(Constant(1), Constant(2))
2734

2835
test("Compound goal linearized arguments") {
29-
val goals = Seq(
30-
Atom("X"),
31-
CompoundGoal(Atom("X"), Variable("O")),
32-
Variable("Z"),
33-
CompoundGoal(Constant(1), Constant(2)))
34-
val complexCompoundGoal = CompoundGoal(goals*)
35-
complexCompoundGoal.linearizedArguments shouldBe goals.take(3) ++ Seq(Constant(1), Constant(2))
36+
val complexGoalConjunction = Conjunction(goals *)
37+
complexGoalConjunction.linearizedArguments shouldBe linearizationExpectation
38+
}
39+
40+
test("Disjunction of goals linearized arguments") {
41+
val complexGoalDisjunction = Disjunction(goals *)
42+
complexGoalDisjunction.linearizedArguments shouldBe linearizationExpectation
3643
}
3744

3845
private val list = PrologList(Atom("a"), Atom("b"), Atom("c"), Atom("d"))

0 commit comments

Comments
 (0)