LMU ☀️ CMSI 585
PROGRAMMING LANGUAGE FOUNDATIONS
HOMEWORK #5 PARTIAL ANSWERS

Bella Denotational Semantics

Abstract Syntax (from the assignment page):

$ \begin{array}{l} n\!: \mathsf{Numeral} \\ i\!: \mathsf{Identifier} \\ e\!: \mathsf{Expression} = n \;|\; i \;|\; \mathtt{true} \;|\; \mathtt{false} \;|\; \mathit{uop} \; e \;|\; e_1 \; \mathit{bop} \; e_2 \;|\; i \; e^* \;|\; e \; \mathtt{?} \; e_1 \; \mathtt{:} \; e_2 \;|\; \mathtt{[} \; e^* \; \mathtt{]} \;|\; e \mathtt{[} e \mathtt{]} \\ s\!: \mathsf{Statement} = \mathtt{let}\;i = e \;|\; \mathtt{func}\;i\;i^*=e \;|\; i = e \;|\; \mathtt{print}\;e \;|\; \mathtt{while}\;e\;b \\ b\!: \mathsf{Block} = \mathtt{block}\; s^* \\ p\!: \mathsf{Program} = \mathtt{program}\; b \end{array} $

Semantic domains:

$\begin{array}{l} \mathsf{Value}\;=_{\textrm{def}} \\ \;\;\;\; \mathsf{Real} \;\cup \\ \;\;\;\; \mathsf{Bool} \;\cup \\ \;\;\;\; \mathsf{Value}^* \;\cup \\ \;\;\;\; (\mathsf{Identifier}^* \times \mathsf{Expression}) \;\cup \\ \;\;\;\; ((\mathsf{Value}^* \rightarrow \mathsf{Value}) \times \mathsf{Nat}) \\ \mathsf{Mem}\;=_{\textrm{def}} \mathsf{Identifier} \rightarrow (\mathsf{Value} \times \{\mathsf{RO},\mathsf{RW}\}) \\ \mathsf{Output}\; =_{\textrm{def}} \mathsf{Value}^* \\ \mathsf{State}\;=_{\textrm{def}} \mathsf{Mem} \times \mathsf{Output} \end{array}$

The semantic functions:

$\begin{array}{l} \mathscr{E}\,: \mathsf{Expression} \rightarrow \mathsf{Mem} \rightarrow \mathsf{Value} \\ \mathscr{S}\,: \mathsf{Statement} \rightarrow \mathsf{State} \rightarrow \mathsf{State} \\ \mathscr{B}\,: \mathsf{Block} \rightarrow \mathsf{State} \rightarrow \mathsf{State} \\ \mathscr{P}\,: \mathsf{Program} \rightarrow \mathsf{Output} \end{array}$

Definitions of the semantic functions, with the assumption that any type clashes in the mathematical world will just make a $\bot$:

$\begin{array}{l} \mathscr{E}[\![n]\!]\,m = n \\ \mathscr{E}[\![\mathsf{true}]\!]\,m = T \\ \mathscr{E}[\![\mathsf{false}]\!]\,m = F \\ \mathscr{E}[\![i]\!]\,m = \mathsf{match}\;m(i) = (x,\_)\;\mathsf{in}\;x \\ \mathscr{E}[\![ -e ]\!]\,m = -\mathscr{E}\,e\,m \\ \mathscr{E}[\![ \mathtt{!}\,e ]\!]\,m = \mathsf{not}\;\mathscr{E}\,e\,m\ \\ \mathscr{E} [\![ e_1 \,\mathtt{+}\, e_2 ]\!] \,m = \mathscr{E}\,e_1\,m + \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \,\mathtt{-}\, e_2 ]\!]\,m = \mathscr{E}\,e_1\,m - \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \,\mathtt{*}\, e_2 ]\!]\,m = \mathscr{E}\,e_1\,m \times \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \,\mathtt{/}\, e_2 ]\!]\,m = \mathscr{E}\,e_1\,m \div \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \,\mathtt{\%}\, e_2]\!]\,m = \mathscr{E}\,e_1\,m \;\mathsf{rem}\; \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \,\mathtt{**}\, e_2]\!]\,m = (\mathscr{E}\,e_1\,m)^{\mathscr{E}\,e_2\,m} \\ \mathscr{E} [\![ e_1 < e_2 ]\!]\,m = \mathscr{E}\,e_1\,m < \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \leq e_2 ]\!]\,m = \mathscr{E}\,e_1\,m \leq \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 = e_2 ]\!]\,m = \mathscr{E}\,e_1\,m = \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \,\mathtt{!=}\, e_2 ]\!]\,m = \mathscr{E}\,e_1\,m \neq \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \geq e_2 ]\!]\,m = \mathscr{E}\,e_1\,m \geq \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 > e_2 ]\!]\,m = \mathscr{E}\,e_1\,m > \mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \,\mathtt{\&\&}\, e_2]\!]\,m = \mathsf{if}\;\mathscr{E}\,e_1\,m = F\;\mathsf{then}\;F\;\mathsf{else}\;\mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \,\mathtt{||}\, e_2]\!]\,m = \mathsf{if}\;\mathscr{E}\,e_1\,m\;\mathsf{then}\;T\;\mathsf{else}\;\mathscr{E}\,e_2\,m \\ \mathscr{E} [\![ e_1 \mathtt{?}\, e_2 \mathtt{:}\, e_3]\!]\,m = \mathsf{if}\;\mathscr{E}\,e_1\,m\;\mathsf{then}\; \mathscr{E}\,e_2\,m\;\mathsf{else}\;\mathscr{E}e_3 m \\ \mathscr{E} [\![ \mathtt{[} e_1\!\ldots\!e_n \mathtt{]} ]\!]\,m = [\mathscr{E}\,e_1\,m \ldots \mathscr{E}\,e_n\,m] \\ \mathscr{E} [\![ e_1\mathtt{[}e_2\mathtt{]} ]\!]\,m = \\ \;\;\;\; \mathsf{let}\; a = \mathscr{E}\,e_1\,m\;\mathsf{in}\; \mathsf{let}\; x = \mathscr{E}\,e_2\,m\;\mathsf{in} \\ \;\;\;\;\;\;\;\; \mathsf{if}\;0 \leq x < |a|\;\mathsf{then}\;a\downarrow x\;\mathsf{else}\;\bot \\ \mathscr{E} [\![ i\;e_1\!\ldots\!e_n]\!]\,m = \\ \;\;\;\; \mathsf{match}\;m(i)=((f,n),\_)\;\mathsf{in} \\ \;\;\;\;\;\;\;\; f(\mathscr{E}\,e_1\,m, \ldots, \mathscr{E}e_n m) \\ \;\;\;\; \mathsf{else}\;\mathsf{match}\;m(i)=(((p_1,\ldots, p_n),e),\_)\;\mathsf{in} \\ \;\;\;\;\;\;\;\; \mathscr{E}\:e\:m[\mathscr{E}\,e_i\,m \,/\, p_i]_{i=1}^n \\ \\ \mathscr{S} [\![ \mathtt{let}\;i=e ]\!](m,o) = \mathsf{match}\;m(i) = \bot \;\mathsf{in}\;(m[(\mathscr{E}\,e\,m,\mathsf{RW})\,/\,i], o) \\ \mathscr{S} [\![ \mathtt{print}\;e ]\!](m,o) = (m, \, o \cdot \mathscr{E}\,e\,m) \\ \mathscr{S} [\![ i=e ]\!](m,o) = \mathsf{match}\;m(i)=(x,\mathsf{RW}) \;\mathsf{in}\; (m[(\mathscr{E}\,e\,m,\mathsf{RW})\,/\,i], o)\\ \mathscr{S} [\![ \mathtt{fun}\;i\;i_1\ldots i_n = e ]\!](m,o) = \mathsf{match}\;m(i) = \bot \;\mathsf{in}\;(m[(((i_1, \ldots, i_n),e),\mathsf{RO})\,/\,i], o) \\ \mathscr{S} [\![ \mathtt{while}\;e\;b ]\!] = \mathrm{fix}\;\lambda w. \lambda (m,o). \mathsf{if}\;\mathscr{E}\;e\;m = F\;\mathsf{then}\;(m,o)\;\mathsf{else}\; w\;(\mathscr{B}\;b\;(m,o)) \\ \\ \mathscr{B}[\![\mathtt{block}\;s_1\!\ldots\!s_n]\!](m,o) = \mathscr{S}s_n( \ldots \mathscr{S}s_1(m,o) \ldots ) \\ \\ \mathscr{P}[\![\mathtt{program}\;b]\!] = \\ \;\;\;\; \mathsf{let}\; (m,o) = \mathscr{B}\,b\,(m_0,()) \;\mathsf{in}\; o \\ \;\;\;\;\;\;\;\; \mathsf{where}\;m_0 = \\ \;\;\;\;\;\;\;\;\;\;\;\;\lambda\,i. \bot \:[\\ \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; (\pi, \mathsf{true})\,/\,\mathtt{π}] \:[\\ \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; (\lambda x.\sqrt{x}, 1)\,/\,\mathtt{sqrt}] \:[\\ \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; (\lambda x.\sin{x}, 1)\,/\,\mathtt{sin}] \:[\\ \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; (\lambda x.\cos{x}, 1)\,/\,\mathtt{cos}] \:[\\ \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; (\lambda x.e^x, 1)\,/\,\mathtt{exp}] \:[\\ \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; (\lambda x.\ln{x}, 1)\,/\,\mathtt{ln}] \:[\\ \;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\; (\lambda (x,y).\sqrt{x^2+y^2}, 2)\,/\,\mathtt{hypot}] \end{array}$

