58 files changed, 729 insertions(+), 219 deletions(-)

M core/core/collections/hash_map.h
M core/core/debug/timer.cpp
M core/core/strings/murmur_hash.cpp
M jasm/assemble/assembler_impl/assembler_impl.cpp
M jasm/assemble/assembler_impl/assembler_impl.h
M jasm/assemble/assembler_impl/expressions_impl.cpp
M jasm/assemble/assembler_impl/methods_impl.cpp
M jasm/assemble/assembler_impl/operators_impl.cpp
M jasm/assemble/assembler_impl/symbols_impl.cpp
M jasm/assemble/assembler_impl/syntax_impl.cpp
M jasm/assemble/methods.cpp
M jasm/assemble/methods.h
M jasm/assemble/value.cpp
M jasm/docs/header.html
M jasm/docs/jasm.css
M jasm/docs/jasm.md
M jasm/exceptions/error_codes.h
M jasm/processor/45gs02/instructions_45gs02.cpp
M jasm/processor/processor.cpp
M jasm/processor/processor.h
M jasm/processor/z80/processor_z80.cpp
M jasm/strings/string_conversions.cpp
M jasm/strings/string_conversions.h
M jasm/syntax/syntax_parser.cpp
M jasm/syntax/syntax_tokens.cpp
M jasm/syntax/syntax_tokens.h
M jasm/unit_tests/results/test_function_min_with_zero_arguments.stdout
M jasm/unit_tests/results/test_function_wrong_number_of_arguments.stdout
M jasm/unit_tests/results/test_instruction_data_label_offsets_45gs02.bin
M jasm/unit_tests/results/test_instruction_data_label_sizes_45gs02.bin
M jasm/unit_tests/results/test_list_erase_with_too_few_arguments.stdout
M jasm/unit_tests/results/test_list_keep_with_too_few_arguments.stdout
A => jasm/unit_tests/results/test_list_sort_expects_macro.stdout
A => jasm/unit_tests/results/test_list_sort_macro_must_return_boolean.stdout
A => jasm/unit_tests/results/test_list_sort_macro_requires_two_arguments.stdout
A => jasm/unit_tests/results/test_list_sort_numbers.bin
A => jasm/unit_tests/results/test_list_sort_takes_one_argument.stdout
A => jasm/unit_tests/results/test_self_assignment_constant.stdout
A => jasm/unit_tests/results/test_self_assignment_variable.stdout
A => jasm/unit_tests/results/test_string_conversion_ascii_7.bin
M jasm/unit_tests/results/test_too_few_arguments_to_variable_argument_function.stdout
M jasm/unit_tests/results/test_wrong_number_of_macro_arguments.stdout
M jasm/unit_tests/test_all_instructions_45gs02.asm
M jasm/unit_tests/test_instruction_data_label_offsets_45gs02.asm
M jasm/unit_tests/test_instruction_data_label_sizes_45gs02.asm
A => jasm/unit_tests/test_list_sort_expects_macro.asm
A => jasm/unit_tests/test_list_sort_macro_must_return_boolean.asm
A => jasm/unit_tests/test_list_sort_macro_requires_two_arguments.asm
A => jasm/unit_tests/test_list_sort_numbers.asm
A => jasm/unit_tests/test_list_sort_takes_one_argument.asm
A => jasm/unit_tests/test_self_assignment_constant.asm
A => jasm/unit_tests/test_self_assignment_variable.asm
A => jasm/unit_tests/test_string_conversion_ascii_7.asm
M jasm/version.h
M jasm/version.py
M jasm/website/site/docs/index.html
M jasm/website/site/docs/jasm.css
M jasm/website/site/index.html
M core/core/collections/hash_map.h +1 -1
@@ 367,7 367,7 @@ private:
 	void allocate(uint32_t hit_size)
 	{
 		_hash_hit_size = hit_size;
-		_hash_miss_size = static_cast<uint32_t>(hit_size * empty_region_percentage * 0.01f);
+		_hash_miss_size = static_cast<uint32_t>(static_cast<float>(hit_size) * empty_region_percentage * 0.01f);
 		_hash_hit_free = _hash_hit_size;
 		_hash_miss_free = _hash_miss_size;
 

          
M core/core/debug/timer.cpp +1 -1
@@ 16,7 16,7 @@ TimerScope::~TimerScope()
 	auto now = std::chrono::system_clock::now();
 	auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start_time);
 
-	debug() << _name << " took " << duration.count() / 1000.0 << '\n';
+	debug() << _name << " took " << static_cast<double>(duration.count()) / 1000.0 << '\n';
 }
 
 } // namespace core

          
M core/core/strings/murmur_hash.cpp +2 -2
@@ 162,7 162,7 @@ uint64_t unaligned_murmur_hash3_x64_64(c
 		DECLARE_SWITCH_FALLTHROUGH
 	case  1: k1 ^= static_cast<uint64_t>(tail[0]) << 0;
 		k1 *= c1; k1 = ROTL64(k1, 31); k1 *= c2; h1 ^= k1;
-	};
+	}
 
 	//----------
 	// finalization

          
