Skip to content

Instantly share code, notes, and snippets.

@Twisol
Created May 5, 2011 03:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Twisol/956475 to your computer and use it in GitHub Desktop.
Save Twisol/956475 to your computer and use it in GitHub Desktop.
ANSI parser written in C++
FG: 7 BG: 0
Italic: 0
Underline: 0
Strike: 0
Text: this is
FG: 9 BG: 0
Italic: 0
Underline: 0
Strike: 0
Text: text
#include "ansi_parser.h"
using std::string;
using std::vector;
ANSIParser::ANSIParser()
: state(STATE_PLAIN), current_parameter(-1)
{}
ANSIParser::~ANSIParser()
{}
void ANSIParser::Parse(const std::string& data)
{
size_t len = data.length();
size_t i = 0;
size_t left = (this->state == STATE_PLAIN) ? i : len;
size_t right = len;
while (i < len) {
char ch = data.at(i);
switch (this->state) {
case STATE_PLAIN:
{
size_t nextESC = data.find_first_of('\x1B', i);
if (nextESC == std::string::npos) {
i = len;
} else {
right = i = nextESC;
this->state = STATE_BEGIN;
}
}
break;
case STATE_BEGIN:
if (ch == '[') {
current_parameter = 0;
this->state = STATE_PARAMETERS;
} else {
this->state = STATE_PLAIN;
}
break;
case STATE_PARAMETERS:
if ('0' <= ch && ch <= '9') {
current_parameter = (current_parameter * 10) + (ch - '0');
} else {
this->parameters.push_back(current_parameter);
current_parameter = -1;
this->state = STATE_PARAMETER_END;
continue; // skip the i += 1
}
break;
case STATE_PARAMETER_END:
if (ch == ';') {
current_parameter = 0;
this->state = STATE_PARAMETERS;
} else {
this->state = STATE_INTERMEDIATE;
continue; // skip the i += 1
}
break;
case STATE_INTERMEDIATE:
if (' ' <= ch && ch <= '/') {
this->intermediate.append(1, ch);
} else {
this->state = STATE_END;
continue; // skip the i += 1
}
break;
case STATE_END:
if ('@' <= ch && ch <= '~') {
if (left < right) {
this->OnText(data.substr(left, right-left));
}
this->OnCommand(ch, this->parameters, this->intermediate);
left = i + 1;
}
this->state = STATE_PLAIN;
this->Reset();
right = len;
break;
}
i += 1;
}
if (left < right) {
this->OnText(data.substr(left, right-left));
}
}
void ANSIParser::Reset()
{
this->current_parameter = -1;
this->parameters.clear();
this->intermediate.clear();
}
#include <string>
#include <vector>
class ANSIParser
{
public:
ANSIParser();
virtual ~ANSIParser();
void Parse(const std::string& data);
void Reset();
protected:
virtual void OnCommand(char command, std::vector<int> parameters, std::string intermediate) = 0;
virtual void OnText(std::string text) = 0;
private:
enum ANSI_STATES {
STATE_PLAIN,
STATE_BEGIN,
STATE_PARAMETERS,
STATE_PARAMETER_END,
STATE_INTERMEDIATE,
STATE_END,
};
ANSI_STATES state;
std::vector<int> parameters;
int current_parameter;
std::string intermediate;
};
#include <bitset>
#include <iostream>
#include "ansi_parser.h"
using std::vector;
using std::string;
using std::bitset;
using std::cout;
using std::endl;
class MyParser : public ANSIParser
{
public:
static const int DEFAULT_FG = 7;
static const int DEFAULT_BG = 0;
MyParser();
virtual ~MyParser();
void ResetAttributes();
protected:
virtual void OnCommand(char command, vector<int> params, string intermediate);
virtual void OnText(string text);
private:
enum STYLE_FLAGS {
STYLE_BOLD,
STYLE_ITALIC,
STYLE_UNDERLINE,
STYLE_STRIKE,
STYLE_INVERSE,
STYLE_CONCEAL,
STYLE_BLINK,
STYLE_XTERM,
STYLE_COUNT_, // must be last
};
typedef bitset<STYLE_COUNT_> styleset;
char fg;
char bg;
styleset flags;
};
MyParser::MyParser()
: fg(DEFAULT_FG), bg(DEFAULT_BG)
{}
MyParser::~MyParser()
{}
void MyParser::ResetAttributes()
{
this->fg = DEFAULT_FG;
this->bg = DEFAULT_BG;
this->flags = 0;
}
void MyParser::OnCommand(char command, vector<int> params, string intermediate)
{
// not handling non-m commands
if (command != 'm') return;
size_t length = params.size();
styleset& flags = this->flags;
// default case: \e[m
if (length == 0) {
this->ResetAttributes();
return;
}
// xterm-256: \e[38;5;Xm for FG or \e[48;5;Xm for BG,
// where X is a number in the interval [0, 255].
if (length == 3 &&
(params[0] == 38 || params[0] == 48) &&
params[1] == 5) {
int num = params[2];
if (0 <= num && num <= 255) {
if (params[0] == 38) {
this->fg = num;
flags[STYLE_XTERM] = true;
} else {
this->bg = num;
}
return;
}
}
// Standard commands (well, mostly)
int param;
for (size_t i = 0; i < length; ++i) {
param = params[i];
switch (param) {
case 0: this->ResetAttributes(); break;
case 1: flags[STYLE_BOLD] = true; break;
case 3: flags[STYLE_ITALIC] = true; break;
case 4: flags[STYLE_UNDERLINE] = true; break;
case 7: flags[STYLE_INVERSE] = true; break;
case 8: flags[STYLE_CONCEAL] = true; break;
case 9: flags[STYLE_STRIKE] = true; break;
case 22: flags[STYLE_BOLD] = false; break;
case 23: flags[STYLE_ITALIC] = false; break;
case 24: flags[STYLE_UNDERLINE] = false; break;
case 27: flags[STYLE_INVERSE] = false; break;
case 28: flags[STYLE_CONCEAL] = false; break;
case 29: flags[STYLE_STRIKE] = false; break;
case 30: case 31: case 32: case 33:
case 34: case 35: case 36: case 37:
this->fg = param - 30;
flags[STYLE_XTERM] = false;
break;
case 39:
this->fg = DEFAULT_FG;
flags[STYLE_XTERM] = false;
break;
case 40: case 41: case 42: case 43:
case 44: case 45: case 46: case 47:
this->bg = param - 40;
break;
case 49:
this->bg = DEFAULT_BG;
break;
case 90: case 91: case 92: case 93:
case 94: case 95: case 96: case 97:
this->fg = param - 90 + 8;
flags[STYLE_XTERM] = true;
break;
case 100: case 101: case 102: case 103:
case 104: case 105: case 106: case 107:
this->bg = param - 100 + 8;
flags[STYLE_XTERM] = true;
break;
}
}
}
void MyParser::OnText(string text)
{
int fg = this->fg;
int bg = this->bg;
styleset& flags = this->flags;
if (flags[STYLE_BOLD] && !flags[STYLE_XTERM]) {
fg += 8;
}
if (flags[STYLE_INVERSE]) {
bg = fg;
fg = this->bg;
}
if (flags[STYLE_CONCEAL]) {
fg = bg;
}
cout << "FG: " << fg << "\tBG: " << bg << endl;
cout << "Italic: " << flags[STYLE_ITALIC] << endl;
cout << "Underline: " << flags[STYLE_UNDERLINE] << endl;
cout << "Strike: " << flags[STYLE_STRIKE] << endl;
cout << "Text: " << text << endl << endl;
}
int main()
{
MyParser ansi;
ansi.Parse("this is \x1B[38;5");
ansi.Parse(";9mtext\x1B[0m");
return 0;
}
@Twisol
Copy link
Author

Twisol commented Jan 28, 2016

Since I've been contacted about the licensing for this Gist, I'll post it here: this code may be licensed under the MIT license, which can be found in full at http://opensource.org/licenses/MIT. In short, you can do basically whatever you like with this code.

FWIW, this code is at least 5 years old, and there are a lot of things I'd have done differently if I'd written it today. If you find it useful, though, have at it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment