HW05 - james-bern/CS136 GitHub Wiki

image

README

Background

PostScript is an interpreted language (like Python). 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! 🎉

Java

Checking Equality

  • To check equality between String's, do NOT use the stringA == stringB.
    • Instead, use stringA.equals(stringB).
  • To check (approximate) equality between double's, do NOT use numA == numB.
    • Instead, use Cow's ARE_EQUAL(numA, numB)

if-else if-...-else

if (...) {
    ...
} else if (...) {
    ...
} else if (...) {
    ...
} else {
    ...
}

PostScript Overview

Tokens

  • Our interpreter will interpret a PostScript program one token at a time.
  • A Token is be one of these four types:
    • a boolean like true and false
    • a double like 4.0
    • a String
      • a built-in function like def, add, sub, mul, sqrt, eq, exch, dup
      • a key (any String's that is NOT a built-in function)
    • a list of tokens like [dup, mul, exch, dup, mul, add, sqrt]

Stack

  • The interpreter uses a stack to store tokens we're still working with

Map

  • The interpreter uses a map (dictionary, table, hash table, hash map) to store key-token pairs

Built-In Functions

  • The add function pops two tokens (which must be doubles) off of the stack, adds them together, and pushes the result onto the stack
  • The def function will let us define our own constants and custom functions
    • The syntax of def is /key value def
      • 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
    • HINT: Check the examples below to make sure you completely understand the syntax
  • The definitions of other built-in functions can be found in this Reference
    • To find text on a webpage use COMMAND + F (or CONTROL + F on Windows)
    • HINT: Check the examples below to make sure you completely understand the order of arguments for sub

PostScript Example Programs

5.0

This extremely simple PostScript program is just one token (the double-type token 5.0)

  • The interpreter process it by pushing 5.0 to the stack
--- BEGIN INTERPRETING [5.0] ----------

> 5.0 // This is the current token being processed

5.0
----- 
stack // This is the state of the PostScript interpreter's stack (and map if relevant) after processing the token

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

This is a slightly more complicated PostScript program

  • The interpreter pushes the first token 5.0 to the stack
  • The interpreter pushes the second token 2.0 to the stack
  • The interpreter processes the String-type token sub
    • It pops 2.0 from the stack
    • It pops 5.0 from the stack
    • It pushes 5.0 - 2.0 -> 3.0 to the stack
  • The interpreter pushes the next token 3.0 to the stack
  • The interpreter processes the String-type token eq
    • It pops 3.0 from the stack
    • It pops 3.0 from the stack
    • It pushes boolean-type token ARE_EQUAL(3.0, 3.0) -> true to the stack
--- BEGIN INTERPRETING [5.0, 2.0, sub, 3.0, eq] ----------

> 5.0 // First, the interpreter pushes the double-type token 5.0 onto the stack

5.0
----- 
stack

> 2.0 // Next, the interpreter pushes the double-type token 2.0 onto the stack

2.0
5.0
----- 
stack

> sub // Next, the interpreter processes the String-type token "sub" (pop, pop, push 5.0 - 2.0)

3.0
----- 
stack

> 3.0 // ...

3.0
3.0
----- 
stack

> eq

true
----- 
stack

--- END INTERPRETING [5.0, 2.0, sub, 3.0, eq] ------------
/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] ------------
/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] ------------

API

Token

  • The Token class represents a PostScript token
  • This class is a "sum type"
    • Token can represent any different kind of tokens (boolean-type, double-type, String-type, or List-type)
class Token {
    // // The type of the Token; can be:
    // Token.TYPE_BOOLEAN
    // Token.TYPE_DOUBLE
    // Token.TYPE_STRING
    // Token.TYPE_LIST
    int type;

    // // Constructors
    // NOTE: You call these like: Token token = new Token(3.0);
    Token(boolean value); // create new boolean-type Token with given value
    Token(double value); // create new double-type Token with given value
    Token(String value); // create new String-type Token with given value
    Token(ArrayList<Token> value); // create new List-type Token with given value

    // // Getters
    // NOTE: You call these like: token.getBoolean()
    // NOTE: These will crash your program if Token is not actually the corresponding type
    boolean getBoolean(); // get value of a boolean-type Token
    double getDouble(); // get value of double-type Token
    String getString(); // get value of String-type Token
    ArrayList<Token> getList(); // get value of List-type Token
}

Stack

  • The stack is a specialized version of Stack<Token>
  • It has specialized functions for pushing and popping if you know a Token's type
static class HW05_Stack {
    // // Core functions
    int size(); // number of Tokens in the stack
    void push(Token token); // push a Token to the stack
    Token pop(); // pop a Token from the stack

