Skip to content

Commit 179c098

Browse files
committed
feat: add declarative prolog dsl
1 parent 1adbd5a commit 179c098

File tree

6 files changed

+196
-58
lines changed

6 files changed

+196
-58
lines changed

src/main/scala/io/github/kelvindev15/PrologProgram.scala

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.github.kelvindev15.prolog
2+
3+
import io.github.kelvindev15.prolog.core.Term
4+
import io.github.kelvindev15.prolog.core.Theory.Theory
5+
6+
trait PrologProgram:
7+
def staticTheory: Theory
8+
def dynamicTheory: Theory
9+
def goal: Option[Term]
10+
def withGoal(goal: Term): PrologProgram
11+
def setStaticTheory(theory: Theory): PrologProgram
12+
def setDynamicTheory(theory: Theory): PrologProgram
13+
14+
object PrologProgram:
15+
def apply(staticTheory: Theory = Theory(), dynamicTheory: Theory = Theory()): PrologProgram =
16+
PrologProgramImpl(staticTheory, dynamicTheory, None)
17+
18+
private case class PrologProgramImpl(
19+
staticTheory: Theory,
20+
dynamicTheory: Theory,
21+
goal: Option[Term],
22+
) extends PrologProgram:
23+
override def setStaticTheory(theory: Theory): PrologProgram = PrologProgramImpl(theory, dynamicTheory, goal)
24+
override def setDynamicTheory(theory: Theory): PrologProgram = PrologProgramImpl(staticTheory, theory, goal)
25+
override def withGoal(goal: Term): PrologProgram = PrologProgramImpl(staticTheory, dynamicTheory, Some(goal))
26+
27+
28+
29+

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

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,45 @@ trait DSLExtensions:
1616
def :-(body: Term): Rule = Rule(struct, body)
1717

1818
extension (term: Term)
19-
def and(other: Term): Term = Goals.Conjunction(term, other)
19+
def and(other: Term): Struct = Goals.Conjunction(term, other)
2020
@targetName("conjunctWith")
21-
def &:(other: Term): Term = term and other
22-
def or(other: Term): Term = Goals.Disjunction(term, other)
21+
def &:(other: Term): Struct = term and other
22+
def or(other: Term): Struct = Goals.Disjunction(term, other)
2323
@targetName("disjointWith")
24-
def |: (other: Term): Term = term or other
24+
def |: (other: Term): Struct = term or other
2525
@targetName("univ")
26-
def `=..`(other: Term): Term = Struct(Atom("=.."), term, other)
26+
def `=..`(other: Term): Struct = Struct(Atom("=.."), term, other)
2727
@targetName("equality")
28-
def `=`(other: Term): Term = Struct(Atom("="), term, other)
28+
def `=`(other: Term): Struct = Struct(Atom("="), term, other)
2929
@targetName("strictEquality")
30-
def ==(other: Term): Term = Struct(Atom("=="), term, other)
31-
def is(other: Term): Term = Struct(Atom("is"), term, other)
30+
def ==(other: Term): Struct = Struct(Atom("=="), term, other)
31+
def is(other: Term): Struct = Struct(Atom("is"), term, other)
3232
@targetName("plus")
33-
def +(other: Term): Term = Struct(Atom("+"), term, other)
33+
def +(other: Term): Struct = Struct(Atom("+"), term, other)
3434
@targetName("times")
35-
def *(other: Term): Term = Struct(Atom("*"), term, other)
35+
def *(other: Term): Struct = Struct(Atom("*"), term, other)
3636
@targetName("div")
37-
def /(other: Term): Term = Struct(Atom("/"), term, other)
37+
def /(other: Term): Struct = Struct(Atom("/"), term, other)
3838
@targetName("intDiv")
39-
def `//`(other: Term): Term = Struct(Atom("//"), term, other)
40-
def mod(other: Term): Term = Struct(Atom("mod"), term, other)
39+
def `//`(other: Term): Struct = Struct(Atom("//"), term, other)
40+
def mod(other: Term): Struct = Struct(Atom("mod"), term, other)
4141
@targetName("numericEquality")
42-
def =:=(other: Term): Term = Struct(Atom("=:="), term, other)
42+
def =:=(other: Term): Struct = Struct(Atom("=:="), term, other)
4343
@targetName("numericInequality")
44-
def =\=(other: Term): Term = Struct(Atom("=\\="), term, other)
44+
def =\=(other: Term): Struct = Struct(Atom("=\\="), term, other)
4545
@targetName("lessThan")
46-
def <(other: Term): Term = Struct(Atom("<"), term, other)
46+
def <(other: Term): Struct = Struct(Atom("<"), term, other)
4747
@targetName("greaterThan")
48-
def >(other: Term): Term = Struct(Atom(">"), term, other)
48+
def >(other: Term): Struct = Struct(Atom(">"), term, other)
4949
@targetName("greaterOrEqualThan")
50-
def >=(other: Term): Term = Struct(Atom(">="), term, other)
50+
def >=(other: Term): Struct = Struct(Atom(">="), term, other)
5151
@targetName("lessOrEqualThan")
52-
def =<(other: Term): Term = Struct(Atom("=<"), term, other)
52+
def =<(other: Term): Struct = Struct(Atom("=<"), term, other)
5353
@targetName("termLessThan")
54-
def @<(other: Term): Term = Struct(Atom("@<"), term, other)
54+
def @<(other: Term): Struct = Struct(Atom("@<"), term, other)
5555
@targetName("termGreaterThan")
56-
def @>(other: Term): Term = Struct(Atom("@>"), term, other)
56+
def @>(other: Term): Struct = Struct(Atom("@>"), term, other)
5757
@targetName("termGreaterThanOrEqual")
58-
def @>=(other: Term): Term = Struct(Atom("@>="), term, other)
58+
def @>=(other: Term): Struct = Struct(Atom("@>="), term, other)
5959
@targetName("termLessThanOrEqual")
60-
def @=<(other: Term): Term = Struct(Atom("@=<"), term, other)
60+
def @=<(other: Term): Struct = Struct(Atom("@=<"), term, other)

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

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,48 @@ trait DSLPrologBuiltins:
1818
@targetName("iff")
1919
def :-(terms: Term*): Directive = Directive(terms *)
2020

