Add support for custom formulas.
This commit re-frames the formula concept used by the wave growth, swarm amount, and boss health wave configuration properties. It fundamentally changes how these values are calculated, from a static, compile-time set of enum values and hardcoded expressions, to a powerful math expression feature that supports constants, variables, operators, and functions. In part to remain backwards compatible with existing MobArena setups, and in part for a better user experience, the old enum-based expressions are relocated into a new file, `formulas.yml`, as _macros_. The file is written to the plugin folder if missing, and it contains a formula for each of the legacy values for each of the enums. Additionally, it has a global section with some predefined macros for inspiration's sake. The goal of this file is to allow people to define new formulas and reuse them in their wave configurations instead of having to duplicate the same formulas again and again. Parts of the system are extensible. It is possible for other plugins to register additional constants, variables, operators, and functions. Closes #460 Closes #461
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import com.garbagemule.MobArena.MonsterManager;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.waves.WaveManager;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class FormulaMacrosIT {
|
||||
|
||||
static MobArena plugin;
|
||||
static Arena arena;
|
||||
static FormulaMacros macros;
|
||||
static FormulaManager parser;
|
||||
|
||||
static int finalWave = 13;
|
||||
static int currentWave = finalWave - 2;
|
||||
static int liveMonsters = 9;
|
||||
static int initialPlayers = 7;
|
||||
static int livePlayers = initialPlayers - 2;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws IOException {
|
||||
plugin = mock(MobArena.class);
|
||||
File resources = new File("src/main/resources");
|
||||
when(plugin.getDataFolder()).thenReturn(resources);
|
||||
|
||||
arena = mock(Arena.class);
|
||||
WaveManager wm = mock(WaveManager.class);
|
||||
when(wm.getWaveNumber()).thenReturn(currentWave);
|
||||
when(wm.getFinalWave()).thenReturn(finalWave);
|
||||
when(arena.getWaveManager()).thenReturn(wm);
|
||||
Set<LivingEntity> monsters = new HashSet<>();
|
||||
for (int i = 0; i < liveMonsters; i++) {
|
||||
monsters.add(mock(LivingEntity.class));
|
||||
}
|
||||
MonsterManager mm = mock(MonsterManager.class);
|
||||
when(mm.getMonsters()).thenReturn(monsters);
|
||||
when(arena.getMonsterManager()).thenReturn(mm);
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (int i = 0; i < livePlayers; i++) {
|
||||
players.add(mock(Player.class));
|
||||
}
|
||||
when(arena.getPlayersInArena()).thenReturn(players);
|
||||
when(arena.getPlayerCount()).thenReturn(initialPlayers);
|
||||
|
||||
macros = FormulaMacros.create(plugin);
|
||||
macros.reload();
|
||||
|
||||
parser = FormulaManager.createDefault();
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class Global {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"wave-squared", currentWave * currentWave},
|
||||
{"wave-inverted", finalWave - currentWave},
|
||||
{"five-each", livePlayers * 5},
|
||||
{"double-team", (double) livePlayers / 2},
|
||||
{"top-up", 10 - liveMonsters},
|
||||
{"dead-man-walking", initialPlayers - livePlayers},
|
||||
});
|
||||
}
|
||||
|
||||
String macro;
|
||||
double expected;
|
||||
|
||||
public Global(String macro, double expected) {
|
||||
this.macro = macro;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String value = macros.get("global", macro);
|
||||
Formula formula = parser.parse(value);
|
||||
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class WaveGrowth {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"slow", 0.5},
|
||||
{"medium", 0.65},
|
||||
{"fast", 0.8},
|
||||
{"psycho", 1.2},
|
||||
});
|
||||
}
|
||||
|
||||
String macro;
|
||||
double exponent;
|
||||
|
||||
public WaveGrowth(String macro, double exponent) {
|
||||
this.macro = macro;
|
||||
this.exponent = exponent;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String value = macros.get("wave-growth", macro);
|
||||
Formula formula = parser.parse(value);
|
||||
|
||||
double result = (int) formula.evaluate(arena);
|
||||
|
||||
double base = (int) Math.ceil(initialPlayers / 2.0) + 1;
|
||||
double expected = (int) (base * Math.pow(currentWave, exponent));
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Old wave growth formula is different, so we'll just have a
|
||||
* different test for it all-together.
|
||||
*/
|
||||
public static class WaveGrowthOld {
|
||||
|
||||
@Test
|
||||
public void oldWaveGrowth() {
|
||||
String value = macros.get("wave-growth", "old");
|
||||
Formula formula = parser.parse(value);
|
||||
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
double expected = currentWave + initialPlayers;
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class SwarmAmount {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"low", 10},
|
||||
{"medium", 20},
|
||||
{"high", 30},
|
||||
{"psycho", 60},
|
||||
});
|
||||
}
|
||||
|
||||
String macro;
|
||||
double multiplier;
|
||||
|
||||
public SwarmAmount(String macro, double multiplier) {
|
||||
this.macro = macro;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String value = macros.get("swarm-amount", macro);
|
||||
Formula formula = parser.parse(value);
|
||||
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
double expected = (double) (initialPlayers / 2) * multiplier;
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class BossHealth {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"verylow", 4},
|
||||
{"low", 8},
|
||||
{"medium", 15},
|
||||
{"high", 25},
|
||||
{"veryhigh", 40},
|
||||
{"psycho", 60},
|
||||
});
|
||||
}
|
||||
|
||||
String macro;
|
||||
double multiplier;
|
||||
|
||||
public BossHealth(String macro, double multiplier) {
|
||||
this.macro = macro;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String value = macros.get("boss-health", macro);
|
||||
Formula formula = parser.parse(value);
|
||||
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
double expected = (initialPlayers + 1) * 20 * multiplier;
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Try actually loading a non-default formulas.yml with a couple
|
||||
* of different types of formulas in it to test that the loading
|
||||
* itself actually works.
|
||||
*/
|
||||
public static class TestFile {
|
||||
|
||||
@Test
|
||||
public void loadsUnorthodoxFile() throws IOException {
|
||||
plugin = mock(MobArena.class);
|
||||
File resources = new File("src/test/resources");
|
||||
when(plugin.getDataFolder()).thenReturn(resources);
|
||||
|
||||
FormulaMacros subject = FormulaMacros.create(plugin);
|
||||
subject.reload();
|
||||
|
||||
assertThat(subject.get("numbers", "one"), equalTo("1"));
|
||||
assertThat(subject.get("numbers", "two"), equalTo("2"));
|
||||
assertThat(subject.get("constants", "three-point-one-four"), equalTo("pi"));
|
||||
assertThat(subject.get("constants", "eulers-number"), equalTo("e"));
|
||||
assertThat(subject.get("variables", "live"), equalTo("<live-players>"));
|
||||
assertThat(subject.get("variables", "max"), equalTo("<max-players>"));
|
||||
assertThat(subject.get("operators", "two-plus-two"), equalTo("2 + 2"));
|
||||
assertThat(subject.get("operators", "one-times-two"), equalTo("1 * 2"));
|
||||
assertThat(subject.get("functions", "square-root"), equalTo("sqrt(9)"));
|
||||
assertThat(subject.get("functions", "maximum"), equalTo("max(1, 2)"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import com.garbagemule.MobArena.MonsterManager;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.waves.WaveManager;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class FormulaManagerIT {
|
||||
|
||||
static Arena arena;
|
||||
static FormulaManager subject;
|
||||
|
||||
static int finalWave = 13;
|
||||
static int currentWave = finalWave - 2;
|
||||
static int liveMonsters = 9;
|
||||
static int initialPlayers = 7;
|
||||
static int livePlayers = initialPlayers - 2;
|
||||
static int deadPlayers = initialPlayers - livePlayers;
|
||||
static int minPlayers = 3;
|
||||
static int maxPlayers = initialPlayers + 3;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
arena = mock(Arena.class);
|
||||
WaveManager wm = mock(WaveManager.class);
|
||||
when(wm.getWaveNumber()).thenReturn(currentWave);
|
||||
when(wm.getFinalWave()).thenReturn(finalWave);
|
||||
when(arena.getWaveManager()).thenReturn(wm);
|
||||
Set<LivingEntity> monsters = new HashSet<>();
|
||||
for (int i = 0; i < liveMonsters; i++) {
|
||||
monsters.add(mock(LivingEntity.class));
|
||||
}
|
||||
MonsterManager mm = mock(MonsterManager.class);
|
||||
when(mm.getMonsters()).thenReturn(monsters);
|
||||
when(arena.getMonsterManager()).thenReturn(mm);
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (int i = 0; i < livePlayers; i++) {
|
||||
players.add(mock(Player.class));
|
||||
}
|
||||
when(arena.getPlayersInArena()).thenReturn(players);
|
||||
when(arena.getPlayerCount()).thenReturn(initialPlayers);
|
||||
when(arena.getMinPlayers()).thenReturn(minPlayers);
|
||||
when(arena.getMaxPlayers()).thenReturn(maxPlayers);
|
||||
|
||||
subject = FormulaManager.createDefault();
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class NumberLiterals {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"0"},
|
||||
{"1"},
|
||||
{"-1"},
|
||||
{"1337"},
|
||||
{"3.14"},
|
||||
{"1e4"},
|
||||
{"-1e4"},
|
||||
{"1e-4"},
|
||||
{"-1e-4"},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
double expected;
|
||||
|
||||
public NumberLiterals(String input) {
|
||||
this.input = input;
|
||||
this.expected = Double.parseDouble(input);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Formula formula = subject.parse(input);
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class DefaultConstants {
|
||||
|
||||
@Parameters(name = "{0} = {1}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"pi", Math.PI},
|
||||
{"e", Math.E},
|
||||
{"pi^e", Math.pow(Math.PI, Math.E)},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
double expected;
|
||||
|
||||
public DefaultConstants(String input, double expected) {
|
||||
this.input = input;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Formula formula = subject.parse(input);
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class DefaultVariables {
|
||||
|
||||
@Parameters(name = "{0} = {1}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"<current-wave>", currentWave},
|
||||
{"<final-wave>", finalWave},
|
||||
{"<initial-players>", initialPlayers},
|
||||
{"<live-players>", livePlayers},
|
||||
{"<dead-players>", deadPlayers},
|
||||
{"<min-players>", minPlayers},
|
||||
{"<max-players>", maxPlayers},
|
||||
{"<live-monsters>", liveMonsters},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
double expected;
|
||||
|
||||
public DefaultVariables(String input, double expected) {
|
||||
this.input = input;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Formula formula = subject.parse(input);
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* With custom variables, we are manipulating the internal
|
||||
* state of the manager, so we need to use a local subject.
|
||||
*/
|
||||
public static class CustomVariables {
|
||||
|
||||
FormulaManager subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
subject = FormulaManager.createDefault();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveRegisteredCustomVariable() {
|
||||
subject.registerVariable("bob", a -> 7.5);
|
||||
|
||||
Formula formula = subject.parse("2.5 + <bob>");
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
double expected = 10;
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwsOnUnknownCustomVariable() {
|
||||
assertThrows(
|
||||
UnknownToken.class,
|
||||
() -> subject.parse("2 + <bob>")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class DefaultUnaryOperators {
|
||||
|
||||
@Parameters(name = "{0} = {1}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"1 + +1.2", 1 + +1.2},
|
||||
{"1 + -1.2", 1 + -1.2},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
double expected;
|
||||
|
||||
public DefaultUnaryOperators(String input, double expected) {
|
||||
this.input = input;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Formula formula = subject.parse(input);
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class DefaultOperators {
|
||||
|
||||
@Parameters(name = "{0} = {1}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"1+-2", 1 + -2},
|
||||
{"3-+4", 3 - +4},
|
||||
{"3*7.5", 3 * 7.5},
|
||||
{"10/2.5", 10 / 2.5},
|
||||
{"9%4", 9 % 4},
|
||||
{"2^-8", Math.pow(2, -8)},
|
||||
{"-2^-8", -Math.pow(2, -8)},
|
||||
{"(-2)^-8", Math.pow(-2, -8)},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
double expected;
|
||||
|
||||
public DefaultOperators(String input, double expected) {
|
||||
this.input = input;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Formula formula = subject.parse(input);
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class DefaultUnaryFunctions {
|
||||
|
||||
@Parameters(name = "{0} = {1}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"sqrt(4)", 2},
|
||||
{"sqrt(9)", 3},
|
||||
{"abs(2)", 2},
|
||||
{"abs(-2)", 2},
|
||||
{"ceil(8.2)", 9},
|
||||
{"ceil(8.7)", 9},
|
||||
{"floor(8.2)", 8},
|
||||
{"floor(8.7)", 8},
|
||||
{"round(8.2)", 8},
|
||||
{"round(8.7)", 9},
|
||||
{"sin(pi / 2)", Math.sin(Math.PI / 2)},
|
||||
{"cos(pi / 3)", Math.cos(Math.PI / 3)},
|
||||
{"tan(pi / 4)", Math.tan(Math.PI / 4)},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
double expected;
|
||||
|
||||
public DefaultUnaryFunctions(String input, double expected) {
|
||||
this.input = input;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Formula formula = subject.parse(input);
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class DefaultBinaryFunctions {
|
||||
|
||||
@Parameters(name = "{0} = {1}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"min(1, 2)", 1},
|
||||
{"min(2, 1)", 1},
|
||||
{"max(1, 2)", 2},
|
||||
{"max(2, 1)", 2},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
double expected;
|
||||
|
||||
public DefaultBinaryFunctions(String input, double expected) {
|
||||
this.input = input;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Formula formula = subject.parse(input);
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* With custom functions, we are manipulating the internal
|
||||
* state of the manager, so we need to use a local subject.
|
||||
*/
|
||||
public static class CustomFunctions {
|
||||
|
||||
FormulaManager subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
subject = FormulaManager.createDefault();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveRegisteredCustomFunctions() {
|
||||
subject.registerUnaryFunction("flip", a -> -a);
|
||||
subject.registerBinaryFunction("car", (a, b) -> a);
|
||||
subject.registerBinaryFunction("cdr", (a, b) -> b);
|
||||
|
||||
Formula formula = subject.parse("flip(car(1, 2) + cdr(3, 4))");
|
||||
double result = formula.evaluate(arena);
|
||||
|
||||
double expected = -(1 + 4);
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwsOnUnknownCustomFunction() {
|
||||
assertThrows(
|
||||
UnknownToken.class,
|
||||
() -> subject.parse("flip(1)")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class LexemeMatcher extends TypeSafeMatcher<Lexeme> {
|
||||
|
||||
private final TokenType type;
|
||||
private final String value;
|
||||
|
||||
private LexemeMatcher(TokenType type, String value) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(Lexeme item) {
|
||||
if (item.token.type != type) {
|
||||
return false;
|
||||
}
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
return Objects.equals(item.value, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText(type.name() + " '" + value + "'");
|
||||
}
|
||||
|
||||
public static Matcher<Lexeme> matches(TokenType type, String value) {
|
||||
return new LexemeMatcher(type, value);
|
||||
}
|
||||
|
||||
public static Matcher<Lexeme> matches(TokenType type) {
|
||||
return new LexemeMatcher(type, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.garbagemule.MobArena.formula.LexemeMatcher.matches;
|
||||
import static com.garbagemule.MobArena.formula.TokenType.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class LexerConstantTest {
|
||||
|
||||
static Environment env;
|
||||
static Lexer subject;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class ConstantLiterals {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"pi"},
|
||||
{"e"},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
|
||||
public ConstantLiterals(String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(matches(IDENTIFIER, input)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class InvalidConstants {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownConstant() {
|
||||
assertThrows(
|
||||
UnknownToken.class,
|
||||
() -> subject.tokenize("pie")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.garbagemule.MobArena.formula.LexemeMatcher.matches;
|
||||
import static com.garbagemule.MobArena.formula.TokenType.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class LexerFunctionTest {
|
||||
|
||||
static Environment env;
|
||||
static Lexer subject;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class UnaryFunctions {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return asList(new Object[][]{
|
||||
{"sqrt"},
|
||||
{"abs"},
|
||||
{"ceil"},
|
||||
{"floor"},
|
||||
{"round"},
|
||||
{"sin"},
|
||||
{"cos"},
|
||||
{"tan"},
|
||||
});
|
||||
}
|
||||
|
||||
String name;
|
||||
String input;
|
||||
|
||||
public UnaryFunctions(String name) {
|
||||
this.name = name;
|
||||
this.input = name + "(1.2)";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(IDENTIFIER, name),
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "1.2"),
|
||||
matches(RIGHT_PAREN)
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class BinaryFunctions {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return asList(new Object[][]{
|
||||
{"min"},
|
||||
{"max"},
|
||||
});
|
||||
}
|
||||
|
||||
String name;
|
||||
String input;
|
||||
|
||||
public BinaryFunctions(String name) {
|
||||
this.name = name;
|
||||
this.input = name + "(1, 2)";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(IDENTIFIER, name),
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "1"),
|
||||
matches(COMMA),
|
||||
matches(NUMBER, "2"),
|
||||
matches(RIGHT_PAREN)
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class InvalidFunctions {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownFunction() {
|
||||
assertThrows(
|
||||
UnknownToken.class,
|
||||
() -> subject.tokenize("best(1.2)")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.garbagemule.MobArena.formula.LexemeMatcher.matches;
|
||||
import static com.garbagemule.MobArena.formula.TokenType.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class LexerNumberTest {
|
||||
|
||||
static Environment env;
|
||||
static Lexer subject;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class PositiveNumberLiterals {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return asList(new Object[][]{
|
||||
{"0"},
|
||||
{"1"},
|
||||
{"1337"},
|
||||
{"3.14"},
|
||||
{"1e4"},
|
||||
{"1e-4"},
|
||||
{"1.2e4"},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
|
||||
public PositiveNumberLiterals(String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(matches(NUMBER, input)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class NegativeNumberLiterals {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return asList(new Object[][]{
|
||||
{"-0"},
|
||||
{"-1"},
|
||||
{"-1337"},
|
||||
{"-3.14"},
|
||||
{"-1e4"},
|
||||
{"-1e-4"},
|
||||
{"-1.2e4"},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
String number;
|
||||
|
||||
public NegativeNumberLiterals(String input) {
|
||||
this.input = input;
|
||||
this.number = input.substring(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(UNARY_OPERATOR, "-"),
|
||||
matches(NUMBER, number)
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.garbagemule.MobArena.formula.LexemeMatcher.matches;
|
||||
import static com.garbagemule.MobArena.formula.TokenType.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class LexerOperatorTest {
|
||||
|
||||
static Environment env;
|
||||
static Lexer subject;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class BinaryOperators {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"+"},
|
||||
{"-"},
|
||||
{"*"},
|
||||
{"/"},
|
||||
{"%"},
|
||||
{"^"},
|
||||
});
|
||||
}
|
||||
|
||||
String symbol;
|
||||
String input;
|
||||
|
||||
public BinaryOperators(String symbol) {
|
||||
this.symbol = symbol;
|
||||
this.input = "1" + symbol + "2";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(NUMBER, "1"),
|
||||
matches(BINARY_OPERATOR, symbol),
|
||||
matches(NUMBER, "2")
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class UnaryOperators {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"+"},
|
||||
{"-"},
|
||||
});
|
||||
}
|
||||
|
||||
String symbol;
|
||||
String input;
|
||||
|
||||
public UnaryOperators(String symbol) {
|
||||
this.symbol = symbol;
|
||||
this.input = symbol + "1";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(UNARY_OPERATOR, symbol),
|
||||
matches(NUMBER, "1")
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class OperatorAmbiguity {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longestMatchInfix() {
|
||||
env.registerBinaryOperator("--", 2, true, null);
|
||||
|
||||
List<Lexeme> result = subject.tokenize("1---2");
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(NUMBER, "1"),
|
||||
matches(BINARY_OPERATOR, "--"),
|
||||
matches(UNARY_OPERATOR, "-"),
|
||||
matches(NUMBER, "2")
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longestMatchPrefix() {
|
||||
env.registerUnaryOperator("--", 4, null);
|
||||
|
||||
List<Lexeme> result = subject.tokenize("1---2");
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(NUMBER, "1"),
|
||||
matches(BINARY_OPERATOR, "-"),
|
||||
matches(UNARY_OPERATOR, "--"),
|
||||
matches(NUMBER, "2")
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longestMatchBoth() {
|
||||
env.registerUnaryOperator("--", 4, null);
|
||||
env.registerBinaryOperator("--", 2, true, null);
|
||||
|
||||
List<Lexeme> result = subject.tokenize("1---2");
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(NUMBER, "1"),
|
||||
matches(BINARY_OPERATOR, "--"),
|
||||
matches(UNARY_OPERATOR, "-"),
|
||||
matches(NUMBER, "2")
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class InvalidOperators {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidOperator() {
|
||||
assertThrows(
|
||||
LexerError.class,
|
||||
() -> subject.tokenize("1@2")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.garbagemule.MobArena.formula.LexemeMatcher.matches;
|
||||
import static com.garbagemule.MobArena.formula.TokenType.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class LexerParenthesisTest {
|
||||
|
||||
static Environment env;
|
||||
static Lexer subject;
|
||||
|
||||
public static class ParenthesizedExpressions {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parenthesizedPositiveNumberLiteral() {
|
||||
String input = "(2)";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "2"),
|
||||
matches(RIGHT_PAREN)
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parenthesizedNegativeNumberLiteral() {
|
||||
String input = "(-2)";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(LEFT_PAREN),
|
||||
matches(UNARY_OPERATOR, "-"),
|
||||
matches(NUMBER, "2"),
|
||||
matches(RIGHT_PAREN)
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negatedParenthesizedPositiveNumberLiteral() {
|
||||
String input = "-(2)";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(UNARY_OPERATOR, "-"),
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "2"),
|
||||
matches(RIGHT_PAREN)
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleExpression() {
|
||||
String input = "(2+3)";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "2"),
|
||||
matches(BINARY_OPERATOR, "+"),
|
||||
matches(NUMBER, "3"),
|
||||
matches(RIGHT_PAREN)
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedExpression() {
|
||||
String input = "(((2+3)))";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(LEFT_PAREN),
|
||||
matches(LEFT_PAREN),
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "2"),
|
||||
matches(BINARY_OPERATOR, "+"),
|
||||
matches(NUMBER, "3"),
|
||||
matches(RIGHT_PAREN),
|
||||
matches(RIGHT_PAREN),
|
||||
matches(RIGHT_PAREN)
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleExpressions() {
|
||||
String input = "(2+3)*-(4-5)^(6/7)";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "2"),
|
||||
matches(BINARY_OPERATOR, "+"),
|
||||
matches(NUMBER, "3"),
|
||||
matches(RIGHT_PAREN),
|
||||
matches(BINARY_OPERATOR, "*"),
|
||||
matches(UNARY_OPERATOR, "-"),
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "4"),
|
||||
matches(BINARY_OPERATOR, "-"),
|
||||
matches(NUMBER, "5"),
|
||||
matches(RIGHT_PAREN),
|
||||
matches(BINARY_OPERATOR, "^"),
|
||||
matches(LEFT_PAREN),
|
||||
matches(NUMBER, "6"),
|
||||
matches(BINARY_OPERATOR, "/"),
|
||||
matches(NUMBER, "7"),
|
||||
matches(RIGHT_PAREN)
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.garbagemule.MobArena.formula.LexemeMatcher.matches;
|
||||
import static com.garbagemule.MobArena.formula.TokenType.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
@RunWith(Enclosed.class)
|
||||
public class LexerVariableTest {
|
||||
|
||||
static Environment env;
|
||||
static Lexer subject;
|
||||
|
||||
public static class VariableExpressions {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleVariableExpression() {
|
||||
env.registerVariable("a", null);
|
||||
String input = "<a>";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(matches(VARIABLE, "a")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiVariableExpression() {
|
||||
env.registerVariable("a", null);
|
||||
env.registerVariable("b", null);
|
||||
String input = "<a>+<b>";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(VARIABLE, "a"),
|
||||
matches(BINARY_OPERATOR, "+"),
|
||||
matches(VARIABLE, "b")
|
||||
)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unknownVariable() {
|
||||
assertThrows(
|
||||
FormulaError.class,
|
||||
() -> subject.tokenize("<a>")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public static class InvalidVariables {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{"<"},
|
||||
{">"},
|
||||
{"<a"},
|
||||
{"a>"},
|
||||
{"<<a>"},
|
||||
{"<a>>"},
|
||||
{"<a >"},
|
||||
{"< a>"},
|
||||
});
|
||||
}
|
||||
|
||||
String input;
|
||||
|
||||
public InvalidVariables(String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
env.registerVariable("a", null);
|
||||
assertThrows(
|
||||
LexerError.class,
|
||||
() -> subject.tokenize(input)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.garbagemule.MobArena.formula;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.garbagemule.MobArena.formula.LexemeMatcher.matches;
|
||||
import static com.garbagemule.MobArena.formula.TokenType.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
|
||||
public class LexerWhitespaceTest {
|
||||
|
||||
Environment env;
|
||||
Lexer subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
env = Environment.createDefault();
|
||||
subject = new Lexer(env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresWhitespace() {
|
||||
String input = " 1+ 5 - 2\t ^ \n8";
|
||||
|
||||
List<Lexeme> result = subject.tokenize(input);
|
||||
|
||||
assertThat(result, contains(asList(
|
||||
matches(NUMBER, "1"),
|
||||
matches(BINARY_OPERATOR, "+"),
|
||||
matches(NUMBER, "5"),
|
||||
matches(BINARY_OPERATOR, "-"),
|
||||
matches(NUMBER, "2"),
|
||||
matches(BINARY_OPERATOR, "^"),
|
||||
matches(NUMBER, "8")
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
numbers:
|
||||
one: 1
|
||||
two: 2
|
||||
|
||||
constants:
|
||||
three-point-one-four: pi
|
||||
eulers-number: e
|
||||
|
||||
variables:
|
||||
live: <live-players>
|
||||
max: <max-players>
|
||||
|
||||
operators:
|
||||
two-plus-two: 2 + 2
|
||||
one-times-two: 1 * 2
|
||||
|
||||
functions:
|
||||
square-root: sqrt(9)
|
||||
maximum: max(1, 2)
|
||||
Reference in New Issue
Block a user