mirror of
				https://github.com/TomHarte/CLK.git
				synced 2025-11-04 00:16:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			277 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			277 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
//
 | 
						|
//  Log.hpp
 | 
						|
//  Clock Signal
 | 
						|
//
 | 
						|
//  Created by Thomas Harte on 18/06/2018.
 | 
						|
//  Copyright © 2018 Thomas Harte. All rights reserved.
 | 
						|
//
 | 
						|
 | 
						|
#pragma once
 | 
						|
 | 
						|
#include <cstdio>
 | 
						|
#include <cstdarg>
 | 
						|
#include <cstring>
 | 
						|
#include <string>
 | 
						|
 | 
						|
namespace Log {
 | 
						|
// TODO: if adopting C++20, std::format would be a better model to apply below.
 | 
						|
// But I prefer C files to C++ streams, so here it is for now.
 | 
						|
// Also noting: Apple's std::format support seems to require macOS 13.3, so
 | 
						|
// that'd be a bitter pill to swallow.
 | 
						|
 | 
						|
enum class Source {
 | 
						|
	ADBDevice,
 | 
						|
	ADBGLU,
 | 
						|
	Amiga,
 | 
						|
	AmigaDisk,
 | 
						|
	AmigaCopper,
 | 
						|
	AmigaChipset,
 | 
						|
	AmigaBlitter,
 | 
						|
	AppleIISCSICard,
 | 
						|
	Archimedes,
 | 
						|
	ARMIOC,
 | 
						|
	ARMMEMC,
 | 
						|
	ARMVIDC,
 | 
						|
	AtariST,
 | 
						|
	AtariSTDMAController,
 | 
						|
	BBCMicro,
 | 
						|
	CommodoreStaticAnalyser,
 | 
						|
	CMOSRTC,
 | 
						|
	CRT,
 | 
						|
	DirectAccessDevice,
 | 
						|
	Enterprise,
 | 
						|
	Floppy,
 | 
						|
	i8272,
 | 
						|
	I2C,
 | 
						|
	IDE,
 | 
						|
	IntelligentKeyboard,	// Could probably be subsumed into 'Keyboard'?
 | 
						|
	IWM,
 | 
						|
	Keyboard,
 | 
						|
	M50740,
 | 
						|
	Macintosh,
 | 
						|
	MasterSystem,
 | 
						|
	MultiMachine,
 | 
						|
	MFP68901,
 | 
						|
	MOS6526,
 | 
						|
	MSX,
 | 
						|
	NCR5380,
 | 
						|
	OpenGL,
 | 
						|
	PCCompatible,
 | 
						|
	PCPOST,
 | 
						|
	PIC,
 | 
						|
	PIT,
 | 
						|
	Plus4,
 | 
						|
	PCMTrack,
 | 
						|
	SCC,
 | 
						|
	SCSI,
 | 
						|
	SZX,
 | 
						|
	TapeUEF,
 | 
						|
	TMS9918,
 | 
						|
	TZX,
 | 
						|
	Vic20,
 | 
						|
	WDFDC,
 | 
						|
};
 | 
						|
 | 
						|
enum class EnabledLevel {
 | 
						|
	None,				// No logged statements are presented.
 | 
						|
	Errors,				// The error stream is presented, but not the info stream.
 | 
						|
	ErrorsAndInfo,		// All streams are presented.
 | 
						|
};
 | 
						|
 | 
						|
constexpr EnabledLevel enabled_level(const Source source) {
 | 
						|
#ifdef NDEBUG
 | 
						|
	return EnabledLevel::None;
 | 
						|
#endif
 | 
						|
 | 
						|
	// Allow for compile-time source-level enabling and disabling of different sources.
 | 
						|
	switch(source) {
 | 
						|
		default:
 | 
						|
			return EnabledLevel::ErrorsAndInfo;
 | 
						|
 | 
						|
		// The following are all things I'm not actively working on.
 | 
						|
		case Source::AmigaBlitter:
 | 
						|
		case Source::AmigaChipset:
 | 
						|
		case Source::AmigaCopper:
 | 
						|
		case Source::AmigaDisk:
 | 
						|
		case Source::DirectAccessDevice:
 | 
						|
		case Source::IWM:
 | 
						|
		case Source::MFP68901:
 | 
						|
		case Source::NCR5380:
 | 
						|
		case Source::SCC:
 | 
						|
		case Source::SCSI:
 | 
						|
		case Source::I2C:
 | 
						|
//		case Source::PCPOST:
 | 
						|
			return EnabledLevel::None;
 | 
						|
 | 
						|
		case Source::Floppy:
 | 
						|
//		case Source::Keyboard:
 | 
						|
			return EnabledLevel::Errors;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
constexpr const char *prefix(const Source source) {
 | 
						|
	switch(source) {
 | 
						|
		case Source::ADBDevice:					return "ADB device";
 | 
						|
		case Source::ADBGLU:					return "ADB GLU";
 | 
						|
		case Source::Amiga:						return "Amiga";
 | 
						|
		case Source::AmigaBlitter:				return "Blitter";
 | 
						|
		case Source::AmigaChipset:				return "Chipset";
 | 
						|
		case Source::AmigaCopper:				return "Copper";
 | 
						|
		case Source::AmigaDisk:					return "Disk";
 | 
						|
		case Source::AppleIISCSICard:			return "SCSI card";
 | 
						|
		case Source::Archimedes:				return "Archimedes";
 | 
						|
		case Source::ARMIOC:					return "IOC";
 | 
						|
		case Source::ARMMEMC:					return "MEMC";
 | 
						|
		case Source::ARMVIDC:					return "VIDC";
 | 
						|
		case Source::AtariST:					return "AtariST";
 | 
						|
		case Source::AtariSTDMAController:		return "DMA";
 | 
						|
		case Source::BBCMicro:					return "BBC";
 | 
						|
		case Source::CommodoreStaticAnalyser:	return "Commodore Static Analyser";
 | 
						|
		case Source::CMOSRTC:					return "CMOSRTC";
 | 
						|
		case Source::CRT:						return "CRT";
 | 
						|
		case Source::DirectAccessDevice:		return "Direct Access Device";
 | 
						|
		case Source::Enterprise:				return "Enterprise";
 | 
						|
		case Source::Floppy:					return "Floppy";
 | 
						|
		case Source::i8272:						return "i8272";
 | 
						|
		case Source::I2C:						return "I2C";
 | 
						|
		case Source::IDE:						return "IDE";
 | 
						|
		case Source::IntelligentKeyboard:		return "IKYB";
 | 
						|
		case Source::IWM:						return "IWM";
 | 
						|
		case Source::Keyboard:					return "Keyboard";
 | 
						|
		case Source::M50740:					return "M50740";
 | 
						|
		case Source::Macintosh:					return "Macintosh";
 | 
						|
		case Source::MasterSystem:				return "SMS";
 | 
						|
		case Source::MOS6526:					return "MOS6526";
 | 
						|
		case Source::MFP68901:					return "MFP68901";
 | 
						|
		case Source::MultiMachine:				return "Multi-machine";
 | 
						|
		case Source::MSX:						return "MSX";
 | 
						|
		case Source::NCR5380:					return "5380";
 | 
						|
		case Source::OpenGL:					return "OpenGL";
 | 
						|
		case Source::Plus4:						return "Plus4";
 | 
						|
		case Source::PCCompatible:				return "PC";
 | 
						|
		case Source::PCPOST:					return "POST";
 | 
						|
		case Source::PIC:						return "PIC";
 | 
						|
		case Source::PIT:						return "PIT";
 | 
						|
		case Source::PCMTrack:					return "PCM Track";
 | 
						|
		case Source::SCSI:						return "SCSI";
 | 
						|
		case Source::SCC:						return "SCC";
 | 
						|
		case Source::SZX:						return "SZX";
 | 
						|
		case Source::TapeUEF:					return "UEF";
 | 
						|
		case Source::TMS9918:					return "TMS9918";
 | 
						|
		case Source::TZX:						return "TZX";
 | 
						|
		case Source::Vic20:						return "Vic20";
 | 
						|
		case Source::WDFDC:						return "WD FDC";
 | 
						|
	}
 | 
						|
 | 
						|
	return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
template <Source source, bool enabled>
 | 
						|
struct LogLine;
 | 
						|
 | 
						|
struct RepeatAccumulator {
 | 
						|
	std::string last;
 | 
						|
	Source source;
 | 
						|
 | 
						|
	size_t count = 0;
 | 
						|
	FILE *stream;
 | 
						|
};
 | 
						|
 | 
						|
struct AccumulatingLog {
 | 
						|
	inline static thread_local RepeatAccumulator accumulator_;
 | 
						|
};
 | 
						|
 | 
						|
template <Source source>
 | 
						|
struct LogLine<source, true>: private AccumulatingLog {
 | 
						|
public:
 | 
						|
	explicit LogLine(FILE *const stream) noexcept :
 | 
						|
		stream_(stream) {}
 | 
						|
 | 
						|
	~LogLine() {
 | 
						|
		if(output_ == accumulator_.last && source == accumulator_.source && stream_ == accumulator_.stream) {
 | 
						|
			++accumulator_.count;
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		if(!accumulator_.last.empty()) {
 | 
						|
			const char *const unadorned_prefix = prefix(accumulator_.source);
 | 
						|
			std::string prefix;
 | 
						|
			if(unadorned_prefix) {
 | 
						|
				prefix = "[";
 | 
						|
				prefix += unadorned_prefix;
 | 
						|
				prefix += "] ";
 | 
						|
			}
 | 
						|
 | 
						|
			if(accumulator_.count > 1) {
 | 
						|
				fprintf(
 | 
						|
					accumulator_.stream,
 | 
						|
					"%s%s [* %zu]\n",
 | 
						|
						prefix.c_str(),
 | 
						|
						accumulator_.last.c_str(),
 | 
						|
						accumulator_.count
 | 
						|
				);
 | 
						|
			} else {
 | 
						|
				fprintf(
 | 
						|
					accumulator_.stream,
 | 
						|
					"%s%s\n",
 | 
						|
						prefix.c_str(),
 | 
						|
						accumulator_.last.c_str()
 | 
						|
				);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		accumulator_.count = 1;
 | 
						|
		accumulator_.last = output_;
 | 
						|
		accumulator_.source = source;
 | 
						|
		accumulator_.stream = stream_;
 | 
						|
	}
 | 
						|
 | 
						|
	template <size_t size, typename... Args>
 | 
						|
	auto &append(const char (&format)[size], Args... args) {
 | 
						|
#pragma GCC diagnostic push
 | 
						|
#pragma GCC diagnostic ignored "-Wformat"
 | 
						|
		const auto append_size = std::snprintf(nullptr, 0, format, args...);
 | 
						|
		const auto end = output_.size();
 | 
						|
		output_.resize(output_.size() + size_t(append_size) + 1);
 | 
						|
		std::snprintf(output_.data() + end, size_t(append_size) + 1, format, args...);
 | 
						|
		output_.pop_back();
 | 
						|
#pragma GCC diagnostic pop
 | 
						|
		return *this;
 | 
						|
	}
 | 
						|
 | 
						|
	template <size_t size, typename... Args>
 | 
						|
	auto &append_if(const bool condition, const char (&format)[size], Args... args) {
 | 
						|
		if(!condition) return *this;
 | 
						|
		return append(format, args...);
 | 
						|
	}
 | 
						|
 | 
						|
private:
 | 
						|
	FILE *stream_;
 | 
						|
	std::string output_;
 | 
						|
};
 | 
						|
 | 
						|
template <Source source>
 | 
						|
struct LogLine<source, false> {
 | 
						|
	explicit LogLine(FILE *) noexcept {}
 | 
						|
 | 
						|
	template <size_t size, typename... Args>
 | 
						|
	auto &append(const char (&)[size], Args...) { return *this; }
 | 
						|
 | 
						|
	template <size_t size, typename... Args>
 | 
						|
	auto &append_if(bool, const char (&)[size], Args...) { return *this; }
 | 
						|
};
 | 
						|
 | 
						|
template <Source source>
 | 
						|
class Logger {
 | 
						|
public:
 | 
						|
	Logger() = delete;
 | 
						|
 | 
						|
	static constexpr bool InfoEnabled = enabled_level(source) == EnabledLevel::ErrorsAndInfo;
 | 
						|
	static constexpr bool ErrorsEnabled = enabled_level(source) >= EnabledLevel::Errors;
 | 
						|
 | 
						|
	static auto info()	{	return LogLine<source, InfoEnabled>(stdout);	}
 | 
						|
	static auto error()	{	return LogLine<source, ErrorsEnabled>(stderr);	}
 | 
						|
};
 | 
						|
 | 
						|
}
 |