I'm an engineer and thus obligated to be as lazy as possible 
I'm still not an engineer, just half-way through, so I can still be not lazy sometimes

I'm implementing the method I described, currently my table is defined like this:
static const auto PMS154_INSTRUCTIONS_SPEC = std::vector<instr_spec_t>
{
// {mnemonic, opcode, opcode_mask, {args...}}
{"nop", 0x0000, 0x3FFF, {}},
{"addc", 0x0060, 0x3FFF, {{Accumulator, 0, 0}}},
{"subc", 0x0061, 0x3FFF, {{Accumulator, 0, 0}}},
{"izsn", 0x0062, 0x3FFF, {{Accumulator, 0, 0}}},
{"izsn", 0x0063, 0x3FFF, {{Accumulator, 0, 0}}},
// ...
{"wdreset", 0x0070, 0x3FFF, {}},
{"pushaf", 0x0072, 0x3FFF, {}},
{"popaf", 0x0073, 0x3FFF, {}},
// ...
{"xor", 0x00C0, 0x3FC0, {{IO, 0, 6}, {Accumulator, 0, 0}}},
{"mov", 0x0180, 0x3FC0, {{IO, 0, 6}, {Accumulator, 0, 0}}},
{"mov", 0x01C0, 0x3FC0, {{Accumulator, 0, 0}, {IO, 0, 6}}},
{"ret", 0x0200, 0x3F00, {{Immediate, 0, 8}}},
{"stt16", 0x0300, 0x3F81, {{Memory, 0, 7}}},
{"ldt16", 0x0301, 0x3F81, {{Memory, 0, 7}}},
{"idxm", 0x0380, 0x3F81, {{Memory, 0, 7}, {Accumulator, 0, 0}}},
{"idxm", 0x0381, 0x3F81, {{Accumulator, 0, 0}, {Memory, 0, 7}}},
{"swapc", 0x0400, 0x3E00, {{IO, 0, 6}, {Bit_N, 6, 3}}},
{"comp", 0x0600, 0x3F80, {{Accumulator, 0, 0}, {Memory, 0, 7}}},
{"comp", 0x0680, 0x3F80, {{Memory, 0, 7}, {Accumulator, 0, 0}}},
{"nadd", 0x0700, 0x3F80, {{Accumulator, 0, 0}, {Memory, 0, 7}}},
{"nadd", 0x0780, 0x3F80, {{Memory, 0, 7}, {Accumulator, 0, 0}}},
// ...
};
Which I hope you agree is still pretty readable thanks to various new C++ things that allow it to be written like that. Maybe with opcodes written in binary would have been easier but well...
And the hearth of the disassembler code is literally 60 lines of code, for the assembler will just be a little bit more. Once you have such a table you can just iterate over all the table and find whatever matches. If you are disassembling you check the opcode with the mask, if you are assembling you check the mnemonic and the parameters in their respective order. If you need to assemble for a variant of the instruction set you just pass another table.
Anyway I'm following my initial idea to create a "libpdk" which does everything as I previously described. That doesn't mean my approach is orthogonal to what you are doing with radare2, i.e. you could then use the library with the assembling/disassembling capabilities to create the plugin pretty easily just by calling in the library code.