@@ 258,7 258,7 @@ uint64_t murmur_hash3_x64_64(const void 
 		DECLARE_SWITCH_FALLTHROUGH
 	case  1: k1 ^= static_cast<uint64_t>(tail[0]) << 0;
 		k1 *= c1; k1 = ROTL64(k1, 31); k1 *= c2; h1 ^= k1;
-	};
+	}
 
 	//----------
 	// finalization

          
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 +54 -50
@@ 142,66 142,70 @@ 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;
+	}
 }
 
 const Value &Assembler::evaluate_expression_tree(bool generate, ValueVector &expression_values, const ExpressionComponent components[], uint32_t num_components)

          
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 +16 -4
@@ 9,6 9,15 @@ namespace jasm {
 
 using namespace core;
 
+/// Returns "was" if number is 1, otherwise "were"
+const char *was_or_were(uint32_t number)
+{
+	if (number != 1) {
+		return "were";
+	}
+	return "was";
+}
+
 bool Assembler::verify_numeric_argument(bool generate, const ExpressionComponent &operator_component, const Value &right, Value &result)
 {
 	if (!is_numeric(right)) {

          
@@ 159,9 168,11 @@ bool Assembler::verify_num_arguments_and
 			const ExpressionComponent &ec = components[operator_index];
 			std::stringstream ss;
 			ss << "Wrong number of arguments. " << num_args - skip_args << " argument";
-			if (num_args - skip_args != 1)
+			if (num_args - skip_args != 1) {
 				ss << "s";
-			ss << " was expected and " << num_args_count << " was given.";
+			}
+			ss << ' ' << was_or_were(num_args - skip_args) << " expected and " << num_args_count;
+			ss << ' ' << was_or_were(num_args_count) << " given.";
 			report_error(ec.source_location, AssemblyErrorCodes::WrongNumberOfArguments, ss.str());
 		}
 		set_unknown(result);

          
@@ 1088,7 1099,7 @@ void Assembler::operator_function_call(b
 			ss << to_string(func_type) << " takes at least " << func_desc.num_arguments << " argument";
 			if (func_desc.num_arguments != 1)
 				ss << "s";
-			ss << " and " << num_args << " was given.";
+			ss << " and " << num_args << ' ' << was_or_were(num_args) << " given.";
 			report_error(ec.source_location, AssemblyErrorCodes::TooFewFunctionArguments, ss.str());
 		}
 		set_unknown(result);

          
@@ 1183,7 1194,7 @@ void Assembler::operator_method_closure_
 			if (method_desc.num_arguments != 1) {
 				ss << "s";
 			}
-			ss << " and " << num_args << " was given.";
+			ss << " and " << num_args << ' ' << was_or_were(num_args) << " given.";
 			report_error(ec.source_location, AssemblyErrorCodes::NoOfArgumentsDoesNotMatchMethodSignature, ss.str());
 		}
 		set_unknown(result);

          
@@ 1559,6 1570,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/symbols_impl.cpp +18 -4
@@ 663,11 663,25 @@ void Assembler::evaluate_symbol(bool gen
 	// and previous pass references must be allowed for constants.
 
 	if (variable_reference != nullptr) {
-		value.type = ValueType::ValueReference;
-		value.set_found_in_current_pass(found_in_current_pass);
-		value.variable_reference_hash = combined_hash;
-		value.type_hash = _static_value_reference_type;
+		if (variable_reference->type_hash == 0) {
+			// This can happen if the symbol is created and used in the same expression.
+			// So the variable hasn't been properly initialized yet.
+			set_unknown(value);
+			value.global = true;
+			value.storage_type = StorageType::Constant; // Not entirely sure what to set this to. Type must be checked before this.
+			
+			if (generate) {
+				std::stringstream ss;
+				ss << "A symbol's definition can't refer to itself.";
+				report_error(component.source_location, AssemblyErrorCodes::SymbolRecursion, ss.str());
+			}
 
+		} else {
+			value.type = ValueType::ValueReference;
+			value.set_found_in_current_pass(found_in_current_pass);
+			value.variable_reference_hash = combined_hash;
+			value.type_hash = _static_value_reference_type;
+		}
 	} else {
 		// symbol was not found or is ambiguous
 

          
M jasm/assemble/assembler_impl/syntax_impl.cpp +15 -2
@@ 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)) {

          
@@ 1366,7 1379,7 @@ const SyntaxToken *Assembler::parse_for_
 			const ExpressionToken *expr = static_cast<const ExpressionToken *>(t);
 			std::stringstream ss;
 			ss << "For loop ran a million loops. There doesn't seem to be an end to this.";
-			report_error(expr->source_location, AssemblyErrorCodes::ForLoopSanityCheckTriggered, ss.str());
+			report_fatal_error(expr->source_location, AssemblyErrorCodes::ForLoopSanityCheckTriggered, ss.str());
 			break;
 		}
 

          
@@ 2231,7 2244,7 @@ const SyntaxToken *Assembler::parse_stat
 		break;
 		
 	case SyntaxTokenType::Instruction:
-		assert(t->processor == _processor_type);
+		assert(is_equivalent(t->processor, _processor_type));
 		t = _processor->parse_instruction(*this, generate, t, export_enabled);
 		break;
 

          
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/assemble/value.cpp +1 -1
@@ 67,7 67,7 @@ std::string_view to_string(ValueType typ
 	case ValueType::MethodClosure:
 		return std::string_view("method closure");
 	case ValueType::NumTypes:
-		return nullptr;
+		return std::string_view();
 	}
 	UNREACHABLE_CODE(return std::string_view());
 }

          
M jasm/docs/header.html +1 -0
@@ 6,6 6,7 @@ 
 		<meta name="description" content="This is the documentation for the jAsm assembler.">
 		<meta name="keywords" content="jAsm,6502,z80,assembler,asm,cross-assembler">
 		<meta name="author" content="Jonas Hultén">
+		<meta name="viewport" content="width=device-width, initial-scale=1">
 		<link rel="shortcut icon" href="images/favicon.ico">
 		<link href="jasm.css" rel="stylesheet">
 	</head>

          
M jasm/docs/jasm.css +43 -12
@@ 1,3 1,12 @@ 
+@viewport {
+	width: device-width;
+	zoom: 1.0;
+}
+img {
+	width: auto;
+	max-width: 100%;
+	height: auto;
+}
 body
 {
 	width: 800px;

          
@@ 62,18 71,6 @@ h3
 	font-size: 1.1em;
 	color: #606060;
 }
-div.section
-{
-	width: 380px;
-	margin-right: 40px;
-	margin-top: 40px;
-	margin-bottom: 40px;
-	float: left;
-}
-div.section.even
-{
-	margin-right: 0;
-}
 a
 {
 	text-decoration: none;

          
@@ 119,3 116,37 @@ th, td
 	border: 1px solid #a1a1a1;
 	padding: 6px 6px;
 }
+div.section
+{
+	width: 380px;
+	margin-right: 40px;
+	margin-top: 40px;
+	margin-bottom: 40px;
+	float: left;
+}
+div.section.even
+{
+	margin-right: 0;
+}
+@media only screen and (max-width: 840px)
+{
+	body
+	{
+		width: auto;
+		margin: 16px;
+	}
+	div.section
+	{
+		width: 100%;
+		margin-left: auto;
+		margin-right: auto;
+		float: none;
+	}
+	div.section.even
+	{
+		width: 100%;
+		margin-left: auto;
+		margin-right: auto;
+		float: none;
+	}
+}

          
M jasm/docs/jasm.md +14 -4
@@ 1516,7 1516,7 @@ The label will contain the address to th
 	lda value: #0
 	inc value // increment what is loaded by the previous instruction next time it is executed
 
-On Z80, there are at most two arguments so in some cases two labels can be defined to instruction arguments.
+On Z80, there are at most two arguments so in some cases two labels can be defined to instruction arguments. Note how the label is placed when using indirect addressing modes. It has to be placed before the parenthesis.
 
 	[z80]
 	ld hl, index

          
@@ 2055,16 2055,20 @@ The following format properties are supp
 		<th>Comment</th>
 	</tr>
 	<tr>
