HW05 - james-bern/CS136 GitHub Wiki

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! 🎉
- To check equality between String's, do NOT use the
stringA == stringB
.- Instead, use
stringA.equals(stringB)
.
- Instead, use
- To check (approximate) equality between double's, do NOT use
numA == numB
.- Instead, use Cow's
ARE_EQUAL(numA, numB)
- Instead, use Cow's
if (...) {
...
} else if (...) {
...
} else if (...) {
...
} else {
...
}
- 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
andfalse
- 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 built-in function like
- a list of tokens like
[dup, mul, exch, dup, mul, add, sqrt]
- a boolean like
- The interpreter uses a stack to store tokens we're still working with
- The interpreter uses a map (dictionary, table, hash table, hash map) to store key-token pairs
- 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
- When defining a new constant using
- HINT: Check the examples below to make sure you completely understand the syntax
- The syntax of
- The definitions of other built-in functions can be found in this Reference
- To find text on a webpage use
COMMAND
+F
(orCONTROL
+F
on Windows) -
HINT: Check the examples below to make sure you completely understand the order of arguments for
sub
- To find text on a webpage use
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
- It pops
- 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
- It pops
--- 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] ------------
- 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
}
- 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()
}
// 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);
}
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);
- 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);
- 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();
}
-
A-
- Make
processSingleToken(...)
work for the first three example programs -
HINT: The Docs now include useful
String
functions 🙂👍
- Make
-
A
- Make
processSingleToken(...)
work for all four example programs - HINT: This doesn't take very much code, but is a little tricky :)
- Make
-
A+
- Write a Postscript program that...
- Defines a custom function
is_prime
usingdef
- *NOTE: you may implement and use any standard PostScript commands from the Reference, e.g.,
if
,mod
).
- *NOTE: you may implement and use any standard PostScript commands from the Reference, e.g.,
- Calls
is_prime
on2
,3
,33
, and37
- (Or, if you're feeling really ambitious, uses some sort of PostScript for loop or something to call
is_prime
on the first100
numbers)
- (Or, if you're feeling really ambitious, uses some sort of PostScript for loop or something to call
- Defines a custom function
- Make
processSingleToken(...)
work for the PostScript program you wrote :D -
NOTE: You will likely need to also extend/modify
tokenizePostScriptProgram(...)
🙂👍 I recommend using aStack<ArrayList<Token>> stack;
is_prime num is_prime returns boolean for whether num is prime
- Write a Postscript program that...
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);
}
}
👀
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);
}
}