Bella Interpreter

bella.ts
// Bella Interpreter

// Abstract Syntax
//   n: Nml
//   i: Ide
//   e: Exp = n | i | true | false | uop e | e bop e | e ? e : e
//          | i e* | [ e* ] | e[e]
//   s: Stm = let i = e | func i i* = e | i = e | print e | while e b
//   b: Blo = block s*
//   p: Pro = program b

// Semantic domains

type BuiltInFunction = (...args: Value[]) => Value;
type UserFunction = [Identifier[], Expression];
export type Value = number | boolean | Value[] | BuiltInFunction | UserFunction;

type Memory = Map<string, Value>;
type Output = Value[];
type State = [Memory, Output];

// Custom type guards

function isUserFunction(v: Value): v is UserFunction {
  return Array.isArray(v) && Array.isArray(v[0]) && v[0].length === 2;
}

function isBuiltInFunction(v: Value): v is BuiltInFunction {
  return typeof v === "function";
}

function isArray(x: Value): x is Value[] {
  return Array.isArray(x);
}

// Semantic Functions

export interface Expression {
  interpret(m: Memory): Value;
}

export class Numeral implements Expression {
  constructor(public value: number) {}
  interpret(_: Memory): Value {
    return this.value;
  }
}

export class BooleanLiteral implements Expression {
  constructor(public value: boolean) {}
  interpret(_: Memory): Value {
    return this.value;
  }
}

export class Identifier implements Expression {
  constructor(public name: string) {}
  interpret(m: Memory): Value {
    const entity = m.get(this.name);
    if (entity === undefined) {
      throw new Error("Identifier not declared");
    }
    return entity;
  }
}

export class UnaryExpression implements Expression {
  constructor(public operator: string, public expression: Expression) {}
  interpret(m: Memory): Value {
    const x = this.expression.interpret(m);
    if (this.operator === "-") {
      if (typeof x !== "number") {
        throw new Error("Operand must be a number");
      }
      return -x;
    } else if (this.operator === "!") {
      if (typeof x !== "boolean") {
        throw new Error("Operand must be a boolean");
      }
      return !x;
    }
    throw new Error("Unknown operator");
  }
}