+		<td>ascii7</td>
+		<td>7 bit ascii format.</td>
+	</tr>
+	<tr>
 		<td>petscii</td>
 		<td>The character set used in Commodore 8 bit computers.</td>
 	</tr>
 	<tr>
 		<td>zx80</td>
-		<td></td>
+		<td>Sinclair specific character set.</td>
 	</tr>
 	<tr>
 		<td>zx81</td>
-		<td></td>
+		<td>Sinclair specific character set.</td>
 	</tr>
 </table>
 

          
@@ 2244,6 2248,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>

          
@@ 2352,7 2362,7 @@ There are a number of functions in the r
 	<tr>
 		<td><code>[6502]|string(value [, property, ...])</code></td>
 		<td>numeric|string, string, ...</td>
-		<td>Converts <code>[6502]|value</code> to a string according to a specific character set. See [String Conversions](#string-conversions) for details about properties.</td>
+		<td>Converts <code>[6502]|value</code> to a string according to a specific character set. See String Conversions for details about properties.</td>
 		<td><code>[6502]|string("Hello", "petscii", "lowercase")</code><br/><code>[6502]|string(123, "zx81")</code></td>
 	</tr>
 	<tr>

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

          
M jasm/processor/45gs02/instructions_45gs02.cpp +3 -3
@@ 166,7 166,7 @@ uint32_t __addressing_modes_mask[static_
 	/* SEI */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | Imp | ___ | ____ | __ | ___ ,
 	/* SMB */ __ | ___ | ___ | ____ | ___ | ____ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ___ | ____ | Bb | ___ ,
 	/* STA */ Bp | Abs | Bpx | AbsX | ___ | AbsY | BpIX | ____ | ___ | ____ | ____ | ___ | BpIY | BpIZ | ___ | BIQZ | SpIY | ___ | ___ | ____ | __ | ___ ,
-	/* STQ */ Bp | Abs | ___ | ____ | ___ | ____ | ____ | ____ | ___ | ____ | BInd | ___ | ____ | ____ | BIQ | ____ | SpIY | ___ | ___ | ____ | __ | ___ ,
+	/* STQ */ Bp | Abs | ___ | ____ | ___ | ____ | ____ | ____ | ___ | ____ | BInd | ___ | ____ | ____ | BIQ | ____ | ____ | ___ | ___ | ____ | __ | ___ ,
 	/*        Bp   Abs   Bpx   AbsX   Bpy   AbsY   BpIX   IndX   Rel   RelW   BInd   Ind   BpIY   BpIZ | BIQ | BIQZ   SpIY   Imp   Imm   ImmW   Bb   Bbr */
 	/* STX */ Bp | Abs | ___ | ____ | Bpy | AbsY | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ___ | ____ | __ | ___ ,
 	/* STY */ Bp | Abs | Bpx | AbsX | ___ | ____ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ____ | ____ | ___ | ___ | ____ | __ | ___ ,

          
@@ 469,8 469,8 @@ OpCodes __opcodes[static_cast<int>(Instr
 	/*          BP,       ABSO,     BPX,      ABSX,     BPY,  ABSY,     BPIX,     INDX, RELA, RELW, BIND, INDI, BPIY,     BPIZ,     BIQ,  BIQZ,          SPIY,     IMPL, IMME, IMMW, BB,   BBR,*/
 	/* STA */ {{op(0x85), op(0x8d), op(0x95), op(0x9d), op(), op(0x99), op(0x81), op(), op(), op(), op(), op(), op(0x91), op(0x92), op(), op(0xea,0x92), op(0x82), op(), op(), op(), op(), op()}},
 
-	/*          BP,                 ABSO,               BPX,  ABSX, BPY,  ABSY, BPIX, INDX, RELA, RELW, BIND,               INDI, BPIY, BPIZ, BIQ,                     BIQZ, SPIY,               IMPL, IMME, IMMW, BB,   BBR,*/
-	/* STQ */ {{op(0x42,0x42,0x85), op(0x42,0x42,0x8d), op(), op(), op(), op(), op(), op(), op(), op(), op(0x42,0x42,0x92), op(), op(), op(), op(0x42,0x42,0xea,0x92), op(), op(0x42,0x42,0x82), op(), op(), op(), op(), op()}},
+	/*          BP,                 ABSO,               BPX,  ABSX, BPY,  ABSY, BPIX, INDX, RELA, RELW, BIND,               INDI, BPIY, BPIZ, BIQ,                     BIQZ, SPIY, IMPL, IMME, IMMW, BB,   BBR,*/
+	/* STQ */ {{op(0x42,0x42,0x85), op(0x42,0x42,0x8d), op(), op(), op(), op(), op(), op(), op(), op(), op(0x42,0x42,0x92), op(), op(), op(), op(0x42,0x42,0xea,0x92), op(), op(), op(), op(), op(), op(), op()}},
 
 	/*          BP,       ABSO,     BPX,  ABSX, BPY,      ABSY,     BPIX, INDX, RELA, RELW, BIND, INDI, BPIY, BPIZ, BIQ,  BIQZ, SPIY, IMPL, IMME, IMMW, BB,   BBR,*/
 	/* STX */ {{op(0x86), op(0x8e), op(), op(), op(0x96), op(0x9b), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op(), op()}},

          
M jasm/processor/processor.cpp +10 -0
@@ 48,6 48,16 @@ bool is_processor(const std::string_view
 	return false;
 }
 
+bool is_equivalent(ProcessorType p1, ProcessorType p2)
+{
+	if (p1 == p2) {
+		return true;
+	}
+	return
+		(p1 == ProcessorType::Mos6502 || p1 == ProcessorType::Mos6510 || p1 == ProcessorType::Mos8502) &&
+		(p2 == ProcessorType::Mos6502 || p2 == ProcessorType::Mos6510 || p2 == ProcessorType::Mos8502);
+}
+
 Processor::Processor()
 {
 }

          
M jasm/processor/processor.h +1 -0
@@ 32,6 32,7 @@ enum class ProcessorType : uint8_t
 std::string_view to_string(ProcessorType p);
 
 bool is_processor(const std::string_view &processor_name, ProcessorType &processor);
+bool is_equivalent(ProcessorType p1, ProcessorType p2);
 
 struct TokenData
 {

          
M jasm/processor/z80/processor_z80.cpp +1 -1
@@ 413,7 413,7 @@ const Token *ProcessorZ80::parse_and_out
 		case InstructionArgumentType::NumTypes:
 			assert(false);
 			throw AssemblyException("Internal error");
-	};
+	}
 	return t;
 }
 

          
M jasm/strings/string_conversions.cpp +119 -1
@@ 29,6 29,7 @@ const std::string_view StringConversions
 	std::string_view("petscii"),
 	std::string_view("zx80"),
 	std::string_view("zx81"),
+	std::string_view("ascii7"),
 };
 
 const std::string_view StringConversions::_subformats[static_cast<size_t>(StringConversions::SubFormat::NumFormats)] = {

          
@@ 59,6 60,7 @@ StringConversions::StringConversions()
 	_conversions.insert(format_hash(Format::Petscii, SubFormat::UppercaseScreen, Locale::English));
 	_conversions.insert(format_hash(Format::ZX80, SubFormat::Default, Locale::English));
 	_conversions.insert(format_hash(Format::ZX81, SubFormat::Default, Locale::English));
+	_conversions.insert(format_hash(Format::Ascii7, SubFormat::Default, Locale::English));
 
 	{
 		// fill petascii lowercase

          
@@ 69,10 71,32 @@ StringConversions::StringConversions()
 		for (wchar_t c = 0; c < 26; ++c)
 			map[c + L'a'] = c + 65;
 		map[L'['] = 91;
-		map[L'\u00a3'] = 92;
+		map[L'\u00a3'] = 92; // pound
 		map[L']'] = 93;
+		map[L'\u2191'] = 94;
+		map[L'\u2190'] = 95;
+		map[L'\u2501'] = 96; // horizontal line
 		for (wchar_t c = 0; c < 26; ++c)
 			map[c + 'A'] = c + 97;
+		map[L'\u254b'] = 123; // junction
+		map[L'\u2503'] = 125; // vertical line
+
+		map[L'\u2592'] = 166; // medium checker
+
+		map[L'\u2523'] = 171; // vertical and right crossing
+		map[L'\u2597'] = 172; // quadrant lower right
+		map[L'\u2517'] = 173; // up and right bend
+		map[L'\u2513'] = 174; // down and left bend
+		map[L'\u250f'] = 176; // down and right bend
+		map[L'\u253b'] = 177; // up and horizontal crossing
+		map[L'\u2533'] = 178; // down and horizontal crossing
+		map[L'\u252b'] = 179; // vertical and left crossing
+		map[L'\u2596'] = 187; // quadrant lower left
+		map[L'\u259d'] = 188; // quadrant upper right
+		map[L'\u251b'] = 189; // up and left bend
+		map[L'\u2598'] = 190; // quadrant upper right
+		map[L'\u259a'] = 191; // quadrant upper left lower right
+
 	}
 
 	{

          
@@ 86,6 110,38 @@ StringConversions::StringConversions()
 		map[L'['] = 91;
 		map[L'\u00a3'] = 92;
 		map[L']'] = 93;
+		map[L'\u2191'] = 94;
+		map[L'\u2190'] = 95;
+		map[L'\u2501'] = 96; // horizontal line
+		map[L'\u2660'] = 97; // spades
+		map[L'\u2572'] = 109; // nw diagonal line
+		map[L'\u2571'] = 110; // ne diagonal line
+		map[L'\u25cf'] = 113; // filled circle
+		map[L'\u2665'] = 115; // heart
+		map[L'\u2573'] = 118; // cross
+		map[L'\u25cb'] = 119; // circle
+		map[L'\u2663'] = 120; // club
+		map[L'\u2666'] = 122; // diamond
+		map[L'\u254b'] = 123; // junction
+		map[L'\u2503'] = 125; // vertical line
+		map[L'\u23c0'] = 126; // pi
+		map[L'\u25e5'] = 127; // upper right triangle
+		map[L'\u2592'] = 166; // medium checker
+		map[L'\u25e4'] = 169; // upper left triangle
+
+		map[L'\u2523'] = 171; // vertical and right crossing
+		map[L'\u2597'] = 172; // quadrant lower right
+		map[L'\u2517'] = 173; // up and right bend
+		map[L'\u2513'] = 174; // down and left bend
+		map[L'\u250f'] = 176; // down and right bend
+		map[L'\u253b'] = 177; // up and horizontal crossing
+		map[L'\u2533'] = 178; // down and horizontal crossing
+		map[L'\u252b'] = 179; // vertical and left crossing
+		map[L'\u2596'] = 187; // quadrant lower left
+		map[L'\u259d'] = 188; // quadrant upper right
+		map[L'\u251b'] = 189; // up and left bend
+		map[L'\u2598'] = 190; // quadrant upper right
+		map[L'\u259a'] = 191; // quadrant upper left lower right
 	}
 
 	{

          
@@ 99,8 155,29 @@ StringConversions::StringConversions()
 		map[L'['] = 27;
 		map[L'\u00a3'] = 28;
 		map[L']'] = 29;
+		map[L'\u2191'] = 30;
+		map[L'\u2190'] = 31;
 		for (wchar_t c = 0; c < 26; ++c)
 			map[c + 'A'] = c + 65;
+
+		map[L'\u2501'] = 64; // horizontal line
+		map[L'\u254b'] = 91; // junction
+		map[L'\u2503'] = 93; // vertical line
+		map[L'\u2592'] = 102; // medium checker
+
+		map[L'\u2523'] = 107; // vertical and right crossing
+		map[L'\u2597'] = 108; // quadrant lower right
+		map[L'\u2517'] = 109; // up and right bend
+		map[L'\u2513'] = 110; // down and left bend
+		map[L'\u250f'] = 112; // down and right bend
+		map[L'\u253b'] = 113; // up and horizontal crossing
+		map[L'\u2533'] = 114; // down and horizontal crossing
+		map[L'\u252b'] = 115; // vertical and left crossing
+		map[L'\u2596'] = 123; // quadrant lower left
+		map[L'\u259d'] = 124; // quadrant upper right
+		map[L'\u251b'] = 125; // up and left bend
+		map[L'\u2598'] = 126; // quadrant upper right
+		map[L'\u259a'] = 127; // quadrant upper left lower right
 	}
 
 	{

          
@@ 114,6 191,40 @@ StringConversions::StringConversions()
 		map[L'['] = 27;
 		map[L'\u00a3'] = 28;
 		map[L']'] = 29;
+		map[L'\u2191'] = 30;
+		map[L'\u2190'] = 31;
+
+		map[L'\u2501'] = 64; // horizontal line
+		map[L'\u2660'] = 65; // spades
+		map[L'\u2572'] = 77; // nw diagonal line
+		map[L'\u2571'] = 78; // ne diagonal line
+		map[L'\u25cf'] = 81; // filled circle
+		map[L'\u2665'] = 83; // heart
+		map[L'\u2573'] = 86; // cross
+		map[L'\u25cb'] = 87; // circle
+		map[L'\u2663'] = 88; // club
+		map[L'\u2666'] = 90; // diamond
+		map[L'\u254b'] = 91; // junction
+		map[L'\u2503'] = 93; // vertical line
+
+		map[L'\u23c0'] = 94; // pi
+		map[L'\u25e5'] = 95; // upper right triangle
+		map[L'\u2592'] = 102; // medium checker
+		map[L'\u25e4'] = 105; // upper left triangle
+
+		map[L'\u2523'] = 107; // vertical and right crossing
+		map[L'\u2597'] = 108; // quadrant lower right
+		map[L'\u2517'] = 109; // up and right bend
+		map[L'\u2513'] = 110; // down and left bend
+		map[L'\u250f'] = 112; // down and right bend
+		map[L'\u253b'] = 113; // up and horizontal crossing
+		map[L'\u2533'] = 114; // down and horizontal crossing
+		map[L'\u252b'] = 115; // vertical and left crossing
+		map[L'\u2596'] = 123; // quadrant lower left
+		map[L'\u259d'] = 124; // quadrant upper right
+		map[L'\u251b'] = 125; // up and left bend
+		map[L'\u2598'] = 126; // quadrant upper right
+		map[L'\u259a'] = 127; // quadrant upper left lower right
 	}
 
 	{

          
@@ 279,6 390,13 @@ StringConversions::StringConversions()
 		ConversionMap &map = _conversions.at(format_hash(Format::ZX81, SubFormat::Default, Locale::English));
 		add_characters(conversions, map);
 	}
+
+	{
+		ConversionMap &map = _conversions.at(format_hash(Format::Ascii7, SubFormat::Default, Locale::English));
+		for(wchar_t c = L'\x0'; c < L'\x80'; ++c) {
+			map[c] = c;
+		}
+	}
 }
 
 template<typename F, size_t N>

          
M jasm/strings/string_conversions.h +1 -0
@@ 27,6 27,7 @@ public:
 		Petscii,
 		ZX80,
 		ZX81,
+		Ascii7,
 		NumFormats,
 		Invalid = NumFormats,
 	};

          
