<|start_filename|>eval.js<|end_filename|>
const { SymbolTableImpl, Scope } = require('./symbol-table');
const { ASTNodes } = require('./ast');

const SymbolTable = new SymbolTableImpl();

const Eval = ast => {
  // The empty program evaluates to null.
  if (!ast) {
    return null;
  }

  // The literals evaluate to their values.
  if (ast.type === ASTNodes.Literal) {
    return ast.value;

  // The variables evaluate to the values
  // that are bound to them in the SymbolTable.
  } else if (ast.type === ASTNodes.Identifier) {
    return SymbolTable.lookup(ast.name);

  // if-then-else evaluates to the expression of the
  // then clause if the condition is true, otherwise
  // to the value of the else clause.
  } else if (ast.type === ASTNodes.Condition) {
    if (Eval(ast.condition)) {
      return Eval(ast.then);
    } else {
      return Eval(ast.el);
    }

  // The abstraction creates a new context of execution
  // and registers it's argument in the SymbolTable.
  } else if (ast.type === ASTNodes.Abstraction) {
    const scope = new Scope();
    return x => {
      scope.add(ast.arg.id.name, x);
      SymbolTable.push(scope);
      return Eval(ast.body);
    };

  // IsZero checks if the evaluated value of its
  // expression equals `0`.
  } else if (ast.type === ASTNodes.IsZero) {
    return Eval(ast.expression) === 0;

  // The arithmetic operations manipulate the value
  // of their corresponding expressions:
  // - `succ` adds 1.
  // - `pred` subtracts 1.
  } else if (ast.type === ASTNodes.Arithmetic) {
    const op = ast.operator;
    const val = Eval(ast.expression);
    switch (op) {
      case 'succ':
        return val + 1;
      case 'pred':
        return (val - 1 >= 0) ? val - 1 : val;
    }

  // The application evaluates to:
  // - Evaluation of the left expression.
  // - Evaluation of the right expression.
  // Application of the evaluation of the left expression over
  // the evaluated right expression.
  } else if (ast.type === ASTNodes.Application) {
    const l = Eval(ast.left);
    const r = Eval(ast.right);
    return l(r);
  }
  return true;
};

module.exports.Eval = Eval;



<|start_filename|>package.json<|end_filename|>
{
  "name": "typed-calc",
  "version": "1.0.0",
  "description": "Simply typed lambda calculus",
  "main": "index.js",
  "scripts": {
    "gen": "pegjs simply-typed.peg"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/mgechev/typed-calc.git"
  },
  "author": "<NAME> <<EMAIL>>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mgechev/typed-calc/issues"
  },
  "homepage": "https://github.com/mgechev/typed-calc#readme",
  "devDependencies": {
    "pegjs": "^0.10.0"
  },
  "dependencies": {
    "chalk": "^1.1.3",
    "prettier": "^0.21.0"
  }
}


<|start_filename|>index.js<|end_filename|>
const { parse } = require('./simply-typed');
const { Check } = require('./check');
const { Eval } = require('./eval');
const { green, red } = require('chalk');
const { CompileJS } = require('./compile');
// const { CompileWat } = require('./compile-wat');
const { readFileSync, existsSync } = require('fs');

const getAst = program => {
  const ast = parse(program);

  const diagnostics = Check(ast).diagnostics;
  if (diagnostics.length) {
    console.error(red(diagnostics.join('\n')));
    process.exit(1);
  }

  return ast;
};

const fileName = process.argv.pop();
let mode = process.argv.pop();
let target = process.argv.pop();

if (mode === 'wat' || target === 'compile') {
  [mode, target] = [target, mode];
}

if (!existsSync(fileName)) {
  console.error(`"${fileName}" does not exist.`)
  process.exit(1);
}

const shouldCompile = mode === 'compile';

const userMessage = (shouldCompile ? 'Compiling' : 'Evaluating') +
  ` "${fileName}"` +
  (shouldCompile ? ` to ` + (target === 'wat' ? 'WebAssembly' : 'JavaScript') : '') + '.';

console.log(green(userMessage));
console.log();

