Preprocess and convert C1 controls to their 7 bit equivalent (#7340)

C1 control characters are now first converted to their 7 bit equivalent.
This allows us to unify the logic of C1 and C0 escape handling. This
also adds support for SOS/PM/APC string.

* Unify the logic for C1 and C0 escape handling by converting C1 to C0
  beforehand. This adds support for various C1 characters, including
  IND(8/4), NEL(8/5), HTS(8/8), RI(8/13), SS2(8/14), SS3(8/15),
  OSC(9/13), etc. 
* Add support for SOS/PM/APC escape sequences. Fixes #7032
* Use "Variable Length String" logic to unify the string termination
  handling of OSC, DCS and SOS/PM/APC. This fixes an issue where OSC
  action is successfully dispatched even when terminated with non-ST
  character. Introduced by #6328, the DCS PassThrough is spared from
  this issue. This PR puts them together and add test cases for them.

References:
https://vt100.net/docs/vt510-rm/chapter4.html
https://vt100.net/emu/dec_ansi_parser

Closes #7032
Closes #7317
This commit is contained in:
Chester Liu 2020-09-17 06:30:46 +08:00 committed by GitHub
parent 863e3e5c22
commit f91b53d5fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 392 additions and 110 deletions

View file

@ -20937,6 +20937,8 @@ apay
Apayao
APB
APC
Apc
apc
APDA
APDU
APE
@ -383889,6 +383891,7 @@ sorus
sorva
sory
SOS
Sos
sos
Sosanna
so-seeming

View file

@ -68,14 +68,15 @@ static constexpr bool _isC0Code(const wchar_t wch) noexcept
}
// Routine Description:
// - Determines if a character is a C1 CSI (Control Sequence Introducer)
// This is a single-character way to start a control sequence, as opposed to "ESC[".
// - Determines if a character is a C1 control characters.
// This is a single-character way to start a control sequence, as opposed to using ESC
// and their 7-bit equivalent.
//
// Not all single-byte codepages support C1 control codes--in some, the range that would
// be used for C1 codes are instead used for additional graphic characters.
//
// However, we do not need to worry about confusion whether a single byte \x9b in a
// single-byte stream represents a C1 CSI or some other glyph, because by the time we
// However, we do not need to worry about confusion whether a single byte, for example,
// \x9b in a single-byte stream represents a C1 CSI or some other glyph, because by the time we
// get here, everything is Unicode. Knowing whether a single-byte \x9b represents a
// single-character C1 CSI or some other glyph is handled by MultiByteToWideChar before
// we get here (if the stream was not already UTF-16). For instance, in CP_ACP, if a
@ -86,24 +87,21 @@ static constexpr bool _isC0Code(const wchar_t wch) noexcept
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isC1Csi(const wchar_t wch) noexcept
static constexpr bool _isC1ControlCharacter(const wchar_t wch) noexcept
{
return wch == L'\x9b';
return (wch >= L'\x80' && wch <= L'\x9F');
}
// Routine Description:
// - Determines if a character is a C1 DCS (Device Control Strings)
// This is a single-character way to start a control sequence, as opposed to "ESC P".
//
// See the comment above _isC1Csi for more information on how this is impacted by codepages.
// - Convert a C1 control characters to their 7-bit equivalent.
//
// Arguments:
// - wch - Character to check.
// - wch - Character to convert.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isC1Dcs(const wchar_t wch) noexcept
// - The 7-bit equivalent of the 8-bit control characters.
static constexpr wchar_t _c1To7Bit(const wchar_t wch) noexcept
{
return wch == L'\x90';
return wch - L'\x40';
}
// Routine Description:
@ -144,7 +142,7 @@ static constexpr bool _isEscape(const wchar_t wch) noexcept
}
// Routine Description:
// - Determines if a character is a delimiter between two parameters in a "control sequence".
// - Determines if a character is a delimiter between two parameters in an escape sequence.
// Arguments:
// - wch - Character to check.
// Return Value:
@ -213,17 +211,6 @@ static constexpr bool _isParameterInvalid(const wchar_t wch) noexcept
return _isCsiInvalid(wch) || _isCsiPrivateMarker(wch);
}
// Routine Description:
// - Determines if a character is a string terminator.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isStringTerminator(const wchar_t wch) noexcept
{
return wch == L'\x9C';
}
// Routine Description:
// - Determines if a character is a string terminator indicator.
// Arguments:
@ -287,17 +274,6 @@ static constexpr bool _isOscDelimiter(const wchar_t wch) noexcept
return wch == L';'; // 0x3B
}
// Routine Description:
// - Determines if a character should be initiate the end of an OSC sequence.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isOscTerminationInitiator(const wchar_t wch) noexcept
{
return wch == AsciiChars::ESC;
}
// Routine Description:
// - Determines if a character should be ignored in a operating system control sequence
// Arguments:
@ -320,7 +296,7 @@ static constexpr bool _isOscInvalid(const wchar_t wch) noexcept
// - True if it is. False if it isn't.
static constexpr bool _isOscTerminator(const wchar_t wch) noexcept
{
return wch == AsciiChars::BEL || _isStringTerminator(wch); // Bell character or C1 terminator
return wch == AsciiChars::BEL; // Bell character
}
// Routine Description:
@ -335,17 +311,6 @@ static constexpr bool _isDcsIndicator(const wchar_t wch) noexcept
return wch == L'P'; // 0x50
}
// Routine Description:
// - Determines if a character should initiate the end of a DCS sequence.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isDcsTerminationInitiator(const wchar_t wch) noexcept
{
return wch == AsciiChars::ESC;
}
// Routine Description:
// - Determines if a character is valid for a DCS pass through sequence.
// Arguments:
@ -358,6 +323,42 @@ static constexpr bool _isDcsPassThroughValid(const wchar_t wch) noexcept
return wch >= AsciiChars::SPC && wch < AsciiChars::DEL;
}
// Routine Description:
// - Determines if a character is "start of string" beginning
// indicator.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isSosIndicator(const wchar_t wch) noexcept
{
return wch == L'X'; // 0x58
}
// Routine Description:
// - Determines if a character is "private message" beginning
// indicator.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isPmIndicator(const wchar_t wch) noexcept
{
return wch == L'^'; // 0x5E
}
// Routine Description:
// - Determines if a character is "application program command" beginning
// indicator.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isApcIndicator(const wchar_t wch) noexcept
{
return wch == L'_'; // 0x5F
}
// Routine Description:
// - Determines if a character indicates an action that should be taken in the ground state -
// These are C0 characters and the C1 [single-character] CSI.
@ -367,7 +368,7 @@ static constexpr bool _isDcsPassThroughValid(const wchar_t wch) noexcept
// - True if it is. False if it isn't.
static constexpr bool _isActionableFromGround(const wchar_t wch) noexcept
{
return (wch <= AsciiChars::US) || _isC1Csi(wch) || _isC1Dcs(wch) || _isDelete(wch);
return (wch <= AsciiChars::US) || _isC1ControlCharacter(wch) || _isDelete(wch);
}
#pragma warning(pop)
@ -926,12 +927,42 @@ void StateMachine::_EnterDcsTermination() noexcept
_trace.TraceStateChange(L"DcsTermination");
}
// Routine Description:
// - Moves the state machine into the SosPmApcString state.
// This state is entered:
// 1. When the Sos character is seen after an Escape entry
// 2. When the Pm character is seen after an Escape entry
// 3. When the Apc character is seen after an Escape entry
// Arguments:
// - <none>
// Return Value:
// - <none>
void StateMachine::_EnterSosPmApcString() noexcept
{
_state = VTStates::SosPmApcString;
_trace.TraceStateChange(L"SosPmApcString");
}
// Routine Description:
// - Moves the state machine into the SosPmApcStringTermination state.
// This state is entered:
// 1. When an ESC is seen in a SOS/PM/APC string. This escape will be followed by a
// '\', as to encode a 0x9C as a 7-bit ASCII char stream.
// Arguments:
// - <none>
// Return Value:
// - <none>
void StateMachine::_EnterSosPmApcTermination() noexcept
{
_state = VTStates::SosPmApcTermination;
_trace.TraceStateChange(L"SosPmApcStringTermination");
}
// Routine Description:
// - Processes a character event into an Action that occurs while in the Ground state.
// Events in this state will:
// 1. Execute C0 control characters
// 2. Handle a C1 Control Sequence Introducer
// 3. Print all other characters
// 2. Print all other characters
// Arguments:
// - wch - Character that triggered the event
// Return Value:
@ -943,14 +974,6 @@ void StateMachine::_EventGround(const wchar_t wch)
{
_ActionExecute(wch);
}
else if (_isC1Csi(wch) && _isInAnsiMode)
{
_EnterCsiEntry();
}
else if (_isC1Dcs(wch) && _isInAnsiMode)
{
_EnterDcsEntry();
}
else
{
_ActionPrint(wch);
@ -1019,6 +1042,10 @@ void StateMachine::_EventEscape(const wchar_t wch)
{
_EnterDcsEntry();
}
else if (_isSosIndicator(wch) || _isPmIndicator(wch) || _isApcIndicator(wch))
{
_EnterSosPmApcString();
}
else
{
_ActionEscDispatch(wch);
@ -1299,7 +1326,7 @@ void StateMachine::_EventOscString(const wchar_t wch)
_ActionOscDispatch(wch);
_EnterGround();
}
else if (_isOscTerminationInitiator(wch))
else if (_isEscape(wch))
{
_EnterOscTermination();
}
@ -1322,13 +1349,6 @@ void StateMachine::_EventOscString(const wchar_t wch)
// - wch - Character that triggered the event
// Return Value:
// - <none>
void StateMachine::_EventOscTermination(const wchar_t wch)
{
_trace.TraceOnEvent(L"OscTermination");
_ActionOscDispatch(wch);
_EnterGround();
}
// Routine Description:
// - Processes a character event into an Action that occurs while in the Ss3Entry state.
@ -1497,24 +1517,16 @@ void StateMachine::_EventDcsEntry(const wchar_t wch)
// Routine Description:
// - Processes a character event into an Action that occurs while in the DcsIgnore state.
// Events in this state will:
// 1. Enter ground on a String terminator
// 2. Ignore everything else.
// In this state the entire DCS string is considered invalid and we will ignore everything.
// The termination state is handled outside when an ESC is seen.
// Arguments:
// - wch - Character that triggered the event
// Return Value:
// - <none>
void StateMachine::_EventDcsIgnore(const wchar_t wch) noexcept
void StateMachine::_EventDcsIgnore() noexcept
{
_trace.TraceOnEvent(L"DcsIgnore");
if (_isStringTerminator(wch))
{
_EnterGround();
}
else
{
_ActionIgnore();
}
_ActionIgnore();
}
// Routine Description:
@ -1603,10 +1615,9 @@ void StateMachine::_EventDcsParam(const wchar_t wch)
// Routine Description:
// - Processes a character event into an Action that occurs while in the DcsPassThrough state.
// Events in this state will:
// 1. Enter ground on a String terminator
// 2. Pass through if character is valid.
// 3. If we see a ESC, enter the DcsTermination state.
// 4. Ignore everything else.
// 1. Pass through if character is valid.
// 2. If we see a ESC, enter the DcsTermination state.
// 3. Ignore everything else.
// Arguments:
// - wch - Character that triggered the event
// Return Value:
@ -1614,16 +1625,11 @@ void StateMachine::_EventDcsParam(const wchar_t wch)
void StateMachine::_EventDcsPassThrough(const wchar_t wch)
{
_trace.TraceOnEvent(L"DcsPassThrough");
if (_isStringTerminator(wch))
{
// TODO:GH#7316: The Dcs sequence has successfully terminated. This is where we'd be dispatching the DCS command.
_EnterGround();
}
if (_isC0Code(wch) || _isDcsPassThroughValid(wch))
{
_ActionDcsPassThrough(wch);
}
else if (_isDcsTerminationInitiator(wch))
else if (_isEscape(wch))
{
_EnterDcsTermination();
}
@ -1634,21 +1640,52 @@ void StateMachine::_EventDcsPassThrough(const wchar_t wch)
}
// Routine Description:
// - Handle the two-character termination of a DCS sequence.
// - Handle SOS/PM/APC string.
// Events in this state will:
// 1. Enter ground on a string terminator
// 2. Pass on everything else as the start of a regular escape sequence
// 1. If we see a ESC, enter the SosPmApcTermination state.
// 2. Ignore everything else.
// Arguments:
// - wch - Character that triggered the event
// Return Value:
// - <none>
void StateMachine::_EventDcsTermination(const wchar_t wch)
void StateMachine::_EventSosPmApcString(const wchar_t wch) noexcept
{
_trace.TraceOnEvent(L"DcsTermination");
_trace.TraceOnEvent(L"SosPmApcString");
if (_isEscape(wch))
{
_EnterSosPmApcTermination();
}
else
{
_ActionIgnore();
}
}
// Routine Description:
// - Handle "Variable Length String" termination.
// Events in this state will:
// 1. Trigger the corresponding action and enter ground if we see a string terminator,
// 2. Otherwise treat this as a normal escape character event.
// Arguments:
// - wch - Character that triggered the event
// Return Value:
// - <none>
void StateMachine::_EventVariableLengthStringTermination(const wchar_t wch)
{
if (_isStringTerminatorIndicator(wch))
{
// TODO: The Dcs sequence has successfully terminated. This is where we'd be dispatching the DCS command.
if (_state == VTStates::OscTermination)
{
_ActionOscDispatch(wch);
}
else if (_state == VTStates::DcsTermination)
{
// TODO:GH#7316: The Dcs sequence has successfully terminated. This is where we'd be dispatching the DCS command.
}
else if (_state == VTStates::SosPmApcTermination)
{
// We don't support any SOS/PM/APC control string yet.
}
_EnterGround();
}
else
@ -1680,12 +1717,40 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
_ActionExecute(wch);
_EnterGround();
}
else if (_isEscape(wch) && _state != VTStates::OscString && _state != VTStates::DcsPassThrough)
// Preprocess C1 control characters and treat them as ESC + their 7-bit equivalent.
else if (_isC1ControlCharacter(wch))
{
// When we are in "Variable Length String" state, a C1 control character
// should effectively acts as an ESC and move us into the corresponding
// termination state.
if (_IsVariableLengthStringState())
{
if (_state == VTStates::OscString)
{
_EnterOscTermination();
}
else if (_state == VTStates::DcsPassThrough)
{
_EnterDcsTermination();
}
else if (_state == VTStates::SosPmApcString)
{
_EnterSosPmApcTermination();
}
_EventVariableLengthStringTermination(_c1To7Bit(wch));
}
// Enter Escape state and pass the converted 7-bit character.
else
{
_EnterEscape();
_EventEscape(_c1To7Bit(wch));
}
}
// Don't go to escape from the "Variable Length String" state - ESC (and C1 String Terminator)
// can be used to terminate variable length control string.
else if (_isEscape(wch) && !_IsVariableLengthStringState())
{
// Don't go to escape from the OSC string state - ESC can be used to
// terminate OSC strings.
//
// Same for DCS pass through state.
_EnterEscape();
}
else
@ -1712,7 +1777,7 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
case VTStates::OscString:
return _EventOscString(wch);
case VTStates::OscTermination:
return _EventOscTermination(wch);
return _EventVariableLengthStringTermination(wch);
case VTStates::Ss3Entry:
return _EventSs3Entry(wch);
case VTStates::Ss3Param:
@ -1722,7 +1787,7 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
case VTStates::DcsEntry:
return _EventDcsEntry(wch);
case VTStates::DcsIgnore:
return _EventDcsIgnore(wch);
return _EventDcsIgnore();
case VTStates::DcsIntermediate:
return _EventDcsIntermediate(wch);
case VTStates::DcsParam:
@ -1730,7 +1795,11 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
case VTStates::DcsPassThrough:
return _EventDcsPassThrough(wch);
case VTStates::DcsTermination:
return _EventDcsTermination(wch);
return _EventVariableLengthStringTermination(wch);
case VTStates::SosPmApcString:
return _EventSosPmApcString(wch);
case VTStates::SosPmApcTermination:
return _EventVariableLengthStringTermination(wch);
default:
return;
}
@ -1952,3 +2021,16 @@ void StateMachine::_AccumulateTo(const wchar_t wch, size_t& value) noexcept
value = MAX_PARAMETER_VALUE;
}
}
// Routine Description:
// - Determines if the engine is in "Variable Length String" state, which is a combination
// of all states that are expecting a string that has a undetermined length.
//
// Arguments:
// - <none>
// Return Value:
// - True if it is. False if it isn't.
const bool StateMachine::_IsVariableLengthStringState() const noexcept
{
return _state == VTStates::OscString || _state == VTStates::DcsPassThrough || _state == VTStates::SosPmApcString;
}

