diff --git a/docs/dictionary/command/difference.lcdoc b/docs/dictionary/command/difference.lcdoc new file mode 100644 index 00000000000..d953ef7c18e --- /dev/null +++ b/docs/dictionary/command/difference.lcdoc @@ -0,0 +1,67 @@ +Name: difference + +Type: command + +Syntax: difference with [into ] + +Summary: +Removes from an which have a +corresponding in another , and leaves all +others alone. + +Introduced: 9.0 + +OS: mac, windows, linux, ios, android + +Platforms: desktop, server, mobile + +Example: +local tLeft, tRight +put "green" into tLeft["color"] +put "left" into tLeft["align"] + +put "blue" into tRight["color"] +put "100" into tRight["width"] + +difference tLeft with tRight + +# RESULT +# the keys of tLeft = "align" +# tRight unchanged + +Parameters: +targetArray (array): +The value to modify. + + +templateArray (array): +The array to difference with. + +destinationArray (optional array): +A variable to set as the destination instead of mutating + +Description: +Use the to filter out +from an according to the contents of another . + +Each key of is checked to see whether there is a matching + in . The of + that match an of the +are removed from . + +The content of individual elements of the does not +affect the final result. Only which exist +in the , not their content, controls which + of are retained and which are +removed. + +If the into clause is used the operation of the commands is the same as +the non-into form except that does not have to be a +variable, and the result of the operation is placed into + rather than mutating . + +References: split (command), union (command), element (glossary), +array (glossary), command (glossary), key (glossary), element (keyword), +intersect (command), symmetric difference (command) + +Tags: properties diff --git a/docs/dictionary/command/intersect.lcdoc b/docs/dictionary/command/intersect.lcdoc index badda962c0d..5bc1451923e 100644 --- a/docs/dictionary/command/intersect.lcdoc +++ b/docs/dictionary/command/intersect.lcdoc @@ -2,7 +2,7 @@ Name: intersect Type: command -Syntax: intersect with [recursively] +Syntax: intersect with [recursively] [into ] Summary: Removes from an if they have no @@ -80,6 +80,8 @@ The value to modify. templateArray (array): The array to intersect with. +destinationArray (optional array): +A variable to set as the destination instead of mutating Description: Use the to filter out @@ -106,8 +108,16 @@ removed. If and have the same set of , the does not change the value of . +If the into clause is used the operation of the commands is the same as +the non-into form except that does not have to be a +variable, and the result of the operation is placed into + rather than mutating . + +Changes: +The `into` clause was added in LiveCode 9. + References: split (command), union (command), element (glossary), -array (glossary), command (glossary), key (glossary), element (keyword) +array (glossary), command (glossary), key (glossary), element (keyword), +difference (command), symmetric difference (command) Tags: properties - diff --git a/docs/dictionary/command/symmetric-difference.lcdoc b/docs/dictionary/command/symmetric-difference.lcdoc new file mode 100644 index 00000000000..67f73d34524 --- /dev/null +++ b/docs/dictionary/command/symmetric-difference.lcdoc @@ -0,0 +1,70 @@ +Name: symmetric difference + +Type: command + +Syntax: symmetric difference with [into ] + +Summary: +Removes from a target which have a +corresponding in a template , and adds + from the template that have no +which have no corresponding in the target . + +Introduced: 9.0 + +OS: mac, windows, linux, ios, android + +Platforms: desktop, server, mobile + +Example: +local tLeft, tRight +put "green" into tLeft["color"] +put "left" into tLeft["align"] + +put "blue" into tRight["color"] +put "100" into tRight["width"] + +symmetric difference tLeft with tRight + +# RESULT +# the keys of tLeft = "align" & "width" +# tRight unchanged + +Parameters: +targetArray (array): +The value to modify. + + +templateArray (array): +The array to symmetric difference with. + +destinationArray (optional array): +A variable to set as the destination instead of mutating + +Description: +Use the to set the +from an according to the contents of another . + +Each key of is checked to see whether there is a matching + in . The of + that match an of the +are removed from while the of +the that have no corresponding in +the are added to the . + +The content of individual elements of the does not +affect the final result. Only which exist +in the , not their content, controls which + of are retained and which are +removed. + +If the into clause is used the operation of the commands is the same as +the non-into form except that does not have to be a +variable, and the result of the operation is placed into + rather than mutating . + +References: split (command), union (command), element (glossary), +array (glossary), command (glossary), key (glossary), element (keyword), +intersect (command), difference (command) + +Tags: properties diff --git a/docs/dictionary/command/union.lcdoc b/docs/dictionary/command/union.lcdoc index edcfd3729e9..23ceac739a0 100644 --- a/docs/dictionary/command/union.lcdoc +++ b/docs/dictionary/command/union.lcdoc @@ -2,7 +2,7 @@ Name: union Type: command -Syntax: union with [recursively] +Syntax: union with [recursively] [into ] Summary: Combines two arrays. @@ -79,6 +79,8 @@ The value to modify. templateArray (array): The array to intersect with. +destinationArray (optional array): +A variable to set as the destination instead of mutating Description: Use the to combine two , eliminating @@ -106,9 +108,17 @@ in the , not their content, controls which but different content in each , the does not change the value of . +If the into clause is used the operation of the commands is the same as +the non-into form except that does not have to be a +variable, and the result of the operation is placed into + rather than mutating . + +Changes: +The `into` clause was added in LiveCode 9. + References: add (command), intersect (command), keys (function), element (glossary), key (glossary), command (glossary), array (glossary), -execute (glossary), element (keyword), + (operator) +execute (glossary), element (keyword), + (operator), +difference (command), symmetric difference (command) Tags: properties - diff --git a/docs/notes/bugfix-11323.md b/docs/notes/bugfix-11323.md new file mode 100644 index 00000000000..887969b3dca --- /dev/null +++ b/docs/notes/bugfix-11323.md @@ -0,0 +1,19 @@ +# New array commands `difference` and `symmetric difference` + +The `difference` command removes all keys from the destination +which are present in the source, and leaves all others alone. + +The `symmetric difference` command removes all keys from the +destination which are present in the source, and adds all keys +from the source which are not present in the destination. + +Additionally the `into` clause has been added to all array set +set operations (`union`, `intersect`, `difference`, `symmetric difference`) +allowing commands such as: + + intersect tLeft with tRight into tResult + +The operation of the commands is the same as the non-into form +except that tLeft does not have to be a variable, and the result +of the operation is placed into tResult rather than mutating +tLeft. diff --git a/engine/src/cmds.h b/engine/src/cmds.h index 13895487c15..150722ba2e8 100644 --- a/engine/src/cmds.h +++ b/engine/src/cmds.h @@ -23,6 +23,7 @@ along with LiveCode. If not see . */ #include "regex.h" #include "util.h" #include "uidc.h" +#include "variable.h" // general commands in cmds.cc @@ -1756,41 +1757,35 @@ class MCSplit : public MCArrayOp class MCSetOp : public MCStatement { - MCVarref *destvar; - MCExpression *source; -protected: - bool intersect : 1; - // MERG-2013-08-26: [[ RecursiveArrayOp ]] Support nested arrays in union and intersect - bool recursive : 1; public: - MCSetOp() - { - source = NULL; - destvar = NULL; - } - virtual ~MCSetOp(); + enum Op + { + kOpNone, + kOpUnion, + kOpUnionRecursively, + kOpIntersect, + kOpIntersectRecursively, + kOpDifference, + kOpSymmetricDifference + }; + +private: + MCAutoPointer destvar; + MCAutoPointer destexpr; + MCAutoPointer source; + Op op = kOpNone; + bool is_into = false; + +public: + MCSetOp(Op p_op) + : op(p_op) + { + } virtual Parse_stat parse(MCScriptPoint &); virtual void exec_ctxt(MCExecContext &ctxt); virtual void compile(MCSyntaxFactoryRef); }; -class MCArrayIntersectCmd : public MCSetOp -{ -public: - MCArrayIntersectCmd() - { - intersect = True; - } -}; - -class MCArrayUnionCmd : public MCSetOp -{ -public: - MCArrayUnionCmd() - { - intersect = False; - } -}; // MCStack manipulation comands in cmdss.cc diff --git a/engine/src/cmdsm.cpp b/engine/src/cmdsm.cpp index 3113d7b5dd5..c363e05b718 100644 --- a/engine/src/cmdsm.cpp +++ b/engine/src/cmdsm.cpp @@ -850,26 +850,39 @@ void MCArrayOp::compile(MCSyntaxFactoryRef ctxt) MCSyntaxFactoryEndStatement(ctxt); } - -MCSetOp::~MCSetOp() -{ - delete destvar; - delete source; -} - Parse_stat MCSetOp::parse(MCScriptPoint &sp) { initpoint(sp); + + if (op == kOpSymmetricDifference) + { + if (sp.skip_token(SP_COMMAND, TT_STATEMENT, S_DIFFERENCE) == PS_ERROR) + { + MCperror->add(PE_ARRAYOP_NODIFFERENCE, sp); + return PS_ERROR; + } + } + // MW-2011-06-22: [[ SERVER ]] Update to use SP findvar method to take into account // execution outwith a handler. + MCerrorlock++; Symbol_type type; + MCScriptPoint tsp(sp); if (sp.next(type) != PS_NORMAL || type != ST_ID - || sp.findvar(sp.gettoken_nameref(), &destvar) != PS_NORMAL + || sp.findvar(sp.gettoken_nameref(), &(&destvar)) != PS_NORMAL || destvar -> parsearray(sp) != PS_NORMAL) { - MCperror->add(PE_ARRAYOP_BADARRAY, sp); - return PS_ERROR; - } + sp = tsp; + MCerrorlock--; + destvar.Reset(); + if (sp.parseexp(False, True, &(&destexpr)) != PS_NORMAL) + { + MCperror->add(PE_ARRAYOP_BADARRAY, sp); + return PS_ERROR; + } + } + else + MCerrorlock--; if (sp.skip_token(SP_REPEAT, TT_UNDEFINED, RF_WITH) == PS_ERROR && sp.skip_token(SP_FACTOR, TT_PREP, PT_BY) == PS_ERROR) @@ -878,14 +891,50 @@ Parse_stat MCSetOp::parse(MCScriptPoint &sp) return PS_ERROR; } - if (sp.parseexp(True, False, &source) != PS_NORMAL) + if (sp.parseexp(True, False, &(&source)) != PS_NORMAL) { MCperror->add(PE_ARRAYOP_BADEXP, sp); return PS_ERROR; } // MERG-2013-08-26: [[ RecursiveArrayOp ]] Support nested arrays in union and intersect - recursive = sp.skip_token(SP_SUGAR, TT_UNDEFINED, SG_RECURSIVELY) == PS_NORMAL; + if (sp.skip_token(SP_SUGAR, TT_UNDEFINED, SG_RECURSIVELY) == PS_NORMAL) + { + if (op == kOpIntersect) + op = kOpIntersectRecursively; + else if (op == kOpUnion) + op = kOpUnionRecursively; + else + { + MCperror->add(PE_ARRAYOP_BADRECURSIVE, sp); + return PS_ERROR; + } + } + + if (sp.skip_token(SP_FACTOR, TT_PREP, PT_INTO) == PS_NORMAL) + { + if (!destexpr) + { + destexpr.Reset(destvar.Release()); + } + + Symbol_type ttype; + if (sp.next(ttype) != PS_NORMAL || ttype != ST_ID + || sp.findvar(sp.gettoken_nameref(), &(&destvar)) != PS_NORMAL + || destvar -> parsearray(sp) != PS_NORMAL) + { + MCperror->add(PE_ARRAYOP_BADARRAY, sp); + return PS_ERROR; + } + + is_into = true; + } + + if (!destvar && is_into) + { + MCperror->add(PE_ARRAYOP_DSTNOTCONTAINER, sp); + return PS_ERROR; + } return PS_NORMAL; } @@ -894,40 +943,63 @@ void MCSetOp::exec_ctxt(MCExecContext &ctxt) { // ARRAYEVAL MCAutoValueRef t_src; - if (!ctxt . EvalExprAsValueRef(source, EE_ARRAYOP_BADEXP, &t_src)) + if (!ctxt . EvalExprAsValueRef(*source, EE_ARRAYOP_BADEXP, &t_src)) return; + MCAutoValueRef t_dst; MCContainer t_container; - if (!destvar -> evalcontainer(ctxt, t_container)) - { - ctxt . LegacyThrow(EE_ARRAYOP_BADEXP); - return; - } + if (!is_into) + { + if (!destvar -> evalcontainer(ctxt, t_container)) + { + ctxt . LegacyThrow(EE_ARRAYOP_BADEXP); + return; + } - MCAutoValueRef t_dst; - if (!t_container.eval(ctxt, &t_dst)) - return; + if (!t_container.eval(ctxt, &t_dst)) + return; + } + else + { + if (!ctxt.EvalExprAsValueRef(*destexpr, EE_ARRAYOP_BADEXP, &t_dst)) + { + return; + } + } MCAutoValueRef t_dst_value; - if (intersect) + switch(op) { - // MERG-2013-08-26: [[ RecursiveArrayOp ]] Support nested arrays in union and intersect - if (recursive) - MCArraysExecIntersectRecursive(ctxt, *t_dst, *t_src, &t_dst_value); - else + case kOpIntersect: MCArraysExecIntersect(ctxt, *t_dst, *t_src, &t_dst_value); - } - else - { - // MERG-2013-08-26: [[ RecursiveArrayOp ]] Support nested arrays in union and intersect - if (recursive) - MCArraysExecUnionRecursive(ctxt, *t_dst, *t_src, &t_dst_value); - else + break; + case kOpIntersectRecursively: + MCArraysExecIntersectRecursively(ctxt, *t_dst, *t_src, &t_dst_value); + break; + case kOpUnion: MCArraysExecUnion(ctxt, *t_dst, *t_src, &t_dst_value); + break; + case kOpUnionRecursively: + MCArraysExecUnionRecursively(ctxt, *t_dst, *t_src, &t_dst_value); + break; + case kOpDifference: + MCArraysExecDifference(ctxt, *t_dst, *t_src, &t_dst_value); + break; + case kOpSymmetricDifference: + MCArraysExecSymmetricDifference(ctxt, *t_dst, *t_src, &t_dst_value); + break; + case kOpNone: + MCUnreachable(); + break; } if (!ctxt . HasError()) - t_container.set(ctxt, *t_dst_value); + { + if (!is_into) + t_container.set(ctxt, *t_dst_value); + else + destvar->set(ctxt, *t_dst_value); + } } void MCSetOp::compile(MCSyntaxFactoryRef ctxt) @@ -938,19 +1010,29 @@ void MCSetOp::compile(MCSyntaxFactoryRef ctxt) destvar -> compile(ctxt); source -> compile(ctxt); - if (intersect) + switch(op) { - if (recursive) - MCSyntaxFactoryExecMethodWithArgs(ctxt, kMCArraysExecIntersectRecursiveMethodInfo, 0, 1, 0); - else + case kOpIntersect: MCSyntaxFactoryExecMethodWithArgs(ctxt, kMCArraysExecIntersectMethodInfo, 0, 1, 0); - } - else - { - if (recursive) - MCSyntaxFactoryExecMethodWithArgs(ctxt, kMCArraysExecUnionRecursiveMethodInfo, 0, 1, 0); - else + break; + case kOpIntersectRecursively: + MCSyntaxFactoryExecMethodWithArgs(ctxt, kMCArraysExecIntersectRecursivelyMethodInfo, 0, 1, 0); + break; + case kOpUnion: MCSyntaxFactoryExecMethodWithArgs(ctxt, kMCArraysExecUnionMethodInfo, 0, 1, 0); + break; + case kOpUnionRecursively: + MCSyntaxFactoryExecMethodWithArgs(ctxt, kMCArraysExecUnionRecursivelyMethodInfo, 0, 1, 0); + break; + case kOpDifference: + MCSyntaxFactoryExecMethodWithArgs(ctxt, kMCArraysExecDifferenceMethodInfo, 0, 1, 0); + break; + case kOpSymmetricDifference: + MCSyntaxFactoryExecMethodWithArgs(ctxt, kMCArraysExecSymmetricDifferenceMethodInfo, 0, 1, 0); + break; + case kOpNone: + MCUnreachable(); + break; } MCSyntaxFactoryEndStatement(ctxt); diff --git a/engine/src/exec-array.cpp b/engine/src/exec-array.cpp index 45069f37302..d4e46845e66 100644 --- a/engine/src/exec-array.cpp +++ b/engine/src/exec-array.cpp @@ -49,9 +49,11 @@ MC_EXEC_DEFINE_EXEC_METHOD(Arrays, SplitByRow, 2) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, SplitByColumn, 2) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, SplitAsSet, 3) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, Union, 3) +MC_EXEC_DEFINE_EXEC_METHOD(Arrays, UnionRecursively, 3) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, Intersect, 3) -MC_EXEC_DEFINE_EXEC_METHOD(Arrays, UnionRecursive, 3) -MC_EXEC_DEFINE_EXEC_METHOD(Arrays, IntersectRecursive, 3) +MC_EXEC_DEFINE_EXEC_METHOD(Arrays, IntersectRecursively, 3) +MC_EXEC_DEFINE_EXEC_METHOD(Arrays, Difference, 3) +MC_EXEC_DEFINE_EXEC_METHOD(Arrays, SymmetricDifference, 3) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, ArrayEncode, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, ArrayDecode, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, MatrixMultiply, 3) @@ -581,8 +583,31 @@ void MCArraysExecSplitAsSet(MCExecContext& ctxt, MCStringRef p_string, MCStringR // end if // end repeat // +// if right is not an array then no-op +// else if left is not an array then left = right +// +// Semantics of 'symmetric difference tLeft with tRight' +// +// repeat for each key tKey in tRight +// if tKey is among the keys of tLeft then +// delete tLeft[tKey] +// else if tKey is not among the keys of tLeft then +// put tRight[tKey] into tLeft[tKey] +// end if +// end repeat +// +// if right is not an array then no-op +// else if left is not an array then left = right +// -void MCArraysDoUnion(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, bool p_recursive, MCValueRef& r_result) +enum MCArrayDoUnionOp +{ + kMCArrayDoUnionOpUnion, + kMCArrayDoUnionOpUnionRecursively, + kMCArrayDoUnionOpSymmetricDifference, +}; + +static void MCArraysDoUnion(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCArrayDoUnionOp p_op, MCValueRef& r_result) { if (!MCValueIsArray(p_src)) { @@ -617,27 +642,35 @@ void MCArraysDoUnion(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, bo { bool t_key_exists; t_key_exists = MCArrayFetchValue(t_dst_array, ctxt . GetCaseSensitive(), t_key, t_dst_value); - - if (t_key_exists && !p_recursive) - continue; - - MCAutoValueRef t_recursive_result; + if (!t_key_exists) { - t_recursive_result = t_src_value; + if (!MCArrayStoreValue(*t_result, ctxt . GetCaseSensitive(), t_key, t_src_value)) + { + ctxt . Throw(); + return; + } } - else if (p_recursive) + else if (p_op == kMCArrayDoUnionOpUnionRecursively) { - MCArraysDoUnion(ctxt, t_dst_value, t_src_value, true, &t_recursive_result); - + MCAutoValueRef t_recursive_result; + MCArraysDoUnion(ctxt, t_dst_value, t_src_value, p_op, &t_recursive_result); if (ctxt . HasError()) return; + + if (!MCArrayStoreValue(*t_result, ctxt . GetCaseSensitive(), t_key, *t_recursive_result)) + { + ctxt . Throw(); + return; + } } - - if (!MCArrayStoreValue(*t_result, ctxt . GetCaseSensitive(), t_key, *t_recursive_result)) + else if (p_op == kMCArrayDoUnionOpSymmetricDifference) { - ctxt . Throw(); - return; + if (!MCArrayRemoveValue(*t_result, ctxt.GetCaseSensitive(), t_key)) + { + ctxt.Throw(); + return; + } } } @@ -655,8 +688,28 @@ void MCArraysDoUnion(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, bo // end if // end repeat // +// if left is not an array then no-op +// else if right is not an array then left = empty +// +// Semantics of 'difference tLeft with tRight' +// +// repeat for each key tKey in tLeft +// if tKey is among the keys of tRight then +// delete variable tLeft[tKey] +// end if +// end repeat +// +// if left is not an array then no-op +// else if right is not an array then no-op + +enum MCArrayDoIntersectOp +{ + kMCArrayDoIntersectOpIntersect, + kMCArrayDoIntersectOpIntersectRecursively, + kMCArrayDoIntersectOpDifference, +}; -void MCArraysDoIntersect(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, bool p_recursive, MCValueRef& r_result) +static void MCArraysDoIntersect(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCArrayDoIntersectOp p_op, MCValueRef& r_result) { if (!MCValueIsArray(p_dst)) { @@ -664,10 +717,17 @@ void MCArraysDoIntersect(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src return; } - + if (!MCValueIsArray(p_src)) { - r_result = MCValueRetain(kMCEmptyString); + if (p_op != kMCArrayDoIntersectOpDifference) + { + r_result = MCValueRetain(kMCEmptyString); + } + else + { + r_result = MCValueRetain(p_dst); + } return; } @@ -691,11 +751,8 @@ void MCArraysDoIntersect(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src { bool t_key_exists; t_key_exists = MCArrayFetchValue(t_src_array, ctxt . GetCaseSensitive(), t_key, t_src_value); - - if (t_key_exists && !p_recursive) - continue; - - if (!t_key_exists) + + if (t_key_exists == (p_op == kMCArrayDoIntersectOpDifference)) { if (!MCArrayRemoveValue(*t_result, ctxt . GetCaseSensitive(), t_key)) { @@ -703,10 +760,10 @@ void MCArraysDoIntersect(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src return; } } - else if (p_recursive) + else if (p_op == kMCArrayDoIntersectOpIntersectRecursively) { MCAutoValueRef t_recursive_result; - MCArraysDoIntersect(ctxt, t_dst_value, t_src_value, true, &t_recursive_result); + MCArraysDoIntersect(ctxt, t_dst_value, t_src_value, p_op, &t_recursive_result); if (ctxt . HasError()) return; @@ -721,24 +778,35 @@ void MCArraysDoIntersect(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src void MCArraysExecUnion(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result) { - MCArraysDoUnion(ctxt, p_dst, p_src, false, r_result); + MCArraysDoUnion(ctxt, p_dst, p_src, kMCArrayDoUnionOpUnion, r_result); +} + +void MCArraysExecUnionRecursively(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result) +{ + MCArraysDoUnion(ctxt, p_dst, p_src, kMCArrayDoUnionOpUnionRecursively, r_result); +} + +void MCArraysExecSymmetricDifference(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result) +{ + MCArraysDoUnion(ctxt, p_dst, p_src, kMCArrayDoUnionOpSymmetricDifference, r_result); } void MCArraysExecIntersect(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result) { - MCArraysDoIntersect(ctxt, p_dst, p_src, false, r_result); + MCArraysDoIntersect(ctxt, p_dst, p_src, kMCArrayDoIntersectOpIntersect, r_result); } -void MCArraysExecUnionRecursive(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result) +void MCArraysExecIntersectRecursively(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result) { - MCArraysDoUnion(ctxt, p_dst, p_src, true, r_result); + MCArraysDoIntersect(ctxt, p_dst, p_src, kMCArrayDoIntersectOpIntersectRecursively, r_result); } -void MCArraysExecIntersectRecursive(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result) +void MCArraysExecDifference(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result) { - MCArraysDoIntersect(ctxt, p_dst, p_src, true, r_result); + MCArraysDoIntersect(ctxt, p_dst, p_src, kMCArrayDoIntersectOpDifference, r_result); } + //////////////////////////////////////////////////////////////////////////////// void MCArraysEvalArrayEncode(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef p_version, MCDataRef& r_encoding) diff --git a/engine/src/exec.h b/engine/src/exec.h index 5c9dd84c340..15aa851a3f6 100644 --- a/engine/src/exec.h +++ b/engine/src/exec.h @@ -1870,9 +1870,11 @@ extern MCExecMethodInfo *kMCArraysExecSplitMethodInfo; extern MCExecMethodInfo *kMCArraysExecSplitByColumnMethodInfo; extern MCExecMethodInfo *kMCArraysExecSplitAsSetMethodInfo; extern MCExecMethodInfo *kMCArraysExecUnionMethodInfo; +extern MCExecMethodInfo *kMCArraysExecUnionRecursivelyMethodInfo; extern MCExecMethodInfo *kMCArraysExecIntersectMethodInfo; -extern MCExecMethodInfo *kMCArraysExecUnionRecursiveMethodInfo; -extern MCExecMethodInfo *kMCArraysExecIntersectRecursiveMethodInfo; +extern MCExecMethodInfo *kMCArraysExecIntersectRecursivelyMethodInfo; +extern MCExecMethodInfo *kMCArraysExecDifferenceMethodInfo; +extern MCExecMethodInfo *kMCArraysExecSymmetricDifferenceMethodInfo; extern MCExecMethodInfo *kMCArraysEvalArrayEncodeMethodInfo; extern MCExecMethodInfo *kMCArraysEvalArrayDecodeMethodInfo; extern MCExecMethodInfo *kMCArraysEvalMatrixMultiplyMethodInfo; @@ -1900,9 +1902,11 @@ void MCArraysExecSplit(MCExecContext& ctxt, MCStringRef p_string, MCStringRef p_ void MCArraysExecSplitByColumn(MCExecContext& ctxt, MCStringRef p_string, MCArrayRef& r_array); void MCArraysExecSplitAsSet(MCExecContext& ctxt, MCStringRef p_string, MCStringRef p_element_delimiter, MCArrayRef& r_array); void MCArraysExecUnion(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result); +void MCArraysExecUnionRecursively(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result); void MCArraysExecIntersect(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result); -void MCArraysExecUnionRecursive(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result); -void MCArraysExecIntersectRecursive(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result); +void MCArraysExecIntersectRecursively(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result); +void MCArraysExecDifference(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result); +void MCArraysExecSymmetricDifference(MCExecContext& ctxt, MCValueRef p_dst, MCValueRef p_src, MCValueRef& r_result); void MCArraysEvalArrayEncode(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef version, MCDataRef& r_encoding); void MCArraysEvalArrayDecode(MCExecContext& ctxt, MCDataRef p_encoding, MCArrayRef& r_array); void MCArraysEvalMatrixMultiply(MCExecContext& ctxt, MCArrayRef p_left, MCArrayRef p_right, MCArrayRef& r_result); diff --git a/engine/src/lextable.cpp b/engine/src/lextable.cpp index 5ed6a3fa641..33e316c7f54 100644 --- a/engine/src/lextable.cpp +++ b/engine/src/lextable.cpp @@ -309,6 +309,7 @@ LT command_table[] = {"define", TT_STATEMENT, S_DEFINE}, {"dehilite", TT_STATEMENT, S_UNHILITE}, {"delete", TT_STATEMENT, S_DELETE}, + {"difference", TT_STATEMENT, S_DIFFERENCE}, // MW-2008-11-05: [[ Dispatch Command ]] 'dispatch' is a statement keyword {"disable", TT_STATEMENT, S_DISABLE}, {"dispatch", TT_STATEMENT, S_DISPATCH}, @@ -410,6 +411,7 @@ LT command_table[] = {"stop", TT_STATEMENT, S_STOP}, {"subtract", TT_STATEMENT, S_SUBTRACT}, {"switch", TT_STATEMENT, S_SWITCH}, + {"symmetric", TT_STATEMENT, S_SYMMETRIC}, {"then", TT_THEN, S_UNDEFINED}, {"throw", TT_STATEMENT, S_THROW}, {"toplevel", TT_STATEMENT, S_TOP_LEVEL}, diff --git a/engine/src/newobj.cpp b/engine/src/newobj.cpp index bed3b3f443f..4257872acc6 100644 --- a/engine/src/newobj.cpp +++ b/engine/src/newobj.cpp @@ -91,6 +91,8 @@ MCStatement *MCN_new_statement(int2 which) return new MCDefine; case S_DELETE: return new MCDelete; + case S_DIFFERENCE: + return new MCSetOp(MCSetOp::kOpDifference); case S_DISABLE: return new MCDisable; // MW-2008-11-05: [[ Dispatch Command ]] Create a dispatch statement object @@ -149,7 +151,7 @@ MCStatement *MCN_new_statement(int2 which) case S_INSERT: return new MCInsert; case S_INTERSECT: - return new MCArrayIntersectCmd; + return new MCSetOp(MCSetOp::kOpIntersect); case S_KILL: return new MCKill; case S_LAUNCH: @@ -269,6 +271,8 @@ MCStatement *MCN_new_statement(int2 which) return new MCSubtract; case S_SWITCH: return new MCSwitch; + case S_SYMMETRIC: + return new MCSetOp(MCSetOp::kOpSymmetricDifference); case S_THROW: return new MCThrowKeyword; case S_TOP_LEVEL: @@ -286,7 +290,7 @@ MCStatement *MCN_new_statement(int2 which) case S_UNHILITE: return new MCUnhilite; case S_UNION: - return new MCArrayUnionCmd; + return new MCSetOp(MCSetOp::kOpUnion); case S_UNLOAD: return new MCUnload; case S_UNLOCK: diff --git a/engine/src/parsedef.h b/engine/src/parsedef.h index c933c73c752..ff136762d37 100644 --- a/engine/src/parsedef.h +++ b/engine/src/parsedef.h @@ -2016,6 +2016,7 @@ enum Statements { S_DECRYPT, S_DEFINE, S_DELETE, + S_DIFFERENCE, S_DISABLE, // MW-2008-11-05: [[ Dispatch Command ]] This is the 'dispatch' token's tag S_DISPATCH, @@ -2109,6 +2110,7 @@ enum Statements { S_START, S_STOP, S_SUBTRACT, + S_SYMMETRIC, S_SWITCH, S_THROW, S_TOP_LEVEL, diff --git a/engine/src/parseerrors.h b/engine/src/parseerrors.h index 3f0f34d6934..53038cf5967 100644 --- a/engine/src/parseerrors.h +++ b/engine/src/parseerrors.h @@ -1780,6 +1780,15 @@ enum Parse_errors // {PE-0576} messageDigest: bad parameters PE_MESSAGEDIGEST_BADPARAM, + + // {PE-0577} setop: missing 'difference' + PE_ARRAYOP_NODIFFERENCE, + + // {PE-0578} setop: 'recursive' only makes sense for union or intersect + PE_ARRAYOP_BADRECURSIVE, + + // {PE-0579} setop: destination is not a container (did you mean to use 'into'?) + PE_ARRAYOP_DSTNOTCONTAINER, }; extern const char *MCparsingerrors; diff --git a/tests/lcs/core/array/setoperations.livecodescript b/tests/lcs/core/array/setoperations.livecodescript index 7f13dac96d9..425ca1569d1 100644 --- a/tests/lcs/core/array/setoperations.livecodescript +++ b/tests/lcs/core/array/setoperations.livecodescript @@ -62,6 +62,23 @@ on TestArrayUnion TestAssert "union: left array, right array, additional key", tArray3[1] is "b" end TestArrayUnion +on TestArrayUnionInto + local tLeft, tRight, tOut + put "a" into tLeft[1] + put "b" into tRight[2] + union tLeft with tRight into tOut + TestAssert "union into: left array unchanged", \ + the number of elements in tLeft is 1 and \ + tLeft[1] is "a" + TestAssert "union into: right array unchanged", \ + the number of elements in tRight is 1 and \ + tRight[2] is "b" + TestAssert "union into: out correct", \ + the number of elements in tOut is 2 and \ + tOut[1] is "a" and \ + tOut[2] is "b" +end TestArrayUnionInto + on TestNestedArrayNonRecursiveUnion local tLeftArray2, tRightArray2 @@ -109,6 +126,118 @@ on TestNestedArrayRecursiveUnion end TestNestedArrayRecursiveUnion +on TestArrayRecursiveUnionInto + local tLeft, tRight, tOut + put "a" into tLeft[1][1] + put "b" into tRight[1][2] + union tLeft with tRight recursively into tOut + TestAssert "union into: left array unchanged", \ + the number of elements in tLeft is 1 and \ + the number of elements in tLeft[1] is 1 and \ + tLeft[1][1] is "a" + TestAssert "union into: right array unchanged", \ + the number of elements in tRight is 1 and \ + the number of elements in tRight[1] is 1 and \ + tRight[1][2] is "b" + TestAssert "union into: out correct", \ + the number of elements in tOut is 1 and \ + the number of elements in tOut[1] is 2 and \ + tOut[1][1] is "a" and \ + tOut[1][2] is "b" +end TestArrayRecursiveUnionInto + +/* +Semantics of array symmetric difference: + +function ArraySymmetricDifference(pLeft, pRight) + repeat for each key tKey in pRight + if tKey is not among the keys of pLeft then + put pRight[tKey] into pLeft[tKey] + else + delete variable pLeft[tKey] + end if + end repeat + + return pLeft +end ArraySymmetricDifference + +*/ +on TestArraySymmetricDifference + local tLeftNonArray, tRightNonArray + + put "a" into tLeftNonArray + put "b" into tRightNonArray + symmetric difference tLeftNonArray with tRightNonArray + TestAssert "symmetric difference: left non-array, right non-array", tLeftNonArray is "a" + + local tRightArray + put "a" into tLeftNonArray + put "b" into tRightArray[1] + symmetric difference tLeftNonArray with tRightArray + TestAssert "symmetric difference: left non-array, right array", tLeftNonArray[1] is "b" + + local tLeftArray + put "a" into tLeftArray[1] + symmetric difference tLeftArray with tRightNonArray + TestAssert "symmetric difference: left array, right non-array", tLeftArray[1] is "a" + + put empty into tLeftArray + put "a" into tLeftArray[1] + put empty into tRightArray + put "b" into tRightArray[1] + symmetric difference tLeftArray with tRightArray + TestAssert "symmetric difference: left array, right array, same key", \ + tLeftArray is not an array + + put empty into tLeftArray + put "a" into tLeftArray[1] + put empty into tRightArray + put "b" into tRightArray[1] + put "c" into tRightArray[2] + symmetric difference tLeftArray with tRightArray + TestAssert "symmetric difference: left array, right array, right superset of left", \ + "1" is not among the keys of tLeftArray and tLeftArray[2] is "c" + + put empty into tLeftArray + put "a" into tLeftArray[1] + put "c" into tLeftArray[2] + put empty into tRightArray + put "b" into tRightArray[1] + symmetric difference tLeftArray with tRightArray + TestAssert "symmetric difference: left array, right array, right subset of left", \ + "1" is not among the keys of tLeftArray and tLeftArray[2] is "c" + + put empty into tLeftArray + put "a" into tLeftArray[1] + put "c" into tLeftArray[2] + put empty into tRightArray + put "b" into tRightArray[1] + put "d" into tRightArray[3] + symmetric difference tLeftArray with tRightArray + TestAssert "symmetric difference: left array, right array, neither is subset of other", \ + the number of elements in tLeftArray is 2 and \ + "1" is not among the keys of tLeftArray and \ + tLeftArray[2] is "c" and \ + tLeftArray[3] is "d" +end TestArraySymmetricDifference + +on TestArraySymmetricDifferenceInto + local tLeft, tRight, tOut + put "a" into tLeft[1] + put "b" into tRight[2] + symmetric difference tLeft with tRight into tOut + TestAssert "symmetric difference into: left array unchanged", \ + the number of elements in tLeft is 1 and \ + tLeft[1] is "a" + TestAssert "symmetric difference into: right array unchanged", \ + the number of elements in tRight is 1 and \ + tRight[2] is "b" + TestAssert "symmetric difference into: out correct", \ + the number of elements in tOut is 2 and \ + tOut[1] is "a" and \ + tOut[2] is "b" +end TestArraySymmetricDifferenceInto + /* Semantics of array intersect: @@ -160,6 +289,24 @@ on TestArrayIntersect end TestArrayIntersect +on TestArrayIntersectInto + local tLeft, tRight, tOut + put "a" into tLeft[1] + put "c" into tLeft[2] + put "b" into tRight[2] + put "d" into tRight[3] + intersect tLeft with tRight into tOut + TestAssert "intersect into: left array unchanged", \ + the number of elements in tLeft is 2 and \ + tLeft[1] is "a" and tLeft[2] is "c" + TestAssert "intersect into: right array unchanged", \ + the number of elements in tRight is 2 and \ + tRight[2] is "b" and tRight[3] is "d" + TestAssert "intersect into: out correct", \ + the number of elements in tOut is 1 and \ + tOut[2] is "c" +end TestArrayIntersectInto + on TestNestedArrayNonRecursiveIntersect local tLeftArray2, tRightArray2 @@ -209,3 +356,114 @@ on TestNestedArrayRecursiveIntersect TestAssert "recursive intersect: left value array, right value array, key present in left and right", tLeftArray4[1][2] is "b" end TestNestedArrayRecursiveIntersect + +on TestArrayRecursiveIntersectInto + local tLeft, tRight, tOut + put "a" into tLeft[1][1] + put "c" into tLeft[1][2] + put "b" into tRight[1][2] + put "d" into tRight[1][3] + intersect tLeft with tRight recursively into tOut + TestAssert "intersect into: left array unchanged", \ + the number of elements in tLeft is 1 and \ + the number of elements in tLeft[1] is 2 and \ + tLeft[1][1] is "a" and tLeft[1][2] is "c" + TestAssert "intersect into: right array unchanged", \ + the number of elements in tRight is 1 and \ + the number of elements in tRight[1] is 2 and \ + tRight[1][2] is "b" and tRight[1][3] is "d" + TestAssert "intersect into: out correct", \ + the number of elements in tOut is 1 and \ + the number of elements in tOut[1] is 1 and \ + tOut[1][2] is "c" +end TestArrayRecursiveIntersectInto + +/* +Semantics of array difference: + +function ArrayDifference(pLeft, pRight) + repeat for each key tKey in pLeft + if tKey is among the keys of pRight then + delete variable pLeft[tKey] + end if + end repeat + + return pLeft +end ArrayDifference + +*/ +on TestArrayDifference + local tLeftNonArray, tRightNonArray + + put "a" into tLeftNonArray + put "b" into tRightNonArray + difference tLeftNonArray with tRightNonArray + TestAssert "difference: left non-array, right non-array", tLeftNonArray is "a" + + local tRightArray + put "a" into tLeftNonArray + put "b" into tRightArray[1] + difference tLeftNonArray with tRightArray + TestAssert "difference: left non-array, right array", tLeftNonArray is "a" + + local tLeftArray + put "a" into tLeftArray[1] + difference tLeftArray with tRightNonArray + TestAssert "difference: left array, right non-array", tLeftArray[1] is "a" + + put empty into tLeftArray + put "a" into tLeftArray[1] + put empty into tRightArray + put "b" into tRightArray[1] + difference tLeftArray with tRightArray + TestAssert "difference: left array, right array, same key", \ + tLeftArray is not an array + + put empty into tLeftArray + put "a" into tLeftArray[1] + put empty into tRightArray + put "b" into tRightArray[1] + put "c" into tRightArray[2] + difference tLeftArray with tRightArray + TestAssert "difference: left array, right array, right superset of left", \ + tLeftArray is not an array + + put empty into tLeftArray + put "a" into tLeftArray[1] + put "c" into tLeftArray[2] + put empty into tRightArray + put "b" into tRightArray[1] + difference tLeftArray with tRightArray + TestAssert "difference: left array, right array, right subset of left", \ + the number of elements in tLeftArray is 1 and \ + tLeftArray[2] is "c" + + put empty into tLeftArray + put "a" into tLeftArray[1] + put "c" into tLeftArray[2] + put empty into tRightArray + put "b" into tRightArray[1] + put "d" into tRightArray[3] + difference tLeftArray with tRightArray + TestAssert "difference: left array, right array, neither is subset of other", \ + the number of elements in tLeftArray is 1 and \ + tLeftArray[2] is "c" +end TestArrayDifference + +on TestArrayDifferenceInto + local tLeft, tRight, tOut + put "a" into tLeft[1] + put "c" into tLeft[2] + put "b" into tRight[2] + put "d" into tRight[3] + difference tLeft with tRight into tOut + TestAssert "difference into: left array unchanged", \ + the number of elements in tLeft is 2 and \ + tLeft[1] is "a" and tLeft[2] is "c" + TestAssert "difference into: right array unchanged", \ + the number of elements in tRight is 2 and \ + tRight[2] is "b" and tRIght[3] is "d" + TestAssert "difference into: out correct", \ + the number of elements in tOut is 1 and \ + tOut[1] is "a" +end TestArrayDifferenceInto