Skip to content

Commit

Permalink
add message handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiayang committed Jul 25, 2020
1 parent 69a2c8d commit 9cbc8e1
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 32 deletions.
53 changes: 42 additions & 11 deletions source/commands/command.cpp
Expand Up @@ -17,15 +17,23 @@ namespace ikura::interp

namespace ikura::cmd
{
static void process_command(ikura::str_view user, ikura::str_view username, const Channel* chan, ikura::str_view cmd);
static void process_command(interp::CmdContext& cs, ikura::str_view user, ikura::str_view username, const Channel* chan, ikura::str_view cmd);
static Message generateResponse(ikura::str_view user, const Channel* chan, ikura::str_view msg);

Message value_to_message(const interp::Value& val);

bool processMessage(ikura::str_view userid, ikura::str_view username, const Channel* chan, ikura::str_view message, bool enablePings)
{
interp::CmdContext cs;
cs.executionStart = util::getMillisecondTimestamp();
cs.callername = username;
cs.callerid = userid;
cs.channel = chan;

auto pref = chan->getCommandPrefix();
if(!pref.empty() && message.find(pref) == 0)
{
process_command(userid, username, chan, message.drop(pref.size()));
process_command(cs, userid, username, chan, message.drop(pref.size()));
return true;
}
else if(enablePings && chan->shouldReplyMentions())
Expand All @@ -34,6 +42,35 @@ namespace ikura::cmd
chan->sendMessage(generateResponse(userid, chan, message));
}

// process on_message handlers
// TODO: move this out of the big lock
if(username == "zhiayang")
interpreter().perform_write([&cs, &message, &chan](interp::InterpState& interp) {
auto [ val, _ ] = interp.resolveVariable("__on_message", cs);
if(!val.has_value())
return;

auto& handlers = val.value();
if(!handlers.is_list() || !handlers.type()->elm_type()->is_function()
|| !handlers.type()->elm_type()->is_same(interp::Type::get_function(interp::Type::get_string(), { interp::Type::get_string() })))
{
lg::warn("interp", "__on_message list has wrong type (expected [(str) -> str], found %s)", handlers.type()->str());
return;
}

// TODO: pass more information to the handler (eg username, channel, etc)
for(auto& handler : handlers.get_list())
{
const auto& fn = handler.get_function();
auto copy = cs;
copy.arguments = { interp::Value::of_string(message.str()) };

lg::log("interp", "running message handler '%s'", handler.get_function()->getName());
if(auto res = fn->run(&interp, copy); res && res->type()->is_string())
chan->sendMessage(value_to_message(res.unwrap()));
}
});

return false;
}

Expand Down Expand Up @@ -185,15 +222,9 @@ namespace ikura::cmd



static void process_one_command(ikura::str_view userid, ikura::str_view username, const Channel* chan,
static void process_one_command(interp::CmdContext& cs, ikura::str_view userid, ikura::str_view username, const Channel* chan,
ikura::str_view cmd_str, ikura::str_view arg_str, bool pipelined, bool doExpand, std::string* out)
{
interp::CmdContext cs;
cs.executionStart = util::getMillisecondTimestamp();
cs.callername = username;
cs.callerid = userid;
cs.channel = chan;

cmd_str = cmd_str.trim();
arg_str = arg_str.trim();

Expand Down Expand Up @@ -256,7 +287,7 @@ namespace ikura::cmd
}
}

static void process_command(ikura::str_view userid, ikura::str_view username, const Channel* chan, ikura::str_view input)
static void process_command(interp::CmdContext& cs, ikura::str_view userid, ikura::str_view username, const Channel* chan, ikura::str_view input)
{
if(input.empty())
return;
Expand All @@ -275,7 +306,7 @@ namespace ikura::cmd
// zpr::println("A = '%s'", arg_str);

auto pipelined = !subsequent.empty();
process_one_command(userid, username, chan, cmd_str, arg_str, pipelined, do_expand, &piped_input);
process_one_command(cs, userid, username, chan, cmd_str, arg_str, pipelined, do_expand, &piped_input);

input = subsequent;
if(!pipelined)
Expand Down
40 changes: 40 additions & 0 deletions source/include/interp.h
Expand Up @@ -167,6 +167,41 @@ namespace ikura::interp
uint8_t flags() const { return this->_flags; }
void set_flags(uint8_t f) { this->_flags = f; }

Value decay() const;

Value(const Value&) = default;

Value& operator = (const Value& other)
{
if(&other == this)
return *this;

auto copy = other;
return (*this = std::move(copy));
}

Value& operator = (Value&& rhs)
{
if(&rhs == this)
return *this;

if(rhs._type->is_void()) ;
else if(rhs._type->is_map()) this->v_map = std::move(rhs.v_map);
else if(rhs._type->is_bool()) this->v_bool = std::move(rhs.v_bool);
else if(rhs._type->is_list()) this->v_list = std::move(rhs.v_list);
else if(rhs._type->is_char()) this->v_char = std::move(rhs.v_char);
else if(rhs._type->is_double()) this->v_double = std::move(rhs.v_double);
else if(rhs._type->is_integer()) this->v_integer = std::move(rhs.v_integer);
else if(rhs._type->is_complex()) this->v_complex = std::move(rhs.v_complex);
else if(rhs.is_lvalue()) this->v_lvalue = std::move(rhs.v_lvalue);
else if(rhs.is_function()) this->v_function = rhs.v_function;
else assert(false);

this->_type = rhs._type;
this->_flags = rhs._flags;
this->v_is_lvalue = rhs.v_is_lvalue;
return *this;
}

bool operator == (const Value& other) const
{
Expand Down Expand Up @@ -227,6 +262,10 @@ namespace ikura::interp
std::vector<Value> v_list;
std::map<Value, Value> v_map;
};

static Value decay(const Value& v);
static std::vector<Value> decay(const std::vector<Value>& vs);
static std::map<Value, Value> decay(const std::map<Value, Value>& vs);
};

