feat: Atom parsing
This commit is contained in:
parent
bd5c825ca2
commit
e749f8c6cb
8 changed files with 212 additions and 11 deletions
|
@ -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 }
|
||||||
|
|
|
@ -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
116
src/parser/Parser.kt
Normal 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 ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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}"
|
||||||
)
|
)
|
||||||
|
|
4
tests/parser/ParseFromTextTests.kt
Normal file
4
tests/parser/ParseFromTextTests.kt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
class ParseFromTextTests {
|
||||||
|
}
|
76
tests/parser/ParseTests.kt
Normal file
76
tests/parser/ParseTests.kt
Normal 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'")
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue