Added sort method for the list type.
M jasm/assemble/assembler_impl/assembler_impl.cpp +1 -0
@@ 377,6 377,7 @@ void Assembler::setup_fundamental_types(
 		type.name_hashes[static_cast<uint32_t>(ListProperties::Erase)] = hash_constant(0xa55568a0a6adcd51ULL, "erase");
 		type.name_hashes[static_cast<uint32_t>(ListProperties::Keep)] = hash_constant(0xe994c15f836f30b9ULL, "keep");
 		type.name_hashes[static_cast<uint32_t>(ListProperties::Clear)] = hash_constant(0xdb95283bae679a06ULL, "clear");
+		type.name_hashes[static_cast<uint32_t>(ListProperties::Sort)] = hash_constant(0x26a61d7a44962ed6, "sort");
 		type.name_hashes[static_cast<uint32_t>(ListProperties::Empty)] = hash_constant(0x5ed3e28128ac2df7ULL, "empty");
 		type.name_hashes[static_cast<uint32_t>(ListProperties::Length)] = hash_constant(0xea9dd03ab3c476a3ULL, "length");
 		_static_list_type = add_type_to_type_map(type, p);

          
M jasm/assemble/assembler_impl/assembler_impl.h +21 -0
@@ 361,6 361,22 @@ private:
 		return referred_value.type == ValueType::DictReference;
 	}
 
