InputCommon: Add a new ExpressionParser to replace the old hack language

This contains a new, hand-written expression parser to replace the old
hack language based on string munging. The new approach is a simple
AST-based evaluation approach, instead of the "list of operations"
infix-based hack that there was before.

The new language for configuration has support for parentheses, and
counts "!" as a unary operator instead of the binary "NOT OR" operator
it was before. A simple example:

  (X & Y) | !B

Explicit device references, and complex device names ("Right Y+") are
handled with backticks and colons:

  (`SDL/0/6 axis joystick:Right X+` & `DInput/0/Keyboard Mouse:A`)

The basic editor UI that inserts tokens has not been updated to reflect
the new language.
This commit is contained in:
Jasper St. Pierre 2013-06-13 23:09:55 -04:00
parent 877106b027
commit 6246f6e815
7 changed files with 634 additions and 130 deletions

View File

@ -3,7 +3,8 @@ set(SRCS Src/ControllerEmu.cpp
Src/UDPWiimote.cpp Src/UDPWiimote.cpp
Src/UDPWrapper.cpp Src/UDPWrapper.cpp
Src/ControllerInterface/ControllerInterface.cpp Src/ControllerInterface/ControllerInterface.cpp
Src/ControllerInterface/Device.cpp) Src/ControllerInterface/Device.cpp
Src/ControllerInterface/ExpressionParser.cpp)
if(WIN32) if(WIN32)
set(SRCS ${SRCS} set(SRCS ${SRCS}

View File

@ -167,6 +167,7 @@
<ClCompile Include="Src\ControllerEmu.cpp" /> <ClCompile Include="Src\ControllerEmu.cpp" />
<ClCompile Include="Src\ControllerInterface\ControllerInterface.cpp" /> <ClCompile Include="Src\ControllerInterface\ControllerInterface.cpp" />
<ClCompile Include="Src\ControllerInterface\Device.cpp" /> <ClCompile Include="Src\ControllerInterface\Device.cpp" />
<ClCompile Include="Src\ControllerInterface\ExpressionParser.cpp" />
<ClCompile Include="Src\ControllerInterface\DInput\DInput.cpp" /> <ClCompile Include="Src\ControllerInterface\DInput\DInput.cpp" />
<ClCompile Include="Src\ControllerInterface\DInput\DInputJoystick.cpp" /> <ClCompile Include="Src\ControllerInterface\DInput\DInputJoystick.cpp" />
<ClCompile Include="Src\ControllerInterface\DInput\DInputKeyboardMouse.cpp" /> <ClCompile Include="Src\ControllerInterface\DInput\DInputKeyboardMouse.cpp" />
@ -180,6 +181,7 @@
<ClInclude Include="Src\ControllerEmu.h" /> <ClInclude Include="Src\ControllerEmu.h" />
<ClInclude Include="Src\ControllerInterface\ControllerInterface.h" /> <ClInclude Include="Src\ControllerInterface\ControllerInterface.h" />
<ClInclude Include="Src\ControllerInterface\Device.h" /> <ClInclude Include="Src\ControllerInterface\Device.h" />
<ClInclude Include="Src\ControllerInterface\ExpressionParser.h" />
<ClInclude Include="Src\ControllerInterface\DInput\DInput.h" /> <ClInclude Include="Src\ControllerInterface\DInput\DInput.h" />
<ClInclude Include="Src\ControllerInterface\DInput\DInputJoystick.h" /> <ClInclude Include="Src\ControllerInterface\DInput\DInputJoystick.h" />
<ClInclude Include="Src\ControllerInterface\DInput\DInputKeyboardMouse.h" /> <ClInclude Include="Src\ControllerInterface\DInput\DInputKeyboardMouse.h" />

View File

@ -23,6 +23,12 @@
<ClCompile Include="Src\ControllerInterface\DInput\DInput.cpp"> <ClCompile Include="Src\ControllerInterface\DInput\DInput.cpp">
<Filter>ControllerInterface\DInput</Filter> <Filter>ControllerInterface\DInput</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Src\ControllerInterface\ExpressionParser.cpp">
<Filter>ControllerInterface</Filter>
</ClCompile>
<ClCompile Include="Src\ControllerInterface\Device.cpp">
<Filter>ControllerInterface</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Src\ControllerEmu.h" /> <ClInclude Include="Src\ControllerEmu.h" />
@ -42,6 +48,9 @@
<ClInclude Include="Src\ControllerInterface\Device.h"> <ClInclude Include="Src\ControllerInterface\Device.h">
<Filter>ControllerInterface\Device</Filter> <Filter>ControllerInterface\Device</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Src\ControllerInterface\ExpressionParser.h">
<Filter>ControllerInterface</Filter>
</ClInclude>
<ClInclude Include="Src\ControllerInterface\DInput\NamedKeys.h"> <ClInclude Include="Src\ControllerInterface\DInput\NamedKeys.h">
<Filter>ControllerInterface\DInput</Filter> <Filter>ControllerInterface\DInput</Filter>
</ClInclude> </ClInclude>

View File

@ -21,6 +21,8 @@
#include "Thread.h" #include "Thread.h"
using namespace ciface::ExpressionParser;
namespace namespace
{ {
const float INPUT_DETECT_THRESHOLD = 0.55f; const float INPUT_DETECT_THRESHOLD = 0.55f;
@ -187,48 +189,10 @@ bool ControllerInterface::UpdateOutput(const bool force)
// //
ControlState ControllerInterface::InputReference::State( const ControlState ignore ) ControlState ControllerInterface::InputReference::State( const ControlState ignore )
{ {
//if (NULL == device) if (parsed_expression)
//return 0; return parsed_expression->GetValue();
else
ControlState state = 0; return 0.0f;
std::vector<DeviceControl>::const_iterator
ci = m_controls.begin(),
ce = m_controls.end();
// bit of hax for "NOT" to work at start of expression
if (ci != ce)
{
if (ci->mode == 2)
state = 1;
}
for (; ci!=ce; ++ci)
{
const ControlState istate = ci->control->ToInput()->GetState();
switch (ci->mode)
{
// OR
case 0 :
state = std::max(state, istate);
break;
// AND
case 1 :
state = std::min(state, istate);
break;
// NOT
case 2 :
state = std::max(std::min(state, 1.0f - istate), 0.0f);
break;
// ADD
case 3 :
state += istate;
break;
}
}
return std::min(1.0f, state * range);
} }
// //
@ -240,17 +204,9 @@ ControlState ControllerInterface::InputReference::State( const ControlState igno
// //
ControlState ControllerInterface::OutputReference::State(const ControlState state) ControlState ControllerInterface::OutputReference::State(const ControlState state)
{ {
const ControlState tmp_state = std::min(1.0f, state * range); if (parsed_expression)
parsed_expression->SetValue(state);
// output ref just ignores the modes ( |&!... ) return 0.0f;
std::vector<DeviceControl>::iterator
ci = m_controls.begin(),
ce = m_controls.end();
for (; ci != ce; ++ci)
ci->control->ToOutput()->SetState(tmp_state);
return state; // just return the output, watever
} }
// //
@ -262,65 +218,13 @@ ControlState ControllerInterface::OutputReference::State(const ControlState stat
void ControllerInterface::UpdateReference(ControllerInterface::ControlReference* ref void ControllerInterface::UpdateReference(ControllerInterface::ControlReference* ref
, const DeviceQualifier& default_device) const , const DeviceQualifier& default_device) const
{ {
ref->m_controls.clear(); delete ref->parsed_expression;
ref->parsed_expression = NULL;
// adding | to parse the last item, silly ControlFinder finder(*this, default_device, ref->is_input);
std::istringstream ss(ref->expression + '|'); ExpressionParseStatus status;
status = ParseExpression(ref->expression, finder, &ref->parsed_expression);
const std::string mode_chars("|&!^"); // XXX: do something with status?
ControlReference::DeviceControl devc;
std::string dev_str;
std::string ctrl_str;
char c = 0;
while (ss.read(&c, 1))
{
const size_t f = mode_chars.find(c);
if (mode_chars.npos != f)
{
// add ctrl
if (ctrl_str.size())
{
DeviceQualifier devq;
// using default device or alterate device inside `backticks`
if (dev_str.empty())
devq = default_device;
else
devq.FromString(dev_str);
// find device
Device* const def_device = FindDevice(devq);
if (def_device)
{
if (ref->is_input)
devc.control = FindInput(ctrl_str, def_device);
else
devc.control = FindOutput(ctrl_str, def_device);
if (devc.control)
ref->m_controls.push_back(devc);
}
}
// reset stuff for next ctrl
devc.mode = (int)f;
ctrl_str.clear();
}
else if ('`' == c)
{
// different device
if (std::getline(ss, dev_str, '`').eof())
break; // no terminating '`' character
}
else
{
ctrl_str += c;
}
}
} }
// //
@ -388,7 +292,7 @@ Device::Control* ControllerInterface::OutputReference::Detect(const unsigned int
// ignore device // ignore device
// don't hang if we don't even have any controls mapped // don't hang if we don't even have any controls mapped
if (m_controls.size()) if (BoundCount() > 0)
{ {
State(1); State(1);
unsigned int slept = 0; unsigned int slept = 0;

View File

@ -9,6 +9,7 @@
#include "Common.h" #include "Common.h"
#include "Thread.h" #include "Thread.h"
#include "ExpressionParser.h"
#include "Device.h" #include "Device.h"
// enable disable sources // enable disable sources
@ -53,30 +54,28 @@ public:
class ControlReference class ControlReference
{ {
friend class ControllerInterface; friend class ControllerInterface;
public: public:
virtual ~ControlReference() {}
virtual ControlState State(const ControlState state = 0) = 0; virtual ControlState State(const ControlState state = 0) = 0;
virtual Device::Control* Detect(const unsigned int ms, Device* const device) = 0; virtual Device::Control* Detect(const unsigned int ms, Device* const device) = 0;
size_t BoundCount() const { return m_controls.size(); }
ControlState range; ControlState range;
std::string expression; std::string expression;
const bool is_input; const bool is_input;
virtual ~ControlReference() {
delete parsed_expression;
}
int BoundCount() {
if (parsed_expression)
return parsed_expression->num_controls;
else
return 0;
}
protected: protected:
ControlReference(const bool _is_input) : range(1), is_input(_is_input) {} ControlReference(const bool _is_input) : range(1), is_input(_is_input), parsed_expression(NULL) {}
ciface::ExpressionParser::Expression *parsed_expression;
struct DeviceControl
{
DeviceControl() : control(NULL), mode(0) {}
Device::Control* control;
int mode;
};
std::vector<DeviceControl> m_controls;
}; };
// //

View File

@ -0,0 +1,516 @@
#include "ExpressionParser.h"
#include <cassert>
#include <iostream>
#include <string>
#include <map>
#include <vector>
using namespace ciface::Core;
namespace ciface
{
namespace ExpressionParser
{
enum TokenType
{
TOK_DISCARD,
TOK_INVALID,
TOK_EOF,
TOK_LPAREN,
TOK_RPAREN,
TOK_AND,
TOK_OR,
TOK_NOT,
TOK_CONTROL,
};
inline std::string OpName(TokenType op)
{
switch (op)
{
case TOK_AND:
return "And";
case TOK_OR:
return "Or";
case TOK_NOT:
return "Not";
default:
assert(false);
return "";
}
}
class Token
{
public:
TokenType type;
ControlQualifier qualifier;
Token(TokenType type_) : type(type_) {}
Token(TokenType type_, ControlQualifier qualifier_) : type(type_), qualifier(qualifier_) {}
operator std::string()
{
switch (type)
{
case TOK_INVALID:
return "Invalid";
case TOK_DISCARD:
return "Discard";
case TOK_EOF:
return "EOF";
case TOK_LPAREN:
return "(";
case TOK_RPAREN:
return ")";
case TOK_AND:
return "&";
case TOK_OR:
return "|";
case TOK_NOT:
return "!";
case TOK_CONTROL:
return "Device(" + (std::string)qualifier + ")";
}
}
};
class Lexer {
public:
std::string expr;
std::string::iterator it;
Lexer(std::string expr_) : expr(expr_)
{
it = expr.begin();
}
bool FetchBacktickString(std::string &value, char otherDelim = 0)
{
while (it != expr.end())
{
char c = *it;
if (c == '`')
return false;
if (c > 0 && c == otherDelim)
return true;
value += c;
it++;
}
return false;
}
Token GetControlQualifier()
{
ControlQualifier qualifier;
std::string value;
if (FetchBacktickString(value, ':'))
{
// Found colon, this is the device name
qualifier.has_device = true;
qualifier.device_qualifier.FromString(value);
FetchBacktickString(value);
}
qualifier.control_name = value;
return Token(TOK_CONTROL, qualifier);
}
Token NextToken()
{
if (it == expr.end())
return Token(TOK_EOF);
char c = *it++;
switch (c)
{
case ' ':
case '\t':
case '\n':
case '\r':
return Token(TOK_DISCARD);
case '(':
return Token(TOK_LPAREN);
case ')':
return Token(TOK_RPAREN);
case '&':
return Token(TOK_AND);
case '|':
return Token(TOK_OR);
case '!':
return Token(TOK_NOT);
case '`':
return GetControlQualifier();
default:
return Token(TOK_INVALID);
}
}
ExpressionParseStatus Tokenize(std::vector<Token> &tokens)
{
while (true)
{
Token tok = NextToken();
if (tok.type == TOK_DISCARD)
continue;
if (tok.type == TOK_INVALID)
{
tokens.empty();
return EXPRESSION_PARSE_SYNTAX_ERROR;
}
tokens.push_back(tok);
if (tok.type == TOK_EOF)
break;
}
return EXPRESSION_PARSE_SUCCESS;
}
};
class ExpressionNode
{
public:
virtual ~ExpressionNode() {}
virtual ControlState GetValue() { return 0; }
virtual void SetValue(ControlState state) {}
virtual operator std::string() { return ""; }
};
class ControlExpression : public ExpressionNode
{
public:
ControlQualifier qualifier;
Device::Control *control;
ControlExpression(ControlQualifier qualifier_, Device::Control *control_) : qualifier(qualifier_), control(control_) {}
virtual ControlState GetValue()
{
return control->ToInput()->GetState();
}
virtual void SetValue(ControlState value)
{
control->ToOutput()->SetState(value);
}
virtual operator std::string()
{
return "`" + (std::string)qualifier + "`";
}
};
class BinaryExpression : public ExpressionNode
{
public:
TokenType op;
ExpressionNode *lhs;
ExpressionNode *rhs;
BinaryExpression(TokenType op_, ExpressionNode *lhs_, ExpressionNode *rhs_) : op(op_), lhs(lhs_), rhs(rhs_) {}
virtual ~BinaryExpression()
{
delete lhs;
delete rhs;
}
virtual ControlState GetValue()
{
ControlState lhsValue = lhs->GetValue();
ControlState rhsValue = rhs->GetValue();
switch (op)
{
case TOK_AND:
return std::min(lhsValue, rhsValue);
case TOK_OR:
return std::max(lhsValue, rhsValue);
default:
assert(false);
return 0;
}
}
virtual void SetValue(ControlState value)
{
// Don't do anything special with the op we have.
// Treat "A & B" the same as "A | B".
lhs->SetValue(value);
rhs->SetValue(value);
}
virtual operator std::string()
{
return OpName(op) + "(" + (std::string)(*lhs) + ", " + (std::string)(*rhs) + ")";
}
};
class UnaryExpression : public ExpressionNode
{
public:
TokenType op;
ExpressionNode *inner;
UnaryExpression(TokenType op_, ExpressionNode *inner_) : op(op_), inner(inner_) {}
virtual ~UnaryExpression()
{
delete inner;
}
virtual ControlState GetValue()
{
ControlState value = inner->GetValue();
switch (op)
{
case TOK_NOT:
return 1.0f - value;
default:
assert(false);
return 0;
}
}
virtual void SetValue(ControlState value)
{
switch (op)
{
case TOK_NOT:
inner->SetValue(1.0f - value);
default:
assert(false);
}
}
virtual operator std::string()
{
return OpName(op) + "(" + (std::string)(*inner) + ")";
}
};
Device *ControlFinder::FindDevice(ControlQualifier qualifier)
{
if (qualifier.has_device)
return container.FindDevice(qualifier.device_qualifier);
else
return container.FindDevice(default_device);
}
Device::Control *ControlFinder::FindControl(ControlQualifier qualifier)
{
Device *device = FindDevice(qualifier);
if (is_input)
return device->FindInput(qualifier.control_name);
else
return device->FindOutput(qualifier.control_name);
}
class Parser
{
public:
Parser(std::vector<Token> tokens_, ControlFinder &finder_) : tokens(tokens_), finder(finder_)
{
m_it = tokens.begin();
}
ExpressionParseStatus Parse(Expression **expr_out)
{
Expression *expr;
ExpressionNode *expr_node;
ExpressionParseStatus status = Toplevel(&expr_node);
if (status != EXPRESSION_PARSE_SUCCESS)
return status;
expr = new Expression();
expr->expr = expr_node;
expr->num_controls = CountNumControls();
*expr_out = expr;
return EXPRESSION_PARSE_SUCCESS;
}
private:
std::vector<Token> tokens;
std::vector<Token>::iterator m_it;
ControlFinder &finder;
Token Chew()
{
return *m_it++;
}
Token Peek()
{
return *m_it;
}
bool Expects(TokenType type)
{
Token tok = Chew();
return tok.type == type;
}
ExpressionParseStatus Atom(ExpressionNode **expr_out)
{
Token tok = Chew();
switch (tok.type)
{
case TOK_CONTROL:
{
Device::Control *control = finder.FindControl(tok.qualifier);
if (control == NULL)
return EXPRESSION_PARSE_NO_DEVICE;
*expr_out = new ControlExpression(tok.qualifier, control);
return EXPRESSION_PARSE_SUCCESS;
}
case TOK_LPAREN:
return Paren(expr_out);
default:
return EXPRESSION_PARSE_SYNTAX_ERROR;
}
}
bool IsUnaryExpression(TokenType type)
{
switch (type)
{
case TOK_NOT:
return true;
default:
return false;
}
}
ExpressionParseStatus Unary(ExpressionNode **expr_out)
{
ExpressionParseStatus status;
if (IsUnaryExpression(Peek().type))
{
Token tok = Chew();
ExpressionNode *atom_expr;
if ((status = Atom(&atom_expr)) != EXPRESSION_PARSE_SUCCESS)
return status;
*expr_out = new UnaryExpression(tok.type, atom_expr);
return EXPRESSION_PARSE_SUCCESS;
}
return Atom(expr_out);
}
bool IsBinaryToken(TokenType type)
{
switch (type)
{
case TOK_AND:
case TOK_OR:
return true;
default:
return false;
}
}
ExpressionParseStatus Binary(ExpressionNode **expr_out)
{
ExpressionParseStatus status;
if ((status = Unary(expr_out)) != EXPRESSION_PARSE_SUCCESS)
return status;
while (IsBinaryToken(Peek().type))
{
Token tok = Chew();
ExpressionNode *unary_expr;
if ((status = Unary(&unary_expr)) != EXPRESSION_PARSE_SUCCESS)
{
delete *expr_out;
return status;
}
*expr_out = new BinaryExpression(tok.type, *expr_out, unary_expr);
}
return EXPRESSION_PARSE_SUCCESS;
}
ExpressionParseStatus Paren(ExpressionNode **expr_out)
{
ExpressionParseStatus status;
// lparen already chewed
if ((status = Toplevel(expr_out)) != EXPRESSION_PARSE_SUCCESS)
return status;
if (!Expects(TOK_RPAREN))
{
delete *expr_out;
return EXPRESSION_PARSE_SYNTAX_ERROR;
}
return EXPRESSION_PARSE_SUCCESS;
}
ExpressionParseStatus Toplevel(ExpressionNode **expr_out)
{
return Binary(expr_out);
}
int CountNumControls()
{
int count = 0;
for (std::vector<Token>::iterator it = tokens.begin(); it != tokens.end(); ++it)
if (it->type == TOK_CONTROL)
count++;
return count;
}
};
ControlState Expression::GetValue()
{
return expr->GetValue();
}
void Expression::SetValue(ControlState value)
{
expr->SetValue(value);
}
Expression::~Expression()
{
delete expr;
}
ExpressionParseStatus ParseExpression(std::string str, ControlFinder &finder, Expression **expr_out)
{
ExpressionParseStatus status;
Expression *expr;
*expr_out = NULL;
if (str == "")
return EXPRESSION_PARSE_SUCCESS;
Lexer l(str);
std::vector<Token> tokens;
status = l.Tokenize(tokens);
if (status != EXPRESSION_PARSE_SUCCESS)
return status;
Parser p(tokens, finder);
status = p.Parse(&expr);
if (status != EXPRESSION_PARSE_SUCCESS)
return status;
*expr_out = expr;
return EXPRESSION_PARSE_SUCCESS;
}
}
}

View File

@ -0,0 +1,73 @@
#ifndef _EXPRESSIONPARSER_H_
#define _EXPRESSIONPARSER_H_
#include <string>
#include "Device.h"
namespace ciface
{
namespace ExpressionParser
{
class ControlQualifier
{
public:
bool has_device;
Core::DeviceQualifier device_qualifier;
std::string control_name;
ControlQualifier() : has_device(false) {}
operator std::string()
{
if (has_device)
return device_qualifier.ToString() + ":" + control_name;
else
return control_name;
}
};
class ControlFinder
{
public:
ControlFinder(const Core::DeviceContainer &container_, const Core::DeviceQualifier &default_, const bool is_input_) : container(container_), default_device(default_), is_input(is_input_) {}
Core::Device::Control *FindControl(ControlQualifier qualifier);
private:
Core::Device *FindDevice(ControlQualifier qualifier);
const Core::DeviceContainer &container;
const Core::DeviceQualifier &default_device;
bool is_input;
};
class Parser;
class ExpressionNode;
class Expression
{
friend class Parser;
public:
Expression() : expr(NULL) {}
~Expression();
ControlState GetValue();
void SetValue (ControlState state);
int num_controls;
private:
ExpressionNode *expr;
};
enum ExpressionParseStatus
{
EXPRESSION_PARSE_SUCCESS = 0,
EXPRESSION_PARSE_SYNTAX_ERROR,
EXPRESSION_PARSE_NO_DEVICE,
};
ExpressionParseStatus ParseExpression(std::string expr, ControlFinder &finder, Expression **expr_out);
}
}
#endif