Skip to content

Commit c195032

Browse files
author
Monte Goulding
committed
[[ FilterArray ]] Implement filter keys|elements of <array>
1 parent b05310e commit c195032

File tree

11 files changed

+917
-298
lines changed

11 files changed

+917
-298
lines changed

docs/dictionary/command/filter.lcdoc

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ Name: filter
22

33
Type: command
44

5-
Syntax: filter [lines of | items of] <filterSource> {with | without | [not] matching} [wildcard pattern | regex pattern] <filterPattern> [into <targetContainer>]
5+
Syntax: filter [{lines | items | keys | elements} of] <filterSource> {with | without | [not] matching} [{wildcard pattern | regex pattern}] <filterPattern> [into <targetContainer>]
66

77
Summary:
88
Filters each line or item in a source container or expression, removing
9-
the lines or items that do or don't match a pattern.
9+
the lines, items, keys or elements that do or don't match a pattern.
1010

1111
Introduced: 1.0
1212

@@ -38,34 +38,49 @@ put "123,234,345,456,567,678,789" into field "Numbers"
3838
filter items of field "Numbers" matching "*[!35-9]"
3939
-- field contains "234"
4040

41+
Example:
42+
local tArray
43+
put true into tArray["foo"]
44+
put false into tArray["bar"]
45+
filter tArray with "f*"
46+
put the keys of tArray is "foo"
47+
48+
Example:
49+
local tArray
50+
put true into tArray["foo"]
51+
put false into tArray["bar"]
52+
filter elements of tArray without "f*"
53+
put the keys of tArray is "foo"
54+
4155
Parameters:
4256
filterSource:
43-
An expression that evaluates to a string, or a container.
57+
An expression that evaluates to a string, array, or a container.
4458

4559
filterPattern:
46-
An expression used to match certain lines or items.
60+
An expression used to match certain lines, items, keys or elements.
4761

4862
targetContainer:
4963
An expression that evaluates to a container
5064

5165
It:
5266
If the filter command is used on a <filterSource> which is not a
5367
container, and no <targetContainer> is specified, the filtered string
54-
will be placed in the <it> variable.
68+
or array will be placed in the <it> variable.
5569

5670
Description:
57-
Use the <filter> command to pick specific lines or items in a container
58-
or expression.
71+
Use the <filter> command to pick specific lines, items, keys or elements
72+
in a container or expression.
5973

6074
The <filter>...with form and the <filter>...matching form retain the
61-
lines or items that contain a match for the specified <filterPattern>.
75+
lines, items, keys or elements that contain a match for the specified
76+
<filterPattern>.
6277

6378
The <filter>...without form and the <filter>...not matching form discard
64-
the lines or items that do not contain a match for the specified
65-
<filterPattern>.
79+
the lines, items, keys or elements that do not contain a match for the
80+
specified <filterPattern>.
6681

67-
If you don't specify lines or items, all lines of the container are
68-
filtered.
82+
If you don't specify lines, items, keys or elements then lines are
83+
filtered by default.
6984

7085
If a regex pattern is specified, the <filterPattern> evaluates to a
7186
regular expression.
@@ -121,10 +136,10 @@ could be used only for a container. The <filter>...[not] matching form
121136
was added in version 6.1 to clarify the pattern handling. The
122137
<filter>...into form was added in version 6.1. In previous versions, the
123138
filter command always replaced the contents of the original container.
139+
Filtering of array keys and elements was added in version 8.1.
124140

125141
References: replace (command), sort (command), matchChunk (function),
126142
matchText (function), replaceText (function), it (keyword),
127143
caseSensitive (property)
128144

129145
Tags: text processing
130-

docs/notes/feature-filterarray.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Filtering array keys and elements has been added to the filter command
2+
3+
The filter command now supports the filtering of arrays by matching keys
4+
or elements.
5+
6+
Example:
7+
8+
local tArray
9+
put true into tArray["foo"]
10+
put false into tArray["bar"]
11+
filter keys of tArray with "f*"
12+
put the keys of tArray is "foo"

engine/engine-sources.gypi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@
173173
'src/unicode.cpp',
174174
'src/util.cpp',
175175
'src/uuid.cpp',
176+
'src/patternmatcher.h',
177+
'src/patternmatcher.cpp',
176178

177179
# Group "Core - Objects"
178180
'src/aclip.h',