+	inline bool is_macro(const Value &value) const
+	{
+		if (value.type == ValueType::MacroReference) {
+			return true;
+		}
+
+		if (!value.is_value_reference()) {
+			return false;
+		}
+
+		// value reference type must follow the reference and check its type
+		const Value &referred_value = follow_reference(value);
+
+		return referred_value.type == ValueType::MacroReference;
+	}
+
 	inline bool is_valid_key(const Value &value)
 	{
 		return is_integer(value) || is_string(value) || is_boolean(value);

          
@@ 472,6 488,10 @@ private:
 	/// @return True if it is a string.
 	bool verify_string_argument(bool generate, const ExpressionComponent &operator_component, const Value &right, Value &result);
 
+	/// Verify that a value is a macro. Throws an exception if it isn't.
+	/// @return True if it is a macro.
+	bool verify_macro_type(bool generate, const Value &value, const SourceLocation &source_location);
+	
 	/// Verify that two arguments have comparable types.
 	/// Throws an exception if they haven't or writes an unknown value to the result depending on @a generate.
 	/// @return True if they are comparable.

          
@@ 722,6 742,7 @@ private:
 	void method_list_erase(bool generate, Value &object, ValueVector &expression_values, uint32_t op, const ExpressionComponent components[], uint32_t arg1);
 	void method_list_keep(bool generate, Value &object, ValueVector &expression_values, uint32_t op, const ExpressionComponent components[], uint32_t arg1);
 	void method_list_clear(bool generate, Value &object, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op);
+	void method_list_sort(bool generate, Value &object, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1);
 	void method_dict_get(bool generate, Value &object, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1);
 	void method_dict_set(bool generate, Value &object, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1, uint32_t arg2);
 	void method_dict_erase(bool generate, Value &object, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1);

          
M jasm/assemble/assembler_impl/expressions_impl.cpp +53 -49
@@ 142,65 142,69 @@ void Assembler::recurse_evaluate_express
 	Value &value = expression_values[root_index];
 
 	switch (root.type) {
-	case ExpressionComponentType::Integer:
-		set_integer(value, root.integer);
-		break;
+		case ExpressionComponentType::Integer:
+			set_integer(value, root.integer);
+			break;
 
-	case ExpressionComponentType::Float:
-		set_float(value, root.floating_point);
-		break;
+		case ExpressionComponentType::Float:
+			set_float(value, root.floating_point);
+			break;
 
-	case ExpressionComponentType::GlobalSymbol:
-	case ExpressionComponentType::LocalSymbol:
-	case ExpressionComponentType::AutoSymbol:
-		evaluate_symbol(generate, root, value);
-		break;
+		case ExpressionComponentType::GlobalSymbol:
+		case ExpressionComponentType::LocalSymbol:
+		case ExpressionComponentType::AutoSymbol:
+			evaluate_symbol(generate, root, value);
+			break;
 
-	case ExpressionComponentType::String:
-		value.type = ValueType::StringValue;
-		value.type_hash = _static_string_type;
-		value.string_hash = root.string;
-		break;
+		case ExpressionComponentType::String:
+			value.type = ValueType::StringValue;
+			value.type_hash = _static_string_type;
+			value.string_hash = root.string;
+			break;
+
+		case ExpressionComponentType::Boolean:
+			set_boolean(value, root.boolean);
+			break;
 
-	case ExpressionComponentType::Boolean:
-		set_boolean(value, root.boolean);
-		break;
-
-	case ExpressionComponentType::ProgramCounter:
-		// we should not need to guard against unknown values here
-		// since this should be handled by operators or labels
-		value = _program_counter;
-		break;
+		case ExpressionComponentType::ProgramCounter:
+			// we should not need to guard against unknown values here
+			// since this should be handled by operators or labels
+			value = _program_counter;
+			break;
 
-	case ExpressionComponentType::Typename:
-		switch(root.typename_type) {
-		case TypenameType::Byte:
-			value.type = ValueType::ByteTypename;
-			value.type_hash = _static_byte_type_type;
+		case ExpressionComponentType::Typename:
+			switch(root.typename_type) {
+			case TypenameType::Byte:
+				value.type = ValueType::ByteTypename;
+				value.type_hash = _static_byte_type_type;
+				break;
+			case TypenameType::Word:
+				value.type = ValueType::WordTypename;
+				value.type_hash = _static_word_type_type;
+				break;
+			case TypenameType::Long:
+				value.type = ValueType::LongTypename;
+				value.type_hash = _static_long_type_type;
+				break;
+			case TypenameType::NumTypes:
+				// this should never happen
+				assert(false);
+				break;
+			}
 			break;
-		case TypenameType::Word:
-			value.type = ValueType::WordTypename;
-			value.type_hash = _static_word_type_type;
+			
+		case ExpressionComponentType::Value:
+			value = *root.value;
 			break;
-		case TypenameType::Long:
-			value.type = ValueType::LongTypename;
-			value.type_hash = _static_long_type_type;
-			break;
-		case TypenameType::NumTypes:
-			// this should never happen
+
+		case ExpressionComponentType::FunctionArgumentStart:
+			// this is only used while creating the expression tree
 			assert(false);
 			break;
-		}
-		break;
 
-	case ExpressionComponentType::FunctionArgumentStart:
-		// this is only used while creating the expression tree
-		assert(false);
-		break;
-
-	case ExpressionComponentType::Operator:
-		evaluate_operator(generate, expression_values, components, root_index);
-		break;
+		case ExpressionComponentType::Operator:
+			evaluate_operator(generate, expression_values, components, root_index);
+			break;
 	};
 }
 

          
