diff --git a/src/lexer/Lexer.kt b/src/lexer/Lexer.kt index 526d619..7e92cd6 100644 --- a/src/lexer/Lexer.kt +++ b/src/lexer/Lexer.kt @@ -1,9 +1,7 @@ package lexer -import java.util.LinkedList - class Lexer(private val source: String) { - private var tokens: List = LinkedList() + private var tokens: List = emptyList() private val position = LexerPosition(0, 0, -1) /** @@ -14,8 +12,8 @@ class Lexer(private val source: String) { while (hasNext()) { val char: Char = peek() tokens += when { - char == '(' -> scanSymbol(TokenType.LEFT_PARENTHESES) - char == ')' -> scanSymbol(TokenType.RIGHT_PARENTHESES) + char == '(' -> scanSymbol(TokenType.LEFT_PARENTHESIS) + char == ')' -> scanSymbol(TokenType.RIGHT_PARENTHESIS) char == '.' -> scanSymbol(TokenType.DOT) char == '"' -> scanQuotedString() char == '%' -> { scanComment(); continue } diff --git a/src/lexer/TokenType.kt b/src/lexer/TokenType.kt index c2b9441..a5216c6 100644 --- a/src/lexer/TokenType.kt +++ b/src/lexer/TokenType.kt @@ -2,11 +2,10 @@ package lexer enum class TokenType { ALPHANUMERIC, + // TODO Replace with SMALL_LETTER, CAPITAL_LETTER, DIGIT, HEX_DIGIT, ... ? - LEFT_PARENTHESES, RIGHT_PARENTHESES, + LEFT_PARENTHESIS, RIGHT_PARENTHESIS, DOT, - // Operators - EOF } diff --git a/src/parser/Parser.kt b/src/parser/Parser.kt new file mode 100644 index 0000000..977c227 --- /dev/null +++ b/src/parser/Parser.kt @@ -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) { + private var position: Int = 0 + + fun parse(): List { + val terms = mutableListOf() + + // 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): 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 "" + } +} diff --git a/src/prolog/ast/terms/Atom.kt b/src/prolog/ast/terms/Atom.kt index 3c252e5..3a6afad 100644 --- a/src/prolog/ast/terms/Atom.kt +++ b/src/prolog/ast/terms/Atom.kt @@ -13,4 +13,12 @@ open class Atom(val name: String) : Goal(), Head, Body, Resolvent { override fun toString(): String { return name } + + override fun equals(other: Any?): Boolean { + return (this === other) || (other is Atom && functor == other.functor) + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } } \ No newline at end of file diff --git a/src/prolog/logic/terms.kt b/src/prolog/logic/terms.kt index 744e1b5..6bf2665 100644 --- a/src/prolog/logic/terms.kt +++ b/src/prolog/logic/terms.kt @@ -1,4 +1,4 @@ -package prolog.builtins +package prolog.logic import prolog.ast.terms.Atom import prolog.ast.terms.Term diff --git a/tests/lexer/ScanTests.kt b/tests/lexer/ScanTests.kt index 808ef1a..cf21d26 100644 --- a/tests/lexer/ScanTests.kt +++ b/tests/lexer/ScanTests.kt @@ -127,12 +127,12 @@ class ScanTests { assertEquals(3, tokens.size) assertEquals( - TokenType.LEFT_PARENTHESES, + TokenType.LEFT_PARENTHESIS, tokens[0].type, "Expected LEFT_PARENTHESES token, got ${tokens[0].type}" ) assertEquals( - TokenType.RIGHT_PARENTHESES, + TokenType.RIGHT_PARENTHESIS, tokens[1].type, "Expected RIGHT_PARENTHESES token, got ${tokens[1].type}" ) diff --git a/tests/parser/ParseFromTextTests.kt b/tests/parser/ParseFromTextTests.kt new file mode 100644 index 0000000..c7de15e --- /dev/null +++ b/tests/parser/ParseFromTextTests.kt @@ -0,0 +1,4 @@ +package parser + +class ParseFromTextTests { +} \ No newline at end of file diff --git a/tests/parser/ParseTests.kt b/tests/parser/ParseTests.kt new file mode 100644 index 0000000..6b3d214 --- /dev/null +++ b/tests/parser/ParseTests.kt @@ -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'") + } +} \ No newline at end of file