const program = readFileSync(fileName, { encoding: 'utf-8' });
const ast = getAst(program);

if (mode === 'compile') {
  let result = '';
  if (target === 'wat') {
    result = CompileWat(ast);
  } else {
    result = CompileJS(ast);
  }
  console.log(result);
} else {
  console.log(Eval(ast));
}


<|start_filename|>check.js<|end_filename|>
const { SymbolTableImpl, Scope } = require('./symbol-table');
const { ASTNodes, Types } = require('./ast');

const SymbolTable = new SymbolTableImpl();

const typeEq = (a, b) => {
  if (a instanceof Array && b instanceof Array) {
    if (a.length !== b.length) {
      return false;
    } else {
      for (let i = 0; i < a.length; i += 1) {
        if (!typeEq(a[i], b[i])) {
          return false;
        }
      }
      return true;
    }
  } else {
    if (typeof a === 'string' && typeof b === 'string') {
      return a === b;
    }
  }
  return false;
};

const Check = (ast, diagnostics) => {
  diagnostics = diagnostics || [];

  // By definition empty AST is correct
  if (!ast) {
    return {
      diagnostics
    };
  }

  // Literals:
  // - 0 is of type Natural
  // - false and true are of type Boolean
  // Everything else is incorrect.
  if (ast.type === ASTNodes.Literal) {
    if (ast.value === 0) {
      return {
        diagnostics,
        type: Types.Natural
      };
    } else if (ast.value === false || ast.value === true) {
      return {
        diagnostics,
        type: Types.Boolean
      };
    } else {
      diagnostics.push('Unknown type literal');
      return {
        diagnostics
      };
    }

    // We get the type of identifier from the symbol table
  } else if (ast.type === ASTNodes.Identifier) {
    return {
      diagnostics,
      type: SymbolTable.lookup(ast.name)
    };

    // if-then-else block is correct if:
    // - The condition is of type Boolean.
    // - Then and else are of the same type.
  } else if (ast.type === ASTNodes.Condition) {
    if (!ast.then || !ast.el || !ast.condition) {
      diagnostics.push('No condition for a conditional expression');
      return {
        diagnostics
      };
    }
    const c = Check(ast.condition);
    diagnostics = diagnostics.concat(c.diagnostics);
    const conditionType = c.type;
    if (!typeEq(conditionType, Types.Boolean)) {
      diagnostics.push('Incorrect type of condition of condition');
      return {
        diagnostics
      };
    }
    const thenBranch = Check(ast.then);
    diagnostics = diagnostics.concat(thenBranch.diagnostics);
    const thenBranchType = thenBranch.type;
    const elseBranch = Check(ast.el);
    diagnostics = diagnostics.concat(elseBranch.diagnostics);
    const elseBranchType = elseBranch.type;
    if (typeEq(thenBranchType, elseBranchType)) {
      return thenBranch;
    } else {
      diagnostics.push('Incorrect type of then/else branches');
      return {
        diagnostics
      };
    }

    // Abstraction registers its argument in the SymbolTable
    // and returns a pair:
    // - The type of its argument.
    // - Type of its body, which may depend on the type
    // of the argument registered in the SymbolTable.
  } else if (ast.type === ASTNodes.Abstraction) {
    const scope = new Scope();
    scope.add(ast.arg.id.name, ast.arg.type);
    SymbolTable.push(scope);
    if (!ast.body) {
      diagnostics.push('No body of a function');
      return {
        diagnostics
      };
    }
    const body = Check(ast.body);
    const bodyType = body.type;
    diagnostics = diagnostics.concat(body.diagnostics);
    if (!bodyType) {
      diagnostics.push('Incorrect type of the body');
      return {
        diagnostics
      };
    }
    return {
      diagnostics,
      type: [ast.arg.type, bodyType]
    };

    // The type of IsZero is Boolean but in case
    // its argument is not Natural the program is incorrect.
  } else if (ast.type === ASTNodes.IsZero) {
    const body = Check(ast.expression);
    diagnostics = diagnostics.concat(body.diagnostics);
    const bodyType = body.type;
    if (!typeEq(bodyType, Types.Natural)) {
      diagnostics.push('Incorrect type of IsZero');
      return {
        diagnostics
      };
    }
    return {
      diagnostics,
      type: Types.Boolean
    };

    // The type of the arithmetic operations are Natural
    // but in case the type of the body is not the entire
    // program is incorrect.
  } else if (ast.type === ASTNodes.Arithmetic) {
    const body = Check(ast.expression);
    diagnostics = diagnostics.concat(body.diagnostics);
    const bodyType = body.type;
    if (!typeEq(bodyType, Types.Natural)) {
      diagnostics.push(`Incorrect type of ${ast.operation}`);
      return {
        diagnostics
      };
    }
    return {
      diagnostics,
      type: Types.Natural
    };

    // The type of:
    // e1: T1, e2: T2, e1 e2: T1
  } else if (ast.type === ASTNodes.Application) {
    const l = Check(ast.left);
    const leftType = l.type || [];
    diagnostics = diagnostics.concat(l.diagnostics);
    const r = Check(ast.right);
    const rightType = r.type || [];
    diagnostics = diagnostics.concat(r.diagnostics);
    if (leftType.length) {
      if (!ast.right || leftType[0] === rightType) {
        return {
          diagnostics,
          type: leftType[1]
        };
      } else {
        diagnostics.push('Incorrect type of application');
        return {
          diagnostics
        };
      }
    } else {
      return { diagnostics };
    }
  }
  return { diagnostics };
};

