0% found this document useful (0 votes)
3 views

22-1-midterm

The document outlines the instructions and structure for a midterm exam in CS 320, Spring 2022, consisting of 13 questions across 11 pages. It includes details on expression syntax, semantics, and specific programming language constructs, as well as example questions related to programming concepts. Students are required to answer in Korean and/or English, and the exam is closed-book with a time limit of 150 minutes.

Uploaded by

vedapac571
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

22-1-midterm

The document outlines the instructions and structure for a midterm exam in CS 320, Spring 2022, consisting of 13 questions across 11 pages. It includes details on expression syntax, semantics, and specific programming language constructs, as well as example questions related to programming concepts. Students are required to answer in Korean and/or English, and the exam is closed-book with a time limit of 150 minutes.

Uploaded by

vedapac571
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 11

Midterm Exam

CS 320, Spring 2022

Instructions

• You have 150 minutes to complete this closed-book exam.


• There are 11 pages and 13 questions.

• You should write answers in Korean and/or English.


• n ranges over integers (n ∈ Z).
• x ranges over identifiers (x ∈ Id ).
• e ranges over expressions (e ∈ Expr ).

• v ranges over values (v ∈ Val ).


• a ranges over addresses (a ∈ Addr ).
• σ ranges over environments.

• M ranges over stores.


• We use parentheses to represent expressions clearly. For example, we may write λx.(x + x) instead of
λx.x + x to make sure that we do not mean (λx.x) + x. You can use parentheses in your answers.
• We often omit the terms “evaluation” and “evaluating” for brevity. For example, “the result of e is v”
means that the result of evaluating e is v, and “e terminates” means that evaluation of e terminates.

• If a question says “write the result of e,” then


– you should write v when e results in v.
– you should write “not terminate” when e does not terminate.
– you should write “error” when e incurs a run-time error.

• When you write expressions in your answers, you can freely choose between concrete syntax and
abstract syntax to write the expressions. For example, both { x => x } and λx.x are possible.
• Pages 2 and 3 show the definitions of the languages defined in the lectures. If you need the definitions
while solving the questions, refer to them.

1
Concrete syntax
[VAE]
expr ::= num | "(" expr "+" expr ")" | "(" expr "-" expr ")" | "(" expr "*" expr ")"
| "if0" "(" expr ")" "{" expr "}" "else" "{" expr "}" | "{" expr ";" expr "}"
| id | "{" "val" id "=" expr ";" expr "}"
[FAE, LFAE]
expr ::= ... | "{" id "=>" expr "}" | expr "(" expr ")"
[RFAE]
expr ::= ... | "{" "def" id "(" id ")" "=" expr ";" expr "}"
[BFAE]
expr ::= ... | "Box" "(" expr ")" | expr "." "get" | expr "." "set" "(" expr ")"
[MFAE]
expr ::= ... | "{" id "=" expr "}"
Abstract syntax
Each language uses only a part of the below definition, according to the concrete syntax defined above.
e ::= n | e + e | e − e | e × e | if0 e e e | e; e | x | val x = e; e
| λx.e | e e | def x(x) = e; e | ref e | !e | e := e | x = e
sealed trait Expr
case class Num(n: Int) extends Expr
case class Add(l: Expr, r: Expr) extends Expr
case class Sub(l: Expr, r: Expr) extends Expr
case class Mul(l: Expr, r: Expr) extends Expr
case class If0(c: Expr, t: Expr, f: Expr) extends Expr
case class Seq(l: Expr, r: Expr) extends Expr
case class Id(x: String) extends Expr
case class Val(x: String, e: Expr, b: Expr) extends Expr
case class Fun(x: String, b: Expr) extends Expr
case class App(f: Expr, a: Expr) extends Expr
case class Rec(f: String, x: String, b: Expr, e: Expr) extends Expr
case class NewBox(e: Expr) extends Expr
case class OpenBox(b: Expr) extends Expr
case class SetBox(b: Expr, e: Expr) extends Expr
case class Set(x: String, e: Expr) extends Expr
Values
[VAE]
v ::= n
sealed trait Value
case class NumV(n: Int) extends Value
[FAE, RFAE]
v ::= · · · | hλx.e, σi
case class CloV(x: String, b: Expr, var env: Env) extends Value
[BFAE, MFAE]
v ::= · · · | a
case class AddrV(a: Addr) extends Value
[LFAE]
v ::= · · · | (e, σ)
case class ExprV(e: Expr, env: Env) extends Value

