Skip to content

Commit c89ee90

Browse files
committed
feat: redesign api
1 parent 9a04641 commit c89ee90

20 files changed

+384
-144
lines changed

docs/docs/documentazione/analysis.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ o *termini composti* (chiamati anche *strutture*).
4646
### Constanti
4747

4848
Le constanti sono ciò che danno il nome agli oggetti o alle relazioni. Possono essere di due tipi: *atomi* o *numeri*.
49-
* Gli atomi si compongo di lettere e/o cifre. Il primo carattere di un atomo deve essere un lettera maiuscola.
49+
* Gli atomi si compongo di lettere e/o cifre. Il primo carattere di un atomo deve essere un lettera minuscola.
5050
Non ci sono vincoli sulla composizione della sequenza di caratteri se questa è racchiusa tra apici `''`.
5151
Gli atomi possono contenere anche i simboli `+`, `-` `*`,`/`, `\ `,`~`, `^`,`<`, `>`, `:`, `.`, `?`, `@`,
5252
`#`, `$`, `&`. La sequenza di caratteri può contenere il carattere `_` per aumentare la leggibilità.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.github.kelvindev15.prolog
2+
3+
import io.github.kelvindev15.prolog.Prolog.Functors
4+
import io.github.kelvindev15.prolog.RecursiveStruct.BinaryRecursiveStruct
5+
import io.github.kelvindev15.prolog.utils.TermVisitor
6+
7+
trait CompoundGoal extends BinaryRecursiveStruct:
8+
override val functor: Constant.Atom = Functors.COMPOUND_GOAL
9+
override def accept[T](visitor: TermVisitor[T]): T = visitor.visit(this)
10+
11+
object CompoundGoal:
12+
def ifNecessary(args: Term*): Term = args.size match
13+
case 0 => throw IllegalArgumentException("Cannot create a goal from an empty sequence")
14+
case 1 => args.head
15+
case _ => CompoundGoal(args*)
16+
17+
def apply(args: Term*): CompoundGoal = BinaryRecursiveStruct.fold(CompoundGoalImpl.apply)(args*)
18+
def unapply(compoundGoal: CompoundGoal): Option[(Term, Term)] = Option((compoundGoal.first, compoundGoal.second))
19+
20+
private case class CompoundGoalImpl(left: Term, right: Term) extends CompoundGoal
Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
package io.github.kelvindev15.prolog
22

3+
import io.github.kelvindev15.prolog.Struct.quoteIfFunctorIsMalformed
4+
import io.github.kelvindev15.prolog.utils.TermVisitor
5+
36
trait Constant extends Term:
4-
final override val isGround: Boolean = true
5-
final override val variables: Seq[Variable] = Seq()
7+
val value: Any
8+
override def variables: Iterable[Variable] = Seq()
9+
override def isGround: Boolean = true
10+
override def accept[T](visitor: TermVisitor[T]): T = visitor.visit(this)
611

712
object Constant:
8-
def apply(value: Any): Constant = value match {
9-
case v: (Double | Int) => Numeric(v)
10-
case v: String => Atom(v)
11-
case invalid => throw IllegalArgumentException(s"$invalid is an invalid value for a constant")
12-
}
13-
14-
trait Atom extends Constant with Struct
13+
def apply(value: Any): Constant = value match
14+
case n: (Int | Double) => Numeric(n)
15+
case s: String => Atom(s)
16+
case _ => throw IllegalArgumentException("Cannot create a constant with the provided argument")
17+
trait Atom extends Constant with Struct:
18+
override val value: String
19+
override val arity: Int = 0
20+
override val arguments: Iterable[Term] = Seq()
21+
override val functor: Atom = this
1522

1623
object Atom:
17-
def apply(value: String): Atom = AtomImpl(value)
24+
private def removeQuotes(value: String) = value.replaceAll("^'+|'+$", "")
25+
def apply(value: String): Atom = AtomImpl(removeQuotes(value))
26+
private case class AtomImpl(private val _value: String) extends Atom:
27+
override val value: String = quoteIfFunctorIsMalformed(_value)
1828