M jasm/syntax/syntax_parser.cpp +74 -74
@@ 2462,86 2462,86 @@ const Token *SyntaxParser::parse_inner_s
 		t = skip_whitespaces(t);
 
 		switch (t->type) {
-		case TokenType::Keyword:
-			t = parse_keyword(t);
-			break;
-
-		case TokenType::Symbol:
-		{
-			uint8_t instruction = invalid_instruction;
-			if (is_instruction(*t, instruction)) {
-				t = _processor->parse_instruction(*this, _source_files, t, instruction);
-			} else {
-				t = parse_label_or_statement(t);
-			}
-			break;
-		}
-		case TokenType::Operator:
-		{
-			if (t->operator_index == OperatorType::Semicolon) {
-				// semicolon to separate statements are ok
-				t = consume_next_token();
+			case TokenType::Keyword:
+				t = parse_keyword(t);
 				break;
-			}
-
-			// check for period in local labels
-			if (t->operator_index == OperatorType::Period) {
-				t = parse_label_or_statement(t);
-				break;
-			}
-
-			// check for scope start/end
-			if (t->operator_index == OperatorType::LeftCurly) {
-				t = parse_scope(t);
+
+			case TokenType::Symbol:
+			{
+				uint8_t instruction = invalid_instruction;
+				if (is_instruction(*t, instruction)) {
+					t = _processor->parse_instruction(*this, _source_files, t, instruction);
+				} else {
+					t = parse_label_or_statement(t);
+				}
 				break;
 			}
-			if (t->operator_index == OperatorType::RightCurly)
+			case TokenType::Operator:
+			{
+				if (t->operator_index == OperatorType::Semicolon) {
+					// semicolon to separate statements are ok
+					t = consume_next_token();
+					break;
+				}
+
+				// check for period in local labels
+				if (t->operator_index == OperatorType::Period) {
+					t = parse_label_or_statement(t);
+					break;
+				}
+
+				// check for scope start/end
+				if (t->operator_index == OperatorType::LeftCurly) {
+					t = parse_scope(t);
+					break;
+				}
+				if (t->operator_index == OperatorType::RightCurly)
+					return t;
+
+				constexpr bool end_at_unmatched_parenthesis = false;
+				constexpr bool end_at_newline = false;
+				t = parse_and_output_expression(t, end_at_unmatched_parenthesis, end_at_newline);
+				break;
+			}
+
+			case TokenType::Boolean:
+			case TokenType::Char:
+			case TokenType::Integer:
+			case TokenType::Float:
+			case TokenType::String:
+			case TokenType::Typename:
+			{
+				constexpr bool end_at_unmatched_parenthesis = false;
+				constexpr bool end_at_newline = false;
+				t = parse_and_output_expression(t, end_at_unmatched_parenthesis, end_at_newline);
+				break;
+			}
+
+			case TokenType::End:
 				return t;
 
-			constexpr bool end_at_unmatched_parenthesis = false;
-			constexpr bool end_at_newline = false;
-			t = parse_and_output_expression(t, end_at_unmatched_parenthesis, end_at_newline);
-			break;
-		}
-
-		case TokenType::Boolean:
-		case TokenType::Char:
-		case TokenType::Integer:
-		case TokenType::Float:
-		case TokenType::String:
-		case TokenType::Typename:
-		{
-			constexpr bool end_at_unmatched_parenthesis = false;
-			constexpr bool end_at_newline = false;
-			t = parse_and_output_expression(t, end_at_unmatched_parenthesis, end_at_newline);
-			break;
+			case TokenType::ProcessorKeyword:
+			{
+				std::stringstream ss;
+				ss << "Unexpected " << to_string(t->type);
+				throw AssemblyException(_source_files, t->source_location, AssemblyErrorCodes::UnexpectedProcessorKeyword, ss.str());
+			}
+
+			case TokenType::Processor:
+				t = parse_processor(t);
+				break;
+			
+			case TokenType::Whitespace:
+			case TokenType::Newline:
+				// this should never happen since whitespace has already been skipped
+			case TokenType::NumTypes:
+			{
+				assert(false);
+				std::stringstream ss;
+				ss << "Internal error: unexpected token " << to_string(t->type);
+				throw AssemblyException(_source_files, t->source_location, AssemblyErrorCodes::InternalError, ss.str());
+			}
 		}
-
-		case TokenType::End:
-			return t;
-
-		case TokenType::ProcessorKeyword:
-		{
-			std::stringstream ss;
-			ss << "Unexpected " << to_string(t->type);
-			throw AssemblyException(_source_files, t->source_location, AssemblyErrorCodes::UnexpectedProcessorKeyword, ss.str());
-		}
-
-		case TokenType::Processor:
-			t = parse_processor(t);
-			break;
-		
-		case TokenType::Whitespace:
-		case TokenType::Newline:
-			// this should never happen since whitespace has already been skipped
-		case TokenType::NumTypes:
-		{
-			assert(false);
-			std::stringstream ss;
-			ss << "Internal error: unexpected token " << to_string(t->type);
-			throw AssemblyException(_source_files, t->source_location, AssemblyErrorCodes::InternalError, ss.str());
-		}
-		};
 	}
 }
 

          
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

          
M jasm/unit_tests/results/test_function_min_with_zero_arguments.stdout +1 -1
@@ 1,2 1,2 @@ 
-unit_tests/test_function_min_with_zero_arguments.asm(5,15) : Error 3065 : Too few function arguments given. Function min takes at least 1 argument and 0 was given.
+unit_tests/test_function_min_with_zero_arguments.asm(5,15) : Error 3065 : Too few function arguments given. Function min takes at least 1 argument and 0 were given.
 Assembly ended with errors.

          
