In the Notes on Programming Language Syntax page, an example parser for a simple language is given, using C syntax....

profilebebitaclho200f
  1. In the Notes on Programming Language Syntax page, an example parser for a simple language is given, using C syntax. Write the parser using F#, but you may only use functional programming and immutable date.  Create the list of tokens as a discriminated union, which (in the simplest case) looks like an enumeration.          
  2. type TERMINAL = IF|THEN|ELSE|BEGIN|END|PRINT|SEMICOLON|ID|EOF
  3. With this type declared, you can use the terminals like you would use enumerated values in Java.
  4. Use immutable data. The C-code example uses mutable data. Pass the program into the start symbol function. Pass the input yet to be processed to each non-terminal function.
  5. The main function might look like this:
        let test_program program =
          let result = program |> S
          match result with 
          | [] -> failwith "Early termination or missing EOF"
          | x::xs -> if x = EOF then accept() else error()

    You do not have to parse input strings. Assume that the parsing has been done. Pass a list of tokens that represent a program into the start symbol. Try these program examples:

    [IF;ID;THEN;BEGIN;PRINT;ID;SEMICOLON;PRINT;ID;END;ELSE;PRINT;ID;EOF]
    	      
    [IF;ID;THEN;IF;ID;THEN;PRINT;ID;ELSE;PRINT;ID;ELSE;BEGIN;PRINT;ID;END;EOF]
          
    Causes error: 
    [IF;ID;THEN;BEGIN;PRINT;ID;SEMICOLON;PRINT;ID;SEMICOLON;END;ELSE;PRINT;ID;EOF]

    Print an accept message when the input is valid and completely consumed. Generate appropriate error messages for incorrect symbols, not enough input, and too much input.

  6. Once you have the parser recognizing input, generate a parse tree using a discriminated type.
  7. Implement a parser using functional programming and immutable data for the unambiguous grammar for arithmetic expressions, from the Notes on Programming Language Syntax.
        E -> E + T | E - T | T
        T -> T * F | T / F | F
        F -> i | (E)

    Use the suggestion in the notes to get around the fact that this grammar appears to need more than one lookahead token.

  8. Once you have the parser recognizing input, generate a parse tree using a discriminated type.
  9. Recall that an F# function that takes two arguments can be coded in either uncurried form (in which case it takes a pair as its input) or curried form (in which case it takes the first argument and returns a function that takes the second argument). In fact it is easy to convert from one form to the other in F#. To this end, define an F# function curry f that converts an uncurried function to a curried function, and an F# function uncurry f that does the opposite conversion. For example,

      > (+);;
        val it : (int -> int -> int) = <fun:it@13-7>
        > let plus = uncurry (+);;
        val plus : (int * int -> int)
        > plus (2,3);;
        val it : int = 5
        > let cplus = curry plus;;
        val cplus : (int -> int -> int)
        > let plus3 = cplus 3;;
        val plus3 : (int -> int)
        > plus3 10;;
        val it : int = 13

    What are the types of curry and uncurry?

  • Given vectors u = (u1, u2,..., un) and v = (v1, v2,..., vn), the inner product of u and v is defined to be u1*v1 + u2*v2 + ... + u n*vn. Write a curried F# function inner that takes two vectors represented as int list and returns their inner product.
  • Throw an exception if the lists do not have the same length.
  • Do not use any built-in or borrowed functions. Write it from scratch.
  • Use tail recursion.
  1. > inner [1;2;3] [4;5;6];; val it : int = 32
  2. Given an m-by-n matrix A and an n-by-p matrix B, the product of A and B is an m-by-p matrix whose entry in position (i,j) is the inner product of row i of A with column j of B. For example,
                  / 0 1 \
    / 1 2 3 \  *  | 3 2 |  =  /  9 11 \
    \ 4 5 6 /     \ 1 2 /     \ 21 26 /

    Write an uncurried F# function to do matrix multiplication:

      > multiply ([[1;2;3];[4;5;6]], [[0;1];[3;2];[1;2]]);;
        val it : int list list = [[9; 11]; [21; 26]]

    Assume that the dimensions of the matrices are appropriate.

    Hint: Use transpose (from Homework 1), inner, and List.map.

  3. Evaluate the asymptotic time complexity of this function:
    let rec oddeven = function
    | [] -> []
    | x::xs -> if x % 2 = 0 
               then oddeven xs @ [x]
               else x :: oddeven xs
  4. Two powerful List functions provided by F# are List.fold and List.foldBack. These are similar to List.reduce and List.reduceBack, but more general. Both take a binary function f, an initial value i, and a list [x1;x2;x3;...;xn]. Then List.fold returns
      (f ... (f (f (f i x1) x2) x3) ... xn)
    
    while List.foldBack returns
      (f x1 (f x2 (f x3 ... (f xn i) ... )))
    
    In spite of this complicated behavior, they can be implemented very simply:
      > let rec fold f a = function
        | []    -> a
        | x::xs -> fold f (f a x) xs;;
    
      val fold : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
    
      > let rec foldBack f xs a =
          match xs with
          | []    -> a
          | y::ys -> f y (foldBack f ys a);;
    
      val foldBack : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b
    
    (Note that they don't take their arguments in the same order.)

    Each of these functions can be used to implement flatten, which "flattens" a list of lists:

      let flatten1 xs = List.fold (@) [] xs
    
      let flatten2 xs = List.foldBack (@) xs []
    

    For example,

      > flatten1 [[1;2];[];[3];[4;5;6]];;
      val it : int list = [1; 2; 3; 4; 5; 6]
    

    Compare the efficiency of flatten1 xs and flatten2 xs, both in terms of asymptotic time compexity and experimentally. To make the analysis simpler, assume that xs is a list of the form [[1];[2];[3];...;[n]].

  5. Interpreter 0 In this problem, we begin our exploration of the use of F# for language-oriented programming. You will write an F# program to evaluate arithmetic expressions written in the language given by the following context-free grammar:
      E -> n | -E | E + E | E - E | E * E | E / E | (E)
    
    In the above, n is an integer literal, -E is the negation of E, the next four terms are the sum, difference, product, and quotient of expressions, and (E) is used to control the order of evaluation of expressions, as in the expression 3*(5-1).

    Rather than working directly with the concrete syntax above, we will imagine that we have a parser that parses input into an abstract syntax tree, as is standard in real compilers. Hence your interpreter will take an input of the following discriminated union type:

      type Exp =
        Num of int
      | Neg of Exp
      | Sum of Exp * Exp
      | Diff of Exp * Exp
      | Prod of Exp * Exp
      | Quot of Exp * Exp
    

    Note how this definition mirrors the grammar given above. For instance, the constructor Num makes an integer into an Exp, and the constructor Sum makes a pair of Exp's into an Exprepresenting their sum. Interpreting abstract syntax trees is much easier than trying to interpret concrete syntax directly. Note that there is no need for a constructor corresponding to parentheses, as the example given above would simply be represented by

      Prod(Num 3, Diff(Num 5, Num 1))
    

    which represents the parse tree which looks like

    Your job is to write an F# function evaluatethat takes an abstract syntax tree and returns the result of evaluating it. Most of the time, evaluating a tree will produce an integer, but we must address the possibility of dividing by zero. This could be handled by raising an exception, but instead we choose to make use of the built-in F# type

      type 'a option = None | Some of 'a
    

    Thus evaluate will have type Exp -> int option, allowing it to return Some m in the case of a successful evaluation, and Nonein the case of an evaluation that fails due to dividing by zero. For example,

      > evaluate (Prod(Num 3, Diff(Num 5, Num 1)));;
      val it : int option = Some 12
      > evaluate (Diff(Num 3, Quot(Num 5, Prod(Num 7, Num 0))));;
      val it : int option = None
    

    Naturally, evaluate e should use recursion to evaluate each of e's sub-expressions; it should also use match to distinguish between the cases of successful or failed sub-evaluations. To get you started, here is the beginning of the definition of evaluate:

      let rec evaluate = function
      | Num n -> Some n
      | Neg e -> match evaluate e with
           | ...
    
  6. Record
  7. Create a record type for Name, Credits and GPA.
  8. Create a record instance with the values "Jones", 109, 3.85.
  9. Discriminated Union
  10. Create a discriminated union for Coordinates that can be a Tuple, Threeple or Fourple that represent tuples of size two, three and four. The type for the union should be polymorphic.
  11. Instantiate a Tuple of integers, a Threeple of floats and a Fourple of strings.
  12. Create a function that has a parameter of a binary function and Coordinate. Apply the function to the Coordinate like List.reduce. Call the function with (+) for each of the Coordinates in part (b).
  13. Binary Tree
    1. Write a function that searches for a value in a binary tree and then removes that node. Be sure to handle all of these cases:
    • The value is not in the tree.
    • Both children are Lf.
    • One child node is a Br the other is a Lf.
    • Both children are Br nodes.
  14. Draw a memory diagram with before and after views for the case where both children are Br nodes.

 

    • 7 years ago
    • 20
    Answer(0)
    Bids(0)