19-
private case class AtomImpl(value: String) extends Atom
20-
21-
trait Numeric extends Constant
29+
trait Numeric extends Constant:
30+
override val value: AnyVal
2231

2332
object Numeric:
2433
def apply(value: AnyVal): Numeric = NumericImpl(value)
25-
2634
private case class NumericImpl(value: AnyVal) extends Numeric
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
package io.github.kelvindev15.prolog
22

3+
import scala.annotation.targetName
4+
5+
/*object LangFeatures:
6+
extension (a: Atom)
7+
def apply(args: Term*): CompoundTerm = CompoundTerm(a, args*)
8+
9+
extension (t: Term)
10+
@targetName("univ")
11+
def `=..`(o: Term): Term = Struct("=..", t, o)
12+
given Conversion[String, Atom] with
13+
override def apply(value: String): Atom = Atom(value)
14+
*/
315
object Language:
416
object Builtins
517

18+
/*val `true`: Atom = "true"
19+
val fail: Atom = "fail"
20+
def `var`(term: Term): Term = "var"(term)
21+
def nonvar(term: Term): Term = "nonvar"(term)
22+
def atom(term: Term): Term = "atom"(term)
23+
def number(term: Term): Term = "number"(term)
24+
def atomic(term: Term): Term = "atomic"(term)
25+
def clause(x: Term, y: Term): Term = "clause"(x, y)
26+
def asserta(x: Term): Term = "asserta"(x)
27+
def assertz(x: Term): Term = "assertz"(x)
28+
def retract(x: Term): Term = "retract"(x)
29+
def listing(x: Atom): Term = "listing"(x)
30+
def functor(t: Term, f: Term, n: Term): Term = "functor"(t, f, n)
31+
def arg(n: Term, t: Term, a: Term): Term = "functor"(n, t, a)
32+
@targetName("univ")
33+
def `=..`(x: Term, l: Term): Term = x.`=..`(l)
34+
def atom_chars(a: Term, l: Term): Term = "atom_chars"(a, l)
35+
def number_chars(a: Term, l: Term): Term = "number_chars"(a, l)
36+
@targetName("cut")
37+
val `!`: Term = "!"
38+
*/
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.github.kelvindev15.prolog
2+
3+
import io.github.kelvindev15.prolog.Constant.Atom
4+
5+
import scala.util.matching.Regex
6+
7+
object Prolog:
8+
object Syntax:
9+
val AtomRegex: Regex = "^[a-z][a-zA-Z_0-9]*$".r
10+
val VariableRegex: Regex = "[A-Z_][a-zA-Z_0-9]*".r
11+
12+
object Functors:
13+
val COMPOUND_GOAL: Atom = Atom(",")
14+
val CLAUSE: Atom = Atom(":-")
15+
val CONS: Atom = Atom(".")
16+
val TRUE: Atom = Atom("true")
17+
val FAIL: Atom = Atom("fail")
18+
val EMPTY_LIST: Atom = Atom("[]")
19+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.github.kelvindev15.prolog
2+
3+
import io.github.kelvindev15.prolog.Constant.Atom
4+
import io.github.kelvindev15.prolog.utils.BinaryToFlatVisitor
5+
6+
trait RecursiveStruct extends Struct:
7+
def linearizedArguments: Iterable[Term]
8+
9+
object RecursiveStruct:
10+
trait BinaryRecursiveStruct extends RecursiveStruct:
11+
final override val arity: Int = 2
12+
val left: Term
13+
val right: Term
14+
val first: Term = left
15+
val second: Term = right
16+
final override val arguments: Iterable[Term] = Seq(left, right)
17+
override def linearizedArguments: Iterable[Term] = accept(BinaryToFlatVisitor())
18+
19+
object BinaryRecursiveStruct:
20+
object Tuple:
21+
def unapply(tuple: BinaryRecursiveStruct): Option[(Term, Term)] =
22+
Option((tuple.left, tuple.right))
23+
24+
def fold[T <: BinaryRecursiveStruct](struct: (Term, Term) => T)(args: Term*): T = args.size match
25+
case n if n < 2 => throw IllegalArgumentException("There must be at least two arguments")
26+
case 2 => struct(args.head, args.tail.head)
27+
case _ => struct(args.head, fold(struct)(args.tail *))

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

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