module.exports.Check = ast => Check(ast);


<|start_filename|>compile.js<|end_filename|>
const { SymbolTableImpl, Scope } = require('./symbol-table');
const { ASTNodes } = require('./ast');

const prettier = require("prettier");

const SymbolTable = new SymbolTableImpl();

// Doesn't support lazy evaluation
// so examples like `demo/correct4.lambda` will
// not be transpiled correctly.
const CompileJS = ast => {
  // The empty program compiles to an empty program ''.
  if (!ast) {
    return '';
  }

  // The literals compile to their values.
  if (ast.type === ASTNodes.Literal) {
    return ast.value;

  // The variables compile to the identifiers.
  } else if (ast.type === ASTNodes.Identifier) {
    return ast.name;

  // if-then-else compiles to an if-then-else
  // construct in the target language.
} else if (ast.type === ASTNodes.Condition) {
    const targetCondition = CompileJS(ast.condition);
    const targetThen = CompileJS(ast.then);
    const targetElse = CompileJS(ast.el);
    return `${targetCondition} ? ${targetThen} : ${targetElse}\n`;
  // The abstraction compiles to a function
  // from the target language.
} else if (ast.type === ASTNodes.Abstraction) {
  return `(${ast.arg.id.name} => {
  return ${CompileJS(ast.body)}
})`;

  // IsZero checks if the evaluated value of its
  // expression equals `0`.
  } else if (ast.type === ASTNodes.IsZero) {
    return `${CompileJS(ast.expression)} === 0\n`;

  // The arithmetic operations manipulate the value
  // of their corresponding expressions:
  // - `succ` adds 1.
  // - `pred` substracts 1.
  } else if (ast.type === ASTNodes.Arithmetic) {
    const op = ast.operator;
    const val = CompileJS(ast.expression);
    switch (op) {
      case 'succ':
        return `${val} + 1\n`;
      case 'pred':
        return `(${val} - 1 >= 0) ? ${val} - 1 : 0\n`;
    }

  // The application compiles to:
  // Invocation of the compiled left expression over
  // the compiled right expression.
  } else if (ast.type === ASTNodes.Application) {
    const l = CompileJS(ast.left);
    const r = CompileJS(ast.right);
    return `${l}(${r})\n`;
  } else {
    return '';
  }
};

module.exports.CompileJS = ast =>
  prettier.format(CompileJS(ast), {
    printWidth: 80,
    tabWidth: 2,
    trailingComma: 'none',
    bracketSpacing: true,
    parser: 'babylon'
  });
