prog8/compiler/test/TestPipes.kt

513 lines
19 KiB
Kotlin
Raw Normal View History

2022-01-06 21:45:36 +00:00
package prog8tests
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
2022-02-27 15:27:02 +00:00
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.FunctionCallStatement
2022-01-06 21:45:36 +00:00
import prog8.ast.statements.Pipe
2022-02-27 15:27:02 +00:00
import prog8.ast.statements.VarDecl
import prog8.code.core.DataType
import prog8.code.core.Position
2022-03-11 19:35:25 +00:00
import prog8.code.target.C64Target
2022-02-27 15:27:02 +00:00
import prog8.compiler.astprocessing.AstPreprocessor
import prog8.parser.Prog8Parser.parseModule
2022-03-21 00:01:21 +00:00
import prog8.code.core.SourceCode
import prog8.code.target.VMTarget
2022-02-27 15:27:02 +00:00
import prog8tests.helpers.*
2022-01-06 21:45:36 +00:00
class TestPipes: FunSpec({
2022-02-27 15:27:02 +00:00
test("pipe expression parse tree after preprocessing") {
val text = """
main {
sub start() {
uword xx = 9999 |> func1() |> func2()
|> func1() |> func2()
|> func1()
}
sub func1(uword arg) -> uword {
return arg+1111
}
sub func2(uword arg) -> uword {
return arg+2222
}
2022-03-02 20:29:09 +00:00
}"""
2022-02-27 15:27:02 +00:00
val src = SourceCode.Text(text)
val module = parseModule(src)
val errors = ErrorReporterForTests()
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val preprocess = AstPreprocessor(program, errors, C64Target())
preprocess.visit(program)
errors.errors.size shouldBe 0
preprocess.applyModifications()
program.entrypoint.statements.size shouldBe 1
val pipe = (program.entrypoint.statements.single() as VarDecl).value as PipeExpression
pipe.source shouldBe NumericLiteral(DataType.UWORD, 9999.0, Position.DUMMY)
pipe.segments.size shouldBe 5
var call = pipe.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[2] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[3] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[4] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
}
test("pipe statement parse tree after preprocessing") {
val text = """
main {
sub start() {
9999 |> func1() |> func2()
|> func1() |> func2()
|> func3()
}
sub func1(uword arg) -> uword {
return arg+1111
}
sub func2(uword arg) -> uword {
return arg+2222
}
sub func3(uword arg) {
; nothing
}
2022-03-02 20:29:09 +00:00
}"""
2022-02-27 15:27:02 +00:00
val src = SourceCode.Text(text)
val module = parseModule(src)
val errors = ErrorReporterForTests()
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val preprocess = AstPreprocessor(program, errors, C64Target())
preprocess.visit(program)
errors.errors.size shouldBe 0
preprocess.applyModifications()
program.entrypoint.statements.size shouldBe 1
val pipe = program.entrypoint.statements.single() as Pipe
pipe.source shouldBe NumericLiteral(DataType.UWORD, 9999.0, Position.DUMMY)
pipe.segments.size shouldBe 5
var call = pipe.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[2] as IFunctionCall
call.target.nameInSource shouldBe listOf("func1")
call.args.size shouldBe 0
call = pipe.segments[3] as IFunctionCall
call.target.nameInSource shouldBe listOf("func2")
call.args.size shouldBe 0
call = pipe.segments[4] as IFunctionCall
call.target.nameInSource shouldBe listOf("func3")
call.args.size shouldBe 0
}
test("correct pipe statements (no opt)") {
2022-01-06 21:45:36 +00:00
val text = """
%import floats
%import textio
main {
sub start() {
2022-02-24 22:35:16 +00:00
1.234 |> addfloat()
|> floats.print_f()
2022-01-06 21:45:36 +00:00
2022-03-02 20:29:09 +00:00
startvalue(99) |> addword()
2022-02-24 22:35:16 +00:00
|> txt.print_uw()
2022-02-24 22:35:16 +00:00
9999 |> abs() |> txt.print_uw()
9999 |> txt.print_uw()
99 |> abs() |> lsb() |> txt.print_ub()
2022-02-24 22:35:16 +00:00
99 |> txt.print_ub()
2022-01-06 21:45:36 +00:00
}
2022-03-02 20:29:09 +00:00
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
2022-01-06 21:45:36 +00:00
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
2022-03-02 20:29:09 +00:00
}"""
2022-03-07 20:41:12 +00:00
val result = compileText(C64Target(), optimize = false, text, writeAssembly = true)!!
2022-01-06 21:45:36 +00:00
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
2022-01-06 21:45:36 +00:00
val pipef = stmts[0] as Pipe
2022-02-24 22:35:16 +00:00
pipef.source shouldBe instanceOf<NumericLiteral>()
pipef.segments.size shouldBe 2
2022-02-27 15:27:02 +00:00
var call = pipef.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
call = pipef.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("floats", "print_f")
2022-01-06 21:45:36 +00:00
val pipew = stmts[1] as Pipe
2022-02-24 22:35:16 +00:00
pipef.source shouldBe instanceOf<NumericLiteral>()
pipew.segments.size shouldBe 2
2022-02-27 15:27:02 +00:00
call = pipew.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
call = pipew.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("txt", "print_uw")
stmts[2] shouldBe instanceOf<Pipe>()
stmts[3] shouldBe instanceOf<Pipe>()
stmts[4] shouldBe instanceOf<Pipe>()
stmts[5] shouldBe instanceOf<Pipe>()
}
test("correct pipe statements (with opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
1.234 |> addfloat()
|> floats.print_f()
2022-03-02 20:29:09 +00:00
startvalue(99) |> addword()
2022-02-27 15:27:02 +00:00
|> txt.print_uw()
; these should be optimized into just the function calls:
9999 |> abs() |> txt.print_uw()
9999 |> txt.print_uw()
99 |> abs() |> txt.print_ub()
99 |> txt.print_ub()
}
2022-03-02 20:29:09 +00:00
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
2022-02-27 15:27:02 +00:00
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
2022-03-02 20:29:09 +00:00
}"""
2022-03-07 20:41:12 +00:00
val result = compileText(C64Target(), optimize = true, text, writeAssembly = true)!!
2022-02-27 15:27:02 +00:00
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
val pipef = stmts[0] as Pipe
pipef.source shouldBe instanceOf<FunctionCallExpression>()
(pipef.source as IFunctionCall).target.nameInSource shouldBe listOf("addfloat")
pipef.segments.size shouldBe 1
val callf = pipef.segments[0] as IFunctionCall
callf.target.nameInSource shouldBe listOf("floats", "print_f")
val pipew = stmts[1] as Pipe
pipef.source shouldBe instanceOf<FunctionCallExpression>()
2022-03-02 20:29:09 +00:00
(pipew.source as IFunctionCall).target.nameInSource shouldBe listOf("startvalue")
pipew.segments.size shouldBe 2
val callw = pipew.segments[1] as IFunctionCall
2022-02-27 15:27:02 +00:00
callw.target.nameInSource shouldBe listOf("txt", "print_uw")
var stmt = stmts[2] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_uw")
stmt = stmts[3] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_uw")
stmt = stmts[4] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_ub")
stmt = stmts[5] as FunctionCallStatement
stmt.target.nameInSource shouldBe listOf("txt", "print_ub")
2022-01-06 21:45:36 +00:00
}
test("incorrect type in pipe statement") {
2022-01-06 21:45:36 +00:00
val text = """
%option enable_floats
main {
sub start() {
2022-02-27 15:27:02 +00:00
1.234 |> addfloat()
|> addword() |> addword()
2022-01-06 21:45:36 +00:00
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
2022-03-02 20:29:09 +00:00
}"""
2022-01-06 21:45:36 +00:00
val errors = ErrorReporterForTests()
2022-03-07 20:41:12 +00:00
compileText(C64Target(), false, text, errors=errors) shouldBe null
2022-01-06 21:45:36 +00:00
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "incompatible"
}
2022-02-27 15:27:02 +00:00
test("correct pipe expressions (no opt)") {
val text = """
%import floats
%import textio
main {
sub start() {
2022-02-27 15:27:02 +00:00
float @shared fl = 1.234 |> addfloat()
|> addfloat()
2022-03-02 20:29:09 +00:00
uword @shared ww = startvalue(99) |> addword()
2022-02-27 15:27:02 +00:00
|> addword()
ubyte @shared cc = 30 |> abs() |> sqrt16()
cc = cc |> abs() |> sqrt16()
}
2022-03-02 20:29:09 +00:00
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
2022-03-02 20:29:09 +00:00
}"""
2022-03-07 20:41:12 +00:00
val result = compileText(C64Target(), optimize = false, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 8
val assignf = stmts[1] as Assignment
val pipef = assignf.value as PipeExpression
2022-02-24 22:35:16 +00:00
pipef.source shouldBe instanceOf<NumericLiteral>()
pipef.segments.size shouldBe 2
2022-02-27 15:27:02 +00:00
var call = pipef.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
call = pipef.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("addfloat")
val assignw = stmts[3] as Assignment
val pipew = assignw.value as PipeExpression
2022-03-02 20:29:09 +00:00
pipew.source shouldBe instanceOf<IFunctionCall>()
2022-02-24 22:35:16 +00:00
pipew.segments.size shouldBe 2
2022-02-27 15:27:02 +00:00
call = pipew.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
call = pipew.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("addword")
var assigncc = stmts[5] as Assignment
2022-02-27 15:27:02 +00:00
val value = assigncc.value as PipeExpression
value.source shouldBe NumericLiteral(DataType.UBYTE, 30.0, Position.DUMMY)
value.segments.size shouldBe 2
call = value.segments[0] as IFunctionCall
call.target.nameInSource shouldBe listOf("abs")
2022-02-27 15:27:02 +00:00
call = value.segments[1] as IFunctionCall
call.target.nameInSource shouldBe listOf("sqrt16")
assigncc = stmts[6] as Assignment
val pipecc = assigncc.value as PipeExpression
2022-02-27 15:27:02 +00:00
pipecc.source shouldBe instanceOf<IdentifierReference>()
2022-02-24 22:35:16 +00:00
pipecc.segments.size shouldBe 2
pipecc.segments[0] shouldBe instanceOf<BuiltinFunctionCall>()
2022-02-27 15:27:02 +00:00
pipecc.segments[1] shouldBe instanceOf<BuiltinFunctionCall>()
}
2022-02-27 15:27:02 +00:00
test("correct pipe expressions (with opt)") {
val text = """
2022-02-27 15:27:02 +00:00
%import floats
%import textio
main {
sub start() {
2022-02-27 15:27:02 +00:00
float @shared fl = 1.234 |> addfloat()
|> addfloat()
2022-03-02 20:29:09 +00:00
uword @shared ww = startvalue(99) |> addword()
2022-02-27 15:27:02 +00:00
|> addword()
ubyte @shared cc = 80 |> abs() |> sqrt16() ; will be optimized away into a const number
cc = cc |> abs() |> sqrt16()
2022-02-27 15:27:02 +00:00
}
2022-03-02 20:29:09 +00:00
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
2022-02-27 15:27:02 +00:00
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
2022-03-02 20:29:09 +00:00
}
"""
2022-03-07 20:41:12 +00:00
val result = compileText(C64Target(), optimize = true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
2022-02-27 15:27:02 +00:00
stmts.size shouldBe 8
val assignf = stmts[1] as Assignment
val pipef = assignf.value as PipeExpression
pipef.source shouldBe instanceOf<FunctionCallExpression>()
pipef.segments.size shouldBe 1
pipef.segments[0] shouldBe instanceOf<FunctionCallExpression>()
2022-02-27 15:27:02 +00:00
val assignw = stmts[3] as Assignment
val pipew = assignw.value as PipeExpression
2022-02-27 15:27:02 +00:00
pipew.source shouldBe instanceOf<FunctionCallExpression>()
2022-03-02 20:29:09 +00:00
pipew.segments.size shouldBe 2
2022-02-24 22:35:16 +00:00
pipew.segments[0] shouldBe instanceOf<FunctionCallExpression>()
2022-03-02 20:29:09 +00:00
pipew.segments[1] shouldBe instanceOf<FunctionCallExpression>()
2022-02-27 15:27:02 +00:00
var assigncc = stmts[5] as Assignment
val value = assigncc.value as NumericLiteral
value.number shouldBe 8.0
2022-02-27 15:27:02 +00:00
assigncc = stmts[6] as Assignment
val pipecc = assigncc.value as PipeExpression
pipecc.source shouldBe instanceOf<BuiltinFunctionCall>()
(pipecc.source as BuiltinFunctionCall).target.nameInSource shouldBe listOf("abs")
2022-02-27 15:27:02 +00:00
pipecc.segments.size shouldBe 1
pipecc.segments[0] shouldBe instanceOf<BuiltinFunctionCall>()
(pipecc.segments[0] as BuiltinFunctionCall).target.nameInSource shouldBe listOf("sqrt16")
}
test("incorrect type in pipe expression") {
val text = """
%option enable_floats
main {
sub start() {
2022-02-27 15:27:02 +00:00
uword result = 1.234 |> addfloat()
|> addword() |> addword()
}
sub addfloat(float fl) -> float {
return fl+2.22
}
sub addword(uword ww) -> uword {
return ww+2222
}
}
"""
val errors = ErrorReporterForTests()
2022-03-07 20:41:12 +00:00
compileText(C64Target(), false, text, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "incompatible"
}
test("correct pipe statement with builtin expression") {
val text = """
%import textio
main {
sub start() {
uword ww = 9999
ubyte bb = 99
2022-02-27 15:27:02 +00:00
ww |> abs() |> txt.print_uw()
bb |> abs() |> lsb() |> txt.print_ub()
}
}
"""
2022-03-07 20:41:12 +00:00
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.program.entrypoint.statements
stmts.size shouldBe 7
2022-02-27 15:27:02 +00:00
val pipeww = stmts[4] as Pipe
pipeww.source shouldBe instanceOf<BuiltinFunctionCall>()
pipeww.segments.size shouldBe 1
pipeww.segments[0] shouldBe instanceOf<IFunctionCall>()
2022-02-27 15:27:02 +00:00
val pipebb = stmts[5] as Pipe
pipebb.source shouldBe instanceOf<BuiltinFunctionCall>()
pipebb.segments.size shouldBe 2
2022-02-27 15:27:02 +00:00
pipebb.segments[0] shouldBe instanceOf<IFunctionCall>()
pipebb.segments[1] shouldBe instanceOf<IFunctionCall>()
}
test("pipe statement with type errors") {
val text = """
%import textio
main {
sub start() {
uword ww = 9999
2022-02-27 15:27:02 +00:00
9999 |> abs() |> txt.print_ub()
ww |> abs() |> txt.print_ub()
}
}
"""
val errors = ErrorReporterForTests()
2022-03-07 20:41:12 +00:00
compileText(C64Target(), optimize = false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "UWORD incompatible"
errors.errors[1] shouldContain "UWORD incompatible"
}
2022-03-02 20:29:09 +00:00
test("pipe detects invalid number of args") {
val text = """
main {
sub start() {
uword ww = startvalue() |> addword()
|> addword()
ubyte cc = 30 |> abs(99) |> sqrt16(22)
2022-03-02 20:29:09 +00:00
}
sub startvalue(ubyte arg) -> uword {
return arg+9999
}
sub addword(uword ww) -> uword {
return ww+2222
}
}"""
val errors = ErrorReporterForTests()
2022-03-07 20:41:12 +00:00
compileText(C64Target(), optimize = false, text, writeAssembly = false, errors=errors) shouldBe null
2022-03-02 20:29:09 +00:00
errors.errors.size shouldBe 3
errors.errors[0] shouldContain ":4:32: invalid number of arguments"
errors.errors[1] shouldContain ":7:42: invalid number of arguments"
errors.errors[2] shouldContain ":7:56: invalid number of arguments"
2022-03-02 20:29:09 +00:00
}
test("non-unary funcions in pipe ok for target virtual") {
val text = """
main {
sub start() {
uword @shared wvalue = add(3,4) |> add(48) |> mkword(234)
}
sub add(ubyte first, ubyte second) -> ubyte {
return first+second
}
}"""
val errors = ErrorReporterForTests()
val result = compileText(VMTarget(), optimize = false, text, writeAssembly = true, errors=errors)!!
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 0
result.program.entrypoint.statements.size shouldBe 3
}
test("non-unary funcions in pipe not yet ok for other targets") {
// NOTE: once other targets also support this, merge this into the test above
val text = """
main {
sub start() {
uword @shared wvalue = add(3,4) |> add(48) |> mkword(234)
}
sub add(ubyte first, ubyte second) -> ubyte {
return first+second
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), optimize = false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "only unary"
errors.errors[1] shouldContain "only unary"
}
2022-01-06 21:45:36 +00:00
})