M jasm/assemble/assembler_impl/methods_impl.cpp +88 -6
@@ 17,6 17,7 @@ Method Assembler::method_pointer(MethodT
 		{ &Assembler::method_list_erase},
 		{ &Assembler::method_list_keep},
 		{ &Assembler::method_list_clear},
+		{ &Assembler::method_list_sort},
 		{ &Assembler::method_dict_get},
 		{ &Assembler::method_dict_set},
 		{ &Assembler::method_dict_erase},

          
@@ 79,7 80,7 @@ void Assembler::method_list_push(bool /*
 
 	ListDynamicObject &source_list = object.object_reference.deref<ListDynamicObject>();
 	source_list.prepare_for_write();
-	auto &list_contents = *source_list.value;
+	std::vector<Value> &list_contents = *source_list.value;
 	list_contents.emplace_back();
 	list_contents.back().copy_value(follow_reference_or_value(expression_values[arg1]));
 

          
@@ 93,7 94,7 @@ void Assembler::method_list_pop(bool gen
 
 	ListDynamicObject &source_list = object.object_reference.deref<ListDynamicObject>();
 	source_list.prepare_for_write();
-	auto &list_contents = *source_list.value;
+	std::vector<Value> &list_contents = *source_list.value;
 
 	if (list_contents.empty()) {
 		if (generate) {

          
@@ 119,7 120,7 @@ void Assembler::method_list_insert(bool 
 
 	ListDynamicObject &source_list = object.object_reference.deref<ListDynamicObject>();
 	source_list.prepare_for_write();
-	auto &list_contents = *source_list.value;
+	std::vector<Value> &list_contents = *source_list.value;
 
 	// return the list
 	result.copy_value(object);

          
@@ 183,7 184,7 @@ void Assembler::method_list_erase(bool g
 
 	ListDynamicObject &source_list = object.object_reference.deref<ListDynamicObject>();
 	source_list.prepare_for_write();
-	auto &list_contents = *source_list.value;
+	std::vector<Value> &list_contents = *source_list.value;
 
 	// return the list
 	result.copy_value(object);

          
@@ 205,7 206,7 @@ void Assembler::method_list_keep(bool ge
 	}
 
 	ListDynamicObject &source_list = object.object_reference.deref<ListDynamicObject>();
-	auto &list_contents = *source_list.value;
+	std::vector<Value> &list_contents = *source_list.value;
 
 	// clamp arguments within vector range
 	range = clamp_range_by_value(range, core::Range<int32_t>{0, static_cast<int32_t>(list_contents.size())});

          
@@ 222,13 223,94 @@ void Assembler::method_list_clear(bool /
 	Value &result = expression_values[op];
 
 	ListDynamicObject &source_list = object.object_reference.deref<ListDynamicObject>();
-	// create new vector and copy contents
+	// create new vector
 	source_list.value = std::make_shared<std::vector<Value>>();
 
 	// return the list
 	result.copy_value(object);
 }
 
+void Assembler::method_list_sort(bool generate, Value &object, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1)
+{
+	Value &result = expression_values[op];
+	
+	ListDynamicObject &source_list = object.object_reference.deref<ListDynamicObject>();
+	// clone vector if necessary
+	source_list.prepare_for_write();
+
+	// return the list
+	result.copy_value(object);
+
+	// verify sort macro argument
+	const Value &arg1_value = follow_reference_or_value(expression_values[arg1]);
+	if (!verify_macro_type(generate, arg1_value, components[arg1].source_location)) {
+		return;
+	}
+	
+	// create components for the expression evaluation
+	const ExpressionComponent &arg1_component = components[arg1];
+	ExpressionComponent macro_call_components[4] = {
+		// the call operator
+		ExpressionComponent(arg1_component.source_location, OperatorType::Call),
+		// first child - the macro
+		arg1_component,
+		// second child - first macro argument
+		ExpressionComponent(arg1_component.source_location, ExpressionComponentType::Value),
+		// third child - second macro argument
+		ExpressionComponent(arg1_component.source_location, ExpressionComponentType::Value),
+	};
+	macro_call_components[0].first_child = 1; // operator has macro as first child
+	macro_call_components[1].next_sibling = 2; // macro has first argument as next sister
+	macro_call_components[2].next_sibling = 3; // macro has first argument as next sister
+	
+	ValueVector macro_call_values;
+	macro_call_values.resize(4);
+
+	uint32_t call_counter = 0;
+	
+	// create a lambda to do comparison
+	auto less_than = [this, generate, &macro_call_components, &macro_call_values, &arg1_component, &call_counter](Value &a, Value &b) -> bool {
+		// Start a new variable scope to make each variable in each loop unique.
+		// Otherwise it will not be possible to declare constants inside the called macro.
+		{
+			uint64_t scope_hash = _symbol_environment.local_symbol_scope_stack.back();
+			uint64_t combined_hash = core::murmur_hash3_x64_64(&call_counter, sizeof(call_counter), scope_hash);
+			if (UNLIKELY(_dump_symbols)) {
+				combine_and_store_hash_name(scope_hash, combined_hash, "sort" + std::to_string(call_counter));
+			}
+
+			const bool uses_loop_variable = false;
+			enter_variable_scope(combined_hash, uses_loop_variable);
+		}
+
+		// prepare components for the expression evaluation
+		macro_call_components[2].value = &a;
+		macro_call_components[3].value = &b;
+		
+		recurse_evaluate_expression_tree(generate, macro_call_values, macro_call_components, 0);
+		
+		// Remove the local variable scope for next iteration.
+		const bool uses_continue_variable = false;
+		leave_variable_scope(uses_continue_variable);
+		
+		++call_counter;
+
+		if (!is_boolean(macro_call_values[0])) {
+			if (generate) {
+				std::stringstream ss;
+				ss << "Sort argument macro is expected to return a boolean value but got " << to_string(follow_reference_or_value(macro_call_values[0]).type) << '.';
+				report_error(arg1_component.source_location, AssemblyErrorCodes::ExpectedBooleanResult, ss.str());
+			}
+			return true;
+		}
+		return macro_call_values[0].bool_value;
+	};
+	
+	// now sort
+	std::vector<Value> &sort_list = *source_list.value;
+	std::sort(std::begin(sort_list), std::end(sort_list), less_than);
+}
+
 void Assembler::method_dict_get(bool generate, Value &object, ValueVector &expression_values, const ExpressionComponent components[], uint32_t op, uint32_t arg1)
 {
 	Value &result = expression_values[op];

          
M jasm/assemble/assembler_impl/operators_impl.cpp +1 -0
@@ 1559,6 1559,7 @@ void Assembler::operator_list_period(boo
 	case ListProperties::Erase:
 	case ListProperties::Keep:
 	case ListProperties::Clear:
+	case ListProperties::Sort:
 		{
 			MethodType method = static_cast<MethodType>(static_cast<uint32_t>(MethodType::ListStart) + key_index);
 			set_method(result, method, arg1);

          
M jasm/assemble/assembler_impl/syntax_impl.cpp +13 -0
@@ 886,6 886,19 @@ bool Assembler::verify_integer_type(bool
 	return true;
 }
 
+bool Assembler::verify_macro_type(bool generate, const Value &value, const SourceLocation &source_location)
+{
+	if (!is_macro(value)) {
+		if (generate) {
+			std::stringstream ss;
+			ss << "Macro reference was expected but got " << to_string(value.type) << '.';
+			report_error(source_location, AssemblyErrorCodes::MacroValueExpected, ss.str());
+		}
+		return false;
+	}
+	return true;
+}
+
 bool Assembler::verify_integer_in_range(bool generate, const Value &value, int32_t low, int32_t high, const SourceLocation &source_location)
 {
 	if (!verify_integer_type(generate, value, source_location)) {

          
M jasm/assemble/methods.cpp +2 -0
@@ 22,6 22,7 @@ const MethodDesc &method_info(MethodType
 		{not_lazy, variable_args, mutating, 1}, // list erase
 		{not_lazy, variable_args, mutating, 1}, // list keep
 		{not_lazy, fixed_args, mutating, 0}, // list clear
+		{not_lazy, fixed_args, mutating, 1}, // list sort
 		{not_lazy, fixed_args, not_mutating, 1}, // dict get
 		{not_lazy, fixed_args, mutating, 2}, // dict set
 		{not_lazy, fixed_args, mutating, 1}, // dict erase

          
@@ 44,6 45,7 @@ std::string_view to_string(MethodType ty
 		std::string_view("erase"),
 		std::string_view("keep"),
 		std::string_view("clear"),
+		std::string_view("sort"),
 		std::string_view("get"),
 		std::string_view("set"),
 		std::string_view("erase"),

          
M jasm/assemble/methods.h +2 -0
@@ 27,6 27,7 @@ enum class ListProperties : uint8_t
 	Erase,
 	Keep,
 	Clear,
+	Sort,
 	
 	Empty,
 	Length,

          
@@ 68,6 69,7 @@ enum class MethodType : uint8_t
 	ListErase,
 	ListKeep,
 	ListClear,
+	ListSort,
 
 	DictGet,
 	DictStart = DictGet,

          
M jasm/docs/jasm.md +6 -0
@@ 2244,6 2244,12 @@ The list type can hold a collection of v
 		<td><code>[6502]|var aa = list(1, 2, 3)</code><br/><code>[6502]|aa.clear() // []</code></td>
 	</tr>
 	<tr>
+		<td><code>[6502]|sort(before)</code></td>
+		<td>macro</td>
+		<td>Sort the elements in the list according to an item ordering macro that takes two arguments and returns true if the first argument should be before the second. The list is returned.</td>
+		<td><code>[6502]|const .less = macro(.a, .b) { return .a < .b }</code><br/><code>[6502]|var aa = list(8, 4, 5, 1)</code><br/><code>[6502]|aa.sort(.less) // [1, 4, 5, 8]</code></td>
+	</tr>
+	<tr>
 		<td><code>[6502]|empty</code></td>
 		<td></td>
 		<td>Returns <code>[6502]|true</code> if the list is empty, otherwise <code>[6502]|false</code>.</td>

          
M jasm/exceptions/error_codes.h +2 -0
@@ 205,6 205,8 @@ enum class AssemblyErrorCodes
 	RecursiveIncludes,
 	CantFindIncludeFile,
 	AddressingModeRequiresBitSizeArgument,
+	MacroValueExpected,
+	ExpectedBooleanResult,
 
 	// assembler warnings
 	SubroutineFallthrough = 3500,

          
M jasm/syntax/syntax_tokens.cpp +1 -0
@@ 31,6 31,7 @@ std::string_view to_string(ExpressionCom
 		std::string_view("local symbol"),
 		std::string_view("auto symbol"),
 		std::string_view("program counter"),
+		std::string_view("value"),
 	};
 	static_assert(sizeof(names) / sizeof(names[0]) == static_cast<size_t>(ExpressionComponentType::NumTypes), "Number of expression component types doesn't match number of strings");
 

          
M jasm/syntax/syntax_tokens.h +13 -0
@@ 10,6 10,8 @@ 
 
 namespace jasm
 {
+	
+	struct Value;
 
 /// @addtogroup syntax
 /// @{

          
@@ 74,6 76,7 @@ enum class ExpressionComponentType : uin
 	LocalSymbol,
 	AutoSymbol,
 	ProgramCounter,
+	Value, // only used when evaluating expressions from C++
 
 	FunctionArgumentStart, ///< Marks the beginning of function call arguments to avoid having to count arguments in syntax analysis.
 	NumTypes = FunctionArgumentStart

          
@@ 126,6 129,15 @@ struct ExpressionComponent
 		, next_sibling(0)
 	{
 	}
+	explicit ExpressionComponent(const SourceLocation &location, Value *value)
+		: type(ExpressionComponentType::String)
+		, source_location(location)
+		, value(value)
+		, namespaces_handle(0)
+		, first_child(0)
+		, next_sibling(0)
+	{
+	}
 	explicit ExpressionComponent(const SourceLocation &location, TypenameType value)
 		: type(ExpressionComponentType::Typename)
 		, source_location(location)

          
@@ 176,6 188,7 @@ struct ExpressionComponent
 		TypenameType typename_type;
 		OperatorType operator_type;
 		uint64_t symbol; ///< Symbol hash for global, local or auto symbols.
+		Value *value; ///< Evaluated value, only used when evaluating expressions from C++ code to call macros for example.
 	};
 	// 8 byte aligned
 	uint64_t namespaces_handle; ///< Hashes for namespace used when referring to a global symbol

          
A => jasm/unit_tests/results/test_list_sort_expects_macro.stdout +2 -0
@@ 0,0 1,2 @@ 
+unit_tests/test_list_sort_expects_macro.asm(4,10) : Error 3114 : Macro reference was expected but got integer.
+Assembly ended with errors.

          
A => jasm/unit_tests/results/test_list_sort_macro_must_return_boolean.stdout +2 -0
@@ 0,0 1,2 @@ 
+unit_tests/test_list_sort_macro_must_return_boolean.asm(9,10) : Error 3115 : Sort argument macro is expected to return a boolean value but got integer.
+Assembly ended with errors.

          
A => jasm/unit_tests/results/test_list_sort_macro_requires_two_arguments.stdout +2 -0
@@ 0,0 1,2 @@ 
+unit_tests/test_list_sort_macro_requires_two_arguments.asm(9,10) : Error 3025 : Wrong number of arguments. 1 argument was expected and 2 was given.
+Assembly ended with errors.

          
A => jasm/unit_tests/results/test_list_sort_numbers.bin +1 -0
@@ 0,0 1,1 @@ 
+
  No newline at end of file

          
A => jasm/unit_tests/results/test_list_sort_takes_one_argument.stdout +2 -0
@@ 0,0 1,2 @@ 
+unit_tests/test_list_sort_takes_one_argument.asm(4,9) : Error 3053 : Number of method arguments doesn't match method signature. Method sort takes 1 argument and 0 was given.
+Assembly ended with errors.

          
A => jasm/unit_tests/test_list_sort_expects_macro.asm +4 -0
@@ 0,0 1,4 @@ 
+// assembler command line arguments: 6502 [-v0]
+
+var lst = list(5,8,2,6)
+lst.sort(1)

          
A => jasm/unit_tests/test_list_sort_macro_must_return_boolean.asm +9 -0
@@ 0,0 1,9 @@ 
+// assembler command line arguments: 6502 [-v0]
+
+macro before(.a, .b)
+{
+	return .a
+}
+
+var lst = list(5,8,2,6)
+lst.sort(before)

          
A => jasm/unit_tests/test_list_sort_macro_requires_two_arguments.asm +16 -0
@@ 0,0 1,16 @@ 
+// assembler command line arguments: 6502 [-v0]
+
+macro before(.a)
+{
+	return true
+}
+
+var lst = list(5,8,2,6)
+lst.sort(before)
+
+section code, "main", 0
+{
+	for(var .v in lst) {
+		define byte = .v
+	}
+}
  No newline at end of file

          
A => jasm/unit_tests/test_list_sort_numbers.asm +16 -0
@@ 0,0 1,16 @@ 
+// assembler command line arguments: 6502 [-v0]
+
+macro before(.a, .b)
+{
+	return .a < .b
+}
+
+var lst = list(5,8,2,6)
+lst.sort(before)
+
+section code, "main", 0
+{
+	for(var .v in lst) {
+		define byte = .v
+	}
+}

          
A => jasm/unit_tests/test_list_sort_takes_one_argument.asm +4 -0
@@ 0,0 1,4 @@ 
+// assembler command line arguments: 6502 [-v0]
+
+var lst = list(5,8,2,6)
+lst.sort()

          
M jasm/website/site/docs/index.html +6 -0
@@ 2347,6 2347,12 @@ aa
         <td><code><span class="keyword">var</span> aa <span class="operator">=</span> <span class="function">list</span><span class="special">(</span><span class="literal">1</span>, <span class="literal">2</span>, <span class="literal">3</span><span class="special">)</span></code><br/><code>aa.clear<span class="special">(</span><span class="special">)</span> <span class="comment">// []</span></code></td>
     </tr>
     <tr>
+        <td><code>sort<span class="special">(</span>before<span class="special">)</span></code></td>
+        <td>macro</td>
+        <td>Sort the elements in the list according to an item ordering macro that takes two arguments and returns true if the first argument should be before the second. The list is returned.</td>
+        <td><code><span class="keyword">const</span> .less <span class="operator">=</span> <span class="keyword">macro</span><span class="special">(</span>.a, .b<span class="special">)</span> <span class="special">{</span> <span class="keyword">return</span> .a < .b <span class="special">}</span></code><br/><code><span class="keyword">var</span> aa <span class="operator">=</span> <span class="function">list</span><span class="special">(</span><span class="literal">8</span>, <span class="literal">4</span>, <span class="literal">5</span>, <span class="literal">1</span><span class="special">)</span></code><br/><code>aa.sort<span class="special">(</span>.less<span class="special">)</span> <span class="comment">// [1, 4, 5, 8]</span></code></td>
+    </tr>
+    <tr>
         <td><code>empty</code></td>
         <td></td>
         <td>Returns <code><span class="literal">true</span></code> if the list is empty, otherwise <code><span class="literal">false</span></code>.</td>