|
| = |
|
| ∈ |
| |||
| ⊂ |
|
| = |
|
| = |
|
| = |
|
| = |
|
| = |
|
| ::= |
| |||
| |
| ||||
⋮ | |||||
| |
|
| = |
|
| ::= |
|
| = |
|
| = |
|
| ::= |
| |||
| |
|
| = |
|
| = |
|
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
|
| = |
|
| = |
|
| ::= |
| |||
| |
| ||||
| |
| ||||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
|
| = |
|
| ::= |
| |||
| |
| ||||
| ::= |
| |||
| |
| ||||
| ::= |
|
| ::= |
|
| ::= |
|
character string (concrete syntax) |
⇒ | lexer/ tokenizer |
⇒ | token sequence |
⇒ | parser | ⇒ | parse tree (abstract syntax) |
character string (concrete syntax) |
⇒ |
parser |
⇒ | parse tree (abstract syntax) |
| ::= |
|
| ::= |
|
a1.py
. Please follow the gsubmit directions.
a1-tests.py
. You should be able to place it in the same directory as a1.py
and run it. Feel free to modify or extend it as you see fit.
tokenize()
that takes two arguments: a regular expression string and a character string to tokenize. It should then use the regular expression to convert the second string into a token sequence (represented in Python as a list of strings) and return it.
directions()
that takes a token sequence (represented in Python as a list of strings) and parses that token sequence into a parse tree according to the following grammar definition:
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
|
"Up"
, "Down"
, "Left"
, "Right"
, or "Stop"
. An example input and output are provided below.
Note that the output below omits the token list because it was generated using the json
module. It is fine if your implementation of directions
returns an empty token list along with the parse tree.
analyse()
that takes a grammar definition in the above format as its one argument. The function should return a single string (letter case must match):
"Predictive"
if the grammar can be parsed with a predictive recursive descent parsing algorithm;
"Backtracking"
if the grammar can be parsed with a backtracking recursive descent parsing algorithm;
"Neither"
if the grammar fall into neither of the above categories.
d
, you can use [key for key in d]
; to get the list of keys in a list of dictionaries, you can use [key for d in ds for key in d]
; and to check whether a string x
is in a list or set X
, you can use x in X
.
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| ::= |
|
program()
, formula()
, term()
, variable()
, and number()
. Each function corresponds to one of the productions. Below is an implementation of number()
:
Your algorithm must parse all programs in this programming language correctly. However, it need not return useful error messages if the input provided does not conform to the grammar definition.
variable()
that takes a single token string as an argument and, if the token sequence conforms to the grammar definition, returns a tuple containing two values: an instance of the abstract syntax (i.e., parse tree) with the root node label "Variable"
, and the remaining, unparsed suffix of the input token sequence.term()
that takes a single token string as an argument and, if the token sequence conforms to the grammar definition, returns a tuple containing two values: an instance of the abstract syntax (i.e., parse tree) with an appropriate root node label ("Plus"
, "Mult"
, "Log"
, "Variable"
, or "Number"
), and the remaining, unparsed suffix of the input token sequence.
formula()
that takes a single token string as an argument and, if the token sequence conforms to the grammar definition, returns a tuple containing two values: an instance of the abstract syntax (i.e., parse tree) with an appropriate root node label ("True"
, "False"
, "Not"
, "And"
, "Or"
, "Equal"
, or "Less"
), and the remaining, unparsed suffix of the input token sequence.program()
that takes a single token string as an argument and, if the token sequence conforms to the grammar definition, returns a tuple containing two values: an instance of the abstract syntax (i.e., parse tree) with an appropriate root node label ("Print"
, "Assign"
, or "End"
), and the remaining, unparsed suffix of the input token sequence.complete()
that takes a single string as an argument. If the input string conforms to the grammar of the programming language, the function should return a parse tree corresponding to that input string.
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| |
| ||||
| ::= |
| |||
| ::= |
|
assign
... :=
corresponds to :=
, ==
corresponds to equal
, <
corresponds to less
, +
corresponds to plus
, and *
corresponds to mult
).
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
|
condition()
function with a generic parser for base cases that contain an arbitrary number of terminals in each sequence.
Any call to condition(...)
can then be replaced with a call to parseBaseCases(seqsCondition, ...)
.
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
|
| ::= |
| |||
| |
| ||||
| |
| ||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
|
[Name-of-Inference-Rule] |
|
[Example] |
|
[Algorithm-Case] |
|
[Algorithm-Case] |
|
expressions (abstract syntax) |
⇒ | evaluation algorithm |
⇒ | values (abstract syntax) |
| ::= |
| |||
| ::= |
|
[True] |
|
[False] |
|
[Not-True] |
|
[Not-False] |
|
[And-True-True] |
|
[And-True-False] |
|
[And-False-True] |
|
[And-False-False] |
|
[Or-True-True] |
|
[Or-True-False] |
|
[Or-False-True] |
|
[Or-False-False] |
|
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
| = |
|
[True] |
|
[False] |
|
[Not] |
|
[And] |
|
[Or] |
|
vnot
, vand
, and vor
correspond to ¬, ∧, and ∨, respectively, in the operational semantics.
statement sequence (abstract syntax) |
⇒ | execution algorithm |
⇒ | output (record of side effects) |
| ::= |
| |||
| ::= |
| |||
| ::= |
|
[Statement-Print] |
|
[Statement-End] |
|
[Formula-True] |
|
[Formula-False] |
|
[Formula-Not] |
|
[Formula-And] |
|
[Formula-Or] |
|
evaluate()
from a previous example.
print()
function and turn this into an interpreter that uses the Python runtime system directly to interpret the program. However, we must be careful about where we put the call to print()
: it must be invoked in a way that conforms to a pre-order traversal of the syntax tree.
|
⇒ | execution algorithm |
⇒ | output (record of side effects) |
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
|
[Statement-Assign] |
|
[Statement-Print] |
|
[Statement-End] |
|
[Formula-True] |
|
[Formula-False] |
|
[Formula-Not] |
|
[Formula-And] |
|
[Formula-Or] |
|
[Formula-Variable] |
|
vnot
, vand
, and vor
are the same as those in a previous example.
|
⇒ | execution algorithm |
⇒ |
|
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
⋮ |
[Statement-Assign] |
|
[Statement-Print] |
|
[Statement-Repeat-Twice] |
|
[Statement-End] |
|
evaluate()
is the same as the one in a previous example, while the implementation of execute()
from that example must be modified so that it also returns an updated version of the environment data structure.
hw2/parse.py
and hw2/interpret.py
. Please follow the gsubmit directions and remember to put your files in the hw2
directory.
a2-tests.py
. Feel free to modify or extend it as you see fit.
parse.py
. You should adapt the techniques and/or code from Assignment #1 where appropriate to complete this problem. Recall that all parsers take a sequence of tokens (and possibly a flag) as their input, and return a tuple containing a parse tree and a sequence of remaining tokens.variable()
and number()
that can parse token sequences that conform to the following two productions:
| ::= |
| |||
| ::= |
|
formula()
that can parse token sequences that conform to the following production. You may need to perform left factoring on a production before you can implement a working parser. Note that the or operator is omitted intentionally to avoid dealing with issues related to left-associativity. When parsing the choice "( formula )", you should simply return the parse tree that is obtained from the recursive call corresponding to formula.
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
term()
that can parse token sequences that conform to the following productions. These productions ensure that the traditional order of operations for addition and multiplication is preserved in a parser tree. However, you may need to perform left factoring on a production before you can implement a working parser. When parsing the choice "( term )", you should simply return the parse tree that is obtained from the recursive call corresponding to term.
| ::= |
| |||
| |
| ||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
|
program()
that can parse token sequences that conform to the following production. Note that the last choice in the production defining program is meaningful: it means that an empty token sequence is a valid program. The parse tree corresponding to an empty program should be 'End'
.
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| | |||||
| ::= |
| |||
| |
| ||||
interpret.py
. Your interpreter must conform to the operational semantics for this language, as presented in each of the problem parts below.evalTerm(env, t)
that takes an environment env
and a parse tree t
as its two arguments. The function should return the value that corresponds to the evaluation of the parse tree t
, as determined by the operational semantics below.[Term-Number] |
|
[Term-Variable] |
|
[Term-Log] |
|
[Term-Plus] |
|
[Term-Mult] |
|
evalFormula(env, f)
that takes an environment env
and a parse tree f
as its two arguments. The function should return the value that corresponds to the evaluation of the parse tree f
, as determined by the operational semantics below.[Formula-True] |
|
[Formula-False] |
|
[Formula-Variable] |
|
[Formula-Not] |
|
[Formula-And] |
|
execProgram(env, s)
that takes an environment env
and a parse tree s
as its two arguments. The function should return a tuple containing the output (represented as a list of values) and an updated environment that represent the result of the execution of the program s
as determined by the operational semantics below.[Statement-Print] |
|
[Statement-Assign] |
|
[Statement-If-False] |
|
[Statement-If-True] |
|
[Statement-While-False] |
|
[Statement-While-True] |
|
[Statement-End] |
|
interpret()
that takes a single string as its argument. The function should tokenize the string, parse it to generate a parse tree, and then execute it to obtain an output. It should then return the output. Your implementation only needs to interpret entire programs; you do not need to interpret stand-alone terms or formulas.evalFormula()
to use "short-circuiting" when evaluating the and
operator: if the left-hand subtree evaluates to 'False'
, then do not evaluate the right subtree at all.evalProgram()
not to evaluate the expression in an assignment statement if the variable being assigned never appears in the rest of the program. You may want to implement a helper function that checks if a variable appears in a parse tree corresponding to a program.program (abstract syntax) |
⇒ |
|
⇒ | output and/or value(s) |
character string (concrete syntax) |
⇒ |
interpreter |
⇒ | output and/or value(s) |
program (e.g., input/configuration, instructions, etc.) |
computing device (e.g., difference engine, modern CPU, quantum computer, etc.) |
physical laws (mechanics, electricity, chemistry, biology, quantum mechanics, etc.) |
memory
|
||
central processing unit (CPU) |
machine program | ||
machine languauge | ||
|
FORTRAN program | ||
FORTRAN compiler | ||
machine program | ||
machine languauge | ||
|
application | ||
high-level program | ||
compiler | ||
machine program | ||
machine languauge | ||
|
user A
|
user B
|
|||||||||
high-level program |
high-level program |
high-level program |
high-level program |
|||||||
compiler | ||||||||||
machine program |
machine program |
machine program |
machine program |
|||||||
machine languauge | ||||||||||
operating system | ||||||||||
|
application | application | application | application |
operating system | |||
hardware |
app. | app. | |||||||||||||
web app. platform | ||||||||||||||
browser | app. | app. | ||||||||||||
API | API | OS | OS | |||||||||||
web service | web service | virtual machine | virtual machine | |||||||||||
dist. platform | web server | virtualization layer | ||||||||||||
controller | controller | OS | host operating system | |||||||||||
node | node | node | node | node | node | node | node | server | hardware (web/cloud server) |
source language abstract syntax |
⇒ | compiler | ⇒ | target language abstract syntax |
source language concrete syntax |
libraries | |||||
⇓ | ⇓ | |||||
parser | linker | ⇒ | target language concrete syntax |
|||
⇓ | ⇑ | |||||
source language abstract syntax |
⇒ | compilation algorithm |
⇒ | target language abstract syntax |
source language abstract syntax |
⇒ | comp. alg. A |
⇒ | IR #1 |
⇒ | comp. alg. B |
⇒ | IR #2 |
⇒ | comp. alg. C |
⇒ | target language abstract syntax |
| ::= |
| |||
| | |||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| ::= |
| |||
| ::= |
|
| ::= |
| |||
| | |||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| ::= |
| |||
| ::= |
|
copy()
function below can be viewed as a macro that generates a specific machine language program.
increment()
that takes a single argument, a memory address, and generates a machine program that increments the value at the address specified memory address. We could then implement the following macro, which will set an entire region of memory to a particular constant.
setAllToInRegion()
on specific inputs generates a machine language program for a particular region.
| ::= |
|
compileFormula()
that takes as its input the parse tree, and a variable heap
that represents the highest address in memory that contains a computed value (in our case, 1
can represent true and 0
can represent false). The function returns a list of machine language instructions that computes that value, as well as the memory address where that value would be stored if anyone actually executed (or simulated) the machine language program.
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| | |||||
| ::= |
|
hw3/parse.py
(there is no need to modify this file);hw3/interpret.py
;hw3/machine.py
;hw3/compile.py
.hw3
directory.
a3-tests.py
. You should be able to place it in the same directory with the other assignment files and run it. Feel free to modify or extend it as you see fit.
/hw3/solutions
.
hw3/interpret.py
. The abstract syntax for the language is presented below:
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| | |||||
[Term-Variable] |
|
[Formula-Variable] |
|
[Formula-Or] |
|
[Statement-Assign] |
|
[Statement-Procedure] |
|
[Statement-Call] |
|
evalTerm(env, t)
that takes an environment env
and a parse tree t
as its two arguments. The function should return the value that corresponds to the evaluation of the parse tree t
, as determined by the operational semantics.evalFormula(env, f)
that takes an environment env
and a parse tree f
as its two arguments. The function should return the value that corresponds to the evaluation of the parse tree t
, as determined by the operational semantics.execProgram(env, s)
that takes an environment env
and a parse tree s
as its two arguments. The function should return a tuple containing an updated environment and the output (represented as a list of values) that represent the result of the execution of the program s
as determined by the operational semantics.hw3/machine.py
already contains a simulator for the machine language with which you will be working; the machine language is defined in detail in a previous example in the lecture notes.
Although any correct implementation is acceptable, it is suggested that you follow the conventions below:
-1
, for the stack;7
to store the memory address of the top of the stack;8
and higher for the heap (i.e., results of computations).hw3/machine.py
.increment(addr)
that takes a single integer argument addr
. The function should return a list of instructions (in which each instruction is represented as a string in the list). These instructions should correspond to a machine language program that increments by 1
the integer stored in the memory location addr
and cleans up any memory addresses it used in the process by setting them back to 0
.decrement(addr)
that takes a single integer argument addr
. The function should return a sequence of instructions (represented as a Python list of strings). These instructions should correspond to a machine language program that decrements by 1
the integer stored in the memory location addr
and cleans up any memory addresses it used in the process by setting them back to 0
.call(name)
that takes a single argument: name
is a string corresponding to the name of a procedure. The function should return a sequence of instructions that performs the following operations:
name
supplied;procedure(name, body)
that takes two arguments: name
is a string corresponding to the name of a procedure, and body
is a sequence of machine language instructions (represented as a Python list of strings). The function should return a sequence of instructions that includes:
compileTerm(env, t, heap)
that takes three arguments: env
is a mapping from variables to memory addresses, t
is a term parse tree, and heap
is the memory address of the current top of the heap. The function should return a tuple (insts, addr, heap)
in which insts
is a sequence of machine language instructions (represented as a Python list of strings) that perform the computation represented by the parse tree, addr
is the address of the result, and heap
is an integer representing the memory of the top of the heap after the computation is performed.compileFormula(env, f, heap)
. The requirements for this function are the same as those for compileTerm(env, t, heap)
, except that it must handle formula parse trees.compileProgram(env, s, heap)
that takes three arguments: env
is a mapping from variables to memory addresses, s
is a program parse tree, and heap
is the memory address of the current top of the heap. The function should return a tuple (env, insts, heap)
in which env
is an updated environment, insts
is a sequence of machine language instructions (represented as a Python list of strings) that perform the computation represented by the parse tree, and heap
is an integer representing the memory of the top of the heap after the computation is performed.compile(s)
that takes a single string s
that is a concrete syntax representation of a program in the source programming language and returns its compiled form: a sequence of instructions in the target machine language.source language abstract syntax |
⇒ ⇐ |
optimization algorithm #1 |
⇓ | ||
compilation algorithm A |
||
⇓ | ||
intermediate representation (IR) #1 |
⇒ ⇐ |
optimization algorithm #2 |
⇓ | ||
compilation algoritm B |
||
⇓ | ||
intermediate representation (IR) #2 |
⇒ ⇐ |
optimization algorithm #3 |
⇓ | ||
compilation algorithm C |
||
⇓ | ||
target language abstract syntax |
⇒ ⇐ |
optimization algorithm #4 |
source language abstract syntax |
⇒ | compilation algorithm |
⇒ | target language abstract syntax |
⇓ | ⇓ | |||
source language operational semantics (interpreter) |
target language operational semantics (simulator) |
|||
⇓ | ⇓ | |||
source language abstract syntax for values/outputs |
⇒ | simple conversion algorithm for values/outputs |
⇒ | target language abstract syntax for values/outputs |
| = |
|
| ::= |
|
source language concrete syntax |
target language abstract syntax |
|||||||
⇓ | ⇑ | |||||||
parser | ⇒ | source language abstract syntax |
⇒ | static analysis algorithms |
⇒ | abstract syntax or IR |
⇒ | compilation algorithms |
⇓ | ⇓ | |||||||
syntax errors |
other errors (unbound variables, type errors, etc.) |
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
|
| ::= |
| |||
| |
|
[Formula-True] |
|
[Formula-False] |
|
[Formula-Not] |
|
[Formula-And] |
|
[Formula-Or] |
|
[Term-Number] |
|
[Term-Plus] |
|
| ::= |
| |||
| | |||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| |
| ||||
| |
|
[Statement-Assign] |
|
[Statement-End] |
|
[Variable] |
|
[Formula-True] |
|
[Formula-False] |
|
[Formula-Not] |
|
[Formula-And] |
|
[Formula-Or] |
|
[Term-Number] |
|
[Term-Plus] |
|
[Term-Mult] |
|
| ::= |
| |||
| |
| ||||
| | |||||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| |
| ||||
| |
|
[Statement-Assign] |
|
[Statement-Function] |
|
[Statement-End] |
|
[Term-Variable] |
|
[Term-Number] |
|
[Term-Plus] |
|
[Term-Apply] |
|
| ::= |
| |||
| |
| ||||
| | |||||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| |
| ||||
| |
| ||||
| ::= |
| |||
| |
| ||||
[Statement-Assign] |
|
[Statement-Function] |
|
[Statement-End] |
|
[Term-Variable] |
|
[Term-Number] |
|
[Term-Concat] |
|
[Term-Apply] |
|
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| | |||||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
|
[Expression-Number] |
|
[Expression-Mult] |
|
[Statement-Print] |
|
[Statement-For] |
|
[Statement-Procedure] |
|
[Statement-Call] |
|
[Statement-End] |
|
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| | |||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
|
[Duration-Minutes] |
|
[Duration-Hours] |
|
[Duration-Days] |
|
[Statement-Reserve] |
|
[Statement-For] |
|
[Statement-Procedure] |
|
[Statement-Call] |
|
[Statement-End] |
|
imperative | procedural | object- oriented |
declarative | functional | static type checking |
garbage collection |
side effects |
||
machine languages |
some support (indexed memory, branching) |
no support | no support | no support | no support | none | no | yes | |
FORTRAN (initial release) |
full support | no support | no support | no support | no support | very limited |
no | yes | |
C | full support | full support | no support | no support | some support (function pointers) |
very limited |
no | yes | |
C++ | full support | full support | full support | class declarations, some type checking |
function pointers and some extensions (e.g., STL) |
limited | no | yes | |
Java | full support | full support | full support | type system, class declarations, abstract classes, interfaces, type checking, and type inference |
generics, interfaces, objects as wrappers, anonymous classes |
extensive | yes | yes | |
PHP | full support | full support | full support | no support | some support | none | yes (≥ 5.3) |
yes | |
Python | full support | full support | full support | no support | full support | none | yes | yes | |
JavaScript | full support | full support | full support | no support | full support | none | yes | yes | |
Haskell | meta-support (monads) |
meta-support (functions) |
no support | full support | full support | extensive | yes | some (IO monads) |
|
SQL | some support (insertion/ deletion; sequences of statements in versions like MS-SQL) |
some support (procedures in versions like MS-SQL) |
no support | full support (definition of schemas and tables; ability to evaluate queries over tables) |
some support (custom map and aggregation procedures in MS-SQL) |
some | yes | some | |
Amazon Web Services APIs |
some support | no support | no support | some support | some support | none | n/a | yes |
| ::= |
| |||
| ::= |
|
| = |
|
| = |
|
| = |
|
| = |
|
| = |
|
| ≠ |
|
| ::= |
| |||
| ::= |
|
| = |
|
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
| = |
| |||
==
operator), and Haskell supports the automatic derivation of this algorithm for any data type (using the deriving Eq
directive).
Tree
algebraic data type. However, in order to actually display instances of the data type, it is necessary to ask the Haskell interpreter to derive a definition of a show
function automatically from the definition of the algebraic data type (something that is done implicitly in, for example, Python). Haskell requires that this be requested explicitly because it also allows users to define their own variants of such functions.
_
) in patterns makes it possible to write even more concise definitions.
==
. To have the Haskell interpreter or compiler derive it automatically, you can use deriving Eq
. These can be combined with other deriving
directives, such as in the example below.
hw4/parse.py
(there is no need to modify this file);hw4/interpret.py
;hw4/Tree.hs
.hw4
directory.
a4-tests.py
. You should be able to place it in the same directory with the other assignment files and run it. Feel free to modify or extend it as you see fit.
hw4/interpret.py
.subst(s, a)
that takes two arguments: a substitution s
(represented as a Python dictionary), and an abstract syntax tree a
. The function should return a new abstract syntax tree in which every variable in the tree that is in the domain of s
has been substituted according to the substitution s
. You may assume that variables are always represented using a subtree of the form {"Variable":[ ... ]}
, as in all previous examples and assignments.unify(a, b)
that takes two arguments: two syntax trees a
and b
. The function should return the smallest substitution s
that satisfies the following equation:
hw4/interpret.py
. The abstract syntax for the language is presented below; a complete parser that parses the concrete syntax for this language into the abstract syntax below can be found in hw4/parse.py
. Notice that in the concrete syntax, constructor names always begin with an uppercase letter, while variable names always begin with a lowercase letter.
| ::= |
| |||
| | |||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| ::= |
| |||
| ::= |
| |||
| ::= |
|
build(m, d)
that takes a finite map m
(i.e., a Python dictionary mapping names to lists of (pattern, expression) tuples) and a declaration parse tree d
. The function should return the finite map m
that represents the module definition that is assembled according to the operational semantics presented below:[Declaration-Function-First] |
|
[Declaration-Function-More] |
|
[Declaration-End] |
|
| ::= |
| |||
| |
| ||||
| |
|
evaluate(m, env, e)
that takes a module m
, an environment env
, and an expression abstract syntax tree e
as its three arguments. The function should return the value that corresponds to the evaluation of the abstract syntax tree e
, as determined by the operational semantics presented below. Note that the [Expression-Apply] requires using a unification algorithm to obtain a substitution σ that unifies p and v1.[Expression-Constructor-Args] |
|
[Expression-Constructor-No-Args] |
|
[Expression-Number] |
|
[Expression-Variable] |
|
[Expression-Plus] |
|
[Expression-Apply] |
|
build()
and evaluate()
are implemented, you can use the interact()
function in hw4/interpret.py
to query modules.hw4/Tree.hs
. You will be working with the following data type definition, which has already been included in the file:
Tree
in Tree.hs
so that the functions (==)
and show
are automatically generated for this data type.leaves :: Tree -> Integer
that returns an integer representing the total number of leaves in the input tree.
nodes :: Tree -> Integer
that returns an integer representing the total number of nodes in the input tree.
height :: Tree -> Integer
that returns an integer representing the height of the input tree. Trees consisting of a Leaf
are defined to have height 1
.
perfect :: Tree -> Bool
that returns True
if the input tree is perfect and False
otherwise.
degenerate :: Tree -> Bool
that returns True
if the tree supplied is degenerate, and False
otherwise.
infinite :: Tree
that has no leaves.
| ::= |
| |||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
| |
| ||||
⋮ | |||||
| |
| ||||
| |
| ||||
| |
| ||||
⋮ | |||||
[Integer]
.
Tree
data type that we have already seen. If we were to define our own algebraic data type for lists in Haskell, for example, we could do so as follows (the terms "nil" and "cons" are commonly used in the functional language community for the empty list and for the list node constructor, respectively):
[]
, and the list node constructor is the operator (:)
, which can also be written as an infix operator :
. The following three syntaxes are equivalent, and evaluate to the same list [1,2,3]
:
String
; strings in Haskell are just lists of characters:
GameStrategyAlgorithm
is equivalent to the type [[(Integer, Integer)]] -> String
(i.e., the type for functions that take lists of lists of integer pairs and return a string):
1
in every position:
(:)
list node constructor:
n
elements of a list (note that this function is already included in the Prelude
).
take
.
addInfiniteLists
function to add two infinite lists. Suppose we have the following definitions:
3
:
g
is a function that takes an integer as an argument. It then throws this argument away and returns the function f
. Its type reflects this. Thus, the result of evaluating an expression such as g(4)
is a function of type Integer -> Integer
, and this function can be applied to another argument.
g
that can be partially applied to only some of its arguments, but that still uses all of its arguments? We can do so by listing multiple, space-separated parameters in the definition of g
.
g
to arguments one at a time, as before.
0
, 1
, 2
, and so on) and built-in operators (such as +
, *
, and so on).
Meters
.
Meters
to strings.
$
syntax to easily write down abstract syntax trees.
parseTree
will yield the actual algebraic data type instance (i.e., a parse tree).
if ... then ... else ...
for writing conditional expressions:
if ... then ... else ...
syntax (or function, as we have seen above) can be useful in certain situations.
String
and a value (e.g., an Integer
). For example:
lookup
. However, since it returns a result that is wrapped in a Maybe
constructor, we can unwrap it using, e.g.:
Red
has the lowest frequency and Blue
has the highest frequency, we can define an ordering on all instances of this data type by making the Color
type a member of the Ord
type class. Note that we could use wildcards (_
) to make the definition more concise.
<
, <=
, >
, and >=
to compare colors, as well as library functions such as max :: Color -> Color -> Color
, min :: Color -> Color -> Color
, maximum :: [Color] -> Color
, and minimum :: [Color] -> Color
.
compose
?
compose
; let's substitute one the right-most Function
synonym with its definition:
->
type operator is right-associative; this means we can remove the right-most set of parentheses without changing the meaning of the type:
compose
: it takes two Function
arguments, and one Integer
argument:
(.)
. So we could also have defined compose
as follows:
X
or O
somewhere on the board. We can model this using a function that, given a move, transforms one board into another board. We use a list comprehension that updates only the cell with the corresponding position.
X
and O
players take alternating turns.
Board
), and can have any number of child trees (including zero).
nextMoves
takes a Board
and a Cell
(to indicate which player is taking a turn), and uses a list comprehension to choose only the board cells that are empty. For each empty cell, it adds a new board state to the list in which that position is filled with the specified player's symbol.
win :: Cell -> Board -> Bool
that checks if a given board is already a win for a particular player. We can then add an Ord
instance to compare boards from player X
's perspective: a board is "greater" (or "better") than another board if either the board is a win for X
, or the other board is a loss for O
.
<
, <=
, >
, and >=
to compare boards, as well as library functions such as max :: Board -> Board -> Board
and maximum :: [Board] -> Board
to choose the "best" boards.
hw5
directory.
a5-tests.hs
. You should be able to place it in the same directory with the other assignment files and load it. Feel free to modify or extend it as you see fit.
hw5/Interpreter.hs
.eval :: ([(String, Value)], Exp) -> Value
that takes two arguments: an environment data structure of type [(String, Value)]
and an expression abstract syntax tree of type Exp
. It should return the value that is the result of evaluating the expression abstract syntax tree according to the following operational semantics:[Exp-Value] |
|
[Exp-Variable] |
|
[Exp-Plus] |
|
[Exp-Not] |
|
[Exp-And] |
|
[Exp-Or] |
|
exec :: ([(String, Value)], Stmt) -> Output
that takes two arguments: an environment data structure of type [(String, Value)]
and a statement abstract syntax tree of type Stmt
. It should return the output that is the result of executing the statement abstract syntax tree according to the following operational semantics:[Statement-Assign] |
|
[Statement-Print] |
|
[Statement-End] |
|
Exp
by using the built-in Haskell infix operator +
instead of the constructor Plus
, and by using numeric literals such as 123
instead of Number 123
.hw5/Allocation.hs
.
Alloc
:
Graph
data type:
graph :: Alloc -> [Item] -> Graph
that takes a starting allocation and a list of items, and returns the full graph of all possible state space traversals available to an algorithm given the list of items. An example is provided below (spacing has been adjusted for legibility):
alloc :: Graph -> Alloc
that makes it easy to retrieve the allocation within the root node of a particular graph of type Graph
.
a :: Alloc
is better than another allocation b :: Alloc
if the difference between the two bin totals in a
is less than the difference between the two bin totals in b
. Add an instance declaration for the Alloc
type so that it is possible to use the built-in Haskell infix operators <
and <=
to compare two allocations according to their differences. You may use the Haskell library function abs :: Integer -> Integer
to compute the absolute value of an integer. If you're getting a stack overflow when testing <
, make sure you also define <=
explicitly.
g :: Graph
is better than another graph g' :: Graph
if the difference between the two bin totals in the root node of g
is less than the difference between the two bin totals in the root node of g'
. Add an instance declaration for the Graph
type so that it is possible to use the built-in Haskell infix operator <
to compare two graphs.
final :: Graph -> [Alloc]
that returns an aggregate list of the allocations within all the leaf nodes of the supplied state space graph.depth :: Integer -> Graph -> [Alloc]
that returns the allocations within the state space graph nodes that are at depth exactly n
within the state space graph (the root node is at depth 0
).hw5/Allocation.hs
.
greedy :: Strategy
that takes a graph as an input. It should choose and return the better child of the two children of the root of the graph.patient :: Integer -> Strategy
that takes an integer argument n
and a graph as its two inputs, and chooses and returns the best descendant of the supplied graph that can be found at depth n
(an allocation is best if it is better than all others at that depth). If n
is 0
, the function should simply return the graph supplied to it as an input.optimal :: Strategy
that takes a state space graph, and returns the leaf node in the state space graph that has the best allocation. Hint: you can define this function in two lines; look carefully over the functions already available to you, and think recursively.metaCompose :: Strategy -> Strategy -> Strategy
that takes two strategies as arguments. It should apply the first strategy to obtain a new subgraph, and then it should apply the second strategy to that subgraph and return the result.
metaRepeat :: Integer -> Strategy -> Strategy
that repeats a given strategy a specified number of times. Your definition of metaRepeat
should take an Integer
and a Graph
argument. If the integer is 0
, it should simply return the graph unchanged. If the integer is positive, it should apply the strategy the specified number of times to the graph and then return the result.metaGreedy :: Strategy -> Strategy -> Strategy
that takes two strategies as its inputs. It should apply the two strategies to its input graph and should return the better of the two results that it obtains.impatient
is superior to patient
, and one way in which it is inferior.fit :: Graph -> [Strategy] -> Strategy
that takes a graph and a list of strategies as inputs; this metastrategy should choose the strategy in the list of strategies that has the best performance on the given graph, and return it. Hint: define a way to compare strategies and use minimumBy
.Set
data structure for holding sets of Integer
values that has an underlying list implementation.
Type -> Type
, and so on).
Set
data structure for holding sets of values of any type a
.
Set
data structure for holding sets of values of any type a
that has a corresponding size :: Set a -> Int
function.
<
operator by making the type Set a
(for any possible a
) a member of the Ord
type class.
a
must be in the Eq
type class. This is because the definition of Set a
contains a deriving Eq
clause, which means that a type Set a
can only be used in a module if the parameter type a
is a member of the Eq
type class (otherwise, Haskell would not be able to compare two sets for equality using the automatically derived definition of (==)
generated by the deriving Eq
clause).
Integer
lists that does nothing.
(:)
and []
) in the input list with a binary operator and a base case (in this case, (+)
and 0
). This way, the fold
function now computes the sum of the elements in the list.
(:)
, so it must be of type Integer -> Integer -> Integer
, and a replacement for []
, which must be of type Integer
.
fold
implementation to define many common functions on lists of integers.
(:)
and []
) is called foldr
.
foldr
function can be used to implement many of the usual library functions one might find useful when working with lists, and this is what is done in the Haskell libraries. Below are some examples (some of these are simplified versions of what is actually found in the libraries).
1
to every element in an Integer
list. For example, we want to achieve the following using a call to foldr
:
1
to its first argument, then builds the list node:
foldr
:
addOneThenCons
:
foldr
:
consIfPositive
:
(Name, Age)
:
Name
entries in the table; this query amounts to a map operation:
Age
range; this query amounts to a filter operation:
Age
fields of all the entries; this query amounts to fold operation (it is often called an aggregation operation):
map
, filter
, foldr
, and λ abstractions instead of list comprehensions (the Haskell compiler simply converts list comprehensions into some combination of these during compilation):
map
, filter
, reduce
(similar to Haskell's foldr
), and lambda abstractions:
li
elements and updates the text content of each to indicate the item number corresponding to that element. Notice that the .text()
function takes a function as an argument (the function supplied as an argument takes an index as an input and returns a string):
.each()
function.
hw6
directory.
a6-tests.hs
. You should be able to place it in the same directory with the other assignment files and load it. Feel free to modify or extend it as you see fit.
hw6/Tree.hs
.leaves :: Tree a -> Integer
that takes a tree and returns the total number of leaves in the tree. An Empty
tree contains no leaves.
insert :: a -> Tree a -> Tree a
that takes an argument of any type a
and another argument of type Tree a
and inserts the new item of type a
into the tree so that the height of the resulting tree is minimized. In other words, the definition should be recursive:
fold :: (a -> a -> a) -> Tree a -> a
that takes a function of type a -> a -> a
and a tree of type Tree a
, and performs a fold over the tree using the function of type a -> a -> a
in order to return a single result of type a
. You may assume that the input tree is always non-empty.
hw6/Metaql.hs
. The abstract syntaxes for statements, expressions, and types for this programming language are defined below.
| ::= |
| |||
| ::= |
| |||
| ::= |
|
[Exp-Integer] |
|
[Exp-String] |
|
[Exp-Plus] |
|
[Exp-Conc] |
|
[Stmt-Print] |
|
[Stmt-End] |
|
instance Num Exp
declaration so that it is possible to use (*) :: Exp -> Exp -> Exp
to build abstract syntax trees representing string concatenation operations.
tyExp :: Exp -> Maybe Type
that takes an Exp
abstrax syntax tree and, if it conforms to the type inference rules, returns its type (wrapped using the Just
constructor). If the expression has no valid type according to the type inference rules, tyExp
should return Nothing
.tyStmt :: Stmt -> Maybe Type
that takes a Stmt
abstrax syntax tree and, if it conforms to the type inference rules, returns its type (wrapped using the Just
constructor). If the statement has no valid type according to the type inference rules, tyStmt
should return Nothing
.flat :: Exp -> [Exp]
that takes an Exp
abstract syntax tree and returns a list that contains all the leaf nodes (i.e., integers and strings) in the original tree (in the order obtained via an in-order traversal of the tree).size :: Exp -> Int
that takes an Exp
abstract syntax tree and returns the number of leaf nodes (i.e., integers and strings) in the tree.hw6/Metaql.hs
.
You should not evaluate the expressions for this problem. The number of nodes and leaves in the abstract syntax tree must remain the same after refactoring has occurred. Solutions that do not conform to this requirement will receive no credit.refactorInt :: Exp -> Exp
that takes an Exp
abstrax syntax tree as its argument. If the tree has Metaql type TyInt
, the function should take advantage of the associative and commutative properties of addition by returning a tree with an equivalent meaning (i.e., the result of evaluating the tree should not change) that is of the least possible height. If the tree is not of Metaql type TyInt
, no changes should be made. Hint: flatten the tree, use foldr
and insert
to build a tree of type Tree Exp
, and then use fold
over the tree to rebuild the expression using the binary operator that corresponds to the tree's original type.refactorStr :: Exp -> Exp
that takes an Exp
abstrax syntax tree as its argument. If the tree has Metaql type TyStr
, the function should take advantage of the associative property of string concatenation by returning a degenerate tree with an equivalent meaning (i.e., the result of evaluating the tree should not change) in which only right-associative concatenation occurs. If the tree is not of Metaql type TyStr
, no changes should be made. Hint: flatten the tree and use foldr
together with other standard list functions to rebuild the tree.refactor :: Stmt -> Stmt
that refactors all the Exp
subtrees so that all subtrees of Metaql type TyInt
have the minimum height possible, and so that all subtrees of Metaql type TyStr
only contain right-associated applications of concatenation.hw6/Compile.hs
. The abstract syntaxes for statements in Protoql are defined below; the file hw6/Protoql.hs
contains a Haskell implementation of an abstract syntax data structure for Protoql.
| ::= |
| |||
| ::= |
| |||
| |
| ||||
| |
| ||||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
| |||
| ::= |
|
[Input] |
|
[Query-Left] |
|
[Query-Right] |
|
[Output] |
|
compExp :: [InLabel] -> OutLabel -> Exp -> [Query]
that takes a list of unused input labels, a target output label, and an Exp
abstract syntax tree. The function should return a list of Protoql queries that corresponds to a valid Protoql program that computes the result of the expression:
Exp
tree is a base case, emit an appropriate Input() Protoql instruction and specify its destination using the OutLabel
argument;Exp
tree is a binary operator, obtain the Protoql instructions for each subtree (use size
together with take
and drop
to ensure that the input label lists for the two recursive calls do not overlap), then return a list containing all the instructions for the two subtrees, and one additional Query() instruction corresponding to the binary operation that must be performed.compStmt :: [InLabel] -> Stmt -> [Query]
that takes a list of unused input labels, and a Metaql Stmt
abstract syntax tree. The function should return a list of Protoql queries that corresponds to a valid Protoql program that computes the results of all the Print statements in the Metaql Stmt
tree. Each result should be sent to a new Output() query with its own unique input label.compile :: Stmt -> Maybe Program
that takes a Metaql Stmt
abstrax syntax tree, ensures that the tree conforms to the Metaql type inference rules, and if so, optimizes the tree, and compiles it into a valid Protoql program. If the Stmt
tree does not conform to the type system, the function should not attempt to optimize or compile the tree; it should return Nothing
.time :: Stmt -> Maybe Integer
that takes a Metaql Stmt
abstrax syntax tree and computes the minimum running time in seconds of the fastest valid Protoql program into which it can be compiled (i.e., running the Protoql program should yield a correct result under all circumstances).
servers :: [Exp] -> Integer
that computes the minimum number of servers that is required to run all the queries in the input list in parallel in the fastest time possible (as determined by a correct implementation of time
).
maximum
and/or minimum
, foldr
, zip
, sum
, and other list functions to implement a very concise solution.reassign :: [Query] -> [Query]
that updates the server entries in all the queries with new server names so that the running time of the query under the new semantics will conform to the output of time
. You may assume the servers 0.protoql.org
, 1.protoql.org
, 2.protoql.org
, and so on are all available.height :: Graph -> Integer
that computes the height of a Graph
.
width :: Graph -> Integer
that computes the width of a Graph
.
| = |
|
|
| = |
|
| = |
|
AN
can never be unified with the constructor N
, there is no substitution that can be applied to both sides of the original equation to make both sides equivalent.
"cat"
is the subject?
N
leaf constructor can only occur at the botton of a NounPhrase
tree. In other words, the pattern would need to look something like the following:
|
| ::= |
| |||
| |
| ||||
| |
|
| ::= |
| |||
| |
| ||||
| ::= |
| |||
| |
|
| ::= |
| |||
| ::= |
| |||
|
|
|
input file |
loop unroller |
machine instruction sequences |
||||||
⇓ | ⇑ ⇓ | ⇑ | ||||||
parser | ⇒ | abstract syntax trees |
⇒ | type checker |
⇒ | abstract syntax trees |
⇒ | compiler |
⇓ | ⇓ | |||||||
error messages |
error messages |
exhaustive case generator |
||||
⇓ | ||||
abstract syntax trees |
||||
⇓ | ⇓ | |||
interpreter | compiler | ⇒ | machine instruction sequences |
|
⇓ | ⇓ | |||
outputs | outputs | ⇐ | simulator | |
⇓ | ⇓ | |||
output comparison function |
csa2/csa3
.True
and False
.and
, or
, and not
.+
, *
, -
, and /
. The infix operator //
represents integer division, and the infix operators **
represents exponentiation. Negative integers are prefixed with the negation operator -
.==
, !=
, <
, >
, <=
, >=
are available.int()
function can convert a string that looks like an integer into an integer.'
or "
characters. Strings can be treated as lists of single-character strings. Another way to look at this is that there is no distinction between a character and a string: all characters are just strings of length 1. Multiline strings can be delimited using """
or '''
(i.e., three quotation mark characters at the beginning and end of the string literal).''
or ""
.+
.len()
returns the length of a string.s[i]
). These characters are also strings themselves.[
and ]
, with the individual list entries separated by commas. Lists cannot be members of sets.[]
.+
.len()
returns the length of a list.a[i]
).in
relational operator.(
and )
, with entries separated by commas. The main distinction between lists and tuples is that tuples are hashable (i.e., they can be members of sets).()
.x
is denoted using (x, )
.+
.list()
function.tuple()
function.len()
returns the length of a tuple.t[i]
).in
relational operator.set()
..union()
and .intersect
correspond to the standard set operations.set()
function.list()
or list()
function, respectively.len()
returns the size of a set.in
relational operator.frozenset()
function.
{}
.list(d.keys())
.list(d.values())
.len()
returns the number of entries in the dictionary.d[key]
).for
clause. This will filter which combinations are actually used to add a value to the resulting list.
type()
can be used to determine the type of a value. Below, we provide examples of how to check whether a given expression has one of the common Python types:
if
, else
, and/or elif
), an iteration construct (i.e., a for
or while
loop), a return
statement, or a break
or continue
statement.def
keyword, followed by the name of the function or procedure, and then by one or more arguments (delimited by parentheses and separated by commas).
else
). The body of each branch is an indented sequence of statements.
if
). The body is executed over and over until the expression in the condition evaluates to False
, or a break
statement is encountered.
csa2/csa3
.Example.hs
should contain the Example
module definition). The module body consists of a series of declarations, which can appear in any order; the only exception is that if there are multiple declarations for a function, they must all be grouped together.
True
and False
.&&
, ||
, and not
.if ... then ... else ...
is also available (similar to Python's ... if ... else ...
and C/C++/Java's ternary operator ... ? ... : ...
.Int
and an unbounded-size type Integer
.+
, *
, -
, /
, and ^
. The Prelude
function div
represents integer division. The Prelude
functions min
and max
make it possible to compute the minimum and maximum of two integers, respectively, and the function minimum
and maximum
make it possible to compute the minimum and maximum of a list of integers. Negative integers are prefixed with the negation operator -
.==
, /=
(not equal), <
, >
, <=
, >=
are available."
characters (individual characters are always delimited using '
). Strings can be treated as lists of characters.""
.++
.Prelude
function length
returns the length of a string.[
and ]
, with the individual list entries separated by commas.[]
.:
.++
.Prelude
function length
returns the length of a list.Prelude
function elem
(note that this relies on the ==
operation being defined on the type of the elements in the list).(
and )
, with entries separated by commas.()
.(e)
is equivalent to e
for any Haskell expression e
.