Skip to content

Commit 8350d59

Browse files
committed
feat: implement asTerm conversion
1 parent d9e1714 commit 8350d59

File tree

13 files changed

+104
-16
lines changed

13 files changed

+104
-16
lines changed

src/main/scala/io/github/kelvindev15/prolog/core/Constant.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ trait Constant extends Term:
88
override def variables: Seq[Variable] = Seq()
99
override def isGround: Boolean = true
1010
override def accept[T](visitor: TermVisitor[T]): T = visitor.visit(this)
11+
override def asTerm: Term = this
1112

1213
object Constant:
1314
def apply(value: Any): Constant = value match

src/main/scala/io/github/kelvindev15/prolog/core/Goals.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,16 @@ object Goals:
1414
def wrapIfNecessary(args: Term*): Term = BinaryRecursiveStruct.wrapIfNecessary(Conjunction.apply)(args*)
1515
def apply(args: Term*): Conjunction = BinaryRecursiveStruct.fold(ConjunctionImpl.apply)(args*)
1616
def unapply(conjunction: Conjunction): Option[(Term, Term)] = Option((conjunction.first, conjunction.second))
17-
1817
private case class ConjunctionImpl(left: Term, right: Term) extends Conjunction
1918

2019
trait Disjunction extends BinaryRecursiveStruct:
21-
override val functor: Constant.Atom = Atom(";")
20+
override val functor: Constant.Atom = Functors.GOAL_DISJUNCTION
2221
override def accept[T](visitor: TermVisitor[T]): T = visitor.visit(this)
2322

2423
object Disjunction:
25-
def wrapIfNecessary(args: Term*): Term = BinaryRecursiveStruct.wrapIfNecessary(Disjunction.apply)(args *)
26-
def apply(args: Term*): Conjunction = BinaryRecursiveStruct.fold(DisjunctionImpl.apply)(args *)
24+
def wrapIfNecessary(args: Term*): Term = BinaryRecursiveStruct.wrapIfNecessary(Disjunction.apply)(args*)
25+
def apply(args: Term*): Disjunction = BinaryRecursiveStruct.fold(DisjunctionImpl.apply)(args*)
2726
def unapply(disjunction: Disjunction): Option[(Term, Term)] = Option((disjunction.first, disjunction.second))
28-
private case class DisjunctionImpl(left: Term, right: Term) extends Conjunction
27+
private case class DisjunctionImpl(left: Term, right: Term) extends Disjunction
2928

3029

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ object Prolog:
1717
val CONS: Atom = Atom(".")
1818
val TRUE: Atom = Atom("true")
1919
val FAIL: Atom = Atom("fail")
20+
val INDICATOR: Atom = Atom("/")

src/main/scala/io/github/kelvindev15/prolog/core/PrologList.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ object PrologList:
3737
override val arity: Int = 0
3838
override val arguments: Seq[Term] = Seq()
3939
override val size: Int = 0
40+
override def asTerm: Term = Functors.EMPTY_LIST
41+
4042
override val functor: Constant.Atom = Functors.EMPTY_LIST
4143

42-
private case class ConsImpl(head: Term, tail: (PrologList | Variable)) extends Cons
44+
private case class ConsImpl(head: Term, tail: (PrologList | Variable)) extends Cons:
45+
override def asTerm: Term = Struct(Functors.CONS, head.asTerm, tail.asTerm)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ object RecursiveStruct:
1414
def right: Term
1515
final override val arguments: Seq[Term] = Seq(left, right)
1616
override def linearizedArguments: Seq[Term] = accept(BinaryToFlatVisitor())
17+
final override def asTerm: Term = Struct(functor, left.asTerm, right.asTerm)
1718

1819
object BinaryRecursiveStruct:
1920
def wrapIfNecessary(struct: Seq[Term] => BinaryRecursiveStruct)(args: Term*): Term = args.size match

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.github.kelvindev15.prolog.core
33
import io.github.kelvindev15.prolog.core.Constant.Atom
44
import io.github.kelvindev15.prolog.core.Goals.Conjunction
55
import io.github.kelvindev15.prolog.core.Prolog.Functors.CLAUSE
6-
import io.github.kelvindev15.prolog.core.Prolog.Syntax
6+
import io.github.kelvindev15.prolog.core.Prolog.{Functors, Syntax}
77
import io.github.kelvindev15.prolog.utils.TermVisitor
88

99
trait Struct extends Term:
@@ -24,19 +24,27 @@ object Struct:
2424

2525
private case class StructImpl(functor: Atom, arguments: Term*) extends Struct:
2626
override val arity: Int = arguments.size
27+
override def asTerm: Term = this
2728