View file

@ -86,6 +86,8 @@ namespace Microsoft::Console::VirtualTerminal
void _EnterDcsIntermediate() noexcept;
void _EnterDcsPassThrough() noexcept;
void _EnterDcsTermination() noexcept;
void _EnterSosPmApcString() noexcept;
void _EnterSosPmApcTermination() noexcept;
void _EventGround(const wchar_t wch);
void _EventEscape(const wchar_t wch);
@ -96,18 +98,19 @@ namespace Microsoft::Console::VirtualTerminal
void _EventCsiParam(const wchar_t wch);
void _EventOscParam(const wchar_t wch) noexcept;
void _EventOscString(const wchar_t wch);
void _EventOscTermination(const wchar_t wch);
void _EventSs3Entry(const wchar_t wch);
void _EventSs3Param(const wchar_t wch);
void _EventVt52Param(const wchar_t wch);
void _EventDcsEntry(const wchar_t wch);
void _EventDcsIgnore(const wchar_t wch) noexcept;
void _EventDcsIgnore() noexcept;
void _EventDcsIntermediate(const wchar_t wch);
void _EventDcsParam(const wchar_t wch);
void _EventDcsPassThrough(const wchar_t wch);
void _EventDcsTermination(const wchar_t wch);
void _EventSosPmApcString(const wchar_t wch) noexcept;
void _EventVariableLengthStringTermination(const wchar_t wch);
void _AccumulateTo(const wchar_t wch, size_t& value) noexcept;
const bool _IsVariableLengthStringState() const noexcept;
enum class VTStates
{
@ -129,7 +132,9 @@ namespace Microsoft::Console::VirtualTerminal
DcsIntermediate,
DcsParam,
DcsPassThrough,
DcsTermination
DcsTermination,
SosPmApcString,
SosPmApcTermination
};
Microsoft::Console::VirtualTerminal::ParserTracing _trace;

