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 | ||||
| } | ||||
							
								
								
									
										101
									
								
								src/Main.kt
									
										
									
									
									
								
							
							
						
						
									
										101
									
								
								src/Main.kt
									
										
									
									
									
								
							|  | @ -1,9 +1,98 @@ | |||
| fun main() { | ||||
|     for (i in 1..10) { | ||||
|         println("Hello, Kotlin Command Line Utility!") | ||||
|     } | ||||
| 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 testMe(): String { | ||||
|     return "Hello, Kotlin Command Line Utility!" | ||||
| 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() { | ||||
|     SourceFileReader().readFile("tests/better_parser/resources/parent.pl") | ||||
| 
 | ||||
|     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}") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										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.literalToken | ||||
| import com.github.h0tk3y.betterParse.lexer.regexToken | ||||
| import com.github.h0tk3y.betterParse.lexer.token | ||||
| import com.github.h0tk3y.betterParse.parser.Parser | ||||
| import prolog.ast.arithmetic.Float | ||||
| import prolog.ast.arithmetic.Integer | ||||
|  | @ -16,28 +17,32 @@ import prolog.ast.terms.Variable | |||
| 
 | ||||
| open class SimplePrologParser : Grammar<Any>() { | ||||
|     // Prolog tokens | ||||
|     private 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 nameToken: Token by regexToken("[a-z][a-zA-Z0-9_]*") | ||||
|     protected val variableToken: Token by regexToken("[A-Z][a-zA-Z0-9_]*") | ||||
| 
 | ||||
|     // Arithmetic tokens | ||||
|     private val floatToken: Token by regexToken("-?[1-9][0-9]*\\.[0-9]+") | ||||
|     private val integerToken: Token by regexToken("-?([1-9][0-9]*|0)") | ||||
| 
 | ||||
|     // Special tokens | ||||
|     private val comma: Token by literalToken(",") | ||||
|     private val leftParenthesis: Token by literalToken("(") | ||||
|     private val rightParenthesis: Token by literalToken(")") | ||||
|     protected val neck by literalToken(":-") | ||||
|     protected val comma: Token by literalToken(",") | ||||
|     protected val leftParenthesis: Token by literalToken("(") | ||||
|     protected val rightParenthesis: Token by literalToken(")") | ||||
|     protected val dot by literalToken(".") | ||||
| 
 | ||||
|     // Ignored tokens | ||||
|     private val whitespace: Token by regexToken("\\s+", ignore = true) | ||||
|     private val singleLineComment: Token by regexToken("%[^\\n]*", ignore = true) | ||||
|     private val multiLineComment: Token by regexToken("/\\*.*?\\*/", ignore = true) | ||||
|     protected val whitespace: Token by regexToken("\\s+", ignore = true) | ||||
|     protected val singleLineComment: Token by regexToken("%[^\\n]*", 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 | ||||
|     private val atomParser: Parser<Atom> by nameToken use { Atom(text) } | ||||
|     private val variableParser: Parser<Variable> by variableToken use { Variable(text) } | ||||
|     private val structureParser: Parser<Structure> by (atomParser and skip(leftParenthesis) and separated( | ||||
|         parser(this::termParser), | ||||
|     protected val variable: Parser<Variable> by variableToken use { Variable(text) } | ||||
|     protected val atom: Parser<Atom> by nameToken use { Atom(text) } | ||||
|     protected val compound: Parser<Structure> by (atom and skip(leftParenthesis) and separated( | ||||
|         parser(::term), | ||||
|         comma, | ||||
|         acceptZero = true | ||||
|     ) and skip(rightParenthesis)) use { | ||||
|  | @ -45,17 +50,18 @@ open class SimplePrologParser : Grammar<Any>() { | |||
|     } | ||||
| 
 | ||||
|     // Arithmetic parsers | ||||
|     private val integerParser: Parser<Integer> by integerToken use { Integer(text.toInt()) } | ||||
|     private val floatParser: Parser<Float> by floatToken use { | ||||
|     private val int: Parser<Integer> by integerToken use { Integer(text.toInt()) } | ||||
|     private val float: Parser<Float> by floatToken use { | ||||
|         Float(text.toFloat()) | ||||
|     } | ||||
| 
 | ||||
|     private val termParser: Parser<Term> by (floatParser | ||||
|             or integerParser | ||||
|             or variableParser | ||||
|             or structureParser | ||||
|             or atomParser | ||||
|     protected val term: Parser<Term> by (dummy | ||||
|             or float | ||||
|             or int | ||||
|             or variable | ||||
|             or compound | ||||
|             or atom | ||||
|     ) 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 | ||||
| 
 | ||||
| import Debug | ||||
| import prolog.ast.logic.Clause | ||||
| import prolog.ast.logic.Predicate | ||||
| import prolog.ast.logic.Resolvent | ||||
|  | @ -12,13 +13,16 @@ typealias Database = Program | |||
|  * Prolog Program or database. | ||||
|  */ | ||||
| object Program: Resolvent { | ||||
|     private var predicates: Map<Functor, Predicate> = emptyMap() | ||||
|     var predicates: Map<Functor, Predicate> = emptyMap() | ||||
| 
 | ||||
|     init { | ||||
|         setup() | ||||
|     } | ||||
| 
 | ||||
|     private fun setup() { | ||||
|         if (Debug.on) { | ||||
|             println("Setting up Prolog program...") | ||||
|         } | ||||
|         // Initialize the program with built-in predicates | ||||
|         load(listOf( | ||||
|         )) | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ import prolog.logic.unifyLazy | |||
|  * @see [prolog.ast.terms.Variable] | ||||
|  * @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 | ||||
| 
 | ||||
|     override fun solve (goal: Goal, subs: Substitutions): Answers = sequence { | ||||
|  |  | |||
|  | @ -39,6 +39,11 @@ class Predicate : Resolvent { | |||
|      */ | ||||
|     fun add(clause: Clause) { | ||||
|         require(clause.functor == functor) { "Clause functor does not match predicate functor" } | ||||
| 
 | ||||
|         if (Debug.on) { | ||||
|             println("Adding clause $clause to predicate $functor") | ||||
|         } | ||||
| 
 | ||||
|         clauses.add(clause) | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,5 @@ | |||
| package prolog.ast.terms | ||||
| 
 | ||||
| import prolog.ast.arithmetic.Expression | ||||
| 
 | ||||
| typealias Operand = Term | ||||
| 
 | ||||
| abstract class Operator( | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ class Cut() : Atom("!") { | |||
| /** | ||||
|  * 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) { | ||||
|     override fun satisfy(subs: Substitutions): Answers = sequence { | ||||
|         // 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.logic.equivalent | ||||
| 
 | ||||
| class SimplePrologParserTests { | ||||
|     lateinit var parser: Grammar<Term> | ||||
| class SimplePrologPrologParserTests { | ||||
|     private lateinit var parser: Grammar<Term> | ||||
| 
 | ||||
|     @BeforeEach | ||||
|     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. | ||||
|  */ | ||||
| class ScanPrologTests { | ||||
| class ScanPrologParserTests { | ||||
|     @Test | ||||
|     fun scan_simple_atom() { | ||||
|         val tokens = Lexer("atom.").scan() | ||||
		Reference in a new issue