feat: Atom parsing

This commit is contained in:
Tibo De Peuter 2025-04-16 14:04:39 +02:00
parent bd5c825ca2
commit e749f8c6cb
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
8 changed files with 212 additions and 11 deletions

View file

@ -1,9 +1,7 @@
package lexer package lexer
import java.util.LinkedList
class Lexer(private val source: String) { class Lexer(private val source: String) {
private var tokens: List<Token> = LinkedList() private var tokens: List<Token> = emptyList()
private val position = LexerPosition(0, 0, -1) private val position = LexerPosition(0, 0, -1)
/** /**
@ -14,8 +12,8 @@ class Lexer(private val source: String) {
while (hasNext()) { while (hasNext()) {
val char: Char = peek() val char: Char = peek()
tokens += when { tokens += when {
char == '(' -> scanSymbol(TokenType.LEFT_PARENTHESES) char == '(' -> scanSymbol(TokenType.LEFT_PARENTHESIS)
char == ')' -> scanSymbol(TokenType.RIGHT_PARENTHESES) char == ')' -> scanSymbol(TokenType.RIGHT_PARENTHESIS)
char == '.' -> scanSymbol(TokenType.DOT) char == '.' -> scanSymbol(TokenType.DOT)
char == '"' -> scanQuotedString() char == '"' -> scanQuotedString()
char == '%' -> { scanComment(); continue } char == '%' -> { scanComment(); continue }

View file

@ -2,11 +2,10 @@ package lexer
enum class TokenType { enum class TokenType {
ALPHANUMERIC, ALPHANUMERIC,
// TODO Replace with SMALL_LETTER, CAPITAL_LETTER, DIGIT, HEX_DIGIT, ... ?
LEFT_PARENTHESES, RIGHT_PARENTHESES, LEFT_PARENTHESIS, RIGHT_PARENTHESIS,
DOT, DOT,
// Operators
EOF EOF
} }

116
src/parser/Parser.kt Normal file
View file

@ -0,0 +1,116 @@
package parser
import lexer.Token
import lexer.TokenType
import prolog.ast.terms.Atom
import prolog.ast.terms.Term
class Parser(private val tokens: List<Token>) {
private var position: Int = 0
fun parse(): List<Term> {
val terms = mutableListOf<Term>()
// TODO
while (hasNext()) {
terms.add(parseTerm())
}
return terms
}
/**
* Matches the current token with any of the expected types.
*
* @param types The list of expected token types.
* @return True if the current token matches any of the expected types, false otherwise.
*/
private fun match(types: List<TokenType>): Boolean {
for (type in types) {
if (check(type)) {
next()
return true
}
}
return false
}
/**
* Checks if the current token matches the expected type.
*/
private fun check(type: TokenType): Boolean {
return hasNext() && peek().type == type
}
private fun hasNext(): Boolean {
// Check if the position is within the tokens list
// TODO Check for EOF instead?
return position < tokens.size
}
private fun peek(): Token {
// Peek should only be called if there is a next token
if (!hasNext()) {
throw Error("Unexpected end of input")
}
return tokens[position]
}
private fun next(): Token {
val token = peek()
position++
return token
}
private fun previous(): Token {
// Previous should only be called if there is a previous token
if (position == 0) {
throw Error("No previous token")
}
return tokens[position - 1]
}
/* * * * * *
* Parsers *
* * * * * */
private fun parseTerm(): Term {
// TODO Variable
// TODO braced term
// TODO Integer Term
// TODO Float term
// TODO Compound term
// TODO Binary operator
// TODO Unary operator
// TODO list term
// TODO curly bracketed term
return parseAtom()
}
private fun parseAtom(): Atom {
// TODO empty list
// TODO empty braces
return Atom(parseLetterDigit())
// TODO graphic
// TODO quoted
// TODO double quoted
// TODO back quoted
// TODO semicolon
// TODO cut
}
private fun parseLetterDigit(): String {
// Check if the first character is a lowercase letter
if (match(listOf(TokenType.ALPHANUMERIC)) && previous().value[0].isLowerCase()) {
return previous().value
}
// TODO How to fix?
return ""
}
}

View file

@ -13,4 +13,12 @@ open class Atom(val name: String) : Goal(), Head, Body, Resolvent {
override fun toString(): String { override fun toString(): String {
return name return name
} }
override fun equals(other: Any?): Boolean {
return (this === other) || (other is Atom && functor == other.functor)
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
} }

View file

@ -1,4 +1,4 @@
package prolog.builtins package prolog.logic
import prolog.ast.terms.Atom import prolog.ast.terms.Atom
import prolog.ast.terms.Term import prolog.ast.terms.Term

View file

@ -127,12 +127,12 @@ class ScanTests {
assertEquals(3, tokens.size) assertEquals(3, tokens.size)
assertEquals( assertEquals(
TokenType.LEFT_PARENTHESES, TokenType.LEFT_PARENTHESIS,
tokens[0].type, tokens[0].type,
"Expected LEFT_PARENTHESES token, got ${tokens[0].type}" "Expected LEFT_PARENTHESES token, got ${tokens[0].type}"
) )
assertEquals( assertEquals(
TokenType.RIGHT_PARENTHESES, TokenType.RIGHT_PARENTHESIS,
tokens[1].type, tokens[1].type,
"Expected RIGHT_PARENTHESES token, got ${tokens[1].type}" "Expected RIGHT_PARENTHESES token, got ${tokens[1].type}"
) )

View file

@ -0,0 +1,4 @@
package parser
class ParseFromTextTests {
}

View file

@ -0,0 +1,76 @@
package parser
import lexer.Token
import lexer.TokenPosition
import lexer.TokenType
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import prolog.ast.terms.Atom
import prolog.ast.terms.CompoundTerm
class ParseTests {
@Test
fun `parse atom a`() {
val input = Token(TokenType.ALPHANUMERIC, "a", TokenPosition(0, 0, 1))
val result = Parser(listOf(input)).parse()
assertEquals(1, result.size, "Expected 1 term")
assertEquals(Atom("a"), result[0], "Expected atom 'a'")
}
@Test
fun `parse atom foo`() {
val input = Token(TokenType.ALPHANUMERIC, "foo", TokenPosition(0, 0, 3))
val result = Parser(listOf(input)).parse()
assertEquals(1, result.size, "Expected 1 term")
assertEquals(Atom("foo"), result[0], "Expected atom 'foo'")
}
@Test
fun `parse atom foo1`() {
val input = Token(TokenType.ALPHANUMERIC, "foo1", TokenPosition(0, 0, 4))
val result = Parser(listOf(input)).parse()
assertEquals(1, result.size, "Expected 1 term")
assertEquals(Atom("foo1"), result[0], "Expected atom 'foo1'")
}
@Test
fun `parse atom fooBar`() {
val name = "fooBar"
val input = Token(TokenType.ALPHANUMERIC, name, TokenPosition(0, 0, 6))
val result = Parser(listOf(input)).parse()
assertEquals(1, result.size, "Expected 1 term")
assertEquals(Atom(name), result[0], "Expected atom 'fooBar'")
}
@Test
fun `parse atom foo_bar`() {
val name = "foo_bar"
val input = Token(TokenType.ALPHANUMERIC, name, TokenPosition(0, 0, 7))
val result = Parser(listOf(input)).parse()
assertEquals(1, result.size, "Expected 1 term")
assertEquals(Atom(name), result[0], "Expected atom 'foo_bar'")
}
@Test
fun `parse atom my_FooBar1`() {
val name = "my_FooBar1"
val input = Token(TokenType.ALPHANUMERIC, name, TokenPosition(0, 0, 11))
val result = Parser(listOf(input)).parse()
assertEquals(1, result.size, "Expected 1 term")
assertEquals(Atom(name), result[0], "Expected atom 'my_FooBar1'")
}
}