export class BinaryExpression implements Expression {
  constructor(
    public operator: string,
    public left: Expression,
    public right: Expression
  ) {}
  interpret(m: Memory): Value {
    const [x, y] = [this.left.interpret(m), this.right.interpret(m)];
    if (["+", "-", "*", "/", "%", "**"].includes(this.operator)) {
      if (typeof x !== "number" || typeof y !== "number") {
        throw new Error("Operands must be numbers");
      }
      switch (this.operator) {
        case "+":
          return x + y;
        case "-":
          return x - y;
        case "*":
          return x * y;
        case "/":
          return x / y;
        case "%":
          return x % y;
        case "**":
          return x ** y;
      }
    } else if (["<", "<=", "==", "!=", ">=", ">"].includes(this.operator)) {
      if (typeof x !== "number" || typeof y !== "number") {
        throw new Error("Operands must be numbers");
      }
      switch (this.operator) {
        case "<":
          return x < y;
        case "<=":
          return x <= y;
        case "==":
          return x === y;
        case "!=":
          return x !== y;
        case ">=":
          return x >= y;
        case ">":
          return x > y;
      }
    } else if (["&&", "||"].includes(this.operator)) {
      if (typeof x !== "boolean" || typeof y !== "boolean") {
        throw new Error("Operands must be booleans");
      }
      switch (this.operator) {
        case "&&":
          return x && y;
        case "||":
          return x || y;
      }
    }
    throw new Error("Unknown operator");
  }
}

export class Call implements Expression {
  constructor(public callee: Identifier, public args: Expression[]) {}
  interpret(m: Memory): Value {
    const functionValue = m.get(this.callee.name);
    const argValues = this.args.map((arg) => arg.interpret(m));
    if (functionValue === undefined) {
      throw new Error("Identifier was undeclared");
    } else if (isUserFunction(functionValue)) {
      const [parameters, expression] = functionValue;
      if (parameters.length !== this.args.length) {
        throw new Error("Wrong number of arguments");
      }
      const locals = parameters.map((p, i) => [p.name, argValues[i]] as const);
      return expression.interpret(new Map([...m, ...locals]));
    } else if (isBuiltInFunction(functionValue)) {
      return functionValue(...argValues);
    } else {
      throw new Error("Not a function");
    }
  }
}

export class ConditionalExpression implements Expression {
  constructor(
    public test: Expression,
    public consequent: Expression,
    public alternate: Expression
  ) {}
  interpret(m: Memory): Value {
    return this.test.interpret(m)
      ? this.consequent.interpret(m)
      : this.alternate.interpret(m);
  }
}

export class ArrayLiteral implements Expression {
  constructor(public elements: Expression[]) {}
  interpret(m: Memory): Value {
    return this.elements.map((e) => e.interpret(m));
  }
}

export class SubscriptExpression implements Expression {
  constructor(public array: Expression, public subscript: Expression) {}
  interpret(m: Memory): Value {
    const arrayValue = this.array.interpret(m);
    const subscriptValue = this.subscript.interpret(m);
    if (typeof subscriptValue !== "number") {
      throw new Error("Subscript must be a number");
    }
    if (!isArray(arrayValue)) {
      throw new Error("Not an array");
    }
    return arrayValue[subscriptValue];
  }
}

// Statements

export interface Statement {
  interpret([m, o]: State): State;
}

export class VariableDeclaration implements Statement {
  constructor(public id: Identifier, public expression: Expression) {}
  interpret([m, o]: State): State {
    if (m.has(this.id.name)) {
      throw new Error("Identifier already declared");
    }
    const initializer = this.expression.interpret(m);
    return [new Map([...m, [this.id.name, initializer]]), o];
  }
}

export class FunctionDeclaration implements Statement {
  constructor(
    public id: Identifier,
    public parameters: Identifier[],
    public expression: Expression
  ) {}
  interpret([m, o]: State): State {
    if (m.has(this.id.name)) {
      throw new Error("Identifier already declared");
    }
    const fun: UserFunction = [this.parameters, this.expression];
    return [new Map([...m, [this.id.name, fun]]), o];
  }
}

export class Assignment implements Statement {
  constructor(public id: Identifier, public expression: Expression) {}
  interpret([m, o]: State): State {
    if (!m.has(this.id.name)) {
      throw new Error("Variable not declared");
    }
    const initializer = this.expression.interpret(m);
    return [new Map([...m, [this.id.name, initializer]]), o];
  }
}