21-
def `var`(term: Term): Term = Struct(Atom("var"), term)
22-
def nonvar(term: Term): Term = Struct(Atom("nonvar"), term)
23-
def atom(term: Term): Term = Struct(Atom("atom"), term)
24-
def number(term: Term): Term = Struct(Atom("number"), term)
25-
def atomic(term: Term): Term = Struct(Atom("atomic"), term)
26-
def ground(term: Term): Term = Struct(Atom("ground"), term)
21+
def `var`(term: Term): Struct = Struct(Atom("var"), term)
22+
def nonvar(term: Term): Struct = Struct(Atom("nonvar"), term)
23+
def atom(term: Term): Struct = Struct(Atom("atom"), term)
24+
def number(term: Term): Struct = Struct(Atom("number"), term)
25+
def atomic(term: Term): Struct = Struct(Atom("atomic"), term)
26+
def ground(term: Term): Struct = Struct(Atom("ground"), term)
2727

28-
def member(term: Term, l: Term): Term = Struct(Atom("member"), term, l)
29-
def append(l1: Term, l2: Term): Term = Struct(Atom("append"), l1, l2)
28+
def member(term: Term, l: Term): Struct = Struct(Atom("member"), term, l)
29+
def append(l1: Term, l2: Term): Struct = Struct(Atom("append"), l1, l2)
3030

31-
def dynamic(indicators: Indicator*): Term =
31+
def dynamic(indicators: Indicator*): Struct =
3232
Directive(Struct(Atom("dynamic"), Conjunction.wrapIfNecessary(indicators*)))
3333

34-
def clause(head: Term, body: Term): Term = Struct(Atom("clause"), head, body)
35-
def asserta(clause: Term): Term = Struct(Atom("asserta"), clause)
36-
def assertz(clause: Term): Term = Struct(Atom("assertz"), clause)
37-
def retract(clause: Term): Term = Struct(Atom("retract"), clause)
34+
def clause(head: Term, body: Term): Struct = Struct(Atom("clause"), head, body)
35+
def asserta(clause: Term): Struct = Struct(Atom("asserta"), clause)
36+
def assertz(clause: Term): Struct = Struct(Atom("assertz"), clause)
37+
def retract(clause: Term): Struct = Struct(Atom("retract"), clause)
3838

39-
def functor(term: Term, functor: Term, arity: Term): Term =
39+
def functor(term: Term, functor: Term, arity: Term): Struct =
4040
Struct(Atom("functor"), term, functor, arity)
4141