engine/src/cmdsf.cpp

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,8 +1090,8 @@ MCFilter::~MCFilter()
10901090
Parse_stat MCFilter::parse(MCScriptPoint &sp)
10911091
{
10921092
// Syntax :
1093-
// filter [ ( lines | items ) of ] <container_or_exp>
1094-
// ( with | without | [ not ] matching )
1093+
// filter [ { lines | items | keys | elements } of ] <container_or_exp>
1094+
// { with | without | [ not ] matching }
10951095
// [ { wildcard | regex } [ pattern ] ] <pattern>
10961096
// [ into <container> ]
10971097
//
@@ -1108,15 +1108,19 @@ Parse_stat MCFilter::parse(MCScriptPoint &sp)
11081108
chunktype = CT_LINE;
11091109
else if (sp.skip_token(SP_FACTOR, TT_CLASS, CT_ITEM) == PS_NORMAL)
11101110
chunktype = CT_ITEM;
1111-
// If we parsed a chunk then ensure there's an 'of'
1111+
else if (sp.skip_token(SP_FACTOR, TT_FUNCTION, F_KEYS) == PS_NORMAL)
1112+
chunktype = CT_KEY;
1113+
else if (sp.skip_token(SP_FACTOR, TT_CLASS, CT_ELEMENT) == PS_NORMAL)
1114+
chunktype = CT_ELEMENT;
1115+
// If we parsed a chunk then ensure there's an 'of'
11121116
if (chunktype != CT_UNDEFINED && sp.skip_token(SP_FACTOR, TT_OF) != PS_NORMAL)
11131117
t_error = PE_FILTER_BADDEST;
1114-
}
1115-
1116-
// If there was no error and no chunk type then default to line
1117-
if (t_error == PE_UNDEFINED && chunktype == CT_UNDEFINED)
1118-
chunktype = CT_LINE;
1119-
1118+
}
1119+
1120+
// If there was no error and no chunk type then default to line
1121+
if (t_error == PE_UNDEFINED && chunktype == CT_UNDEFINED)
1122+
chunktype = CT_LINE;
1123+
11201124
// Next parse the source container or expression
11211125
if (t_error == PE_UNDEFINED)
11221126
{
@@ -1133,10 +1137,10 @@ Parse_stat MCFilter::parse(MCScriptPoint &sp)
11331137
{
11341138
t_error = PE_FILTER_BADDEST;
11351139
}
1136-
}
1140+
}
11371141
else
1138-
MCerrorlock--;
1139-
}
1142+
MCerrorlock--;
1143+
}
11401144

11411145
// Now look for the filter mode
11421146
if (t_error == PE_UNDEFINED)
@@ -1194,56 +1198,115 @@ Parse_stat MCFilter::parse(MCScriptPoint &sp)
11941198
// JS-2013-07-01: [[ EnhancedFilter ]] Rewritten to support new syntax.
11951199
void MCFilter::exec_ctxt(MCExecContext &ctxt)
11961200
{
1197-
MCAutoStringRef t_source;
1201+
MCValueRef t_source;
11981202
MCAutoStringRef t_pattern;
1199-
MCAutoStringRef t_output;
1200-
bool stat;
12011203

1202-
// Evaluate the container or source expression
1203-
if (container != NULL)
1204+
if (container != NULL)
12041205
{
1205-
if (!ctxt . EvalExprAsStringRef(container, EE_FILTER_CANTGET, &t_source))
1206+
if (!ctxt . EvalExprAsValueRef(container, EE_FILTER_CANTGET, t_source))
12061207
return;
12071208
}
1208-
else
1209+
else
12091210
{
1210-
if (!ctxt . EvalExprAsStringRef(source, EE_FILTER_CANTGET, &t_source))
1211+
if (!ctxt . EvalExprAsValueRef(source, EE_FILTER_CANTGET, t_source))
12111212
return;
12121213
}
1213-
1214+
12141215
if (!ctxt . EvalExprAsStringRef(pattern, EE_FILTER_CANTGETPATTERN, &t_pattern))
12151216
return;
1217+
1218+
MCAutoStringRef t_source_string;
1219+
MCAutoArrayRef t_source_array;
12161220

1217-
if (container == nil && target == nil)
1221+
if (chunktype == CT_LINE || chunktype == CT_ITEM)
12181222
{
1219-
if (matchmode == MA_REGEX)
1220-
MCStringsExecFilterRegexIntoIt(ctxt, *t_source, *t_pattern, discardmatches == True, chunktype == CT_LINE);
1221-
else
1222-
MCStringsExecFilterWildcardIntoIt(ctxt, *t_source, *t_pattern, discardmatches == True, chunktype == CT_LINE);
1223+
if (!ctxt . ConvertToString(t_source, &t_source_string))
1224+
{
1225+
ctxt . LegacyThrow(EE_FILTER_CANTGET);
1226+
return;
1227+
}
12231228
}
12241229
else
12251230
{
1226-
if (matchmode == MA_REGEX)
1227-
MCStringsExecFilterRegex(ctxt, *t_source, *t_pattern, discardmatches == True, chunktype == CT_LINE, &t_output);
1228-
else
1229-
MCStringsExecFilterWildcard(ctxt, *t_source, *t_pattern, discardmatches == True, chunktype == CT_LINE, &t_output);
1231+
if (!ctxt . ConvertToArray(t_source, &t_source_array))
1232+
{
1233+
ctxt . LegacyThrow(EE_FILTER_CANTGET);
1234+
return;
1235+
}
12301236
}
12311237

1238+
bool stat;
12321239

1233-
if ((target != nil || container != nil) && *t_output != nil)
1240+
if (container == nil && target == nil)
12341241
{
1235-
if (target != nil)
1236-
stat = target -> set(ctxt, PT_INTO, *t_output);
1237-
else
1238-
stat = container -> set(ctxt, PT_INTO, *t_output);
12391242

1240-
if (!stat)
1243+
if (chunktype == CT_LINE || chunktype == CT_ITEM)
12411244
{
1242-
ctxt . LegacyThrow(EE_FILTER_CANTSET);
1243-
return;
1245+
if (matchmode == MA_REGEX)
1246+
MCStringsExecFilterRegexIntoIt(ctxt, *t_source_string, *t_pattern, discardmatches == True, chunktype == CT_LINE);
1247+
else
1248+
MCStringsExecFilterWildcardIntoIt(ctxt, *t_source_string, *t_pattern, discardmatches == True, chunktype == CT_LINE);
1249+
}
1250+
else
1251+
{
1252+
if (matchmode == MA_REGEX)
1253+
MCArraysExecFilterRegexIntoIt(ctxt, *t_source_array, *t_pattern, discardmatches == True, chunktype == CT_KEY);
1254+
else
1255+
MCArraysExecFilterWildcardIntoIt(ctxt, *t_source_array, *t_pattern, discardmatches == True, chunktype == CT_KEY);
12441256
}
12451257
}
1258+
else
1259+
{
1260+
if (chunktype == CT_LINE || chunktype == CT_ITEM)
1261+
{
1262+
MCAutoStringRef t_output_string;
1263+
1264+
if (matchmode == MA_REGEX)
1265+
MCStringsExecFilterRegex(ctxt, *t_source_string, *t_pattern, discardmatches == True, chunktype == CT_LINE, &t_output_string);
1266+
else
1267+
MCStringsExecFilterWildcard(ctxt, *t_source_string, *t_pattern, discardmatches == True, chunktype == CT_LINE, &t_output_string);
1268+
1269+
if ((target != nil || container != nil) && *t_output_string != nil)
1270+
{
1271+
if (target != nil)
1272+
stat = target -> set(ctxt, PT_INTO, *t_output_string);
1273+
else
1274+
stat = container -> set(ctxt, PT_INTO, *t_output_string);
1275+
1276+
if (!stat)
1277+
{
1278+
ctxt . LegacyThrow(EE_FILTER_CANTSET);
1279+
return;
1280+
}
1281+
}
12461282

1283+
}
1284+
else
1285+
{
1286+
MCAutoArrayRef t_output_array;
1287+
1288+
if (matchmode == MA_REGEX)
1289+
MCArraysExecFilterRegex(ctxt, *t_source_array, *t_pattern, discardmatches == True, chunktype == CT_KEY, &t_output_array);
1290+
else
1291+
MCArraysExecFilterWildcard(ctxt, *t_source_array, *t_pattern, discardmatches == True, chunktype == CT_KEY, &t_output_array);
1292+
1293+
if ((target != nil || container != nil) && *t_output_array != nil)
1294+
{
1295+
if (target != nil)
1296+
stat = target -> set(ctxt, PT_INTO, *t_output_array);
1297+
else
1298+
stat = container -> set(ctxt, PT_INTO, *t_output_array);
1299+
1300+
if (!stat)
1301+
{
1302+
ctxt . LegacyThrow(EE_FILTER_CANTSET);
1303+
return;
1304+
}
1305+
}
1306+
1307+
}
1308+
}
1309+
12471310
if (ctxt . HasError())
12481311
ctxt . LegacyThrow(EE_FILTER_CANTSET);
12491312
}

engine/src/exec-array.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ along with LiveCode. If not see <http://www.gnu.org/licenses/>. */
3232
#include "exec-interface.h"
3333

3434
#include "stackfileformat.h"
35+
#include "patternmatcher.h"
3536

3637
////////////////////////////////////////////////////////////////////////////////
3738

@@ -1306,3 +1307,86 @@ void MCArraysEvalIsNotAmongTheKeysOf(MCExecContext& ctxt, MCNameRef p_key, MCArr
13061307
}
13071308

13081309
////////////////////////////////////////////////////////////////////////////////
1310+
1311+
void MCArraysExecFilter(MCExecContext& ctxt, MCArrayRef p_source, bool p_without, MCPatternMatcher *p_matcher, bool p_match_key, MCArrayRef &r_result)
1312+
{
1313+
MCAutoArrayRef t_result;
1314+
if (!MCArrayMutableCopy(p_source, &t_result))
1315+
return;
1316+
1317+
MCNameRef t_key;
1318+
MCValueRef t_value;
1319+
uintptr_t t_iterator;
1320+
t_iterator = 0;
1321+
1322+
while(MCArrayIterate(p_source, t_iterator, t_key, t_value))
1323+
{
1324+
bool t_match = p_matcher -> match(ctxt, t_key, p_match_key);
1325+
1326+
if ((t_match && p_without) || (!t_match && !p_without))
1327+
{
1328+
if (!MCArrayRemoveValue(*t_result, ctxt . GetCaseSensitive(), t_key))
1329+
{
1330+
ctxt . Throw();
1331+
return;
1332+
}
1333+
1334+
}
1335+
}
1336+
1337+
r_result = MCValueRetain(*t_result);
1338+
}
1339+
1340+
void MCArraysExecFilterWildcard(MCExecContext& ctxt, MCArrayRef p_source, MCStringRef p_pattern, bool p_without, bool p_match_keys, MCArrayRef &r_result)
1341+
{
1342+
// Create the pattern matcher
1343+
MCPatternMatcher *t_matcher;
1344+
t_matcher = new MCWildcardMatcher(p_pattern, p_source, ctxt . GetStringComparisonType());
1345+
1346+
MCArraysExecFilter(ctxt, p_source, p_without, t_matcher, p_match_keys, r_result);
1347+
1348+
delete t_matcher;
1349+
}
1350+
1351+
void MCArraysExecFilterRegex(MCExecContext& ctxt, MCArrayRef p_source, MCStringRef p_pattern, bool p_without, bool p_match_keys, MCArrayRef &r_result)
1352+
{
1353+
// Create the pattern matcher
1354+
MCPatternMatcher *t_matcher;
1355+
t_matcher = new MCRegexMatcher(p_pattern, p_source, ctxt . GetStringComparisonType());
1356+
1357+
MCAutoStringRef t_regex_error;
1358+
if (!t_matcher -> compile(&t_regex_error))
1359+
{
1360+
delete t_matcher;
1361+
ctxt . LegacyThrow(EE_MATCH_BADPATTERN);
1362+
return;
1363+
}
1364+
1365+
MCArraysExecFilter(ctxt, p_source, p_without, t_matcher, p_match_keys, r_result);
1366+
1367+
delete t_matcher;
1368+
}
1369+
1370+
void MCArraysExecFilterWildcardIntoIt(MCExecContext& ctxt, MCArrayRef p_source, MCStringRef p_pattern, bool p_without, bool p_match_keys)
1371+
{
1372+
MCAutoArrayRef t_result;
1373+
MCArraysExecFilterWildcard(ctxt, p_source, p_pattern, p_without, p_match_keys, &t_result);
1374+
1375+
if (*t_result != nil)
1376+
ctxt . SetItToValue(*t_result);
1377+
else
1378+
ctxt . SetItToEmpty();
1379+
}
1380+
1381+
void MCArraysExecFilterRegexIntoIt(MCExecContext& ctxt, MCArrayRef p_source, MCStringRef p_pattern, bool p_without, bool p_match_keys)
1382+
{
1383+
MCAutoArrayRef t_result;
1384+
MCArraysExecFilterRegex(ctxt, p_source, p_pattern, p_without, p_match_keys, &t_result);
1385+
1386+
if (*t_result != nil)
1387+
ctxt . SetItToValue(*t_result);
1388+
else
1389+
ctxt . SetItToEmpty();
1390+
}
1391+
1392+
////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)