M jasm/unit_tests/results/test_function_wrong_number_of_arguments.stdout +1 -1
@@ 1,2 1,2 @@ 
-unit_tests/test_function_wrong_number_of_arguments.asm(4,11) : Error 3025 : Wrong number of arguments. 1 argument was expected and 2 was given.
+unit_tests/test_function_wrong_number_of_arguments.asm(4,11) : Error 3025 : Wrong number of arguments. 1 argument was expected and 2 were given.
 Assembly ended with errors.

          
M jasm/unit_tests/results/test_instruction_data_label_offsets_45gs02.bin +1 -1
@@ 1,1 1,1 @@ 
-rBBeBBmBBrBBr2BB%BB-BB2BB2BBBBBBBBBBDBBTBB$BB,BBBBBBBBBBBBBBBBRBBEBBMBBRBBRBBBBBBBBBBBBBBBBBBBBBBBBBBBBFBBVBBNBB^BB	BBBB
BBBBBB&BB6BB.BB>BBfBBvBBnBB~BBBBBBBBBBBBBBBBBB
  No newline at end of file
+rBBeBBmBBrBBr2BB%BB-BB2BB2BBBBBBBBBBDBBTBB$BB,BBBBBBBBBBBBBBBBRBBEBBMBBRBBRBBBBBBBBBBBBBBBBBBBBBBBBBBBBFBBVBBNBB^BB	BBBB
BBBBBB&BB6BB.BB>BBfBBvBBnBB~BBBBBBBBBBBBBBBB
  No newline at end of file

          
