Worksheet Lox and Prerequisites (Java and C)

Table of Contents

1. Worksheets

This course requires worksheets to be completed each week.

The purpose is to provide support structure for your study and to provide better coverage of routine introductory exercises prior to completing more challenging homework assignments. Much of the text comes from questions that arise during the course. The worksheets should make the class easier. You are welcome to ask questions about the worksheets on the class forum.

This initial worksheet covers a few elements of Java and C that will be used in this course. You should have seen this material in the introductory classes (or from the classes that were used to waive the introductory classes).

2. Tools

From the resources tab of the course homepage, install the tools for the course.

Play around with Scala, VSCode, Maven, and Java.

From the schedule tab of the course homepage, download the lox interpreter for chapter 13.

Play with the lox interpreter.

2.1. Console / Terminal

Open a console / terminal on your computer.

  • For Windows, use the builtin "Command Prompt" (cmd.exe). The more adventurous might try any of the following:
  • For Linux, open whichever terminal program came with your system. That terminal program will probably run the bash shell, unless you have configured it to use a different shell program.
  • For OS X, use the builtin "Terminal", found in /Applications/Utilities. It will run the zsh shell by default.

Verify that you can change directories (with cd), create directories (with mkdir), list directories (with ls or dir), etc.

Terminology: Windows uses the term "console". Linux, OS X, and others more typically use "terminal"; moreover, a shell program such as zsh reads commands from the terminal, so we might say "open a shell" rather than "open a terminal".

2.2. Home Directory

On Linux and OS X, your home directory probably has the form /home/bob or /Users/bob; this is stored in the HOME environmental variable. The home directory has some special support:

  • You can change to your home directory quickly by just running cd with no directory specified.
  • You can refer to your home directory by ~. For example, mkdir -p ~/doc/mynotes creates a doc/mynotes hierarchy of directories in your home directory.

2.3. Press TAB to Autocomplete

In the Command Prompt and terminal, you can often press the TAB key to autocomplete.

There are different behaviors when autocompletion is used with multiple entries, e.g., multiple files/directories with the same prefix

  • Autocompletion in Command Prompt (Windows) cycles through the entries.
  • Autocompletion in zsh (Linux and OS X) does a partial completion; if you press TAB a second time, it will show you the possible completions and allow you to select one with the arrow keys. You can also cycle through the options by hitting TAB repeatedly.

Using TAB liberally will make your command-line interactions significantly faster.

2.4. Command History in the shell

The terminal keeps track of all the commands you've typed in. This is known as the command history. You can move backwards and forwards through the history using the arrow keys. Try it!

