REPL checkpoint
This commit is contained in:
parent
69c156024a
commit
1b3280a947
21 changed files with 503 additions and 34 deletions
3
src/Debug.kt
Normal file
3
src/Debug.kt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
data object Debug {
|
||||||
|
val on: Boolean = true
|
||||||
|
}
|
97
src/Main.kt
97
src/Main.kt
|
@ -1,9 +1,98 @@
|
||||||
|
import better_parser.PrologParser
|
||||||
|
import better_parser.SimpleReplParser
|
||||||
|
import interpreter.SourceFileReader
|
||||||
|
import prolog.Answer
|
||||||
|
import prolog.Program
|
||||||
|
import prolog.ast.logic.Fact
|
||||||
|
import prolog.ast.logic.Rule
|
||||||
|
import prolog.ast.terms.Atom
|
||||||
|
import prolog.ast.terms.CompoundTerm
|
||||||
|
import prolog.ast.terms.Variable
|
||||||
|
import prolog.builtins.Conjunction
|
||||||
|
|
||||||
|
fun help(): String {
|
||||||
|
println("Unknown command. Type 'h' for help.")
|
||||||
|
println("Commands:")
|
||||||
|
println(" ; - find next solution")
|
||||||
|
println(" a - abort")
|
||||||
|
println(" . - end query")
|
||||||
|
println(" h - help")
|
||||||
|
println(" exit - exit Prolog REPL")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun say(message: String) {
|
||||||
|
println(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun prompt(message: String): String {
|
||||||
|
print("$message ")
|
||||||
|
var input: String = readlnOrNull() ?: help()
|
||||||
|
while (input.isBlank()) {
|
||||||
|
input = readlnOrNull() ?: help()
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
fun prettyResult(result: Answer): String {
|
||||||
|
result.fold(
|
||||||
|
onSuccess = {
|
||||||
|
val subs = result.getOrNull()!!
|
||||||
|
if (subs.isEmpty()) {
|
||||||
|
return "true."
|
||||||
|
}
|
||||||
|
return subs.entries.joinToString(", ") { "${it.key} = ${it.value}" }
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
return "Failure: ${result.exceptionOrNull()!!}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val knownCommands = setOf(";", "a", ".")
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
for (i in 1..10) {
|
SourceFileReader().readFile("tests/better_parser/resources/parent.pl")
|
||||||
println("Hello, Kotlin Command Line Utility!")
|
|
||||||
|
val parser = SimpleReplParser(debug = false)
|
||||||
|
|
||||||
|
say("Prolog REPL. Type 'exit' to quit.")
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val queryString = prompt("?-")
|
||||||
|
|
||||||
|
try {
|
||||||
|
val query = parser.parse(queryString)
|
||||||
|
val answers = query.satisfy(emptyMap())
|
||||||
|
|
||||||
|
if (answers.none()) {
|
||||||
|
say("false.")
|
||||||
|
} else {
|
||||||
|
val iterator = answers.iterator()
|
||||||
|
|
||||||
|
var previous = iterator.next()
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
var command = prompt(prettyResult(previous))
|
||||||
|
|
||||||
|
while (command !in knownCommands) {
|
||||||
|
say("Unknown action: $command (h for help)")
|
||||||
|
command = prompt("Action?")
|
||||||
|
}
|
||||||
|
|
||||||
|
when (command) {
|
||||||
|
";" -> previous = iterator.next()
|
||||||
|
"a" -> break
|
||||||
|
"." -> break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
say(prettyResult(previous))
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Error: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testMe(): String {
|
|
||||||
return "Hello, Kotlin Command Line Utility!"
|
|
||||||
}
|
}
|
||||||
|
|
17
src/better_parser/PrologParser.kt
Normal file
17
src/better_parser/PrologParser.kt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package better_parser
|
||||||
|
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.Grammar
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.parseToEnd
|
||||||
|
import prolog.Program
|
||||||
|
import prolog.ast.logic.Clause
|
||||||
|
import prolog.ast.terms.Atom
|
||||||
|
|
||||||
|
class PrologParser {
|
||||||
|
private val parser: Grammar<List<Clause>> = SimpleSourceParser() as Grammar<List<Clause>>
|
||||||
|
|
||||||
|
public fun parse(input: String) {
|
||||||
|
val clauses: List<Clause> = parser.parseToEnd(input)
|
||||||
|
|
||||||
|
Program.load(clauses)
|
||||||
|
}
|
||||||
|
}
|
70
src/better_parser/PrologSourceParser.kt
Normal file
70
src/better_parser/PrologSourceParser.kt
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package better_parser
|
||||||
|
|
||||||
|
import com.github.h0tk3y.betterParse.combinators.*
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.Grammar
|
||||||
|
import com.github.h0tk3y.betterParse.lexer.literalToken
|
||||||
|
import com.github.h0tk3y.betterParse.lexer.regexToken
|
||||||
|
import com.github.h0tk3y.betterParse.parser.Parser
|
||||||
|
import prolog.ast.logic.Fact
|
||||||
|
import prolog.ast.arithmetic.Integer
|
||||||
|
import prolog.ast.arithmetic.Float
|
||||||
|
import prolog.ast.logic.Clause
|
||||||
|
import prolog.ast.logic.LogicOperand
|
||||||
|
import prolog.ast.logic.Rule
|
||||||
|
import prolog.ast.terms.*
|
||||||
|
import prolog.builtins.Conjunction
|
||||||
|
import prolog.builtins.Disjunction
|
||||||
|
|
||||||
|
class PrologSourceParser : Grammar<List<Clause>>() {
|
||||||
|
// Define the tokens
|
||||||
|
private val atom by regexToken("[a-z][a-zA-Z0-9_]*")
|
||||||
|
private val variable by regexToken("[A-Z][a-zA-Z0-9_]*")
|
||||||
|
private val number by regexToken("-?[0-9]+(\\.[0-9]+)?")
|
||||||
|
private val whitespace by regexToken("\\s+", ignore = true)
|
||||||
|
|
||||||
|
private val comma by literalToken(",")
|
||||||
|
private val semicolon by literalToken(";")
|
||||||
|
private val neck by literalToken(":-")
|
||||||
|
private val lparen by literalToken("(")
|
||||||
|
private val rparen by literalToken(")")
|
||||||
|
private val dot by literalToken(".")
|
||||||
|
|
||||||
|
private val atomParser by atom use { Atom(text) }
|
||||||
|
private val variableParser by variable use { Variable(text) }
|
||||||
|
private val intParser by number use { Integer(text.toInt()) }
|
||||||
|
private val floatParser by number use { Float(text.toFloat()) }
|
||||||
|
private val numberParser by (intParser or floatParser)
|
||||||
|
private val compoundTermParser by (atomParser and skip(lparen) and separated(
|
||||||
|
atomParser or variableParser,
|
||||||
|
comma
|
||||||
|
) and skip(rparen)) use {
|
||||||
|
CompoundTerm(t1, t2.terms)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val termParser: Parser<Term> by (numberParser or variableParser or compoundTermParser or atomParser)
|
||||||
|
|
||||||
|
private val logicOperandParser: Parser<LogicOperand> by (termParser or compoundTermParser or atomParser) map {
|
||||||
|
it as LogicOperand
|
||||||
|
}
|
||||||
|
|
||||||
|
private val conjunctionParser: Parser<Conjunction> by (logicOperandParser and comma and logicOperandParser) use {
|
||||||
|
Conjunction(t1, t3)
|
||||||
|
}
|
||||||
|
private val disjunctionParser: Parser<Disjunction> by (logicOperandParser and semicolon and logicOperandParser) use {
|
||||||
|
Disjunction(t1, t3)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val operatorParser: Parser<Operator> by (conjunctionParser or disjunctionParser)
|
||||||
|
|
||||||
|
private val headParser by (compoundTermParser or atomParser)
|
||||||
|
private val bodyParser by (operatorParser or compoundTermParser or atomParser)
|
||||||
|
|
||||||
|
private val factParser by (headParser and dot) use { Fact(t1 as Head) }
|
||||||
|
private val ruleParser by (headParser and neck and bodyParser and dot) use {
|
||||||
|
Rule(t1 as Head, t3 as Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val clauseParser: Parser<Clause> by (factParser or ruleParser)
|
||||||
|
|
||||||
|
override val rootParser: Parser<List<Clause>> by zeroOrMore(clauseParser)
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import com.github.h0tk3y.betterParse.grammar.parser
|
||||||
import com.github.h0tk3y.betterParse.lexer.Token
|
import com.github.h0tk3y.betterParse.lexer.Token
|
||||||
import com.github.h0tk3y.betterParse.lexer.literalToken
|
import com.github.h0tk3y.betterParse.lexer.literalToken
|
||||||
import com.github.h0tk3y.betterParse.lexer.regexToken
|
import com.github.h0tk3y.betterParse.lexer.regexToken
|
||||||
|
import com.github.h0tk3y.betterParse.lexer.token
|
||||||
import com.github.h0tk3y.betterParse.parser.Parser
|
import com.github.h0tk3y.betterParse.parser.Parser
|
||||||
import prolog.ast.arithmetic.Float
|
import prolog.ast.arithmetic.Float
|
||||||
import prolog.ast.arithmetic.Integer
|
import prolog.ast.arithmetic.Integer
|
||||||
|
@ -16,28 +17,32 @@ import prolog.ast.terms.Variable
|
||||||
|
|
||||||
open class SimplePrologParser : Grammar<Any>() {
|
open class SimplePrologParser : Grammar<Any>() {
|
||||||
// Prolog tokens
|
// Prolog tokens
|
||||||
private val nameToken: Token by regexToken("[a-z][a-zA-Z0-9_]*")
|
protected val nameToken: Token by regexToken("[a-z][a-zA-Z0-9_]*")
|
||||||
private val variableToken: Token by regexToken("[A-Z][a-zA-Z0-9_]*")
|
protected val variableToken: Token by regexToken("[A-Z][a-zA-Z0-9_]*")
|
||||||
|
|
||||||
// Arithmetic tokens
|
// Arithmetic tokens
|
||||||
private val floatToken: Token by regexToken("-?[1-9][0-9]*\\.[0-9]+")
|
private val floatToken: Token by regexToken("-?[1-9][0-9]*\\.[0-9]+")
|
||||||
private val integerToken: Token by regexToken("-?([1-9][0-9]*|0)")
|
private val integerToken: Token by regexToken("-?([1-9][0-9]*|0)")
|
||||||
|
|
||||||
// Special tokens
|
// Special tokens
|
||||||
private val comma: Token by literalToken(",")
|
protected val neck by literalToken(":-")
|
||||||
private val leftParenthesis: Token by literalToken("(")
|
protected val comma: Token by literalToken(",")
|
||||||
private val rightParenthesis: Token by literalToken(")")
|
protected val leftParenthesis: Token by literalToken("(")
|
||||||
|
protected val rightParenthesis: Token by literalToken(")")
|
||||||
|
protected val dot by literalToken(".")
|
||||||
|
|
||||||
// Ignored tokens
|
// Ignored tokens
|
||||||
private val whitespace: Token by regexToken("\\s+", ignore = true)
|
protected val whitespace: Token by regexToken("\\s+", ignore = true)
|
||||||
private val singleLineComment: Token by regexToken("%[^\\n]*", ignore = true)
|
protected val singleLineComment: Token by regexToken("%[^\\n]*", ignore = true)
|
||||||
private val multiLineComment: Token by regexToken("/\\*.*?\\*/", ignore = true)
|
protected val multiLineComment: Token by regexToken("/\\*.*?\\*/", ignore = true)
|
||||||
|
|
||||||
|
protected val dummy by token { _, _ -> -1 } use { throw IllegalStateException("This parser should not be used") }
|
||||||
|
|
||||||
// Prolog parsers
|
// Prolog parsers
|
||||||
private val atomParser: Parser<Atom> by nameToken use { Atom(text) }
|
protected val variable: Parser<Variable> by variableToken use { Variable(text) }
|
||||||
private val variableParser: Parser<Variable> by variableToken use { Variable(text) }
|
protected val atom: Parser<Atom> by nameToken use { Atom(text) }
|
||||||
private val structureParser: Parser<Structure> by (atomParser and skip(leftParenthesis) and separated(
|
protected val compound: Parser<Structure> by (atom and skip(leftParenthesis) and separated(
|
||||||
parser(this::termParser),
|
parser(::term),
|
||||||
comma,
|
comma,
|
||||||
acceptZero = true
|
acceptZero = true
|
||||||
) and skip(rightParenthesis)) use {
|
) and skip(rightParenthesis)) use {
|
||||||
|
@ -45,17 +50,18 @@ open class SimplePrologParser : Grammar<Any>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arithmetic parsers
|
// Arithmetic parsers
|
||||||
private val integerParser: Parser<Integer> by integerToken use { Integer(text.toInt()) }
|
private val int: Parser<Integer> by integerToken use { Integer(text.toInt()) }
|
||||||
private val floatParser: Parser<Float> by floatToken use {
|
private val float: Parser<Float> by floatToken use {
|
||||||
Float(text.toFloat())
|
Float(text.toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
private val termParser: Parser<Term> by (floatParser
|
protected val term: Parser<Term> by (dummy
|
||||||
or integerParser
|
or float
|
||||||
or variableParser
|
or int
|
||||||
or structureParser
|
or variable
|
||||||
or atomParser
|
or compound
|
||||||
|
or atom
|
||||||
) map { it }
|
) map { it }
|
||||||
|
|
||||||
override val rootParser: Parser<Any> = termParser
|
override val rootParser: Parser<Any> = term
|
||||||
}
|
}
|
27
src/better_parser/SimpleReplParser.kt
Normal file
27
src/better_parser/SimpleReplParser.kt
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package better_parser
|
||||||
|
|
||||||
|
import com.github.h0tk3y.betterParse.combinators.times
|
||||||
|
import com.github.h0tk3y.betterParse.combinators.unaryMinus
|
||||||
|
import com.github.h0tk3y.betterParse.combinators.use
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.parseToEnd
|
||||||
|
import com.github.h0tk3y.betterParse.parser.Parser
|
||||||
|
import prolog.ast.logic.LogicOperand
|
||||||
|
import prolog.builtins.Query
|
||||||
|
|
||||||
|
class SimpleReplParser(val debug: Boolean = false) : SimpleSourceParser() {
|
||||||
|
override val rootParser: Parser<Query> by (body * -dot) use { Query(this as LogicOperand) }
|
||||||
|
|
||||||
|
fun parse(input: String): Query {
|
||||||
|
if (debug) {
|
||||||
|
println("Parsing input: $input")
|
||||||
|
}
|
||||||
|
|
||||||
|
val query = parseToEnd(input) as Query
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
println("Parsed query: $query")
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
}
|
48
src/better_parser/SimpleSourceParser.kt
Normal file
48
src/better_parser/SimpleSourceParser.kt
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package better_parser
|
||||||
|
|
||||||
|
import com.github.h0tk3y.betterParse.combinators.*
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.parser
|
||||||
|
import com.github.h0tk3y.betterParse.lexer.literalToken
|
||||||
|
import com.github.h0tk3y.betterParse.parser.Parser
|
||||||
|
import prolog.ast.arithmetic.ArithmeticOperator
|
||||||
|
import prolog.ast.logic.*
|
||||||
|
import prolog.ast.terms.*
|
||||||
|
import prolog.builtins.Conjunction
|
||||||
|
import prolog.builtins.Disjunction
|
||||||
|
|
||||||
|
open class SimpleSourceParser : SimplePrologParser() {
|
||||||
|
protected val simpleLogicOperand: Parser<LogicOperand> by (dummy
|
||||||
|
or compound
|
||||||
|
or atom
|
||||||
|
)
|
||||||
|
protected val logicOperand: Parser<LogicOperand> by (dummy
|
||||||
|
or parser(::operator)
|
||||||
|
or simpleLogicOperand
|
||||||
|
)
|
||||||
|
|
||||||
|
protected val arithmeticOperator: Parser<ArithmeticOperator> by dummy
|
||||||
|
protected val logicOperator: Parser<LogicOperator> by (simpleLogicOperand * comma * logicOperand) use {
|
||||||
|
Conjunction(t1, t3)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val operator: Parser<Operator> by (arithmeticOperator or logicOperator) use { this as Operator }
|
||||||
|
|
||||||
|
protected val head: Parser<Head> by (dummy
|
||||||
|
or compound
|
||||||
|
or atom
|
||||||
|
)
|
||||||
|
protected val body: Parser<Body> by (dummy
|
||||||
|
or operator
|
||||||
|
or head
|
||||||
|
) use { this as Body }
|
||||||
|
|
||||||
|
// ----
|
||||||
|
|
||||||
|
private val rule: Parser<Rule> by (head * -neck * body) use { Rule(t1, t2) }
|
||||||
|
private val fact: Parser<Fact> by head use { Fact(this) }
|
||||||
|
|
||||||
|
private val clause: Parser<Clause> by ((rule or fact) * -dot)
|
||||||
|
private val clauses: Parser<List<Clause>> by zeroOrMore(clause)
|
||||||
|
|
||||||
|
override val rootParser: Parser<Any> by clauses
|
||||||
|
}
|
23
src/interpreter/SourceFileReader.kt
Normal file
23
src/interpreter/SourceFileReader.kt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package interpreter
|
||||||
|
|
||||||
|
import better_parser.PrologParser
|
||||||
|
|
||||||
|
class SourceFileReader {
|
||||||
|
private val parser = PrologParser()
|
||||||
|
|
||||||
|
fun readFile(filePath: String) {
|
||||||
|
return try {
|
||||||
|
val file = java.io.File(filePath)
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw IllegalArgumentException("File not found: $filePath")
|
||||||
|
}
|
||||||
|
|
||||||
|
val content = file.readText()
|
||||||
|
|
||||||
|
// Parse the content using SimpleSourceParser
|
||||||
|
parser.parse(content)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Error reading file: $filePath", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package prolog
|
package prolog
|
||||||
|
|
||||||
|
import Debug
|
||||||
import prolog.ast.logic.Clause
|
import prolog.ast.logic.Clause
|
||||||
import prolog.ast.logic.Predicate
|
import prolog.ast.logic.Predicate
|
||||||
import prolog.ast.logic.Resolvent
|
import prolog.ast.logic.Resolvent
|
||||||
|
@ -12,13 +13,16 @@ typealias Database = Program
|
||||||
* Prolog Program or database.
|
* Prolog Program or database.
|
||||||
*/
|
*/
|
||||||
object Program: Resolvent {
|
object Program: Resolvent {
|
||||||
private var predicates: Map<Functor, Predicate> = emptyMap()
|
var predicates: Map<Functor, Predicate> = emptyMap()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setup() {
|
private fun setup() {
|
||||||
|
if (Debug.on) {
|
||||||
|
println("Setting up Prolog program...")
|
||||||
|
}
|
||||||
// Initialize the program with built-in predicates
|
// Initialize the program with built-in predicates
|
||||||
load(listOf(
|
load(listOf(
|
||||||
))
|
))
|
||||||
|
|
|
@ -18,7 +18,7 @@ import prolog.logic.unifyLazy
|
||||||
* @see [prolog.ast.terms.Variable]
|
* @see [prolog.ast.terms.Variable]
|
||||||
* @see [Predicate]
|
* @see [Predicate]
|
||||||
*/
|
*/
|
||||||
abstract class Clause(private val head: Head, private val body: Body) : Resolvent {
|
abstract class Clause(val head: Head, val body: Body) : Resolvent {
|
||||||
val functor: Functor = head.functor
|
val functor: Functor = head.functor
|
||||||
|
|
||||||
override fun solve (goal: Goal, subs: Substitutions): Answers = sequence {
|
override fun solve (goal: Goal, subs: Substitutions): Answers = sequence {
|
||||||
|
|
|
@ -39,6 +39,11 @@ class Predicate : Resolvent {
|
||||||
*/
|
*/
|
||||||
fun add(clause: Clause) {
|
fun add(clause: Clause) {
|
||||||
require(clause.functor == functor) { "Clause functor does not match predicate functor" }
|
require(clause.functor == functor) { "Clause functor does not match predicate functor" }
|
||||||
|
|
||||||
|
if (Debug.on) {
|
||||||
|
println("Adding clause $clause to predicate $functor")
|
||||||
|
}
|
||||||
|
|
||||||
clauses.add(clause)
|
clauses.add(clause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package prolog.ast.terms
|
package prolog.ast.terms
|
||||||
|
|
||||||
import prolog.ast.arithmetic.Expression
|
|
||||||
|
|
||||||
typealias Operand = Term
|
typealias Operand = Term
|
||||||
|
|
||||||
abstract class Operator(
|
abstract class Operator(
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Cut() : Atom("!") {
|
||||||
/**
|
/**
|
||||||
* Conjunction (and). True if both Goal1 and Goal2 are true.
|
* Conjunction (and). True if both Goal1 and Goal2 are true.
|
||||||
*/
|
*/
|
||||||
class Conjunction(private val left: LogicOperand, private val right: LogicOperand) :
|
class Conjunction(val left: LogicOperand, private val right: LogicOperand) :
|
||||||
LogicOperator(Atom(","), left, right) {
|
LogicOperator(Atom(","), left, right) {
|
||||||
override fun satisfy(subs: Substitutions): Answers = sequence {
|
override fun satisfy(subs: Substitutions): Answers = sequence {
|
||||||
// Satisfy the left part first, which either succeeds or fails
|
// Satisfy the left part first, which either succeeds or fails
|
||||||
|
|
4
src/repl/Repl.kt
Normal file
4
src/repl/Repl.kt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package repl
|
||||||
|
|
||||||
|
class Repl {
|
||||||
|
}
|
|
@ -16,8 +16,8 @@ import prolog.ast.terms.Term
|
||||||
import prolog.ast.terms.Variable
|
import prolog.ast.terms.Variable
|
||||||
import prolog.logic.equivalent
|
import prolog.logic.equivalent
|
||||||
|
|
||||||
class SimplePrologParserTests {
|
class SimplePrologPrologParserTests {
|
||||||
lateinit var parser: Grammar<Term>
|
private lateinit var parser: Grammar<Term>
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
132
tests/better_parser/SimpleSourcePrologParserTests.kt
Normal file
132
tests/better_parser/SimpleSourcePrologParserTests.kt
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package better_parser
|
||||||
|
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.Grammar
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.parseToEnd
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource
|
||||||
|
import prolog.ast.logic.Clause
|
||||||
|
import prolog.ast.logic.Fact
|
||||||
|
import prolog.ast.logic.Rule
|
||||||
|
import prolog.ast.terms.CompoundTerm
|
||||||
|
import prolog.ast.terms.Structure
|
||||||
|
import prolog.ast.terms.Variable
|
||||||
|
import prolog.builtins.Conjunction
|
||||||
|
import prolog.builtins.Disjunction
|
||||||
|
|
||||||
|
class SimpleSourcePrologParserTests {
|
||||||
|
private lateinit var parser: Grammar<List<Clause>>
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
parser = SimpleSourceParser() as Grammar<List<Clause>>
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = [
|
||||||
|
"john.",
|
||||||
|
"mary.",
|
||||||
|
"jimmy.",
|
||||||
|
"male(john).",
|
||||||
|
"male(jimmy).",
|
||||||
|
"female(mary).",
|
||||||
|
"not(not(true)).",
|
||||||
|
"not(a, not(b, c), d, not(not(a)))."
|
||||||
|
])
|
||||||
|
fun `parse simple fact`(input: String) {
|
||||||
|
val result = parser.parseToEnd(input)
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Expected 1 fact")
|
||||||
|
assertTrue(result[0] is Fact, "Expected a fact")
|
||||||
|
assertEquals(input, "${result[0].toString()}.", "Expected fact to be '$input'")
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = [
|
||||||
|
"john. mary.",
|
||||||
|
"likes(john, mary). likes(mary, john).",
|
||||||
|
"belgium. capital(belgium, brussels).",
|
||||||
|
"plus(1, 2, 3). plus(3, 4, 7).",
|
||||||
|
])
|
||||||
|
fun `parse multiple facts`(input: String) {
|
||||||
|
val result = parser.parseToEnd(input)
|
||||||
|
|
||||||
|
assertEquals(2, result.size, "Expected 2 facts")
|
||||||
|
assertTrue(result[0] is Fact, "Expected a fact")
|
||||||
|
assertTrue(result[1] is Fact, "Expected a fact")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `simplest rule`() {
|
||||||
|
val input = "a :- b."
|
||||||
|
|
||||||
|
val result = parser.parseToEnd(input)
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Expected 1 rule")
|
||||||
|
assertTrue(result[0] is Rule, "Expected a rule")
|
||||||
|
assertEquals("a :- b", result[0].toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = [
|
||||||
|
"parent(X, Y) :- father(X, Y).",
|
||||||
|
"parent(X, Y) :- mother(X, Y)."
|
||||||
|
])
|
||||||
|
fun `parse simple rule`(input: String) {
|
||||||
|
val result = parser.parseToEnd(input)
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Expected 1 rule")
|
||||||
|
assertTrue(result[0] is Rule, "Expected a rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parse rule with very verbose checks`() {
|
||||||
|
val input = "parent(X, Y) :- father(X, Y)."
|
||||||
|
|
||||||
|
val result = parser.parseToEnd(input)
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Expected 1 rule")
|
||||||
|
assertTrue(result[0] is Rule, "Expected a rule")
|
||||||
|
|
||||||
|
val rule = result[0] as Rule
|
||||||
|
|
||||||
|
assertTrue(rule.head is Structure, "Expected head to be a structure")
|
||||||
|
val head = rule.head as Structure
|
||||||
|
assertEquals("parent/2", head.functor, "Expected functor 'parent/2'")
|
||||||
|
assertEquals(Variable("X"), head.arguments[0], "Expected first argument 'X'")
|
||||||
|
assertEquals(Variable("Y"), head.arguments[1], "Expected second argument 'Y'")
|
||||||
|
|
||||||
|
assertTrue(rule.body is Structure, "Expected body to be a structure")
|
||||||
|
val body = rule.body as Structure
|
||||||
|
assertEquals("father/2", body.functor, "Expected functor 'father/2'")
|
||||||
|
assertEquals(Variable("X"), body.arguments[0], "Expected first argument 'X'")
|
||||||
|
assertEquals(Variable("Y"), body.arguments[1], "Expected second argument 'Y'")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parse rule with conjunction`() {
|
||||||
|
val input = "father(X, Y) :- parent(X, Y), male(X)."
|
||||||
|
|
||||||
|
val result = parser.parseToEnd(input)
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Expected 1 rule")
|
||||||
|
assertInstanceOf(Rule::class.java, result[0], "Expected a rule")
|
||||||
|
val rule = result[0] as Rule
|
||||||
|
assertInstanceOf(Conjunction::class.java, rule.body, "Expected body to be a conjunction")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parse rule with nested conjunction`() {
|
||||||
|
val input = "guest(X, Y) :- invited(Y, X), has_time(X), not(sick(Y))."
|
||||||
|
|
||||||
|
val result = parser.parseToEnd(input)
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Expected 1 rule")
|
||||||
|
val rule = result[0] as Rule
|
||||||
|
assertTrue(rule.body is Conjunction, "Expected body to be a conjunction")
|
||||||
|
val conjunction = rule.body as Conjunction
|
||||||
|
assertEquals("invited/2", (conjunction.left as CompoundTerm).functor, "Expected functor 'invited/2'")
|
||||||
|
}
|
||||||
|
}
|
1
tests/better_parser/resources/a.pl
Normal file
1
tests/better_parser/resources/a.pl
Normal file
|
@ -0,0 +1 @@
|
||||||
|
a.
|
1
tests/better_parser/resources/foo.pl
Normal file
1
tests/better_parser/resources/foo.pl
Normal file
|
@ -0,0 +1 @@
|
||||||
|
foo.
|
9
tests/better_parser/resources/parent.pl
Normal file
9
tests/better_parser/resources/parent.pl
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
male(john).
|
||||||
|
male(jimmy).
|
||||||
|
female(mary).
|
||||||
|
parent(john, jimmy).
|
||||||
|
parent(mary, jimmy).
|
||||||
|
father(X, Y) :- parent(X, Y), male(X).
|
||||||
|
mother(X, Y) :- parent(X, Y), female(X).
|
||||||
|
|
||||||
|
kan_goed_koken(miriam).
|
32
tests/interpreter/SourceFileReaderTests.kt
Normal file
32
tests/interpreter/SourceFileReaderTests.kt
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package interpreter
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import prolog.Program
|
||||||
|
|
||||||
|
class SourceFileReaderTests {
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
Program.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun a() {
|
||||||
|
val inputFile = "tests/better_parser/resources/a.pl"
|
||||||
|
val reader = SourceFileReader()
|
||||||
|
|
||||||
|
reader.readFile(inputFile)
|
||||||
|
|
||||||
|
println(Program.predicates)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun foo() {
|
||||||
|
val inputFile = "tests/better_parser/resources/foo.pl"
|
||||||
|
val reader = SourceFileReader()
|
||||||
|
|
||||||
|
reader.readFile(inputFile)
|
||||||
|
|
||||||
|
println(Program.predicates)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import kotlin.test.assertEquals
|
||||||
*
|
*
|
||||||
* These tests are based on the Prolog syntax.
|
* These tests are based on the Prolog syntax.
|
||||||
*/
|
*/
|
||||||
class ScanPrologTests {
|
class ScanPrologParserTests {
|
||||||
@Test
|
@Test
|
||||||
fun scan_simple_atom() {
|
fun scan_simple_atom() {
|
||||||
val tokens = Lexer("atom.").scan()
|
val tokens = Lexer("atom.").scan()
|
Reference in a new issue