├── README.md
├── code
├── closure.ml
├── datatypes.ml
├── goleta.dl
├── highorder.ml
└── recursion.ml
├── homework
├── README.md
├── hw1
│ ├── .ocamlformat
│ ├── README.md
│ ├── dune-project
│ ├── hw1.opam
│ ├── lib
│ │ ├── dune
│ │ ├── part1.ml
│ │ ├── part1.mli
│ │ ├── part2.ml
│ │ ├── part2.mli
│ │ ├── part3.ml
│ │ ├── part3.mli
│ │ ├── part4.ml
│ │ ├── part4.mli
│ │ └── util.ml
│ ├── res
│ │ ├── programs.001.png
│ │ ├── programs.002.png
│ │ ├── programs.003.png
│ │ └── programs.004.png
│ ├── sol.md
│ └── test
│ │ ├── dune
│ │ ├── hw1_test.ml
│ │ ├── part1.ml
│ │ ├── part2.ml
│ │ ├── part3.ml
│ │ ├── part4.ml
│ │ └── utils.ml
├── hw2
│ ├── .ocamlformat
│ ├── Makefile
│ ├── README.md
│ ├── bin
│ │ ├── dune
│ │ └── repl.ml
│ ├── dune-project
│ ├── hw2.opam
│ ├── lib
│ │ ├── part1
│ │ │ ├── dune
│ │ │ ├── part1.ml
│ │ │ └── part1.mli
│ │ └── part2
│ │ │ ├── ast.ml
│ │ │ ├── dune
│ │ │ ├── err.ml
│ │ │ ├── eval.ml
│ │ │ ├── eval.mli
│ │ │ ├── examples
│ │ │ ├── add.lp
│ │ │ ├── apply.lp
│ │ │ └── true.lp
│ │ │ ├── parse_util.ml
│ │ │ ├── parser.mly
│ │ │ ├── scanner.mll
│ │ │ ├── vars.ml
│ │ │ └── vars.mli
│ ├── sol.md
│ └── test
│ │ ├── dune
│ │ ├── hw2_test.ml
│ │ ├── test_part1.ml
│ │ └── test_part2.ml
├── hw3
│ ├── .ocamlformat
│ ├── Makefile
│ ├── README.md
│ ├── bin
│ │ ├── dune
│ │ └── repl.ml
│ ├── dune-project
│ ├── hw3.opam
│ ├── lamp
│ │ ├── ast.ml
│ │ ├── cmd.ml
│ │ ├── dune
│ │ ├── err.ml
│ │ ├── eval.ml
│ │ ├── eval.mli
│ │ ├── lamp.opam
│ │ ├── lexer.mll
│ │ ├── menhir_parser.mly
│ │ ├── parser.ml
│ │ ├── vars.ml
│ │ └── vars.mli
│ └── test
│ │ ├── dune
│ │ ├── examples
│ │ ├── add_n.lp
│ │ ├── fib.lp
│ │ ├── fib_int.lp
│ │ ├── max_ext.lp
│ │ ├── mutual_rec_int.lp
│ │ ├── primes.lp
│ │ └── primes_internal.lp
│ │ ├── hw3_test.ml
│ │ └── test_lamp.ml
├── hw4
│ ├── .ocamlformat
│ ├── README.md
│ ├── bin
│ │ ├── dune
│ │ └── repl.ml
│ ├── dune-project
│ ├── hw4.opam
│ ├── lamp
│ │ ├── ast.ml
│ │ ├── cmd.ml
│ │ ├── dune
│ │ ├── err.ml
│ │ ├── eval.ml
│ │ ├── eval.mli
│ │ ├── lexer.mll
│ │ ├── menhir_parser.mly
│ │ ├── parser.ml
│ │ ├── typecheck.ml
│ │ ├── typecheck.mli
│ │ ├── vars.ml
│ │ └── vars.mli
│ └── test
│ │ ├── dune
│ │ ├── examples
│ │ ├── add_n.lp
│ │ └── fib.lp
│ │ ├── hw4_test.ml
│ │ └── test_typing.ml
├── hw5
│ ├── .ocamlformat
│ ├── Makefile
│ ├── README.md
│ ├── bin
│ │ ├── dune
│ │ └── repl.ml
│ ├── dune-project
│ ├── hw5.opam
│ ├── lamp
│ │ ├── ast.ml
│ │ ├── cmd.ml
│ │ ├── curry_howard.ml
│ │ ├── curry_howard.mli
│ │ ├── dune
│ │ ├── err.ml
│ │ ├── eval.ml
│ │ ├── eval.mli
│ │ ├── lexer.mll
│ │ ├── menhir_parser.mly
│ │ ├── parser.ml
│ │ ├── typecheck.ml
│ │ ├── typecheck.mli
│ │ ├── typeinfer.ml
│ │ ├── typeinfer.mli
│ │ ├── vars.ml
│ │ └── vars.mli
│ └── test
│ │ ├── dune
│ │ ├── examples
│ │ ├── add_n.lp
│ │ ├── fib.lp
│ │ ├── foldr.lp
│ │ ├── length.lp
│ │ ├── length_poly.lp
│ │ └── map_filter.lp
│ │ ├── hw5_test.ml
│ │ ├── test_typing.ml
│ │ └── test_utils.ml
└── lamp.pdf
├── lectures
├── 2-26-note.pdf
├── CS162-Curry-Howard-Isomorphism.pdf
├── CS162-Final-Review-Programming-Languages.pdf
├── curry_howard.ml
├── final-review-note.pdf
├── final-review.pdf
├── inference-rules.pdf
├── lambda-plus.pdf
├── lecture1.key
├── lecture1.pdf
├── lecture10.key
├── lecture10.pdf
├── lecture11.key
├── lecture11.pdf
├── lecture12.key
├── lecture12.pdf
├── lecture13.key
├── lecture13.pdf
├── lecture14.key
├── lecture14.pdf
├── lecture15.key
├── lecture15.pdf
├── lecture16.pdf
├── lecture2.key
├── lecture2.pdf
├── lecture3.key
├── lecture3.pdf
├── lecture4.key
├── lecture4.pdf
├── lecture5.key
├── lecture5.pdf
├── lecture6.key
├── lecture6.pdf
├── lecture7.key
├── lecture7.pdf
├── lecture8.key
├── lecture8.pdf
├── lecture9.key
├── lecture9.pdf
├── midterm-review.pdf
└── win25-2-13-note.pdf
└── sections
├── sec01
├── install.md
└── notes.md
└── sec02
└── notes.pdf
/README.md:
--------------------------------------------------------------------------------
1 | # CS162 Programming Languages
2 |
3 | Programming Languages are the bricks, mortar and steel of the information age. Over the last thirty years, a variety of languages with diverse features have been developed, expressive radically different perspectives of the idea of computation. CS 162 is an introduction to these different perspectives, the fundamental concepts of languages, and how modern language techniques and idioms can be used to engineer software systems. For this purpose, we will learn how to build a new programming language, λ+, from scratch, including its operational semantics, type checking, type inference, and correctness.
4 |
5 | # Logistics
6 | Instructor: Yu Feng (yufeng@cs.ucsb.edu)
7 | - Office hour: Tue, 9am-10am (HFH 2157)
8 |
9 | Class: Mon, Wed, 5pm, Theater & Dance West, 1701
10 |
11 | Discussion sections (Friday):
12 | - 10-10:50am, GIRV 2128
13 | - 11-11:50am, GIRV 2128
14 | - 12-12:50pm, GIRV 1115
15 | - 1-1:50pm, GIRV 1112
16 |
17 | TAs:
18 | - Junrui Liu ([junrui@ucsb.edu](mailto:junrui@ucsb.edu))
19 | - Office hours: Monday 3-4pm and Thursday 10:30-11:30am at TA Trailer Room 103 ([map](https://ucsb-cs8.github.io/w20/info/mentorPhotos/trailer-map.png))
20 | - Hongbo Wen ([hongbowen@ucsb.edu](mailto:hongbowen@ucsb.edu))
21 | - Office hours: Thursday 2-3pm at CSIL
22 | - Hanzhi Liu ([hanzhi@ucsb.edu](mailto:hanzhi@ucsb.edu))
23 | - Office hours: Thursday 3-4pm at CSIL
24 |
25 | Reader:
26 | - Jiaming Shan
27 |
28 |
29 | Textbook (optional): [Types and Programming Languages](https://www.amazon.com/Types-Programming-Languages-MIT-Press/dp/0262162091)
30 |
31 | # Schedule
32 | | Date | Topic | Slides | Read | Out | Due |
33 | | ---------------- | -------------------------- | ------------------------------- | ---------------- | ---------------------- | --- |
34 | | 1/6 | Hello, World! | [lec1](lectures/lecture1.pdf) | | | |
35 | | 1/8 | OCaml crash course I | [lec2](lectures/lecture2.pdf) | | | |
36 | | 1/13 | OCaml crash course II | [lec3](lectures/lecture3.pdf) | | [hw1](./homework/hw1/) | |
37 | | 1/15 | OCaml crash course III | [lec4](lectures/lecture4.pdf) | | | |
38 | | 1/20 (MLK) | No class | | | | |
39 | | 1/22 | Lambda Calculus I | [lec5](lectures/lecture5.pdf) | | [hw2](./homework/hw2) | hw1 |
40 | | 1/27 | Lambda Calculus II | [lec6](lectures/lecture6.pdf) | 8.1,8.2, 9.1-9.3 | | |
41 | | 1/29 | (Cancelled) | | | | |
42 | | 2/3 | λ+ | | | | |
43 | | 2/5 | Operational Semantics I | [lec7](lectures/lecture7.pdf) | 10.3 | | |
44 | | 2/10 | Operational Semantics II | [lec8](lectures/lecture8.pdf) | | | |
45 | | 2/12 | Type Checking | [lec9](lectures/lecture9.pdf) | | [hw3](./homework/hw3) | hw2 |
46 | | 2/17 (President) | No class | | | | |
47 | | 2/19 | Type Checking (continued) | [lec10](lectures/lecture10.pdf) | 22.1-22.4 | | |
48 | | 2/24 | Type Inference | [lec11](lectures/lecture11.pdf) | | [hw4](./homework/hw4) | hw3 |
49 | | 2/26 | Type Inference (continued) | [note](lectures/2-26-note.pdf) | | | |
50 | | 3/3 | Polymorphism | [lec12](lectures/lecture12.pdf) | | | |
51 | | 3/5 | Polymorphism (continued) | | 22.7 | [hw5](./homework/hw5) | hw4 |
52 | | 3/10 | Curry Howard isomorphism | [CHI](lectures/CS162-Curry-Howard-Isomorphism.pdf) | | | |
53 | | 3/12 | Final Review | [lecture](lectures/CS162-Final-Review-Programming-Languages.pdf) | | | |
54 | | 3/12 | Final Problems | [note](lectures/final-review-note.pdf) | | | |
55 | | 3/18 | Final | | | | |
56 | | 3/21 | | | | | hw5 |
57 |
58 | # Grading
59 |
60 | 1. 5 Programming Assignments: 60%
61 | 2. Final Exam: 40%
62 | 3. Class Participation:1%
63 |
64 | Below is the grading system used by CS162 (No curving).
65 |
66 | | Letter | Percentage |
67 | | ------ | ---------- |
68 | | A | 93–100% |
69 | | A- | 85–92% |
70 | | B+ | 80–84% |
71 | | B | 75–79% |
72 | | B- | 70–74% |
73 | | C+ | 65–69% |
74 | | C | 60–64% |
75 | | F | <60% |
76 |
77 | Credit: https://en.wikipedia.org/wiki/Academic_grading_in_the_United_States
78 |
79 |
80 | # Useful resources
81 |
82 | You will find the [λ+ materials](./homework/lamp.pdf) very helpful during
83 | this course.
84 |
85 | These resources are helpful for learning OCaml:
86 |
87 | 1. [OCaml From the Ground Up](https://ocamlbook.org/): this is a good
88 | step-by-step introduction to OCaml.
89 | 2. [Real World OCaml](https://dev.realworldocaml.org/guided-tour.html): a
90 | comprehensive guide on OCaml: how to use it, the ecosystem and tooling, and
91 | common libraries.
92 | 3. [The OCaml system](https://ocaml.org/releases/4.11/htmlman/index.html): the
93 | official user manual for OCaml. Part I is helpful for seeing examples of what
94 | OCaml has to offer. You may also want to look at Part III, Chapter 17 for
95 | information on how to use the debugger.
96 | 4. [OCaml official documentation](https://ocaml.org/learn/)
97 | 5. [Learning OCaml in Y mins](https://learnxinyminutes.com/docs/ocaml/)
98 |
99 |
100 | # Academic Integrity
101 | - Cheating WILL be taken seriously. It is not fair toward honest students to take cheating lightly, nor is it fair to the cheater to let him/her go on thinking that cheating is a reasonable alternative in life.
102 | - The following is not considered cheating:
103 | - discussing broad ideas about programming assignments in groups, without being at a computer (with code-writing and debugging done individually, later).
104 | - The following is considered cheating:
105 | - discussing programming assignments with someone who has already completed the problem, or looking at their completed solution.
106 | - looking at anyone else’s solution
107 | - Previous versions of the class.
108 | - leaving your code (for example in an online repository) visible to others, leading others to look at your solution.
109 | - receiving, providing, or soliciting assistance from unauthorized sources during a test.
110 | - Programming assignments are not intended to be grade-makers, but to prepare you for the tests, which are the grade-makers. Cheating on the programming assignment is not only unethical, but shows a fundamental misunderstanding of the purpose of these assignments.
111 | - Penalties: First time: a zero for the assignment; Second time: an “F” in the course.
112 |
113 |
--------------------------------------------------------------------------------
/code/closure.ml:
--------------------------------------------------------------------------------
1 | let x = 1;;
2 | let f y = x + y;;
3 | let x = 2;;
4 | let y = 3;;
5 | f (x + y);;
6 |
7 |
8 |
9 | let x = 1;;
10 | let f y =
11 | let x = 2 in
12 | fun z -> x + y + z
13 | ;;
14 |
15 | let x = 100;;
16 | let g = (f 4);;
17 | let y = 100;;
18 | (g 1);;
19 |
--------------------------------------------------------------------------------
/code/datatypes.ml:
--------------------------------------------------------------------------------
1 | (* DataTypes *)
2 |
3 |
4 | type attrib =
5 | | Name of string
6 | | Age of int
7 | | DOB of int * int * int
8 | | Address of string
9 | | Height of float
10 | | Alive of bool
11 | | Email of string
12 | ;;
13 |
14 | let a1 = Name "Bob";;
15 |
16 | let a2 = Height 5.83;;
17 |
18 | let year = 1977 ;;
19 |
20 | let a3 = DOB (9,8,year) ;;
21 |
22 | let a_l = [a1;a2;a3];;
23 |
24 |
25 |
26 |
27 |
28 |
29 | let a1 = (Alive false);;
30 | let a1 = (Name "Bob");;
31 | let a1 = (Age 11);;
32 | match a1 with
33 | | Name s -> 0
34 | | Age i -> i
35 | | _ -> 10;;
36 |
37 |
38 |
39 |
40 | match (Name "Hi") with
41 | | Name s -> (Printf.printf "Hello %s\n" s;0)
42 | | Age i -> (Printf.printf "%d years old\n" i;0)
43 | | _ -> (Printf.printf "\n"; 0)
44 | ;;
45 |
46 |
47 |
48 |
49 |
50 | match (Age 10) with
51 | | Age i when i < 10 -> Printf.sprintf "%d (young)" i
52 | | Age i -> Printf.sprintf "%d (older)" i
53 | | Email s -> Printf.sprintf "%s" s
54 | | _ -> ""
55 | ;;
56 |
57 |
58 | let to_str a =
59 | match a with
60 | | Name s -> s
61 | | Age i -> Printf.sprintf "%d" i
62 | | DOB (d,m,y) -> Printf.sprintf "%d / %d / %d" d m y
63 | | Address addr -> Printf.sprintf "%s" addr
64 | | Height h -> Printf.sprintf "%f" h
65 | | Alive b -> Printf.sprintf "%b" b
66 | | Email e -> Printf.sprintf "%s" e
67 | ;;
68 |
69 |
70 |
71 | type nat =
72 | | Z
73 | | S of nat;;
74 |
75 |
76 |
77 |
78 | Z;; (* represents 0 *)
79 | S Z;; (* represents 1 *)
80 | S (S Z);; (* represents 2 *)
81 |
82 |
83 |
84 |
85 |
86 |
87 | let rec plus n m =
88 | match n with
89 | | Z -> m
90 | | S n' -> S (plus n' m);;
91 |
92 |
93 |
94 | plus (S (S Z)) (S Z);;
95 |
96 |
97 |
98 |
99 |
100 |
101 | type int_list =
102 | | Nil
103 | | Cons of (int * int_list) ;;
104 |
105 |
106 | let l = Cons (10, Cons (10, Cons (10, Nil)));;
107 |
108 |
109 | let rec length l =
110 | match l with
111 | | Nil -> 0
112 | | Cons (i,t) -> 1 + length t;;
113 |
114 | let rec length l =
115 | match l with
116 | | [] -> 0
117 | | h::t -> 1+length t;;
118 |
119 |
120 | let max x y = if x > y then x else y;;
121 |
122 |
123 |
124 |
125 | let rec list_max xs =
126 | match xs with
127 | | Nil -> 0
128 | | Cons (x, xs') -> max x (list_max xs');;
129 |
130 | let rec list_max xs =
131 | match xs with
132 | | [] -> 0
133 | | x::xs' -> max x (list_max xs');;
134 |
--------------------------------------------------------------------------------
/code/goleta.dl:
--------------------------------------------------------------------------------
1 | .decl sunny(c:symbol)
2 | .decl hot(c:symbol)
3 | .decl dry(c:symbol)
4 |
5 | .output dry
6 |
7 | sunny("goleta").
8 | sunny("ucsb").
9 | hot("ucsb").
10 |
11 | dry(c) :- sunny(c), hot(c).
12 |
--------------------------------------------------------------------------------
/code/highorder.ml:
--------------------------------------------------------------------------------
1 | let max x y = if x < y then y else x;;
2 |
3 | (* return max element of list l *)
4 | let list_max l =
5 | let rec l_max l =
6 | match l with
7 | [] -> 0
8 | | h::t -> max h (l_max t)
9 | in
10 | l_max l;;
11 |
12 |
13 | let list_max2 l =
14 | let rec helper cur l =
15 | match l with
16 | [] -> cur
17 | | h::t -> helper (max cur h) t
18 | in
19 | helper 0 l;;
20 |
21 |
22 | (* concatenate all strings in a list *)
23 | let concat l =
24 | let rec helper cur l =
25 | match l with
26 | [] -> cur
27 | | h::t -> helper (cur ^ h) t
28 | in
29 | helper "" l;;
30 |
31 | (* fold, the coolest function! *)
32 | let rec fold f cur l =
33 | match l with
34 | [] -> cur
35 | | h::t -> fold f (f cur h) t;;
36 |
37 | let list_max = fold max 0 [1;2;3];;
38 |
39 | let concat = fold (^) "" ["a";"b";"c"];;
40 |
41 |
42 | let rec map f l =
43 | match l with
44 | [] -> []
45 | | h::t -> (f h)::(map f t);;
46 |
47 |
48 | let incr x = x+1;;
49 |
50 | let map_incr = map incr;;
51 |
52 | map_incr [1;2;3];;
53 |
54 | let compose f1 f2 = fun x -> (f1 (f2 x));;
55 |
56 | let map_incr_2 = compose map_incr map_incr;;
57 | map_incr_2 [1;2;3];;
58 |
59 | let map_incr_3 = compose map_incr map_incr_2;;
60 | map_incr_3 [1;2;3];;
61 |
--------------------------------------------------------------------------------
/code/recursion.ml:
--------------------------------------------------------------------------------
1 | type tree =
2 | Leaf of int
3 | | Node of tree*tree;;
4 |
5 |
6 | Node(Node(Leaf 1, Leaf 2), Leaf 3);;
7 |
8 |
9 | let rec sum_leaf t =
10 | match t with
11 | | Leaf n -> n
12 | | Node(t1,t2) -> (sum_leaf t1) + (sum_leaf t2);;
13 |
14 |
15 | sum_leaf (Node(Node(Leaf 1, Leaf 2), Leaf 3));;
16 |
17 | let rec fact n =
18 | if n<=0
19 | then 1
20 | else n * fact (n-1);;
21 |
22 | fact 3;;
23 |
24 | let fact2 x =
25 | let rec helper x curr =
26 | if x <= 0
27 | then curr
28 | else helper (x - 1) (x * curr)
29 | in
30 | helper x 1;;
31 |
32 | fact2 3;;
33 |
34 |
35 |
--------------------------------------------------------------------------------
/homework/README.md:
--------------------------------------------------------------------------------
1 | ## CS162 Programming Assignments
2 |
3 | The main goal of the programming assignments in CS162 is to build λ+, a small programming language from scratch.
4 |
5 | 1. [Grab your weapon! OCaml in action](hw1/)
6 |
7 | Since you will use OCaml to implement your λ+ programming language, in this assignment, you will complete some basic programming tasks (e.g., recursions, pattern matching, data types, etc.) in OCaml as the warm-up.
8 |
9 |
10 | 2. [Higher order functions in OCaml + Interpreting λ+](hw2/)
11 |
12 | In this assignment, you will first practice defining and using higher order functions in OCaml. Next, you will implement an interpreter for a subset of λ+ based on its operational semantics.
13 |
14 | 3. [Interpreting λ+ (continued)](hw3/)
15 |
16 | In this assignment, you will finish the interpreter for the full λ+ language.
17 |
18 |
19 | 4. [Type check your λ+ program](hw4/)
20 |
21 | In this assignment, you will write a type checker for your λ+ program such that your checker can reject ill-typed λ+ programs.
22 |
23 | 5. [Infer the types for your λ+](hw5/)
24 |
25 | In this assignment, you will implement the type inference algorithm that we learn in the class.
26 |
27 |
30 |
31 |
35 |
--------------------------------------------------------------------------------
/homework/hw1/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.26.2
3 |
--------------------------------------------------------------------------------
/homework/hw1/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.11)
2 |
3 | (name hw1)
4 |
5 | (generate_opam_files true)
6 |
7 | (source
8 | (github username/reponame))
9 |
10 | (authors "Author Name")
11 |
12 | (maintainers "Maintainer Name")
13 |
14 | (documentation https://url/to/documentation)
15 |
16 | (package
17 | (name hw1)
18 | (allow_empty)
19 | (synopsis "A short synopsis")
20 | (description "A longer description")
21 | (depends
22 | (ocaml
23 | (= 5.1.1))
24 | dune
25 | base
26 | alcotest
27 | ppx_deriving
28 | ppx_jane
29 | ppx_import
30 | utop
31 | fmt)
32 | (tags
33 | (topics "to describe" your project)))
34 |
35 | ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
36 |
--------------------------------------------------------------------------------
/homework/hw1/hw1.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | synopsis: "A short synopsis"
4 | description: "A longer description"
5 | maintainer: ["Maintainer Name"]
6 | authors: ["Author Name"]
7 | tags: ["topics" "to describe" "your" "project"]
8 | homepage: "https://github.com/username/reponame"
9 | doc: "https://url/to/documentation"
10 | bug-reports: "https://github.com/username/reponame/issues"
11 | depends: [
12 | "ocaml" {= "5.1.1"}
13 | "dune" {>= "3.11"}
14 | "base"
15 | "alcotest"
16 | "ppx_deriving"
17 | "ppx_jane"
18 | "ppx_import"
19 | "utop"
20 | "fmt"
21 | "odoc" {with-doc}
22 | ]
23 | build: [
24 | ["dune" "subst"] {dev}
25 | [
26 | "dune"
27 | "build"
28 | "-p"
29 | name
30 | "-j"
31 | jobs
32 | "@install"
33 | "@runtest" {with-test}
34 | "@doc" {with-doc}
35 | ]
36 | ]
37 | dev-repo: "git+https://github.com/username/reponame.git"
38 |
--------------------------------------------------------------------------------
/homework/hw1/lib/dune:
--------------------------------------------------------------------------------
1 | (env
2 | (dev
3 | (flags
4 | (:standard -warn-error -A))))
5 |
6 | (library
7 | (name hw1)
8 | (libraries base fmt)
9 | (preprocess
10 | (pps ppx_deriving.show))
11 | (modules util part1 part2 part3 part4))
12 |
--------------------------------------------------------------------------------
/homework/hw1/lib/part1.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Util
3 |
4 | let rec fib (n : int) : int = todo ()
5 |
--------------------------------------------------------------------------------
/homework/hw1/lib/part1.mli:
--------------------------------------------------------------------------------
1 | val fib : int -> int
2 |
--------------------------------------------------------------------------------
/homework/hw1/lib/part2.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Util
3 |
4 | let rec compress (equal : 'a -> 'a -> bool) (xs : 'a list) : 'a list = todo ()
5 | let max (xs : int list) : int option = todo ()
6 | let rec join (xs : 'a option list) : 'a list option = todo ()
7 |
8 | let insert (key : 'k) (value : 'v) (dict : ('k * 'v) list) : ('k * 'v) list =
9 | (key, value) :: dict
10 |
11 | let rec lookup (equal : 'k -> 'k -> bool) (key : 'k) (dict : ('k * 'v) list) :
12 | 'v option =
13 | todo ()
14 |
--------------------------------------------------------------------------------
/homework/hw1/lib/part2.mli:
--------------------------------------------------------------------------------
1 | val compress : ('a -> 'a -> bool) -> 'a list -> 'a list
2 |
3 | val max : int list -> int option
4 |
5 | val join : 'a option list -> 'a list option
6 |
7 | val insert : 'k -> 'v -> ('k * 'v) list -> ('k * 'v) list
8 |
9 | val lookup : ('k -> 'k -> bool) -> 'k -> ('k * 'v) list -> 'v option
10 |
--------------------------------------------------------------------------------
/homework/hw1/lib/part3.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Util
3 |
4 | type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree [@@deriving show]
5 |
6 | let rec equal_tree (equal : 'a -> 'a -> bool) (t1 : 'a tree) (t2 : 'a tree) :
7 | bool =
8 | todo ()
9 |
10 | let timestamp (t : 'a tree) : (int * 'a) tree = todo ()
11 |
--------------------------------------------------------------------------------
/homework/hw1/lib/part3.mli:
--------------------------------------------------------------------------------
1 | type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree [@@deriving show]
2 |
3 | val equal_tree : ('a -> 'a -> bool) -> 'a tree -> 'a tree -> bool
4 | val timestamp : 'a tree -> (int * 'a) tree
5 |
--------------------------------------------------------------------------------
/homework/hw1/lib/part4.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Util
3 |
4 | type expr =
5 | | Const of int
6 | | X
7 | | Add of expr * expr
8 | | Mul of expr * expr
9 | | Compose of expr * expr
10 |
11 | (* Pretty-printer *)
12 | let rec pp_expr ppf =
13 | let open Fmt in
14 | function
15 | | Const n -> int ppf n
16 | | X -> string ppf "x"
17 | | Add (e1, e2) -> pf ppf "@[(%a + %a)@]" pp_expr e1 pp_expr e2
18 | | Mul (e1, e2) -> pf ppf "@[(%a * %a)@]" pp_expr e1 pp_expr e2
19 | | Compose (e1, e2) -> pf ppf "@[(%a; %a)@]" pp_expr e1 pp_expr e2
20 |
21 | (* Convert an expression into a pretty string *)
22 | let show_expr (e : expr) : string = Fmt.to_to_string pp_expr e
23 | let rec eval_expr (x : int) (e : expr) : int = todo ()
24 | let rec simplify (e : expr) : expr = todo ()
25 |
26 | type poly = int list [@@deriving show]
27 |
28 | let rec eval_poly (x : int) (p : poly) : int = bonus ()
29 | let rec normalize (e : expr) : poly = bonus ()
30 | let semantic_equiv (e1 : expr) (e2 : expr) : bool = bonus ()
31 |
--------------------------------------------------------------------------------
/homework/hw1/lib/part4.mli:
--------------------------------------------------------------------------------
1 | type expr =
2 | | Const of int
3 | | X
4 | | Add of expr * expr
5 | | Mul of expr * expr
6 | | Compose of expr * expr
7 |
8 | val pp_expr : expr Fmt.t
9 | val eval_expr : int -> expr -> int
10 | val simplify : expr -> expr
11 |
12 | type poly = int list
13 |
14 | val pp_poly : poly Fmt.t
15 | val eval_poly : int -> poly -> int
16 | val normalize : expr -> poly
17 | val semantic_equiv : expr -> expr -> bool
18 |
--------------------------------------------------------------------------------
/homework/hw1/lib/util.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | let todo () = failwith "TODO"
4 | let bonus () = failwith "Bonus"
5 |
6 | let show_list (show : 'a -> string) (xs : 'a list) : string =
7 | Fmt.to_to_string (Fmt.list (Fmt.of_to_string show)) xs
8 |
9 | let show_option (show : 'a -> string) (x : 'a option) : string =
10 | match x with None -> "None" | Some x -> "Some " ^ show x
11 |
--------------------------------------------------------------------------------
/homework/hw1/res/programs.001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/homework/hw1/res/programs.001.png
--------------------------------------------------------------------------------
/homework/hw1/res/programs.002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/homework/hw1/res/programs.002.png
--------------------------------------------------------------------------------
/homework/hw1/res/programs.003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/homework/hw1/res/programs.003.png
--------------------------------------------------------------------------------
/homework/hw1/res/programs.004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/homework/hw1/res/programs.004.png
--------------------------------------------------------------------------------
/homework/hw1/sol.md:
--------------------------------------------------------------------------------
1 | # Solutions to HW1 Exercises
2 |
3 |
4 | ## Part 1
5 |
6 | ### Problem 2
7 | 1. Say we have `n : int`. Then `Some n : int option`.
8 | 2. `None : int option`
9 | 3. Yes. Say we have `x : t`. Then `Some x : t option`. Or `None: t option`.
10 | 4. Yes. `None : t option`.
11 | 5. Not always. We can get a `t` out of a `t option` if and only if `t option` is the case of `Some x`, in which case we can extract `x : t`. If you want to define a function of type `t option -> t`, you'd also have to provide a "default" value in case `t option` is `None`, so you would end up defining `t -> t option -> t`. In fact, the generic function `'a option -> 'a` is not definable as `'a` can be anything, and there is no way to provide a default value for any `'a`.
12 |
13 |
14 | ### Problem 3
15 | 1. Say we have `n : int`, `b : bool`, and `s : string`, then `(n, b, s) : int * bool * string`.
16 | 2. Say we have `x : t1` and `y : t2`:
17 | 1. Yes, with `(x, y) : t1 * t2`.
18 | 2. Yes, with `(y, x) : t2 * t1`.
19 | 3. Yes, with `(y, x, x, y, x) : t2 * t1 * t1 * t2 * t1`.
20 | 3. Yes. Say `p : t1 * t2`. You can pattern match on `p` as in `match p with (x, y) -> ...` or `let (x, y) = p in ...`.
21 | ```
22 | ## Part 4
23 |
24 | ### Problem 0
25 | 1. With x = 1,
26 | ```ocaml
27 | (3 * (4 + x); 1 + x + 5) * 2; 100 * x
28 | == (3 * (4 + 1); 1 + x + 5) * 2; 100 * x
29 | == (3 * 5; 1 + x + 5) * 2; 100 * x
30 | == (15; 1 + x + 5) * 2; 100 * x
31 | == ( 1 + 15 + 5) * 2; 100 * x
32 | == 21 * 2; 100 * x
33 | == 42; 100 * x
34 | == 4200
35 | ```
36 | 2. Here's one possible parse:
37 | ```
38 | Compose(
39 | Mul(
40 | Compose(
41 | Add(
42 | Mul(Const 3, Const 4),
43 | X
44 | ),
45 | Add(
46 | Add(
47 | Const 1,
48 | X
49 | ),
50 | Const 5
51 | )
52 | ),
53 | Const 2
54 | ),
55 | Mul(
56 | Const 100,
57 | X
58 | )
59 | )
60 | ```
61 |
62 | 3. Potential sources of ambiguity:
63 | - `*` has higher precedence than `+`
64 | - It's not clear whether `*` and `+` are left-associative or right-associative
65 | - It is not clear what is the precedence of `;`.
66 |
67 |
--------------------------------------------------------------------------------
/homework/hw1/test/dune:
--------------------------------------------------------------------------------
1 | (test
2 | (name hw1_test)
3 | (libraries fmt alcotest hw1)
4 | (preprocess
5 | (staged_pps ppx_import ppx_deriving.show ppx_deriving.eq)))
6 |
--------------------------------------------------------------------------------
/homework/hw1/test/hw1_test.ml:
--------------------------------------------------------------------------------
1 | (* open Base *)
2 |
3 | let () =
4 | Alcotest.run "hw1"
5 | (List.map
6 | (fun (name, tests) ->
7 | (name, List.map (Alcotest.test_case name `Quick) tests))
8 | (Part1.tests @ Part2.tests @ Part3.tests @ Part4.tests))
9 |
--------------------------------------------------------------------------------
/homework/hw1/test/part1.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Hw1.Part1
3 |
4 | let test_fib = Utils.test_io Alcotest.int "same int" fib
5 |
6 | (** A list of (input, output) pairs *)
7 | let fib_tests = [ (10, 55) (* add your tests here *) ]
8 |
9 | let tests = [ ("fib", List.map ~f:test_fib fib_tests) ]
10 |
--------------------------------------------------------------------------------
/homework/hw1/test/part2.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Hw1.Part2
3 |
4 | let test_compress =
5 | Utils.test_io Alcotest.(list string) "same list" (compress String.equal)
6 |
7 | let test_max = Utils.test_io Alcotest.(option int) "same int" max
8 | let test_join = Utils.test_io Alcotest.(option (list int)) "same list" join
9 |
10 | let test_lookup =
11 | Utils.test_io
12 | Alcotest.(option string)
13 | "same string"
14 | (Utils.uncurry (lookup Int.equal))
15 |
16 | (** A list of (input, output) pairs *)
17 | let compress_tests = [ ([ "a"; "a" ], [ "a" ]) (* add your tests here *) ]
18 |
19 | (** A list of (input, output) pairs *)
20 | let max_tests = [ ([ 1; 2 ], Some 2) (* add your tests here *) ]
21 |
22 | (** A list of (input, output) pairs *)
23 | let join_tests =
24 | [ ([ Some 1; Some 2 ], Some [ 1; 2 ]) (* add your tests here *) ]
25 |
26 | (** A list of ((input key * input dict) * output) pairs *)
27 | let lookup_tests = [ ((1, [ (1, "hi") ]), Some "hi") (* add your tests here *) ]
28 |
29 | let tests =
30 | [
31 | ("compress", List.map ~f:test_compress compress_tests);
32 | ("max", List.map ~f:test_max max_tests);
33 | ("join", List.map ~f:test_join join_tests);
34 | ("lookup", List.map ~f:test_lookup lookup_tests);
35 | ]
36 |
--------------------------------------------------------------------------------
/homework/hw1/test/part3.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Hw1.Part3
3 |
4 | module Tree = struct
5 | type 'a t = [%import: 'a Hw1.Part3.tree] [@@deriving eq]
6 |
7 | (* A cleaner pretty-print function *)
8 | let rec pp ppf_a ppf = function
9 | | Leaf -> Fmt.pf ppf "Leaf"
10 | | Node (x, l, r) ->
11 | Fmt.pf ppf "Node (@[%a,@;%a,@;%a@])" ppf_a x (pp ppf_a) l (pp ppf_a)
12 | r
13 | end
14 |
15 | (** Wrapper functions for constructing trees.
16 | You can either use plain constructors to build an tree, or
17 | call the functions in this module. *)
18 | module DSL = struct
19 | (** Build a leaf node *)
20 | let leaf = Leaf
21 |
22 | (** Build a node with some data, a left subtree and a right subtree *)
23 | let node x l r = Node (x, l, r)
24 |
25 | (** Build a terminal node with some data (no subtrees) *)
26 | let term x = node x leaf leaf
27 | end
28 |
29 | let test_equal_tree =
30 | Utils.test_io Alcotest.bool "same bool" (Utils.uncurry (equal_tree Int.equal))
31 |
32 | let test_timestamp =
33 | let t =
34 | Alcotest.(
35 | testable
36 | (Tree.pp (Fmt.of_to_string [%derive.show: int * char]))
37 | (Tree.equal [%derive.eq: int * char]))
38 | in
39 | Utils.test_io t "same tree" timestamp
40 |
41 | (** A list of ((input tree * input tree) * expected output) tests *)
42 | let equal_tree_tests =
43 | [ (DSL.(node 1 leaf leaf, node 1 leaf leaf), true) (* add your tests here *) ]
44 |
45 | (** A list of (input tree * output tree) tests *)
46 | let timestamp_tests =
47 | [
48 | DSL.
49 | ( node 'o'
50 | (node 'm' (term 'c') (term 'a'))
51 | (node 'y' (term 'a') (term 'l')),
52 | node (0, 'o')
53 | (node (1, 'm') (term (2, 'c')) (term (3, 'a')))
54 | (node (4, 'y') (term (5, 'a')) (term (6, 'l'))) );
55 | (* add your tests here *)
56 | ]
57 |
58 | let tests =
59 | [
60 | ("equal_tree", List.map ~f:test_equal_tree equal_tree_tests);
61 | ("timestamp", List.map ~f:test_timestamp timestamp_tests);
62 | ]
63 |
--------------------------------------------------------------------------------
/homework/hw1/test/part4.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Hw1.Part4
3 |
4 | module Expr = struct
5 | type t = [%import: Hw1.Part4.expr] [@@deriving show, eq]
6 | end
7 |
8 | module Poly = struct
9 | type t = [%import: Hw1.Part4.poly] [@@deriving show, eq]
10 | end
11 |
12 | (** Wrapper functions for constructing ASTs.
13 | You can either use plain constructors to build an AST, or
14 | call the functions in this module. *)
15 | module DSL = struct
16 | (** Build a constant AST node *)
17 | let const (n : int) : expr = Const n
18 |
19 | (** Build a variable AST node *)
20 | let x : expr = X
21 |
22 | (** Build an addition AST node with a left AST and a right AST *)
23 | let add (e1 : expr) (e2 : expr) : expr = Add (e1, e2)
24 |
25 | (** Build a multiplication AST node with a left AST and a right AST *)
26 | let mul (e1 : expr) (e2 : expr) : expr = Mul (e1, e2)
27 |
28 | (** Build a composition AST node with a left AST and a right AST *)
29 | let comp (e1 : expr) (e2 : expr) : expr = Compose (e1, e2)
30 | end
31 |
32 | let test_eval_expr =
33 | Utils.test_io Alcotest.(int) "same int" (Utils.uncurry eval_expr)
34 |
35 | let test_simplify =
36 | Utils.test_io Alcotest.(testable Expr.pp Expr.equal) "same expr" simplify
37 |
38 | let test_eval_poly =
39 | Utils.test_io Alcotest.(int) "same int" (Utils.uncurry eval_poly)
40 |
41 | let test_normalize = Utils.test_io Alcotest.(list int) "same list" normalize
42 |
43 | let test_semantic_equiv =
44 | Utils.test_io Alcotest.bool "same bool" (Utils.uncurry semantic_equiv)
45 |
46 | let eval_expr_tests =
47 | [ ((3, DSL.(comp (add (const 1) (mul (const 2) x)) (add x (const 5)))), 12) ]
48 |
49 | let simplify_tests =
50 | [ DSL.(add x (add x (mul (const 1) (add (const 0) x))), add x (add x x)) ]
51 |
52 | let eval_poly_tests = [ ((3, [ 6; 7; 3 ]), 54) ]
53 |
54 | let normalize_tests =
55 | [
56 | ( DSL.(
57 | add (const 4) (mul (add x (const 2)) (add (mul (const 3) x) (const 1)))),
58 | [ 6; 7; 3 ] );
59 | ]
60 |
61 | let semantic_equiv_tests =
62 | [
63 | ( DSL.
64 | ( add (const 4) (mul (add x (const 2)) (add (mul (const 3) x) (const 1))),
65 | add (const 6) (add (mul (const 7) x) (mul (const 3) (mul x x))) ),
66 | true );
67 | ]
68 |
69 | let tests =
70 | [
71 | ("eval_expr", List.map ~f:test_eval_expr eval_expr_tests);
72 | ("simplify", List.map ~f:test_simplify simplify_tests);
73 | ("eval_poly", List.map ~f:test_eval_poly eval_poly_tests);
74 | ("normalize", List.map ~f:test_normalize normalize_tests);
75 | ("semantic_equiv", List.map ~f:test_semantic_equiv semantic_equiv_tests);
76 | ]
77 |
--------------------------------------------------------------------------------
/homework/hw1/test/utils.ml:
--------------------------------------------------------------------------------
1 | let test_io t msg f (i, o) () =
2 | Alcotest.(check' t) ~msg ~expected:o ~actual:(f i)
3 |
4 | let uncurry f (x, y) = f x y
5 |
--------------------------------------------------------------------------------
/homework/hw2/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.26.2
--------------------------------------------------------------------------------
/homework/hw2/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: zip
2 |
3 | FILES := lib/part1/part1.ml lib/part2/eval.ml
4 | ARCHIVE := submission.zip
5 |
6 | $(ARCHIVE): $(FILES)
7 | zip -j $(ARCHIVE) $(FILES)
8 |
9 | zip: $(ARCHIVE)
--------------------------------------------------------------------------------
/homework/hw2/bin/dune:
--------------------------------------------------------------------------------
1 | ; ---------------
2 | ; generate date information
3 |
4 | (rule
5 | (target build_metadata.ml)
6 | (deps (universe))
7 | (action
8 | (run bash "-c" "echo let date = \\\"$(date)\\\" > %{target}")))
9 |
10 | ; ---------------
11 | ; statically link the binary for CSIL if using musl libc
12 |
13 | (rule
14 | (target build_flags)
15 | (enabled_if
16 | (= %{ocaml-config:c_compiler} "musl-gcc"))
17 | (action
18 | (write-file %{target} "(-ccopt -static)")))
19 |
20 | (rule
21 | (target build_flags)
22 | (enabled_if
23 | (<> %{ocaml-config:c_compiler} "musl-gcc"))
24 | (action
25 | (write-file %{target} "()")))
26 |
27 | ; ---------------
28 | ; lamp repl
29 |
30 | (executable
31 | (name repl)
32 | (modules build_metadata repl)
33 | (libraries lamp linenoise)
34 | (flags
35 | :standard
36 | (:include build_flags))
37 | (modes exe))
38 |
--------------------------------------------------------------------------------
/homework/hw2/bin/repl.ml:
--------------------------------------------------------------------------------
1 | open Lamp
2 | open Ast
3 | open Eval
4 | open Format
5 |
6 | module Opts = struct
7 | let path : string option ref = ref None
8 | end
9 |
10 | let handler (f : unit -> unit) : unit =
11 | try f () with
12 | | Parsing.Parse_error -> Fmt.epr "Syntax error\n%!"
13 | | Err.Syntax { sl; sc; el; ec } ->
14 | Fmt.epr "Syntax error: %d.%d-%d.%d\n%!" sl sc el ec
15 | | Err.Lexing { l; s } ->
16 | Fmt.epr "Lexing error: At offset %d: unexpected character: %s\n%!" l s
17 | | Stuck msg -> printf "runtime error: %s\n%!" msg
18 | | Stack_overflow ->
19 | Fmt.epr
20 | "Interpreter stack overflow; too many recursive function calls\n%!"
21 |
22 | let rec repl () =
23 | match LNoise.linenoise "> " with
24 | | None -> ()
25 | | Some l ->
26 | LNoise.history_add l |> ignore;
27 | handler (fun () ->
28 | let e = Parse_util.parse l in
29 | Fmt.pr "<== %a\n%!" Pretty.expr e;
30 | let v = eval e in
31 | Fmt.pr "==> %a\n%!" Pretty.expr v);
32 | repl ()
33 |
34 | let read_args () =
35 | let set_file s = Opts.path := Some s in
36 | let opts = [] in
37 | Arg.parse opts set_file ""
38 |
39 | let () =
40 | read_args ();
41 | match !Opts.path with
42 | | Some file_name ->
43 | let ch = open_in file_name in
44 | let contents = really_input_string ch (in_channel_length ch) in
45 | close_in ch;
46 | handler (fun () ->
47 | let e = Parse_util.parse contents in
48 | let v = eval e in
49 | Fmt.pr "%a\n%!" Pretty.expr v)
50 | | None ->
51 | (* repl mode *)
52 | Fmt.pr "Welcome to lambda+! Built on: %s\n%!" Build_metadata.date;
53 | LNoise.history_set ~max_length:100 |> ignore;
54 | repl ()
55 |
--------------------------------------------------------------------------------
/homework/hw2/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.11)
2 |
3 | (name hw2)
4 |
5 | (generate_opam_files true)
6 |
7 | (source
8 | (github username/reponame))
9 |
10 | (authors "Author Name")
11 |
12 | (maintainers "Maintainer Name")
13 |
14 | (documentation https://url/to/documentation)
15 |
16 | (package
17 | (name hw2)
18 | (allow_empty)
19 | (synopsis "A short synopsis")
20 | (description "A longer description")
21 | (depends
22 | (ocaml
23 | (= 5.1.1))
24 | dune
25 | base
26 | alcotest
27 | ppx_deriving
28 | ppx_jane
29 | utop
30 | linenoise
31 | fmt)
32 | (tags
33 | (topics "to describe" your project)))
34 |
35 | ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
36 |
--------------------------------------------------------------------------------
/homework/hw2/hw2.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | synopsis: "A short synopsis"
4 | description: "A longer description"
5 | maintainer: ["Maintainer Name"]
6 | authors: ["Author Name"]
7 | tags: ["topics" "to describe" "your" "project"]
8 | homepage: "https://github.com/username/reponame"
9 | doc: "https://url/to/documentation"
10 | bug-reports: "https://github.com/username/reponame/issues"
11 | depends: [
12 | "ocaml" {= "5.1.1"}
13 | "dune" {>= "3.11"}
14 | "base"
15 | "alcotest"
16 | "ppx_deriving"
17 | "ppx_jane"
18 | "utop"
19 | "linenoise"
20 | "fmt"
21 | "odoc" {with-doc}
22 | ]
23 | build: [
24 | ["dune" "subst"] {dev}
25 | [
26 | "dune"
27 | "build"
28 | "-p"
29 | name
30 | "-j"
31 | jobs
32 | "@install"
33 | "@runtest" {with-test}
34 | "@doc" {with-doc}
35 | ]
36 | ]
37 | dev-repo: "git+https://github.com/username/reponame.git"
38 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part1/dune:
--------------------------------------------------------------------------------
1 | (env
2 | (dev
3 | (flags
4 | (:standard -warn-error -A))))
5 |
6 | (library
7 | (name part1))
8 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part1/part1.ml:
--------------------------------------------------------------------------------
1 | let todo () = failwith "TODO"
2 | let singletons (xs : 'a list) : 'a list list = todo ()
3 | let map2d (f : 'a -> 'b) (xss : 'a list list) : 'b list list = todo ()
4 | let product (xs : 'a list) (ys : 'b list) : ('a * 'b) list list = todo ()
5 | let power (xs : 'a list) : 'a list list = todo ()
6 |
7 | let both : 'a option -> 'b option -> ('a * 'b) option =
8 | fun x -> match x with Some x -> todo () | None -> todo ()
9 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part1/part1.mli:
--------------------------------------------------------------------------------
1 | val singletons : 'a list -> 'a list list
2 | val map2d : ('a -> 'b) -> 'a list list -> 'b list list
3 | val product : 'a list -> 'b list -> ('a * 'b) list list
4 | val power : 'a list -> 'a list list
5 | val both : 'a option -> 'b option -> ('a * 'b) option
6 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/ast.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | (** Binary operators *)
4 | type binop = Add | Sub | Mul [@@deriving equal, show]
5 |
6 | type 'a binder = string * 'a [@@deriving equal, show]
7 | (** Binder *)
8 |
9 | (** AST of Lambda+ expressions *)
10 | type expr =
11 | (* arithmetic *)
12 | | Num of int
13 | | Binop of binop * expr * expr
14 | (* variable *)
15 | | Var of string
16 | (* lambda calculus *)
17 | | Lambda of expr binder
18 | | App of expr * expr
19 | (* let expression *)
20 | | Let of expr * expr binder
21 | [@@deriving equal, show]
22 |
23 | (** Pretty-printers *)
24 | module Pretty = struct
25 | open Fmt
26 |
27 | let binop : binop Fmt.t =
28 | Fmt.(using (function Add -> "+" | Sub -> "-" | Mul -> "*") string)
29 |
30 | let rec expr : expr Fmt.t =
31 | fun ppf ->
32 | let is_complex = function Num _ | Var _ -> false | _ -> true in
33 | let pp_nested pp ppf e =
34 | if is_complex e then (parens pp) ppf e else pp ppf e
35 | in
36 | function
37 | | Num n -> int ppf n
38 | | Var x -> string ppf x
39 | | Binop (op, e1, e2) ->
40 | pf ppf "@[<2>%a@ %a@ %a@]" (pp_nested pp_expr) e1 pp_binop op
41 | (pp_nested pp_expr) e2
42 | | Lambda (x, e) -> pf ppf "@[<2>lambda %s.@ %a@]" x pp_expr e
43 | | Let (e1, (x, e2)) ->
44 | pf ppf "@[let %s = %a in@;%a@]" x pp_expr e1 pp_expr e2
45 | | App (e1, e2) ->
46 | pf ppf "@[<2>%a@ %a@]" (pp_nested pp_expr) e1 (pp_nested pp_expr) e2
47 | end
48 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/dune:
--------------------------------------------------------------------------------
1 | ; Build configuration
2 |
3 | ; Treat warnings as non-fatal
4 |
5 | (env
6 | (dev
7 | (flags
8 | (:standard -warn-error -A))))
9 |
10 | (data_only_dirs examples)
11 |
12 | ; ---------------
13 | ; lamp main files
14 |
15 | (library
16 | (name lamp)
17 | (preprocess
18 | (pps ppx_jane ppx_deriving.show))
19 | (libraries base fmt)
20 | (modules ast scanner parser parse_util vars eval err))
21 |
22 | (ocamllex scanner)
23 |
24 | (ocamlyacc parser)
25 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/err.ml:
--------------------------------------------------------------------------------
1 | exception Syntax of { sl : int; sc : int; el : int; ec : int }
2 | exception Lexing of { l : int; s : string }
3 |
4 | let syntax_error () =
5 | let start_pos = Parsing.rhs_start_pos 1 in
6 | let end_pos = Parsing.rhs_end_pos 1 in
7 | let sl = start_pos.pos_lnum
8 | and sc = start_pos.pos_cnum - start_pos.pos_bol
9 | and el = end_pos.pos_lnum
10 | and ec = end_pos.pos_cnum - end_pos.pos_bol in
11 | raise (Syntax { sl; sc; el; ec })
12 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/eval.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | let todo () = failwith "TODO"
4 | let bonus () = failwith "BONUS"
5 |
6 | exception Stuck of string
7 | (** Exception indicating that evaluation is stuck *)
8 |
9 | (** Raises an exception indicating that evaluation got stuck. *)
10 | let im_stuck msg = raise (Stuck msg)
11 |
12 | (** Computes the set of free variables in the given expression *)
13 | let rec free_vars (e : expr) : Vars.t =
14 | (* This line imports the functions in Vars, so you can write [diff .. ..]
15 | instead of [Vars.diff .. ..] *)
16 | let open Vars in
17 | (* Your code goes here *)
18 | match e with
19 | | Num _ -> empty
20 | | Binop (_, e1, e2) -> todo ()
21 | | Var x -> todo ()
22 | | Lambda binder -> todo ()
23 | | App (e1, e2) -> todo ()
24 | | Let (e1, binder) -> union (free_vars e1) (todo ())
25 |
26 | (** Perform substitution c[x -> e], i.e., substituting x with e in c *)
27 | let rec subst (x : string) (e : expr) (c : expr) : expr =
28 | match c with
29 | | Num n -> Num n
30 | | Binop (op, c1, c2) -> todo ()
31 | | Var y -> todo ()
32 | | Lambda binder -> todo ()
33 | | App (c1, c2) -> todo ()
34 | | Let (c1, binder) -> Let (subst x e c1, todo ())
35 |
36 | (** Evaluate expression e *)
37 | let rec eval (e : expr) : expr =
38 | try
39 | match e with
40 | | Num n -> Num n
41 | | Binop (op, e1, e2) -> todo ()
42 | | Var x -> todo ()
43 | | Lambda binder -> todo ()
44 | | App (e1, e2) -> todo ()
45 | | Let (e1, (x, e2)) -> todo ()
46 | | _ -> im_stuck (Fmt.str "Ill-formed expression: %a" Pretty.expr e)
47 | with Stuck msg ->
48 | im_stuck (Fmt.str "%s\nin expression %a" msg Pretty.expr e)
49 |
50 | type sigma = (string * expr) list
51 | (** Substitution *)
52 |
53 | (** Perform simultaneous substitution c[sigma], i.e., substituting variables in c according to sigma *)
54 | let rec subst_multi (sigma : sigma) (c : expr) : expr = bonus ()
55 |
56 | (** Alpha-equivalence *)
57 | let alpha_equiv (e1 : expr) (e2 : expr) : bool = bonus ()
58 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/eval.mli:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | exception Stuck of string
4 |
5 | val free_vars : expr -> Vars.t
6 | (** Return the set of free variable references in an expression *)
7 |
8 | val subst : string -> expr -> expr -> expr
9 | (** Substitution *)
10 |
11 | val eval : expr -> expr
12 | (** Interpret an expression *)
13 |
14 | type sigma = (string * expr) list
15 | (** Substitution *)
16 |
17 | val subst_multi : sigma -> expr -> expr
18 | (** Simultaneous substitution *)
19 |
20 | val alpha_equiv : expr -> expr -> bool
21 | (** Alpha equivalence *)
22 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/examples/add.lp:
--------------------------------------------------------------------------------
1 | 1 + 2
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/examples/apply.lp:
--------------------------------------------------------------------------------
1 | fun apply with f, x =
2 | f x
3 | in (lambda x. x * 2) x
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/examples/true.lp:
--------------------------------------------------------------------------------
1 | lambda x, y. x
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/parse_util.ml:
--------------------------------------------------------------------------------
1 | (** Helper function for parsing an expression. Useful for testing. *)
2 | let parse (s : string) : Ast.expr =
3 | Parser.main Scanner.token (Lexing.from_string s)
4 |
5 | let parse_file (f : string) : Ast.expr =
6 | let ch = open_in f in
7 | let contents = really_input_string ch (in_channel_length ch) in
8 | close_in ch;
9 | parse contents
10 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/parser.mly:
--------------------------------------------------------------------------------
1 | %{
2 | open Ast
3 | let mk_lambdas (xs : string list) (e : expr) =
4 | let f x e' = Lambda((x,e')) in
5 | List.fold_right f xs e
6 |
7 | %}
8 |
9 | /* Tokens */
10 |
11 | %token EOF FUN REC MATCH BAR END GT EQ LT LPAREN RPAREN DOT COMMA
12 | %token TRUE FALSE AND OR
13 | %token LET IN IF THEN ELSE WITH LAMBDA
14 | %token NIL CONS
15 | %token TYINT TYLIST THINARROW COLON LBRACK RBRACK
16 |
17 | %token PLUS SUB TIMES APP
18 | %token NUMBER
19 | %token ID
20 |
21 | %nonassoc LPAREN RPAREN ID NIL NUMBER TRUE FALSE LBRACK RBRACK
22 | %right LAMBDA
23 | %left AND OR
24 | %left LT GT EQ
25 | %left PLUS SUB
26 | %left TIMES
27 | %right CONS
28 | %left APP
29 |
30 | %right THINARROW TYLIST
31 |
32 | %start main
33 | %type main
34 |
35 | %%
36 |
37 | main:
38 | | expr EOF { $1 }
39 | | error EOF { Err.syntax_error() }
40 |
41 | bind:
42 | | ID { $1 }
43 |
44 | bindlist:
45 | | bind { [$1] }
46 | | bind COMMA bindlist { $1 :: $3 }
47 |
48 |
49 | /* split up exprs into multiple parts. This is to avoid reducing expr
50 | prematurely, otherwise we end up with situations like (lambda f. f 3) parsed
51 | as ((lambda f. f) 3)
52 | */
53 | expr:
54 | | LAMBDA bindlist DOT expr %prec LAMBDA { mk_lambdas $2 $4 }
55 | | FUN bind WITH bindlist EQ expr IN expr { let x = $2 in Let(mk_lambdas $4 $6, (x,$8)) }
56 | | LET bind EQ expr IN expr { Let($4, ($2,$6)) }
57 | | binop { $1 }
58 | | term { $1 }
59 |
60 | atom:
61 | | ID { Var($1) }
62 | | NUMBER { Num($1) }
63 |
64 | binop:
65 | | expr PLUS expr { Binop(Add, $1, $3) }
66 | | expr SUB expr { Binop(Sub, $1, $3) }
67 | | expr TIMES expr { Binop(Mul, $1, $3) }
68 |
69 | term:
70 | | atom { $1 }
71 | | LPAREN expr RPAREN { $2 }
72 | | term term %prec APP { App($1, $2) }
73 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/scanner.mll:
--------------------------------------------------------------------------------
1 | {
2 | open Parser
3 | }
4 |
5 | rule token = parse
6 | | [' ' '\r' '\t'] { token lexbuf }
7 | | '\n' { Lexing.new_line lexbuf; token lexbuf }
8 | | "let" { LET }
9 | | "in" { IN }
10 | | "fun" { FUN }
11 | | "rec" { REC }
12 | | "with" { WITH }
13 | | "match" { MATCH }
14 | | "end" { END }
15 | | "lambda" { LAMBDA }
16 | | "if" { IF }
17 | | "then" { THEN }
18 | | "else" { ELSE }
19 | | "true" { TRUE }
20 | | "false" { FALSE }
21 | | "Nil" { NIL }
22 | | "::" { CONS }
23 | | "+" { PLUS }
24 | | "-" { SUB }
25 | | "*" { TIMES }
26 | | "Int" { TYINT }
27 | | "List" { TYLIST }
28 | | ['0'-'9']+ as n { NUMBER(int_of_string(n)) }
29 | | ['a'-'z' 'A'-'Z' '_']['a'-'z' 'A'-'Z' '0'-'9' '_']* as x { ID(x) }
30 | | "->" { THINARROW }
31 | | ':' { COLON }
32 | | ">" { GT }
33 | | '=' { EQ }
34 | | "&&" { AND }
35 | | "||" { OR }
36 | | "|" { BAR }
37 | | "<" { LT }
38 | | '(' { LPAREN }
39 | | ')' { RPAREN }
40 | | '[' { LBRACK }
41 | | ']' { RBRACK }
42 | | '.' { DOT }
43 | | ',' { COMMA }
44 | | "//" { comment lexbuf }
45 | | eof { EOF }
46 | | _ { raise (Err.Lexing {l = Lexing.lexeme_start lexbuf; s= Lexing.lexeme lexbuf} ) }
47 |
48 | and comment = parse
49 | | '\n' { Lexing.new_line lexbuf; token lexbuf }
50 | | _ { comment lexbuf }
51 | | eof { EOF }
52 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/vars.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | type t = Set.M(String).t
4 |
5 | let pp : t Fmt.t =
6 | Fmt.using Set.to_list Fmt.(braces @@ list ~sep:(any ", ") string)
7 |
8 | let equal = Set.equal
9 | let empty = Set.empty (module String)
10 | let diff = Set.diff
11 | let singleton = Set.singleton (module String)
12 | let add s v = Set.add v s
13 | let union = Set.union
14 | let mem s v = Set.mem v s
15 | let of_list = Set.of_list (module String)
16 |
--------------------------------------------------------------------------------
/homework/hw2/lib/part2/vars.mli:
--------------------------------------------------------------------------------
1 | (** Signature for variable set module *)
2 |
3 | type t
4 | (** type of variable set *)
5 |
6 | val pp : t Fmt.t
7 | (** pretty printer for variable set *)
8 |
9 | val equal : t -> t -> bool
10 | (** equality function for variable set *)
11 |
12 | val empty : t
13 | (** empty set *)
14 |
15 | val mem : string -> t -> bool
16 | (** [mem x s] is true iff [x] is in [s] *)
17 |
18 | val diff : t -> t -> t
19 | (** [diff s1 s2] is the set of elements in [s1] but not in [s2] *)
20 |
21 | val singleton : string -> t
22 | (** [singleton x] is the singleton set containing [x] *)
23 |
24 | val add : string -> t -> t
25 | (** [add x s] is the set [s] with [x] added to it *)
26 |
27 | val union : t -> t -> t
28 | (** [union s1 s2] is the union of [s1] and [s2] *)
29 |
30 | val of_list : string list -> t
31 | (** [of_list l] converts a list [l] into a set *)
32 |
--------------------------------------------------------------------------------
/homework/hw2/sol.md:
--------------------------------------------------------------------------------
1 | # Solutions to HW2 Exercises
2 |
3 |
4 | ## Part 1
5 |
6 | ### Problem 1.1
7 | ```ocaml
8 | fun (x: int) (y: int) (z: int): int -> x + y + z
9 | ```
10 |
11 | ### Problem 1.2
12 | See [lecture 4](../../lectures/lecture4.pdf).
13 |
14 | ### Problem 1.6:
15 | See the [library implementation](https://github.com/ocaml/ocaml/blob/c82ce40504f0875969bf86b22e4d6ec7e26b3153/stdlib/list.ml#L125).
16 |
17 | ### Problem 1.7
18 |
19 | ```ocaml
20 | List.fold_left (fun acc (k,v) ->
21 | match acc with
22 | | None -> if equal k key then Some v else None
23 | | Some _ -> acc) None dict
24 | ```
25 |
26 | ### Problem 1.8
27 | 1. `compress`
28 | - Using `fold_right`:
29 | ```ocaml
30 | List.fold_right (fun x acc ->
31 | match acc with
32 | | [] -> [x]
33 | | y::ys -> if equal x y then acc else x::acc) lst []
34 | ```
35 | - `compress` can't be implemented with just `fold_left` alone, but if you have `List.rev` available, you can do:
36 | ```ocaml
37 | List.rev (List.fold_left (fun acc x ->
38 | match acc with
39 | | [] -> [x]
40 | | y::ys -> if equal x y then acc else x::acc) [] xs)
41 | ```
42 | Altneratively, if you have `last_opt: 'a list -> 'a option` that returns the last element of a list (and `None` if the list is empty), you can do:
43 |
44 | ```ocaml
45 | List.fold_left (fun acc x ->
46 | match last_opt acc with
47 | | None -> [x]
48 | | Some y -> if equal x y then acc else acc @ [x])
49 | [] xs
50 | ```
51 |
52 | 2. `max`:
53 | - Using `fold_right`:
54 | ```ocaml
55 | match xs with
56 | | [] -> None
57 | | x::xs -> Some (List.fold_right (fun x acc -> if x > acc then x else acc) xs x)
58 | ```
59 | - Using `fold_left`:
60 | ```ocaml
61 | match xs with
62 | | [] -> None
63 | | x::xs -> Some (List.fold_left (fun acc x -> if x > acc then x else acc) x xs)
64 | ```
65 | 3. `join`:
66 | - Using `fold_right`:
67 | ```ocaml
68 | List.fold_right (fun x acc ->
69 | match x, acc with
70 | | Some x, Some acc -> Some (x::acc)
71 | | _ -> None) xs (Some [])
72 | ```
73 | - Using `fold_left`:
74 | ```ocaml
75 | List.fold_left (fun acc x ->
76 | match x, acc with
77 | | Some x, Some acc -> Some (acc @ [x])
78 | | _ -> None) (Some []) xs
79 | ```
80 | 4. It suffices to observe that `map` itself can be implemented using `fold_right` as:
81 | ```ocaml
82 | List.fold_right (fun x acc -> f x :: acc) xs []
83 | ```
84 | or using `fold_left` as:
85 | ```ocaml
86 | List.fold_left (fun acc x -> acc @ [f x]) [] xs
87 | ```
88 | 5. See above.
89 | 6. `filter`:
90 | - Using `fold_right`:
91 | ```ocaml
92 | List.fold_right (fun x acc ->
93 | if p x then x::acc else acc) xs []
94 | ```
95 | - Using `fold_left`:
96 | ```ocaml
97 | List.fold_left (fun acc x ->
98 | if p x then acc @ [x] else acc) [] xs
99 | ```
100 | 7. `length`:
101 | - Using `fold_right`:
102 | ```ocaml
103 | List.fold_right (fun _ acc -> acc + 1) xs 0
104 | ```
105 | - Using `fold_left`:
106 | ```ocaml
107 | List.fold_left (fun acc _ -> acc + 1) 0 xs
108 | ```
109 | 8. `id`:
110 | - Using `fold_right`:
111 | ```ocaml
112 | List.fold_right (fun x acc -> x::acc) xs []
113 | ```
114 | - Using `fold_left`:
115 | ```ocaml
116 | List.fold_left (fun acc x -> acc @ [x]) [] xs
117 | ```
118 | 9. `rev` (note the symmetry with the previous problem):
119 | - Using `fold_right`:
120 | ```ocaml
121 | List.fold_right (fun x acc -> acc @ [x]) xs []
122 | ```
123 | - Using `fold_left`:
124 | ```ocaml
125 | List.fold_left (fun acc x -> x::acc) [] xs
126 | ```
127 | 10. `append`:
128 | - Using `fold_right`:
129 | ```ocaml
130 | let append xs ys =
131 | List.fold_right (fun x acc -> x::acc) xs ys
132 | ```
133 | - Not possible using `fold_left` if `@` isn't allowed.
134 | 11. `flatten`:
135 | - Using `fold_right`:
136 | ```ocaml
137 | List.fold_right (fun x acc -> x @ acc) xs []
138 | ```
139 | - Using `fold_left`:
140 | ```ocaml
141 | List.fold_left (fun acc x -> acc @ x) [] xs
142 | ```
143 | 12. `concat_map`:
144 | - Using `fold_right`:
145 | ```ocaml
146 | List.fold_right (fun x acc -> f x @ acc) xs []
147 | ```
148 | - Using `fold_left`:
149 | ```ocaml
150 | List.fold_left (fun acc x -> acc @ f x) [] xs
151 | ```
152 | Note that `concat_map` is just `map` followed by flatten.
153 |
154 |
155 |
156 |
157 |
158 | ## Part 2
159 |
160 | ### Problem 2.0
161 |
162 | 1. `let x = 2 in let y = x * x in x + y`:
163 | ```ocaml
164 | Let(
165 | Num 2,
166 | ("x",
167 | Let(
168 | Binop (Mul, Var "x", Var "x"),
169 | ("y",
170 | Binop (Add, Var "x", Var "y")
171 | )
172 | )
173 | )
174 | )
175 | ```
176 |
177 | 2. `(lambda x, y. let z = x + y in z * z) 2 3`
178 | ```ocaml
179 | App (
180 | App (
181 | Lambda (
182 | "x",
183 | Lambda (
184 | "y",
185 | Let (
186 | Binop (Add, Var "x", Var "y"),
187 | (
188 | "z",
189 | Binop (Mul, Var "z", Var "z")
190 | )
191 | )
192 | )
193 | ),
194 | Num 2),
195 | Num 3)
196 | ```
197 | Note that application is left-associative.
198 |
199 | 3. `fun f with x = let x = x + 1 in x in f f`. First, `fun F with X,Y,.. = E1 in E2` is desugared into `let F = lambda X, Y,.. . E1 in E2`. The AST is thus
200 | ```ocaml
201 | Let (
202 | Lambda (
203 | "x",
204 | Let (
205 | Binop (Add, Var "x", Num 1),
206 | ("x", Var "x")
207 | )
208 | ),
209 | ("f", App (Var "f", Var "f"))
210 | )
211 | ```
212 |
--------------------------------------------------------------------------------
/homework/hw2/test/dune:
--------------------------------------------------------------------------------
1 | (test
2 | (name hw2_test)
3 | (libraries fmt base alcotest part1 lamp)
4 | (preprocess
5 | (pps ppx_jane))
6 | (deps
7 | (source_tree ../lib/part4)))
8 |
--------------------------------------------------------------------------------
/homework/hw2/test/hw2_test.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | let () =
4 | Alcotest.run "hw2"
5 | (List.map
6 | ~f:(fun (name, tests) ->
7 | (name, List.map ~f:(Alcotest.test_case name `Quick) tests))
8 | (Test_part1.tests @ Test_part2.tests))
9 |
--------------------------------------------------------------------------------
/homework/hw2/test/test_part1.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Part1
3 |
4 | let test_singletons (xs, expected) () =
5 | Alcotest.(check' (list (list int)))
6 | ~msg:"same list" ~expected ~actual:(singletons xs)
7 |
8 | let test_map2d (f, xxs, expected) () =
9 | Alcotest.(check' (list (list int)))
10 | ~msg:"same list" ~expected ~actual:(map2d f xxs)
11 |
12 | let test_product (xs, ys, expected) () =
13 | Alcotest.(check' (list (list (pair int bool))))
14 | ~msg:"same list" ~expected ~actual:(product xs ys)
15 |
16 | let test_power (xs, expected) () =
17 | let sort =
18 | Fn.compose
19 | (* sort subsets *)
20 | (List.sort ~compare:[%compare: int list])
21 | (* sort elements in each subset *)
22 | (List.map ~f:(List.sort ~compare:[%compare: int]))
23 | in
24 | Alcotest.(check' (list (list int)))
25 | ~msg:"same list after sorting" ~expected:(sort expected)
26 | ~actual:(sort (power xs))
27 |
28 | let test_both (x, y, expected) () =
29 | Alcotest.(
30 | check'
31 | (option (pair int string))
32 | ~msg:"same option" ~expected ~actual:(both x y))
33 |
34 | let singletons_tests =
35 | [ (* input *) ([ 6; 7; 3 ], (* expected output *) [ [ 6 ]; [ 7 ]; [ 3 ] ]) ]
36 |
37 | let map2d_tests =
38 | [
39 | ( (* input function *) (fun x -> x + 1),
40 | (* input list *) [ [ 1; 2 ]; [ 3; 4 ] ],
41 | (* expected output *) [ [ 2; 3 ]; [ 4; 5 ] ] );
42 | ]
43 |
44 | let product_tests =
45 | [
46 | ( (* input xs *) [ 1; 2 ],
47 | (* input ys *) [ true; false ],
48 | (* expected output *)
49 | [ [ (1, true); (1, false) ]; [ (2, true); (2, false) ] ] );
50 | ]
51 |
52 | let power_tests =
53 | [
54 | ( (* input *) [ 1; 2; 3 ],
55 | (* expected output *)
56 | [ []; [ 1 ]; [ 2 ]; [ 3 ]; [ 1; 2 ]; [ 1; 3 ]; [ 2; 3 ]; [ 1; 2; 3 ] ] );
57 | ]
58 |
59 | let both_tests =
60 | [
61 | ( (* input 1 *) Some 1,
62 | (* input 2 *) Some "a",
63 | (* expected output *) Some (1, "a") );
64 | ]
65 |
66 | let tests =
67 | [
68 | ("singletons", List.map ~f:test_singletons singletons_tests);
69 | ("map2d", List.map ~f:test_map2d map2d_tests);
70 | ("product", List.map ~f:test_product product_tests);
71 | ("power", List.map ~f:test_power power_tests);
72 | ("both", List.map ~f:test_both both_tests);
73 | ]
74 |
--------------------------------------------------------------------------------
/homework/hw2/test/test_part2.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Lamp
3 | open Ast
4 | open Eval
5 |
6 | module Dsl = struct
7 | let lam x e = Lambda (x, e)
8 | let ( ?? ) x = Var x
9 | let ( ! ) n = Num n
10 | let ( + ) e1 e2 = Binop (Add, e1, e2)
11 | let ( - ) e1 e2 = Binop (Sub, e1, e2)
12 | let ( * ) e1 e2 = Binop (Mul, e1, e2)
13 | let let_ x e1 ~in_:e2 = Let (e1, (x, e2))
14 | let app e1 e2 = App (e1, e2)
15 | end
16 |
17 | (* Unit test utilities *)
18 | let texpr = Alcotest.testable Pretty.expr equal_expr
19 | let tvars = Alcotest.testable Vars.pp Vars.equal
20 |
21 | let parse s =
22 | try Parse_util.parse s
23 | with _ -> Alcotest.fail (Fmt.str "parse failed for %s" s)
24 |
25 | let parse_file filename =
26 | try Parse_util.parse_file filename
27 | with _ -> Alcotest.fail (Fmt.str "parse failed for %s" filename)
28 |
29 | (** Test free variable function *)
30 | let test_free_vars ((e, expected) : expr * string list) () =
31 | Alcotest.(check' tvars)
32 | ~msg:"same set" ~expected:(Vars.of_list expected) ~actual:(free_vars e)
33 |
34 | (** Test free variable function with concrete syntax input *)
35 | let test_free_vars_s ((e_str, expected) : string * string list) () =
36 | test_free_vars (parse e_str, expected) ()
37 |
38 | (** Test substitution c[ x |-> e ] = expected *)
39 | let test_subst (x, e, c, expected) () =
40 | let c' =
41 | try subst x e c with Stuck msg -> failwith ("Got stuck!\n" ^ msg)
42 | in
43 | Alcotest.(check' texpr) ~msg:"same expr" ~expected ~actual:c'
44 |
45 | (** Test substitution c[ x |-> e ] = expected, with concrete syntax inputs *)
46 | let test_subst_s (x, e, c, expected) =
47 | test_subst (x, parse e, parse c, parse expected)
48 |
49 | (** Check an expression evaluate to the expected value *)
50 | let test_eval (e, expected) () =
51 | let v = try eval e with Stuck msg -> failwith ("Got stuck!\n" ^ msg) in
52 | Alcotest.(check' texpr) ~msg:"eval" ~expected ~actual:v
53 |
54 | (** Check a expression (concrete syntax) evaluate to the expected value (concrete syntax) *)
55 | let test_eval_s (e_str, expected_str) =
56 | test_eval (parse e_str, parse expected_str)
57 |
58 | (** Check an expression gets stuck during evaluation *)
59 | let test_stuck (e : expr) () =
60 | try
61 | let v = eval e in
62 | Alcotest.fail (Fmt.str "evaluated to %a" Pretty.expr v)
63 | with Stuck _ -> ()
64 |
65 | (** Check a expression (concrete syntax) gets stuck during evaluation *)
66 | let test_stuck_s (e_str : string) = test_stuck (parse e_str)
67 |
68 | let test_subst_multi (sigma, e, expected) () =
69 | let e' =
70 | try subst_multi sigma e with Stuck msg -> failwith ("Got stuck!\n" ^ msg)
71 | in
72 | Alcotest.(check' texpr) ~msg:"same expr" ~expected ~actual:e'
73 |
74 | let test_subst_multi_s (sigma, e, expected) =
75 | test_subst_multi
76 | (List.map ~f:(fun (x, s) -> (x, parse s)) sigma, parse e, parse expected)
77 |
78 | let test_alpha_equiv (e1, e2, expected) () =
79 | Alcotest.(check' bool)
80 | ~msg:"alpha equiv" ~expected ~actual:(alpha_equiv e1 e2)
81 |
82 | let test_alpha_equiv_s (e1, e2, expected) =
83 | test_alpha_equiv (parse e1, parse e2, expected)
84 |
85 | let free_vars_tests = [ ("lambda x. y", [ "y" ]) ]
86 |
87 | let subst_tests =
88 | [ test_subst_s ("tmp", "1", "tmp + tmp2", (*expected *) "1 + tmp2") ]
89 |
90 | let eval_tests =
91 | [
92 | test_eval_s ((* input *) "1+2", (* expected *) "3");
93 | test_eval_s
94 | ( "let q = lambda x. x+1 in let b = lambda x, _. x in let a = lambda \
95 | y,x,f. f y (y x f) in let l = lambda x. x 0 (lambda _.q) in let k = \
96 | lambda p. p (lambda p.p) (lambda q,d,p. a (d p)) in let j = lambda p. \
97 | p (lambda p.b) (lambda q,d,p.k p (d p)) in let x = lambda p. p \
98 | (lambda p.a b) (lambda q,d,p.j p (d p)) in let m = lambda x. x b \
99 | (lambda x,y.x) in let o = lambda p. p (lambda p.p) (lambda q,d,p. d \
100 | (m p)) in let f = lambda f,x,y. f y x in l b + l (m (a (a b))) + l (f \
101 | o (a (a (a (a b)))) (a (a b))) + l (k (a b) (a (a b))) + l (j (a (a \
102 | b)) (a (a b))) + l (f x (a (a (a (a (a b))))) (a b))",
103 | "15" );
104 | ]
105 |
106 | let eval_stuck_tests = [ test_stuck_s (* input *) "(lambda x. x) + 1" ]
107 |
108 | let subst_multi_tests =
109 | [
110 | test_subst_multi_s
111 | ([ ("x", "1"); ("y", "2") ], "x + y", (* expected *) "1 + 2");
112 | ]
113 |
114 | let alpha_equiv_tests =
115 | [
116 | test_alpha_equiv_s ("lambda x. x", "lambda y. y", (* expected output *) true);
117 | test_alpha_equiv_s
118 | ("lambda x. y", "lambda y. x", (* expected output *) false);
119 | ]
120 |
121 | let tests =
122 | [
123 | ("free_vars", List.map ~f:test_free_vars_s free_vars_tests);
124 | ("subst", subst_tests);
125 | ("eval", eval_tests);
126 | ("eval_stuck", eval_stuck_tests);
127 | ("subst_multi", subst_multi_tests);
128 | ("alpha_equiv", alpha_equiv_tests);
129 | ]
130 |
--------------------------------------------------------------------------------
/homework/hw3/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.26.2
--------------------------------------------------------------------------------
/homework/hw3/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: zip
2 |
3 | FILES := lamp/eval.ml
4 | ARCHIVE := submission.zip
5 |
6 | $(ARCHIVE): $(FILES)
7 | zip -j $(ARCHIVE) $(FILES)
8 |
9 | zip: $(ARCHIVE)
--------------------------------------------------------------------------------
/homework/hw3/bin/dune:
--------------------------------------------------------------------------------
1 | ; Build configuration
2 |
3 | ; Treat warnings as non-fatal
4 |
5 | (env
6 | (dev
7 | (flags
8 | (:standard -warn-error -A))))
9 |
10 | ; ---------------
11 | ; generate date information
12 |
13 | (rule
14 | (target build_metadata.ml)
15 | (deps (universe))
16 | (action
17 | (run bash "-c" "echo let date = \\\"$(date)\\\" > %{target}")))
18 |
19 | ; ---------------
20 | ; statically link the binary for CSIL if using musl libc
21 |
22 | (rule
23 | (target build_flags)
24 | (enabled_if
25 | (= %{ocaml-config:c_compiler} "musl-gcc"))
26 | (action
27 | (write-file %{target} "(-ccopt -static)")))
28 |
29 | (rule
30 | (target build_flags)
31 | (enabled_if
32 | (<> %{ocaml-config:c_compiler} "musl-gcc"))
33 | (action
34 | (write-file %{target} "()")))
35 |
36 | ; ---------------
37 | ; lamp repl
38 |
39 | (executable
40 | (name repl)
41 | (modules build_metadata repl)
42 | (libraries base lamp linenoise cmdliner)
43 | (flags
44 | :standard
45 | (:include build_flags))
46 | (modes exe))
47 |
48 | (install
49 | (section bin)
50 | (files repl.exe))
51 |
--------------------------------------------------------------------------------
/homework/hw3/bin/repl.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Lamp
3 | open Ast
4 |
5 | module Opts = struct
6 | let path : string option ref = ref None
7 | let quiet : bool ref = ref false
8 | end
9 |
10 | let () =
11 | let open Parser in
12 | Expr.pp_exceptions ();
13 | Command.pp_exceptions ();
14 | Script.pp_exceptions ()
15 |
16 | let get_eval () e = Eval.eval e
17 |
18 | let handler (f : unit -> unit) : unit =
19 | try f () with
20 | | Eval.Stuck msg -> Fmt.pr "runtime error: %s\n%!" msg
21 | | Stack_overflow ->
22 | Fmt.epr
23 | "Interpreter stack overflow; too many recursive function calls\n%!"
24 |
25 | let context : (string * expr) list ref = ref []
26 | let get_context () = context
27 |
28 | let print_context () =
29 | Fmt.pr ">> context >>\n%!";
30 | Fmt.pr " @[%a@]\n%!"
31 | Fmt.(
32 | vbox (list ~sep:cut (box (pair ~sep:(any " ->@ ") string Pretty.expr))))
33 | (Base.List.rev !(get_context ()));
34 | Fmt.pr "<< context <<\n%!"
35 |
36 | let context_expr e =
37 | Base.List.fold_left ~init:e
38 | ~f:(fun e (x, def) -> Ast.DSL.let_ x def ~in_:e)
39 | !(get_context ())
40 |
41 | let check_and_run (e : expr) : expr = get_eval () e
42 |
43 | let handle_let_or_eval (c : Cmd.t) =
44 | match c with
45 | | CLet (x, def) ->
46 | (let v = check_and_run (context_expr def) in
47 | Fmt.pr "%s = @[%a@]\n%!" x Pretty.expr v);
48 | get_context () := (x, def) :: !(get_context ())
49 | | CEval e ->
50 | Fmt.pr "<== @[%a@]\n%!" Pretty.expr e;
51 | if not !Opts.quiet then Fmt.pr "<== AST:\n%a\n%!" Ast.pp_expr e;
52 | let v = check_and_run (context_expr e) in
53 | Fmt.pr "[eval] ==> @[%a@]\n%!" Pretty.expr v
54 | | _ -> failwith "Impossible"
55 |
56 | let replayable = function Cmd.CLet _ -> true | _ -> false
57 |
58 | let rec repl () =
59 | try
60 | match LNoise.linenoise "> " with
61 | | None -> ()
62 | | Some l ->
63 | LNoise.history_add l |> ignore;
64 | handler (fun () ->
65 | match Lamp.Parser.Command.parse_string l with
66 | | CPrint -> print_context ()
67 | | CClear ->
68 | get_context () := [];
69 | Fmt.pr ". context cleared\n%!"
70 | | CLoad f ->
71 | (* match LNoise.history_load ~filename:f with
72 | | Ok () -> *)
73 | Fmt.pr ". loading history from %s\n%!" f;
74 | Fmt.pr ". replaying history...\n%!";
75 | (* load content of file f and split it into lines *)
76 | Parser.Script.parse_file f
77 | |> Base.List.filter ~f:replayable
78 | |> Base.List.iter ~f:(fun c ->
79 | try handle_let_or_eval c with _ -> ());
80 | Fmt.pr ". history replayed\n%!"
81 | (* | Error e -> Fmt.pr ". error loading history: %s\n%!" e) *)
82 | | CSave f -> (
83 | match LNoise.history_save ~filename:f with
84 | | Ok () -> Fmt.pr ". history saved to %s\n%!" f
85 | | Error e -> Fmt.pr ". error saving history: %s\n%!" e)
86 | | (CLet _ | CEval _) as c -> handle_let_or_eval c);
87 | repl ()
88 | with Stdlib.Sys.Break -> repl ()
89 |
90 | let read_args () =
91 | let set_file s = Opts.path := Some s in
92 | let opts =
93 | [
94 | ("-q", Stdlib.Arg.Set Opts.quiet, "suppress AST printing (default: off)");
95 | ]
96 | in
97 | Stdlib.Arg.parse opts set_file ""
98 |
99 | let main () =
100 | read_args ();
101 | match !Opts.path with
102 | | Some file_name ->
103 | handler (fun () ->
104 | let e = Parser.Expr.parse_file file_name in
105 | if not !Opts.quiet then Fmt.pr "Parsed: @[%a@]\n%!" Pretty.expr e;
106 | let v = check_and_run e in
107 | Fmt.pr "@[%a@]\n%!" Pretty.expr v)
108 | | None ->
109 | (* repl mode *)
110 | Fmt.pr "Welcome to lambda+! Built on: %s\n%!" Build_metadata.date;
111 | LNoise.history_set ~max_length:100 |> ignore;
112 | repl ()
113 | ;;
114 |
115 | main ()
116 |
--------------------------------------------------------------------------------
/homework/hw3/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.11)
2 |
3 | (name hw3)
4 |
5 | (generate_opam_files true)
6 |
7 | (source
8 | (github username/reponame))
9 |
10 | (authors "Junrui Liu, Bryan Tan, Yu Feng")
11 |
12 | (maintainers "Junrui Liu")
13 |
14 | (documentation https://url/to/documentation)
15 |
16 | (using menhir 2.1)
17 |
18 | (package
19 | (name hw3)
20 | (allow_empty)
21 | (synopsis "A short synopsis")
22 | (description "A longer description")
23 | (depends
24 | dune
25 | base
26 | alcotest
27 | ppx_deriving
28 | ppx_jane
29 | utop
30 | linenoise
31 | fmt
32 | nice_parser
33 | menhir)
34 | (tags
35 | (topics "to describe" your project)))
36 |
37 | ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
38 |
--------------------------------------------------------------------------------
/homework/hw3/hw3.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | synopsis: "A short synopsis"
4 | description: "A longer description"
5 | maintainer: ["Junrui Liu"]
6 | authors: ["Junrui Liu, Bryan Tan, Yu Feng"]
7 | tags: ["topics" "to describe" "your" "project"]
8 | homepage: "https://github.com/username/reponame"
9 | doc: "https://url/to/documentation"
10 | bug-reports: "https://github.com/username/reponame/issues"
11 | depends: [
12 | "dune" {>= "3.11"}
13 | "base"
14 | "alcotest"
15 | "ppx_deriving"
16 | "ppx_jane"
17 | "utop"
18 | "linenoise"
19 | "fmt"
20 | "nice_parser"
21 | "menhir"
22 | "odoc" {with-doc}
23 | ]
24 | build: [
25 | ["dune" "subst"] {dev}
26 | [
27 | "dune"
28 | "build"
29 | "-p"
30 | name
31 | "-j"
32 | jobs
33 | "@install"
34 | "@runtest" {with-test}
35 | "@doc" {with-doc}
36 | ]
37 | ]
38 | dev-repo: "git+https://github.com/username/reponame.git"
39 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/ast.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | (** Binary operators *)
4 | type binop = Add | Sub | Mul [@@deriving equal, show]
5 |
6 | (** Relational comparison operators *)
7 | type relop = Eq | Lt | Gt [@@deriving equal, show]
8 |
9 | type 'a binder = string * 'a [@@deriving equal, show]
10 |
11 | (** AST of Lambda+ expressions *)
12 | type expr =
13 | (* binding *)
14 | | Var of string
15 | (* lambda calculus *)
16 | | Lambda of expr binder
17 | | App of expr * expr
18 | (* let expression *)
19 | | Let of expr * expr binder
20 | (* boolean *)
21 | | True
22 | | False
23 | | IfThenElse of expr * expr * expr
24 | (* arithmetic *)
25 | | Num of int
26 | | Binop of binop * expr * expr
27 | | Comp of relop * expr * expr
28 | (* list *)
29 | | ListNil
30 | | ListCons of expr * expr
31 | | ListMatch of expr * expr * expr binder binder
32 | (* fix *)
33 | | Fix of expr binder
34 | (* external choice *)
35 | | Both of expr * expr
36 | | I1 of expr
37 | | I2 of expr
38 | (* internal choice *)
39 | | E1 of expr
40 | | E2 of expr
41 | | Either of expr * expr binder * expr binder
42 | [@@deriving equal, show]
43 |
44 | (** Pretty-printers *)
45 | module Pretty = struct
46 | open Fmt
47 |
48 | let pp_binop : binop Fmt.t =
49 | fun ppf op ->
50 | string ppf (match op with Add -> "+" | Sub -> "-" | Mul -> "*")
51 |
52 | let pp_relop : relop Fmt.t =
53 | fun ppf op -> string ppf (match op with Eq -> "=" | Lt -> "<" | Gt -> ">")
54 |
55 | let rec expr : expr Fmt.t =
56 | fun ppf ->
57 | let is_complex = function
58 | | Num _ | Var _ | True | False | Both _ | ListNil | I1 _ | I2 _ -> false
59 | | _ -> true
60 | in
61 | let pp_nested pp ppf e =
62 | if is_complex e then (parens pp) ppf e else pp ppf e
63 | in
64 | let pp = pp_nested expr in
65 | function
66 | | Num n -> int ppf n
67 | | Var x -> string ppf x
68 | | Binop (op, e1, e2) -> pf ppf "%a %a@ %a" pp e1 pp_binop op pp e2
69 | | Lambda (x, e) -> pf ppf "lambda %s.@ %a" x expr e
70 | | Let (e1, (x, e2)) ->
71 | pf ppf "@[le@[t %s = %a in@]@ @[%a@]@]" x expr e1 expr e2
72 | | App (e1, e2) -> pf ppf "%a@ %a" pp e1 pp e2
73 | | True -> string ppf "true"
74 | | False -> string ppf "false"
75 | | IfThenElse (e1, e2, e3) ->
76 | pf ppf "@[if %a@ then %a@ else %a@]" expr e1 expr e2 expr e3
77 | | Comp (op, e1, e2) -> pf ppf "%a %a@ %a" pp e1 pp_relop op pp e2
78 | | ListNil -> pf ppf "Nil"
79 | | ListCons (e1, e2) -> pf ppf "%a ::@ %a" pp e1 pp e2
80 | | ListMatch (e1, e2, (h, (t, e3))) ->
81 | pf ppf "@[match %a with@;| Nil -> %a@;| %s :: %s -> %a@;end@]" expr
82 | e1 expr e2 h t expr e3
83 | | E1 e -> pf ppf "$1@ %a" pp e
84 | | E2 e -> pf ppf "$2@ %a" pp e
85 | | Either (e1, (x, e2), (y, e3)) ->
86 | pf ppf
87 | "@[match @[%a@] with@;| $1 %s -> @[%a@]@;| $2 %s -> @[%a@]@;end@]"
88 | expr e1 x expr e2 y expr e3
89 | | Fix (f, e) -> pf ppf "fix %s is@ %a" f expr e
90 | | Both (e1, e2) -> pf ppf "(%a,@ %a)" expr e1 expr e2
91 | | I1 e -> pf ppf "%a.1" pp e
92 | | I2 e -> pf ppf "%a.2" pp e
93 | end
94 |
95 | module DSL = struct
96 | let lam x e = Lambda (x, e)
97 | let v x = Var x
98 | let i n = Num n
99 | let ( + ) e1 e2 = Binop (Add, e1, e2)
100 | let ( - ) e1 e2 = Binop (Sub, e1, e2)
101 | let ( * ) e1 e2 = Binop (Mul, e1, e2)
102 | let let_ x e1 ~in_:e2 = Let (e1, (x, e2))
103 | let app e1 e2 = App (e1, e2)
104 | let ( = ) e1 e2 = Comp (Eq, e1, e2)
105 | let ( < ) e1 e2 = Comp (Lt, e1, e2)
106 | let ( > ) e1 e2 = Comp (Gt, e1, e2)
107 | let if_ e1 ~then_:e2 ~else_:e3 = IfThenElse (e1, e2, e3)
108 | let fix x ~is:e = Fix (x, e)
109 | let nil = ListNil
110 | let cons e1 e2 = ListCons (e1, e2)
111 |
112 | let match_ e1 ~with_nil:e2 ~with_cons:(x, y, e3) =
113 | ListMatch (e1, e2, (x, (y, e3)))
114 |
115 | let pair e1 e2 = Both (e1, e2)
116 | let fst e = I1 e
117 | let snd e = I2 e
118 | end
119 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/cmd.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | type t =
4 | | CLet of string * expr
5 | | CLoad of string
6 | | CSave of string
7 | | CPrint
8 | | CClear
9 | | CEval of expr
10 |
11 | type script = t list
12 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/dune:
--------------------------------------------------------------------------------
1 | ; Build configuration
2 |
3 | ; Treat warnings as non-fatal
4 |
5 | (env
6 | (dev
7 | (flags
8 | (:standard -warn-error -A))))
9 |
10 | ; ---------------
11 | ; lamp main files
12 |
13 | (library
14 | (name lamp)
15 | (preprocess
16 | (pps ppx_deriving.show ppx_jane ppx_sexp_conv))
17 | (libraries base fmt nice_parser))
18 |
19 | (ocamllex lexer)
20 |
21 | (menhir
22 | (modules menhir_parser))
23 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/err.ml:
--------------------------------------------------------------------------------
1 | exception Syntax of { sl : int; sc : int; el : int; ec : int }
2 | exception Lexing of { l : int; s : string }
3 |
4 | let syntax_error () =
5 | let start_pos = Parsing.rhs_start_pos 1 in
6 | let end_pos = Parsing.rhs_end_pos 1 in
7 | let sl = start_pos.pos_lnum
8 | and sc = start_pos.pos_cnum - start_pos.pos_bol
9 | and el = end_pos.pos_lnum
10 | and ec = end_pos.pos_cnum - end_pos.pos_bol in
11 | raise (Syntax { sl; sc; el; ec })
12 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/eval.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | let todo () = failwith "TODO"
4 |
5 | exception Stuck of string
6 | (** Exception indicating that evaluation is stuck *)
7 |
8 | (** Raises an exception indicating that evaluation got stuck. *)
9 | let im_stuck msg = raise (Stuck msg)
10 |
11 | (** Computes the set of free variables in the given expression *)
12 | let rec free_vars (e : expr) : Vars.t =
13 | (* This line imports the functions in Vars, so you can write [diff .. ..]
14 | instead of [Vars.diff .. ..] *)
15 | let open Vars in
16 | match e with _ -> todo ()
17 |
18 | (** Perform substitution c[x -> e], i.e., substituting x with e in c *)
19 | let rec subst (x : string) (e : expr) (c : expr) : expr =
20 | match c with _ -> todo ()
21 |
22 | (** Evaluate expression e *)
23 | let rec eval (e : expr) : expr =
24 | try match e with _ -> todo ()
25 | with Stuck msg ->
26 | im_stuck (Fmt.str "%s\nin expression %a" msg Pretty.expr e)
27 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/eval.mli:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | exception Stuck of string
4 |
5 | val free_vars : expr -> Vars.t
6 | (** Return the set of free variable references in an expression *)
7 |
8 | val subst : string -> expr -> expr -> expr
9 | (** Substitution *)
10 |
11 | val eval : expr -> expr
12 | (** Interpret an expression *)
13 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/lamp.opam:
--------------------------------------------------------------------------------
1 | opam-version: "2.0"
2 | name: "lambda-plus"
3 | version: "0.1"
4 | synopsis: "Lambda+ programming language"
5 | depends: [
6 | "alcotest" {with-test}
7 | "linenoise"
8 | "dune" {>= "2.4"}
9 | ]
10 | maintainer: ["junrui@ucsb.edu" "bryantan@ucsb.edu" "yufeng@cs.ucsb.edu"]
11 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/lexer.mll:
--------------------------------------------------------------------------------
1 | {
2 | open Menhir_parser
3 | exception LexError of string
4 |
5 | let failwith msg = raise (LexError msg)
6 |
7 | let illegal c =
8 | failwith (Printf.sprintf "[lexer] unexpected character: '%c'" c)
9 | }
10 |
11 | let ws = ' ' | '\t'
12 | let newline = "\r\n" | '\r' | '\n'
13 |
14 | rule next_token = parse
15 | | ws+ { next_token lexbuf }
16 | | newline { Lexing.new_line lexbuf; next_token lexbuf }
17 | | "#load" { CLOAD }
18 | | "#save" { CSAVE }
19 | | "#print" { CPRINT }
20 | | "#clear" { CCLEAR }
21 | | "#let" { CLET }
22 | | "let" { LET }
23 | | "in" { IN }
24 | | "fun" { FUN }
25 | | "rec" { REC }
26 | | "with" { WITH }
27 | | "match" { MATCH }
28 | | "end" { END }
29 | | "lambda" { LAMBDA }
30 | | "if" { IF }
31 | | "then" { THEN }
32 | | "else" { ELSE }
33 | | "true" { TRUE }
34 | | "false" { FALSE }
35 | | "fix" { FIX }
36 | | "is" { IS }
37 | | ".1" { FST }
38 | | ".2" { SND }
39 | | "Nil" { NIL }
40 | | "::" { CONS }
41 | | "+" { PLUS }
42 | | "-" { SUB }
43 | | "*" { TIMES }
44 | | "$1" { E1 }
45 | | "$2" { E2 }
46 | | ['0'-'9']+ as n { NUMBER(Base.Int.of_string(n)) }
47 | | ['a'-'z' 'A'-'Z' '_']['a'-'z' 'A'-'Z' '0'-'9' '_']* as x { ID(x) }
48 | | ['a'-'z' 'A'-'Z' '0'-'9' '_' '-' '/' ]+ ".txt" as x { FILE(x) }
49 | | "->" { THINARROW }
50 | | ">" { GT }
51 | | '=' { EQ }
52 | | "|" { BAR }
53 | | "<" { LT }
54 | | '(' { LPAREN }
55 | | ')' { RPAREN }
56 | | '.' { DOT }
57 | | ',' { COMMA }
58 | | "//" { comment lexbuf }
59 | | eof { EOF }
60 | | _ as c { illegal c }
61 |
62 | and comment = parse
63 | | newline { Lexing.new_line lexbuf; next_token lexbuf }
64 | | _ { comment lexbuf }
65 | | eof { EOF }
66 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/menhir_parser.mly:
--------------------------------------------------------------------------------
1 | %{
2 |
3 | let mk_lambdas (xs : string list) (e : Ast.expr) : Ast.expr =
4 | let f x e' = Ast.Lambda((x,e')) in
5 | List.fold_right f xs e
6 | %}
7 |
8 | /* Tokens */
9 |
10 | %token EOF FUN REC MATCH BAR END GT EQ LT LPAREN RPAREN DOT COMMA FIX IS
11 | %token TRUE FALSE
12 | %token LET IN IF THEN ELSE WITH LAMBDA
13 | %token NIL CONS
14 | %token THINARROW
15 | %token CLET CPRINT CCLEAR CLOAD CSAVE
16 | %token FST SND E1 E2
17 |
18 | %token PLUS SUB TIMES
19 | %token NUMBER
20 | %token ID
21 | %token FILE
22 |
23 | %nonassoc IN DOT ELSE IS
24 | %right CONS
25 | %left LT GT EQ
26 | %left PLUS SUB
27 | %left TIMES
28 | %nonassoc E1 E2
29 |
30 | %start expr_eof
31 | %type expr_eof
32 |
33 | %start repl_command_eof
34 | %type repl_command_eof
35 |
36 | %start script_eof
37 | %type script_eof
38 |
39 | %%
40 |
41 | file_command:
42 | | CLET bind EQ expr
43 | { let x = $2 in Cmd.CLet(x,$4) }
44 | | CPRINT
45 | { Cmd.CPrint}
46 | | CCLEAR
47 | { Cmd.CClear}
48 | | CLOAD FILE
49 | { Cmd.CLoad $2 }
50 | | CSAVE FILE
51 | { Cmd.CSave $2 }
52 |
53 | repl_command:
54 | | c=file_command
55 | { c }
56 | | expr
57 | { Cmd.CEval $1 }
58 |
59 | repl_command_eof:
60 | | c=repl_command EOF
61 | { c }
62 |
63 | script_eof:
64 | | cs=list(file_command) EOF
65 | { cs }
66 |
67 | expr_eof:
68 | | e=expr EOF
69 | { e }
70 |
71 | bind:
72 | | x=ID
73 | { x }
74 |
75 | bindlist:
76 | | l=separated_nonempty_list(COMMA, bind)
77 | { l }
78 |
79 | expr:
80 | | e=app
81 | { e }
82 | | LAMBDA bs=bindlist DOT e=expr
83 | { mk_lambdas bs e }
84 | | FUN REC x=bind WITH bs=bindlist EQ e1=expr IN e2=expr
85 | { Let(Fix ((x, mk_lambdas bs e1)), (x, e2)) }
86 | | FUN x=bind WITH xs=bindlist EQ e1=expr IN e2=expr
87 | { Let(mk_lambdas xs e1, (x, e2)) }
88 | | FIX x=bind IS e=expr
89 | { Fix (x,e) }
90 | | LET b=bind EQ e1=expr IN e2=expr
91 | { let x = b in
92 | Ast.Let (e1, (x,e2)) }
93 | | IF e1=expr THEN e2=expr ELSE e3=expr
94 | { IfThenElse(e1, e2, e3) }
95 | | e=list_match
96 | { e }
97 | | e=sum_match
98 | { e }
99 | | e=binop(expr)
100 | { e }
101 | | E1; e=expr
102 | { E1 e }
103 | | E2; e=expr
104 | { E2 e }
105 |
106 | binop(expr):
107 | | expr PLUS expr
108 | { Binop(Add, $1, $3) }
109 | | expr SUB expr
110 | { Binop(Sub, $1, $3) }
111 | | expr TIMES expr
112 | { Binop(Mul, $1, $3) }
113 | | expr LT expr
114 | { Comp(Lt, $1, $3) }
115 | | expr GT expr
116 | { Comp(Gt, $1, $3) }
117 | | expr EQ expr
118 | { Comp(Eq, $1, $3) }
119 | | expr CONS expr
120 | { ListCons($1, $3) }
121 |
122 | app:
123 | | term
124 | { $1 }
125 | | e1=app e2=term
126 | { App(e1, e2) }
127 |
128 | nil_branch:
129 | | NIL THINARROW e=expr
130 | { e }
131 |
132 | cons_branch:
133 | | x=ID CONS y=ID THINARROW e=expr
134 | { (x, y, e) }
135 |
136 | list_match:
137 | | MATCH e1=expr WITH option(BAR) e2=nil_branch BAR b3=cons_branch END
138 | {
139 | let (x,y,e3) = b3 in
140 | Ast.ListMatch(e1, e2, (x, (y, e3)))}
141 |
142 | branch(TAG):
143 | | TAG x=ID THINARROW e=expr
144 | { (x, e) }
145 |
146 | sum_match:
147 | | MATCH e1=expr WITH option(BAR) b2=branch(E1) BAR b3=branch(E2) END
148 | { Ast.Either(e1, b2, b3) }
149 |
150 | term:
151 | | x=ID
152 | { Ast.Var x }
153 | | n=NUMBER
154 | { Ast.Num n }
155 | | TRUE
156 | { Ast.True }
157 | | FALSE
158 | { Ast.False }
159 | | NIL
160 | { ListNil }
161 | | LPAREN e=expr RPAREN
162 | { e }
163 | | LPAREN e1=expr COMMA e2=expr RPAREN
164 | { Ast.Both(e1, e2) }
165 | | e=term FST
166 | { Ast.I1 e }
167 | | e=term SND
168 | { Ast.I2 e }
169 |
170 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/parser.ml:
--------------------------------------------------------------------------------
1 | module Expr = Nice_parser.Make (struct
2 | type result = Ast.expr
3 | type token = Menhir_parser.token
4 |
5 | exception ParseError = Menhir_parser.Error
6 |
7 | let parse = Menhir_parser.expr_eof
8 |
9 | include Lexer
10 | end)
11 |
12 | module Command = Nice_parser.Make (struct
13 | type result = Cmd.t
14 | type token = Menhir_parser.token
15 |
16 | exception ParseError = Menhir_parser.Error
17 |
18 | let parse = Menhir_parser.repl_command_eof
19 |
20 | include Lexer
21 | end)
22 |
23 | module Script = Nice_parser.Make (struct
24 | type result = Cmd.script
25 | type token = Menhir_parser.token
26 |
27 | exception ParseError = Menhir_parser.Error
28 |
29 | let parse = Menhir_parser.script_eof
30 |
31 | include Lexer
32 | end)
33 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/vars.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | type t = Set.M(String).t
4 |
5 | let pp : t Fmt.t =
6 | Fmt.using Set.to_list Fmt.(braces @@ list ~sep:(any ", ") string)
7 |
8 | let show = Fmt.to_to_string pp
9 | let equal = Set.equal
10 | let compare = Set.compare_direct
11 | let empty = Set.empty (module String)
12 | let diff = Set.diff
13 | let singleton = Set.singleton (module String)
14 | let add s v = Set.add v s
15 | let union = Set.union
16 | let mem s v = Set.mem v s
17 | let of_list = Set.of_list (module String)
18 | let to_list = Set.to_list
19 | let size = Set.length
20 |
--------------------------------------------------------------------------------
/homework/hw3/lamp/vars.mli:
--------------------------------------------------------------------------------
1 | (** Signature for variable set module *)
2 |
3 | type t [@@deriving equal, compare, show]
4 | (** type of variable set *)
5 |
6 | val empty : t
7 | (** empty set *)
8 |
9 | val mem : string -> t -> bool
10 | (** [mem x s] is true iff [x] is in [s] *)
11 |
12 | val diff : t -> t -> t
13 | (** [diff s1 s2] is the set of elements in [s1] but not in [s2] *)
14 |
15 | val singleton : string -> t
16 | (** [singleton x] is the singleton set containing [x] *)
17 |
18 | val add : string -> t -> t
19 | (** [add x s] is the set [s] with [x] added to it *)
20 |
21 | val union : t -> t -> t
22 | (** [union s1 s2] is the union of [s1] and [s2] *)
23 |
24 | val of_list : string list -> t
25 | (** [of_list l] converts a list [l] into a set *)
26 |
27 | val to_list : t -> string list
28 | (** [to_list x] converts a set [s] into a list *)
29 |
30 | val size : t -> int
31 | (** Return the size of a set *)
32 |
--------------------------------------------------------------------------------
/homework/hw3/test/dune:
--------------------------------------------------------------------------------
1 | (test
2 | (name hw3_test)
3 | (libraries fmt alcotest lamp)
4 | (preprocess
5 | (pps ppx_jane ppx_deriving.show))
6 | (deps
7 | (source_tree examples)))
8 |
--------------------------------------------------------------------------------
/homework/hw3/test/examples/add_n.lp:
--------------------------------------------------------------------------------
1 | // increments every element of a list by n
2 | fun rec add_n with l, n =
3 | match l with
4 | | Nil -> Nil
5 | | x::xs -> x + n :: add_n xs n
6 | end
7 | in add_n (1::2::Nil) 10 // evaluates to 11::12::Nil
--------------------------------------------------------------------------------
/homework/hw3/test/examples/fib.lp:
--------------------------------------------------------------------------------
1 | fun rec fib with n =
2 | if n = 0 then 0
3 | else if n = 1 then 1
4 | else fib (n-1) + fib (n-2)
5 | in fib 30
--------------------------------------------------------------------------------
/homework/hw3/test/examples/fib_int.lp:
--------------------------------------------------------------------------------
1 | fun rec fib with p, n =
2 | if n = 0 then p.1
3 | else
4 | let x = p.1 in
5 | let y = p.2 in
6 | fib (y, x+y) (n-1)
7 | in fib (0, 1) 30
--------------------------------------------------------------------------------
/homework/hw3/test/examples/max_ext.lp:
--------------------------------------------------------------------------------
1 | fun rec max with l =
2 | match l with
3 | | Nil -> $2 false
4 | | x::t -> $1 (match max t with
5 | | $1 y -> if x > y then x else y
6 | | $2 _ -> x
7 | end)
8 | end
9 | in
10 | max (7::10::5::1::4::2::Nil)
--------------------------------------------------------------------------------
/homework/hw3/test/examples/mutual_rec_int.lp:
--------------------------------------------------------------------------------
1 | // Mutual recursion can be realized by combining fix and internal choice
2 | let even_odd = fix even_odd is
3 | (
4 | lambda n.
5 | let odd = even_odd.2 in
6 | if n = 0 then true
7 | else odd (n-1)
8 | ,
9 | lambda n.
10 | let even = even_odd.1 in
11 | if n = 0 then false
12 | else even (n-1)
13 | ) in
14 | let even = even_odd.1 in
15 | let odd = even_odd.2 in
16 | if odd 11111 then even 11111 else even 0
--------------------------------------------------------------------------------
/homework/hw3/test/examples/primes.lp:
--------------------------------------------------------------------------------
1 | // an infinite list can be represented as a lambda that,
2 | // when called with anything, returns a list whose head is
3 | // the first element of the list and whose tail is another
4 | // infinite list
5 |
6 | // an infinite list containing n, n+1, n+2, ...
7 | fun rec countfrom with n =
8 | lambda _. n :: countfrom (n+1) in
9 |
10 | // the infinite list of natural numbers 0, 1, 2, ...
11 | let nat = countfrom 0 in
12 |
13 | // force the materialization of an infinite list by calling it with
14 | // a dummy argument
15 | fun force with l = l false in
16 |
17 | // filter a (potentially infinite) list
18 | fun rec filter with l, p =
19 | lambda _.
20 | match force l with
21 | | Nil -> Nil
22 | | h::t ->
23 | if p h then h :: filter t p
24 | else force (filter t p)
25 | end in
26 |
27 | // take the length-n prefix of a (potentially infinite) list
28 | fun rec take with l, n =
29 | match force l with
30 | | Nil -> Nil
31 | | h::t -> if n = 0 then Nil else h::(take t (n-1))
32 | end in
33 |
34 | // check if x divides y
35 | fun rec divides with x, y =
36 | if x > y then false
37 | else if x = y then true
38 | else divides x (y-x) in
39 |
40 | // boolean negation
41 | let not = lambda b. if b then false else true in
42 |
43 | // sieve of Eratosthenes
44 | // https://youtu.be/bnRNiE_OVWA
45 | fun rec sieve with l =
46 | lambda _.
47 | match force l with
48 | | Nil -> Nil // unreachable branch
49 | | h :: t ->
50 | h :: sieve (filter t (lambda n. not (divides h n)))
51 | end in
52 |
53 | // infinite list of primes: 2, 3, 5, 7, ...
54 | let primes = sieve (countfrom 2) in
55 |
56 | // the first 50 primes
57 | take primes 50
58 |
59 | // feel free to replace 50 with other numbers!
--------------------------------------------------------------------------------
/homework/hw3/test/examples/primes_internal.lp:
--------------------------------------------------------------------------------
1 | // an infinite list can be represented as an internal choice (an object)
2 | // with two behaviors: querying the head element, and querying the tail
3 |
4 | fun hd with l = l.1 in
5 | fun tl with l = l.2 in
6 |
7 | // take the length-n prefix of a (potentially infinite) list
8 | fun rec take with l, n =
9 | if n = 0
10 | then Nil
11 | else hd l :: take (tl l) (n-1) in
12 |
13 | // an infinite list containing n, n+1, n+2, ...
14 | fun rec countfrom with n =
15 | (n, countfrom (n+1)) in
16 |
17 | // the infinite list of natural numbers 0, 1, 2, ...
18 | let nat = countfrom 0 in
19 |
20 | // take nat 10
21 |
22 | // filter an infinite list
23 | fun rec filter with l, p =
24 | let h = hd l in
25 | if p h
26 | then (h, filter (tl l) p)
27 | else filter (tl l) p in
28 |
29 | // check if x divides y
30 | fun rec divides with x, y =
31 | if x > y then false
32 | else if x = y then true
33 | else divides x (y-x)
34 | in
35 |
36 | let is_even = lambda n. divides 2 n in
37 |
38 | // take (filter nat is_even) 10
39 |
40 | // boolean negation
41 | let not = lambda b. if b then false else true in
42 |
43 | // sieve of Eratosthenes
44 | // https://youtu.be/bnRNiE_OVWA
45 | fun rec sieve with l =
46 | let h = hd l in
47 | (h, sieve (filter (tl l) (lambda n. not (divides h n)))) in
48 |
49 | // infinite list of primes: 2, 3, 5, 7, ...
50 | let primes = sieve (countfrom 2) in
51 |
52 | // the first 50 primes
53 | take primes 50
54 |
55 | // feel free to replace 50 with other numbers!
--------------------------------------------------------------------------------
/homework/hw3/test/hw3_test.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | let () =
4 | Alcotest.run "hw3"
5 | (List.map
6 | ~f:(fun (name, tests) ->
7 | (name, List.map ~f:(Alcotest.test_case name `Quick) tests))
8 | Test_lamp.tests)
9 |
--------------------------------------------------------------------------------
/homework/hw3/test/test_lamp.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Lamp
3 | open Ast
4 | open Eval
5 |
6 | exception Timeout
7 |
8 | (* run a function with specified timeout:
9 | https://discuss.ocaml.org/t/computation-with-time-constraint/5548/9 *)
10 | let with_timeout timeout f =
11 | let _ =
12 | Stdlib.Sys.set_signal Stdlib.Sys.sigalrm
13 | (Stdlib.Sys.Signal_handle (fun _ -> raise Timeout))
14 | in
15 | ignore (Unix.alarm timeout);
16 | try
17 | let r = f () in
18 | ignore (Unix.alarm 0);
19 | r
20 | with e ->
21 | ignore (Unix.alarm 0);
22 | raise e
23 |
24 | (* Unit test utilities *)
25 | let texpr = Alcotest.testable Pretty.expr equal_expr
26 | let tvars = Alcotest.testable Vars.pp Vars.equal
27 |
28 | let parse s =
29 | try Parser.Expr.parse_string s
30 | with _ -> Alcotest.fail ("Failed to parse: " ^ s)
31 |
32 | (** Test free variable function *)
33 | let test_free_vars (e : expr) (expected : string list) () =
34 | Alcotest.(check' tvars)
35 | ~msg:"same set" ~expected:(Vars.of_list expected) ~actual:(free_vars e)
36 |
37 | (** Test free variable function with concrete syntax input *)
38 | let test_free_vars_s (e_str : string) (expected : string list) () =
39 | test_free_vars (parse e_str) expected ()
40 |
41 | (** Test substitution c[ x |-> e ] = expected *)
42 | let test_subst ~(x : string) ~(e : expr) ~(c : expr) (expected : expr) () =
43 | let c' =
44 | try subst x e c with Stuck msg -> failwith ("Got stuck!\n" ^ msg)
45 | in
46 | Alcotest.(check' texpr) ~msg:"same expr" ~expected ~actual:c'
47 |
48 | (** Test substitution c[ x |-> e ] = expected, with concrete syntax inputs *)
49 | let test_subst_s ~(x : string) ~(e : string) ~(c : string) (expected : string)
50 | () =
51 | test_subst ~x ~e:(parse e) ~c:(parse c) (parse expected) ()
52 |
53 | (** Check an expression evaluate to the expected value *)
54 | let test_eval_with (e : expr) (expected : expr) ~f () =
55 | try
56 | with_timeout 10 (fun () ->
57 | let v = f e in
58 | Alcotest.(check' texpr)
59 | ~msg:(Fmt.str "%a" Pretty.expr e)
60 | ~expected ~actual:v)
61 | with
62 | | Stuck msg -> Alcotest.fail ("Got stuck!\n" ^ msg)
63 | | Timeout -> Alcotest.fail "Timeout!"
64 |
65 | (** Check an expression evaluate to the expected value *)
66 | let test_eval = test_eval_with ~f:eval
67 |
68 | (** Check a expression (concrete syntax) evaluate to the expected value (concrete syntax) *)
69 | let test_eval_s (e_str : string) (expected_str : string) () =
70 | test_eval (parse e_str) (parse expected_str) ()
71 |
72 | let test_eval_file (filename : string) (expected_str : string) () =
73 | let e = Parser.Expr.parse_file filename in
74 | let expected = parse expected_str in
75 | test_eval e expected ()
76 |
77 | (** Check an expression gets stuck during evaluation *)
78 | let test_stuck (e : expr) () =
79 | try
80 | let v = eval e in
81 | Alcotest.fail (Fmt.str "evaluated to %a" Pretty.expr v)
82 | with Stuck _ -> Alcotest.(check' unit) ~msg:"stuck" ~expected:() ~actual:()
83 |
84 | (** Check a expression (concrete syntax) gets stuck during evaluation *)
85 | let test_stuck_s (e_str : string) = test_stuck (parse e_str)
86 |
87 | let free_vars_tests =
88 | let t = test_free_vars_s in
89 | [ t "fix x is y" [ "y" ] ]
90 |
91 | let subst_tests =
92 | let t x e c = test_subst_s ~x ~e ~c in
93 | [ t (* arguments *) "var" "1" "var < var" (*expected *) "1 < 1" ]
94 |
95 | let eval_tests =
96 | (* test an input expression evaluates to the expected output *)
97 | let t = test_eval_s in
98 | (* parse the file containing an input expression, and check that it evaluates to the expected output *)
99 | let tf = test_eval_file in
100 | [
101 | t (* input *) "1+2" (* expected *) "3";
102 | tf (* input file *) "examples/fib.lp" (* expected *) "832040";
103 | tf "examples/add_n.lp" "11::12::Nil";
104 | tf "examples/primes.lp"
105 | "2::3::5::7::11::13::17::19::23::29::31::37::41::43::47::53::59::61::67::71::Nil";
106 | ]
107 |
108 | let eval_stuck_tests =
109 | let t = test_stuck_s in
110 | [ t (* input *) "if 1 then 2 else 3" ]
111 |
112 | let tests =
113 | [
114 | ("free_vars", free_vars_tests);
115 | ("subst", subst_tests);
116 | ("eval", eval_tests);
117 | ("eval_stuck", eval_stuck_tests);
118 | ]
119 |
--------------------------------------------------------------------------------
/homework/hw4/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.26.2
--------------------------------------------------------------------------------
/homework/hw4/README.md:
--------------------------------------------------------------------------------
1 | # Homework Assignment 4
2 |
3 | **Due Wednesday, March 5th, 11:59PM (Pacific Time)**
4 |
5 | In this assignment, you will use OCaml to implement the type system of λ+.
6 |
7 | ## Instructions
8 |
9 | 0. Make sure you download the latest version of the reference manual [using this link](https://github.com/fredfeng/CS162/blob/master/homework/lamp.pdf). You will need to refer to the chapter on type checking in the language reference manual.
10 | 1. Either clone this directory, or download the zipped directory using [this link](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Ffredfeng%2FCS162%2Ftree%2Fmaster%2Fhomework%2Fhw4).
11 | 2. The files required for this assignment are located in this folder. In particular, you will need to implement the `abstract_eval` function in [lamp/typecheck.ml](./lamp/typecheck.ml).
12 | 3. You must not change the type signatures of the original functions. Otherwise, your program will not compile. If you accidentally change the type signatures, you can refer to the corresponding `.mli` file to see what the expected type signatures are.
13 | 4. Once you're done, submit `lamp/typecheck.ml` to Gradescope.
14 | 5. If your program contains print statements, please remove them before submitting. You do not need to submit any other file, including any `.mli` file or test code. The autograder will automatically compile your code together with our testing infrastructure and run the tests.
15 |
16 |
17 |
18 | ### Mandatory type annotations
19 |
20 | Note that the typing rules only provide a method for proving that a given expression has a *given* type. In a type "checker", we are more interested in _computing_ the type of an expression.
21 |
22 | This is usually straightforward when the type of an expression can be assembled from the types of its subexpressions. However, this is not the case with lambdas, fix, and nil, which we require mandatory type annotations from the programmer that uses $\lambda^+$. (It is possible to write an algorithm to _infer_ the annotations needed; in fact, you will be implementing type inference in HW5.)
23 |
24 | We thus add type annotations to the concrete syntax of $\lambda^+$ as follows:
25 | * Lambdas are now of the form `lambda x: T. e`, where `T` is the type of `x`.
26 | * Nil is now of the form `Nil[T]`, where `T` is the type of the elements of the list, **not the type of the list itself**!
27 | * The fixed-point operator is now of the form `fix x: T is e`, where `T` is the type of `x`.
28 | * The `fun` syntax has changed so that it is now
29 | `fun f: Tr with x1: T1, ..., xn: Tn = e1 in e2`, where `Ti` is the
30 | type of `xi` for each `i`, and `Tr` is the *return type* of the function `f`.
31 |
32 |
33 | In terms of abstract syntax, the appropriate AST constructors have been augmented to take type annotations of the form `ty option`:
34 | ```diff
35 | type expr =
36 | ...
37 | - | Lambda of expr binder
38 | + | Lambda of ty option * expr binder
39 |
40 | - | Fix of expr binder
41 | + | Fix of ty option * expr binder
42 |
43 | - | ListNil
44 | + | ListNil of ty option
45 | ...
46 | ```
47 | The reason we use `ty option` instead of `ty` even though those type annotations are mandatory for type checking is that in HW5 the annotations will become optional, so we anticipate the change and keep the AST consistent across assignments.
48 |
49 | But for this assignment, you can assume that **any expression that is not correctly annotated is ill-typed**. In other words, when your code expects a mandatory type annotation (for `ListNil`, `Lambda`, or `Fix`), you can assume the `ty option` part of the AST is always `Some ty`. In case the annotation is missing (i.e., it's a `None`), then you can simply crash the program or do whatever you want.
50 |
51 |
52 |
53 | ### Optional type annotations
54 |
55 | In contrast with the mandatory annotations discussed above, there are also "truly optional" type annotations. We introduce the syntax `e : T` to denote the annotation that `e` has type `T`. The associated AST constructor is `Annot of expr * ty`.
56 |
57 | In the concrete syntax, let-bindings can also take an optional type annotation via `let x: T = e1 in e2`. This will be de-sugared into `let x = (e1 : T) in e2` after parsing, so we don't need to change the AST constructor for `Let`. For example, `let f: Int -> Int = lambda x: Int. x + 5 in f 0` will be parsed into:
58 | ```ocaml
59 | Let(
60 | App(Var "f", Num 0),
61 | (
62 | "f",
63 | Annot(
64 | Lambda(
65 | Some TInt,
66 | ("x", Binop(Add, Var "x", Num 5))),
67 | TFun(TInt, TInt)
68 | )
69 | )
70 | )
71 | ```
72 |
73 | Your code should correctly handle all optional annotations according to the T-Annot rule in the reference manual.
74 |
75 |
76 |
77 | ## Part 1: 📝 Exercises
78 |
79 | For each of the following expressions $e$:
80 | - Does there exist a type $T$ such that $\cdot \vdash e: T$, i.e., whether the expression is well-typed under an empty typing environment. If so, draw the type derivation tree.
81 | - If no such $T$ exists, does there exists a combination of $\Gamma$ and $T$ such that $\Gamma \vdash e: T$. If so, draw the type derivation tree.
82 |
83 | 1. `lambda x: Bool. x + 2 * x`
84 | 2. `x`
85 | 3. `(lambda x: Int. x + 2 * y) 3`
86 | 4. `10 < 10`
87 | 5. `1::2::3+4::Nil[Int]`
88 | 6. `Nil[Bool]::(lambda x:Bool.x::Nil[Bool->Bool])::true::Nil[Bool]`
89 | 7. `if true then false else true`
90 | 8. `match Nil[Bool] with Nil -> Nil[Bool] | _::_ -> Nil[Int] end`
91 | 9. `(if 3>4 then 5 else 7+10*3) = 10`.
92 | 10. `let f = lambda x:Bool. if x then false else true in f (10 > 0)`
93 | 11. `1 :: 10 :: Nil[Int] :: Nil[Int]`
94 | 12. `(1::10) :: Nil[Int] :: Nil[List[Int]]`
95 | 13. `match 1::Nil[Int] with Nil -> 0 | hd::_ -> hd end`
96 | 14. `match 1::Nil[Int] with Nil -> 0 | _::tl -> tl end`
97 | 15. `match 1::2 with Nil -> 3 | x::y -> x+y end`
98 | 16. `(fix recur: Int -> Int is lambda n: Int. if n < 1 then 1 else recur (n-1) + recur (n-2)) 2`.
99 | 17. `(fix recur:List[Bool] -> Int is lambda xs: List[Bool]. match xs with Nil -> 0 | _::ys -> 1 + recur ys end) (false::true::Nil)`
100 | 18. `(fix recur:List[List[Int]] -> Int is lambda xs: List[List[Int]]. match xs with Nil -> 0 | _::ys -> 1 + recur ys end) Nil[List[Int]]`
101 | 19. `(fix recur: Int -> Int -> Bool is lambda n: Int. recur (n-1) 10`
102 | 20. `(fix recur: Int -> Int is lambda n: Int. n-1) 10`
103 |
104 |
105 |
106 | ## Part 2: Implementing the type checker (🧑💻)
107 |
108 | Your task is to implement the `abstract_eval` function in [lamp/typecheck.ml](./lamp/typecheck.ml). Given a typing environment and expression, `abstract_eval` should either 1) evaluate the expression "abstractly" and return the type of the expression; or 2) raise a `Type_error` by calling `ty_err` if the expression cannot be proven to be well-typed. You need to implement the typing rules as described in the reference manual.
109 |
110 | Before implementing `abstract_eval`, we suggest you implement an `equal_ty` helper function that checks if two types are the same. Later in your `abstract_eval`, use this function to check if the type of an expression matches the expected type, or to assert that two expressions have the same type.
111 |
112 | We encourage you to construct your own test cases and test your type checker locally. Use the typing rules in combination with your own intuition to construct well-typed and ill-typed expressions. You can find unit test helpers in `test/test_typing.ml`, and you can execute unit tests with `dune runtest`.
113 |
114 | If you wish to test your type checker interactively through REPL, run `dune exec bin/repl.exe -- -typed`, which will spawn an interpreter with type checking enabled. Although we will not be testing your `eval` function when grading this homework, feel free to copy over your `eval.ml` (otherwise the REPL will crash whenever it calls the stub `eval` function). If you do, you will need to slightly modify your `eval` to account for the type annotations.
115 |
116 | The reference interpreter includes a type checker. You can enable type checking by running `~junrui/lamp -typed`.
117 |
118 |
119 |
120 | ## Tips
121 | * Start with the basics: implement the typing rules for integer, booleans, nil, and cons values.
122 | * Move on to the arithmetic, comparison, if-then-else, and type annotation rules. Make sure that a type error is raised when e.g. adding a number to a list or comparing two expression of different types.
123 | * Lastly, implement the remaining rules, which require you to handle the typing environment $\Gamma$.
124 |
125 |
126 |
127 | ## Part 3: Extra Credit Problems
128 |
129 |
130 | Click to show extra credit problems
131 |
132 |
133 | ### Problem 3.1 (1pt):
134 |
135 | We introduce two new language constructs:
136 |
137 | 1. The first one is called *unit*.
138 | - There is one syntactic form to make a unit value: `()`. In this AST, this is represented by `Unit` in the AST. There is no way to consume the unit value.
139 | - We introduce a new concrete type `()` for the unit value (represented by `TUnit` in the AST).
140 |
141 | Your task is to reverse engineer the operational semantics **and** the typing rule(s) of unit by augmenting your `eval` and `abstract_eval` functions.
142 |
143 | 2. The second construct is called *void*.
144 | - There is no syntactic form to make a void value. There is one way to consume a void value: `match e with end`, represented as `Absurd of expr` in the AST.
145 | - We introduce a new concrete type `!` for void (represented by `TVoid` in the AST. Your task is to reverse engineer the operational semantics of void by augmenting your `eval` function.
146 |
147 | *Though experiment:* Is it possible to type check void in your `abstract_eval` function? If so, how? If not, what would you need to change in the type system to make it possible?
148 |
149 | ----
150 | ### Problem 3.2 (1pt)
151 | In HW3, you reverse-engineered the operational semantics of *internal choice*.
152 | > Spoiler
153 | > Internal choices are just pairs/tuples with *lazy* evaluation semantics.
154 | >
155 |
156 | Now, we introduce a new concrete type `T1 * T2` to represent an internal choice between values of type `T1` and values of type `T2`. This type is represented in the AST by `TProd` (short for **prod**uct). Your task is to reverse engineer the typing rules of products.
157 |
158 |
159 |
160 | ----
161 | ### Problem 3.3 (1pt)
162 | In HW3, you reverse-engineered the operational semantics of *external choice*.
163 | > Spoiler
164 | > External choices are just enums with exactly two cases.
165 | >
166 |
167 | We introduce a new concrete type `T1 + T2` to represent an *external choice* between values of type `T1` and values of type `T2`. This type is represented in the AST by `TSum`.
168 |
169 | Your tasks are:
170 |
171 | 1. On paper, design the typing rules for `E1`, `E2`, and `Either`. Then, you use your typing rules to draw the type derivation tree for the [max_ext.lp](../hw3/test/examples/max_ext.lp) example program from HW3.
172 | 2. Is it possible to implement your rules in the `abstract_eval` function? If so, how? If not, what would you need to change in the type system or the type checking algorithm to make it possible?
173 |
174 | You do not need to write any code for this problem; simply let Junrui know your solutions by DM'ing him on Slack or going to his office hours.
175 |
176 |
177 |
178 | ----
179 | #### Problem 3.4 (1pt)
180 | Define a function `size: typ -> int option` that computes the number of behaviorally distinct expressions that provably have a given type. If the type has infinitely many such members, return `None`.
181 |
182 | For example,
183 | - `size(Bool)` should return `Some 2` since the only boolean values are `true` and `false`. Note that we consider `true` and `2>1` to be behaviorally indistinguishable, since both of them evaluate to `true` under any context, so we don't count them separately.
184 | - `size(Int)` should return `None` since there are infinitely many integers.
185 | - `size(Bool -> Bool)` should return `Some 4`, since there are 4 boolean functions with distinct behaviors:
186 | 1. The identity function that returns its input unchanged
187 | 2. The constant function that always returns `true`
188 | 3. The constant function that always returns `false`
189 | 4. The function that negates its input
190 |
191 | In implementing `size`:
192 | - You need to handle all of the newly introduced types, including `TUnit`, `TVoid`, `TProd`, and `TSum`.
193 | - You do not need to consider expressions that may not terminate. I.e., you only need to count expressions that don't use `Fix`.
194 | - You do not need to consider ill-typed expressions like `1+true`.
195 |
196 |
197 | Hint
198 | There's an edge case for the list type. The other ones are elementary-school math.
199 |
200 |
201 |
--------------------------------------------------------------------------------
/homework/hw4/bin/dune:
--------------------------------------------------------------------------------
1 | ; Build configuration
2 |
3 | ; Treat warnings as non-fatal
4 |
5 | (env
6 | (dev
7 | (flags
8 | (:standard -warn-error -A))))
9 |
10 | ; ---------------
11 | ; generate date information
12 |
13 | (rule
14 | (target build_metadata.ml)
15 | (deps (universe))
16 | (action
17 | (run bash "-c" "echo let date = \\\"$(date)\\\" > %{target}")))
18 |
19 | ; ---------------
20 | ; statically link the binary for CSIL if using musl libc
21 |
22 | (rule
23 | (target build_flags)
24 | (enabled_if
25 | (= %{ocaml-config:c_compiler} "musl-gcc"))
26 | (action
27 | (write-file %{target} "(-ccopt -static)")))
28 |
29 | (rule
30 | (target build_flags)
31 | (enabled_if
32 | (<> %{ocaml-config:c_compiler} "musl-gcc"))
33 | (action
34 | (write-file %{target} "()")))
35 |
36 | ; ---------------
37 | ; lamp repl
38 |
39 | (executable
40 | (name repl)
41 | (modules build_metadata repl)
42 | (libraries base lamp linenoise cmdliner)
43 | (flags
44 | :standard
45 | (:include build_flags))
46 | (modes exe))
47 |
48 | (install
49 | (section bin)
50 | (files repl.exe))
51 |
--------------------------------------------------------------------------------
/homework/hw4/bin/repl.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Lamp
3 | open Ast
4 |
5 | module Opts = struct
6 | let path : string option ref = ref None
7 | let fast : bool ref = ref false
8 | let quiet : bool ref = ref false
9 | let typed : bool ref = ref false
10 | let tyinf : bool ref = ref false
11 | let poly : bool ref = ref false
12 | end
13 |
14 | let () =
15 | let open Parser in
16 | Expr.pp_exceptions ();
17 | Command.pp_exceptions ();
18 | Script.pp_exceptions ();
19 | Ty.pp_exceptions ()
20 |
21 | let get_eval () e = Eval.eval e
22 |
23 | let handler (f : unit -> unit) : unit =
24 | try f () with
25 | | Eval.Stuck msg -> Fmt.pr "runtime error: %s\n%!" msg
26 | | Stack_overflow ->
27 | Fmt.epr
28 | "Interpreter stack overflow; too many recursive function calls\n%!"
29 | | Typecheck.Type_error s -> Fmt.epr "Type error: %s\n%!" s
30 |
31 | let context : ((string * ty option) * expr) list ref = ref []
32 | let get_context () = context
33 |
34 | let print_context () =
35 | Fmt.pr ">> context >>\n%!";
36 | Fmt.pr " @[%a@]\n%!"
37 | Fmt.(
38 | vbox
39 | (list ~sep:cut
40 | (box
41 | (pair ~sep:(any " ->@ ")
42 | (pair string (option (const string " : " ++ Pretty.ty)))
43 | Pretty.expr))))
44 | (Base.List.rev !(get_context ()));
45 | Fmt.pr "<< context <<\n%!"
46 |
47 | let context_expr e =
48 | Base.List.fold_left ~init:e
49 | ~f:(fun e ((x, t), def) ->
50 | Ast.Let
51 | ((match t with None -> def | Some t -> Ast.Annot (def, t)), (x, e)))
52 | !(get_context ())
53 |
54 | let check_and_run (e : expr) : expr =
55 | (if !Opts.typed then
56 | let ty = Typecheck.abstract_eval [] e in
57 | Fmt.pr "<-- type: @[%a@]\n%!" Pretty.ty ty);
58 | get_eval () e
59 |
60 | let handle_let_or_eval (c : Cmd.t) =
61 | match c with
62 | | CLet (x, t, def) ->
63 | (let v = check_and_run (context_expr def) in
64 | Fmt.pr "%s = @[%a@]\n%!" x Pretty.expr v);
65 | get_context () := ((x, t), def) :: !(get_context ())
66 | | CEval e ->
67 | Fmt.pr "<== @[%a@]\n%!" Pretty.expr e;
68 | if not !Opts.quiet then Fmt.pr "<== AST:\n@[%a@]\n%!" Ast.pp_expr e;
69 | let v = check_and_run (context_expr e) in
70 | Fmt.pr "[eval] ==> @[%a@]\n%!" Pretty.expr v
71 | | _ -> failwith "Impossible"
72 |
73 | let replayable = function Cmd.CLet _ -> true | _ -> false
74 |
75 | let rec repl () =
76 | try
77 | match LNoise.linenoise "> " with
78 | | None -> ()
79 | | Some l ->
80 | LNoise.history_add l |> ignore;
81 | handler (fun () ->
82 | match Lamp.Parser.Command.parse_string l with
83 | | CPrint -> print_context ()
84 | | CClear ->
85 | get_context () := [];
86 | Fmt.pr ". context cleared\n%!"
87 | | CLoad f ->
88 | (* match LNoise.history_load ~filename:f with
89 | | Ok () -> *)
90 | Fmt.pr ". loading history from %s\n%!" f;
91 | Fmt.pr ". replaying history...\n%!";
92 | (* load content of file f and split it into lines *)
93 | Parser.Script.parse_file f
94 | |> Base.List.filter ~f:replayable
95 | |> Base.List.iter ~f:(fun c ->
96 | try handle_let_or_eval c with _ -> ());
97 | Fmt.pr ". history replayed\n%!"
98 | (* | Error e -> Fmt.pr ". error loading history: %s\n%!" e) *)
99 | | CSave f -> (
100 | match LNoise.history_save ~filename:f with
101 | | Ok () -> Fmt.pr ". history saved to %s\n%!" f
102 | | Error e -> Fmt.pr ". error saving history: %s\n%!" e)
103 | | (CLet _ | CEval _) as c -> handle_let_or_eval c
104 | | _ -> ());
105 | repl ()
106 | with Stdlib.Sys.Break -> repl ()
107 |
108 | let read_args () =
109 | let set_file s = Opts.path := Some s in
110 | let opts =
111 | [
112 | ( "-typed",
113 | Stdlib.Arg.Set Opts.typed,
114 | "enable type checking in interpreter (default: off)" );
115 | ( "-tyinf",
116 | Stdlib.Arg.Set Opts.tyinf,
117 | "enable type inference in interpreter (default: off)" );
118 | ( "-poly",
119 | Stdlib.Arg.Set Opts.poly,
120 | "enable let-polymorphism (default: off)" );
121 | ("-q", Stdlib.Arg.Set Opts.quiet, "suppress AST printing (default: off)");
122 | ( "-fast",
123 | Stdlib.Arg.Set Opts.fast,
124 | "use closure semantics instead of eager substitution (default: off)" );
125 | ]
126 | in
127 | Stdlib.Arg.parse opts set_file ""
128 |
129 | let main () =
130 | read_args ();
131 | match !Opts.path with
132 | | Some file_name ->
133 | handler (fun () ->
134 | let v = check_and_run (Parser.Expr.parse_file file_name) in
135 | Fmt.pr "@[%a@]\n%!" Pretty.expr v)
136 | | None ->
137 | (* repl mode *)
138 | Fmt.pr "Welcome to lambda+! Built on: %s\n%!" Build_metadata.date;
139 | LNoise.history_set ~max_length:100 |> ignore;
140 | repl ()
141 | ;;
142 |
143 | main ()
144 |
--------------------------------------------------------------------------------
/homework/hw4/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.11)
2 |
3 | (name hw4)
4 |
5 | (generate_opam_files true)
6 |
7 | (source
8 | (github username/reponame))
9 |
10 | (authors "Junrui Liu, Bryan Tan, Yu Feng")
11 |
12 | (maintainers "Junrui Liu")
13 |
14 | (documentation https://url/to/documentation)
15 |
16 | (using menhir 2.1)
17 |
18 | (package
19 | (name hw4)
20 | (allow_empty)
21 | (synopsis "A short synopsis")
22 | (description "A longer description")
23 | (depends dune base menhir nice_parser alcotest utop linenoise fmt)
24 | (tags
25 | (topics "to describe" your project)))
26 |
27 | ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
28 |
--------------------------------------------------------------------------------
/homework/hw4/hw4.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | synopsis: "A short synopsis"
4 | description: "A longer description"
5 | maintainer: ["Junrui Liu"]
6 | authors: ["Junrui Liu, Bryan Tan, Yu Feng"]
7 | tags: ["topics" "to describe" "your" "project"]
8 | homepage: "https://github.com/username/reponame"
9 | doc: "https://url/to/documentation"
10 | bug-reports: "https://github.com/username/reponame/issues"
11 | depends: [
12 | "dune" {>= "3.11"}
13 | "base"
14 | "menhir"
15 | "nice_parser"
16 | "alcotest"
17 | "utop"
18 | "linenoise"
19 | "fmt"
20 | "odoc" {with-doc}
21 | ]
22 | build: [
23 | ["dune" "subst"] {dev}
24 | [
25 | "dune"
26 | "build"
27 | "-p"
28 | name
29 | "-j"
30 | jobs
31 | "@install"
32 | "@runtest" {with-test}
33 | "@doc" {with-doc}
34 | ]
35 | ]
36 | dev-repo: "git+https://github.com/username/reponame.git"
37 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/ast.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | (** Monomorphic types *)
4 | type ty =
5 | | TVar of string (** Type variable *)
6 | | TInt
7 | | TBool
8 | | TList of ty
9 | | TFun of ty * ty
10 | | TUnit
11 | | TVoid
12 | | TProd of ty * ty
13 | | TSum of ty * ty
14 | [@@deriving equal, show]
15 |
16 | (** Binary operators *)
17 | type binop = Add | Sub | Mul [@@deriving equal, show]
18 |
19 | (** Relational comparison operators *)
20 | type relop = Eq | Lt | Gt [@@deriving equal, show]
21 |
22 | type 'a binder = string * 'a [@@deriving equal, show]
23 |
24 | (** AST of Lambda+ expressions *)
25 | type expr =
26 | (* arithmetic *)
27 | | Num of int
28 | | Binop of binop * expr * expr
29 | (* binding *)
30 | | Var of string
31 | (* lambda calculus *)
32 | | Lambda of ty option * expr binder
33 | | App of expr * expr
34 | (* let expression *)
35 | | Let of expr * expr binder
36 | (* booleans *)
37 | | True
38 | | False
39 | | IfThenElse of expr * expr * expr
40 | | Comp of relop * expr * expr
41 | (* lists *)
42 | | ListNil of ty option
43 | | ListCons of expr * expr
44 | | ListMatch of expr * expr * expr binder binder
45 | (* fix *)
46 | | Fix of ty option * expr binder
47 | (* type annotation *)
48 | | Annot of expr * ty
49 | (* unit *)
50 | | Unit
51 | (* internal choice *)
52 | | Both of expr * expr
53 | | I1 of expr
54 | | I2 of expr
55 | (* void *)
56 | | Absurd of expr
57 | (* external choice *)
58 | | E1 of expr
59 | | E2 of expr
60 | | Either of expr * expr binder * expr binder
61 | [@@deriving equal, show]
62 |
63 | (** Pretty-printers *)
64 | module Pretty = struct
65 | open Fmt
66 |
67 | (** Pretty print a [ty] *)
68 | let rec ty : ty Fmt.t =
69 | fun ppf ->
70 | let open Fmt in
71 | function
72 | | TVar x -> string ppf x
73 | | TInt -> string ppf "Int"
74 | | TBool -> string ppf "Bool"
75 | | TFun (t1, t2) -> pf ppf "@[%a -> %a@]" (pp_nested pp_ty) t1 pp_ty t2
76 | | TList t -> pf ppf "List[%a]" pp_ty t
77 | | TSum (t1, t2) ->
78 | pf ppf "@[%a + %a@]" (pp_nested pp_ty) t1 (pp_nested pp_ty) t2
79 | | TProd (t1, t2) ->
80 | pf ppf "@[%a * %a@]" (pp_nested pp_ty) t1 (pp_nested pp_ty) t2
81 | | TUnit -> string ppf "()"
82 | | TVoid -> string ppf "!"
83 |
84 | and pp_nested pp ppf t =
85 | let is_complex_ty = function TFun _ | TProd _ -> true | _ -> false in
86 | if is_complex_ty t then Fmt.parens pp ppf t else pp ppf t
87 |
88 | let binop : binop Fmt.t =
89 | fun ppf op ->
90 | string ppf (match op with Add -> "+" | Sub -> "-" | Mul -> "*")
91 |
92 | let relop : relop Fmt.t =
93 | fun ppf op -> string ppf (match op with Eq -> "=" | Lt -> "<" | Gt -> ">")
94 |
95 | let rec expr : expr Fmt.t =
96 | fun ppf ->
97 | let is_complex = function
98 | | Num _ | Var _ | True | False | Both _ | ListNil _ -> false
99 | | _ -> true
100 | in
101 | let pp_nested pp ppf e =
102 | if is_complex e then (parens pp) ppf e else pp ppf e
103 | in
104 | let pp = pp_nested pp_expr in
105 | function
106 | | Num n -> int ppf n
107 | | Var x -> string ppf x
108 | | Binop (op, e1, e2) -> pf ppf "%a %a@ %a" pp e1 pp_binop op pp e2
109 | | Lambda (topt, (x, e)) ->
110 | pf ppf "lambda %s%a.@ %a" x
111 | (option (const string ": " ++ pp_ty))
112 | topt pp_expr e
113 | | Let (e1, (x, e2)) ->
114 | pf ppf "@[le@[t %s = %a in@]@ @[%a@]@]" x pp_expr e1 pp_expr e2
115 | | App (e1, e2) -> pf ppf "%a@ %a" pp e1 pp e2
116 | | True -> string ppf "true"
117 | | False -> string ppf "false"
118 | | IfThenElse (e1, e2, e3) ->
119 | pf ppf "@[if %a@ then %a@ else %a@]" pp_expr e1 pp_expr e2 pp_expr
120 | e3
121 | | Comp (op, e1, e2) -> pf ppf "%a %a@ %a" pp e1 pp_relop op pp e2
122 | | ListNil topt -> pf ppf "Nil%a" (option (brackets pp_ty)) topt
123 | | ListCons (e1, e2) -> pf ppf "%a ::@ %a" pp e1 pp e2
124 | | ListMatch (e1, e2, (h, (t, e3))) ->
125 | pf ppf "@[match %a with@;| Nil -> %a@;| %s :: %s -> %a@;end@]"
126 | pp_expr e1 pp_expr e2 h t pp_expr e3
127 | | E1 e -> pf ppf "$1@ %a" pp e
128 | | E2 e -> pf ppf "$2@ %a" pp e
129 | | Either (e1, (x, e2), (y, e3)) ->
130 | pf ppf
131 | "@[match @[%a@] with@;| $1 %s -> @[%a@]@;| $2 %s -> @[%a@]@;end@]"
132 | pp_expr e1 x pp_expr e2 y pp_expr e3
133 | | Fix (topt, (f, e)) ->
134 | pf ppf "fix %s%a is@ %a" f
135 | (option (const string ": " ++ pp_ty))
136 | topt pp_expr e
137 | | Both (e1, e2) -> pf ppf "(%a,@ %a)" pp e1 pp e2
138 | | I1 e -> pf ppf "%a.1" pp e
139 | | I2 e -> pf ppf "%a.2" pp e
140 | | Annot (e, t) -> pf ppf "%a :@ %a" pp e pp_ty t
141 | | Unit -> string ppf "()"
142 | | Absurd e -> pf ppf "match %a with end" pp e
143 | end
144 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/cmd.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | type t =
4 | | CLet of string * ty option * expr
5 | | CLoad of string
6 | | CSave of string
7 | | CPrint
8 | | CClear
9 | | CEval of expr
10 | | CSynth of ty
11 |
12 | type script = t list
13 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/dune:
--------------------------------------------------------------------------------
1 | ; Build configuration
2 |
3 | ; Treat warnings as non-fatal
4 |
5 | (env
6 | (dev
7 | (flags
8 | (:standard -warn-error -A))))
9 |
10 | ; ---------------
11 | ; lamp main files
12 |
13 | (library
14 | (name lamp)
15 | (preprocess
16 | (pps ppx_deriving.show ppx_jane ppx_sexp_conv))
17 | (libraries base fmt nice_parser))
18 |
19 | (ocamllex lexer)
20 |
21 | (menhir
22 | (modules menhir_parser))
23 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/err.ml:
--------------------------------------------------------------------------------
1 | exception Syntax of { sl : int; sc : int; el : int; ec : int }
2 | exception Lexing of { l : int; s : string }
3 |
4 | let syntax_error () =
5 | let start_pos = Parsing.rhs_start_pos 1 in
6 | let end_pos = Parsing.rhs_end_pos 1 in
7 | let sl = start_pos.pos_lnum
8 | and sc = start_pos.pos_cnum - start_pos.pos_bol
9 | and el = end_pos.pos_lnum
10 | and ec = end_pos.pos_cnum - end_pos.pos_bol in
11 | raise (Syntax { sl; sc; el; ec })
12 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/eval.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | let todo () = failwith "TODO"
4 |
5 | exception Stuck of string
6 | (** Exception indicating that evaluation is stuck *)
7 |
8 | (** Raises an exception indicating that evaluation got stuck. *)
9 | let im_stuck msg = raise (Stuck msg)
10 |
11 | (** Computes the set of free variables in the given expression *)
12 | let rec free_vars (e : expr) : Vars.t =
13 | (* This line imports the functions in Vars, so you can write [diff .. ..]
14 | instead of [Vars.diff .. ..] *)
15 | let open Vars in
16 | match e with _ -> todo ()
17 |
18 | (** Perform substitution c[x -> e], i.e., substituting x with e in c *)
19 | let rec subst (x : string) (e : expr) (c : expr) : expr =
20 | match c with _ -> todo ()
21 |
22 | (** Evaluate expression e *)
23 | let rec eval (e : expr) : expr =
24 | try match e with _ -> todo ()
25 | with Stuck msg ->
26 | im_stuck (Fmt.str "%s\nin expression %a" msg Pretty.expr e)
27 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/eval.mli:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | exception Stuck of string
4 |
5 | val free_vars : expr -> Vars.t
6 | (** Return the set of free variable references in an expression *)
7 |
8 | val subst : string -> expr -> expr -> expr
9 | (** Substitution *)
10 |
11 | val eval : expr -> expr
12 | (** Interpret an expression *)
13 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/lexer.mll:
--------------------------------------------------------------------------------
1 | {
2 | open Menhir_parser
3 | exception LexError of string
4 |
5 | let failwith msg = raise (LexError msg)
6 |
7 | let illegal c =
8 | failwith (Printf.sprintf "[lexer] unexpected character: '%c'" c)
9 | }
10 |
11 | let ws = ' ' | '\t'
12 | let newline = "\r\n" | '\r' | '\n'
13 |
14 | rule next_token = parse
15 | | ws+ { next_token lexbuf }
16 | | newline { Lexing.new_line lexbuf; next_token lexbuf }
17 | | "#load" { CLOAD }
18 | | "#save" { CSAVE }
19 | | "#print" { CPRINT }
20 | | "#clear" { CCLEAR }
21 | | "#let" { CLET }
22 | | "#synth" { CSYNTH }
23 | | "let" { LET }
24 | | "in" { IN }
25 | | "fun" { FUN }
26 | | "rec" { REC }
27 | | "with" { WITH }
28 | | "match" { MATCH }
29 | | "end" { END }
30 | | "lambda" { LAMBDA }
31 | | "if" { IF }
32 | | "then" { THEN }
33 | | "else" { ELSE }
34 | | "true" { TRUE }
35 | | "false" { FALSE }
36 | | "fix" { FIX }
37 | | "is" { IS }
38 | | ".1" { FST }
39 | | ".2" { SND }
40 | | "Nil" { NIL }
41 | | "::" { CONS }
42 | | "+" { PLUS }
43 | | "-" { SUB }
44 | | "*" { TIMES }
45 | | "Int" { TYINT }
46 | | "Bool" { TYBOOL }
47 | | "List" { TYLIST }
48 | | '\'' { TICK }
49 | | "->" { THINARROW }
50 | | ':' { COLON }
51 | | ">" { GT }
52 | | '=' { EQ }
53 | | "|" { BAR }
54 | | "<" { LT }
55 | | '(' { LPAREN }
56 | | ')' { RPAREN }
57 | | '[' { LBRACK }
58 | | ']' { RBRACK }
59 | | '.' { DOT }
60 | | ',' { COMMA }
61 | | "$1" { CASE1 }
62 | | "$2" { CASE2 }
63 | | '!' { VOID }
64 | | "//" { comment lexbuf }
65 | | "forall" { FORALL }
66 | | ['0'-'9']+ as n { NUMBER(Base.Int.of_string(n)) }
67 | | ['a'-'z' 'A'-'Z' '_']['a'-'z' 'A'-'Z' '0'-'9' '_']* as x { ID(x) }
68 | | ['a'-'z' 'A'-'Z' '0'-'9' '_' '-' '/' ]+ ".txt" as x { FILE(x) }
69 | | eof { EOF }
70 | | _ as c { illegal c }
71 |
72 | and comment = parse
73 | | newline { Lexing.new_line lexbuf; next_token lexbuf }
74 | | _ { comment lexbuf }
75 | | eof { EOF }
76 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/menhir_parser.mly:
--------------------------------------------------------------------------------
1 | %{
2 |
3 | let mk_lambdas (xs : (string * Ast.ty option) list) (e : Ast.expr) (r: Ast.ty option) =
4 | let f (x, topt) e' = Ast.Lambda(topt, (x,e')) in
5 | List.fold_right f xs (match r with None -> e | Some r -> Annot(e, r))
6 |
7 | let mk_tfun_of_list (xs: (string * Ast.ty option) list) (r: Ast.ty option) : Ast.ty option =
8 | match r with
9 | | Some r ->
10 | List.fold_right (fun (x,topt) fo ->
11 | match topt, fo with
12 | | Some t, Some r -> Some (Ast.TFun(t,r))
13 | | _ -> None) xs (Some r)
14 | | None -> None
15 | let mk_let (x: string) (ty: Ast.ty option) (e1: Ast.expr) (e2: Ast.expr) : Ast.expr =
16 | match ty with
17 | | Some t -> Ast.Let(Ast.Annot(e1, t), (x, e2))
18 | | None -> Ast.Let(e1, (x, e2))
19 | %}
20 |
21 | /* Tokens */
22 |
23 | %token EOF FUN REC MATCH BAR END GT EQ LT LPAREN RPAREN DOT COMMA FIX IS
24 | %token TRUE FALSE TYBOOL
25 | %token LET IN IF THEN ELSE WITH LAMBDA
26 | %token NIL CONS
27 | %token TYINT TYLIST THINARROW COLON LBRACK RBRACK
28 | %token CLET CPRINT CCLEAR CLOAD CSAVE CSYNTH
29 | %token TICK FORALL
30 | %token FST SND CASE1 CASE2 VOID
31 |
32 | %token PLUS SUB TIMES
33 | %token NUMBER
34 | %token ID
35 | %token FILE
36 |
37 | %nonassoc IN DOT ELSE IS CASE1 CASE2
38 | %nonassoc COLON
39 | %right CONS
40 | %left LT GT EQ
41 | %left PLUS SUB
42 | %right THINARROW
43 | %left TIMES
44 |
45 | %start expr_eof
46 | %type expr_eof
47 |
48 | %start repl_command_eof
49 | %type repl_command_eof
50 |
51 | %start script_eof
52 | %type script_eof
53 |
54 | %start ty_eof
55 | %type ty_eof
56 |
57 |
58 | %%
59 |
60 | file_command:
61 | | CLET bind EQ expr
62 | { let (x,t) = $2 in Cmd.CLet(x,t,$4) }
63 | | CPRINT
64 | { Cmd.CPrint}
65 | | CCLEAR
66 | { Cmd.CClear}
67 | | CLOAD FILE
68 | { Cmd.CLoad $2 }
69 | | CSAVE FILE
70 | { Cmd.CSave $2 }
71 | | CSYNTH ty
72 | { Cmd.CSynth $2 }
73 |
74 | repl_command:
75 | | c=file_command
76 | { c }
77 | | expr
78 | { Cmd.CEval $1 }
79 |
80 | repl_command_eof:
81 | | c=repl_command EOF
82 | { c }
83 |
84 | script_eof:
85 | | cs=list(file_command) EOF
86 | { cs }
87 |
88 | expr_eof:
89 | | e=expr EOF
90 | { e }
91 |
92 | ty_var:
93 | | TICK ID
94 | { ("\'" ^ $2) }
95 |
96 | ty_atom:
97 | | x=ty_var
98 | { Ast.TVar x }
99 | | TYINT
100 | { Ast.TInt }
101 | | TYBOOL
102 | { Ast.TBool }
103 | | LPAREN t=ty RPAREN
104 | { t }
105 | | LPAREN RPAREN
106 | { Ast.TUnit }
107 | | VOID
108 | { Ast.TVoid }
109 |
110 | ty_eof:
111 | | t=ty EOF
112 | { t }
113 |
114 | ty:
115 | | t=ty_atom
116 | { t }
117 | | t1=ty THINARROW t2=ty
118 | { TFun (t1, t2) }
119 | | t1=ty TIMES t2=ty
120 | { TProd (t1, t2) }
121 | | t1=ty PLUS t2=ty
122 | { TSum (t1, t2) }
123 | | TYLIST LBRACK t=ty RBRACK
124 | { TList t }
125 |
126 | bind:
127 | | x=ID COLON t=ty
128 | { (x, Some t) }
129 | | x=ID
130 | { (x, None) }
131 |
132 | bindlist:
133 | | l=separated_nonempty_list(COMMA, bind)
134 | { l }
135 |
136 | expr:
137 | | e=app
138 | { e }
139 | | e=expr COLON t=ty
140 | { Ast.Annot(e, t) }
141 | | LAMBDA bs=bindlist DOT e=expr
142 | { mk_lambdas bs e None }
143 | | FUN REC b=bind WITH bs=bindlist EQ e1=expr IN e2=expr
144 | { let (x,r) = b in
145 | Let(Fix (mk_tfun_of_list bs r, (x, mk_lambdas bs e1 r)), (x, e2)) }
146 | | FUN b=bind WITH bs=bindlist EQ e1=expr IN e2=expr
147 | { let (x,r) = b in
148 | Let(mk_lambdas bs e1 r, (x, e2)) }
149 | | FIX b=bind IS e=expr
150 | { let (x, topt) = b in Fix (topt, (x, e)) }
151 | | LET b=bind EQ e1=expr IN e2=expr
152 | { let (x, topt) = b in
153 | mk_let x topt e1 e2 }
154 | | IF e1=expr THEN e2=expr ELSE e3=expr
155 | { IfThenElse(e1, e2, e3) }
156 | | e=list_match
157 | { e }
158 | | e=sum_match
159 | { e }
160 | | e=void_match
161 | { e }
162 | | e=binop(expr)
163 | { e }
164 | | CASE1; e=expr
165 | { E1 e }
166 | | CASE2; e=expr
167 | { E2 e }
168 |
169 | binop(expr):
170 | | expr PLUS expr
171 | { Binop(Add, $1, $3) }
172 | | expr SUB expr
173 | { Binop(Sub, $1, $3) }
174 | | expr TIMES expr
175 | { Binop(Mul, $1, $3) }
176 | | expr LT expr
177 | { Comp(Lt, $1, $3) }
178 | | expr GT expr
179 | { Comp(Gt, $1, $3) }
180 | | expr EQ expr
181 | { Comp(Eq, $1, $3) }
182 | | expr CONS expr
183 | { ListCons($1, $3) }
184 |
185 | app:
186 | | term
187 | { $1 }
188 | | e1=app e2=term
189 | { App(e1, e2) }
190 |
191 | tyarg_opt:
192 | | LBRACK ty RBRACK
193 | { Some $2 }
194 | |
195 | { None }
196 |
197 | nil_branch:
198 | | NIL THINARROW e=expr
199 | { e }
200 |
201 | cons_branch:
202 | | x=ID CONS y=ID THINARROW e=expr
203 | { (x, y, e) }
204 |
205 | list_match:
206 | | MATCH e1=expr WITH option(BAR) e2=nil_branch BAR b3=cons_branch END
207 | {
208 | let (x,y,e3) = b3 in
209 | Ast.ListMatch(e1, e2, (x, (y, e3)))}
210 |
211 | case(TAG):
212 | | TAG x=ID THINARROW e=expr
213 | { (x, e) }
214 |
215 | sum_match:
216 | | MATCH e1=expr WITH option(BAR) b2=case(CASE1) BAR b3=case(CASE2) END
217 | { Ast.Either(e1, b2, b3) }
218 |
219 | void_match:
220 | | MATCH e1=expr WITH END
221 | { Ast.Absurd e1 }
222 |
223 | term:
224 | | x=ID
225 | { Ast.Var x }
226 | | n=NUMBER
227 | { Ast.Num n }
228 | | TRUE
229 | { Ast.True }
230 | | FALSE
231 | { Ast.False }
232 | | NIL t=tyarg_opt
233 | { ListNil t }
234 | | LPAREN e=expr RPAREN
235 | { e }
236 | | LPAREN e1=expr COMMA e2=expr RPAREN
237 | { Ast.Both(e1, e2) }
238 | | e=term FST
239 | { Ast.I1 e }
240 | | e=term SND
241 | { Ast.I2 e }
242 | | LPAREN RPAREN
243 | { Ast.Unit }
244 |
245 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/parser.ml:
--------------------------------------------------------------------------------
1 | module Expr = Nice_parser.Make (struct
2 | type result = Ast.expr
3 | type token = Menhir_parser.token
4 |
5 | exception ParseError = Menhir_parser.Error
6 |
7 | let parse = Menhir_parser.expr_eof
8 |
9 | include Lexer
10 | end)
11 |
12 | module Command = Nice_parser.Make (struct
13 | type result = Cmd.t
14 | type token = Menhir_parser.token
15 |
16 | exception ParseError = Menhir_parser.Error
17 |
18 | let parse = Menhir_parser.repl_command_eof
19 |
20 | include Lexer
21 | end)
22 |
23 | module Script = Nice_parser.Make (struct
24 | type result = Cmd.script
25 | type token = Menhir_parser.token
26 |
27 | exception ParseError = Menhir_parser.Error
28 |
29 | let parse = Menhir_parser.script_eof
30 |
31 | include Lexer
32 | end)
33 |
34 | module Ty = Nice_parser.Make (struct
35 | type result = Ast.ty
36 | type token = Menhir_parser.token
37 |
38 | exception ParseError = Menhir_parser.Error
39 |
40 | let parse = Menhir_parser.ty_eof
41 |
42 | include Lexer
43 | end)
44 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/typecheck.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 | open Base
3 |
4 | type env = (string * ty) list
5 | (** Typing environment, aka Gamma *)
6 |
7 | (** Helper function to look up a variable in the env *)
8 | let find : env -> string -> ty option = List.Assoc.find ~equal:String.equal
9 |
10 | (** Helper function to insert a (variable, ty) pair into the env *)
11 | let add : env -> string -> ty -> env = List.Assoc.add ~equal:String.equal
12 |
13 | exception Type_error of string
14 |
15 | let ty_err msg = raise (Type_error msg)
16 | let rec equal_ty (t1 : ty) (t2 : ty) : bool = failwith "TODO"
17 |
18 | let rec abstract_eval (env : env) (e : expr) : ty =
19 | try
20 | match e with
21 | (* T-Int rule *)
22 | | Num _ -> TInt
23 | (* T-True and T-false *)
24 | | True | False -> TBool
25 | (* Your code here *)
26 | | _ -> failwith "TODO"
27 | with Type_error msg -> ty_err (msg ^ "\nin expression " ^ show_expr e)
28 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/typecheck.mli:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | type env = (string * ty) list
4 |
5 | val abstract_eval : env -> Ast.expr -> ty
6 |
7 | exception Type_error of string
8 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/vars.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | type t = Set.M(String).t
4 |
5 | let pp : t Fmt.t =
6 | Fmt.using Set.to_list Fmt.(braces @@ list ~sep:(any ", ") string)
7 |
8 | let show = Fmt.to_to_string pp
9 | let equal = Set.equal
10 | let compare = Set.compare_direct
11 | let empty = Set.empty (module String)
12 | let diff = Set.diff
13 | let singleton = Set.singleton (module String)
14 | let add s v = Set.add v s
15 | let union = Set.union
16 | let mem s v = Set.mem v s
17 | let of_list = Set.of_list (module String)
18 | let to_list = Set.to_list
19 | let size = Set.length
20 |
--------------------------------------------------------------------------------
/homework/hw4/lamp/vars.mli:
--------------------------------------------------------------------------------
1 | (** Signature for variable set module *)
2 |
3 | type t [@@deriving equal, compare, show]
4 | (** type of variable set *)
5 |
6 | val empty : t
7 | (** empty set *)
8 |
9 | val mem : string -> t -> bool
10 | (** [mem x s] is true iff [x] is in [s] *)
11 |
12 | val diff : t -> t -> t
13 | (** [diff s1 s2] is the set of elements in [s1] but not in [s2] *)
14 |
15 | val singleton : string -> t
16 | (** [singleton x] is the singleton set containing [x] *)
17 |
18 | val add : string -> t -> t
19 | (** [add x s] is the set [s] with [x] added to it *)
20 |
21 | val union : t -> t -> t
22 | (** [union s1 s2] is the union of [s1] and [s2] *)
23 |
24 | val of_list : string list -> t
25 | (** [of_list l] converts a list [l] into a set *)
26 |
27 | val to_list : t -> string list
28 | (** [to_list x] converts a set [s] into a list *)
29 |
30 | val size : t -> int
31 | (** Return the size of a set *)
32 |
--------------------------------------------------------------------------------
/homework/hw4/test/dune:
--------------------------------------------------------------------------------
1 | (test
2 | (name hw4_test)
3 | (libraries fmt alcotest lamp)
4 | (preprocess
5 | (pps ppx_deriving.show ppx_jane))
6 | (deps
7 | (source_tree examples)))
8 |
--------------------------------------------------------------------------------
/homework/hw4/test/examples/add_n.lp:
--------------------------------------------------------------------------------
1 | fun rec map : List[Int] with f: Int -> Int, l: List[Int] =
2 | match l with
3 | | Nil -> Nil[Int]
4 | | x::xs -> f x :: map f l
5 | end in
6 |
7 | // increments every element of a list by n
8 | fun add_n : List[Int] with l: List[Int], n: Int =
9 | map (lambda m: Int. n + m) l in
10 |
11 | add_n (1::2::Nil[Int]) 10
--------------------------------------------------------------------------------
/homework/hw4/test/examples/fib.lp:
--------------------------------------------------------------------------------
1 | fun rec fib: Int with n: Int =
2 | if n = 0 then 0
3 | else if n = 1 then 1
4 | else
5 | let x = fib (n-1) in
6 | let y : Int = fib (n-2) in
7 | x + y
8 | in (fib 30 : Int)
--------------------------------------------------------------------------------
/homework/hw4/test/hw4_test.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | let () =
4 | Alcotest.run "hw4"
5 | (List.map
6 | ~f:(fun (name, tests) ->
7 | (name, List.map ~f:(Alcotest.test_case name `Quick) tests))
8 | Test_typing.tests)
9 |
--------------------------------------------------------------------------------
/homework/hw4/test/test_typing.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Lamp
3 | open Ast
4 |
5 | exception Timeout
6 |
7 | (* run a function with specified timeout:
8 | https://discuss.ocaml.org/t/computation-with-time-constraint/5548/9 *)
9 | let with_timeout timeout f =
10 | let _ =
11 | Stdlib.Sys.set_signal Stdlib.Sys.sigalrm
12 | (Stdlib.Sys.Signal_handle (fun _ -> raise Timeout))
13 | in
14 | ignore (Unix.alarm timeout);
15 | try
16 | let r = f () in
17 | ignore (Unix.alarm 0);
18 | r
19 | with e ->
20 | ignore (Unix.alarm 0);
21 | raise e
22 |
23 | (* Unit test utilities *)
24 | let texpr = Alcotest.testable Pretty.expr equal_expr
25 | let tvars = Alcotest.testable Vars.pp Vars.equal
26 | let tty = Alcotest.testable Pretty.ty Ast.equal_ty
27 |
28 | let parse s =
29 | try Parser.Expr.parse_string s
30 | with _ -> Alcotest.fail ("Failed to parse: " ^ s)
31 |
32 | let parse_file filename =
33 | try Parser.Expr.parse_file filename
34 | with _ -> Alcotest.fail ("Failed to parse file: " ^ filename)
35 |
36 | let parse_ty s =
37 | try Parser.Ty.parse_string s
38 | with _ -> Alcotest.fail ("Failed to parse: " ^ s)
39 |
40 | (** Helper function to check that the type checker abstractly evaluates
41 | * the expression to type t *)
42 | let check_well_typed ~gamma e t () =
43 | let tact =
44 | try Typecheck.abstract_eval gamma e
45 | with Typecheck.Type_error msg -> failwith ("Type error!\n" ^ msg)
46 | in
47 | Alcotest.(check' tty)
48 | ~msg:(Fmt.str "%a" Pretty.expr e)
49 | ~expected:t ~actual:tact
50 |
51 | (** Helper function to check that the type checker abstractly evaluates
52 | * the expression (given as a string) to type t *)
53 | let check_well_typed_s ~gamma s t () =
54 | check_well_typed
55 | ~gamma:(List.map gamma ~f:(fun (x, t) -> (x, parse_ty t)))
56 | (parse s) (parse_ty t) ()
57 |
58 | let check_well_typed_file s t () =
59 | check_well_typed ~gamma:[] (parse_file s) (parse_ty t) ()
60 |
61 | (** Helper function to check that the type checker determines that the expression is
62 | ill-typed. *)
63 | let check_ill_typed ~gamma s () =
64 | try
65 | let t = Typecheck.abstract_eval gamma (parse s) in
66 | Alcotest.fail ("abstractly evaluated to " ^ show_ty t)
67 | with Typecheck.Type_error _ -> ()
68 |
69 | let well_typed_tests =
70 | [
71 | check_well_typed_s ~gamma:[] (* input expression *) "0"
72 | (* expected output type *) "Int";
73 | check_well_typed_s
74 | ~gamma:[ ("x", "Bool") ]
75 | (* input expression *) "x" (* expected output type *) "Bool";
76 | check_well_typed_file "examples/fib.lp" "Int";
77 | check_well_typed_file "examples/add_n.lp" "List[Int]";
78 | ]
79 |
80 | let ill_typed_tests =
81 | [ check_ill_typed ~gamma:[] (* input expression *) "1 + true" ]
82 |
83 | let tests = [ ("well_typed", well_typed_tests); ("ill_typed", ill_typed_tests) ]
84 |
--------------------------------------------------------------------------------
/homework/hw5/.ocamlformat:
--------------------------------------------------------------------------------
1 | profile = default
2 | version = 0.26.2
--------------------------------------------------------------------------------
/homework/hw5/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: zip
2 |
3 | FILES := lamp/typeinfer.ml lamp/curry_howard.ml
4 | ARCHIVE := submission.zip
5 |
6 | $(ARCHIVE): $(FILES)
7 | zip -j $(ARCHIVE) $(FILES)
8 |
9 | zip: $(ARCHIVE)
--------------------------------------------------------------------------------
/homework/hw5/bin/dune:
--------------------------------------------------------------------------------
1 | ; Build configuration
2 |
3 | ; Treat warnings as non-fatal
4 |
5 | (env
6 | (dev
7 | (flags
8 | (:standard -warn-error -A))))
9 |
10 | ; ---------------
11 | ; generate date information
12 |
13 | (rule
14 | (target build_metadata.ml)
15 | (deps (universe))
16 | (action
17 | (run bash "-c" "echo let date = \\\"$(date)\\\" > %{target}")))
18 |
19 | ; ---------------
20 | ; statically link the binary for CSIL if using musl libc
21 |
22 | (rule
23 | (target build_flags)
24 | (enabled_if
25 | (= %{ocaml-config:c_compiler} "musl-gcc"))
26 | (action
27 | (write-file %{target} "(-ccopt -static)")))
28 |
29 | (rule
30 | (target build_flags)
31 | (enabled_if
32 | (<> %{ocaml-config:c_compiler} "musl-gcc"))
33 | (action
34 | (write-file %{target} "()")))
35 |
36 | ; ---------------
37 | ; lamp repl
38 |
39 | (executable
40 | (name repl)
41 | (modules build_metadata repl)
42 | (libraries base lamp linenoise cmdliner)
43 | (flags
44 | :standard
45 | (:include build_flags))
46 | (modes exe))
47 |
48 | (install
49 | (section bin)
50 | (files repl.exe))
51 |
--------------------------------------------------------------------------------
/homework/hw5/bin/repl.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Lamp
3 | open Ast
4 |
5 | module Opts = struct
6 | let path : string option ref = ref None
7 | let fast : bool ref = ref false
8 | let quiet : bool ref = ref false
9 | let typed : bool ref = ref false
10 | let tyinf : bool ref = ref true
11 | end
12 |
13 | let () =
14 | let open Parser in
15 | Expr.pp_exceptions ();
16 | Command.pp_exceptions ();
17 | Script.pp_exceptions ();
18 | Ty.pp_exceptions ()
19 |
20 | let get_eval () e = Eval.eval e
21 |
22 | let handler (f : unit -> unit) : unit =
23 | try f () with
24 | | Eval.Stuck msg -> Fmt.pr "runtime error: %s\n%!" msg
25 | | Stack_overflow ->
26 | Fmt.epr
27 | "Interpreter stack overflow; too many recursive function calls\n%!"
28 | | Typecheck.Type_error s -> Fmt.epr "Type error: %s\n%!" s
29 | | Typeinfer.Type_error s -> Fmt.epr "Type error: %s\n%!" s
30 |
31 | let context : ((string * ty option) * expr) list ref = ref []
32 | let meta_context : ((string * ty option) * expr) list ref = ref []
33 | let get_context () = context
34 |
35 | let print_context () =
36 | Fmt.pr ">> context >>\n%!";
37 | Fmt.pr " @[%a@]\n%!"
38 | Fmt.(
39 | vbox
40 | (list ~sep:cut
41 | (box
42 | (pair ~sep:(any " ->@ ")
43 | (pair string (option (const string " : " ++ Pretty.ty)))
44 | Pretty.expr))))
45 | (Base.List.rev !(get_context ()));
46 | Fmt.pr "<< context <<\n%!"
47 |
48 | let context_expr e =
49 | Base.List.fold_left ~init:e
50 | ~f:(fun e ((x, t), def) -> Ast.Expr.lett x ~t def ~in_:e)
51 | !(get_context ())
52 |
53 | let check_and_run (e : expr) : expr =
54 | (if !Opts.typed then
55 | let ty = Typecheck.abstract_eval [] e in
56 | Fmt.pr "type ==> @[%a@]\n%!" Pretty.ty ty
57 | else if !Opts.tyinf then
58 | let ty = Typeinfer.infer e in
59 | Fmt.pr "inferred type ==> @[%a@]\n%!" Pretty.ty ty);
60 | get_eval () e
61 |
62 | let handle_let_or_eval (c : Cmd.t) =
63 | match c with
64 | | CLet (x, t, def) ->
65 | (let v = check_and_run (context_expr def) in
66 | Fmt.pr "%s = @[%a@]\n%!" x Pretty.expr v);
67 | get_context () := ((x, t), def) :: !(get_context ())
68 | | CEval e ->
69 | Fmt.pr "<== @[%a@]\n%!" Pretty.expr e;
70 | if not !Opts.quiet then Fmt.pr "<== AST:\n@[%a@]\n%!" Ast.Pretty.expr e;
71 | let v = check_and_run (context_expr e) in
72 | Fmt.pr "[eval] ==> @[%a@]\n%!" Pretty.expr v
73 | | _ -> failwith "Impossible"
74 |
75 | let commands = [ "#print"; "#clear"; "#load"; "#save" ]
76 | let replayable = function Cmd.CLet _ -> true | _ -> false
77 |
78 | let rec repl () =
79 | try
80 | match LNoise.linenoise "> " with
81 | | None -> ()
82 | | Some l ->
83 | LNoise.history_add l |> ignore;
84 | handler (fun () ->
85 | match Lamp.Parser.Command.parse_string l with
86 | | CPrint -> print_context ()
87 | | CClear ->
88 | get_context () := [];
89 | Fmt.pr ". context cleared\n%!"
90 | | CLoad f ->
91 | (* match LNoise.history_load ~filename:f with
92 | | Ok () -> *)
93 | Fmt.pr ". loading history from %s\n%!" f;
94 | Fmt.pr ". replaying history...\n%!";
95 | (* load content of file f and split it into lines *)
96 | Parser.Script.parse_file f
97 | |> Base.List.filter ~f:replayable
98 | |> Base.List.iter ~f:(fun c ->
99 | try handle_let_or_eval c with _ -> ());
100 | Fmt.pr ". history replayed\n%!"
101 | (* | Error e -> Fmt.pr ". error loading history: %s\n%!" e) *)
102 | | CSave f -> (
103 | match LNoise.history_save ~filename:f with
104 | | Ok () -> Fmt.pr ". history saved to %s\n%!" f
105 | | Error e -> Fmt.pr ". error saving history: %s\n%!" e)
106 | | (CLet _ | CEval _) as c -> handle_let_or_eval c
107 | | CSynth t -> (
108 | match Curry_howard.synthesize t with
109 | | Some e ->
110 | Fmt.pr "[synthesis] ==> %a\n%!" Fmt.(Pretty.expr) e;
111 | ignore @@ check_and_run e
112 | | None -> Fmt.pr "[synthesis] fail\n%!"));
113 | repl ()
114 | with Stdlib.Sys.Break -> repl ()
115 |
116 | let read_args () =
117 | let set_file s = Opts.path := Some s in
118 | let opts =
119 | [
120 | ( "-typed",
121 | Stdlib.Arg.Set Opts.typed,
122 | "enable type checking in interpreter (default: off)" );
123 | ( "-tyinf",
124 | Stdlib.Arg.Set Opts.tyinf,
125 | "enable type inference in interpreter (default: off)" );
126 | ("-q", Stdlib.Arg.Set Opts.quiet, "suppress AST printing (default: off)");
127 | ( "-fast",
128 | Stdlib.Arg.Set Opts.fast,
129 | "use closure semantics instead of eager substitution (default: off)" );
130 | ]
131 | in
132 | Stdlib.Arg.parse opts set_file ""
133 |
134 | let main () =
135 | read_args ();
136 | match !Opts.path with
137 | | Some file_name ->
138 | handler (fun () ->
139 | let v = check_and_run (Parser.Expr.parse_file file_name) in
140 | Fmt.pr "@[%a@]\n%!" Pretty.expr v)
141 | | None ->
142 | (* repl mode *)
143 | Fmt.pr "Welcome to lambda+! Built on: %s\n%!" Build_metadata.date;
144 | LNoise.history_set ~max_length:100 |> ignore;
145 | repl ()
146 | ;;
147 |
148 | main ()
149 |
--------------------------------------------------------------------------------
/homework/hw5/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.11)
2 |
3 | (name hw5)
4 |
5 | (generate_opam_files true)
6 |
7 | (source
8 | (github username/reponame))
9 |
10 | (authors "Junrui Liu, Bryan Tan, Yu Feng")
11 |
12 | (maintainers "Junrui Liu")
13 |
14 | (documentation https://url/to/documentation)
15 |
16 | (using menhir 2.1)
17 |
18 | (package
19 | (name hw5)
20 | (allow_empty)
21 | (synopsis "A short synopsis")
22 | (description "A longer description")
23 | (depends
24 | (ocaml
25 | (= 4.14.1))
26 | dune
27 | base
28 | alcotest
29 | ppx_deriving
30 | ppx_import
31 | ppx_sexp_conv
32 | utop
33 | linenoise
34 | fmt)
35 | (tags
36 | (topics "to describe" your project)))
37 |
38 | ; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
39 |
--------------------------------------------------------------------------------
/homework/hw5/hw5.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | synopsis: "A short synopsis"
4 | description: "A longer description"
5 | maintainer: ["Junrui Liu"]
6 | authors: ["Junrui Liu, Bryan Tan, Yu Feng"]
7 | tags: ["topics" "to describe" "your" "project"]
8 | homepage: "https://github.com/username/reponame"
9 | doc: "https://url/to/documentation"
10 | bug-reports: "https://github.com/username/reponame/issues"
11 | depends: [
12 | "ocaml" {= "4.14.1"}
13 | "dune" {>= "3.11"}
14 | "base"
15 | "alcotest"
16 | "ppx_deriving"
17 | "ppx_import"
18 | "ppx_sexp_conv"
19 | "utop"
20 | "linenoise"
21 | "fmt"
22 | "odoc" {with-doc}
23 | ]
24 | build: [
25 | ["dune" "subst"] {dev}
26 | [
27 | "dune"
28 | "build"
29 | "-p"
30 | name
31 | "-j"
32 | jobs
33 | "@install"
34 | "@runtest" {with-test}
35 | "@doc" {with-doc}
36 | ]
37 | ]
38 | dev-repo: "git+https://github.com/username/reponame.git"
39 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/ast.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | (** Monomorphic types *)
4 | type ty =
5 | | TVar of string (** Type variable *)
6 | | TInt
7 | | TBool
8 | | TList of ty
9 | | TFun of ty * ty
10 | | TProd of ty * ty
11 | | TSum of ty * ty
12 | | TUnit
13 | | TVoid
14 | [@@deriving equal, compare, show, sexp]
15 |
16 | (** Binary operators *)
17 | type binop = Add | Sub | Mul [@@deriving equal, show]
18 |
19 | (** Relational comparison operators *)
20 | type relop = Eq | Lt | Gt [@@deriving equal, show]
21 |
22 | type 'a binder = string * 'a [@@deriving equal, show]
23 |
24 | (** AST of Lambda+ expressions *)
25 | type expr =
26 | (* arithmetic *)
27 | | Num of int
28 | | Binop of binop * expr * expr
29 | (* binding *)
30 | | Var of string
31 | (* lambda calculus *)
32 | | Lambda of ty option * expr binder
33 | | App of expr * expr
34 | (* let expression *)
35 | | Let of expr * expr binder
36 | (* booleans *)
37 | | True
38 | | False
39 | | IfThenElse of expr * expr * expr
40 | | Comp of relop * expr * expr
41 | (* lists *)
42 | | ListNil of ty option
43 | | ListCons of expr * expr
44 | | ListMatch of expr * expr * expr binder binder
45 | (* cases *)
46 | | E1 of expr
47 | | E2 of expr
48 | | Either of expr * expr binder * expr binder
49 | (* fix *)
50 | | Fix of ty option * expr binder
51 | (* products *)
52 | | Both of expr * expr
53 | | I1 of expr
54 | | I2 of expr
55 | (* type annotation *)
56 | | Annot of expr * ty
57 | (* unit *)
58 | | Unit
59 | (* void *)
60 | | Absurd of expr
61 | [@@deriving equal, show]
62 |
63 | (** Pretty-printers *)
64 | module Pretty = struct
65 | open Fmt
66 |
67 | (** Pretty print a [ty] *)
68 | let rec ty : ty Fmt.t =
69 | fun ppf ->
70 | let open Fmt in
71 | function
72 | | TVar x -> string ppf x
73 | | TInt -> string ppf "Int"
74 | | TBool -> string ppf "Bool"
75 | | TFun (t1, t2) -> pf ppf "@[%a -> %a@]" (pp_nested ty) t1 ty t2
76 | | TList t -> pf ppf "List[%a]" ty t
77 | | TSum (t1, t2) -> pf ppf "@[%a + %a@]" (pp_nested ty) t1 (pp_nested ty) t2
78 | | TProd (t1, t2) -> pf ppf "@[%a * %a@]" (pp_nested ty) t1 (pp_nested ty) t2
79 | | TUnit -> string ppf "()"
80 | | TVoid -> string ppf "!"
81 |
82 | and pp_nested pp ppf t =
83 | let is_complex_ty = function TFun _ | TProd _ -> true | _ -> false in
84 | if is_complex_ty t then Fmt.parens pp ppf t else pp ppf t
85 |
86 | let pp_binop : binop Fmt.t =
87 | fun ppf op ->
88 | string ppf (match op with Add -> "+" | Sub -> "-" | Mul -> "*")
89 |
90 | let pp_relop : relop Fmt.t =
91 | fun ppf op -> string ppf (match op with Eq -> "=" | Lt -> "<" | Gt -> ">")
92 |
93 | let rec expr : expr Fmt.t =
94 | fun ppf ->
95 | let is_complex = function
96 | | Num _ | Var _ | True | False | Both _ | ListNil _ -> false
97 | | _ -> true
98 | in
99 | let pp_nested pp ppf e =
100 | if is_complex e then (parens pp) ppf e else pp ppf e
101 | in
102 | let pp = pp_nested expr in
103 | function
104 | | Num n -> int ppf n
105 | | Var x -> string ppf x
106 | | Binop (op, e1, e2) -> pf ppf "%a %a@ %a" pp e1 pp_binop op pp e2
107 | | Lambda (topt, (x, e)) ->
108 | pf ppf "lambda %s%a.@ %a" x
109 | (option (const string ": " ++ ty))
110 | topt expr e
111 | | Let (e1, (x, e2)) ->
112 | pf ppf "@[le@[t %s = %a in@]@ @[%a@]@]" x expr e1 expr e2
113 | | App (e1, e2) -> pf ppf "%a@ %a" pp e1 pp e2
114 | | True -> string ppf "true"
115 | | False -> string ppf "false"
116 | | IfThenElse (e1, e2, e3) ->
117 | pf ppf "@[if %a@ then %a@ else %a@]" expr e1 expr e2 expr e3
118 | | Comp (op, e1, e2) -> pf ppf "%a %a@ %a" pp e1 pp_relop op pp e2
119 | | ListNil topt -> pf ppf "Nil%a" (option (brackets ty)) topt
120 | | ListCons (e1, e2) -> pf ppf "%a ::@ %a" pp e1 pp e2
121 | | ListMatch (e1, e2, (h, (t, e3))) ->
122 | pf ppf "@[match %a with@;| Nil -> %a@;| %s :: %s -> %a@;end@]" expr
123 | e1 expr e2 h t expr e3
124 | | E1 e -> pf ppf "$1@ %a" pp e
125 | | E2 e -> pf ppf "$2@ %a" pp e
126 | | Either (e1, (x, e2), (y, e3)) ->
127 | pf ppf
128 | "@[match @[%a@] with@;| $1 %s -> @[%a@]@;| $2 %s -> @[%a@]@;end@]"
129 | expr e1 x expr e2 y expr e3
130 | | Fix (topt, (f, e)) ->
131 | pf ppf "fix %s%a is@ %a" f
132 | (option (const string ": " ++ ty))
133 | topt expr e
134 | | Both (e1, e2) -> pf ppf "(%a,@ %a)" pp e1 pp e2
135 | | I1 e -> pf ppf "%a.1" pp e
136 | | I2 e -> pf ppf "%a.2" pp e
137 | | Annot (e, t) -> pf ppf "%a :@ %a" pp e ty t
138 | | Unit -> string ppf "()"
139 | | Absurd e -> pf ppf "match %a with end" pp e
140 | end
141 |
142 | module Expr = struct
143 | let lam x e = Lambda (None, (x, e))
144 | let lamt ~t x e = Lambda (Some t, (x, e))
145 | let v x = Var x
146 | let i n = Num n
147 | let ( + ) e1 e2 = Binop (Add, e1, e2)
148 | let ( - ) e1 e2 = Binop (Sub, e1, e2)
149 | let ( * ) e1 e2 = Binop (Mul, e1, e2)
150 | let let_ x e1 ~in_:e2 = Let (e1, (x, e2))
151 | let app e1 e2 = App (e1, e2)
152 | let ( = ) e1 e2 = Comp (Eq, e1, e2)
153 | let ( < ) e1 e2 = Comp (Lt, e1, e2)
154 | let ( > ) e1 e2 = Comp (Gt, e1, e2)
155 | let if_ e1 ~then_:e2 ~else_:e3 = IfThenElse (e1, e2, e3)
156 | let fix x ~is:e = Fix (None, (x, e))
157 | let nil = ListNil None
158 | let cons e1 e2 = ListCons (e1, e2)
159 |
160 | let match_ e1 ~with_nil:e2 ~with_cons:(x, y, e3) =
161 | ListMatch (e1, e2, (x, (y, e3)))
162 |
163 | let pair e1 e2 = Both (e1, e2)
164 | let fst e = I1 e
165 | let snd e = I2 e
166 | let ( @: ) e t = Annot (e, t)
167 |
168 | let lett x ~t e1 ~in_:e2 =
169 | match t with None -> let_ x e1 ~in_:e2 | Some t -> Let (e1 @: t, (x, e2))
170 | end
171 |
172 | module Ty = struct
173 | (* Helper functions to construct types.
174 | * Example: You can write [DSL.(list int => bool)] instead of
175 | * [IFun (IList (IInt), IBool)] *)
176 |
177 | let ( ?? ) (x : string) = TVar x
178 | let int = TInt
179 | let bool = TBool
180 | let ( => ) (t1 : ty) (t2 : ty) : ty = TFun (t1, t2)
181 | let list (t : ty) : ty = TList t
182 | let ( * ) (a : ty) (b : ty) : ty = TProd (a, b)
183 | let ( + ) (a : ty) (b : ty) : ty = TSum (a, b)
184 | end
185 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/cmd.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | type t =
4 | | CLet of string * ty option * expr
5 | | CLoad of string
6 | | CSave of string
7 | | CPrint
8 | | CClear
9 | | CEval of expr
10 | | CSynth of ty
11 |
12 | type script = t list
13 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/curry_howard.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Ast
3 |
4 | let todo () = failwith "TODO"
5 |
6 | (** These types can be interpreted as propositions *)
7 | let types : (string * ty) list =
8 | List.map
9 | ~f:(fun (p, s) -> (p, Parser.Ty.parse_string s))
10 | [
11 | ("always_true", "()");
12 | ("always_false", "!");
13 | ("everything", "'p");
14 | ("everything_implies_truth", "'p -> ()");
15 | ("falsehood_implies_everything", "! -> 'q");
16 | ("everything_implies_itself", "'p -> 'p");
17 | ("modus_ponens", "'p * ('p -> 'q) -> 'q");
18 | ("both_true_implies_left_true", "'p * 'q -> 'p");
19 | ("either_true_implies_left_true", "'p + 'q -> 'p");
20 | ("conjunction_is_commutative", "'p * 'q -> 'q * 'p");
21 | ("disjunction_is_commutative", "'p + 'q -> 'q + 'p");
22 | ( "conjunction_distributes_over_disjunction",
23 | "'p * ('q + 'r) -> ('p * 'q) + ('p * 'r)" );
24 | ( "disjunction_distributes_over_conjunction",
25 | "'p + ('q * 'r) -> ('p + 'q) * ('p + 'r)" );
26 | ("curry", "('p * 'q -> 'r) -> ('p -> ('q -> 'r))");
27 | ("uncurry", "('p -> ('q -> 'r)) -> ('p * 'q -> 'r)");
28 | ]
29 |
30 | (** For each proposition, determine whether it is valid (i.e. the truth table is always T) *)
31 | let is_valid () : (string * bool) list =
32 | [
33 | ("always_true", todo ());
34 | ("everything", todo ());
35 | ("everything_implies_truth", todo ());
36 | ("falsehood_implies_everything", todo ());
37 | ("everything_implies_itself", todo ());
38 | ("modus_ponens", todo ());
39 | ("both_true_implies_left_true", todo ());
40 | ("either_true_implies_left_true", todo ());
41 | ("conjunction_is_commutative", todo ());
42 | ("disjunction_is_commutative", todo ());
43 | ("conjunction_distributes_over_disjunction", todo ());
44 | ("disjunction_distributes_over_conjunction", todo ());
45 | ("curry", todo ());
46 | ("uncurry", todo ());
47 | ]
48 |
49 | (** For each type, give a lambda-plus expression that can be inferred to have said type.
50 | If there're no such expressions, simply put a [None]. Otherwise, put [Some ] where
51 | is the expression in concrete syntax. *)
52 | let expressions () : (string * expr option) list =
53 | List.map
54 | ~f:(fun (p, s) -> (p, Option.map ~f:Parser.Expr.parse_string s))
55 | [
56 | ("always_true", Some "()");
57 | ("everything", None);
58 | ("everything_implies_truth", todo ());
59 | ("falsehood_implies_everything", todo ());
60 | ("everything_implies_itself", todo ());
61 | ("modus_ponens", todo ());
62 | ("both_true_implies_left_true", todo ());
63 | ("either_true_implies_left_true", todo ());
64 | ("conjunction_is_commutative", todo ());
65 | ("disjunction_is_commutative", todo ());
66 | ("conjunction_distributes_over_disjunction", todo ());
67 | ("disjunction_distributes_over_conjunction", todo ());
68 | ("curry", todo ());
69 | ("uncurry", todo ());
70 | ]
71 |
72 | let synthesize (t : ty) : expr option = todo ()
73 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/curry_howard.mli:
--------------------------------------------------------------------------------
1 | val types : (string * Ast.ty) list
2 | val is_valid : unit -> (string * bool) list
3 | val expressions : unit -> (string * Ast.expr option) list
4 | val synthesize : Ast.ty -> Ast.expr option
5 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/dune:
--------------------------------------------------------------------------------
1 | (env
2 | (dev
3 | (flags
4 | (:standard -warn-error -A))))
5 |
6 | (library
7 | (name lamp)
8 | (preprocess
9 | (pps ppx_deriving.show ppx_jane ppx_sexp_conv))
10 | (libraries base fmt nice_parser))
11 |
12 | (ocamllex lexer)
13 |
14 | (menhir
15 | (modules menhir_parser))
16 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/err.ml:
--------------------------------------------------------------------------------
1 | exception Syntax of { sl : int; sc : int; el : int; ec : int }
2 | exception Lexing of { l : int; s : string }
3 |
4 | let syntax_error () =
5 | let start_pos = Parsing.rhs_start_pos 1 in
6 | let end_pos = Parsing.rhs_end_pos 1 in
7 | let sl = start_pos.pos_lnum
8 | and sc = start_pos.pos_cnum - start_pos.pos_bol
9 | and el = end_pos.pos_lnum
10 | and ec = end_pos.pos_cnum - end_pos.pos_bol in
11 | raise (Syntax { sl; sc; el; ec })
12 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/eval.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | let todo () = failwith "TODO"
4 |
5 | exception Stuck of string
6 | (** Exception indicating that evaluation is stuck *)
7 |
8 | (** Raises an exception indicating that evaluation got stuck. *)
9 | let im_stuck msg = raise (Stuck msg)
10 |
11 | (** Computes the set of free variables in the given expression *)
12 | let rec free_vars (e : expr) : Vars.t =
13 | (* This line imports the functions in Vars, so you can write [diff .. ..]
14 | instead of [Vars.diff .. ..] *)
15 | let open Vars in
16 | match e with _ -> todo ()
17 |
18 | (** Perform substitution c[x -> e], i.e., substituting x with e in c *)
19 | let rec subst (x : string) (e : expr) (c : expr) : expr =
20 | match c with _ -> todo ()
21 |
22 | (** Evaluate expression e *)
23 | let rec eval (e : expr) : expr =
24 | try match e with _ -> todo ()
25 | with Stuck msg ->
26 | im_stuck (Fmt.str "%s\nin expression %a" msg Pretty.expr e)
27 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/eval.mli:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | exception Stuck of string
4 |
5 | val free_vars : expr -> Vars.t
6 | (** Return the set of free variable references in an expression *)
7 |
8 | val subst : string -> expr -> expr -> expr
9 | (** Substitution *)
10 |
11 | val eval : expr -> expr
12 | (** Interpret an expression *)
13 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/lexer.mll:
--------------------------------------------------------------------------------
1 | {
2 | open Menhir_parser
3 | exception LexError of string
4 |
5 | let failwith msg = raise (LexError msg)
6 |
7 | let illegal c =
8 | failwith (Printf.sprintf "[lexer] unexpected character: '%c'" c)
9 | }
10 |
11 | let ws = ' ' | '\t'
12 | let newline = "\r\n" | '\r' | '\n'
13 |
14 | rule next_token = parse
15 | | ws+ { next_token lexbuf }
16 | | newline { Lexing.new_line lexbuf; next_token lexbuf }
17 | | "#load" { CLOAD }
18 | | "#save" { CSAVE }
19 | | "#print" { CPRINT }
20 | | "#clear" { CCLEAR }
21 | | "#let" { CLET }
22 | | "#synth" { CSYNTH }
23 | | "let" { LET }
24 | | "in" { IN }
25 | | "fun" { FUN }
26 | | "rec" { REC }
27 | | "with" { WITH }
28 | | "match" { MATCH }
29 | | "end" { END }
30 | | "lambda" { LAMBDA }
31 | | "if" { IF }
32 | | "then" { THEN }
33 | | "else" { ELSE }
34 | | "true" { TRUE }
35 | | "false" { FALSE }
36 | | "fix" { FIX }
37 | | "is" { IS }
38 | | ".1" { FST }
39 | | ".2" { SND }
40 | | "Nil" { NIL }
41 | | "::" { CONS }
42 | | "+" { PLUS }
43 | | "-" { SUB }
44 | | "*" { TIMES }
45 | | "Int" { TYINT }
46 | | "Bool" { TYBOOL }
47 | | "List" { TYLIST }
48 | | '\'' { TICK }
49 | | "->" { THINARROW }
50 | | ':' { COLON }
51 | | ">" { GT }
52 | | '=' { EQ }
53 | | "|" { BAR }
54 | | "<" { LT }
55 | | '(' { LPAREN }
56 | | ')' { RPAREN }
57 | | '[' { LBRACK }
58 | | ']' { RBRACK }
59 | | '.' { DOT }
60 | | ',' { COMMA }
61 | | "$1" { CASE1 }
62 | | "$2" { CASE2 }
63 | | '!' { VOID }
64 | | "//" { comment lexbuf }
65 | | ['0'-'9']+ as n { NUMBER(Base.Int.of_string(n)) }
66 | | ['a'-'z' 'A'-'Z' '_']['a'-'z' 'A'-'Z' '0'-'9' '_']* as x { ID(x) }
67 | | ['a'-'z' 'A'-'Z' '0'-'9' '_' '-' '/' ]+ ".txt" as x { FILE(x) }
68 | | eof { EOF }
69 | | _ as c { illegal c }
70 |
71 | and comment = parse
72 | | newline { Lexing.new_line lexbuf; next_token lexbuf }
73 | | _ { comment lexbuf }
74 | | eof { EOF }
75 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/menhir_parser.mly:
--------------------------------------------------------------------------------
1 | %{
2 |
3 | let mk_lambdas (xs : (string * Ast.ty option) list) (e : Ast.expr) (r: Ast.ty option) =
4 | let f (x, topt) e' = Ast.Lambda(topt, (x,e')) in
5 | List.fold_right f xs (match r with None -> e | Some r -> Annot(e, r))
6 |
7 | let mk_tfun_of_list (xs: (string * Ast.ty option) list) (r: Ast.ty option) : Ast.ty option =
8 | match r with
9 | | Some r ->
10 | List.fold_right (fun (x,topt) fo ->
11 | match topt, fo with
12 | | Some t, Some r -> Some (Ast.TFun(t,r))
13 | | _ -> None) xs (Some r)
14 | | None -> None
15 | let mk_let (x: string) (ty: Ast.ty option) (e1: Ast.expr) (e2: Ast.expr) : Ast.expr =
16 | match ty with
17 | | Some t -> Ast.Let(Ast.Annot(e1, t), (x, e2))
18 | | None -> Ast.Let(e1, (x, e2))
19 | %}
20 |
21 | /* Tokens */
22 |
23 | %token EOF FUN REC MATCH BAR END GT EQ LT LPAREN RPAREN DOT COMMA FIX IS
24 | %token TRUE FALSE TYBOOL
25 | %token LET IN IF THEN ELSE WITH LAMBDA
26 | %token NIL CONS
27 | %token TYINT TYLIST THINARROW COLON LBRACK RBRACK
28 | %token CLET CPRINT CCLEAR CLOAD CSAVE CSYNTH
29 | %token TICK
30 | %token FST SND CASE1 CASE2 VOID
31 |
32 | %token PLUS SUB TIMES
33 | %token NUMBER
34 | %token ID
35 | %token FILE
36 |
37 | %nonassoc IN DOT ELSE IS CASE1 CASE2
38 | %nonassoc COLON
39 | %right CONS
40 | %left LT GT EQ
41 | %right THINARROW
42 | %left PLUS SUB
43 | %left TIMES
44 |
45 | %start expr_eof
46 | %type expr_eof
47 |
48 | %start repl_command_eof
49 | %type repl_command_eof
50 |
51 | %start script_eof
52 | %type script_eof
53 |
54 | %start ty_eof
55 | %type ty_eof
56 |
57 |
58 | %%
59 |
60 | file_command:
61 | | CLET bind EQ expr
62 | { let (x,t) = $2 in Cmd.CLet(x,t,$4) }
63 | | CPRINT
64 | { Cmd.CPrint}
65 | | CCLEAR
66 | { Cmd.CClear}
67 | | CLOAD FILE
68 | { Cmd.CLoad $2 }
69 | | CSAVE FILE
70 | { Cmd.CSave $2 }
71 | | CSYNTH ty
72 | { Cmd.CSynth $2 }
73 |
74 | repl_command:
75 | | c=file_command
76 | { c }
77 | | expr
78 | { Cmd.CEval $1 }
79 |
80 | repl_command_eof:
81 | | c=repl_command EOF
82 | { c }
83 |
84 | script_eof:
85 | | cs=list(file_command) EOF
86 | { cs }
87 |
88 | expr_eof:
89 | | e=expr EOF
90 | { e }
91 |
92 | ty_var:
93 | | TICK ID
94 | { ("\'" ^ $2) }
95 |
96 | ty_atom:
97 | | x=ty_var
98 | { Ast.TVar x }
99 | | TYINT
100 | { Ast.TInt }
101 | | TYBOOL
102 | { Ast.TBool }
103 | | LPAREN t=ty RPAREN
104 | { t }
105 | | LPAREN RPAREN
106 | { Ast.TUnit }
107 | | VOID
108 | { Ast.TVoid }
109 |
110 | ty_eof:
111 | | t=ty EOF
112 | { t }
113 |
114 | ty:
115 | | t=ty_atom
116 | { t }
117 | | t1=ty THINARROW t2=ty
118 | { TFun (t1, t2) }
119 | | t1=ty TIMES t2=ty
120 | { TProd (t1, t2) }
121 | | t1=ty PLUS t2=ty
122 | { TSum (t1, t2) }
123 | | TYLIST LBRACK t=ty RBRACK
124 | { TList t }
125 |
126 | bind:
127 | | x=ID COLON t=ty
128 | { (x, Some t) }
129 | | x=ID
130 | { (x, None) }
131 |
132 | bindlist:
133 | | l=separated_nonempty_list(COMMA, bind)
134 | { l }
135 |
136 | expr:
137 | | e=app
138 | { e }
139 | | e=expr COLON t=ty
140 | { Ast.Annot(e, t) }
141 | | LAMBDA bs=bindlist DOT e=expr
142 | { mk_lambdas bs e None }
143 | | FUN REC b=bind WITH bs=bindlist EQ e1=expr IN e2=expr
144 | { let (x,r) = b in
145 | Let(Fix (mk_tfun_of_list bs r, (x, mk_lambdas bs e1 r)), (x, e2)) }
146 | | FUN b=bind WITH bs=bindlist EQ e1=expr IN e2=expr
147 | { let (x,r) = b in
148 | Let(mk_lambdas bs e1 r, (x, e2)) }
149 | | FIX b=bind IS e=expr
150 | { let (x, topt) = b in Fix (topt, (x, e)) }
151 | | LET b=bind EQ e1=expr IN e2=expr
152 | { let (x, topt) = b in
153 | mk_let x topt e1 e2 }
154 | | IF e1=expr THEN e2=expr ELSE e3=expr
155 | { IfThenElse(e1, e2, e3) }
156 | | e=list_match
157 | { e }
158 | | e=sum_match
159 | { e }
160 | | e=void_match
161 | { e }
162 | | e=binop(expr)
163 | { e }
164 | | CASE1; e=expr
165 | { E1 e }
166 | | CASE2; e=expr
167 | { E2 e }
168 |
169 | binop(expr):
170 | | expr PLUS expr
171 | { Binop(Add, $1, $3) }
172 | | expr SUB expr
173 | { Binop(Sub, $1, $3) }
174 | | expr TIMES expr
175 | { Binop(Mul, $1, $3) }
176 | | expr LT expr
177 | { Comp(Lt, $1, $3) }
178 | | expr GT expr
179 | { Comp(Gt, $1, $3) }
180 | | expr EQ expr
181 | { Comp(Eq, $1, $3) }
182 | | expr CONS expr
183 | { ListCons($1, $3) }
184 |
185 | app:
186 | | term
187 | { $1 }
188 | | e1=app e2=term
189 | { App(e1, e2) }
190 |
191 | tyarg_opt:
192 | | LBRACK ty RBRACK
193 | { Some $2 }
194 | |
195 | { None }
196 |
197 | nil_branch:
198 | | NIL THINARROW e=expr
199 | { e }
200 |
201 | cons_branch:
202 | | x=ID CONS y=ID THINARROW e=expr
203 | { (x, y, e) }
204 |
205 | list_match:
206 | | MATCH e1=expr WITH option(BAR) e2=nil_branch BAR b3=cons_branch END
207 | {
208 | let (x,y,e3) = b3 in
209 | Ast.ListMatch(e1, e2, (x, (y, e3)))}
210 |
211 | case(TAG):
212 | | TAG x=ID THINARROW e=expr
213 | { (x, e) }
214 |
215 | sum_match:
216 | | MATCH e1=expr WITH option(BAR) b2=case(CASE1) BAR b3=case(CASE2) END
217 | { Ast.Either(e1, b2, b3) }
218 |
219 | void_match:
220 | | MATCH e1=expr WITH END
221 | { Ast.Absurd e1 }
222 |
223 | term:
224 | | x=ID
225 | { Ast.Var x }
226 | | n=NUMBER
227 | { Ast.Num n }
228 | | TRUE
229 | { Ast.True }
230 | | FALSE
231 | { Ast.False }
232 | | NIL t=tyarg_opt
233 | { ListNil t }
234 | | LPAREN e=expr RPAREN
235 | { e }
236 | | LPAREN e1=expr COMMA e2=expr RPAREN
237 | { Ast.Both(e1, e2) }
238 | | e=term FST
239 | { Ast.I1 e }
240 | | e=term SND
241 | { Ast.I2 e }
242 | | LPAREN RPAREN
243 | { Ast.Unit }
244 |
245 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/parser.ml:
--------------------------------------------------------------------------------
1 | module Expr = Nice_parser.Make (struct
2 | type result = Ast.expr
3 | type token = Menhir_parser.token
4 |
5 | exception ParseError = Menhir_parser.Error
6 |
7 | let parse = Menhir_parser.expr_eof
8 |
9 | include Lexer
10 | end)
11 |
12 | module Command = Nice_parser.Make (struct
13 | type result = Cmd.t
14 | type token = Menhir_parser.token
15 |
16 | exception ParseError = Menhir_parser.Error
17 |
18 | let parse = Menhir_parser.repl_command_eof
19 |
20 | include Lexer
21 | end)
22 |
23 | module Script = Nice_parser.Make (struct
24 | type result = Cmd.script
25 | type token = Menhir_parser.token
26 |
27 | exception ParseError = Menhir_parser.Error
28 |
29 | let parse = Menhir_parser.script_eof
30 |
31 | include Lexer
32 | end)
33 |
34 | module Ty = Nice_parser.Make (struct
35 | type result = Ast.ty
36 | type token = Menhir_parser.token
37 |
38 | exception ParseError = Menhir_parser.Error
39 |
40 | let parse = Menhir_parser.ty_eof
41 |
42 | include Lexer
43 | end)
44 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/typecheck.ml:
--------------------------------------------------------------------------------
1 | open Ast
2 | open Base
3 |
4 | type env = (string * ty) list
5 | (** Typing environment, aka Gamma *)
6 |
7 | (** Helper function to look up a variable in the env *)
8 | let find : env -> string -> ty option = List.Assoc.find ~equal:String.equal
9 |
10 | (** Helper function to insert a (variable, ty) pair into the env *)
11 | let add : env -> string -> ty -> env = List.Assoc.add ~equal:String.equal
12 |
13 | exception Type_error of string
14 |
15 | let ty_err msg = raise (Type_error msg)
16 | let rec equal_ty (t1 : ty) (t2 : ty) : bool = failwith "TODO"
17 |
18 | let rec abstract_eval (env : env) (e : expr) : ty =
19 | try
20 | match e with
21 | (* T-Int rule *)
22 | | Num _ -> TInt
23 | (* T-True and T-false *)
24 | | True | False -> TBool
25 | (* Your code here *)
26 | | _ -> failwith "TODO"
27 | with Type_error msg -> ty_err (msg ^ "\nin expression " ^ show_expr e)
28 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/typecheck.mli:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | val equal_ty : ty -> ty -> bool
4 | val abstract_eval : (string * ty) list -> Ast.expr -> ty
5 |
6 | exception Type_error of string
7 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/typeinfer.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Ast
3 |
4 | let todo () = failwith "TODO"
5 |
6 | (* Shadow the Base version of common list functions *)
7 | let map = Stdlib.List.map
8 | let fold_left = Stdlib.List.fold_left
9 | let fold_right = Stdlib.List.fold_right
10 | let filter = Stdlib.List.filter
11 |
12 | exception Type_error of string
13 |
14 | let ty_err msg = raise (Type_error msg)
15 |
16 | (**********************************************
17 | * Typing Environment (Gamma) *
18 | *********************************************)
19 |
20 | type gamma = (string * ty) list [@@deriving equal, compare, show]
21 | (** Gamma is the type environment that maps variables to types *)
22 |
23 | let uncurry f (x, y) = f x y
24 |
25 | (** Pretty-printer for gamma *)
26 | let pp_gamma : gamma Fmt.t =
27 | let open Fmt in
28 | let pp_pair = hbox (pair ~sep:(any " : ") string Pretty.ty) in
29 | vbox
30 | @@ iter_bindings ~sep:comma (fun f l -> List.iter ~f:(uncurry f) l) pp_pair
31 |
32 | (** Find the type of a variable in gamma *)
33 | let find : gamma -> string -> ty option = List.Assoc.find ~equal:String.equal
34 |
35 | (** Add a (var, type) pair to gamma *)
36 | let add : gamma -> string -> ty -> gamma = List.Assoc.add ~equal:String.equal
37 |
38 | type cons = ty * ty [@@deriving equal, compare, show]
39 | (** A constraint is a pair (t1,t2) that asserts t1 == t2 *)
40 |
41 | (** Pretty-printer for cons *)
42 | let pp_cons : cons Fmt.t =
43 | fun ppf (t1, t2) -> Fmt.pf ppf "%a == %a" Pretty.ty t1 Pretty.ty t2
44 |
45 | (*******************************************
46 | * Type Substitution (Sigma) *
47 | *******************************************)
48 |
49 | type soln = (string * ty) list [@@deriving equal, compare, show]
50 | (** The solution to a list of type equations is
51 | * a substitution from type variables to types *)
52 |
53 | (** Pretty-printer for soln *)
54 | let pp_soln : soln Fmt.t =
55 | let open Fmt in
56 | let pp_pair = hbox (pair ~sep:(any " |-> ") string Pretty.ty) in
57 | iter_bindings (fun f l -> List.iter ~f:(uncurry f) l) pp_pair
58 |
59 | (*******************************************
60 | * Type Inference Utils *
61 | *******************************************)
62 |
63 | module Utils = struct
64 | (** Substitute type variable [x] with type [t] in [ty] context [c] *)
65 | let rec subst (x : string) (t : ty) (c : ty) : ty = todo ()
66 |
67 | (** Compute the free variable set of an [ty] *)
68 | let rec free_vars (t : ty) : Vars.t =
69 | match t with
70 | | TVar x -> Vars.singleton x
71 | | TInt | TBool -> Vars.empty
72 | | TList t' -> free_vars t'
73 | | TFun (t1, t2) | TProd (t1, t2) | TSum (t1, t2) ->
74 | Vars.union (free_vars t1) (free_vars t2)
75 | | TUnit | TVoid -> Vars.empty
76 |
77 | (** Apply a soln [s] to type [t] by performing all substitutions in [s] *)
78 | let apply_soln (s : soln) (t : ty) : ty = todo ()
79 |
80 | let cata ~var : ty -> ty =
81 | let rec go = function
82 | | TVar x -> var x
83 | | (TInt | TBool | TUnit | TVoid) as t -> t
84 | | TList t -> TList (go t)
85 | | TFun (t1, t2) -> TFun (go t1, go t2)
86 | | TProd (t1, t2) -> TProd (go t1, go t2)
87 | | TSum (t1, t2) -> TSum (go t1, go t2)
88 | in
89 | go
90 |
91 | let subst_multi (s : soln) : ty -> ty =
92 | cata ~var:(fun x ->
93 | match List.Assoc.find ~equal:String.equal s x with
94 | | Some t -> t
95 | | None -> raise (Type_error Fmt.(str "Unbound type variable %s" x)))
96 |
97 | (** Alpha-rename type variables (to 't0, 't1, 't2, ...) *)
98 | let normalize (t : ty) : ty =
99 | let s =
100 | t |> free_vars |> Vars.to_list
101 | |> List.mapi ~f:(fun i x -> (x, TVar ("'t" ^ Int.to_string (i + 1))))
102 | in
103 | subst_multi s t
104 | end
105 |
106 | (*******************************************
107 | * Type Inference Engine *
108 | *******************************************)
109 | module Infer = struct
110 | (** The list of accumulated constraints *)
111 | let _cs : cons list ref = ref []
112 |
113 | (** Add a constraint to the accumulator. Call it with [t1 === t2]. *)
114 | let ( === ) (t1 : ty) (t2 : ty) : unit =
115 | (* If you prefer the "printf" school of debugging, uncomment the following line,
116 | BUT DON'T FORGET TO REMOVE IT BEFORE YOU SUBMIT *)
117 | (* Fmt.epr "[constraint] %a\n%!" pp_cons (t1, t2); *)
118 | _cs := (t1, t2) :: !_cs
119 |
120 | (** Return the current list of constraints *)
121 | let curr_cons_list () : cons list = !_cs
122 |
123 | (******************************************
124 | * Fresh Variable Helpers *
125 | ******************************************)
126 |
127 | (** Counter to produce fresh variables *)
128 | let var_counter = ref 1
129 |
130 | (** Type string *)
131 | let ty_str_of_int (i : int) : string = "'X" ^ Int.to_string i
132 |
133 | (** Return the current var counter and increment it *)
134 | let incr () =
135 | let v = !var_counter in
136 | var_counter := v + 1;
137 | v
138 |
139 | (** Generate a fresh string. For internal use only. *)
140 | let fresh_var_str () : string = ty_str_of_int (incr ())
141 |
142 | (** Generate a fresh [ty] type variable. Call it using [fresh_var ()]. *)
143 | let fresh_var () : ty = TVar (fresh_var_str ())
144 |
145 | (*******************************************
146 | * Constraint Generation *
147 | *******************************************)
148 |
149 | (** Abstractly evaluate an expression to a type.
150 | * This function also generates constraints and accumulates them into
151 | * the list [cs] whenever you call [t1 === t2]. *)
152 | let rec abstract_eval (gamma : gamma) (e : expr) : ty =
153 | (* The following line loads functions in Ast.Ty module, allowing you to write
154 | [int] for [TInt], [bool] for [TBool], [t1 => t2] for [TFun(t1, t2)],
155 | [list t] for TList(t), and [t1 * t2] for [TProd(t1, t2)].
156 | However, you don't have to use the Ast.Ty functions, and you can just
157 | call the appropriate [ty] constructors. *)
158 | let open Ty in
159 | (* If you prefer the "printf" school of debugging, uncomment the following line,
160 | BUT DON'T FORGET TO COMMENT IT OUT BEFORE YOU SUBMIT *)
161 | (* Fmt.epr "[abstract_eval] %a\n%!" Ast.Pretty.expr e; *)
162 | (* Fmt.epr "[abstract_eval] Gamma:\n%! %a\n%!" pp_gamma gamma; *)
163 | try match e with Num _ -> TInt | True | False -> TBool | _ -> todo ()
164 | with Type_error msg ->
165 | ty_err (msg ^ Fmt.(str "\nin expression %a" Pretty.expr e))
166 |
167 | (*******************************************
168 | * Constraint Solving *
169 | *******************************************)
170 |
171 | (** unification algorithm *)
172 | and unify (cs : cons list) : soln =
173 | match cs with
174 | | [] ->
175 | (* empty solution *)
176 | []
177 | | c :: cs' ->
178 | (* extract the first constraint as [c], and the remaining as [cs'] *)
179 | todo ()
180 | end
181 |
182 | (*******************************************
183 | * Type inference *
184 | *******************************************)
185 |
186 | (** Infer the type of expression [e] in the environment [g] *)
187 | let infer_with_gamma ~(gamma : gamma) (e : expr) : ty =
188 | let t = Infer.abstract_eval gamma e in
189 | let s = Infer.unify (Infer.curr_cons_list ()) in
190 | Utils.apply_soln s t |> Utils.normalize
191 |
192 | (** Infer the type of expression [e] *)
193 | let infer (e : expr) : ty = infer_with_gamma ~gamma:[] e
194 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/typeinfer.mli:
--------------------------------------------------------------------------------
1 | open Ast
2 |
3 | type gamma = (string * ty) list
4 |
5 | exception Type_error of string
6 |
7 | module Utils : sig
8 | val normalize : ty -> ty
9 | end
10 |
11 | type soln = (string * ty) list [@@deriving show, equal]
12 |
13 | val infer : expr -> ty
14 | val infer_with_gamma : gamma:gamma -> expr -> ty
15 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/vars.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | type t = Set.M(String).t
4 |
5 | let pp : t Fmt.t =
6 | Fmt.using Set.to_list Fmt.(braces @@ list ~sep:(any ", ") string)
7 |
8 | let show = Fmt.to_to_string pp
9 | let equal = Set.equal
10 | let compare = Set.compare_direct
11 | let empty = Set.empty (module String)
12 | let diff = Set.diff
13 | let singleton = Set.singleton (module String)
14 | let add s v = Set.add v s
15 | let union = Set.union
16 | let mem s v = Set.mem v s
17 | let of_list = Set.of_list (module String)
18 | let to_list = Set.to_list
19 | let size = Set.length
20 |
--------------------------------------------------------------------------------
/homework/hw5/lamp/vars.mli:
--------------------------------------------------------------------------------
1 | (** Signature for variable set module *)
2 |
3 | type t [@@deriving equal, compare, show]
4 | (** type of variable set *)
5 |
6 | val empty : t
7 | (** empty set *)
8 |
9 | val mem : string -> t -> bool
10 | (** [mem x s] is true iff [x] is in [s] *)
11 |
12 | val diff : t -> t -> t
13 | (** [diff s1 s2] is the set of elements in [s1] but not in [s2] *)
14 |
15 | val singleton : string -> t
16 | (** [singleton x] is the singleton set containing [x] *)
17 |
18 | val add : string -> t -> t
19 | (** [add x s] is the set [s] with [x] added to it *)
20 |
21 | val union : t -> t -> t
22 | (** [union s1 s2] is the union of [s1] and [s2] *)
23 |
24 | val of_list : string list -> t
25 | (** [of_list l] converts a list [l] into a set *)
26 |
27 | val to_list : t -> string list
28 | (** [to_list x] converts a set [s] into a list *)
29 |
30 | val size : t -> int
31 | (** Return the size of a set *)
32 |
--------------------------------------------------------------------------------
/homework/hw5/test/dune:
--------------------------------------------------------------------------------
1 | (env
2 | (dev
3 | (flags
4 | (:standard -warn-error -A))))
5 |
6 | (test
7 | (name hw5_test)
8 | (libraries fmt alcotest lamp)
9 | (preprocess
10 | (pps ppx_jane ppx_deriving.show))
11 | (deps
12 | (source_tree examples)))
13 |
--------------------------------------------------------------------------------
/homework/hw5/test/examples/add_n.lp:
--------------------------------------------------------------------------------
1 | // increments every element of a list by n
2 | (fix add_n is
3 | lambda l, n.
4 | match l with
5 | | Nil -> Nil
6 | | x::xs -> x + n :: add_n xs n
7 | end
8 | ) (1::2::Nil[Int]) 10 // evaluates to 11::12::Nil
--------------------------------------------------------------------------------
/homework/hw5/test/examples/fib.lp:
--------------------------------------------------------------------------------
1 | fun rec fib with n =
2 | if n = 0 then 0
3 | else if n = 1 then 1
4 | else
5 | let x = fib (n-1) in
6 | let y = fib (n-2) in
7 | x + y
8 | in (fib 30 : Int)
--------------------------------------------------------------------------------
/homework/hw5/test/examples/foldr.lp:
--------------------------------------------------------------------------------
1 | let foldr = fix foldr is
2 | lambda init: 'b, f: 'a -> 'b -> 'b, xs: List['a].
3 | (match xs with
4 | | Nil -> init
5 | | y::ys -> f y (foldr init f ys)
6 | end : 'b)
7 | in
8 | let map: List['a] -> List['b] = lambda f: 'a -> 'b.
9 | foldr Nil (lambda x,acc. f x :: acc)
10 | in
11 | map (lambda x.x>1) (1::2::3::Nil)
12 |
--------------------------------------------------------------------------------
/homework/hw5/test/examples/length.lp:
--------------------------------------------------------------------------------
1 | fun rec length: Int with xs: List['a] =
2 | match xs with
3 | | Nil -> 0
4 | | _::ys -> 1 + length ys
5 | end
6 | in
7 | length (Nil::Nil::Nil)
--------------------------------------------------------------------------------
/homework/hw5/test/examples/length_poly.lp:
--------------------------------------------------------------------------------
1 | fun rec length: Int with xs: List['a] =
2 | match xs with
3 | | Nil -> 0
4 | | _::ys -> 1 + length ys
5 | end
6 | in
7 | length (1::Nil) + length (false::true::false::Nil)
--------------------------------------------------------------------------------
/homework/hw5/test/examples/map_filter.lp:
--------------------------------------------------------------------------------
1 | let map =
2 | fix map is lambda f: 'a -> 'b, xs:List['a].
3 | match xs with
4 | | Nil -> Nil
5 | | x::ys -> f x :: map f ys
6 | end
7 | in
8 | let filter =
9 | fix filter is lambda p: 'a -> Bool, xs:List['a].
10 | match xs with
11 | | Nil -> Nil
12 | | x::ys -> if p x then x::filter p ys else filter p ys
13 | end
14 | in
15 | let pipe = lambda x,f. f x
16 | in
17 | pipe (pipe (pipe (pipe (pipe (pipe
18 | (1::2::3::Nil)
19 | (filter (lambda x. x > 1)))
20 | (map (lambda x. x - 2)))
21 | (map (lambda x. x * 2)))
22 | (map (lambda x. x > 0)))
23 | (filter (lambda b. b)))
24 | (map (lambda b. if b then (lambda x,y.x) else (lambda x,y.y)))
--------------------------------------------------------------------------------
/homework/hw5/test/hw5_test.ml:
--------------------------------------------------------------------------------
1 | open Base
2 |
3 | let () =
4 | Alcotest.run "hw5"
5 | (List.map
6 | ~f:(fun (name, tests) ->
7 | (name, List.map ~f:(Alcotest.test_case name `Quick) tests))
8 | Test_typing.tests)
9 |
--------------------------------------------------------------------------------
/homework/hw5/test/test_typing.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Lamp
3 | open Ast
4 |
5 | exception Timeout
6 |
7 | (* run a function with specified timeout:
8 | https://discuss.ocaml.org/t/computation-with-time-constraint/5548/9 *)
9 | let with_timeout timeout f =
10 | let _ =
11 | Stdlib.Sys.set_signal Stdlib.Sys.sigalrm
12 | (Stdlib.Sys.Signal_handle (fun _ -> raise Timeout))
13 | in
14 | ignore (Unix.alarm timeout);
15 | try
16 | let r = f () in
17 | ignore (Unix.alarm 0);
18 | r
19 | with e ->
20 | ignore (Unix.alarm 0);
21 | raise e
22 |
23 | (* Unit test utilities *)
24 | let texpr = Alcotest.testable Pretty.expr equal_expr
25 | let tvars = Alcotest.testable Vars.pp Vars.equal
26 | let tty = Alcotest.testable Pretty.ty equal_ty
27 |
28 | let parse s =
29 | try Parser.Expr.parse_string s
30 | with _ -> Alcotest.fail ("Failed to parse: " ^ s)
31 |
32 | let parse_file filename =
33 | try Parser.Expr.parse_file filename
34 | with _ -> Alcotest.fail ("Failed to parse file: " ^ filename)
35 |
36 | let parse_ty s =
37 | try Parser.Ty.parse_string s
38 | with _ -> Alcotest.fail ("Failed to parse: " ^ s)
39 |
40 | (** Helper function to check that [e] is inferred to have type [expected]
41 | * under [gamma]. The [p] flag enables let-polymorphism. *)
42 | let check_well_typed ~gamma (e : expr) (expected : ty) () =
43 | let actual =
44 | try with_timeout 1 (fun () -> Typeinfer.infer_with_gamma ~gamma e)
45 | with Typeinfer.Type_error msg -> failwith ("Type error!\n" ^ msg)
46 | in
47 | Alcotest.(check' tty)
48 | ~msg:(Fmt.str "%a" Pretty.expr e)
49 | ~expected:(Typeinfer.Utils.normalize expected)
50 | ~actual:(Typeinfer.Utils.normalize actual)
51 |
52 | (** Helper function to check that [e_str] (concrete syntax) is inferred
53 | * to have type [expected] under [gamma]. The [p] flag enables
54 | * let-polymorphism. *)
55 | let check_well_typed_s ~gamma (e_str : string) (expected_str : string) () =
56 | check_well_typed ~gamma (parse e_str) (parse_ty expected_str) ()
57 |
58 | let check_well_typed_file ~gamma filename t () =
59 | check_well_typed ~gamma (parse_file filename) t ()
60 |
61 | (** Helper function to check that type inference determines that [e] is ill-typed *)
62 | let check_ill_typed ~gamma (e : expr) () =
63 | try
64 | let t = with_timeout 1 (fun () -> Typeinfer.infer_with_gamma ~gamma e) in
65 | Alcotest.fail Fmt.(str "inferred %a" Pretty.ty t)
66 | with Typeinfer.Type_error _ -> ()
67 |
68 | (** Helper function to check that type inference determines that [e_str]
69 | (concrete syntax) is ill-typed. *)
70 | let check_ill_typed_s ~gamma (e_str : string) () =
71 | check_ill_typed ~gamma (parse e_str) ()
72 |
73 | let well_typed_tests =
74 | let t = check_well_typed_s ~gamma:[] in
75 | [
76 | check_well_typed_s (* typing environment *)
77 | ~gamma:[] (* input expression *) "0" (* expected inferred type *) "Int";
78 | t "lambda x. x" "'a -> 'a";
79 | ]
80 |
81 | let ill_typed_tests = [ check_ill_typed_s ~gamma:[] "true+1" ]
82 | let tests = [ ("well_typed", well_typed_tests); ("ill_typed", ill_typed_tests) ]
83 |
--------------------------------------------------------------------------------
/homework/hw5/test/test_utils.ml:
--------------------------------------------------------------------------------
1 | open Base
2 | open Ast
3 | open Typeinfer
4 |
5 | exception Timeout
6 |
7 | let get_var_name i = "_" ^ Int.to_string i
8 |
9 | let _normalize t =
10 | let rename = ref (Map.empty (module String)) in
11 | let mem x = Map.mem !rename x in
12 | let get x = TVar (Map.find_exn !rename x) in
13 | let set x t = rename := Map.add_exn !rename ~key:x ~data:t in
14 | let counter = ref 0 in
15 | let next () =
16 | counter := !counter - 1;
17 | get_var_name !counter
18 | in
19 | let rec helper = function
20 | | TVar x ->
21 | if mem x then get x
22 | else
23 | let y = next () in
24 | set x y;
25 | get x
26 | | TInt -> TInt
27 | | TBool -> TBool
28 | | TFun (t1, t2) -> TFun (helper t1, helper t2)
29 | | TList t -> TList (helper t)
30 | | TProd (t1, t2) -> TProd (helper t1, helper t2)
31 | in
32 | let t' = helper t in
33 | (t', !rename)
34 |
35 | let normalize t = fst (_normalize t)
36 |
37 | let normalize_pty = function
38 | | Mono t -> Mono (normalize t)
39 | | Scheme (xs, t) ->
40 | let t', rename = _normalize t in
41 | let xs' =
42 | xs |> Vars.to_list
43 | |> List.map ~f:(fun x -> Map.find_exn rename x)
44 | |> Vars.of_list
45 | in
46 | Scheme (xs', t')
47 |
48 | exception DoesNotRefine
49 |
50 | (** t1 refines t2 if there's a substitution from t2 to t1 *)
51 | let refines t1 t2 =
52 | let t1 = normalize t1 in
53 | let t2 = normalize t2 in
54 | let refinement = ref (Map.empty (module String)) in
55 |
56 | let rec helper t1 t2 =
57 | (* Fmt.epr "helper\n%! %a <= %a\n%!" Pretty.pp_ty t1 Pretty.pp_ty t2; *)
58 | match (t1, t2) with
59 | | _, TVar x -> (
60 | match Map.find !refinement x with
61 | | None -> refinement := Map.add_exn !refinement ~key:x ~data:t1
62 | | Some t1' -> if not (equal_ty t1 t1') then raise DoesNotRefine else ())
63 | | TInt, TInt -> ()
64 | | TBool, TBool -> ()
65 | | TFun (t1, t2), TFun (t1', t2') ->
66 | helper t1 t1';
67 | helper t2 t2'
68 | | TList t1, TList t2 -> helper t1 t2
69 | | TProd (t1, t2), TProd (t1', t2') ->
70 | helper t1 t1';
71 | helper t2 t2'
72 | | _ -> raise DoesNotRefine
73 | in
74 | try
75 | let () = helper t1 t2 in
76 | true
77 | with DoesNotRefine -> false
78 |
79 | (* Unit test utilities *)
80 | let texpr = Alcotest.testable Pretty.pp_expr equal_expr
81 | let tvars = Alcotest.testable Vars.pp Vars.equal
82 | let tty = Alcotest.testable Pretty.pp_ty equal_ty
83 |
84 | let tpty =
85 | Alcotest.testable pp_pty (fun p1 p2 ->
86 | equal_pty (normalize_pty p1) (normalize_pty p2))
87 |
88 | let tsigma =
89 | Alcotest.testable pp_sigma (fun s1 s2 ->
90 | let sort = List.sort ~compare:(fun (x, _) (y, _) -> String.compare x y) in
91 | let s1' = sort s1 in
92 | let s2' = sort s2 in
93 | equal_sigma s1' s2')
94 |
95 | let generalize gamma t =
96 | let module I = Infer (struct
97 | let polymorphic = true
98 | end) in
99 | I.generalize gamma t
100 |
101 | let solve : cons list -> sigma =
102 | let module I = Infer (struct
103 | let polymorphic = false
104 | end) in
105 | I.solve
106 |
107 | let parse s =
108 | try Parse_util.parse s with _ -> Alcotest.fail ("Failed to parse: " ^ s)
109 |
110 | let parse_file filename =
111 | try Parse_util.parse_file filename
112 | with _ -> Alcotest.fail ("Failed to parse file: " ^ filename)
113 |
114 | let parse_ty s =
115 | try Parse_util.parse_ty s with _ -> Alcotest.fail ("Failed to parse: " ^ s)
116 |
117 | (* run a function with specified timeout:
118 | https://discuss.ocaml.org/t/computation-with-time-constraint/5548/9 *)
119 | let with_timeout timeout f =
120 | let _ =
121 | Stdlib.Sys.set_signal Stdlib.Sys.sigalrm
122 | (Stdlib.Sys.Signal_handle (fun _ -> raise Timeout))
123 | in
124 | ignore (Unix.alarm timeout);
125 | try
126 | let r = f () in
127 | ignore (Unix.alarm 0);
128 | r
129 | with e ->
130 | ignore (Unix.alarm 0);
131 | raise e
132 |
--------------------------------------------------------------------------------
/homework/lamp.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/homework/lamp.pdf
--------------------------------------------------------------------------------
/lectures/2-26-note.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/2-26-note.pdf
--------------------------------------------------------------------------------
/lectures/CS162-Curry-Howard-Isomorphism.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/CS162-Curry-Howard-Isomorphism.pdf
--------------------------------------------------------------------------------
/lectures/CS162-Final-Review-Programming-Languages.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/CS162-Final-Review-Programming-Languages.pdf
--------------------------------------------------------------------------------
/lectures/curry_howard.ml:
--------------------------------------------------------------------------------
1 | (* OCaml's type system *)
2 | (* algebraic data type + parametric polymorphism *)
3 |
4 | (* 1. algebraic data type = generalized enum *)
5 |
6 | (* basic enum *)
7 | type color = Red | Green | Blue
8 |
9 | (* each case is a constructor that accepts some data *)
10 | type expr = NumLit of int | Add of int * int | ...
11 |
12 | (* parametric polymorphism = "template" for producing new types *)
13 |
14 | (* e.g., represent the fact that a computation may either return an int, or may produce an error *)
15 | type maybe_int = Nothing | Just of int
16 | let safe_div (x: int) (y: int) : maybe_int =
17 | if y = 0 then Nothing else Just (x/y)
18 | (* what if the computation returns either a bool, or may produce an error *)
19 | type maybe_bool = Nothing | Just of bool
20 | (* what if the computation returns either a float, or may produce an error *)
21 | type maybe_float = Nothing | Just of float
22 | (* there are infinitely many types in OCaml (because we can manufacture new ones using algebraic data types) *)
23 |
24 | (* so we write a single template, where the return type becomes a parameter and may be replaced with any type *)
25 | type 'a maybe = Nothing | Just of 'a
26 | let safe_div (x: int) (y: int) : int maybe =
27 | if y = 0 then Nothing else Just (x/y)
28 |
29 | (* 2. polymorphism = function works over multiple types *)
30 | (* parametric = those multiple types are represented using a type parameter *)
31 | (* algebraic data type + parametric polymorphism *)
32 |
33 | (* side note: there can be multiple parameters *)
34 | type 'a exception_bad = Error of 'a | Just of 'a
35 | (* problem: the data carried by error must have the same type as Just *)
36 | type ('e, 'a) myexception = Error of 'e | Just of 'a
37 |
38 | (* Two perspectives on OCaml types:
39 | 1. Types = sets
40 | 2. Types = logical propositions *)
41 |
42 | (* Perspective 1: types are sets *)
43 | (* What can you do with sets:
44 | - check for membership relationship (manifested by simple type systems like lambda-plus')
45 | - check for subset relationship (need a more powerful types like subset types)
46 | - measure its size!
47 | *)
48 | type mybool = MyTrue | MyFalse
49 | (* |mybool| = 2 *)
50 | type myunit = MyUnitValue
51 | (* |myunit| = 1 *)
52 | type myvoid = |
53 | (* |myvoid| = 0 *)
54 | type color = Red | Green | Blue
55 | (* |color| = 3 *)
56 | type myint = MyZero | MySucc of myint
57 | (* |myint| = inf = |Z| *)
58 |
59 | type 'a idenitty = Id of 'a
60 | (* |mybool myidentity| = ? *)
61 | (* |myunit myidentity| = ? *)
62 | (* |myint myidentity| = ? *)
63 | (* |'a myidentity| = |'a| *)
64 | type ('a, 'b) pair = Pair of 'a * 'b
65 | (* |(myunit, myunit) pair| = 1 *)
66 | (* |(mybool, myunit) pair| = 2 *)
67 | (* |(mybool, mycolor) pair| = 6 *)
68 | (* |(myvoid, myunit) pair| = 0 *)
69 | (* |('a, 'b) pair| = |'a| * |'b| *)
70 | type ('a, 'b) either = Left of 'a | Right of 'b
71 | (* |(myunit, myunit) either| = 2 *)
72 | (* |(mybool, myunit) either| = 3 *)
73 | (* |(mybool, mycolor) either| = 5 *)
74 | (* |(myvoid, myunit) pair| = 1 *)
75 | (* |('a, 'b) either| = |'a| + |'b| *)
76 | (* "algebraic" because "|" is like "+", and "*" is like "\times" *)
77 |
78 | type 'a mylist = MyNil | 'a * 'a mylist
79 | (* |mybool mylist| = 1 + 2 + 4 + 8 + ... *)
80 | (* Suppose |'a| = x. |'a mylist| = x^0 + x^1 + x^2 + x^3 + ... *)
81 | (* list <-> the polynomial function f(x) = x^0 + x^1 + x^2 + x^3 + ... *)
82 |
83 | (* in general, an algebraic data type (with k parameters) is equivalent to (<->) a polynomial function in k variables *)
84 | (* Let 'x t1 <-> f(x), 'y t2 <-> g(y) *)
85 |
86 | (* what can we do to polynomial functions? *)
87 | (* addition: f(x) + g(y). What type corresponds to h(x,y) = f(x) + g(y) ? *)
88 | (* multiplication: f(x) * g(y). What type corresponds to h(x,y) = f(x) * g(y) ? *)
89 | (* taking derivative: f'(x). What type corresponds to h(x) = f'(x) ?
90 | answer: the zipper data structure: *)
91 |
92 |
93 | (* Perspective 2: Interpreting OCaml types as logical propositions *)
94 |
95 |
96 | (* There are two equivalent ways of defining two-argument functions *)
97 |
98 | (* curried: *)
99 | let add : int -> int -> int
100 | = fun x -> fun y -> x + y
101 |
102 | (* uncurried: *)
103 | let add': (int * int) -> int = fun (x,y) -> x+y
104 |
105 | (* `curry` takes a uncurried function, and returns an equivalent curried function *)
106 | let curry (f: ('a * 'b) -> 'c) : 'a -> 'b -> 'c =
107 | fun x -> fun y -> f (x,y)
108 | (* The type looks like the proposition: ((A & B) -> C) -> (A -> B -> C) *)
109 |
110 | (* `uncurry` takes a curried function, and returns an equivalent uncurried function *)
111 | let uncurry (g: 'a -> 'b -> 'c) : ('a * 'b) -> 'c =
112 | fun xy ->
113 | match xy with
114 | | (x, y) -> g x y
115 | (* The type looks like the proposition: (A -> B -> C) -> ((A & B) -> C) *)
116 |
117 | (* Those two propositions are valid: their truth tables only contain TRUE! *)
118 |
119 | type ('a, 'b) p_and = | Both of 'a * 'b
120 | (* Interpretation:
121 | To prove A && B, we need to a proof of A and a proof of B *)
122 |
123 | let curry (f: ('a, 'b) p_and -> 'c) : 'a -> 'b -> 'c =
124 | fun x -> fun y -> f (Both (x, y))
125 |
126 | let uncurry (g: 'a -> 'b -> 'c) : ('a, 'b) p_and -> 'c =
127 | fun xy ->
128 | match xy with
129 | | Both (x,y) -> g x y
130 |
131 | (* A && B -> B && A *)
132 | let proof_that_and_is_commutative: ('a, 'b) p_and -> ('b, 'a) p_and =
133 | fun xy ->
134 | match xy with
135 | | Both (x, y) -> Both (y, x)
136 |
137 | type ('a, 'b) p_or = Left of 'a | Right of 'b
138 | (* Interpretation:
139 | To prove A || B, we need to either a proof of A, or a proof of B *)
140 | (* this type is the same as myexception & either *)
141 |
142 | (* A || B -> B || A *)
143 | let proof_that_or_is_commutative: ('a, 'b) p_or -> ('b, 'a) p_or =
144 | fun x_or_y ->
145 | match x_or_y with
146 | | Left x -> Right x
147 | | Right y -> Left y
148 |
149 | (* A && (B || C) -> A && B || A && C *)
150 | let proof_that_and_is_distributive: ('a, ('b, 'c) p_or) p_and -> (('a, 'b) p_and, ('a, 'c) p_and) p_or =
151 | fun a__and__b_or_c ->
152 | match a__and__b_or_c with
153 | | Both (a, b_or_c) ->
154 | match b_or_c with
155 | | Left b -> Left (Both (a, b))
156 | | Right c -> Right (Both (a, c))
157 |
158 | (* There is only one proof of TRUE *)
159 | type p_true = | Obvious
160 | (* this is the same as myunit *)
161 |
162 | (* There is no proof of FALSE *)
163 | type p_false = |
164 | (* this is the same as myvoid *)
165 |
166 | (* A -> TRUE *)
167 | let proof_that_anything_implies_true: 'a -> p_true =
168 | fun _ -> Obvious
169 |
170 | (* FALSE -> A *)
171 | let proof_that_false_implies_anything: p_false -> 'a =
172 | fun f -> match f with | _ -> failwith "this branch is unreachable"
173 |
174 | (* TRUE && FALSE -> FALSE *)
175 | let proof_that_true_and_false_implies_false: (p_true, p_false) p_and -> p_false =
176 | fun t_f ->
177 | match t_f with
178 | | Both (t, f) -> proof_that_false_implies_anything f
179 |
180 | (* "not A" is defined to be "A implies FALSE" *)
181 | type 'a p_not = 'a -> p_false
182 |
183 | (* Sanity check 1: not TRUE -> FALSE *)
184 | let proof_that_not_true_implies_false : p_true p_not -> p_false =
185 | fun not_t ->
186 | not_t Obvious
187 |
188 | (* Or equivalently *)
189 | let not_not_true_is_provable : p_true p_not p_not =
190 | proof_that_not_true_implies_false
191 |
192 | (* Sanity check 2: not FALSE *)
193 | let not_false_is_provable: p_false p_not =
194 | proof_that_false_implies_anything
195 |
196 | (* Sanity check 3: A -> not A -> FALSE *)
197 | let contradiction: 'a -> ('a p_not) -> p_false =
198 | fun a ->
199 | fun not_a ->
200 | not_a a
201 |
202 | (* A -> not (not A) *)
203 | (* which is A -> ((A -> FALSE) -> FALSE) *)
204 | let proof_that_a_implies_not_not_a: 'a -> 'a p_not p_not =
205 | fun a ->
206 | (* want not (not A), which is (A -> FALSE) -> FALSE *)
207 | fun not_a -> contradiction a not_a
208 |
209 | (* (A->B) -> (not B -> not A) *)
210 | (* which is (A->B) -> ((B->FALSE) -> (A->FALSE)) *)
211 | (* Example:
212 | If you've taken CS162, then you love OCaml
213 | <-> If you don't love OCaml, then you haven't taken CS162
214 | I.e.,
215 | in an imaginary world where everyone who has taken CS162 loves OCaml,
216 | we know that anyone who doesn't love OCaml must haven't taken CS162. *)
217 | let proof_of_contrapositive: ('taken -> 'love) -> ('love p_not -> 'taken p_not) =
218 | fun taken_implies_love ->
219 | fun not_love ->
220 | (* Want: not taken_cs162, which is taken_cs162 -> FALSE *)
221 | (fun taken ->
222 | (* Want: FALSE *)
223 | let love = taken_implies_love taken (* modus ponens <=> function application *)
224 | in contradiction love not_love)
225 |
226 | (* The following three intuitive propositions are not provable! *)
227 | (* let not_not_a_implies_a: 'a p_not p_not -> 'a =
228 | fun not_not_a -> ??? *)
229 | (* let law_of_excluded_middle: ('a, 'a p_not) p_or = ??? *)
230 | (* let de_morgans_law: ('a, 'b) p_or p_not -> ('a p_not, 'b p_not) p_and =
231 | fun not__a_or_b -> ??? *)
232 |
233 | (* Curry-Howard correspondence *)
234 | (* Type = Proposition *)
235 | (* Program = Proof *)
236 | (* Program P has type T = There is a constructive proof P for proposition T *)
237 |
238 | (* Coq is a language with a type system strictly more powerful than OCaml's *)
239 | (* You can encode any statement you want to prove into a type. Then you can write a functional program with that type.
240 | If your program type-checks, then you have a certificate that the statement is formally proven, and the proof has been mechanically verified by the type checker. *)
241 | (* Catch: if the type system is so powerful, why don't we use it everywhere?
242 | Answer: The type system is so powerful that the halting problem can be stated as a type.
243 | So type checking can't be fully automated (otherwise the halting problem would be decidable).
244 | As a consequence, programmers need to provide a lot of hints (aka type annotations) to the type checker,
245 | which is too much for practical purposes.
246 | The typical ratio of program to annotation in Coq is usually 1/10.
247 | I.e., every 1 line of code needs 10 lines of annotation.
248 | This is clearly impractical for most software systems.
249 | However, the tradeoff may be worthwhile for safety-critical systems (like aviation/medical/finacial software).
250 |
251 | Mainstream functional languages usually use a much less powerful type system called System F (or Hindley-Milner),
252 | which is expressive enough that a lot of interesting and common properties can still be encoded and
253 | automatically checked, but not so expressive to encode the halting problem.
254 | *)
--------------------------------------------------------------------------------
/lectures/final-review-note.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/final-review-note.pdf
--------------------------------------------------------------------------------
/lectures/final-review.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/final-review.pdf
--------------------------------------------------------------------------------
/lectures/inference-rules.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/inference-rules.pdf
--------------------------------------------------------------------------------
/lectures/lambda-plus.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lambda-plus.pdf
--------------------------------------------------------------------------------
/lectures/lecture1.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture1.key
--------------------------------------------------------------------------------
/lectures/lecture1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture1.pdf
--------------------------------------------------------------------------------
/lectures/lecture10.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture10.key
--------------------------------------------------------------------------------
/lectures/lecture10.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture10.pdf
--------------------------------------------------------------------------------
/lectures/lecture11.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture11.key
--------------------------------------------------------------------------------
/lectures/lecture11.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture11.pdf
--------------------------------------------------------------------------------
/lectures/lecture12.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture12.key
--------------------------------------------------------------------------------
/lectures/lecture12.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture12.pdf
--------------------------------------------------------------------------------
/lectures/lecture13.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture13.key
--------------------------------------------------------------------------------
/lectures/lecture13.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture13.pdf
--------------------------------------------------------------------------------
/lectures/lecture14.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture14.key
--------------------------------------------------------------------------------
/lectures/lecture14.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture14.pdf
--------------------------------------------------------------------------------
/lectures/lecture15.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture15.key
--------------------------------------------------------------------------------
/lectures/lecture15.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture15.pdf
--------------------------------------------------------------------------------
/lectures/lecture16.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture16.pdf
--------------------------------------------------------------------------------
/lectures/lecture2.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture2.key
--------------------------------------------------------------------------------
/lectures/lecture2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture2.pdf
--------------------------------------------------------------------------------
/lectures/lecture3.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture3.key
--------------------------------------------------------------------------------
/lectures/lecture3.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture3.pdf
--------------------------------------------------------------------------------
/lectures/lecture4.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture4.key
--------------------------------------------------------------------------------
/lectures/lecture4.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture4.pdf
--------------------------------------------------------------------------------
/lectures/lecture5.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture5.key
--------------------------------------------------------------------------------
/lectures/lecture5.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture5.pdf
--------------------------------------------------------------------------------
/lectures/lecture6.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture6.key
--------------------------------------------------------------------------------
/lectures/lecture6.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture6.pdf
--------------------------------------------------------------------------------
/lectures/lecture7.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture7.key
--------------------------------------------------------------------------------
/lectures/lecture7.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture7.pdf
--------------------------------------------------------------------------------
/lectures/lecture8.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture8.key
--------------------------------------------------------------------------------
/lectures/lecture8.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture8.pdf
--------------------------------------------------------------------------------
/lectures/lecture9.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture9.key
--------------------------------------------------------------------------------
/lectures/lecture9.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/lecture9.pdf
--------------------------------------------------------------------------------
/lectures/midterm-review.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/midterm-review.pdf
--------------------------------------------------------------------------------
/lectures/win25-2-13-note.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/lectures/win25-2-13-note.pdf
--------------------------------------------------------------------------------
/sections/sec01/install.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Setting up OCaml
4 |
5 | > Adapted from [this guide](https://github.com/fredfeng/CS162/blob/winter-2021/sections/section1/install_ocaml.md) written by Bryan Tan.
6 |
7 |
8 | ## Step 1. Installing `opam`
9 |
10 | First, you need to install `opam`, OCaml's package manager, which allows you to install OCaml-related tools.
11 |
12 |
13 | ### macOS
14 | Make sure you have [homebrew](https://brew.sh/). Then run
15 | ```
16 | brew install opam
17 | ```
18 |
19 |
20 | ### Windows
21 | Windows users are recommended to use [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install), and then follow the instructions under the **(Non-CSIL) Linux** section, since OCaml doesn't work too well on Windows.
22 |
23 | As a last resort, you can also try CSIL, although some students have found it difficult to set up the environment on CSIL.
24 |
25 |
26 | ### (Non-CSIL) Linux
27 | Follow [these step](https://opam.ocaml.org/doc/Install.html#Binary-distribution), under the "Binary distribution" section. Make sure you install opam version >= 2.1.0.
28 |
29 |
30 | ### CSIL
31 |
32 | Log onto a CSIL machine. Download the `opam` binary using:
33 | ```bash
34 | curl -LR 'https://github.com/ocaml/opam/releases/download/2.1.4/opam-2.1.4-x86_64-linux' -o opam
35 | ```
36 |
37 | Then make sure the downloaded binary is executable, and move it to `~/bin/opam`:
38 | ```bash
39 | chmod +x opam
40 | mkdir -p ~/bin/
41 | mv opam ~/bin/opam
42 | ```
43 | Check to make sure it's on `PATH`:
44 | ```
45 | [junrui@csilvm-01 ~]$ opam --version
46 | 2.1.4
47 | ```
48 |
49 | > **Note:** If you had an unsuccessful installation of `opam`, please run `rm -rf ~/.opam` to clean up partial installation before retrying the steps below.
50 |
51 |
52 |
53 |
54 | ## Step 2. Installing OCaml, for real
55 |
56 | > **Note:** Use the commands below one-by-one, read the instructions on the screen and proceed accordingly, since some of the commands below are interactive.
57 |
58 | After installing `opam`, you need to first tell your shell where the stuff `opam` installs lives. Use the following command to initialize the environment:
59 |
60 | ```
61 | opam init
62 | ```
63 |
64 | Please note that:
65 | - You need to have at least 1GB of available disk space. Otherwise the initialization will fail with a cryptic error message.
66 | - It may prompt you once or twice during the process. We highly recommend you **respond with Y to each prompt** to make your life easier in the future.
67 | - This command will take 30-40 minutes to run. Don't force quit even if you see the `Done` message. Only exit once you see a new shell prompt in which you can type the next shell command. ~~One more reason to not wait until the last minute to start hw1.~~
68 |
69 |
70 | After `opam init` is done, type the following command so you can have the OCaml tools in your current shell session:
71 |
72 | ```
73 | eval `opam env`
74 | ```
75 |
76 | Now, you are ready to install OCaml. Use the following command to install a version of OCaml (called a switch by `opam`):
77 |
78 | ```
79 | opam switch create cs162 ocaml-base-compiler.5.1.1
80 | ```
81 |
82 | After the command above is done, you need to tell your shell where OCaml is again:
83 |
84 | ```
85 | eval $(opam env)
86 | ```
87 |
88 | At this point, if everything went well, when you type `ocaml`, you should see a prompt like this when you type `ocaml`:
89 |
90 | ```
91 | % ocaml
92 | OCaml version 5.1.1
93 | Enter #help;; for help.
94 |
95 | #
96 | ```
97 |
98 | Then, you can proceed to the next stage, installing [`utop`](https://opam.ocaml.org/blog/about-utop/).
99 |
100 |
101 | ## Step 3. Installing `utop`.
102 |
103 | `utop` is is an enhanced REPL interpreter for OCaml with features like autocompletion. Install it with
104 |
105 | ```
106 | opam install utop
107 | ```
108 |
109 | Then `utop` should be available as a command. You can run as a REPL interpreter by entering a few OCaml expressions, e.g.,
110 |
111 | ```ocaml
112 | 1 + 2;;
113 | ```
114 |
115 | You should see
116 | ```ocaml
117 | - : int = 3
118 | ```
119 | as a reponse. That is, OCaml infers that the expression we entered has type `int`, and it evaluates the expression to `3`.
120 |
121 | In REPL mode, every expression needs to be terminated with `;;` before you hit the enter key. Otherwise, you'll just start a new line by hitting enter, and `utop` will patiently wait for you to type `;;` before it can start interpreting the expression. This contrasts with file mode, in which you do not have to terminate every expression with `;;`.
122 |
123 |
124 | ## Loading an OCaml file in `utop`
125 |
126 | Make a new file called `hello.ml` containing the following line:
127 | ```
128 | print_endline "hello!"
129 | ```
130 |
131 | Now, open `utop` and load `hello.ml` with
132 | ```
133 | #use "hello.ml";;
134 | ```
135 |
136 | You should see `hello!` printed on your console, along with
137 | ```ocaml
138 | - : unit = ()
139 | ```
140 | indicating that the result of evaluating `print_endline "hello!"` is the unit value `()`, whose type is the unit type. (The unit type is usually used to indicate that the expression has some side effect without procuding a concrete value.)
141 |
142 | For homework assignments, you can use any text editor you like. We recommend [VSCode](https://code.visualstudio.com/) with the [OCaml Platform plugin](https://marketplace.visualstudio.com/items?itemName=ocamllabs.ocaml-platform). In addition, if you are working remotely with CSIL, you might want to install the [remote SSH plugin](https://code.visualstudio.com/docs/remote/ssh-tutorial) on your laptop to avoid having to manually sync files between your computer and CSIL.
--------------------------------------------------------------------------------
/sections/sec02/notes.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fredfeng/CS162/c5c78d469233522607bac75e0b6c4295c07f4151/sections/sec02/notes.pdf
--------------------------------------------------------------------------------