567 lines
17 KiB
Kotlin
567 lines
17 KiB
Kotlin
package io.github.kotlin.fibonacci
|
|
|
|
import kotlinx.coroutines.test.runTest
|
|
import net.sergeych.ling.*
|
|
import kotlin.math.PI
|
|
import kotlin.test.*
|
|
|
|
class ScriptTest {
|
|
|
|
@Test
|
|
fun parseNewlines() {
|
|
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
|
val source = src.toSource()
|
|
assertEquals(
|
|
Token(expected, source.posAt(row, col), type),
|
|
parseLing(source)[offset]
|
|
)
|
|
}
|
|
check("1", Token.Type.INT, 0, 0, "1 + x\n2", 0)
|
|
check("+", Token.Type.PLUS, 0, 2, "1 + x\n2", 1)
|
|
check("x", Token.Type.ID, 0, 4, "1 + x\n2", 2)
|
|
check("\n", Token.Type.NEWLINE, 0, 5, "1 + x\n2", 3)
|
|
// check("2", Token.Type.INT, 1, 0, "1 + x\n2", 4)
|
|
// check("", Token.Type.EOF, 1, 0, "1 + x\n2", 5)
|
|
|
|
}
|
|
|
|
@Test
|
|
fun parseNumbersTest() {
|
|
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {
|
|
val source = src.toSource()
|
|
assertEquals(
|
|
Token(expected, source.posAt(row, col), type),
|
|
parseLing(source)[offset]
|
|
)
|
|
}
|
|
check("1", Token.Type.INT, 0, 0, "1")
|
|
check("7", Token.Type.INT, 0, 0, "7")
|
|
check("17", Token.Type.INT, 0, 0, "17")
|
|
check("17", Token.Type.INT, 0, 0, "17.")
|
|
check(".", Token.Type.DOT, 0, 2, "17.", 1)
|
|
|
|
// decimals
|
|
check("17.2", Token.Type.REAL, 0, 0, "17.2")
|
|
check("17.2", Token.Type.REAL, 0, 0, "17.2")
|
|
check("17.2", Token.Type.REAL, 0, 0, "17.2 ")
|
|
check("17.2", Token.Type.REAL, 0, 1, " 17.2")
|
|
check("17.2", Token.Type.REAL, 0, 2, " 17.2 ")
|
|
check("17.2e0", Token.Type.REAL, 0, 0, "17.2e0")
|
|
check("17.2e-22", Token.Type.REAL, 0, 0, "17.2e-22")
|
|
check("17.2e22", Token.Type.REAL, 0, 0, "17.2e+22")
|
|
check("17.2e22", Token.Type.REAL, 0, 0, "17.2E+22")
|
|
check("17.2e22", Token.Type.REAL, 0, 0, "17.2E22")
|
|
check("17.2e-22", Token.Type.REAL, 0, 0, "17.2E-22")
|
|
|
|
// hex
|
|
check("1", Token.Type.HEX, 0, 0, "0x1")
|
|
check("12", Token.Type.HEX, 0, 0, "0x12")
|
|
check("12abcdef", Token.Type.HEX, 0, 0, "0x12abcdef.gh")
|
|
check(".", Token.Type.DOT, 0, 10, "0x12abcdef.gh", 1)
|
|
check("gh", Token.Type.ID, 0, 11, "0x12abcdef.gh", 2)
|
|
|
|
check("5", Token.Type.INT, 0, 0, "5 6")
|
|
check("6", Token.Type.INT, 0, 2, "5 6", 1)
|
|
|
|
}
|
|
|
|
@Test
|
|
fun parserLabelsTest() {
|
|
val src = "label@ break@label".toSource()
|
|
val tt = parseLing(src)
|
|
assertEquals(Token("label", src.posAt(0, 0), Token.Type.LABEL), tt[0])
|
|
assertEquals(Token("break", src.posAt(0, 7), Token.Type.ID), tt[1])
|
|
assertEquals(Token("label", src.posAt(0, 12), Token.Type.ATLABEL), tt[2])
|
|
}
|
|
|
|
@Test
|
|
fun parse0Test() {
|
|
val src = """
|
|
println("Hello")
|
|
println( "world" )
|
|
""".trimIndent().toSource()
|
|
|
|
val p = parseLing(src).listIterator()
|
|
|
|
assertEquals(Token("println", src.posAt(0, 0), Token.Type.ID), p.next())
|
|
assertEquals(Token("(", src.posAt(0, 7), Token.Type.LPAREN), p.next())
|
|
assertEquals(Token("Hello", src.posAt(0, 8), Token.Type.STRING), p.next())
|
|
assertEquals(Token(")", src.posAt(0, 15), Token.Type.RPAREN), p.next())
|
|
assertEquals(Token("\n", src.posAt(0, 16), Token.Type.NEWLINE), p.next())
|
|
assertEquals(Token("println", src.posAt(1, 0), Token.Type.ID), p.next())
|
|
assertEquals(Token("(", src.posAt(1, 7), Token.Type.LPAREN), p.next())
|
|
assertEquals(Token("world", src.posAt(1, 9), Token.Type.STRING), p.next())
|
|
assertEquals(Token(")", src.posAt(1, 17), Token.Type.RPAREN), p.next())
|
|
}
|
|
|
|
@Test
|
|
fun parse1Test() {
|
|
val src = "2 + 7".toSource()
|
|
|
|
val p = parseLing(src).listIterator()
|
|
|
|
assertEquals(Token("2", src.posAt(0, 0), Token.Type.INT), p.next())
|
|
assertEquals(Token("+", src.posAt(0, 2), Token.Type.PLUS), p.next())
|
|
assertEquals(Token("7", src.posAt(0, 4), Token.Type.INT), p.next())
|
|
}
|
|
|
|
@Test
|
|
fun compileNumbersTest() = runTest {
|
|
assertEquals(ObjInt(17), eval("17"))
|
|
assertEquals(ObjInt(17), eval("+17"))
|
|
assertEquals(ObjInt(-17), eval("-17"))
|
|
|
|
|
|
assertEquals(ObjInt(1970), eval("1900 + 70"))
|
|
assertEquals(ObjInt(1970), eval("2000 - 30"))
|
|
|
|
// assertEquals(ObjReal(3.14), eval("3.14"))
|
|
assertEquals(ObjReal(314.0), eval("3.14e2"))
|
|
assertEquals(ObjReal(314.0), eval("100 3.14e2"))
|
|
assertEquals(ObjReal(314.0), eval("100\n 3.14e2"))
|
|
}
|
|
|
|
@Test
|
|
fun compileBuiltinCallsTest() = runTest {
|
|
// println(eval("π"))
|
|
val pi = eval("Math.PI")
|
|
assertIs<ObjReal>(pi)
|
|
assertTrue(pi.value - PI < 0.000001)
|
|
assertTrue(eval("Math.PI+1").toDouble() - PI - 1.0 < 0.000001)
|
|
|
|
assertTrue(eval("sin(Math.PI)").toDouble() - 1 < 0.000001)
|
|
assertTrue(eval("sin(π)").toDouble() - 1 < 0.000001)
|
|
}
|
|
|
|
@Test
|
|
fun varsAndConstsTest() = runTest {
|
|
val context = Context(pos = Pos.builtIn)
|
|
assertEquals(
|
|
ObjVoid, context.eval(
|
|
"""
|
|
val a = 17
|
|
var b = 3
|
|
""".trimIndent()
|
|
)
|
|
)
|
|
assertEquals(17, context.eval("a").toInt())
|
|
assertEquals(20, context.eval("b + a").toInt())
|
|
assertFailsWith<ScriptError> {
|
|
context.eval("a = 10")
|
|
}
|
|
assertEquals(10, context.eval("b = a - 3 - 4; b").toInt())
|
|
assertEquals(10, context.eval("b").toInt())
|
|
}
|
|
|
|
@Test
|
|
fun functionTest() = runTest {
|
|
val context = Context(pos = Pos.builtIn)
|
|
context.eval(
|
|
"""
|
|
fun foo(a, b) {
|
|
a + b
|
|
}
|
|
""".trimIndent()
|
|
)
|
|
assertEquals(17, context.eval("foo(3,14)").toInt())
|
|
assertFailsWith<ScriptError> {
|
|
assertEquals(17, context.eval("foo(3)").toInt())
|
|
}
|
|
|
|
context.eval(
|
|
"""
|
|
fn bar(a, b=10) {
|
|
a + b + 1
|
|
}
|
|
""".trimIndent()
|
|
)
|
|
assertEquals(10, context.eval("bar(3, 6)").toInt())
|
|
assertEquals(14, context.eval("bar(3)").toInt())
|
|
}
|
|
|
|
@Test
|
|
fun simpleClosureTest() = runTest {
|
|
val context = Context(pos = Pos.builtIn)
|
|
context.eval(
|
|
"""
|
|
var global = 10
|
|
|
|
fun foo(a, b) {
|
|
global + a + b
|
|
}
|
|
""".trimIndent()
|
|
)
|
|
assertEquals(27, context.eval("foo(3,14)").toInt())
|
|
context.eval("global = 20")
|
|
assertEquals(37, context.eval("foo(3,14)").toInt())
|
|
}
|
|
|
|
@Test
|
|
fun nullAndVoidTest() = runTest {
|
|
val context = Context(pos = Pos.builtIn)
|
|
assertEquals(ObjVoid, context.eval("void"))
|
|
assertEquals(ObjNull, context.eval("null"))
|
|
}
|
|
|
|
@Test
|
|
fun arithmeticOperatorsTest() = runTest {
|
|
assertEquals(2, eval("5/2").toInt())
|
|
assertEquals(2.5, eval("5.0/2").toDouble())
|
|
assertEquals(2.5, eval("5/2.0").toDouble())
|
|
assertEquals(2.5, eval("5.0/2.0").toDouble())
|
|
|
|
assertEquals(1, eval("5%2").toInt())
|
|
assertEquals(1.0, eval("5.0%2").toDouble())
|
|
|
|
assertEquals(77, eval("11 * 7").toInt())
|
|
|
|
assertEquals(2.0, eval("floor(5.0/2)").toDouble())
|
|
assertEquals(3, eval("ceil(5.0/2)").toInt())
|
|
|
|
assertEquals(2.0, eval("round(4.7/2)").toDouble())
|
|
assertEquals(3.0, eval("round(5.1/2)").toDouble())
|
|
}
|
|
|
|
@Test
|
|
fun arithmeticParenthesisTest() = runTest {
|
|
assertEquals(17, eval("2 + 3 * 5").toInt())
|
|
assertEquals(17, eval("2 + (3 * 5)").toInt())
|
|
assertEquals(25, eval("(2 + 3) * 5").toInt())
|
|
assertEquals(24, eval("(2 + 3) * 5 -1").toInt())
|
|
}
|
|
|
|
@Test
|
|
fun stringOpTest() = runTest {
|
|
assertEquals("foobar", eval(""" "foo" + "bar" """).toString())
|
|
assertEquals("foo17", eval(""" "foo" + 17 """).toString())
|
|
}
|
|
|
|
@Test
|
|
fun eqNeqTest() = runTest {
|
|
assertEquals(ObjBool(true), eval("val x = 2; x == 2"))
|
|
assertEquals(ObjBool(false), eval("val x = 3; x == 2"))
|
|
assertEquals(ObjBool(true), eval("val x = 3; x != 2"))
|
|
assertEquals(ObjBool(false), eval("val x = 3; x != 3"))
|
|
|
|
assertTrue { eval("1 == 1").toBool() }
|
|
assertTrue { eval("true == true").toBool() }
|
|
assertTrue { eval("true != false").toBool() }
|
|
assertFalse { eval("true == false").toBool() }
|
|
assertFalse { eval("false != false").toBool() }
|
|
|
|
assertTrue { eval("2 == 2 && 3 != 4").toBool() }
|
|
}
|
|
|
|
@Test
|
|
fun gtLtTest() = runTest {
|
|
assertTrue { eval("3 > 2").toBool() }
|
|
assertFalse { eval("3 > 3").toBool() }
|
|
assertTrue { eval("3 >= 2").toBool() }
|
|
assertFalse { eval("3 >= 4").toBool() }
|
|
assertFalse { eval("3 < 2").toBool() }
|
|
assertFalse { eval("3 <= 2").toBool() }
|
|
assertTrue { eval("3 <= 3").toBool() }
|
|
assertTrue { eval("3 <= 4").toBool() }
|
|
assertTrue { eval("3 < 4").toBool() }
|
|
assertFalse { eval("4 < 3").toBool() }
|
|
assertFalse { eval("4 <= 3").toBool() }
|
|
}
|
|
|
|
@Test
|
|
fun ifTest() = runTest {
|
|
// if - single line
|
|
var context = Context(pos = Pos.builtIn)
|
|
context.eval(
|
|
"""
|
|
fn test1(n) {
|
|
var result = "more"
|
|
if( n >= 10 )
|
|
result = "enough"
|
|
result
|
|
}
|
|
""".trimIndent()
|
|
)
|
|
assertEquals("enough", context.eval("test1(11)").toString())
|
|
assertEquals("more", context.eval("test1(1)").toString())
|
|
|
|
// if - multiline (block)
|
|
context = Context(pos = Pos.builtIn)
|
|
context.eval(
|
|
"""
|
|
fn test1(n) {
|
|
var prefix = "answer: "
|
|
var result = "more"
|
|
if( n >= 10 ) {
|
|
var prefix = "bad:" // local prefix
|
|
prefix = "too bad:"
|
|
result = "enough"
|
|
}
|
|
prefix + result
|
|
}
|
|
""".trimIndent()
|
|
)
|
|
assertEquals("answer: enough", context.eval("test1(11)").toString())
|
|
assertEquals("answer: more", context.eval("test1(1)").toString())
|
|
|
|
// else single line1
|
|
context = Context(pos = Pos.builtIn)
|
|
context.eval(
|
|
"""
|
|
fn test1(n) {
|
|
if( n >= 10 )
|
|
"enough"
|
|
else
|
|
"more"
|
|
}
|
|
""".trimIndent()
|
|
)
|
|
assertEquals("enough", context.eval("test1(11)").toString())
|
|
assertEquals("more", context.eval("test1(1)").toString())
|
|
|
|
// if/else with blocks
|
|
context = Context(pos = Pos.builtIn)
|
|
context.eval(
|
|
"""
|
|
fn test1(n) {
|
|
if( n > 20 ) {
|
|
"too much"
|
|
} else if( n >= 10 ) {
|
|
"enough"
|
|
}
|
|
else {
|
|
"more"
|
|
}
|
|
}
|
|
""".trimIndent()
|
|
)
|
|
assertEquals("enough", context.eval("test1(11)").toString())
|
|
assertEquals("more", context.eval("test1(1)").toString())
|
|
assertEquals("too much", context.eval("test1(100)").toString())
|
|
}
|
|
|
|
@Test
|
|
fun lateInitTest() = runTest {
|
|
assertEquals(
|
|
"ok", eval(
|
|
"""
|
|
|
|
var late
|
|
|
|
fun init() {
|
|
late = "ok"
|
|
}
|
|
|
|
init()
|
|
late
|
|
""".trimIndent()
|
|
).toString()
|
|
)
|
|
|
|
}
|
|
|
|
@Test
|
|
fun whileTest() = runTest {
|
|
assertEquals(
|
|
5.0,
|
|
eval(
|
|
"""
|
|
var acc = 0
|
|
while( acc < 5 ) acc = acc + 0.5
|
|
acc
|
|
"""
|
|
).toDouble()
|
|
)
|
|
assertEquals(
|
|
5.0,
|
|
eval(
|
|
"""
|
|
var acc = 0
|
|
// return from while
|
|
while( acc < 5 ) {
|
|
acc = acc + 0.5
|
|
acc
|
|
}
|
|
"""
|
|
).toDouble()
|
|
)
|
|
assertEquals(
|
|
3.0,
|
|
eval(
|
|
"""
|
|
var acc = 0
|
|
while( acc < 5 ) {
|
|
acc = acc + 0.5
|
|
if( acc >= 3 ) break
|
|
}
|
|
|
|
acc
|
|
|
|
"""
|
|
).toDouble()
|
|
)
|
|
assertEquals(
|
|
17.0,
|
|
eval(
|
|
"""
|
|
var acc = 0
|
|
while( acc < 5 ) {
|
|
acc = acc + 0.5
|
|
if( acc >= 3 ) break 17
|
|
}
|
|
"""
|
|
).toDouble()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun whileNonLocalBreakTest() = runTest {
|
|
assertEquals(
|
|
"ok2:3:7", eval(
|
|
"""
|
|
var t1 = 10
|
|
outer@ while( t1 > 0 ) {
|
|
var t2 = 10
|
|
while( t2 > 0 ) {
|
|
t2 = t2 - 1
|
|
if( t2 == 3 && t1 == 7) {
|
|
break@outer "ok2:"+t2+":"+t1
|
|
}
|
|
}
|
|
t1 = t1 - 1
|
|
t1
|
|
}
|
|
""".trimIndent()
|
|
).toString()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun bookTest0() = runTest {
|
|
assertEquals(
|
|
"just 3",
|
|
eval(
|
|
"""
|
|
val count = 3
|
|
val res = if( count > 10 ) "too much" else "just " + count
|
|
println(count)
|
|
println(res)
|
|
res
|
|
""".trimIndent()
|
|
)
|
|
.toString()
|
|
)
|
|
assertEquals(
|
|
"just 3",
|
|
eval(
|
|
"""
|
|
val count = 3
|
|
var res = if( count > 10 ) "too much" else "it's " + count
|
|
res = if( count > 10 ) "too much" else "just " + count
|
|
res
|
|
""".trimIndent()
|
|
)
|
|
.toString()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun testIncr() = runTest {
|
|
val c = Context()
|
|
c.eval("var x = 10")
|
|
assertEquals(10, c.eval("x++").toInt())
|
|
assertEquals(11, c.eval("x++").toInt())
|
|
assertEquals(12, c.eval("x").toInt())
|
|
|
|
assertEquals(12, c.eval("x").toInt())
|
|
assertEquals(12, c.eval("x").toInt())
|
|
}
|
|
|
|
@Test
|
|
fun testDecr() = runTest {
|
|
val c = Context()
|
|
c.eval("var x = 9")
|
|
assertEquals(9, c.eval("x--").toInt())
|
|
assertEquals(8, c.eval("x--").toInt())
|
|
assertEquals(7, c.eval("x--").toInt())
|
|
assertEquals(6, c.eval("x--").toInt())
|
|
assertEquals(5, c.eval("x").toInt())
|
|
}
|
|
|
|
@Test
|
|
fun testDecrIncr() = runTest {
|
|
val c = Context()
|
|
c.eval("var x = 9")
|
|
assertEquals(9, c.eval("x++").toInt())
|
|
assertEquals(10, c.eval("x++").toInt())
|
|
assertEquals(11, c.eval("x").toInt())
|
|
assertEquals(11, c.eval("x--").toInt())
|
|
assertEquals(10, c.eval("x--").toInt())
|
|
assertEquals(9, c.eval("x--").toInt())
|
|
assertEquals(8, c.eval("x--").toInt())
|
|
assertEquals(7, c.eval("x + 0").toInt())
|
|
}
|
|
|
|
@Test
|
|
fun testDecrIncr2() = runTest {
|
|
val c = Context()
|
|
c.eval("var x = 9")
|
|
assertEquals(9, c.eval("x--").toInt())
|
|
assertEquals(8, c.eval("x--").toInt())
|
|
assertEquals(7, c.eval("x--").toInt())
|
|
assertEquals(6, c.eval("x").toInt())
|
|
assertEquals(6, c.eval("x++").toInt())
|
|
assertEquals(7, c.eval("x++").toInt())
|
|
assertEquals(8, c.eval("x")
|
|
.also {
|
|
println("${it.toDouble()} ${it.toInt()} ${it.toLong()} ${it.toInt()}")
|
|
}
|
|
.toInt())
|
|
}
|
|
|
|
@Test
|
|
fun testDecrIncr3() = runTest {
|
|
val c = Context()
|
|
c.eval("var x = 9")
|
|
assertEquals(9, c.eval("x++").toInt())
|
|
assertEquals(10, c.eval("x++").toInt())
|
|
assertEquals(11, c.eval("x++").toInt())
|
|
assertEquals(12, c.eval("x").toInt())
|
|
assertEquals(12, c.eval("x--").toInt())
|
|
assertEquals(11, c.eval("x").toInt())
|
|
}
|
|
|
|
@Test
|
|
fun testIncrAndDecr() = runTest {
|
|
val c = Context()
|
|
assertEquals(
|
|
"8", c.eval(
|
|
"""
|
|
var x = 5
|
|
x--
|
|
x--
|
|
x++
|
|
x * 2
|
|
"""
|
|
).toString()
|
|
)
|
|
|
|
assertEquals("4", c.eval("x").toString())
|
|
// assertEquals( "8", c.eval("x*2").toString())
|
|
// assertEquals( "4", c.eval("x+0").toString())
|
|
}
|
|
|
|
@Test
|
|
fun bookTest2() = runTest {
|
|
val src = """
|
|
fn check(amount, prefix = "answer: ") {
|
|
prefix + if( amount > 100 )
|
|
"enough"
|
|
else
|
|
"more"
|
|
|
|
}
|
|
""".trimIndent()
|
|
eval(src)
|
|
}
|
|
|
|
} |