CS 331 Spring 2025  >  Exam Review Problems, Set C


CS 331 Spring 2025
Exam Review Problems, Set C

This is the third set of exam review problems.

Problems

Review problems are given below. Answers are in the Answers section of this document. Do not turn these in.

  1. A Predictive Recursive-Descent parser cannot be based directly on a grammar that is not LL(k). Therefore, your friend Egbert says, “If I need to write a Predictive Recursive-Descent parser, and the grammar I am given is not LL(k), then my only option is to weep, wail, gnash my teeth, run in circles screaming, and give up in despair!” Explain why Egbert is wrong.
    1. In a Predictive Recursive-Descent parser, if we call a parsing function, and it indicates a syntax error, what must we do?
    2. Why?
    1. What is an abstract syntax tree?
    2. What are abstract syntax trees commonly used for?
  2. Name and explain the 4 actions that a shift-reduce automaton can perform at each step in its execution.
    1. How fast do practical lexers and parsers run?
    2. True or false: Practical parsing methods can handle all CFLs.
    3. What is the worst-case runtime for a typical parsing method that can handle all CFLs?
  3. What is a shotgun parser?
    1. What is manifest typing?
    2. What is an extensible type system.
    3. What is a sound type system?
    4. What is wrong with the term strong typing (other than the kitten problem)?
  4. Describe Haskell’s type system.
    1. Static or dynamic?
    2. Mostly manifest or mostly implicit?
    3. Fixed set of types or extensible?
    4. Types apply to variables?
    1. True or false: Haskell is oriented primarily toward imperative programming.
    2. What keyword—if any—introduces a Haskell function?
    3. True or false: Haskell has first-class functions.
    4. True or false: Haskell allows for lambda functions.
    5. True or false: By default, Haskell does lazy evaluation.
    6. How can we change the above evaluation strategy to a different one?
    1. True or false. Ordinary Haskell variables declared inside a function are always local variables.
    2. What keyword or function do we use to import a module in Haskell?
    1. True or false: Haskell has exactly one kind of loop, a while-loop.
    2. What requirement does Haskell place on its implementations that makes recursion less of a problem than it can be in many other PLs.
  5. What is lazy evaluation?
    1. What is cons?
    2. How do we write cons in Haskell?
    1. What is a predicate?
    2. What does the filter operation do?
    3. True or false. Every map or filter operation can be replaced by a list comprehension.
    4. What does the zip operation do?
    1. What is a Haskell typeclass?
    2. What Haskell function is used to convert a value to a string? What typeclass is used to overload this function?
    3. What Haskell function is used to convert a string to another type of value? What typeclass is used to overload this function?
    1. Explain what a Haskell I/O action is.
    2. When we combine multiple Haskell I/O actions into a single I/O action, what information does the resulting I/O action contain?
    3. What operators can we use to combine I/O actions into a single I/O action?
    4. Haskell has a construct that is more convenient than using these operators. What contruct is this?
    1. What Haskell construct creates an I/O action describing no side effects?
    2. True or false. Execution of the above construct causes an immediate exit of the function it is in.
  6. Haskell has two constructions that allow for binding an identifier to a value inside a do-expression. Explain both of these.
    1. What is the primary Haskell keyword used to create a new type?
    2. What Haskell keyword is used to declare that a type lies in some particular typeclass?
    1. Briefly explain Haskell’s Maybe.
    2. Briefly explain Haskell’s Either.