struct Command;
Expand Down Expand Up @@ -262,6 +301,7 @@ namespace ikura::interp
Result<interp::Value> evaluateExpr(ikura::str_view expr, CmdContext& cs);

Result<bool> addGlobal(ikura::str_view name, interp::Value val);
Result<bool> removeGlobal(ikura::str_view name);

virtual void serialise(Buffer& buf) const override;
static std::optional<InterpState> deserialise(Span& buf);
Expand Down
9 changes: 7 additions & 2 deletions source/interp/builtin.cpp
Expand Up @@ -309,11 +309,16 @@ namespace ikura::interp
if(arg_str.find(' ') != std::string::npos || arg_str.empty())
return chan->sendMessage(Message("'undef' takes exactly 1 argument"));

std::string err;
auto done = interpreter().wlock()->removeCommandOrAlias(arg_str);
if(!done)
{
if(auto res = interpreter().wlock()->removeGlobal(arg_str); !res)
err = res.error();
}

chan->sendMessage(Message(
done ? zpr::sprint("removed '%s'", arg_str)
: zpr::sprint("'%s' does not exist", arg_str)
err.empty() ? zpr::sprint("removed '%s'", arg_str) : err
));
}

Expand Down
62 changes: 47 additions & 15 deletions source/interp/expr.cpp
Expand Up @@ -25,7 +25,7 @@ namespace ikura::interp::ast
auto _e = this->expr->evaluate(fs, cs);
if(!_e) return _e;

auto e = _e.unwrap();
auto e = _e.unwrap().decay();

if(this->op == TT::Plus)
{
Expand Down Expand Up @@ -97,13 +97,14 @@ namespace ikura::interp::ast
|| left->type()->elm_type()->is_void()
|| rhs.type()->elm_type()->is_void()))
{
auto rl = rhs.get_list();
auto rl = rhs.decay().get_list();

// plus equals will modify, plus will make a new temporary.
if(op == TT::Plus)
{
auto tmp = left->get_list(); tmp.insert(tmp.end(), rl.begin(), rl.end());
return Value::of_list(left->type()->elm_type(), tmp);
auto tmp = left->decay().get_list();
tmp.insert(tmp.end(), rl.begin(), rl.end());
return Value::of_list(left->type()->elm_type(), std::move(tmp));
}
else
{
Expand Down Expand Up @@ -199,13 +200,42 @@ namespace ikura::interp::ast

Result<Value> BinaryOp::evaluate(InterpState* fs, CmdContext& cs) const
{
auto lhs = this->lhs->evaluate(fs, cs);
auto rhs = this->rhs->evaluate(fs, cs);
if(this->op == TT::LogicalAnd || this->op == TT::LogicalOr)
{
// do short-circuiting stuff.
auto lhs = this->lhs->evaluate(fs, cs);
if(!lhs) return lhs;

if(!lhs) return lhs;
if(!rhs) return rhs;
if(!lhs->is_bool())
return zpr::sprint("non-boolean type '%s' on lhs of '%s'", lhs->type()->str(), this->op_str);

if(lhs->get_bool() && this->op == TT::LogicalOr)
return Value::of_bool(true);

else if(!lhs->get_bool() && this->op == TT::LogicalAnd)
return Value::of_bool(false);

else
{
auto rhs = this->rhs->evaluate(fs, cs);
if(!rhs) return rhs;

if(!rhs->is_bool())
return zpr::sprint("non-boolean type '%s' on rhs of '%s'", rhs->type()->str(), this->op_str);

return perform_binop(fs, this->op, this->op_str, lhs.unwrap(), rhs.unwrap());
return Value::of_bool(rhs->get_bool());
}
}
else
{
auto lhs = this->lhs->evaluate(fs, cs);
auto rhs = this->rhs->evaluate(fs, cs);

if(!lhs) return lhs;
if(!rhs) return rhs;

return perform_binop(fs, this->op, this->op_str, lhs.unwrap(), rhs.unwrap());
}
}

Result<Value> TernaryOp::evaluate(InterpState* fs, CmdContext& cs) const
Expand Down Expand Up @@ -251,15 +281,17 @@ namespace ikura::interp::ast
}

// check if they're assignable.
if(!ltyp->is_same(rhs->type()))
if(auto right = rhs->cast_to(ltyp); !right)
{
return zpr::sprint("cannot assign value of type '%s' to variable of type '%s'",
rhs->type()->str(), ltyp->str());
}

// ok
*lhs->get_lvalue() = rhs.unwrap();
return lhs;
else
{
// ok
*lhs->get_lvalue() = right.value().decay();
return lhs;
}
}