View file

@ -56,7 +56,7 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
TEST_METHOD(TestEscapePath)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:uiTest", L"{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17}") // one value for each type of state test below.
TEST_METHOD_PROPERTY(L"Data:uiTest", L"{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19}") // one value for each type of state test below.
END_TEST_METHOD_PROPERTIES()
size_t uiTest;
@ -66,7 +66,7 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
StateMachine mach(std::move(engine));
// The OscString state shouldn't escape out after an ESC.
// Same for DcsPassThrough state.
// Same for DcsPassThrough and SosPmApcString state.
bool shouldEscapeOut = true;
switch (uiTest)
@ -181,6 +181,19 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
mach._state = StateMachine::VTStates::DcsTermination;
break;
}
case 18:
{
Log::Comment(L"Escape from SosPmApcString");
shouldEscapeOut = false;
mach._state = StateMachine::VTStates::SosPmApcString;
break;
}
case 19:
{
Log::Comment(L"Escape from SosPmApcTermination");
mach._state = StateMachine::VTStates::SosPmApcTermination;
break;
}
}
mach.ProcessCharacter(AsciiChars::ESC);
@ -405,6 +418,19 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestC1Osc)
{
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(L'\x9d');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(AsciiChars::BEL);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestOscStringSimple)
{
auto dispatch = std::make_unique<DummyDispatch>();
@ -596,6 +622,35 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestOscStringInvalidTermination)
{
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L']');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L'1');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L's');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscTermination);
mach.ProcessCharacter(L'['); // This is not a string terminator.
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestDcsEntry)
{
auto dispatch = std::make_unique<DummyDispatch>();
@ -752,6 +807,143 @@ class Microsoft::Console::VirtualTerminal::OutputEngineTest final
mach.ProcessCharacter(L'\\');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestDcsInvalidTermination)
{
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'P');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
mach.ProcessCharacter(L'q');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsTermination);
mach.ProcessCharacter(L'['); // This is not a string terminator.
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiEntry);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::CsiParam);
mach.ProcessCharacter(L'm');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestSosPmApcString)
{
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'X');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'1');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'2');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcTermination);
mach.ProcessCharacter(L'\\');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'^');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'3');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'4');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcTermination);
mach.ProcessCharacter(L'\\');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'_');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'5');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'6');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcTermination);
mach.ProcessCharacter(L'\\');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
TEST_METHOD(TestC1StringTerminator)
{
auto dispatch = std::make_unique<DummyDispatch>();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
// C1 ST should terminate OSC string.
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L']');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L'1');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscParam);
mach.ProcessCharacter(L';');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L's');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::OscString);
mach.ProcessCharacter(L'\x9c');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
// C1 ST should terminate DCS passthrough string.
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'P');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsEntry);
mach.ProcessCharacter(L'q');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough);
mach.ProcessCharacter(L'#');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::DcsPassThrough);
mach.ProcessCharacter(L'1');
mach.ProcessCharacter(L'\x9c');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
// C1 ST should terminate SOS/PM/APC string.
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'X');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'1');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'\x9c');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'^');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'2');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'\x9c');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
mach.ProcessCharacter(AsciiChars::ESC);
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Escape);
mach.ProcessCharacter(L'_');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'3');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::SosPmApcString);
mach.ProcessCharacter(L'\x9c');
VERIFY_ARE_EQUAL(mach._state, StateMachine::VTStates::Ground);
}
};
class StatefulDispatch final : public TermDispatch