    // // Special push functions
    void pushBoolean(boolean value); // push(new Token(value));
    void pushDouble(double value); // push(new Token(value));
    void pushString(String value); // push(new Token(value));
    void pushList(ArrayList<Token> value); // push(new Token(value));

    // // Special pop functions
    boolean popBoolean(); // return pop().getBoolean()
    double popDouble(); // return pop().getDouble()
    String popString(); // return pop().getString()
    ArrayList<Token> popList(); // return pop().getList()
}

Map

// A Map stores key-value pairs; similar to a Python dictionary 
class Map<String, Token> {
    // Put key-value pair into the map
    void put(String key, Token value);

    // Get value corresponding to key;
    // NOTE: this crashes the program if key not in map
    Token get(String key);

    // Returns whether map contains a key-value pair with key
    boolean containsKey(String key);
}

Top-Level Functions

static void interpretListOfTokens(ArrayList<Token> tokens, HW05_Stack stack, HashMap<String, Token> map);
static void processSingleToken(Token token, HW05_Stack stack, HashMap<String, Token> map);
static ArrayList<Token> tokenizePostScriptProgram(String program);

Usage

Pushing

  • If you know you're about to push a double-type Token with value num, you have three equivalent options (Option A is probably the easiest to read):
// Option A
stack.pushDouble(num);

// Option B
stack.push(new Token(num));

// Option C
Token token = new Token(num);
stack.push(token);

Popping

  • If you know you're about to pop a double-type Token, you have three equivalent options (Option A is probably the easiest to read):
// Option A
double num = stack.popDouble();

// Option B
double num = stack.pop().getDouble();

// Option C
Token token = stack.pop();
double num = token.getDouble();
  • If you don't know what type of Token you're popping, you can always do something like this:
Token token = stack.pop();
if (token.type == Token.TYPE_BOOLEAN) {
    boolean bool = token.getBoolean();
    ...
} else if (token.type == Token.TYPE_DOUBLE) {
    double num = token.getDouble();
    ...
} else if (token.type == Token.TYPE_STRING) {
    String string = token.getString();
    ...
} else if (token.type == Token.TYPE_LIST) {
    ArrayList<Token> list = token.getList();
}

TODO's

  • A-

    • Make processSingleToken(...) work for the first three example programs
    • HINT: The Docs now include useful String functions 🙂👍
  • A

    • Make processSingleToken(...) work for all four example programs
    • HINT: This doesn't take very much code, but is a little tricky :)
  • A+

    • Write a Postscript program that...
      • Defines a custom function is_prime using def
        • *NOTE: you may implement and use any standard PostScript commands from the Reference, e.g., if, mod).
      • 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)
    • Make processSingleToken(...) work for the PostScript program you wrote :D
    • NOTE: You will likely need to also extend/modify tokenizePostScriptProgram(...) 🙂👍 I recommend using a Stack<ArrayList<Token>> stack;
    is_prime
    num is_prime
    returns boolean for whether num is prime

Starter Code

import java.util.*;
class HW05 extends Cow {

	static class Token {
		int type;

		Token(Token token) { // copy constructor
			this.type = token.type;
			_boolean = token._boolean;
			_double = token._double;
			_String = token._String;
			_List = token._List;
		}
		Token(boolean value) { type = Token.TYPE_BOOLEAN; _boolean = value; } 
		Token(double value) { type = Token.TYPE_DOUBLE; _double  = value; }
		Token(String value) { type = Token.TYPE_STRING; _String  = value; }
		Token(ArrayList<Token> value) { type = Token.TYPE_LIST; _List = value; }

		boolean getBoolean() { ASSERT(type == Token.TYPE_BOOLEAN); return _boolean; }
		double getDouble() { ASSERT(type == Token.TYPE_DOUBLE); return _double; }
		String getString() { ASSERT(type == Token.TYPE_STRING); return _String; }
		ArrayList<Token> getList() { ASSERT(type == Token.TYPE_LIST); return _List; }

		// --

		private boolean _boolean;
		private double _double;
		private String _String;
		private ArrayList<Token> _List;

		static final int TYPE_NONE = 0;
		static final int TYPE_BOOLEAN = 1;
		static final int TYPE_DOUBLE = 2;
		static final int TYPE_STRING = 3;
		static final int TYPE_LIST = 4;


		public String toString() {
			if (type == Token.TYPE_BOOLEAN) { return "" + _boolean; }
			if (type == Token.TYPE_DOUBLE ) { return "" + _double;  }
			if (type == Token.TYPE_STRING ) { return "" + _String;  }
			if (type == Token.TYPE_LIST   ) { return "" + _List;  }
			ASSERT(false);
			return "";
		}
	}

