Download Advanced Programming Concepts: OCaml, Context-Free Grammars, and Parsing and more Quizzes Programming Languages in PDF only on Docsity! CMSC 330, Spring 2009, Quiz 3 Practice Problems 1. OCaml Polymorphic Types Consider a OCaml module Bst that implements a binary search tree: module Bst = struct type bst = Empty | Node of int * bst * bst let empty = Empty (* empty binary search tree *) let is_empty = function (* return true for empty bst *) Empty -> true | Node (_, _, _) -> false let rec insert n = function (* insert n into binary search tree *) Empty -> Node (n, Empty, Empty) | Node (m, left, right) -> if m = n then Node (m, left, right) else if n < m then Node(m, (insert n left), right) else Node(m, left, (insert n right)) (* Implement the following functions val min : bst -> int val remove : int -> bst -> bst val fold : ('a -> int -> 'a) -> 'a -> bst -> 'a val size : bst -> int *) let rec min = (* return smallest value in bst *) let rec remove n t = (* tree with n removed *) let rec fold f a t = (* apply f to nodes of t in inorder *) let size t = (* # of non-empty nodes in t *) end a. Is insert tail recursive? Explain why or why not. b. Implement min as a tail-recursive function. Raise an exception for an empty bst. Any reasonable exception is fine. c. Implement remove. The result should still be a binary search tree. d. Implement fold as an inorder traversal of the tree so that the code List.rev (fold (fun a m -> m::a) [] t) will produce an (ordered) list of values in the binary search tree. e. Implement size using fold. 2. Recursive Descent Parser in OCaml The example OCaml recursive descent parser 15-parseArith_fact.ml employs a number of shortcuts. For instance, the function parseS handles the grammar rules for S T + S | T directly instead of first applying left factoring: S T A A + S | epsilon However, we can still identify where code corresponding to parseA was inserted directly in the code for parseS, in the comments below: let rec parseS lr = (* parseS *) let x = parseT lr in (* S T A *) match !lr with (* parseA *) | ('+'::t) -> (* if lookahead = First( + S ) *) lr := t; (* A + S *) Sum (x,parseS lr) | _ -> x (* A epsilon *) Similarly, the function parseF handles the grammar rules for F -> U ! | U directly instead of rewriting the grammar, creating the following productions: F -> ? B -> ? You must identify where code corresponding to parseB was inserted directly in the code for parseF in the comments below: let rec parseF lr = (* parseF *) let rec fHelper lr tmp = match !lr with (* parseB *) | ('!'::t) -> (* 1: if lookahead = First( ? ) *) lr := t; (* 2: ? ? *) Fact (fHelper lr tmp) | _ -> tmp (* 3: ? ? *) in let x = parseU lr in (fHelper lr x) (* 4: ? ? *) a. What rule should have been applied to the productions for F? b. What productions for F & B would be created by applying the rule? c. What sentential form should appear in place of ? in comment 1? d. What production should appear in place of ? in comment 2? e. What production should appear in place of ? in comment 3? f. What production should appear in place of ? in comment 4? 3. Context Free Grammars a. List the 4 components of a context free grammar. b. Describe the relationship between terminals, non-terminals, and productions. c. Define ambiguity. d. Describe the difference between scanning & parsing. 4. Describing Grammars a. Describe the language accepted by the following grammar: S abS | a