IO Operators
This commit is contained in:
parent
b9f419a59d
commit
82a8fccf87
22 changed files with 450 additions and 199 deletions
|
@ -1,3 +0,0 @@
|
||||||
data object Debug {
|
|
||||||
val on: Boolean = true
|
|
||||||
}
|
|
101
src/Main.kt
101
src/Main.kt
|
@ -1,98 +1,15 @@
|
||||||
import better_parser.PrologParser
|
import interpreter.FileLoader
|
||||||
import better_parser.SimpleReplParser
|
import io.Logger
|
||||||
import interpreter.SourceFileReader
|
|
||||||
import prolog.Answer
|
|
||||||
import prolog.Program
|
import prolog.Program
|
||||||
import prolog.ast.logic.Fact
|
import prolog.ast.logic.Clause
|
||||||
import prolog.ast.logic.Rule
|
import repl.Repl
|
||||||
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() {
|
||||||
SourceFileReader().readFile("tests/better_parser/resources/parent.pl")
|
// TODO Make this a command line argument
|
||||||
|
// Turn on debug mode
|
||||||
|
Logger.level = Logger.Level.DEBUG
|
||||||
|
|
||||||
val parser = SimpleReplParser(debug = false)
|
FileLoader().load("tests/parser/resources/parent.pl")
|
||||||
|
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Repl().start()
|
||||||
}
|
}
|
||||||
|
|
39
src/interpreter/FileLoader.kt
Normal file
39
src/interpreter/FileLoader.kt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package interpreter
|
||||||
|
|
||||||
|
import io.Logger
|
||||||
|
import parser.ScriptParser
|
||||||
|
import prolog.Program
|
||||||
|
import prolog.ast.logic.Clause
|
||||||
|
|
||||||
|
class FileLoader {
|
||||||
|
private val parser = ScriptParser()
|
||||||
|
|
||||||
|
fun load(filePath: String): () -> Unit {
|
||||||
|
val input = readFile(filePath)
|
||||||
|
|
||||||
|
Logger.debug("Parsing content of $filePath")
|
||||||
|
val clauses: List<Clause> = parser.parse(input)
|
||||||
|
|
||||||
|
Program.load(clauses)
|
||||||
|
|
||||||
|
// TODO Pass next commands to execute
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readFile(filePath: String): String {
|
||||||
|
try {
|
||||||
|
Logger.debug("Reading $filePath")
|
||||||
|
val file = java.io.File(filePath)
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
Logger.error("File $filePath does not exist")
|
||||||
|
throw IllegalArgumentException("File not found: $filePath")
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.readText()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error("Error reading file: $filePath")
|
||||||
|
throw RuntimeException("Error reading file: $filePath", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
src/interpreter/Preprocessor.kt
Normal file
4
src/interpreter/Preprocessor.kt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package interpreter
|
||||||
|
|
||||||
|
class Preprocessor {
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
10
src/io/IoHandler.kt
Normal file
10
src/io/IoHandler.kt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package io
|
||||||
|
|
||||||
|
interface IoHandler {
|
||||||
|
fun prompt(
|
||||||
|
message: String,
|
||||||
|
hint: () -> String = { "Please enter a valid input." }
|
||||||
|
): String
|
||||||
|
|
||||||
|
fun say(message: String)
|
||||||
|
}
|
24
src/io/Logger.kt
Normal file
24
src/io/Logger.kt
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package io
|
||||||
|
|
||||||
|
object Logger {
|
||||||
|
enum class Level {
|
||||||
|
DEBUG, INFO, WARN, ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
val defaultLevel: Level = Level.WARN
|
||||||
|
var level: Level = defaultLevel
|
||||||
|
|
||||||
|
private val io: IoHandler = Terminal()
|
||||||
|
|
||||||
|
fun log(message: String, messageLevel: Level = defaultLevel) {
|
||||||
|
if (level <= messageLevel) {
|
||||||
|
val text = "$messageLevel: $message\n"
|
||||||
|
io.say(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun debug(message: String) = log(message, Level.DEBUG)
|
||||||
|
fun info(message: String) = log(message, Level.INFO)
|
||||||
|
fun warn(message: String) = log(message, Level.WARN)
|
||||||
|
fun error(message: String) = log(message, Level.ERROR)
|
||||||
|
}
|
63
src/io/Terminal.kt
Normal file
63
src/io/Terminal.kt
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package io
|
||||||
|
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.BufferedWriter
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles input and output from a terminal.
|
||||||
|
*/
|
||||||
|
class Terminal(
|
||||||
|
inputStream: InputStream = System.`in`,
|
||||||
|
outputStream: OutputStream = System.`out`,
|
||||||
|
errorStream: OutputStream = System.err
|
||||||
|
) : IoHandler {
|
||||||
|
val input: BufferedReader = inputStream.bufferedReader()
|
||||||
|
val output: BufferedWriter = outputStream.bufferedWriter()
|
||||||
|
val error: BufferedWriter = errorStream.bufferedWriter()
|
||||||
|
|
||||||
|
override fun prompt(
|
||||||
|
message: String,
|
||||||
|
hint: () -> String
|
||||||
|
): String {
|
||||||
|
say("$message ")
|
||||||
|
var input: String = readLine()
|
||||||
|
while (input.isBlank()) {
|
||||||
|
say(hint(), error)
|
||||||
|
input = readLine()
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun say(message: String) {
|
||||||
|
output.write(message)
|
||||||
|
output.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun say(message: String, writer: BufferedWriter = output) {
|
||||||
|
writer.write(message)
|
||||||
|
writer.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a line from the input stream.
|
||||||
|
*/
|
||||||
|
fun readLine(reader: BufferedReader = input): String {
|
||||||
|
val line = reader.readLine()
|
||||||
|
if (line == null) {
|
||||||
|
Logger.info("End of stream reached")
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanup() {
|
||||||
|
Logger.info("Closing terminal streams")
|
||||||
|
input.close()
|
||||||
|
output.close()
|
||||||
|
error.close()
|
||||||
|
System.exit(0)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.Grammar
|
||||||
|
import com.github.h0tk3y.betterParse.grammar.parseToEnd
|
||||||
|
import parser.grammars.QueryGrammar
|
||||||
import prolog.builtins.Query
|
import prolog.builtins.Query
|
||||||
|
|
||||||
class ReplParser: Parser {
|
class ReplParser : Parser {
|
||||||
override fun parse(input: String): Query {
|
private val grammar: Grammar<Query> = QueryGrammar() as Grammar<Query>
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
override fun parse(input: String): Query = grammar.parseToEnd(input)
|
||||||
}
|
}
|
16
src/parser/grammars/QueryGrammar.kt
Normal file
16
src/parser/grammars/QueryGrammar.kt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package parser.grammars
|
||||||
|
|
||||||
|
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.parser.Parser
|
||||||
|
import prolog.ast.logic.LogicOperand
|
||||||
|
import prolog.builtins.Query
|
||||||
|
|
||||||
|
class QueryGrammar : TermsGrammar() {
|
||||||
|
protected val query: Parser<Query> by (body * -dot) use {
|
||||||
|
Query(this as LogicOperand)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val rootParser: Parser<Any> by query
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package prolog
|
package prolog
|
||||||
|
|
||||||
import Debug
|
import io.Logger
|
||||||
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
|
||||||
|
@ -16,13 +16,14 @@ object Program: Resolvent {
|
||||||
var predicates: Map<Functor, Predicate> = emptyMap()
|
var predicates: Map<Functor, Predicate> = emptyMap()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
Logger.debug("Initializing ${this::class.java.simpleName}")
|
||||||
setup()
|
setup()
|
||||||
|
Logger.debug("Initialization of ${this::class.java.simpleName} complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setup() {
|
private fun setup() {
|
||||||
if (Debug.on) {
|
Logger.debug("Setting up ${this::class.java.simpleName}")
|
||||||
println("Setting up Prolog program...")
|
|
||||||
}
|
|
||||||
// Initialize the program with built-in predicates
|
// Initialize the program with built-in predicates
|
||||||
load(listOf(
|
load(listOf(
|
||||||
))
|
))
|
||||||
|
@ -35,6 +36,7 @@ object Program: Resolvent {
|
||||||
fun query(goal: Goal): Answers = solve(goal, emptyMap())
|
fun query(goal: Goal): Answers = solve(goal, emptyMap())
|
||||||
|
|
||||||
override fun solve(goal: Goal, subs: Substitutions): Answers {
|
override fun solve(goal: Goal, subs: Substitutions): Answers {
|
||||||
|
Logger.debug("Solving goal $goal")
|
||||||
val functor = goal.functor
|
val functor = goal.functor
|
||||||
// If the predicate does not exist, return false
|
// If the predicate does not exist, return false
|
||||||
val predicate = predicates[functor] ?: return emptySequence()
|
val predicate = predicates[functor] ?: return emptySequence()
|
||||||
|
@ -57,6 +59,8 @@ object Program: Resolvent {
|
||||||
// If the predicate does not exist, create a new one
|
// If the predicate does not exist, create a new one
|
||||||
predicates += Pair(functor, Predicate(listOf(clause)))
|
predicates += Pair(functor, Predicate(listOf(clause)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.debug("Loaded clause $clause into predicate $functor")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +78,7 @@ object Program: Resolvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
|
Logger.debug("Clearing ${this::class.java.simpleName}")
|
||||||
predicates = emptyMap()
|
predicates = emptyMap()
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ class Predicate : Resolvent {
|
||||||
*/
|
*/
|
||||||
constructor(clauses: List<Clause>) {
|
constructor(clauses: List<Clause>) {
|
||||||
this.functor = clauses.first().functor
|
this.functor = clauses.first().functor
|
||||||
|
|
||||||
require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" }
|
require(clauses.all { it.functor == functor }) { "All clauses must have the same functor" }
|
||||||
this.clauses = clauses.toMutableList()
|
this.clauses = clauses.toMutableList()
|
||||||
}
|
}
|
||||||
|
@ -39,11 +38,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ abstract class Operator(
|
||||||
private val symbol: Atom,
|
private val symbol: Atom,
|
||||||
private val leftOperand: Operand? = null,
|
private val leftOperand: Operand? = null,
|
||||||
private val rightOperand: Operand
|
private val rightOperand: Operand
|
||||||
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)) {
|
) : CompoundTerm(symbol, listOfNotNull(leftOperand, rightOperand)), Term {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return when (leftOperand) {
|
return when (leftOperand) {
|
||||||
null -> "${symbol.name} $rightOperand"
|
null -> "${symbol.name} $rightOperand"
|
||||||
|
|
|
@ -22,4 +22,15 @@ open class Structure(val name: Atom, var arguments: List<Argument>) : Goal(), He
|
||||||
else -> "${name.name}(${arguments.joinToString(", ")})"
|
else -> "${name.name}(${arguments.joinToString(", ")})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Structure) return false
|
||||||
|
if (functor != other.functor) return false
|
||||||
|
return arguments.zip(other.arguments).all { (a, b) -> a == b }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return javaClass.hashCode()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
package prolog.builtins
|
package prolog.builtins
|
||||||
|
|
||||||
|
import io.Logger
|
||||||
|
import io.Terminal
|
||||||
|
import parser.ReplParser
|
||||||
import prolog.Answers
|
import prolog.Answers
|
||||||
import prolog.Substitutions
|
import prolog.Substitutions
|
||||||
import prolog.ast.logic.Satisfiable
|
import prolog.ast.logic.Satisfiable
|
||||||
|
@ -7,13 +10,60 @@ import prolog.ast.terms.Atom
|
||||||
import prolog.ast.terms.Operator
|
import prolog.ast.terms.Operator
|
||||||
import prolog.ast.terms.Term
|
import prolog.ast.terms.Term
|
||||||
import prolog.logic.applySubstitution
|
import prolog.logic.applySubstitution
|
||||||
|
import prolog.logic.unifyLazy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write [Term] to the current output, using brackets and operators where appropriate.
|
||||||
|
*/
|
||||||
class Write(private val term: Term) : Operator(Atom("write"), null, term), Satisfiable {
|
class Write(private val term: Term) : Operator(Atom("write"), null, term), Satisfiable {
|
||||||
override fun satisfy(subs: Substitutions): Answers {
|
override fun satisfy(subs: Substitutions): Answers {
|
||||||
val t = applySubstitution(term, subs)
|
val t = applySubstitution(term, subs)
|
||||||
|
|
||||||
println(t.toString())
|
Terminal().say(t.toString())
|
||||||
|
|
||||||
return sequenceOf(Result.success(emptyMap()))
|
return sequenceOf(Result.success(emptyMap()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a newline character to the current output stream.
|
||||||
|
*/
|
||||||
|
object Nl : Atom("nl"), Satisfiable {
|
||||||
|
override fun satisfy(subs: Substitutions): Answers {
|
||||||
|
Terminal().say("\n")
|
||||||
|
return sequenceOf(Result.success(emptyMap()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next Prolog term from the current input stream and unify it with [Term].
|
||||||
|
*
|
||||||
|
* On reaching end-of-file, [Term] is unified with the [Atom] `end_of_file`.
|
||||||
|
*/
|
||||||
|
class Read(private val term: Term) : Operator(Atom("read"), null, term), Satisfiable {
|
||||||
|
private val io = Terminal()
|
||||||
|
private val parser = ReplParser()
|
||||||
|
|
||||||
|
private fun readTerm(): Term {
|
||||||
|
val input = io.readLine()
|
||||||
|
Logger.debug("Read input: $input")
|
||||||
|
|
||||||
|
return when (input) {
|
||||||
|
"end_of_file" -> Atom("end_of_file")
|
||||||
|
else -> {
|
||||||
|
val out = parser.parse(input).query
|
||||||
|
Logger.debug("Parsed input: $out")
|
||||||
|
out as? Term ?: throw IllegalArgumentException("Expected a term, but got: $out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun satisfy(subs: Substitutions): Answers = sequence {
|
||||||
|
val t1 = applySubstitution(term, subs)
|
||||||
|
|
||||||
|
val t2 = readTerm()
|
||||||
|
Logger.debug("Read term: $t2")
|
||||||
|
|
||||||
|
yieldAll(unifyLazy(t1, t2, subs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,6 @@ import prolog.ast.logic.LogicOperand
|
||||||
import prolog.ast.terms.Atom
|
import prolog.ast.terms.Atom
|
||||||
import prolog.ast.logic.LogicOperator
|
import prolog.ast.logic.LogicOperator
|
||||||
|
|
||||||
class Query(private val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) {
|
class Query(val query: LogicOperand) : LogicOperator(Atom("?-"), null, query) {
|
||||||
override fun satisfy(subs: Substitutions): Answers = query.satisfy(subs)
|
override fun satisfy(subs: Substitutions): Answers = query.satisfy(subs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,10 @@ fun applySubstitution(term: Term, subs: Substitutions): Term = when {
|
||||||
val structure = term as Structure
|
val structure = term as Structure
|
||||||
Structure(structure.name, structure.arguments.map { applySubstitution(it, subs) })
|
Structure(structure.name, structure.arguments.map { applySubstitution(it, subs) })
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> term
|
else -> term
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Combine with the other applySubstitution function
|
//TODO Combine with the other applySubstitution function
|
||||||
fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when {
|
fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when {
|
||||||
variable(expr, subs) -> applySubstitution(expr as Term, subs) as Expression
|
variable(expr, subs) -> applySubstitution(expr as Term, subs) as Expression
|
||||||
|
@ -29,6 +31,7 @@ fun applySubstitution(expr: Expression, subs: Substitutions): Expression = when
|
||||||
expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
|
expr.arguments = expr.arguments.map { applySubstitution(it, subs) }
|
||||||
expr
|
expr
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> expr
|
else -> expr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +43,7 @@ private fun occurs(variable: Variable, term: Term, subs: Substitutions): Boolean
|
||||||
val structure = term as Structure
|
val structure = term as Structure
|
||||||
structure.arguments.any { occurs(variable, it, subs) }
|
structure.arguments.any { occurs(variable, it, subs) }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +60,14 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
|
||||||
yield(Result.success(subs + (variable to t2)))
|
yield(Result.success(subs + (variable to t2)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable(t2, subs) -> {
|
variable(t2, subs) -> {
|
||||||
val variable = t2 as Variable
|
val variable = t2 as Variable
|
||||||
if (!occurs(variable, t1, subs)) {
|
if (!occurs(variable, t1, subs)) {
|
||||||
yield(Result.success(subs + (variable to t1)))
|
yield(Result.success(subs + (variable to t1)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compound(t1, subs) && compound(t2, subs) -> {
|
compound(t1, subs) && compound(t2, subs) -> {
|
||||||
val structure1 = t1 as Structure
|
val structure1 = t1 as Structure
|
||||||
val structure2 = t2 as Structure
|
val structure2 = t2 as Structure
|
||||||
|
@ -75,14 +81,17 @@ fun unifyLazy(term1: Term, term2: Term, subs: Substitutions): Answers = sequence
|
||||||
}
|
}
|
||||||
// Combine the results of all unifications
|
// Combine the results of all unifications
|
||||||
val combinedResults = results.reduce { acc, result ->
|
val combinedResults = results.reduce { acc, result ->
|
||||||
acc.flatMap { a -> result.map { b ->
|
acc.flatMap { a ->
|
||||||
|
result.map { b ->
|
||||||
if (a.isSuccess && b.isSuccess) a.getOrThrow() + b.getOrThrow() else emptyMap()
|
if (a.isSuccess && b.isSuccess) a.getOrThrow() + b.getOrThrow() else emptyMap()
|
||||||
} }.map { Result.success(it) }
|
}
|
||||||
|
}.map { Result.success(it) }
|
||||||
}
|
}
|
||||||
yieldAll(combinedResults)
|
yieldAll(combinedResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,6 +138,7 @@ fun compare(term1: Term, term2: Term, subs: Substitutions): Int {
|
||||||
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Number -> {
|
is Number -> {
|
||||||
when (t2) {
|
when (t2) {
|
||||||
is Variable -> 1
|
is Variable -> 1
|
||||||
|
@ -139,6 +149,7 @@ fun compare(term1: Term, term2: Term, subs: Substitutions): Int {
|
||||||
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Atom -> {
|
is Atom -> {
|
||||||
when (t2) {
|
when (t2) {
|
||||||
is Variable -> 1
|
is Variable -> 1
|
||||||
|
@ -148,6 +159,7 @@ fun compare(term1: Term, term2: Term, subs: Substitutions): Int {
|
||||||
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Structure -> {
|
is Structure -> {
|
||||||
when (t2) {
|
when (t2) {
|
||||||
is Variable -> 1
|
is Variable -> 1
|
||||||
|
@ -164,9 +176,11 @@ fun compare(term1: Term, term2: Term, subs: Substitutions): Int {
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
else -> throw IllegalArgumentException("Cannot compare $t1 with $t2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,90 @@
|
||||||
package repl
|
package repl
|
||||||
|
|
||||||
|
import io.Logger
|
||||||
|
import io.Terminal
|
||||||
|
import parser.ReplParser
|
||||||
|
import prolog.Answer
|
||||||
|
import prolog.Answers
|
||||||
|
|
||||||
class Repl {
|
class Repl {
|
||||||
|
private val io = Terminal()
|
||||||
|
private val parser = ReplParser()
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
io.say("Prolog REPL. Type '^D' to quit.\n")
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
printAnswers(query())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error("Error parsing REPL: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun query(): Answers {
|
||||||
|
val queryString = io.prompt("?-", { "" })
|
||||||
|
val query = parser.parse(queryString)
|
||||||
|
return query.satisfy(emptyMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printAnswers(answers: Answers) {
|
||||||
|
val knownCommands = setOf(";", "a", ".", "h")
|
||||||
|
|
||||||
|
if (answers.none()) {
|
||||||
|
io.say("false.")
|
||||||
|
} else {
|
||||||
|
val iterator = answers.iterator()
|
||||||
|
var previous = iterator.next()
|
||||||
|
io.say(prettyPrint(previous))
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
var command = io.prompt("")
|
||||||
|
|
||||||
|
while (command !in knownCommands) {
|
||||||
|
io.say("Unknown action: $command (h for help)\n")
|
||||||
|
command = io.prompt("Action?")
|
||||||
|
}
|
||||||
|
|
||||||
|
when (command) {
|
||||||
|
";" -> previous = iterator.next()
|
||||||
|
"a" -> return
|
||||||
|
"." -> return
|
||||||
|
"h" -> {
|
||||||
|
help()
|
||||||
|
io.say("Action?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.say(prettyPrint(previous))
|
||||||
|
}
|
||||||
|
|
||||||
|
io.say("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun help(): String {
|
||||||
|
io.say("Commands:\n")
|
||||||
|
io.say(" ; find next solution\n")
|
||||||
|
io.say(" a abort\n")
|
||||||
|
io.say(" . end query\n")
|
||||||
|
io.say(" h help\n")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun prettyPrint(result: Answer): String {
|
||||||
|
result.fold(
|
||||||
|
onSuccess = {
|
||||||
|
val subs = result.getOrNull()!!
|
||||||
|
if (subs.isEmpty()) {
|
||||||
|
return "true."
|
||||||
|
}
|
||||||
|
return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
val text = "Failure: ${it.message}"
|
||||||
|
Logger.warn(text)
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -12,8 +12,8 @@ class SourceFileReaderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun a() {
|
fun a() {
|
||||||
val inputFile = "tests/better_parser/resources/a.pl"
|
val inputFile = "tests/parser/resources/a.pl"
|
||||||
val reader = SourceFileReader()
|
val reader = FileLoader()
|
||||||
|
|
||||||
reader.readFile(inputFile)
|
reader.readFile(inputFile)
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ class SourceFileReaderTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun foo() {
|
fun foo() {
|
||||||
val inputFile = "tests/better_parser/resources/foo.pl"
|
val inputFile = "tests/parser/resources/foo.pl"
|
||||||
val reader = SourceFileReader()
|
val reader = FileLoader()
|
||||||
|
|
||||||
reader.readFile(inputFile)
|
reader.readFile(inputFile)
|
||||||
|
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package parser
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
|
||||||
import org.junit.jupiter.api.Assertions.assertInstanceOf
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import prolog.ast.logic.Fact
|
|
||||||
import prolog.ast.terms.Atom
|
|
||||||
import prolog.logic.equivalent
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class ScriptParserTests {
|
|
||||||
private lateinit var parser: ScriptParser
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setup() {
|
|
||||||
parser = ScriptParser()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `parse single atom`() {
|
|
||||||
val input = """
|
|
||||||
a.
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
val result = parser.parse(input)
|
|
||||||
val expected = Fact(Atom("a"))
|
|
||||||
|
|
||||||
assertEquals(1, result.size, "Should return one result")
|
|
||||||
assertInstanceOf(Fact::class.java, result[0], "Result should be a fact")
|
|
||||||
assertTrue(
|
|
||||||
equivalent(expected.head, result[0].head, emptyMap()),
|
|
||||||
"Expected fact 'a'"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,8 +5,3 @@ parent(john, jimmy).
|
||||||
parent(mary, jimmy).
|
parent(mary, jimmy).
|
||||||
father(X, Y) :- parent(X, Y), male(X).
|
father(X, Y) :- parent(X, Y), male(X).
|
||||||
mother(X, Y) :- parent(X, Y), female(X).
|
mother(X, Y) :- parent(X, Y), female(X).
|
||||||
|
|
||||||
:- write(hello),
|
|
||||||
nl.
|
|
||||||
|
|
||||||
:- write(hello2).
|
|
|
@ -1,8 +1,10 @@
|
||||||
package prolog.builtins
|
package prolog.builtins
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertInstanceOf
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.params.ParameterizedTest
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
import org.junit.jupiter.params.provider.ValueSource
|
import org.junit.jupiter.params.provider.ValueSource
|
||||||
|
@ -13,6 +15,7 @@ import java.io.ByteArrayOutputStream
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import prolog.ast.arithmetic.Integer
|
import prolog.ast.arithmetic.Integer
|
||||||
import prolog.ast.terms.Variable
|
import prolog.ast.terms.Variable
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
|
||||||
class IoOperatorsTests {
|
class IoOperatorsTests {
|
||||||
private var outStream = ByteArrayOutputStream()
|
private var outStream = ByteArrayOutputStream()
|
||||||
|
@ -44,7 +47,7 @@ class IoOperatorsTests {
|
||||||
|
|
||||||
assertEquals(1, result.size, "Should return one result")
|
assertEquals(1, result.size, "Should return one result")
|
||||||
assertTrue(result[0].isSuccess, "Result should be successful")
|
assertTrue(result[0].isSuccess, "Result should be successful")
|
||||||
assertEquals(name, outStream.toString().trim(), "Output should match the atom")
|
assertEquals(name, outStream.toString(), "Output should match the atom")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -79,7 +82,86 @@ class IoOperatorsTests {
|
||||||
assertEquals(1, result.size, "Should return one result")
|
assertEquals(1, result.size, "Should return one result")
|
||||||
assertTrue(result[0].isSuccess, "Result should be successful")
|
assertTrue(result[0].isSuccess, "Result should be successful")
|
||||||
|
|
||||||
val output = outStream.toString().trim()
|
val output = outStream.toString()
|
||||||
assertTrue(output == expected1 || output == expected2, "Output should match the arithmetic expression")
|
assertTrue(output == expected1 || output == expected2, "Output should match the arithmetic expression")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `write nl`() {
|
||||||
|
val nl = Nl
|
||||||
|
|
||||||
|
val result = nl.satisfy(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Should return one result")
|
||||||
|
assertTrue(result[0].isSuccess, "Result should be successful")
|
||||||
|
assertTrue(outStream.toString().contains("\n"), "Output should contain a newline")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `read term`() {
|
||||||
|
val inputStream = ByteArrayInputStream("hello.".toByteArray())
|
||||||
|
System.setIn(inputStream)
|
||||||
|
|
||||||
|
val read = Read(Variable("X"))
|
||||||
|
|
||||||
|
val result = read.satisfy(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Should return one result")
|
||||||
|
assertTrue(result[0].isSuccess, "Result should be successful")
|
||||||
|
val answer = result[0].getOrNull()!!
|
||||||
|
assertTrue(answer.containsKey(Variable("X")), "Result should be successful")
|
||||||
|
assertInstanceOf(Atom::class.java, answer[Variable("X")], "Output should be an atom")
|
||||||
|
assertEquals(Atom("hello"), answer[Variable("X")], "Output should match the read term")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `read between(1, 2, 3)`() {
|
||||||
|
val inputStream = ByteArrayInputStream("between(1, 2, 3).".toByteArray())
|
||||||
|
System.setIn(inputStream)
|
||||||
|
|
||||||
|
val read = Read(Variable("X"))
|
||||||
|
|
||||||
|
val result = read.satisfy(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Should return one result")
|
||||||
|
assertTrue(result[0].isSuccess, "Result should be successful")
|
||||||
|
val answer = result[0].getOrNull()!!
|
||||||
|
assertTrue(answer.containsKey(Variable("X")), "Result should be successful")
|
||||||
|
assertInstanceOf(CompoundTerm::class.java, answer[Variable("X")], "Output should be a compound term")
|
||||||
|
assertEquals(
|
||||||
|
CompoundTerm(Atom("between"), listOf(Integer(1), Integer(2), Integer(3))),
|
||||||
|
answer[Variable("X")],
|
||||||
|
"Output should match the read term"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `read foo(a, X, b, Y, c, Z)`() {
|
||||||
|
val inputStream = ByteArrayInputStream("foo(A, x, B, y, C, z).".toByteArray())
|
||||||
|
System.setIn(inputStream)
|
||||||
|
|
||||||
|
val read = Read(CompoundTerm(Atom("foo"), listOf(
|
||||||
|
Atom("a"),
|
||||||
|
Variable("X"),
|
||||||
|
Atom("b"),
|
||||||
|
Variable("Y"),
|
||||||
|
Atom("c"),
|
||||||
|
Variable("Z")
|
||||||
|
)))
|
||||||
|
|
||||||
|
val result = read.satisfy(emptyMap()).toList()
|
||||||
|
|
||||||
|
assertEquals(1, result.size, "Should return one result")
|
||||||
|
assertTrue(result[0].isSuccess, "Result should be successful")
|
||||||
|
|
||||||
|
val answer = result[0].getOrNull()!!
|
||||||
|
|
||||||
|
assertTrue(answer.containsKey(Variable("X")), "Result should be successful")
|
||||||
|
assertTrue(answer.containsKey(Variable("Y")), "Result should be successful")
|
||||||
|
assertTrue(answer.containsKey(Variable("Z")), "Result should be successful")
|
||||||
|
|
||||||
|
assertEquals(Atom("x"), answer[Variable("X")], "Output should match the read term")
|
||||||
|
assertEquals(Atom("y"), answer[Variable("Y")], "Output should match the read term")
|
||||||
|
assertEquals(Atom("z"), answer[Variable("Z")], "Output should match the read term")
|
||||||
|
}
|
||||||
}
|
}
|
Reference in a new issue