42-
def arg(number: Term, term: Struct, arg: Term): Term = Struct(Atom("arg"), number, term, arg)
42+
def arg(number: Term, term: Struct, arg: Term): Struct = Struct(Atom("arg"), number, term, arg)
4343

44-
def atom_chars(atom: Term, list: Term): Term = Struct(Atom("atom_chars"), atom, list)
45-
def number_chars(atom: Term, list: Term): Term = Struct(Atom("atom"), atom, list)
44+
def atom_chars(atom: Term, list: Term): Struct = Struct(Atom("atom_chars"), atom, list)
45+
def number_chars(atom: Term, list: Term): Struct = Struct(Atom("atom"), atom, list)
4646

4747
@targetName("cut")
4848
val `!`: Atom = Atom("!")
4949

5050
val repeat: Atom = Atom("repeat")
5151

52-
def call(goal: Term): Term = Struct(Atom("call"), goal)
53-
def once(goal: Term): Term = Struct(Atom("once"), goal)
54-
def not(goal: Term): Term = Struct(Atom("not"), goal)
52+
def call(goal: Term): Struct = Struct(Atom("call"), goal)
53+
def once(goal: Term): Struct = Struct(Atom("once"), goal)
54+
def not(goal: Term): Struct = Struct(Atom("not"), goal)
5555

56-
def findall(res: Term, goal: Term, list: Term): Term = Struct(Atom("findall"), res, goal, list)
56+
def findall(res: Term, goal: Term, list: Term): Struct = Struct(Atom("findall"), res, goal, list)
5757

5858
@targetName("naf")
59-
def !(goal: Term): Term = Struct(Atom("\\+"), goal)
59+
def !(goal: Term): Struct = Struct(Atom("\\+"), goal)
6060

61-
def op(precedence: Constant.Numeric, associativity: AssociativitySpec, name: Atom): Term =
61+
def op(precedence: Constant.Numeric, associativity: AssociativitySpec, name: Atom): Struct =
6262
Struct(Atom("op"), precedence, associativity, name)
6363