export class PrintStatement implements Statement {
  constructor(public expression: Expression) {}
  interpret([m, o]: State): State {
    return [m, [...o, this.expression.interpret(m)]];
  }
}

export class WhileStatement implements Statement {
  constructor(public expression: Expression, public block: Block) {}
  interpret([m, o]: State): State {
    let state: State = [m, o];
    while (this.expression.interpret(state[0])) {
      state = this.block.interpret(state);
    }
    return state;
  }
}

export class Block {
  constructor(public statements: Statement[]) {}
  interpret([m, o]: State): State {
    let state: State = [m, o];
    for (let statement of this.statements) {
      state = statement.interpret(state);
    }
    return state;
  }
}

export class Program {
  constructor(public block: Block) {}
  interpret(): Output {
    const initialMemory: Memory = new Map<string, Value>([
      ["pi", Math.PI as Value],
      ["sqrt", Math.sqrt as Value],
      ["sin", Math.sin as Value],
      ["cos", Math.cos as Value],
      ["ln", Math.log as Value],
      ["exp", Math.exp as Value],
      ["hypot", Math.hypot as Value],
    ]);
    const [_, o] = this.block.interpret([initialMemory, []]);
    return o;
  }
}

export function interpret(p: Program) {
  return p.interpret();
}

Bella Extension With Static Types

Abstract Syntax with (1) type annotations on variable declarations and parameter declarations and (2) tuple types (and tuple expressions). The problem as assigned did not say we had to have $n$-tuples for arbitrary $n$, so I've kept things simple and allowed only pairs:

$ \begin{array}{l} n\!: \mathsf{Numeral} \\ i\!: \mathsf{Identifier} \\ t\!: \mathsf{Type} = \texttt{num} \;|\; \texttt{bool} \;|\; \texttt{list}\;t \;|\; \texttt{tuple}\;t\;t \\ e\!: \mathsf{Expression} = n \;|\; i \;|\; \mathtt{true} \;|\; \mathtt{false} \;|\; \mathit{uop} \; e \;|\; e_1 \; \mathit{bop} \; e_2 \;|\; i \; e^* \;|\; e \; \mathtt{?} \; e_1 \; \mathtt{:} \; e_2 \;|\; \mathtt{[} \; e^* \; \mathtt{]} \;|\; e \mathtt{[} e \mathtt{]} \;|\; \mathtt{(} e\;e \mathtt{)} \\ s\!: \mathsf{Statement} = \mathtt{let}\;i\;\texttt{:}\;t = e \;|\; \mathtt{func}\;i\;(i\;\texttt{:}\;t)^*=e \;|\; i = e \;|\; \mathtt{print}\;e \;|\; \mathtt{while}\;e\;b \\ b\!: \mathsf{Block} = \mathtt{block}\; s^* \\ p\!: \mathsf{Program} = \mathtt{program}\; b \end{array} $

We have to update some rules and add new ones. Let’s do the tuple expression first, as it’s the simplest:

$$\frac{e_1,c \Longrightarrow t_1 \quad e_2,c \Longrightarrow t_2} {[\![\mathtt{(}e_1\;e_2\mathtt{)}]\!],c \Longrightarrow (\mathsf{tuple},t_1,t_2)}$$

The identifier expression rule needs to allow tuples:

$$\frac{c(i) = (t, \_) \quad t \in \{ \mathsf{num}, \mathsf{bool}, (\mathsf{list},\_), (\mathsf{tuple},\_,\_) \}} {[\![i]\!],c \Longrightarrow t}$$

For the variable declaration, we want the type of the initializing expression to be exactly the one in the declaration:

$$\frac{ e,c \Longrightarrow t \;\;\;\; c(i) = \bot} { [\![\mathtt{let}\;i:t=e]\!],c \Longrightarrow c[(t,\mathsf{RW})\,/\,i]}$$

Interestingly, it seems that the operational semantics in the notes for function declarations and calls already supported parameter type declarations, so it seems there is nothing to do here.