2
Semantics
[VAE, FAE, RFAE] (VAE and FAE use only a part of the below rules.)
fin
σ ∈ Id →7 Val
type Env = Map[String, Value]
σ ` e1 ⇒ n1 σ ` e2 ⇒ n2 σ ` e1 ⇒ n1 σ ` e2 ⇒ n2 σ ` e1 ⇒ n1 σ ` e2 ⇒ n2
σ`n⇒n
σ ` e1 + e2 ⇒ n1 + n2 σ ` e1 − e2 ⇒ n1 − n2 σ ` e1 × e2 ⇒ n1 × n2

σ ` e1 ⇒ 0 σ ` e 2 ⇒ v2 σ ` e 1 ⇒ v1 v1 6= 0 σ ` e 3 ⇒ v3 σ ` e 1 ⇒ v1 σ ` e 2 ⇒ v2
σ ` if0 e1 e2 e3 ⇒ v2 σ ` if0 e1 e2 e3 ⇒ v3 σ ` e 1 ; e 2 ⇒ v2

x ∈ Domain(σ) σ ` e 1 ⇒ v1 σ[x 7→ v1 ] ` e2 ⇒ v2
σ ` λx.e ⇒ hλx.e, σi
σ ` x ⇒ σ(x) σ ` val x = e1 ; e2 ⇒ v2

σ ` e1 ⇒ hλx.e, σ 0 i σ ` e 2 ⇒ v2 σ 0 [x 7→ v2 ] ` e ⇒ v σ 0 = σ[x1 7→ hλx2 .e1 , σ 0 i] σ 0 ` e2 ⇒ v2


σ ` e1 e2 ⇒ v σ ` def x1 (x2 ) = e1 ; e2 ⇒ v2

[BFAE] (Showing only representative rules)


fin
σ ∈ Id →7 Val
fin
M ∈ Addr →7 Val
type Addr = Int
type Env = Map[String, Value]
type Sto = Map[Addr, Value]
σ, M ` e ⇒ v, M1 a 6∈ Domain(M1 ) σ, M ` e ⇒ a, M1 a ∈ Domain(M1 )
σ, M ` ref e ⇒ a, M1 [a 7→ v] σ, M `!e ⇒ M1 (a), M1

σ, M ` e1 ⇒ a, M1 σ, M1 ` e2 ⇒ v, M2
σ, M ` e1 := e2 ⇒ v, M2 [a 7→ v]

[MFAE] (Showing only representative rules)


fin
σ ∈ Id →7 Addr
fin
M ∈ Addr →7 Val
type Addr = Int
type Env = Map[String, Addr]
type Sto = Map[Addr, Value]
x ∈ Domain(σ) σ(x) ∈ Domain(M ) σ, M1 ` e ⇒ v, M2 x ∈ Domain(σ)
σ, M ` x ⇒ M (σ(x)), M σ, M1 ` x = e ⇒ v, M2 [σ(x) 7→ v]

σ, M ` e1 ⇒ hλx.e, σ 0 i, M1
0
σ, M1 ` e2 ⇒ v , M2 a 6∈ Domain(M2 ) σ 0 [x 7→ a], M2 [a 7→ v 0 ] ` e ⇒ v, M3
σ, M ` e1 e2 ⇒ v, M3

[LFAE] (Showing only representative rules)


