diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index c8e583b8a..0c67d5ff8 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1285,11 +1285,11 @@ internal class AstChecker(private val program: Program, for(expr in expressions.drop(1)) { // just keep the first expression value as-is val functionName = expr as? IdentifierReference - val function = functionName?.targetStatement(program) - if(functionName!=null && function!=null) { - when (function) { + val target = functionName?.targetStatement(program) + if(functionName!=null && target!=null) { + when (target) { is BuiltinFunctionPlaceholder -> { - val func = BuiltinFunctions.getValue(function.name) + val func = BuiltinFunctions.getValue(target.name) if(func.parameters.size!=1) errors.err("can only use unary function", expr.position) else if(!func.hasReturn && expr !== expressions.last()) @@ -1307,17 +1307,21 @@ internal class AstChecker(private val program: Program, } } is Subroutine -> { - if(function.parameters.size!=1) + if(target.parameters.size!=1) errors.err("can only use unary function", expr.position) - else if(function.returntypes.size!=1 && expr !== expressions.last()) + else if(target.returntypes.size!=1 && expr !== expressions.last()) errors.err("function must return a single value", expr.position) - val paramDt = function.parameters.firstOrNull()?.type + val paramDt = target.parameters.firstOrNull()?.type if(paramDt!=null && !(valueDt isAssignableTo paramDt)) errors.err("pipe value datatype $valueDt incompatible with function argument $paramDt", functionName.position) - if(function.returntypes.isNotEmpty()) - valueDt = function.returntypes.single() + if(target.returntypes.isNotEmpty()) + valueDt = target.returntypes.single() + } + is VarDecl -> { + if(!(valueDt isAssignableTo target.datatype)) + errors.err("final pipe value datatype can't be stored in pipe ending variable", functionName.position) } else -> { throw FatalAstException("weird function") diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index acc7ac861..5064f59e4 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -412,6 +412,26 @@ internal class StatementReorderer(val program: Program, checkUnusedReturnValues(functionCallStatement, function, program, errors) return tryReplaceCallWithGosub(functionCallStatement, parent, program, options) } + + override fun after(pipe: Pipe, parent: Node): Iterable { + val last = pipe.expressions.lastOrNull() as? IdentifierReference + val variable = last?.targetVarDecl(program) + if(variable!=null) { + val target = AssignTarget(last, null, null, last.position) + if(pipe.expressions.size>2) + { + val value = PipeExpression(pipe.expressions.dropLast(1).toMutableList(), pipe.position) + val assign = Assignment(target, value, AssignmentOrigin.OPTIMIZER, pipe.position) + return listOf(IAstModification.ReplaceNode(pipe, assign, parent)) + } + else if(pipe.expressions.size==2) + { + val assign = Assignment(target, pipe.expressions[0], AssignmentOrigin.OPTIMIZER, pipe.position) + return listOf(IAstModification.ReplaceNode(pipe, assign, parent)) + } + } + return noModifications + } } diff --git a/compiler/test/TestPipes.kt b/compiler/test/TestPipes.kt index c8adf80d4..29e1f0653 100644 --- a/compiler/test/TestPipes.kt +++ b/compiler/test/TestPipes.kt @@ -142,12 +142,44 @@ class TestPipes: FunSpec({ value.number shouldBe 194.0 assigncc = stmts[6] as Assignment - val pipecc = assignw.value as PipeExpression + val pipecc = assigncc.value as PipeExpression pipecc.expressions.size shouldBe 2 - pipecc.expressions[0] shouldBe instanceOf() + pipecc.expressions[0] shouldBe instanceOf() pipecc.expressions[1] shouldBe instanceOf() } + test("correct pipe expressions with variables at end") { + val text = """ + %import textio + + main { + sub start() { + uword @shared ww + ubyte @shared cc + + 9999 |> addword |> addword |> ww + 30 |> sin8u |> cos8u |> cc ; will be optimized away into a const number + } + sub addword(uword ww) -> uword { + return ww+2222 + } + } + """ + val result = compileText(C64Target(), true, text, writeAssembly = true).assertSuccess() + val stmts = result.program.entrypoint.statements + stmts.size shouldBe 7 + + val assignw = stmts[4] as Assignment + val pipew = assignw.value as PipeExpression + pipew.expressions.size shouldBe 2 + pipew.expressions[0] shouldBe instanceOf() + pipew.expressions[1] shouldBe instanceOf() + + val assigncc = stmts[5] as Assignment + val value = assigncc.value as NumericLiteral + value.number shouldBe 194.0 + } + test("incorrect type in pipe expression") { val text = """ %option enable_floats diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 1c320e37a..34e1180c4 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -701,12 +701,13 @@ If you want to ignore a return value of a subroutine, you should prefix the call Otherwise the compiler will issue a warning about discarding a result value. Deeply nested function calls can be rewritten as a chain using the *pipe operator* ``|>`` as long as they -are unary functions (taking a single argument). +are unary functions (taking a single argument). Various possibilities of using this operator are explained +in the syntax reference for this operator. .. note:: **Order of evaluation:** - The order of evaluation of arguments is *unspecified* and should not be relied upon. + The order of evaluation of arguments to a single function call is *unspecified* and should not be relied upon. There is no guarantee of a left-to-right or right-to-left evaluation of the call arguments. .. caution:: diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 72ffa9682..83d31c24b 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -549,6 +549,15 @@ pipe: ``|>`` |> determine_score |> add_bonus + Finally, if you like the left-to-right flow, it's possible to use the name of a variable as the last term. This just means that the pipe's resulting value is + stored in that variable (it's just another way of writing an assignment). So the above can also be written as:: + + uword score + get_player(1) + |> determine_score + |> add_bonus + |> score + address of: ``&`` This is a prefix operator that can be applied to a string or array variable or literal value. It results in the memory address (UWORD) of that string or array in memory: ``uword a = &stringvar`` diff --git a/docs/source/todo.rst b/docs/source/todo.rst index b8566d7f1..3d8299b60 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,7 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- allow the last term in a pipe *statement* to be a variable, rewrite this as var = +- sin8u(30) is different from sin8u(xx) where xx=30, so the const eval is off. Also test/fix other builtin functions! Need help with diff --git a/examples/test.p8 b/examples/test.p8 index 0e6824f28..9031970aa 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,13 +3,28 @@ main { sub start() { ubyte xx = 30 - ubyte cc + byte cc -; cc = 30 |> sin8u |> cos8u +; cc=0 +; 30 |> sin8u |> cos8u |> cc ; txt.print_ub(cc) ; txt.nl() - cc = xx |> sin8u |> cos8u - txt.print_ub(cc) +; cc=0 +; xx |> sin8u |> cos8u |> cc +; txt.print_ub(cc) +; txt.nl() + 100 |> cc + txt.print_b(cc) + txt.nl() + -100 |> abs |> abs |> cc + txt.print_b(cc) + txt.nl() + cc |> abs |> abs |> cc + txt.print_b(cc) + txt.nl() + cc = -100 + cc |> abs |> abs |> cc + txt.print_b(cc) txt.nl() repeat {