Skip to content

Commit

Permalink
Lecture 05 & Homework 02 (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmheim authored Mar 18, 2024
1 parent fe140fd commit e3de5c7
Show file tree
Hide file tree
Showing 11 changed files with 985 additions and 14 deletions.
2 changes: 2 additions & 0 deletions .vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default defineConfig({
{ text: '02: Lists & Trees', link: '/lectures/lecture02'},
{ text: '03: Higher Order Functions', link: '/lectures/lecture03'},
{ text: '04: Pattern Matching & Lazy Evaluation', link: '/lectures/lecture04'},
{ text: '05: Macros & Interpreters', link: '/lectures/lecture05'},
]
},

Expand All @@ -50,6 +51,7 @@ export default defineConfig({
link: '/homework/',
items: [
{ text: '01: ASCII Art', link: '/homework/hw01' },
{ text: '02: SVGen Interpreter', link: '/homework/hw02' },
]
},

Expand Down
2 changes: 1 addition & 1 deletion homework/hw01.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ASCII Art - Homework Assignment 01
# ASCII Art - Homework 01


This homework assignment aims to practice applications of higher-order functions for processing
Expand Down
329 changes: 329 additions & 0 deletions homework/hw02.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
---
outline: deep
---

# Interpreter of SVGen - Homework 02

This homework assignment aims to implement an interpreter of a simple
programming language which I call SVGen. This language allows writing programs
generating SVG images. Thus the interpreter evaluates a given program and
returns a string whose content is a valid SVG image.
[SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) is a XML-based
vector image format. Our SVGen programs support only a fragment of the SVG
specification to keep the assignment simple.

::: danger Important!
1. The interpreter should be implemented in Racket, but you are not allowed to
use Racket built-in function `eval`.

2. All your code is required to be in a single file called `hw2.rkt`.
:::

SVGen is a LISP-like language. Thus your interpreter does not have to parse the
source file, but you will be given an abstract syntax tree (AST) directly
represented as a nested list consisting of the language primitives. An example
of a simple SVGen program is shown below:

```scheme
'((define STYLE "fill:pink;opacity:0.5;stroke:black;stroke-width:2")
(define END 15)
(define (recur-circ x y r)
(circle x y r STYLE)
(when (> r END)
(recur-circ (+ x r) y (floor (/ r 2)))
(recur-circ (- x r) y (floor (/ r 2)))
(recur-circ x (+ y r) (floor (/ r 2)))
(recur-circ x (- y r) (floor (/ r 2))))))
```
This program recursively generates circles with smaller and smaller radii. Once
the radius is smaller than the constant `END`, the program stops. Evaluating
the expression `'(recur-circ 200 200 100)` returns a string containing an SVG
image:

<img src="/img/svg-ex1.svg" style="width: 50%; margin: auto;">


## Interpreter specification

Your task is to implement a function
```
(execute width height prg expr)
```
where `width` and `height` is a width and height of the SVG image respectively. The argument `prg`
is an SVGen program consisting of function and constant definitions, and `expr` is an expression to
be evaluated (typically, it is a function call of a function defined in `prg`). For example, if
$width=500$ and $height=400$, the `execute` function returns a string
```xml
<svg width="500" height="400">...content...</svg>
```
where the `...content...` is the result of the interpreter by evaluating `expr` using the
definitions in `prg`. To simplify the task, we split the function definitions part in `prg` from
the expression `expr` whose evaluation gives the content of the SVG-tag. So you can process the
function definitions `prg` in advance and create a global environment in which the expression `expr`
is evaluated afterward. For example the result of the program `prg` in the `recur-circ` code
snippet depicted in the circle figure was returned by the call
```scheme
(execute 400 400 prg '(recur-circ 200 200 100))`
```

::: danger Important
The function `execute` has to be exported from your file `hw2.rkt`!
Thus your file must contain `(provide execute)`.
:::

## SVGen syntax and semantics

Now we define the syntax and semantics of SVGen programs.

### Syntax
The syntax of SVGen is specified by a grammar below. A grammar consists of rules of the form `LHS
-> RHS` where `LHS` is a non-terminal symbol and `RHS` is either a sequence of symbols or several
sequences separated by the pipe |. The rule states that the non-terminal symbol `LHS` can be
rewritten into one of the sequences of symbols separated by `|`. To define arbitrarily long
sequences, we use the formal language operators `*` and `+`. For a symbol `<symbol>` the notation
`<symbol>*` (resp. `<symbol>+`) stands for any finite (resp. finite nonempty) sequence of
`<symbol>`s separated by spaces.

```
<program> -> (<definition>*)
<definition> -> (define (<id> <id>*) <expression>+)
| (define <cid> <val>)
<expression> -> <application>
| (if <bool-exp> <expression> <expression>)
| (when <bool-exp> <expression>+)
<application> -> (<svg-op> <arg>*)
| (<id> <arg>*)
<bool-exp> -> (<bool-op> <num-exp>*)
<arg> -> <string>
| <num-exp>
| <cid>
<num-exp> -> <num>
| <id>
| <cid>
| (<num-op> <num-exp>*)
<string> -> any Racket string, e.g. "fill:blue"
<num> -> any Racket number, e.g. 3.14
<cid> -> any Racket symbol starting with an uppercase character, e.g. STYLE
<val> -> <string> | <num>
<id> -> any Racket symbol starting with a lowercase character, e.g. x1
<num-op> -> + | - | * | / | floor | cos | sin
<svg-op> -> circle | rect | line
<bool-op> -> = | < | >
```

We will comment on its parts. An SVGen program is a list of definitions as specified by the rule
`<program> -> (<definition>*)`. Each definition is either a function definition of the form `(define
(<id> <id>*) <expression>+)` or a constant definition of the form `(define <cid> <val>)`.

The constant definitions consist of an identifier `<cid>` and a value `<val>`. To distiguish
constants from other `<id>`s, `<cid>` is a Racket symbol starting with an uppercase letter. The
value `<val>` is either a string (e.g., `"fill:red"`) or a numerical value (e.g., `3.14`).

The syntax of the function definitions is the same as in Racket. The first identifier `<id>` is the
name of the function and the sequence `<id>*` represents its arguments. The body of the function
definition consists of nonempty seqeuence of expressions. Moreover, the only variables occuring in
the body are among the function arguments. For instance, the body of `(define (f x y) <body>)` may
contain only variables `x,y`. The body expressions might also refer to defined constants. Note that
there can be no nested definitions.

Each expression is either a *function application* or *if-expression* or *when-expression*. The
if-expression `(if <bool-exp> <expression> <expression>)` has the same syntax as if-expressions in
Racket, i.e., the condition `<bool-exp>` is followed by a then-expression which is followed by an
else-expression. The when-expression `(when <bool-exp> <expression>+)` contains a condition
`<bool-exp>` followed by a nonempty sequence of expressions. The conditions `<bool-exp>` are of the
form `(<bool-op> <num-exp>*)`, where `<bool-op>` is one of `=, <, >` followed by numeric
expressions.

The function application `<application>` represents a function call. Either it is a call of an
SVG-primitive function (i.e., one of `circle, rect, line`) or a defined function. Each argument
`<arg>` of a function call can be a string or a numeric expression or a constant. The numeric
expressions `<num-exp>` have the same syntax as in Racket. They are built up from variables,
numbers, constants, and functions `+, -, *, /, floor, cos, sin`.

### Semantics

The evaluation of an expression with respect to an SVGen program returns a string representing a
valid SVG image. The output string is generated by the function calls of SVG-primitives which
generate corresponding SVG-tags. The semantics of the SVG-primitives is defined as follows:

* The `(circle x y r style)` is evaluated to the SVG-tag `<circle>`, where $x,y$ are coordinates of its origin, $r$ is its radius, and $style$ is a string of style options. For example,
```scheme
> (circle 50 40 20 "fill:blue")
<circle cx="50" cy="40" r="20" style="fill:blue"/>
```
* The `(rect x y width height style)` is evaluated to the SVG-tag `<rect>`, where $x,y$ are coordinates of its origin, $width$, $height$ is its width and height respectively, and $style$ is a string of style options. For example,
```scheme
> (rect 10 20 30 40 "fill:blue")
<rect x="10" y="20" width="30" height="40" style="fill:blue"/>
```
* The `(line x1 y1 x2 y2 style)` is evaluated to the SVG-tag `<line>`, where $x1,y1$ are coordinates of its origin, $x2,y2$ coordinates of the final point, and $style$ is a string of style options. For example,
```scheme
> (line 10 20 30 40 "stroke:black;stroke-width:5")
<line x1="10" y1="20" x2="30" y2="40" style="stroke:black;stroke-width:5"/>
```

The rest of the SVGen semantics is quite straightforward following the Racket semantics.
The interpretation of the conditional expression `(when <bool-exp> <expression>+)` evaluates the
nonempty sequence of expressions only if the condition `<bool-exp>` is evaluated to true.
If the condition `<bool-exp>` is evaluated to false, the when-expression returns the empty string
`""`.

## Further hints
Try to make your solution structured by splitting your code into several independent pieces
and design covering test cases for all the pieces.
For example, I split my solution into the following parts:
* Functions generating SVG-tags
* Environment functions
* Evaluator functions

### Functions generating SVG-tags
This part is quite straightforward. You can devise a clever solution using higher-order functions.
It is convenient to use the Racket function `format` to create a particular string.
For example,
```scheme
> (format "<svg width=\"~a\" height=\"~a\">" 200 100)
"<svg width=\"200\" height=\"100\">"
```
Note the escape backslash character allowing to enter double quotes.
The `format` function also converts numerical values into a string automatically.

### Environment functions
To evaluate an application of a defined function, the interpreter has to know all the function and constant definitions
from `prg`, and all the values to be bound to the function arguments. You need to design
a data structure capturing these data. I call it an environment. It has three parts. The first two consist
of the function and constant definitions. They can be processed
in advance because the interpreter gets them separately. Thus the first two parts remain constant during the evaluation
of a given expression.

The last part of the environment is more dynamic. Once you need to evaluate a function call `(f e1 e2 ...)`,
you have to first evaluate the expressions `e1 e2 ...` obtaining some values $v_1,v_2,\ldots$,
then you can create a new environment whose last part
is created based on the values $v_1,v_2,\ldots$,
and finally, you can evaluate the body of `f` in this new environment.

Note that SVGen has no bindings scopes. The only variables are just parameters in the function definitions (apart from
the defined constants).
Consequently, the second part of the environment is fully determined by a function call.
Thus there is no need to extend the envinroment by bindings from the outer scope (unlike a Racket interpreter).

### Evaluator functions
These functions form the core of the interpreter. They should follow the grammar of the language.
Roughly speaking, for each rule of the grammar, there is a corresponding function recognizing which
of the right-hand side applies. Once it is clear, the expression is decomposed into particular
parts; to implement such a function, it is convenient to employ pattern matching. Each part is
either a terminal symbol (like a number, a string, a primitive function) or corresponds to a rule in
the grammar. If it is a terminal symbol, its evaluation is given by its semantics (e.g., the symbol
`'+` stands for the Racket function `+`). If it corresponds to a rule, it can be evaluated
recursively by the corresponding evaluator function.

## Test cases

This section presents a few test cases to show how the interpreter should behave. Similarly, as in the first
homework assignment, your `execute` function returns a string. If you test it directly in DrRacket REPL, the
REPL displays the string value full of the escape character `\`. To see the result without escape characters, one
has to apply function `display` to the result. Such displayed string can then be copied into your clipboard
and pasted into any SVG viewer (I use the online editors in \url{https://www.w3schools.com/graphics/svg_intro.asp}).
In the following examples, we will display the resulting SVG format on several lines indented for better readability.
However, to simplify your task,
your output string does not need to contain any newline characters or whitespace between SVG tags.

::: tip Line
```scheme
> (display (execute 400 400 '()
'(line 10 20 30 40 "stroke:black;stroke-width:5")))
<svg width="400" height="400">
<line x1="10" y1="20" x2="30" y2="40" style="stroke:black;stroke-width:5"/>
</svg>
```
:::


::: tip Circle
```scheme
> (display (execute 400 400
'((define STYLE "fill:red"))
'(circle 200 200 (floor (/ 200 3)) STYLE)))
<svg width="400" height="400">
<circle cx="200" cy="200" r="66" style="fill:red"/>
</svg>
```
:::

::: tip Rectangles
```scheme
> (define test1
'((define (start)
(rect 0 0 100 100 "fill:red")
(rect 100 0 100 100 "fill:green")
(rect 200 0 100 100 "fill:blue"))))
> (display (execute 400 400 test1 '(start)))
<svg width="400" height="400">
<rect x="0" y="0" width="100" height="100" style="fill:red"/>
<rect x="100" y="0" width="100" height="100" style="fill:green"/>
<rect x="200" y="0" width="100" height="100" style="fill:blue"/>
</svg>
```
:::

::: tip Circles
```scheme
(define test2
'((define STYLE "fill:red;opacity:0.2;stroke:red;stroke-width:3")
(define START 195)
(define END 10)
(define (circles x r)
(when (> r END)
(circle x 200 r STYLE)
(circles (+ x (floor (/ r 2))) (floor (/ r 2)))))))
> (display (execute 400 400 test2 '(circles 200 START)))
<svg width="400" height="400">
<circle cx="200" cy="200" r="195" style="fill:red;opacity:0.2;stroke:red;stroke-width:3"/>
<circle cx="297" cy="200" r="97" style="fill:red;opacity:0.2;stroke:red;stroke-width:3"/>
<circle cx="345" cy="200" r="48" style="fill:red;opacity:0.2;stroke:red;stroke-width:3"/>
<circle cx="369" cy="200" r="24" style="fill:red;opacity:0.2;stroke:red;stroke-width:3"/>
<circle cx="381" cy="200" r="12" style="fill:red;opacity:0.2;stroke:red;stroke-width:3"/>
</svg>
```
:::

::: tip Recursive Tree
```scheme
(define tree-prg
'((define STYLE1 "stroke:black;stroke-width:2;opacity:0.9")
(define STYLE2 "stroke:green;stroke-width:3;opacity:0.9")
(define FACTOR 0.7)
(define PI 3.14)
(define (draw x1 y1 x2 y2 len angle)
(if (> len 30)
(line x1 y1 x2 y2 STYLE1)
(line x1 y1 x2 y2 STYLE2))
(when (> len 20)
(recur-tree x2 y2 (floor (* len FACTOR)) angle)
(recur-tree x2 y2 (floor (* len FACTOR)) (+ angle 0.3))
(recur-tree x2 y2 (floor (* len FACTOR)) (- angle 0.6))))
(define (recur-tree x1 y1 len angle)
(draw x1
y1
(+ x1 (* len (cos angle)))
(+ y1 (* len (sin angle)))
len
angle))))
```
Evaluating this program by
```scheme
(display (execute 400 300 tree-prg '(recur-tree 200 300 100 (* PI 1.5))))
```
<img src="/img/svg-ex2.svg" style="width: 50%; margin: auto;">

generates the tree above.
:::
10 changes: 0 additions & 10 deletions homework/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ Dates for opening of the assignment / deadlines are:
| 4 | 23.04. | 13.05. |


## 01: ASCII Art (Racket)

::: danger Important
Submit your solution as a zip archive containing a single file with the name **`hw1.rkt`**.
:::

The description of the assignment can be found [here](hw01).

The assignment will open on the **05.03.**

## Note on the private test data

A part of any programming assignment is generating test cases and testing the produced algorithm. It
Expand Down
Binary file added img/ast.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e3de5c7

Please sign in to comment.