feat(parser): Terms parsing
This commit is contained in:
parent
5d8f2b6f35
commit
69c156024a
2 changed files with 217 additions and 0 deletions
61
src/better_parser/SimplePrologParser.kt
Normal file
61
src/better_parser/SimplePrologParser.kt
Normal 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
|
||||||
|
}
|
156
tests/better_parser/SimplePrologParserTests.kt
Normal file
156
tests/better_parser/SimplePrologParserTests.kt
Normal 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'"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue