/* Copyright (C) 2003-2013 Runtime Revolution Ltd. This file is part of LiveCode. LiveCode is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License v3 as published by the Free Software Foundation. LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with LiveCode. If not see . */ #include "prefix.h" #include "globdefs.h" #include "filedefs.h" #include "objdefs.h" #include "parsedef.h" #include "mcio.h" #include "globals.h" #include "objectstream.h" #include "util.h" #include "variable.h" #include "mcio.h" #include "exec.h" #include "exec-interface.h" //////////////////////////////////////////////////////////////////////////////// MC_EXEC_DEFINE_EVAL_METHOD(Arrays, Keys, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, Extents, 2) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, Combine, 4) // SN-2014-09-01: [[ Bug 13297 ]] Combining by column deserves its own function as it is too // different from combining by row MC_EXEC_DEFINE_EXEC_METHOD(Arrays, CombineByRow, 2) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, CombineByColumn, 2) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, CombineAsSet, 3) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, Split, 4) 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, 2) MC_EXEC_DEFINE_EXEC_METHOD(Arrays, Intersect, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, ArrayEncode, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, ArrayDecode, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, MatrixMultiply, 3) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, TransposeMatrix, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, IsAnArray, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, IsNotAnArray, 2) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, IsAmongTheKeysOf, 3) MC_EXEC_DEFINE_EVAL_METHOD(Arrays, IsNotAmongTheKeysOf, 3) //////////////////////////////////////////////////////////////////////////////// void MCArraysEvalKeys(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef& r_string) { if (MCArrayListKeys(p_array, '\n', r_string)) return; ctxt . Throw(); } //////////////////////////////////////////////////////////////////////////////// struct array_element_t { MCNameRef key; MCValueRef value; }; struct combine_array_t { uindex_t index; array_element_t *elements; }; static bool list_array_elements(void *p_context, MCArrayRef p_array, MCNameRef p_key, MCValueRef p_value) { combine_array_t *ctxt; ctxt = (combine_array_t *)p_context; ctxt -> elements[ctxt -> index] . key = p_key; ctxt -> elements[ctxt -> index] . value = p_value; ctxt -> index++; return true; } static int compare_array_element(const void *a, const void *b) { const array_element_t *t_left, *t_right; t_left = (const array_element_t *)a; t_right = (const array_element_t *)b; return MCStringCompareTo(MCNameGetString(t_left -> key), MCNameGetString(t_right -> key), kMCStringOptionCompareExact); } //////////////////////////////////////////////////////////////////////////////// // combine by row or column expects an integer-indexed array struct array_int_indexed_element_t { index_t key; MCValueRef value; }; struct combine_int_indexed_array_t { uindex_t index; array_int_indexed_element_t *elements; MCExecContext* converter; }; static bool list_int_indexed_array_elements(void *p_context, MCArrayRef p_array, MCNameRef p_key, MCValueRef p_value) { combine_int_indexed_array_t *ctxt; ctxt = (combine_int_indexed_array_t *)p_context; MCAutoNumberRef t_key; if (ctxt -> converter -> ConvertToNumber(MCNameGetString(p_key), &t_key)) { index_t t_key_num = MCNumberFetchAsInteger(*t_key); if (t_key_num < 1) // Invalid index return false; ctxt -> elements[ctxt -> index] . key = t_key_num; ctxt -> elements[ctxt -> index] . value = p_value; ++(ctxt -> index); return true; } else return false; } static int compare_int_indexed_elements(const void *a, const void* b) { const array_int_indexed_element_t *t_left, *t_right; t_left = (const array_int_indexed_element_t *)a; t_right = (const array_int_indexed_element_t *)b; return (t_left -> key - t_right -> key < 0) ? -1 : (t_left -> key != t_right -> key ? 1 : 0); } //////////////////////////////////////////////////////////////////////////////// void MCArraysExecCombine(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef p_element_delimiter, MCStringRef p_key_delimiter, MCStringRef& r_string) { bool t_success; t_success = true; uindex_t t_count; t_count = MCArrayGetCount(p_array); MCAutoStringRef t_string; if (t_success) t_success = MCStringCreateMutable(0, &t_string); combine_array_t t_lisctxt; t_lisctxt . elements = nil; if (t_success) t_success = MCMemoryNewArray(t_count, t_lisctxt . elements); if (t_success) { t_lisctxt . index = 0; MCArrayApply(p_array, list_array_elements, &t_lisctxt); qsort(t_lisctxt . elements, t_count, sizeof(array_element_t), compare_array_element); for(uindex_t i = 0; i < t_count; i++) { MCAutoStringRef t_value_as_string; t_success = ctxt . ConvertToString(t_lisctxt . elements[i] . value, &t_value_as_string); if (!t_success) break; t_success = (p_key_delimiter == nil || (MCStringAppend(*t_string, MCNameGetString(t_lisctxt . elements[i] . key)) && MCStringAppend(*t_string, p_key_delimiter)))&& MCStringAppend(*t_string, *t_value_as_string) && (i == t_count - 1 || MCStringAppend(*t_string, p_element_delimiter)); if (!t_success) break; } } if (t_success) t_success = MCStringCopy(*t_string, r_string); MCMemoryDeleteArray(t_lisctxt . elements); if (t_success) return; // Throw the current error code (since last library call returned false). ctxt . Throw(); } // Should be removed when 'combine by row' and 'combine by column' only differs by the delimiter used, // not by the way the array is handled - any index is fine for combine by row void MCArraysExecCombineByRow(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef &r_string) { MCAutoListRef t_list; MCListCreateMutable(ctxt . GetRowDelimiter(), &t_list); uindex_t t_count = MCArrayGetCount(p_array); combine_array_t t_lisctxt; bool t_success; t_lisctxt . elements = nil; t_lisctxt . index = 0; t_success = MCMemoryNewArray(t_count, t_lisctxt . elements); if (t_success) { MCArrayApply(p_array, list_array_elements, &t_lisctxt); qsort(t_lisctxt . elements, t_count, sizeof(array_element_t), compare_array_element); for (int i = 0; i < t_count && t_success; ++i) { MCAutoStringRef t_string; if (ctxt . ConvertToString(t_lisctxt . elements[i] . value, &t_string)) t_success = MCListAppend(*t_list, *t_string); else t_success = false; } MCMemoryDeleteArray(t_lisctxt . elements); } if (t_success && MCListCopyAsString(*t_list, r_string)) return; ctxt . Throw(); } // SN-2014-09-01: [[ Bug 13297 ]] Combining by column deserves its own function as it is too // different from combining by row void MCArraysExecCombineByColumn(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef &r_string) { MCStringRef t_row_delimiter, t_col_delimiter; t_row_delimiter = ctxt . GetRowDelimiter(); t_col_delimiter = ctxt . GetColumnDelimiter(); MCAutoListRef t_list; MCListCreateMutable(t_row_delimiter, &t_list); uindex_t t_count = MCArrayGetCount(p_array); combine_int_indexed_array_t t_lisctxt; bool t_success; t_lisctxt . elements = nil; t_lisctxt . index = 0; t_lisctxt . converter = &ctxt; t_success = MCMemoryNewArray(t_count, t_lisctxt . elements); if (t_success) { if (MCArrayApply(p_array, list_int_indexed_array_elements, &t_lisctxt)) { bool t_valid_keys; qsort(t_lisctxt . elements, t_count, sizeof(array_element_t), compare_int_indexed_elements); // Combine by row/column is only valid if all the indices are consecutive numbers // Otherwise, an empty string is returned - no error index_t t_last_index; t_valid_keys = true; t_last_index = 0; for (int i = 0; i < t_count && t_valid_keys; ++i) { if (!t_last_index) t_last_index = t_lisctxt . elements[i] . key; else t_valid_keys = ++t_last_index == t_lisctxt . elements[i] . key; } if (t_valid_keys) { // SN-2014-09-01: [[ Bug 13297 ]] // We need to store the converted strings in a array, to be able to iterate through the elements by one row-delimitated // at a time MCStringRef* t_strings; uindex_t *t_next_row_indices; t_strings = NULL; t_next_row_indices = NULL; /* UNCHECKED */ MCMemoryNewArray(t_count, t_strings); // MCMemoryNewArray initialises all t_next_row_indices elements to 0. /* UNCHECKED */ MCMemoryNewArray(t_count, t_next_row_indices); for (int i = 0; i < t_count && t_success; ++i) { if (t_lisctxt . elements[i] . key == 0) // The index 0 is ignored continue; t_success = ctxt . ConvertToString(t_lisctxt . elements[i] . value, t_strings[i]); } // SN-2014-09-01: [[ Bug 13297 ]] Added a missed part in column-combining: // only combining row-by-row the array elements. if (t_success) { uindex_t t_elements_over; // We iterate as long as one element still has uncombined rows while (t_success && t_elements_over != t_count) { MCAutoListRef t_row; t_success = MCListCreateMutable(t_col_delimiter, &t_row); t_elements_over = 0; // Iterate through all the elements of the array for (int i = 0; i < t_count && t_success; ++i) { // Only consider this element if it has any uncombined rows remaining if (t_next_row_indices[i] < MCStringGetLength(t_strings[i])) { MCRange t_cell_range; if (MCStringFind(t_strings[i], MCRangeMake(t_next_row_indices[i], UINDEX_MAX), t_row_delimiter, ctxt.GetStringComparisonType(), &t_cell_range)) { // We found a row delimiter, so we stop the copy range before it and update the next index from which to look t_success = MCListAppendSubstring(*t_row, t_strings[i], MCRangeMake(t_next_row_indices[i], t_cell_range . offset - t_next_row_indices[i])); t_next_row_indices[i] = t_cell_range . offset + t_cell_range . length; } else { // No row delimiter: we copy the remaining part of the string and mark the element // as wholly combined by setting the next index to the length of the element t_success = MCListAppendSubstring(*t_row, t_strings[i], MCRangeMake(t_next_row_indices[i], UINDEX_MAX)); t_next_row_indices[i] = MCStringGetLength(t_strings[i]); } } else { // Everything has been combined in this element t_elements_over++; MCListAppend(*t_row, kMCEmptyString); } } // One more row has been combined - doing it anyway mimics the previous behaviour of having an empty row // added in the end when combining by columns MCListAppend(*t_list, *t_row); } } MCMemoryDeleteArray(t_next_row_indices); MCMemoryDeleteArray(t_strings); } } MCMemoryDeleteArray(t_lisctxt . elements); } if (t_success && MCListCopyAsString(*t_list, r_string)) return; ctxt . Throw(); } void MCArraysExecCombineAsSet(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef p_element_delimiter, MCStringRef& r_string) { // String into which the combined keys are accumulated MCAutoStringRef t_string; // The array keys are not added in any particular order MCNameRef t_key; MCValueRef t_value_ignored; uintptr_t t_iterator = 0; while (MCArrayIterate(p_array, t_iterator, t_key, t_value_ignored)) { bool t_success; t_success = true; if (*t_string == nil) t_success = MCStringMutableCopy(MCNameGetString(t_key), &t_string); else t_success = MCStringAppendFormat(*t_string, "%@%@", p_element_delimiter, t_key); if (!t_success) { ctxt . Throw(); return; } } MCStringCopy(*t_string, r_string); } ////////// void MCArraysExecSplit(MCExecContext& ctxt, MCStringRef p_string, MCStringRef p_element_delimiter, MCStringRef p_key_delimiter, MCArrayRef& r_array) { if (MCStringSplit(p_string, p_element_delimiter, p_key_delimiter, ctxt . GetStringComparisonType(), r_array)) return; ctxt . Throw(); } void MCArraysExecSplitByColumn(MCExecContext& ctxt, MCStringRef p_string, MCArrayRef& r_array) { MCStringRef t_row_delim, t_column_delim; t_row_delim = ctxt . GetRowDelimiter(); t_column_delim = ctxt . GetColumnDelimiter(); // Output array MCAutoArrayRef t_array; if (!MCArrayCreateMutable(&t_array)) { ctxt . Throw(); return; } // Temporary array for storing columns MCAutoArray t_temp_array; // Iterate over the rows of the input string uindex_t t_offset, t_length; t_offset = 0; t_length = MCStringGetLength(p_string); bool t_success; t_success = true; uindex_t t_row_index; t_row_index = 0; while (t_success && t_offset < t_length) { // Find the end of this row MCRange t_row_found; if (!MCStringFind(p_string, MCRangeMake(t_offset, UINDEX_MAX), t_row_delim, ctxt . GetStringComparisonType(), &t_row_found)) { t_row_found . offset = t_length; t_row_found . length = 0; } // Iterate over the cells of this row uindex_t t_cell_offset, t_column_index; t_cell_offset = t_offset; t_column_index = 0; while (t_success && t_cell_offset <= t_row_found . offset) { // Find the end of this cell MCRange t_cell_found; if (!MCStringFind(p_string, MCRangeMake(t_cell_offset, UINDEX_MAX), t_column_delim, ctxt . GetStringComparisonType(), &t_cell_found) || t_cell_found . offset > t_row_found . offset) { t_cell_found . offset = t_row_found . offset; // AL-2014-08-04: [[ Bug 13090 ]] Make sure cell offset is incremented eventually when the delimiter is not found t_cell_found . length = 1; } // Check that the output array has a slot for this column if (t_temp_array.Size() <= t_column_index) t_temp_array.Extend(t_column_index + 1); // Check that a string has been created to store this column MCRange t_range; t_range = MCRangeMake(t_cell_offset, t_cell_found . offset - t_cell_offset); if (t_temp_array[t_column_index] == nil) { t_success = MCStringCreateMutable(0, t_temp_array[t_column_index]); // AL-2014-08-04: [[ Bug 13090 ]] If we are creating a new column, make sure we pad with empty cells 'above' this one uindex_t t_rows = t_row_index; while (t_success && t_rows--) t_success = MCStringAppend(t_temp_array[t_column_index], t_row_delim); if (t_success) t_success = MCStringAppendSubstring(t_temp_array[t_column_index], p_string, t_range); // SN-2014-09-01: [[ Bug 13297 ]] 'split by column' should append a row delimiter // *after* each element, including the last one if (t_success) t_success = MCStringAppend(t_temp_array[t_column_index], t_row_delim); } else { // AL-2014-06-12: [[ Bug 12610 ]] Range parameter to MCStringFormat must be a pointer to an MCRange t_success = MCStringAppendFormat(t_temp_array[t_column_index], "%*@", &t_range, p_string); // SN-2014-09-01: [[ Bug 13297 ]] 'split by column' should append a row delimiter // *after* each element, including the last one if (t_success) t_success = MCStringAppend(t_temp_array[t_column_index], t_row_delim); } // Next cell t_column_index++; t_cell_offset = t_cell_found . offset + t_cell_found . length; } // AL-2014-08-04: [[ Bug 13090 ]] Pad the rest of this row with empty cells index_t t_pad_number; t_pad_number = t_temp_array . Size() - t_column_index; if (t_success && t_pad_number > 0) { while (t_success && t_pad_number--) t_success = MCStringAppend(t_temp_array[t_column_index++], t_row_delim); } // Next row t_row_index++; t_offset = t_row_found . offset + t_row_found . length; } // Convert the temporary array into a "proper" array for (uindex_t i = 0; i < t_temp_array.Size() && t_success; i++) { t_success = MCArrayStoreValueAtIndex(*t_array, i + 1, t_temp_array[i]); MCValueRelease(t_temp_array[i]); } if (t_success) t_success = MCArrayCopy(*t_array, r_array); if (!t_success) ctxt . Throw(); } void MCArraysExecSplitAsSet(MCExecContext& ctxt, MCStringRef p_string, MCStringRef p_element_delimiter, MCArrayRef& r_array) { // Split the incoming string into its components MCAutoArrayRef t_keys; if (!MCStringSplit(p_string, p_element_delimiter, nil, ctxt . GetStringComparisonType(), &t_keys)) { ctxt . Throw(); return; } // The new array, with keys equal to the components of the string MCAutoArrayRef t_array; if (!MCArrayCreateMutable(&t_array)) { ctxt . Throw(); return; } MCNameRef t_key_ignored; MCValueRef t_value; uintptr_t t_iterator = 0; while (MCArrayIterate(*t_keys, t_iterator, t_key_ignored, t_value)) { // The value stored at each key is a boolean "true" MCNewAutoNameRef t_keyname; if (!ctxt . ConvertToName(t_value, &t_keyname) || !MCArrayStoreValue(*t_array, ctxt . GetCaseSensitive(), *t_keyname, kMCTrue)) { ctxt . Throw(); return; } } MCArrayCopy(*t_array, r_array); } //////////////////////////////////////////////////////////////////////////////// void MCArraysDoUnion(MCExecContext& ctxt, MCArrayRef p_dst_array, MCArrayRef p_src_array, bool p_recursive) { MCNameRef t_key; MCValueRef t_src_value; MCValueRef t_dst_value; uintptr_t t_iterator; t_iterator = 0; bool t_is_array; while(MCArrayIterate(p_src_array, t_iterator, t_key, t_src_value)) { if (MCArrayFetchValue(p_dst_array, ctxt . GetCaseSensitive(), t_key, t_dst_value)) { if (p_recursive && MCValueIsArray(t_dst_value) && MCValueIsArray(t_src_value)) { MCArraysExecUnionRecursive(ctxt, (MCArrayRef)t_dst_value, (MCArrayRef)t_src_value); if (ctxt . HasError()) return; } continue; } if (!MCArrayStoreValue(p_dst_array, ctxt . GetCaseSensitive(), t_key, MCValueRetain(t_src_value))) { ctxt . Throw(); return; } } } void MCArraysDoIntersect(MCExecContext& ctxt, MCArrayRef p_dst_array, MCArrayRef p_src_array, bool p_recursive) { MCNameRef t_key; MCValueRef t_src_value; MCValueRef t_dst_value; uintptr_t t_iterator; t_iterator = 0; bool t_is_array; while(MCArrayIterate(p_dst_array, t_iterator, t_key, t_dst_value)) { if (MCArrayFetchValue(p_src_array, ctxt . GetCaseSensitive(), t_key, t_src_value)) { if (p_recursive && MCValueIsArray(t_dst_value) && MCValueIsArray(t_src_value)) { MCArraysExecIntersectRecursive(ctxt, (MCArrayRef)t_dst_value, (MCArrayRef)t_src_value); if (ctxt . HasError()) return; } continue; } if (!MCArrayRemoveValue(p_dst_array, ctxt . GetCaseSensitive(), t_key)) { ctxt . Throw(); return; } } } void MCArraysExecUnion(MCExecContext& ctxt, MCArrayRef p_dst_array, MCArrayRef p_src_array) { MCArraysDoUnion(ctxt, p_dst_array, p_src_array, false); } void MCArraysExecIntersect(MCExecContext& ctxt, MCArrayRef p_dst_array, MCArrayRef p_src_array) { MCArraysDoIntersect(ctxt, p_dst_array, p_src_array, false); } void MCArraysExecUnionRecursive(MCExecContext& ctxt, MCArrayRef p_dst_array, MCArrayRef p_src_array) { MCArraysDoUnion(ctxt, p_dst_array, p_src_array, true); } void MCArraysExecIntersectRecursive(MCExecContext& ctxt, MCArrayRef p_dst_array, MCArrayRef p_src_array) { MCArraysDoIntersect(ctxt, p_dst_array, p_src_array, true); } //////////////////////////////////////////////////////////////////////////////// void MCArraysEvalArrayEncode(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef p_version, MCDataRef& r_encoding) { bool t_success; t_success = true; IO_handle t_stream_handle; t_stream_handle = nil; if (t_success) { t_stream_handle = MCS_fakeopenwrite(); if (t_stream_handle == nil) t_success = false; } // AL-2014-05-15: [[ Bug 12203 ]] Add version parameter to arrayEncode, to allow // version 7.0 variant to preserve unicode. MCInterfaceStackFileVersion t_version; if (p_version != nil) MCInterfaceStackFileVersionParse(ctxt, p_version, t_version); // AL-2014-05-22: [[ Bug 12547 ]] Make arrayEncode encode in 7.0 format by default. bool t_legacy; t_legacy = p_version != nil && t_version . version < 7000; if (t_legacy) { MCObjectOutputStream *t_stream; t_stream = nil; if (t_success) { t_stream = new MCObjectOutputStream(t_stream_handle); if (t_stream == nil) t_success = false; } if (t_success) { if (t_stream -> WriteU8(kMCEncodedValueTypeLegacyArray) != IO_NORMAL) t_success = false; } if (t_success) if (MCArraySaveToStreamLegacy(p_array, false, *t_stream) != IO_NORMAL) t_success = false; if (t_success) t_stream -> Flush(true); delete t_stream; } else { if (t_success) if (IO_write_uint1(kMCEncodedValueTypeArray, t_stream_handle) != IO_NORMAL) t_success = false; if (t_success) if (IO_write_valueref_new(p_array, t_stream_handle) != IO_NORMAL) t_success = false; } ////////// void *t_buffer; size_t t_length; t_buffer = nil; t_length = 0; if (MCS_closetakingbuffer(t_stream_handle, t_buffer, t_length) != IO_NORMAL) t_success = false; if (t_success) t_success = MCDataCreateWithBytes((const byte_t *)t_buffer, t_length, r_encoding); free(t_buffer); if (t_success) return; ctxt . Throw(); } void MCArraysEvalArrayDecode(MCExecContext& ctxt, MCDataRef p_encoding, MCArrayRef& r_array) { bool t_success; t_success = true; IO_handle t_stream_handle; t_stream_handle = nil; if (t_success) { t_stream_handle = MCS_fakeopen(MCDataGetBytePtr(p_encoding), MCDataGetLength(p_encoding)); if (t_stream_handle == nil) t_success = false; } uint8_t t_type; if (t_success) if (IO_read_uint1(&t_type, t_stream_handle) != IO_NORMAL) t_success = false; // AL-2014-05-01: [[ Bug 11989 ]] If the type is 'empty' then just return the empty array. if (t_success && t_type == kMCEncodedValueTypeEmpty) { r_array = MCValueRetain(kMCEmptyArray); return; } // AL-2014-05-15: [[ Bug 12203 ]] Check initial byte for version 7.0 encoded array. bool t_legacy; t_legacy = t_type < kMCEncodedValueTypeArray; MCArrayRef t_array; t_array = nil; if (t_success) t_success = MCArrayCreateMutable(t_array); if (t_legacy) { if (t_success) if (MCS_putback(t_type, t_stream_handle) != IO_NORMAL) t_success = false; MCObjectInputStream *t_stream; t_stream = nil; if (t_success) { t_stream = new MCObjectInputStream(t_stream_handle, MCDataGetLength(p_encoding), false); if (t_stream == nil) t_success = false; } if (t_success) if (t_stream -> ReadU8(t_type) != IO_NORMAL) t_success = false; if (t_success) if (MCArrayLoadFromStreamLegacy(t_array, *t_stream) != IO_NORMAL) t_success = false; delete t_stream; } else { if (t_success) if (IO_read_valueref_new((MCValueRef &)t_array, t_stream_handle) != IO_NORMAL) t_success = false; } MCS_close(t_stream_handle); if (t_success) { r_array = t_array; return; } MCValueRelease(t_array); ctxt . Throw(); } //////////////////////////////////////////////////////////////////////////////// bool MCArraysSplitIndexes(MCNameRef p_key, integer_t*& r_indexes, uindex_t& r_count, bool& r_all_integers) { r_indexes = nil; r_count = 0; MCStringRef t_string = MCNameGetString(p_key); uindex_t t_string_len = MCStringGetLength(t_string); if (t_string_len == 0) return true; r_all_integers = true; uindex_t t_start, t_finish; t_start = 0; t_finish = 0; for(;;) { if (!MCStringFirstIndexOfChar(t_string, ',', t_start, kMCCompareExact, t_finish)) t_finish = t_string_len; if (!MCMemoryResizeArray(r_count + 1, r_indexes, r_count)) return false; MCAutoStringRef t_substring; MCAutoNumberRef t_number; MCStringCopySubstring(t_string, MCRangeMake(t_start, t_finish - t_start), &t_substring); if (!MCNumberParse(*t_substring, &t_number)) { r_indexes[r_count - 1] = 0; r_all_integers = false; break; } else r_indexes[r_count - 1] = MCNumberFetchAsInteger(*t_number); if (t_finish >= t_string_len) break; t_start = t_finish + 1; } return true; } struct array_extent_t { integer_t min; integer_t max; }; bool MCArraysCopyExtents(MCArrayRef self, array_extent_t*& r_extents, uindex_t& r_count) { uintptr_t t_index = 0; MCNameRef t_key; MCValueRef t_value; bool t_has_extents = false; uindex_t t_dimensions = 0; MCAutoArray t_extents; while (MCArrayIterate(self, t_index, t_key, t_value)) { MCAutoPointer t_indexes; uindex_t t_index_count; bool t_all_integers; if (!MCArraysSplitIndexes(t_key, &t_indexes, t_index_count, t_all_integers)) return false; if (!t_all_integers || t_index_count == 0) { t_has_extents = false; break; } if (!t_has_extents) { t_dimensions = t_index_count; t_has_extents = true; if (!t_extents.New(t_dimensions)) return false; for (uindex_t i = 0; i < t_dimensions; i++) t_extents[i].min = t_extents[i].max = (*t_indexes)[i]; } else { if (t_dimensions != t_index_count) { t_has_extents = false; break; } for (uindex_t i = 0; i < t_dimensions; i++) { integer_t t_number = (*t_indexes)[i]; if (t_number > t_extents[i].max) t_extents[i].max = t_number; if (t_number < t_extents[i].min) t_extents[i].min = t_number; } } } if (!t_has_extents) { r_extents = nil; r_count = 0; return true; } t_extents.Take(r_extents, r_count); return true; } bool MCArraysCopyExtents(MCArrayRef self, MCListRef& r_list) { MCAutoArray t_extents; if (!MCArraysCopyExtents(self, t_extents.PtrRef(), t_extents.SizeRef())) return false; uindex_t t_dimensions = t_extents.Size(); if (t_dimensions == 0) { r_list = MCValueRetain(kMCEmptyList); return true; } MCAutoListRef t_list; if (!MCListCreateMutable('\n', &t_list)) return false; for (uindex_t i = 0; i < t_dimensions; i++) { MCAutoStringRef t_string; if (!MCStringFormat(&t_string, "%d,%d", t_extents[i].min, t_extents[i].max)) return false; if (!MCListAppend(*t_list, *t_string)) return false; } return MCListCopy(*t_list, r_list); } void MCArraysEvalExtents(MCExecContext& ctxt, MCArrayRef p_array, MCStringRef& r_string) { MCAutoListRef t_list; if (MCArraysCopyExtents(p_array, &t_list) && MCListCopyAsString(*t_list, r_string)) return; ctxt.Throw(); } //////////////////////////////////////////////////////////////////////////////// struct matrix_t { integer_t rows, row_offset; integer_t columns, column_offset; real64_t values[0]; }; inline bool MCMatrixNew(integer_t p_rows, integer_t p_cols, integer_t p_row_offset, integer_t p_col_offset, matrix_t*& r_matrix) { if (!MCMemoryNew(sizeof(matrix_t) + sizeof(real64_t) * p_rows * p_cols, (void*&)r_matrix)) return false; r_matrix->columns = p_cols; r_matrix->column_offset = p_col_offset; r_matrix->rows = p_rows; r_matrix->row_offset = p_row_offset; return true; } inline real64_t& MCMatrixEntry(matrix_t *m, integer_t r, integer_t c) { return m->values[r * m->columns + c]; } #define extent_size(x) (x.max - x.min + 1) bool MCArraysCopyMatrix(MCExecContext& ctxt, MCArrayRef self, matrix_t*& r_matrix) { MCAutoArray t_extents; if (!MCArraysCopyExtents(self, t_extents.PtrRef(), t_extents.SizeRef()) || t_extents.Size() != 2) return false; integer_t t_rows = extent_size(t_extents[0]); integer_t t_cols = extent_size(t_extents[1]); integer_t t_row_offset = t_extents[0].min; integer_t t_col_offset = t_extents[1].min; if (MCArrayGetCount(self) != t_rows * t_cols) return false; MCAutoPointer t_matrix; if (!MCMatrixNew(t_rows, t_cols, t_row_offset, t_col_offset, &t_matrix)) return false; for (integer_t row = 0; row < t_rows; row++) { for (integer_t col = 0; col < t_cols; col++) { MCAutoStringRef t_string; MCNewAutoNameRef t_name; MCValueRef t_value; if (!MCStringFormat(&t_string, "%d,%d", row + t_row_offset, col + t_col_offset) || !MCNameCreate(*t_string, &t_name) || !MCArrayFetchValue(self, true, *t_name, t_value) || !ctxt.ConvertToReal(t_value, MCMatrixEntry(*t_matrix, row, col))) return false; } } t_matrix.Take(r_matrix); return true; } bool MCArraysCreateWithMatrix(matrix_t *p_matrix, MCArrayRef& r_array) { MCAutoArrayRef t_array; if (!MCArrayCreateMutable(&t_array)) return false; for (integer_t r = 0; r < p_matrix->rows; r++) { for (integer_t c = 0; c < p_matrix->columns; c++) { MCAutoStringRef t_string; MCNewAutoNameRef t_key; MCAutoNumberRef t_value; if (!MCStringFormat(&t_string, "%d,%d", r + p_matrix->row_offset, c + p_matrix->column_offset) || !MCNameCreate(*t_string, &t_key) || !MCNumberCreateWithReal(MCMatrixEntry(p_matrix, r, c), &t_value) || !MCArrayStoreValue(*t_array, true, *t_key, *t_value)) return false; } } return MCArrayCopy(*t_array, r_array); } bool MCMatrixMultiply(matrix_t *p_a, matrix_t *p_b, matrix_t*& r_c) { if (p_a->columns != p_b->rows || p_a->column_offset != p_b->column_offset || p_a->row_offset != p_b->row_offset) return false; MCAutoPointer t_c; if (!MCMatrixNew(p_a->rows, p_b->columns, p_a->row_offset, p_a->column_offset, &t_c)) return false; for (index_t i = 0; i < p_a->rows; i++) { for (index_t j = 0; j < p_b->columns; j++) { real64_t t_total = 0; for (index_t k = 0; k < p_a->columns; k++) t_total += MCMatrixEntry(p_a, i, k) * MCMatrixEntry(p_b, k, j); MCMatrixEntry(*t_c, i, j) = t_total; } } t_c.Take(r_c); return true; } void MCArraysEvalMatrixMultiply(MCExecContext& ctxt, MCArrayRef p_left, MCArrayRef p_right, MCArrayRef& r_result) { // MW-2014-03-14: [[ Bug 11924 ]] If both are empty arrays, then the result // is empty. if (MCArrayIsEmpty(p_left) && MCArrayIsEmpty(p_right)) { r_result = MCValueRetain(kMCEmptyArray); return; } // MW-2014-03-14: [[ Bug 11924 ]] If either array is empty, then its a mismatch. MCAutoPointer t_left, t_right, t_product; if (MCArrayIsEmpty(p_left) || MCArrayIsEmpty(p_right) || !MCArraysCopyMatrix(ctxt, p_left, &t_left) || !MCArraysCopyMatrix(ctxt, p_right, &t_right) || !MCMatrixMultiply(*t_left, *t_right, &t_product)) { ctxt.LegacyThrow(EE_MATRIXMULT_MISMATCH); return; } if (MCArraysCreateWithMatrix(*t_product, r_result)) return; ctxt.Throw(); } //////////////////////////////////////////////////////////////////////////////// bool MCArraysCopyTransposed(MCArrayRef self, MCArrayRef& r_transposed) { MCAutoArray t_extents; if (!MCArraysCopyExtents(self, t_extents.PtrRef(), t_extents.SizeRef()) || t_extents.Size() != 2) return false; integer_t t_rows = extent_size(t_extents[0]); integer_t t_cols = extent_size(t_extents[1]); integer_t t_row_end = t_extents[0].min + t_rows; integer_t t_col_end = t_extents[1].min + t_cols; if (MCArrayGetCount(self) != t_rows * t_cols) return false; MCAutoArrayRef t_transposed; if (!MCArrayCreateMutable(&t_transposed)) return false; for (integer_t r = t_extents[0].min; r < t_row_end; r++) { for (integer_t c = t_extents[1].min; c < t_col_end; c++) { MCAutoStringRef t_src_string, t_dst_string; MCNewAutoNameRef t_src_name, t_dst_name; MCValueRef t_value; if (!MCStringFormat(&t_src_string, "%d,%d", r, c) || !MCStringFormat(&t_dst_string, "%d,%d", c, r)) return false; if (!MCNameCreate(*t_src_string, &t_src_name) || !MCNameCreate(*t_dst_string, &t_dst_name)) return false; if (!MCArrayFetchValue(self, true, *t_src_name, t_value) || !MCArrayStoreValue(*t_transposed, true, *t_dst_name, t_value)) return false; } } return MCArrayCopy(*t_transposed, r_transposed); } void MCArraysEvalTransposeMatrix(MCExecContext& ctxt, MCArrayRef p_matrix, MCArrayRef& r_result) { if (!MCArraysCopyTransposed(p_matrix, r_result)) ctxt.LegacyThrow(EE_TRANSPOSE_MISMATCH); } //////////////////////////////////////////////////////////////////////////////// void MCArraysEvalIsAnArray(MCExecContext& ctxt, MCValueRef p_value, bool& r_result) { r_result = MCValueGetTypeCode(p_value) == kMCValueTypeCodeArray; } void MCArraysEvalIsNotAnArray(MCExecContext& ctxt, MCValueRef p_value, bool& r_result) { r_result = MCValueGetTypeCode(p_value) != kMCValueTypeCodeArray; } //////////////////////////////////////////////////////////////////////////////// void MCArraysEvalIsAmongTheKeysOf(MCExecContext& ctxt, MCNameRef p_key, MCArrayRef p_array, bool& r_result) { MCValueRef t_value; r_result = MCArrayFetchValue(p_array, ctxt.GetCaseSensitive(), p_key, t_value); } void MCArraysEvalIsNotAmongTheKeysOf(MCExecContext& ctxt, MCNameRef p_key, MCArrayRef p_array, bool& r_result) { MCArraysEvalIsAmongTheKeysOf(ctxt, p_key, p_array, r_result); r_result = !r_result; } ////////////////////////////////////////////////////////////////////////////////