Let's make a programming language, kinda

The Challenge

We need a tiny language to express combinations of permissions

// Basic expressions
has_permissions('profile:read')

// Basic combinations
has_permissions('transactions:read' || 'financials:manage')

// Compound expressions, with parentheses
has_permissions(('transactions:read' && 'expenses:read') || 'financials:manage')

How to get lost in too much theoretical mumbo-jumbo and forget that you have a day job and still need to pay the bills

DSLs

a.k.a

(Domain Specific Language)

So, like, where do you start?

Expressions

class UnaryExpr {
  value = undefined;

  constructor(value) {
    this.value = value;
  }

  or(other) {
    return new BinaryExpr(this, other, '||');
  }

  and(other) {
    return new BinaryExpr(this, other, '&&');
  }

  toString() {
    return `${this.value}`;
  }
}

Unary Expressions

class BinaryExpr {
  left = undefined;
  right = undefined;
  op = undefined;

  constructor(left, right, op) {
    this.left = left;
    this.right = right;
    if (op !== '||' && op !== '&&') {
      throw Error(`invalid operator value: ${op}`);
    }
    this.op = op;
  }

  or(other) {
    return new BinaryExpr(this, other, '||')
  }

  and(other) {
    return new BinaryExpr(this, other, '&&')
  }

  toString() {
    return `(${this.left} ${this.op} ${this.right})`;
  }
}

Binary Expressions

const expr = new BinaryExpr('left', 'right', '||');
const e2 = expr.and(new BinaryExpr('lhs', 'rhs', '&&'));
console.log(e2.toString());



> (left && (lhs && rhs))

Put 'em together

class Permission extends UnaryExpr {}



const trxRead = new Permission('transactions:read');
const trxWrite = new Permission('transactions:write');
const finManage = new Permission('financials:manage');
const perms = trxRead.and(trxWrite).or(finManage);


console.log((perms.toString()));
> (transactions:read || financials:manage)


const perms = trxRead.and(trxWrite).or(finManage);

console.log((perms.toString()));
> ((transactions:read && transactions:write) || financials:manage)

CanIDoThatPlzThankYou

"Abstract" Syntax Tree

We don't need no stinkin' AST!

Wait, actually, we do need one

Oh, we already have one! kinda

Just don't look too closely...

The "Interpreter"

class Interpreter {
  constructor(permissions) {
    this.permissions = permissions || [];
  }

  exec(expr) {
    if (expr instanceof lang.UnaryExpr) {
      return this.permissions.includes(expr.value);
    }

    if (expr instanceof lang.BinaryExpr) {
      switch (expr.op) {
      case '&&':
        return this.exec(expr.left) && this.exec(expr.right);
      case '||':
        return this.exec(expr.left) || this.exec(expr.right);
      default:
        throw Error(`RuntimeError: invalid operator ${expr.op}`);
      }
    }

    throw Error(`RuntimeError: unsupported expression type ${expr}`);
  }
}

Run It

const lang = require('./minilang');
const runtime = require('./runtime');

const trxRead = new lang.Permission('transactions:read');
const trxWrite = new lang.Permission('transactions:read');
const finManage = new lang.Permission('financials:manage');
const userPerms = ['transactions:read', 'expenses:read'];

const int = new runtime.Interpreter(userPerms);
const trxReadAndWrite = trxRead.and(trxWrite);

console.log(`User can read/write transactions: ${int.exec(trxReadAndWrite)}`);
> User can read/write transactions: true


console.log(`User can manage financials: ${int.exec(finManage)}`);
> User can manage financials: false


int.permissions = ['financials:manage'];
console.log(`User can manage financials: ${int.exec(perms)}`);
> User can manage financials: true

Highly Recommended

FIN

Made with Slides.com