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

⚠️ For checking equality between String's, do NOT use the==
.- Instead, use
string.equals("sub")
.
- Instead, use
- For checking (approximate) equality between double's, do NOT use
==
.- Instead, use
Math.abs(num - 5.0) < 0.0001
.
- Instead, use
- This may be a useful pattern for this lab :)
if (...) {
} else if (...) {
} else if (...) {
} else {
}
- 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.)
- Here is an example of a tiny PostScript program:
- Our interpreter will interpret a PostScript program one token at a time. A tokens can be...
- a boolean like
true
andfalse
- 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
-
built-in functions like
- 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 🙂👍
- a boolean like
- 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] ------------
-
- 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 usingdef
, 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) --> pushespi
onto the stack - sees double
3.14
--> pushes3.14
onto the stack - sees built-in function
def
--> pops3.14
off of the stack, popspi
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 topi
from the map (3.14
), pushes3.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] ------------
-
👀
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 returns5.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
isdouble
,bool
isboolean
,any
is (some)Token
,proc
islist
. - 🚨 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 computenum1 - 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, pops2.0
and1.0
, and pushes1.0 - 2.0 -> -1.0
(🚨 note the minus sign!)> sub -1.0 ----- stack
- First,
- Note that
num2
gets popped beforenum1
gets popped! Be careful!
- This means that the teeny tiny PostScript program
- Consider
// 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);
}
- 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
}
- 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
}
- 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));
- If you know the token you're about to pop must be, e.g., type
double
(e.g., when handling thesub
command), then you should callstackPopDouble()
-
stackPopDouble()
is a helper function that callsstackPopToken().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();
} ...
-
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 push5.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
andeq
- ✨ The second example uses these built-in functions:
add
,def
- ✨ See the reading for how to use
def
(/myKeyName ... def
, andmyKeyName
)
- ✨ See the reading for how to use
- ✨ The first example uses these built-in functions:
- ✅ Implement an
-
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
- ✨ The third example uses these built-in functions:
- ✅ Implement an
-
To get an S:
- ✅ Implement everything above.
- ✅ Write a Postscript program that...
- Defines a custom function
is_prime
usingdef
(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
- 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
- ✅ 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 aStack<ArrayList<Token>> stack;
- ✅ You may also have to extend/modify
- 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.
// 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);
}
}