33
import io.github.kelvindev15.prolog.Constant.Atom
4-
import io.github.kelvindev15.prolog.Term.Recursive
4+
import io.github.kelvindev15.prolog.Prolog.Functors.{CLAUSE, COMPOUND_GOAL}
5+
import io.github.kelvindev15.prolog.Prolog.Syntax
6+
import io.github.kelvindev15.prolog.utils.TermVisitor
57

6-
trait Struct extends Term
8+
trait Struct extends Term:
9+
val functor: Atom
10+
val arity: Int
11+
val arguments: Iterable[Term]
12+
13+
override def variables: Iterable[Variable] = arguments.flatMap(_.variables)
14+
override def isGround: Boolean = arguments.forall(_.isGround)
15+
16+
override def accept[T](visitor: TermVisitor[T]): T = visitor.visit(this)
717

818
object Struct:
19+
private def isFunctorWellFormed(functor: String): Boolean = functor.matches(Syntax.AtomRegex.regex)
20+
def quoteIfFunctorIsMalformed(functor: String): String =
21+
if (isFunctorWellFormed(functor)) functor else s"'$functor'"
922
def apply(functor: Atom, args: Term*): Struct = StructImpl(functor, args*)
1023

11-
private case class StructImpl(functor: Atom, args: Term*) extends Struct:
12-
override def isGround: Boolean = args.forall(_.isGround)
13-
override def variables: Seq[Variable] = args.collect { case t: Variable => t }
24+
object Functor:
25+
def unapply(struct: Struct): Option[Atom] = Option(struct.functor)
26+
27+
object Args:
28+
def unapply(struct: Struct): Option[Iterable[Term]] = Option(struct.arguments)
29+
30+
private case class StructImpl(functor: Atom, arguments: Term*) extends Struct:
31+
override val arity: Int = arguments.size
1432

1533
trait Rule extends Struct:
1634
val head: Struct
1735
val body: Term
18-
19-
override def isGround: Boolean = head.isGround && body.isGround
20-
override def variables: Seq[Variable] = head.variables ++ body.variables
36+
final override val functor: Atom = CLAUSE
37+
final override val arity: Int = 2
38+
final override val arguments: Iterable[Term] = Seq(head, body)
39+
final override def isGround: Boolean = head.isGround && body.isGround
40+
final override def variables: Iterable[Variable] = head.variables ++ body.variables
2141

2242
object Rule:
23-
private val cmpGoalPredicate = Recursive.of(Atom(","), 2)
24-
25-
def apply(head: Struct, body: Term*): Rule =
26-
if (body.size > 1)
27-
RuleImpl(head, cmpGoalPredicate.fold(body*))
28-
else
29-
RuleImpl(head, body.headOption.getOrElse(Atom("true")))
30-
31-
private case class RuleImpl(head: Struct, body: Term) extends Rule
43+
def apply(head: Struct, args: Term*): Rule = RuleImpl(head, CompoundGoal.ifNecessary(args*))
44+
private case class RuleImpl(head: Struct, body: Term) extends Rule
3245

3346
trait Fact extends Rule:
34-
final override val body = Atom("true")
47+
override val body: Term = Atom("true")
3548

3649
object Fact:
37-
def apply(fact: Struct): Fact = FactImpl(fact)
50+
def apply(head: Struct): Fact = FactImpl(head)
51+
private case class FactImpl(head: Struct) extends Fact
52+
53+
54+
3855

39-
private case class FactImpl(override val head: Struct) extends Fact
Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,12 @@
11
package io.github.kelvindev15.prolog
22

3-
import io.github.kelvindev15.prolog.Constant.Atom
3+
import io.github.kelvindev15.prolog.utils.TermVisitor
44

5-
trait Term:
6-
def isGround: Boolean
7-
def variables: Seq[Variable]
8-
9-
object Term:
10-
trait Recursive:
11-
val functor: Atom
12-
val arity: Int
13-
def fold(term: Term*): Term
14-
15-
object Recursive:
16-
def of(functor: Atom, arity: Int): Recursive = RecursiveImpl(functor, arity)
5+
trait Visitable:
6+
self: Term =>
7+
def accept[T](visitor: TermVisitor[T]): T
178