M jasm/unit_tests/results/test_instruction_data_label_sizes_45gs02.bin +1 -1
@@ 1,1 1,1 @@ 
-rBBeBBm4BBrBBr2BB%BB-4BB2BB2BBBBBB4BB4BBDBBTBB$BB,4BBBB4BBBBBBBBBB4BB4RBBEBBM4BBRBBRBBBBBB4BB4BBBBBB4BB4BB4BBBBBBBBBBFBBVBBN4BB^4BB	BBBB
4BBBBBB&BB6BB.4BB>4BBfBBvBBn4BB~4BBBB4BBBBBBBB4BBBBBB
  No newline at end of file
+rBBeBBm4BBrBBr2BB%BB-4BB2BB2BBBBBB4BB4BBDBBTBB$BB,4BBBB4BBBBBBBBBB4BB4RBBEBBM4BBRBBRBBBBBB4BB4BBBBBB4BB4BB4BBBBBBBBBBFBBVBBN4BB^4BB	BBBB
4BBBBBB&BB6BB.4BB>4BBfBBvBBn4BB~4BBBB4BBBBBBBB4BBBB
  No newline at end of file

          
M jasm/unit_tests/results/test_list_erase_with_too_few_arguments.stdout +1 -1
@@ 1,2 1,2 @@ 
-unit_tests/test_list_erase_with_too_few_arguments.asm(5,19) : Error 3053 : Number of method arguments doesn't match method signature. Method erase takes at least 1 argument and 0 was given.
+unit_tests/test_list_erase_with_too_few_arguments.asm(5,19) : Error 3053 : Number of method arguments doesn't match method signature. Method erase takes at least 1 argument and 0 were given.
 Assembly ended with errors.

          
M jasm/unit_tests/results/test_list_keep_with_too_few_arguments.stdout +1 -1
@@ 1,2 1,2 @@ 
-unit_tests/test_list_keep_with_too_few_arguments.asm(5,18) : Error 3053 : Number of method arguments doesn't match method signature. Method keep takes at least 1 argument and 0 was given.
+unit_tests/test_list_keep_with_too_few_arguments.asm(5,18) : Error 3053 : Number of method arguments doesn't match method signature. Method keep takes at least 1 argument and 0 were given.
 Assembly ended with errors.

          
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 were 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 were given.
+Assembly ended with errors.

          
A => jasm/unit_tests/results/test_self_assignment_constant.stdout +2 -0
@@ 0,0 1,2 @@ 
+unit_tests/test_self_assignment_constant.asm(3,11) : Error 3116 : A symbol's definition can't refer to itself.
+Assembly ended with errors.

          
A => jasm/unit_tests/results/test_self_assignment_variable.stdout +2 -0
@@ 0,0 1,2 @@ 
+unit_tests/test_self_assignment_variable.asm(3,9) : Error 3116 : A symbol's definition can't refer to itself.
+Assembly ended with errors.

          
A => jasm/unit_tests/results/test_string_conversion_ascii_7.bin +1 -0
@@ 0,0 1,1 @@ 
+!#$abcAB
  No newline at end of file

          
M jasm/unit_tests/results/test_too_few_arguments_to_variable_argument_function.stdout +1 -1
@@ 1,2 1,2 @@ 
-unit_tests/test_too_few_arguments_to_variable_argument_function.asm(5,18) : Error 3065 : Too few function arguments given. Function format takes at least 1 argument and 0 was given.
+unit_tests/test_too_few_arguments_to_variable_argument_function.asm(5,18) : Error 3065 : Too few function arguments given. Function format takes at least 1 argument and 0 were given.
 Assembly ended with errors.

          
M jasm/unit_tests/results/test_wrong_number_of_macro_arguments.stdout +1 -1
@@ 1,2 1,2 @@ 
-unit_tests/test_wrong_number_of_macro_arguments.asm(10,3) : Error 3025 : Wrong number of arguments. 2 arguments was expected and 1 was given.
+unit_tests/test_wrong_number_of_macro_arguments.asm(10,3) : Error 3025 : Wrong number of arguments. 2 arguments were expected and 1 was given.
 Assembly ended with errors.

          
