Skip to content

Commit

Permalink
Move the VT parser's parse state into instanced storage (#3110)
Browse files Browse the repository at this point in the history
The VT parser used to be keeping a boolean used to determine whether it
was in bulk or single-character parse mode in a function-level static.
That turned out to not be great.

Fixes #3108; fixes #3073.
  • Loading branch information
DHowett authored Oct 9, 2019
1 parent cd40faa commit dd2fbef
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 12 deletions.
19 changes: 8 additions & 11 deletions src/terminal/parser/stateMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ StateMachine::StateMachine(IStateMachineEngine* const pEngine) :
// rgusParams Initialized below
_sOscNextChar(0),
_sOscParam(0),
_currRunLength(0)
_currRunLength(0),
_fProcessingIndividually(false)
{
ZeroMemory(_pwchOscStringBuffer, sizeof(_pwchOscStringBuffer));
ZeroMemory(_rgusParams, sizeof(_rgusParams));
Expand Down Expand Up @@ -1346,20 +1347,16 @@ void StateMachine::ProcessString(const wchar_t* const rgwch, const size_t cch)
_pwchSequenceStart = rgwch;
_currRunLength = 0;

// This should be static, because if one string starts a sequence, and the next finishes it,
// we want the partial sequence state to persist.
static bool s_fProcessIndividually = false;

for (size_t cchCharsRemaining = cch; cchCharsRemaining > 0; cchCharsRemaining--)
{
if (s_fProcessIndividually)
if (_fProcessingIndividually)
{
// If we're processing characters individually, send it to the state machine.
ProcessCharacter(*_pwchCurr);
_pwchCurr++;
if (_state == VTStates::Ground) // Then check if we're back at ground. If we are, the next character (pwchCurr)
{ // is the start of the next run of characters that might be printable.
s_fProcessIndividually = false;
_fProcessingIndividually = false;
_pwchSequenceStart = _pwchCurr;
_currRunLength = 0;
}
Expand All @@ -1371,13 +1368,13 @@ void StateMachine::ProcessString(const wchar_t* const rgwch, const size_t cch)
FAIL_FAST_IF(!(_pwchSequenceStart + _currRunLength <= rgwch + cch));
_pEngine->ActionPrintString(_pwchSequenceStart, _currRunLength); // ... print all the chars leading up to it as part of the run...
_trace.DispatchPrintRunTrace(_pwchSequenceStart, _currRunLength);
s_fProcessIndividually = true; // begin processing future characters individually...
_fProcessingIndividually = true; // begin processing future characters individually...
_currRunLength = 0;
_pwchSequenceStart = _pwchCurr;
ProcessCharacter(*_pwchCurr); // ... Then process the character individually.
if (_state == VTStates::Ground) // If the character took us right back to ground, start another run after it.
{
s_fProcessIndividually = false;
_fProcessingIndividually = false;
_pwchSequenceStart = _pwchCurr + 1;
_currRunLength = 0;
}
Expand All @@ -1391,13 +1388,13 @@ void StateMachine::ProcessString(const wchar_t* const rgwch, const size_t cch)
}

// If we're at the end of the string and have remaining un-printed characters,
if (!s_fProcessIndividually && _currRunLength > 0)
if (!_fProcessingIndividually && _currRunLength > 0)
{
// print the rest of the characters in the string
_pEngine->ActionPrintString(_pwchSequenceStart, _currRunLength);
_trace.DispatchPrintRunTrace(_pwchSequenceStart, _currRunLength);
}
else if (s_fProcessIndividually)
else if (_fProcessingIndividually)
{
// One of the "weird things" in VT input is the case of something like
// <kbd>alt+[</kbd>. In VT, that's encoded as `\x1b[`. However, that's
Expand Down
4 changes: 4 additions & 0 deletions src/terminal/parser/stateMachine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,9 @@ namespace Microsoft::Console::VirtualTerminal
const wchar_t* _pwchCurr;
const wchar_t* _pwchSequenceStart;
size_t _currRunLength;

// This is tracked per state machine instance so that separate calls to Process*
// can start and finish a sequence.
bool _fProcessingIndividually;
};
}
1 change: 1 addition & 0 deletions src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<ItemGroup>
<ClCompile Include="InputEngineTest.cpp" />
<ClCompile Include="OutputEngineTest.cpp" />
<ClCompile Include="StateMachineTest.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stateMachineTest.cpp">
<ClCompile Include="InputEngineTest.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="OutputEngineTest.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="StateMachineTest.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\precomp.cpp">
Expand Down
113 changes: 113 additions & 0 deletions src/terminal/parser/ut_parser/StateMachineTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "precomp.h"
#include "WexTestClass.h"
#include "../../inc/consoletaeftemplates.hpp"

#include "stateMachine.hpp"

using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;

namespace Microsoft
{
namespace Console
{
namespace VirtualTerminal
{
class StateMachineTest;
class TestStateMachineEngine;
};
};
};

using namespace Microsoft::Console::VirtualTerminal;

class Microsoft::Console::VirtualTerminal::TestStateMachineEngine : public IStateMachineEngine
{
public:
bool ActionExecute(const wchar_t /* wch */) override { return true; };
bool ActionExecuteFromEscape(const wchar_t /* wch */) override { return true; };
bool ActionPrint(const wchar_t /* wch */) override { return true; };
bool ActionPrintString(const wchar_t* const /* rgwch */,
size_t const /* cch */) override { return true; };

bool ActionPassThroughString(const wchar_t* const /* rgwch */,
size_t const /* cch */) override { return true; };

bool ActionEscDispatch(const wchar_t /* wch */,
const unsigned short /* cIntermediate */,
const wchar_t /* wchIntermediate */) override { return true; };

bool ActionClear() override { return true; };

bool ActionIgnore() override { return true; };

bool ActionOscDispatch(const wchar_t /* wch */,
const unsigned short /* sOscParam */,
wchar_t* const /* pwchOscStringBuffer */,
const unsigned short /* cchOscString */) override { return true; };

bool ActionSs3Dispatch(const wchar_t /* wch */,
const unsigned short* const /* rgusParams */,
const unsigned short /* cParams */) override { return true; };

bool FlushAtEndOfString() const override { return false; };
bool DispatchControlCharsFromEscape() const override { return false; };
bool DispatchIntermediatesFromEscape() const override { return false; };

// ActionCsiDispatch is the only method that's actually implemented.
bool ActionCsiDispatch(const wchar_t /* wch */,
const unsigned short /* cIntermediate */,
const wchar_t /* wchIntermediate */,
_In_reads_(cParams) const unsigned short* const rgusParams,
const unsigned short cParams) override
{
csiParams.emplace(rgusParams, rgusParams + cParams);
return true;
}

// This will only be populated if ActionCsiDispatch is called.
std::optional<std::vector<unsigned short>> csiParams;
};

class Microsoft::Console::VirtualTerminal::StateMachineTest
{
TEST_CLASS(StateMachineTest);

TEST_CLASS_SETUP(ClassSetup)
{
return true;
}

TEST_CLASS_CLEANUP(ClassCleanup)
{
return true;
}

TEST_METHOD(TwoStateMachinesDoNotInterfereWithEachother);
};

void StateMachineTest::TwoStateMachinesDoNotInterfereWithEachother()
{
auto firstEnginePtr{ std::make_unique<TestStateMachineEngine>() };
// this dance is required because StateMachine presumes to take ownership of its engine.
const auto& firstEngine{ *firstEnginePtr.get() };
StateMachine firstStateMachine{ firstEnginePtr.release() };

auto secondEnginePtr{ std::make_unique<TestStateMachineEngine>() };
const auto& secondEngine{ *secondEnginePtr.get() };
StateMachine secondStateMachine{ secondEnginePtr.release() };

firstStateMachine.ProcessString(L"\x1b[12"); // partial sequence
secondStateMachine.ProcessString(L"\x1b[3C"); // full sequence on second parser
firstStateMachine.ProcessString(L";34m"); // completion to previous partial sequence on first parser

std::vector<unsigned short> expectedFirstCsi{ 12u, 34u };
std::vector<unsigned short> expectedSecondCsi{ 3u };

VERIFY_ARE_EQUAL(expectedFirstCsi, firstEngine.csiParams);
VERIFY_ARE_EQUAL(expectedSecondCsi, secondEngine.csiParams);
}
1 change: 1 addition & 0 deletions src/terminal/parser/ut_parser/sources
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ SOURCES = \
$(SOURCES) \
OutputEngineTest.cpp \
InputEngineTest.cpp \
StateMachineTest.cpp \

# The InputEngineTest requires VTRedirMapVirtualKeyW, which means we need the
# ServiceLocator, which means we need the entire host and all it's dependencies,
Expand Down

0 comments on commit dd2fbef

Please sign in to comment.