2829
trait Indicator extends Struct:
29-
val functor: Atom
30-
val arity: Int
31-
override val arguments: Seq[Term] = Seq(functor, Constant(arity))
30+
val functor: Atom = Functors.INDICATOR
31+
val arity: Int = 2
32+
val indicatedFunctor: Atom
33+
val indicatedArity: Constant.Numeric
34+
override val arguments: Seq[Term] = Seq(indicatedFunctor, indicatedArity)
3235

3336
object Indicator:
34-
def apply(functor: Atom, arity: Int): Indicator = IndicatorImpl(functor, arity)
35-
private case class IndicatorImpl(functor: Atom, arity: Int) extends Indicator
37+
def apply(indicatedFunctor: Atom, indicatedArity: Constant.Numeric): Indicator =
38+
IndicatorImpl(indicatedFunctor, indicatedArity)
39+
private case class IndicatorImpl(indicatedFunctor: Atom, indicatedArity: Constant.Numeric) extends Indicator:
40+
override def asTerm: Term = Struct(functor, indicatedFunctor, indicatedArity)
3641

3742
trait Clause extends Struct:
3843
val head: Option[Struct]
3944
val body: Term
45+
final override def asTerm: Term = head
46+
.map { h => Struct(functor, h.asTerm, body.asTerm) }
47+
.getOrElse(Struct(functor, body.asTerm))
4048

4149
trait Directive extends Clause:
4250
final override val head: Option[Struct] = None

src/main/scala/io/github/kelvindev15/prolog/core/Term.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ trait Visitable:
99
trait Term extends Visitable:
1010
def isGround: Boolean
1111
def variables: Seq[Variable]
12+
def asTerm: Term

src/main/scala/io/github/kelvindev15/prolog/core/Variable.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ object Variable:
1515
case n if n.matches(VariableRegex.regex) => Var(name)
1616
case _ => throw IllegalArgumentException("Incorrect name of a variable")
1717
def anonymous(): Variable = Var("_")
18-
private case class Var(name: String) extends Variable
18+
private case class Var(name: String) extends Variable:
19+
override def asTerm: Term = this
1920

src/main/scala/io/github/kelvindev15/prolog/dsl/DSLExtensions.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ object DSLExtensions:
1616

1717
extension (term: Term)
1818
def and(other: Term): Term = Goals.Conjunction(term, other)
19+
@targetName("conjunctWith")
20+
def &:(other: Term): Term = term and other
1921
def or(other: Term): Term = Goals.Disjunction(term, other)
22+
@targetName("disjointWith")
23+
def |: (other: Term): Term = term or other
2024
@targetName("univ")
2125
def `=..`(other: Term): Term = Struct(Atom("=.."), term, other)
2226
@targetName("equality")

src/main/scala/io/github/kelvindev15/prolog/dsl/PrologDSL.scala

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,16 @@ trait PrologDSL:
1212
export DSLVariables.*
1313

1414
def theory(clauses: Clause*): Theory = Theory(clauses*)
15-
15+
16+
private def wrap(terms: Term*)(f: (Term, Term) => Term): Term = terms.size match
17+
case 1 => terms.head
18+
case n if n > 1 => f(terms.head, wrap(terms.tail*)(f))
19+
20+
@targetName("disjunction")
21+
def ||(terms: Term*): Term = wrap(terms*)(_ or _)
22+
@targetName("conjunction")
23+
def &&(terms: Term*): Term = wrap(terms*)(_ and _)
24+
1625
def list(terms: Term*): PrologList = PrologList(terms *)
1726
def cons(term: Term, tail: (PrologList | Variable)): PrologList = Cons(term, tail)
1827
def cons(terms: Term*)(tail: (PrologList | Variable)): PrologList = terms.size match
@@ -23,4 +32,4 @@ trait PrologDSL:
2332
def head(terms: Term*): Seq[Term] = terms
2433
extension (list: Seq[Term])
2534
@targetName("pipe")
26-
def |(tail: (PrologList | Variable)) = cons(list*)(tail)
35+
def |(tail: (PrologList | Variable)): Term = cons(list*)(tail)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.github.kelvindev15.prolog.utils
2+
3+
import io.github.kelvindev15.prolog.core.Term
4+
5+
trait TermConvertible:
6+
def toTerm: Term

src/test/scala/io/github/kelvindev15/dsl/TestDSL.scala

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

33