M jasm/unit_tests/test_all_instructions_45gs02.asm +0 -1
@@ 364,6 364,5 @@ section code, "main", $1000
 	stq nn			// 424285
 	stq nnnn		// 42428d
 	stq (nn)		// 424292
-	stq (nn,sp),y	// 424282
 	stq [nn]		// 4242ea92
 }

          
M jasm/unit_tests/test_instruction_data_label_offsets_45gs02.asm +0 -4
@@ 299,10 299,6 @@ section code, "main", $1000
 		stq .lbl:(nn)		// 424292
 	}
 	{
-		static_assert(.lbl == * + 3, "wrong instruction data offset")
-		stq .lbl:(nn,sp),y	// 424282
-	}
-	{
 		static_assert(.lbl == * + 4, "wrong instruction data offset")
 		stq .lbl:[nn]		// 4242ea92
 	}

          
M jasm/unit_tests/test_instruction_data_label_sizes_45gs02.asm +0 -4
@@ 298,10 298,6 @@ section code,	 "main",	 $8000 {
 	}
 	{
 		static_assert(sizeof(.lbl) == 1, "wrong instruction data size")
-		stq .lbl:(nn,sp),y	// 424282
-	}
-	{
-		static_assert(sizeof(.lbl) == 1, "wrong instruction data size")
 		stq .lbl:[nn]		// 4242ea92
 	}
 }

          
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()

          
A => jasm/unit_tests/test_self_assignment_constant.asm +7 -0
@@ 0,0 1,7 @@ 
+// assembler command line arguments: 65c02 [-v0]
+
+const q = q
+
+section code, "main", 0, 100
+{
+}

          
A => jasm/unit_tests/test_self_assignment_variable.asm +7 -0
@@ 0,0 1,7 @@ 
+// assembler command line arguments: 65c02 [-v0]
+
+var q = q
+
+section code, "main", 0, 100
+{
+}

          
A => jasm/unit_tests/test_string_conversion_ascii_7.asm +8 -0
@@ 0,0 1,8 @@ 
+// assembler command line arguments: 6502 [-v0]
+
+section code, "main", 0
+{
+	define byte[] = {
+		string("!#$abcABC", "ascii7", "high_bit_term")
+	}
+}
  No newline at end of file

          
M jasm/version.h +1 -1
@@ 1,1 1,1 @@ 
-1,29
+1,30

          
M jasm/version.py +3 -3
@@ 28,11 28,11 @@ Returns a tuple with revision (as int) a
 
 	# Code for Mercurial
 	hg_output = subprocess.check_output(["hg", "summary"])
-	hg_output = hg_output.decode("latin1")
+	hg_output = hg_output.decode("utf-8")
 	for line in hg_output.splitlines():
-		m = re.match(r"parent:\s*(\d+):([0-9a-zA-Z]+)\s+.*", line)
+		m = re.match(r"(parent|förälder):\s*(\d+):([0-9a-zA-Z]+)\s+.*", line)
 		if m:
-			return (int(m.group(1)), m.group(2))
+			return (int(m.group(2)), m.group(3))
 
 	# Code for Git
 	#current_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip()

          
M jasm/website/site/docs/index.html +17 -6
@@ 6,6 6,7 @@ 
 		<meta name="description" content="This is the documentation for the jAsm assembler.">
 		<meta name="keywords" content="jAsm,6502,z80,assembler,asm,cross-assembler">
 		<meta name="author" content="Jonas Hultén">
+		<meta name="viewport" content="width=device-width, initial-scale=1">
 		<link rel="shortcut icon" href="images/favicon.ico">
 		<link href="jasm.css" rel="stylesheet">
 	</head>

          
@@ 224,7 225,7 @@ 
 <span class="instruction">bru</span> loop
 </code></pre>
 
-<p>Just like 65C02, the bit operation instructions don't have the bit in the instruction name as some assemblers do. Instead it is a separate argument. To follow convention, there is no '#' before the bit number to indicate immediate mode, even if that would be more consistent.</p>
+<p>Just like 65CE02, the bit operation instructions don't have the bit in the instruction name as some assemblers do. Instead it is a separate argument. To follow convention, there is no '#' before the bit number to indicate immediate mode, even if that would be more consistent.</p>
 
 <pre><code><span class="instruction">bbr</span> <span class="literal">0</span>, zp, label
 <span class="instruction">bbs</span> <span class="literal">1</span>, zp, label

          
@@ 237,7 238,7 @@ 
 <pre><code><span class="instruction">lda</span> <span class="special">(</span><span class="literal">$55</span>,sp<span class="special">)</span>,y
 </code></pre>
 
-<p>The new indirect quad addressing mode is written using brackets.</p>
+<p>The indirect quad addressing mode is written using brackets.</p>
 
 <pre><code><span class="instruction">lda</span> <span class="special">[</span><span class="literal">$55</span><span class="special">]</span>,z
 </code></pre>

          
@@ 1598,7 1599,7 @@ a comment */</span>
 <span class="instruction">inc</span> value <span class="comment">// increment what is loaded by the previous instruction next time it is executed</span>
 </code></pre>
 
-<p>On Z80, there are at most two arguments so in some cases two labels can be defined to instruction arguments.</p>
+<p>On Z80, there are at most two arguments so in some cases two labels can be defined to instruction arguments. Note how the label is placed when using indirect addressing modes. It has to be placed before the parenthesis.</p>
 
 <pre><code><span class="instruction">ld</span> hl, index
 <span class="instruction">ld</span> bc, data

          
@@ 2156,16 2157,20 @@ aa
         <th>Comment</th>
     </tr>
     <tr>
+        <td>ascii7</td>
+        <td>7 bit ascii format.</td>
+    </tr>
+    <tr>
         <td>petscii</td>
         <td>The character set used in Commodore 8 bit computers.</td>
     </tr>
     <tr>
         <td>zx80</td>
-        <td></td>
+        <td>Sinclair specific character set.</td>
     </tr>
     <tr>
         <td>zx81</td>
-        <td></td>
+        <td>Sinclair specific character set.</td>
     </tr>
 </table>
 

          
@@ 2346,6 2351,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>

          
@@ 2457,7 2468,7 @@ bb.<span class="keyword">pop</span><span
     <tr>
         <td><code><span class="function">string</span><span class="special">(</span>value <span class="special">[</span>, property, ...<span class="special">]</span><span class="special">)</span></code></td>
         <td>numeric|string, string, ...</td>