	static ArrayList<Token> tokenizePostScriptProgram(String program) {
		ArrayList<Token> result = new ArrayList<>();
		ArrayList<Token> list = null;
		ArrayList<Token> target = result;
		Scanner scanner = new Scanner(program);
		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));
				}
			}
		}
		scanner.close();
		return result;
	}

	static class HW05_Stack {
		Token pop() { return _stack.pop(); }
		boolean popBoolean() { return _stack.pop().getBoolean(); }
		double popDouble() { return _stack.pop().getDouble(); }
		String popString() { return _stack.pop().getString(); }
		ArrayList<Token> PopList() { return _stack.pop().getList(); }

		void push(Token token) { _stack.push(new Token(token)); }
		void pushBoolean(boolean value) { _stack.push(new Token(value)); }
		void pushDouble(double value) { _stack.push(new Token(value)); }
		void pushString(String value) { _stack.push(new Token(value)); }
		void pushList(ArrayList<Token> value) { _stack.push(new Token(value)); }

		int size() { return _stack.size(); }

		// ---

		private Stack<Token> _stack;

		HW05_Stack() {
			_stack = new Stack<>();
		}

		public String toString() {
			StringBuilder stringBuilder = new StringBuilder();
			ArrayList<Token> tmp = new ArrayList<>();
			for (Token token : _stack) { tmp.add(token); }
			for (int i = tmp.size() - 1; i >= 0; --i) {
				stringBuilder.append(tmp.get(i).toString());
				if (i != 0) stringBuilder.append('\n');
			}
			return stringBuilder.toString();
		}
	}

	static void interpretListOfTokens(ArrayList<Token> tokens, HW05_Stack stack, HashMap<String, Token> map) {
		boolean PRINT_TOKEN = true;
		boolean PRINT_STACK_AND_MAP = true;
		boolean PRINT_BEGIN_AND_END_INTERPRETING = true;


		if (PRINT_BEGIN_AND_END_INTERPRETING) {
			System.out.println("--- BEGIN INTERPRETING " + tokens + " ----------\n");
		}

		for (Token token : tokens) {
			if (PRINT_TOKEN) {
				System.out.println("> " + token + "\n");
			}

			processSingleToken(token, stack, map);

			if (PRINT_STACK_AND_MAP) {
				// NOTE: only prints map if nonempty
				if (stack.size() > 0) PRINT(stack);
				System.out.println("----- \nstack");
				if (map.size() > 0) {
					System.out.print("+ map ");
					PRINT(map);
				}
				System.out.println();
			}
		}

		if (PRINT_BEGIN_AND_END_INTERPRETING) {
			System.out.println("--- END INTERPRETING " + tokens + " ------------\n");
		}
	}

	static void processSingleToken(Token token, HW05_Stack stack, HashMap<String, Token> map) {

		// TODO

	}

	public static void main(String[] arguments) {
		String program = "5.0";
		// 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> tokens = tokenizePostScriptProgram(program);
		HW05_Stack stack = new HW05_Stack(); 
		HashMap<String, Token> map = new HashMap<>();
		interpretListOfTokens(tokens, stack, map);
	}

}

Solution

👀
import java.util.*;
class HW05A extends Cow {

	static class Token {
		int type;

		Token(Token token) { // copy constructor
			this.type = token.type;
			_boolean = token._boolean;
			_double = token._double;
			_String = token._String;
			_List = token._List;
		}
		Token(boolean value) { type = Token.TYPE_BOOLEAN; _boolean = value; } 
		Token(double value) { type = Token.TYPE_DOUBLE; _double  = value; }
		Token(String value) { type = Token.TYPE_STRING; _String  = value; }
		Token(ArrayList<Token> value) { type = Token.TYPE_LIST; _List = value; }

		boolean getBoolean() { ASSERT(type == Token.TYPE_BOOLEAN); return _boolean; }
		double getDouble() { ASSERT(type == Token.TYPE_DOUBLE); return _double; }
		String getString() { ASSERT(type == Token.TYPE_STRING); return _String; }
		ArrayList<Token> getList() { ASSERT(type == Token.TYPE_LIST); return _List; }

		// --

		private boolean _boolean;
		private double _double;
		private String _String;
		private ArrayList<Token> _List;

		static final int TYPE_NONE = 0;
		static final int TYPE_BOOLEAN = 1;
		static final int TYPE_DOUBLE = 2;
		static final int TYPE_STRING = 3;
		static final int TYPE_LIST = 4;


		public String toString() {
			if (type == Token.TYPE_BOOLEAN) { return "" + _boolean; }
			if (type == Token.TYPE_DOUBLE ) { return "" + _double;  }
			if (type == Token.TYPE_STRING ) { return "" + _String;  }
			if (type == Token.TYPE_LIST   ) { return "" + _List;  }
			ASSERT(false);
			return "";
		}
	}

