A simple implementation of a call-by-value
language with explicit parametric polymorphism
Author: Frank Pfenning, Aug 2020 (v0.1)
Oct 5, 2020 (v0.3)
Oct 12, 2020 (v0.4)

If binary is available (on linux.andrew.cmu.edu, at ~fp/bin)

$ $bindir/lambda examples/comb.lam

To build (requires mlton, see mlton.org)

$ cd src
$ make lambda
$ ./lambda ../examples/comb.lam

See examples/*.{lam,poly,cbv} for some simple examples 

To see options, invoke

$ ./lambda -h

All options can be given on the command line or at the
beginning of the file.  For example,

$ ./lambda --abort=warning <file>.cbv

or in <file>.cbv

#options --abort=warning

Tokens
======

<whitespace> ::= ' ' | \n | \t | \r | \v | \f

<alpha> ::= [a-zA-Z]
<decor> ::= [_']
<digit> ::= [0-9]
<id>    ::= (<alpha> | <decor>) (<alpha> | <decor> | <digit>)*
<tag>   ::= '<id>       [cbv0]
<nat>   ::= <digit>*

Comments
========

single line:        % ... \n
nested delimited:   (* ... *)

Expressions and declarations
============================

Types (in order of precedence)
tau1 tau2         left associative (prec. 4)
*                 right associative (prec. 3)
+                 right associative (prec. 2)
->                right associative (prec. 1)
!a. ?a. $a. 'i    weak prefixes, binding a (max scope) (prec. 0)

Terms (in order of precedence)
e1 e2   e [tau]              left associative, strongest precedence
fold unfold 'i \x. $x. fst snd  prefix
, ;                          right associative, weakest precedence

Tagged and untagged sums and products [cbv3]
  sum('i:tau_i) + sum('j:tau_i) is flattened to sum('k:tau_k)
  otherwise tau + sigma is parsed into ('l:tau) + ('r:sigma)

  prod('i:tau_i) & sum('j:tau_k) is flattened to prod('k:tau_k)
  otherwise tau & sigma is parsed into ('l:tau) & ('r:sigma)

  () stands for empty tagged sum or product sum() or prod()

Type functions and applications can be used only for type definitions
  type d = \a1. ... \an. tau (defines d : type -> ... -> type)
  d tau1 ... taun            (is a type)

<tp> ::= '!' <id> '.' <tp>   % !a. tau
      |  <tp> '->' <tp>      % tau1 -> tau2
      |  <tp> '*' <tp>       % tau1 * tau2 [cbv0]
      |  '1'                 % 1           [cbv0]
      |  <tp> '+' <tp>       % tau1 + tau2 [cbv0]
      |  '0'                 % 0           [cbv0]
      |  <tag> : <tp>        % 'i : tau    [cbv1]
      |  <tp> '&' <tp>       % tau1 & tau2 [cbv3]
      |  '(' ')'             % ()          [cbv3]
      |  '$' <id> '.' <tp>   % $a. tau     [cbv0]
      |  '?' <id> '.' <tp>   % ?a. tau     [cbv2]
      |  '\' <id> '.' <tp>   % \a. tau     [cbv4]
      |  <tp> <tp>           % tau1 tau2   [cbv4]
      |  '(' <tp> ')'        % (tau)
      |  <id>                % a

<exp> ::= <id>                            % x            (variable)
       |  '\' <id> [':' <tp> ] '.' <exp>  % \x. e        (lambda abstraction)
       |  <exp> <exp>                     % e1 e2        (application)
       |  '(' <exp> ')'                   % (e)          (scoping)
       |  '/\' <id> '.' <exp>             % /\a. e       (type abstraction)
       |  <exp> '[' <tp> ']'              % e [tau]      (type application)
       |  <exp> ',' <exp>                 % (e1,e2)      (pair)     [cbv0]
       |  '(' ')'                         % ()           (unit)     [cbv0]
       |  <tag> <exp>                     % 'i e         (tag)      [cbv0/1]
       |  '(|' <record> '|)'              % (| r |)      (record)   [cbv3]
       |  <exp> '.' <tag>                 % e.'i         (projection)    [cbv3]
       |  'case' <exp> 'of' '(' [ <brs> ] ')'  % case e of (brs) (case)  [cbv0]
       |  'fold' <exp>                    % fold e       (fold)     [cbv0]
       |  'unfold' <exp>                  % unfold e     (unfold)   [cbv0]
       |  '[' <tp> ']' ',' <exp>          % ([tau], e)   (pack)     [cbv2]
       |  '$' <id> [ ':' <tp> ] '.' <exp> % $x. e        (fix)      [cbv0]

<brs> ::= <pat> '=>' <exp> [ '|' <brs> ]  % p1 => e1 | ...  [cbv0]

<record> ::= <tag> '=>' <exp> [ '|' <record> ] % 'i1 => e1 | ... [cbv3]

<pat> ::= <id>                            % x            (variable)    [cbv1]
       |  <pat> ',' <pat>                 % (p1,p2)      (pair)        [cbv1]
       |  '(' ')'                         % ()           (unit)        [cbv1]
       |  <tag> <pat>                     % 'i p         (injection)   [cbv1]
       |  'fold' <pat>                    % fold p       (fold)        [cbv1]
       |  '[' <id> ']' ',' <pat>          % ([a],p)      (pack)        [cbv2]
       |  '(' <pat> ')'

<dec> ::= 'type' <id> '=' <tp>             % type a = tau 
       |  'decl' <id> ':' <tp>             % decl x : tau 
       |  'defn' <id> '=' <exp>            % defn x = e
       |  'norm' [ <nat> ] <id> '=' <exp>  % norm x = e  -->  defn x = norm(e)
       |  'conv' <exp> '=' <exp>           % conv e1 = e2     verify norm(e1) = norm(e2)
       |  'fail' <dec>                     % fail <dec>       succeeds if <dec> fails
       |  'eval' [ <nat> ] <id> '=' <exp>  % eval x = e  -->  defn x = eval(e)  [cbv0]

<pragma> ::= '#' ... \n            % for regression testing

<prog> ::= <pragma>* <dec>*

Statics for --lang=cbv
======================

See lecture notes for Lecture 11 at
http://www.cs.cmu.edu/~fp/courses/15814-f20/schedule.html
and information below on --lang=poly

Cases in the grammar above new for [cbv] are marked as such

Variables in patterns or tags in sums may not be repeated

Patterns in a case expression that are not exhaustive
or redundant generate warnings.  With --abort=warning (either
on the command line or at the beginning of a .cbv file with
#options --abort=warning) this will turn into an error.

The bidirectional typechecker uses subtyping, unless
disabled with --subtyping=false).

Statics for --lang=poly
=======================
Signature Sigma refers to all preceding successful declarations and definitions
redefinition is not allowed

Sigma ::= . | Sigma, a = tau | Sigma, x : tau

type a = tau  requires Sigma ; . |- tau type
decl x : tau  requires Sigma ; . |- tau type
defn x = e    requires Sigma ; . |- x : tau and Sigma ; . |- e <= tau
                    or Sigma ; . |- e => tau for some tau
norm x = e    requires Sigma ; . |- x : tau and Sigma ; . |- e <= tau
                    or Sigma ; . |- e => tau for some tau

conv e1 = e2  requires Sigma ; . |- e1 => tau
                   and Sigma ; . |- e2 => tau
              for some tau

Typing
======

Sigma,Gamma ::= . | Gamma, a : type | Gamma, x : tau
All type variables a distinct
All expression variables x distinct
Use alpha-conversion to maintain this presupposition

=========================== 
Judgment Sigma |- Gamma ctx
=========================== 
presupposes . |- Sigma ctx

Sigma |- Gamma ctx
------------------------------
Sigma |- (Gamma, a : type) ctx

Sigma |- Gamma ctx
Sigma ; Gamma |- tau : type
-----------------------------
Sigma |- (Gamma, x : tau) ctx

====================================
Judgment Sigma ; Gamma |- tau : type
==================================== 
presupposes . |- Sigma ctx
    and Sigma |- Gamma ctx

a : type in Sigma
a : type not in Gamma
--------------------------
Sigma ; Gamma |- a : type

a : type in Gamma
------------------------
Sigma ; Gamma |- a : type

Sigma ; Gamma |- tau1 : type
Sigma ; Gamma |- tau2 : type
-------------------------------------
Sigma ; Gamma |- tau1 -> tau2 : type

Sigma ; Gamma, a : type |- tau : type
--------------------------------------
Sigma ; Gamma |- !a. tau : type

=====================================
Judgments Sigma ; Gamma |- e => sigma (e synthesizes sigma)
          Sigma ; Gamma |- e <= tau   (e checks against tau)
===================================== 
presuppose     . |- Sigma ctx
           Sigma |- Gamma ctx
           Sigma ; Gamma |- tau : type
ensure     Sigma ; Gamma |- sigma : type

x : tau in Sigma
x : tau not in Gamma
-------------------------- syn/def
Sigma ; Gamma |- x => tau

x : tau in Gamma
-------------------------- syn/var
Sigma ; Gamma |- x => tau

Sigma ; Gamma |- e1 => tau2 -> tau1
Sigma ; Gamma |- e2 <= tau2
------------------------------------ syn/app
Sigma ; Gamma |- e1 e2 => tau1

[Sigma ; Gamma |- tau1' = tau1]
Sigma ; Gamma, x1 : tau1 |- e2 <= tau2 
-------------------------------------------------- chk/lam
Sigma ; Gamma |- \x1 [:tau1']. e2 <= tau1 -> tau2 

Sigma ; Gamma |- tau1 type
Sigma ; Gamma |- e2 <= tau2 
------------------------------------------------ syn/lam
Sigma ; Gamma |- \x1 : tau1. e2 => tau1 -> tau2 

Sigma ; Gamma, a : type |- e <= tau 
------------------------------------ chk/tplam
Sigma ; Gamma |- /\a. e <= !a. tau 

Sigma ; Gamma, a : type |- e => tau 
------------------------------------ syn/tplam
Sigma ; Gamma |- /\a. e => !a. tau 

Sigma ; Gamma |- e => !a. sigma
Sigma ; Gamma |- tau : type
---------------------------------------- syn/tpapp
Sigma ; Gamma |- e[tau] => [tau/a]sigma

Sigma ; Gamma |- e => tau'
Sigma ; Gamma |- tau' = tau
--------------------------- chk/syn
Sigma ; Gamma |- e <= tau

Examples
========

--lang=lam

defn K = \x. \y. x
defn S = \x. \y. \z. x z (y z)
defn I = \x. x
conv S K K = I

% confirming associativity
conv S = \x. \y. \z. (x z) (y z)

% cannot omit the pair of parentheses
fail conv S = \x. \y. \z. x z y z

--lang=poly

% with type synthesis
type bool = !a. a -> a -> a

defn true = /\a. \x:a. \y:a. x
defn false = /\a. \x:a. \y:a. y

% with type declarations
type nat = !a. (a -> a) -> a -> a

decl zero : nat
decl succ : nat -> nat

defn zero = /\a. \s. \z. z
defn succ = \n. /\a. \s. \z. s (n [a] s z)

decl plus : nat -> nat -> nat
defn plus = \n. \k. n [nat] succ k

decl times : nat -> nat -> nat
defn times = \n. \k. n [nat] (plus k) zero

type nat2 = !c. (nat -> nat -> c) -> c

decl pair : nat -> nat -> nat2
defn pair = \x. \y. /\c. \p. p x y

decl pred2 : nat -> nat2
defn pred2 = \n. n [nat2] (\p. p [nat2] (\x. \y. pair (succ x) x)) (pair zero zero)

decl pred : nat -> nat
defn pred = \n. pred2 n [nat] (\x. \y. y)


Statics for --lang=lam
======================
Vars refers to all preceding successfully defined variables
redefinition is not allowed

Vars ::= . | Vars, x     (x <> '_', all x distinct)

type a = tau  disallowed
decl x : tau  disallowed

defn x = e    requires Vars ; . |- e closed  (unless x = '_')
norm x = e    requires Vars ; . |- e closed  (unless x = '_')
conv e1 = e2