-        <td>Converts <code>value</code> to a string according to a specific character set. See [String Conversions](#string-conversions) for details about properties.</td>
+        <td>Converts <code>value</code> to a string according to a specific character set. See String Conversions for details about properties.</td>
         <td><code><span class="function">string</span><span class="special">(</span><span class="literal">"Hello"</span>, <span class="literal">"petscii"</span>, <span class="literal">"lowercase"</span><span class="special">)</span></code><br/><code><span class="function">string</span><span class="special">(</span><span class="literal">123</span>, <span class="literal">"zx81"</span><span class="special">)</span></code></td>
     </tr>
     <tr>

          
M jasm/website/site/docs/jasm.css +43 -12
@@ 1,3 1,12 @@ 
+@viewport {
+	width: device-width;
+	zoom: 1.0;
+}
+img {
+	width: auto;
+	max-width: 100%;
+	height: auto;
+}
 body
 {
 	width: 800px;

          
@@ 62,18 71,6 @@ h3
 	font-size: 1.1em;
 	color: #606060;
 }
-div.section
-{
-	width: 380px;
-	margin-right: 40px;
-	margin-top: 40px;
-	margin-bottom: 40px;
-	float: left;
-}
-div.section.even
-{
-	margin-right: 0;
-}
 a
 {
 	text-decoration: none;

          
@@ 119,3 116,37 @@ th, td
 	border: 1px solid #a1a1a1;
 	padding: 6px 6px;
 }
+div.section
+{
+	width: 380px;
+	margin-right: 40px;
+	margin-top: 40px;
+	margin-bottom: 40px;
+	float: left;
+}
+div.section.even
+{
+	margin-right: 0;
+}
+@media only screen and (max-width: 840px)
+{
+	body
+	{
+		width: auto;
+		margin: 16px;
+	}
+	div.section
+	{
+		width: 100%;
+		margin-left: auto;
+		margin-right: auto;
+		float: none;
+	}
+	div.section.even
+	{
+		width: 100%;
+		margin-left: auto;
+		margin-right: auto;
+		float: none;
+	}
+}

          
M jasm/website/site/index.html +66 -14
@@ 6,9 6,19 @@ 
 		<meta name="description" content="This is the official home for the jAsm assembler.">
 		<meta name="keywords" content="jAsm,6502,65c02,65ce02,45gs04,mega65,z80,assembler,asm,cross-assembler">
 		<meta name="author" content="Jonas Hultén">
+		<meta name="viewport" content="width=device-width, initial-scale=1">
 		<link rel="shortcut icon" href="images/favicon.ico">
 		
 		<style>
+			@viewport {
+				width: device-width;
+				zoom: 1.0;
+			}
+			img {
+				width: auto;
+				max-width: 100%;
+				height: auto;
+			}
 			body
 			{
 				background-color: #ffffff;

          
@@ 29,18 39,6 @@ 
 				font-size: 1.4em;
 				color: #606060;
 			}
-			div.section
-			{
-				width: 380px;
-				margin-right: 40px;
-				margin-top: 40px;
-				margin-bottom: 40px;
-				float: left;
-			}
-			div.section.even
-			{
-				margin-right: 0;
-			}
 			a
 			{
 				text-decoration:none;

          
@@ 56,10 54,52 @@ 
 				color: #933611;
 			}
 
+			div.page
+			{
+				margin: 0 auto;
+				width: 800px;
+				text-align: left;
+				margin-top: 40px;
+				margin-bottom: 40px;
+			}
+			div.section
+			{
+				width: 380px;
+				margin-right: 40px;
+				margin-top: 40px;
+				margin-bottom: 40px;
+				float: left;
+			}
+			div.section.even
+			{
+				margin-right: 0;
+			}
+
+			@media only screen and (max-width: 840px)
+			{
+				div.page
+				{
+					width: 100%;
+				}
+				div.section
+				{
+					width: 100%;
+					margin-left: auto;
+					margin-right: auto;
+					float: none;
+				}
+				div.section.even
+				{
+					width: 100%;
+					margin-left: auto;
+					margin-right: auto;
+					float: none;
+				}
+			}
 		</style>
 	</head>
 	<body>
-		<div style="margin: 0 auto; width: 800px; text-align: left; margin-top: 40px; margin-bottom: 40px;">
+		<div class="page">
 			<div style="text-align: center;">
 				<img src="images/logo_400.png" alt="jAsm Logo">
 			</div>

          
@@ 72,7 112,7 @@ 
 			<div class="section odd">
 				<h1>The Assembler</h1>
 				<p>
-					This is the official home for the 6502, 65C02, 65CE02 and Z80 assembler jAsm. jAsm was written by me, Jonas Hult&eacute;n, during 7 months in 2015. It is heavily geared towards C64 development, since that's what it's intended to be used for. It currently runs on the 64-bit Linux and Windows platforms but it should be fairly easy to port it to other platforms since it has no dependencies except the standard C++ library.
+					This is the official home for the 6502, 65C02, 65CE02, 45GS02 and Z80 assembler jAsm. jAsm was written by me, Jonas Hult&eacute;n, during 7 months in 2015. It is heavily geared towards C64 development, since that's what it's intended to be used for. It currently runs on the 64-bit Linux and Windows platforms but it should be fairly easy to port it to other platforms since it has no dependencies except the standard C++ library.
 				</p>
 				<h1>The Documentation</h1>
 				<p>

          
@@ 81,9 121,11 @@ 
 				<h1>The Binaries</h1>
 				<ul>
 					<li>
+						<a href="binaries/jasm_1.30_linux64.7z">jAsm 1.30 for 64-bit Linux</a>
 						<a href="binaries/jasm_1.29_linux64.7z">jAsm 1.29 for 64-bit Linux</a>
 					</li>
 					<li>
+						<a href="binaries/jasm_1.30_win64.7z">jAsm 1.30 for 64-bit Windows</a>
 						<a href="binaries/jasm_1.29_win64.7z">jAsm 1.29 for 64-bit Windows</a>
 					</li>
 				</ul>

          
@@ 99,6 141,16 @@ 
 				<h1>Version History</h1>
 				<ul>
 					<li>
+						1.30
+						<ul>
+							<li>Fixed a crash when a symbol definition referred to the constant or variable being created.</li>
+							<li>Added loop limit of 1 million iterations.</li>
+							<li>Added 7-bit ASCII conversion format.</li>
+							<li>The list type now has a sort function.</li>
+							<li>stq (nn,sp),y has been removed from 45gs02 instruction set</li>
+						</ul>
+					</li>
+					<li>
 						1.29
 						<ul>
 							<li>Renamed the 'map' type to 'dict' to support the map instruction of the 45gs02.</li>