Refactor the Instruction parser into an Expression one.

Now support more arithmeric operator (mainly *, /) and recurcive bracket
This commit is contained in:
Thomas Andres Gomez 2025-02-04 18:43:05 +01:00
parent 409acf748f
commit ce51a3be0a
19 changed files with 824 additions and 448 deletions

View file

@ -1,138 +0,0 @@
package com.pixelized.desktop.lwa.parser
import com.pixelized.desktop.lwa.parser.arithmetic.Instruction
import com.pixelized.desktop.lwa.parser.arithmetic.ArithmeticParser
import kotlin.test.Test
import kotlin.test.assertFails
class InstructionParserTest {
@Test
fun testDiceInstructionParse() {
val parser = ArithmeticParser()
fun test(
instruction: String,
expectedModifier: Instruction.Dice.Modifier?,
expectedQuantity: Int,
expectedFaces: Int,
) {
val dice = parser.parse(value = instruction).first()
assert(dice is Instruction.Dice) {
"Instruction should be ArithmeticInstruction.Dice but was: ${dice::class.java.simpleName}"
}
(dice as? Instruction.Dice)?.let {
assert(dice.modifier == expectedModifier) {
"$instruction modifier should be:\"$expectedModifier\", but was: ${dice.modifier}"
}
assert(dice.quantity == expectedQuantity) {
"$instruction quantity should be \"$expectedQuantity\" but was ${dice.quantity}"
}
assert(dice.faces == expectedFaces) {
"$instruction faces should be \"$expectedFaces\" but was ${dice.faces}"
}
}
}
test(
instruction = "1d100",
expectedModifier = null,
expectedQuantity = 1,
expectedFaces = 100,
)
test(
instruction = "a2d6",
expectedModifier = Instruction.Dice.Modifier.ADVANTAGE,
expectedQuantity = 2,
expectedFaces = 6,
)
test(
instruction = "d1d2",
expectedModifier = Instruction.Dice.Modifier.DISADVANTAGE,
expectedQuantity = 1,
expectedFaces = 2,
)
test(
instruction = "e6d6",
expectedModifier = Instruction.Dice.Modifier.EMPHASIS,
expectedQuantity = 6,
expectedFaces = 6,
)
}
@Test
fun testWordInstructionParse() {
val parser = ArithmeticParser()
ArithmeticParser.words.map { instruction ->
val word = parser.parse(value = instruction).first()
assert(word is Instruction.Word) {
"Instruction should be ArithmeticInstruction.Word but was: ${word::class.java.simpleName}"
}
(word as? Instruction.Word)?.let {
assert(it.type.name == instruction) {
"Instruction should be $instruction, but was ${it.type.name}"
}
}
}
}
@Test
fun testFlatInstructionParse() {
val parser = ArithmeticParser()
"100".let { instruction ->
val flat = parser.parse(value = instruction).first()
assert(flat is Instruction.Flat) {
"Instruction should be ArithmeticInstruction.Flat but was: ${flat::class.java.simpleName}"
}
(flat as? Instruction.Flat)?.let {
assert("${it.value}" == instruction) {
"Instruction should be $instruction, but was ${it.value}"
}
}
}
}
@Test
fun testRollParse() {
val parser = ArithmeticParser()
fun test(
instruction: Instruction,
expectedInstruction: Instruction,
) {
assert(instruction == expectedInstruction) {
"Arithmetic instruction should be $expectedInstruction but was: $instruction"
}
}
val instructions = parser.parse(
value = "1+1d6+2-BDC+BDD",
)
test(
instruction = instructions[0],
expectedInstruction = Instruction.Flat(sign = 1, value = 1),
)
test(
instruction = instructions[1],
expectedInstruction = Instruction.Dice(sign = 1, modifier = null, quantity = 1, faces = 6),
)
test(
instruction = instructions[2],
expectedInstruction = Instruction.Flat(sign = 1, value = 2),
)
test(
instruction = instructions[3],
expectedInstruction = Instruction.Word(sign = -1, type = Instruction.Word.Type.BDC),
)
test(
instruction = instructions[4],
expectedInstruction = Instruction.Word(sign = 1, type = Instruction.Word.Type.BDD),
)
}
}

View file