fin
σ ∈ Id →7 Val
type Env = Map[String, Value]
σ ` e ⇒ v1 v1 ⇓ v 2 σ ` e 1 ⇒ v1 v1 ⇓ n1 σ ` e 2 ⇒ v2 v2 ⇓ n 2
(e, σ) ⇓ v2 σ ` e1 + e2 ⇒ n1 + n2

σ ` e1 ⇒ v 1 v1 ⇓ hλx.e, σ 0 i σ 0 [x 7→ (e2 , σ)] ` e ⇒ v


σ ` e1 e2 ⇒ v

3
1) (5pts) The following paragraph explains basic concepts in programming languages:

(a) defines how expressions look like, and (b) defines what expressions evaluate
to. There are two sorts of (a) : (c) , which describes an expression as a string, and
(d) , which describes an expression as a tree. A(n) (e) is a program that converts
a string following (c) to a tree following (d) .

Fill the blanks with some of the following terms.

syntax concrete semantics BNF form concrete syntax semantics


abstract semantics interpreter parser abstract syntax compiler

2) (5pts) Consider the following RFAE expression:


{
def a(x) = a(x);
1 2 3 4
{
val a = { a => a };
5 6 7
{
val a = a;
8 9
(a + 1)
10
}
}
}
(The numbers below the identifiers are not parts of the expression. They are for convenience of writing
answers. Use the numbers to denote identifiers in your answers.)

a) (3pts) Write every bound occurrence and its binding occurrence. (To say that the first a in line
2 is a bound occurrence whose binding occurrence is a in line 7, write 1->10 in your answer.)
b) (2pts) Write every shadowed occurrence and its shadowing occurrence. (To say that the first a
in line 2 is shadowed by a in line 7, write 1->10 in your answer.)

3) (5pts) Write an FAE expression that has a free identifier but evaluates to a value without incurring
any run-time errors.

4
4) (10pts) Consider the following functions:
• bindings: returns the set of every identifier that has at least one binding occurrence in a given expression
• frees: returns the set of every identifier that has at least one free occurrence in a given expression
For example, bindings(e) and frees(e) evaluate to Set("x") and Set("y") respectively, where e
denotes { val x = 1; y }. Consider the following implementation of bindings and frees for RFAE:
def bindings(e: Expr): Set[String] = e match {
case Num(n) => Set()
case Id(x) => Set()
case Val(x, e, b) => (bindings(e) ++ bindings(b)) + x
case App(f, a) => bindings(f) ++ bindings(a)
case Fun(x, b) => (a)
case Rec(f, x, b, e) => (b) }
def frees(e: Expr): Set[String] = e match {
case Num(n) => Set()
case Id(x) => Set(x)
case Val(x, e, b) => frees(e) ++ (frees(b) - x)
case App(f, a) => frees(f) ++ frees(a)
case Fun(x, b) => (c)
case Rec(f, x, b, e) => (d) }
Fill the blanks to complete the implementation. You may refer to the following to deal with sets:
• Where s is a set, s + v denotes s ∪ {v}, and s - v denotes s \ {v}.
• Where s1 and s2 are sets, s1 ++ s2 denotes s1 ∪ s2, and s1 -- s2 denotes s1 \ s2.
5) (10pts) Consider PFAE, which extends FAE with pairs:
case class Pair(f: Expr, s: Expr) extends Expr
case class Fst(e: Expr) extends Expr
case class Snd(e: Expr) extends Expr
Pair creates a new pair; Fst gives the first value of a pair; Snd gives the second value of a pair.
This question asks you to implement the desugar function, which takes a PFAE expression and returns
an expression that lacks Pair, Fst, and Snd but has the same behavior as the given expression. Precisely
speaking, desugar satisfies the following:
• If e evaluates to an integer, desugar(e) evaluates to the same integer.
• If e evaluates to a function, desugar(e) evaluates to a function.
• If e evaluates to a pair, desugar(e) evaluates to a function.
• If e does not terminate, desugar(e) does not terminate.
• If e incurs a run-time error, desugar(e) can have any behavior.
def desugar(e: Expr): Expr = e match {
case Num(n) => Num(n)
case Id(x) => Id(x)
case Fun(x, b) => Fun(x, desugar(b))
case App(f, a) => App(desugar(f), desugar(a))
case Fst(e) =>
// Apply ‘e‘ to a function that takes two arguments and returns the former
App(desugar(e), Fun("x", Fun("y", Id("x"))))
case Snd(e) =>
// Apply ‘e‘ to a function that takes two arguments and returns the latter
App(desugar(e), Fun("x", Fun("y", Id("y"))))
case Pair(f, s) => A }
Fill the blank to complete the implementation.

5
6) (5pts) Write the result of each of the following FAE expressions:

a) {
val fac = { n =>
if0 (n) { 1 } else { (n * fac((n - 1))) }
};
fac(5)
}
b) {
val facX = { facY => { n =>
if0 (n) { 1 } else { (n * facY(facY)((n - 1))) }
}};
facX(facX)(5)
}
c) {
val facX = { facY => { n =>
if0 (n) { 1 } else { (n * facY(facY)((n - 1))) }
}};
{
val fac = { n => facX(facX)(n) };
fac(5)
}
}
d) {
val facX = { facY => { n =>
if0 (n) { 1 } else { (n * facY(facY)((n - 1))) }
}};
{
val fac = facX(facX);
fac(5)
}
}
e) {
val facX = { facY => { n => {
val fac = facY(facY);
if0 (n) { 1 } else { (n * fac((n - 1))) }
}}};
{
val fac = { n => facX(facX)(n) };
fac(5)
}
}

You will get max(0, (number of correct answers) − 2 × (number of wrong answers)) points. If you do
not write anything, it will be considered neither correct nor wrong.

6
7) (10pts) If a language supports mutation, we can implement mkRec more easily. Consider the following
BFAE expression:
{
val mkRec = { b => {
val box = Box({ x => y });
{
val f = b( (a) );
{
box.set( (b) );
(c)

}
}
}};
{
val fac = mkRec({ fac => {
n => if0 (n) { 1 } else { (n * fac((n - 1))) }
}});
fac(5)
}
}
The key idea is to put a dummy function in a box, create a recursive function with the box, and
replace the dummy function with the correct recursive function. Fill the blanks to complete the
implementation. fac must be a function computing the factorial of a given integer, and the result of
the whole expression must be 120.

8) (5pts) Consider the following interp for MFAE that prints given arguments each time it is called:
def malloc(sto: Sto): Addr = (sto.keySet + 0).max + 1

def interp(e: Expr, env: Env, sto: Sto): (Value, Sto) = {


println(s"$e, $env, $sto")
e match {
case Num(n) => (NumV(n), sto)
case Id(x) => (sto(env(x)), sto)
case Fun(x, b) => (CloV(x, b, env), sto)
case App(f, a) =>
val (fv, fs) = interp(f, env, sto)
val (av, as) = interp(a, env, fs)
fv match {
case CloV(x, b, fenv) =>
val addr = malloc(as)
interp(b, fenv + (x -> addr), as + (addr -> av))
case _ => error() }
... }
Let e be an expression { x => x }({ x => x }(1)), which is App(Fun("x", Id("x")), App(Fun("x",
Id("x")), Num(1))). What does interp(e, Map(), Map()) print? You can use any notation you
prefer in your answer. For example, you may write [1 -> 3, 2 -> <{ x => x }, []>] to denote a
store that maps the address 1 to the integer 3 and the address 2 to the closure hλx.x, ∅i. If you think
your notation is confusing, describe what your notation means in the answer.

7
9) (10pts) This question implements an interpreter for PMFAE, which extends MFAE with pointers. Pointers
are addresses as values. A pointer to a variable x denotes the address of x. For this purpose, we add
new sorts of an expression and a value as follows:
e ::= · · · | &x | ∗ e | ∗ e := e
v ::= · · · | a
We implement them as follows:
case class Ref(x: String) extends Expr
case class Deref(p: Expr) extends Expr
case class Assign(p: Expr, e: Expr) extends Expr
case class PtrV(a: Addr) extends Value

Ref(x) denotes &x; Deref(e) denotes ∗e; Assign(e1 , e2 ) denotes ∗e1 := e2 ; PtrV(a) denotes a.
The semantics of the new expressions is as follows:

x ∈ Domain(σ) σ, M1 ` e ⇒ a, M2 a ∈ Domain(M2 )
σ, M ` &x ⇒ σ(x), M σ, M1 ` ∗e ⇒ M2 (a), M2