44
import io.github.kelvindev15.prolog.core.Constant.Atom
5+
import io.github.kelvindev15.prolog.core.Goals.{Conjunction, Disjunction}
56
import io.github.kelvindev15.prolog.core.Struct.{Directive, Fact, Rule}
67
import io.github.kelvindev15.prolog.core.Variable.anonymous
78
import io.github.kelvindev15.prolog.core.{Constant, PrologList, RecursiveStruct, Struct, Term, Variable}
@@ -16,11 +17,20 @@ class TestDSL extends AnyFunSuite with Matchers with PrologDSL:
1617
f shouldBe a [Fact]
1718

1819
test("Creation of a rule"):
19-
val r = "grandfather"(X, Y) :- ("father"(X, Z) and "father"(Z, Y))
20+
val r = "grandfather"(X, Y) :- &&("father"(X, Z), "father"(Z, Y))
2021
r shouldBe a [Rule]
2122
r.head.get shouldBe Struct(Atom("grandfather"), Variable("X"), Variable("Y"))
2223
r.body shouldBe a [RecursiveStruct]
2324

25+
test("Creation of a compound goal"):
26+
Seq(A &: B &: C, &&(A, B, C)) foreach { _ shouldBe Conjunction(A, B, C) }
27+
(A and B and C) shouldBe Conjunction(Conjunction(A, B), C)
28+
29+
30+
test("Creation of a disjunction of goals"):
31+
Seq(A |: B |: C, ||(A, B, C)) foreach { _ shouldBe Disjunction(A, B, C) }
32+
(A or B or C) shouldBe Disjunction(Disjunction(A, B), C)
33+
2434
test("Creation of a directive"):
2535
val d: Term = :-("op"(1199, "xfx", "-->"))
2636
d shouldBe a [Directive]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.github.kelvindev15.prolog.core
2+
3+
import io.github.kelvindev15.prolog.core.Constant.Atom
4+
import io.github.kelvindev15.prolog.core.Goals.{Conjunction, Disjunction}
5+
import io.github.kelvindev15.prolog.core.PrologList.{Cons, Nil}
6+
import io.github.kelvindev15.prolog.core.Struct.{Directive, Fact, Indicator, Rule}
7+
import org.scalatest.funsuite.AnyFunSuite
8+
import org.scalatest.matchers.should.Matchers
9+
10+
class TestTermConversion extends AnyFunSuite with Matchers:
11+
12+
test("A rule is a binary struct with :- as a functor"):
13+
val head = Struct(Atom("grandfather"), Variable("X"), Variable("Y"))
14+
val bodyArgs = Seq(Struct(Atom("father"), Variable("X"), Variable("Z")),
15+
Struct(Atom("father"), Variable("Z"), Variable("Y")))
16+
Rule(head, bodyArgs*).asTerm shouldBe Struct(Atom(":-"), head, Struct(Atom(","), bodyArgs*))
17+
18+
test("A fact is a binary struct with :- as a functor"):
19+
val head = Struct(Atom("sequence"), Atom("a"), Atom("b"), Atom("c"), Atom("d"))
20+
Fact(head).asTerm shouldBe Struct(Atom(":-"), head, Atom("true"))
21+
22+
test("A directive is a unary struct with :- as a functor"):
23+
Directive(Struct(Atom("dynamic"), Indicator(Atom("foo"), Constant.Numeric(2)))).asTerm shouldBe
24+
Struct(Atom(":-"), Struct(Atom("/"), Atom("foo"), Constant.Numeric(2)))
25+
26+
test("A conjunction of goals is a binary recursive struct with , as a functor"):
27+
val functor = Atom(",")
28+
Conjunction(Atom("a"), Atom("b"), Atom("c")).asTerm shouldBe
29+
Struct(functor, Atom("a"), Struct(functor, Atom("b"), Atom("c")))
30+
31+
test("A disjunction of goals is a binary recursive struct with , as a function"):
32+
val functor = Atom(";")
33+
Disjunction(Atom("a"), Atom("b"), Atom("c")).asTerm shouldBe
34+
Struct(functor, Atom("a"), Struct(functor, Atom("b"), Atom("c")))
35+
36+
test("The functor of an empty list is []"):
37+
Nil.asTerm shouldBe Atom("[]")
38+
39+
test("A prolog list is binary recursive struct with . as a functor"):
40+
val functor = Atom(".")
41+
PrologList(Atom("a"), Atom("b"), Atom("c"), Atom("d")).asTerm shouldBe
42+
Struct(functor, Atom("a"), Struct(functor, Atom("b"),
43+
Struct(functor, Atom("c"),
44+
Struct(functor, Atom("d"), Atom("[]")))))

0 commit comments

Comments
 (0)