diff --git a/docs/source/operations/conversions/index.rst b/docs/source/operations/conversions/index.rst index b335c2ac65..7c05f0fba2 100644 --- a/docs/source/operations/conversions/index.rst +++ b/docs/source/operations/conversions/index.rst @@ -19,5 +19,6 @@ conversions. pop push set + stack topocentric unitconvert diff --git a/docs/source/operations/conversions/pop.rst b/docs/source/operations/conversions/pop.rst index 2c6d264aec..5687d67210 100644 --- a/docs/source/operations/conversions/pop.rst +++ b/docs/source/operations/conversions/pop.rst @@ -33,6 +33,9 @@ function as a no-operation that passes the coordinate through unchanged. Similarly, if no coordinate component is available on the stack to be popped the operation does nothing. +As of PROJ 9.4.0, this operation is supersed by the :ref:`stack` operation. +Note that the stacks of ``swap`` and ``push/pop`` are totally separated. + Examples ################################################################################ diff --git a/docs/source/operations/conversions/push.rst b/docs/source/operations/conversions/push.rst index a0dbed1ceb..0907d0b63c 100644 --- a/docs/source/operations/conversions/push.rst +++ b/docs/source/operations/conversions/push.rst @@ -32,6 +32,9 @@ the :ref:`pop operation`. If the push operation is used by itself, e.g. not in a pipeline, it will function as a no-operation that passes the coordinate through unchanged. +As of PROJ 9.4.0, this operation is supersed by the :ref:`stack` operation. +Note that the stacks of ``swap`` and ``push/pop`` are totally separated. + Examples ################################################################################ diff --git a/docs/source/operations/conversions/stack.rst b/docs/source/operations/conversions/stack.rst new file mode 100644 index 0000000000..7ff1d50445 --- /dev/null +++ b/docs/source/operations/conversions/stack.rst @@ -0,0 +1,192 @@ +.. _stack: + +================================================================================ +Pipeline stack manipulation +================================================================================ + +.. versionadded:: 9.4.0 + +Push/pop/swap operation operating on a stack of coordinate tuples, attached to +a pipeline. + ++---------------------+--------------------------------------------------------+ +| **Alias** | stack | ++---------------------+--------------------------------------------------------+ +| **Domain** | 4D | ++---------------------+--------------------------------------------------------+ +| **Input type** | Any | ++---------------------+--------------------------------------------------------+ +| **Output type** | Any | ++---------------------+--------------------------------------------------------+ + +This operation allows the user to manipulate a stack of coordinate tuples which +is held by a :ref:`pipeline` instance. +Each coordinate tuple in the stack can have up to 4 components. Different tuples +in the stack may have a different number of components. +This operation is a no-operation if used outside of a pipeline. + +``stack`` supersedes the :ref:`push` and :ref:`pop` operations. +Note that the stacks of ``stack`` and ``push/pop`` are totally separated. + + +Modes +################################################################################ + +The stack operation has several (mutually exclusive in a single step) modes: + +* ``push``: causes one or several components of coordinates to be saved for + application in a later step. A saved coordinate component is moved, or + *pushed*, to a memory stack that is part of a :ref:`pipeline`. The + pipeline coordinate stack is inspired by the stack data structure that is + commonly used in computer science. + + The value of the ``push`` option is a comma-separated list of components, whose + value is 1,2,3 or 4. The specified components are saved in a tuple, in the + order they are specified. So ``+proj=stack +push=3,1`` will push on top of the + stack a tuple with the 3rd component of the current coordinate value followed + by the 1st component of the current coordinate value. + + When such a step is applied in the reverse direction, a ``push`` is executed + as a ``pop``. + +* ``pop``: causes one or several components of coordinates, pushed previously, + to be loaded, or *popped*, from the memory stack, into the current coordinate + value. + + The value of the ``pop`` option is a comma-separated list of components, whose + value is 1,2,3 or 4. The first value of the top stack tuple is loaded into + the first specified index, the second value into the second specified index, + etc. So ``+proj=pop +push=3,1`` will load into the 3rd component of the current + coordinate value the first element of the tuple at the top of the stack, and + will load into the 1st compoment of the current coordinate value the second + element of the tuple at the top of the stack. + + The number of components specified should exactly match the number of values + in the top stack tuple. + + The top stack tuple is removed from the stack after the operation has completed. + + When such a step is applied in the reverse direction, a ``pop`` is executed + as a ``push``. + +* ``swap``: causes the top and antepenultimate elements of the stack to be + inverted. (This implements the semantics of the swap operator of the Forth + programming language) + + This is an advanced mode that is typically used when doing + coordinate transformations on compound CRS, where different steps expect/output + orthometric heights versus ellipsoidal height. + + There must be at least 2 elements in the stack for this operation to be valid. + + The common use case of swap involves a pattern like: push component, modify + compoment, push modified component, swap, pop component, do something, pop + component (realistic example given below). + + The swap operator does not require that the number of values in the top and + antepenultimate elements is the same, although in practical cases, it is + expected that they should be identical. + + A ``swap`` is executed in the same way in the forward and reverve directions. + + +Example involving push and pop +################################################################################ + +A common use of the push and pop sub-operations is in 3D +:ref:`Helmert` transformations where only the horizontal components +are needed. This is often the case when combining heights from a legacy +vertical reference with a modern geocentric reference. Below is a an example of +such a transformation, where the horizontal part is transformed with a Helmert +operation but the vertical part is kept exactly as the input was. + +:: + + $ echo 12 56 12.3 2020 | cct +proj=pipeline \ + +step +proj=stack +push=3 \ + +step +proj=cart \ + +step +proj=helmert +x=3000 +y=1000 +z=2000 \ + +step +proj=cart +inv \ + +step +proj=stack +pop=3 + + 12.0056753463 55.9866540552 12.3000 2000.0000 + +Note that the third coordinate component in the output is the same as the input. + +The same transformation without the push and pop operations would look like this:: + + $ echo 12 56 12.3 2020 | cct +proj=pipeline \ + +step +proj=cart \ + +step +proj=helmert +x=3000 +y=1000 +z=2000 \ + +step +proj=cart +inv + + 12.0057 55.9867 3427.7404 2000.0000 + +Here the vertical component is adjusted significantly. + +Example involving swap +################################################################################ + +The below example demonstrates a pipeline transforming coordinates in ETRS 89 +(longitude, latitude, ellipsoidal height) to (longitude, latitude) in the +S-JTSK/05 datum and orthometric height in the Baltic 1957 datum. + +:: + + echo 15 50 100 | cct -d 10 +proj=pipeline \ + +step +proj=stack +push=3 +omit_inv \ # (1) + +step +proj=vgridshift +grids=CR2005.tif \ # (2) + +step +proj=stack +push=3 \ # (3) + +step +proj=stack +swap \ # (4) + +step +proj=stack +pop=3 \ # (5) + +step +proj=cart +ellps=GRS80 \ # (6) + +step +inv +proj=helmert +x=572.213 +y=85.334 +z=461.94 \ + +rx=-4.9732 +ry=-1.529 +rz=-5.2484 +s=3.5378 +convention=coordinate_frame \ + +step +inv +proj=cart +ellps=bessel \ + +step +proj=stack +pop=3 # (7) + + 15.0011680291 50.0007534747 55.0384863419 + +Let's examine step by step, when executing the pipeline in the forward direction: + +1. Save the ETRS89 ellipsoidal height on the stack +2. Apply the geoid model to transform the ETRS89 ellipsoidal height into a Baltic 1957 orthometric height +3. Save the Baltic 1957 on the stack ("above" the ETRS89 ellipsoidal height) +4. Swap the top 2 tuples of the stack, that is now the ETRS89 height will be on top of the Baltic 1957 one. +5. Pop the ETRS89 height from the stack as the active Z value. +6. Apply a 3D Helmert transformation to go from ETRS89 to S-JTSK/05 +7. Pop the Baltic 1957 height from the stack as the active Z value. + +When run in the inverse direction, the steps are interpreted as: + +7. Push the Baltic 1957 height on the stack +6. Apply the inverse 3D Helmert transformation to go from S-JTSK/05 to ETRS89 +5. Push the ETRS89 height on the stack +4. Swap the top 2 tuples of the stack, that is now the Baltic 1957 height will be on top of the ETRS89 one. +3. Pop the Baltic 1957 height from the stack as the active Z value. +2. Apply the inverse geoid model to transform the Baltic 1957 orthometric height into a ETRS89 ellipsoidal one. +1. Do not apply this step in the reverse direction ! We got what we want. + +Parameters +################################################################################ + +.. option:: +push=idx1[,idx2,[,idx3[,idx4]]] + + Push up to 4 components from the current coordinate on the stack. + Each index is between 1 and 4. + +.. option:: +pop=idx1[,idx2,[,idx3[,idx4]]] + + Pop the top stack value into the specified components. Each index is between 1 and 4. + +.. option:: +swap + + Swap the top and antepenultimate elements of the stack. + + +Further reading +################################################################################ + +#. `Stack data structure on Wikipedia `_ + +#. `Forth stack operators `_ diff --git a/src/pipeline.cpp b/src/pipeline.cpp index 1fa6dd0c96..42a3b40c75 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -101,6 +101,7 @@ Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 #include #include #include +#include // std::swap #include #include "geodesic.h" @@ -110,6 +111,7 @@ Thomas Knudsen, thokn@sdfe.dk, 2016-05-20 PROJ_HEAD(pipeline, "Transformation pipeline manager"); PROJ_HEAD(pop, "Retrieve coordinate value from pipeline stack"); PROJ_HEAD(push, "Save coordinate value on pipeline stack"); +PROJ_HEAD(stack, "Manipulate coordinate values on pipeline stack"); /* Projection specific elements for the PJ object */ namespace { // anonymous namespace @@ -132,19 +134,31 @@ struct Step { ~Step() { proj_destroy(pj); } }; +//! Stack item of the +proj=stack operator +struct StackData { + std::vector values{}; // up to 4 values +}; + struct Pipeline { char **argv = nullptr; char **current_argv = nullptr; std::vector steps{}; - std::stack stack[4]; + + // Superseded stack for +proj=push / +prop=pop operators + std::stack supersededStack[4]; + + // "New-gen" stack of the +proj=stack operator + std::vector newStack{}; }; +/* Data for superseded +push / +pop operator */ struct PushPop { bool v1; bool v2; bool v3; bool v4; }; + } // anonymous namespace static void pipeline_forward_4d(PJ_COORD &point, PJ *P); @@ -160,8 +174,17 @@ static void pipeline_reassign_context(PJ *P, PJ_CONTEXT *ctx) { proj_assign_context(step.pj, ctx); } +static void pipeline_reset(struct Pipeline *pipeline) { + // Clear stack for use cases with unbalanced push/pop + pipeline->newStack.clear(); + + for (int i = 0; i < 4; ++i) + pipeline->supersededStack[i] = std::stack(); +} + static void pipeline_forward_4d(PJ_COORD &point, PJ *P) { auto pipeline = static_cast(P->opaque); + pipeline_reset(pipeline); for (auto &step : pipeline->steps) { if (!step.omit_fwd) { if (!step.pj->inverted) @@ -177,6 +200,7 @@ static void pipeline_forward_4d(PJ_COORD &point, PJ *P) { static void pipeline_reverse_4d(PJ_COORD &point, PJ *P) { auto pipeline = static_cast(P->opaque); + pipeline_reset(pipeline); for (auto iterStep = pipeline->steps.rbegin(); iterStep != pipeline->steps.rend(); ++iterStep) { const auto &step = *iterStep; @@ -196,6 +220,7 @@ static PJ_XYZ pipeline_forward_3d(PJ_LPZ lpz, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.lpz = lpz; auto pipeline = static_cast(P->opaque); + pipeline_reset(pipeline); for (auto &step : pipeline->steps) { if (!step.omit_fwd) { point = pj_approx_3D_trans(step.pj, PJ_FWD, point); @@ -212,6 +237,7 @@ static PJ_LPZ pipeline_reverse_3d(PJ_XYZ xyz, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.xyz = xyz; auto pipeline = static_cast(P->opaque); + pipeline_reset(pipeline); for (auto iterStep = pipeline->steps.rbegin(); iterStep != pipeline->steps.rend(); ++iterStep) { const auto &step = *iterStep; @@ -230,6 +256,7 @@ static PJ_XY pipeline_forward(PJ_LP lp, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.lp = lp; auto pipeline = static_cast(P->opaque); + pipeline_reset(pipeline); for (auto &step : pipeline->steps) { if (!step.omit_fwd) { point = pj_approx_2D_trans(step.pj, PJ_FWD, point); @@ -246,6 +273,7 @@ static PJ_LP pipeline_reverse(PJ_XY xy, PJ *P) { PJ_COORD point = {{0, 0, 0, 0}}; point.xy = xy; auto pipeline = static_cast(P->opaque); + pipeline_reset(pipeline); for (auto iterStep = pipeline->steps.rbegin(); iterStep != pipeline->steps.rend(); ++iterStep) { const auto &step = *iterStep; @@ -638,6 +666,10 @@ PJ *OPERATION(pipeline, 0) { return P; } +// --------------------------------------------------------------------------- +// Superseded +proj=push / +proj=pop operators +// --------------------------------------------------------------------------- + static void push(PJ_COORD &point, PJ *P) { if (P->parent == nullptr) return; @@ -647,13 +679,13 @@ static void push(PJ_COORD &point, PJ *P) { struct PushPop *pushpop = static_cast(P->opaque); if (pushpop->v1) - pipeline->stack[0].push(point.v[0]); + pipeline->supersededStack[0].push(point.v[0]); if (pushpop->v2) - pipeline->stack[1].push(point.v[1]); + pipeline->supersededStack[1].push(point.v[1]); if (pushpop->v3) - pipeline->stack[2].push(point.v[2]); + pipeline->supersededStack[2].push(point.v[2]); if (pushpop->v4) - pipeline->stack[3].push(point.v[3]); + pipeline->supersededStack[3].push(point.v[3]); } static void pop(PJ_COORD &point, PJ *P) { @@ -664,24 +696,24 @@ static void pop(PJ_COORD &point, PJ *P) { static_cast(P->parent->opaque); struct PushPop *pushpop = static_cast(P->opaque); - if (pushpop->v1 && !pipeline->stack[0].empty()) { - point.v[0] = pipeline->stack[0].top(); - pipeline->stack[0].pop(); + if (pushpop->v1 && !pipeline->supersededStack[0].empty()) { + point.v[0] = pipeline->supersededStack[0].top(); + pipeline->supersededStack[0].pop(); } - if (pushpop->v2 && !pipeline->stack[1].empty()) { - point.v[1] = pipeline->stack[1].top(); - pipeline->stack[1].pop(); + if (pushpop->v2 && !pipeline->supersededStack[1].empty()) { + point.v[1] = pipeline->supersededStack[1].top(); + pipeline->supersededStack[1].pop(); } - if (pushpop->v3 && !pipeline->stack[2].empty()) { - point.v[2] = pipeline->stack[2].top(); - pipeline->stack[2].pop(); + if (pushpop->v3 && !pipeline->supersededStack[2].empty()) { + point.v[2] = pipeline->supersededStack[2].top(); + pipeline->supersededStack[2].pop(); } - if (pushpop->v4 && !pipeline->stack[3].empty()) { - point.v[3] = pipeline->stack[3].top(); - pipeline->stack[3].pop(); + if (pushpop->v4 && !pipeline->supersededStack[3].empty()) { + point.v[3] = pipeline->supersededStack[3].top(); + pipeline->supersededStack[3].pop(); } } @@ -723,3 +755,152 @@ PJ *OPERATION(pop, 0) { return setup_pushpop(P); } + +// --------------------------------------------------------------------------- +// Implementation of +proj=stack operator +// --------------------------------------------------------------------------- + +//! Configuration data for the +proj=stack operator +struct StackConfig { + //! Up to 4 values, each between 0 and 3. + std::vector components{}; +}; + +static PJ *pj_stack_destructor(PJ *P, int errlev) { + if (nullptr == P) + return nullptr; + + delete static_cast(P->opaque); + P->opaque = nullptr; + + return pj_default_destructor(P, errlev); +} + +static void stack_push(PJ_COORD &point, PJ *P) { + if (P->parent == nullptr) + return; + + auto pipeline = static_cast(P->parent->opaque); + const auto *stackConfig = static_cast(P->opaque); + + StackData stackData; + for (int component : stackConfig->components) + stackData.values.push_back(point.v[component]); + pipeline->newStack.emplace_back(std::move(stackData)); +} + +static void stack_pop(PJ_COORD &point, PJ *P) { + if (P->parent == nullptr) + return; + + auto pipeline = static_cast(P->parent->opaque); + const auto *stackConfig = static_cast(P->opaque); + + if (pipeline->newStack.empty()) { + proj_context_errno_set(P->ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); + proj_log_error(P, _("Stack: pop: Stack is empty")); + point = proj_coord_error(); + return; + } + + const auto &stackData = pipeline->newStack.back(); + if (stackConfig->components.size() != stackData.values.size()) { + proj_context_errno_set(P->ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); + proj_log_error(P, + _("Stack: pop: Trying to pop stack tuples which has %d " + "elements into %d components"), + static_cast(stackData.values.size()), + static_cast(stackConfig->components.size())); + point = proj_coord_error(); + return; + } + for (size_t i = 0; i < stackData.values.size(); ++i) + point.v[stackConfig->components[i]] = stackData.values[i]; + pipeline->newStack.pop_back(); +} + +static void stack_swap(PJ_COORD &point, PJ *P) { + if (P->parent == nullptr) + return; + + auto pipeline = static_cast(P->parent->opaque); + + if (pipeline->newStack.size() < 2) { + proj_context_errno_set(P->ctx, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); + proj_log_error( + P, _("Stack: swap: Stack should contain at least 2 elements")); + point = proj_coord_error(); + return; + } + + std::swap(pipeline->newStack[pipeline->newStack.size() - 1], + pipeline->newStack[pipeline->newStack.size() - 2]); +} + +PJ *OPERATION(stack, 0) { + + P->destructor = pj_stack_destructor; + + const auto pushExists = pj_param_exists(P->params, "push"); + const auto popExists = pj_param_exists(P->params, "pop"); + const auto swapExists = pj_param_exists(P->params, "swap"); + if (pushExists) { + P->fwd4d = stack_push; + P->inv4d = stack_pop; + } else if (popExists) { + P->fwd4d = stack_pop; + P->inv4d = stack_push; + } else if (swapExists) { + P->fwd4d = stack_swap; + P->inv4d = stack_swap; + } else { + proj_log_error(P, + _("One of push, pop or swap argument should be used")); + return pj_stack_destructor(P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); + } + + if ((pushExists ? 1 : 0) + (popExists ? 1 : 0) + (swapExists ? 1 : 0) > 1) { + proj_log_error( + P, _("At most one of push, pop or swap argument should be used")); + return pj_stack_destructor(P, PROJ_ERR_INVALID_OP_WRONG_SYNTAX); + } + + if (pushExists || popExists) { + auto stackConfig = new StackConfig; + P->opaque = stackConfig; + + const char *componentsStr = + pj_param(P->ctx, P->params, pushExists ? "spush" : "spop").s; + const char *s = componentsStr; + while (*s != '\0') { + if (stackConfig->components.size() == 4) { + proj_log_error(P, _("Too many axis")); + return pj_stack_destructor( + P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + const int nCurComponent = atoi(s); + if (nCurComponent <= 0 || nCurComponent > 4) { + proj_log_error(P, _("invalid axis '%d'"), nCurComponent); + return pj_stack_destructor( + P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + // Go from 1-based convention to 0-based + stackConfig->components.push_back(nCurComponent - 1); + while (*s != '\0' && *s != ',') + s++; + if (*s == ',') + s++; + } + + if (stackConfig->components.empty()) { + proj_log_error(P, _("No axis specified")); + return pj_stack_destructor(P, + PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + } + + P->left = PJ_IO_UNITS_WHATEVER; + P->right = PJ_IO_UNITS_WHATEVER; + + return P; +} diff --git a/src/pj_list.h b/src/pj_list.h index e0b2035687..5dd7bd6810 100644 --- a/src/pj_list.h +++ b/src/pj_list.h @@ -153,6 +153,7 @@ PROJ_HEAD(set, "Set coordinate value") PROJ_HEAD(sinu, "Sinusoidal (Sanson-Flamsteed)") PROJ_HEAD(som, "Space Oblique Mercator") PROJ_HEAD(somerc, "Swiss. Obl. Mercator") +PROJ_HEAD(stack, "Manipulate coordinate values on pipeline stack") PROJ_HEAD(stere, "Stereographic") PROJ_HEAD(sterea, "Oblique Stereographic Alternative") PROJ_HEAD(gstmerc, diff --git a/test/gie/4D-API_cs2cs-style.gie b/test/gie/4D-API_cs2cs-style.gie index 1b0c1541fe..e67b01e907 100644 --- a/test/gie/4D-API_cs2cs-style.gie +++ b/test/gie/4D-API_cs2cs-style.gie @@ -557,4 +557,140 @@ direction inverse accept 1 2 3 4 expect 10 20 30 40 +------------------------------------------------------------------------------- +# Test proj=stack +------------------------------------------------------------------------------- + +# push, set, pop with a single component +operation +proj=pipeline \ + +step +proj=stack +push=1 \ + +step +proj=set +v_1=10 \ + +step +proj=stack +pop=1 + +accept 10 20 30 40 +expect 10 20 30 40 + +# push, set, pop with 2 components, and popping in different order than pushin +operation +proj=pipeline \ + +step +proj=stack +push=2,1,3,4 \ + +step +proj=stack +pop=1,2,4,3 + +accept 10 20 30 40 +expect 20 10 40 30 + +# Test swap +operation +proj=pipeline \ + +step +proj=stack +push=1 \ + +step +proj=set +v_1=10 \ + +step +proj=stack +push=1 \ + +step +proj=stack +swap \ + +step +proj=stack +pop=1 \ + +step +proj=set +v_1=-10 \ + +step +proj=stack +pop=1 + +accept 1 2 3 4 +expect 10 2 3 4 + +# Test repeated execution of a pipeline with unbalanced push/pop to check +# that it gets cleared at each execution +operation +proj=pipeline \ + +step +proj=stack +push=1 +omit_inv \ + +step +proj=set +v_1=10 + +accept 10 20 30 40 +expect 10 20 30 40 +roundtrip 1000 + +# Simulate ETRS89 to S-JTSK/05 + Baltic 1957 height +operation +proj=pipeline \ + +step +proj=stack +push=3 +omit_inv \ # Save ETRS89 height (and on inverse, keep Baltic57 height) + +step +proj=affine +zoff=-50 \ # Simulate geoid grid + +step +proj=stack +push=3 \ # Save Baltic 57 height + +step +proj=stack +swap \ # Swap ETRS89 height and Baltic 57 height + +step +proj=stack +pop=3 \ # Restore ETRS89 height + +step +proj=cart +ellps=GRS80 \ + +step +inv +proj=helmert +x=572.213 +y=85.334 +z=461.94 \ + +rx=-4.9732 +ry=-1.529 +rz=-5.2484 +s=3.5378 +convention=coordinate_frame \ + +step +inv +proj=cart +ellps=bessel \ + +step +proj=stack +pop=3 # Restore Baltic57 height +tolerance 1 mm + +accept 15 50 1000 +expect 15.0011679017 50.0007533757 950.0000 +roundtrip 1 + + +############### +# Error casesĀ # +############### + +# Missing operator for stack +operation +proj=pipeline \ + +step +proj=set +v_1=10 \ + +step +proj=stack +expect failure errno invalid_op_wrong_syntax + + +# More than 1 operator for stack +operation +proj=pipeline \ + +step +proj=set +v_1=10 \ + +step +proj=stack +push=1 +pop=1 +expect failure errno invalid_op_wrong_syntax + + +# push without value +operation +proj=pipeline \ + +step +proj=set +v_1=10 \ + +step +proj=stack +push +expect failure errno invalid_op_illegal_arg_value + + +# Illegal component index +operation +proj=pipeline \ + +step +proj=set +v_1=10 \ + +step +proj=stack +push=0 +expect failure errno invalid_op_illegal_arg_value + + +# Illegal component index +operation +proj=pipeline \ + +step +proj=set +v_1=10 \ + +step +proj=stack +push=5 +expect failure errno invalid_op_illegal_arg_value + + +# Too many component indices +operation +proj=pipeline \ + +step +proj=set +v_1=10 \ + +step +proj=stack +push=1,2,3,4,5 +expect failure errno invalid_op_illegal_arg_value + + +# More pop than push +operation +proj=pipeline \ + +step +proj=set +v_1=10 \ + +step +proj=stack +pop=1 + +accept 1 2 3 4 +expect failure errno invalid_op_wrong_syntax + + +# Different number of popped components than pushed +operation +proj=pipeline \ + +step +proj=stack +push=1 \ + +step +proj=stack +pop=1,2 + +accept 1 2 3 4 +expect failure errno invalid_op_wrong_syntax + + +# Swap with only one element on the stack +operation +proj=pipeline \ + +step +proj=stack +push=1 \ + +step +proj=stack +swap + +accept 1 2 3 4 +expect failure errno invalid_op_wrong_syntax + +