In some shells, you can also search backwards through the history by holding the control key and pressing the key for r. This key combination is often abbreviated Ctrl-R (although it's lowercase). Once you've typed what you are searching for, you can press Ctrl-R again to search further backwards. If you go to far back, you can press Ctrl-S to search forward.

2.5. Command History in other REPLs

The shell is a Read-Eval-Print loop (REPL). Many other REPLs also have a command history. Try the arrow keys in Scala. 😊. Try them in Lox. 🙁.

On Unix systems (Linux, Mac) you can get command history by installing rlwrap. On a mac the install command is brew install rlwrap.

You can precede any shell command with rlwrap in order to get a command history. For example, try running lox this way:

mvn -q clean compile && rlwrap java -cp target/classes com.craftinginterpreters.lox.Lox x.lox

3. Lox

From the schedule tab of the course homepage, download the lox interpreter for chapter 13. Because we deal with many versions of the language, I will sometimes identify them by chapter. Thus, the lox interpreter of chapter 13 will be called lox13.

Follow the instructions on the resources page to compile and run lox13. Play with the interpreter. Type in the programs from chapter 3 of the book and watch them execute.

Note that lox does not print anything unless you have an explicit print statement:

$ java -cp target/classes com.craftinginterpreters.lox.Lox  
lox13> 1+3
[line 1] Error at end: Expect ';' after expression.
lox13> 1+3;
lox13> print 1+3;
4

Multi-line programs must be included in a file, such as this:

fun returnFunction() {
  var outside = "outside";

  fun inner() {
    print outside;
  }

  return inner;
}

var fn = returnFunction();
fn();
$ java -cp target/classes com.craftinginterpreters.lox.Lox returnFunction1.lox
outside

Enter CTRL-D to exit the Lox REPL.

4. Scala

The Scala REPL prompt is scala>. You can enter Scala expressions and definitions at the Scala REPL prompt. Try this out by entering 1 + 2. You should see something like the following.

scala> 1 + 2
res0: Int = 3

Write some Scala expressions for Boolean, integer, character, and String literals. Write some Scala expressions involving arithmetic. Try them in the Scala REPL.

You can bind the expressions to variables:

val x = true

or just type them in directly:

true

Solution: Scala Literals and Arithmetic

Here is a Scala version of the returnFunction from lox. This time, it returns a pair of functions rather than a single function. This is not easy to express in lox because it does not have built-in pairs.

def returnFunction() : (()=>Unit, ()=>Unit) = {
  var outside = "outside";

  def inner() = {
    println (outside);
  }
  def changeInner() = {
    outside = "something else";
  }
  return (inner, changeInner);
}

def main (args:Array[String]) = {
  var (fn1, fn2) = returnFunction();
  fn1();
  fn2();
  fn1();
}
$ scala returnFunction2.scala
outside
something else

You can also run it like this:

$ scala                      
Welcome to Scala 3.4.1 (21, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.

scala> :load returnFunction2.scala
def returnFunction(): (() => Unit, () => Unit)
def main(args: Array[String]): Unit
                                                                                
scala> main(Array())
outside
something else

Here we are invoking the main function with an empty Array.

Enter CTRL-D to exit the Scala REPL.

5. Lexical Structure

Take a look at the lexical structure of scala by examining the Language Reference

6. Visitor Pattern

Compare and contrast the following three versions of the same code.

6.1. Simple Java

abstract class Pastry {
  public void printMe (); 
}
class Beignet extends Pastry {
  @Override
  public void printMe() {
    System.out.println ("It's a beignet");
  }
}
class Cruller extends Pastry {
  @Override
  public void printMe() {
    System.out.println ("It's a cruller");
  }
}

class PastryTest {
  public static void main (String[] args) {
    var ps = new Pastry[] {new Beignet(), new Cruller()};
    for (var p : ps) {
      p.printMe();
    }
  }
}
$ javac PastryTest.java 
$ java PastryTest 
It's a beignet
It's a cruller

6.2. Scala match

enum Pastry:
  case Beignet
  case Cruller
import Pastry.*

def printPastry (p: Pastry) =
  p match
    case Beignet => println("It's a Beignet")
    case Cruller => println("It's a Cruller")

def main(args:Array[String]) = 
  var ps = List(Beignet,  Cruller)
  for (p <- ps) do printPastry(p)
$ scala Pastry.scala 
It's a Beignet
It's a Cruller

6.3. Java Visitor

interface PastryVisitor {
  public void visitBeignet(Beignet beignet); 
  public void visitCruller(Cruller cruller);
}
abstract class Pastry {
  public abstract void accept(PastryVisitor visitor);
}
class Beignet extends Pastry {
  @Override
  public void accept(PastryVisitor visitor) {
    visitor.visitBeignet(this);
  }
}
class Cruller extends Pastry {
  @Override
  public void accept(PastryVisitor visitor) {
    visitor.visitCruller(this);
  }
}

class PastryPrinter implements PastryVisitor {
  public void visitBeignet(Beignet beignet) {
    System.out.println ("It's a beignet");
  }
  public void visitCruller(Cruller cruller) {
    System.out.println ("It's a cruller");
  }
}
public class PastryTest {
  public static void main (String[] args) {
    var printer = new PastryPrinter ();
    var ps = new Pastry[] {new Beignet(), new Cruller()};
    for (var p : ps) {
      p.accept(printer);
    }
  }
}
$ javac PastryTest.java 
$ java PastryTest 
It's a beignet
It's a cruller

7. Prerequisite: Java

7.1. Hello World

Write a hello world program in Java. Your program should print "Hello world!" to the console / terminal, and then exit.

Solution: Hello World in Java

7.2. Recursion

Modify the following fractal function.

public class Fractal {
  public static void fractal (int x) {
    if (x > 0) {
      System.out.print (x + " ");
    }
  }

  public static void main (String[] args) {
    if (args.length != 1) {
      System.out.println ("usage: java Fractal <number>");
      System.exit (1);
    }
    int num = Integer.parseInt (args[0]);
    fractal (num);
    System.out.println ();
  }
}

Your function should print numbers as follows

input |  output
------|--------
1     | 1
2     | 1 2 1
3     | 1 2 1 3 1 2 1
4     | 1 2 1 3 1 2 1 4 1 2 1 3 1 2 1
etc...

Solution: Recursion in Java

7.3. Linked list

Write a singly-linked list class in Java.

Include a sensible toString method.

Include functions to add and remove elements from the front of the list.

Write a main program that creates a list with 5 elements, prints the list, then deletes the first 2 elements and prints the list again.

8. Prerequisite: C

8.1. C or C++ Compiler

You can run code online using repl.it. You can see the assembly code produced by a compiler using godbolt.org.

Mac and linux will come with gcc or clang preinstalled.

On windows, you could try any of the following:

8.2. Hello World

Write a hello world program in C. Your program should print "Hello world!" to the console / terminal, and then exit.

Compile your program with a C compiler, then run it from the console / terminal.

Solution: Hello World in C

8.3. Pointers

Consider the following C program. Replace the comments with code, so that the value of x is 10 when it is printed.

#include <stdio.h>
#include <stdlib.h>

void update (int* p) {
  /* TODO */
}

int main () {
  int x = 5;
  update (/* TODO */);
  printf ("%d\n", x);
}

Solution: Pointers

8.4. OPTIONAL: Allocation Location

Consider the following C program.

#include <stdio.h>
#include <stdlib.h>

int x = 5;

int main () {
  int y = 10;
  int* p = (int*) malloc (sizeof (int));
}

Answer the following questions:

  1. Where in memory is x allocated?
  2. Where in memory is y allocated?
  3. Where in memory is p allocated?
  4. Where in memory is p pointing to?

Solution: Allocation Location

8.5. OPTIONAL: Call-Stack Allocation

Consider the following C program.

#include <stdio.h>
#include <stdlib.h>

void countdown (int n) {
  if (n <= 0) {
    printf ("done\n");
  } else {
    printf ("counting down\n");
    countdown (n - 1);
  }
}

int main () {
  countdown (5);
}

When it is executed:

  1. What will be printed?
  2. What is the maximum number of activation records (also known as stack frames) on the call stack at one point during execution?

Solution: Call-Stack Allocation

8.6. OPTIONAL: Dangling Pointer

Consider the following two C programs. Which one of these programs is problematic and why?

#include <stdio.h>
#include <stdlib.h>

int foo (int n) {
  int result = 2 * n;
  return result;
}

int main () {
  int x = foo (5);
  int y = foo (7);
  printf ("%d\n", x);
  printf ("%d\n", y);
}
#include <stdio.h>
#include <stdlib.h>

int* foo (int n) {
  int result = 2 * n;
  return &result;
}

int main () {
  int* p = foo (5);
  int* q = foo (7);
  printf ("%p %d\n", p, *p);
  printf ("%p %d\n", q, *q);  
}

Solution: Dangling Pointer

9. Solutions

9.1. Solution: Scala Literals and Arithmetic

val b1 = true
val b2 = false

val b3 = !b2
val b4 = b1 && b2 
val b5 = b1 || b2 

val n1 = 2
val n2 = 3

val n3 = n1 + n2
val n4 = n1 - n2
val n5 = n1 * n2
val n6 = n1 / n2

/* Character literals are delimited by an apostrophe. */
val c1 = 'h'
val c2 = 'e'
val c3 = 'e'

/* String literals are delimited by double quotes. */
val s1 = "hello"
val s2 = "world"

/* The string concatenation function is written infix as "+". */
val s3 = s1 + " " + s2

9.2. Solution: Hello World in Java

public class Hello {
  public static void main (String[] args) {
    System.out.println ("Hello world");
  }
}
$ javac Hello.java
$ java Hello
Hello world

9.3. Solution: Recursion in Java

public class Fractal {
  public static void fractal (int x) {
    if (x > 0) {
      fractal (x-1);
      System.out.print (x + " ");
      fractal (x-1);      
    }
  }

  public static void main (String[] args) {
    if (args.length != 1) {
      System.out.println ("usage: java Fractal <number>");
      System.exit (1);
    }
    int num = Integer.parseInt (args[0]);
    fractal (num);
    System.out.println ();
  }
}
$ javac Fractal.java
$ java Fractal 4
1 2 1 3 1 2 1 4 1 2 1 3 1 2 1 

9.4. Solution: Hello World in C

#include <stdio.h>
#include <stdlib.h>

int main () {
  printf ("Hello world!\n");
  return 0;
}
$ gcc -o hello hello.c
$ ./hello 
Hello world!

9.5. Solution: Pointers

#include <stdio.h>
#include <stdlib.h>

void update (int* p) {
  *p = 10;
}

int main () {
  int x = 5;
  update (&x);
  printf ("%d\n", x);
}
$ gcc -o pointers-01-solution pointers-01-solution.c
$ ./pointers-01-solution 
10

9.6. Solution: Allocation Location

  1. Global memory.
  2. In an activation record for main (also known as stack frame) stored on the call stack.
  3. In an activation record for main (also known as stack frame) stored on the call stack.
  4. On the heap.

9.7. Solution: Call-Stack Allocation

  1. This is printed.

    counting down
    counting down
    counting down
    counting down
    counting down
    done
    
  2. There are at least 8 activation records on the call stack when done is printed by printf. There may be more since we do not know the implementation of printf.
    • main
    • count with n = 5
    • count with n = 4
    • count with n = 3
    • count with n = 2
    • count with n = 1
    • count with n = 0
    • printf

9.8. Solution: Dangling Pointer

The first program is fine.

The second program is problematic because p is a dangling pointer after foo returns. That is, p contains a pointer to an area of memory that is not defined, and when it is dereferenced (using *p) the behavior is not guaranteed: it might be 10, some other value, or crash the program. This happens because the result variable is allocated in the activation record for foo, and the address of result is returned from foo, but the activation record for foo is deallocated (hence the memory storing result is also deallocated) when foo returns.

Author: James Riely

Created: 2024-04-18 Thu 09:12

Validate