Answers

  1. Egbert is wrong, because, even if a grammar is not LL(k), we can often convert it to a grammar that is LL(k) that generates the same language and specifies much the same structure. In that case, we can convert the grammar to a usable one and then write our parser based on that grammar instead. (This is exactly what we did in class with the expression grammar that ended up giving us rdparser3.lua.)
    1. In a Predictive Recursive-Descent parser, if we call a parsing function, and it indicates a syntax error, then we must immediately return, flagging a syntax error.
    2. The above is true because to do otherwise would require backtracking, and a Predictive Recursive-Descent parser does not backtrack.
    1. An abstract syntax tree (AST) is a way of representing the structure of input. In an AST, each internal node represents an operation (defined broadly). The node’s subtrees are the ASTs for the entities the operation is applied to.
    2. ASTs are commonly used as the output of programming-language parsers—in particular, the parsers that form the first step in the process of compiling a program.
  2. The 4 actions that a shift-reduce automaton can perform at each step in its execution are as follows.
    • SHIFT. Push the current input symbol onto the stack, along with a specified state, and advance the input to the next symbol.
    • REDUCE. Run a production backward, popping its right-hand side from the stack, and pushing its left-hand side, along with a state obtained from a goto table lookup.
    • ACCEPT. Terminate, indicating a successful parse (input is syntactically correct).
    • ERROR. Terminate, indicating an unsuccessful parse (input contains a syntax error).
    1. Practical lexers and parsers run in linear time.
    2. False. Known practical parsing methods cannot handle all CFLs.
    3. The worst-case runtime for a typical parsing method that can handle all CFLs is cubic time [O(n3)].
  3. A shotgun parser is a parser that emits code directly, rather than producing an AST and allowing another code module to produce code.
    1. Manifest typing is when the type of an entity is explicitly stated in source code.
    2. An extensible type system is one in which programs can define new types.
    3. A sound type system is one that does static typing and guarantees that operations that are incorrect for a type will not be performed.
    4. The problem with the term strong typing is that it has no standard definition; it is used by different people in different ways (and sometimes by the same person in different ways).
    1. Haskell does static typing.
    2. Haskell’s typing is mostly implicit, but type annotations are allowed.
    3. Haskell’s type system is extensible; programs may defined new types.
    4. Variables do have types in Haskell.
    1. False. Haskell is oriented primarily toward declarative programming.
    2. Haskell functions are not introduced by a keyword.
    3. True. Haskell has first-class functions.
    4. True. Haskell allows for lambda functions.
    5. True. By default, Haskell does lazy evaluation.
    6. We can do evaluation that is not lazy by using Haskell’s seq primitive.
    1. True. Ordinary Haskell variables declared inside a function are always local variables.
    2. In Haskell, we import a module using the import keyword.
    1. False. Haskell has no loops at all.
    2. Haskell requires its implementations to do tail-call optimization (TCO).
  4. Lazy evaluation means that an expression is only evaluated when its value is needed.
    1. Cons is an operation that constructs a list from its first item and a list of all its other items.
    2. We write cons in Haskell as a colon (:).
    1. A predicate is a function that returns a Boolean value.
    2. The filter operation takes a predicate and a list. It returns a list of all items in the original list for which the predicate returns true.
    3. True. Every map or filter operation can be replaced by a list comprehension.
    4. The zip operation takes two lists. It returns a single list of pairs, each pair containing one item from each of the original lists.
    1. A Haskell typeclass is a collection of types that all implement some particular interface.
    2. The Haskell function show (lower-case s) converts a value to a string. This function is overloaded using typeclass Show (upper-case S).
    3. The Haskell function read (lower-case r) converts a string to some type of value. This function is overloaded using typeclass Read (upper-case R).
    1. A Haskell I/O action is a representation of some I/O to perform. It holds a description of a sequence of zero or more side effects, along with a wrapped value.
    2. When we combine multiple Haskell I/O actions into a single I/O action, the resulting I/O action contains descriptions of all the side effects from the original I/O actions, along with the value wrapped by the last one.
    3. We use the >> and >>= operators to combine I/O actions into a single I/O action.
    4. Haskell’s do-expression construct is more convenient than using these operators.
    1. Haskells return inside an I/O do-expression creates an I/O action describing no side effects.
    2. False. Execution of a Haskell return does not cause an immediate exit of the function it is in.
  5. Haskell has two constructions that allow for binding an identifier to a value inside a do-expression.
    • <- Binds an identifier to the value wrapped by an I/O action. For example, “line <- getLine” binds the name line to the string wrapped by the I/O action returned by getLine.
    • let ... = Binds an identifier to a non-wrapped value. For example, “let a = b+c” binds the name a to the result of adding b and c.
    1. The primary Haskell keyword used to create a new type is data.
    2. The Haskell keyword instance is used to declare that a type lies in some particular typeclass.
    1. Haskell’s Maybe creates a new type that is much the same as an existing type, but contains one new value. Values in the existing type are marked with Just. The new value is Nothing.
    2. Haskell’s Either creates a new type that can take on values from either of two existing types. Values in the first of the two existing types are marked with Left. Values in the second are marked with Right.