18-
private case class RecursiveImpl(functor: Atom, arity: Int) extends Recursive:
19-
if (arity <= 1)
20-
throw IllegalArgumentException("Arity should be at least 2")
21-
22-
override def fold(term: Term*): Term =
23-
if (term.size == arity)
24-
Struct(functor, term *)
25-
else if (term.size < arity || (term.size - arity) % (arity - 1) != 0)
26-
throw IllegalArgumentException("Number of provided terms are incompatible with the arity of this predicate")
27-
else
28-
val tmp = term.splitAt(arity - 1)
29-
Struct(functor, tmp._1 :+ fold(tmp._2*)*)
9+
trait Term extends Visitable:
10+
def isGround: Boolean
11+
def variables: Iterable[Variable]
12+
Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package io.github.kelvindev15.prolog
22

3+
import io.github.kelvindev15.prolog.Prolog.Syntax.VariableRegex
4+
import io.github.kelvindev15.prolog.utils.TermVisitor
5+
36
trait Variable extends Term:
47
val name: String
58
final def isAnonymous: Boolean = name == "_"
69
final override def isGround: Boolean = false
7-
final override def variables: Seq[Variable] = Seq(this)
8-
10+
final override def variables: Iterable[Variable] = Seq(this)
11+
override def accept[T](visitor: TermVisitor[T]): T = visitor.visit(this)
12+
913
object Variable:
10-
def apply(name: String): Variable =
11-
require(
12-
name == "_" || name.matches("""\p{Upper}[a-zA-Z0-9_]*"""),
13-
"A variable name should be an alphanumerical string beginning with an with an uppercase letter"
14-
)
15-
VariableImpl(name: String)
16-
17-
private case class VariableImpl(name: String) extends Variable
14+
def apply(name: String): Variable = name match
15+
case n if n.matches(VariableRegex.regex) => Var(name)
16+
case _ => throw IllegalArgumentException("Incorrect name of a variable")
17+
def anonymous(): Variable = Var("_")
18+
private case class Var(name: String) extends Variable
19+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.github.kelvindev15.prolog.dsl
2+
3+
trait PrologDSL:
4+
def program(using scope: Any)(): Any = ???
5+
6+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.github.kelvindev15.prolog.utils
2+
3+
import io.github.kelvindev15.prolog.RecursiveStruct.BinaryRecursiveStruct
4+
import io.github.kelvindev15.prolog.{CompoundGoal, Term}
5+
import io.github.kelvindev15.prolog.RecursiveStruct.BinaryRecursiveStruct.Tuple
6+
7+
class BinaryToFlatVisitor extends TermVisitor[Iterable[Term]]:
8+
override def visit(tuple: BinaryRecursiveStruct): Iterable[Term] = tuple match
9+
case Tuple(l, r @ Tuple(_, _)) => Seq(l) ++ visit(r)
10+
case Tuple(l, r) => Seq(l, r)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package io.github.kelvindev15.prolog.utils
2+
3+
import io.github.kelvindev15.prolog.Constant.{Atom, Numeric}
4+
import io.github.kelvindev15.prolog.RecursiveStruct.BinaryRecursiveStruct
5+
import io.github.kelvindev15.prolog.Struct.{Fact, Rule}
6+
import io.github.kelvindev15.prolog.{Constant, Struct, Variable}
7+
8+
trait TermVisitor[T]:
9+
def visit(constant: Constant): T = throw NotImplementedError()
10+
def visit(atom: Atom): T = throw NotImplementedError()
11+
def visit(numeric: Numeric): T = throw NotImplementedError()
12+
def visit(variable: Variable): T = throw NotImplementedError()
13+
def visit(struct: Struct): T = throw NotImplementedError()
14+
def visit(rule: Rule): T = throw NotImplementedError()
15+
def visit(fact: Fact): T = throw NotImplementedError()
16+
def visit(binaryRecursiveStruct: BinaryRecursiveStruct): T = throw NotImplementedError()
17+

src/test/scala/io/github/kelvindev15/PrologProgramTest.scala

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)