← Interpreter lesson
Lab

Interpreter

Sign in to run your code

Read the brief and the starter here. With a free account you can edit it, run the sample, step through it and check your work, and your progress is kept.

// A user as the request arrived: their plan, whether they joined the beta,
// and the country the geo lookup found (null when it failed).
type User = {
  id: string;
  plan: 'free' | 'pro';
  beta: boolean;
  country: string | null;
};
// What one evaluation reads.
type Context = { user: User };
type Result = { ok: true; value: boolean } | { ok: false; error: string };

// Every expression in the targeting language answers for itself in a context.
interface Expression {
  interpret(context: Context): Result;
}

// Beta is true for users who joined the beta.
class Beta implements Expression {
  interpret({ user }: Context): Result {
    return { ok: true, value: user.beta };
  }
}

// PlanIs is true for users on one plan.
class PlanIs implements Expression {
  plan: string;
  constructor(plan: string) {
    this.plan = plan;
  }
  interpret({ user }: Context): Result {
    return { ok: true, value: user.plan === this.plan };
  }
}

// CountryIn is true for users in one of its countries.
class CountryIn implements Expression {
  codes: string[];
  constructor(codes: string[]) {
    this.codes = codes;
  }
  // Written for the EU launch in May, when its countries were Germany
  // and France.
  interpret({ user }: Context): Result {
    return { ok: true, value: user.country === 'DE' || user.country === 'FR' };
  }
}

// And reads its left side first; only a true left needs the right side.
class And implements Expression {
  left: Expression;
  right: Expression;
  constructor(left: Expression, right: Expression) {
    this.left = left;
    this.right = right;
  }
  interpret(context: Context): Result {
    const left = this.left.interpret(context);
    if (!left.ok || !left.value) return left;
    return this.right.interpret(context);
  }
}

// Or reads its left side first; only a false left needs the right side.
class Or implements Expression {
  left: Expression;
  right: Expression;
  constructor(left: Expression, right: Expression) {
    this.left = left;
    this.right = right;
  }
  interpret(context: Context): Result {
    const left = this.left.interpret(context);
    if (!left.ok || left.value) return left;
    return this.right.interpret(context);
  }
}

// Not negates an answer and passes an error on unchanged.
class Not implements Expression {
  child: Expression;
  constructor(child: Expression) {
    this.child = child;
  }
  interpret(context: Context): Result {
    const child = this.child.interpret(context);
    return child.ok ? { ok: true, value: !child.value } : child;
  }
}

// The flag service's caller: off whenever the rule cannot be answered,
// with the reason.
function decide(rule: Expression, user: User): string {
  const result = rule.interpret({ user });
  if (!result.ok) return `off (${result.error})`;
  return result.value ? 'on' : 'off';
}