σ, M1 ` e1 ⇒ a, M2 σ, M2 ` e2 ⇒ v, M3
σ, M1 ` ∗e1 := e2 ⇒ v, M3 [a 7→ v]

Fill the blanks to complete the following interp:


def interp(e: Expr, env: Env, sto: Sto): (Value, Sto) = e match {
case Num(n) => (NumV(n), sto)
case Id(x) => (sto(env(x)), sto)
case Fun(x, b) => (CloV(x, b, env), sto)
case App(f, a) =>
val (fv, fs) = interp(f, env, sto)
val (av, as) = interp(a, env, fs)
fv match {
case CloV(x, b, fenv) =>
val addr = malloc(as)
interp(b, fenv + (x -> addr), as + (addr -> av))
case _ => error()
}
case Set(x, e) =>
val (v, s) = interp(e, env, sto)
(v, s + (env(x) -> v))
case Ref(x) => (a)
case Deref(p) => (b)
case Assign(p, e) => (c)
}
You may use the following function in your answer:
def getAddr(v: Value): Addr = v match {
case PtrV(a) => a
case _ => error()
}

8
10) (5pts) The following figure describes mark-and-sweep garbage collection. Initially, the heap is full
with four records. The garbage collector attaches the W (white) tag to each record and starts marking.
After marking all the live records, the garbage collector frees unreachable records. As a result, only
three records remain.

Write all the missing steps to complete the figure. Do not draw the heap; write only the tags of the
records in each step. There are three kinds of a tag: W, G (gray), or B (black). When writing the tags
in a single step, follow the “Z” order (top-left, top-right, bottom-left, bottom-right) to traverse the
records. For example, to claim that the following figure is correct, write WGBW, BGWB as your answer.

Your answer must satisfy the following conditions:


• If the tag of a record is W in a certain step, then the tag of the same record is W or G in the next step.
• If the tag of a record is G in a certain step, then the tag of the same record is G or B in the next step.
Note that there are at least four missing steps.

9
11) (15pts) Suppose that a garbage-collected interepreter uses the following three kinds of a record:
• Tag 1: a record containing one integer
• Tag 2: a record containing one pointer
• Tag 99: forwarding pointer (to to-space)
The interpreter has one register, which always contains a pointer, and a memory pool of size 24. Its
garbage collector is a two-space copying collector, so each space is of size 12. Records are allocated
consecutively in from-space, starting from 0. Consider the following garbage collector implementation:

var reg = 6 // Register, which always contains a pointer


// Heap of size 24; the first half is the from-space; the second half is the to-space
val heap = Array(2, 4, 1, 7, 2, 0, 2, 8, 2, 2, 2, 8,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
var free = 12 // The address where a record will be allocated next in the to-space
var scan = 12 // A pointer to the first unscanned (i.e., gray) record in the to-space

// Shows the current memory


def show() = println(s"$reg, $heap, $free, $scan")

// Runs two-space copying GC while showing the memory after each step
def gc() = {
show()
reg = copy(reg); show()
while (scan < free) { doScan(); show() }}

// Copies the record at ‘addr‘ to the to-space


// Returns the new address of the record after copying
// Does not copy again if the record has been copied already
def copy(addr: Int): Int = heap(addr) match {
case 1 | 2 => // Copies the record; installs a forwarding pointer; advances ‘free‘
(a)
case 99 => // No copying
(b)

// Scans the record at ‘scan‘


def doScan() = heap(scan) match {
case 1 => scan += 2 // The record does not have a pointer; just advances ‘scan‘
case 2 =>
(c) // The record has a pointer; copies the record pointed by the pointer
scan += 2 // Advances ‘scan‘
}
When the garbage collector runs, it prints the following memory trace:
6, 2 4 1 7 2 0 2 8 2 2 2 8 0 0 0 0 0 0 0 0 0 0 0 0, 12, 12
12, 2 4 1 7 2 0 99 12 2 2 2 8 2 8 0 0 0 0 0 0 0 0 0 0, 14, 12
12, 2 4 1 7 2 0 99 12 99 14 2 8 2 14 2 2 0 0 0 0 0 0 0 0, 16, 14
12, 2 4 99 16 2 0 99 12 99 14 2 8 2 14 2 16 1 7 0 0 0 0 0 0, 18, 16
12, 2 4 99 16 2 0 99 12 99 14 2 8 2 14 2 16 1 7 0 0 0 0 0 0, 18, 18
Fill the blanks to complete the implementation. Each blank requires one or more lines of code. Note
that when arr is an array, arr(i) gives the ith element of arr, and arr(i) = v updates the ith
element of arr to v. Arrays are zero-indexed.

10
12) (5pts) Consider the following interp for LFAE that prints given arguments each time it is called:
def strict(v: Value): Value = v match {
case ExprV(e, env) => strict(interp(e, env))
case _ => v }
def interp(e: Expr, env: Env): Value = {
println(s"$e, $env")
e match {
case Num(n) => NumV(n)
case Add(l, r) => (strict(interp(l, env)), strict(interp(r, env))) match {
case (NumV(x), NumV(y)) => NumV(x + y)
case _ => error() }
case Id(x) => env(x)
case Fun(x, b) => CloV(x, b, env)
case App(f, a) => strict(interp(f, env)) match {
case CloV(x, b, fenv) => interp(b, fenv + (x -> ExprV(a, env)))
case _ => error() }}}
Let e be an expression { x => (x + x) }((1 + 2)), which is App(Fun("x", Add(Id("x"), Id("x"))),
Add(Num(1), Num(2))). What does interp(e, Map()) print? You can use any notation you prefer
in your answer. If you think your notation is confusing, describe what your notation means in the
answer.

13) (10pts) This question asks you to extend FAE with promises, which originate from the Racket pro-
gramming language. A promise is a value that encapsulates an expression to be evaluated on demand
via force. Due to the introduction of promises, values are now defined as follows:
v ::= n | hλx.e, σi | delay(e, σ) | lazy(e, σ)
While n and hλx.e, σi are usual number and closure values, delay(e, σ) and lazy(e, σ) are two different
kinds of a promise value. They are similar in that both encapsulate expressions, but their behaviors
are slightly different when being forced.
When a value is forced, a value comes out as the result. The semantics of force is as follows:
• n
When forced, it produces n as the result.
• hλx.e, σi
When forced, it produces hλx.e, σi as the result.
• delay(e, σ)
When forced, it evaluates e under σ and produces e’s result as the result.
• lazy(e, σ)
When forced, it evaluates e under σ. Then, e’s result is also forced to obtain a value v. v is the
result of forcing lazy(e, σ).
There are three kinds of an expression treating a promise: e ::= · · · | delay e | lazy e | force e.
• delay e
Creates a delay promise with e and the current environment.
• lazy e
Creates a lazy promise with e and the current environment.
• force e
Evaluates e and forces e’s result to obtain the result.
a) (5pts) Define the semantics of force of the form v ⇓ v . v1 ⇓ v2 means forcing v1 results in v2 .
b) (5pts) Define the operational semantics of the form σ ` e ⇒ v for delay e, lazy e, and force e.

—End of Exam—

11

You might also like