mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-21 02:37:44 +00:00
Merge pull request #1488 from TomHarte/ATKeyboard
Implement some proportion of the AT keyboard controller.
This commit is contained in:
commit
143d1d5e35
@ -14,7 +14,7 @@ using namespace Zilog::SCC;
|
||||
|
||||
namespace {
|
||||
|
||||
Log::Logger<Log::Source::SCC> log;
|
||||
Log::Logger<Log::Source::SCC> logger;
|
||||
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ std::uint8_t z8530::read(const int address) {
|
||||
|
||||
case 2: // Handled non-symmetrically between channels.
|
||||
if(address & 1) {
|
||||
log.error().append("Unimplemented: register 2 status bits");
|
||||
logger.error().append("Unimplemented: register 2 status bits");
|
||||
} else {
|
||||
result = interrupt_vector_;
|
||||
|
||||
@ -111,11 +111,11 @@ void z8530::write(const int address, const std::uint8_t value) {
|
||||
case 2: // Interrupt vector register; used only by Channel B.
|
||||
// So there's only one of these.
|
||||
interrupt_vector_ = value;
|
||||
log.info().append("Interrupt vector set to %d", value);
|
||||
logger.info().append("Interrupt vector set to %d", value);
|
||||
break;
|
||||
|
||||
case 9: // Master interrupt and reset register; there is also only one of these.
|
||||
log.info().append("Master interrupt and reset register: %02x", value);
|
||||
logger.info().append("Master interrupt and reset register: %02x", value);
|
||||
master_interrupt_control_ = value;
|
||||
break;
|
||||
}
|
||||
@ -152,7 +152,7 @@ uint8_t z8530::Channel::read(const bool data, const uint8_t pointer) {
|
||||
if(data) {
|
||||
return data_;
|
||||
} else {
|
||||
log.info().append("Control read from register %d", pointer);
|
||||
logger.info().append("Control read from register %d", pointer);
|
||||
// Otherwise, this is a control read...
|
||||
switch(pointer) {
|
||||
default:
|
||||
@ -237,10 +237,10 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
data_ = value;
|
||||
return;
|
||||
} else {
|
||||
log.info().append("Control write: %02x to register %d", value, pointer);
|
||||
logger.info().append("Control write: %02x to register %d", value, pointer);
|
||||
switch(pointer) {
|
||||
default:
|
||||
log.info().append("Unrecognised control write: %02x to register %d", value, pointer);
|
||||
logger.info().append("Unrecognised control write: %02x to register %d", value, pointer);
|
||||
break;
|
||||
|
||||
case 0x0: // Write register 0 — CRC reset and other functions.
|
||||
@ -248,13 +248,13 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
switch(value >> 6) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 1:
|
||||
log.error().append("TODO: reset Rx CRC checker.");
|
||||
logger.error().append("TODO: reset Rx CRC checker.");
|
||||
break;
|
||||
case 2:
|
||||
log.error().append("TODO: reset Tx CRC checker.");
|
||||
logger.error().append("TODO: reset Tx CRC checker.");
|
||||
break;
|
||||
case 3:
|
||||
log.error().append("TODO: reset Tx underrun/EOM latch.");
|
||||
logger.error().append("TODO: reset Tx underrun/EOM latch.");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -262,24 +262,24 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
switch((value >> 3)&7) {
|
||||
default: /* Do nothing. */ break;
|
||||
case 2:
|
||||
// log.info().append("reset ext/status interrupts.");
|
||||
// logger.info().append("reset ext/status interrupts.");
|
||||
external_status_interrupt_ = false;
|
||||
external_interrupt_status_ = 0;
|
||||
break;
|
||||
case 3:
|
||||
log.error().append("TODO: send abort (SDLC).");
|
||||
logger.error().append("TODO: send abort (SDLC).");
|
||||
break;
|
||||
case 4:
|
||||
log.error().append("TODO: enable interrupt on next Rx character.");
|
||||
logger.error().append("TODO: enable interrupt on next Rx character.");
|
||||
break;
|
||||
case 5:
|
||||
log.error().append("TODO: reset Tx interrupt pending.");
|
||||
logger.error().append("TODO: reset Tx interrupt pending.");
|
||||
break;
|
||||
case 6:
|
||||
log.error().append("TODO: reset error.");
|
||||
logger.error().append("TODO: reset error.");
|
||||
break;
|
||||
case 7:
|
||||
log.error().append("TODO: reset highest IUS.");
|
||||
logger.error().append("TODO: reset highest IUS.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -304,7 +304,7 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't.
|
||||
b0 = 1 => external interrupt is enabled; 0 => it isn't.
|
||||
*/
|
||||
log.info().append("Interrupt mask: %02x", value);
|
||||
logger.info().append("Interrupt mask: %02x", value);
|
||||
break;
|
||||
|
||||
case 0x2: // Write register 2 - interrupt vector.
|
||||
@ -319,7 +319,7 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
|
||||
case 2: receive_bit_count = 6; break;
|
||||
case 3: receive_bit_count = 8; break;
|
||||
}
|
||||
log.info().append("Receive bit count: %d", receive_bit_count);
|
||||
logger.info().append("Receive bit count: %d", receive_bit_count);
|
||||
|
||||
/*
|
||||
b7,b6:
|
||||
|
49
Machines/PCCompatible/CPUControl.hpp
Normal file
49
Machines/PCCompatible/CPUControl.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// CPUControl.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 08/03/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Memory.hpp"
|
||||
#include "ProcessorByModel.hpp"
|
||||
#include "Registers.hpp"
|
||||
#include "Segments.hpp"
|
||||
|
||||
#include "Analyser/Static/PCCompatible/Target.hpp"
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class CPUControl {
|
||||
public:
|
||||
CPUControl(
|
||||
Registers<processor_model(model)> ®isters,
|
||||
Segments<processor_model(model)> &segments,
|
||||
Memory<model> &memory
|
||||
) : registers_(registers), segments_(segments), memory_(memory) {}
|
||||
|
||||
void reset() {
|
||||
registers_.reset();
|
||||
segments_.reset();
|
||||
}
|
||||
|
||||
void set_a20_enabled(const bool enabled) {
|
||||
// Assumed: this'll be something to set on Memory.
|
||||
log_.info().append("A20 line is now: ", enabled);
|
||||
}
|
||||
|
||||
private:
|
||||
Registers<processor_model(model)> ®isters_;
|
||||
Segments<processor_model(model)> &segments_;
|
||||
Memory<model> &memory_;
|
||||
|
||||
Log::Logger<Log::Source::PCCompatible> log_;
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -8,14 +8,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CPUControl.hpp"
|
||||
#include "PIC.hpp"
|
||||
#include "Speaker.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
/*!
|
||||
Provides an implementation of either an XT- or AT-style keyboard controller,
|
||||
as determined by the model template parameter.
|
||||
*/
|
||||
template <Analyser::Static::PCCompatible::Model, typename Enable = void>
|
||||
class KeyboardController;
|
||||
|
||||
/*!
|
||||
Models the XT keyboard controller.
|
||||
*/
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class KeyboardController<model, typename std::enable_if_t<is_xt(model)>> {
|
||||
public:
|
||||
@ -85,6 +93,8 @@ public:
|
||||
pics_.pic[0].template apply_edge<1>(true);
|
||||
}
|
||||
|
||||
void set_cpu_control(CPUControl<model> *) {}
|
||||
|
||||
private:
|
||||
enum class Mode {
|
||||
NormalOperation = 0b01,
|
||||
@ -99,24 +109,43 @@ private:
|
||||
int reset_delay_ = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
Models the AT keyboard controller.
|
||||
*/
|
||||
template <Analyser::Static::PCCompatible::Model model>
|
||||
class KeyboardController<model, typename std::enable_if_t<is_at(model)>> {
|
||||
public:
|
||||
KeyboardController(PICs<model> &pics, Speaker &speaker) : pics_(pics), speaker_(speaker) {}
|
||||
|
||||
void run_for([[maybe_unused]] const Cycles cycles) {
|
||||
void run_for(const Cycles cycles) {
|
||||
if(!write_delay_) return;
|
||||
write_delay_ -= cycles.as<int>();
|
||||
if(write_delay_ <= 0) {
|
||||
write_delay_ = 0;
|
||||
perform_command();
|
||||
}
|
||||
}
|
||||
|
||||
void post([[maybe_unused]] const uint8_t value) {
|
||||
void post(const uint8_t value) {
|
||||
input_ = value;
|
||||
has_input_ = true;
|
||||
command_phase_ = CommandPhase::WaitingForCommand;
|
||||
pics_.pic[0].template apply_edge<1>(true);
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
void write([[maybe_unused]] const uint16_t port, [[maybe_unused]] const IntT value) {
|
||||
void write(const uint16_t port, const uint8_t value) {
|
||||
switch(port) {
|
||||
default:
|
||||
log_.error().append("Unimplemented AT keyboard write: %04x to %04x", value, port);
|
||||
break;
|
||||
|
||||
case 0x0060:
|
||||
// log_.error().append("Keyboard parameter set to %02x", value);
|
||||
output_ = value;
|
||||
has_output_ = true;
|
||||
write_delay_ = 10;
|
||||
break;
|
||||
|
||||
case 0x0061:
|
||||
// TODO:
|
||||
// b7: 1 = reset IRQ 0
|
||||
@ -124,30 +153,170 @@ public:
|
||||
// b2: enable parity check
|
||||
speaker_.set_control(value & 0x01, value & 0x02);
|
||||
break;
|
||||
|
||||
case 0x0064:
|
||||
// log_.info().append("AT keyboard command %04x", value);
|
||||
command_ = value;
|
||||
has_command_ = true;
|
||||
command_phase_ = CommandPhase::WaitingForData;
|
||||
|
||||
write_delay_ = performance_delay(command_);
|
||||
if(!write_delay_) {
|
||||
perform_command();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IntT>
|
||||
IntT read([[maybe_unused]] const uint16_t port) {
|
||||
IntT read(const uint16_t port) {
|
||||
switch(port) {
|
||||
default:
|
||||
log_.error().append("Unimplemented AT keyboard read from %04x", port);
|
||||
// log_.error().append("Unimplemented AT keyboard read from %04x", port);
|
||||
break;
|
||||
|
||||
case 0x0060:
|
||||
log_.error().append("Read from keyboard controller of %02x", input_);
|
||||
has_input_ = false;
|
||||
return input_;
|
||||
|
||||
case 0x0061:
|
||||
// In a real machine bit 4 toggles as a function of memory refresh; it is often
|
||||
// used by BIOSes to check that refresh is happening, with no greater inspection
|
||||
// than that it is toggling. So toggle on read.
|
||||
//
|
||||
// TODO: is this really from the keyboard controller?
|
||||
refresh_toggle_ ^= 0x10;
|
||||
log_.info().append("AT keyboard: %02x from %04x", refresh_toggle_, port);
|
||||
|
||||
// log_.info().append("AT keyboard: %02x from %04x", refresh_toggle_, port);
|
||||
return refresh_toggle_;
|
||||
|
||||
case 0x0064: {
|
||||
// Status:
|
||||
// b7 = 1 => parity error on transmission;
|
||||
// b6 = 1 => receive timeout;
|
||||
// b5 = 1 => transmit timeout;
|
||||
// b4 = 1 => keyboard active;
|
||||
// b3 = 1 = data at 0060 is command, 0 = data;
|
||||
// b2 = 1 = selftest OK; 0 = just powered up or reset;
|
||||
// b1 = 1 => 'input' buffer full (i.e. don't write 0x60 or 0x64 now — this is input to the controller);
|
||||
// b0 = 1 => 'output' data is full (i.e. reading from 0x60 now makes sense — output is to PC).
|
||||
const uint8_t status =
|
||||
0x10 |
|
||||
(command_phase_ == CommandPhase::WaitingForData ? 0x08 : 0x00) |
|
||||
(is_tested_ ? 0x04 : 0x00) |
|
||||
(has_output_ ? 0x02 : 0x00) |
|
||||
(has_input_ ? 0x01 : 0x00);
|
||||
log_.error().append("Reading status: %02x", status);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return IntT(~0);
|
||||
}
|
||||
|
||||
void set_cpu_control(CPUControl<model> *const control) {
|
||||
cpu_control_ = control;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr bool requires_parameter(const uint8_t command) {
|
||||
return
|
||||
(command >= 0x60 && command < 0x80) ||
|
||||
(command == 0xc1) || (command == 0xc2) ||
|
||||
(command >= 0xd1 && command < 0xd5);
|
||||
}
|
||||
|
||||
static constexpr int performance_delay(const uint8_t command) {
|
||||
if(requires_parameter(command)) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
switch(command) {
|
||||
case 0xaa: return 5;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void perform_command() {
|
||||
// Wait for a command.
|
||||
if(!has_command_) return;
|
||||
|
||||
// Wait for a parameter if one is needed.
|
||||
if(requires_parameter(command_) && !has_output_) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto info = log_.info();
|
||||
info.append("Keyboard command: %02x", command_);
|
||||
if(has_output_) {
|
||||
info.append(" / %02x", output_);
|
||||
}
|
||||
}
|
||||
|
||||
// Consume command and parameter, and execute.
|
||||
has_command_ = has_output_ = false;
|
||||
|
||||
switch(command_) {
|
||||
default:
|
||||
log_.info().append("Keyboard command unimplemented", command_);
|
||||
break;
|
||||
|
||||
case 0xaa: // Self-test; 0x55 => no issues found.
|
||||
log_.error().append("Keyboard self-test");
|
||||
post(0x55);
|
||||
break;
|
||||
|
||||
case 0xd1: // Set output byte. b1 = the A20 gate.
|
||||
log_.error().append("Should set A20 gate: %d", output_ & 0x02);
|
||||
cpu_control_->set_a20_enabled(output_ & 0x02);
|
||||
break;
|
||||
|
||||
case 0x60:
|
||||
is_tested_ = output_ & 0x4;
|
||||
break;
|
||||
|
||||
case 0xc0: // Read input port.
|
||||
post(0xd0);
|
||||
break;
|
||||
|
||||
case 0xf0: case 0xf1: case 0xf2: case 0xf3:
|
||||
case 0xf4: case 0xf5: case 0xf6: case 0xf7:
|
||||
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
|
||||
case 0xfc: case 0xfd: case 0xfe: case 0xff:
|
||||
log_.error().append("Should reset: %x", command_ & 0x0f);
|
||||
|
||||
if(!(command_ & 1)) {
|
||||
cpu_control_->reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
command_phase_ = CommandPhase::WaitingForCommand;
|
||||
}
|
||||
|
||||
Log::Logger<Log::Source::PCCompatible> log_;
|
||||
|
||||
PICs<model> &pics_;
|
||||
Speaker &speaker_;
|
||||
CPUControl<model> *cpu_control_ = nullptr;
|
||||
|
||||
uint8_t refresh_toggle_ = 0;
|
||||
|
||||
bool has_input_ = false;
|
||||
uint8_t input_;
|
||||
bool has_output_ = false;
|
||||
uint8_t output_;
|
||||
bool has_command_ = false;
|
||||
uint8_t command_;
|
||||
|
||||
int write_delay_ = 0;
|
||||
|
||||
bool is_tested_ = false;
|
||||
enum class CommandPhase {
|
||||
WaitingForCommand,
|
||||
WaitingForData,
|
||||
} command_phase_ = CommandPhase::WaitingForCommand;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "PCCompatible.hpp"
|
||||
|
||||
#include "CGA.hpp"
|
||||
#include "CPUControl.hpp"
|
||||
#include "DMA.hpp"
|
||||
#include "FloppyController.hpp"
|
||||
#include "KeyboardController.hpp"
|
||||
@ -292,7 +293,7 @@ public:
|
||||
if constexpr (is_xt(model)) {
|
||||
ppi_.write(port, uint8_t(value));
|
||||
} else {
|
||||
keyboard_.write(port, value);
|
||||
keyboard_.write(port, uint8_t(value));
|
||||
}
|
||||
break;
|
||||
|
||||
@ -884,14 +885,15 @@ private:
|
||||
segments(registers),
|
||||
memory(registers, segments),
|
||||
flow_controller(registers, segments),
|
||||
cpu_control(registers, segments, memory),
|
||||
io(pit, dma, ppi, pics, card, fdc, keyboard, rtc)
|
||||
{
|
||||
keyboard.set_cpu_control(&cpu_control);
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
registers.reset();
|
||||
segments.reset();
|
||||
cpu_control.reset();
|
||||
}
|
||||
|
||||
InstructionSet::x86::Flags flags;
|
||||
@ -899,6 +901,7 @@ private:
|
||||
Segments<x86_model> segments;
|
||||
Memory<pc_model> memory;
|
||||
FlowController<pc_model> flow_controller;
|
||||
CPUControl<pc_model> cpu_control;
|
||||
IO<pc_model, video> io;
|
||||
static constexpr auto model = processor_model(pc_model);
|
||||
} context_;
|
||||
@ -917,7 +920,11 @@ private:
|
||||
using namespace PCCompatible;
|
||||
|
||||
namespace {
|
||||
#ifndef NDEBUG
|
||||
static constexpr bool ForceAT = true;
|
||||
#else
|
||||
static constexpr bool ForceAT = false;
|
||||
#endif
|
||||
|
||||
template <Target::VideoAdaptor video>
|
||||
std::unique_ptr<Machine> machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
@ -942,10 +949,10 @@ std::unique_ptr<Machine> Machine::PCCompatible(
|
||||
const ROMMachine::ROMFetcher &rom_fetcher
|
||||
) {
|
||||
const Target *const pc_target = dynamic_cast<const Target *>(target);
|
||||
|
||||
using VideoAdaptor = Target::VideoAdaptor;
|
||||
switch(pc_target->adaptor) {
|
||||
case Target::VideoAdaptor::MDA: return machine<Target::VideoAdaptor::MDA>(*pc_target, rom_fetcher);
|
||||
case Target::VideoAdaptor::CGA: return machine<Target::VideoAdaptor::CGA>(*pc_target, rom_fetcher);
|
||||
case VideoAdaptor::MDA: return machine<VideoAdaptor::MDA>(*pc_target, rom_fetcher);
|
||||
case VideoAdaptor::CGA: return machine<VideoAdaptor::CGA>(*pc_target, rom_fetcher);
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,9 @@
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
/*!
|
||||
Implements enough of the MC146818 to satisfy those BIOSes I've tested.
|
||||
*/
|
||||
class RTC {
|
||||
public:
|
||||
template <int address>
|
||||
|
@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "InstructionSets/x86/Model.hpp"
|
||||
#include "Numeric/RegisterSizes.hpp"
|
||||
|
||||
namespace PCCompatible {
|
||||
|
||||
|
@ -1457,6 +1457,7 @@
|
||||
4B1FBE222D7B739700BAC888 /* FloppyController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FloppyController.hpp; sourceTree = "<group>"; };
|
||||
4B1FBE232D7B73C800BAC888 /* Speaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4B1FBE242D7B753000BAC888 /* KeyboardController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardController.hpp; sourceTree = "<group>"; };
|
||||
4B1FBE252D7C0B2200BAC888 /* CPUControl.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CPUControl.hpp; sourceTree = "<group>"; };
|
||||
4B2005402B804AA300420C5C /* OperationMapper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = OperationMapper.hpp; sourceTree = "<group>"; };
|
||||
4B2005422B804D6400420C5C /* ARMDecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ARMDecoderTests.mm; sourceTree = "<group>"; };
|
||||
4B2005462B8BD7A500420C5C /* Registers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Registers.hpp; sourceTree = "<group>"; };
|
||||
@ -2560,6 +2561,7 @@
|
||||
children = (
|
||||
425739372B051EA800B7D1E4 /* PCCompatible.cpp */,
|
||||
429B13632B20234B006BB4CB /* CGA.hpp */,
|
||||
4B1FBE252D7C0B2200BAC888 /* CPUControl.hpp */,
|
||||
4267A9C92B0D4F17008A59BB /* DMA.hpp */,
|
||||
4B1FBE222D7B739700BAC888 /* FloppyController.hpp */,
|
||||
4B1FBE242D7B753000BAC888 /* KeyboardController.hpp */,
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#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.
|
||||
@ -88,7 +90,7 @@ constexpr bool is_enabled(const Source source) {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char *prefix(Source source) {
|
||||
constexpr const char *prefix(const Source source) {
|
||||
switch(source) {
|
||||
default: return nullptr;
|
||||
|
||||
@ -137,41 +139,56 @@ constexpr const char *prefix(Source source) {
|
||||
}
|
||||
}
|
||||
|
||||
template <Source source, bool enabled>
|
||||
struct LogLine;
|
||||
|
||||
template <Source source>
|
||||
struct LogLine<source, true> {
|
||||
public:
|
||||
explicit LogLine(FILE *const stream) noexcept : stream_(stream) {
|
||||
const auto source_prefix = prefix(source);
|
||||
if(!source_prefix) return;
|
||||
|
||||
output_.resize(strlen(source_prefix) + 4);
|
||||
std::snprintf(output_.data(), output_.size(), "[%s] ", source_prefix);
|
||||
output_.pop_back();
|
||||
}
|
||||
|
||||
~LogLine() {
|
||||
fprintf(stream_, "%s\n", output_.c_str());
|
||||
}
|
||||
|
||||
template <size_t size, typename... Args>
|
||||
void 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
|
||||
}
|
||||
|
||||
private:
|
||||
std::string output_;
|
||||
FILE *stream_;
|
||||
};
|
||||
|
||||
template <Source source>
|
||||
struct LogLine<source, false> {
|
||||
explicit LogLine(FILE *) noexcept {}
|
||||
|
||||
template <size_t size, typename... Args>
|
||||
void append(const char (&)[size], Args...) {}
|
||||
};
|
||||
|
||||
template <Source source>
|
||||
class Logger {
|
||||
public:
|
||||
static constexpr bool enabled = is_enabled(source);
|
||||
|
||||
struct LogLine {
|
||||
public:
|
||||
LogLine(FILE *const stream) : stream_(stream) {
|
||||
if constexpr (!enabled) return;
|
||||
|
||||
const auto source_prefix = prefix(source);
|
||||
if(source_prefix) {
|
||||
fprintf(stream_, "[%s] ", source_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
~LogLine() {
|
||||
if constexpr (!enabled) return;
|
||||
fprintf(stream_, "\n");
|
||||
}
|
||||
|
||||
void append(const char *const format, ...) {
|
||||
if constexpr (!enabled) return;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stream_, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
private:
|
||||
FILE *stream_;
|
||||
};
|
||||
|
||||
LogLine info() { return LogLine(stdout); }
|
||||
LogLine error() { return LogLine(stderr); }
|
||||
LogLine<source, enabled> info() { return LogLine<source, enabled>(stdout); }
|
||||
LogLine<source, enabled> error() { return LogLine<source, enabled>(stderr); }
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user