Z OLD Homework 06 - james-bern/CS136 GitHub Wiki

image

README (Before Lab)

Overview

⚠️ Review: Checking Equality

  • ⚠️ For checking equality between String's, do NOT use the ==.
    • Instead, use string.equals("sub").
  • For checking (approximate) equality between double's, do NOT use ==.
    • Instead, use Math.abs(num - 5.0) < 0.0001.

Review: `if-else if-...-else

  • This may be a useful pattern for this lab :)
if (...) {

} else if (...) {

} else if (...) {

} else {

}

PostScript

  • PostScript is a cool programming language.
    • Here is an example of a tiny PostScript program: 2.0 3.0 add
    • Like Python, PostScript is an interpreted language.
      • PostScript programs are NOT compiled.
      • Instead, they are interpreted by another program called an interpreter.
        • In this homework, you will write a simple but very real PostScript interpreter! 🎉 (Some features not included.)

Tokens

  • Our interpreter will interpret a PostScript program one token at a time. A tokens can be...
    • a boolean like true and false
    • a double like 4.0
    • a String, including...
      • built-in functions like def, add, sub, mul, sqrt, eq, exch, dup
      • any String's that is NOT a built-in function is a key
    • a list of tokens like [dup, mul, exch, dup, mul, add, sqrt]
      • (that whole list is one token!)
      • a list is (only) used to define a custom function 🙂👍

Stack

  • the interpreter uses a stack to store tokens we're still working with
    • the add function pops two tokens (which must be doubles) off of the stack, adds them together, and pushes the result onto the stack

      👀 given the teeny tiny PostScript program `2.0 3.0 add`, a working interpreter...
      --- BEGIN INTERPRETING [2.0, 3.0, add] ----------
      
      // sees double 2.0
      
      > 2.0
      
      // pushes 2.0 onto the stack
      
      2.0
      ----- 
      stack
      
      // sees double 3.0
      
      > 3.0
      
      // pushes 3.0 onto the stack
      
      3.0
      2.0
      ----- 
      stack
      
      // sees built-in function `add`
      
      > add
      
      // pops 3.0 off of the stack
      // pops 2.0 off of the stack
      // computes 3.0 + 2.0 -> 5.0
      // pushes 5.0 onto the stack
      
      5.0
      ----- 
      stack
      
      
      --- END INTERPRETING [2.0, 3.0, add] ------------
      

Map

  • the interpreter uses a map (dictionary, table, hash table, hash map) to store key-token pairs
    • the def function will let us define our own constants and custom functions

      • ⚠️ when defining a new constant using def, you prefix the name with a /, like /pi
      • after the constant has been defined, you use it without a slash, like pi
      👀 given the teeny tiny PostScript program `/pi 3.14 def pi`, a working interpreter...
      • sees key /pi (with a slash) --> pushes pi onto the stack
      • sees double 3.14 --> pushes 3.14 onto the stack
      • sees built-in function def --> pops 3.14 off of the stack, pops pi off of the stack, and puts the key-value pair (pi, 3.14) into the map
      • sees key pi (without a slash) --> gets the value corresponding to pi from the map (3.14), pushes 3.14 onto the stack
      --- BEGIN INTERPRETING [/pi, 3.14, def, pi] ----------
      
      
      > /pi
      
      pi
      ----- 
      stack
      
      
      > 3.14
      
      3.14
      pi
      ----- 
      stack
      
      
      > def
      
      ----- 
      stack
       + map {pi=3.14}
      
      
      > pi
      
      3.14
      ----- 
      stack
       + map {pi=3.14}
      
      
      --- END INTERPRETING [/pi, 3.14, def, pi] ------------
      

Longer Examples

👀

basics

  • This program first calculates $5.0 - 2.0$.
  • The program then checks whether the result is (approximately) equal to $3$. It sure is! 🙂
5.0 2.0 sub 3.0 eq
--- BEGIN INTERPRETING [5.0, 2.0, sub, 3.0, eq] ----------


> 5.0

5.0
----- 
stack


> 2.0

2.0
5.0
----- 
stack


> sub

3.0
----- 
stack


> 3.0

3.0
3.0
----- 
stack


> eq

true
----- 
stack


--- END INTERPRETING [5.0, 2.0, sub, 3.0, eq] ------------

defining and using a constant

  • This program first defines a constant $\pi = 3.14$.
  • The program then uses pi to define another constant $\tau = 2\pi$.
/pi 3.14 def /tau pi pi add def
--- BEGIN INTERPRETING [/pi, 3.14, def, /tau, pi, pi, add, def] ----------


> /pi

pi
----- 
stack


> 3.14

3.14
pi
----- 
stack


> def

----- 
stack + map {pi=3.14}


> /tau

tau
----- 
stack + map {pi=3.14}


> pi

3.14
tau
----- 
stack + map {pi=3.14}


> pi

3.14
3.14
tau
----- 
stack + map {pi=3.14}


> add

6.28
tau
----- 
stack + map {pi=3.14}


> def

----- 
stack + map {pi=3.14, tau=6.28}


--- END INTERPRETING [/pi, 3.14, def, /tau, pi, pi, add, def] ------------

defining and using a custom function

  • This program first defines a custom function pythag(a, b) which computes $c = \sqrt{a^2 + b^2}$
  • The program then calls pythag(3.0, 4.0), which returns 5.0. 🙂👍

Hint: Read the sample output carefully!

/pythag { dup mul exch dup mul add sqrt } def 3.0 4.0 pythag
--- BEGIN INTERPRETING [/pythag, [dup, mul, exch, dup, mul, add, sqrt], def, 3.0, 4.0, pythag] ----------


> /pythag

pythag
----- 
stack


> [dup, mul, exch, dup, mul, add, sqrt]

[dup, mul, exch, dup, mul, add, sqrt]
pythag
----- 
stack


> def

----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> 3.0

3.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> 4.0

4.0
3.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> pythag


--- BEGIN INTERPRETING [dup, mul, exch, dup, mul, add, sqrt] ----------


> dup

4.0
4.0
3.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> mul

16.0
3.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> exch

3.0
16.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> dup

3.0
3.0
16.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> mul

9.0
16.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> add

25.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


> sqrt

5.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


--- END INTERPRETING [dup, mul, exch, dup, mul, add, sqrt] ------------


5.0
----- 
stack
 + map {pythag=[dup, mul, exch, dup, mul, add, sqrt]}


--- END INTERPRETING [/pythag, [dup, mul, exch, dup, mul, add, sqrt], def, 3.0, 4.0, pythag] ------------

PostScript Reference (Control + F / ⌘ + F is your friend)

  • 🚨 In the reference, num is double, bool is boolean, any is (some) Token, proc is list.
  • 🚨 The order of arguments in the Documentation can be confusing!
    • Consider sub, which has the following documentation:
      sub
          num1 num2 sub
          num1 - num2
      
      • This means that the teeny tiny PostScript program num1 num2 sub will compute num1 - num2.
      • Let's work out what happens on paper for 1.0 2.0 sub:
        • First, 1.0 gets pushed onto the stack
          > 1.0
          
          1.0
          -----
          stack
          
        • Next, 2.0 gets pushed onto the stack
          > 2.0
          
          2.0
          1.0
          -----
          stack
          
        • Then, sub gets called, pops 2.0 and 1.0, and pushes 1.0 - 2.0 -> -1.0 (🚨 note the minus sign!)
          > sub
          
          -1.0
          -----
          stack
          
      • Note that num2 gets popped before num1 gets popped! Be careful!
// example of how sub might be implemented using our API (see below)

double num2 = stackPopDouble();
double num1 = stackPopDouble();

...

if (string.equals("sub")) {
    stackPush(num1 - num2);
}

Starter Code Documentation

Token

  • Simple class that can represent any type of token.
    • Convenient getters with assert statements.
    • Static method to "tokenize" a PostScript program (turn it into a list of tokens).
class Token {
    int type; // type of Token; can be Token.TYPE_BOOLEAN, Token.TYPE_DOUBLE, Token.TYPE_STRING, Token.TYPE_LIST

    Token(boolean          value); // creates a new boolean type token with given value
    Token(double           value); // creates a new double  type token with given value
    Token(String           value); // creates a new string  type token with given value
    Token(ArrayList<Token> value); // creates a new list    type token with given value

    boolean          getBoolean(); // gets token's value as a  boolean          (crashes if not type boolean) 
    double           getDouble();  // gets token's value as a  double           (crashes if not type double) 
    String           getString();  // gets token's value as a  String           (crashes if not type string)
    ArrayList<Token> getList();    // gets token's value as an ArrayList<Token> (crashes if not type list) 

    static ArrayList<Token> tokenize(String postScriptProgram); // turns PostScript program into a list of tokens
}

Interpreter

  • Class that wraps around the stack and the map.
    • Method to interpret a PostScript program that helpfully prints out the stack and map for you.
    • Method to interpret a single token (the only thing you need to implement).
    • Convenient methods for working with the stack and map with assert statements.
class Interpreter {
    void interpretListOfTokens(ArrayList<Token> tokens); // interprets a list of tokens
    void interpretToken(Token token); // interprets a single token

    Token            stackPopToken();   // pops a Token off of the stack
    boolean          stackPopBoolean(); // pops a Token off of the stack and gets its value as a  boolean           (crashes if not type boolean)
    double           stackPopDouble();  // pops a Token off of the stack and gets its value as a  double            (crashes if not type double)
    String           stackPopString();  // pops a Token off of the stack and gets its value as a  String            (crashes if not type string)
    ArrayList<Token> stackPopList();    // pops a Token off of the stack and gets its value as an ArrayList<Token>  (crashes if not type list)
    void stackPush(Token            token); // pushes a token onto the stack
    void stackPush(boolean          value); // creates a new boolean type token with given value and pushes it onto the stack
    void stackPush(double           value); // creates a new double  type token with given value and pushes it onto the stack
    void stackPush(String           value); // creates a new string  type token with given value and pushes it onto the stack
    void stackPush(ArrayList<Token> value); // creates a new list    type token with given value and pushes it onto the stack
    
    void    mapPut(String key, Token value); // puts a key-value pair into the map
    Token   mapGet(String key); // for the given key, looks up the corresponding value in the map (crashes if key not in map)
    boolean mapContainsKey(String key); // returns whether the map contains a key-value pair with the given key
}

stackPush(...)

  • There are multiple versions of stackPush(...); in general use I recommend using the one that's easiest to read.
// // Two ways of pushing a new double type token with value 1.0 to the stack:

// Option A
stackPush(1.0);

// Option B (totally equivalent, but harder to read)
stackPush(new Token(1.0));

Special-purpose vs. General-purpose stackPop*() functions

  • If you know the token you're about to pop must be, e.g., type double (e.g., when handling the sub command), then you should call stackPopDouble()
    • stackPopDouble() is a helper function that calls stackPopToken().getDouble()
double num = stackPopDouble();
  • However, if you don't know what type the token you're about to pop is, then you will have to use stackPopToken() and, depending on the situation, use something like:
Token any = stackPopToken();
if (any.type == Token.TYPE_BOOLEAN) {
    boolean bool = any.getBoolean();
    ...
} else if (any.type == Token.TYPE_DOUBLE) {
    double num = any.getDouble();
    ...
} else if (any.type == Token.TYPE_STRING) {
    String string = any.getString();
    ...
} else if (any.type == Token.TYPE_LIST) {
    ArrayList<Token> list = any.getList();
} ...

Specification

  • To get a B:

    • ✅ Implement an interpretToken(Token token); that can successfully run the first two example programs.
      • ✅ Check your output against the example programs described above in this writeup.
      • 🚨 Start simple! 5.0 is a great program! All it should do is push 5.0 onto the stack.
      • 🚨 Get the first example program working first!
      • 🚨 Only implement what you need!
        • ✨ The first example uses these built-in functions: sub and eq
        • ✨ The second example uses these built-in functions: add, def
          • ✨ See the reading for how to use def (/myKeyName ... def, and myKeyName)
  • To get an A:

    • ✅ Implement an interpretToken(Token token); that can successfully run all three example programs.
      • ✨ The third example uses these built-in functions: dup, mul, exch, add, sqrt, def
  • To get an S:

    • ✅ Implement everything above.
    • ✅ Write a Postscript program that...
      1. Defines a custom function is_prime using def (you may implement and use any standard PostScript commands from the reference, e.g., if, mod).
        is_prime
            num is_prime
            returns boolean for whether num is prime
        
      2. Calls is_prime on 2, 3, 33, and 37
        • Or, if you're feeling really ambitious, uses some sort of PostScript for loop or something to call is_prime on the first 100 numbers
    • ✅ Implement an interpretToken(Token token); that can successfully run the PostScript program you wrote :D
      • ✅ You may also have to extend/modify tokenize(...). 🙂👍 I recommend using a Stack<ArrayList<Token>> stack;

Submission Instructions

  • Self-grade your assignment according to the rubric and put your grade in a comment at the top of your code file (if you scored lower than a B, just put C).
    • 🚨 You must do this in order to receive points.
  • Upload your code file to GradeScope by the due date.
  • Go over your code with Jim or a Grading TA in Lab.
    • If you are trying for an S, you must demo to Jim.

Starter Code

// TODO: Make sure you read the Starter Code Documentation first!
// TODO: Before changing/adding anything, Compile and Run this Starter Code.

import java.util.*;

class HW06 {
    static class Token {
        int type;
        private boolean          _valueIfTypeBoolean;
        private double           _valueIfTypeDouble;
        private String           _valueIfTypeString;
        private ArrayList<Token> _valueIfTypeList;

        static final int TYPE_BOOLEAN = 0;
        static final int TYPE_DOUBLE  = 1;
        static final int TYPE_STRING  = 2;
        static final int TYPE_LIST    = 3;
        static final String[] _typeNames = { "boolean", "double", "String", "list" };
        static String stringFromType(int type) { return _typeNames[type]; }

        Token(boolean          value) { type = TYPE_BOOLEAN; _valueIfTypeBoolean = value; } 
        Token(double           value) { type = TYPE_DOUBLE;  _valueIfTypeDouble  = value; }
        Token(String           value) { type = TYPE_STRING;  _valueIfTypeString  = value; }
        Token(ArrayList<Token> value) { type = TYPE_LIST;    _valueIfTypeList    = value; }

        public String toString() {
            if (type == TYPE_BOOLEAN) { return "" + _valueIfTypeBoolean; }
            if (type == TYPE_DOUBLE ) { return "" + _valueIfTypeDouble;  }
            if (type == TYPE_STRING ) { return "" + _valueIfTypeString;  }
            if (type == TYPE_LIST   ) { return "" + _valueIfTypeList;  }
            assert false : "Token type not recognized; Token type = " + type;
            return "";
        }

        boolean          getBoolean() { assert type == TYPE_BOOLEAN : "Token type is " + stringFromType(type); return _valueIfTypeBoolean; }
        double           getDouble()  { assert type == TYPE_DOUBLE  : "Token type is " + stringFromType(type); return _valueIfTypeDouble;  }
        String           getString()  { assert type == TYPE_STRING  : "Token type is " + stringFromType(type); return _valueIfTypeString;  }
        ArrayList<Token> getList()    { assert type == TYPE_LIST    : "Token type is " + stringFromType(type); return _valueIfTypeList;    }

        static ArrayList<Token> tokenize(String postScriptProgram) {
            ArrayList<Token> result = new ArrayList<>();
            ArrayList<Token> list = null;
            ArrayList<Token> target = result;
            Scanner scanner = new Scanner(postScriptProgram);
            scanner.useLocale(Locale.US);
            while (scanner.hasNext()) {
                if (scanner.hasNextDouble()) {
                    target.add(new Token(scanner.nextDouble()));
                } else if (scanner.hasNextBoolean()) {
                    target.add(new Token(scanner.nextBoolean()));
                } else {
                    String string = scanner.next().trim();
                    if (string.equals("{")) {
                        list = new ArrayList<Token>();
                        target = list;
                    } else if (string.equals("}")) {
                        target = result;
                        target.add(new Token(list));
                    } else if (string.length() != 0) {
                        target.add(new Token(string));
                    }
                }
            }
            return result;
        }
    }

    static class Interpreter {
        Stack<Token> _stack;
        HashMap<String, Token> _map;
        
        Interpreter() {
            _stack = new Stack<>();
            _map = new HashMap<>();
        }

        Token            stackPopToken()   { assert _stack.size() > 0 : "can't pop; stack is empty"; return _stack.pop(); }
        boolean          stackPopBoolean() { Token token = stackPopToken(); return token.getBoolean(); }
        double           stackPopDouble()  { Token token = stackPopToken(); return token.getDouble();  }
        String           stackPopString()  { Token token = stackPopToken(); return token.getString();  }
        ArrayList<Token> stackPopList()    { Token token = stackPopToken(); return token.getList();  }

        void stackPush(Token            token) { _stack.push(token);            }
        void stackPush(boolean          value) { _stack.push(new Token(value)); }
        void stackPush(double           value) { _stack.push(new Token(value)); }
        void stackPush(String           value) { _stack.push(new Token(value)); }
        void stackPush(ArrayList<Token> value) { _stack.push(new Token(value)); }
        void stackPrint() {
            ArrayList<Token> tmp = new ArrayList<>();
            for (Token token : _stack) { tmp.add(token); }
            for (int i = tmp.size() - 1; i >= 0; --i) { System.out.println(tmp.get(i)); }
        }
        
        boolean mapContainsKey(String key) { return _map.containsKey(key); }
        void mapPut(String key, Token value) { _map.put(key, value); }
        Token mapGet(String key) { assert mapContainsKey(key); return _map.get(key); }
        void mapPrint() { System.out.println(_map); }

        void interpretListOfTokens(ArrayList<Token> listOfTokens) {
            boolean PRINT_TOKEN = true;
            boolean PRINT_STACK_AND_MAP = true; // NOTE: only prints map if nonempty
            boolean PRINT_BEGIN_AND_END_INTERPRETING = true;
            if (PRINT_BEGIN_AND_END_INTERPRETING) { System.out.println("\n- BEGIN INTERPRETING " + listOfTokens + " -\n\n"); }
            for (int tokenIndex = 0; tokenIndex < listOfTokens.size(); ++tokenIndex) {
                if (PRINT_TOKEN) { System.out.println("> " + listOfTokens.get(tokenIndex) + "\n"); }
                interpretToken(listOfTokens.get(tokenIndex));
                if (PRINT_STACK_AND_MAP) { stackPrint(); System.out.println("----- \nstack"); if (_map.size() > 0) { System.out.print(" + map "); mapPrint(); } System.out.println("\n"); }
            }
            if (PRINT_BEGIN_AND_END_INTERPRETING) { System.out.println("-- END INTERPRETING " + listOfTokens + " --\n\n"); }
        }

        void interpretToken(Token token) {
            // TODO: Implement this function (if-else if is your friend!)
            // NOTE: Resist the urge to write "helper functions." First make sure you actually need them!
            // NOTE: You can get an A with ~50 lines of code.
            //       (But feel free to use more. Just not like...way more.)
            // NOTE: Do NOT make a new instance of the Interpreter class.

        }
    }

    public static void main(String[] arguments) {
        // Definitely write your own teeny tiny PostScript programs to help you develop!
        // A good program to start with is just "5.0"
        // Implement one feature at a time!

        String program = "5.0 2.0 sub 3.0 eq";
        // String program = "/pi 3.14 def /tau pi pi add def";
        // String program = "/pythag { dup mul exch dup mul add sqrt } def 3.0 4.0 pythag";

        ArrayList<Token> listOfTokens = Token.tokenize(program);
        
        Interpreter interpreter = new Interpreter();
        interpreter.interpretListOfTokens(listOfTokens);
    }
}
⚠️ **GitHub.com Fallback** ⚠️