@ -0,0 +1,68 @@
package com.pixelized.desktop.lwa.parser.dice
import org.junit.Test
class DiceParserTest {
@Test
fun testInvalidExpression() {
val parser = DiceParser()
parser.test(expression = "", expected = null)
parser.test(expression = " ", expected = null)
parser.test(expression = "1", expected = null)
parser.test(expression = "d6", expected = null)
parser.test(expression = "ad6", expected = null)
parser.test(expression = "dd6", expected = null)
parser.test(expression = "ed6", expected = null)
}
@Test
fun testFaces() {
val parser = DiceParser()
parser.test(expression = "1d4", expected = Dice(modifier = null, quantity = 1, faces = 4))
parser.test(expression = "1d6", expected = Dice(modifier = null, quantity = 1, faces = 6))
parser.test(expression = "1d8", expected = Dice(modifier = null, quantity = 1, faces = 8))
}
@Test
fun testQuantity() {
val parser = DiceParser()
parser.test(expression = "2d6", expected = Dice(modifier = null, quantity = 2, faces = 6))
parser.test(expression = "3d6", expected = Dice(modifier = null, quantity = 3, faces = 6))
parser.test(expression = "4d6", expected = Dice(modifier = null, quantity = 4, faces = 6))
}
@Test
fun testWhitespace() {
val parser = DiceParser()
parser.test(expression = " 2d6", expected = Dice(modifier = null, quantity = 2, faces = 6))
parser.test(expression = "2d6 ", expected = Dice(modifier = null, quantity = 2, faces = 6))
}
@Test
fun testModifier() {
val parser = DiceParser()
parser.test(
expression = "a1d6",
expected = Dice(modifier = Dice.Modifier.ADVANTAGE, quantity = 1, faces = 6)
)
parser.test(
expression = "d1d6",
expected = Dice(modifier = Dice.Modifier.DISADVANTAGE, quantity = 1, faces = 6)
)
parser.test(
expression = "e1d6",
expected = Dice(modifier = Dice.Modifier.EMPHASIS, quantity = 1, faces = 6)
)
}
private fun DiceParser.test(
expression: String,
expected: Dice?,
) {
val result = parse(expression = expression)
assert(result == expected) {
"DiceParser.parse(expression=$expression) is expected to return:$expected, but was:$result"
}
}
}

View file

@ -0,0 +1,112 @@
package com.pixelized.desktop.lwa.parser.expression
import com.pixelized.desktop.lwa.parser.dice.DiceParser
import com.pixelized.desktop.lwa.parser.expression.ExpressionParser.Error
import com.pixelized.desktop.lwa.parser.word.WordParser
import org.junit.Test
import kotlin.test.assertFailsWith
class ExpressionParserTest {
@Test
fun testInvalidExpression() {
val parser = ExpressionParser(
diceParser = DiceParser(),
wordParser = WordParser(),
)
parser.test("", null)
parser.test(" ", null)
assertFailsWith(Error.UnRecognizedToken::class) {
parser.test("pouet", null)
}
assertFailsWith(Error.ExpectedTokenCharacter::class) {
parser.test("1+", null)
}
assertFailsWith(Error.ExpectedOperator::class) {
parser.test("(153", null)
}
assertFailsWith(Error.ExpectedOperator::class) {
parser.test("min(1+1)", null)
}
}
@Test
fun testArithmeticExpression() {
val parser = ExpressionParser(
diceParser = DiceParser(),
wordParser = WordParser(),
)
parser.test(
expression = "-1",
expected = Expression.Inversion(Expression.Flat(1)),
)
parser.test(
expression = "+1",
expected = Expression.Flat(1),
)
parser.test(
expression = "1+2",
expected = Expression.Add(Expression.Flat(1), Expression.Flat(2))
)
parser.test(
expression = "1-2",
expected = Expression.Minus(Expression.Flat(1), Expression.Flat(2))
)
parser.test(
expression = "1*2",
expected = Expression.Prod(Expression.Flat(1), Expression.Flat(2))
)
parser.test(
expression = "1/2",
expected = Expression.Div(Expression.Flat(1), Expression.Flat(2))
)
}
@Test
fun testFunctionExpression() {
val parser = ExpressionParser(
diceParser = DiceParser(),
wordParser = WordParser(),
)
parser.test(
expression = "min(1,2)",
expected = Expression.Minimum(Expression.Flat(1), Expression.Flat(2))
)
parser.test(
expression = "max(1,2)",
expected = Expression.Maximum(Expression.Flat(1), Expression.Flat(2))
)
}
@Test
fun testArithmeticPriorityExpression() {
val parser = ExpressionParser(
diceParser = DiceParser(),
wordParser = WordParser(),
)
parser.test(
expression = "(1+2)*3",
expected = Expression.Prod(
Expression.Add(Expression.Flat(1), Expression.Flat(2)),
Expression.Flat(3),
)
)
parser.test(
expression = "1+2*3",
expected = Expression.Add(
Expression.Flat(1),
Expression.Prod(Expression.Flat(2), Expression.Flat(3)),
)
)
}
private fun ExpressionParser.test(
expression: String,
expected: Expression?,
) {
val result = parse(input = expression)
assert(result == expected) {
"ExpressionParser.parse(input=$expression) is expected to return:$expected, but was:$result"
}
}
}

View file

@ -0,0 +1,34 @@
package com.pixelized.desktop.lwa.parser.word
import org.junit.Test
class WordParserTest {
@Test
fun testInvalidExpression() {
val parser = WordParser()
parser.test(expression = "", expected = null)
parser.test(expression = " ", expected = null)
parser.test(expression = "1", expected = null)
parser.test(expression = "1d6", expected = null)
parser.test(expression = "pouet", expected = null)
}
@Test
fun testValidExpression() {
val parser = WordParser()
Word.Type.entries.forEach { type ->
parser.test(expression = type.name, expected = Word(type))
}
}
private fun WordParser.test(
expression: String,
expected: Word?,
) {
val result = parse(value = expression)
assert(result == expected) {
"WordParser.parse(value=$expression) is expected to return:$expected, but was:$result"
}
}
}