64-
def length(term: Term, len: Term): Term = Struct(Atom("length"), term, len)
64+
def length(term: Term, len: Term): Struct = Struct(Atom("length"), term, len)
6565

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.github.kelvindev15.prolog.dsl
2+
3+
import io.github.kelvindev15.prolog.PrologProgram
4+
import io.github.kelvindev15.prolog.core.{Struct, Term}
5+
import io.github.kelvindev15.prolog.core.Struct.{Clause, Directive, Fact, Rule}
6+
import io.github.kelvindev15.prolog.core.Theory.Theory
7+
import io.github.kelvindev15.prolog.dsl.DeclarativeDSL.{MutableDynamicTheoryWrapper, MutableTheoryWrapper}
8+
9+
10+
trait DeclarativeDSL:
11+
dsl: PrologDSL =>
12+
private var prologProgram: PrologProgram = PrologProgram()
13+
14+
def prolog(program: PrologProgram ?=> Unit): PrologProgram =
15+
given p: PrologProgram = prologProgram
16+
program
17+
prologProgram
18+
19+
def staticTheory(using program: PrologProgram)(theory: MutableTheoryWrapper ?=> Unit): Unit =
20+
given t: MutableTheoryWrapper = MutableTheoryWrapper()
21+
theory
22+
prologProgram = program.setStaticTheory(t.theory)
23+
24+
def dynamicTheory(using program: PrologProgram)(theory: MutableDynamicTheoryWrapper ?=> Unit): Unit =
25+
given t: MutableDynamicTheoryWrapper = MutableTheoryWrapper()
26+
theory
27+
prologProgram = prologProgram.setDynamicTheory(t.theory)
28+
29+
def assert(using dynamicTheory: MutableDynamicTheoryWrapper)(clause: Clause): Unit =
30+
dynamicTheory add "assert"(clause)
31+
32+
def assertA(using dynamicTheory: MutableDynamicTheoryWrapper)(clause: Clause): Unit =
33+
dynamicTheory add "asserta"(clause)
34+
35+
def assertZ(using dynamicTheory: MutableDynamicTheoryWrapper)(clause: Clause): Unit =
36+
dynamicTheory add "assertz"(clause)
37+
38+
def rule(using theory: MutableTheoryWrapper | MutableDynamicTheoryWrapper)(rule: Rule): Unit = theory add rule
39+
def fact(using theory: MutableTheoryWrapper | MutableDynamicTheoryWrapper)(fact: Fact): Unit = theory add fact
40+
def directive(using theory: MutableTheoryWrapper)(directive: Directive): Unit = theory add directive
41+
def clause(using theory: MutableTheoryWrapper | MutableDynamicTheoryWrapper)(c: Clause): Unit = theory add c
42+
43+
def solve(using program: PrologProgram)(goal: () => Term): Unit =
44+
prologProgram = prologProgram withGoal goal()
45+
46+
object DeclarativeDSL:
47+
trait MutableDynamicTheoryWrapper:
48+
var theory: Theory = Theory()
49+
def add(clause: Clause): Theory = { theory = theory add clause; theory }
50+
def remove(clause: Clause): Theory = { theory = theory remove clause; theory }
51+
52+
trait MutableTheoryWrapper extends MutableDynamicTheoryWrapper
53+
object MutableTheoryWrapper:
54+
def apply(t: Theory): MutableTheoryWrapper = new MutableTheoryWrapper:
55+
theory = t
56+
def apply(clauses: Clause*): MutableTheoryWrapper = new MutableTheoryWrapper:
57+
theory = Theory(clauses *)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.github.kelvindev15.dsl
2+
3+
import io.github.kelvindev15.prolog.PrologProgram
4+
import io.github.kelvindev15.prolog.core.Constant.Atom
5+
import io.github.kelvindev15.prolog.core.{Constant, Struct, Variable}
6+
import io.github.kelvindev15.prolog.core.Struct.{Directive, Fact, Rule}
7+
import io.github.kelvindev15.prolog.dsl.{DeclarativeDSL, PrologDSL}
8+
import org.scalatest.funsuite.AnyFunSuite
9+
import org.scalatest.matchers.should.Matchers
10+
11+
class TestDeclarativeDSL extends AnyFunSuite with Matchers with PrologDSL with DeclarativeDSL:
12+
test("Creation of an empty prolog program"):
13+
val program: PrologProgram = prolog { }
14+
program.staticTheory shouldBe empty
15+
program.dynamicTheory shouldBe empty
16+
program.goal shouldBe empty
17+
18+
test("Creation of a simple static theory"):
19+
val program: PrologProgram = prolog {
20+
staticTheory {
21+
fact { "likes"("alice", X) }
22+
rule { "grandfather"(X, Y) :- ("father"(X, Z) &: "father"(Z, Y)) }
23+
directive { :- ("dynamic"("foo" / 2)) }
24+
}
25+
}
26+
program.staticTheory should have size 3
27+
program.staticTheory.head shouldBe a [Fact]
28+
program.staticTheory.tail.head shouldBe a [Rule]
29+
program.staticTheory.tail.tail.head shouldBe a [Directive]
30+
31+
test("Defining multiple times a theory overrides the theory"):
32+
val program: PrologProgram = prolog {
33+
staticTheory {
34+
fact { "person"("luca") }
35+
}
36+
staticTheory { }
37+
38+
dynamicTheory {
39+
assert { "foo"(X, Y) :- (2 is X + Y) }
40+
}
41+
dynamicTheory { }
42+
}
43+
program.staticTheory shouldBe empty
44+
program.dynamicTheory shouldBe empty
45+
46+
test("Asserts on dynamic theories"):
47+
prolog {
48+
dynamicTheory {
49+
assertA { "foo"(X, Y) :- (2 is X + Y) }
50+
assertZ { "bar"(X, Y) :- (2.5 is X + Y) }
51+
}
52+
}.dynamicTheory shouldBe Seq(
53+
Fact(Struct(Atom("asserta"),
54+
Rule(
55+
Struct(Atom("foo"), Variable("X"), Variable("Y")),
56+
Struct(Atom("is"), Constant.Numeric(2), Struct(Atom("+"), Variable("X"), Variable("Y")))))),
57+
58+
Fact(Struct(Atom("assertz"),
59+
Rule(
60+
Struct(Atom("bar"), Variable("X"), Variable("Y")),
61+
Struct(Atom("is"),
62+
Constant.Numeric(2.5), Struct(Atom("+"), Variable("X"), Variable("Y")))))),
63+
)

0 commit comments

Comments
 (0)