feat(parser): Terms parsing

This commit is contained in:
Tibo De Peuter 2025-04-17 21:44:34 +02:00
parent 5d8f2b6f35
commit 69c156024a
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
2 changed files with 217 additions and 0 deletions

View file

@ -0,0 +1,61 @@
package better_parser
import com.github.h0tk3y.betterParse.combinators.*
import com.github.h0tk3y.betterParse.grammar.Grammar
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.parser.Parser
import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
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_]*")
// 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(")")
// 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)
// 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),
comma,
acceptZero = true
) and skip(rightParenthesis)) use {
Structure(t1, t2.terms)
}
// Arithmetic parsers
private val integerParser: Parser<Integer> by integerToken use { Integer(text.toInt()) }
private val floatParser: Parser<Float> by floatToken use {
Float(text.toFloat())
}
private val termParser: Parser<Term> by (floatParser
or integerParser
or variableParser
or structureParser
or atomParser
) map { it }
override val rootParser: Parser<Any> = termParser
}

View file

@ -0,0 +1,156 @@
package better_parser
import com.github.h0tk3y.betterParse.grammar.Grammar
import com.github.h0tk3y.betterParse.grammar.parseToEnd
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
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.arithmetic.Float
import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.logic.equivalent
class SimplePrologParserTests {
lateinit var parser: Grammar<Term>
@BeforeEach
fun setup() {
parser = SimplePrologParser() as Grammar<Term>
}
@ParameterizedTest
@ValueSource(strings = ["a", "foo", "foo1", "fooBar", "foo_bar"])
fun `parse atom`(name: String) {
val result = parser.parseToEnd(name)
assertEquals(Atom(name), result, "Expected atom '$name'")
}
@ParameterizedTest
@ValueSource(strings = ["X", "X1", "X_1"])
fun `parse variable`(name: String) {
val result = parser.parseToEnd(name)
assertEquals(Variable(name), result, "Expected atom '$name'")
}
@Test
fun `empty compound term`() {
val input = "f()"
val result = parser.parseToEnd(input)
assertTrue(
equivalent(Structure(Atom("f"), emptyList()), result, emptyMap()),
"Expected atom 'f'"
)
}
@Test
fun `parse compound term f(a)`() {
val input = "f(a)"
val result = parser.parseToEnd(input)
assertTrue(
equivalent(Structure(Atom("f"), listOf(Atom("a"))), result, emptyMap()),
"Expected atom 'f(a)'"
)
}
@Test
fun `parse compound term f(a, b)`() {
val input = "f(a, b)"
val result = parser.parseToEnd(input)
assertTrue(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Atom("b"))), result, emptyMap()),
"Expected atom 'f(a, b)'"
)
}
@Test
fun `parse compound term with variable f(a, X)`() {
val input = "f(a, X)"
val result = parser.parseToEnd(input)
assertTrue(
equivalent(Structure(Atom("f"), listOf(Atom("a"), Variable("X"))), result, emptyMap()),
"Expected atom 'f(a, X)'"
)
}
@Test
fun `parse nested compound term f(a, g(b))`() {
val input = "f(a, g(b))"
val result = parser.parseToEnd(input)
assertTrue(
equivalent(
Structure(Atom("f"), listOf(Atom("a"), Structure(Atom("g"), listOf(Atom("b"))))),
result,
emptyMap()
),
"Expected atom 'f(a, g(b))'"
)
}
@Test
fun `parse compound term with variable f(a, g(X))`() {
val input = "f(a, g(X))"
val result = parser.parseToEnd(input)
assertTrue(
equivalent(
Structure(Atom("f"), listOf(Atom("a"), Structure(Atom("g"), listOf(Variable("X"))))),
result,
emptyMap()
),
"Expected atom 'f(a, g(X))'"
)
}
@ParameterizedTest
@ValueSource(ints = [-987654321, -543, -21, -1, 0, 1, 5, 12, 345, 123456789])
fun `parse integer`(number: Int) {
val input = number.toString()
val result = parser.parseToEnd(input)
assertEquals(Integer(number), result, "Expected integer '$number'")
}
@Test
fun `parse float`() {
val input = "42.0"
val result = parser.parseToEnd(input)
assertTrue(
equivalent(Float(42.0f), result, emptyMap()),
"Expected float '42.0'"
)
}
@Test
fun `parse negative float`() {
val input = "-42.0"
val result = parser.parseToEnd(input)
assertTrue(
equivalent(Float(-42.0f), result, emptyMap()),
"Expected float '-42.0'"
)
}
}