	static ArrayList<Token> tokenizePostScriptProgram(String program) {
		ArrayList<Token> result = new ArrayList<>();
		ArrayList<Token> list = null;
		ArrayList<Token> target = result;
		Scanner scanner = new Scanner(program);
		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));
				}
			}
		}
		scanner.close();
		return result;
	}

	static class HW05_Stack {
		Token pop() { return _stack.pop(); }
		boolean popBoolean() { return _stack.pop().getBoolean(); }
		double popDouble() { return _stack.pop().getDouble(); }
		String popString() { return _stack.pop().getString(); }
		ArrayList<Token> PopList() { return _stack.pop().getList(); }

		void push(Token token) { _stack.push(new Token(token)); }
		void pushBoolean(boolean value) { _stack.push(new Token(value)); }
		void pushDouble(double value) { _stack.push(new Token(value)); }
		void pushString(String value) { _stack.push(new Token(value)); }
		void pushList(ArrayList<Token> value) { _stack.push(new Token(value)); }

		int size() { return _stack.size(); }

		// ---

		private Stack<Token> _stack;

		HW05_Stack() {
			_stack = new Stack<>();
		}

		public String toString() {
			StringBuilder stringBuilder = new StringBuilder();
			ArrayList<Token> tmp = new ArrayList<>();
			for (Token token : _stack) { tmp.add(token); }
			for (int i = tmp.size() - 1; i >= 0; --i) {
				stringBuilder.append(tmp.get(i).toString());
				if (i != 0) stringBuilder.append('\n');
			}
			return stringBuilder.toString();
		}
	}

	static void interpretListOfTokens(ArrayList<Token> tokens, HW05_Stack stack, HashMap<String, Token> map) {
		boolean PRINT_TOKEN = true;
		boolean PRINT_STACK_AND_MAP = true;
		boolean PRINT_BEGIN_AND_END_INTERPRETING = true;


		if (PRINT_BEGIN_AND_END_INTERPRETING) {
			System.out.println("--- BEGIN INTERPRETING " + tokens + " ----------\n");
		}

		for (Token token : tokens) {
			if (PRINT_TOKEN) {
				System.out.println("> " + token + "\n");
			}

			processSingleToken(token, stack, map);

			if (PRINT_STACK_AND_MAP) {
				// NOTE: only prints map if nonempty
				if (stack.size() > 0) PRINT(stack);
				System.out.println("----- \nstack");
				if (map.size() > 0) {
					System.out.print("+ map ");
					PRINT(map);
				}
				System.out.println();
			}
		}

		if (PRINT_BEGIN_AND_END_INTERPRETING) {
			System.out.println("--- END INTERPRETING " + tokens + " ------------\n");
		}
	}

	static void processSingleToken(Token token, HW05_Stack stack, HashMap<String, Token> map) {
		if (token.type == Token.TYPE_STRING) {
			String string = token.getString();
			if (string.equals("dup")) {
				Token any = stack.pop();
				stack.push(any);
				stack.push(any);
			} else if (string.equals("exch")) {
				Token any2 = stack.pop();
				Token any1 = stack.pop();
				stack.push(any2);
				stack.push(any1);
			} else if (string.equals("def")) {
				Token value = stack.pop();
				String key = stack.popString();
				map.put(key, value);
			} else if (string.equals("sqrt")) {
				stack.pushDouble(Math.sqrt(stack.popDouble()));
			} else {
				if (string.charAt(0) == '/') {
					stack.pushString(string.substring(1, string.length()));
				} else if (map.containsKey(string)) {
					Token any = map.get(string);
					if (any.type == Token.TYPE_LIST) {
						interpretListOfTokens(any.getList(), stack, map);
					} else {
						processSingleToken(any, stack, map);
					}
				} else {
					double num2 = stack.popDouble();
					double num1 = stack.popDouble();
					if      (string.equals("add")) { stack.pushDouble (num1 +  num2); }
					else if (string.equals("sub")) { stack.pushDouble (num1 -  num2); }
					else if (string.equals("eq" )) { stack.pushBoolean(num1 == num2); }
					else if (string.equals("neq")) { stack.pushBoolean(num1 != num2); }
					else if (string.equals("mul")) { stack.pushDouble (num1 *  num2); }
				} 
			}
		} else {
			stack.push(token);
		}
	}

	public static void main(String[] arguments) {
		// String program = "5.0";
		// 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> tokens = tokenizePostScriptProgram(program);
		HW05_Stack stack = new HW05_Stack(); 
		HashMap<String, Token> map = new HashMap<>();
		interpretListOfTokens(tokens, stack, map);
	}

}
⚠️ **GitHub.com Fallback** ⚠️