Result<Value> ComparisonOp::evaluate(InterpState* fs, CmdContext& cs) const
Expand Down Expand Up @@ -489,7 +521,7 @@ namespace ikura::interp::ast
for(size_t i = first; i < last; i++)
refs.push_back(Value::of_lvalue(&list[i]));

return Value::of_list(base->type()->elm_type(), refs);
return Value::of_list(base->type()->elm_type(), std::move(refs));
}
else
{
Expand Down
21 changes: 20 additions & 1 deletion source/interp/interp.cpp
Expand Up @@ -107,7 +107,7 @@ namespace ikura::interp
return zpr::sprint("'%s' is already a builtin global", name);

if(auto it = this->globals.find(name); it != this->globals.end())
return zpr::sprint("redefinition of global '%s'", name);
return zpr::sprint("global '%s' already defined", name);

if(val.type()->has_generics())
return zpr::sprint("cannot create values of generic type ('%s')", val.type()->str());
Expand All @@ -117,6 +117,22 @@ namespace ikura::interp
return true;
}

Result<bool> InterpState::removeGlobal(ikura::str_view name)
{
if(is_builtin_var(name) || name.find_first_of("0123456789") == 0)
return zpr::sprint("cannot remove builtin globals");

if(auto it = this->globals.find(name); it != this->globals.end())
{
this->globals.erase(it);
return true;
}
else
{
return zpr::sprint("'%s' does not exist", name);
}
}

Result<Value> InterpState::evaluateExpr(ikura::str_view expr, CmdContext& cs)
{
auto exp = ast::parseExpr(expr);
Expand Down Expand Up @@ -251,6 +267,9 @@ namespace ikura::interp
if(!rd.read(&builtinPerms))
return { };

// big hax: since the deserialisation of globals can potentially require the commands array, we just
// override it, even though this isn't supposed to touch the current interp state... bleh.
interpreter().wlock()->commands = interp.commands;

ikura::string_map<interp::Value> globals;
if(!rd.read(&globals))
Expand Down
5 changes: 3 additions & 2 deletions source/interp/parser.cpp
Expand Up @@ -169,9 +169,10 @@ namespace ikura::interp::ast
{
switch(op)
{
case TT::Period: return 8000;
// function calls must have higher precedence than dotop, so we parse `a.b()` as `a.{b()}` rather than `{a.b}()`
case TT::LParen: return 9000;

case TT::LParen: return 3000;
case TT::Period: return 8000;

case TT::LSquare: return 2800;

Expand Down
46 changes: 45 additions & 1 deletion source/interp/value.cpp
Expand Up @@ -261,15 +261,59 @@ namespace ikura::interp
if(this->is_double() && type->is_complex())
return Value::of_complex(this->get_double(), 0);

if((this->is_list() && type->is_list()) || (this->is_map() && type->is_map()))
if(this->is_list() && type->is_list())
return Value::of_list(type->elm_type(), this->v_list);

if(this->is_map() && type->is_map())
return Value::of_map(type->key_type(), type->elm_type(), decay(this->v_map));

if(this->is_function() && type->is_function())
return *this;

return { };
}

std::vector<Value> Value::decay(const std::vector<Value>& vs)
{
return zfu::map(vs, [](const auto& x) -> auto {
return x.decay();
});
}

std::map<Value, Value> Value::decay(const std::map<Value, Value>& vs)
{
std::map<Value, Value> map;
for(auto& [ a, b ] : vs)
map.insert({ a.decay(), b.decay() });

return map;
}

Value Value::decay(const Value& v)
{
if(v.is_lvalue())
{
return v.v_lvalue->decay();
}
else if(v.is_list())
{
return Value::of_list(v.type()->elm_type(), decay(v.v_list));
}
else if(v.is_map())
{
return Value::of_map(v.type()->key_type(), v.type()->elm_type(), decay(v.v_map));
}
else
{
return v;
}
}

Value Value::decay() const
{
return Value::decay(*this);
}


bool Value::is_lvalue() const { return this->v_is_lvalue; }
bool Value::is_list() const { return this->_type->is_list(); }
Expand Down

0 comments on commit 9cbc8e1

Please sign in to comment.